The init run spawns a coder subagent through agentLifecycle.run with an
AbortController that was a local of generateAgentsMd, so nothing outside
the service could cancel it: Ctrl+C in clients only cancels the main
agent's loop turn, which does not exist during /init.
ISessionInitService gains cancelInit(): the service keeps the in-flight
run's AbortController (cleared in finally), and cancelInit aborts it with
a user-cancellation reason. Cancellations propagate unwrapped from
generateAgentsMd (UserCancellationError / AbortError, never
SESSION_INIT_FAILED, no subagent.failed event) so callers can distinguish
"aborted" from "failed".
* fix(agent-core-v2): keep invalidated OAuth flows observable during login
invalidateFlows() deleted flows outright when the provider configuration
changed, including terminal ones. That voided the TERMINAL_RETENTION_MS
window and, worse, left the status guards in handleSuccess /
finalizeAuthentication blind when the invalidation landed in the
microtask gap after toolkit.login resolved: setTerminal then wrote
'authenticated' onto an orphaned state the client could never observe,
so the login hung after a successful browser authorization.
Transition affected pending flows to a visible 'cancelled' terminal
status instead, mirroring abortExisting().
Also normalize the KIMI_CODE_BASE_URL env override at the source in
kimiCodeBaseUrl(): provision persists it verbatim while the model
refresh rewrites it normalized, and the deep-equal diff between the two
shapes fired a spurious providers-changed event mid-login.
* style(agent-core-v2): drop non-header comments from the oauth login fix
* chore: cover agent-core-v2 in the oauth login fix changeset
* feat(server): allow X-Kimi-Client-* headers in CORS for cross-origin desktop renderer
* feat(server): reflect request headers in CORS preflight
For allowed origins, echo the preflight's Access-Control-Request-Headers back in Access-Control-Allow-Headers (falling back to CORS_ALLOW_HEADERS for non-preflight responses). Newly added client request headers (e.g. X-Request-Id) no longer require a matching server-side allowlist change, unblocking desktop app://renderer cross-origin requests that were failing preflight on headers absent from the fixed list.
* fix(agent-core): mark auto-approved plan exits as not user-reviewed
In auto permission mode ExitPlanMode is approved without user
involvement, but its result read "## Approved Plan:" exactly like a
genuine user approval and the transcript showed a green "Approved"
chip. The model treated that as a signal to start executing, even
when the user had asked it to stop after planning.
- Emit an "auto-approved, not user-reviewed" result with a note that
execution follows the user's original instructions (v1 + v2).
- Extend the auto-mode reminder: plan approvals are automatic and are
not a user signal to proceed (v1 + v2).
- Render an "Auto-approved" warning-toned chip in the TUI, keeping
backward compatibility with the old result marker.
- Document the Auto mode plan-exit behavior in interaction guides.
* fix(agent-core): only mark plan exits as auto-approved in auto permission mode
Address review feedback on the auto-approved plan exit change:
- Branch the direct-execution output on the permission mode in both
engines: only auto mode yields the "auto-approved, not
user-reviewed" result and telemetry outcome. In manual / yolo modes
the direct path means a configured or session allow/ask rule let the
call through — an explicit user decision that keeps the user-approved
output, the Approved transcript chip, and the approved outcome.
- Move the v2 rationale out of the method body into the file header,
per the agent-core-v2 comment convention.
- Drop the absolute "only Auto mode" wording from the interaction
guides (en/zh).
Enforce the package comment convention that comments live only in the
top-of-file block: strip mid-file doc, prose, and trailing comments
while preserving header blocks and eslint/oxlint/@ts-* suppression
comments. No code tokens are changed.
* fix(agent-core-v2): fix wire migration rewrite race and reject unversioned logs (8 files)
- await the migration rewrite in wireRecordService.restore() so restore only resolves once the migrated log is durable and rewrite failures surface
- serialize AppendLogStore.rewrite() behind the log flush so appends arriving during the whole-file replace land after the new content
- reject wire logs missing the metadata envelope (missingWireMetadataError) in restore, session fork, and agent create instead of fabricating a current-version envelope over legacy records
* fix(agent-core-v2): stop background tasks on session close (10 files)
- add IAgentTaskService.stopAllOnExit: suppress terminal notifications, then SIGTERM → grace → SIGKILL for every active task (v1 stopBackgroundTasksOnExit parity, gated by keepAliveOnExit)
- call stopAllOnExit from agentLifecycle.remove before scope disposal, and abort live tasks in AgentTaskService.dispose as a last resort for disposal paths that bypass the graceful close
- honor [task] killGracePeriodMs in the stop grace window and bind the v1 KIMI_CODE_BACKGROUND_KEEP_ALIVE_ON_EXIT env override for keepAliveOnExit
* fix(agent-core-v2): root task persistence at the owning agent's scope
- persist task records under <sessionScope>/agents/<agentId>/tasks/ (v1's
per-agent layout) so tasks written by older versions are found on resume
- stop one agent's restore from loading, marking lost, or re-notifying
another agent's tasks
- add a per-agent isolation test covering loadFromDisk + reconcile
* fix(agent-core-v2): address task lifecycle review findings
* fix(agent-core-v2): preserve queued appends across rewrites
* test(agent-core-v2): cover rewrite cutover flush
* fix(agent-core-v2): surface append durability failures
* fix(agent-core-v2): retire released append log buffers
* fix(agent-core-v2): preserve session-level task records
* fix(agent-core-v2): harden append log cutover recovery
* fix(agent-core-v2): suppress only background exit notifications
---------
Co-authored-by: qer <wbxl2000@outlook.com>
* feat(agent-core-v2): support caller-supplied MCP servers on session create
- add mcpServers to CreateSessionOptions, forwarded to the session's first
ensureMcpReady call so caller-supplied servers ride on the initial load
- ensureMcpReady accepts callerServers, honored only by the call that starts
the initial load; later calls just await the in-flight single-flight load
- merge precedence mirrors v1 (rpc/core-impl.ts): file config <
caller-supplied < plugin servers
* feat(agent-core-v2): file tools reach skill roots; add skillDirs seed
- add extendWorkspaceWithSkillRoots to path-access; Read/Write/Edit/Grep/
Glob and media tools merge skill-catalog roots into the workspace per
call (v1 merged once at construction), so skill dirs outside the cwd
stay reachable and late-loading roots are picked up
- add skillCatalogRuntimeOptionsSeed; wire --skillsDir into the v2 print
CLI and skillDirs into kap-server startServer (v1 SDK skillDirs parity)
* chore: add changesets for v2 caller MCP servers and skill dirs fixes
* feat(agent-core-v2): support caller-supplied MCP servers on session create
- add mcpServers to CreateSessionOptions, forwarded to the session's first
ensureMcpReady call so caller-supplied servers ride on the initial load
- ensureMcpReady accepts callerServers, honored only by the call that starts
the initial load; later calls just await the in-flight single-flight load
- merge precedence mirrors v1 (rpc/core-impl.ts): file config <
caller-supplied < plugin servers
* chore: add changeset for v2 caller MCP servers
- rehydrate restored `blobref:` media URLs to inline `data:` URIs from the
agent's blob store, matching the shape of live emissions
- pass tool results carrying media (e.g. ReadMediaFile) through as raw
content parts instead of flattening them to empty text
- use wire record times for `created_at`, nudged to stay strictly
increasing, instead of a synthesized session-start offset
- carry per-entry record times through `ContextTranscript` and the
live-tail merge
* fix(agent-core-v2): surface provider rejection on post-refresh 401
- map a 401 that survives forced token refresh to PROVIDER_AUTH_ERROR
carrying the provider's sanitized message, instead of a misleading
AUTH_LOGIN_REQUIRED re-login prompt
- propagate provider auth errors through full compaction instead of
wrapping them as compaction failure
- export sanitizeStatusErrorMessage for reuse
* fix(agent-core-v2): align repeat-reminder and AskUserQuestion copy with v1
- rewrite repeated tool call reminders to redirect instead of prohibit (port of v1 #1518)
- simplify makeReminderText2 to (repeatCount), no longer echoing tool name and args
- treat dismissed AskUserQuestion as no answer, not the recommended pick (port of v1 #1550)
- update toolDedupe test assertions and regenerate affected wire snapshots
* chore: add changeset for v2 reminder and question copy alignment
* fix(kap-server): drain in-flight heartbeat writes before unlinking the instance file
- count writes that passed the released check and await them in release()
so their atomic rename cannot recreate the file after unlink
- harden the heartbeat test: 1ms cadence plus a post-release re-check
catches the recreate-after-unlink race (verified failing on old code)
* docs(agent-core-v2): move toProviderAuthError rationale to the file header
Address Codex review: agent-core-v2 keeps comments in the top-of-file
header only, never beside functions
Resolve the login environment via resolveKimiCodeLoginAuth so
KIMI_CODE_BASE_URL / KIMI_CODE_OAUTH_HOST steer the credential slot
login writes to the same way they steer runtime token reads — fixes
"login succeeds but every call 401s" against non-default environments.
The provisioned provider entry now records the login environment and
credential slot explicitly, and logout deletes from the runtime
(env-aware) slot.
- drop the "missing a base URL" rejection in ModelResolverService so a
structured provider without base_url resolves to undefined and the wire
provider applies its protocol default endpoint, matching v1
- relax baseUrl to optional across Model, ModelImplInit, and
ProtocolAdapterConfig; guard the anthropic /v1 strip against undefined
* fix(protocol): make server_hello heartbeat_ms optional
kap-server dropped the server-initiated WS heartbeat and no longer emits
heartbeat_ms in server_hello, but the published v1 schema still required
it, so spec-compliant clients rejected the handshake before subscribing.
- mark heartbeat_ms optional in serverHelloPayloadSchema (advisory only)
- add a ws-control test for a server_hello without heartbeat_ms
- align kimi-web WireServerHello and the server-e2e handshake assertion
* fix(agent-core-v2): pass media parts through tool_result projection
- keep raw kosong content-part array for tool results carrying
image/video/audio parts instead of flattening to text
- restore ReadMediaFile media rendering after session reload/resume
* chore: add changeset for optional server_hello heartbeat_ms
* docs(agent-core-v2): move tool_result media rationale to module header
Per the agent-core-v2 comment conventions, comments live solely in the
top-of-file block — move the media-passthrough rationale out of the
buildProtocolContent JSDoc into the module header.
* fix(agent-core-v2): ignite toolDedupe plugin on agent creation
- ignite IAgentToolDedupeService in AgentLifecycleService before external
hooks so it sits ahead of permission on onBeforeExecuteTool
- mirror the ignition in the test harness AgentTestContext
- add lifecycle test asserting the dedupe hooks are registered
* fix(agent-core-v2): carry session files and cron tasks over on fork
- copy blobs, plans, background-task output, and media originals into the
forked session dir (excluding state.json, wire logs, and logs/)
- duplicate the source session's cron tasks with fresh ids retagged to the
target session via the shared CRON_SESSION_TAG
- roll back the half-fork on failure: drop the materialized handle from the
live registry and delete the target session dir
* feat(kap-server): drop WS heartbeat; never terminate idle connections
- remove ping interval / pong timeout / socket.terminate() from both the
v1 (WsConnectionV1) and v2 (WsConnection) WebSocket connections
- v2 protocol: drop pong from the client message schema, remove PingMessage
and ReadyMessage.heartbeatMs; the ready frame is now bare { type: 'ready' }
- v1 protocol: remove buildPing/PingFrame and ServerHelloPayload.heartbeat_ms;
server_hello no longer advertises a heartbeat
- drop pingIntervalMs/pongTimeoutMs options from registerWs/registerWsV1
* feat(agent-core-v2): gate image formats and add media-stripped resend
- add image-format-policy as the single source of truth for the
provider-accepted image MIME set (PNG/JPEG/GIF/WebP), with MIME
normalization, data-URL parsing, byte sniffing, and refusal notices
- enforce the format gate at every ingestion point: ReadMediaFile refuses
unsupported formats with per-OS conversion guidance, MCP tool results and
prompt step requests drop them for a text notice, and kap-server prompt
routes gate inline, uploaded, and remote-URL images on the sniffed bytes
- resend once with every media part replaced by a text marker when the
provider rejects an image's format, and keep later steps of the same turn
on the media-stripped projection (v1 parity)
- commit the WebP decoder wasm as a base64 module for the bundled CLI, with
a regenerate script
* feat(agent-core-v2): add media-degraded 413 resend and WebP re-encoding
- resend once with the media-degraded projection after an HTTP 413
body-size rejection: all but the two most recent media parts are replaced
by text markers, and later steps of the turn stay degraded (stripped
still wins over degraded)
- generalize stripAllMediaParts into degradeOlderMediaParts; the
media-stripped resend is now the keepRecent=0 case
- re-encode non-animated WebP through the wasm decoder and the PNG/JPEG
ladder instead of passing oversized WebP through (animated WebP still
passes through whole)
- stop lossless PNG rescaling at a 1000px floor and switch to the JPEG
ladder below it, so small byte budgets stay readable
- add the @jsquash/webp dependency
* feat(agent-core-v2): add flag-gated fault injection for recovery testing
- add the faultInjection domain: a one-shot arm/take latch that raises a
deterministic provider failure (HTTP 413 body-size or image-format 400)
before the provider is contacted, so the media-degraded / media-stripped
recovery resends can be exercised end-to-end against a real provider
- gate arming behind the new fault-injection experimental flag
(KIMI_CODE_EXPERIMENTAL_FAULT_INJECTION), off by default
- expose IAgentPromptService and IFaultInjectionService over the kap-server
/api/v2 RPC channel
- add a klient example that drives the REST ingestion gate and both
recovery resends against a live server
* feat(server): default to kap-server and remove the v1 server package
- kimi server run / kimi web now boot kap-server (agent-core-v2 engine)
unconditionally; the KIMI_CODE_EXPERIMENTAL_FLAG gate on the server
path is gone (the kimi -p print-mode gate stays)
- move the OS service manager (svc: launchd/systemd/schtasks) from
packages/server into packages/kap-server and export it there
- repoint the CLI server subcommands, tests, and dev scripts at
kap-server; relabel the web dev backend presets default/multi
- delete packages/server and update workspace bookkeeping (flake.nix,
pnpm-lock.yaml, changeset ignore docs, AGENTS.md, agent-core-dev skill)
* test(server-e2e): remove scenarios that depend on v1 debug endpoints
Scenarios 04-stateless-controls, 10-prompt-queue-steer and
12-send-and-cancel assert through the /api/v1/debug/prompts/*
introspection routes, which only the deleted v1 server mounted —
kap-server's --debug-endpoints is a documented no-op, so these
scenarios can only 404 now. The vitest e2e files using the same
surface already skip when it is absent.
* fix(web): size workspace picker from full content
* chore: add web workspace picker changeset
* refactor(web): use intrinsic workspace picker sizing
* fix(web): cap workspace picker to conversation pane
* fix(agent-core-v2): support services.moonshot_search config for WebSearch
The v2 engine only resolved the WebSearch backend from the managed Kimi OAuth provider, so an environment that configures web search purely via [services.moonshot_search] (api key, no login) got no provider and the WebSearch tool stayed hidden. Register the services config section and resolve the provider config-first, mirroring v1 precedence.
* fix(agent-core-v2): preserve custom services config
* fix(web): make mid-turn delta offsets step-relative
Reset in-flight text and client stream alignment at step boundaries so
resync seeds only the current step instead of duplicating prior steps.
* fix(web): dedupe resync-seeded messages by normalized content
The exact-JSON content signature missed duplicates when the two copies
differed by thinking signature, tool progress, part boundaries, or the
tool set (finished parallel tools leave running_tools). Reduce content
to concatenated stream text plus sorted tool-call ids and treat a
covered subset as the duplicate, merging the seed's tool progress into
the existing cards before dropping it.
* fix(agent-core-v2): append sessions to legacy index so v1 TUI can resume web-created sessions
* chore: remove changeset
* refactor: rename appendLegacySessionIndexEntry to appendSessionIndexEntry
* fix(agent-core-v2): address review feedback on session index append
- Await appendLogStore.flush() so the shared index is durable before create/fork returns
- Append fork entries only after wire copy and agent replay complete
- Move method-level rationale into the file header per package comment conventions
* fix(web): restore the goal card after a page refresh
* fix(web): assert goal endpoint URL without stringification
* fix(web): skip goal recovery write when a live goal event wins the race
* fix(web): track goal events with a per-session version so clears win the recovery race
- recognize ucrt64/clang64/clangarm64 prefixes in git --exec-path
alongside legacy mingw32/mingw64 when locating bash on Windows
- port the three MSYS2 regression tests from kaos to the v2 probe
* fix: send the product User-Agent on provider registry and catalog fetches
Registry (api.json) and models.dev catalog fetches only carried the
runtime default User-Agent while every other outbound request sends
kimi-code-cli/<version>. Thread an optional userAgent through
fetchCustomRegistry / fetchCatalog and the shared refresh host, pass
the product UA from the CLI, TUI, and both daemons, and seed a default
product UA in kap-server that hosts can override via opts.seeds.
* refactor: use options for registry fetches
* fix(tui): deliver pasted media in /skill and plugin command args via file tags
Skill and plugin command args are a plain-text RPC channel, so pasted image/video placeholders never reached the model. Rewrite matched placeholders into <image|video path="..."> tags pointing at cache-dir copies (the same convention pasted videos already use), and gate them on model media capabilities like the normal prompt path.
* fix(tui): deliver pasted media when steering with Ctrl-S
Ctrl-S steer dropped media: queued messages were reduced to their text and the editor draft never went through placeholder extraction, so session.steer only ever received placeholder strings. Carry the queued items' extracted parts, extract the editor draft on the spot (with the same capability gate as a normal submit), and merge everything into a part list when any item has media.
* fix(tui): avoid empty text separator when steering consecutive media
The '\n\n' item separator could land as a standalone whitespace-only text part between two media parts, which normalizePromptInput rejects — failing the steer after the queue and editor draft were already cleared. Only append the separator onto a trailing text part so it always merges.
* fix(tui): keep blank-line separator between media and text steer items
The separator was only appended onto trailing text parts, so a media item followed by a plain-text steer item lost the historical '\n\n' separation. Always append it — it merges into the adjacent text part — except between two touching media parts, where a standalone whitespace-only text part would be rejected by normalizePromptInput.
* fix(tui): use escape-proof media references in /skill args
Skill args are XML-escaped on render (renderSkillAttributes + expandSkillParameters), which mangled the <image|video path> tags into escaped text. Add a 'plain' reference style (Attached image file: <path> (open it with ReadMediaFile)) with no tag/attribute boundary characters and use it for the skill channel; plugin command args expand verbatim and keep the tag form.
* fix(tui): strip XML boundary chars from plain-style video cache names
Video cache names carry the original label, which permits <>&"; in the plain (/skill) reference style those chars get XML-escaped in args and the reference no longer matches the file on disk. Strip them from the cache name in plain mode; the tag channel is unchanged (its attribute escaping already handles them).
* fix(tui): surface media materialization failures as TUI errors
The cache copy inside media placeholder rewriting can throw (unwritable cache dir, vanished video source) before any RPC .catch is installed, escaping the fire-and-forget slash-command dispatcher as an unhandled rejection. Catch it in sendSkillActivation, activatePluginCommand, and the Ctrl-S draft extraction, show an error, and leave queue/draft/session state untouched.
Add the plumbing to debug kimi-web against the v1 (server) and v2
(kap-server) engines side by side:
- root dev:v1 / dev:v2 scripts; v2 boots kap-server with the
multi_server flag on a fixed port so both engines can run at once
- dev-proxy backend switcher: GET/POST /__kimi-dev/backend repoints
the /api/v1 proxy at runtime (HTTP + WS) without a Vite restart
- dev-only sidebar backend pill reading /meta's backend field, with a
one-click switcher menu; Settings shows the backend engine too
- re-fetch /meta on every WS (re)connect so the badge stays truthful
across backend restarts and switches
- strip the browser Origin header on proxied requests: v1's WS upgrade
path rejected the Vite-origin vs server-Host mismatch with 403