* feat(kimi-code): keep print-mode goal runs alive until the goal settles
- applyPrintBackgroundPolicy waits for goal continuation turns via a goalActive hook before applying the exit/drain/steer mode, bounded by the wait ceiling
- createPrintTurnEndings skips the timeout when the remaining budget is not finite
- update the changeset to cover the goal-run lifecycle
* fix(kimi-code): wake the print goal wait periodically so settled goals exit promptly
* fix(server): synthesize session status only from the main agent's turn
A sub-agent runs its own turn on the shared session channel, and the
broadcaster synthesized event.session.status_changed from every agent's
turn.started/turn.ended. A foreground sub-agent finishing mid-turn thus
emitted a bogus idle transition that clients read as 'turn finished'
(notifications, sounds, unread dots, queued-message drain), while the
real main-agent turn end was swallowed by dedup.
Gate the status synthesis on MAIN_AGENT_ID, matching the existing
main-only rule in InFlightTurnTracker.
* fix(server): emit idle when a sub-agent turn ends as the last active agent
Defensive follow-up to the main-only status synthesis: ensureState seeds
lastStatus from ISessionActivity, which counts any agent's turn, so a
session first activated while only a sub-agent was active would stay
running for subscribers until the next main turn. A sub-agent's own
lease and loop state are already released when its turn.ended is
published, so a session-wide idle activity read cannot be a
mid-main-turn foreground sub-agent — emit idle in that case only.
Dedup keeps it a no-op everywhere else.
* revert: drop the defensive idle emit for sub-agent-only turn endings
This reverts 7918b45fe. The stale-seed scenario it guarded is
effectively unreachable in production: sessions are activated at
creation / first prompt and stay active for the process lifetime, and
persisted detached tasks are reconciled as lost on restart, never
resumed. The guard also raised a new behavioral question of its own
(failed/cancelled sub-agent endings would emit a success-style idle).
Keep the PR to the minimal main-only status synthesis; the theoretical
corner self-corrects on the next REST status pull or main turn.
* 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