mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-10 17:29:18 +00:00
85 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7cd64766c8
|
feat: isolate the full-text search index from the session index and the main thread (#2701)
* feat(minidb): instrument open lifecycle with phase timings and status Add MiniDb.lifecycleStatus() exposing the no-generation/generation-load/ wal-catch-up/full-rebuild/ready/degraded state machine plus per-phase timings (generation candidate load, store/non-text/text image load, postings integrity check, WAL scan/apply, full recovery, text rebuild hosting), so snapshot load, WAL catch-up and full rebuild can be told apart in diagnostics. Also add a repeatable open-lifecycle bench (small data, large WAL delta, large full-text generation, corrupt generation) and fixtures proving a healthy generation open performs no full-corpus tokenization while a corrupt or missing generation falls back. Log search-index and query-store open diagnostics in kap-server and agent-core-v2 so a listSessions call can be attributed to the database it touches. No persistence format or product behavior change. * feat(agent-core-v2): isolate the session index from the global search index Harden the separation between the session read model and the full-text search index so session operations never depend on search availability: - Reject text index definitions in MiniDbQueryStore at definition level, keeping the session query-store a structural-only read model with no postings/tokenizer artifacts, and assert its generation carries no full-text files. - Share one authoritative scan between the first list and the initial projection (single-flight) instead of scanning twice; reads may only join an in-flight scan, and every fallback read folds the mirror's pending queue so read-your-writes holds while preparing. - Keep withReadModel() fallback semantics pinned by tests: uninitialized/preparing reads hit authoritative metadata immediately, ready reads use the read model, degraded keeps falling back with a diagnosable status reason. - Guard session metadata writes so a mirror failure degrades only the read model and never fails the session lifecycle. - Prove via tests that listSessions/--resume/--continue never open the global search DB (including when search-index is unopenable), and that only real full-text search requests report building/stale/degraded. * perf(minidb): slice open-time work so it never blocks the main thread Make the whole generation-open path cooperative: - Replace the synchronous postings/store CRC verification with chunked async variants (readGenerationFileCheckedAsync, verifyFileIntegrityAsync) that keep the exact bytes/crc-mismatch error semantics. - Give the WAL-delta apply a primitive-op + wall-clock budget (walApplySlicer), so a batch frame unrolling into thousands of ops can no longer run as one uninterruptible slice; torn-tail, corrupt-batch and read-only behaviors are unchanged. - Slice the big attach loops: Store.bulkLoadRefsAsync + SkipList.bulkLoadAsync for the store image, async parsers and loadImageAsync for secondary/compound images, and TextIndex.attachImageAsync for the docs/dictionary map construction. - Queue text builds on worker-slot pressure (WorkerSlots.acquireBounded, bounded by MiniDb.textBuildSlotWaitMs, abort-aware) instead of falling back to an unbounded inline build; a persisted drought hosts the bounded inline core as the explicit last resort with stats accounting. Bench (bench/open-lifecycle, seed 42): event-loop delay max across the four open scenarios drops from 45/734/331/492 ms to ~12-28 ms with wall time flat or better. * feat(kap-server): run the global search index in a dedicated worker Move the whole search-index MiniDb lifecycle (open, generation load, WAL replay, sync, rebuild, compaction) off the main thread into a long-lived worker_threads host, so it never shares the event loop with TUI input: - Add a versioned request/response protocol and worker entry hosting a host-agnostic SearchIndexCore; the same core also backs an inline backend kept as the explicit rollback (KIMI_CODE_EXPERIMENTAL_SEARCH_WORKER=false, flag default ON). - The worker exclusively owns the search-index handle. The lock token is reported at acquire time (new MiniDb OpenOptions.onLockAcquired hook) and reaped on dirty exit; an orphan-lock detector (same-pid lock row whose token no live holder owns) recovers the window where the token report is lost, so a mid-open crash can never freeze the index into a silent permanent read-only. - Crash handling: in-flight requests are rejected with typed errors, respawn uses capped exponential backoff, per-request watchdogs terminate wedged workers, and beginClose propagates into the worker so dispose stays bounded during a long sync. Page tokens pin a boot-salted generation, so tokens issued before a transparent worker restart fail closed with invalid_page_token. - The main process keeps the sync coordinator (debounce/coalescing/ single-flight), live transcript routing, query normalization and page-token codec; searches keep reading the published generation and report building/stale/degraded instead of waiting for sync/rebuild. - Wire the worker into the CLI packaging: self-contained worker bundles for npm dist and the SEA asset manifest/installer/smoke check, plus a dev runtime (type-stripping + .ts resolve hook) scoped to worker execArgv. * feat(kap-server): model search and session-index lifecycles explicitly Consolidate the two-index separation into explicit, diagnosable lifecycles: - Surface the global search state machine (stopped / opening / building / ready / degraded / closing) end to end: SearchIndexCore.lifecycleState, SearchWorkerHost lifecycle snapshots cached from RPC responses (and invalidated across worker generations), a never-throwing status() carrying the lifecycle, and a synchronous lifecycleReport() that neither kicks the open nor spawns the worker. Corrupt search-index rebuilds are announced with a dedicated warn log so building, stale, degraded, corrupt and worker-unavailable stay distinguishable. - Turn MiniDb read-only replica catch-up fully cooperative: catchUpWalAsync scans frames with the windowed async scanner and yields per primitive op on the shared walApplySlicer budget, while a per-instance catchUpChain serializes concurrent catch-ups so each caller keeps its atomic watermark advance. The stale synchronous implementations are removed. - Pin the dependency direction and availability timing with tests: session list/create/resume survive a corrupt or unopenable search index (also end-to-end with a dead query-store), search generation reuse and stale-serving keep working across restarts, concurrent cold callers open the index / spawn the worker exactly once, resume-then- fetchSessions performs no duplicate authoritative scan, and a clean dispose releases the lock and settles at stopped. - Document the experimental flag surface (persistence_minidb_readmodel, search_worker) in the root guide. * feat(agent-core-v2): default the session read model on and roll out the separation Rollout and validation for the index separation plan: - Flip persistence_minidb_readmodel to default ON (rollback via KIMI_CODE_EXPERIMENTAL_PERSISTENCE_MINIDB_READMODEL=false or the experimental config section); session list/--resume/--continue now always go through the isolated session read model with the authoritative fallback. Test harnesses pin the flag off where shared fixtures require hermetic homes, while the dedicated suites keep explicit on/off coverage. - Add a probe proving the main thread stays responsive while the search worker rebuilds and swaps a generation (reindex), completing the TUI responsiveness matrix. - Record the rollout state in the agent-core-v2 guide (session index section) and the root flag line. - Add changesets for the CLI (worker isolation, session index independence) and minidb (cooperative open lifecycle). Validation: full suites green across minidb (551), agent-core-v2 (4760), kap-server (1005), node-sdk (343), klient (91) and the CLI app (2567); open-lifecycle bench event-loop delay max is down from 45/734/331/492 ms to ~16-22 ms across the four scenarios with wall time flat or better. * fix(agent-core-v2): evict deleted sessions from the mirror queue and drain the index on close Two issues surfaced by the read-model default in the acp-server suite: - ISessionIndex.remove only deleted from the query store, but a summary still queued in the mirror was folded back into reads (and re-written by the next flush), resurrecting a deleted session in listings. The mirror now exposes evict(id): drop the queued summary and wait out an in-flight flush before the store delete. - RunningAcpServer.close and SDKRpcClientV2.close disposed the engine without awaiting the asynchronous mirror flush / query-store close, so a host removing homeDir right after close() raced in-flight shard closes (ENOTEMPTY). Both now follow the kap-server shutdown order: drain the mirror while the store is open, dispose, then await the drains. * fix(minidb): pause active expiry during the sliced bulk load The store's active-expire timer is armed at construction, so during a sliced bulkLoadRefsAsync a tick can fire mid-load: it reaps a TTL key from the map while the order skiplist is still the old empty one, and the final bulkLoadAsync then rebuilds order from the stale orderEntries snapshot — resurrecting the expired key in the ordered index (and duplicating it if the key is later set again). The sync bulkLoadRefs had no yield windows, so guard the async path with a bulkLoading flag that defers expiry ticks until the load settles (finally-safe). * chore: consolidate changesets into the TUI startup freeze fix |
||
|
|
335588e259
|
feat(agent-core-v2): persist the last turn outcome into session metadata for cold listings (#2666)
* feat(agent-core-v2): persist the last turn outcome into session metadata for cold listings A cold session (no live handle) reported no lastTurnReason, so after a server restart the session list could not mark a session whose last turn failed until it was opened and resumed. A new Session-scope SessionOutcomeRecorder subscribes to the activity aggregate's turn_ended changes and persists the outcome (completed/failed) into the session metadata document; the summary pipeline (mirror + cold reader) carries it as SessionSummary .lastTurnReason, and toWireSession falls back to it when no live fact exists. 'cancelled' is deliberately not persisted: it is also what an in-flight turn ends with during scope disposal, and writing there races the host's home-dir teardown. Verified end to end with an isolated home and a dead provider: a turn fails, the server restarts, and GET /sessions reports last_turn_reason=failed without opening the session. * fix(klient): carry lastTurnReason/lastTurnOutcome in the validated contracts Review follow-up: zod strips unknown keys on parse, so the new outcome fields never reached klient callers; add them to the session summary and metadata/patch/key schemas (contract parity test covers the engine mirror). * fix(agent-core-v2): persist user-cancelled outcomes, never teardown aborts Review follow-up: skipping every 'cancelled' left a stale earlier outcome in the metadata (e.g. a prior failed reported for a session whose latest turn was stopped by the user). The recorder now subscribes to the main agent's turn.ended facts directly and keys on interruptReason: user_cancelled is persisted like any other terminal state, while programmatic aborts — including the cancel every in-flight turn suffers during scope disposal — are never written, so no metadata write races the host's home-dir teardown. * fix(kap-server): only fall back to the persisted outcome for cold sessions Review follow-up: a warm session that just started a new turn clears its live lastTurn, and the unconditional ?? fallback would then report the previous turn's persisted outcome for a turn that is still running. SessionFacts now reports whether a live handle exists, and the wire projection only reads the persisted value when the session is cold. * docs(agent-core-v2): keep the outcome-recorder header at role level * fix(agent-core-v2): settle turn outcomes on turn start and drain metadata writes on close Review follow-ups: - a new main turn now clears the persisted outcome (turn.started), so a process that dies mid-retry no longer reports the previous turn's terminal state for a turn that never ended - the dedupe marker only advances after a successful write, so a failed persist no longer suppresses the next identical outcome - session metadata writes are tracked in a module-level pending set with drainSessionMetadataWrites(), awaited by kap-server close alongside the mirror/query-store drains — an event-driven write (e.g. the outcome recorder) can no longer land in a session dir while the host removes it * fix(agent-core-v2): track the metadata dispose flag locally Disposable exposes no public isDisposed accessor; keep a class-local flag set in the dispose override. * fix(kap-server): drain session metadata writes before the mirror and disposal A write still in flight when close() begins must settle before the mirror flushes its summary into the read model and before scope disposal marks the service disposed — not after. * fix(agent-core-v2): reattach the recorder when the main agent is recreated Review follow-up: a failed bootstrap still fires onDidCreate before the handle is dropped; the subscription then pointed at a dead bus and the guard blocked any later reattach. Track onDidDispose and reset so the next main creation attaches cleanly. * test(agent-core-v2): resolve the recorder through the scoped DI harness Review follow-up: construct SessionOutcomeRecorder via registerScopedService + a Session-scope test host (stubbed lifecycle/metadata), so the test covers the production registration path; add the durable-value adoption case. * fix(agent-core-v2): unbreak CI — iterable Promise.all and the debug channel surface - Promise.all takes the pending-writes set directly (oxlint error) - the disposed flag moves into a _register'd marker instead of a public dispose() override, which the debug channels listing (and its test) correctly rejects as framework plumbing * fix(kap-server): surface persisted failures on the v2 session status The v2 list folds the outcome into activity.status, which previously read only live facts — a cold session always looked idle. Cold sessions now map a persisted failed outcome to status 'failed' (completed and cancelled stay idle, matching the live fold); warm sessions are unchanged, and the statuses filter inherits the mapping. * refactor(agent-core-v2): name the persisted field lastTurnReason Aligns with the established name for the same concept end to end (activity view's lastTurnReason, the v1 wire's last_turn_reason, and the SessionSummary mirror), instead of introducing a third variant. * fix(agent-core-v2): drain pending metadata writes before session teardown Review follow-up: closing/archiving a session right after a turn ended could dispose the scope while the outcome write was still queued, and delete() removes the session dir immediately after close. Await the pending metadata writes before the handle goes away. * fix(node-sdk): carry lastTurnReason through the SDK session summary Review follow-up: the in-process SDK path maps the engine summary through v2SummaryToSessionSummary, which dropped the new outcome field. Add it to the public SessionSummary type and the mapper; the parity gate projects it away (the v1 engine never records an outcome). * fix(node-sdk): populate lastTurnReason on live SDK summaries Review follow-up: resumeSession/reloadSession build their summary from the live session's metadata document, which now carries the outcome — surface it there too so the SDK reports it consistently for live and listed sessions. * fix(agent-core-v2): carry the last turn outcome across session forks Review follow-up: fork skips state.json when copying the session dir, so the fork's fresh metadata never had the outcome and a restart dropped a marker the warm fork was still reporting. The fork's metadata patch now inherits the source's lastTurnReason. * fix(agent-core-v2): settle pending outcome writes before reading a fork source Review follow-up: a fork requested right after the source's turn ended could read the metadata before the recorder's queued write landed, inheriting a stale or absent outcome. Drain pending metadata writes first. * fix(agent-core-v2): backfill restored outcomes into the session metadata Review follow-up: for sessions whose last turn ended before this field existed, the cold-resume seed restores the outcome into the activity view without a turn.ended fact, so the recorder never persisted it and cold listings stayed blank. The recorder now also watches the main agent's activity updates and backfills the restored outcome when nothing is persisted yet. * fix(agent-core-v2): never backfill restored cancellations Review follow-up: a restored 'cancelled' cannot be told apart from a programmatic abort (the activity event carries no interruptReason), and those are never persisted. Backfill now covers only completed/failed; user stops are still persisted from the live turn.ended fact. * refactor(agent-core-v2): rename the outcome recorder to outcome mirror Mirror is the codebase's established term for a write side that reflects live state into a store (SessionIndexMirror); Recorder has no precedent. * fix(agent-core-v2): backfill without bumping recency; header-only comments Review follow-ups: - a mere resume must not float an old session to the top of the list: metadata updates accept touchUpdatedAt:false and the outcome mirror's backfill uses it (live outcome writes keep bumping — turn end is a recency moment) - the mirror service's inline notes move into the file header per the package comment convention - drop the redundant |undefined from the SDK's optional outcome field * fix(node-sdk): read the live outcome for resumed session summaries Review follow-up: on a fresh resume the restored outcome can still be queued as a metadata backfill, so the document may lag a tick; the live activity aggregate already holds it. Resume/reload summaries now prefer the live value and fall back to the metadata field. * fix(agent-core-v2): confine the outcome backfill to pure resumes Review follow-up: the view publishes its turn.ended fold before this mirror's own turn.ended handler runs, so a live ending reached the backfill branch first and got persisted without the recency bump. The backfill now only applies when no turn ever started in this process — live endings always take the bumped write. * fix(agent-core-v2): drain the session-index mirror before session teardown Review follow-up: settling the metadata write alone left the fresh summary in the mirror's pending queue, so a list right after close could read a stale outcome from the read model. close/archive now also drain ISessionIndexMirror. Test harnesses register a mirror stub for the new dependency. * docs(agent-core-v2): fold the metadata drain contract into the file header * chore: include the SDK package in the changeset; fold the drain note into the header * fix(agent-core-v2): backfill restored cancellations too, quietly Review follow-ups: dropping every restored cancel loses legitimate user stops whose live write never landed (or was rejected) before a restart — cold surfaces never mark cancelled anyway, so healing them is harmless and strictly more accurate. The metadata disposal note moves into the file header per the comment convention. * fix(node-sdk): prefer the live outcome over the index in SDK listings Review follow-up: a live session that just started a new turn after a failure can briefly keep the stale outcome in the index while the mirror's clear is queued. listSessions now reads the live activity aggregate for warm sessions, matching the kap-server cold-only fallback. * fix(node-sdk): never read the metadata outcome for a live session Review follow-up: with a retry in flight the live aggregate has no outcome while the document may still hold the previous failure — the fallback showed the stale one. Live summaries now take the live aggregate's answer alone; the restored outcome is already seeded there on resume. |
||
|
|
02c026d487
|
feat(agent-core-v2): tombstone removed MCP servers and freeze plugin prompt inputs (#2694)
* 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 |
||
|
|
cfd14a1fe2
|
feat(kap-server): accept attachments on skill activation (#2693)
* feat(kap-server): accept attachments on skill activation
The :activate endpoint only took {args?}, so REST clients (web/desktop
composers) could not attach uploads to a /skill invocation — attachments
were silently dropped at the edge.
- activateSkillRequestSchema gains an optional attachments field carrying
the image/video/file subset of the prompt content wire shape.
- The skills route resolves them through the same edge pipeline as prompt
submissions (validate file refs → materialize/compress → convert),
extracted from routes/prompts.ts into lib/promptMedia.ts.
- AgentSkillService.activate appends the resolved parts after the rendered
skill prompt in the activation's user message; SkillActivationInput
gains an optional content field. The native RPC/TUI path is unchanged.
- Attachment failures map to 40407 file.not_found / 40001
validation.failed, mirroring the prompts route.
* fix(kap-server): drop the unused parseKimiFileUrl import in promptMedia
* refactor: address review — header-only comments in the skill domain, provider id on protocol URL sources
- agent-core-v2 keeps comments solely in the top-of-file block (scoped
guide): SkillActivationInput.content documented in the skill.ts header,
the activate() note folded into the skillService.ts header.
- packages/protocol's image/video URL source gains the optional
provider-issued id, matching the kap-server wire schema so parsing the
public contract no longer strips it.
* fix(kap-server): validate the skill before materializing activation attachments
An unknown or non-user-activatable skill name with attachments ran the
media pipeline first, streaming bytes into the session/cache dirs and
compressing images for a request that activate() would reject with
40415/40912. The route now checks the session catalog up front (the
service still re-validates) so invalid activations leave no disk or CPU
side effects.
|
||
|
|
8c766a6c30
|
feat(agent-core-v2): add the L3 unit layer and the Feature seam (#2678)
* feat(agent-core-v2): add the L3 unit layer and the Feature seam - introduce the L3 Service/Fiber unit layer: the Service base class with this.provide/effect/on/get/ref capabilities, the fiber runtime with thenable FiberHandles, collection contribution points, and the per-scope-kind ScopeUnits materialization fold - provide each scope's static registration batch as one atomic provideAll cascade transaction (waiting-area activation, sticky Failed on construction error) - add the DI unit inspection surface: App-scope debug ledger / dependency graph / cascade history services and the kimi-inspect DI view - add the Feature unit seam (IFeatureManager + feature assembly), port plan mode onto it, and add the contributed-command seam (agent-command domain + node-sdk RPC types) - remove the legacy dep-graph tooling - apply the header-only comment convention across src and test: strip non-header narration, keep the file header, tooling pragmas, and NOTE comments * feat(kap-server): gate the event.di.* debug feed to kimi-inspect connections - add an opt-in target set in SessionEventBroadcaster; the global fan-out now skips event.di.* frames for connections that never opted in, so kimi-web and other clients no longer receive the high-churn DI feed - WsConnectionV1 opts a connection in when client_hello carries client_id 'kimi-inspect'; removeGlobalTarget drops the opt-in on close - temporary gate until a client-declared event-type whitelist lands * chore(agent-core-v2): fix oxlint errors in the DI unit layer - build the live-ref container chain without aliasing this (no-this-alias) - snapshot the materialized map with Array.from and document why the copy is required (no-useless-spread) * test(klient): use string scope kinds in the lifecycle handle fakes The engine's LifecycleScope is a string enum now; the facade test doubles still returned the old numeric kinds and failed the handleWireSchema output validation. * build(nix): update the pnpmDeps fetch hash |
||
|
|
7b2784b9b7
|
feat: surface the bound model and thinking effort on subagent UIs (#2679)
* feat: surface the bound model on subagent UIs The subagent.spawned event now carries the display-normalized model alias (the derived __secondary__ entry resolves to its base alias), so clients can show which model a subagent is bound to. The TUI subagent card, swarm panel header, and background-agent entry show it at spawn; the WS snapshot roster and REST /tasks (background/detached subagents) carry it too, keeping the model visible across client reconnects. * feat: carry the subagent thinking effort alongside the model The spawned event, snapshot roster, and REST /tasks now also carry the child's effective thinking effort (read from the child profile at spawn, the same vocabulary as agent.status.updated). UIs show it only when it diverges from the main session's current effort — an inherited level adds no information, and 'off' is never shown. * feat(tui): show the bound model and effort in the /tasks browser The task browser's Detail pane renders Model and Effort rows for agent tasks (raw alias and level — it is the inspector surface, so no diff filtering), and its minimum height grows to fit the new rows. The values were already persisted on SubagentTaskInfo; the TaskInfo union, its zod schemas (protocol, kap-server, klient contract), and the v1 type declaration now carry them so nothing strips them in transit. * feat(tui): show concrete subagent effort levels unconditionally Display rule simplified: any concrete effort tier (low/high/max/…) is shown next to the model — including when it matches the main session's level. Only the boolean states stay hidden: 'off' (no thinking) and 'on' (generic thinking) carry no level information. * docs: trim the changeset entry * fix(tui): keep the model and effort on background-agent entries across resume replayBackgroundProjection only copied agentId/parentToolCallId/ description, so a background subagent that outlived a resume lost its model/effort on the later terminal transcript entry. The projection now threads the persisted values (catalog-mapped model; boolean effort states dropped), and session replay passes the loaded model catalog through. * fix(agent-core-v2): normalize the derived secondary alias regardless of the flag A child bound while the secondary-model experiment was on keeps __secondary__ in its persisted binding; if the flag is later switched off with the recipe still configured, resolveSecondaryModel() gated the normalization and the sentinel leaked back onto resumed subagents. subagentDisplayModel now reads the recipe straight from config (the flag gates new bindings, not the interpretation of existing ones), which also drops SessionSwarmService's now-unused IFlagService dependency. Also adds the SDK package to the release: the new SubagentSpawnedEvent/AgentTaskInfo fields are SDK-visible types. * fix(agent-core-v2): normalize the status-frame model at the source A derived-bound child republishes agent.status.updated right after spawn with its raw modelAlias, which overwrote the spawned event's normalized display model on single-subagent cards (swarm headers were first-wins and escaped). emitStatusUpdated now maps through subagentDisplayModel, a no-op for the never-derived main agent. Also moves the inline comments added by this branch into top-of-file headers per the v2 comment convention. * fix(tui): clamp the /tasks detail frame to the available body At terminals near the minimum height the forced 10-row detail frame overflowed the body and truncated the preview frame's border. The detail height now caps out at whatever leaves the preview its borders plus one content row, with a regression test at exactly MIN_HEIGHT. * fix: normalize inherited derived aliases and keep model/effort on replayed terminal entries - resolveSubagentBinding's caller-fallback branch also maps through subagentDisplayModel: a caller itself bound to the derived entry (a resumed subagent making a nested Agent call) no longer publishes __secondary__. - The replayed background-task terminal notification builds its metadata with the persisted model (catalog-mapped) and concrete effort, matching the live completion path. - Drops the inline comments this branch added inside v2 test bodies; the scenario context lives in the source file headers. |
||
|
|
2b893733f9
|
fix(kap-server): bypass Node arg quoting for explorer /select, on Windows (#2645)
explorer.exe parses its raw command line rather than argv, so Node's default spawn quoting breaks the `/select,` argument whenever the path contains spaces: the command line becomes `"/select,\"C:\...\""`, which explorer rejects, silently opening the Documents folder instead of selecting the file. Quote only the path portion and launch with windowsVerbatimArguments so the command line keeps the documented `/select,"C:\some dir\f.txt"` form. |
||
|
|
510fbe7ec5
|
refactor(kap-server): wrap /api/v2/sessions in the v1 response envelope (#2644)
- return the domain-grouped page payload inside { code, msg, data,
request_id } and carry business outcomes in code (40001 invalid
params with details, 40922 page_token mismatch) instead of raw HTTP
statuses plus an { error: { code, message } } body
- add ErrorCode.PAGE_TOKEN_MISMATCH (40922)
- register the route via defineRoute (shared runtime validation and
envelope-wrapped OpenAPI docs); fold include-domain validation into
the query schema and replace the preprocess/doc-twin pair with
scalar-or-array union params
- update the kimi-inspect client to unwrap the envelope and sync the
two AGENTS.md guides
|
||
|
|
6f1cd7ca22
|
feat: add v2 sessions API and spreadsheet-like session table in kimi-inspect (#2640)
- kap-server: add GET /api/v2/sessions with a domain-grouped response (workspace / meta / activity, opt-in git), status / archived / updated_after filters, three sort orders, and fingerprint-bound opaque cursor pagination - kimi-inspect: rebuild the chat sidebar as a spreadsheet-like session table on the v2 endpoint — preset views (All / Opened / Archived / By workspace / Git), column visibility config, header sort toggles, cursor-paged Load more, and localStorage-persisted panel prefs - live activity frames from the WS hub override the REST status badge; session created / meta-updated events invalidate the v2-sessions query |
||
|
|
98ee35afd2
|
feat(agent-core-v2): add custom agent identity (#2573)
* refactor(agent-core-v2): simplify context tags and shared copy
Rename the context-injection tags to `<skill-loaded>` and
`<plugin-instructions>`, drop the product prefix from the CronCreate tool
description and the default agent description, and point the MCP OAuth
callback page back to "your terminal" instead of naming one client.
The callback page is shared by the ACP host, the web UI, and embedding
hosts, so naming a single client was inaccurate there. The tags and the
two descriptions read exactly the same without the prefix. Verified no
runtime consumer matches the old tag names; the updated snapshots cover
the tool descriptions that changed.
* feat(agent-core-v2): add a switch for the product-documentation skills
Five builtin skills document this CLI itself — `update-config`,
`custom-theme`, `mcp-config`, `check-kimi-code-docs`, and
`import-from-cc-codex`. Their names and descriptions sit in the system
prompt on every turn, which is dead weight for runs that will never
reconfigure the CLI.
Add a top-level `builtin_product_skills` field (also settable through
`KIMI_CODE_BUILTIN_PRODUCT_SKILLS`) to drop them. On by default, so
nothing changes unless it is set; the trade when off is that the model
loses the guided flows for those tasks.
Filtering happens where the catalog is assembled — a later filter would
leave the skills advertised to the model. The whole section is one
scalar, so it exercises the section-level env binding branch and needs
its own strip: `stripEnvBoundFields` only walks object fields, so an env
override would otherwise be written back into `config.toml`.
* feat(agent-core-v2): add custom agent identity
Add an `[identity]` config section (`name`, optional `slug`, both also
settable through `KIMI_CODE_IDENTITY_NAME` / `KIMI_CODE_IDENTITY_SLUG`)
that sets the identity the agent presents: the name it calls itself in
the system prompt, the `User-Agent` product token sent to third-party
providers, and the client name announced to MCP servers. Leaving it
unset changes nothing.
Until now every one of these was fixed, which left no way to run the
agent as part of another product — an internal deployment, a fork with
its own branding, an embedding host.
The identity resolves inside the engine rather than being seeded by each
host, so it applies to every launch surface — including headless runs,
which today seed no display name at all and fall through to the built-in
default.
Two deliberate asymmetries:
- The display name is a filling value with a fallback chain (config >
host-declared > the consumer's own default); the slug is a rewriting
value with two states only, so with no identity configured the
rewriting paths are equivalent to not existing.
- The rewrite happens in the outbound header assembly, the one layer
that knows which vendor it is building for. Vendors declaring
`hostHeaders: 'full'` keep the host's own product token, which that
header set is built around and which backends key on; the configured
identity applies to the third-party path.
Resolution is lazy throughout: config loads asynchronously, and a
constructor snapshot would freeze the pre-load value under some startup
orderings.
Two input edges the resolver has to absorb, since both would otherwise
reach the User-Agent builder and either break it or quietly rewrite the
header: blank and whitespace-only values read as unset in the file just
as they already did in the env, so a stray `name = ""` cannot claim an
identity; and a name that folds away to nothing under slug
normalization (a CJK-only name, say) falls back to a neutral token
rather than producing a blank product, which the builder rejects.
* fix(agent-core-v2): keep the file value when a scalar env binding fails to parse
`config.ts` documents that an env value failing its binding's `parse` is
ignored, and `applyEnvBindings` honors that for object fields by
assigning only when the resolved value is defined. `applySectionEnv`
returned the parse result straight through for whole-section scalar
bindings, so a blank or mistyped variable resolved to `undefined` and
cleared the configured file value instead of being ignored.
Nothing hit this before: every existing section either binds object
fields or is env-only. `builtin_product_skills` is the first
whole-section scalar binding, where exporting an empty or misspelled
`KIMI_CODE_BUILTIN_PRODUCT_SKILLS` would silently undo a configured
`false`.
* feat(agent-core-v2): extend the custom identity to discovery and global MCP
Two outbound paths still announced the built-in product name under a
configured identity:
- `DiscoveryService` read the host User-Agent straight from bootstrap
args when refreshing provider models, so custom registries — which are
third-party endpoints — saw the original token while chat requests to
the same class of endpoint saw the configured one.
- `SDKRpcClientV2` builds its own global `McpOAuthService` plus a
throwaway `McpConnectionManager` for server testing, neither of which
goes through the workspace-owned manager that carries the resolver.
Both now resolve the identity from the App scope.
* refactor(agent-core-v2): neutralize remaining copy and align comments
The synthetic MCP authentication tool description is injected into the
model context and still named the product; it and the OAuth callback
pages now use client-neutral wording. "Return to your terminal" was no
improvement over naming a client — both assume what the host is, and
that page serves the ACP host, the web UI and embedding hosts alike.
Comments introduced by the identity work move into their module headers,
per the domain convention. Interface field docs stay: the rule names
functions, methods and statements, and field-level docs are established
across the codebase.
The new tests gain scenario headers and dispose the scoped hosts they
create, and the `[identity]` docs state which engine reads the section.
* fix(agent-core-v2): read the product-skill switch after config is ready
`BuiltinSkillSource` is the lowest-priority skill source, so the workspace
catalog loads it first — before `IConfigService` has finished loading — and
keeps the contribution it returns for the life of the handler, with no
reload path and no change event. Reading `builtin_product_skills` eagerly
therefore stranded the startup configuration: an explicit `false` could be
ignored for the whole process. `UserFileSkillSource` already awaits config
readiness for exactly this ordering; this source now does the same.
Also record the identity collaborator in the two module headers that gained
the dependency without documenting it, and scope the
`builtin_product_skills` docs to the engine that reads it, matching the
note the identity section already carries.
* fix(agent-core-v2): apply the product-skill switch to session-less listings
`builtin_product_skills = false` only reached the scoped skill source. The
SDK's `listWorkspaceSkills` and the server's `GET /workspaces/{id}/skills`
both composed the raw `BUILTIN_SKILLS` constant, and the web app feeds its
pre-session onboarding menu from that route — so the five product skills
stayed listed until a session existed, then vanished from the session's
catalog.
Move the decision into `visibleBuiltinSkills(enabled)` next to the constant
and route every consumer through it, reading the switch via the shared
`builtinProductSkillsEnabled`. Keeping "what counts as a product skill" in
one place is the point: three copies of the predicate would drift the next
time a builtin is added. The SDK listing also awaits config readiness,
which it did not do before.
* fix(node-sdk): await config before materializing the global MCP OAuth provider
`McpOAuthService` caches providers by store key and stamps the client name
when it first builds one, and the preceding `globalMcpConfig.get()` reads
`mcp.json` directly rather than through `IConfigService`. So a
`beginGlobalMcpServerAuth` call made right after the harness is created
could resolve the identity before config finished loading, pinning the
built-in label for the rest of the process — including the OAuth dynamic
registration a third-party MCP server records.
`testGlobalMcpServer` already awaited config readiness for its own reasons;
this path now does too.
* refactor(agent-core-v2): drop the unused builtin-skill registrar
`registerBuiltinSkills` stamped the raw constant into a catalog for "edge
composition without a Session" — exactly the shape that now has to respect
`builtin_product_skills`. It has no callers in v2 and is not exported from
the package index, so it was dead code that also stood as an invitation to
bypass the switch. v1 keeps its own copy.
Every remaining path composes builtins through `visibleBuiltinSkills`.
* fix(agent-core-v2): send the configured identity on custom-registry imports
`:import_registry` fetched a user-supplied third-party URL with a
hardcoded `kimi-code-kap-server` User-Agent, so the first request to a
registry announced the product while every scheduled refresh of the same
registry announced the configured identity. The hardcoded value was wrong
on its own terms too: that token names the server, and this path also runs
in the CLI.
Both services now project the identity through `identityUserAgent`, which
carries the two guards (no host header, or no identity) once instead of
per caller. The model catalog keeps an inline copy on purpose — kosong is
a foundational layer and must not import an app domain.
Sweeping the remaining outbound User-Agent sources found no further gaps:
WebFetch deliberately sends a Chrome-like UA, the models.dev catalog fetch
sends none from the CLI, and kap-server's `user-agent` reads are inbound.
* docs: scope the identity env vars and condense the changeset
The environment-variable reference advertised all three new variables
without noting that only the agent-core-v2 engine reads them; the
configuration page already carried that note. Added in both locales.
The changeset had grown into two paragraphs of implementation detail,
which is what would land in the CLI release changelog. `gen-changesets`
asks for one short sentence plus at most a one-line usage hint.
* docs(agent-core-v2): describe the identity as what the agent calls itself
The module headers had drifted into describing the feature by what it
keeps off the wire rather than what it configures. Reworded so they state
the capability: the identity is the name the agent uses for itself, and
the unset case is a no-op rather than something "safe". The product-skill
switch excludes skills rather than hiding them.
Wording only; behavior and structure unchanged.
* test(agent-core-v2): cover the identity on custom-registry imports
The import path switched from a hardcoded `kimi-code-kap-server` token to
the host User-Agent projected through the identity, but nothing asserted
it. Two cases pin both halves: a configured identity reaches the request,
and an unconfigured one leaves the host header intact — the second matters
because a single case would also pass if one hardcoded value had simply
replaced another.
Both fail against the previous implementation.
* fix(node-sdk): guard every global MCP OAuth path behind config readiness
`McpOAuthService` caches providers by store key and stamps the client name
when it first builds one, so any path that can materialize a provider has
to run after config has loaded. `beginGlobalMcpServerAuth` awaited
readiness, but `resetGlobalMcpServerAuth` reaches the same cache through
`invalidate()` -> `getProvider()` without waiting: resetting auth right
after the harness is constructed pinned the built-in client name, and the
await added to the begin path could not help because it then reused that
cached provider.
Rather than add the missing await, the accessor is now async and holds the
guard itself, so the service cannot be obtained before config is ready and
a future entry point cannot forget. The remaining `configReady` in
`testGlobalMcpServer` stays — that one is for its own `[mcp]` section read.
* fix(agent-core-v2): send the configured identity on models.dev requests
The directory fetch behind `listModelsDevProviders` / `getModelsDevProvider`
still hardcoded a `kimi-code-kap-server` User-Agent, so browsing or importing
from models.dev announced the built-in product — and claimed to be the server
even when running in the CLI. Only the custom-registry import had been fixed.
`getModelsDevCatalog` now takes the User-Agent from its caller: the module is
plain module-level state with no container access, and the value depends on
the host and the configured identity, which only the calling service can see.
All four third-party fetches in that service share one helper.
Where the host states no User-Agent, a neutral token stands in rather than
dropping the header — these are directories the service chooses to call, so
there is no host intent to preserve, unlike the provider requests the model
catalog assembles.
Both new tests fail against the previous hardcoded value.
* test(agent-core-v2): assert the product-skill set literally
The expected sets were derived from the same `productSpecific` field the
production filter reads, so a builtin silently losing its marker would just
move between sets and leave every assertion green — while staying visible to
the model once the switch is off. The five names are now literal, with a test
asserting the marked set matches them exactly.
Dropping the marker from one skill now fails four tests instead of none.
Also states the App scope in the identity contract header, per the domain's
comment convention for contract files.
* fix(agent-core-v2): normalize the host-declared display name too
Blank and padded values were normalized on the config side but not on the
host fallback, so an embedding host passing `displayName: " "` rendered
"You are ," into the system prompt, and a padded name kept its padding.
Same rule now applies to every source of the name.
Also names `agentIdentity` as the collaborator in the request-headers
adapter header, which described the value it obtains without saying which
domain resolves it.
The three new cases fail against the previous implementation.
* fix(agent-core-v2): keep the configured slug when the host sends no User-Agent
The neutral fallback added for hosts that state no `User-Agent` discarded a
configured identity along with it: `identityUserAgent` returns `undefined`
as soon as there is no host header to rewrite, so `?? DEFAULT_IDENTITY_SLUG`
sent the literal `agent` even when `[identity].slug` was set — precisely the
case that fallback exists to serve. The configured slug now stands on its
own, with the neutral token reserved for having neither.
The four combinations of (host header, configured slug) had three tests; the
missing one is the one that was wrong. It now fails without this change.
`outboundUserAgent` also awaits config readiness before reading the identity,
so a browse issued right after bootstrap cannot send the pre-load value — the
guard lives in the accessor rather than at its four call sites, matching how
the same race is handled elsewhere in this branch.
Both headers here and in `discoveryService` now name `agentIdentity` as the
collaborator resolving that token.
* test(acp-server): follow the renamed skill-activation tag
`acp-server` arrived on main after the tag rename, so its two assertions
still expected `kimi-skill-loaded` and failed once the branches met. Also
updates the web app's CSS comment, which named the old tag from the start
of this branch — a comment, so nothing ever failed on it.
Found by CI: the merge verification only ran agent-core-v2's suite, and
this package is neither a dependency nor a dependent of it.
* fix(agent-core-v2): present the configured slug on registry refreshes too
The previous round taught the import path to fall back to the configured
slug when the host states no `User-Agent`, but left the scheduled refresh
of the same registry on the bare projection — so one registry could see
`acme` on import and the runtime default on refresh.
Extracting `identityUserAgent` had made the two paths share a function
without sharing the policy. The choice itself is now the shared piece:
`identityUserAgentOrDefault` always yields a value, for the directories
this process chooses to call, while `identityUserAgent` stays the form
that rewrites only what the host already sends — what a provider request
needs, where the host's silence is its own choice.
* docs(agent-core-v2): move new member docs into the module headers
The domain's comment convention is absolute — comments live solely in the
top-of-file block — and I had read the "functions, methods, or statements"
clause as leaving interface members out. It does not: only 25 of 734 v2
sources carry an indented block, so the members I documented were the
exception, not the pattern.
Seven members across six files move into their headers. `types.ts` had no
header at all, so it gains one.
* fix(agent-core-v2): connect session MCP overlays after config is ready
The shared manager reaches `connectAll` through `initialize()`, which awaits
the config domain first; `sessionOverlay` called it straight away. A session
carrying ephemeral `mcpServers` created right after bootstrap therefore
resolved the client name before config had loaded and initialized under the
built-in one.
The blast radius is wider than that one connection: a remote server sends
the overlay through `hasTokens()`, which materializes an OAuth provider on
the *shared* service and caches it by store key — so the early name outlives
the connection that raced. The overlay now connects behind `mcpConfig.ready`,
leaving the returned readiness promise unchanged.
* fix(agent-core-v2): reload builtin skills when their switch changes
The workspace catalog keeps each source's contribution for the life of the
handler, so a `builtin_product_skills` toggle never reached an existing
handler's sessions. That was harmless while every surface read the same
constant — but routing the session-less listings through the config made the
two views disagree, since those read the switch on every call.
Follows `ExtraFileSkillSource`: subscribe to the owning section and fire
`onDidChange`, which the catalog already turns into a source reload. The
test asserts an unrelated section does not trigger it.
* fix(agent-core-v2): apply the identity to self-configured web services
`[services.moonshot_search]` and `[services.moonshot_fetch]` name their own
`base_url`, so both services can point at an endpoint the user chose — but
each forwarded the host request headers verbatim, sending the built-in
product token there under a configured identity.
Only the services-config path is rewritten; the managed OAuth path keeps the
host headers as they are, being the endpoint the session authenticated
against. The distinction is the same one the model catalog draws per vendor.
`identityHeaders` carries the rewrite across a whole header set, so this is
the fourth caller sharing the projection rather than repeating its guards.
A pair of tests pins both halves.
My earlier sweep classified these two as official by their names instead of
asking who chooses the URL, which is why they were missed. The contract
header is also condensed here, per the convention below.
* docs(agent-core-v2): condense the identity headers to their contracts
The comment convention is one sentence with two halves — comments live only
in the top-of-file block, *and* that block states the module's role without
narrating implementation. Moving the member docs up last round satisfied the
first and broke the second: the headers ended up spelling out the slug
folding algorithm, the strip mechanics, and the load order.
Kept what a caller or the next editor would get wrong without it (why the
value is read rather than snapshotted, what `undefined` obliges a consumer
to do, why this source waits for config). Dropped what the code already
says. 22/12/12/13 lines, against 53 in `catalogService.ts` — length was
never the problem.
* fix(agent-core-v2): rebuild active prompts when the builtin skills change
Reloading the catalog on a `builtin_product_skills` toggle left existing
agents holding the old listing: `AgentProfileService` refreshes the prompt
only for the plugin source, so a disabled switch kept advertising skills
that were gone, and enabling it left them missing until an unrelated
refresh.
The plugin source is special because it also contributes prompt sections
(#2314), and the file-backed sources are left out for cost — their fs
watches would rebuild every agent's prompt on each edit. The builtin source
has no watch: it changes only when its config switch is toggled, so it
belongs with the plugin source rather than with the file ones.
Subscribing to the catalog rather than the config section is load-bearing.
The catalog fires after the contribution is replaced, whereas a config
subscription would race the reload, and `resolveSkillListing` only awaits
the catalog's *initial* readiness — so the rebuilt prompt could read the
listing it was meant to replace.
The source id is a named constant now, so the subscription does not match
on a bare string.
* refactor(agent-core-v2): freeze the agent identity for the process lifetime
The identity is announced outward (MCP initialize, OAuth registration,
provider request logs) and cannot be re-announced, so mid-process changes
could only ever apply partially. Resolve it once when config first loads
and hold it for the life of the process: IAgentIdentity now hands out a
frozen snapshot via resolved()/current(), carrying finished products
(outbound User-Agent variants, rewritten header set) so call sites stop
composing host headers with the slug themselves. The kosong host-headers
port carries two finished layers and the catalog only picks one; consumers
gain no invalidation obligations because the value can never change after
the freeze. [identity] edits take effect on the next start (documented).
* fix(agent-core-v2): locate the User-Agent header case-insensitively
HTTP header names are case-insensitive, but the snapshot builder looked up
'User-Agent' by exact key: an embedding host spelling it 'user-agent' got no
third-party UA and kept its own product token on the services path even with
an identity configured. The builder now locates every case variant and
rewrites each in place, keeping the host's spelling. Also corrects the two
web-service headers that still described both paths as sending the bootstrap
headers, naming agentIdentity as the collaborator behind the config path,
and documents that a resumed session keeps its recorded system prompt.
* fix(agent-core-v2): attribute header provenance from the finished third-party layer
Inspection reconstructed the non-full host layer from the raw headers with an
exact-case 'User-Agent' lookup, so a host spelling the header 'user-agent'
got a resolved User-Agent with no provenance entry even though the runtime
sends the rewritten value. buildModel now captures the port's finished
third-party layer in the trace and attribution reads it, keeping inspect()
on the same resolution pass as get(). Also condenses the identity contract
header to its external role, and documents that an existing MCP OAuth
authorization keeps the client registration it was granted under (reset the
server's auth to register under the new identity).
* fix(agent-core-v2): keep web tool backends from racing the identity freeze
An env-configured [services] endpoint is visible before config finishes
loading, and FetchURLTool / WebSearchTool materialized their backends at
construction — so a fast bootstrap could hit the identity snapshot's
pre-freeze guard during agent creation, and the composed backend pinned
config and login state for the agent's lifetime against the service's
documented per-call resolution. Both tools now resolve their backend per
invocation, the WebSearch activation gate checks presence alone through the
new hasWebSearchProvider() (no provider composition, no identity read), and
bind() awaits the identity freeze before materializing the model, whose
resolution reads the identity through the host-headers port.
|
||
|
|
119a33f7f1
|
feat(minidb): persistent index generations and lifecycle hardening (#2604)
Some checks are pending
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* perf(minidb): skip idle everysec fsyncs and add lifecycle stats - everysec WAL now fsyncs on the timer only while dirty (tracked by a write/sync generation watermark); close() keeps its unconditional final sync, and background sync failures surface via walFsyncErrors plus a sticky lastWalFsyncError instead of being silently swallowed - add WAL queue/group-commit counters (walQueuedBytes, walMaxQueuedBytes, walGroupCommits, walGroupCommitFrames) and lifecycle phase stats: recovery bytes/frames/duration, index/text rebuild durations, compaction total/snapshot/rotation/postings durations, rotation pause, and query candidates/decoded/sorted rows; add a syncIntervalMs open option threaded through compaction WAL rotation - rewrite the bench on fixed-seed synthetic data with a stable machine-readable JSON report (cold open 10k/50k/100k, word/ngram search, idle-fsync acceptance, 100k compaction, event-loop delay, peak heap/RSS per scenario) and pin the schema in test/bench-json.test.ts; add app-side baselines with loose complexity budgets in sessionIndex and searchService tests - fix ClusterDb lock-pool closeAll() leaking in-flight shard opens and drain the query store's async close on server shutdown, eliminating the ENOTEMPTY directory-teardown race * perf(minidb): bound startup rebuild and steady-state hot paths - rebuild all derived indexes in one shared store walk: a single decode per record fans out to staged builders, dt rebuild reads record metadata only, and index-less opens no longer decode at all - rank full-text results with a bounded min-heap plus a stable key tie-break instead of sorting every candidate - remove/overwrite text docs via a docID -> delta-terms reverse map instead of scanning the whole delta vocabulary - validate unique batches incrementally against touched postings instead of copying the full per-index owner map - reap due TTL entries from the expiry heap on the write path instead of a full-store sweep per write * feat(session-index): add minidb read model with keyset pagination - add ISessionIndex read-model lifecycle (prepare/status, ready/degraded states) behind the persistence_minidb_readmodel experimental flag - add ISessionIndexMirror write side recording fresh summaries into a bounded, coalescing queue after the authoritative document is durable - replace the offset cursor with before/after keyset pagination; rename list/countActive to listRecent/count - extend IQueryStore with ordered columns and pageByColumn, plus getMany/listKeys/dropCollection - wire the read model through kap-server routes and start, and update the klient sessions contract - index every session for global search instead of the 500 most recent * feat(kap-server): bound search sync lifecycle, pagination, and query budgets - split search requests from sync work: searchIndex() no longer awaits runSync/reopen/reindex; a single-flight sync coordinator with debounce and backpressure runs in the background, stale generations keep serving with explicit stale/degraded state, and refresh/sync/reindex failures surface via lastRefreshError instead of being swallowed - scope file-meta keys by session id (\0meta\file\<sessionId>\<hash>) with lazy + one-shot background migration from the legacy hash-only keys, so one session sync only touches its own meta rows - make authoritative scans incremental (mtime/ino/size rescan conditions, unchanged files no longer rewrite meta) and read wire deltas in 1 MiB chunks instead of whole-file buffer + split - replace offset pagination with versioned v2 keyset page tokens (fingerprint + index generation + sort boundary); generation changes fail old tokens with invalid_page_token, legacy v1 offset tokens are served once and upgraded, and pages collect via bounded top-K instead of full sort + offset skip - add query budgets enforced at the postings/score stage: max query terms, literal length cap, postings visit budget (minidb searchBounded/maxVisits with prefix decoding that never fabricates hits and skips the postings LRU), candidate caps, deadline and text budget; truncation is reported via incomplete reasons candidate_cap/postings_budget/deadline - reopen read-only dbs by opening the next handle before closing the previous one so a failed refresh keeps the old generation serving; failed opens now self-heal through search traffic 100k-message bench: first page p95 < 300ms and page-100 cost on par with page 1; event-loop delay during queries stays sub-millisecond. * fix(minidb): poison, roll back, and recover the WAL on write failures - give WAL writes a commit point: a failed flushBatch poisons the WAL (WAL_POISONED, tracked separately as walWriteErrors vs walFsyncErrors), rejects queued frames in reverse enqueue order, and stops scheduling further batches; everysec background sync failures stay non-rejecting per stage-1 semantics - recover in place to a known-safe point: a serialized recovery chain truncates the WAL back to the first un-acked frame, rebuilds size/nextOffset, and clears the poison; writes queue behind the recovery gate (zero-cost when idle), a failed truncate flips the instance into an explicit writeDisabled state, and a stale truncate offset (WAL file replaced by a rotation) skips the truncate - roll failed flush groups back as a unit: frames are stamped with their batchId, MiniDb keeps per-group earliest pre-state, and the first rejection restores every key of the group (rejected writes no longer reappear after reopen, and in-memory state matches reopen for any failure interleaving); the per-op seq guard remains for cross-group and rotation-retry races - wrap applyOp and the following in-memory mutations so a contract violation poisons the WAL and rolls the group back instead of escaping as a half-commit; frames never enqueued (seal race) roll back per-op without poisoning - tag errors past the commit point with ambiguous: true so callers can distinguish "definitely not applied" from "maybe applied but revoked" - close() waits for the recovery chain to go idle and backup() fences behind in-flight recovery before copying files Controlled A/B bench (22 alternating iterations, 100k concurrent sets): write-path throughput regression is within the 2% budget. * fix(minidb): turn the file lock into an instance-owned serialized lease - distinguish lock ownership by instance instead of pid: every acquire mints a pid:uuid token carried by lock/bid/watch files, inspect().mine compares tokens, liveness still follows pid, tokenless legacy files keep the old stale-takeover path, and hasLiveForeignWatch excludes self by token so same-process contenders see each other (closing the double-win takeover and the cross-instance release); a live same-pid lock is still respected, and re-acquiring a held lock is idempotent - serialize acquire/renew/release through a per-instance promise-chain mutex: renew re-checks held inside the chain and release waits for an in-flight renew, eliminating the renew/rename-after-unlink ghost lock - make MiniDb.close() a state machine (open/closing/closed) with a shared closePromise: cleanup runs per-resource try/catch in dependency order (text indexes, store, valueReader, WAL, lock), aggregates every cleanup error into an AggregateError, stays in 'closing' on failure so a retry finishes the cleanup, and no longer leaks the lock when the WAL close fails; a rejected in-flight compaction no longer escapes the cleanup pass * fix(minidb): keep readers on one consistent file generation - add an internal persistent-files module as the single source of truth for the persisted file set (snapshot, WAL, sidecars, postings pattern, fingerprint subset); lock-pool fingerprints, persistentFiles, open stale-tmp cleanup, and backup/restore filtering all derive from it, and fingerprints upgrade to dev:ino:size:mtimeMs so compound sidecar changes can no longer hide from cluster readers - pair snapshot and WAL generations during recovery (transitional stat-pairing until stage-5 manifests): each pass anchors the fds it scans, re-stats afterwards, tolerates append-only WAL growth, retries bounded times on generation churn with a clean store reset, and throws RECOVERY_GENERATION_CHURN when churn exceeds the budget; the disk-mode ValueReader attach re-validates inodes so stale offsets never read a replaced file - make the rotation directory fsyncs strict: failures abort the rotation through the existing rollback path instead of being swallowed, while platforms without directory fsync degrade once with a warn and stats.dirFsyncUnsupported * fix(minidb): serialize index-definition sidecar mutations, persist before publish - extract the promise-chain mutex into a shared createSerializer() and give each sidecar family (secondary/compound/text) its own chain: create/drop run uninterruptibly (memory change + rebuild + persist), different families stay independent, and the data write path never shares these chains - reverse the publication order to staged -> persist -> publish: a create stages the definition, rebuilds via the staged builder, persists the sidecar including the new definition, then publishes atomically; any failure discards the staged state leaving live and sidecar untouched (no phantom indexes, retry-safe); a drop persists the sidecar without the definition before removing it live; text index create/drop adopt the same pattern, replacing the hand-rolled unwind, and a dropping marker keeps compaction postings rebuilds out of the persist window - feed staged indexes from the incremental write path (add/remove/ checkUnique/checkUniqueBatch visit live+staged) so writes landing in the persist window are not lost at publish; queries still see live only - harden writeFileAtomic: instance-unique tmp names (.tmp-pid-seq), a strict fsyncDir after rename so a successful persist is crash durable, and whitelist-based stale-tmp cleanup that never touches lock tmp files * fix(minidb): validate writes before any side effect, canonicalize values once - canonical value at the write boundary: the json codec re-parses the encoded bytes once and every downstream consumer (unique checks, secondary/compound/text indexes, dt extraction) sees exactly the persisted representation, so getter/toJSON/Proxy documents can no longer diverge between the index view and the storage view - reorder the set/batch pipeline so every fallible check happens before any visible side effect: prepare (key/ttl checks, encoding, canonical decode, index field extraction, tokenization) -> unique checks -> ensureMemoryFor eviction -> commit; a constraint failure now leaves the database untouched (no more evicted victims on rejected inserts), and applyOp is structurally pure against pre-validated data - tokenize at the prepare boundary: TextIndex gains prepareAdd/ addPrepared and the buildQueue carries validated key+tokens mutations instead of raw docs, so a throwing custom tokenizer can no longer poison the live view or the queue, and custom-tokenizer output is rejected per token over 0xffff bytes before it can permanently break postings rebuilds; prepared tokens are keyed by index instance so a same-name drop+create mid-write re-tokenizes instead of crossing tokenizers - strict batch structure validation: scanBatchOpRefs/decodeBatchOps reject unknown op types, out-of-bounds lengths, and trailing bytes (offset must equal body length), so a valid-CRC but malformed batch is skipped as a unit and counted via RecoveryInfo.corruptBatches instead of being partially applied Bench vs the stage-1 baseline: json write throughput regression is within the 5% budget (median ~2-4% depending on the measurement). * feat(minidb): add OpTracker drain primitive and atomic backup, harden tests - introduce the internal OpTracker (close gate + in-flight counter with enter/leave/close/whenIdle and reference-counted pause/resume) and drive every shutdown/drain path from it: WAL background syncs are tracked so close() waits out an in-flight sync before closing the fd, cluster lock-pool closeAll() closes the gates and drains busy callbacks before closing handles, and MiniDb writes pass a write gate - make backup() atomic with a defined linearization point: pause the write gate, drain in-flight writes (every acknowledged write is now included), copy to a sibling temp dir with per-file fsyncs, write the manifest last as the commit marker, and rename into place; failures clean up and leave no partial backup, and concurrent writes are rejected with BACKUP_IN_PROGRESS - reap emptied compound-index groups on remove (the groups map no longer grows monotonically), move the open-time mkdir behind the readOnly check so a read-only open of a missing directory fails with ENOENT instead of creating it, and never run a destructive rebuild for a read-only open failure (explicit or onLockFail fallback) - consolidate every review fault-injection repro into the formal suite behind deterministic barrier helpers (programmable writev/sync/ rename/tokenize hooks) and convert the six timing-based tests to barrier/tick-driven assertions; the .tmp repro scripts are removed The converted timing tests and the full suite pass 50 repeat runs (including under CPU load injection) with zero flakes. * feat(minidb): persist derived indexes as atomic generations, open from WAL delta - checkpoint the store, dt/secondary/compound indexes, and text dictionary/postings/docs into immutable generations under generations/g-NNNNNN published atomically (tmp build, per-file checksums and fsyncs, dir rename, CURRENT swap, strict dir fsyncs); the manifest records the format version, WAL/snapshot checkpoint anchors, per-index definition hashes, and codec/value-mode compatibility - open now loads the published generation and replays only the WAL delta after its checkpoint: no full value decode, corpus tokenization, or postings rewrite on a normal reopen (warm opens are 3.5-13.8x faster at 100k/1M records); a definition change rebuilds only the affected index, and corrupt generation files fall back to the previous generation or the legacy full recovery without ever touching the authoritative snapshot/WAL - build generations transactionally with compaction (rotation plus derived state publish as one unit, replacing the synchronous rebuildTextPostings tail), capture concurrent writes through a sealed op queue with byte/op caps, hard-link clean postings and the snapshot into the new generation, and repoint every live text base into the CURRENT generation after publish - cluster/read-only refresh watches CURRENT and the WAL watermark: pure generation publishes keep readers on incremental catch-up while rotations reopen onto the new generation; writers building the next generation never disturb readers of the current one - legacy databases open through the old path unchanged and gain their first generation in the background; OpenOptions.indexGenerations: false fully restores the pre-generation behavior * feat(minidb): workerize text-index builds and split MiniDb into facets - split the monolithic src/index.ts into facet modules (mini-db, types, value-codec, memory-guard, backup, query-engine, text-registry, wal-group, generation-builder/loader, write-path, read-path, index-admin, lifecycle, stats) and move text-index.ts to text-index/ - run corpus-scale text-index builds off the main thread via the bounded worker engine (src/worker/), exported through the new worker-runtime subpath, with inline fallback for small corpora and rollback switches - defer the open-time fallback text rebuild into a maintenance task; searches on a not-yet-committed base raise TextIndexBuildingError - add the unified maintenance scheduler, bounded async read surface, and a maintenance bench - kap-server search: switch to searchBoundedAsync and serve the building page while the index base rebuilds after fallback recovery - kimi-code: install the SEA-bundled minidb text-build worker at startup, bundle it via the native asset scripts, and add the startup-trace util plus the KIMI_TUI_INPUT_LATENCY debug probe * fix(minidb): treat win32 EPERM as unsupported directory fsync - extract isUnsupportedDirectoryFsyncError and cover win32 EPERM - drop the one-shot console.warn; stats.dirFsyncUnsupported carries the degraded state * fix(kap-server): harden search-index dispose and drain lifecycle - dispose() now closes an OpTracker gate and drains in-flight sync/refresh passes before closing the db, so no background write can hit a closed handle; the deleteSessionDocs loop and trailing stats write skip once the gate closes (review #20) - drainGlobalSearchDisposals loops to a fixpoint so disposals registered while a drain is in flight are also awaited (review #21) - pin the post-open failure semantics with a regression test: a failed text-index setup closes the handle and the next open reacquires the writer lock instead of self-locking read-only (review #19) - export OpTracker from the minidb root for the search service's drain * chore: fix oxlint type-aware lint errors |
||
|
|
3126422757
|
feat(mcp): carry an absolute expiresAt on the OAuth authorization-url update (#2609)
* feat(mcp): carry an absolute expiresAt on the OAuth authorization-url update The authenticate flow waits for the OAuth callback with a fixed budget, but the authorization-url tool update surfaced to embedding hosts did not say when that window ends — hosts had to hardcode a mirror of the 15-minute constant to render countdowns. Include the absolute deadline (now + effective wait timeout) in the update payload for v1 and v2. Resolve #2607 * fix(protocol,kap-server): accept expiresAt in the OAuth authorization-url update schemas The zod validators mirrored the pre-expiresAt payload shape and would strip the new field at the kap-server boundary. --------- Co-authored-by: zouying <zouying@moonshot.cn> |
||
|
|
c39687318c
|
fix(kap-server): accept question ids containing colons on resolve (#2585)
Some checks are pending
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix(kap-server): accept question ids containing colons on resolve Some OpenAI-compatible providers emit tool_call ids like `AskUserQuestion:0`, which the question service adopts as the question id. The action-suffix parse then rejected the bare resolve POST as an unsupported action (40001), so clients could never submit answers. When the suffix parse fails, fall back to matching the full tail against the pending question list before emitting 40001. Also add maxRetries to the test home cleanup to absorb the async query-store shard flush (ENOTEMPTY on macOS), matching fs.test.ts. * fix(kap-server): preserve 40902 on duplicate resolve of colon-id questions A retried bare resolve of a colon-bearing question id re-entered the invalid-suffix fallback after the question settled, found no pending match, and returned 40001 — bypassing the recently-resolved idempotency window. Accept the tail in the fallback when it is recently resolved so the shared duplicate-resolve path emits 40902 as documented. |
||
|
|
21185447fe
|
feat(agent-core-v2): add tokenCounting service with strategy config and measured anchors (#2563)
* feat(agent-core-v2): add tokenCounting service with strategy config and measured anchors - add IAgentTokenCountingService as the single owner of token counts: context size, full-request size, and estimate primitives, replacing the scattered contextSize/tokenEstimate/fullCompaction paths - add [token_counting] config section with strategy = measured+estimated (default) / measured / estimated, plus the KIMI_TOKEN_COUNTING_STRATEGY env override; measured zeroes all estimates, estimated ignores anchors - keep a live measured-anchor ledger in TokenCountingModel: each LLM exchange writes a real anchor, undo truncates the ledger so the surviving prefix restores its REAL measured size instead of a re-estimate, and compaction rebases to a single anchor that blends the compaction exchange's measured summary output tokens - skip writing an anchor when the stream reports no usage event instead of anchoring emptyUsage() zeros, which zeroed the context size and silenced compaction for providers without usage reporting - return the strategy-resolved size (not measured) from rpc getContext so the tokenCount contract stays correct under the estimated strategy - migrate all consumers (contextMemory, fullCompaction, llmRequester, rpc, mirrorAgentRun, sessionLegacy, kap-server legacyStatus, node-sdk, kimi-inspect) to the new service; edge bridges no longer read the wire model directly - document [token_counting] and KIMI_TOKEN_COUNTING_STRATEGY in the bilingual config reference * fix(kap-server): omit maxContextTokens instead of pushing 0 when unknown - readLegacyStatus falls back to the default model's context limit when no model is bound, and omits maxContextTokens entirely when the limit is unknown (0 is the engine's UNKNOWN_CAPABILITY marker, not a real limit) - profileService no longer emits maxContextTokens in agent.status.updated when the bound model alias does not resolve * fix(agent-core-v2): resolve token_counting strategy only at the reporting edge - keep measured anchors and heuristic estimates both recorded and feeding internal logic (compaction triggers, budgets, overflow backoff) regardless of the configured strategy - add IAgentTokenCountingService.statusSize() as the single strategy-resolved outward reading and route the WS/REST/RPC status surfaces through it - fix the context-size display falling back to provider-reported usage under the estimated strategy - fix compaction overflow backoff retrying identical messages until failure under the measured strategy (the strategy-gated estimator read as 0) |
||
|
|
6ba75a173b
|
feat(config): add deprecation mechanism and rename loop retry limit (#2572)
* feat(config): add deprecation mechanism and rename loop retry limit - agent-core-v2 config: declarative section `deprecations` (deprecated TOML keys are ignored and report a warning diagnostic; the file is never rewritten) and env binding `deprecatedEnv` (old var still resolves as a fallback with a warning), surfaced via the new `IConfigService.onDidChangeDiagnostics` event - loop_control: rename `max_retries_per_step` to `max_attempts_per_step` and `KIMI_LOOP_MAX_RETRIES_PER_STEP` to `KIMI_LOOP_MAX_ATTEMPTS_PER_STEP`; `max_steps_per_run` moves onto the same mechanism (no longer silently mapped) - kap-server: push the global `event.config.warning` WS event to every connection whenever the config warning set changes - TUI: show config diagnostics in warning yellow at startup instead of the dim startup notice - docs: config-files/env-vars (en+zh), regenerated config manifest, and the agent-core-dev config guide * feat(cli): validate config.toml against v2 section registry in doctor - add v2/validate-config.ts: validate config.toml with the agent-core-v2 ConfigRegistry, reporting registered-section schema failures as errors and unknown top-level keys / deprecated keys and env vars as non-fatal warnings - route `kimi doctor` config validation through the v2 validator when the KIMI_CODE_EXPERIMENTAL_FLAG master switch is on (lazy dynamic import, keeping the v2 module graph off the default path) - let doctor checks surface non-fatal warning messages on OK results * chore: downgrade loop-control changeset to patch |
||
|
|
071b6a50d9
|
refactor(kap-server): own v1 message history and snapshot assembly (#2562)
- move the v1 message protocol and projection out of the engine into kap-server and delete the engine-side messageLegacy edge adapter - add a shared message history loader that folds the main agent's wire journal into full history across compactions, backing both the messages routes and the snapshot endpoint - drop the disk-reading SnapshotReader fast path; assemble snapshots from engine services for cold and live sessions, removing the KIMI_SNAPSHOT_READER, KIMI_SNAPSHOT_TIMEOUT_MS and KIMI_SNAPSHOT_CACHE_LIMIT knobs - collect persisted wire record rebuild helpers in the transcript service |
||
|
|
3e425212b6
|
refactor(agent-core-v2): code all domain failure modes as Error2 (#2552)
* refactor(agent-core-v2): code all domain failure modes as Error2 - wrap bare throws across agent/session/app/workspace/os/wire/kosong/mcpCore domains in coded Error2, keeping messages verbatim and moving structured data into details with the original error as cause - add new wire codes (agent.already_exists/already_running/not_a_subagent/not_owned/type_not_allowed/max_tokens_exceeded, task.limit_exceeded, cron.expression_invalid, web.invalid_url/private_address/fetch_failed, mcp.oauth_failed, skill.parse_failed/nested_too_deep, wire.migration_missing) to the protocol KimiErrorCode union and the kap-server zod schema; register shell.git_bash_not_found and session.plan_mode_invalid - re-base domain error classes onto Error2 (SkillParseError, UnsupportedSkillTypeError, HostFolder*, AgentFileParseError, NestedSkillTooDeepError, AlreadyAuthorizedError, HttpFetchError) keeping class names and instanceof consumers intact - convert caller-bug and unreachable guards outside _base to BugIndicatingError - fix the agent tool's task-limit remap never firing by branching on the task.limit_exceeded code instead of a stale message string * refactor(kosong): make ChatProviderError family born-coded via Error2 - move the provider/context code string constants to kosong/contract/errors.ts and compute each class's wire code at construction (status code / finish reason) - move sanitizeStatusErrorMessage to the contract and fold status details (statusCode / requestId / traceId) into Error2 details at birth - slim translateProviderError down to the abort guard plus the foreign-error fallback; ProtocolErrors keeps registering the domain via re-exported constants - update errors.md conventions and tests for the pass-through behavior * feat(storage): add permission_denied and disk_full error codes - extend StorageErrors with storage.permission_denied / storage.disk_full (both non-retryable, with user-facing actions) - map errno at the backend boundary in toStorageIoError: EACCES/EPERM, ENOSPC, unexpected ENOENT → not_found, everything else io_failed; the message now carries the mapped reason - register the two codes in the KimiErrorCode protocol union and the kap-server zod schema, and document the mapping in errors.md * fix(protocol): mirror all KimiErrorCode values in kimiErrorCodeSchema the zod enum lagged the type union by 35 codes (agent.*, os.fs.*, os.process.*, storage.*, wire.*, skill/task/mcp/cron/web additions), so protocol consumers could type the new codes but rejected them at runtime validation; spotted by Codex review on #2552 |
||
|
|
e22479a62e
|
feat(kap-server): expose effective experimental flags in /meta (#2417)
Some checks failed
CI / build (push) Has been cancelled
CI / test (1) (push) Has been cancelled
CI / test (2) (push) Has been cancelled
CI / test (3) (push) Has been cancelled
CI / test (4) (push) Has been cancelled
CI / test (5) (push) Has been cancelled
CI / test-pi-tui (push) Has been cancelled
CI / test-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / typecheck (push) Has been cancelled
Release / Release (push) Has been cancelled
Nix Build / Check flake.nix workspace sync (push) Has been cancelled
Release / Deploy docs (push) Has been cancelled
Release / Native release artifact (push) Has been cancelled
Release / Publish native release assets (push) Has been cancelled
Nix Build / nix build .#kimi-code (push) Has been cancelled
|
||
|
|
1f3f5dadaa
|
feat(agent-core-v2): interruption reminder for user-cancelled turns (#2400)
* feat(agent-core-v2): interruption reminder for user-cancelled turns
When the user interrupts a turn with Esc, append a durable
<system-reminder> (origin: injection/interruption) to the agent context
via a new loop aspect watching turn.ended, so the model learns the
previous turn was deliberately cut off. The marker persists to the
wire, replays on resume, stays hidden from transcripts, skips non-user
aborts and steer, and does not stack on repeated cancels.
Two supporting fixes:
- An aborted LLM stream now persists its accumulated partial
text/thinking as content.part loop events instead of dropping every
produced token; gated on the turn signal so retried or
step-cancelled attempts keep their partial output out of the record.
- The turn.cancel wire op carries an optional reason
('user_cancelled' | 'aborted') so cold readers can tell deliberate
interrupts from programmatic aborts. Goal-lifecycle cancels now pass
an explicit programmatic reason to keep that field honest.
* feat(transcript): mark user-cancelled turns with an interruption marker
Project the deliberate user interrupt onto the transcript timeline: the
live projector emits an 'interruption' marker when a turn ends with
interruptReason 'user_cancelled', and the cold fold consumes the
persisted turn.cancel reason into the same marker. Programmatic aborts
keep surfacing through their own outlets (errors, goal/task state), and
queued cancels that left no visible residue are skipped.
* fix(agent-core-v2): make user-turn cancellation idempotent and reconcile interruption reminders on restore
* fix(transcript): dedupe user-cancelled interruption markers by turn in the cold fold
* chore(agent-core-v2): regenerate state manifest after merging main
* refactor(agent-core-v2): split interruptionReminder out of the loop domain
The loop domain owns turn execution mechanics; whether an interrupted turn
should produce a model-visible reminder is a model-context policy. Move it
into its own L4 domain with its own wire model that cross-reduces the
loop's turn.cancel fact, and rename the op to interruptionReminder.recorded.
---------
Signed-off-by: Haozhe <yanghaozhe@moonshot.ai>
Co-authored-by: Haozhe <yanghaozhe@moonshot.ai>
|
||
|
|
44d34bbd56
|
refactor(agent-core-v2): move host runtime args onto IBootstrapService (#2460)
* fix(agent-core-v2): resolve package self-references in check-import-boundaries Imports spelled @moonshot-ai/agent-core-v2/<path> (the legal `./*` export self-reference) were treated as external packages, letting kosong layer violations through that spelling pass the checker. * refactor(agent-core-v2): move host runtime args onto IBootstrapService - Add HostArgs under BootstrapInput.args / IBootstrapService.args (agentFiles, skillDirs, requestHeaders, displayName, replyStyleGuide), mirroring VS Code's NativeParsedArgs on the environment service - Remove the narrow per-domain runtime-options services and their seed functions: IAgentCatalogRuntimeOptions, ISkillCatalogRuntimeOptions, IHostIdentity - Reduce IHostRequestHeaders to a pure kosong port contract and bridge it from bootstrap args via a new app/kosongConfig adapter, keeping kosong free of app-layer imports - Pass host args through bootstrap() at the composition roots (kap-server, v2 print CLI, node-sdk) instead of seeding services - Persist SDK provider removal as one atomic multi-section config replace * fix(config): persist provider refresh updates atomically - expose atomic multi-section config replacement through klient and SDK - stage provider removals before one atomic write in TUI refresh - briefly drain startup refresh during shutdown |
||
|
|
ed7a4cc095
|
feat(kap-server): add session-less POST /workspace/fs:search route (#2437)
* feat(kap-server): let fs:search resolve a workspace ref for draft sessions
- fs:search accepts a workspace id or absolute root in the session_id slot
so the @ file mention works before the session exists
- kimi-web searchFiles falls back to the active workspace id in draft state
* fix(agent-core-v2): report empty thinking level for unbound main agent
- sessionLegacyService.status returns thinking_level '' when the main
agent has no bound model (mirroring model: undefined), so clients
fall back to the catalog default instead of folding in the wire
model's 'off' zero value
- add regression test for a never-bound main agent status
- add web changesets: draft @ file mention, new-session thinking level
* perf(minidb): make text index rebuilds async and non-blocking
- TextIndex.build() yields to the event loop during tokenization and
batches postings writes (~1 MiB), so large rebuilds no longer
hard-block the host process
- writes landing mid-build are queued and replayed onto the new base at
swap time, keeping the rebuilt index exact
- PostingsFile.rebuildSync renamed to async rebuild with a synchronous
commit section (beforeRename hook + atomic rename)
- onCompacted hook is now awaited (sync or async); open-time compaction
runs in the background so open() returns without blocking on the
snapshot rewrite and postings rebuild
- compaction skips the postings rebuild when the index's write buffer is
clean (needsRebuild)
- createTextIndex registers before building so concurrent writes feed
the build queue; dropTextIndex throws while a build is in flight
* refactor(agent-core-v2): rename workspaceHandler to sessionLifecycle
- rename IWorkspaceHandlerService to ISessionLifecycleService and move
src/workspace/workspaceHandler/ to src/workspace/sessionLifecycle/;
update all consumers (gateway, sessionExport, sessionLegacy,
sessionLookup, kap-server, klient, node-sdk, kimi-inspect, kimi-code)
- rename IStateService to IAppStateService and add the Workspace-scope
IWorkspaceStateService, so the state domain spans all four scope tiers
- add cascading StateRegistry.inspect(): each tier injects the parent
tier's registry and folds App to current scope into one StateInspection
tree; check-domain-layers gains a Rule 2b exemption for state-on-state
imports
* feat(kap-server): add session-less POST /workspace/fs:search route
Carry the workspace reference (registered id or absolute root) in the
request body and resolve it to the same Workspace-scope fs service the
session route uses, so clients no longer borrow the session route's
{session_id} slot. kimi-web's @ file mention now calls this route with
the workspace ref instead of a session id; the session-route fallback
stays for wire compatibility.
* refactor(agent-core-v2): register workspace-scope service state into IWorkspaceStateService
- move workspaceDirs / workspaceInstructions / workspaceSkillCatalog / workspaceTrust
runtime state from bare instance fields into the workspace state container
- extend gen-state-manifest.mts to scan app/workspace scopes, emitting
AppStateSnapshot / WorkspaceStateSnapshot alongside Session/Agent
- regenerate docs/state-manifest.d.ts and update AGENTS.md + agent-core-dev skill
- update affected tests to register the state services and assert the new state keys
|
||
|
|
17dfd49768
|
feat(agent-core-v2): introduce the Workspace domain and the agent-profile registry extension point (#2366)
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat(agent-core-v2): insert Workspace lifecycle scope and remove mutable cwd paths
- Insert LifecycleScope.Workspace between App and Session
- Delete session/workspaceCommand domain (addAdditionalDir) and node-sdk RPC
- Remove profile cwd mutation; cwd is fixed at creation
- Make ISessionWorkspaceContext read-only; seed additionalDirs at creation
- Remove TUI and vscode /add-dir commands (to return workspace-scoped)
* feat(agent-core-v2): add Workspace scope with handler-owned session lifecycle
- Add IWorkspaceLifecycleService (App scope): handler registry,
create-or-get handlerFor with inflight join
- Add workspace/workspaceContext seed and workspace/workspaceHandler
(session create/resume/fork as handler child scopes)
- Delete App-level ISessionLifecycleService; callers compose
index -> handlerFor -> handler via sessionLookup helpers
- Slim IBootstrapService; persistence addressing via handler chain
(disk layout byte-identical)
- kap-server routes rewire internally; /api/v1 wire unchanged,
debug surface gains workspace addressing
- Pin red line in domain lint: session/agent must not import
workspace domains
* feat(agent-core-v2): collect workspace resources into the handler scope
- Add Workspace-scope catalogs for skills and agent profiles,
instructions service, and a shared MCP connection manager
(built at materialization, refreshed by watch/plugin events)
- Session catalogs keep their APIs but read seeded snapshots and
refresh via change events; ISessionMcpService removed
- Session create options carry no mcpServers; MCP sources are
config file (wins on name conflicts) and plugins only
- Agent profile/mcp consume the seeded providers
* feat(agent-core-v2): restore add-dir as a workspace-level capability
- Add workspace/workspaceDirs: shared additional-dir set with
addDir({path, persist}); persist=true writes .kimi-code/local.toml,
local.toml watch drives cross-process refresh
- ISessionWorkspaceContext becomes a live read view fed by the
ISessionWorkspaceInfo seed contract and change events
- Restore Session.addAdditionalDir in kimi-code-sdk 1:1, mapping to
the workspace service; restore TUI/vscode /add-dir verbatim
* feat(agent-core-v2): collect os-level services into the workspace scope
- Move fs service, fs watch (shared subscription fan-out), process
runner, and a git facade to Workspace scope; sessionFs domain removed
- Add IWorkspaceToolPolicy with workspace veto wired through tool
activation, execution guard, composed evaluation, and profile
prompt projection; injected via ISessionToolPolicyGate seed
- kap-server fs routes and fs.watch bridge remap to the workspace
services; wire unchanged
* refactor(agent-core-v2): clean up workspace-domain leftovers and docs
- Drop dead code: v2 mergeCallerMcpServers, the transitional
ISessionContext.additionalDirs field, an unreachable guard
- Fix stale domain references in comments; correct test names
- Give the fs-watch refresh test a realistic wait budget under load
- Document the four-scope model and workspace domain in AGENTS.md,
agent-core-v2 docs, and the agent-core-dev skill
* test(node-sdk): wait for the initial MCP connect to settle in the parity list test
v1 connects in the background after create resolves while v2 awaits it
inside create, so an immediate list can catch either side still pending
under CI load
* refactor(agent-core-v2): extract git work-tree discovery into the git domain
- add the pure findGitWorkTree probe in app/git/workTree and expose it
as IGitService.findWorkTree
- switch the git permission policies off the local
findLocalGitWorkTreeMarker helper to the DI service
- reuse findGitWorkTree for AGENTS.md project-root discovery in
agent/profile/context.ts
- add findWorkTree coverage to gitService.test.ts
* feat(kimi-inspect): add Workspace Services view
- add WorkspaceServicesView rail view with a workspace picker on top;
proxies resolve workspace-scope Services on the /workspace/:id route
- extend ChannelScope, ServiceTarget, and ServicePanelDef scope with
'workspace', routed via client.workspace(id).service
- wire the new view into NavRail and App
* refactor(agent-core-v2): extract mcpCore and workspaceMcpConfig domains
- move the scope-agnostic MCP connection layer (stdio/http/sse clients,
connection manager, oauth, config schema, tool naming) from agent/mcp
to the new mcpCore domain
- move the [mcp] config section to app/mcpConfig and OAuth credential
persistence to app/mcpConfig/oauthStore
- introduce the workspace/workspaceMcpConfig domain owning the effective
MCP server set (mcp.json files + plugin contributions, refreshed by
fs watch); workspaceMcp keeps pure connection orchestration
- update the plugin domain, session MCP handle, klient/node-sdk
contracts, and tests accordingly
* refactor(agent-core-v2): remove the fault-injection experimental feature
- delete the faultInjection domain (flag definition, IFaultInjectionService
contract, FaultInjectionService implementation)
- drop the requester-side take() injection point and the constructor
dependency from llmRequester
- remove the flag-gated test cases and the IFlagService stub they needed
- regenerate the state manifest without the faultInjection state keys
* feat(agent-core-v2): gate project-level MCP config behind workspace trust
Add the Workspace-scope IWorkspaceTrust service: an explicit, per-workspace
trust marker persisted under the home (IAtomicDocumentStore, keyed by
encodeWorkDirKey(root)) so a checked-out tree cannot pre-trust itself.
While a workspace is untrusted, workspaceMcpConfig skips the project-level
.mcp.json and .kimi-code/mcp.json files (user-level config and plugin
contributions still load); a trust flip reuses the reload path, so project
servers connect on trust and disconnect on untrust.
Expose the state over kap-server REST: GET /workspaces/{id}/trust,
POST /workspaces/{id}/trust, POST /workspaces/{id}/untrust.
* feat(kimi-inspect): replace the workspace picker with a directory browser
The Workspace Services view now keeps a server-side directory browser in a
left sidebar (over IHostFolderBrowser) instead of a <select> of registered
workspaces. Entries that are registered workspaces carry a workspace badge
plus their IWorkspaceTrust trust state; selecting an unregistered folder
registers it on demand via IWorkspaceService.createOrTouch.
* fix(agent-core-v2): resolve the effective cwd into the profile binding
A default-bound agent recorded no cwd in its profile.bind payload, and no
caller configures ProfileServiceOptions.cwd, so the profile service's cwd
getter fell through to '' and refreshSystemPrompt() rebuilt the prompt
from the server process's cwd: an AGENTS.md edit dropped the workspace
instructions (or swapped in unrelated ones).
bind() now persists the resolved effective cwd (the input's, or the
session's when the input omits it) into profile.bind — the Model's cwd
stays creation-fixed and is always set. The getter's last resort is the
session's own cwd (the value legacy bindings resolved against) instead of
a bare ''.
* refactor(agent-core-v2): introduce the contribution/registry/catalog extension point for agent profiles
- App-scope IAgentProfileRegistry: any scope can register an
AgentProfileContribution keyed by (sourceId, workspaceKey); dedup per
source id, change events drive catalog re-projection
- workspaceAgentProfileLoader domain owns agent-file discovery end to end
(parse / roots / SYSTEM.md / explicit runtime files) with five
Workspace-scope loaders (workspace / user / plugin / extra / explicit)
tagged with the handler's workspaceId; internals live under internal/
- SessionAgentProfileCatalog projects the registry directly (name dedup,
priority adjudication, builtin override rule, inspect()); the
workspace-catalog + sessionData seed relay is gone
- builtin code contributions register as the 'builtin' entry via
BuiltinAgentProfileLoader; plugin agent roots are provided by the
plugin domain as PluginAgentRoot
- remove cwd from the profile binding chain (BindAgentInput /
ProfileBindingSnapshot / AgentConfigData / ProfileModelState /
profile.bind op) — it is always the session's frozen cwd; legacy
wire.jsonl records replay fine (the schema strips the field)
- share markdown frontmatter parsing via _base/text/frontmatter
* fix(agent-core-v2): reconcile the workspace refactor with main
- restore the branch's klient workspaceId scope extension lost to a
file-level conflict resolution (main had no further changes there)
- stub the plugin system-prompt dependencies main added to the profile
service in the profileOps / skillCatalog tests
- correct PLUGIN_SKILL_SOURCE_ID to the App skillSource domain (Agent
scope must not import the Workspace domain)
- kap-server workspaceLayout test supplies the now-required hostIdentity
- regenerate wire/state/config manifests
* fix(agent-core-v2): export the agent-file parse primitives the v2 print CLI consumes
The internal/ split kept parseAgentFileText / resolveAgentPath off the
package entry, but apps/kimi-code's v2 print runner imports them from
@moonshot-ai/agent-core-v2 for --agent-file. Export the two symbols by
name; everything else under internal/ stays domain-private.
* feat(agent-core-v2): return cwd listing for empty fs:search query
An empty fs:search query used to fail request validation (query had a
minimum length of 1), so @-mention pickers had no starting set right
after typing "@". The workspace fs service now answers an empty query
with the workspace root's top-level entries — directories first,
hidden entries excluded, gitignore and exclude_globs honored — mapped
into the search-hit shape (score 1, empty match positions) and capped
by limit. The mirrored protocol wire schema is relaxed in sync.
* test: cover cron-fired steer context and titled session creation
- agent-core-v2: e2e asserting a cron-fired steer turn carries earlier
tool results (the CronCreate job id) into the provider request
- klient: conformance case creating a titled session through implicit
workspace materialization
|
||
|
|
ea81c9a3c5
|
feat(kap-server): expose the managed-account profile at GET /oauth/userinfo (#2363)
* feat(kap-server): expose the managed-account profile at GET /oauth/userinfo * refactor(oauth): serve the userinfo profile as the camelCase domain type end to end * style(agent-core-v2): drop the method-local comment on getManagedUserInfo * chore: drop the userinfo endpoint changeset * test(kap-server): pass the required host identity in the userinfo route test |
||
|
|
40172c7ca9
|
feat: unify the host identity across OAuth, telemetry, and kap-server (#2382)
* refactor(oauth): make X-Msh-Platform an explicit host identity field X-Msh-Platform was hardcoded to kimi_code_cli in createKimiDeviceHeaders, so non-CLI hosts could not state their own platform and the desktop had to patch the header after the fact. KimiHostIdentity now carries a required platform (every host declares its own value; the CLI constant stays the fallback only for direct createKimiDeviceHeaders callers), and userAgentProduct is renamed to productName so the transport identity uses one name everywhere. All in-repo identity constructions pass platform explicitly; the wire value for CLI and VS Code hosts is unchanged (kimi_code_cli). * feat(agent-core-v2): carry the host identity in the bootstrap snapshot Replace the flat clientVersion field with a required clientIdentity (KimiHostIdentity) so every consumer reads the same host identity object: OAuthToolkitService now passes it to the OAuth toolkit, which means the OAuth device-flow endpoints (device authorization, token polling, refresh) on the kap-server path finally send the full X-Msh-* device headers instead of none, and the telemetry cloud appender reads client_version from the same source. A built-in CLI fallback keeps bare bootstrap() calls in tests working; composition roots must pass their own identity. The session export manifest grows an optional desktopVersion field (payload plumbed through; filled by kap-server in a follow-up). * feat(agent-core): thread the host identity into the managed auth facades The v1 managed auth facade constructed its OAuth toolkit without an identity, so token refreshes from inside the core went out without any X-Msh-* device headers. createManagedAuthFacade now takes an optional KimiHostIdentity and every call site supplies one: CoreProcessService._defaultOAuthTokenResolver forwards the core process's options.identity (the same source _defaultKimiRequestHeaders uses), and the DI-held services (oauth / auth summary / model catalog) read it from a new optional identity field on IEnvironmentService. The library-level "no identity, no device headers" contract is unchanged. * feat(kap-server)!: require the host identity and derive request headers from it ServerStartOptions.hostIdentity is now a required ServerHostIdentity (KimiHostIdentity + optional prompt display fields), replacing both the old optional HostIdentityOverrides (renamed to PromptIdentityOverrides, its productName field now displayName) and the version option (renamed to serverVersion — it is the engine version reported as server_version, while the host product version travels in hostIdentity.version). The server now feeds bootstrap's clientIdentity from hostIdentity and derives the default outbound headers (User-Agent + X-Msh-*) from it via createKimiDefaultHeaders, so kap-server-hosted OAuth flows and model / WebSearch requests carry the real host identity instead of a hardcoded kimi-code-cli fallback UA. Explicit header seeds still win as an escape hatch. Session export manifests record the host product version: kimiCodeVersion now carries hostIdentity.version (the engine version no longer appears), and desktop exports (desktop: true) are additionally stamped with a desktopVersion field. The instance registry keeps its host_version wire field for compatibility (kimi-inspect reads it); only the in-memory name changed to serverVersion. * feat(cli): wire the CLI host identity into the kimi web server kimi web now passes createKimiCodeHostIdentity(version) as the server's hostIdentity, so web-UI OAuth flows and the engine's outbound requests carry the explicit CLI identity (productName + version + platform). The explicit hostRequestHeadersSeed is dropped — kap-server derives the same headers from hostIdentity — and buildKimiDefaultHeaders goes away with its only consumer. * test(klient): drop clientVersion from the bootstrap contract parity list * chore: add changesets for the host identity unification * feat(cli): tag kimi web requests with a (web) User-Agent suffix kimi web shares the CLI product token and platform, so its outbound requests were indistinguishable from direct CLI runs upstream. Its host identity now carries userAgentSuffix 'web', putting web-UI traffic at kimi-code-cli/<version> (web) while X-Msh-Platform stays kimi_code_cli. * fix(klient): keep the env() clientVersion wire field after the bootstrap identity switch The bootstrap snapshot replaced the flat clientVersion scalar with clientIdentity, which broke klient's env() fan-out (RPCError: method not found). The wire surface keeps clientVersion — now sourced from clientIdentity.version — and bootstrapService gains a clientIdentity read (registered in envContract with an object schema) for consumers that want the full identity. * feat(oauth): send the product User-Agent on OAuth requests The OAuth endpoints used to receive only the X-Msh-* device headers (undici's default UA otherwise), which left the OAuth host unable to distinguish runtime surfaces — notably kimi web, whose platform matches the CLI and whose only distinguishing mark is the (web) UA suffix. The toolkit now feeds the full identity headers (User-Agent + X-Msh-*) into every device authorization, token polling, and refresh request; the request-header type widens from DeviceHeaders to OAuthRequestHeaders. * feat(vscode): report kimi_code_vscode as the extension's platform The VS Code extension inherited the CLI's hardcoded X-Msh-Platform value; with platform now an explicit identity field it declares its own, so the managed endpoints and OAuth host can tell extension traffic apart from CLI runs. * refactor(agent-core-v2)!: require the client identity at the composition root The bootstrap fallback identity fabricated a kimi-code-cli/unknown host for any caller that forgot to pass one — the same silent-misreport pattern this series set out to remove, and it made "required" a lie. BootstrapInput.clientIdentity is now required, so a missing identity fails at compile time instead of being papered over. Test and example callers pass a shared fixture (klient examples and test engines get one each); the node-sdk v2 client asserts its host identity with the oauth helper. Also folds DeviceHeaders from an interface into a type alias so it stays assignable to the widened OAuthRequestHeaders record. * feat(oauth)!: require and validate the platform in device headers Drops the quiet CLI fallback in createKimiDeviceHeaders (the same silent-misreport pattern removed from the bootstrap identity): platform is now a required option, validated with the same required-ASCII rule as the version — empty or all-non-ASCII values throw instead of emitting a blank X-Msh-Platform, and header-unsafe characters are stripped rather than sent raw. * fix(node-sdk): seed the host request headers on the v2 client path The interactive v2 engine path (experimental flag) bootstrapped without a hostRequestHeaders seed, so managed vendor calls went out with the SDK's default User-Agent (OpenAI/JS) and no X-Msh-* at all — v1 passes the full identity headers on the same requests. The v2 client now seeds the headers from its asserted host identity, and a test pins the seed. * chore: simplify the CLI changeset wording |
||
|
|
b850c5f8f5
|
fix(node-sdk): wire applyPersistedSecondaryModel to agent-core-v2 (#2345)
* test(node-sdk): drop v1-only subagentNames from the resume parity projection Custom agent files made v1's resumed agent config carry the bound profile's delegatable subagent roster; v2's resumed agent state has no equivalent field, so the resume parity cases fail on main. Project the engine-owned field away instead of pinning it as a resume-data gap. * fix(node-sdk): wire applyPersistedSecondaryModel to agent-core-v2 On the v2 engine route the /secondary_model command persisted the recipe but failed to apply it to the current session: the SDK method fell through to the base class's not_implemented getRpc(). v1 pushes a reloaded config snapshot into the session because its spawn binding, tool descriptions, and cached startup warning all read that snapshot. agent-core-v2 resolves the secondary model live against IConfigService at spawn time and rebuilds the tool description per read, so the setConfig write already takes effect session-wide. The override keeps the rest of v1's contract: config reload, the same loud validations (session lookup, persist-first recipe check, pointed-model resolution wrapped at [secondary_model].model), and a warning-cache refresh via a new recheckSecondaryModelWarning on the session warning service. getSessionWarnings also surfaces the v2 secondary-model warning next to the AGENTS.md one, matching v1's aggregate. * fix(agent-core-v2): surface the subagent's bound model on status events The v2 model slice rides only the bind-time agent.status.updated, which precedes subagent.spawned and is dropped by clients that key child events off the spawn, so subagent cards never learned the model — and a single-step run emits no usage/context slice until it ends, so the model only appeared at completion. Re-affirm the binding right after the spawn announcement via a new IAgentProfileService.republishStatus, and fold a consistent usage/context/model snapshot into every status event at both v1 edges (kap-server's broadcaster and the in-process SDK session wiring, resolving the secondary-model derived id to a readable display name. EOF ) * Delete .changeset/subagent-card-model.md Signed-off-by: 7Sageer <sag77r@hotmail.com> * style(agent-core-v2): remove inline implementation comments --------- Signed-off-by: 7Sageer <sag77r@hotmail.com> |
||
|
|
ceaa96942b
|
feat(kap-server): add global message search with literal and live-session modes (#2321)
* feat(kap-server): add the /api/v1/search global message search endpoint Cross-session full-text search over user messages, assistant text and session titles, backed by a minidb index under <home>/search-index with a single-writer lock election and read-only WAL catch-up for other processes. Hits carry transcript anchors (turn ordinal and step id) so clients can jump straight to the matching turn or step. * feat(kimi-inspect): add a search view with chat-timeline navigation The left rail gains a Search view over the global search endpoint, with role and sort filters and cursor pagination. Clicking a hit switches to the chat view and navigates to its session, agent, turn and step — the channel pages the turn into the loaded window, scrolls it into view and flashes the target briefly. * chore(kimi-code): start the dev server without built web assets The repo's dev server scripts (dev:server, dev:kap-server, dev:kap-server:multi, dev:server:restart) now set KIMI_CODE_DEV_SERVER=1. When it is set and dist-web/index.html is missing, kimi web starts the API server without the bundled web UI instead of failing at startup, so backend dev no longer requires a kimi-web build. * feat(kap-server): add literal substring search and a live session route - minidb: text indexes accept an injectable tokenizer/queryTokenizer, and a hashed 2/3-gram tokenizer (NFKC + lowercase, code-point windows) backs substring search; tokenizer names persist in db.textindexes.json with backward-compatible defaults - /api/v1/search gains mode: 'literal' — n-gram candidates confirmed against the original text (zero false positives), with an 'candidate_cap' incomplete flag when the candidate set truncates - container.session_id queries against a session live in this process scan the in-memory transcript store instead of the index (both modes); the response's source: live|index field names the serving route and rides in the page-token fingerprint - kimi-inspect: exact-match toggle and source badge in the search view, plus an in-chat session search bar with jump-to-hit navigation * test(kimi-inspect): avoid stringifying BodyInit in search api tests |
||
|
|
d03a4886fd
|
feat(server): remove the 50 MiB upload size cap and stream uploads to disk (#2312)
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
- add writeStream/putStream to the storage and blob store interfaces so large values are written incrementally (tmp + fsync + rename) instead of being buffered in memory - FileService.save now streams the request body straight to the blob store and only counts bytes for FileMeta.size - drop the multipart fileSize limit and the file.too_large error from the /files upload path (41301 stays in the wire protocol for session export) |
||
|
|
b0f43aea28
|
feat(oauth): return structured managed usage rows (#2300)
* feat(oauth): return structured managed usage rows
Stop formatting plan-usage labels and reset hints into English strings
at the oauth layer. The parser still absorbs backend field drift, but
now emits a stable structured row (name / window{duration,unit} / used
/ limit / resetAt) that kap-server passes through and clients localize
themselves:
- window: normalized from duration/timeUnit (minute/hour/day/week),
whole-hour minute windows fold to hours, unnamed summaries are the
weekly limit
- resetAt: absolute ISO timestamp; relative reset_in/ttl seconds are
converted at parse time
- TUI /usage panel formats labels and reset hints locally
* refactor(oauth): parse managed usage strictly to the current payload shape
Drop the defensive drift tolerance (alternate reset-time spellings,
reset_in/ttl seconds, remaining-derived used, title/scope names,
top-level duration/timeUnit, fuzzy time-unit matching) and parse only
what the platform actually sends: numeric strings, resetTime, nested
detail/window records, TIME_UNIT_* enums.
* fix(node-sdk): update managed usage smoke example and facade tests for structured rows
|
||
|
|
7e30add445
|
feat(kap-server): add global fs:mkdir endpoint (#2281)
* feat(kap-server): add global fs:mkdir endpoint Add POST /api/v1/fs:mkdir to create a directory on the host filesystem by absolute path, backing the folder picker's "new folder" action. Implemented directly on node:fs/promises.mkdir in the transport layer for now, non-recursive by design, with wire errors mapped to the existing fs.* codes (40001/40409/40411/40919). * test(kap-server): update api surface snapshot for fs:mkdir * test(kap-server): stop export tests from holding server.close() open The export download tests reused pooled undici keep-alive connections, so afterEach's server.close() could wait out fastify's 72s default keepAliveTimeout and die on the 10s hook timeout (flaky on CI). Send connection: close on the streamed export requests, matching the fs:content tests. |
||
|
|
77618e38c3
|
feat(kap-server): wire cloud telemetry for engine events (#2230)
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat(kap-server): wire cloud telemetry for engine events The v2 engine registers a full telemetry event catalog, but kap-server never attached an appender, so events from web-hosted sessions were dropped to the null appender. Add an opt-in `telemetry` start option that attaches a CloudAppender (app_name kimi-code-cli, ui_mode web, matching the v1 `kimi web` host conventions), still gated by the config `telemetry` toggle, with periodic flush and a bounded flush on close. The option defaults off so tests never post to the real endpoint; the CLI's `kimi web` host enables it. * fix(kap-server): honor telemetry disable environment * fix(agent-core-v2): isolate session telemetry context * fix(kap-server): seed telemetry client version * fix(cli): share telemetry shutdown deadline * fix(telemetry): make shutdown ownership durable * fix(kap-server): keep telemetry shutdown best-effort |
||
|
|
a77ee03829
|
feat(agent-core-v2): add hostIdentity domain for host-overridable system prompt identity (#2144)
* feat(agent-core-v2): add hostIdentity domain for host-overridable system prompt identity
- add app-scope hostIdentity domain (L3) with productName / replyStyleGuide
overrides and hostIdentitySeed for composition roots
- render ${product_name} and ${reply_style_guide} in the base system prompt,
falling back to CLI defaults when the host provides no override
- seed the variables from AgentProfileService via IHostIdentity
- expose hostIdentity on kap-server's ServerStartOptions
* chore: add changeset for host identity system prompt
* fix(agent-core-v2): adapt hostIdentity registration to ScopeActivation
The DI refactor on main removed _base/di/extensions (InstantiationType);
register with ScopeActivation.OnScopeCreated instead, and regenerate the
state manifest for the new SystemPromptContext fields.
---------
Co-authored-by: liruifengv <liruifeng1024@gmail.com>
|
||
|
|
3b017821cf
|
feat(kap-server): accept secondary_model in the config API (#2228)
* feat(kap-server): accept secondary_model in the config API POST /api/v1/config now accepts secondary_model, persisted to the [secondary_model] config section via the generic per-domain dispatch. GET /config also hides the synthesized __secondary__ derived entry from the models view, matching the GET /models listing. * Update config-api-secondary-model.md Signed-off-by: 7Sageer <sag77r@hotmail.com> --------- Signed-off-by: 7Sageer <sag77r@hotmail.com> |
||
|
|
48bf3d4c28
|
feat(kap-server): bundle the desktop app log into session exports on request (#2223)
* feat(kap-server): bundle the desktop app log into session exports on request * refactor(agent-core-v2): align the desktop log export with repo conventions |
||
|
|
d40d0d305d
|
refactor(agent-core-v2): make undo domain-owned (#2055)
* refactor(agent-core-v2): rebuild undo as wire-level journal rewind Replace the compensating context.undo op with a wire-layer rewind primitive: a log.cut control record with a persisted target, applied uniformly by the wire during fold. Turn boundaries become first-class (TurnIndexModel indexing turn.prompt record positions), models declare a temporal classification (rewindable), and a single IAgentRewindService owns the undo pipeline (quiesce -> precheck -> cut -> reconcile) with all entry points converged. - wire: log.cut record, rewindable model flag, re-fold rebuild; OpApplyContext.recordIndex for position-aware reducers - rewind service: aborts the active turn, cancels in-flight compaction, preserves the pending queue, rebases measured tokens, reconciles lastPrompt, tracks conversation_undo - todo list, plan mode, task-notification delivery and the turn index now rewind together with the undone turns - transcript reducer applies cut ranges so snapshot/messages surfaces stay consistent with the model context - REST/RPC/debug undo entry points converge on the rewind service; TUI parses the v2 undo-unavailable error shape - legacy context.undo records keep replaying for old journals * refactor(agent-core-v2): keep undo domain-owned * refactor: enhance /undo functionality for consistency and safety, including todo list rollback and improved event handling * chore: clean up undo changeset artifacts * refactor: rebuild rewind consistency * fix: make conversation undo durable and consistent * fix(agent-core-v2): stabilize undo restoration * fix: keep TUI undo on legacy error contract * refactor(agent-core-v2): drop unused full compaction cancel API Undo now rejects with session.busy while compaction runs instead of cancelling it, so the awaitable cancel() added for the earlier rewind semantics has no callers left. Remove it from the interface and implementation; the RPC cancel path keeps using the task abort controller directly. * fix(agent-core-v2): remove injected context on undo * chore(agent-core-v2): regenerate wire manifest * docs(agent-core-dev): rename rewind to undo in layer table * fix(agent-core-v2): undo prompt-owned image reminders * refactor: remove transcript undo reconciliation * fix(kap-server): map undo busy errors * refactor(agent-core-v2): rename undo participant registry and attribute checkpoint depth - Rename IAgentConversationUndoReconciliationRegistry to IAgentConversationUndoParticipantRegistry (conversationUndoParticipants). - Return the limiting model from checkpointDepth and include it in the SESSION_UNDO_UNAVAILABLE details; report checkpoint_lost instead of compaction_boundary when no compaction explains the missing depth. - Add a registry invariant test: every model reacting to context.* ops must be registered via defineCheckpointedModel or explicitly exempt. * Delete .changeset/fix-undo-injections.md Signed-off-by: 7Sageer <sag77r@hotmail.com> --------- Signed-off-by: 7Sageer <sag77r@hotmail.com> |
||
|
|
cc9b25e132
|
fix(kap-server): flush public and control WS frames immediately (#2221)
Only subscription events now enter the timed coalescing buffer. server_hello, acks, resync_required, and global session events join the outbound FIFO and flush it right away, so they no longer wait behind the flush window and cannot overtake earlier buffered subscription frames. The broadcaster marks global fan-out with the new BroadcastDelivery 'immediate' lane. |
||
|
|
bf8e967d5c
|
refactor(agent-core-v2): register tools as scoped services (#2196)
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* refactor(agent-core-v2): register agent tools as DI services with profile-aware activation - replace module-level registerTool + Eager AgentBuiltinToolsRegistrar with registerAgentTool double registration (Agent-scope DI service + contribution table) - add toolActivation domain: AgentToolActivationService filters contributions by the bound Profile's tool policy using declared names, resolves instances lazily via accessor.get, and re-activates on agent.status.updated - rename BuiltinTool to AgentTool service interface; tools become Agent-scope services with decorator-injected dependencies (e.g. AgentTool -> SubagentTool/ISubagentTool) - AgentLifecycleService.create runs one activation pass after restore and profile binding so tools reflect the Profile before the first turn - update tool registrations, scripts, and tests; add toolActivationService tests * refactor(agent-core-v2): centralize builtin tools under agent/tools - move builtin tools from scattered domain folders (plan/tools, goal/tools, os/backends/node-local/tools, task/tools, etc.) into a unified agent/tools/ directory - split each tool into a kebab-case definition file (bash.ts) and a registration file (bashTool.ts) pairing with its prompt markdown - update imports across src, tests, kap-server, and TUI comments * fix(agent-core-v2): keep agent tools out of scope-creation instantiation Main's instantiateAll constructs every registered service at scope creation, but agent tool constructors may legitimately throw when their host capability is absent (e.g. WebSearchTool without a configured provider), and profile-aware activation must stay the only resolution path so the runtime registry holds real instances, never proxies. - add SyncDescriptor.instantiateWithScope (default true) and let registerScopedService opt registrations out of the instantiateAll sweep - registerAgentTool passes the opt-out, restoring lazy activation-driven construction on top of the eager-scope semantics - regenerate docs/state-manifest.d.ts * refactor(agent-core-v2): replace delayed DI with scope activation - add explicit OnScopeCreated and OnDemand activation modes - remove delayed proxy and idle initialization support - migrate service registrations, tests, and DI guidance |
||
|
|
a2401cc1ed
|
feat(kap-server): add provider write endpoints and models.dev/registry import (#2110)
* fix(agent-core-v2): honor explicit undefined field deletes in modelsToToml
The models TOML transform merged each entry as { ...oldRaw, ...converted },
so a field the new record carried with an explicit undefined — deleted from
'converted' by setDefined — was put right back from the old on-disk raw.
Field-level clears issued through config.replace (e.g. the provider write
routes dropping base_url, display_name, capabilities) only took effect in
memory and resurrected on the next boot. Merge via setDefined onto the old
raw directly, matching providerEntryToToml.
* feat(kap-server): add provider write endpoints and models.dev/registry import
Add the provider management write surface plus server-proxied catalogs so
API clients (the desktop app first) can manage providers without editing
config.toml by hand:
- POST/PUT/DELETE /providers: manual create, replace-style edit (new_id
rename with pointer migration, tri-state api_key, form-unknown fields
merged through), and delete with real alias removal.
- GET /providers/{id} additionally reveals the stored api_key for edit
prefill on the loopback+bearer transport; the list stays redacted.
- GET /catalog/providers[{id}]: pruned models.dev directory with import
eligibility resolved server-side (10-min cache, stale fallback, built-in
snapshot, shared in-flight fetch).
- POST /providers:import_catalog and :import_registry (collection actions
next to :refresh): catalog/registry imports with TUI-aligned
remove-then-apply refresh semantics, OAuth-managed guards, and the
registry source blob that scheduled refreshes rediscover.
Write-path consistency: field-level clears assign explicit undefined (the
TOML transforms only drop keys that way), import refreshes swap aliases in
two passes so stale on-disk fields cannot ride along, re-imports keep the
stored credential when api_key is omitted, and multi-step writes serialize
through a process-local chain. Global default_provider/default_model are
never modified by provider writes.
* fix(kap-server): cache the built-in catalog fallback and guard alias-key collisions on replace
- Cache the built-in snapshot on upstream failure, so an offline install no
longer pays the full upstream timeout on every catalog call.
- Reject a PUT whose rebuilt alias keys collide with an alias owned by
another provider (foreign-prefix keys are global), checked before any
write so a collision never lands half the edit.
* fix(kap-server): make toCatalogProviderItem's switch explicit for older TS control-flow
* chore(agent-core-v2): drop inline rationale per package comment conventions
* feat(kap-server): seed default_model on provider create/import when none is configured
A fresh setup has no global default model, so the first provider added
through the write endpoints left GET /auth permanently unready and new
sessions without a selected model. Seed default_model from the created
provider's default (or first) model on POST /providers, :import_catalog
and :import_registry when — and only when — no default is configured;
an existing pointer is never moved, not even a dangling one.
* refactor(agent-core-v2): own the models.dev provider import in the engine
kap-server's provider write endpoints imported @moonshot-ai/kosong and
@moonshot-ai/kimi-code-oauth directly for the models.dev browse/import
and custom-registry import. Move that capability behind a new App-scope
IModelsDevImportService so the server only talks to agent-core-v2:
- app/kosongConfig/modelsDev.ts mirrors the third-party api.json schema
and hosts the pure translation (wire inference, endpoint resolution,
model pruning, thinking/gateway overrides) ported from kosong's
catalog.ts; kosong's own type surface stays limited to built-in
vocabulary
- modelsDevUpstream.ts owns the directory fetch, 10-minute cache, and
built-in snapshot fallback; modelsDevImportService.ts owns the import
orchestration (tri-state api_key, two-pass alias swap, OAuth-managed
rejection, serialized writes) with coded modelsDev.* errors
- kap-server routes shrink to wire mapping (zod schemas + numeric error
code mapping) and drop the kosong/oauth dependencies
The /api/v1 surface is byte-identical: the apiSurface snapshot and all
existing modelCatalog tests pass unchanged.
* chore: drop the branch's changesets
* fix(agent-core-v2): route the models.dev always-thinking exemption through kosong
The zero-tolerance vendor-name gate forbids a 'kimi' compare outside the
kosong layer, and the engine-side models.dev port carried one inline.
Kosong (the vendor layer, owner of thinking semantics) now answers the
verdict — wireHasProtocolThinkingDisable — and kosongConfig/modelsDev.ts
asks it instead of comparing wire ids.
---------
Co-authored-by: haozhe.yang <yanghaozhe@moonshot.ai>
|
||
|
|
7b62ed5b2c
|
feat: support a configurable secondary model for subagents (#2064)
* feat: support a configurable secondary model for subagents * refactor: move the subagent model config to a consumer-neutral [secondary_model] The secondary model becomes a model-domain concept next to default_model so future consumers beyond subagents can share it: [secondary_model] model / effort in config.toml, KIMI_SECONDARY_MODEL / KIMI_SECONDARY_EFFORT env overrides, and the Agent / AgentSwarm per-spawn choice renamed from "subagent" to "secondary". * docs: replace "v2 engine only" notes with the concrete effective surfaces State that [secondary_model], its env overrides, the Agent/AgentSwarm model parameter, and SYSTEM.md take effect only under kimi web and experimental kimi -p (the TUI ignores them), and that --agent / --agent-file are available only under experimental kimi -p. Also drop the SYSTEM.md claim of parity with --agent/--agent-file, which was inaccurate: SYSTEM.md is an agent-core-v2 app-domain feature and also applies under kimi web, while the flags are gated at the CLI. * fix: review follow-ups for the subagent secondary model - Drop the "cheaper" claim from the Agent/AgentSwarm model parameter descriptions and the advertised model list — the secondary model is not necessarily the cheaper one. - Downgrade the changeset to patch, note the kimi web / experimental kimi -p effective surface, and tighten the wording. - Remove the onWillRestore stub fields from two lifecycle stubs; they belong to upcoming lifecycle work, not to this change. * fix(agent-core-v2): prevent ghost agents from invalid model bindings * fix: narrow the secondary-model error hint to missing-alias failures The model catalog's not-configured throw now carries details.model, and wrapSubagentModelError only decorates errors whose details.model matches the bound model. Malformed [models.*] entries and unrelated config.invalid failures during agent creation pass through untouched instead of being misattributed to an invalid secondary-model alias. * fix: mark subagent resume semantics as breaking * chore(agent-core-v2): follow header-only comment convention * fix(agent-core-v2): await agent restore preparation * feat(agent-core-v2): support agent model preferences * Update subagent-secondary-model.md Signed-off-by: 7Sageer <12210216@mail.sustech.edu.cn> * fix: validate secondary models before agent creation * Delete .changeset/secondary-model-startup-warning.md Signed-off-by: 7Sageer <12210216@mail.sustech.edu.cn> * Add secondary_model config section for subagents Individual agents can override this via the new `model_preference` field in their agent file. Signed-off-by: 7Sageer <sag77r@hotmail.com> * feat(agent-core-v2): support override patches in [secondary_model] The recipe is now `model` plus the flattened ModelOverride field set. With any patch field set, a config overlay synthesizes a derived registry entry (base copy, patch merged into overrides, aliases dropped) so subagent spawning rides the standard effectiveModelConfig merge; with none, subagents bind the pointed entry directly. `default_effort` replaces `effort` (KIMI_SECONDARY_EFFORT rebinds) and doubles as the explicit subagent thinking; unset, thinking resolves naturally instead of inheriting the caller. The overlay strips the derived entry (and any defaultModel pointer to it) from writes, and the kap-server GET /models route hides it from pickers. * fix(agent-core-v2): fire section events for overlay-rewritten domains rebuildEffective only committed the caller-named domains, so a ConfigEffectiveOverlay or section env binding that rewrote a sibling domain (setting [secondary_model] synthesizes a derived models entry; removing the recipe retracts it) left consumers of the models section stale. Widen the commit candidates with every domain the recompute actually changed; commit() deepEqual-guards each candidate, so the widening costs nothing. * feat(agent-core-v2): gate secondary model behind experimental flag * Update subagent-secondary-model.md Signed-off-by: 7Sageer <sag77r@hotmail.com> --------- Signed-off-by: 7Sageer <12210216@mail.sustech.edu.cn> Signed-off-by: 7Sageer <sag77r@hotmail.com> |
||
|
|
d751b6796c
|
feat(kap-server): global session work status, transcript subscribe_v2, and plan endpoint (#2094)
* feat(session): add core work aggregate and consume it at WS/REST edges
- agent-core-v2: add the sessionActivity domain (ISessionActivityView,
Session scope) folding each agent's activity view and the interaction
kernel into busy / main_turn_active / pending_interaction /
last_turn_reason, firing cause-tagged change events only on real
tuple changes
- kap-server: resolveSessionFacts reads the core view; the WS
broadcaster drops its per-agent fold/dedup and delegates to the
view, deferring turn_ended emissions until after the turn.ended
frame so busy:false never precedes it; global events now fan out
to every established connection via addGlobalTarget without any
subscription
- kimi-inspect: add src/activity (GlobalEventsWs + SessionActivityHub
+ useSessionActivities) consuming the global push; the Sidebar
renders running / approval / question / failed badges per session
* feat(kap-server): decouple transcript stream from agent_filter
- transcript frames (transcript.reset/ops) are governed by the per-agent
transcript grades alone and bypass the legacy agent allowlist; the filter
still gates session_event delivery
- client_hello is handshake-only in code: hello and subscribe share one
attach path, and hello's inline subscription fields are deprecated
(subscriptions made optional) in both protocol packages
- flush deferred work_changed(busy:false) from a microtask so it always
lands after the matching turn.ended frame
* fix(kimi-inspect): restore session activity hub in the browser
- bind the default fetch in SessionActivityHub: a member call hits the
browser's Illegal invocation (receiver is not the global object), and
the swallowed error left the REST seed never firing, so the Sidebar
badges never populated on refresh
- create the hub in useEffect + useState instead of useMemo: under
StrictMode the first mount's cleanup closes the memo-created hub for
the rest of the page's life
* chore: add changeset for the global session work status push
* feat(kap-server): add transcript plan endpoint for ExitPlanMode calls
- add GET /sessions/{id}/transcript/plan projecting one ExitPlanMode
call's plan info (content, path, options, review outcome) from the
first available fact: the linked approval interaction's request
display, the live tool frame's display, or the tool result output
- add TOOL_CALL_NOT_FOUND (40416) error code
- add transcriptPlanResponseSchema to the transcript contract
- cover live, auto-mode, cold-rebuild, and error paths in tests
- update the API surface snapshot and AGENTS.md
* feat: move transcript subscriptions to subscribe_v2 and extend the plan endpoint
- add subscribe_v2 / unsubscribe_v2 WS control frames as the only
carrier of per-agent transcript grades and the transcript_since
cursor; client_hello / subscribe no longer accept transcript fields
- serialize control-frame handling per connection so interleaved
attaches cannot overwrite fresher subscription state
- make tool_call_id optional on GET /sessions/{id}/transcript/plan:
omitted lists every ExitPlanMode call with recoverable plan content
as a plans array
- add a Plan lookup card to the kimi-inspect agent inspector via
fetchTranscriptPlan
- add TOOL_CALL_NOT_FOUND (40416) to the shared protocol error codes
- update AGENTS.md and add changesets
|
||
|
|
188c0fcbf7
|
refactor(agent-core-v2): decouple kosong from config persistence (#2068)
Some checks are pending
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* refactor(agent-core-v2): decouple kosong from config persistence
- keep the kosong provider/model registries in memory and sync them with
config.toml through a new app/kosongConfig two-way bridge: hydrate on
startup, push config section changes into the registries, and persist
runtime mutations (discovery refresh, OAuth provisioning, default-model
pointer changes) back to disk
- declare the providers/models/thinking section constants, zod schemas,
env bindings, and TOML transforms in a single app/kosongConfig
configSection.ts; kosong keeps hand-written types only
- pin every schema to its kosong type at compile time via
AssertExact<Equal<...>> (_base/utils/typeEquality)
- register a transitional auth>kosongConfig exception in the domain-layer
checker for the OAuth provisioning flows
* chore(agent-core-v2): drop unused IConfigService import from authLegacy
* fix(agent-core-v2): reconcile registry with env-pinned default pointers
A registry-originated default-model/provider write lands only in the
config user layer when an effective overlay pins the section
(KIMI_MODEL_NAME pins defaultModel to the reserved env model): the
effective value does not move and no change event fires, so the registry
diverged from the effective config view — catalog/auth reads reported the
user pick while profile resolution kept the pinned model. After
persisting, the bridge now re-asserts the effective value into the
registry, restoring the pre-refactor behavior where every default-pointer
write was arbitrated by the effective view.
* fix(agent-core-v2): await persistence before resolving kosong registry mutations
ProviderService/ModelService mutations resolved as soon as the in-memory
registry updated, while the kosongConfig bridge persisted the change on an
unawaited private chain — callers (klient kosong.*, set_default route,
OAuth provision, discovery refresh) could observe success before the write
reached config.toml, and a restart right after could lose it.
- fire registry change events through AsyncEmitter and await delivery in
set/delete/replaceAll/setDefaultX, so a mutation resolves only after
listeners' waitUntil work completes; loadAll keeps synchronous timing
- the bridge hooks its persists into waitUntil, hoists the equality guards
into the listeners so config-originated echoes stay fully synchronous,
and serializes persists on the chain as before
- retry a failed persist with bounded backoff (3 attempts); failures are
logged, never rejected to callers, and the in-memory change stands
- default-pointer change events now carry an { id } payload (fireAsync
requires object events)
- add the klient kosong-config stress example covering read-after-write,
concurrent bursts, and restart durability
|
||
|
|
64f053cf46
|
feat: agent-core-v2 permission/workspace refactors and transcript durability (#2021)
* 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
|
||
|
|
4c763f6763
|
feat: send prompt-attached videos directly with the prompt (#1999)
Some checks are pending
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Release / Native release artifact (push) Blocked by required conditions
Release / Release (push) Waiting to run
CI / test-windows (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Deploy docs (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat: send prompt-attached videos directly with the prompt
Videos attached to a prompt (pasted in the TUI, uploaded in the web UI)
previously reached the model only after it opened the file with
ReadMediaFile — an extra tool round trip that could leave the video
unseen when the model never made that call. They are now uploaded
through the model provider's file channel and embedded directly in the
user message as the provider-issued reference; ReadMediaFile stays as
the fallback and as the model's own way to open video files.
- agent-core: new uploadVideo agent RPC and Session.uploadVideo in the
SDK; the TUI uploads pasted videos at submit time, falling back to
the file-tag form on failure
- kap-server: inline file-source video prompt parts at the REST edge
and map provider file ids back to local uploads behind
GET /files/llm/{llm_id}
- kimi-web: play provider-referenced prompt videos after reload/resume
* fix: fall back to inline video when the upload channel fails
ReadMediaFile only used the provider's video upload channel when one
was bound, and surfaced a hard tool error when the upload itself
failed — on providers without a files endpoint (or a transient upload
failure) the model was told the video could not be read at all. Now a
missing or failing upload falls back to delivering the video inline
(base64), the same shape providers without an upload channel already
get. Both engines (agent-core and agent-core-v2) are fixed.
* test: cover prompt video edge cases and failure paths
Stress coverage for the prompt video pipeline:
- agent-core uploadVideo RPC: extension/magic classification
(.txt with video bytes accepted, extension trusted when magic is
absent), exact 100MB boundary, directory and nonexistent paths
- TUI: mixed per-video outcomes (one inlined, one tag fallback),
submission order behind an in-flight upload, queued video messages
carrying final uploaded parts
- kap-server: per-video provider-id mappings, Range requests through
the llm redirect, mapping persistence across a server restart
* fix: surface auth rejections from the video upload channel
The base64 fallback for a failing video upload must not mask auth
rejections: a 401/403 (surfaced as provider.auth_error) drives the
credential force-refresh and a clear auth error, while an inline
payload would just be rejected again by the next request. Only a
missing or broken upload channel (no files endpoint, network/server
errors) falls back to inline delivery.
* fix: keep the by-design no-hook video error from degrading to inline
Main's contract for a provider with no video upload hook is an honest
"does not support video upload" tool error — an inline payload would
be dropped on that protocol's wire anyway. The base64 fallback now only
applies to an upload channel that exists but failed at runtime. The
no-hook throw gets a stable type (VideoUploadUnsupportedError) so the
two cases are told apart without matching message text.
* fix: constrain the llm video route param to a safe alphabet
The provider file id is used as a blob-store key, and the node-fs
backend joins scope and key into a storage path. An id containing an
encoded path separator (%2F) could address a different storage path
than the intended llm-video mapping; the route now only accepts the
provider-id alphabet.
* style(agent-core-v2): move added explanations into top-of-file comment blocks
The package's comment convention keeps comments solely in the top-of-file
block; relocate the new notes (url-source id pairing, video delivery
fallback, VideoUploadUnsupportedError) from functions and schema fields
into their module headers.
* fix: reject prompt video uploads when the model lacks video input
The uploadVideo RPC only checked the provider's upload channel, so an
SDK caller on a text-only or unknown-capability model could obtain a
valid-looking video_url part the current model is not supposed to
accept. The TUI capability guard does not cover this public path, so
the agent now gates on video_in itself.
* fix: sniff uploaded video bytes before inlining them
The inline branch trusted the upload-time content type; now the bytes
are sniffed first, and anything magic-confirmed as a non-video kind
(e.g. an image mislabeled as video/mp4) falls back to the file-tag
form instead of being uploaded. Like the image gate, bytes are
authoritative where the container format allows it — an MPEG-PS
lookalike still rides the extension, matching ReadMediaFile.
* test: keep the generation stub pending until the abort lands
An immediately-answered 404 ends the turn (non-retryable) before the
test's abort call, racing the cleanup into a 409; the stub now hangs
generation until the abort cancels it.
* fix: validate prompt file references before mutating session controls
A stale file_id failed inside media resolution — after the model,
thinking and permission overrides had already been applied — so a
rejected prompt still changed the session's controls. File references
are now checked up front, keeping failed submits side-effect free.
* fix: serialize prompt submissions per session
A slow provider video upload let a later text-only request reach the
queue ahead of an earlier video one, silently reordering the
conversation for REST clients and multiple tabs. Submissions to the
same session now chain, matching the ordering the TUI already
guarantees locally.
* fix: play reloaded provider videos through the authenticated fetch path
A video recovered from an ms:// reference carried only the bare redirect
URL, which 401s under daemon auth when loaded natively. The attachment
now keeps the provider file id (llmFileId) end to end, and AuthMedia
fetches the bytes with the Bearer credential through the daemon's llm
redirect — the same blob-URL path uploaded files already use.
* fix: fence pending video submits against session and model switches
A slow paste-upload left the TUI idle, so /new, the session picker or
/model could fire mid-upload; the continuation then dispatched the old
session's provider reference into the newly selected session or a
model that cannot resolve it. The dispatch now re-checks that the
session and model are unchanged and asks for a resend instead.
* fix: preserve the caller's video path verbatim in uploadVideo
Trimming the path changed the filesystem target before validation and
upload, so a name that legitimately starts or ends with whitespace
resolved to the wrong file; the trim is now only the emptiness check.
* fix: forward provider-issued ids on image URL prompt parts too
The shared url-source schema accepts id for image and video parts, but
only the video path forwarded it, dropping provider-keyed image ids
between prompt acceptance and the model request.
* fix(web): reconcile inlined video echoes into the optimistic user message
The loose user-message matcher counted media parts and <video path>
tags but not the [video:ms://…] text shape, so a racing server echo of
an inlined upload slipped through as a duplicate user bubble.
* fix: queue bash submits behind a pending video upload
A bash-mode submit could start while a pasted video was still
uploading, recording shell context before the earlier prompt was
dispatched and reordering user actions; it now chains behind the
upload like normal submits.
* test: cast the driver through unknown for the session-switch fence test
* fix: validate prompt file references before resolving the prompt agent
A stale file_id posted to a fresh or cold session materialized the main
agent (registering it in session metadata and igniting agent-scoped
services) before the request was rejected. The file check now runs
first, so failed submits create nothing and mutate nothing.
* fix: key the playback mapping by the id embedded in the reference URL
Projections and clients read the provider id from the ms:// URL, not
the id field, so an upload that returns an id-less or mismatched
reference would inline fine but 404 on playback. The mapping now
derives its key from the URL, falling back to the explicit id.
* fix: sanitize provider video ids on the write side of the playback map
The read route got the safe-alphabet guard, but recordLlmVideoRef still
used the provider-returned id verbatim as a blob-store key; a crafted
provider response could write the mapping outside the llm-video
namespace. Out-of-alphabet ids are now dropped on both put and get.
* fix(web): keep recovered provider videos resendable through the edit path
The composer reload path only honored fileId and fell back to a
bare fetch(url) without the Bearer token, so editing a reloaded
provider-video turn dropped the chip after a 401. llmFileId is now
threaded through and the bytes are re-uploaded via the authenticated
llm redirect.
* fix: fall back to inline video for no-hook providers whose wire carries it
The by-design no-hook error is only the honest answer when the wire
would drop an inline payload anyway (the OpenAI family). Protocols
that convert video_url (kimi, anthropic, google-genai, vertex) now
take the base64 fallback instead of failing every video read; the
registrar computes the flag from the model's protocol.
* test: satisfy the full IBlobStore shape in the playback-map stub
The tsgo typecheck job rejects a structural stub missing _serviceBrand
and list even though plain tsc accepted it.
* fix: queue prompt-producing slash commands behind pending uploads
Skill activations and plugin commands started their turn immediately
while a pasted video was still uploading, so the earlier video prompt
queued behind them and user actions ran out of order. Both paths now
chain behind inputSubmitChain (and re-check session/model at dispatch)
like normal and bash submits.
* fix: validate media kinds in the prompt file-reference preflight
The preflight only proved a referenced file exists; a real upload used
with the wrong kind (e.g. a PDF submitted as video) still passed it and
mutated session controls before assertMediaFile rejected the request.
The kind assertion now runs up front with the existence check.
* fix(web): play sent and recovered videos in the file preview
The media preview returned early for every kind except image, so a
user-turn video chip's play action was a no-op even with llmFileId
threaded through. The preview now handles video: bytes come from the
authenticated file/llm fetch into a blob URL and render in a native
player.
* fix(web): preview recovered videos with the authenticated blob URL
The llm re-upload branch fetched bytes with auth but kept the protected
redirect URL as the chip preview, which 401s as a native video src; the
fetched blob now becomes the preview URL, mirroring the fileId branch.
* style(agent-core-v2): move the inline-fallback note into the module header
Same package comment convention as before: rationale lives in the
top-of-file block, not beside the registration call.
* fix: fence delayed bash submits to the originating session
The chained bash callback ran runShellCommandFromInput against whatever
session was active at dispatch time, so a command submitted in session
A could execute in session B's workspace and be recorded there after a
mid-upload switch. The originating session is now captured at submit
and re-verified at dispatch, like the prompt and skill paths.
* fix: emit prompt video telemetry from the agent scope
video_upload is an agent-level event requiring ambient agent identity,
but the uploader was built with the Core-scoped session view, leaving
prompt-upload events unattributable. The route now resolves telemetry
from the target agent for the uploader while image compression keeps
the session-scoped view.
* fix: preserve provider image ids through legacy projections and web mappers
The url-source id accepted by the prompt schema was dropped again by
the legacy message projection and the web wire mapper, so provider-
keyed image references lost their id across messages, snapshots, and
undo responses. It now flows through both directions.
* fix(web): revoke recovered video blob URLs before dropping attachments
A failed llm re-upload removed the attachment without revoking the
freshly created preview blob URL, pinning the whole video in the
browser blob store until page unload on every failed edit attempt.
* fix: serialize foreground slash commands behind pending uploads
/compact and /init started their turn while a pasted video was still
uploading, so the earlier message landed after them — and compaction
summarized the context without it. Prompt-producing builtin commands
now share the same queueBehindPendingUploads chain (with the
session/model dispatch fence) as skill, plugin, and bash submits.
* fix: defer session controls until media preparation succeeds
Media resolution now runs before any profile/model/thinking/permission/
denylist mutation, with the uploader resolved transiently from the
requested (or currently bound) model — a failed submission leaves the
session's controls untouched. A concurrent model switch during
preparation is rejected with session.busy instead of enqueueing a
reference uploaded for the previous model.
* chore: bump the new SDK video upload API as a minor release
Session.uploadVideo is new public API surface, not a patch-level tweak.
* fix: re-check busy state before draining upload-queued commands
A slash command deferred behind a video upload ran the moment the video
prompt dispatched, landing on an already-running turn: beginSessionRequest
wiped the active turn's live pane, and /init or /compact started on top
of it. The deferred callbacks for skills, plugin commands, /compact and
/init now re-run the resolver's busy check at dispatch and show the same
blocked message the user would get when typing while streaming.
* fix: resolve profile-bound models before choosing the uploader
A first prompt carrying "profile" without "model" resolved the upload
model from the still-unbound alias, so no uploader was installed and
every attached video fell back to a tool-read tag even when the
configured default model supports provider upload. The transient
resolution now mirrors AgentProfileService.bind: an explicit body model
wins, a profile bind falls back to the configured default model, and
only otherwise does the currently bound alias apply.
* refactor: resolve prompt videos at request time inside the engine
Move prompt-video delivery out of the submission edge: the TUI submits
synchronously with a local file:// part the v1 turn resolves before the
message enters history, and kap-server carries an internal kimi-file://
reference the v2 requester resolves against the effective model with an
app-scoped upload cache. History keeps the durable local file id, the
/messages projection emits structured video parts, and the web plays
videos back through the authenticated /files channel - deleting the
submit fences, per-session serialization, provider-id reverse mapping,
redirect endpoint, and the unreleased SDK upload API.
* fix: propagate abort through video upload delivery instead of degrading
A turn cancelled mid-upload used to be treated as an ordinary upload
failure: v1 fell back to an inline base64 part and appended the degraded
message to history, and the v2 resolver memoized the tag fallback for the
rest of the agent's lifetime. Both catch sites now check the delivery
signal itself - abort rejections vary in shape by provider - and re-throw
so cancellation ends the turn (v1, classified as cancelled via the abort
reason) or the request (v2, not memoized, so the next turn uploads).
* fix: keep the tag form for no-upload providers whose wire drops inline video
An OpenAI-family model configured with video_in but no provider upload
channel used to receive prompt videos as an inline base64 part - which
chat completions rejects and the Responses adapter degrades to an
omitted-video placeholder, persisting ~4/3x the file size in history for
bytes the model never sees. The prompt path now mirrors the v2 resolver's
protocol gate and degrades to the <video path> tag instead; ReadMediaFile's
own delivery is unchanged. Also merges the prompt-video changesets into a
single user-facing entry.
* fix: escape the NUL separator in the video upload cache key
The cache-key template literal contained a literal NUL byte instead of
the \0 escape, which made Git classify the whole source file as binary
- no inline diffs, unreliable text tooling. The escape produces the
byte-identical runtime string, so hashed cache keys are unchanged.
* fix: check the abort signal before the inline video fallback
The no-uploader inline path (and the post-upload-failure fall-through)
never consulted the delivery signal, so cancelling a turn while the video
bytes were being read still base64-encoded the file and appended the
degraded message to history. The inline branch now re-throws the abort
reason first, matching the upload catch.
* fix: retry transient prompt video upload failures on later steps
A generic upload failure used to memoize its tag fallback for the rest of
the agent's lifetime, freezing a transient files-endpoint error into a
permanently degraded video. The resolver now marks failure-born fallbacks
as non-memoizable: the current request keeps the lightweight tag form and
the next step retries the upload. Structural outcomes (successful uploads,
capability and sniff fallbacks, no-hook inline) stay memoized for
step-retry stability.
|
||
|
|
ec88d352e8
|
fix: five correctness follow-ups to the catalog metadata work (#2030)
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix: five correctness follow-ups to the catalog metadata work - Normalize a configured effort value (case/whitespace) before thinking resolution on both engines, so "OFF" is read as off instead of being sent upstream as an invalid effort. - Clamp a declared input cap to the effective context window in the effective model resolution, so an override lowering max_context_size cannot leave a stale larger cap behind. - Report context usage against the same effective cap (max_input_tokens ?? max_context_tokens) in the SDK getStatus and the v2 legacy status projections, matching the session-event and service surfaces. - Preserve concretely declared per-model endpoints when the override npm is unrecognized, via the same OpenAI-compatible fallback used for top-level entries. - Attribute resolved.maxInputSize and resolved.capabilities .max_input_tokens in the model inspector to their config, override, or clamp provenance. * fix: honor observed context caps and publish the effective cap in v2 status - The overflow-learned provider window is now written into both max_context_tokens and max_input_tokens of the effective compaction context, so the strategy cannot bypass it by re-selecting the raw catalog input cap during overflow recovery. - agent.status.updated publishes max_input_tokens ?? max_context_tokens, matching the other v1/v2 status surfaces. * fix: never mutate config on clamp, cap usage ratio, and refuse proprietary override SDKs - effectiveModelAlias / effectiveModelConfig now build a copy before clamping maxInputSize to the effective window instead of rewriting the caller's config record in place. - context_usage is clamped to 1 in the v2 legacy status, the v1 session service, and the SDK getStatus; the default-model status fallback also resolves the input cap. - Overrides naming known proprietary SDKs (Bedrock, Cohere) are refused before the OpenAI-compatible fallback, matching top-level import behavior. - The inspector attributes a clamped maxInputSize to the clamp even when the raw value came from models.*.overrides. * fix: normalize forced efforts, prefer input cap in WS status, keep raw event ratio - Whitespace-only configured efforts now read as absent, and the KIMI_MODEL_THINKING_EFFORT override is lowercased on both engines. - kap-server's WS legacy status publishes max_input_tokens ?? max_context_tokens like the other status surfaces. - SDK getStatus keeps the ratio unclamped on purpose (>100% is the documented overflow signal on that path); schema-bounded REST status stays clamped to 1. - Pin the provider-observed window beating a declared input cap with a v1 full-compaction regression test. * fix: attribute resolved.maxInputSize provenance and drop test-body comments in v2 |
||
|
|
8e0dcf3049
|
feat(kap-server): add GET /api/v1/oauth/usage for managed account plan usage (#2027) | ||
|
|
a3699dd6aa
|
feat(kap-server): expose per-tool active state in GET /api/v1/tools (#2005)
* feat(kap-server): expose per-tool active state in GET /api/v1/tools * Update tools-list-active-flag.md Signed-off-by: 7Sageer <7sageer@djwcb.cn> --------- Signed-off-by: 7Sageer <7sageer@djwcb.cn> |
||
|
|
d67a2003ab
|
feat(kap-server): add GET /api/v1/fs:content endpoint for host files (#2012)
* feat(kap-server): add GET /api/v1/fs:content endpoint for host files - serve any host file by absolute path as raw content with Content-Type, ETag caching, and single-range support - extract shared file metadata helpers (binary detection, line counting, etag, mime/language guessing) into agent-core-v2 _base/utils/fileMeta - extract pickHeader/parseRangeHeader into kap-server lib/httpRange for reuse across download/content routes * fix(kap-server): resolve lint and typecheck errors in fs:content route Return the reply.send(stream) result through the handler promise instead of using return-await, matching the session fs download route pattern. * fix(kap-server): reject non-regular files in fs:content Device nodes, FIFOs, sockets, and zero-size virtual files (/proc, /sys) would otherwise hang the request or stream unbounded data. Addresses review feedback; also folds fileMeta symbol comments into the header block per the agent-core-v2 header-only comment convention. |
||
|
|
74da87a457
|
feat(kap-server): broadcast agent.created/agent.disposed session events (#1997)
* feat(kap-server): broadcast agent.created/agent.disposed session events - emit durable agent.created / agent.disposed facts from the SessionEventBroadcaster lifecycle callbacks, ahead of the agent's own events, and let them bypass per-subscription agent allowlists - add the agent.created / agent.disposed wire types and zod schemas - stamp disposedAt on the transcript roster entry via TranscriptStore.markDisposed so REST consumers can tell a dead agent from a live one * chore: add changeset for agent lifecycle events |
||
|
|
ce0e3ceb04
|
feat: support custom agent files (#1735)
Some checks are pending
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / typecheck (push) Waiting to run
* feat: support custom agent files
Discover Markdown+frontmatter agent definitions from user, project, and configured directories; merge them into a session-level profile catalog (priority: builtin < user < extra < project < explicit). Custom agents work as subagents via the Agent tool and as the main agent.
- agent-core-v2: new agentFileCatalog domain (discovery/parsing/profile factory, mirroring skillRoots/parseFrontmatter) and sessionAgentProfileCatalog merged view; AgentProfile.tools becomes optional (undefined = all tools) and gains disallowedTools deny list, evaluated in profileService.isToolActive and persisted in session wire records so resume keeps the gate even if the file is gone
- CLI: restore --agent/--agent-file for the v2 print runner (KIMI_CODE_EXPERIMENTAL_FLAG); the v1 TUI rejects them with a clear v2-only error
- kap-server/protocol: optional profile field on prompt submission with first-bind semantics (same-name no-op, different name rejected)
- docs: custom agents section (en/zh) + config/CLI references + changeset
* chore: shorten custom agents changeset
* fix(agent-core-v2): stringify caught errors in agent catalog log calls
* docs: drop v2 engine notes from custom agents docs
* fix: align custom agent binding semantics across engine and edges
- agent-core-v2: bind now owns the first-bind invariant — switching
profiles after bind throws profile.already_bound (checked again in the
synchronous segment before the first wire dispatch, so concurrent binds
cannot both pass); unknown names throw profile.unknown; same-name
rebinds keep the persisted thinking effort.
- kap-server / CLI: edges degrade to error mapping / same-name no-op
instead of their own divergent guards.
- agent files: reject non-string mode values, honor disallowedTools in
the append-mode Skill probe, pass --agent-file through unresolved so
the engine can expand ~, reject empty --agent-file values.
- session catalog: ready is recoverable via reload() after a fatal
source failure, and agent-file discovery is kicked at session
materialize so resumed sessions see file agents from the first turn.
- docs: first-bind semantics, name: agent override, tools: [] meaning,
--agent-file last-wins.
* fix: tighten custom agent behavior
* fix: address custom agent review findings
* fix(agent-core,agent-core-v2): keep active-tool wire records replayable by v1
Binding a profile without a tool allowlist (the default for file-defined
custom agents) persisted a tools.set_active_tools record with no `names`
key. v1 clients discover v2 sessions through the shared session index and
replay newer wire versions without migration, so the record crashed v1
resume with a TypeError and wedged the session permanently.
The v2 engine no longer writes the record when the base set is already
"every tool active" (its absence encodes the same state), and v1 replay
now skips records that lack `names` as defense in depth for wires already
written by preview builds. A same-name rebind that resets an allowlist to
"all tools" has no v1-safe encoding and is left as a documented gap (no
production caller today); a future tools.reset_active_tools Op is safe
because v1 silently no-ops unknown record types.
* fix(agent-core-v2): tolerate unreadable directories in agent-file discovery
A single unreadable subdirectory (EACCES anywhere in the scanned tree)
previously aborted the whole discovery pass, zeroing every agent of that
source on every session start with one path-less warning. The walker now
skips-and-warns per directory below the root (mirroring the skill
discovery it parallels), root-level failures are isolated per root so one
bad root no longer takes its siblings down, and only a genuinely transient
whole-fs outage (os.fs.unavailable) still propagates so the session
catalog keeps its previous contribution. Source-level warnings now name
the offending path, and repeated skip warnings are capped with a summary
that samples the suppressed paths.
Also consolidates the path primitives (~ expansion, base-relative
resolution, realpath type probes) shared by the root resolvers, the
walker, and the explicit-file source into agentFileCatalog/paths.ts, and
tightens parser diagnostics: frontmatter null is treated as absent, and a
present-but-wrong-typed name/description reports a type error instead of
"missing".
* fix(agent-core-v2): warn when a same-name builtin suppresses a file profile
A directory-discovered agent file colliding with a builtin profile
without override: true was silently dropped at merge time. The suppression
now logs a warning naming the profile and the opt-in.
* refactor(agent-core-v2): pass skillActive explicitly to renderSystemPrompt
The third parameter was a full tool list used only for includes('Skill'),
which forced the agent-file profile factory to answer a boolean question
with sentinel lists. The template now takes an explicit skillActive flag;
a skillActiveFor helper keeps builtin call sites derived from their tool
arrays.
* refactor(agent-core-v2): fall back to the configured default model in bind
BindAgentInput.model is now optional: the engine resolves a missing model
against the configured defaultModel and throws model.not_configured when
neither is set, so edges no longer each re-implement the fallback.
* fix(agent-core-v2,kap-server): reject unsupported thinking atomically at first bind
A REST prompt carrying profile + an unsupported thinking effort bound the
session first and failed setThinking after, wedging the session on an
identity the user never successfully used. The effort is now validated up
front when the caller marks it as an explicit request (strictThinking):
the bind rejects before any await or state mutation, and the requested
effort rides along in the bind instead of a separate setThinking. Internal
spawn/fork paths pass inherited thinking without the flag and keep the
previous clamp behavior — a persisted effort that drifted out of the
model's support list must not break subagent spawning. The route's
now-redundant model fallback is dropped in favor of the engine-side
default.
* fix(agent-core-v2): await the agent profile catalog at session materialize
The catalog's ready promise was only kicked, so a resumed session's first
turn could render the Agent tool description without the file-defined
agent types. Discovery is local-fs and cheap, so materialize now awaits
it; ready only rejects for a fatal explicit-source error, which is exactly
the case that should fail fast. A failure there now also removes and
disposes the half-materialized handle instead of leaving it registered in
the session cache.
* test: cover the --agent-file fatal path and tidy profile registration hygiene
The v2 print CLI now has a test asserting an invalid --agent-file fails
before any turn. The denylist profiles in binding.test.ts register in a
beforeAll (idempotent, scoped to the describe's run window) instead of at
module scope during collection.
* docs: align custom agent docs with v2-engine gating
--agent/--agent-file are rejected without the v2 engine, so restore the
requirement in the Agents and Command Reference pages (and use
KIMI_CODE_EXPERIMENTAL_FLAG=1 in the examples), note that tool lists only
shape model-visible disclosure (permission rules are the enforcement
layer), and remind authors of delegation-bound agents to state the
handoff contract in the prompt body.
* test(agent-core-v2): revert unrelated style churn in fs/workspace tests
Keep these files' diff limited to the realpath fakes the feature needs;
the lint-preference rewrites belong to a separate cleanup.
* feat(agent-core-v2): add permanent system prompt override via SYSTEM.md
Read $KIMI_CODE_HOME/SYSTEM.md on every startup and inject it as the default main-agent profile (name "agent", override: true), replacing the builtin default system prompt while inheriting builtin tools and description. Missing or empty files are ignored; unreadable files warn and fall back to the builtin profile.
The body supports variable substitution (${skills}, ${agents_md}, ${cwd}, ${cwd_listing}, ${os}, ${shell}, ${now}); unknown variables pass through verbatim.
Priority: --agent-file / --agent / project override > SYSTEM.md > same-name user-scope scan files.
* feat(agent-core-v2): gate tools globally and accept session disabledTools
Add a [tools] config section: "enabled" acts as a global allowlist (empty = unconstrained), "disabled" as a denylist applied on top, both intersected with the active profile's policy in isToolActive (mcp glob supported).
Plumb a session-persistent disabledTools parameter through the stack: v2 RPC PromptPayload, REST "disabled_tools" (protocol and kap-server parallel schemas), klient contract/facade, and node-sdk. The server applies it via profileService.setSessionDisabledTools, which replaces the client-owned denylist, keeps the profile's own deny, persists across resume, and rejects calls before a profile is bound with profile.not_bound (mapped to 40001). v1 core-api gains a type-only field and ignores it.
* fix: enforce session tool policy across agents
* fix(agent-core-v2): enforce tool policy at execution
* fix(agent-core-v2): align subagent tool descriptions with policy
* fix(agent-core-v2): harden custom agent policy state
* fix(agent-core-v2): harden custom agent lifecycle
* refactor(agent-core-v2): persist profile binding in a single profile.bind record
* fix(agent-core-v2): skip unreadable paths during agent file discovery
* fix(agent-core-v2): exempt select_tools from the executor policy guard
- share one composed profile/global/session tool-policy evaluation between
the executor gate and prompt rendering instead of two verbatim copies
- tolerate context-build failure in system prompt refresh instead of
rejecting callers (config watcher void-fire, session policy fan-out)
* test(agent-core-v2): resolve profile and tool-policy SUTs by interface
- drop the Object.assign patching of tool-policy methods onto the shared
profile service; rename describes so the SUT ownership is accurate
- classify profile.bind as v2-only with the accepted v1-replay tradeoff
documented, un-red the wire vocabulary guard test
- cover the select_tools guard exemption with an executor-level test
* fix(agent-core-v2): enforce explicit select_tools policy
* feat(agent-core-v2): accept Claude-style tool lists and rename agent-file mode to promptMode
* feat(agent-core-v2): unify prompt templating on ${var}
- Replace the nunjucks renderer with a single ${var} regex renderer
(unknown placeholders pass through verbatim) and drop nunjucks from
agent-core-v2.
- Merge the variable tables into one catalog shared by the builtin
system.md, SYSTEM.md, and agent file bodies; adds additional_dirs_info
plus code-composed blocks (windows_notes, additional_dirs_section,
skills_section).
- Replace the agent-file promptMode field with ${base_prompt}: bodies
are always rendered as templates, and ${base_prompt} expands to the
effective default profile prompt (honoring the SYSTEM.md override).
- Migrate the builtin system.md, goal reminders, compaction instruction,
and tool description templates to the same syntax.
* docs: complete agent priority chain and link SYSTEM.md precedence
* test: fix invalid custom agent fixture
* fix(cli): reject multiple agent selectors
* feat(agent-core-v2): add subagents allowlist to agent files
* fix(agent-core-v2): persist the subagent allowlist in the profile binding
The delegation allowlist now rides the profile.bind record like the tool
denylist, so a resumed session keeps enforcing it even when the source
agent file was deleted or changed. Agent/AgentSwarm resolve the caller's
allowlist from the persisted binding data instead of looking the profile
up in the live catalog.
* feat(agent-core-v2): warn on tool patterns that never match
Profile bind/apply and [tools] config changes now statically flag
entries that can never activate anything — wildcards without the mcp__
prefix (a bare * in an allowlist disables everything, in a denylist
nothing), incomplete mcp__ literals, and names no registered or
builtin-profile tool has — via a tool-pattern-no-match warning event,
once per pattern, instead of letting the tool set silently shrink. The
known-name vocabulary is the live registry plus literal names from the
builtin profiles, so flag-gated tools stay known and a typo in one agent
file cannot legitimize the same typo in another.
* docs: align --agent-file docs with the single-selector CLI
The flag accepts exactly one file and conflicts with --agent, but the
docs still described the earlier repeatable, composable design.
* docs: note the agent-file trust model and never-matching tool patterns
Spell out that project-scoped agent files can replace the default main
agent's whole system prompt (unlike AGENTS.md reference injection), and
list the three tool-pattern shapes that never match and now raise a
warning.
* chore: slim changeset wording to user-facing language
Drop wire record names, enforcement mechanics, and template syntax from
the entries; split the v1 resume fix into its own patch changeset; add a
patch entry for the tool-pattern warnings.
* chore: shorten changeset entries to one-line summaries
* Delete .changeset/v1-resume-v2-sessions.md
Signed-off-by: 7Sageer <12210216@mail.sustech.edu.cn>
* test: drop class-instance spread in sessionLifecycle test stub
* chore: clear the comments
---------
Signed-off-by: 7Sageer <12210216@mail.sustech.edu.cn>
|
||
|
|
6dd4fd3368
|
refactor(agent-core-v2): rebuild the model wire layer on the kosong architecture (#1970)
* refactor(v2): land kosong contract layer (L0 wire contract) * refactor(v2): land kosong protocol layer (L1 traits and base registry) * refactor(v2): land kosong provider layer (bases, trait composers, kimi definition) * refactor(v2): land kosong model and catalog layers * refactor(v2): migrate engine callers to kosong, drop old llmProtocol layer * test(v2): migrate agent-core-v2 tests and harness to kosong * refactor: sync peripheral packages to kosong architecture * refactor(v2): replace provider dialects with per-protocol definitions * refactor(v2): merge the vertexai protocol into google-genai via providerOptions * refactor(v2): remove vendor-name gates outside the kosong layer * feat(v2): add model resolution inspection and connectivity ping * refactor(v2): remove the unused platform config layer * test(klient): pin invalid-input behavior across providers in e2e * refactor(agent-core-v2): merge kimi traits, split bases by protocol - merge the seven kimi trait modules into kimi.contrib.ts as two trait objects: kimiOpenAITrait (all native-transport hooks) and kimiAnthropicTrait (thinking only) - move bases/* implementations into per-protocol directories (openai/, anthropic/, google-genai/) and rename openai.contrib.ts to openai-legacy.contrib.ts - add per-directory index.ts registration barrels (import = registration) and exempt them in check-domain-layers.mjs alongside *.contrib.ts * refactor(agent-core-v2): reorganize model and request-layer types - consolidate shared model types (ModelOverrides, CompletionBudgetConfig/Params, ResolvedModelAuthMaterial, ThinkingDefaults/ModelThinkingMetadata) into kosong/model/model.types.ts and drop modelOverrides.ts - rename L2 request types to the ModelRequest* prefix: LLMEvent -> ModelRequestEvent, LLMRequestInput -> ModelRequestInput, LLMCallParams -> ModelRequestParams - extract ModelRequestTiming to replace three duplicated copies of the stream-timing shape - rename L3 llmRequester types with the Agent prefix to match IAgentLLMRequesterService (AgentLLMRequestOverrides/Finish/Task/Source/ PartHandler/LogFields) - delete the unused LLMRequestParams type * refactor(agent-core-v2): fold kosong/catalog into kosong/model - merge IModelCatalogService's enumeration surface (listModels / listProviders / getProvider / setDefaultModel and the wire shapes) into IModelCatalog; delete kosong/catalog/modelCatalog.ts - move the remote refresh path to the new IProviderDiscoveryService (discovery.ts + discoveryService.ts, renamed from catalog/modelCatalogService.ts) - relocate configSection / errors to discoveryConfigSection.ts / errors.ts; DEFAULT_MODEL_SECTION now lives in kosong/model/model.ts - drop the L3 catalog layer from check-domain-layers.mjs - kap-server routes / refresh scheduler / channelRegistry follow the split; klient renames the contract modelCatalogService to modelResolver and adds providerDiscovery; kimi-inspect reads via IModelCatalog - modelRequesterImpl: drop the streamedAnyPart backfill, onMessagePart already delivers every part - tests: remove the app/modelCatalog and kosong/catalog suites, add the kosong/model catalog and discovery suites * feat(klient): trim trailing undefined args and add boundary smoke probes - add trimTrailingUndefined helper so optional trailing args no longer cross the wire as null in http/ipc transports, which defeated server-side default parameters - add model-requester-boundary smoke probe for ChatProvider error wrapping behavior against real config and a local stub - add kimi-select-tools smoke probe verifying the kimi-only wire encoding of dynamic tool declarations - extend smoke.ts with a models set/get/delete round-trip, catalog list assertions, and update AGENTS.md with the new scripts * fix(agent-core-v2): declare openai chat hooks as function properties Method-shorthand members on OpenAIChatCompletionsHooks tripped typescript-eslint(unbound-method) at every extraction site (`const hook = this._hooks?.convertMessage` and friends), failing the repo-wide lint job. Every implementation is a plain closure composed by openaiHooks.ts, so declare the members as function-typed properties, which matches the actual semantics and clears the four errors. * fix: repair stale references surfaced by the origin/main rebase - agent-core-v2: point vacuousContent's ContentPart import at kosong/contract - kap-server: rewrite the transcript test seed as IModelCatalog (IModelResolver is gone) - klient: inline onceEvent/waitFor after the http transport helpers were dropped - kimi-inspect: remove useLiveEvent from ModelCatalogView; catalog polls on a slow interval * chore: downgrade the kosong architecture changeset to patch |
||
|
|
5ae60fa673
|
feat(transcript): add unified transcript layer, drop the /api/v2 RPC surface (#1888)
* feat(transcript): add unified transcript layer and v1 surface
- add packages/transcript: agent-granular L1 store, idempotent L2 ops,
off/turn/block/delta L3 granularity, L4 view registry, and turn-cursor
pagination; sole owner of all transcript wire types
- kap-server: engine-event-driven TranscriptService with history backfill,
GET /sessions/{id}/transcript, and transcript.ops WS deltas with
per-connection granularity control
- kimi-inspect: render ChatView from the transcript surface (REST pages +
delta-only WS) instead of context memory
- sync flake.nix workspace lists and document packages/transcript and
packages/server-e2e in AGENTS.md
* fix(transcript): project live prompts and anchor backfilled items
- agent-core-v2: carry the extracted prompt text on turn.started so the
transcript projector can render the user input at turn open (the context
append with the same text is not a bus event and lands later)
- kap-server: keep the prompt through turn.ended; anchor backfilled
markers/taskrefs to their following snapshot turn so replaying history
after live turns arrived keeps the historical order
- transcript: add an optional beforeTurn placement anchor to
marker.upsert / taskref.upsert; anchored inserts land before the first
turn at or past the anchor instead of appending blindly
Addresses review feedback on #1888.
* fix(transcript): adopt backfilled stream frames and group goal turns
- kap-server: on mid-stream attach, adopt the backfill-seeded stream frame
(id + offset) instead of opening an empty one whose upsert clobbered the
seeded text and whose offset-0 appends could not land
- transcript: group a turn-opening system_trigger (goal continuation) into
its own turn so cold rebuilds keep the turn boundary and stay
ordinal-aligned with the engine's live turn numbering
Addresses review feedback on #1888.
* fix(transcript): drop stale live stores on close and heal after turns
- drop a session's live transcript store when the session closes or archives
(lifecycle events plus a re-check on the cached-entry path) so reads fall
back to the cold rebuild instead of serving a stale store
- re-read an ended turn from the persisted wire records (debounced per
agent) and merge it back live-first: headers keep live state/timestamps
while origin/prompt recover from disk, and truncated text/thinking frames
from a mid-turn attach are restored only when the persisted text is
longer — a fresh live frame or a lagging flush is never reverted
Addresses review feedback on #1888.
* fix(transcript): adopt seeded tool frames and route subagent questions
- kap-server: adopt the backfill-seeded tool frame when a tool.result
arrives for a call that started before the projector attached, so the
output lands instead of being dropped (the producer-store lookups now
ride one projector options object)
- agent-core-v2: record the owning agent on question interactions
(ISessionQuestionService.request gains an agentId option, passed by
AskUserQuestionTool from its agent scope) so a subagent's questions
route to its own transcript and WS events instead of 'main'
Addresses review feedback on #1888.
* fix(transcript): adopt parent frames, namespace live markers, redact resets
- kap-server: fall back to store adoption when subagent.spawned links a
parent tool call that started before the projector attached, so the
agentRefs link is not lost on mid-bind attaches
- kap-server: id live markers in their own namespace (live-mN) — the cold
rebuild numbers its markers m1... too, and a colliding id made the
store's upsert replace the historical marker instead of appending
- transcript/kap-server: redact transcript.reset snapshots to the
subscriber's grade (below 'block' the step/frame detail is stripped), so
a 'turn'-grade subscription no longer receives full content on resets
Addresses review feedback on #1888.
* fix(transcript): fold blocked turns into failed, drop inline v2 comment
- kap-server: map turn.ended reason 'blocked' to the 'failed' transcript
state, matching the engine's TurnEndReason wire contract and the v1
mapTurnReason folding (it was presented as a user cancellation)
- agent-core-v2: move the question agentId rationale into the ask-user
file header — package rules keep comments in the top-of-file block only
Addresses review feedback on #1888.
* fix(transcript): keep roster descriptors and resolved-event agents
- kap-server: keep the metadata-seeded roster descriptor (parentAgentId /
label) when an on-demand history backfill lands its roster entry, instead
of downgrading it to { agentId, type }
- kap-server: remember each interaction's owning agent and stamp it on the
resolved question/approval v1 events — they were hard-coded to 'main', so
an agent-filtered subscriber saw a subagent's question open but never
close
Addresses review feedback on #1888.
* fix(transcript): defer pending seeding, skip ghost roster entries
- kap-server: announce interactions pending at bind time only after the
initial backfill (new TranscriptBinding.seedPendingInteractions), and
adopt the seeded tool frame on the request/resolve paths, so a
pre-existing approval lands next to its backfilled tool call and keeps
the approvalId back-link
- kap-server: skip the roster entry when a probed agent id has neither a
roster presence nor persisted content (agent_id=nope no longer conjures
a ghost subagent)
- agent-core-v2: fold the turnEvents import rationale into the loop file
header (package rule: comments live in the top-of-file block only)
Addresses review feedback on #1888.
* fix(transcript): reject hostile agent ids, honor the agent allowlist
- transcript/kap-server: validate agent ids as plain names (no separators,
no dot segments) at the REST query layer and again before the id is
joined into the wire-records path — an authenticated client could
otherwise read a wire.jsonl outside the agents directory
- kap-server: compose the legacy v1 agent allowlist with transcript
grades on every fan-out path (initial resets, per-ops fan-out,
roster-driven resets), so a filtered connection no longer receives
other agents' transcript frames
Addresses review feedback on #1888.
* fix(transcript): settle foreground shell tasks on shell.completed
- agent-core-v2: emit a transient shell.completed event when a foreground
`!` command settles (detached runs keep reporting through the task
lifecycle) — the generic task.terminated never fires for foreground
tasks, so their transcript cards were stuck at 'running'
- kap-server: map shell.completed to the terminal transcript task state
(completed/failed) and classify the event as volatile on both v1
durability gates, like its shell.* siblings
Addresses review feedback on #1888.
* fix(transcript): replay early resolves, reset on a widened filter
- kap-server: register bind-time pending interactions without frames so a
resolve arriving before the post-backfill seed still routes, then replay
it at seed time — request and resolve land together with the approvalId
back-link, instead of the interaction vanishing entirely
- kap-server: treat an agent newly admitted by a broadened legacy agent
filter as owed a transcript.reset even when its grade transition is a
no-op (delta → delta) — its ops were suppressed so far, so it has no
baseline otherwise
Addresses review feedback on #1888.
* fix(transcript): keep materialized transcripts on agent disposal
- kap-server: only the projector dies with the agent scope — the
materialized transcript and roster entry now survive disposal (the
roster mirrors session metadata, which keeps completed agents).
Dropping them lost already-served history for good: the backfill cache
dedupes per agent, so the next read rebuilt an empty shell instead of
replaying the persisted records
Addresses review feedback on #1888.
* fix(transcript): anchor refreshes by oldest turn, group slash turns
- kimi-inspect: re-cover the previously loaded window after a refresh by
paging until the previous OLDEST turn is loaded again (extracted as
recoverLoadedWindow) — a count-based stop silently dropped the window's
head once new turns shifted the server window
- transcript: group user-slash skill/plugin activations into their own
turn (marker included), mirroring the engine's isRealUserPrompt — their
assistant output no longer folds into the previous turn on cold rebuilds;
other triggers stay marker-only
Addresses review feedback on #1888.
* fix(transcript): compare all tool fields, overlay in-flight backfills
- transcript: include toolCallId/name/view/input in the tool frame
equality check — an upsert correcting only those was dropped as a no-op,
leaving stale tool metadata on clients
- kap-server: overlay the loop's active turn as 'running' after a backfill
(cold grouping marks every rebuilt turn completed, so a live turn showed
as finished until it ended); snapshot data supplies origin/prompt, and a
projector-owned running header is never downgraded
Addresses review feedback on #1888.
* fix(transcript): gate ops before the seed, re-assert running headers
- kap-server: gate the transcript ops fan-out (and roster-driven resets) on
a per-connection seeded flag set only after the baseline reset has
landed — a subscriber joining mid-stream no longer receives deltas
against an empty baseline
- kap-server: always re-assert the loop's active turn as 'running' after
the snapshot ops in a backfill (skipping the overlay when a live running
header existed let the snapshot's cold 'completed' header downgrade it);
live header fields win over the snapshot's
Addresses review feedback on #1888.
* docs(agent-core-v2): fold turnEvents notes into the file header
Move the turn.started prompt rationale from field/function TSDoc into the
module header — package rules keep comments in the top-of-file block only.
Addresses review feedback on #1888.
* fix(transcript): emit shell failure output, dispose per-agent listeners
- agent-core-v2: emit the synthesized failure text as a final shell.output
chunk before shell.completed (it was never streamed, so failed
foreground commands showed empty output in transcript tasks until a
full rebuild)
- kap-server: track each agent's bus subscription per agent and dispose it
in onDidDispose — the listener captures the projector, so a disposed
agent no longer keeps projecting late events into the store
Addresses review feedback on #1888.
* fix(transcript): route shell events by task id, tidy question docs
- agent-core-v2: keep the commandId → foreground-task-id mapping and carry
taskId on shell.output / shell.completed, so consumers attaching
mid-command (having missed shell.started) can still route output and the
terminal state
- kap-server: fall back to the event's taskId in the shell output/completed
projectors and seed the shell task before the first chunk, so output is
preserved and the terminal upsert cannot clobber it with an empty tail
- agent-core-v2: fold the question agentId note into the question.ts file
header (package rule: comments live in the top-of-file block only)
Addresses review feedback on #1888.
* fix(transcript): tighten agent id validation, match kinds in heals
- transcript: constrain agent ids to a filename-safe shape
([A-Za-z0-9._-], <=128 chars) — NUL-containing or overlong ids made the
wire-records read throw unhandled errors (500) instead of failing
validation
- kap-server: require the live frame's kind to match before the post-turn
heal's length shortcut — a kind-mismatched frame (the projector guessed
the stream kind wrong mid-turn) is now replaced by the persisted one
instead of being skipped
Addresses review feedback on #1888.
* fix(transcript): heal missed tool results, seed pendings per agent
- kap-server: re-emit tool frames in the post-turn heal when the live step
lacks the frame or the live frame lacks the outcome the persisted one
carries (a tool.result dropped in the attach race is otherwise
unrecoverable); live-only extras (display / agentRefs / approvalId) are
preserved, and frames with a live outcome stay untouched
- kap-server: scope seedPendingInteractions by agent — the initial seed
after backfillMain covers main-owned pendings, and each subagent's
pendings seed after its own on-demand backfill, so placement and the
approvalId back-link find the persisted tool frames
Addresses review feedback on #1888.
* fix(protocol): register shell.completed on the v1 event surface
- packages/protocol: add ShellCompletedEvent (plus optional taskId on
shell.output / shell.completed and prompt on turn.started) to the event
interfaces, zod schemas, the agent event union, and the volatile list —
schema-validating consumers previously rejected the forwarded
shell.completed frames outright
- kap-server: mirror the same fields in the v1 events-zod module
Addresses review feedback on #1888.
* test(node-sdk): cover shell.completed in the exhaustive event switch
The SDK's session-event type test asserts exhaustiveness with assertNever;
register the new event there (CI typecheck caught it).
Addresses review feedback on #1888.
* fix(transcript): source cold-session rosters from session metadata
- kap-server: add TranscriptService.readColdRoster (persisted state.json →
descriptors, mapped like the live seeding) and use it for the cold
transcript path — the requested agent id is only appended when it has
content (or is main), so an empty probe (agent_id=nope) no longer
fabricates a ghost roster entry, matching the live path
Addresses review feedback on #1888.
* fix(transcript): emit taskrefs when seeding missed shell commands
- kap-server: the mid-command-attach seeding in onShellOutput now emits
the matching taskref.upsert (exactly like onShellStarted), and
onShellCompleted emits one when the whole command was missed — the task
no longer exists only in the global map with no timeline item to render
Addresses review feedback on #1888.
* fix(transcript): defer unseeded live pendings, keep cold tools running
- kap-server: pendings created before their owning agent's seed has run
now defer into the same unseeded queue as bind-time ones (tracked per
agent) — announcing them during the backfill window misplaced them into
a synthetic step with no later repair
- transcript: cold grouping initializes tool frames as 'running' and lets
the tool-message branch transition them to done/error — an approval-
gated or still-executing tool no longer shows as completed on rebuilds
Addresses review feedback on #1888.
* fix(transcript): seed live-created agents, merge backfills live-first
- kap-server: agents created after binding are marked seeded immediately —
their projector covers every event from creation on, so their pendings
announce without waiting for an explicit history read (which previously
left live subagent approvals/questions stuck in the unseeded queue)
- kap-server: the initial backfill merges turns live-first via
healTurnOps (snapshotToOps gains a turn-mapper parameter) — live frame
fields landed during the disk read (display/approvalId, longer text)
are no longer replaced by the staler persisted version
Addresses review feedback on #1888.
* fix(transcript): count pages in turn segments, not head units
- transcript: the leading non-turn unit no longer consumes a turn slot —
pages are counted in turn segments and the head unit rides only with the
page reaching the first turn. A timeline with a head marker and exactly
pageSize turns used to drop the marker from the newest page and
hallucinate an older marker-only page (has_more: true with no older
turns)
Addresses review feedback on #1888.
* fix(transcript): send baseline resets after cursor replay
- kap-server: broadcaster.subscribe gains deferTranscriptReset (recording
prev grades/filter per target) plus flushTranscriptSeed; the v1
connection defers the transcript baseline on cursor-carrying
(re)subscribes and flushes it after replay — a reconnecting client no
longer sees the reset's current seq ahead of the replayed lower-seq
backlog
Addresses review feedback on #1888.
* fix(transcript): gate the ops fan-out only when a reset is coming
- kap-server: willSendTranscriptReset decides upfront whether any reset
will be sent (grade upgrade or widened legacy filter); a same-grades
resubscribe no longer un-seeds the target, so ops emitted mid-resubscribe
keep flowing instead of being silently dropped by the fan-out gate
Addresses review feedback on #1888.
* fix(transcript): seed subscribers even when no reset is owed
- kap-server: a no-reset subscription (e.g. a client subscribing to a
fresh session with an empty roster) now still marks the target seeded
after subscribeTranscript completes — roster resets and ops would
otherwise stay gated forever once agents appear
Addresses review feedback on #1888.
* fix(transcript): guard mismatched appends, expose prompt via klient
- transcript: appendAtOffset now treats an overlapping chunk whose head
does not match the local tail as a gap (diverged stream) instead of
silently rewriting from the offset and dropping local content
- klient: add the optional prompt field to the turn.started event schema
so SDK listeners receive it instead of zod stripping it
Addresses review feedback on #1888.
* fix(transcript): open turns for subagent run prompts in cold grouping
- transcript: add the subagent system trigger to the turn-opening set —
a subagent's run prompt (persisted as system_trigger/'subagent') always
launches a new engine turn, so resumed subagent histories no longer fold
the response into the previous turn or lose the prompt
Addresses review feedback on #1888.
* fix(transcript): guard bus subscriptions independently of projectors
- kap-server: subscribeAgent now guards on a dedicated subscribedAgents
set instead of projector existence — a projector seeded before its
agent's handle exists (e.g. during an on-demand backfill) no longer
blocks the bus subscription, so the agent's live events keep flowing
Addresses review feedback on #1888.
* fix(kimi-inspect): reconcile the transcript on every socket open
- apps/kimi-inspect: TranscriptWs now reports onReconnected on the FIRST
successful open too, not only on re-established ones — ops emitted
between the REST page load and the subscription (a delayed or failed
first connection) were previously lost onto a stale store; the consumer's
refresh guard drops the no-op call while the initial load is in flight
Addresses review feedback on #1888.
* fix(transcript): derive the active step, dedupe tool error rendering
- kap-server: the projector gains a stepOrdinal lookup backed by the
engine's activity view (resolved lazily through the agent lifecycle), so
deltas after a late attach at step >= 2 land in the real active step
instead of a synthesized t<N>.1
- apps/kimi-inspect: render a tool frame's error only when it differs from
its output — onToolResult sets both to the same string for failed tools,
which drew the failure twice in red
Addresses review feedback on #1888.
* fix(kimi-inspect): reconcile the transcript on the subscribe ack
- apps/kimi-inspect: TranscriptWs now fires onReconnected when the
subscribe ack for its client_hello arrives instead of at socket open —
the server attaches the transcript stream only after processing
client_hello, so a refresh fired at open could finish before the
subscription was active and still miss the ops in between
Addresses review feedback on #1888.
* fix(kimi-inspect): coalesce concurrent transcript refreshes instead of dropping
A subscribe ack landing while the initial REST load was still in flight
hit the `if (refreshing) return` guard, so ops emitted between the REST
page snapshot and the WS subscribe were neither in the page nor
delivered over the socket. Replace the drop guard with a coalesced
runner: at most one refresh in flight, and triggers during a run are
collapsed into exactly one follow-up run after it settles.
* fix(kap-server): force the transcript baseline after cursor-based replay
A cursor re-subscribe at unchanged grades deferred its baseline and then
compared against the previous grades on flush, so no reset was sent —
while volatile ops fanned out during the deferral had been dropped,
leaving the client with a permanent gap. flushTranscriptSeed now always
seeds a full baseline (previous grades no longer tracked in the deferred
record), and a regression test covers the same-grade cursor resubscribe.
Also drop the inline comments added to shellCommandService.ts — the
agent-core-v2 convention keeps commentary in the top-of-file block; the
context moved there.
* fix(kap-server): harden transcript seeding against stale and wildcard subs
Two subscribeTranscript gaps found in review:
- Re-read the target's subscription after the history awaits: subscribe
work runs asynchronously, so an overlapping downgrade/unsubscribe used
to be answered with resets computed from the stale spec. The reset
loop now uses the latest grades/filter from state.targets and bails
when the target is gone or no longer graded.
- Backfill roster agents admitted via the wildcard grade, not just
explicitly named ones: a historical subagent seeded into the roster
from session metadata had no materialized AgentTranscript, so
wildcard subscribers silently never received its baseline reset.
Adds regression tests for the wildcard backfill, the mid-seed
downgrade, and the mid-seed unsubscribe (all three fail without the
fix); makeCore now accepts persisted agent metadata for roster seeding.
* fix(transcript): let meta.merge clear mode badges on mode exit
`agent.status.updated` with `planMode: false` / `swarmMode: false` was
dropped by the transcript projector because `meta.merge` could only set
mode badges, never clear them — clients kept rendering an exited mode
until the next full reset. The merge wire shape now accepts `null` per
mode key (set = object, clear = null, absent = keep): the reducer
deletes the key and normalizes an empty `modes` away, the zod schema
validates the nullable form, and the projector emits the clearing op
for exit events.
* fix(agent-core-v2): keep system-turn steering text out of turn prompts
`turn.started.prompt` was populated from the turn input for every
origin, so system-triggered turns (goal continuation, subagent run,
cron) exposed their internal steering text to live transcript
consumers; the cold rebuild mirrored the same leak when grouping
persisted history. The loop now populates the prompt only for
displayable user origins (user input, or a user-slash skill/plugin
activation) via the new isDisplayablePromptOrigin gate, and the cold
grouping opens hidden-origin turns promptless. Turns still open
normally — only the prompt text is withheld.
* fix(kap-server): reattach the transcript fan-out after a session reload
When the engine session closed or archived, TranscriptService dropped
the live store together with its ops listener set, but the
broadcaster's SessionState kept its transcriptStream — so
ensureTranscriptStream returned early for a later subscribe on the
resumed session, delivering a fresh reset but never the live
transcript.ops. The stream is now pinned to its TranscriptStore
instance and the fan-out re-registers whenever a rebuilt store shows
up. Adds a regression test that drops the service entry mid-stream and
asserts ops keep flowing after resubscribe (fails without the fix).
* fix(transcript): map legacy background_task origins in cold rebuilds
Legacy/v1 sessions persist background-task notifications with
origin.kind === 'background_task' (the live mapper already handles that
spelling), but the cold grouping only mapped 'task' — after a restart
those turns fell through to { kind: 'other' } and lost their taskId, so
the transcript could no longer associate the notification turn with its
background task. Both spellings now share the task-origin branch.
* fix(kap-server): project no-taskId shell failures into the transcript
A foreground `!` command that failed before onForegroundTaskStart ran
(Bash validation/spawn/registerTask errors) published shell.output /
shell.completed with taskId undefined, and the projector's guard
dropped them — the live transcript lost the stderr and the terminal
state of a command that did run. Shell events now resolve their task as
the id learned at shell.started, else the event's own taskId, else a
synthetic per-command id (shell-<commandId>), so early failures land
like any other shell task.
* refactor: drop the /api/v2 RPC surface and the klient http transport
- kap-server: remove the /api/v2 REST routes and /api/v2/ws socket (registerRpcRoutes renamed to serviceDispatcherRoutes; transport/ws/{eventMap,registerWs,wsClient,wsConnection,wsProtocol} deleted). /api/v1/debug/* is now the only RPC surface — a reflection dispatcher over the entire scoped DI registry with no whitelist — and /api/v1/ws the only WS endpoint
- klient: drop the http transport (transports/http/*, transports/ws/wsSocket.ts) and the kap-server devDependency; transports reduce to the ipc|memory subpath entries, and the dual/v2 e2e suites go with them
- kimi-inspect: target /api/v1/debug only with no fallback, replace the Service-event push channel (wsChannel/wsSocket) with on-demand fetch plus 15 s polling, and show a blocking "Debug surface unavailable" screen on connection failure
- transcript: add global attachment/interaction/todo entities (model, ops, wire schema) and project them from engine events in kap-server's coreEventMap
* fix(kimi-inspect): drop the unused TranscriptTodo import
|