* fix(agent-core-v2): make Gemini tool call ids unique across turns
* fix(kosong): recover tool names from entropy-suffixed Gemini call ids
The orphan tool-message fallback in toolCallIdToName stripped only one
trailing "_<suffix>" segment, so with the new
"{tool_name}_{upstream_id}_{entropy}" id shape it returned
"AgentSwarm_0" instead of "AgentSwarm" once the preceding assistant
call had been compacted away. Strip the fixed 8-hex entropy suffix
first, then the upstream id segment, in both the kosong and
agent-core-v2 providers. Also drop the inline implementation comment
that agent-core-v2 forbids outside the file header.
---------
Co-authored-by: haozhe.yang <yanghaozhe@moonshot.ai>
Add transport and persisted-session smoke scripts for HTTP, WebSocket, and scoped service calls.
Type-check all examples and document remote-server usage and side effects.
* feat: raise default per-step LLM retry budget to 10 attempts
The default of 3 attempts only produced ~1.5s of backoff (0.5s/1s), so
sustained provider overload (429) surfaced to the user almost
immediately. With 10 attempts the exponential ramp (500ms base, x2,
32s cap, 25% jitter) waits out multi-minute overload windows before
failing the turn. Applies to both agent-core (chatWithRetry) and
agent-core-v2 (stepRetry service); loop_control.max_retries_per_step
still overrides.
* test(agent-core): pin retry-dependent tests to the new default budget
- error-paths: the warn-log attempt field now reads 1/10 with the
default 10-attempt budget
- goal-session pause tests: every LLM call throws a retryable error, so
the default 10-attempt backoff (~2min) blew the 5s test timeout; pin
maxRetriesPerStep=1 since the pause behavior does not depend on the
retry count
* fix(kimi-web): improve mobile safe-area handling
* fix(kimi-web): restore dock-height fallback where ChatDock is absent
* fix(kimi-web): pin the app shell to the visual viewport height
* fix(kimi-web): pin the app shell to the visual viewport
* chore: add changeset for mobile safe-area fixes
* feat(agent-core): drop default timeouts for print-mode background tasks
- add `bash_task_timeout_s` config under [background] (0 = no timeout),
covering the background Bash default and the re-arm after a foreground
command is moved to the background on timeout
- allow `[subagent] timeout_ms = 0` (no timeout) and thread it through
Agent/AgentSwarm task registration and the swarm batch timer
- fill both with 0 in print-mode config defaults so `kimi -p` never kills
background work by wall-clock and only the model stops a task;
interactive defaults are unchanged
- sync the Bash tool description/parameter text with the effective
default and update user docs (en/zh) plus changeset
* test(agent-core): satisfy KimiConfig providers requirement in print-defaults tests
* fix(agent-core): clear the foreground deadline on detach when the background timeout is disabled
`detach()` only re-arms when `detachTimeoutMs` is defined, so passing
`undefined` with `bash_task_timeout_s = 0` kept the armed foreground
deadline and a manually detached command was still killed at its
foreground timeout. Pass `0` instead so the reset clears the timer.
Caught by Codex review on #1737.
* fix(kap-server): carry the live subagent roster in the session snapshot
* fix(kap-server): clear the subagent roster on the next main turn start
* fix(kap-server): exclude background subagents from the snapshot roster
* fix(kap-server): finalize live roster entries when the main turn aborts
* fix(kap-server): drop roster entries when foreground subagents detach
* fix(web): expand the swarm card by default while subagents are running
* docs(kap-server): qualify the roster-clearing durability claim
* fix(telemetry): deliver session_started in v2 headless mode
session_started/session_load_failed fire inside create()/resume(), but the CloudAppender was installed only after resolveNativeSession() returned, so they were dropped to the null appender.
- run-v2-print: install the appender before resolving the session; reconcile a resumed session's real model via setContext afterwards.
- sessionLifecycle: bind the session id in announceCreated before emitting so session_started carries session_id.
- CloudAppender.setContext: allow updating the model context.
* fix(agent-core-v2): bind session id on session_load_failed
The resume failure path emitted session_load_failed without binding the
session id first, so the event went out with session_id null even though
the service knows which session failed to load. Bind it via setContext
before track2, mirroring the announceCreated path for session_started,
and update the two tests that locked the empty context in as expected.
- change the default print background mode from 'exit' to 'steer' so
`kimi -p` stays alive while background tasks are pending and each
completion steers a new main turn
- add print-mode config defaults (applyPrintModeConfigDefaults):
effectively unbounded wait ceiling and turn cap, 72h subagent
timeout; explicit user config always wins
- thread the new `uiMode` option from SDKRpcClient through KimiCore
into session create/resume
- clamp setTimeout delays to 0x7fffffff so huge timeouts are not
clamped to 1ms by Node
* 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.