* fix(kap-server): decouple workspace trust from runtime initialization
* fix: preserve workspace trust errors
* fix: emit workspace trust recovery changes
* fix: complete workspace trust recovery
* fix(agent-core-v2): reclaim session locks from zombie processes (#4320)
(cherry picked from commit 0a90e2e8a2)
* feat: let users take over a session lock left by an exited process (#4328)
* feat: let users take over a session lock left by an exited process
Session locks judged alive by mistake (a different hostname, pid reuse, or a failed process-state probe) blocked the session with no way out. Add a takeover path: the lock service evicts another process's lock, the SDK exposes Session.takeOverLock(), kap-server adds POST /api/v1/sessions/{id}:takeover, and the TUI offers Take over once a retry is still occupied. KIMI_CODE_SESSION_LOCK=0 turns the lock off for a process.
* fix(agent-core-v2): evict the lock directly on takeover
Takeover went through the reclaim marker, so a stale marker left by a crashed reclaimer on another host made takeover a silent no-op while the API reported success. Unlink the lock file directly instead.
* fix(agent-core-v2): surface takeover failures when the lock cannot be removed
Takeover swallowed unlink errors, so a lock file another user owns in a shared sticky directory was reported as taken over while the next retry stayed occupied. Ignore only ENOENT and let other errors reach the caller. Also fix the run gate test bootstrap stub typing.
* fix(tui): simplify the session takeover copy
* fix(tui): tie the takeover warning to the takeover action
* fix(tui): drop the holder line from the takeover dialog
* refactor(tui): move the session occupancy dialogs into a controller
(cherry picked from commit e7f1a99fc5)
* chore: sync web dist from code-app
code-app: 76483646b7c46a61bff868f1473535e0a32b473d
---------
Co-authored-by: 7Sageer <sag77r@hotmail.com>
* feat(agent-core-v2): add cross-process session occupancy lock
Multiple clients (TUI / kap-server / VS Code / ACP) running core in
separate processes could open the same session and corrupt each
other's wire.jsonl and metadata. Each session now holds a cross-process
occupancy lock ({homeDir}/locks/sessions/{id}.lock) taken only while
writing: runs acquire it on submit / compact / undo / cron / delete /
fork / cold-archive, the append log holds it before flushing, and a
stale check compares the main agent wire's on-disk length with the
length this process knows.
Collisions surface as session.occupied (holder in details); writes by
another client detected after acquire surface as session.stale and the
caller reloads before continuing. kap-server maps them to
SESSION_OCCUPIED (40903) / SESSION_STALE (40930), reloads the session
on stale, and re-attaches WS subscriptions; the TUI shows an occupancy
dialog (retry/cancel) or reloads and replays history keeping the input;
print exits with code 4 on occupied and reloads automatically on stale;
VS Code / ACP get minimal occupancy notices. Locks are released a few
seconds after the session goes idle, on close, and on process exit;
locking I/O failures degrade to unlocked operation with a warning.
* docs: document session occupancy error codes and lock directory
* fix: address codex review on session occupancy lock
- metadata: re-read state.json on the first write of each lock-hold
epoch (holdToken), so pure-metadata changes made by another client
(title, archive) are not clobbered by turn-end lastPrompt writes
- lifecycle: hold an explicit lease across live archive(), matching
delete(), so the idle release timer cannot drop the lock mid-teardown
- tui: route the occupied dialog's Cancel selection through the same
cleanup as Esc (restore input, release recalled media)
- acp: tell stale-session users to close and reopen the session
- append-log: warn when retirement drops records kept in memory because
the session lock was unavailable
* fix: always run the staleness check in beginSessionRun
The prompt channels (AgentPromptChannel.submit/submitSteer, kap
/prompts, promptWithSkills) write prompt metadata before calling
loop.submit, and the metadata write acquires the session lock first.
beginSessionRun then short-circuited on 'already held' and skipped the
wire-length staleness check, so the main prompt path never detected
updates written by another client. Drop the already-held shortcut: the
check is a single stat and always passes while the lock is continuously
held, so every acquisition path now runs it regardless of order.
* fix(print): preserve occupied handling on the stale retry
If another client acquires the session between the reload and the
retry, the retried turn now goes through the same conflict handler:
session.occupied reports to stderr / stream-json and exits with code 4
instead of bubbling out as exit code 1.
* fix: do not mistake own in-flight writes for external modification
storage.append grows the file (writeFile) before fsync/close complete,
and knownLength only catches up afterwards; a beginSessionRun landing
in that window (steer, queued prompts, cron fires during a turn) was
misjudged as session.stale and deleted the lock file mid-turn. Track
in-flight bytes per log and compare disk against known + in-flight.
Cover the analogous rewrite window (post-rename syncDir) with an
expected-final-length marker.
* fix: bound the staleness comparison and check once per lock hold
The equality check disk !== known + inFlight moved the false-positive
window to before the append lands (mkdir/open) and into chunked
writes, so beginSessionRun during a turn (steer, cron, queued prompts)
could misjudge, delete the lock file mid-turn, and later append behind
another client. Compare known <= disk <= known + inFlight instead, and
evaluate the check only on a fresh hold (holdToken epoch): re-entrant
runs pass through without a stat, matching the RFC's 'already held
passes through' while every acquisition path still triggers the check
exactly once per hold.
Also unify the occupancy prompt copy across clients: the engine error
message is now the single user-facing text ('该会话正在被其他 Kimi
Code 进程使用;请停止其他进程中的会话或退出该进程后重试'), and the
TUI dialog, /compact /undo errors, print output, and the VS Code
fallback all display it instead of maintaining their own wordings.
* fix: address third-round codex review
- metadata: hold an explicit lease across each state.json write so the
idle release timer cannot unlink the lock mid-write
- print: preflight the session lock before goal creation; make goal
creation idempotent and emit the goal summary only for attempts that
did not fail with session.stale, so a stale reload + retry neither
duplicates the goal nor the summary
- tui: route every prompt-launch path (plain, goal steer, plugin
command, inline skill, batch steer) through one conflict handler
with the occupied dialog and stale reload + input restore
- tui: snapshot and restore the attachment store across stale reloads
so pasted media placeholders still resolve after history replay
* fix: move session occupancy to a fresh error code, use English copy, release locks on exit
SESSION_OCCUPIED reused 40903, which belonged to PROMPT_ALREADY_COMPLETED until #3747 retired it, and web clients still allowlist 40903 for that old meaning; reusing it conflated the two semantics. Move session occupancy to the never-used 40931 and update the server API reference in both locales.
The occupied error message was hardcoded in Chinese while every other engine error is English, so the TUI, print mode, VS Code, and ACP clients all rendered mixed-language output; replace it with an English equivalent.
The lock release hook ran on 'beforeExit', which never fires when the process leaves through process.exit (SIGHUP in the TUI goes through emergencyTerminalExit), leaving stale lock files that could keep a session looking occupied forever after a hostname change or pid reuse. releaseAll is fully synchronous, so hook 'exit' instead.
* fix(agent-core-v2): decide prompt-derived titles after re-reading session metadata
applyPromptMetadataUpdate decided whether to derive a title from the
prompt from the in-memory metadata cache, which is only refreshed when
the session lock is re-acquired. If another client named the session in
the meantime, the stale cache still looked untitled, so the next prompt
from this client overwrote the other client's title — both on the stale
path, where the prompt is rejected afterwards, and on the fresh path,
where the other client only changed the title on disk.
Move the untitled check into SessionMetadata.applyPromptMetadata, which
runs after ensureWriteOwnership() has re-read state.json, so the
decision is based on the latest persisted metadata. The published
SessionMetaUpdated event now carries the derived title only when it was
actually written.
* fix: address fourth-round codex review
- vscode: classify session.occupied and session.stale as preflight errors so the webview toasts them instead of silently swallowing the send, and give session.occupied the same English copy the engine emits (the toast text comes from getUserMessage in the extension host).
- engine: beginSessionRun now takes the session context and agent lifecycle service, and on the first check of each lock hold it runs externallyModified over every loaded agent's wire (scope agents/<agentId>, key wire.jsonl) instead of only the main agent's, so a fork that only touched a subagent journal is detected as stale. All four call sites (loop submit, manual compaction begin, undo, print preflight) pass the extra dependency.
- tui: keep the request in flight across a stale reload instead of dropping to idle, so input submitted during the reload queues rather than racing the closing session; park the queue around switchToSession (resetSessionRuntime would clear it) and drain it into the reloaded session afterwards; when restoring the rejected text, prepend it before content the user typed during the reload instead of overwriting it.
- kap-server: clear the in-flight turn tracker and subagent roster in detachSession so a reattached session no longer reports stale in-flight turns or running subagents in REST snapshots and WS resyncs.
* fix: harden session conflict recovery
Detect external appends that race journal reads so a later run cannot miss session.stale. Keep deferred wire migrations pending and finish them before appending, while rejecting the append if migration still cannot complete. Restore the complete plugin command after an occupied cancellation, including commands without arguments.
* fix: scope session staleness cache to session context
Replace the process-global session-id map with a WeakMap keyed by ISessionContext so closed and reloaded sessions do not retain cache entries for the lifetime of the process.
* fix: document session conflicts for metadata routes
Declare SESSION_OCCUPIED and SESSION_STALE in the response schemas for profile updates, title generation, and child creation. Cover the OpenAPI declarations and the existing HTTP 200 conflict envelopes with regression tests.
* refactor: centralize session conflict handling
Move session error mapping and stale reloads into one shared route helper, reuse shared conflict response declarations, and cover skill activation conflicts. Remove the duplicate prompt, session, and skill error mappers while preserving their existing envelopes.
* fix(sdk): restate session conflict errors
* fix(tui): keep the reloaded session open and resend after a stale conflict
* fix(agent-core-v2): lazily rebuild registered agents after a session reload
* refactor: simplify session conflict handling
* refactor: hygiene fixes from session-lock review
- kap-server: move SESSION_STALE to 40932 (collided with
DEFAULT_WORKSPACE_UNAVAILABLE at 40930) and pin ErrorCode value
uniqueness with a test
- node-sdk: fold six catch-and-restate copies into a restate() helper
- acp-server: sync mapPromptLaunchError docstring with the
occupied/stale branches
- replace hardcoded session.stale/session.occupied literals with
ErrorCodes constants
- inline the single-use sessionStaleError
- restore the strict timer-leak assertion with a warmup baseline
instead of a blanket +1 allowance
- narrow SessionState.detached to ?: true
* refactor: introduce session-level run gate
- replace the beginSessionRun free function with ISessionRunGate, a
Session-scoped service; loop/compaction/undo now inject one
dependency instead of four, and sessionLock.ts no longer imports
session-layer modules
- tryHold() absorbs the duplicated try/catch hold copies in
wireService and sessionMetadataService
- resolve IAgentLifecycleService lazily inside the gate to avoid a
construction-time cycle with agentLifecycleService
* refactor: tighten session lock and conflict recovery structure
- Narrow ISessionLockService to hold/tryHold/release/lease/held and share
the hold bookkeeping between the memory and node-fs backends; drop
holdToken and the module-level exit hook state.
- Session metadata refreshes on a new hold via onDidHold and skips agent
registration through tryHold instead of catching session.occupied.
- AppendLogStore reads sizes through IFileSystemStorageService.sizeSync and
exposes a generic write guard instead of a session-lock binding.
- kap: reload stale sessions only at the call sites that can raise
session.stale and log reload failures; error mapping stays side-effect free.
- TUI: recover from a stale conflict through the in-place session reload with
history replay, removing the same-session switch special case, the media
store snapshot, and the queue parking.
- print: bound stale reloads with a simple retry loop.
* refactor(agent-core-v2): merge session lock release into run gate
* test(agent-core-v2): reuse append log fixtures
* refactor(agent-core-v2): break run gate and agent lifecycle dependency cycle
* refactor: reload stale sessions in core instead of each host
Stale detection already lived in core, but every host re-implemented the
recovery: TUI, print, and kap each closed and resumed the session, and TUI
and web resent the rejected input themselves.
Core now exposes reloadOnStale: on session.stale it closes and resumes the
session, lets the caller refresh, then retries the call once (undo reloads
and still rejects, since its count depends on what the user saw).
- kap: prompt submit, skill activation, and compact retry in place instead
of returning 40932; undo still returns 40932 after reloading
- print: retries through reloadOnStale on the same session id and keeps the
original permission mode to restore
- SDK: run-starting calls reload and retry internally; Session gains
setStaleReloadHandler so hosts refresh before the retry
- TUI: drops its own reload/resend/queue-merge path; the stale handler
replays history and keeps in-flight messages
* docs(server-api): note that only undo returns 40932 after a stale reload
* refactor(agent-core-v2): ask each agent's wire whether it was modified externally
* refactor(agent-core-v2): clarify agent restoration helper name
* Delete .changeset/ensure-agent-lazy-rebuild.md
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* fix(tui): wrap session occupied notice
---------
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* feat: notify the agent of external changes after /reload
* refactor: route watch-based AGENTS.md change alerts through the change notifier
* test: pin the missed detection of trailing AGENTS.md content deletions
* fix(agent-core-v2): anchor change-notifier comparisons to delivered beliefs
* fix(agent-core-v2): diff the full subagent model pool in change notifications
* fix(agent-core-v2): mute stale-session auto notifications during session reload
* chore(agent-core-v2): regenerate manifests and complete notifier stubs for the reload fixes
* chore: update the reload-change-reminders changeset
* test(agent-core-v2): cover the announced-then-deleted AGENTS.md sequence
* fix(vis): cover the changeNotifier.snapshot wire record
* fix(agent-core-v2): harden change-notifier beliefs for legacy wires and edge baselines
* fix(agent-core-v2): unmute the stale notifier when a session reload fails midway
* fix(agent-core-v2): seed change-notifier beliefs from the rendered snapshot
* fix(agent-core-v2): await the change-notifier snapshot dispatch
* fix(vis): harden the change-notifier snapshot renderer for legacy records
* test: isolate the reload e2e from the watch channel and tighten seed assertions
* chore(agent-core-v2): regenerate the wire manifest for optional belief fields
* chore: drop the changeset for reload change reminders
* fix(agent-core-v2): diff reload reminders against persisted beliefs first
* fix(agent-core-v2): recompute the bound subagent allowlist on catalog changes
* fix(agent-core-v2): roll back change-notifier beliefs on conversation undo
* feat(agent-core-v2): inline added profile metadata in subagent change reminders
* fix(agent-core-v2): detect legacy AGENTS.md truncation and gate skill reminders
* fix(agent-core-v2): reset change-notifier beliefs on context clear
* feat(kap-server): project the reload session action into the OpenAPI document
* fix(node-sdk): keep plugin-only reloads from refreshing workspace catalogs
* fix(agent-core-v2): gate and filter subagent change reminders
* fix(agent-core-v2): evaluate reminder gates against the persisted binding
* fix(kap-server): scope reload catalog refreshes to the target workspace
* fix(node-sdk): scope reload catalog refreshes to the target workspace
* fix(agent-core-v2): reset change-notifier beliefs on compaction
* fix(agent-core-v2): hash AGENTS.md files from one atomic snapshot
* fix(agent-core-v2): close first-watch and legacy gaps in change detection
* fix(kap-server): block reload during compaction and reconcile after failed reload
* fix(node-sdk): block reload during compaction and reconcile after failed reload
* feat: support model migration metadata
* chore: revert web bundle sync
* feat: persist model upgrade metadata
* fix: resolve model upgrade targets
* fix: keep model upgrade metadata consistent
* fix: complete model upgrade metadata handling
* fix: handle aliased upgrade targets and RFC3339 variants
* fix: resolve bare model upgrade targets
* feat: prompt for model migration in the TUI
* fix(tui): defer model migration until the editor is idle
* fix(tui): preserve model migration prompts and preferences
* fix(tui): hold the model migration prompt while /new replaces the session
* fix(tui): keep the resume cache hint from replacing an open dialog
* fix(tui): keep the thinking effort across a model migration when supported
* fix(tui): strip terminal control sequences from migration notices
* fix(tui): warn about prompt cache loss before a mid-conversation migration
* docs: drop the effort persistence claim from the migration wording
* test: trim redundant migration test cases
* fix: switch to the declared replacement when a registry drops the default model
A registry retires a model by dropping it after declaring `upgrade`, but
the refresh deleted the alias together with its upgrade metadata and
cleared the default, leaving users without a model and with a misleading
login hint. Refresh and re-import now move a dropped default to the
upgrade target persisted by an earlier refresh, clearing it as before
when that target is gone too. The TUI warns about the switch and moves
the live session off the dropped model once no dialog is open.
* docs: fix the /reload-tui command spelling in config-files
* fix: resolve migration placeholders at import and keep acknowledgements out of tui.toml
Migration text placeholders were only filled in by the TUI, so the model
catalog served raw templates to every other client. The registry import
now stores the final text and the TUI renders it as is.
Migration acknowledgements are display state rather than preferences, so
they now live in model-migrations.json under the data directory instead of
tui.toml; preference saves and acknowledgements no longer rewrite each
other, and /reload has nothing to sync.
Also carry the dropped model's record in the refresh result instead of
snapshotting it in the TUI, give performModelSwitch explicit persistence
modes instead of a boolean-or-mode flag, fold the Markdown notice into the
picker's notice option, and rename waitForLazyCreation to
waitForSessionAssembly now that it also waits for /new.
* refactor(tui): move the shared model switch out of the /model command
performModelSwitch and its helpers became shared once the migration
prompt and the retired-default switch started calling them, yet they
stayed in the /model command module, leaving a controller depending on
command internals. Move them unchanged to tui/utils/model-switch.ts, which
the command module and the migration controller now both import.
* feat: surface retired default model redirects in refresh results and catalog events
A registry-side retirement only existed in the OAuth orchestrator's
in-process RefreshResult, so kap-server's scheduled refreshes redirected
the default model without any client-visible signal. The refresh response
now carries an optional retired_default { from, to, from_model }, mapped
through to the REST refresh endpoints and the global
event.model_catalog.changed broadcast, and mirrored in the klient
contract.
* refactor(tui): consume the engine's provider refresh instead of orchestrating its own
The TUI ran the OAuth refresh orchestrator in-process through its own
persistence host (including a dead v1 fallback), duplicating what the
engine's discovery service already does. KimiHarness now exposes
refreshProviderModels (via the klient facade over
IProviderDiscoveryService), so the TUI deletes its orchestration glue and
reads retired_default from the engine response. Also fix the klient
facade deriving RefreshProviderModelsResponse from the OAuth login
refresh, which hid retired_default from clients.
* feat: move model retirement follow-through into the engine
The discovery service now records retirements, emits onDidRetireModel,
and answers retiredModelReplacement; agent profiles follow a retirement
live and on restore, so the TUI only shows the notice and steers the
session-less selection. The wire retired_default drops from_model in
favor of an engine-resolved from_display_name. Removes the now-dead
supportsAtomicSectionReplace / replaceConfigSections /
removeProviderFromConfig SDK exports (no remaining consumers).
* Update model migration guidance in documentation
Prompt for migration when the TUI editor is idle and switch to the declared replacement when a registry drops the default model.
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* test(oauth): drop the duplicate dropped-default redirect case
---------
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* feat: show NotifyUser progress in desktop and web
Give NotifyUser a one-line title, report the latest update on the
session status, and forward subagent updates to the parent so GUI
clients can show them on subagent cards.
* feat: turn on NotifyUser progress updates by default
Flip the notify_user flag default to true; the flag stays as the opt-out
(`[experimental] notify_user = false` or KIMI_CODE_EXPERIMENTAL_NOTIFY_USER=0).
Register the new subagent.update wire record in the vis renderers and
record-type union so the manifest coverage test passes.
* refactor: trim NotifyUser surfaces to conversation progress rows
* feat: make NotifyUser title optional and enforce single-line titles
* feat: make NotifyUser opt-in via the settings lab
* docs: describe NotifyUser as off by default with the settings lab opt-in
* feat(tower): coordinate missions with deterministic orchestration
* fix(tower): disable goal mode
* feat(tower): degrade gracefully on tower state loss
When .tower/comms is deleted while .tower/ survives (e.g. git clean),
tower tools no longer hard-fail with not-initialized:
- TowerStatus returns a state-lost report; never-initialized repos
(no .tower/ at all) keep the existing not-initialized behavior
- TowerSend/TowerComplete recover a minimal state (directory skeleton,
empty missions/roster, recoveredAt marker, base taken from the
current checkout branch) and keep delivering; a caller whose roster
entry was lost is re-registered as a placeholder named after its
agent id
- TowerComplete degrades explicitly after a loss: the report is
delivered to the tower as a completion-report-state-lost message and
no mission is marked completed
- TowerMerge/TowerRebase and every other locked mutation fail with an
explicit state-lost error instead of silently running against an
empty history
- the recoveredAt marker is shown on the TowerStatus dashboard and in
the status summary, reminding the tower to report the history loss
to the user
* feat(tower): veto destructive bash commands in the tower main checkout
* fix(tower): allow git clean dot pathspecs inside tower worktrees
* fix(tower): restore attachment guidance for mission context
* fix(tower): engage bash guard only while the workspace has an owner
* fix(tower): veto git clean only with force and -x/-X; drop markInboxRead
* chore(tower): drop per-fix changesets to keep a single concise one
* fix(tower): check tower owner before preparing the user base
* feat(tower): show a notice when goal activation ends tower mode
* fix(tower): unify the mode-mutex exit reason across goal, plan, and swarm
* refactor(tower): drop token stamping from protocol artifacts
* fix(agent-core-v2): deny tower workers subagent delegation
Remove the Agent tool and the subagents allowlist from the tower-worker
profile so workers and reviewers cannot nest subagents inside a mission;
nested subagents bypassed the tower concurrency budget and the tower's
steering. Drop the now-unreachable reviewer-only Agent-resume veto and
its tests; the branch-lease and foreground-resume vetoes stay.
* test(tower): stub ILogService in every AgentTowerService test scope
The fork-restore tests build second DI scopes that register
AgentTowerService without stubbing ILogService, leaving this.log
undefined. When reconcileForeignTower fires a fire-and-forget exit and
the ownership release rejects, the rejection handler's this.log.warn
throws a TypeError that surfaces as an unhandled rejection in the full
suite. Add ix2.stub(ILogService, stubLog()) to all nine scopes that
lacked it, matching the existing sibling pattern.
---------
Co-authored-by: konghuanjun <konghuanjun@moonshot.ai>
* feat(kimi-code): show what trusting activates in the workspace trust prompt
* fix(kimi-code): keep trust prompt non-blocking and disclose only external additional dirs
* fix(kimi-code): mark trust disclosure incomplete when discovery times out or fails
* fix(kimi-code): exclude disabled MCP servers from trust disclosure and cap key lists
* fix(kimi-code): disclose disabled user MCP servers shadowed by project config
* test(kimi-code): merge redundant trust prompt disclosure cases
* test(node-sdk): merge mirrored user/project MCP override cases
* Update trust-panel-disclosure.md
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* fix(kimi-code): disclose symlinked additional dirs by real target in the trust prompt
* fix(kimi-code): bound trust disclosure I/O and harden path containment
* fix(kimi-code): tighten trust prompt disclosure copy
* refactor(kimi-code): compute the trust disclosure inside the engine
The disclosure composition (project-origin MCP servers, additional dirs
with realpath containment, instruction sources) moves from the SDK into
agent-core-v2's workspace scope as WorkspaceTrustDisclosureService,
constructed alongside the other generation services. The SDK's
getWorkspaceTrustInfo becomes a thin forward, so the config-merge
semantics live next to the workspaceTrust gate that owns them instead
of being re-derived downstream.
Also drops the shadowed-user-server section and the per-section
deadlines with their disclosureComplete flag: the engine reads the
same loaded state session creation awaits, so the prompt needs no
timeout plumbing of its own, and the empty state stays a single honest
copy.
* refactor(kimi-code): compress trust disclosure tests and disclose only effective profiles
Merge mirrored test cases across the three suites and share fixtures;
small source trims (shared wrap helper, context.workspaceId).
The disclosed agent profiles now pass the same resolution the session
catalog applies: a workspace profile clashing with a builtin without
override: true, or losing to a higher-priority candidate, is no longer
reported as activated.
* test(kimi-code): drop the engine disclosure suite in favor of e2e coverage
The SDK e2e suite exercises the disclosure through the real engine, so
the stub-based engine unit file duplicated it; a builtin-clash agent
case moves there to keep the suppressed-profile behavior pinned.
Also folds the sanitize vectors into the MCP render case and trims the
trust type docs.
* fix(kimi-code): cap untrusted MCP target fields and correct profile wording
A crafted .mcp.json can supply arbitrarily long command/args/url
strings; each field now truncates at 80 chars before wrapping. The
profiles line no longer claims they feed the agent — a non-default
workspace profile is only made available as a subagent, the default
agent still binds the default profile.
* fix(kimi-code): honor profile overrides and reveal instruction symlinks in trust disclosure
A workspace profile with override: true replaces a same-name builtin
or higher-priority candidate in the session catalog, so the disclosure
now applies the same replace semantics instead of dropping it.
Instruction paths get the same realpath treatment as additional dirs:
a project AGENTS.md symlinked to a file outside the project renders
its real target instead of hiding behind the lexical path.
* fix(kimi-code): disclose skill roots escaping the project in the trust prompt
Read/Write/Edit/Grep/Glob all add getSkillRoots() to their allowed
roots, so a .kimi-code/skills symlink to an external directory is an
outside-project file access grant that trusting activates. The
disclosure now merges those roots (resolved via the same projectRoots()
the skill source uses) into the outside-directories list. Also caps
each displayed MCP key name, not just the key count.
* fix(kimi-code): cap the bearer-token env var label in the trust prompt
* refactor(kimi-code): disclose only real locations in the trust prompt
The path → realPath arrow is dropped; gated directories and AGENTS.md
entries are disclosed as their resolved real locations, and the pair
type collapses back to plain strings. Entries resolving to the same
real directory collapse into one.
* fix(kimi-code): tighten the trust prompt list caps to fit one screen
* fix(kimi-code): lower the trust prompt list caps further
* fix(agent-core-v2): keep explicit agent-file precedence in the trust disclosure
* Revert "fix(agent-core-v2): keep explicit agent-file precedence in the trust disclosure"
This reverts commit 6e5c45d1b69061a041cc991a0882b1c651fc6003.
* fix(agent-core-v2): preserve profile priority in trust disclosure
* fix(kimi-code): keep workspace trust choices visible
* fix(kimi-code): align trust prompt paging keys
* docs: remove trust prompt keyboard additions
* feat(kimi-code): summarize workspace trust with configuration sources
* chore(agent-core-v2): sync workspace instruction state manifest
* fix(kimi-code): recognize dot-prefixed project directories in trust summary
* refactor(kimi-code): reuse existing trust configuration sources
* fix(kimi-code): keep trust disclosure independent of skill watchers
* refactor(kimi-code): condense additional directory disclosure
---------
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* feat(agent-core-v2): support api_key_env for provider credentials
Providers can declare api_key_env = "VAR" in config.toml to read their
Bearer token from an environment variable instead of storing it inline.
The value is resolved per request (rotation-friendly); an unset or empty
variable fails with config.invalid naming the provider and variable.
apiKey+apiKeyEnv and apiKeyEnv+oauth are rejected as conflicts.
The /models refresh path resolves the same chain (inline apiKey ->
apiKeyEnv -> env sub-table); a provider whose variable is unset lands in
failed[] without affecting others, and open-platform providers keep the
env declaration instead of persisting the resolved secret.
Custom registries: applyCustomRegistryProvider consumes the api.json env
field — imported providers get apiKeyEnv, the legacy inline apiKey is
dropped, and a hand-edited apiKeyEnv survives refreshes (tracked via
source.envKey). kimi provider add --api-key and the TUI registry token
field are now optional so public registries import without a key.
* docs: document api_key_env provider credentials
* fix(oauth): preserve hand-edited apiKeyEnv on registry refresh and address review nits
* fix(oauth): validate apiKeyEnv conflicts during refresh and address review nits
* fix(oauth): mirror apiKeyEnv in the klient provider contract and harden refresh validation
* fix(oauth): stop auto-binding registry env names to provider credentials
* fix(oauth): preserve hand-edited apiKeyEnv across registry re-imports
* fix(oauth): scope apiKeyEnv preservation to the owning registry
* fix(agent-core-v2): preserve apiKeyEnv across models.dev catalog re-imports
* fix(kap-server): drop apiKeyEnv when the provider update route receives a replacement key
* fix(agent-core-v2): replace provider credential fields on TOML writeback
* fix(kap-server): normalize whitespace-only api_key in the provider update route
* refactor(oauth): unify provider credential rules into shared primitives
A new provider-credential module in the oauth package owns the credential
precedence chain, the mutual-exclusion conflicts, the user-facing messages,
and the write-side tri-state reconciliation; the refresh orchestrator,
agent-core-v2 (model-auth, catalog, auth readiness), and the kap-server
provider routes compose it instead of re-deriving the rules per layer.
applyOpenPlatformConfig now takes credential: {apiKey}|{apiKeyEnv} and
merges onto the existing record, so open-platform refreshes no longer drop
hand-written fields (customHeaders, the env sub-table) on the inline-key
path either, and the refresh's write-then-undo block is gone.
Registry imports converge on removeCustomRegistryEntries +
applyCustomRegistryEntries (prior-snapshot provenance): the exported
capture/restore helpers are gone, the CLI persists a re-import with a
single setConfig instead of per-id removeProvider RPCs, and
default_model/default_provider survive a re-import whenever they still
resolve. kap-server's importCustomRegistry keeps the purge-then-rewrite
two-phase replace the config service's raw-merging writeback requires.
kap-server create/replace routes accept api_key_env (tri-state, mutually
exclusive with api_key), and GET /config plus the provider catalog item
expose the binding. A declared-but-unset api_key_env now fails readiness
checks with config.invalid (was auth.token_missing), matching request-time
behavior. Registry 401/403 without a key prints an authentication hint.
* fix(cli): persist registry re-import removals on both harness generations
The v2 harness's setConfig deep-merges per domain (retaining omitted keys
and skipping undefined defaults), so a shrunk re-import left removed
providers and aliases in config.toml and kept a cleared default dangling.
The CLI and TUI registry flows now share persistRegistryImport: on v2 the
removal phase and the rebuilt records go out as two atomic
replaceConfigSections writes (the writeback merges alias fields from raw
TOML, so the purge must be persisted first); on v1 the removeProvider RPC
cascades the removals before one merge write. defaultModel/defaultProvider
snapshots are captured before any removal so the restore works on both
paths.
* fix(oauth): reject oauth before the legacy env fallback and persist cleared thinking on re-import
refreshProviderModels' env sub-table fallback now throws the same
apiKey+oauth conflict the chat path rejects, instead of authenticating
with the fallback key and rewriting the provider to drop oauth.
persistRegistryImport writes the cleared thinking section through the
replacement payload, and drops the v1 fallback branch — registry imports
always persist through replaceConfigSections, so the removal phase no
longer needs per-id removeProvider RPCs.
* chore: consolidate provider credential changesets into one entry
* chore: trim changeset to the api_key_env entry
* fix: harden provider credential status and registry imports
* fix(oauth): expose browser-safe provider credential rules
* fix(node-sdk): replace serialized provider credentials
* fix(oauth): reject credential conflicts during refresh
* fix: harden custom registry re-imports
Reject reserved provider collisions and credential endpoint drift before mutation. Replace registry-owned sections atomically with compare-and-set guards, and carry the replacement contract through klient and node-sdk.
* refactor(registry): centralize custom registry import ownership
Route CLI and TUI imports through the engine via the typed SDK and Klient operation. Keep registry replacement policy inside the engine and preserve credential edits made while fetching the registry.
Cover the shared entry point with real configuration storage and memory/IPC transport tests.
* test: trim redundant api_key_env and registry-import coverage
* fix(cli): reuse the stored registry key on keyless re-import
Both first-party clients encoded "no key given" as an empty string, but
the engine's fallback chain (options.apiKey ?? registryKeyFromExisting)
only reuses the key parked in the provider's source blob when the option
is undefined. A keyless re-import of a private registry therefore went
out without an Authorization header and failed with 401 from the CLI
and the TUI dialog, while the kap-server REST route (which passes
undefined) reused the stored key fine.
Pass undefined from the CLI handler and the TUI import dialog instead
of normalizing to ''. The dialog's result type now marks apiKey
optional, matching the engine contract.
* test: pin CAS rejection and credential-conflict behavior
The coverage trim went past redundancy in three places, leaving the
PR's headline guards untested. Restore the compare-and-set rejection
path (guarded value changed on disk -> retry error, no write) and the
absent-section guard normalization in config.test.ts, the
apiKey+apiKeyEnv / apiKeyEnv+oauth conflict rejections at runtime
resolution (modelAuth) and at the refresh orchestrator (discovery,
with per-provider failure isolation), and the open-platform refresh
through api_key_env proving the resolved secret never lands in the
provider record. Also add the first direct unit tests for the shared
declaredProviderCredential / reconcileProviderCredentialUpdate
primitives.
* refactor(oauth): share reserved provider id rules between import and refresh
* Delete .changeset/registry-import-concurrent-credentials.md
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* test: trim cross-layer duplicate registry and credential coverage
---------
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* 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