Commit graph

107 commits

Author SHA1 Message Date
qer
eae29201d7 fix(transcript): guard the prompt backfill against missing services; align stream expectations with prompt.accepted on the bus 2026-08-21 02:41:11 +08:00
Haozhe
3fdce983f8
fix: drain in-flight persistence and log writes on session close and shutdown (#3122) 2026-08-20 17:19:20 +08:00
Haozhe
a09d904140
refactor(agent-core-v2): migrate agent domains to model-as-container architecture (#3103) 2026-08-20 16:06:59 +08:00
liruifengv
3d7762003a
feat(kimi-code): support two OAuth login endpoints (#2862)
* feat(kimi-code): add China/International region selection for OAuth login

- Add region profiles (cn/overseas) and resolver in @moonshot-ai/kimi-code-oauth:
  env override → persisted login host → install-channel marker → default cn
- /login now offers Kimi Code (China) / Kimi Code (International); the CLI
  login entries (kimi login, kimi acp --login) accept --region cn|overseas
- Update/plugin/site/telemetry endpoints derive from the selected region;
  plugin trust list covers both .com and .ai hosts
- kap-server: POST /oauth/login accepts an optional region; new GET /oauth/region

* fix(oauth): keep an explicit default-slot login ahead of the install marker

A China login persists no oauthHost (the default credential slot carries
no host trace), so after switching back from International the resolver
fell through to a stale overseas install marker. Treat a persisted
default-slot oauth ref (key === oauth/kimi-code) as an explicit-cn signal
that outranks the marker; getRegion() on the v2 side mirrors it.

* fix(agent-core-v2): thread the default-slot key through capability region resolution

Capability installs resolved the region from the persisted oauthHost only,
so an explicit China login (which persists no host) lost to a stale
overseas install marker. Pass the oauth ref key through as well, matching
getRegion(). Also move the region contract notes into the auth.ts file
header per the package comment convention.

* fix(agent-core-v2): honor the region-marker opt-out for the telemetry endpoint

Hosts that set KIMI_CODE_REGION_MARKER=off (the desktop embedded server)
skip the install marker in getRegion(), but the default telemetry endpoint
still consulted it, so a stale overseas marker could split the reported
region from the telemetry destination.

* feat(cli): show region site domains in login platform selector

* chore: reword oauth login changesets

* fix: honor the region marker opt-out in the CLI and capability resolvers

* refactor: rename login region values to mainland-cn and global

* fix: keep the --region help text in English

* fix: simplify the --region help text to site domains

* feat: drop the suggested login platform order

* feat: split a browser-safe region profile table out of the region resolver

* Revert "feat: split a browser-safe region profile table out of the region resolver"

This reverts commit a037b1143e.

* fix: read the install marker from the bootstrapped home directory

* fix: resolve the server plugin marketplace from the active login region

* feat: expose the login region option through the klient auth facade

* fix: drop a comment from the v2 auth region test

* fix: keep scoped base-only logins on their environment for a bare login

* fix: invalidate the region cache on the provider-manager logout path

* fix: route client-config fetches through the active region profile

* fix: resolve the telemetry endpoint per flush so a login region switch applies in-process

* test: expect the telemetry endpoint resolver in the CLI init assertions

* fix: resolve the default telemetry endpoint from the bootstrapped home

* chore: reword the oauth login changeset around the two login methods

* chore: trim the oauth login changeset to the headline

* feat: let hosts override the region marker env through the server bootstrap env bag
2026-08-20 11:24:02 +08:00
Luyu Cheng
8440801de4
feat(agent-core-v2): add the WaitFor tool for waiting on background tasks (#3060)
* feat(agent-core-v2): add the WaitFor tool for waiting on background tasks

* fix(agent-core-v2): mark WaitFor deliveries only after formatting succeeds

* fix(agent-core-v2): cancel losing waits once the WaitFor race resolves

* test(node-sdk): project WaitFor out of the v1-v2 resume parity roster

* fix(agent-core-v2): gate WaitFor goal guidance behind the wait_for flag

* fix(agent-core-v2): gate WaitFor goal guidance on actual tool availability

* fix(agent-core-v2): enforce the wait_for flag at WaitFor execution time

* fix(agent-core-v2): consult the live tool policy in the WaitFor availability check
2026-08-19 11:26:59 +08:00
Haozhe
98ebda840a
fix(kimi-code): revert the todo panel to its pre-turn state on undo (#3016)
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-vscode-legacy (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
* fix(kimi-code): revert the todo panel to its pre-turn state on undo

* fix(kimi-code): hide all-done todo lists on undo refresh and detach SDK todo state
2026-08-18 12:00:19 +08:00
Haozhe
3ded08084a
fix(protocol): expose turn ended event time (#3011)
* fix(protocol): expose turn ended event time

* fix(protocol): expose turn ended event time

* chore(changeset): remove patch release entry

* test(node-sdk): align background task parity expectations
2026-08-18 10:16:12 +08:00
Haozhe
2265305e81
refactor(agent-core-v2): replace defineOp/Model with Event2 dispatch and replayable states (#2909)
* refactor(agent-core-v2): replace defineOp/Model with Event2 dispatch and replayable states

- replace defineOp/Op/OpDescriptor/toEvent and defineModel/defineCheckpointedModel with Event2 subclasses: durable classes declare static durable + schema, serialize() keeps the wire record shape byte-frozen, transient classes stay off the journal
- define states via defineState(...).replayable(...).on(Event2, fold): immer produceWithPatches folds with atomic prepare/commit, .undoable() trait drives prompt-submit checkpoints and context.undo, ephemeral kv keys keep imperative set
- degrade IWireService to a journal adapter; the agent event dispatcher owns the pipeline (fold -> set -> appendRecord -> publish) and silent restore
- align downstream event surfaces: kap-server WS envelope timestamp from event.time, klient event schemas gain time, node-sdk/acp-server/print wiring updated
- rewrite gen-wire-manifest/gen-state-manifest for the unified registry and replace the op-uniqueness lint with event-uniqueness

* fix(ci): repair Event2 prompt and media projections

- restore prompt admission and session media materialization
- align transcript, WS, SDK, and replayable media state projections
- update affected tests and generated state manifest

* fix(ci): update prompt event and projection expectations

- update snapshots for the durable prompt.accepted event
- normalize prompt.steered media in transcript projections
2026-08-17 17:38:50 +08:00
7Sageer
59dde734f3
feat(agent-core): unify the v1 MCP management plane (#2858)
* feat(agent-core): unify the v1 MCP management plane

- McpServerRegistry: one config view over global (layered mcp.json),
  plugin (manifests, read-only, final effective config), and caller
  (SDK-injected) servers; name collisions keep both entries.
- Write plane: add/update/removeGlobalMcpServer mutate the user-level
  file and push into live sessions; getGlobalMcpServer returns the
  effective config; mutations of read-only entries are rejected.
- testGlobalMcpServer accepts an inline config; addSessionMcpServer
  connects a server in one live session with an optional persist flag;
  reconnect accepts a replacement config and re-resolves via the registry.
- One process-wide McpOAuthService shared with every session: obtained_at
  stamps, offline token state, single-flight and proactive refresh, and
  credential events. Sessions self-subscribe in the constructor, so even
  initializing sessions see every event; token writes serialize through
  the process-local OAuthTokenTransaction per credential identity.
- inspectAppMcpServers + locator-addressed begin/complete/cancel/reset
  cover plugin servers; inspection output redacts env/headers to sorted
  key lists; locator OAuth ops reject ambiguous shared runtime names.
- The legacy auth-status surface reads the registry (offline by default,
  verify=true probes) and never mutates credentials.
- VS Code panel receives source/origin/mutable and hides mutating
  actions on read-only entries.
- v2 client facade in node-sdk mirrors the surface over agent-core-v2
  (plugin inventory stays v1-only for now).

* fix(agent-core): close the v1 MCP live-session reconciliation gaps

Recompute each live session's MCP target from the registry's runtime
resolution (enabled plugin > project layer > user file; caller injection
shadows everything) behind every config mutation, instead of per-path
patching: shadowed file layers recover when a plugin winner is disabled or
removed, removing a user-level entry resurrects its project-layer shadow,
disabled plugin descriptors no longer block removals, persisted session
adds validate against the session's project layer and broadcast to other
live sessions, and per-session sync failures are logged with context.

Session status entries and read-only management entries now report
redacted config views (envKeys/headerKeys instead of literal env/headers
values); core-internal reconciliation compares full configs via the
connection manager's raw-entry accessor.

OAuth: interactive flows are serialized per credential (concurrent
begins join the in-flight flow instead of clobbering its PKCE/state), a
malformed credential meta sidecar no longer aborts core start, grants
inside the refresh-ahead window refresh immediately while far-future
grants re-arm through a max-length timer, and the service shuts its
timers and flows down with KimiCore/SDKRpcClient close.

* fix(agent-core): route the proactive MCP OAuth refresh through the token transaction

 refreshNow ran its /token request with the SDK default fetch, outside the
 credential-serializing OAuthTokenTransaction that every other token write
 uses; a slower response carrying an older rotating refresh token could
 overwrite a newer grant written by a concurrent transport-side refresh.

* fix(agent-core): keep disabled MCP servers out of auth-state classification

The unified mcpServerAuthState dropped the previous enabled short-circuit,
so a disabled oauth-flagged server reported oauth-required — or was even
probed over the network — instead of not-applicable.

* fix(kimi-code-sdk): short-circuit disabled MCP servers in the v2 auth-status classifier

The v2 parity copy of v1's mcpServerAuthState missed the same enabled
guard v1 just regained; a disabled oauth-flagged entry would report
oauth-required (or be probed). The parity suite now pins the disabled
case on both engines.

* fix(kimi-code): refresh the VS Code MCP list with the workspace cwd after mutations

The add/update/remove RPCs return a cwd-less management list, so the
webview broadcast dropped project-layer entries until the next full load;
re-list with the workspace cwd after every mutation instead.

* fix(agent-core): keep SDK token saves matched to the OAuth token transaction

saveTokens stamped obtained_at onto a fresh object before calling
tokenTransaction.save, so it never matched the exact payload the
transaction recorded for a grant fetch; the consume path was dead and
every save re-wrote. Between the fetch and the SDK callback an intervening
clear could then be overwritten — the resurrected grant came back after a
reset. The write callback stamps the durable record instead.

* fix(agent-core): reject ambiguous legacy name-based MCP auth lookups

The legacy begin/reset auth RPCs took the registry's first name match,
silently starting OAuth for one entry of a runtime-name collision while
the locator path refused the same ambiguity; align them on the shared
uniqueness rule and point callers at the locator-addressed variants.

* fix(agent-core): propagate registry errors during live-session MCP sync

resolveMcpRuntimeTarget collapsed every registry failure into "no target":
a project config file that turned malformed mid-session made sync treat a
still-configured server as gone (tearing down the live connection) and
made config-aware reconnects report "no longer configured" instead of the
actionable config error. Absence still resolves to undefined; malformed
config now propagates — per-session sync logs and keeps the entry, and
reconnect surfaces config.invalid.

* fix(agent-core): close the remaining registry-error and ambiguity gaps

The management guard lookup mapped every registry failure to "absent",
so a malformed project config let a persisted session add write a
user-level entry over an unknown state; only not-found is a miss now. And
the name-only connection test now shares the auth paths' uniqueness rule
instead of probing the first match of a runtime-name collision.

* fix(agent-core): probe the enabled MCP entry under a disabled-name collision

The name-only connection test counted enabled matches for its ambiguity
guard but still probed the first registry match, and the file layers list
before plugins. With a disabled file entry shadowing an enabled plugin of
the same runtime name, Test probed the disabled entry instead of the one a
live session would run. Select the sole enabled match, falling back to the
first entry only when every match is disabled so it reports as disabled.

* fix(agent-core): let session-local MCP adds shadow plugin entries

Caller injection shadows every registry source at session start, plugins
included, and reconciliation leaves caller entries untouched; the live
non-persist add path rejected plugin-owned names anyway, so SDK clients
could not apply the same per-session override without a restart. Gate the
plugin-source rejection on persist: session-local adds connect as caller,
while persisted adds stay rejected as user-level writes behind a read-only
owner.

* fix(agent-core): normalize session MCP names before connecting

The persisted store trims server names, but addSessionMcpServer used the
raw name for the live connect and cross-session reconciliation: a padded
name persisted under the trimmed key while the requesting session ran and
reconciled the raw one, and a blank name connected with no identity at
all. Normalize once up front (rejecting blank) so the store write, the
session entry, and reconciliation agree on the same server.

* fix(agent-core,node-sdk): close the collision-selection and probe-freshness gaps

The legacy name-only auth resolver started from the first registry match,
so a disabled file-layer shadow plus an enabled plugin of the same runtime
name was misread as an ambiguity conflict; select the sole enabled match
before judging ambiguity, exactly like the test probe path. On the v2
client, addSessionMcpServer connected the raw name while the store wrote
the trimmed key — normalize once for both, and route the verify-triggered
auth probes through the per-call OAuth service instead of the cached one
whose providers snapshot tokens at construction, so a grant saved after
the first probe is honored.

* fix(agent-core): normalize global MCP mutation names and guard disabled reconnect swaps

The global add/update/remove mutations guarded and reconciled with the raw
server name while the store persisted the trimmed key, so a padded name
left live sessions unreconciled and could slip past the plugin read-only
guard; normalize once before lookup, persistence, and reconciliation. And
a config-carrying reconnect assigned the replacement before the disabled
check fired, leaving a connected entry that reported the disabled config;
reject disabled replacements before mutating, keeping the same error.

* fix(agent-core): skip proactive refresh while an interactive flow owns the credential

refreshNow reset the shared provider's flow state before and after the
token request; when a proactive timer (or a manual refresh) fired while
beginAuthorization was waiting on the browser callback for the same store
key, that wiped the redirect URL, PKCE verifier, and state the in-flight
flow needed — complete() then failed the exchange even though the user
authorized. Refresh now skips when an interactive flow is active for the
credential: the flow delivers fresh tokens on completion, and the 401
transport path is the backstop if it fails.

* fix(agent-core): allow global MCP adds over disabled plugin descriptors

A disabled plugin entry is absent from the runtime target, but the
read-only guard still treated it as the owner, so a user-level fallback
could only exist if it predated the plugin disable. Relax the shared
guard: disabled plugin descriptors never block mutations (disabled
project entries still shadow the user file and keep their rejection).

* fix(node-sdk): close the v2 session-MCP parity gaps

A v2 reconnect with an explicit enabled:false replacement config used
connect()'s upsert semantics — closing the live client and reporting
success where v1's manager reconnect rejects before applying anything;
reject disabled replacements up front with the same error. And a persisted
v2 session add never consulted the workspace config, so a same-named
project-layer entry was silently shadowed: the user-level write never
takes effect while the direct workspace-manager upsert displaces the
project config for every live session. Resolve the workspace layers and
reject like v1's read-only rule.

* fix(agent-core): keep __proto__-named MCP servers through config parsing

A z.record() parse rebuilds its output via property assignment, so a
server literally named __proto__ hit the prototype setter and vanished
before validation; the layer merge then repeated the same trap with plain
object accumulators. Parse the server map entry-by-entry over the JSON own
keys and accumulate into null-prototype maps, so session startup and the
unified registry keep the declared server and its origin.

* fix(node-sdk): begin v2 MCP auth against a fresh OAuth service

The v2 begin path ran through the cached globalMcpOAuth, whose providers
snapshot tokens at construction: a grant another process saved (or reset)
after that cache materialized was invisible, so begin could open a browser
flow over a valid grant, or report already-authorized off a removed one.
Build the service per call — the read path and the verify probes already
do — and route the status list through the same helper. The test fixture
grows a real token endpoint honoring one rotating refresh token; the
regression fails against the cached-service implementation on v2.

* fix(agent-core): broadcast SDK-driven MCP token invalidations to live sessions

* test(agent-core-v2): give the no-op reconnect test runtime plumbing

The branch added the case against a bare McpConnectionManager, but #2961
made stdio connects resolve the runtime through runtimeResolver, matching
every other case in the file.
2026-08-17 13:19:51 +08:00
7Sageer
d833a1a893
feat: engine-native image references via kimi-file:// media resolver (#2593)
* feat: engine-native image references via kimi-file:// media resolver

* fix(agent-core-v2): regenerate state manifest for media resolver rename

* feat(agent-core-v2): add audio MediaKind and tag/ref fold helpers to media ref contract

* fix(agent-core-v2): synthesize image path tag when degrading bare file references

* fix(agent-core-v2): scrub dangling alias re-exports in contract type generator

* feat(transcript): project paired media tag+ref as single attachments in read models

* fix(kimi-code): fall back to inline image when cache write fails after upload

* fix(agent-core-v2): pair media path tags with refs by adjacency and path, keep unpaired tags

* fix(kap-server): fold media tag+ref pairs out of prompt snapshot projection

* fix(kap-server): list attachment-only prompts as empty user messages

* fix(kap-server): keep live attachment ids across transcript overlay and heal

* fix(kap-server): keep promptAttachments off the legacy session event wire

* fix(kap-server): inherit the backfilled turn header on mid-turn terminal projection

A projector that attached after turn.started built the terminal turn.upsert
with an empty header, and the whole-header replace downstream wiped the
backfilled origin / prompt / attachmentIds — only the debounced best-effort
heal could restore them. Fall back to the producer store's seeded header
(via a new optional ProjectorLookups.turn) when currentTurn misses, and
cover the mid-turn attach path with a service-level regression test.

* refactor(agent-core-v2): move media ref contract out of kosong into agent/media

The kimi-file:// daemon reference grammar, media path tags, and the tag/ref
fold are engine-internal conventions, not provider-wire contract; keep
src/kosong untouched. Root exports and SDK re-exports are unchanged.

* feat(agent-core-v2): materialize prompt media into the session media dir

Pasted and uploaded media now materialize under the session's own media/
dir instead of the shared cache, so the copies follow the session's
lifecycle: fork carries them along, session deletion cleans them up.

A new Session-scope ISessionMediaStore owns the dir: atomic tmp+rename
materialization with a unified extension policy, and canonical-vs-hint
display-path resolution. The persisted ?path= is a write-time snapshot —
readers prefer the session-canonical location, so fork and home relocation
never hand the model a dead path. Prompt intake normalizes every daemon
reference through the single enqueue funnel (REST edge, SDK prompt/steer,
gateway), serialized in arrival order to keep the FIFO across the async
file I/O. The kap-server edge materializes through the same store with a
shared-cache fallback, and the request-time resolver refreshes stale
persisted and memoized path tags; a claimed video reference degrades to
its tag alone instead of duplicating it.

* fix(agent-core-v2): take prompt media intake off the enqueue critical path

The record now joins the FIFO synchronously and its daemon-ref intake runs
as a per-record promise, awaited by the launch and steer paths before the
message is consumed — queue order, list/abort visibility, and prompt
submission latency no longer wait on file I/O, and a slow intake no longer
head-of-line blocks later prompts. The launching record is tracked so abort
and clear stay reachable inside the launch window; startNext re-checks
cancellation after every await (intake race, hook, turn admission), a
cancelled record is never re-queued, and a compaction requeue waits for
onDidFinishCompaction instead of busy-looping the scheduler.

* fix(agent-core-v2): record the claiming ref in the media path-tag pairing

pairMediaPathTagRefs now exposes claimingRefByTagIndex, and claimingRefIndex
reads it instead of recovering the claimer by path equality — which
mis-attributed a tag when two different fileIds carried the same path in an
interleaved sequence, breaking the pair and leaking the tag as user text.
Also covers the memoized-video-tag claimed-drop branch.

* fix(transcript): fold upload pairs in user-slash turns and pin pairing parity

The cold rebuild's user-slash branch now folds the turn-opening input like
any user turn (claimed tag out of the prompt text, one attachment entity),
matching the live projection. The ref extraction is consolidated into the
contract module (daemonFileRefFromPairingPart, the mirror of the engine's
daemonFileRefFromPart) and the mirror carries the new claimingRefByTagIndex
map. A new kap-server parity test imports both implementations and asserts
identical pairings over shared fixtures, so the engine/mirror pair can no
longer drift silently.

* fix(kap-server): fold upload media tags out of the search index

The global search indexer concatenated every text part of a persisted user
message, so the upload pair's <image path> tag made pure-image prompts
searchable and wrote the materialization path into the index — breaking the
module's documented pure-image invariant and diverging from the live route.
textOfContent now folds the pair like every other read model (with a
fold-safe coercion for malformed wire parts). Also pins the prompt-media
cache-dir fallback with a read-only session media dir test (skipped as root).

* feat(node-sdk): re-export the media fold helpers and cover the v1 uploadFile rejection

foldMediaPathTagRefs and matchSingleMediaPathTag join the daemon
file-reference helper re-exports so hosts can fold the upload tag+ref pair
without importing agent-core-v2; the v1 harness's uploadFile not_implemented
rejection is pinned by a test.

* fix(kimi-code): fold upload pairs in replay/export and keep media tags atomic in steer input

Resumed-session replay rendered the upload pair raw — the <image path> tag
as user text and the kimi-file:// url as an XML-ish reference — and the
markdown export leaked the tag into both the turn body and the overview
topic. contentPartsToText and the exporter now fold the pair, and daemon
references render as a bare [image]/[video] placeholder. combineSteerInput
moves to tui/utils/steer-input and no longer merges a standalone media tag
into adjacent text, which would have broken the engine-side pairing for
steered image messages.

* fix(kimi-code): drop the steer separator before a leading media tag

A queued pure-image message opens with a standalone `<media path>` tag,
which combineSteerInput keeps atomic. With the previous item ending in a
media part, the '\n\n' separator landed as a stranded whitespace-only text
part between the media part and the tag, normalizePromptInput rejected the
steer, and the already-cleared queue lost the messages. Treat a leading
standalone tag as media so the separator is dropped there.

* fix: clean staged media lifecycle

* refactor(agent-core-v2): narrow the mediaRef root exports and drop a deprecated alias

* fix: keep staged media alive through turn

* fix(agent-core-v2): reject non-upload ids at the session media store

A daemon reference's fileId becomes a storage key in the session media
store, but only the file domain validated the id shape — a crafted
kimi-file://<id> reaching the request-time resolver's canonical-read
fallback could traverse out of the session media dir. Share the file
domain's id regex and guard every store entry point: reads miss,
materialize declines, and the display path falls back to the hint.

* fix(kap-server): project steered prompt content without leaking daemon refs

prompt.steered published the raw engine content parts — kimi-file://
refs carrying the absolute materialization path plus the paired
<media path> tag — to both the legacy session_event wire (whose schema
declares the protocol content shape) and the transcript prompt entity.
Route both through one shared prompt-content projection: the upload
pair folds into a single {kind:'file'} part, matching the REST prompt
list and the no-path-leak rule every sibling surface already follows.

* refactor: align daemon-ref naming and drop a duplicate re-export

The deprecated videoResolverService alias also re-exported
mediaResolvedKey, which made the package root's star exports ambiguous
and silently dropped the name. The new transcript contract mirror now
uses the canonical daemon-ref vocabulary instead of the deprecated
kimi-file spelling.

* test(agent-core-v2): pin image abort rethrow, video canonical read-through, release-once

Mirror the video abort contract on the new image path (an aborted read
cancels the request instead of degrading to a tag), cover the video
fallback that uploads the session-canonical bytes after the transient
upload is released, and assert the staged-upload release fires exactly
once on the intake success path.

* fix(kimi-code): bind goal-steer staging leases to the running turn

sendMessageInternal read the turn context only after beginSessionRequest
had cleared it, so a steer buffered into a running goal turn never got
its staging lease bound — the staged daemon upload and cache copies
lived until session close instead of being released at the consuming
turn's end. Capture the live turn id before the reset (only while a
turn is actually streaming; the id outlives its turn otherwise).

Also move the staging-lease state machine off the KimiTUI coordinator
into a self-contained StagingLeaseTracker with injected effects, drop
the duplicate media-tag builder in image-placeholder in favor of the
SDK helper, and fix the paste-in-flight comment to match the gate's
real granularity.

* fix(kap-server): project prompt.queued content without leaking daemon refs

The broadcaster projected prompt.steered and stripped turn.started
attachments but forwarded prompt.queued raw, leaking kimi-file:// URLs
and absolute materialization paths to every subscribed WS connection
and the journal. Fold the tag+ref pair into a {kind:'file'} part, same
as steered.

* fix: keep compressed uploads retrievable and close the steer abort window

Two review fixes around prompt media intake:

- The compressed re-save was released right after intake (and carried a
  1h expiry) while every client read model projects its file id,
  leaving historical compressed images unfetchable. Keep the re-save as
  an ordinary upload; roll it back only when preparation or submission
  fails before the engine takes the prompt. The engine's
  PromptInput.release hook loses its only producer and is removed.
- A prompt aborted while its steer awaited the loop's step assignment
  was flipped back to 'steered' and its content could still
  materialize into a later turn. Re-check the reservations after the
  assignment await and abort the undispatched request when the check
  fails.

* perf(agent-core-v2): memoize inlined image parts across request steps

A successful image inline depends only on the immutable upload bytes, so
it is memoized per file id (size-bounded) in media.resolved and reused
across steps, retries, and media-recovery reprojections instead of
re-reading and re-encoding on every request. Degrade forms are never
memoized since they depend on the message's tag pairing. Also make the
never-empty message placeholder kind-aware (video vs image).

* refactor: author media tag+ref pairs in the engine prompt intake

Edges (TUI, kap-server REST) now submit bare kimi-file references and the
engine intake materializes the bytes, synthesizes the paired media path
tag, and falls back to the shared cache dir when the session store is
unavailable, replacing per-edge pair construction and duplicate
materialization copies.

Thread the prompt id from submission through to turn.started (REST
prompt_id, WS event, SDK prompt option) so the TUI binds staged-media
leases to turns exactly; the origin heuristic stays as fallback and
ambiguous claims now surface a staging_lease_invariant telemetry warning.

Also lands the pending resendable-extraction fix for cache-hint resubmits
after a session switch.

* fix: decouple media persistence from prompt intake

* refactor(agent-core-v2): project the turn prompt in a single fold pass

* test: slim redundant media-ref coverage across layers

Fold duplicate pinning of the same media tag+ref rules into shared
helpers and it.each tables, and drop assertions that restate behavior
already covered at another layer:

- drop the kimiFileUrl alias describe (mediaRef.test.ts covers the
  aliased functions with more cases)
- drop pairMediaPathTagRefs describe in favor of the parity fixtures
- merge the identical prompt.steered/prompt.queued broadcast tests
- parameterize the resolver degradation matrix and prompt intake
  fixtures (enqueueMedia/gatedImage/expectMediaPair helpers)
- drop REST-level context-memory pairing assertions (engine-level
  intake tests pin the same shapes); keep the caption->system-reminder
  assertion, the only cover of extractCompressionCaptions
- drop the turn-finish-during-intake steer-cancel vector and the
  switch-session release driver test (unit-level lease tests remain)

Net -762 lines; 645 tests green across agent-core-v2, kap-server,
transcript, node-sdk, klient, and the TUI.

* chore: fix oxlint warnings introduced by image-file-ref changes

* fix: harden image file reference lifecycle

* fix: close image reference lifecycle gaps

* fix: preserve session media paths on replay

* chore: streamline image-file-ref changesets

* refactor: make daemon media references self-contained, dropping tag+ref pairing

A daemon-ref media part now carries everything a read model needs — the
kind from the part type and the materialization path from the reference's
`?path=` — so prompt intake no longer authors a paired `<media path>`
tag, and the pairing/fold machinery (pairMediaPathTagRefs /
foldMediaPathTagRefs and their mirror copy) is deleted across the engine,
transcript, kap-server, node-sdk, and the TUI. The request-time resolver
synthesizes the degrade tag from the reference path whenever bytes cannot
reach the provider. Standalone tags stay user-visible text, and never
reach the search index or prompt metadata.

* fix: reconcile image file references with main after rebase

Main removed the agent RPC aggregation layer (agent/rpc) and moved
LifecycleScope to app/scopes. Fold the branch's RPC-side behavior into
the new structure: PromptPayload carries promptId/disabledTools, and
AgentPromptService.submit admits the client-chosen id through the
reservation (duplicate rejects before any session state changes) and
applies the denylist through toolPolicy. Regenerate the wire/state
manifests.

* fix(kimi-code): run paste ingestion in the background, wait bounded at submit

The paste callback awaited compression + original persistence + the
daemon upload while CustomEditor queued every keystroke, so a slow
ingestion stalled all typing. Settle the callback once the placeholder
lands and track the rest as ImageAttachment.pending; the send path gives
a referenced pending ingestion a bounded wait (2s) so paste-then-Enter
still submits the compressed/daemon-ref form, and falls back to the
inline form when ingestion has not finished. Media-free submits stay
fully synchronous.

* fix(protocol): mirror prompt_id in the shared prompt submission schema

kap-server's local REST schema accepts a client-chosen prompt_id, but
the shared promptSubmissionSchema stripped it as an unknown key, so
clients validating through @moonshot-ai/protocol lost the id and the
turn.started promptId correlation never matched.

* fix(klient): normalize file-store errors to public RPC errors on both transports

The fileService save/get wire adaptation ran outside the dispatcher's
error normalization, so a stale or expired upload id surfaced as the
engine's raw Error2 on the memory transport and as a generic 50001 on
ipc. Map file.not_found to the public NOT_FOUND RPCError in the shared
dispatcher so both transports reject identically, and pin the parity in
the conformance suite.

* fix(agent-core-v2): keep launching media prompts visible in the queue snapshot

startNext shifts the launching record out of pending before its media
intake settles, so list()/GET /prompts reported neither an active nor a
queued prompt during the intake window even though the submission was
accepted and abortable. Report the launching record as still queued,
matching the prompt.queued event already published for it.

* fix(node-sdk): strip internal promptAttachments from SDK turn.started events

The in-process v2 event mapper forwarded the whole domain event, so SDK
session.onEvent consumers saw the transcript-projection-only
promptAttachments field that kap-server explicitly strips from the WS
wire event. Drop it in the mapper so both consumers share the same
turn.started field set.

* fix(kimi-code): align staging lease id multiplicity with retain count

A lease's flat id list conflated two cases: one submission referencing
the same image twice (one retain) and a batched steer merging two queued
messages sharing the image (two retains). Occurrence-wise release
over-consumed in the first case and batch-wise release would
under-consume in the second. Dedupe each extraction's ids at the lease
creation sites so list multiplicity always equals the retain count, and
release one retain per occurrence.

* fix(agent-core-v2): check video_in before honoring memoized video uploads

The video memo hit path returned a cached ms:// part before the current
model's capability check, so switching to a same-provider model with
video_in:false sent a video part the model cannot accept instead of
degrading to the path tag. Gate on capability first, mirroring the image
strategy.

* fix(kimi-code): keep recalled queued media staged instead of releasing it

Recalling a queued media prompt into the editor is not a discard, but
the recall path released the staged files: image attachments lost their
daemon upload (resubmit silently downgraded to inline), and a recalled
video's cache copy was deleted even though re-materialization needs a
source that may already be gone. Recall now consumes only the retain
(the next submit re-retains), retires the cache copy to session
lifetime, and rebases the video attachment onto that copy.

* fix(agent-core-v2): count launching media prompts in prompt.queued queueLength

startNext shifts the record into launchingItem before publishQueued
computes the count, so a media prompt's prompt.queued reported
queueLength 0 even though the prompt is accepted, abortable, and listed
as queued. Compute the count from the same snapshot list() exposes.

* refactor(agent-core-v2): drop the session media shared-cache fallback

Intake keeps the upload-backed reference when the canonical write fails
instead of double-writing into an unowned global cache scope; the session
media store's reads collapse to the canonical scope, and non-filesystem
deployments no longer write every media blob twice.

* refactor(agent-core-v2): stop persisting materialization paths in daemon file references

The kimi-file:// reference persisted in context memory bundled a durable
identity (fileId) with a perishable machine-local absolute path (?path=),
which forked sessions and home relocations would stale. The reference now
carries only the file id; the display path is derived from the session
media store by file id at read time. Parsers tolerate and strip the legacy
?path= query so old records keep resolving.

* fix(agent-core-v2): skip atomic-write temp siblings in session media by-id resolution

The fs backend stages atomic writes at <key>.tmp.<pid>.<hex> next to the
target key, and the media store's prefix-listing predicate matched them, so
a lookup racing an unfinished materialize could return the partial copy as
the canonical file.

* fix(kimi-code): close the staging-lease gap between extraction and dispatch

Create the staging lease right after extraction so every pre-dispatch exit
releases through the tracker: validation/session failures release it,
queueing defers it to the queue item's raw ids/paths, and the cache-hint
stash takes over ownership. A forgotten exit now degrades to an unclaimed
lease swept at session close instead of a permanently retained upload.

The cache-hint restore exits (dismiss, chained restore, session switch
during fetch, failed compact/new-session) previously returned only the
text to the editor, leaking the extraction's retains and staged cache
copies. They now go through queue-recall semantics: retains are consumed,
staged copies retire, and recalled videos rebase onto them.

* fix(agent-core-v2): bound the inline image memo with a private byte-budgeted LRU

A memoized inline image part pins a multi-MB base64 string, and the agent
state registry's snapshot/inspect path serializes every registered state
in full — so the memo no longer lives in agentState. It is now a private
per-file-id LRU with the existing 8MB per-entry cap plus a 64MB total
budget; eviction simply re-reads the bytes on the next request. The video
memo stays in agentState.

* fix(kap-server): fall back to the staged upload on the session media route

Prompt intake materializes bytes into the session media store
asynchronously and best-effort, but a session_media ref is projected to
clients as soon as the prompt is queued — so the download route could 404
during the intake window, and forever after an intake failure. The route
now reads the canonical session store first and falls back to the App-scope
staged upload, adapting it to the same served shape; only a double miss is
a 404. The header note also records that resolving the store resumes cold
sessions, an accepted short-term semantic with a TODO for a cold-read
channel.
2026-08-17 13:11:28 +08:00
Luyu Cheng
61591bce09
feat(agent-core-v2): bundle multiple skill activations into one prompt submission (#2934)
* feat(agent-core-v2): support grouped multi-skill prompt submissions

Add IAgentSkillService.promptWithSkills: one or more skill activations
are validated up front (an unknown or empty submission rejects with no
side effects), recorded with a shared submissionId, and enqueued ahead
of the prompt through the prompt queue's messagesBefore support, so the
whole group materializes atomically as a single turn. Undo cuts, the
transcript projection, and the undo precheck treat the group as one
unit (stopping at the next anchor even when submission ids collide);
hook-result messages are skipped like injections during those walks.
Submit hooks run against every message of the group, and user-slash
skill activations count as user-submitted content for the UserPromptSubmit
hook's origin filter.

Surface it through the contract layers: protocol gains submissionId on
the user / skill_activation origins and on the skill.activated event
(kap-server zod mirrored), klient exposes agentSkillContract.promptWithSkills
with parity assertions, and the SDK grows session.promptWithSkills —
implemented on the v2 engine and rejecting loudly on the deprecated v1
engine, which is otherwise untouched.

* fix(agent-core-v2): reject empty skill lists in grouped prompt submissions

- Validate that promptWithSkills receives at least one skill, enforced in
  the engine and as a non-empty constraint in the klient wire schema.
- Restore the released versions and changelog sections for agent-core-v2,
  klient, and node-sdk that the branch cut had reverted.
- Move statement-level narration into the owning file headers per the
  package comment conventions.
- Align the hook-result undo tests with the reachable record ordering
  (hook results are recorded before the group materializes).

* refactor(agent-core-v2): bundle grouped skill activations into the prompt message

Replace the submissionId-correlated message group with a single bundled
user message: the rendered skill blocks precede the caller's parts in the
content, and every activation's metadata rides the prompt origin's new
skillActivations field. The bundle is one anchor by construction, so undo
needs no group-cutting logic and the messagesBefore prompt seam disappears;
the submit hook fires once per submission. skill.activated still fires per
skill (transient ops, live-only); resume rebuilds the per-skill view from
the prompt origin. Contract chain (protocol, kap-server, klient, node-sdk)
drops submissionId accordingly.

* fix(agent-core-v2): keep bundled skill blocks out of prompt-facing projections

- The transcript cold rebuild expands a bundled prompt's origin
  skillActivations back into per-skill markers (the live path already
  projects them from skill.activated events).
- turn.started.prompt, the session title excerpt source, and the fork
  lastPrompt now derive from the caller's own parts, excluding the
  rendered skill blocks the engine prepends to the bundled content.
- Drop the redundant undefined unions from the new origin fields.
- Move the activateSkill test narration into the file header.
2026-08-17 00:09:18 +08:00
Haozhe
84da6629b1
refactor(agent-core-v2): decouple workspace from session DI via runtime binding (#2961)
* refactor(agent-core-v2): decouple workspace from session DI via runtime binding

* fix(agent-core-v2): unblock session external hooks and scope workspaceMcp seeds

- externalHooksService: inject App-level ISessionManager instead of the
  unregistered ISessionLifecycleService so SessionStart/SessionEnd hooks
  actually activate in production; keep sessionId matching and tolerate
  absent lifecycle events
- workspaceMcpService: ignore onWillCreateSession events whose session
  belongs to another workspace, preventing cross-workspace
  ISessionMcpHandle seed overrides
- update externalHooks integration tests, agent harness, and workspaceMcp
  tests; add reloadSources coverage in skillCatalog tests

* fix(agent-core-v2): honor the bound runtime in prompt context, swarm spawn, and ACP sessions

- map system-prompt cwd, directory listing, and additional dirs through
  RuntimeWorkspaceView, and skip the listing when the bound runtime has
  no fs capability
- pass the caller agent's runtime binding to AgentSwarm child creation
  and prompt-prefix execution instead of hardcoding local
- expose the ACP client filesystem through the ACP session runtime and
  build its shell/path environment from the probed host instead of
  hardcoded Linux
- dispatch klient facade createChild to sessionManager.createChild so
  child sessions keep their parent markers

* fix(agent-core-v2): resolve routed fs and tool paths with runtime path semantics

- WorkspaceFsService resolves via the bound runtime's RuntimePath (extended with basename/dirname) instead of node:path, so mapped roots such as C:\\repo stay runtime-local.
- Read/Write/Glob/Grep pass skill roots through mapRoots via RuntimeWorkspaceView input, matching Edit.
- acp-server unbinds session runtimes on session/close, not only on delete.
- apps/kimi-code drops the /runtime slash command; SDK runtime methods stay.

* fix(agent-core-v2): retire idle session controllers, untrack disposed runtime resources, and rebuild fs watches on generation replace

* fix(agent-core-v2): resolve oxlint errors in runtime lifecycle fixes

* fix(kap-server): untrack download stream from runtime generation on completion

* fix(kap-server): drop meaningless void operator on tracked dispose
2026-08-16 20:24:12 +08:00
tpoisonooo
f492cd7c9e
feat(agent-core): add tower command to orchestrate multi-agents (#2633)
* feat(packages): implement cowork

feat(packages): update throttle control

feat(agent-core): rename to /tower

feat(agent-core-v2): support tower mode

fix(packages): keep tower teardown from stranding submodule worktrees

A plain `git worktree remove` refuses worktrees containing initialized
submodules even when they are clean, so tower teardown silently left
behind exactly the worktrees whose workers had run builds (the failure
only reached the tool report, never the activity log).

The dirty check is the data-loss gate; once it passes, removal always
passes --force (harmless on a clean worktree, and precisely what
bypasses git's submodule refusal). Kept and failed removals now also
land in the activity log as worktree.keep / worktree.remove.failed.

feat(packages): allow the tower to AskUserQuestion, workers still cannot

The tower-mode AskUserQuestion deny only ever fired on the tower itself:
workers never enter tower mode, and their tower-worker profile simply
does not list the tool. Drop the deny so the tower can clarify
requirements with the human up front; workers and reviewers stay
ask-less and escalate via TowerSend. Auto permission mode still
disables AskUserQuestion for everyone.

fix(agent-core-v2): import LifecycleScope from #/app/scopes

main moved the enum out of #/_base/di/scope; follow the new location in
the two tower services.

test(agent-core-v2): refresh fullCompaction token expectations

main's #2699 counts compaction tokens on the full-request basis, so the
tower tool schemas (default registry) and the /tower skill catalog entry
(system prompt) shift the pinned numbers: +2789 with the default tool
set, +173 with the explicit harness tool list. The 20k-window test keeps
its shape with a 22k window so the post-compaction floor still fits.

feat(agent-core-v2): tower command support secondary model

fix(tower): disable todo-list tool

feat(tower): reviewer keep primary model

fix(tower): tower worker call for authroization

update

* refactor(tower): drop agent-core-v1 version

* feat(tower): remove builtin.ts

* fix(agent-core-v2): verify the recorded base branch before tower merges

* fix(agent-core-v2): activate tower missions only after a successful spawn

* chore(kap-server): correct the search-service activation comment

* fix(tower): allowActivationWhileBusy for all skill

* update

* update

---------

Co-authored-by: konghuanjun <konghuanjun@moonshot.ai>
2026-08-16 15:13:42 +08:00
Grapedge
7475c2e2e3
feat(vscode): switch the extension to the v2 engine with a rollback switch (#2916)
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-vscode-legacy (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 / Publish native release assets (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
The extension now runs on the agent-core-v2 engine by default. The
interface, sessions, and workflows do not change. Two rollback paths
exist, and one function makes the decision
(config/vscode-settings.ts):

- the kimi.useAgentCoreV1 setting (temporary; a window reload applies
  the change);
- the KIMI_CODE_LEGACY_FLAG environment variable, which wins over the
  setting and has the same semantics as in the CLI.

An engine startup failure shows an explicit error that names the
rollback setting. There is no silent fallback. CI runs the extension
test suite on both engines: the sharded run covers the default v2
engine, and a new test-vscode-legacy job reruns the suite with
KIMI_CODE_LEGACY_FLAG=1.

To keep the v2 path identical to v1 for every method the extension
uses, this change also completes the v2-backed SDK client and the v2
engine:

- Implement session deletion in the v2 SDK client.
- Implement fork truncation at a turn index in the v2 engine, with the
  same rules as v1, and reject a fork while the source session has an
  active turn.
- Stop the session-level /init run when the turn is cancelled, as v1
  does.
- Read session metadata without the archived field as not-archived, so
  sessions written by the v1 engine open correctly.

The SDK parity suite now covers session deletion, cancel, and fork
truncation. The known-difference list for the methods the extension
uses is empty.
2026-08-14 17:22:10 +08:00
qer
741708f948
feat(kap-server): add plugin marketplace and capability REST routes (#2868)
* feat(agent-core-v2): surface a machine-key note from capability installs

CapabilityEntry.install now resolves an optional note exposed through
CapabilityInstallProgress.note (wire-visible). The webbridge entry
returns 'user-skill-migrated' when it migrates a pre-existing
standalone skill copy onto the plugin-managed one — clients can
localize the migration instead of the skill silently disappearing
from the user's directory.

* feat(kap-server): add plugin management and capability REST routes

Expose the App-scope plugin and capability services over the wire so
non-CLI hosts (desktop, web) can manage plugins and built-in
capabilities end to end:

- GET  /api/v1/plugins, POST /api/v1/plugins {source},
  POST /api/v1/plugins/{id}:{enable,disable,remove}
- GET  /api/v1/plugins/marketplace — catalog (pluginMarketplaceUrl
  server option / KIMI_CODE_PLUGIN_MARKETPLACE_URL env / production
  default) merged on demand with live install state; updateAvailable
  only on strict semver catalog > installed (no semver dependency)
- GET  /api/v1/capabilities, GET /api/v1/capabilities/{id},
  POST /api/v1/capabilities/{id}:install with client-polled progress
- New wire codes 40418 capability.not_found, 40419 plugin.not_found,
  40923 capability.install_in_progress, 40924 capability.unsupported

Mutations flow through IPluginService, so they serialize with other
install paths and fire onDidReload (session skill catalogs and the
capability shelf-install hook converge).

* fix(kap-server): map plugin input errors to 4xx and correct the unsupported test code

- mapPluginError now translates the domain's validation.failed (40001)
  and fs.path_not_found (40409) instead of collapsing client-fixable
  input mistakes (relative source, nonexistent local path) into a
  50001 internal error
- the non-macOS capability install test expected 40923, which this
  branch assigns to capability.install_in_progress; the unsupported
  code is 40924 (macOS runners skip the case, which is why it only
  fails on Linux/Windows CI)

* fix(kap-server): resolve catalog-relative marketplace sources and widen the unsupported-test skip

- The production CDN catalog carries sources relative to the catalog
  URL (./official/*.zip); clients handing them back to POST /plugins
  would hit the local-path normalizer's 40001. Resolve entry sources
  against the configured catalog URL so every returned source is
  directly installable.
- The 40924 install-rejection test only skipped macOS, but kimi-cu is
  also supported on Windows x64 — running it there would start the
  real installer. Skip on every supported platform.

* fix(kap-server): accept the legacy url/downloadUrl marketplace source aliases

Custom catalogs that the CLI already accepts can carry an entry's source
under url or downloadUrl instead of source; the route's strict schema
rejected the whole catalog with 50001. Normalize the aliases before
validation (same precedence as the CLI parser) so those catalogs keep
working through /api/v1/plugins/marketplace.

* fix(kap-server): support local marketplace catalogs and drop conditional spreads

- KIMI_CODE_PLUGIN_MARKETPLACE_URL accepts a plain path or file://
  catalog in the CLI loader; the route only fetched over HTTP, so local
  catalogs 50001'd for desktop/web hosts. Read local catalogs from disk
  and resolve their relative sources against the catalog's directory.
- Replace the marketplace mapping's conditional spreads with direct
  possibly-undefined properties per the repo rule.

* fix: surface capability install notes through klient and convert file:// entry sources

- The klient capabilities contract omitted install.note, so zod parsing
  stripped it and facade callers (node-sdk, TUI) never saw
  'user-skill-migrated'. Add the field and pin it in the facade test
  fixture.
- A marketplace entry source given as a file:// URL fell through to the
  relative-branch and came back as a garbage path; convert with
  fileURLToPath so the advertised source stays installable.

* test(kap-server): keep the new route tests portable to Windows x64

- The capabilities list assertion treated every non-macOS host as
  unsupported, but kimi-cu is supported on Windows x64 — derive the
  expectation from the same platform predicate.
- file:///abs/... is not a valid absolute file URL on Windows (no drive
  root); build the fixture with pathToFileURL from a temp path instead.

* refactor: align the capability note and test helper with repo conventions

- agent-core-v2 keeps explanatory docs in the top-of-file block only;
  the note contract already lives in the capability types header, so
  drop the two member-level doc blocks.
- The plugins route test helper sets the optional fetch body directly
  instead of via a conditional spread.

* fix(kap-server): expand ~ in local marketplace catalog paths

The CLI loader expands ~/ against the home directory; the route read
the path literally, so KIMI_CODE_PLUGIN_MARKETPLACE_URL=~/catalog.json
50001'd for desktop/web hosts while working in the CLI. Share one
localCatalogPath helper (file:// conversion + tilde expansion) between
the catalog read and the relative-source resolver.

* fix(kap-server): expand home-relative marketplace entry sources

A catalog entry with source '~/...' fell through to the catalog-relative
branch and came back as <catalog-dir>/~/... — unresolvable by POST
/plugins. Expand ~ via the shared helper before the absolute/relative
decision.

* fix(kap-server): match CLI field semantics for source aliases and stub the Windows home

- A blank or non-string source no longer shadows the url/downloadUrl
  aliases; the first valid (non-blank, trimmed) of source/url/downloadUrl
  wins, mirroring the CLI parser's stringField.
- The tilde test also stubs USERPROFILE so os.homedir() resolves to the
  fixture home on Windows runners.

* fix(kap-server): read a blank marketplace tier as missing

The CLI parser trims tier and treats a blank as absent (third-party);
the route's enum rejected the whole catalog with 50001. Normalize the
tier alongside the source aliases in the same preprocess.

* fix(kap-server): derive marketplace versions from GitHub release sources

Entries that omit version but encode it in a GitHub release/tag (or
tree/commit) source never surfaced updateAvailable. Derive the version
from the resolved source — same URL shapes as the CLI parser, validated
with the route's strict x.y.z rule (no semver dependency).

* fix(kap-server): fail catalog validation on a source with no usable value

A whitespace-only source with no valid alias passed z.string().min(1)
untrimmed and resolved against the catalog URL into nonsense. Drop the
key during normalization so the schema reports the entry as missing its
source (same outcome as the CLI's 'must define source').

* fix(kap-server): resolve latest versions for bare GitHub marketplace entries

A catalog row whose source is a bare GitHub repo (the production curated
rows are shaped this way) kept version undefined, so updateAvailable
never fired for exactly the entries most likely to update. Resolve the
latest release tag through the /releases/latest redirect — the UI route,
not the rate-limited API — same as the CLI, degrading to no version on
any failure.

* docs(kap-server): note the marketplace version resolution in the plugins route header

* feat(kap-server): mark capability wiring rows in the marketplace response

A client following only /plugins/marketplace + POST /plugins would
install a capability's wiring plugin without its binary runtime, with
no wire-level way to tell. Entries whose id matches a capability's
wiring plugin now carry capabilityId, so clients route them through
/capabilities/{id}:install — the client-side routing pattern the CLI
established (the upstream design that replaced the server-side hook).

* fix(kap-server): fall back to the source-checkout catalog for the default location

When the marketplace location is the built-in default (no server option
or env override) and the fetch fails, read the repo checkout's own
plugins/marketplace.json — the CLI loader's behavior for offline
source-checkout dev. An explicitly configured catalog still fails hard
with 50001. Bundled installs have no checkout file, so the fallback
simply never fires there.

* fix(kap-server): resolve fallback catalog sources against the fallback file

readMarketplaceCatalog returned only the JSON, so entries from the
source-checkout fallback resolved their relative sources against the
(unreachable) CDN URL — coming back as unusable https paths instead of
local directories. The reader now returns the location actually read,
and source resolution uses it.

* fix(kap-server): honor the CLI's marketplace metadata aliases

Custom catalogs using name / shortDescription / websiteURL (accepted by
the CLI parser) lost those fields to schema stripping, falling back to
the entry id. Normalize the aliases in the same preprocess as the
source/tier normalization.

* fix(kap-server): filter marketplace keywords instead of rejecting the catalog

A keywords array with non-string or blank members failed the strict
schema and took the whole catalog down with 50001. Normalize to the CLI
parser's semantics: non-array reads as missing, arrays keep trimmed
non-blank strings only.

* fix(kap-server): treat a blank or non-string marketplace version as missing

The CLI parser reads version through its lenient stringField and falls
through to source-derived versions; the route's schema rejected a
numeric version with 50001 for the whole catalog. Normalize version in
the preprocess like the other fields — the gh-plugin fixture now
carries a numeric version and still derives 2.0.0 from its tag source.

* fix(kap-server): trim marketplace entry ids before the install-state join

A whitespace-padded id survived validation raw and never matched the
installed records (updateAvailable silently lost). Normalize the id in
the preprocess — trimmed, blank rejected — matching the CLI's
requiredString.

* fix(kap-server): gate capability markers to the default catalog

A custom catalog (env or server option) may legitimately carry a
same-id fork of a capability's wiring plugin; marking it capabilityId
would route users to the built-in install. Apply the marker only for
the default catalog (including the source-checkout fallback), matching
the CLI injecting built-in rows only for the default catalog.

* fix(kap-server): compare marketplace versions with real semver

The hand-rolled strict x.y.z check rejected valid semver the CLI
accepts (v-prefixed, prerelease tags), so updateAvailable diverged
between CLI and wire clients. Take the semver package (already in the
monorepo via the CLI) for the update check and the two source-derived
version validators.

* fix(kap-server): validate marketplace entry types and count the dev server as default

- Custom catalog rows with an unsupported type (e.g. integration) were
  stripped by the schema and advertised as installable plugins; the CLI
  rejects the catalog outright. Model the same plugin/managed/guide
  vocabulary.
- scripts/dev.mjs marks its repo-owned catalog with
  KIMI_CODE_PLUGIN_MARKETPLACE_FROM_DEV_SERVER=1 — honor the flag in
  the isDefault check so capability markers and the checkout fallback
  behave exactly like the CLI under the dev marketplace.

* fix(kap-server): join capability rows through their platform wiring plugin id

kimi-cu installs its wiring plugin as kimi-cu-win on Windows x64, so a
catalog row keyed kimi-cu never matched the installed record there (no
installed state, no updateAvailable). The row mapping now knows each
capability's wiring plugin ids and joins through them.

* fix(kap-server): map plugin load failures to 40001

An install source pointing at a directory/zip with a missing or invalid
manifest throws plugin.load_failed — a client-fixable input error that
fell through to 50001. Map it to validation.failed alongside the other
input mistakes.

* build(kap-server): align @types/semver with the workspace version

sherif rejects multiple workspace versions of one dependency; the CLI
pins @types/semver at ^7.7.0.

* refactor(agent-core-v2): share the plugin marketplace client/parser across hosts

The kap-server marketplace route grew its own copy of the CLI's catalog
loading/parsing logic (lenient aliases, blank-means-missing fields,
source resolution, GitHub version derivation) — two implementations of
a public, hand-writable format would drift on every catalog change.
Move the read/parse/version machinery into the plugin domain as
app/plugin/marketplace (pure functions, no DI): the CLI keeps a thin
wrapper owning configured-source resolution and its checkout fallback,
and the route keeps only the wire concerns (install-state merge,
capabilityId markers, error envelopes). plugins.ts drops ~230 lines of
duplicated machinery.

One deliberate behavior fix rides along: tilde entry sources now expand
against the home directory at parse time (the CLI previously passed
them through literally, failing later at install validation).

* docs(agent-core-v2): fold the marketplace module's member docs into the file header

The package convention keeps explanatory comments in the top-of-file
block only; the moved parser carried several function/member-level
JSDoc blocks from its CLI home. The header now carries the format
contract, leniency rules, source/version resolution order, built-in
masking semantics, and the fallback gating rule.

* docs(agent-core-v2): drop the remaining statement comments in the marketplace module

The header carries the rationale (update semantics, GitHub ref shapes,
the releases/latest choice); the convention allows nothing beside
statements.

* fix(kimi-code): import the shared marketplace module by its deep path

constant/app.ts is evaluated on every CLI invocation; re-exporting from
the agent-core-v2 root would pull the whole engine module graph into
startup. The package's wildcard subpath export lets both CLI files take
only the pure marketplace module (node builtins + semver).

* feat(kap-server): fan plugin and capability lifecycle out as global WS events

Clients currently poll the plugins/capabilities REST surfaces and can
hold stale rows while another client mutates the set. Publish two global
events instead:

- event.plugin.changed — fired off IPluginService.onDidReload, so any
  install/enable/disable/remove from any client reaches every host
- event.capability.changed — every capability install progress
  transition (CapabilityService gains onDidChangeInstall), so rows
  update live and settle is observable without polling

Both ride the existing global fan-out (no subscription needed) and are
documented in the wire schema registry.

* fix: register the lifecycle events in the wire union and tidy the contract header

- event.plugin.changed / event.capability.changed were declared but not
  part of agentEventSchema, leaving the wire catalog incomplete.
- The onDidChangeInstall member doc moves into the capability contract
  file header (package comment convention).

* feat(protocol): mirror the plugin/capability lifecycle events in the shared WS schema

Clients and e2e harnesses validating server frames against
@moonshot-ai/protocol would reject event.plugin.changed /
event.capability.changed. Register both in the shared catalog (TS
interfaces, zod schemas, and both unions), matching the
model_catalog.changed precedent for global events.

* fix(kap-server): prefer the platform wiring plugin when joining capability rows

A stale same-id record (e.g. a raw kimi-cu plugin next to the real
kimi-cu-win wiring on Windows x64) previously won the join, showing the
wrong installed state and update availability. Capability rows now join
through the wiring plugin ids in platform preference order before
falling back to the catalog id.

* fix(kap-server): put the github metadata of plugin summaries on the wire schema

GitHub-sourced plugin summaries carry github {owner, repo, ref,
installedSha} from the domain; the route serializes raw domain objects,
so the field reached clients undocumented. Declare it in
pluginSummarySchema so the OpenAPI surface matches reality.

* test(node-sdk): cover the new lifecycle events in the exhaustive switch

The event-type exhaustiveness test broke when the shared protocol union
gained event.plugin.changed / event.capability.changed.

* fix(kap-server): mark capability progress events volatile

Per-chunk download progress transitions ride the same fan-out as
durable frames and were being persisted to the __global__ journal —
hundreds of stale frames per install. event.capability.changed is
live-only state, so it joins the volatile list alongside
event.di.unit_changed; the settle frame stays recoverable via a direct
capability read. event.plugin.changed remains durable (rare, and a
reconnecting client should replay it).

* feat(kap-server): inject built-in capability rows into the default catalog response

The checked-in production catalog carries kimi-webbridge but not
kimi-cu — the CLI injects built-in rows client-side, so wire clients
never saw Kimi Computer Use in /plugins/marketplace. For the default
catalog the route now appends supported capabilities the catalog lacks
(static descriptors via ICapabilityService.describeCapabilities — no
detector probes), marked with capabilityId and a capability:<id>
sentinel source so installs still route through the capability
surface.

* fix(kap-server): run injected capability rows through the install-state join

The injected kimi-cu row hardcoded installed: undefined, so an
already-installed capability still read as installable. Injection now
happens before projection, so injected rows get the same backing-plugin
join (installed state, update badge, capabilityId marker) as catalog
rows. Also moves the describeCapabilities note into the contract header
(package comment convention).

* test(kap-server): gate the injected-row assertions on platform support

kimi-cu injects only where supported (macOS / Windows x64); on Linux CI
the row is correctly absent.

* fix(protocol): classify capability progress as volatile in the shared catalog

kap-server never journals event.capability.changed (it is in the
server-local volatile list); shared-protocol clients reading
isVolatileEventType would treat per-chunk progress frames as durable
and replayable. Mirror the classification.

* fix(kap-server): hide capability rows on unsupported platforms

Catalog-carried capability rows (kimi-webbridge in the default catalog)
were marked with capabilityId regardless of host support — on an
unsupported platform clients would route into an impossible capability
install. Rows whose capability is unsupported are now excluded from the
default-catalog response entirely (the CLI hides its built-in rows the
same way).
2026-08-14 15:45:34 +08:00
Haozhe
1414d46028
refactor(agent-core-v2): fold session lifecycle hooks into sessionLifecycle events (#2896) 2026-08-13 21:03:51 +08:00
7Sageer
6be26978b1
feat: auto-generate session titles via the managed chat_title tool (#2351)
* feat: auto-generate session titles via the managed chat_title tool

With the auto-title experimental flag on and a managed OAuth login, the
session title is generated from the first prompt, replacing the
truncated-prompt easy title. A custom title set by the user is never
overwritten, and generation failures degrade silently to the easy title.

- oauth: fetchChatTitle for the platform /tools chat_title method
- agent-core (v1): fire-and-forget generation on the first prompt
- agent-core-v2: sessionTitle domain watching the easy-title event
- kap-server: POST /sessions/{id}/title/generate for manual regeneration

* fix: harden auto-generated session titles

* fix: preserve managed title request headers

* Pair auto-title endpoint overrides with matching OAuth credentials

* fix: preserve legacy custom session titles

* fix: preserve automatic session title invariants

* refactor: keep only the on-demand session title generation interface

Drop the automatic wiring on both engines: the v1 (TUI) first-prompt
trigger and the v2 easy-title event watcher. SessionTitleService's
generateTitle() stays as the single on-demand entry point behind the
auto-title flag, backing the kap-server title/generate route. The
changeset goes away too: with no shipped consumer, the remaining
surface is not user-perceivable.

* feat: generate session title from the first recorded prompts

Record up to three sanitized natural-language prompts in session
metadata (skill / plugin activations excluded) and compose the
chat_title input as order-labeled lines truncated to a 1000-char
budget, falling back to lastPrompt for sessions without recorded
prompts.

* test: make session title race tests deterministic

* Generate session titles from agent conversation history

* fix: reject title generation without user prompts

* fix: bound session title prompt history

* feat: enable session title generation without an experimental flag

* test: cover session title generation through the public REST path

* feat: request session title generation from the TUI after each turn

* Retry auto title generation for prompt-derived session titles

* feat: record session title source and harden the generation lifecycle

- persist titleSource (prompt/generated/custom); skip auto-generation over
  an already-generated title unless forced, and never over a custom one
- plumb the force option from the core through klient and node-sdk to the
  REST title/generate endpoint
- drop the title write-back when the session scope was superseded
  mid-flight, and retry once with a force-refreshed token on a 401
- stop closing sessions a concurrent public resume has handed out in the
  temporary resume paths (generateSessionTitle, renameSession)
- accept session.meta.updated patches without lastPrompt in klient event
  validation, and emit exactly one metadata event per applied title
- remove the retired prompts field heal and drop the changeset (the
  behavior is only perceivable on the experimental v2 engine)

* chore: follow agent-core comment convention

* fix: ignore stale session title callbacks

* fix: preserve session title state invariants

* refactor: seed session lifetime instead of querying the workspace handler

The session title service must not depend on the Workspace-tier handler
registry. The handler now seeds each session scope with an abort signal,
fires it synchronously when a close begins, and the title service carries
the signal on its request, drops the write-back once aborted, and drains
an in-flight generation through the onWillCloseSession hook.

* fix: honor the legacy custom title marker over a stale titleKind

A v1 rename spreads the original state.json document, so an explicit
isCustomTitle: true can travel with a stale titleKind. The explicit
marker now wins on load, and every persist double-writes the derived
isCustomTitle so released v1 builds keep recognizing the custom title.

* fix: serialize session access and expose the session title state

The temporary resume/rename/close paths and the public lifecycle
operations now share a per-session queue, so a public resume can never
receive a handle whose cleanup close is already in flight. Session
summaries carry the canonical title state, letting the TUI skip title
generation for sessions whose title was already generated or customized
instead of re-asking after every turn.

* chore: add session title changesets

* fix: close the session lifecycle races around close and title generation

A close/archive is now tracked in a closing registry from its first
synchronous step until disposal: get/list hide the closing session and
resume waits the close out instead of returning the doomed handle, and
fork waits out an in-flight source close. The title service tracks the
whole generateTitle call as the unit the close hook drains, and the
generated-title write re-checks the lifetime signal inside the serialized
metadata update so an abort landing while the update is queued still
vetoes the write-back.

* feat: project the session title state through the session index

readSummary and the read-model mirror carry titleKind, so listSessions
reports the same canonical title state as a resumed session's summary.

* fix: serialize the remaining session access paths in the SDK

forkSession and explicit-id createSession join the per-session queue, and
the harness resume fast path skips a session whose close is in flight
instead of returning the closing facade (which then failed every call
with session.closed); its late onClose no longer evicts the fresh
session either. The harness rename event now carries isCustomTitle so
the TUI stops asking for a generated title after a local rename.

* fix: detach the external abort listener once the chat title request settles

* fix: harden the session close/archive and create/fork lifecycle

The closing registry now records the operation kind: an archive arriving
during a plain close waits it out and lands the archived flag on the
persisted document instead of riding the close to success, and a failing
close hook no longer strands a half-closed session — the teardown always
completes while the hook error still reaches the caller. create and fork
reserve their target id synchronously with the existence check, so a
concurrent create/fork of the same id loses up front and can never tear
down the winner's scope or directory.

* fix: keep forced title regeneration independent and veto queued title writes atomically

Plain generateTitle calls still coalesce onto one shared in-flight
generation, but a forced regeneration always runs on its own so it is
neither swallowed by a plain call's early exit nor shares its result; the
close hook drains every active generation. The allowWhen veto now runs
inside applyUpdate with no await between the check and the mutation, so
an abort cannot slip into the gap.

* fix: carry the title state through the session index and klient contract

The klient session summary schema no longer strips titleKind, and the
index readSummary honors a legacy isCustomTitle marker over a stale
titleKind, so listSessions reports the same canonical title state as a
resumed session.

* fix: coalesce harness resumes, lock fork targets, and cover the title state end to end

Concurrent public resumeSession calls now share one in-flight resume and
one facade instead of building parallel facades over the same engine
handle (a close on either would strand the other). forkSession takes the
source and target queues in sorted order, so fork(A->X) is atomic against
create(X) and fork(B->X) without an ABBA deadlock. The emitMetaUpdated
patch type drops the redundant undefined union, and the SDK tests now
cover facade coalescing and the title state across list and resume.

* fix: serve the canonical title state from the session index and version the read-model cache

readSummary now derives the title state with the same priority chain as
the metadata document's canonical normalization (explicit custom marker,
valid titleKind, legacy false marker, customTitle, plain title), so list
and resume agree on legacy documents too. Read-model cache entries carry
a summary version stamp and older-stamped entries are treated as cold
misses, so an upgraded reader never serves a stale-shaped summary.

* fix: let the newest title generation request win the write-back

A forced regeneration could be followed on disk by an earlier plain
call's slower backend response. Each generation now carries a
monotonically increasing sequence (assigned only once a request actually
proceeds to generation), and the serialized metadata write is vetoed
unless the writer is still the newest request.

* fix: fold archive into close and own the create/fork rollback

An archive requested during a plain close is applied through the live
metadata during the teardown (or lands on the persisted document when it
arrives too late or the close fails), publishes the archived event, and
works on cold sessions too. A resume waiting on a failed close retries
instead of propagating the hook error, the teardown completes even when
the agent drain fails, and the create/fork rollback only ever removes
its own handle — a loser of the reservation race can no longer tear down
the winner's live scope.

* fix: key harness resume coalescing by the full input

Concurrent resumes only share a facade when their inputs match — a
caller passing different dirs, replay, profile, or kaos options gets its
own resume instead of having its options silently dropped.

* refactor(agent-core-v2): drop session close-awareness from title generation

Auto title is best-effort: a generation racing session close no longer
cancels its fetch or guards its write-back, so the per-session
sessionLifetime AbortSignal seed, the onWillCloseSession drain, and the
close-time invalidation go away. The newest-request-wins write-back
predicate stays.

* Delete .changeset/sdk-session-title-kind.md

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* refactor(session-title): drop the unused force regeneration path

Nothing calls force: with it gone, plain calls always coalesce onto the
shared in-flight generation, so the generation sequence and the
caller-supplied allowWhen veto lose their only purpose and go with it.
The title/generate REST route takes no body anymore.

* refactor(agent-core-v2): drop the title state projection from the session index

The listed-session titleKind had no consumer: the TUI's title-generation
gate seeds from the resumed summary, which reads the live metadata
document, and the kap-server REST wire never carried the field. Removing
the projection also retires the read-model summary version stamp (the
remaining shape is fully field-checkable) and the duplicate title-kind
derivation that had to stay in lockstep with sessionMetadata. The klient
list contract and the node-sdk list mapper drop the field with it; the
resumed/live summary still reports the canonical title state.

* refactor(agent-core-v2): inline the transcript live-tail merge into messageLegacy

mergeContextTranscriptWithLive had a single caller; move the logic into
messageLegacyService as the private mergeLiveTail and drop the export.

* refactor(agent-core-v2): drop the closing registry from the session lifecycle

Auto title no longer consumes close-awareness, so the machinery goes
back to the simple forms: close/archive run straight through, resume
no longer waits out an in-flight close, create/fork drop their target
reservation, and a cold archive is a no-op again. Reverts the behavior
of e7c397a7c and cd1cea0fd on top of the sessionLifecycle rename.

* fix(agent-core-v2): complete the HostRequestHeaders migration in the title test

The main merge reduced #/kosong/model/hostRequestHeaders to the pure
port contract; define the test's headers as a plain value matching it
and tidy the SDK test import grouping.

* fix(node-sdk): mark resume telemetry field ignored

* feat(tui): request the session title as soon as a prompt is accepted

* fix(agent-core-v2): drop the numbered user prefixes from the title request input

* refactor(tui): ask for the session title only once per session attach

* refactor(tui): drop the automatic session title trigger

Keep the capability only: the engine-side title generation service, the
POST /sessions/{id}/title/generate route, and the SDK generateSessionTitle
method stay; the TUI no longer requests a title on prompt accept or on
session attach. The changeset now covers the SDK capability instead of a
CLI-facing auto title.

* Delete .changeset/session-title-generation.md

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* feat(agent-core-v2): add forced session title regeneration

ISessionTitleService.generateTitle and the metadata write-back take an
optional force flag that bypasses the custom/generated guards, so an
explicit user request (the desktop/web rename field's Gen Title action)
can overwrite any current title; the applied title is marked generated.
Forced calls skip the in-flight coalescing, and a forced write is plain
last-writer-wins.

kap-server's POST /sessions/{id}/title/generate accepts an optional
{ "force": true } body; klient and the node-sdk plumb the option through
(GenerateSessionTitleInput.force).

Also renumber SESSION_TITLE_UNAVAILABLE to 40923: main assigned 40922 to
PAGE_TOKEN_MISMATCH after this branch forked.

* feat(agent-core-v2): selectable conversation excerpts for title generation

generateTitle gains a source option alongside force:

- user_prompts (default): the existing first-prompts window, unchanged.
- first_turn: the opening user prompt paired with the first turn's final
  assistant text — strict, so a caller asking before the first reply lands
  simply gets unavailable and can retry at the next turn boundary.
- digest: first prompt + latest prompt + the latest turn's final assistant
  text, tolerating a compacted window by using whatever segments survive;
  meant for explicit regeneration on multi-turn sessions.

Assistant segments keep only natural-language text parts (tool calls,
thinking, and media never contribute) and pass through the shared metadata
sanitizer, which redacts secrets and long base64-looking runs; each
segment is capped (user 300, assistant 600/400) so the composed
chat_content stays within the 1000-char budget. The kap-server route,
klient contract, and node-sdk plumb the option through. Excerpt extraction
is covered against the real context memory (loop-event folding), and the
REST surface gains a digest composition case.

* feat(agent-core-v2): gate session title generation behind an experimental flag

Registers the  flag (off by default; env
KIMI_CODE_EXPERIMENTAL_SESSION_TITLE, the master flag, or the
[experimental] config section) and makes generateTitle report
unavailable while it is off, so every entry point — the kap-server
route, klient, node-sdk, and through them the clients' auto trigger and
rename-field action — is inert unless the user opts in.

* refactor(agent-core-v2): rename the title flag to auto_session_title

Snake-case id matching search_worker / persistence_minidb_readmodel; env
KIMI_CODE_EXPERIMENTAL_AUTO_SESSION_TITLE.

* fix(agent-core-v2): land the merge resolution leftovers

The main-merge commit captured pre-fix snapshots of four files; the
actual resolutions only lived in my working tree: the LifecycleScope
import move to #/app/scopes, the titleKind port of
applyPromptMetadataUpdate, the Promise<void> pinning of the metadata
update queue, and the reloadSession runSessionAccess closure.

* test(node-sdk): enable the auto_session_title flag in the title suites

Generation now reports unavailable with the flag off, so the two
title-generation harnesses opt in through the written config's
[experimental] section.

* chore: changeset for the experimental web session titles

* chore: scope the title changeset to the SDK package

* chore: cover the internal packages in the title changesets

---------

Signed-off-by: 7Sageer <sag77r@hotmail.com>
Co-authored-by: liruifengv <liruifeng1024@gmail.com>
2026-08-13 17:59:57 +08:00
7Sageer
c9bfe8b2c8
feat: replace the secondary-model experiment with a declarative subagent model pool (#2700)
* feat: replace secondary-model experiment with [subagent.models] pool

Add a declarative subagent model pool to agent-core-v2: [subagent.models]
maps [models] entry ids to selection hints rendered in the Agent/AgentSwarm
tool descriptions, and [subagent].default_model picks the spawn model when
the caller passes none. The tools' model parameter becomes a free-form
alias string (stripped when no pool is configured), description rendering
is caller-aware (primary (alias) [main model]), and a session-start
validation service fails fast with CONFIG_INVALID on a missing/invalid
default_model or an unresolvable pool alias.

Remove the secondary-model experiment from the v2 engine, node-sdk,
kap-server, and the TUI (the /secondary_model command), and drop the
agent-profile modelPreference / model_preference frontmatter field on v2.
The legacy v1 engine keeps the experiment unchanged; v2 ignores leftover
[secondary_model] config silently.

* fix(agent-core-v2): harden subagent model-pool validation and error/picker mapping

Deep-review follow-ups to the [subagent.models] pool:

- validate the pool before session materialization (after config.ready)
  and before the fork file copy, so a broken pool no longer leaves
  orphaned session dirs or leaked MCP overlay connections; the
  Session-scope validation service stays as a backstop
- reject the reserved "primary" pool alias at startup, and again
  defensively in resolveSubagentBinding so a pool broken by a runtime
  config edit fails loudly at spawn instead of binding the wrong model
- keep the [default] marker when the caller's own model is the pool
  default (primary (alias) [main model] [default])
- recompile the cached tool-args validator when a tool advertises a new
  schema object (mid-session pool edits no longer hit a stale validator)
- map config.invalid to VALIDATION_FAILED in kap-server's session routes,
  the debug transport mapper, and the catch-all error handler
- hide the v1-synthesized __secondary__ entry from the /model and
  /provider pickers again
- fold per-export doc blocks into file headers per package comment
  conventions; add pre-flight/reserved-key/validator/mapping tests and
  document that create/resume/fork all fail on a broken pool

* feat: re-add /secondary_model and accept a lone subagent default_model

- v2 engine: a pool-less [subagent] default_model forms an implicit
  single-entry pool — validated at session create/resume/fork like an
  explicit pool, and advertised through the Agent/AgentSwarm model
  parameter.
- Tool descriptions: the caller's own alias is a normal pool entry
  marked [main model]; the primary line stays distinct because only it
  inherits the caller's thinking level.
- TUI: /secondary_model returns, persisting [subagent] default_model
  (merging into an existing pool with an empty description); the picker
  hides the no-op Thinking footer and rejects the reserved primary
  alias.
- kap-server: /api/v1/config accepts and echoes subagent; the
  snake-to-camel patch conversion preserves user-defined map keys under
  providers/models/experimental/raw without leaking preserve mode into
  a colliding alias's own fields.
- v1 config schema learns subagent.defaultModel/models so the shared
  config.toml round-trips; the v1 engine still ignores them at runtime.
- Docs (en/zh) and changesets updated.

* docs: use public model identifiers in the subagent model pool examples

* refactor: rename /secondary_model to /secondary-model

* test: cover the /secondary-model command name resolution

* Revert "test: cover the /secondary-model command name resolution"

This reverts commit 98a4a6d999.

* feat(agent-core-v2): move the subagent model pool to [secondary_model]

The pool keys (default_model, [secondary_model.models]) now live in their
own [secondary_model] config section instead of [subagent], which keeps
only timeout_ms; legacy [subagent] pool keys are ignored with a
deprecation warning. The SDK config contract carries the pool on the
secondaryModel field, so the TUI /secondary-model command (now also
aliased /subagent-model) and the kap-server /config wire read and write
it directly with no translation layer.

* docs: correct default engine guidance

* feat(agent-core-v2): pin subagents to default_model with [secondary_model] force

force = true removes the main agent's per-spawn model choice: the Agent
and AgentSwarm tools stop advertising the model parameter and every spawn
binds default_model; an explicit choice, "primary" included, is rejected.
The setting requires default_model, rejects a [secondary_model.models]
table, and is validated loudly at session create/resume/fork (lifecycle
preflight plus the Session-scope backstop). The v1 engine declares the
key for write round-trips and excludes it from the recipe patch.

Also documents pool entries as per-alias thinking-level variants via
default_effort overrides.

* docs: use real managed model aliases in the secondary_model examples

The pool examples invented aliases (kimi-hs, fable, codex) and referenced
non-existent model IDs (model = "codex"); they now reference only the
managed aliases provisioned by /login (kimi-code/k3,
kimi-code/kimi-for-coding, kimi-code/kimi-for-coding-highspeed), with the
effort variant derived as kimi-for-coding-highspeed-deep. Also replaces the
versioned kimi-k2.5 alias with kimi-for-coding per the docs model-ID rule.

* chore: simplify the subagent model pool changeset

* feat(agent-core-v2): honor the legacy [secondary_model] model key as a fallback default

* refactor(node-sdk): export the reserved model-alias constants from the SDK

Restore the SECONDARY_DERIVED_MODEL_ALIAS re-export and add
PRIMARY_SUBAGENT_MODEL_CHOICE so the TUI imports both from
@moonshot-ai/kimi-code-sdk instead of vendoring local copies.

* feat(agent-core-v2): keep the subagent model pool behind the secondary-model experiment

Restore the secondary-model flag gating so this change only adds the pool:
with the experiment off the [secondary_model] pool keys stay inert — the
Agent/AgentSwarm tools strip the model parameter, spawns inherit the
caller's model, and startup pool validation is skipped. The /secondary-model
slash command is gated behind the experiment again, and the docs and
changeset describe the flag.

* fix(node-sdk): cascade provider removal into the subagent model pool

Deleting a provider left [secondary_model] entries pointing at the removed
model aliases; with the secondary-model experiment on, every subsequent
session create/resume/fork then failed pool validation. planProviderRemoval
now filters dangling pool entries, and drops the whole section when its
effective default (defaultModel, or the legacy recipe's model fallback)
dangles — folded into the same atomic multi-section replace.

* fix(agent-core-v2): close two subagent model pool validation gaps

Spawn-time resolveSubagentBinding now rejects force combined with a
[secondary_model.models] table, matching the startup pre-flight — a live
session could otherwise reach that invalid state through a deep-merged
config patch and only fail on the next create/resume. The session
lifecycle pre-flight also awaits the kosong model/provider registries'
ready alongside config.ready, so a cold bootstrap no longer fails a valid
pool with CONFIG_INVALID against an empty registry.

* test(agent-core-v2): stub the model/provider registries in handler-chain tests

SessionLifecycleService now awaits IModelService/IProviderService readiness
in its pool pre-flight, so the tests that assemble the real service through
a hand-built container must register the two tokens.

* fix(kap-server): cascade REST provider deletion into the subagent model pool

The DELETE /providers route rewrote only the providers and models
sections, so a pool referencing one of the deleted provider's aliases was
left dangling and the engine's create/resume/fork pool validation failed
every subsequent session until the user repaired the TOML by hand. Filter
dangling pool entries and drop the section when its effective default
dangles, mirroring the SDK's planProviderRemoval semantics.

* fix: keep the subagent pool consistent on provider replace and preserve legacy recipe fields

PUT /providers rebuilds the provider's alias set and can drop or rename
aliases referenced by [secondary_model]; the pool now cascades there too —
renamed aliases are repointed (mirroring the global default-pointer
migration), dropped aliases are filtered, and the section is cleared when
its effective default dangles.

The v2 [secondary_model] schema also declares the legacy recipe patch
fields (default_effort, max_output_size, ...) so validation no longer
strips them: pool resolution keeps ignoring them, but config reads/writes
now round-trip losslessly instead of silently deleting them from
config.toml on any pool write.

* fix(agent-core-v2): cascade the subagent pool on catalog refresh writes

A background provider/model refresh rewrites the [models] table without
touching [secondary_model], so a dropped alias left the pool dangling and
every subsequent session create/resume/fork failed validation until the
user repaired the TOML by hand — the same gap the SDK and REST write
paths already had, but triggered unattended.

The cascade helper now lives in agent-core-v2 next to the section it
protects (cascadeSubagentModelPool): the discovery service folds the pool
into the same atomic replaceSections transition, and kap-server's
provider write routes reuse the shared helper instead of a local copy.

* fix: cover the last two model-table write paths for the subagent pool

ModelsDevImportService's catalog and custom-registry imports rebuild the
[models] table without the pool cascade, so an import that drops a pooled
alias left a dangling pool behind; both final write passes now fold the
pool through cascadeSubagentModelPool (the drop passes deliberately skip
it). The StubConfigService test double now treats a null section value as
a delete, matching the real ConfigService.

The TUI's provider overwrite flow removes an existing provider before
re-adding it, which ran the removal cascade against a model table where
every alias of that provider was absent and silently dropped the pool;
the flow now snapshots secondaryModel up front and restores the entries
that survive the re-add, via the cascade helper re-exported from the SDK.
2026-08-13 12:29:03 +08:00
Liu Zhongnuo
504e6292ed
feat(mcp): inspect effective authorization state in v1 (#2856)
* feat(mcp): inspect effective authorization state

* test(agent-core-v2): register MCP auth coordinator fixture

* fix(mcp): validate runtime names against full catalog

* fix(mcp): reconnect after pending auth updates

* docs(mcp): describe auth coordinator collaborator

* fix(mcp): ignore disabled runtime name collisions

* fix(mcp): serialize OAuth token refresh

* test(mcp): await OAuth credential writes

* fix(mcp): queue trailing credential reconnect

* fix(oauth): preserve access-only refresh winners

* fix(mcp): preserve legacy offline auth state

* fix(mcp): redact inspection credentials

* refactor(mcp): keep app inspection on v1

* fix(mcp): guard legacy auth status mutations

* fix(mcp): avoid deterministic legacy auth probes

* fix(mcp): cover initialization credential updates

---------

Co-authored-by: 刘仲诺 <liuzhongnuo@dev.msh.team>
2026-08-13 12:00:04 +08:00
Haozhe
23e68eee8b
refactor(agent-core-v2): remove the agent RPC aggregation layer (#2871)
* refactor(agent-core-v2): remove the agent RPC aggregation layer

- delete src/agent/rpc/ (AgentRPCService, IAgentRPCService, core-api,
  prompt-metadata, types) and sink each method's orchestration into its
  owning domain service
- prompt: new submit/submitSteer composing disabledTools gating,
  MAIN-only session metadata, and engine-side {turn_id} settlement
- skill: activate now returns PromptLaunchResult and writes session
  metadata internally (MAIN-only, unified across prompt/steer/skill/
  pluginCommand); node-sdk and kap-server drop their edge-side writes
- pluginCommand: new agent-scope domain owning command activation and
  the plugin_command.activated domain event
- permissionMode/loop/fullCompaction: new setModeAndBroadcast /
  cancelFromUser / cancel; setMode and loop.cancel stay pure for
  internal callers
- klient: agentRpcContract split into per-domain contracts; facade
  re-routes to domain channels with its public API unchanged
- node-sdk, kap-server, kimi-inspect and the v2 test harness now call
  domain services directly; ctx.rpc keeps its name as a composed
  adapter
- externally visible: the agentRPCService debug channel is gone and
  session metadata writes are now MAIN-agent-only (see changeset)

* refactor(agent-core-v2): move disabledTools gating out of the prompt domain

Prompt should not own session tool policy: submit no longer accepts or
applies disabledTools. The klient facade keeps its prompt({ disabledTools })
API and composes it edge-side — applying agentToolPolicyService
setSessionDisabledTools before calling agentPromptService.submit, the same
way kap-server's prompt route already does. Over klient, a profile-less
engine now surfaces the raw profile error instead of request.invalid.

Also restores the RPC-removal changeset, which did not make it into the
previous commit.

* chore(agent-core-v2): drop the RPC-removal changeset

* refactor(klient): drop disabledTools from the prompt entry entirely

The prompt path no longer carries session tool gating on any surface:
the klient facade prompt() loses the disabledTools field and calls
agentPromptService.submit directly, and the node-sdk
SessionPromptRpcInput stops accepting or forwarding it (v1 always
ignored the field). Session tool gating remains available through
IAgentToolPolicyService.setSessionDisabledTools, composed at the edge
the way kap-server's prompt route does; the klient toolPolicy contract
added for facade-side composition is removed as unused.
2026-08-13 10:57:01 +08:00
7Sageer
f40bf04998
refactor(agent-core-v2): unify model-facing reminder scheduling (#2623)
* refactor(agent-core-v2): unify model-facing reminder scheduling

Route every model-facing reminder through the contextInjector boundary
scheduler. Past-tense events go through a persisted once-reminder queue
(reminderQueue) that delivers exactly once at turn, step, compaction,
and restore boundaries; present-tense state renders through
context-injection providers reconciled against live history.

- interruption, goal (cancel/budget/fork-cleared), image-compression
  captions, btw, and init reminders enqueue into reminderQueue instead
  of writing the context directly; the interruptionReminder wire model
  is removed and its recorded type is retired silently on replay
- swarm mode announcements render through a provider seeded from the
  replayed history on restore, replacing live side effects and the
  ContextModel pop reducer on swarm_mode.exit
- loadable-tools announcements become an isNewTurn-gated provider,
  dropping the compaction boundary flag
- plugin session-start guidance re-renders as a supersedes reminder at
  the next boundary via a dirty flag instead of appending immediately
- legacy system_trigger origins of migrated reminders still fold on
  replay

* fix(agent-core-v2): make system reminders undo-aware

* test(agent-core-v2): migrate plugin session-start harness

* fix(agent-core-v2): preserve reminder boundary ordering

* refactor(agent-core-v2): narrow reminder and swarm helper exposure

- drop the swarmInjection re-export from the package index; SwarmInjection
  stays a domain-internal collaborator like permissionMode/plan injections
- move INTERRUPTION_REMINDER text back to a private constant in the service;
  only the variant stays in the Ops module
- make reminderQueue.enqueue return void; no caller consumed the entry id

* chore(agent-core-v2): keep comments in module headers

* refactor(agent-core-v2): track reminder state via injection disclosure

- derive swarm active/inactive state from ctx.lastDisclosure instead of
  byte-matching rendered markdown, with variant-only fallback for legacy
  swarm_mode/swarm_mode_exit journal entries
- record once_reminder disclosure (entry id) on queue-appended messages
  and dedupe the crash window by the contiguous tail id set, covering
  multi-entry drains
- move reminderQueue draining behind a sync onWillInject event so the
  injector no longer depends on the queue domain
- centralize the system-reminder wrap format behind wrapSystemReminder /
  systemReminderContent and use injector-provided positions in the plugin
  session-start provider
- spell out the step-boundary fallback and sync-only contract of
  registerAtTurnStart via shouldRunAtBoundary

* fix(agent-core-v2): isolate failing turn-start providers and warn once per missing sessionStart skill

* refactor(agent-core-v2): compute injection positions on read

Drop the per-provider positions cache from the context injector: the
registration scan, the context.spliced index arithmetic, and the
post-restore resync all existed only to mirror what the history already
records. Each provider call now derives its injected positions by
scanning context memory for its surviving injection messages, so silent
history edits (such as vacuous-step folds) can no longer desync a
cached index.

* refactor(agent-core-v2): formalize injector once-channels and raw message results

* refactor(agent-core-v2): declare dynamic tool schemas at injection boundaries

Move the dynamic-tool schema declaration out of toolSelect.load(): the
loaded names are recorded as pending and drained by a dedicated
toolSelectSchemas provider through the contextInjector boundary
scheduler, so the declaration message lands at a quiescent boundary
instead of mid-step inside a streaming tool exchange. The folded
history remains the loaded-tool ledger, so undo, compaction, and
resume still self-heal by re-folding.

* refactor(agent-core-v2): deliver AGENTS.md reminders through the reminder queue

The tool hook now only observes and enqueues a once-per-agent reminder
through the reminderQueue once-channel instead of prepending text to
the tool result: results stay verbatim for the truncation pipeline and
the reminder can never be truncated away with an oversized output. The
reminderQueue is resolved lazily through the instantiation service at
enqueue time, breaking the contextInjector -> loop -> llmRequester ->
profile -> agentsMdReminder constructor cycle.

* refactor(agent-core-v2): make injection disclosures opaque and domain-owned

contextMemory no longer declares the ContextInjectionDisclosure union:
InjectionOrigin.disclosure becomes an opaque unknown, and providers
bind their own payload type through register<D>, so lastDisclosure
arrives at the provider already typed by its own variant. The date,
swarm_mode, and once_reminder payload shapes move into the dateChange,
swarm, and reminderQueue domains respectively; reminderQueue keeps a
runtime guard for its cross-message tail scan, the only place that
reads disclosures it did not write. Persisted origin shapes are
byte-identical, so existing journals replay unchanged.

* fix(agent-core-v2): isolate failing step context providers

A step or compaction boundary provider that threw or rejected made the
injector's inject() promise reject, which propagated through the
onWillBeginStep hook chain and failed the whole turn, and starved every
provider registered after it. Log and skip the bad provider instead,
matching the turn-start path's existing isolation.

* refactor(agent-core-v2): derive injector isNewTurn per injection boundary

Replace the shared read-and-clear isNewTurn flag (set by turn.started and
injectAfterCompaction, consumed by the first inject()) with values each
trigger supplies from an authoritative source: the loop marks a turn's
first step via BeforeStepContext.firstStepOfTurn (standalone runs never
count), and the compaction follow-up passes true explicitly, so
interleaved triggers can no longer consume or steal the marker.

A compaction follow-up that lands inside a step hook chain (the
auto-compaction path) doubles as that step's new-turn delivery: the
enclosing step then injects with isNewTurn false, so the upcoming request
receives one new-turn injection, not two.

* refactor(agent-core-v2): unify disclosure placement and injector param naming

* fix(agent-core-v2): keep pending tool schemas across compaction splices

A load announced by select_tools sits in pendingLoaded until the next
injection boundary declares it. A compaction fold in that window
publishes a replacement splice, and the splice-time reconciliation
dropped the pending entries before the post-compaction inject could
declare them — the model was told "Loaded: X" yet X never became
available. Drop pending entries only on removal splices (undo/clear,
which carry no replacement messages); compaction's replacement splice
keeps them so the declaration lands at the post-compaction boundary.

* fix(agent-core-v2): consume the plugin session-start refresh after a successful render

reconcileSessionStartReminder cleared the refresh-pending flag before
awaiting the render, so a throwing render (skipped by the injector's
provider isolation) lost the forced refresh until the next catalog
change. Consume the flag only after the render resolves, and move the
warn-once rationale into the module header per the comment convention.

* refactor(agent-core-v2): remove the generic reminder queue

* chore(agent-core-v2): drop the stale reminder-queue mention in systemReminder

* test(node-sdk): align side-question fork parity with event-point reminders

* chore(agent-core-v2): address reminder review standards

* docs(agent-core-v2): condense the model-facing reminders section

* refactor(agent-core-v2): write all system reminders through wrapSystemReminder

* fix(agent-core-v2): preserve reminder lifecycle invariants

* refactor(agent-core-v2): reconcile context injections at the step head

Unify the injector's delivery timings into one point on the
onWillBeginStep chain, before the step's request is built:

- providers run before every request instead of after every step, so
  reminders are visible from the first response of a turn
- a compaction splice re-arms the new-turn flag via context.spliced;
  when compaction runs inside the hook chain (full-compaction's
  beforeStep), a follow-up inject at the chain tail keeps the first
  post-compaction request covered
- registerAtTurnStart and injectAfterCompaction are removed;
  reconcileWhenIdle stays as the v1-parity surface for SDK-driven
  triggers (swarm toggle, plugin reload)

* refactor(agent-core-v2): clarify the injector's step-hook handler

Name the handler reconcileAroundStep, rename the rearm flag to
compactionRearmPending with a single takeCompactionRearm() consumer,
and extract isCompactionSplice. Consuming the flag into a local before
computing isNewTurn also avoids hiding the side effect inside a ||
short-circuit.
2026-08-12 13:52:08 +08:00
Haozhe
c212ae9715
fix(kimi-code): show MCP launch targets in the workspace trust prompt (#2843)
* fix(kimi-code): show MCP launch targets in the workspace trust prompt

Render each gated project MCP server's launch target (transport, command,
args, cwd, or url) in the workspace trust prompt without leaking env or
header secrets, stripping terminal control characters from the
workspace-supplied text, default the prompt to "Don't trust", and
resolve fd binaries to absolute paths so untrusted workspaces cannot
plant a bare-name fd executable that runs before trust confirmation.

* fix(kimi-code): resolve stty to an absolute path before the trust gate
2026-08-12 13:34:32 +08:00
liruifengv
3c9e3b297c
feat(kimi-code): paginate the session picker list (#2826)
* feat(kimi-code): paginate the session picker list

The /sessions picker and kimi -r used to materialize the full session
list before showing anything, which gets slow with hundreds of sessions.

- node-sdk: add listSessionsPage (limit/before -> items + nextCursor);
  the v2 engine pages through the session index (draining past entries
  whose workDir is unrecoverable), the v1 engine answers one full page
- TUI: open the picker on the first page, fetch the next page when the
  cursor reaches the fetched end, and drain remaining pages in the
  background once a search query is typed so search still covers all
  sessions
- kimi -r now fetches a one-item page for the latest session

* chore: simplify session picker changeset

* fix(kimi-code): join in-flight page fetch in session search drain

A query typed while a scroll-triggered page fetch was still running
stopped the background drain at the loadingMore early return, leaving
the search covering only the pages fetched so far. fetchMoreSessions
now optionally joins the in-flight fetch and continues with the next
page; scroll triggers still drop when busy.
2026-08-12 11:21:43 +08:00
HydrogenE7
158c81d705
fix: surface a readable error when Git Bash is missing on Windows (#2814)
* fix: surface a readable error when Git Bash is missing on Windows

* fix(agent-core-v2): translate probe rejection into HostProcessError for ready awaiters

- HostEnvironmentService.ready now rejects with the translated
  HostProcessError(shell.git_bash_not_found) instead of the raw
  ProbeShellNotFoundError, matching what sync field reads throw and what
  SDKRpcClientV2.ensureConfigFile() surfaces, while an internal no-op
  handler keeps the rejection from becoming an unhandledRejection.
- Replace the Windows-gated probe-failure tests with vi.mock-stubbed
  deterministic suites that run identically on any platform.
- Move the ProbeShellNotFoundError explanation into the environmentProbe
  file header per the package comment convention.

* fix(agent-core-v2): narrow probe error to Error to satisfy only-throw-error lint

* fix(agent-core-v2): preserve probe error as cause when translating to HostProcessError

* fix(agent-core-v2): keep checked paths out of the public probe error message

* fix(node-sdk): gate the host-environment wait in ensureConfigFile to Windows

The missing-Git-Bash failure is Windows-only, and IHostEnvironment.ready
also covers the login-shell PATH enrichment, which spawns the user's login
shell with a 5s timeout. Awaiting it on POSIX coupled config-only commands
(kimi provider list/remove, export, ...) to the user's shell profile for no
benefit.

---------

Co-authored-by: liruifengv <liruifeng1024@gmail.com>
2026-08-11 21:13:08 +08:00
Haozhe
e702817124
fix(agent-core-v2): degrade idle-session steer to turn launch like v1 (#2723)
- return the enqueue-launched turn instead of rejecting with
  prompt.not_found when no prompt is pending at steer time
- report steer as queued when a manual compaction holds the context
- sync title/lastPrompt metadata on main-agent steer, matching v1
- update the v1-v2 parity test to assert converged behavior
2026-08-11 14:40:15 +08:00
Liu Zhongnuo
437a1b8ba1
fix(sdk): probe MCP auth status through connection (#2731)
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
2026-08-07 22:18:53 +08:00
Liu Zhongnuo
0b2e803d5e
feat(sdk): expose global MCP auth status (#2706)
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 / Publish native release assets (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
2026-08-07 13:11:18 +08:00
Haozhe
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
2026-08-07 07:38:16 +08:00
qer
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.
2026-08-06 20:14:05 +08:00
qer
75fe068a01
fix(cli): stabilize built-in capability installation (#2601)
* fix(cli): show built-in capabilities before the first session exists

The lazy-session refactor left capability calls going through
requireSession(), so on a session-less v2 startup /plugins reported the
capabilities unavailable and hid the built-in rows behind the promo.
Like plugin management, capability readiness and installs are app-global
on the v2 engine: the node-sdk harness gains a capability facade over
the global channel, and the TUI resolves session-or-harness for every
capability call.

* fix(cli): count the dev marketplace server as the default catalog

dev.mjs always points KIMI_CODE_PLUGIN_MARKETPLACE_URL at its own
repo-serving server, which the override gate mistook for a user-configured
marketplace and suppressed the built-in capability rows in every dev run.
The dev server now marks itself, and the gate treats that marked URL as
the default catalog while still honoring real overrides (slash-command
source, user-set env, KIMI_CODE_DEV_MARKETPLACE_URL).

* fix(cli): align built-in capability updates
2026-08-05 14:55:04 +08:00
Haozhe
f881cdd970
feat(cli): default CLI surfaces to the agent-core-v2 engine (#2627)
Some checks are pending
CI / lint (push) Waiting to run
CI / typecheck (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
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 (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
* feat(cli): default to agent-core-v2 engine with KIMI_CODE_LEGACY_FLAG opt-out

- invert the engine gate: isKimiV2Enabled() now returns true unless
  KIMI_CODE_LEGACY_FLAG is truthy; KIMI_CODE_EXPERIMENTAL_FLAG no longer
  selects the engine
- replace the experimental `kimi acp-v2` command with the native v2
  implementation as the default `kimi acp`; the legacy acp-adapter path
  remains under the legacy flag
- drop the acp-v2 experimental flag from the registry
- rename the dev:cli:v2 script to dev:cli:legacy
- update en/zh docs for the new default engine and the legacy flag

* feat(cli): route export and provider through the engine gate

- select the harness via isKimiV2Enabled(): agent-core-v2 by default,
  the legacy harness when KIMI_CODE_LEGACY_FLAG is truthy
- close the harness after each one-shot command so the v2 engine's
  watchers do not keep the process alive
- document both commands in the KIMI_CODE_LEGACY_FLAG env-var entry
2026-08-05 14:42:23 +08:00
Haozhe
f412e105b3
feat(acp-server): bridge questions via elicitation and support host slash commands (#2583)
* fix(acp): preserve cancels that arrive before the turn id is known

A session/cancel landing between prompt submission and the launch
round-trip found driver.turnId undefined and was dropped entirely; the
turn then ran to completion and the prompt resolved end_turn despite
the client's cancel.

The engine's cancel payload makes turnId optional (an empty call
cancels the active turn — the same contract kap-server's cancel route
relies on), so cancel() now issues an unaddressed cancel in that window
and flags the driver; the launch handler re-issues a precisely
addressed cancel once the id lands, and a no-launch outcome settles
cancelled instead of end_turn.

* fix(agent-core-v2): shut session MCP overlays down on service teardown

The ephemeral per-session MCP overlay was only shut down by the
session handle's dispose wrapper, but the DI container disposes session
scopes directly on workspace/app teardown, bypassing the wrapper — so
overlays of sessions still live at shutdown leaked their MCP
connections and stdio child processes.

Track live overlays in the lifecycle service: the handle wrapper
deletes-then-shuts-down (atomic, so close and service disposal can
never double-shutdown), and the service's own dispose shuts down
whatever is still tracked.

* feat(acp-server): bridge questions via elicitation and support host slash commands

- route AskUserQuestion through `elicitation/create` for form-capable
  clients (native multi-question + multi-select), falling back to the
  `request_permission` bridge on RPC failure
- add a `slashCommands` resolver option so hosts can merge their own
  command palette and skill aliases into `available_commands_update`;
  `/help` now lists the merged palette
- bridge `appendText`/`writeBytes` through client text capabilities
  (read-modify-write append, UTF-8-checked byte writes) with local
  filesystem fallbacks
- defer `available_commands_update` until after the lifecycle response
  settles so clients like Zed do not drop the notification
- propagate plan-toggle errors from `setMode` instead of silently
  reporting the new mode; make server `close()` idempotent

* style(acp-server): satisfy oxlint eqeqeq and await-thenable rules

* test(node-sdk): assert v1-v2 tokenCount parity for imports after eager counting
2026-08-04 10:44:56 +08:00
Haozhe
1328b32037
feat(acp): add experimental agent-core-v2 ACP server (kimi acp-v2) (#2571)
* feat(acp): add agent-core-v2 ACP server

- add ACP session lifecycle, configuration, permissions, and event bridging
- expose the experimental kimi acp-v2 command with terminal authentication
- add integration coverage and workspace build configuration

* test: use neutral example domains in test fixtures and docs

- replace placeholder hostnames (evil.com, foo.com, internal.corp,
  real.corp) with example.test / example.com in agent-core-v2 and
  kap-server tests
- replace fixture emails (x@y.com, a@x.com) with example addresses in
  minidb tests and README

* fix(acp): align acp-server with agent-core-v2 interfaces and address review

- add missing appendText to AcpHostFileSystem (IHostFileSystem drift)
- replace IAgentPromptService.prompt with inject
- use Turn.cancel() instead of abortController
- gate FS reverse-RPCs on client capabilities, fallback to local FS
- return PROTOCOL_VERSION constant instead of echoing client version
- remove misleading mcpCapabilities from initialize response
- dispose old session wrapper before replacing on load/resume
- fix object stringification lint error in convert.ts
- add acp-v2 to expected CLI sub-command list in test

* fix(acp): use enqueue for prompt submission, stop advertising unimplemented builtins

- replace IAgentPromptService.inject with enqueue so onBeforeSubmitPrompt
  hooks (prompt-blocking policy) are not bypassed
- stop advertising builtin slash commands (/help, /status, etc.) until
  builtin command execution is implemented
- add comment explaining appendText stays local (ACP has no append RPC)
- update skills test to match new availableCommands behavior

* fix(acp): filter turn events by turnId, surface auth failures as auth_required

- track turnId in driveTurn and ignore events from unrelated turns,
  preventing queued prompts from settling on the running turn
- reject prompt requests with auth_required when turn fails with an
  auth-related error code, enabling ACP client re-auth flow

* fix(acp): gate acp-v2 behind experimental flag, filter sessions by cwd

- add acp-v2 experimental flag (KIMI_CODE_EXPERIMENTAL_ACP_V2) and gate
  CLI command registration behind it
- filter session/list results by requested cwd instead of returning
  sessions from all workspaces
- detect hook-blocked prompts via PromptHandle.state and add TODO for
  streaming block messages once the hook context exposes them

* refactor(acp-server): rewire ACP server onto the klient facade

- replace direct agent-core-v2 scope/service access (ISessionLifecycleService,
  ISessionIndex, IEventBus, ISessionInteractionService, etc.) with the Klient
  facade: klient.global.sessions / klient.session(id) / agent('main') handles
- drive turns via agent.prompt() + session-level agent event subscriptions
  instead of per-prompt IEventBus wiring; settle on turn.ended
- route approval/question bridging through session.interactions events
- hide the thinking config option and skill catalog behind KLIENT-GAP markers
  until klient exposes those surfaces
- acp-fs: pass realpath through to the local inner backend
- klient: session.restore() rejects both null and undefined handles

* feat(agent-core-v2): add session delete and ephemeral per-session MCP servers

- add ISessionLifecycleService.delete: close a live session first, then
  remove its persisted data, evict the index read-model entry, and append
  a deleted tombstone to session_index.jsonl; unknown ids raise
  session.not_found
- add CreateSessionOptions/ResumeSessionOptions.mcpServers: session-owned
  MCP overlay merged over the workspace manager via
  MergedMcpConnectionView (an ephemeral name shadows a workspace server),
  never persisted, released when the session scope tears down
- return PromptLaunchResult from activateSkill so callers get the
  launched turn id and activation failures (unknown skill, busy) surface
- add ISessionSkillCatalog.list() as a wire-friendly catalog snapshot
- add ISessionIndex.remove for read-model eviction on delete

* feat(klient): expose session delete, per-session MCP, skills, and stream events

- session lifecycle contract: delete, resume/restore options, and
  CreateSessionOptions.mcpServers (ephemeral per-session MCP servers)
- add the session skills contract and facade accessors for the
  wire-friendly skill catalog snapshot
- register tool.call.delta, tool.progress, and compaction.* agent stream
  events so consumers can subscribe with typed payloads

* feat(acp-server): align ACP v2 server with acp-adapter capabilities

- complete the klient-facade rewire: ACP client connection holder and
  the terminal/* reverse-RPC runner routed through the Agent scope
- negotiate the protocol version on initialize instead of pinning v1
- compress oversized prompt images at the ACP ingestion point with a
  format gate, caption, and persisted originals; a cancel arriving
  mid-compression settles the prompt as cancelled without a turn
- stream tool call args via tool.call.delta (lazy pending create,
  cumulative replace, started upgrade) and refresh titles via
  tool.progress status updates
- report compaction progress and results after /compact via the
  compaction.* events
- answer unknown slash commands locally instead of sending them to the
  model
- accept legacy "<id>,thinking" model ids and legacy approve /
  approve_for_session approval option ids
- keep sessions without cwd metadata in cwd-filtered session/list
- sanitize wire errors: auth codes map to auth_required, turn.agent_busy
  to invalid_request, everything else to a fixed internal-error message
- bump @agentclientprotocol/sdk to ^1.3.0

* fix(cli): drop stale registerServerCommand call and sherif ACP SDK split

- commands.ts called registerServerCommand, which no longer exists on
  current main (the deprecated `kimi server` shim is registered via
  registerWebCommand), breaking typecheck, build, and every CLI test
  that builds the program
- sherif rejects the @agentclientprotocol/sdk major split between
  acp-adapter (^0.23.0, production kimi acp) and acp-server (^1.3.0,
  experimental); the two hosts legitimately target different SDK
  majors, so ignore the dependency in the sherif invocation

* test: update fixtures for acp-v2 flag and domain rename, refresh nix deps hash

- kap-server origin.test: two CORS cases still used foo.com after the
  whitelist moved to foo.example.com, so the origin was no longer
  whitelisted and the expected CORS headers were withheld
- node-sdk config.test: expect the new acp-v2 experimental flag in the
  harness feature metadata
- flake.nix: update the fetchPnpmDeps hash for the
  @agentclientprotocol/sdk 1.3.0 lockfile change

* fix(acp): widen the ACP v2 auth gate beyond OAuth-only providers

The gate consulted only auth.summarize(), which iterates providers
declaring an oauth section — configurations that authenticate with a
plain apiKey or provider env-bag credentials (no OAuth at all) were
rejected with auth_required even though the default model is fully
usable.

- klient: expose authSummaryService.ensureReady on the global auth
  facade (the contract already declared it)
- acp-server: gate on the engine's own readiness probe for the default
  model — config apiKey / env-bag / OAuth token all count, matching how
  the model is actually used — and fall back to "any logged-in OAuth
  provider" (the legacy adapter's first branch)
- test: an apiKey-only config passes the gate with auth enforcement on;
  the OAuth logout regression is unchanged

* fix(acp): reject concurrent prompts instead of displacing the in-flight turn

A second session/prompt while a turn is running overwrote the session's
only TurnDriver: the engine quietly queues plain prompts submitted
during an active turn (the launch resolves undefined, indistinguishable
from a hook-blocked launch), so the first prompt never settled and both
turns' events went unattributed.

Guard both model-bound launch paths (plain prompt and skill activation)
with a synchronous in-flight check and reject with invalid_request
(turn.agent_busy), matching the legacy adapter's busy semantics. Local
slash handling (builtins, unknown-command answers) is unaffected.
2026-08-04 10:20:24 +08:00
Haozhe
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)
2026-08-04 09:44:21 +08:00
Mangesh Raut
eaab2b6f28
fix(cli): fall back to built-in models.dev catalog when fetch fails (#2416)
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 / nix build .#kimi-code (push) Blocked by required conditions
Nix Build / Check flake.nix workspace sync (push) Waiting to run
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
When the remote models.dev catalog cannot be fetched, fall back to the
built-in catalog so CLI/TUI model selection keeps working offline or
under network failure. Import the shared helper via the #/utils alias.
2026-07-31 19:54:09 +08:00
Haozhe
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
2026-07-31 16:47:34 +08:00
liruifengv
32d693f644
feat(tui): ask for workspace trust on startup with the v2 engine (#2453)
* feat(node-sdk): expose workspace trust state and trust grant on the v2 client

* feat(tui): ask for workspace trust on startup with the v2 engine
2026-07-31 14:21:04 +08:00
Haozhe
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
2026-07-31 00:38:11 +08:00
Haozhe
d36f4c58f6
refactor(agent-core-v2): remove the fault-injection experimental feature (#2399)
- delete the faultInjection domain (IFaultInjectionService, its Agent-scope
  implementation, and the fault-injection experimental flag)
- drop the llmRequester's per-attempt fault consumption point and the
  faultToError helper
- remove the fault-injection test cases and DI wiring from the requester
  service tests, and the domain's layer-registry entry
- regenerate the state manifest without the faultInjection.* state keys
2026-07-30 14:45:31 +08:00
liruifengv
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
2026-07-30 13:45:41 +08:00
7Sageer
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>
2026-07-29 15:40:42 +08:00
7Sageer
efac96c8a9
feat(agent-core): custom agent files and secondary model on the v1 engine (#2232)
* feat(agent-core): custom agent files and secondary model on the v1 engine

Migrate the custom agentfile and secondary-model capabilities from
agent-core-v2 to the v1 engine so they work in the TUI and plain
kimi -p sessions:

- discover Markdown agent files from user/project/extra/explicit
  directories with the v2 precedence rules, a merged session profile
  catalog replacing the hardcoded builtin profile lookups, SYSTEM.md
  main prompt override, and ${base_prompt} backed by the effective
  default
- --agent/--agent-file now work in print mode on the default engine;
  CreateSessionOptions gains agentProfile/agentFiles
- [secondary_model] config + KIMI_SECONDARY_MODEL/EFFORT bind newly
  spawned subagents to a cheaper model behind the secondary-model
  experiment flag, with primary/secondary model params on Agent and
  AgentSwarm and upfront session warnings
- full disallowedTools deny semantics (exact names + mcp__ globs)
  evaluated by the tool manager and persisted in the agent wire

* fix(cli): guard optional agentFiles in the prompt runner

runPrompt is also driven programmatically (headless goal flow) with
options that never pass through the CLI parser defaults, so agentFiles
can be undefined; mirror the addDirs optional-chaining pattern. Also
extend the SDK experimental-feature assertion with the secondary-model
flag.

* fix(agent-core): preserve custom agent bindings on v1

* fix(agent-core): narrow secondary model error hints

* fix(agent-core): persist custom agent profile bindings

* Delete .changeset/sdk-agent-profile-options.md

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* Update v1-custom-agent-files.md

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* Update v1-secondary-model.md

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* Update v1-custom-agent-files.md

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* fix(agent-core): keep SYSTEM.md a prompt-only overlay for delegation

* docs: update agent file and secondary model availability wording

* fix(cli): reject --agent-file combined with session resume

The resume path only forwards the agent file's name for the bound-profile
assertion; the file's content is never re-applied (the session keeps its
creation-time catalog snapshot). Previously the combination was silently
accepted, so an edited file (or a same-named one) appeared to apply but did
not. Reject it at option validation and document the constraint.

* refactor(agent-core): share prompt-section prose and note v2 twins in agentfile headers

The Windows notes, additional-dirs and skills prose blocks existed twice:
inline in the builtin default template (system.md) and as constants in the
agent-file renderer (from-file.ts). Extract them to profile/prompt-sections.ts
as the single source: system.md renders them through injected KIMI_* template
variables and from-file.ts imports the same constants. Rendered prompts are
byte-identical for all four builtin profiles across macOS/Windows and
skills/dirs on/off; a new test pins system.md to the shared constants.

Also mark each profile/agentfile file with the path of its agent-core-v2
counterpart so format/semantics changes land in both engines.

* feat(cli): add /secondary_model command for the subagent model

Mirror /model: a picker with a thinking-effort step that persists [secondary_model] and live-applies to the current session via a new Session.setSecondaryModel RPC (node-sdk wrapper included), so newly spawned subagents bind the new model right away. The /model picker now hides the synthesized __secondary__ derived entry; docs and the update-config builtin skill mention the section.

* feat(tui): show the bound model in subagent run stats

Subagents report their model alias via agent.status.updated after spawn; resolve it to a display name and surface it in tool-call subagent stats and agent-group rows.

* fix(agent-core): validate agent profile before session persistence

* fix(agent-core): refresh subagent tools after model switch

* fix(agent-core): show subagent model preferences

* fix(agent-core): preserve secondary model recipe on live apply

* fix(agent-core): make secondary model apply explicit

* fix(tui): refresh secondary model display state

* chore: merge secondary model changesets into one

* Add /secondary_model command for subagent configuration

Show each subagent's model in the subagent card header and agent-group rows. Requires the secondary-model experiment (KIMI_CODE_EXPERIMENTAL_SECONDARY_MODEL=1); run /secondary_model to pick a model and thinking effort, applied to the current session immediately.

Signed-off-by: 7Sageer <sag77r@hotmail.com>

* fix(agent-core): align explicit agent file precedence

* fix(agent-core): let disallowedTools deny select_tools

* chore(cli): drop engine mention from --agent/--agent-file help text

* feat(cli): support --agent/--agent-file in the interactive TUI

Bind the selected agent profile to the startup session when launching
the TUI with --agent/--agent-file, including the session created after
an OAuth login at startup. Sessions created later in the process (/new)
keep the default profile.

Make both flags creation-only in every mode: combining them with
--session/--continue is now rejected in print mode too, since resume
restores the bound agent from the session automatically.

* fix(agent-core): persist new secondary-model selections under env overrides

stripSecondaryModelConfig restored secondary_model.model/default_effort
from raw whenever KIMI_SECONDARY_MODEL/KIMI_SECONDARY_EFFORT was set, so
a /secondary_model pick made under the env vars was silently discarded
on write. Restore from raw only when the value being written still
equals the env value (an overlay round-trip), mirroring the pointer
check in stripEnvModelConfig; a genuinely different selection now
reaches config.toml.

* fix(cli): report the effective secondary model when env overrides the pick

/secondary_model toasted the picked alias even when
KIMI_SECONDARY_MODEL/KIMI_SECONDARY_EFFORT made the session bind a
different model. Read the effective binding back from the reloaded
config (as /model does from session status) and warn with the
env-overridden values instead.

* feat(tui): show the bound model name in the AgentSwarm panel header

---------

Signed-off-by: 7Sageer <sag77r@hotmail.com>
2026-07-29 12:06:26 +08:00
Haozhe
f79fde2b90
feat(node-sdk): migrate the SDK method surface to agent-core-v2 (#2262)
* feat(node-sdk): add agent-core-v2 backed SDKRpcClientV2 harness

- add SDKRpcClientV2 wiring the v2 engine (DI x Scope) in-process via the
  klient memory transport, with getExperimentalFeatures migrated to
  klient.global.flags.list() and unmigrated methods failing fast
- export createKimiHarnessV2 / SDKRpcClientV2 from the SDK index
- wire the experimental v2 gate into the CLI interactive shell (run-shell)
- extend build-dts to bundle agent-core-v2 and klient declarations

* feat(node-sdk): migrate listWorkspaceSkills to agent-core-v2

- add engineAccessor escape hatch exposing the in-process engine's app-scope
  service accessor for SDK methods the klient facade does not cover yet
- implement listWorkspaceSkills via ISkillDiscovery plus the v2 user/project
  root helpers and BUILTIN_SKILLS (plugin skills and skillDirs still gaps)
- add a v1-v2 parity test pinning identical return values per migrated
  method, with understood gaps listed explicitly in KNOWN_DIFFS

* feat(node-sdk): migrate the SDK method surface to agent-core-v2

- implement the remaining SDKRpcClientBase methods on SDKRpcClientV2,
  routed through the klient facade where covered, the engineAccessor
  escape hatch where the engine has a service, or SDK-side rebuilds on
  v2 primitives where only primitives exist (config shape mapping,
  global mcp.json store, MCP OAuth flows, importContext, session
  warnings, print background policy)
- translate the v2 event stream into the v1 Event union and bridge
  approval/question/user_tool interactions per live session
- rebuild resume replay by folding the v2 wire.jsonl through the v1
  agent restore pipeline, so resumed sessions render history again
- keep deleteSession as not_implemented; the v2 engine has no delete
  capability
- extend the v1-v2 parity suite to every migrated method, pinning
  understood engine differences in KNOWN_DIFFS
- add the dev:cli:v2 root script to launch the TUI on the v2 engine

* fix(node-sdk): await the v2 undo and compaction-cancel agent calls

* test(cli): spread the real oauth module in the telemetry test mock

* feat(node-sdk): forward v2 engine telemetry to the host telemetry client

* feat(cli): gate the v2 TUI route behind a dedicated KIMI_CODE_TUI_V2 switch

* fix(node-sdk): honor skillDirs on the agent-core-v2 SDK route

The v2 SDK client accepted KimiHarnessOptions.skillDirs (the CLI's
--skills-dir) but never seeded it into the engine, so explicit skill
dirs were silently dropped on the v2 TUI route and the Skill tool
could not find skills from them. Seed skillCatalogRuntimeOptions at
bootstrap and let listWorkspaceSkills resolve the explicit dirs as
the user source, matching the engine's session skill catalog.

* refactor(cli): gate the v2 TUI route behind the master experimental flag again

Drop the dedicated KIMI_CODE_TUI_V2 switch: the TUI v2 harness route is
gated by KIMI_CODE_EXPERIMENTAL_FLAG, the same master switch as the
kimi -p v2 route. The gate tests are kept with updated assertions, and
dev:cli:v2 sets the master flag again.
2026-07-28 20:29:45 +08:00
liruifengv
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
2026-07-28 15:03:25 +08:00
Kai
b5efba7abc
fix: consume the model metadata declared by the models.dev catalog (#2015)
* fix: stop advertising Claude thinking efforts for non-Claude models

Models served over the Anthropic protocol whose names carry no Claude
marker (e.g. a catalog-imported Kimi K3) no longer inherit the latest
Opus effort list, so the model selector stops offering levels the model
does not accept. The models.dev catalog import now also parses
reasoning_options and records the declared effort levels on the model
alias, so K3 offers its real levels (low / high / max).

* fix: consume deprecated, override, and input-limit metadata from the models.dev catalog

- Models declared status=deprecated in the catalog are no longer
  offered for import.
- Per-model provider overrides on gateway providers (an npm package
  targeting an Anthropic SDK plus a usable endpoint) now land as alias
  protocol and base_url, so those models are served over the right
  protocol and endpoint; overrides without a usable URL are skipped.
- A declared limit.input now sizes the context budget instead of the
  larger total context window (e.g. gpt-5: 272k instead of 400k).

The model alias schema gains an optional base_url field (not accepted
in overrides) that Anthropic wire resolution prefers over the
provider-level base URL.

* fix: honor thinking-disable semantics and the OpenAI-compatible fallback in catalog imports

- reasoning_options 'none' is the model's off encoding: off_effort flows
  from the catalog through the model alias to the OpenAI wire providers,
  so turning thinking off sends 'none' instead of omitting the effort
  field; models with effort levels but no way to disable thinking are
  imported as always_thinking and no longer offer an Off option.
- Bare Claude family aliases (e.g. sonnet-latest) recover the inferred
  Anthropic effort profile; v2 comment conventions restored.
- Providers whose SDK the catalog does not type now fall back to the
  OpenAI-compatible wire (with a visible "guessed" note) instead of
  being refused; imports lacking a usable endpoint ask for one
  (--base-url on the CLI, a prompt in the TUI). Proprietary SDKs
  (Amazon Bedrock), unrecognized explicit types, and env-placeholder
  URLs are refused with a clear reason.

* fix: align catalog imports with the reference models.dev consumer

- A JSON null tier in declared effort values is now read as the 'none'
  off-encoding (previously such models were wrongly imported as
  always-thinking with no way to turn reasoning off).
- Alpha-status models are filtered out alongside deprecated ones.
- Models whose per-model provider override targets a wire that cannot be
  expressed per-model (e.g. Claude on google-vertex, whose wire here is
  Gemini-mode Vertex, or gpt entries on an Anthropic provider) are
  skipped instead of being imported under the silently wrong protocol.
- interleaved: true no longer pins reasoning_content: the provider's
  default three-field scan is wider and the pinned key only narrowed
  reasoning parsing for gateways answering with another field name.

* fix: require endpoints for Anthropic-compatible catalog imports and honor --base-url

- catalogProviderNeedsBaseUrl now covers the Anthropic wire: a
  non-official Anthropic-compatible vendor without a concrete catalog
  endpoint (e.g. google-vertex-anthropic) must supply --base-url / the
  TUI prompt instead of silently falling back to the default Anthropic
  endpoint.
- --base-url now takes precedence over the catalog-declared endpoint,
  and an empty --base-url is rejected instead of persisting a blank
  endpoint.

* fix: enforce always-on thinking on every wire and refuse Cohere at import

A model that declares always_thinking (e.g. a catalog-imported gpt-5)
no longer resolves to a dishonest off state via thinking.enabled=false
or an SDK/ACP off request: resolution clamps to the model's default
effort on every wire instead of letting upstream keep reasoning while
the UI reports Off. The Anthropic warn-and-send path for unlisted
effort levels is unchanged. Cohere's proprietary SDK joins Amazon
Bedrock on the import-refusal list instead of being guessed as
OpenAI-compatible.

* fix: harden catalog import edge cases

- An explicit but unrecognized catalog type is now refused before
  npm/id inference, so a future catalog protocol is never silently
  miswired through the OpenAI fallback.
- User-supplied --base-url values for Anthropic-wire providers get the
  same trailing-/v1 normalization as catalog endpoints, avoiding
  /v1/v1/messages requests.
- The TUI import prompt rejects env-placeholder base URLs like the CLI
  does.

* fix: await the floating assertion promise in the catalog add CLI test

* refactor: unify catalog import resolution into a single decision function

Wire-type inference, the OpenAI-compatible fallback, proprietary-SDK
refusal, endpoint adaptation, and the base-URL requirement are now
produced together by resolveCatalogImport, one pure resolver consumed
by both the CLI and the TUI — replacing the cooperating predicates
(inferWireType, isGuessedWireType, catalogProviderNeedsBaseUrl) whose
permutations kept producing edge cases. No behavior change.

* fix: close configured-off clamp hole, keep inferWireType compat, carry same-wire override endpoints

- A configured thinking.effort = "off" no longer bypasses the
  always-on clamp: it is treated as absent and the model default
  applies, mirrored on both engines.
- The previously public inferWireType stays as a deprecated
  compatibility wrapper over resolveCatalogImport so existing SDK
  consumers do not break on a patch release.
- Catalog model overrides that stay on the provider's wire but declare
  their own endpoint now persist it on the alias (and the v1 OpenAI
  wire branches honor alias-level base URLs like the Anthropic branch).

* fix: split total window from input cap and close override/endpoint gaps

- max_context_tokens once again means the total context window (used by
  completion budgeting); a model's declared input limit is tracked as
  max_input_tokens, which compaction, context-splice and usage-ratio
  checks prefer — fixing the over-clamping introduced when the input
  cap was stored as the context budget.
- A catalog endpoint declared only as an env placeholder now always
  produces needs-base-url (official SDK included), so credentials are
  never sent to the public vendor host by default.
- api-only per-model overrides are honored as same-wire endpoint
  changes; overrides targeting another known but inexpressible wire
  (e.g. google-genai on an OpenAI gateway) are skipped; same-wire
  models whose declared endpoint is an unusable placeholder are
  skipped instead of silently rerouted.

* style: drop a function-level comment from the v2 thinking resolver

* chore: consolidate the PR's changesets into two user-facing entries
2026-07-22 01:23:31 +08:00
Haozhe
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
2026-07-20 15:33:05 +08:00
Kai
365ba0001d
fix: use timestamped default filename for session debug exports (#1788)
Some checks are pending
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 / build (push) Waiting to run
CI / VSIX package audit (darwin-arm64) (push) Waiting to run
CI / VSIX package audit (all) (push) Waiting to run
CI / VSIX package audit (win32-x64) (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Release / Native release artifact (push) Blocked by required conditions
Release / Desktop 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
Both engine implementations defaulted the export ZIP path to
<sessionId>.zip, so running /export-debug-zip or kimi export twice for
the same session silently overwrote the first archive. The default
filename is now kimi-debug-<shortId>-<timestamp>.zip (UTC, second
precision), matching the /export-md naming convention. Explicit
-o/outputPath behavior is unchanged.
2026-07-16 19:32:06 +08:00
qer
d1ca65e1de
feat(vscode): migrate extension to Node SDK (#1769)
* feat(vscode): migrate extension to Node SDK

* fix(vscode): address CI failures

* fix(vis): handle token count records

* fix(vscode): keep chat toolbar and header readable at narrow widths

* fix(vscode): map yolo to core yolo permission and honor the global yolo setting

* docs(vscode): record Node SDK migration design

* docs(vscode): split breaking changes out of the 0.6.0 changelog

* fix(vscode): keep a resumed session's thinking effort instead of reapplying the default

* fix(vscode): announce session status when a view attaches so the display matches it

* fix(vscode): align webview thinking effort handling with the TUI
2026-07-16 17:27:21 +08:00
_Kerman
df75a0f5c2
refactor(agent-core-v2): derive session busy from agent activity (#1751) 2026-07-15 23:33:58 +08:00
7Sageer
d601847f22
fix: send the product User-Agent on provider registry and catalog fetches (#1597)
* fix: send the product User-Agent on provider registry and catalog fetches

Registry (api.json) and models.dev catalog fetches only carried the
runtime default User-Agent while every other outbound request sends
kimi-code-cli/<version>. Thread an optional userAgent through
fetchCustomRegistry / fetchCatalog and the shared refresh host, pass
the product UA from the CLI, TUI, and both daemons, and seed a default
product UA in kap-server that hosts can override via opts.seeds.

* refactor: use options for registry fetches
2026-07-13 17:45:34 +08:00