mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-20 14:16:22 +00:00
* refactor(agent-core-v2): extract toolApproval domain from permissionGate
- add agent-scoped `toolApproval` domain owning the approval round-trip:
builds approval requests, drives the session/approval broker, publishes
permission.approval.* events, records session approval rules, and
resolves ask continuations
- slim permissionPolicy down to the static risk-adjudication chain; drop
the dynamic `registerPolicy` mechanism
- move harness constraints out of the policy chain into their owning
domains as toolExecutor hooks ordered before 'permission': plan-mode
guard (plan), swarm batch exclusivity and AgentSwarm approve (swarm),
goal-start review (goal), exit-plan review (plan/exitPlanModeReview),
and btw deny (session/btw)
- delete the now-unused policies (deny-all, plan-mode-guard-deny,
plan-mode-tool-approve, goal-start-review-ask, swarm-mode-agent-swarm-
approve, agent-swarm-exclusive-deny)
- update Permission.md, AGENTS.md, and check-domain-layers for the new
domain layout
* feat(kimi-inspect): add App Services view for app-scope service reflection
- add `services` top-level view (`AppServicesView`) on the NavRail, showing
the full-width app-scope Service panel grid; session/agent scopes stay in
the Chat view's Inspector
- extract shared `ServicePanels` from Inspector and add `methodArgs` helper
to build per-method argument editors from channel metadata
- support variadic service calls in `panels.ts` (`call(svc, method, ...args)`)
- update agent-core-dev skill docs for the toolApproval extraction and the
guard/review-off-chain permission design
* refactor(agent-core-v2): restructure workspace domains
- rename workspaceRegistry to workspace and workspaceLocalConfig to
projectLocalConfig
- extract id-spelling resolution into the workspaceAliases domain
(IWorkspaceAliases)
- extract workspace-centric session queries into workspaceSessions
(IWorkspaceSessions)
- update kap-server routes, klient contracts, and related tests to the
new services
* feat(transcript): add op-batch sequencing and point-to-point catch-up
- wire: transcriptSeqSchema with per-agent batch seq watermark on
transcript.reset/ops and the REST transcript response, the
transcript_since subscription cursor, and the GET transcript/ops
catch-up shape; seq stays optional everywhere so legacy peers fall
back to loss-signal-driven refreshes
- wire: drop interactionFrame, interactions live on the interaction
item in the ops stream
- kap-server: TranscriptService assigns consecutive per-agent batch
seqs and retains them in a bounded in-memory journal; the
transcript_since cursor replays journaled batches instead of a
baseline reset, and the baseline reset is now items-empty because
history always pages in over REST
- kimi-inspect: transcript REST/WS clients track the op-batch
watermark and run seq-gap/reconnect catch-up with full-refresh
fallback; add the Transcript audit panel (AuditTrail timeline,
structural diff, state tree) docked right of the chat
- docs: sync AGENTS.md and agent-core-dev skill notes with the new
transcript contract
* feat(transcript): persist plan revisions and task/interaction facts, add user-messages endpoint
- record each ExitPlanMode submission as a versioned plan blob via a reference-only plan.revision op, projected live and cold as a plan.revision marker plus the plan badge (reviewPath, version)
- persist task.started/task.terminated (with a bounded output tail) and interaction.request/interaction.resolved ops, and add foldFacts so a cold transcript rebuilds tasks, interactions, todos and goal/plan/swarm meta from the wire journal
- add GET /sessions/{session_id}/transcript/user-messages returning all turn-opening prompts grouped per agent
* fix(agent-core-v2): anchor external PreToolUse hooks before the permission gate
The toolApproval extraction forces the gate to construct early (planService injects it to anchor plan-guard), so the 'permission' hook registered ahead of 'externalHooks' and a policy ask waited on the approval broker before PreToolUse could block, hanging the turn. Fetch the gate first in registerListeners and register the PreToolUse hook with before: 'permission', falling back to appending when the gate is stubbed without its hook.
* refactor(agent-core-v2): replace ordered onBeforeExecuteTool hook with veto-event pattern
- introduce BeforeToolExecuteEvent with veto/allow/pass/waitUntil statements
- add BeforeToolExecuteEmitter with two-pass fire (immediate then deferred)
- split readiness work into separate onWillExecuteTool participation event
- migrate all domain listeners: permissionGate, plan, goal, swarm, btw,
externalHooks, toolDedupe, mcp
- remove IAgentPermissionGate force-injection for hook ordering
- update docs, tests, and domain-layer check to match
* refactor(agent-core-v2): unify veto payload on ExecutableToolResult
- veto() and waitUntil factories now carry a plain ExecutableToolResult:
isError reads as a denial, anything else as a short-circuit;
the block/reason/syntheticResult weak union is gone
- add denyToolExecution(reason) helper for the common denial shape, and
narrow the fire/authorize return to BeforeExecuteDecision ({ veto } or
{ executionMetadata })
- narrow the policy 'result' resolution to { kind: 'result'; result }
- settle vetoed calls through a single normalize/merge path in the executor
* fix(agent-core-v2): pull up IAgentPermissionGate in agent activation
The permission gate only subscribes `onBeforeExecuteTool` from its
constructor. The veto-event refactor removed the ordering-driven
force-injections that used to pull it up, so without an explicit
resolution tool execution would run without policy adjudication.
* refactor(agent-core-v2): fold systemReminder domain into contextMemory appendTagged
- add `appendTagged(content, tag, origin)` to `IAgentContextMemoryService`,
storing content pure with a `tag` field on `ContextMessage`
- apply the XML tag at projection time in `contextProjector` via the new
`tag.ts` helpers (`wrapTag` / `applyTagToContent`)
- delete the `systemReminder` domain and migrate all call sites
(contextInjector, goal, plugin, prompt, swarm, btw, sessionInit,
toolSelectAnnouncements) to `appendTagged`
- build toolDedupe reminder strings with `wrapTag`
* refactor(klient): merge providers/models/catalog into global.kosong facade
Converge three separate facade namespaces (global.providers,
global.models, global.catalog) into a single global.kosong facade
that exposes two domain concepts: provider (CRUD) and model
(read-only view). Add streaming generate() method for direct
LLM calls through the facade.
- Define ProviderAuth (api-key | oauth), ProviderInput,
AnonymousProviderInput, GenerateInput, GenerateParams,
GenerateEvent as klient-owned public types
- Extend KlientChannel with stream() for AsyncIterable transport
- Add streaming IPC protocol (stream/stream_data/stream_end/
stream_error/stream_cancel frame types)
- Add StreamingProcedureContract, ScopedStreamCaller, and
per-chunk zod validation in the contract layer
- Implement generate via dispatcher special-case routing to
IModelCatalog.getRequester().request()
- Rename events: providers.changed -> kosong.providers.changed,
models.changed -> kosong.models.changed,
catalog.changed -> kosong.changed
- Remove GlobalProvidersFacade, GlobalModelsFacade,
GlobalCatalogFacade, and ModelRecord from public exports
- Update all tests, examples, and README
BREAKING CHANGE: global.providers, global.models, and
global.catalog replaced by global.kosong; event names changed;
ModelRecord no longer exported.
* feat(transcript,kimi-inspect): add tag field to text frames and improve session creation
transcript:
- add optional `tag` field to TextFrame, textFrameSchema, and HistoryMessage
- propagate tag through contextTranscript MutableMessage
kimi-inspect:
- render tagged frames with violet badge and distinct styling in ChatView
- skip cwd prompt for workspace-based session creation in Sidebar
- auto-bind default model on new sessions via resolveDefaultModel
* refactor(transcript): rename wire/ directory to contract/
The transcript package's REST/WS schemas and event types lived in
src/wire/, which collided with the engine's persisted wire.jsonl
record vocabulary. Rename it to src/contract/ so "wire" unambiguously
refers to wire.jsonl records (foldWireRecordFacts, HistoryWireRecord
stay unchanged).
- rename src/wire/{schema,events}.ts to src/contract/
- update index exports and test imports accordingly
- reword comments: "wire shape" -> "contract shape", "on the wire" ->
"in ops" / "in transit" / "on the WS channel" / "transcript API"
- events.ts: "transcript frame" -> "transcript event" for WS envelope
messages, avoiding confusion with TranscriptFrame
- kap-server tests: TranscriptWire/TurnWire/FrameWire/OpsCatchupWire/
UserMessagesWire -> *Contract
- update AGENTS.md references
* refactor(transcript): rename wire/ directory to contract/
The transcript package's REST/WS schemas and event types lived in
src/wire/, which collided with the engine's persisted wire.jsonl
record vocabulary. Rename it to src/contract/ so "wire" unambiguously
refers to wire.jsonl records (foldWireRecordFacts, HistoryWireRecord
stay unchanged).
- rename src/wire/{schema,events}.ts to src/contract/
- update index exports and test imports accordingly
- reword comments: "wire shape" -> "contract shape", "on the wire" ->
"in ops" / "in transit" / "on the WS channel" / "transcript API"
- kap-server tests: TranscriptWire/TurnWire/FrameWire/OpsCatchupWire/
UserMessagesWire -> *Contract
- update AGENTS.md references
* Revert "refactor(agent-core-v2): fold systemReminder domain into contextMemory appendTagged"
This reverts commit 55afaa3d96f729c4f73a71f1fbd23d3f6087453b.
Restore the systemReminder domain: reminders go back to being baked
into message text at write time, and ContextMessage loses the `tag`
field (projection-time wrapping is removed with it).
* feat(transcript): add wire-equivalent detail, dedupe session events
- transcript: add step usage/timing/retry, turn durationMs/error/usage,
tool inputText/progress, task resultSummary/error, meta.agent status,
a global prompts entity, and the 'hook' marker
- kap-server: project the new fields in coreEventMap and suppress
transcript-projected session events on connections subscribed to the
transcript protocol (live fan-out and cursor replay)
- kimi-inspect: mechanical type sync for the new snapshot prompts field
* fix(klient): resolve lint errors in ipc channel stream and e2e matrix test
130 lines
5.1 KiB
TypeScript
130 lines
5.1 KiB
TypeScript
/**
|
|
* Assert-based smoke check for klient against an in-process engine (memory
|
|
* transport). Exercises the `global` facade end-to-end: env snapshot, read
|
|
* models, a workspace round-trip, a provider set/delete round-trip with the
|
|
* `kosong.providers.changed` event, an anonymous-provider set/delete
|
|
* round-trip with the `kosong.models.changed` event, the read-only model
|
|
* catalog, and the error path.
|
|
*
|
|
* pnpm -C packages/klient smoke
|
|
*/
|
|
import { mkdtemp, rm } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
|
|
import { bootstrap, logSeed, resolveLoggingConfig } from '@moonshot-ai/agent-core-v2';
|
|
import { createKlient } from '@moonshot-ai/klient/memory';
|
|
|
|
function assert(cond: boolean, message: string): asserts cond {
|
|
if (!cond) throw new Error(`assertion failed: ${message}`);
|
|
}
|
|
|
|
const tick = (ms: number): Promise<void> =>
|
|
new Promise((resolve) => {
|
|
setTimeout(resolve, ms);
|
|
});
|
|
|
|
async function main(): Promise<void> {
|
|
const homeDir = await mkdtemp(join(tmpdir(), 'klient-smoke-'));
|
|
const { app } = bootstrap({ homeDir }, [
|
|
...logSeed(resolveLoggingConfig({ homeDir, env: process.env })),
|
|
]);
|
|
try {
|
|
const klient = createKlient({ scope: app });
|
|
|
|
const env = await klient.global.env();
|
|
assert(env.platform.length > 0 && env.homeDir.length > 0, 'env snapshot is populated');
|
|
console.log('[ok] env');
|
|
|
|
const page = await klient.global.sessions.list({ limit: 5 });
|
|
assert(Array.isArray(page.items), 'sessions.list returns a page');
|
|
console.log('[ok] sessions.list ->', page.items.length);
|
|
|
|
const workspaces = await klient.global.workspaces.list();
|
|
assert(Array.isArray(workspaces), 'workspaces.list returns an array');
|
|
console.log('[ok] workspaces.list ->', workspaces.length);
|
|
|
|
// Provider round-trip with the klient-level event.
|
|
const seen: string[] = [];
|
|
const sub = klient.events.on('kosong.providers.changed', (event) => {
|
|
seen.push(...event.added, ...event.changed, ...event.removed);
|
|
});
|
|
const name = '__klient_smoke__';
|
|
await klient.global.kosong.addProvider(name, {
|
|
type: 'openai',
|
|
auth: { method: 'api-key', apiKey: 'smoke-key' },
|
|
});
|
|
const got = await klient.global.kosong.getProvider(name);
|
|
assert(got !== undefined, 'kosong.getProvider returns the new provider');
|
|
const deadline = Date.now() + 5_000;
|
|
while (!seen.includes(name) && Date.now() < deadline) await tick(25);
|
|
assert(seen.includes(name), 'kosong.providers.changed fired for the new provider');
|
|
await klient.global.kosong.removeProvider(name);
|
|
sub.dispose();
|
|
console.log('[ok] kosong addProvider/getProvider/removeProvider + kosong.providers.changed');
|
|
|
|
// Anonymous provider round-trip (single-model, all fields inline).
|
|
const seenModels: string[] = [];
|
|
const modelSub = klient.events.on('kosong.models.changed', (event) => {
|
|
seenModels.push(...event.added, ...event.changed, ...event.removed);
|
|
});
|
|
const modelId = '__klient_smoke__';
|
|
await klient.global.kosong.addProvider({
|
|
id: modelId,
|
|
model: 'smoke-model',
|
|
protocol: 'openai',
|
|
baseUrl: 'http://127.0.0.1:1',
|
|
auth: { method: 'api-key', apiKey: 'smoke-key' },
|
|
maxContextSize: 8192,
|
|
});
|
|
const modelDeadline = Date.now() + 5_000;
|
|
while (!seenModels.includes(modelId) && Date.now() < modelDeadline) await tick(25);
|
|
assert(seenModels.includes(modelId), 'kosong.models.changed fired for the new model');
|
|
await klient.global.kosong.removeProvider(modelId);
|
|
modelSub.dispose();
|
|
console.log('[ok] kosong anonymous addProvider/removeProvider + kosong.models.changed');
|
|
|
|
// The read-only catalog projection over the same materialization.
|
|
assert(
|
|
Array.isArray(await klient.global.kosong.listModels()),
|
|
'kosong.listModels returns an array',
|
|
);
|
|
assert(
|
|
Array.isArray(await klient.global.kosong.listProviders()),
|
|
'kosong.listProviders returns an array',
|
|
);
|
|
console.log('[ok] kosong.listModels / listProviders');
|
|
|
|
const config = await klient.global.config.getAll();
|
|
assert(typeof config === 'object' && config !== null, 'config.getAll returns an object');
|
|
console.log('[ok] config.getAll');
|
|
|
|
assert(Array.isArray(await klient.global.flags.list()), 'flags.list returns an array');
|
|
assert(Array.isArray(await klient.global.plugins.list()), 'plugins.list returns an array');
|
|
const auth = await klient.global.auth.status();
|
|
assert(typeof auth.loggedIn === 'boolean', 'auth.status returns a status');
|
|
console.log('[ok] flags / plugins / auth');
|
|
|
|
let rpcError: { name: string; code?: number } | undefined;
|
|
try {
|
|
await klient.global.plugins.info('__definitely_missing__');
|
|
} catch (error) {
|
|
rpcError = error as { name: string; code?: number };
|
|
}
|
|
assert(rpcError !== undefined, 'missing plugin surfaces an error');
|
|
console.log('[ok] error path ->', rpcError.name, rpcError.code);
|
|
|
|
await klient.close();
|
|
console.log('smoke: OK');
|
|
} finally {
|
|
app.dispose();
|
|
await rm(homeDir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
try {
|
|
await main();
|
|
} catch (error) {
|
|
console.error(error);
|
|
process.exit(1);
|
|
}
|