* fix(server): report CLI version as server_version
- kap-server: accept opts.version in startServer, reported as
server_version (/meta, OpenAPI, session exports, lock registry,
default User-Agent); defaults to its own package version
- kimi-code: pass the CLI product version when starting the server
- add boot test covering /api/v1/meta, lock file, and User-Agent
* fix(agent-core-v2): make new sessions resumable by older v1 builds
- add wireRecord.seal() to write the metadata envelope at agent creation,
so fresh logs satisfy v1 replay's first-record-must-be-metadata invariant
- seal the wire log before any op dispatch in AgentLifecycleService.create;
no-op when the log already has records (resume / forked copies)
- seed empty agents/custom maps in session metadata so v1 Session.resume()
can index agents['main'] on a v2-created state.json
- add seal unit tests and lifecycle sealing tests
* fix(kap-server): show real message send times in snapshot history
- read per-record times stamped on wire.jsonl via reduceContextTranscript
and cache them alongside the transcript messages
- prefer each record's real dispatch time for created_at; records without a
stamp fall back to session.createdAt + index, clamped so the page stays
strictly increasing (mirrors MessageLegacyService.list)
- add tests for fallback, clamping, and the page-offset index mapping
* fix(agent-core-v2): backfill missing agents/custom maps in existing session metadata
- load() now heals pre-fix v2 state.json documents that never gained the
agents/custom maps, persisting the backfill so one open on a new build
leaves the session resumable by released v1 builds (Session.resume()
indexes agents['main'] unconditionally)
- updatedAt is deliberately untouched so the format heal does not reorder
session listings
* feat(agent-core-v2): make subagent timeout configurable, default 2h
- add [subagent] config section with timeout_ms and KIMI_SUBAGENT_TIMEOUT_MS env override
- resolve Agent and AgentSwarm per-run timeouts through resolveSubagentTimeoutMs
- update tool descriptions and tests to reflect the 2-hour default
* feat(agent-core-v2): align print-mode background policy with v1
- add printBackgroundMode/printMaxTurns to the task config section with resolvePrintBackgroundMode (keepAliveOnExit fallback)
- apply exit/drain/steer policy to kimi -p on the experimental engine, buffering turn.ended events and failing the run when a steered turn fails
- register the subagent config section from the package entry
* fix(agent-core-v2): strict equality in metadata heal, comments in file headers only
- replace == null with === undefined in the session-metadata heal to
satisfy eqeqeq (oxlint --type-aware in CI)
- move the seal() rationale into the wireRecord contract header and the
agents/custom invariant into the sessionMetadata header per the
tree's header-only comment convention
* fix: emit turn_id on turn_started/ended/interrupted telemetry
The turn lifecycle telemetry events (turn_started, turn_ended,
turn_interrupted) never carried the turn id, while tool_call and
tool_call_dedup_detected did. Any analysis correlating a turn's start,
end, or interruption back to its tool calls had nothing to join on.
Add turn_id (already in scope) to all three track() calls, matching the
existing key/value convention used by tool_call_dedup_detected.
Update the strict turn_started/turn_interrupted assertion to cover it.
* fix(agent-core-v2): emit turn_id on turn_started/ended/interrupted telemetry
Port the v1 fix to agent-core-v2: turn lifecycle telemetry events
(turn_started, turn_ended, turn_interrupted) carried no turn id while
tool_call did, leaving nothing to correlate a turn's start, end, or
interruption back to its tool calls.
Add turn_id to the three event interfaces, the telemetry registry
property docs, and the three track2() calls in AgentLoopService,
matching the existing ToolCallEvent key convention. Extend the
turn telemetry assertions in loop.test.ts to cover it.
* fix: emit turn_id on tool_call telemetry
* docs(agent-core-v2): clarify turn_id is a per-agent index in the telemetry registry
* feat(agent-core-v2): record droppedCount on full-compaction llm.request wire ops
- add per-attempt droppedCount to the llm.request source logFields so
replays can see how much history each retry round blinded
- cover compaction/loop request events in the full-compaction test
* refactor(agent-core-v2): rename select_tools capability to dynamically_loaded_tools
- rename the ModelCapability bit and catalog field across capability.ts,
catalog.ts, modelResolverService and the toolSelect gate
- update toolSelect flag description wording to match
- update all affected tests and the test harness capabilityNames helper
* chore: add changesets for v2 wire trace and capability rename
- drop the in-memory write cache in the v2 registry: every list/get/
mutation is a fresh read-modify-write of workspaces.json under the op
mutex, so v1 writers (touchWorkspaceRegistry) are never clobbered by a
stale snapshot; the session-index merge stays once per process behind
a flag (Codex P1)
- move the shared workspaces.json helper from services/workspace (the
upper facade) to session/store, so the rpc runtime no longer imports
back into services/ (Codex P2)
- register the cwd in workspaces.json when a v1 (TUI/SDK) session is
created, via a shared workspaceRegistryFile read/write/touch module
- merge session_index.jsonl into the catalog once at v2 startup
(awaited during kap-server boot), adding only paths absent from
workspaces.json
- make v2 workspace delete a soft delete through the v1-compatible
deleted_workspace_ids field: the session-index merge never resurrects
tombstoned ids, while an explicit createOrTouch clears the tombstone
SessionLegacyService.status() now resolves the main agent through
ensureMainAgent() -> IAgentLifecycleService.create() (since d158e0a7a),
but the scenario's hand-rolled lifecycle stub only implemented
whenReady, so the test errored with 'create is not a function' before
any assertion ran. Return the existing main agent from create, matching
the real service's create-or-get contract for explicit ids.
When the model provider becomes resolvable only after the agent starts
(async OAuth / managed free-tokens registration), the builtin tool table
stayed empty: initializeBuiltinTools was gated on hasProvider at fixed
checkpoints and none fired. loopTools now self-heals on read once a
provider resolves, so tool calls no longer fail with "Tool not found".
* fix: preserve provider thinking effort values
* fix: resolve Kimi thinking effort fallbacks
* fix: use resolved Kimi effort in v2 requests
* fix: align Kimi effort resolution paths
* fix: synchronize forced Kimi effort state
* fix: synchronize forced Kimi effort in v2 state
* fix: tolerate unresolved models in v2 status
* fix(web): submit thinking level verbatim and drop the hardcoded default
Align kimi-web's thinking-level handling with the TUI:
- Submit the stored level as-is on every prompt path (prompt, steer,
skill activation, BTW side chat) instead of coercing it onto the
target model's declared efforts.
- No stored preference (undefined) instead of a hardcoded 'high'
default: prompts omit the thinking override and the daemon resolves
the config/model default, same as an unset [thinking] in the TUI.
- Model switcher pre-selects the target model's own default level when
switching models; re-selecting the current model keeps the level.
- Display the effective level (stored value, else the model default)
in the composer, mobile sheet, and /status panel.
* chore(web): remove the dead dev:stub script
The stub daemon (dev/stub-daemon.mjs) no longer exists, so the
dev:stub npm script and its docs references were dead weight.
* fix(web): pin the model default thinking level and persist picks globally
- With no stored preference, loadModels() pins the active model's
catalog default_effort as a concrete in-memory value, so what the
UI shows, what prompts submit, and what the session runs always
agree. localStorage stays reserved for levels the user picked.
- setThinking and model switches now also write the daemon-wide
[thinking] config (same mapping as the TUI's thinkingEffortToConfig),
so sessions created by other clients inherit the pick.
Align toProtocolModel with the v1 projection and the protocol wire schema by applying effectiveModelConfig and emitting support_efforts / default_effort, so clients of the v2 /models endpoint see thinking-effort levels again.
Replace the nonexistent getHandle call introduced by the blocked-goal continuation tests with the public get API and the shared MAIN_AGENT_ID constant, restoring typecheck and both continuation tests.
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