* feat(mcp): tombstone removed MCP servers and apply plugin changes immediately (20 files) - add 'removed' MCP server status: workspace config removals call markRemoved instead of remove, keeping tool registrations alive while short-circuiting calls with a removal notice - fire onDidReload after every plugin mutation (install/enable/disable/remove) so workspace consumers refresh contributions immediately - TUI renders the removed status in the MCP panel/startup summary and shows an apply-immediately hint on the v2 engine * feat(agent-core-v2): freeze plugin prompt inputs for live agents (2 files) - snapshot the model skill listing and plugin system-prompt sections on the first successful prompt build and reuse the frozen values for the agent's lifetime, so plugin install / enable / disable / remove / reload never rewrites a live agent's prompt (same keep-live-sessions-stable philosophy as the MCP tombstone) - freeze only on success: a not-yet-ready skill catalog or a failed enabledSystemPrompts() read must not pin empty values for the agent's lifetime - refreshSystemPrompt still rebuilds on catalog change events but reuses the frozen values, so the prompt only moves when non-plugin inputs change (AGENTS.md, [tools] section, session tool policy, compaction); new agents snapshot the then-current state * chore(changeset): add changesets for MCP tombstone and frozen plugin prompt inputs * docs: describe immediate plugin changes and the removed MCP status on the v2 engine * fix(klient): mirror the removed MCP server status in the wire contract * docs: drop the legacy-engine behavior notes from the plugin and MCP pages * fix(agent-core-v2): freeze plugin sections only on a loaded snapshot - enabledSystemPrompts() resolves to its consumption fallback (never rejects) while the initial plugin load has failed; freezing that empty read locked plugin sections out of the live agent even after a later successful reload - expose hasLoadedSnapshot() on IPluginService so resolvePluginSections can tell a real empty snapshot from the fallback before freezing |
||
|---|---|---|
| .. | ||
| examples | ||
| scripts | ||
| src | ||
| test | ||
| AGENTS.md | ||
| CHANGELOG.md | ||
| Dockerfile | ||
| package.json | ||
| README.md | ||
| tsconfig.examples.json | ||
| tsconfig.json | ||
| tsdown.config.ts | ||
| vitest.config.ts | ||
@moonshot-ai/klient
Contract-driven client SDK for the agent-core-v2 engine. One facade, two transports — you pick the transport once at creation; everything after that is byte-identical:
import { bootstrap, logSeed, resolveLoggingConfig } from '@moonshot-ai/agent-core-v2';
import { createKlient } from '@moonshot-ai/klient/memory'; // or '/ipc'
const { app } = bootstrap({ homeDir }, [
...logSeed(resolveLoggingConfig({ homeDir, env: process.env })),
]);
const klient = createKlient({ scope: app });
const env = await klient.global.env();
const sessions = await klient.global.sessions.list({ limit: 20 });
const session = await klient.global.sessions.create({ workDir: process.cwd() });
const agent = klient.session(session.id).agent('main');
agent.events.on('assistant.delta', (e) => process.stdout.write(e.delta));
agent.events.on('prompt.completed', () => console.log('\ndone'));
await agent.prompt({ input: [{ type: 'text', text: 'Say OK.' }] });
await klient.close();
Architecture
facade (klient.global.*, klient.session(id).*, session.agent(id).*, *.events.*)
↓ single-object params, zod-validated
contract (procedure schemas, shared by all transports)
↓
KlientChannel { call, listen } ← the only transport SPI
↓
ipc │ memory
- Facade — aggregated methods, no engine service tokens, no
onDid*/onWill*event names. There is no escape hatch to raw services: the facade is the public contract.klient.global.*—sessions.*(incl.create),workspaces.*,config.*,providers.*,models.*,catalog.*,auth.*,flags.*,plugins.*,hostFs.*,env().klient.session(id).*—get/setTitle/update/status/close/archive/ restore/fork/createChild,approvals.*,questions.*,interactions.*,agents().session.agent(id).*—prompt/steer/cancel/runShellCommand/ cancelShellCommand/getModel/setModel/setPermission/getUsage/getContext/ getPlan*/getTasks*/stopTask/getTaskOutput.
- Contract — every method has a zod input tuple + output schema, validated
on the client before send / after receive (default on;
validate: falseto disable). Validation is sub-µs for typical payloads — cheaper than the JSON serialization the wire already pays. - Events —
klient.events.on(...)for the global bus (config.changed,kosong.models.changed,session.archived, …),session(id).events.on('metadata.changed' | 'interactions.changed' | 'interactions.resolved'), andagent(id).events.on('turn.started' | 'assistant.delta' | 'tool.call.started' | 'prompt.completed' | …). Underlying subscriptions are shared and ref-counted; payloads are validated; bad payloads drop toevents.onError.
Transports
| entry | options | events |
|---|---|---|
@moonshot-ai/klient/ipc |
{ socketPath, token? } |
same socket |
@moonshot-ai/klient/memory |
{ scope } (a bootstrapped engine app scope) |
direct emitter/bus subscription |
ipc and memory share one in-process dispatcher, so they behave identically
by construction; memory additionally JSON round-trips every value so results
cross the same JSON boundary a socket transport would impose. The IPC host
ships with the transport: serveKlientIpc({ scope, socketPath }).
The same conformance suite runs against both transports in this
package's tests (test/helpers/conformance.ts — one test file per transport).
This package also hosts the e2e suites (the retired server-e2e package was
folded in here):
test/e2e/legacy/+test/e2e/harness/— the legacy/api/v1live suites and their client harness (skip unlessKIMI_SERVER_URLis set; the v1 surface has no in-memory equivalent, so these stay live-server-only).
The docker e2e runner (pnpm docker:e2e) runs this whole vitest suite inside
a container against a container-local server. See AGENTS.md for the testing
rules.
Scope
The facade covers the global (app), session, and agent surfaces shown above.
What it deliberately leaves out (for now): onWill/hook-style interception
(engine hooks are in-process OrderedHookSlots and not wire-exposable), file
upload (v1 multipart REST only), and the terminal surface (v1 REST + WS
only).
Smoke check
pnpm -C packages/klient smoke
examples/smoke.ts boots an in-process engine (memory transport) and asserts
the global facade end-to-end — no server needed. examples/basic.ts is a
shorter narrated tour; examples/context-usage.ts traces context-size
readings through a real prompt (requires KIMI_EXAMPLE_MODEL +
KIMI_EXAMPLE_API_KEY).