* perf(tui): batch and cache agent swarm progress rendering
* perf(agent-core-v2): evict completed subagent scopes behind an LRU cache
* fix(agent-core-v2): stop the agent actor in the session machine when its scope is removed
* fix(agent-core-v2): emit a terminal event for the final rate-limited run and defer eviction while background tasks run
* fix(agent-core-v2): include in-flight retries when abandoning suspended agents
* fix(agent-core-v2): roll back the closing flag when scope removal fails and wait per the configured eviction timeout
* fix(agent-core-v2): terminalize subagents spawned after batch cancellation and report task timeouts as failures
* chore: restore upstream/main state of files mangled by lint-staged autofix in the merge commit
oxlint --fix rewrites (prefer-set-has, catch-error-name, prefer toSorted/.at(-1)/toReversed)
touched seven files the branch never modified; one of them (oauth managed-kimi-code.ts)
no longer typechecks. Restore all seven to their exact upstream/main state. Committed
with --no-verify so the same autofix does not re-mangle them.
* fix(agent-core-v2): retry deferred scope evictions without exhausting the attempt budget, dedupe terminal events for starting retries, and patch-level the changeset
* fix(agent-core-v2): finish scope teardown instead of reactivating a partially torn-down scope
* fix(agent-core-v2): terminalize subagent-start hook cancellations and re-acquire resume targets after metadata reads
* fix(tui): record cancelled background agents in the transcript
* fix(agent-core-v2): route mirror terminal events through the batch exactly-once guard
* fix(tui): guard terminal swarm members from re-cancellation and deliver terminal status to cancelled foreground subagent cards
* fix(agent-core-v2): classify eviction as removed when teardown already closed the scope
* Merge remote-tracking branch 'upstream/main' into perf/subagent-scope-eviction
Skip lint-staged for this merge: its oxlint --fix pass rewrites unrelated files
(prefer-set-has etc. mangled seven files in the previous merge commit), while the
merge content is upstream's lint-clean code plus conflict resolutions already
verified by oxlint.
* chore: restore upstream/main changes dropped by a stash roundtrip during the merge
A git stash/pop cycle used to isolate an unrelated flaky test reverted 82 files of the
merge result to the pre-merge branch state, so commit 375ff2a4a missed them. The
working tree already held the correct merged content; this commit restores it.
* fix(agent-core-v2): propagate the cancellation reason from turn abort to tool signals
Cancelling a turn aborted the turn's llm scope and every tool scope
without a reason, so tools and subagents only ever saw the default
AbortError and could not tell a user cancellation from a system abort.
With the swarm terminal-event classification this surfaced as
`subagent.failed` ("This operation was aborted") on Ctrl+C instead of
`subagent.cancelled`.
Carry the reason end to end: `MachineEngine.abort(reason)` sends it on
`input.abort`, the agent machine remembers it for the turn and forwards
it to `turn.abort` and to every tool scope it aborts, the turn machine
passes it to the llm scope, and the loop service hands its cancellation
(a `UserCancellationError` by default) to the engine.
* fix(agent-core-v2): stop rebuilding an evicted subagent after its caller aborts
`createAgentAwaitingClose` polled for the previous scope to finish
closing without ever looking at the caller's abort signal, so cancelling
an `Agent` or `AgentSwarm` resume during that wait still recreated the
scope afterwards. In the `Agent` path the run then rejected before any
terminal event was mirrored, leaving an idle scope that the scope cache
no longer tracked.
Thread the caller's signal into the helper and check it before every
attempt; the Agent tool passes its call controller and the swarm service
passes the attempt signal. Also trim the eviction changeset to the one
sentence the changeset rules ask for.
* fix(agent-core-v2): report task-manager aborts of a subagent as cancelled
When the task manager times out or stops a background subagent it aborts
the run with a plain reason ('Timed out', the TaskStop reason), and the
run rejects with that raw reason. The task manager therefore classifies
the task as timed_out or killed, but the mirror only recognised
`UserCancellationError` as a cancellation and emitted `subagent.failed`,
so the transcript recorded a failure before the authoritative
`background.task.terminated` event could land.
Classify a terminated run by the shape of its abort reason: a real
Error that is not an AbortError (the swarm's own per-item timeout) stays
a failure, everything else that aborted the run is a cancellation. The
swarm's start-failure path uses the same rule.
* fix(agent-core-v2): flush the wire before evicting a subagent scope and re-pick LRU candidates
Two gaps in the scope-cache eviction pass:
- `AgentLifecycleService.remove()` swallows a failed wire flush and still
tears the scope down, so an eviction that hit a disk-full or transient
I/O error would drop the in-memory scope and let a later resume rebuild
from a stale wire. Flush the agent's wire explicitly before removing;
if that fails, keep the scope resident and retry on a later pass. The
idle and task checks are repeated after the flush so a resume that
started meanwhile is not cancelled by the removal.
- The pass snapshotted its candidates up front and re-validated them by
attempt count only, so a scope that was resumed and completed again
while an earlier eviction was awaited kept matching with attempts 0 and
was evicted despite now being the most recently used. Pick the oldest
eligible entry from the live map on every step instead, skipping only
the entries this pass already tried.
---------
Co-authored-by: Kaiyi <me@kaiyi.cool>
The platform /usages payload now serves per-window used ratios
(goods_version, a usages map of limit5h/limit7d/monthTotal/monthCode
entries with used_ratio/reset_time, and boosterWallet) instead of
absolute used/limit rows.
- oauth: parse and fetch the quota model; keep the platform helpers
- toolkit / agent-core-v2 / node-sdk: getManagedUsage returns the quota
- kap-server: /oauth/usage passes the camelCase quota shape through
(the legacy snake_case wire is gone)
- TUI /usage: plan rows are presence-driven from the quota payload;
the monthly row carries a kimi/code breakdown line
- docs: server-api reference updated for the new response shape
* fix(agent-core-v2): resolve tower roster identities to the latest registration
Agent ids restart from agent-0 in each session, so a freshly spawned
worker collided with the previous session's roster entry and every
find-first lookup (caller identity, write guard, death bookkeeping)
resolved to the stale entry: workers read the wrong inbox, sent under
the wrong name, and were denied their own writes, mission updates, and
reviews.
- resolveAgent picks the latest roster entry for an agent id; all
callers (resolveCallerName, write guard, resume veto, died/revived)
go through it
- registerAgent retires same-agent-id entries before appending
- enter() (/tower on) now adopts the workspace roster itself instead of
relying on a manual TowerInit
* fix(agent-core-v2): adopt the tower roster when a replayed tower mode finds a dead foreign owner
A forked or restored session replays the durable TowerModeEnter into an
active tower mode without ever calling enter(), so the roster adoption
added there never ran on the restore path: with the previous owner
session gone, the fork kept the mode (and the stale roster) until a
manual TowerInit. The dead-owner branch of the restore-time
reconciliation now adopts the workspace, mirroring enter()'s takeover
of an idle owner; a live foreign owner still causes an exit.
* fix(agent-core-v2): await the outgoing tower owner's release before adopting its roster
Taking tower mode over from a live but idle owner called the owner's
synchronous exit(), whose ownership release ran fire-and-forget, and
immediately adopted the roster — two concurrent load-modify-save cycles
on .tower/comms/state.json that could lose the new owner or resurrect
the stale roster. exit() now awaits the ownership release and the
takeover awaits the owner's exit(); the remaining callers either await
(RPC/HTTP tower-mode-off) or explicitly ignore the promise (mode mutex,
restore-time self-exit).
* fix(agent-core-v2): fail tower activation when the roster adoption cannot complete
Adoption failures (unreadable or unpersistable state.json) were
swallowed into a log warning, so enter() and the stale-owner restore
both activated tower mode with the foreign owner and stale roster still
in place — the exact identity mismatch the adoption is meant to
prevent, now with a false success report. adoptTowerRoster() now throws
a TowerProtocolError with the underlying cause: enter() propagates it
before any tool is activated, and the restore path drops the replayed
tower mode instead (it must not throw inside the restore hook chain).
* fix(agent-core-v2): propagate unreadable tower state during roster adoption
adopt() treated any state.json read failure as an uninitialized
workspace via isInitialized()'s catch-all, so an existing but
unreadable state file (EACCES, EISDIR, transient I/O) reported a
successful no-op adoption and enter() activated tower mode against the
foreign state — bypassing the activation guard added in the previous
commit. Only ENOENT now means uninitialized; every other read error
propagates into the fail-activation / deactivate-on-restore paths.
---------
Co-authored-by: konghuanjun <konghuanjun@moonshot.ai>
* refactor(agent-core-v2): fold the prompt queue into the agent loop service
Delete AgentPromptService/IAgentPromptService: prompt submit/queue/steer/
abort now ride the agent machine queue through IAgentLoopService's extended
surface (submitPrompt/submitSteerPrompt/enqueuePrompt/steerPrompts/
abortPrompt/drainPrompts/injectPrompt/retryPrompt/promptQueue plus
hooks.onBeforeSubmitPrompt and the promptAdmission reservation slot).
Durable prompt.* event classes move to agent/prompt/promptEvents.ts and keep
their registrations; promptAdmissionKey keeps its fold. Dead assets go away:
promptResolutionKey, PROMPT_ALREADY_COMPLETED (kap-server 40903 path),
promptLaunchingKey and clear(). Compaction gating (queue while compacting,
resume on finish) moves into the loop reservation drain.
The 'agentPromptService' wire name keeps its shape: a thin AgentPromptChannel
facade (same decorator id) forwards submit/submitSteer to the loop, so
klient memory/ipc/debug transports and the kap-server dispatcher are
unchanged.
* test(agent-core-v2): re-pin prompt queue semantics on the loop service
Rewrite promptService.test.ts (25 cases, count unchanged) against the real
AgentLoopService with the scripted-LLM harness, fold the launch-window
reservation out of promptQueue().pending to match the old snapshot contract
exactly, and update the debug-channels pin to the facade's submit/submitSteer
surface.
* refactor(agent-core-v2): lint-align the loop prompt queue surface
* fix(agent-core-v2): settle prompt completions on the turn-result chain
Match the deleted service's microtask position: prompt.completed/aborted and
the completion deferreds fire from a result.then chain attached at bind time
instead of synchronously inside endTurn, so wire/emit ordering with
post-turn producers (tool unregister, next-prompt launch) stays identical to
the baseline snapshots. Steered children move onto the reservation so the
chain can settle them after the active turn is gone.
* refactor(agent-core-v2): move the prompt-submit gate into the agent machine
* refactor(agent-core-v2): keep image compression captions inline in prompt messages
* refactor(agent-core-v2): drop the durable prompt admission facility
* refactor(kap-server): resolve prompt auth at turn runtime instead of submit time
* refactor(agent-core-v2): teach the agent machine queue metadata, multi-id steer, gate rewrite, and pause
* refactor(agent-core-v2): converge prompt submission on machine queue user entries
* refactor(agent-core-v2): replace loop prompt records with a waiter registry fed by machine events
* feat(agent-core-v2): make the compaction attempt limit configurable
Add loop_control.compaction_max_attempts (default 5) to control the
maximum total attempts for a failing compaction request, replacing the
hardcoded MAX_COMPACTION_RETRY_ATTEMPTS constant. The value flows
through ProfileModelContext into the compaction retry loop, covering
both retryable generation errors and empty/truncated response shrinks.
* fix(agent-core-v2): bound compaction shrink attempts and compute retry delays lazily
Address codex review on #3750: the empty/truncated shrink path allowed
maxAttempts + 1 requests (throwing only when the shrink counter exceeded
the limit), and the backoff delay array was allocated eagerly with
maxAttempts - 1 entries before the first request. The shrink path now
stops at the configured limit, and each delay is computed lazily from
the retry index.
* fix(agent-core-v2): count every compaction request against the shared attempt limit
Address the second codex review round on #3750: the retryable-error and
empty/truncated recovery paths kept separate counters, and the retry
budget reset after each shrink, so alternating failure modes could issue
more requests than compaction_max_attempts. A single requestAttempts
counter now gates all recovery paths (the overflow shrink path keeps its
own tighter cap of 3), making the configured value a true maximum on
total requests per compaction round.
* feat(agent-core-v2): persist the human runtime mirror as human.* records in the wire journal
Introduce mode-2 coexistence: the machine eventStore journal becomes a
tree-projection-filtered adapter over the same wire file (single writer via
the WireService append queue), the engine switches from memoryJournal to the
file journal with a seed fallback while no human records exist, and cold
readers skip the new record family constructionally. Adds downgrade
round-trip e2e coverage, whitelists agent.switched/context.undone from
unknown-record telemetry, asserts the undo switch triple after flush, and
resets the machine engine after an undo rerun so machine and wire turn
clocks stay aligned.
Squashed from:
refact(agent-core-v2): project wire journal as an in-memory branch tree behind IAgentJournal
refact(agent-core-v2): undo as branch switch with dual-emitted legacy record and restore replay
refact(agent-core-v2): queue late attachments during restore rerun and flatten fork sources to the active chain
feat(agent-core-v2): persist the human runtime mirror as human.* records in the wire journal
* feat(agent-core-v2): add full compaction to the human agent domain
* refactor(agent-core-v2): keep pending input state events out of the wire journal
* refactor(agent-core-v2): flip agent DI scope ownership to session-spawned agent actors
* refactor(agent-core-v2): build the agent wire and blob views in the session layer
* refactor(agent-core-v2): persist human domain records under the agent.* wire namespace
- replace the human. record prefix with an explicit whitelist (AGENT_WIRE_RECORD_TYPES) guarded against the Event2 vocabulary; legacy human.agent.* journals stay readable through a transition prefix
- unknown-type records skipped during restore now go to the log file instead of the console (malformed records still report to the console)
- the human agent state machine now assigns turn ids and echoes the drained queue item in turn.started; the facade adopts machine ids instead of reserving its own
- the turn machine produces the step ordinal and surfaces it via a first-class step.started fact; the llm requester layer stays step-agnostic
- turn.prompt wire records carry an explicit turnId; turnKey and transcript foldFacts read it directly with a counting fallback for legacy records
- queued prompts are addressed by promptId: cancel(turnId) is active-turn only, cancelQueued targets queue entries, Turn.id resolves once the turn starts
- drop PromptPayload.disabledTools and its klient/node-sdk pass-through (no client consumer)
* refactor(interaction): replace per-agent InteractionService with human-native session facade
- add human/interaction: global interaction machine + tag-query facade (enqueue/respond/findAll/findOne/wait)
- add session-scope SessionInteractionService owning one interaction actor per session
- rewire approval/question/userTool/sessionActivity to the session facade and delete features/interaction
* refactor(interaction): rewire kap-server, klient and node-sdk to the session interaction facade
- kap-server routes, ws broadcaster and transcript binding read ISessionInteractionService (tags replace origin)
- klient memory dispatcher delegates to the session service; zod contract mirrors origin -> tags
- node-sdk session wiring subscribes the session service directly
- register interaction wire events in their new home and regenerate the wire manifest
* refactor(agent-core-v2): replace interaction DI services with a global human singleton
resolve rebase conflicts
- scrub all ambient KIMI_CODE_* env vars in the agent-core-v2 and
kap-server test setups so developer shell env cannot leak in
- drop the load-sensitive stepRetry fake-timer cases and the three
FSEvents-based skill catalog watch cases
- give node-sdk, klient and the vscode extension project an explicit
15s testTimeout matching their integration-test nature
- retry temp-dir removal in taskManager teardown to absorb async
terminal-effect writes
* fix(telemetry): stop silently dropping key event attributes
- turn_ended now carries error_type (the engine error code) when a turn
fails, matching the v2 event catalog definition that no emitter set
- TelemetryClient reports properties dropped by sanitizeProperties via a
new onUnexpectedError hook, wired to the CLI, web, and v2-print logs
- session_started client attribution keys emit empty strings instead of
null so they survive payload flattening
* fix(telemetry): sanitize at sink accept time and guard the drop handler
Addresses review on #3638:
- Properties are sanitized when the sink accepts the event (at track time
on the direct path, at drain time for the pre-init queue), so drops on
events queued before initialization are reported once
onUnexpectedError is installed
- The onUnexpectedError hook is invoked behind a guard, mirroring the
agent-core-v2 unexpectedError path, so a faulty host handler cannot
abort track()
* fix(telemetry): snapshot queued properties at track time
Addresses review on #3638: the pre-init queue now stores the sanitized
snapshot taken at track time (only primitives survive, so the snapshot
is exact), and the dropped keys ride along as pending diagnostics that
are replayed once the handler is installed. Callers mutating a reused
properties object between track() and initialization can no longer
alter the emitted event or its drop reports
* feat(kimi-code): two-line collapsed tool cards with width-aware headers
Collapsed cards now show the call on line one and a single dim outcome
row on line two: a Bash command's last output line (the live tail while
running) with a line-count chip, a Grep/Glob path sample, or a generic
tool's first output line. Read groups stay header-only and honor the
global expand state; failed calls keep their preview. Headers split into
head / flexible / tail segments so a long command fills the terminal
width before the chip.
* feat(kimi-code): NotifyUser tool and mid-turn update panel
Add an experimental NotifyUser tool (flag notify_user, off by default)
that agent-core-v2 offers only when the host declares the update_panel
UI capability at bootstrap; the TUI declares it through the SDK harness
options. The system prompt's guidance to send updates is injected only
when the tool is active for the agent. The TUI renders the turn's
updates in a panel above the editor (stacked, 8-row window, Ctrl+N pages
back) that the next turn clears, and keeps a one-line card in the
transcript.
* feat: add subagent updates and bidirectional paging
* fix: preserve session tools and prompts when toggling updates
* fix: correct notification delivery and CI regressions
The decode-window split measured part arrival at the moment the event
loop resumed the pending await, so any loop-busy work (rendering,
timers, GC) between parts was reported as server time. Measure loop
utilization across the decode window with perf_hooks and split it into
loop-idle wait (server), per-part processing (client), and a new
clientBlockedMs bucket (loop busy with other work), carried through to
the step events, wire record, transcript contract, and the TUI debug
timing line.
Resuming a session dominated by cron-fired turns mounted an unbounded
transcript: limitAgentReplayByTurns only counted human-typed prompts as
turn boundaries, so the replay limit never engaged (10,478 records for
11 'turns' on an affected session). The mounted transcript made every
render frame O(total lines), starving the event loop during streaming
and collapsing client-side drain throughput.
Treat cron_job and cron_missed replay records as turn boundaries,
matching the TUI transcript window's own boundary rule.
- remote-control is always available: kimi rc, kimi web --remote-control and /remote-control no longer require KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL or the master flag
- add the [database] config section with base (session-index read model) and search (global search worker) kill switches, both defaulting to true
- rename the kill-switch env vars to KIMI_CODE_PERSISTENCE_MINIDB_READMODEL and KIMI_CODE_SEARCH_WORKER (precedence: env > config > default)
* refactor: remove the legacy agent-core v1 package
Delete packages/agent-core (v1 engine) and migrate node-sdk fully onto
agent-core-v2:
- node-sdk owns its public contract (error protocol, KimiConfig schema
and file IO, process-global logging facade, telemetry client
interface); event and interaction types re-export from
@moonshot-ai/protocol or agent-core-v2
- createKimiHarness is now the single harness factory, backed by the
v2 client; the v1 SDKRpcClient is deleted
- CLI and vscode run agent-core-v2 unconditionally;
KIMI_CODE_LEGACY_FLAG and useAgentCoreV1 are removed
- the wire.jsonl replay fold moves into agent-core-v2's replayBuilder
(foldWireRecords), and the v2 journal now persists tool-call display
so replay restores diff/todo displays
- TUI engineV2 branches collapse to the v2 path
* test(vscode): retry temp-dir teardown in replay-resume integration test
* refactor(agent-core-v2): absorb the human layer into src/human and rename vendored kosong to llm-adapter
* feat(agent-core-v2): preserve media attachment names in human message contract
* refactor(agent-core-v2): remove llm-adapter code duplicated in human/llm
* refactor(agent-core-v2): replace the agent loop with a facade over the human turn state machine
* refactor(agent-core-v2): rename the loop adapter layer to loop/machine
* test(acp-server): implement gateway resolve in the scripted provider stub
* refactor(agent-core-v2): restore v2 batch semantics in the loop machine tools adapter
- delegate each step's tool calls to the v2 tool executor as one whole
batch: beginBatch arms the expected call set from the llm.done entry,
per-actor execute calls collect into a pending barrier, and the full
batch is submitted in tool-call order once complete
- drop the serial batchChain/batchStopped/skipped approximation so the
executor's prepare-stage stopBatchAfterThis skip, veto short-circuit,
and ToolScheduler access-based concurrency apply natively again
* fix(build): update flake pnpmDeps hash for the post-absorb lockfile
* fix(agent-core-v2): align loop facade reservations with the machine queue by id
* fix(agent-core-v2): drain persisted wire records before turn end
* fix(agent-core-v2): route executor-level tool errors to the loop error path and backfill aborted tool results on forced abort
* fix(agent-core-v2): keep turn step retrying in the facade step numbering space
* fix(telemetry): deduplicate session_started and model switch events
* fix(telemetry): keep engine session_started for direct v2 SDK clients and avoid model_switch race
* fix(telemetry): preserve model_switch when activation rebinds the same alias
* fix(telemetry): route TUI reload through harness
* test: update /reload message-flow test for harness reload route
* fix(agent-core-v2): rework tower mode enter failures and config-flag assembly
- IAgentTowerService.enter() returns a typed TowerEnterResult: the 4
former silent failure points now carry their reason (not-main-agent /
experiment-off / feature-not-assembled / owned-by-live-session with
the owner session id + title); kap-server and node-sdk map each reason
to a distinct SESSION_TOWER_MODE_INVALID message via
towerEnterFailureMessage() instead of one misleading catch-all.
- Fix config-sourced [experimental] tower flag never assembling the
tower feature: ConfigService seeds its state synchronously at
construction with a best-effort readFileSync of the config document it
owns, so the flag is already visible when TowerFeature's constructor
runs during App-scope creation. No event machinery: a runtime config
flip updates flag reads but does not re-run feature constructors — a
restart is required, pinned by test. docs/flag.md + docs/features.md
document the contract.
- Consolidated changeset for @moonshot-ai/kimi-code.
* fix(tower): release handler when teardown
---------
Co-authored-by: konghuanjun <konghuanjun@moonshot.ai>
FeatureAssemblyService ran during the eager bootstrap batch, while
ConfigService was still loading config.toml asynchronously, so
flags.enabled() always read false for config-sourced flags and gated
features (e.g. tower via [experimental] tower = true) never assembled.
registerFeature now accepts a { flag } option; gated registrations are
assembled in a deferred pass once IConfigService.ready resolves, and
IFeatureAssemblyService.ready exposes that pass. WorkspaceInstanceManager
.materialize also awaits config.ready so session creation cannot race
the deferred assembly.
* refactor(agent-core-v2): rebuild telemetry context as a scope-bound layered registry
* fix(agent-core-v2): prefer ambient model in telemetry envelope and repair print-runner stub
* fix(agent-core-v2): delete only the fragment registered by this telemetry binding
* fix(agent-core-v2): bind telemetry context writes to the binding's own fragment
* fix(agent-core-v2): keep stale telemetry emissions on the binding-owned fragment
* refactor(agent-core-v2): mirror turn-request trace_id into ambient telemetry context
* refactor(agent-core-v2): telemetry key-resolution cleanups
Drop the dead defineAgentTelemetryEvent branch in declaredKeysFor: agent_id is already copied unconditionally as identity plumbing before the declared-key loop, and the loop itself skips agent_id, so the branch had no runtime effect. Name the wire session id property key (WIRE_SESSION_ID_PROPERTY) so the session_id-to-sessionId mapping is explicit.
* refactor(agent-core-v2): flow ambient telemetry context to every event unconditionally
Remove the registry-key filtering and the FILTERED_CONTEXT_KEYS hard
filter from composeTelemetryProperties: the merged ambient now reaches
every event's properties, with session_id still remapped to the
camelCase sessionId key and explicit defined values winning on
collision. Explicit undefined values no longer clobber ambient fields.
With filtering gone, correctness for time-varying fields rests on
lifecycle discipline, so the loop now clears the ambient trace_id when
a step starts (mirroring the activeRequestTrace reset) instead of
relying on an explicit undefined payload value to mask the stale
mirror.
* refactor(agent-core-v2): move thinking_effort into the turn telemetry context
* refactor(agent-core-v2): replace the telemetry fragment registry with parent references
* feat(cli): add the kimi session list subcommand
- add the session command group with list: cwd scope by default, --all/--cwd/--archived/--limit/--json
- pass includeArchived through the SDK listSessions options (v2 klient query, v1 includeArchive mapping)
* docs: condense the session list changeset to one sentence
* fix(cli): sanitize session list rows and tidy option types
- strip control characters and line breaks from user-controlled row fields
- declare cwd/limit as plain optional properties without undefined unions
* fix(agent-core-v2): persist plan.revision as agent-relative key
- replace the session-scoped `path` field in durable plan.revision records
with an agent-relative `key`, resolved to a display path at read and
projection time against the current agent/session scope
- migrate legacy `path` records during wire restore and rewrite the journal
once migration succeeds, emitting low-cardinality
`wire_plan_revision_migrated` telemetry
- tolerate journal truncation during the migration rebuild and keep the
legacy `path` display in cold transcript folds for unmigrated records
- keep external transcript marker `path` and meta `reviewPath` projections
unchanged
* perf(agent-core-v2): fork sessions without materializing the target
- copy session files byte-for-byte (wire.jsonl included, parallel file
writes) and append the forked marker per agent instead of parsing and
rewriting every wire journal
- write the forked session's state.json, agent registry, and session
index entry directly without creating a live session or agent scopes;
fork returns SessionMeta and SessionForkedEvent drops the scope handle
- keep turnIndex truncation on the per-record path
- kap-server fork/createChild routes, the klient facade and wire
contract, and the node-sdk forkSession resume the forked session
through the normal resume path when a live handle is needed
- stop healing truncated source wires during fork; the forked session
repairs its own copy lazily on resume
- add the `kimi fork [--cwd] [-y]` CLI command
Tower mode is now entered only manually — /tower on, or /tower
<base-branch> to also pin the local branch missions merge back into. The
agent can no longer enter it on its own: TowerInit refuses while the mode
is off and points at the slash command.
/tower <base-branch> is deterministic end to end: a missing base branch is
created from the current checkout (uncommitted changes committed onto it
as a labeled WIP snapshot), the checkout switches to it, and the tower
workspace is initialized immediately instead of relying on the model to
apply the base. With an existing workspace the tower rebases onto the
requested base when no missions are open and refuses naming the blocking
missions otherwise. The base rides the tower_mode.enter event into the new
tower.base state key and TowerInit falls back to it.
Tower agent deaths (failed/timed_out/killed/lost) are recorded in the
tower protocol: TowerStatus marks dead roster entries and warns about
missions whose owner died, with a resume hint. The tower console prompt
now requires a per-worker deliverables summary before TowerTeardown.
The session profile REST surface accepts tower_base so web clients can
turn tower mode on with /tower <base-branch> instead of sending the
argument as a prompt.
Co-authored-by: konghuanjun <konghuanjun@moonshot.ai>
* feat(agent-core-v2): report enabled experimental flags in session_started telemetry
Add a sorted, comma-separated experimental_flags property to the v2
session_started event so feature-gated metrics gain a session-granular
exposure denominator, and carry the post-apply enabled set on the TUI
experimental_features_apply event to bound mid-session flag flips.
* feat(node-sdk): carry experimental flags on the harness session_started row
The v2 in-process path emits session_started twice (engine-side from
SessionLifecycleService, harness-side from KimiHarness), and only the
engine row carried experimental_flags, so consumers could double-count
or miss the flag dimension. Merge a per-emission dynamic getter into the
harness row, wired to the in-process engine's flag service, so both
rows report the same enabled flag set.
* fix(node-sdk): keep experimental_flags canonical on session_started
A caller-supplied experimental_flags in per-call sessionStartedProperties
could overwrite the engine-owned dynamic value, breaking row-to-row flag
consistency. Merge the dynamic properties after the session-scoped ones
so engine-owned keys always win.
* fix(agent-core-v2): count tower as exposure only once assembled
The tower flag reacts live to mid-process flips, but TowerFeature's
tools and profiles are assembled at App scope construction, so a session
started after an in-process flip cannot actually use tower. Filter it
out of experimental_flags on both session_started producers unless
isTowerFeatureAssembled says the feature is assembled in this process,
keeping the exposure denominator honest.
* test: recalibrate fixtures for the added constructor param and drop timing-sensitive tower assertion
The WorkspaceInstanceManager test builds the service with positional
args; the new flags param shifted unitHostFactory one slot. The tower
inclusion assertion in the SDK integration test depended on feature
assembly winning the race against async config load, which slow CI
runners lose; tower assembled-state coverage stays with the tower
service tests.
* refactor(agent-core-v2): move flag exposure gating into the flag definition
Telemetry consumers hardcoded tower's assemble-at-startup semantics to
keep it out of experimental_flags until assembled. Push the gate into
FlagDefinitionInput as an optional isExposed predicate owned by the
flag's own domain, expose it as IFlagService.exposedIds(), and let both
session_started producers call it — no feature-specific knowledge leaks
into the SDK or the session lifecycle.
* refactor(agent-core-v2): migrate todo domain to agent-scoped DI service
* refactor(agent-core-v2): migrate skill domain to agent-scoped DI service
* refactor(agent-core-v2): migrate reminder domain to agent-scoped DI service
* refactor(agent-core-v2): migrate interaction domain to agent-scoped DI service
* refactor(agent-core-v2): migrate cron domain to agent-scoped DI service
* refactor(agent-core-v2): migrate dateChange domain to agent-scoped DI service
* refactor(agent-core-v2): migrate goal domain to agent-scoped DI service
* refactor(agent-core-v2): remove agent runtime infrastructure
* docs(agent-core-v2): rewrite agent domain standard as agent-scoped DI service
* fix(agent-core-v2): restore actor services on late feature re-provide and order agent-created events after metadata registration
* feat(agent-core-v2): require approval for dangerous bash commands in all permission modes
* feat(agent-core-v2): deny dangerous commands in auto mode, unwrap command launchers, and skip the guard for non-interactive hosts
* feat(agent-core-v2): support disabling the dangerous-command guard via config
* test(node-sdk): project v2's env-materialized empty permission section in config parity
* feat(secondary-model): enable the subagent model pool by default
* feat(secondary-model): graduate the subagent model pool out of experimental
* fix(secondary-model): honor the v2 default_model key on the legacy engine
* fix(secondary-model): enforce forced subagent pools on the legacy engine
* fix(tower): keep reviewers on primary model
* fix(secondary-model): live-apply picker saves and replace the legacy model key
* fix(secondary-model): accept default_model in the live-apply setter and clear on removal
* fix(secondary-model): revert the legacy v1 engine, keep the v2 pool opt-out
* feat(secondary-model): graduate pool with model source telemetry
* fix(secondary-model): keep legacy engine opt-in
* test(sdk): pin secondary-model engine divergence
* fix(tower): honor forced model for reviewers
* feat(auth): split model readiness from sign-in state in /api/v1/auth
GET /api/v1/auth now reports models_ready (the default model resolves
against the configured catalog, providerless and env-injected models
included) instead of the compound ready flag, and no longer carries
default_model — config values are served by /config alone. The v1
summary schema follows.
OAuth managed-model refreshes now heal a lost default model: the
refresh snapshot includes defaultModel, so an unchanged catalog with
a missing default still lands the write-back branch and re-selects
one. The refresh also rebases onto a fresh config read after the
remote fetch, so a model or thinking change made during the fetch is
no longer overwritten. The shared discovery refresh path (scheduler,
POST /providers/{id}:refresh) heals the default the same way.
Config changes are now published to WS clients on every write path:
a debounced+trailing publisher bridges IConfigService section changes
to ConfigChanged with camelCase changedFields and a full config
projection, and the broadcaster forwards event.config.changed and
event.model_catalog.changed (both previously published but never
delivered). All three event types are registered in the event unions,
so session_event parsing and AsyncAPI describe them.
BREAKING CHANGE: GET /api/v1/auth drops the ready and default_model
fields in favor of models_ready; event.config.changed's changedFields
is now camelCase domain names instead of the raw snake_case request
keys (v1 summary schema follows).
* fix(kap-server): expose the session model in session list projections
GET /api/v1/sessions hardcoded agent_config.model to '' and the v2
projection had no model field at all, so clients could only learn a
session's model via the post-select /status read — which races the WS
replay and often never lands. SessionFacts now carries the live
session's model (same source as the snapshot route), toWireSession
emits it, and the v2 activity domain gains a nullable model field.
* fix(kap-server): gate prompt submission on the effective session model
The submit gate called ensureReady() with no override, so it only ever
validated config.default_model: a session with a bound model (or a
prompt carrying one) was rejected with 40113 whenever default_model was
missing or dangling. Pass the effective model (request model, then the
agent profile's bound model, falling back to default_model inside
ensureReady) on both the prompt submit and btw routes.
* fix(agent-core-v2): honor defaultProvider in model readiness resolution
resolveModelForReady stopped at the flat baseUrl fallback, so a model
that omits provider/providerId and relies on the configured
defaultProvider resolved at runtime (ModelCatalog.resolveProviderContext
falls back to it) while /api/v1/auth reported models_ready:false and the
send gate rejected the prompt. Mirror the runtime order (providerId ->
provider -> defaultProvider -> flat baseUrl) and pass the configured
default provider from both readiness callers.
* fix(kap-server): redact inline model credentials from config responses
toConfigResponse only redacted the providers section, so a model's
inline apiKey/oauth rode GET /config verbatim and, via the new
event.config.changed publisher, every WS connection plus the persistent
event journal. Project the models section the same way: strip
credential fields and report has_api_key.
* fix(agent-core-v2): honor defaultProvider in ensureReady credential checks
The readiness phase learned the defaultProvider fallback, but the
credential phase right after still derived the provider only from the
model's explicit fields: a model omitting provider/providerId passed
readiness yet missed the default provider's apiKey/OAuth material and
prompts failed with auth.token_missing. Mirror the same provider chain
(providerId -> provider -> defaultProvider) when resolving credentials.
* fix(kap-server): validate the model a profile bind will select at the prompt gate
The gate validated the session's current model even for a prompt that
switches profile without a model — but bind falls back to defaultModel
in that case, so a stale session model drew a misleading 40113 before
bind could run. Gate on bind's selection order instead: the request's
explicit model, then the default on a profile switch, then the session's
bound model.
* fix(kap-server): redact inline service credentials from config responses
The earlier redaction covered providers and models, but toConfigResponse
still passed the services section through verbatim: inline or
env-injected apiKey, oauth references, and credential-bearing
customHeaders rode GET /config and, via the event.config.changed
publisher, every WS connection plus the persistent event journal.
Project services the same way: strip apiKey/oauth into has_api_key and
report only the header names as custom_header_keys (the MCP
envKeys/headerKeys convention).
* fix(kap-server): keep unlisted config domains through event validation
The config.changed broadcaster returned the zod-parsed config, which
strips domains absent from configResponseSchema (mcp, identity,
model_catalog, image, tools, token_counting): changedFields named them
while the advertised full snapshot no longer matched GET /api/v1/config.
Make the response projection passthrough (defineRoute validates only
requests, so REST responses are unaffected).
* fix(agent-core-v2): use the exact configured key for model readiness lookups
resolveModelForReady trimmed the model id before the models-table lookup
while ModelCatalog and ensureReady use the configured string as an exact
record key: a whitespace-padded default_model was reported ready and then
crashed the submit gate with an internal error instead of 40113, and a
legitimate key containing spaces was reported dangling. Trim only rejects
blank values now; the lookup always uses the raw key.
* fix(protocol): keep unlisted config domains in the shared event projection
The shared configResponseSchema stripped domains it does not enumerate
(mcp, identity, model_catalog, image, tools, token_counting, subagent,
secondary_model), so event.config.changed parsed through agentEventSchema
named them in changedFields while omitting their values. Make the shared
projection passthrough like the kap-server-local one.
* fix(agent-core-v2): use the exact default_provider key in readiness checks
The defaultProvider fallback trimmed the configured value before the
providers-table lookup while ProviderService and ModelCatalog use the
configured string verbatim: a whitespace-padded default_provider could
build successfully yet report not-ready (40113), or report ready for a
provider runtime resolution cannot find. Trim only rejects blank values;
the lookup uses the raw key.
* chore: sync web dist from code-app
Rebuild the bundled web UI against this branch's /auth contract (models_ready, no ready/default_model): the previous bundle still read the old fields and stayed in the not-ready flow against this server.
code-app: 000d2594ff3e95b553be326126bab3f939b62944
* Revert "chore: sync web dist from code-app"
This reverts commit 9400a24a03863b3e8b780dda251540f824f02f3a.
* fix(oauth): rebase the default selection after the refresh fetch
A provider refresh snapshots the config before the remote catalog fetch;
when the user selects a default model while the fetch is in flight, the
stale snapshot's empty default made an otherwise unchanged catalog enter
the write path and the self-heal persisted the generated default over the
user's newer selection. Each branch now re-reads and rebases the
default/thinking selection after its fetch, before cloning, comparing,
or writing.
* style(kap-server): pass optional custom_header_keys without conditional spread
* fix(secondary-model): stop rewriting the section when providers refresh or are removed
Provider refresh, provider deletion/rename, catalog/registry import,
OAuth logout, and SDK removeProvider used to cascade into the user's
[secondary_model] block: pool entries were silently pruned, and the
whole section was deleted when its effective default dangled. The
cascade ran from a cache-refresh path (including an unattended 6h
scheduler), so upstream model-list changes could irreversibly destroy
hand-written configuration without any notice.
Config is user intent; the catalog is an availability snapshot. Stop
rewriting the section on every provider/models writer. An entry whose
model no longer resolves fails pool validation on the next session
create with a message naming the offending alias, which is the same
fail-fast contract hand-written typos already had.
* chore(sdk): add changeset for the removed secondary-model cascade export
* Delete .changeset/sdk-remove-secondary-model-cascade.md
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* Delete .changeset/secondary-model-no-silent-rewrite.md
Signed-off-by: 7Sageer <sag77r@hotmail.com>
---------
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* feat(kap-server): add task detach action to move foreground tasks to background
* test(kimi-code-sdk): normalize v2-only parentToolCallId in task parity projections
* fix(agent-core-v2): distinguish user-initiated detach in tool result text
* feat(agent-core-v2): mention user-initiated backgrounding in the bash tool description
* feat(agent-core-v2): use a client-agnostic background-task panel hint in the bash tool description
* ci: retrigger checks
* feat(agent-core-v2): client-agnostic human_shell_hint and a detached_by_user marker in tool results
* fix(protocol,docs): declare parent_tool_call_id in the shared task schema and document the detach action
* style: remove added comments
* feat(kimi-code): add remote control web tunnel
Add CLI and TUI entry points for exposing the local web UI remotely.
Bridge HTTP and WebSocket traffic with local authentication and reconnect handling.
* fix(kimi-code): prevent remote control websocket crash
* fix(kimi-code): align websocket dependency versions
* fix(kimi-code): harden remote control connection setup
Reconnect when management closes during the HTTP tunnel handshake.
Reject non-loopback Remote Control binds whose CSP blocks path bootstrap.
* fix(kimi-code): fix remote control rewriting, caching, and WS frame loss
* feat(kimi-code): add remote control QR output
* build: update pnpm dependencies hash
* refactor(kimi-code): remove the --allow-remote-terminals flag
* feat(kimi-code): add remote control lock, rc command, and QR fixes
* fix(kap-server): broadcast user prompts to all session clients on submit
- agent-core-v2: emit prompt.submitted (status running|queued) at enqueue and prompt.started when the turn launches
- kap-server: project prompt.submitted/prompt.started into transcript prompt entities and the live transcript REST response
- update flake.nix pnpmDeps hash for the PR lockfile
* fix(node-sdk): drop v2-only prompt.started from SDK event stream
- event-mapper: add prompt.started to the dropped v2-only prompt lifecycle types (parity with submitted/completed/aborted/steered)
- cli test: assert only visible sub-commands and stub the experimental flag env for determinism
* ci(pkg-pr-new): post custom install comment for npm 12 compatibility
* feat(kimi-code): render remote control QR as inline image on capable terminals
* feat(kimi-code): improve remote control terminal output
- add onboarding, security, device management, and help guidance
- show compact clickable links and QR image fallback details
- report relay and remote device connection lifecycle
* test(agent-core-v2): update tool event snapshot
* revert(ci): keep preview workflow unchanged in rc pr
---------
Co-authored-by: liruifengv <liruifeng1024@gmail.com>
- define the AgentSkill contract and SkillRuntime (activate /
promptWithSkills / recordModelToolActivation) with a lazy, non-durable
provider; recordActivation dispatches SkillActivated directly and the
skillKey / SkillActivate relay is gone
- allow agent runtime descriptors to omit logic (durable definitions
still require it), making skill the first pure-API domain
- featurize the skill domain: app/skillCatalog,
workspace/workspaceSkillCatalog, session/sessionSkillCatalog,
agent/skill, and agent/tools/skill move under src/features/skill;
the static Skill tool registration and the AgentSkillService are gone
- resolve the runtime at the edge: kap-server routes and node-sdk
activateSkill go through IAgentLifecycleService.resolve, and the
klient dispatcher keeps the agentSkillService wire name over a
per-agent view
* fix(transcript): fold mid-turn task notifications into the current turn on cold rebuild
* chore: changeset for the notification fold fix
* fix(transcript): key the notification fold on persisted task-turn boundaries, not the previous message role
* fix(transcript): collect background_task turn origins too, and fall back when the wire has no turn.started records
* fix(transcript): fold consecutive task notifications into the same turn
* fix(transcript): normalize folded notification text to the live title/body form
* fix(transcript): stop folded notification text before child blocks, preserve legacy background_task turns absent from the boundary set
* fix(transcript): truncate folded notification text at the first child-block tag, not just the tag lines
* fix(transcript): drop folded notifications without a persisted step, truncate only at output blocks
* fix(transcript): attach mid-turn task notifications to the following step, cold and live
* fix(transcript): keep other tasks' notifications in task-origin turns
* fix(transcript): derive task-turn boundaries from durable turn.prompt records
* feat(transcript): carry subagent model and thinking effort on task entities
* fix(transcript): mirror turn liveness into meta.activity, live and cold
* fix(transcript): populate the prompts entity from prompt.accepted/queued engine events
* fix(transcript): reconcile liveness and the prompt queue at backfill from the live loop state
* fix(transcript): include the prompts entity in the REST transcript response
* fix(agent-core-v2): publish prompt.accepted on the event bus
* test(transcript): add the contract-level e2e covering every entity the client renders from
* fix(transcript): guard the prompt backfill against missing services; align stream expectations with prompt.accepted on the bus
* test(agent-core-v2): re-record event stream snapshots with prompt.accepted published
* fix(transcript): settle the spawned agent row when its lifecycle redirects to the task row
* test(transcript): move the contract e2e timeout to the describe arg (jest lint)
* fix(transcript): declare task model fields in the wire schema, carry prompt content on accepted, normalize queued content
- transcriptTaskSchema declares model/thinkingEffort: Zod strips
undeclared keys, so schema-driven REST/WS consumers lost both fields
the projector now populates.
- PromptAccepted carries the admitted content (it is the only event a
first-turn prompt ever emits, and the bare id left the prompts entity
permanently partial) — projected with userMessageId and the public
content shape via projectPromptContentParts, same as queued and the
live backfill.
- Regenerate the wire manifest and re-record the affected event-stream
snapshots.
* fix(transcript): preserve task model fields across termination and accepted prompt details across queueing
- onTaskLifecycle carried resultSummary/usage/error/stateReason but
dropped model/thinkingEffort: a completed detached-Agent row lost the
metadata spawned set while running.
- prompt.queued rebuilt the entity from scratch, discarding the
userMessageId and createdAt that prompt.accepted had just stamped —
build the queued update from prev.
* fix(transcript): keep prompt.accepted out of the public v1 event stream
The observable marker made the broadcaster forward every accepted
prompt to v1 WS clients, but events-zod has no accepted variant (v1
surfaces submission through the service-synthesized prompt.submitted)
and the SDK mapper didn't drop it — schema-driven clients could reject
the frame. Drop it at the WS edge and in the SDK's dropped set; the
transcript projection keeps consuming it internally.
* fix(transcript): derive cold task-turn boundaries through undo anchor replays
* fix(transcript): flush trailing folded notifications into the open turn
* fix(transcript): drop trailing buffered notifications to match the live projector
* chore: split transcript changesets per logical change
* fix(transcript): parse only the generated Title/Severity header lines in folded notifications
* feat(agent-core-v2): add the unified MCP management plane
Port the v1 MCP management plane (#2858) onto the v2 DI x Scope engine:
- App-scope IMcpOAuthService shared by every workspace handler and
session overlay: credential events, single-flight refresh, proactive
refresh timers, OAuthTokenTransaction-serialized writes, offline
tokenState, shutdown. Providers read tokens through the store so
grants written or revoked by another process are honored immediately;
http/sse transports ride the transaction fetch.
- IMcpConfigStore: the single write point for the user-level mcp.json
over the filesystem byte store, byte-identical to v1's format, with
per-entry validation, name normalization, __proto__-safe parsing, a
mutation tail, and an onDidWrite event.
- IMcpRegistryService: the unified read view over the layered config
files (with per-entry origins) and plugin manifests (full descriptors
incl. disabled, with provenance); collisions stay visible and runtime
resolution ranks an enabled plugin above the file layers.
- IMcpManagementService: guarded CRUD, connection-test probes, the
locator-addressed inspection/auth-status surface, and
locator-addressed OAuth begin/complete/cancel/reset with ambiguity
rejection. Engine services stay ungated; the mcp_management flag
gates the edge exposure.
- Workspace runtime aligns with v1 precedence (an enabled plugin entry
wins over the file layers, shadows revive), and management writes
reload immediately via onDidWrite instead of the watch debounce.
- node-sdk v2 facade delegates to the engine service (deleting its
in-process duplication); kap-server exposes /api/v2/mcp/* and klient
gains global.mcp.*, both flag-gated.
* fix(agent-core-v2): settle early and cancelled MCP OAuth callbacks
* refactor(node-sdk): write session MCP persists through the engine config store
* refactor(agent-core-v2): strip comments from the MCP management plane files
* fix(agent-core-v2): harden MCP management readiness
* test(node-sdk): cover offline MCP auth statuses
* fix(agent-core-v2): isolate stdio MCP probes
* fix(klient): normalize MCP OAuth errors
* fix(mcp): honor workspace CRUD context and refresh timing
* fix(mcp): drain OAuth refreshes during shutdown
* fix(mcp): guard CRUD across registry collisions
* fix(mcp): canonicalize trust and refresh scheduling
* fix(mcp): close callback listener on setup failure
* fix(mcp): preserve trust and oauth behavior
* fix(oauth): retain refresh tokens after SDK saves
* fix(oauth): stop proactive sweep during shutdown
* fix: await MCP workspace reconciliation
* fix: serialize MCP OAuth and trust cleanup
* fix: reject persisted MCP plugin collisions
* fix: reconcile MCP workspaces concurrently
* fix(mcp): check project-layer trust at the queried cwd
* fix(mcp): expire abandoned OAuth flows after an idle timeout
* fix(mcp): keep mutable user entries writable past read-only collisions
* fix(mcp): abort the auth::complete long poll on client disconnect
* fix(mcp): map OAuth flow failures to wire code 40929
* docs(mcp): note probe credential effects and plane semantics
* chore: add the SDK changeset for MCP management cwd params
* feat(mcp): expose the management plane without the experimental flag
* fix(mcp): preserve auth management semantics
* fix(agent-core-v2): bound MCP OAuth auth-server requests and the shutdown drain
* fix(node-sdk): restate engine MCP management errors as KimiError
* fix(agent-core-v2): preserve shared OAuth flow lifetime
* fix(agent-core-v2): close MCP OAuth cancellation and shutdown gaps
- bound the authorization-code exchange with the request timeout and the
flow/caller abort signals, and make shutdown abort hung begins and close
their callback listeners immediately
- keep token-transaction effect coalescing intact when durable tokens carry
local stamps, and serialize the meta sidecar and tokens-saved event with
the token write inside the lock
- drain transport-driven grants, their trailing SDK save continuations, and
interactive completions during shutdown, with a cancellable deadline
* fix(agent-core-v2): harden MCP probe runtime resolution and path handling
- resolve stdio probes against the containing workspace's runtimes and
reject out-of-workspace probes for non-local runtime_id instead of
silently falling back to a local-only transient registry
- share one Windows-aware path canonicalization across the config loader,
registry trust lookup, trust records, and workspace matching
- keep a UTF-8 BOM fatal for the user-level mcp.json store, matching the
workspace loader and v1
- validate completeServerAuth timeoutMs bounds at the engine boundary
* fix(agent-core-v2): await workspace MCP reconciliation on plugin mutations
Plugin install/enable/disable/remove now resolve only after reload
listeners settle their waitUntil work, so a disabled plugin's MCP server
cannot linger connected and an enabled one is visible to the next
session, matching v1. The workspace MCP consumer joins the barrier while
keeping its log-only failure tolerance; delivery is awaited outside the
mutation queue to avoid self-deadlock through consumption reads.
* fix(mcp): close the SDK, klient, and server edge gaps
- register mcp.oauth_failed in the v1 error registry and restate unknown
engine codes as internal instead of minting undeclared KimiError codes
- route persisted session MCP adds through the same KimiError restating
as the global management methods
- give the klient IPC transport a per-call timeout so completeAuth's long
poll outlives the 30s default, clamped to the Node timer ceiling, and
align the contract timeoutMs upper bound with REST
- await the MCP OAuth service shutdown directly in SDK and server close
before scope disposal
* fix(agent-core-v2): keep file-over-plugin MCP precedence and harden the plane
- Revert the v1-style precedence flip: the workspace merge and
resolveRuntimeTarget keep the file entry above plugins (v2's historical
order; the divergence from v1 is deliberate and documented in AGENTS.md).
- Guards follow each engine's winner: project-layer entries stay read-only,
while plugin entries never block user-level writes, so a file entry may
shadow a plugin and removing it revives the plugin. The parity suite pins
the engine split for a persisted session add over a plugin-owned name.
- inspectServers tolerates a wire-encoded null targets array: klient's ipc
transport sends null for an omitted leading optional argument.
- Fire the config store's onDidWrite after the mutation tail settles, so a
write listener can re-enter the store without deadlocking the queue;
concurrent-mutation and re-entrant-listener tests pin both contracts.
* chore: condense the sdk MCP changeset to one sentence
* test(node-sdk): pin verify:false auth-status parity and fix the sdk changeset
* feat(agent-core-v2): turn /tower into a mode parallel to plan mode
* feat(agent-core-v2): align /tower command semantics with the original skill behavior
* fix(tui): harden the /tower command against mid-turn objectives, legacy engines, and stale status
* fix(agent-core-v2): keep restored tower state inert while the feature flag is off
* fix(agent-core-v2): include TowerInit in the mode tool overlay and report flag-gated tower state
* feat(agent-core-v2): expose tower control tools statically and make the mode injection history-derived
* fix(tui): warn that tower mode needs a restart after a live flag flip; drop redundant undefined from SessionStatus mode fields
* fix(agent-core-v2): let tower mode exit clear persisted state while the flag is off
* fix(agent-core-v2): emit the tower exit reminder through a disabled flag and drop the redundant REST state comparison
* fix(tui): show the Tower mode status row only when the experiment is available
* fix(agent-core-v2): confine tower mode to the main agent and always re-assert it for objectives
* feat(kap-server): project tower mode into transcript modes and reassert explicit toggles
* fix(agent-core-v2): fold the main-agent invariant into the effective tower state
* fix(kap-server): gate the cold tower mode badge behind the experiment flag
* fix(agent-core-v2): reapply the tower tool overlay after a profile bind; fix(kap-server): clear cold tower badges on non-main agents
* fix(protocol): mirror towerMode and tower_mode in the shared zod schemas
* test(agent-core-v2): adapt tower tests to the agent lifecycle context architecture
* chore(agent-core-v2): regenerate the wire manifest; test(node-sdk): look up the main agent via findAgentHandle
* fix(agent-core-v2): exit a replayed tower mode when the workspace belongs to another session
* fix(agent-core-v2): carry tower ownership in the enter record so forks clear inherited mode
* fix(agent-core-v2): apply the tower overlay before dispatching enter and repair it on status updates
* fix(agent-core-v2): validate store ownership before entering tower mode
* fix(agent-core-v2): claim the repository tower owner at enter; fix(tui): confirm mode activation before reporting success
* fix(agent-core-v2): make the first tower claim exclusive and refuse adoption over a live owner
* feat(agent-core-v2): guard tower adoption and teardown with a cross-process ownership lease
* revert(agent-core-v2): drop the cross-process lease and enter-time claim, keep ownership checks process-local
* fix(agent-core): hide the v2-only tower flag from the legacy experiments list
* fix(agent-core-v2): keep tower mode inert until the tower feature is assembled
A live /experiments flip refreshes the flag but cannot re-run App-scope
feature assembly, so the tower tools/profile stay unregistered until a
restart. Gate enter()/isActive on the assembly fact and say so in the
TUI error. Also resolve the AGENTS.md conflict block committed by the
merge, and widen the TUI experimentalFlag type to string now that flags
live in two registries.
* fix(kap-server): gate the cold tower badge on tower feature assembly
Same live-flip gap as the mode machinery: a persisted tower_mode.enter
plus a flag enabled without a restart would still show the badge while
the feature is inert. Require isTowerFeatureAssembled() alongside the
flag, re-exported from agent-core-v2.
* fix(agent-core-v2): liveness-aware tower entry and per-App assembly state
enter() now mirrors TowerInit's adoption rule: a stored owner blocks
entry only while that session is live in this process, so a new session
can enter the mode and reach TowerInit to adopt a stale tower. The
assembly marker is keyed by each App's flag service (WeakSet) instead of
process-global module state, so coexisting Apps no longer leak assembly
into one another.
* fix(agent-core-v2): keep stale-owner adoption across resume; reject tower updates that do not take
exitForeignTower now treats a stored owner as foreign only while that
session is live, so a mode entered by adopting a dead owner's tower
survives close/restart. The TUI validates the model prerequisite before
enabling tower for an objective, and the REST agent_config path throws
session.tower_mode_invalid when enter() did not take effect instead of
acknowledging a no-op.
* fix(agent-core-v2): clear the tower assembly marker when the feature unloads
Register the WeakSet cleanup via the feature's onDispose so the
capability follows the managed unit's lifecycle — after
unprovideUnit('tower'), isActive/enter() no longer treat the retracted
tool set as assembled.
* fix(agent-core-v2): publish tower deactivation on gate loss; reject refused setTowerMode in the SDK
isActive now reconciles its projection: restore and feature-manager unit
changes publish AgentStatusUpdated({ towerMode: false }) when the
persisted mode lost a gate (flag off at runtime, feature unloaded), so
live transcript badges and TUI state stop showing an inert mode. The
SDK's setTowerMode(true) verifies the effective state and throws
session.tower_mode_invalid, matching the REST path.
* fix(agent-core-v2): reconcile tower projection on config changes and cold ownership moves
The last unreconciled gate inputs: a live setConfig writing the
experimental section (no session reload, no units event) now republishes
towerMode:false through the config-change subscription, and the cold
transcript badge mirrors the same ownership/liveness rule as enter() —
retained while the store owner is this session or no live session, cleared
once a live session elsewhere owns the tower.
* fix(agent-core-v2): reconcile the tower projection in both directions
The OFF-only reconcile left a hole: re-enabling the flag in the same
process made isActive true again with no towerMode:true publish, and
enter() could not heal it (it early-returns when already effective).
The projection now tracks the last published state and emits both
false→true and true→false transitions from the same restore/units/config
triggers; direct publishes (enter/exit/restoreTowerTools) keep the
tracker in sync.
* fix(agent-core-v2): validate forked tower ownership even while the flag is off
exitForeignTower's flag short-circuit let a fork restored while the
experiment was disabled keep its inherited enter record; re-enabling the
flag later revived both source and fork over the same store. Ownership
validation is flag-independent state hygiene, so restore now runs it
regardless of the effective flag — a live foreign owner clears the
fork's persisted mode before any gate can rise again.
* fix(agent-core-v2): veto tower tools while the tower experiment is off
With the feature assembled and the tool overlay active, disabling the
flag live left TowerInit/TowerTeardown callable — they have no flag
check — so prompts could still mutate or dismantle .tower/ while the
experiment reported disabled. A dedicated onBeforeExecuteTool hook now
denies every tower tool whenever the flag is off, mirroring the TodoList
veto.
* fix(agent-core-v2): keep tower worker write isolation when the flag turns off
The worker Write/Edit guard is identity-scoped (tower-worker profile),
not feature-activity-scoped: disabling the experiment live must not let
already-spawned detached workers write into the main checkout or other
worktrees. The guard no longer checks the flag; the tower-tool veto
added earlier covers protocol access instead.
* test(agent-core-v2): make tower tests hermetic for CI
Three CI-only failures: towerService git fixtures committed without a
repo-local identity (CI has no global gitconfig), the node-sdk tower
positive tests relied on the developer shell's
KIMI_CODE_EXPERIMENTAL_FLAG=1 master switch instead of enabling the
tower flag explicitly, and the legacy harness experimental-features
expectation still listed the tower entry removed from the v1 registry.