- rename the mode commands to /yolo and /auto (formerly
/ask-when-needed and /never-ask) and drop their on/off arguments
- running either command now opens the permission mode list with the
corresponding mode preselected; Enter confirms the switch
- decouple the choice picker's initial cursor from the current-value
marker via a new initialValue option
- after a mode switch, show the mode-specific description as the yellow
status line instead of the generic unconfirmed-changes warning
* feat(agent-core-v2): allow unanalyzable bash commands in auto permission mode
* feat(agent-core-v2): drop the dangerous command guard in auto permission mode
* fix(agent-core-v2): keep background questions open past turn end and inline their answers
Background AskUserQuestion reused the generic task pipeline end to end, which
broke it in two ways: the pending interaction was still bound to the asking
turn, so it was cancelled the moment the agent finished its turn, and the
turn-end cancel response was then misread as an answer. On top of that the
completion notification only carried a pointer to the task output file,
forcing an extra Read round trip for a few bytes of JSON.
- Detach background question interactions from the asking turn so they stay
pending until answered, stopped, or the agent closes.
- Treat cancelled interaction responses as dismissals.
- Inline the answer JSON in the question task notification and word the
notification as answered or dismissed; fall back to the output file only
when the answer exceeds the inline budget.
- Trim the background launch result to task id, status, and one next step.
- Fold transcript notification summaries before inline answer blocks.
* fix(agent-core-v2): fail background questions on tool errors and translate interaction cancellations in the question service
Follow-up hardening from review:
- The interaction kernel's cancellation response now has a named shape,
InteractionCancellation, and SessionQuestionService.request translates it
into a dismissal (null) before handing the result to callers. The
AskUserQuestion tool no longer inspects answer maps for a cancelled key,
so a bare answer map can never be mistaken for a cancellation.
- QuestionBackgroundTask settles as failed with the tool's message as the
stop reason when the question tool reports an error, instead of writing
the error text as completed output. The generic task notification then
carries the reason, and the answered/dismissed wording is not used.
- The question notification only says answered or dismissed when the task
output parses as an answers payload; any other output keeps the generic
completed wording.
* docs(zh): restyle configuration and customization sections
Editorial pass across 11 pages: clear explanatory dashes, replace arrow
cross-references with inline links, compress oversized table cells while
keeping operational facts (value ranges, override precedence, activation
conditions), split >5-sentence paragraphs by theme, fold interface
contracts and low-frequency internals into details blocks, add map
sentences to multi-paragraph sections, add subcommand overview table to
kimi-command reference. Add /provider manager screenshot to media.
* docs(zh): restore dangerous_command_guard, fix trust prompt default and secondary-model default
- config-files: restore the dangerous_command_guard paragraph dropped
during the style pass (regression, content from upstream #3290)
- mcp: the trust prompt defaults to Trust this folder per
trust-prompt.test.ts; docs had the direction reversed (pre-existing)
- config-files: subagent model pool defaults on since #3334;
KIMI_CODE_EXPERIMENTAL_SECONDARY_MODEL=0 disables (pre-existing staleness)
* docs(zh,en): sync en mirrors and fix anchor slugs
Add restyled en mirrors for all 11 configuration/customization pages,
mirroring the zh structure (section parity, map sentences, compressed
cells, details folds) while keeping en phrasing.
Fix anchor slugs in both locales (underscore kept, dots dropped per
@mdit-vue slugify): loop_control, openai_responses, kimi_model_,
systemmd variants; retarget renamed permission-mode section
(yolo/auto -> The three permission modes / 三种权限模式).
Follow-up to #3403 for the user docs: living pages (en + zh mirrors)
now use the new mode names and descriptions — slash-commands and
interaction references for /ask-when-needed (aliases /yolo, /yes) and
/never-ask (alias /auto), CLI flag and config glosses, guides,
customization pages, and the docs AGENTS.md terminology table. Wire
and config ids (manual/yolo/auto) are unchanged; release notes are
historical and untouched.
* 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
* docs: rework web guide, interaction and getting-started pages (zh/en)
* docs: rename guides/server to guides/web to match the reworked page
* docs: neutralize screenshot workspace names and sync en web guide to zh
* docs: trim web UI screenshot to a single neutral workspace
- retry every failed LLM request indefinitely in runRequest when
KIMI_CODE_INFINITE_RETRY is set, covering turn steps and operation
requests such as compaction
- keep projection recovery ahead of the infinite retry branch, honor
Retry-After, and keep abort effective during backoff waits
- exclude context overflow from infinite retry so the deterministic
turn-level and compaction-level overflow recovery paths still run
- extract retryBackoffDelay for single-attempt backoff computation
Add a dedicated [swarm] config section so AgentSwarm subagent timeouts no longer follow [subagent] timeout_ms: timeout_ms (default 7200000, i.e. 2 hours; 0 = no timeout) with the KIMI_CODE_SWARM_TIMEOUT_MS env override, mirroring the [subagent] section. Deliberate behavior change with no fallback: a value previously set in [subagent] timeout_ms to cover swarms needs to move to [swarm]. Print mode (kimi -p) keeps its no wall-clock cap semantics: [swarm] timeout_ms also defaults to 0 there unless explicitly set. Docs updated in both locales (config-files, env-vars, tools) and the config manifest regenerated.
* fix(agent-core-v2): honor default_effort for secondary-bound subagents
[secondary_model].default_effort was parsed but never consumed, so a
subagent bound to a secondary/pool model resolved its thinking effort
from the main-oriented global [thinking].effort or the middle of the
bound model's support_efforts. Pass the section's default_effort as the
explicit spawn thinking, then fall back to the bound model entry's own
declared default_effort, both ahead of the global thinking config.
* docs(configuration): document secondary_model default_effort precedence
* fix(agent-core-v2): apply the model-default thinking fallback to tower spawns
TowerSpawnTool resolves the subagent binding directly and bypasses
planSpawn, so pool-bound tower workers never saw the bound model
entry's default_effort fallback. Apply the same resolution in launch.
* fix(agent-core-v2): preserve disabled thinking for subagents
- Name authoritative data sources in the intro (World Bank, IMF, OECD, FRED,
WHO, FAO, NBS, Wind, S&P Capital IQ, SEC EDGAR, Caixin, Xinhua Finance,
Gildata) so users can see what backs the plugin
- Turn the eight 'what you can do' scenarios into collapsible details blocks
with question-style headers (title — question?) so the page stays scannable
- Coverage table: add a financial-news/industry-data row (Caixin, Xinhua
Finance); name Yuandian Legal for the legal row; macroeconomics row now
also covers China government data and IGO official statistics
- Only sources approved for both domestic and overseas publicity are named;
restricted sources are described by capability instead
* feat(agent-core-v2): add a fork parameter to the Agent tool
Spawning with fork: true starts the subagent from a one-time snapshot of
the calling agent's completed conversation history — same profile, tool
set, and model — instead of zero context. The seed trims the trailing
open tool exchange (the in-flight Agent call itself) before appending
into the child's context memory, and the first prompt carries an
inheritance notice framing the seeded history as reference material.
Fork rejects resume, a different subagent_type, or a model override as
tool errors, and skips the subagents allowlist since a self-inheritance
is not a delegation.
* fix(agent-core-v2): bind the stale-todo reminder only into the main agent
Subagents share the session todo list but no longer receive the
stale-todo nudge — the reminder injector now registers only on the main
agent, so delegated and forked agents are not prompted to maintain a
list they do not own.
* fix(agent-core-v2): inherit the caller's live binding and label fork launches correctly
Review follow-ups for the Agent tool fork mode:
- overlay the caller's live profile.data() via applyBindingSnapshot after
the catalog re-bind, so ephemeral addActiveTool deltas, the rendered
system prompt, and runtime model/subagents updates survive the fork;
skip the profile prompt prefix since the caller's prefixed first
prompt is already part of the seeded history
- resolve the fork activity label and approval-rule subject from the
caller's own profile instead of falling back to the default subagent
type, so an Agent(<other profile>) rule cannot approve a fork
* fix(agent-core-v2): close inherited in-flight tool calls instead of trimming them
Fork seeding now answers the source's trailing open tool calls with a
synthetic in-flight result instead of cutting the whole trailing
exchange: the seeded history stays protocol-valid, keeps the source's
final step visible as reference, and no longer confuses side-question
(btw) agents forked while the main agent is mid-turn. The close helper
is shared by the Agent tool fork and IAgentLifecycleService.fork.
Fork launches also stop requiring the caller's profile to still exist
in the session catalog: the child is created unbound and overlaid with
the caller's live binding snapshot, matching the lifecycle fork path,
and now records forkedFrom provenance.
* refactor(agent-core-v2): route Agent tool forks through agentLifecycle.fork
* feat(agent-core-v2): add a fork parameter to the AgentSwarm tool
* fix(agent-core-v2): seal partial assistant forks
* fix(agent-core-v2): align fork parameter descriptions
* fix(agent-core-v2): drop the main-only registration gate from goal tools
* fix(agent-core-v2): disclose dates via reminders to keep the system prompt byte-stable
* docs: condense the fork changesets to single sentences
* docs(agent-core-v2): frame the tool-contribution when gate as a fork parity trade-off
* test(agent-core-v2): plug fork coverage gaps and decouple swarm tests from spawn internals
* docs(agent-core-v2): keep the when-gate guidance in the contribution JSDoc only
* fix(agent-core-v2): contribute cron tools to every agent for fork prefix-cache parity
CronCreate/CronList/CronDelete were registered directly into the main
agent's tool registry by SessionCronServiceImpl, bypassing the
AgentToolContribution seam and keying on per-agent identity — so a forked
agent rebuilt a tool surface three tools shorter than its caller and the
inherited prompt prefix missed the cache.
Register the three tools through registerAgentToolService like the goal
tools do (no when gate, identical surface for every agent) and enforce
the main-agent restriction at execution time instead. Also fall back to
DEFAULT_CRON_CONFIG when the config section is absent, since the service
can now be constructed after the main agent exists.
* feat(agent-core-v2): track the fork parameter in the subagent_created event
* fix(agent-core-v2): gate tower orchestration tools at execution time
TowerInit/TowerPlan/TowerSpawn/TowerMerge/TowerTeardown were contributed
with a when predicate keyed on agentId === 'main', so a forked agent
rebuilt a tool surface missing TowerInit (always present for the default
profile) plus the rest of the tower set once it was enabled — breaking
prompt prefix-cache parity with the caller.
Contribute the tools with no when gate (profile policy still controls
visibility) and reject non-main callers at execution time instead.
* test(agent-core-v2): expect the fork field in the subagent_created mirror assertion
* test(agent-core-v2): cover fork subagent first-request prefix parity
* refactor(agent-core-v2): share the main-agent-only tool refusal across cron and goal tools
Goal tools rejected subagent callers by throwing GOAL_UNSUPPORTED_AGENT
from the service, which the executor wrapped as a resolution failure;
cron tools returned a clean refusal but each tool open-coded the same
identity check. Centralize the check and both messages in
agent/tools/mainAgentOnly.ts and use it from all seven tools, keeping
AgentGoalService.assertSupportedAgent as the coded boundary for RPC and
SDK callers.
* refactor(agent-core-v2): keep the goal main-agent gate at the tool layer only
* fix(agent-core-v2): preserve the fork tool surface when inheriting user tools
* Revert "refactor(agent-core-v2): keep the goal main-agent gate at the tool layer only"
This reverts commit fc09a8fa32de43bb6223bf96fcab9a7b200ac051.
* test(agent-core-v2): complete fork lifecycle stub
* Delete .changeset/btw-inflight-tool-calls.md
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* Delete .changeset/todo-reminder-main-only.md
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* Delete .changeset/swarm-fork-context.md
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* Add optional 'fork' parameter to subagent tools
Signed-off-by: 7Sageer <sag77r@hotmail.com>
* docs(agent-core-v2): drop the fork JSDoc comments
* feat(agent-core-v2): add prompt_cache_probe telemetry for forked agents
* feat(agent-core-v2): gate the subagent fork parameter behind an experimental flag
---------
Signed-off-by: 7Sageer <sag77r@hotmail.com>
Split the dense prose walls into a minimal config, a one-line-per-field
table, constraint bullets, a numbered resolution order, and a separate
advanced subsection for per-entry thinking efforts. All behavioral facts
are preserved; zh and en stay mirrored.
* feat(datasource): add NDA/NBS, standards, IGO, xhcj, and caixin sources
* fix(datasource): narrow real-time-news ban to coverage gaps, require PublishTime citation
* fix(datasource): scope the real-time-news limitation to coverage gaps only
* fix(datasource): trim redundant clause in the real-time-news limitation
* fix(datasource): stop on a result that covers the question, not the first success
* fix(datasource): front-load trigger terms in the skill listing description
* fix(datasource): exempt discovery calls from the one-call workflow
GET /api/v2/sessions gains view=by_workspace: one request returns every
workspace with a matching session, each carrying its first group.page_size
sessions under the requested sort plus the workspace's full matching total,
with group-level page_token pagination (40922 on condition drift). Groups
key on the alias-canonical workspace id, so legacy split buckets of one
physical directory merge into a single group, matching the v1 alias
semantics. meta.has_prompt filters sessions by prompt presence (the v1
exclude_empty equivalent) in both views. The flat view and v1 routes stay
byte-compatible.
The global WS stream now fans out event.session.archived (live and cold
paths; payload carries the session id and workspace_id) and
event.workspace.created/updated/deleted, published by the core
IWorkspaceService on every mutation path including the implicit
createOrTouch on session creation.
kimi-inspect consumes the grouped projection as a single-column
workspace/session tree in the chat view; the session pane merges into the
right dock as the Session tab. The server API reference (en + zh) documents
the new parameters, the grouped response, and the new events.
- give the builtin agent profile an explicit subagents allowlist (coder, explore, plan), restoring v1 semantics
- inherit the default profile's allowlist when a caller profile declares none, instead of leaving delegation unrestricted
- pass a lone "*" subagents field through as an explicit unrestricted marker
* docs: document KIMI_CODE_CUSTOM_HEADERS on the env vars page
* docs: address review on KIMI_CODE_CUSTOM_HEADERS entry
- use a neutral gateway header name in the example
- correct the release version to 0.20.2
- scope the override claim to exact-name matches and warn against
case-variant auth headers
* docs: describe protocol-dependent Authorization precedence
On the OpenAI-compatible protocols (kimi/openai/openai_responses) an
exact Authorization custom header is applied after the SDK-generated
bearer token and therefore replaces it; /models listing keeps its own
authentication.
* docs(zh): add the required space before the config-files link
Per the mixed-content spacing rule in docs/AGENTS.md.
---------
Co-authored-by: bj456736 <bj456736@users.noreply.github.com>
* feat(agent-core-v2): add the WaitFor tool for waiting on background tasks
* fix(agent-core-v2): mark WaitFor deliveries only after formatting succeeds
* fix(agent-core-v2): cancel losing waits once the WaitFor race resolves
* test(node-sdk): project WaitFor out of the v1-v2 resume parity roster
* fix(agent-core-v2): gate WaitFor goal guidance behind the wait_for flag
* fix(agent-core-v2): gate WaitFor goal guidance on actual tool availability
* fix(agent-core-v2): enforce the wait_for flag at WaitFor execution time
* fix(agent-core-v2): consult the live tool policy in the WaitFor availability check
* feat(kimi-code): support automatic updates for native installations via staged swap
Native (SEA) installs previously could not self-update on Windows and
relied on 'curl | bash' re-install on Unix. Replace both with a staged
swap updater:
- startup swaps in a staged binary (verified against the release
manifest sha256, smoke-checked via --version) and re-execs it, so the
running process never replaces itself (Windows-safe)
- downloads run in a self-spawned hidden sub-command, in the background
from the update preflight or in the foreground from 'kimi upgrade'
- rollback from .bak on any swap failure; install failures keep the
existing retry/prompt thresholds
* fix(kimi-code): fully clean staged artifacts on swap discard paths
Real-binary smoke testing on macOS surfaced two cleanup gaps in the
discard path: the claimed metadata file was unlinked after the staging
dir rmdir (so the empty dir survived), and the staged exe was
rediscovered via the already-claimed staged.json (so it leaked on the
downgrade-guard path). Pass the known metadata through and order the
unlink before the rmdir.
* fix(kimi-code): restore staged metadata on swap failure and sweep update leftovers at startup
* fix(kimi-code): address codex review on lock contention and swap crash window
- The background native install no longer takes the outer install lock:
the self-spawned downloader holds it for the whole download, and the
parent's spawn-time lock raced the child into a false lastSuccess.
- Smoke-check the staged exe before moving anything, so a bad staged
binary is discarded with the install path never left empty; the
remaining crash window is two adjacent atomic renames (documented,
recoverable via the .bak or by re-running the install script).
* test(kimi-code): align swap test expectation with smoke-before-rename order
The restore-on-failure case now observes the early smoke check's
--version spawn; only the re-exec spawn must be absent.
* fix(kimi-code): stage the bare CDN binary instead of unzipping
The published per-release artifacts are the bare platform binaries
(kimi-code-<target>[.exe]), not zip archives — the staging flow now
streams the download straight to the staged exe after the manifest
sha256 check, and the zip reader is dropped. Verified end-to-end on
macOS against the live CDN: download -> sha256 match -> swap ->
re-exec into the real released binary.
* fix(kimi-code): address second codex review round
- re-exec: forward 128 + signo when the swapped-in child dies by signal
instead of reporting exit 0
- __update_download: only exit 0 without staging when the lock holder is
staging the SAME version; a different in-flight version (or a vanished
lock) no longer surfaces as a successful foreground upgrade
- staging: sweep orphaned .part downloads and unreferenced staged exes
before downloading, preserving live swap claims and their payloads
* feat(kimi-code): show download progress for native updates
The foreground 'kimi upgrade' path streamed 180 MB with a single static
'Downloading…' line. Render progress instead: a throttled in-place
percentage line on a TTY, one line per 32 MB when piped, and plain MB
counts when Content-Length is unknown.
* fix(kimi-code): bound native update downloads with an idle timeout
Codex review: the manifest fetch cleared its timer once headers arrived,
so a stalled response body hung the worker forever, and the binary
download had no abort at all. The manifest timeout now covers body
consumption, and the binary stream aborts after 30 s without a chunk
(total duration stays unbounded for slow networks). The idle timeout is
injectable for tests.
* fix(kimi-code): retry native updates blocked by an orphaned active record
Windows real-machine verification surfaced that a parent exiting before
the downloader's exit event leaves a fresh-looking 'active' record that
silently blocks every background retry for the 6 h TTL. For native
installs, lock liveness is the truth past a 60 s spawn grace window:
a held lock means a download is running, a free lock means the record
is an orphan and a new attempt may start. Package-manager sources keep
the TTL behavior (no lock to prove liveness).
* fix: skip staged swap while another instance holds a fresh claim
sweepStaleNativeUpdateArtifacts already detected an in-progress swap in
a concurrent instance, but the result stayed inside the cleanup helper:
startup still claimed a newly published staged.json and ran a second
swap, so the two launchers could rename the install path and delete each
other's rollback backup. Propagate the in-progress signal and skip
claiming until the existing claim is released or goes stale.
* fix: keep the install lock while its holder process is alive
The install lock went stale purely by age (30 min), but the native
downloader is idle-bounded, not duration-bounded: a slow link can
legitimately take longer. Another startup would then sweep the lock and
spawn a second downloader, and both would write and clean the same
.staging paths. Past the age threshold, fall back to a pid liveness
probe (signal 0) — the lock is stale only when the holder is gone.
* fix: keep recovery artifacts on rollback failure and wait out same-version downloads
Two robustness fixes from review:
- native-swap: when moving the staged exe into place fails AND the
rollback rename fails too (transient lock, AV), the install path is
left absent and no next launch can start. Discarding the staged
payload and claim on top of that removes the second recovery copy.
rollback() now reports its result; on a double failure the swap keeps
the .bak (which IS the old exe), the staged exe and the claim so
manual recovery or a re-install still works.
- update-download: a foreground `kimi upgrade` racing a background
downloader of the same version exited 0 immediately, so the CLI
printed a success message for a download that could still fail. The
worker now waits while the same-version holder is in flight, adopts
the verified staged result (staged.json lands before the lock is
released), and takes over the download when the holder finished
without staging.
* fix: stamp the swap claim with a fresh mtime when claiming
rename() preserves the staged metadata's mtime, which can be arbitrarily
old — the background download often finishes hours before the next
launch claims it. A concurrent launch's sweep would then classify the
live claim as crash residue (older than the 5-minute window) and delete
the claim, the staged exe, and eventually the first swap's rollback
backup. Stamp the claim file with the claim time so the staleness check
measures the swap's liveness, not the download's age.
* fix: stamp the claim before the rename so it is born fresh
Stamping after the rename left a window: a concurrent launch could
inspect the claim between the two syscalls, see the staged metadata's
old mtime, and delete the staged executable mid-swap. utimes the state
file first so the claim carries a fresh timestamp from the instant it is
published — no fresh-looking-later intermediate state exists.
* fix: chmod the staged download before publishing it at its final name
A swap claims only the staged METADATA; the staged exe stays in
.staging/. A concurrent same-version downloader (possible because swaps
do not hold the install lock) then re-downloads and renames its .part
over that path. If the swap moves the file into the install path between
the downloader's rename and its post-publish chmod, the chmod lands on a
path that is already gone and the installation is left non-executable —
every future launch fails. Apply the executable mode to the private
.part file before the publishing rename so the staged exe is executable
from the instant it appears.
* fix: publish the install lock atomically via hard link
The 'wx' open exposed a momentarily empty lock file before its contents
were written. A concurrent acquirer reading in that window got a
SyntaxError, treated the lock as stale, swept it and also won — two
"holders" then ran stageNativeUpdate against the same .staging paths.
Write the lock contents to a unique temp file and hard-link it into
place: link() fails when the destination exists (same exclusivity as
'wx') and the lock path only ever appears fully written.
* fix: serialize stale-lock takeover through a secondary lock
A pathname-level delete can never be conditioned on the file still being
the inspected stale instance, so a plain compare-and-delete still loses
exclusivity: two workers classifying the same stale lock could interleave
unlink and publish such that both won (proven by a 20-way contention
test). Takeovers now go through a secondary create-if-absent lock
(install.lock.takeover): the delete+publish section only ever runs in
one process, staleness is re-validated inside it, and a fast-path creator
that wins the briefly-free path simply beats the takeover. The takeover
lock itself is age-swept (a live section lasts microseconds), and handles
only release the lock instance they own.
* fix: verify lock ownership after publish and preserve freshly staged exes
Two more race fixes from review:
- install-lock: the stale-marker sweep repeats the inspect-then-delete
race one level up — two contenders sweeping the same aged takeover
marker could both win and enter the main-lock section together.
Pathname APIs offer no conditional delete, so both the takeover marker
and the main lock now verify ownership after publishing (unique marker
content, read-back compare): a racing sweep converts to a single
survivor instead of two holders. The irreducible residual (a delete
landing in the microsecond link-to-verify window) degrades to a wasted
download cycle, never a corrupt install — swap claims guard the exe
independently.
- native-swap: sweeping a stale swap claim deleted the exe it referenced
even when a FRESH staged.json referenced the same version-derived name
(a downloader re-staged the version after the swap crashed), throwing
away a verified ~180 MB stage. The sweep now preserves any exe the
current staged metadata still references.
* fix: reject mismatched manifests, take over from dead holders, unique .part names
Three robustness fixes from review:
- native-manifest: the per-release endpoint can answer with ANOTHER
release's manifest (stale cache, mispublish); its checksums would then
be applied to this version's binary and fail verification on every
attempt. Compare the parsed manifest version with the requested one.
- install-lock/update-download: a killed lock holder skips its finally
and never releases, stranding a waiting foreground `kimi upgrade`
forever. A lock whose recorded pid is dead is now stale at any age
(the atomic publish guarantees the pid was alive when written), and
the same-version wait loop polls the acquisition itself, so a dead
holder's lock is taken over within one poll instead of never.
Package-manager spawns are unaffected: they hold the lock only around
the spawn, and the active-record bookkeeping guards that layer.
- native-stage: the download intermediate is now unique per worker
(`.part` carries pid + counter), so overlapping same-version workers
can no longer interleave writes into the same file.
* fix: restrict staging cleanup to updater-owned names and retry short writes
- cleanupStagingOrphans recursively deleted anything it did not
recognize; the staging dir sits next to the exe and can contain files
belonging to the user or another tool. Deletion now requires a
positive match on updater-owned artifact names (staged exes and .part
intermediates) and only ever unlinks files.
- FileHandle.write may persist fewer bytes than requested (short write,
e.g. near disk exhaustion) while the running hash and size already
accounted for the whole chunk — publishing a truncated binary under a
valid checksum. The chunk write now loops until fully persisted.
* fix: scope failure cleanup, recognize all semvers, reverify staged checksums
Three fixes from review:
- native-stage failure cleanup deleted whatever staged update was
currently published — including a concurrent worker's valid result
that its caller had already reported as success. The catch path now
removes only this attempt's own artifacts: its unique .part file and
its staged exe name when the current metadata does not reference it.
- The orphan-cleanup ownership check only matched stable x.y.z names;
prerelease/build-metadata versions (1.2.3-rc.1, 1.2.3+build) would
never be cleaned and accumulate ~180 MB each. Ownership now derives
from the semver contract via the semver package's valid().
- The swap path trusted a staged exe whose size matched, though the
metadata records the release checksum; post-download on-disk damage
could pass the --version smoke check with corrupted bytes.
claimStagedUpdate now re-verifies the staged exe's sha256 before
claiming and discards the stage (for a later re-download) on mismatch
— paid only when an update is actually pending.
* fix: validate versions before path derivation and honor the update opt-out in the swap
- native-stage: stageNativeUpdate derived staging paths (including the
cleanup rm targets) from the version before fetchNativeReleaseManifest
rejected it; a traversal string like `x/../../kimi` would resolve the
staged-exe cleanup onto the running installation. The semver check now
happens before any path is derived, and the staged-metadata schema
constrains exeFileName to a plain file name.
- native-swap: the startup swap ran before the update preflight, so
KIMI_CODE_NO_AUTO_UPDATE / KIMI_CLI_NO_AUTO_UPDATE stopped gating
update behavior once a payload was pending. The swap now honors the
same opt-out: the staged payload stays in place for a later launch
without the variable, and the current exe starts.
* fix: restrict backup cleanup to updater-owned .bak names
cleanupBackups treated every <exe>.*.bak sibling as swap residue, so a
user's own backup like kimi.config.bak in a shared bin directory was
silently deleted on startup. Only the exact <exe>.bak and the numeric
PID fallback <exe>.<pid>.bak are updater-created — cleanup now
positively matches those two formats.
* fix: claim staged metadata before validating it and let manual upgrades bypass the opt-out
- native-swap: claimStagedUpdate validated the metadata and hashed the
staged exe BEFORE the atomic rename, so a concurrent downloader
superseding staged.json in between could get its fresh metadata
claimed under the older object — the smoke check then failed and
discard() deleted the newly published stage, recording a failure for
the wrong version. The claim (utimes + rename) now happens first and
validation acts on exactly the claimed file; discards use a new
discardClaimedUpdate that never removes anything a meanwhile-published
stage references.
- The auto-update env opt-out gated the startup swap unconditionally,
so an explicit `kimi upgrade` with the variable set staged the
version but no launch ever applied it. Stages now record
`manual: true` when they answer a user-initiated install
(`__update_download --manual`, threaded from installUpdate through
the hidden sub-command), and the swap applies manual stages even when
automatic updates are opted out.
* fix: promote adopted stages to manual and preserve claim-referenced payloads
Three follow-up fixes from review:
- An explicit `kimi upgrade` adopting an auto-staged payload (already
on disk, or still downloading via the wait path) returned before the
manual marker applied, so under the env opt-out the swap still skipped
it despite the success message. Both adoption paths now promote the
staged metadata to manual: true via a new promoteStagedUpdateToManual.
- The download-failure cleanup checked only the current staged metadata,
but a live swap holds the metadata renamed aside as its claim — a
failing same-version downloader could delete the exe an active swap
was about to move into place. The catch path now also preserves names
referenced by any live swap claim.
- Restoring a claimed stage after a failed exe move used rename, which
on POSIX replaces a newer staged.json a downloader published during
the smoke check. The restore is now a create-if-absent hard link: it
only lands when the state-file path is still free, and the older claim
is discarded when a newer stage has taken it.
* fix: drop exe deletion from stale-claim cleanup
The stale-claim sweep deleted the referenced exe based on a metadata
snapshot taken before the loop; a downloader republishing the same
version between the read and the unlink would have its fresh payload
deleted after reporting success. Publication can never be synchronized
with a pathname-level snapshot, so the sweep now removes only the claim
files themselves — genuinely unreferenced exes are reaped by the
downloader's own orphan cleanup (keep-set aware) before its next stage.
* fix: never delete the staged exe when discarding a claim
The same publication race existed one level down: a same-version
downloader can rename its fresh payload onto the shared exe path after
the discard's metadata snapshot but before the unlink (payloads publish
before their metadata), and the discard would delete a download whose
caller then reports success with nothing behind it. discardClaimedUpdate
now removes only the claimed metadata file; unreferenced exes are reaped
by the downloader's own orphan cleanup before its next stage.
* fix: only reap staging orphans old enough to be abandoned
The orphan sweep could delete a concurrent worker's freshly renamed
staged exe in the gap before its staged.json lands (payloads publish
before their metadata), turning the admitted duplicate-worker race into
a successful stage with no payload behind it. Unreferenced artifacts are
now only deleted once older than a one-hour grace period — publication
takes milliseconds, so unreferenced AND old means definitively
abandoned.
* fix: honor the persisted auto-update preference in the swap and drop claim-unsafe deletions
- The startup swap gated only on the env opt-out, so a payload staged
automatically still installed after the user disabled automatic
updates via [upgrade] auto_install = false. The swap now loads the
persisted preference (only when an automatic stage is actually
pending) and skips it, exactly like the env opt-out; manual stages
still always apply.
- Superseding a staged version deleted its exe through an uncoordinated
read-then-remove that could pull the payload from a live swap. The
supersede now removes only the old metadata record — the metadata
write atomically replaces it, and an unreferenced exe is reaped by a
later orphan cleanup. removeStagedNativeUpdate, left with no callers,
is removed.
- docs: the kimi upgrade reference (en + zh) no longer claims Windows
native installations cannot upgrade automatically; native installs
download and verify in the foreground and swap on the next start.
* fix: gate on claimed metadata, stop shared-path deletes on failure, exact smoke match
- The opt-out gate evaluated a pre-claim snapshot of the staged
metadata, but the claim could pick up a different (automatic) stage a
downloader published in between — smuggling it past the gate. The
env/preference check now runs on the CLAIMED metadata; when disabled,
the claim is restored via create-if-absent link so a newer stage is
never overwritten and a later launch can still apply it. The checksum
re-verify moves after the gates so opted-out launches stop paying for
the hash.
- The download-failure cleanup still deleted the shared staged-exe path
based on snapshot reference checks — the same publication race as the
paths already fixed. It now removes only the attempt's privately owned
.part file; the shared exe is left for the age-gated orphan cleanup.
- The smoke check accepted the staged version as a substring of the
--version output, so a mispublished 1.2.30 binary would satisfy a
1.2.3 target with a matching manifest checksum. It now requires the
trimmed output to equal the staged version exactly.
* fix: confirm the manual marker before reporting stage adoption
promoteStagedUpdateToManual silently no-oped when a startup swap had
claimed the state file, while the adoption paths still reported success
with manual: true synthesized — under the env opt-out the restored
automatic metadata would then be skipped on every later launch despite
the upgrade's success message. The helper now verifies the marker with a
confirming read (one retry) and returns whether it persisted; the
already-staged branch falls through to a fresh stage when it does not,
and the same-version wait loop only adopts after a confirmed promotion.
* fix(cli): verify the staged payload digest before adopting it as already-staged
readStagedNativeUpdate checks only the recorded size, so a same-size
corruption after the download was adopted and reported as success, only
for the startup swap's claim-time re-verify to reject and discard it.
Compare the actual sha256 before returning already-staged; a mismatch
falls through and re-stages from the CDN.
* fix(cli): keep staged metadata until its replacement is ready
Two related races around staged.json, both reported against the
duplicate-downloader residual:
- stageNativeUpdate deleted the previous record before downloading its
replacement; a pathname-only delete can remove a concurrent worker's
freshly published record, orphaning a payload whose worker already
reported success. The old record now stays until the final atomic
metadata write replaces it.
- promoteStagedUpdateToManual wrote the marker unconditionally onto
whichever generation owned staged.json. It now takes the adopted record
and promotes only while the on-disk metadata still matches it, and the
post-write confirmation requires the promoted candidate itself.
* fix(cli): preserve the exe referenced by the current staged record during orphan cleanup
Since the supersede path now keeps the previous staged.json until the
final atomic write replaces it, an aged staged exe is still the
applicable update while its replacement downloads — but
cleanupStagingOrphans only pinned exes referenced by swap claim files,
so a payload older than the grace period was unlinked out from under
its own record. Read staged.json itself in the pinning pass so the
current record's exe is preserved like any live claim's.
* chore(kimi-code): reword the native auto-update changeset
* chore(kimi-code): trim the native auto-update changeset
* fix(cli): support update locking on filesystems without hard links
link() fails with ENOTSUP/ENOSYS/EPERM on FAT/exFAT and some network
mounts, which aborted every native update before the download. Add a
shared createFileIfAbsent primitive (hard-link a fully written temp
file, falling back to an exclusive create + write) and use it for the
install lock, its takeover marker, and the swap's claim restore. The
fallback's create->write gap is observable, so the lock inspection now
grants young unparseable content a publish grace before sweeping it as
crash residue.
* fix(cli): publish staged exes under unique names and recover orphaned claims
Two related robustness fixes in the staged swap flow:
- A staged executable is now published under a unique per-worker name
(kimi-<version>.<pid>.<epoch-ms>.<n>[.exe]) and never replaced; the
atomic metadata write retargets the pointer. The pathname a swap
validates at claim time can no longer be exchanged by a concurrent
same-version publisher between validation and install.
- restoreClaimedUpdate only drops the claim when the restore landed or a
newer stage holds the state-file path; transient failures retain it.
The stale-claim sweep now restores aged claims (create-if-absent)
instead of deleting them, so a stage orphaned by a dead swap or a
transient restore failure is retried on a later launch.
* fix(cli): verify the staged payload digest in the lock-wait adoption path
waitForStagedUpdate relied on readStagedNativeUpdate, which checks only
the recorded size: while a holder re-stages a same-size-corrupted
payload (its metadata is replaced only when the repaired generation
publishes), a waiter could promote and report the corrupt stage as
downloaded, and startup would later reject its checksum. Apply the same
integrity bar as stageNativeUpdate's already-staged path — adopt only a
payload that hashes to its recorded checksum; a mismatch falls through
to the lock poll, which takes over once the holder finishes without
repairing it.
* fix(cli): serialize swap critical sections and preserve in-flight publishes
- The fresh-claim sweep is only a directory snapshot: two processes could
both pass it before either claimed, then rename the same installed exe
concurrently and delete each other's rollback backup. A create-if-absent
swap mutex (swap.lock, age-gated like the takeover marker) now serializes
the executable-renaming section; the loser restores its claim and defers.
The mutex is released as soon as the new exe is in place, before the
re-exec, so it is never held for the child session's lifetime.
- claimStagedUpdate no longer destroys a claimed record that is unparseable
but was young at claim time: on filesystems without hard links the
exclusive-create publish is observable mid-write, and discarding it would
orphan the staged exe while the writer reports success. Such a record is
put back with the same inode so the writer completes it; aged corrupt
residue and well-formed records with a missing/changed exe are still
discarded.
* fix(cli): keep backup cleanup inside the swap mutex
The early release let a subsequent swap rename the just-installed exe to
the shared .bak path while the previous swap's cleanup was still about to
unlink that same path, destroying the second swap's rollback source. The
mutex now covers the backup cleanup; the cosmetic staging-dir rmdir and
the re-exec stay outside it.
* feat(kap-server): add page-number mode and total to GET /api/v2/sessions
The v2 session list gains a stateless 1-based `page` parameter beside the
opaque page_token cursor for admin-style lists that jump arbitrarily:
each request stays a full independent snapshot, no token is minted, and
`page` + `page_token` together fail 40001. Every response now carries
`total` (the filtered/sorted set size) in both pagination modes.
* feat(kap-server): add meta.updated_before filter to GET /api/v2/sessions
Symmetric with meta.updated_after (inclusive boundary, Unix ms), applied
at the edge over the drained set and bound into the page_token query
fingerprint like every other condition.
* feat(kap-server): add POST /api/v2/sessions:archive and :restore batch endpoints
Batch archive/restore for session-management views: { ids } (non-empty,
≤5000 unique after dedup) answers per-item results in input order with
succeeded/failed counts — only a body validation failure fails the whole
request, and an unknown id folds into its own item as 40401.
The live/cold split keeps the batch cheap: a session with a live handle
goes through the full ISessionLifecycleService chain (agents drain,
scope teardown, mirror drain), while a cold session is never
materialized — the new setColdSessionArchived helper in agent-core-v2
patches the persisted state.json (archived/archivedAt, updatedAt
preserved, mirroring setArchived's touchUpdatedAt: false semantics),
mirrors the flipped summary into the read-model queue, and republishes
the same event.session.archived bus event the live lifecycle emits
(:restore publishes nothing, matching the live restore). Hot items run
with bounded concurrency and the batch ends with one shared
ISessionIndexMirror.drain().
* docs(server-api): document v2 sessions page mode, total, updated_before, and batch archive/restore
* fix(kap-server): deep-import workspace lifecycle symbols in the v2 sessions route
CI's tsgo/rolldown (Linux) fail to bind liveHandlerForSession and
IWorkspaceLifecycleService through the agent-core-v2 package-root
barrel even though it re-exports them; the same files use the
established deep-import pattern already used for the git domain.
* fix(kap-server): inline the live-handler lookup in the batch route
The previous deep imports still fail to resolve on CI's Linux toolchain
(tsgo TS2307, rolldown MISSING_EXPORT) while every other module path
from the same package binds fine. Keep the route self-contained: the
hot-path lookup is a five-line loop over IWorkspaceLifecycleService's
handlers (mirrors agent-core-v2's liveHandlerForSession), and the tests
assert non-materialization behaviorally via the live map instead of
importing the same two symbols for spies.
* fix(kap-server): drive the batch hot path through getLiveSessionById
The phantom only hits the workspaceLifecycle-group symbols in these two
files on CI's Linux toolchain; getLiveSessionById is observed to bind
fine there. It returns the session's live scope directly (no resume),
which is exactly what the batch hot path needs.
* refactor(kap-server): move the batch live/cold split into agent-core-v2
setSessionArchivedBatch owns the split next to the cold patch: live
sessions go through the full lifecycle chain via the workspace handler
accessor (the v1-proven resolution path), cold sessions through the
direct write. The route becomes a thin wire-code adapter, and the batch
tests assert the live chain behaviorally (disposal, events, index)
instead of spying through scope accessors.
* fix(agent-core-v2): import sessionLookup relatively from coldSessionArchive
The '#/app/workspaceLifecycle/*' specifier resolves from src/ and
src/app/* files on CI's Linux toolchain but not from
src/workspace/sessionLifecycle/ (tsgo TS2307, rolldown follows); a
relative import bypasses the package-imports mapping.
* fix(agent-core-v2): migrate the batch hot path to ISessionManager
Main's workspace/session DI refactor removed the workspaceLifecycle
lookup modules; the live branch now goes through the App-level
ISessionManager (the same entry the v1 action route uses post-refactor)
with getLiveSessionById from the new sessionManager lookup.
* feat(kap-server): add the id,archived item projection to GET /api/v2/sessions
fields=id,archived trims each item to { id, archived } for
select-all-matching flows (the session admin page's Gmail-style
select-all). Only that projection gets the relaxed page_size ceiling
(10000); unknown fields, non-pair subsets, and include=git combinations
are 40001, and the projection binds into the page_token fingerprint so
shapes never flip mid-pagination.
* fix(agent-core-v2): serialize the batch cold write against in-flight resumes
Codex review on #2983: while a resume is in flight the live registry
hides the handle, so the batch route could classify the session as cold
and its direct write would race the materializing metadata service (its
stale in-memory document wins the next write, silently un-archiving the
session after the endpoint reported success).
The batch now settles the resume first: SessionManager registers the
whole resume promise synchronously at the App level (controllerForSession
is async, so the controller's own resuming map learns about it a few
microtasks late) and whenResumeSettled awaits it before classification —
a settled resume lands the item on the live chain, a failed one falls
back to the cold path. Also folds the module header down to the
package's external-role comment convention.
* fix(agent-core-v2): publish SessionArchived as an Event2 class in cold archive
* fix(agent-core-v2): serialize batch archive/restore with session lifecycle transitions
* fix(agent-core-v2): serialize session delete with the lifecycle chain
* fix(agent-core-v2): mirror the persisted metadata on cold archive, not the index summary
* docs(agent-core-v2): bring sessionManager comments and new tests to package conventions
* fix(agent-core-v2): normalize legacy session metadata before the cold archive write
* fix(kap-server): serialize the v1 single-session archive with the lifecycle chain
* chore: drop changesets for internal-only protocol work
* fix(agent-core-v2): encode cold-archived metadata for v1 readers
* fix(agent-core-v2): serialize fork and createChild with the source session's chain
* refactor(agent-core-v2): chain every session lifecycle method and hand batch sections unguarded ops
* fix(agent-core-v2): propagate failed resumes to the next settle
* fix(agent-core-v2): roll back the unannounced handle when a resume fails mid-materialization
* fix(agent-core-v2): read and migrate the legacy session-meta location on cold archive
* fix(agent-core-v2): serialize explicit-id session creation with the lifecycle chain
create() with a caller-supplied sessionId bypassed the per-session chain,
so a concurrent batch archive could classify the half-created session as
cold and write archived state that the live metadata service later
overwrites. Creation now queues on the target id's chain whenever an
explicit id is present.
Also type the resume-failure maps as Error and normalize at the catch
site, satisfying only-throw-error.
* style(kap-server): strip comments from the session routes per the no-comments convention
* fix(agent-core-v2): serialize explicit fork and child target ids on the lifecycle chain
fork() and createChild() with a newSessionId locked only the source id, so
a batch archive of the target could slip into the creation window: the
index already knows the half-created session, the batch writes archived
state to its document, and the fork's in-memory metadata later overwrites
it. Both operations now acquire the deduped, sorted key set so multi-key
sections always take locks in one deterministic order.
* feat(agent-core): unify the v1 MCP management plane
- McpServerRegistry: one config view over global (layered mcp.json),
plugin (manifests, read-only, final effective config), and caller
(SDK-injected) servers; name collisions keep both entries.
- Write plane: add/update/removeGlobalMcpServer mutate the user-level
file and push into live sessions; getGlobalMcpServer returns the
effective config; mutations of read-only entries are rejected.
- testGlobalMcpServer accepts an inline config; addSessionMcpServer
connects a server in one live session with an optional persist flag;
reconnect accepts a replacement config and re-resolves via the registry.
- One process-wide McpOAuthService shared with every session: obtained_at
stamps, offline token state, single-flight and proactive refresh, and
credential events. Sessions self-subscribe in the constructor, so even
initializing sessions see every event; token writes serialize through
the process-local OAuthTokenTransaction per credential identity.
- inspectAppMcpServers + locator-addressed begin/complete/cancel/reset
cover plugin servers; inspection output redacts env/headers to sorted
key lists; locator OAuth ops reject ambiguous shared runtime names.
- The legacy auth-status surface reads the registry (offline by default,
verify=true probes) and never mutates credentials.
- VS Code panel receives source/origin/mutable and hides mutating
actions on read-only entries.
- v2 client facade in node-sdk mirrors the surface over agent-core-v2
(plugin inventory stays v1-only for now).
* fix(agent-core): close the v1 MCP live-session reconciliation gaps
Recompute each live session's MCP target from the registry's runtime
resolution (enabled plugin > project layer > user file; caller injection
shadows everything) behind every config mutation, instead of per-path
patching: shadowed file layers recover when a plugin winner is disabled or
removed, removing a user-level entry resurrects its project-layer shadow,
disabled plugin descriptors no longer block removals, persisted session
adds validate against the session's project layer and broadcast to other
live sessions, and per-session sync failures are logged with context.
Session status entries and read-only management entries now report
redacted config views (envKeys/headerKeys instead of literal env/headers
values); core-internal reconciliation compares full configs via the
connection manager's raw-entry accessor.
OAuth: interactive flows are serialized per credential (concurrent
begins join the in-flight flow instead of clobbering its PKCE/state), a
malformed credential meta sidecar no longer aborts core start, grants
inside the refresh-ahead window refresh immediately while far-future
grants re-arm through a max-length timer, and the service shuts its
timers and flows down with KimiCore/SDKRpcClient close.
* fix(agent-core): route the proactive MCP OAuth refresh through the token transaction
refreshNow ran its /token request with the SDK default fetch, outside the
credential-serializing OAuthTokenTransaction that every other token write
uses; a slower response carrying an older rotating refresh token could
overwrite a newer grant written by a concurrent transport-side refresh.
* fix(agent-core): keep disabled MCP servers out of auth-state classification
The unified mcpServerAuthState dropped the previous enabled short-circuit,
so a disabled oauth-flagged server reported oauth-required — or was even
probed over the network — instead of not-applicable.
* fix(kimi-code-sdk): short-circuit disabled MCP servers in the v2 auth-status classifier
The v2 parity copy of v1's mcpServerAuthState missed the same enabled
guard v1 just regained; a disabled oauth-flagged entry would report
oauth-required (or be probed). The parity suite now pins the disabled
case on both engines.
* fix(kimi-code): refresh the VS Code MCP list with the workspace cwd after mutations
The add/update/remove RPCs return a cwd-less management list, so the
webview broadcast dropped project-layer entries until the next full load;
re-list with the workspace cwd after every mutation instead.
* fix(agent-core): keep SDK token saves matched to the OAuth token transaction
saveTokens stamped obtained_at onto a fresh object before calling
tokenTransaction.save, so it never matched the exact payload the
transaction recorded for a grant fetch; the consume path was dead and
every save re-wrote. Between the fetch and the SDK callback an intervening
clear could then be overwritten — the resurrected grant came back after a
reset. The write callback stamps the durable record instead.
* fix(agent-core): reject ambiguous legacy name-based MCP auth lookups
The legacy begin/reset auth RPCs took the registry's first name match,
silently starting OAuth for one entry of a runtime-name collision while
the locator path refused the same ambiguity; align them on the shared
uniqueness rule and point callers at the locator-addressed variants.
* fix(agent-core): propagate registry errors during live-session MCP sync
resolveMcpRuntimeTarget collapsed every registry failure into "no target":
a project config file that turned malformed mid-session made sync treat a
still-configured server as gone (tearing down the live connection) and
made config-aware reconnects report "no longer configured" instead of the
actionable config error. Absence still resolves to undefined; malformed
config now propagates — per-session sync logs and keeps the entry, and
reconnect surfaces config.invalid.
* fix(agent-core): close the remaining registry-error and ambiguity gaps
The management guard lookup mapped every registry failure to "absent",
so a malformed project config let a persisted session add write a
user-level entry over an unknown state; only not-found is a miss now. And
the name-only connection test now shares the auth paths' uniqueness rule
instead of probing the first match of a runtime-name collision.
* fix(agent-core): probe the enabled MCP entry under a disabled-name collision
The name-only connection test counted enabled matches for its ambiguity
guard but still probed the first registry match, and the file layers list
before plugins. With a disabled file entry shadowing an enabled plugin of
the same runtime name, Test probed the disabled entry instead of the one a
live session would run. Select the sole enabled match, falling back to the
first entry only when every match is disabled so it reports as disabled.
* fix(agent-core): let session-local MCP adds shadow plugin entries
Caller injection shadows every registry source at session start, plugins
included, and reconciliation leaves caller entries untouched; the live
non-persist add path rejected plugin-owned names anyway, so SDK clients
could not apply the same per-session override without a restart. Gate the
plugin-source rejection on persist: session-local adds connect as caller,
while persisted adds stay rejected as user-level writes behind a read-only
owner.
* fix(agent-core): normalize session MCP names before connecting
The persisted store trims server names, but addSessionMcpServer used the
raw name for the live connect and cross-session reconciliation: a padded
name persisted under the trimmed key while the requesting session ran and
reconciled the raw one, and a blank name connected with no identity at
all. Normalize once up front (rejecting blank) so the store write, the
session entry, and reconciliation agree on the same server.
* fix(agent-core,node-sdk): close the collision-selection and probe-freshness gaps
The legacy name-only auth resolver started from the first registry match,
so a disabled file-layer shadow plus an enabled plugin of the same runtime
name was misread as an ambiguity conflict; select the sole enabled match
before judging ambiguity, exactly like the test probe path. On the v2
client, addSessionMcpServer connected the raw name while the store wrote
the trimmed key — normalize once for both, and route the verify-triggered
auth probes through the per-call OAuth service instead of the cached one
whose providers snapshot tokens at construction, so a grant saved after
the first probe is honored.
* fix(agent-core): normalize global MCP mutation names and guard disabled reconnect swaps
The global add/update/remove mutations guarded and reconciled with the raw
server name while the store persisted the trimmed key, so a padded name
left live sessions unreconciled and could slip past the plugin read-only
guard; normalize once before lookup, persistence, and reconciliation. And
a config-carrying reconnect assigned the replacement before the disabled
check fired, leaving a connected entry that reported the disabled config;
reject disabled replacements before mutating, keeping the same error.
* fix(agent-core): skip proactive refresh while an interactive flow owns the credential
refreshNow reset the shared provider's flow state before and after the
token request; when a proactive timer (or a manual refresh) fired while
beginAuthorization was waiting on the browser callback for the same store
key, that wiped the redirect URL, PKCE verifier, and state the in-flight
flow needed — complete() then failed the exchange even though the user
authorized. Refresh now skips when an interactive flow is active for the
credential: the flow delivers fresh tokens on completion, and the 401
transport path is the backstop if it fails.
* fix(agent-core): allow global MCP adds over disabled plugin descriptors
A disabled plugin entry is absent from the runtime target, but the
read-only guard still treated it as the owner, so a user-level fallback
could only exist if it predated the plugin disable. Relax the shared
guard: disabled plugin descriptors never block mutations (disabled
project entries still shadow the user file and keep their rejection).
* fix(node-sdk): close the v2 session-MCP parity gaps
A v2 reconnect with an explicit enabled:false replacement config used
connect()'s upsert semantics — closing the live client and reporting
success where v1's manager reconnect rejects before applying anything;
reject disabled replacements up front with the same error. And a persisted
v2 session add never consulted the workspace config, so a same-named
project-layer entry was silently shadowed: the user-level write never
takes effect while the direct workspace-manager upsert displaces the
project config for every live session. Resolve the workspace layers and
reject like v1's read-only rule.
* fix(agent-core): keep __proto__-named MCP servers through config parsing
A z.record() parse rebuilds its output via property assignment, so a
server literally named __proto__ hit the prototype setter and vanished
before validation; the layer merge then repeated the same trap with plain
object accumulators. Parse the server map entry-by-entry over the JSON own
keys and accumulate into null-prototype maps, so session startup and the
unified registry keep the declared server and its origin.
* fix(node-sdk): begin v2 MCP auth against a fresh OAuth service
The v2 begin path ran through the cached globalMcpOAuth, whose providers
snapshot tokens at construction: a grant another process saved (or reset)
after that cache materialized was invisible, so begin could open a browser
flow over a valid grant, or report already-authorized off a removed one.
Build the service per call — the read path and the verify probes already
do — and route the status list through the same helper. The test fixture
grows a real token endpoint honoring one rotating refresh token; the
regression fails against the cached-service implementation on v2.
* fix(agent-core): broadcast SDK-driven MCP token invalidations to live sessions
* test(agent-core-v2): give the no-op reconnect test runtime plumbing
The branch added the case against a bare McpConnectionManager, but #2961
made stdio connects resolve the runtime through runtimeResolver, matching
every other case in the file.
The secondary-model variant example could not work as written: a bare
[models] entry does not inherit the provisioned entry's metadata, and
default_effort only takes effect when it is a member of support_efforts,
which the kimi-for-coding family does not declare. Base the example on
kimi-code/k3 with the full metadata copied, and state both prerequisites.
Also align the full-config example's k3 support_efforts with what /login
provisions (low/high/max, so the shown thinking effort "high" is valid),
and stop describing kimi-for-coding-highspeed as cheap: it is priced
higher, so its pool hint now steers toward latency-sensitive tasks.
* feat(kimi-code): recognize multiple inline skill activations in one prompt
Inline /skill: tokens are recognized anywhere in the prompt (after
whitespace, including on following lines) with completion, highlighting,
and de-duplication. Submitting goes through session.promptWithSkills, so
the engine bundles every activation into the prompt's own user message —
one turn, one undo anchor. Replay rebuilds the per-skill cards from the
prompt origin's skillActivations and shows only the caller's own parts in
the user bubble; undo removes the prompt together with its marked bundle
cards; hook results ahead of a bundle are projected inside its window.
Enter accepts an inline completion without submitting (pi-tui
inlineSlashTrigger), and cache-hint plus /btw pass activations through.
* fix(kimi-code): harden bundled skill submissions against review findings
- Mark bundle cards by entry id, not by index into a captured array: the
transcript window trim may replace the entries array mid-call.
- The replay turn limiter no longer cuts between a bundled prompt and the
hook results recorded immediately before it; the oldest visible bundle
keeps its hook context.
- A leading-combo bundle (/skill:a args /skill:b) now queues while busy
like any other inline-skill prompt, instead of being rejected by the
single-skill slash gate.
* fix(kimi-code): fetch one extra replay turn on resume
The SDK trims the replay to the requested limit before returning it, so a
trim landing between a bundled prompt and its preceding hook results would
make them unrecoverable to the TUI-side limiter. Resume now fetches one
extra turn of margin; preserveBundleHookResults does the final cut without
losing the hook context.
* fix(pi-tui): retrigger inline slash completion as the token grows
When the terminal delivers `/rev` in one stdin chunk, the slash starts an
autocomplete request but the following letters arrived to a null
autocomplete state and — unlike a leading slash command — matched no
retrigger context, so the stale request was discarded and the menu never
appeared. Typing token characters inside an inline slash token now
retriggers completion (with a regression test). Also aligns the startup
resume tests with REPLAY_FETCH_TURN_LIMIT.
* fix(pi-tui): retrigger inline completion on later lines too
isSlashMenuAllowed confines the slash-command menu to the first line, so
reusing it for the inline-slash retrigger context silently disabled
retriggering on every later line — the bare-slash request went stale and
the menu never appeared. The inline context now covers a token-opening
slash on subsequent lines as well (with a regression test).
* fix(kimi-code): activate leading skill tokens in /btw and repeated-token combos
- /btw's initial prompt lives entirely in the slash arguments, so a skill
token there sits at position 0; scan it with includeLeading so
`/btw /skill:review …` actually activates the skill.
- Combo-ness is now decided by the raw inline token count rather than the
deduplicated activation count, so `/skill:review check /skill:review`
submits as a bundled prompt instead of falling through to the
single-skill path with the repeated token swallowed into the args.
* fix(kimi-code): rewrite media placeholders in leading combo arguments
A leading combo's first activation carries the raw slash arguments, so a
pasted media placeholder in them reached the engine unresolved — unlike
the standalone sendSkillActivation path, which rewrites placeholders into
escape-proof plain-text file references first. sendInlineSkillUserInput
now rewrites any arg-carrying activation the same way (covering the busy
queue and /btw intercept paths too), while the media themselves continue
to ride the prompt as extracted parts.
* refactor(kimi-code): skill mentions never carry args in bundled prompts
Align bundled submissions with the mention model: two or more skill
tokens anywhere in the input (the leading one included) make one bundled
prompt in which every token activates by name only, and args stay a
standalone /skill:<name> args concept. This removes the leading combo's
command+args parsing, so the first skill's arguments can no longer leak
the next token (displayed as a duplicated prompt under its card), media
placeholders no longer need arg rewriting, and newline-separated bundles
behave exactly like space-separated ones (parseSlashInput's literal-space
separator no longer decides bundle-ness).
* fix(kimi-code): recognized builtin and plugin commands outrank the bundle rule
The no-args bundle rule claimed any input with two or more skill tokens
before checking what led it, so `/btw check /skill:a /skill:b` was
submitted to the main agent as a bundled prompt instead of opening the
side panel. The intent is now resolved first: builtin and plugin commands
always keep their own path regardless of how many skill tokens their
arguments mention, while skill-led and newline-led inputs still bundle as
before.
* fix(pi-tui): retrigger inline completion on colons and register the local divergences
External skill tokens are shaped /skill:<name>, but the inline-slash
retrigger character classes excluded ':' — typing the colon launched no
replacement request, the bare-slash request went stale, and the menu never
appeared for prefixed skill names. Colons now retrigger completion like
other token characters (with a regression test). Also registers the
inlineSlashTrigger and autocomplete-data divergences in the package's
re-vendor protection list.
* fix(kimi-code): preserve FIFO behind unsteerable bundles and reach indented inline completion
- Ctrl-S steering now stops at the first inline-skill bundle: a later
queued message (or the editor draft) no longer jumps ahead of the
unsteerable bundle into the running turn, so the conversational order
survives steering.
- The leading-whitespace slash-path suppression now yields to the inline
skill context first, so an indented token (` /skill:rev`) completes
like its column-0 equivalent instead of being suppressed as a path.
* feat(tui): print the fork resume command and copy it to the clipboard
* fix(tui): use pushd in the Windows fork resume command so it switches drives
cmd.exe's `cd` only updates the target drive's remembered directory, so a
terminal on another drive would run `kimi --resume` in the wrong working
directory. `pushd` switches drive + directory in both cmd.exe and
PowerShell (`cd /d` would break PowerShell). Addresses the Codex review
comment.
* fix(tui): label OSC 52 clipboard delivery as unverified after fork
copyTextToClipboard falls back to an OSC 52 escape when no native
clipboard provider works; terminals without OSC 52 support silently
drop the sequence, so only native delivery may claim success. Matches
the wording convention of /copy. Addresses the Codex review comment.
---------
Co-authored-by: bj456736 <bj456736@users.noreply.github.com>
- Remove the legacy-engine secondary-model recipe section, the
KIMI_SECONDARY_MODEL / KIMI_SECONDARY_EFFORT env entries, and the
model_preference agent-file field: the default engine never reads
them and the legacy engine is deprecated.
- Remove the backward-compat note for a lone [secondary_model] model
key; the code still reads it, but the docs now only document the
current pool scheme.
- Reframe the secondary_model section around the subagent model pool
instead of a singular secondary model.
- Unify zh terminology: 子 Agent -> subagent, 主 Agent -> main agent,
covering prose, headings, anchors, the sidebar label, and the
docs/AGENTS.md term table.