* fix: adapt grep tool to agent-core-v2 * fix(agent-core-v2): enrich PATH from the user's login shell at startup - port probeLoginShellPath/mergeLoginShellPath/applyLoginShellPath into _base/execEnv/loginShellPath.ts as a pure helper (no DI) - export execFileText from environmentProbe for reuse by the probe - run applyLoginShellPathFromNode concurrently with the host probe in HostEnvironmentService, mirroring kaos LocalKaos.create() Aligns agent-core-v2 with kaos021786f5so the Bash tool finds user-installed tools (e.g. Homebrew's gh) when kimi-code is launched from a GUI or non-login shell. * fix(agent-core-v2): prefer persisted cwd on resume * feat(agent-core-v2): support structured response formats * fix: restore v2 grep telemetry and tests * fix: preserve v2 compaction boundary * fix(agent-core-v2): align agent and swarm tool behavior * feat(ws-v1): add per-agent event subscription filter - protocol: add optional agent_filter to client_hello and subscribe - kap-server: carry per-subscription agent allowlists through the broadcaster and connection, narrowing live fan-out and replay to selected agents while keeping a single global sequence and bypassing the filter for global events - agent-core-v2: degrade MiniDbQueryStore to a no-op read model when the query-store lock is held by another process instead of crashing the host * feat(web): prefix skill slash commands with skill: to distinguish them from built-in commands (#1492) * ci: release packages (#1468) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * docs(changelog): sync 0.23.2 from apps/kimi-code/CHANGELOG.md (#1496) * chore: add changeset for agent swarm parity * fix: align v2 compaction prompt * fix: recover v2 compaction from plain 413 * fix: report v2 compaction retry telemetry * fix: align MCP discovery and output with v1 * fix: align v2 compaction auth guards * fix: align v2 grep behavior with v1 * chore: remove agent swarm changeset * fix(agent-core-v2): restore task resume parity * feat(agent-core-v2): record llm request traces * fix(agent-core-v2): align AskUserQuestion tool chain with v1 - translate wire ids back to question text / option labels when resolving a question over REST, joining multi-select labels with ', ' - enforce unique question texts / option labels and non-empty strings at both the schema and the execution path - cancel pending questions on turn abort or background task stop by dismissing the parked entry (resolves null, v1 broker semantics) - restore the unsupported-client fallback and dismissed-error handling - pass empty header / option description through verbatim and align the model-facing tool description byte-for-byte with v1 - drop the synthetic expires_at field from the question wire shape * fix(agent-core-v2): preserve compaction hook session * test(agent-core-v2): cover concurrent agent background limit * fix(agent-core): report EXIF-rotated image dimensions and raise edge cap to 3000px (#1460) * fix(agent-core): report EXIF-rotated image dimensions and raise edge cap to 3000px Image compression now reports original dimensions in the decoded (EXIF-rotated) space, matching the coordinate system of the sent image and of ReadMediaFile region readback; previously portrait JPEGs (orientation 5-8) got swapped width/height in captions. The longest-edge downscale cap rises from 2000px to 3000px, and the default jimp resize path is documented as the anti-aliased area-average one so it is not accidentally switched to a point-sampled interpolation mode. * test: shrink oversized image fixtures to fit CI timeouts The 3600x3600 fixtures introduced for the 3000px edge cap nearly doubled the pixel area jimp has to decode and deflate, pushing the slowest compression tests past the 5s vitest timeout on CI runners. 3600x1800 keeps every fixture over the cap while restoring roughly the workload of the old 2600x2600 fixtures that CI handled comfortably. * test: pin anti-aliased downscale quality with executable guards A 1px checkerboard probe pins the compressor to full-coverage averaging at integer and fractional ratios, with jimp's point-sampled BILINEAR mode kept as the executable aliasing counter-example (it collapses the 50%-gray pattern to solid black at 4:1). Also guards the other classic downscale bugs: transparent-pixel color bleed, mean-brightness drift, iterative recompression degradation, and zero-size collapse on extreme aspect ratios. * fix(agent-core): report decoded EXIF-rotated dimensions in ReadMediaFile notes The media note derived its original-dimensions line from the header sniff, which reports pre-rotation values for EXIF orientation 5-8 JPEGs. The sent image and region readback both live in the decoded (rotated) space, so portrait photos got axis-swapped coordinate guidance. Once a decode has happened — compression or crop — its dimensions now overwrite the sniffed ones. * fix(agent-core): improve handling of EXIF orientation in image dimensions and metadata * fix(agent-core): sniff EXIF orientation and step budget fallback through 2000px Two follow-ups to the EXIF and 3000px-cap changes: sniffImageDimensions now reads the JPEG EXIF Orientation tag (pure header parse, both byte orders) and reports display-space dimensions for orientations 5-8. Passthrough images — never decoded — previously kept the pre-rotation header size in compression results and media read notes, disagreeing with the decoded space that region readback uses. encodeWithinBudget steps the over-budget fallback through 2000px before the 1000px last resort. Raising the cap to 3000px had left a regression window: an image whose 2000px encode fits the byte budget was sent at 1000px where the old 2000px cap used to send it at 2000px. * fix(kimi-code): record pasted image dimensions in display space The TUI paste path recorded attachment and original dimensions from its raw header parser, which ignores EXIF orientation. For a portrait JPEG the submit-time caption then contradicted the sent image's aspect and region readback coordinates were axis-swapped. Dimensions now come from the compression result, which reports display space on both the compressed and passthrough paths; parseImageMeta remains only the format/mime gate. * feat(agent-core): add image compression and crop telemetry Every image ingestion path now reports an image_compress event — outcome (compressed / passthrough fast, guard, unsupported, unhelpful, error), input/output formats, byte and pixel sizes, EXIF transposition, and duration — and region readback reports an image_crop event with a failure classification and the region's share of the original area. Wiring is per call site via a new CompressImageOptions.telemetry option, so the outcome split and timing are measured inside the compressor while each caller only names its source: ReadMediaFile (tool construction, like GrepTool), MCP tool results (McpOutputOptions), server prompt ingestion (ICoreProcessService now exposes the host telemetry client), ACP prompts (session track adapter), and TUI paste (host.track adapter). Properties are numeric/enum only — never paths or content — and a throwing client can never affect the compression result. * fix(agent-core): run the full JPEG quality ladder at fallback sizes The fallback rescales encoded only at quality 20, so a JPEG whose ladder failed at the fitted size collapsed straight to the lowest quality even when the smaller size left budget headroom for a higher rung (the realistic window is the 1000px step, where the 4x pixel drop pays for q80/q60). Each fallback edge now walks the same q80-to-q20 ladder as the fitted size. * test: shrink heavy JPEG fixtures and add explicit timeouts The fallback-ladder test runs ~11 pure-JS JPEG encodes and the EXIF paste test decodes, rotates, and re-encodes a 6.5MP frame; both sat at the edge of the 5s vitest timeout on CI runners. Narrower fixtures cut the pixel area (the ladder test keeps its width above 2000px so the full fallback chain still runs) and explicit 15s timeouts absorb runner variance. * fix(server): scope prompt image compression telemetry to the session The prompt-ingestion image_compress events were emitted with the bare host telemetry client, while every agent-side source inherits a session-scoped client — so prompt_inline/prompt_file events could not be correlated with their session. The route now wraps the client with withTelemetryContext({ sessionId }) like rpc/core-impl does for session telemetry. * chore(changeset): consolidate image compression changesets One entry covering the cap raise and the EXIF dimension fix, listed for both the CLI and the SDK so the SDK changelog's compression description (previously pinned at 2000px) stays accurate. * fix: count goal creation turn (#1477) * feat(kosong): support structured response formats (#1397) * fix: clarify goal blocked audit guidance (#1481) * feat(agent-core): discard loaded tool schemas on compaction (#1471) Align progressive tool disclosure with the discard-on-compaction model: compaction no longer rebuilds loaded dynamic tool schemas. The boundary announcement re-lists every loadable name, the model re-selects what it still needs, and a from-memory call to a no-longer-loaded tool is rejected by preflight with select guidance. This removes the keep-all rebuild and its half-trigger budget heuristics entirely: the post-compaction floor is back to users + summary, which is structurally outside the auto-compaction trigger band, and the guard baseline degenerates to summary + reinjected reminders. Every downstream mechanism already treated the empty loaded set as its consistent base state (ledger scan, pending clear at the compaction boundary, deferred extras, preflight wording), so this is a strict simplification. Co-authored-by: fengchenchen <fengchenchen@moonshot.ai> * fix(kimi-code): exit 1 when a headless (-p) turn fails (#1483) Headless (`kimi -p`) failures could exit with code 0 when the event loop drained during the shutdown cleanup (e.g. telemetry's unref'd retry backoff when the network is blocked), because the rejection never reached the process.exit(1) call. Set the failure exit code before any await in both the run-prompt catch and the main catch, and keep the cleanup timeout ref'd so the loop stays alive long enough for the rejection to propagate. * feat(plugins): add Vercel plugin to marketplace (#1489) * feat(web): support Enter key to confirm archive and other dialogs (#1490) * feat(web): redesign cron reminder as a message bubble (#1480) * feat(web): redesign cron reminder as a message bubble Restyle the cron trigger notice as a right-aligned user-style message bubble that shows the scheduled prompt in full (wrapping across lines), with a small meta row beneath it for the schedule, status, job id and run time. Extract a shared MessageTime component used by both user messages and the cron reminder so the timestamp format and click-to-expand behavior stay consistent, and give the CronCreate/CronList/CronDelete tools distinct calendar icons. * refactor(web): render cron reminders only as standalone turns Remove the embedded cron block path from the web transcript projector so cron reminder fires always render through the standalone right-aligned bubble path. * chore(web): simplify cron redesign changeset * fix(web): composer model switch also updates global default model (#1491) * fix(web): composer model switch also updates global default model The composer model switcher still switches the active session's model via POST /sessions/{id}/profile (awaited, so the model pill reflects the result), and additionally fires POST /api/v1/config with { default_model } as a fire-and-forget side effect so new sessions inherit the chosen default. The config request is skipped when the model already matches the current default. * fix(web): route ModelPicker overlay selection through the default-model update The overlay opened from the composer's "More models" row (and /model) is a continuation of the same switch flow, so its selection now also bumps the global default model instead of only switching the active session. * fix(web): only persist the default model after a confirmed session switch setModel now returns whether the switch was accepted (true for the draft path), so the composer flow no longer writes a stale or invalid model alias into the global config when the session-level switch failed and rolled back. * feat(web): prefix skill slash commands with skill: to distinguish them from built-in commands (#1492) * ci: release packages (#1468) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix(agent-core-v2): restore native append for Write append mode - add IHostFileSystem.appendText backed by fs.appendFile (O_APPEND) - route WriteTool append through it instead of read-then-rewrite, so existing content is never read, truncated, or clobbered by concurrent writers and a crash mid-append can only lose the new bytes - update typed host-fs test fakes and WriteTool append assertions * fix(agent-core-v2): align task tool prompts with v1 * fix(agent-core-v2): align compaction empty retry * fix(agent-core-v2): restore web search source site and citation reminders - surface source site: add WebSearchResult.siteName, map site_name in the Moonshot provider, and render the Site: line in tool output - restore the per-search inline citation reminder alongside the results - align web-search.md with v1: source-site/result-summary guidance and the static citation reminder * fix(agent-core-v2): route task timeouts through SIGTERM grace + SIGKILL - add terminateWithGrace shared by stop, timeoutMs, detachTimeoutMs, and track deadlines: cancel/SIGTERM -> 5s grace -> forceStop (SIGKILL) - coerce a post-abort self-settled `killed` to `timed_out` so a deadline stays reported as timed_out, matching v1 settlementForOutcome - add manager tests for SIGTERM-ignored escalation, graceful-exit within the grace window, and detachTimeout teardown * fix(agent-core-v2): restore fs.grep streaming early-kill and symlink reporting - stream `rg --json` in fs.grep and SIGKILL once max_total_matches/max_files is reached, restoring v1 early-stop instead of buffering the whole output - report symlinks as kind 'symlink' in fs search/list/stat via lstat and never descend into symlinked directories - remove the unused os grepSearch helper (dead, non-streaming, bypassed ISessionProcessRunner) * test(agent-core-v2): align truncated compaction retry * fix(agent-core-v2): align MCP tool results with v1 * fix(agent-core-v2): align blocked compaction failures * fix(agent-core-v2): align manual compaction tool projection * fix(agent-core-v2): preserve tail in windowed compaction * fix(agent-core-v2): read todos from wire model, sanitize replay - make SessionTodoService a stateless facade over the main agent's TodoModel: getTodos reads wire.getModel(TodoModel) live, setTodos only dispatches a todo.set op, and onDidChange is bridged from wire.subscribe(TodoModel); the in-memory list copy is gone so the live and post-replay views cannot drift - sanitize todo.set payloads in apply via readTodoItems, so replayed or hand-written records cannot poison the model or downstream renders - update todo tests to a sanitizing/notifying/replaying wire stub and cover malformed todo.set replay and main-absent reads - record the main-agent-wire persistence debt for the ISessionWireService move * fix(agent-core-v2): port v1 Bash tool output cap, saved-output reference, and background gating (#1503) * fix: align v2 task observable behavior * fix: v2 full compaction * fix(agent-core-v2): align task wait timeout behavior * chore: webSearch & FetchUrl Sync #1260 * fix: align background agent guidance * fix(agent-core-v2): align full compaction with v1 * fix(agent-core-v2): align v1 wire records * fix(agent-core-v2): remember observed compaction context window * fix: align Agent / AgentSwarm * fix(agent-core-v2): port v1 parity fixes for hooks, anthropic, thinking config and add-dir (#1504) * fix(agent-core-v2): hide console window when running hooks on Windows Port the v1 hooks runner fix: extract buildHookSpawnOptions and pass windowsHide:true so hook child processes no longer flash a console window on Windows, mirroring the node-local process host defaults. Includes the same regression tests as v1. * fix(agent-core-v2): port anthropic max_tokens ceiling and override fixes Port two v1 kosong fixes to the v2 anthropic provider: - Fall back to the nearest lower catalogued minor when resolving the Claude output ceiling, and catalogue Opus 4.8's documented 128k cap, so an uncatalogued minor no longer drops to the family baseline. - Treat an explicit defaultMaxTokens as the final max_tokens value instead of clamping it to the built-in ceiling. Mirrors the v1 regression tests in a new anthropic-max-tokens test file. * refactor(agent-core-v2): converge thinking config to enabled/effort Port the v1 thinking-config overhaul (#1132's config side) to v2: - ThinkingConfigSchema becomes { enabled, effort, keep }; the mode enum, the separate defaultThinking section, and the KIMI_MODEL_THINKING_MODE / KIMI_MODEL_DEFAULT_THINKING env bindings are removed. - The effort resolver drops the mode/defaultThinking branches and no longer normalizes a requested 'on' to a concrete effort in core; 'on' is taken verbatim and normalization stays at the UI boundary. - OAuth login/refresh and catalog refresh now persist the thinking.enabled value computed by the shared oauth apply/restore logic instead of dropping it and writing the removed default_thinking key, so [thinking] enabled = false actually disables thinking and the login default survives on disk. Mirrors the v1 resolver regression tests and adds a persistence regression for the refresh path. * docs(agent-core-v2): fix stale loop-event comments after wire parity The v1.4 wire-parity alignment switched the v2 live loop to stream turns as context.append_loop_event records, but three comments still described the old world (restore-only Op, "v2 never emits loop events"). Update them to match the actual write path: non-loop appends use append_message, the loop persists loop events byte-compatible with v1, and the fold runs both at live dispatch time and on replay. * fix(agent-core-v2): load workspace additional dirs on session create and resume The /add-dir command persisted remembered dirs to .kimi-code/local.toml, but session materialization never read them back and offered no caller additionalDirs entry point — a remembered dir silently stopped applying to new, resumed, and forked sessions. Mirror v1's createSession/resumeSession: merge the project-local local.toml dirs with caller-supplied additionalDirs (relative paths resolve against workDir) and seed the session workspace context in materializeSession, so create/resume/fork all pick them up. A broken local.toml fails the create loudly with CONFIG_INVALID, same as v1. Tests mirror v1's runtime coverage for the load/merge/dedupe/resume/fork scenarios. * fix(kimi-code): forward create-session additional dirs from the v2 harness The in-process v2 print-mode harness dropped the SDK CreateSessionOptions additionalDirs when calling ISessionLifecycleService.create, so --add-dir never reached the v2 resolver. Pass it through. * fix(agent-core-v2): align full compaction observability * fix(agent-core-v2): remove compact hook trigger state * feat(agent-core-v2): enhance agent lifecycle with context size tracking and concurrency checks * fix(agent-core-v2): align media reads with v1 note channel and EXIF handling (#1505) * fix(agent-core-v2): align media reads with v1 note channel and EXIF handling Port two agent-core changes into agent-core-v2: - Move the ReadMediaFile media summary from an inline <system> text part onto the tool result's note side channel, so raw <system> markup never renders in UIs (matching the MCP output path). - Report image dimensions in the decoded EXIF-rotated space: the header sniff now reads the JPEG Orientation tag, and once a decode happened (compression or crop) its dimensions overwrite the sniffed ones, so portrait photos no longer get axis-swapped coordinate guidance. - Raise the longest-edge downscale cap from 2000px to 3000px, step the over-budget fallback through 2000px before the 1000px last resort, and run the full JPEG quality ladder at fallback sizes. - Report image_compress / image_crop telemetry for media reads (source read_media), with EXIF transposition and crop failure classification. The tool description also regains the downsampling recovery guidance (region / full_resolution readback) that the v2 copy predated. * fix(agent-core-v2): align v1 wire records * fix(agent-core-v2): hide compression captions and register media tools in production Port the remaining v1 media gaps into agent-core-v2: - Reroute inline image-compression captions out of user messages: the prompt service splits them at the append chokepoint (prompt and steer flush) and delivers them through the built-in system-reminder injection (origin {kind: 'injection', variant: 'image_compression'}), which every UI hides. Session titles/lastPrompt strip the caption the same way. The model still receives the full note. - Register ReadMediaFile in production: media tools cannot use the module-level contribution table (capabilities are unknown until a model binds), so a new Eager agent-scope registrar re-runs registerMediaTools on every agent.status.updated where the model alias or its media capabilities changed, rebinding the video uploader and dropping the tool when the model loses media input. * fix(agent-core-v2): port v1 parity fixes for hooks, anthropic, thinking config and add-dir (#1504) * fix(agent-core-v2): hide console window when running hooks on Windows Port the v1 hooks runner fix: extract buildHookSpawnOptions and pass windowsHide:true so hook child processes no longer flash a console window on Windows, mirroring the node-local process host defaults. Includes the same regression tests as v1. * fix(agent-core-v2): port anthropic max_tokens ceiling and override fixes Port two v1 kosong fixes to the v2 anthropic provider: - Fall back to the nearest lower catalogued minor when resolving the Claude output ceiling, and catalogue Opus 4.8's documented 128k cap, so an uncatalogued minor no longer drops to the family baseline. - Treat an explicit defaultMaxTokens as the final max_tokens value instead of clamping it to the built-in ceiling. Mirrors the v1 regression tests in a new anthropic-max-tokens test file. * refactor(agent-core-v2): converge thinking config to enabled/effort Port the v1 thinking-config overhaul (#1132's config side) to v2: - ThinkingConfigSchema becomes { enabled, effort, keep }; the mode enum, the separate defaultThinking section, and the KIMI_MODEL_THINKING_MODE / KIMI_MODEL_DEFAULT_THINKING env bindings are removed. - The effort resolver drops the mode/defaultThinking branches and no longer normalizes a requested 'on' to a concrete effort in core; 'on' is taken verbatim and normalization stays at the UI boundary. - OAuth login/refresh and catalog refresh now persist the thinking.enabled value computed by the shared oauth apply/restore logic instead of dropping it and writing the removed default_thinking key, so [thinking] enabled = false actually disables thinking and the login default survives on disk. Mirrors the v1 resolver regression tests and adds a persistence regression for the refresh path. * docs(agent-core-v2): fix stale loop-event comments after wire parity The v1.4 wire-parity alignment switched the v2 live loop to stream turns as context.append_loop_event records, but three comments still described the old world (restore-only Op, "v2 never emits loop events"). Update them to match the actual write path: non-loop appends use append_message, the loop persists loop events byte-compatible with v1, and the fold runs both at live dispatch time and on replay. * fix(agent-core-v2): load workspace additional dirs on session create and resume The /add-dir command persisted remembered dirs to .kimi-code/local.toml, but session materialization never read them back and offered no caller additionalDirs entry point — a remembered dir silently stopped applying to new, resumed, and forked sessions. Mirror v1's createSession/resumeSession: merge the project-local local.toml dirs with caller-supplied additionalDirs (relative paths resolve against workDir) and seed the session workspace context in materializeSession, so create/resume/fork all pick them up. A broken local.toml fails the create loudly with CONFIG_INVALID, same as v1. Tests mirror v1's runtime coverage for the load/merge/dedupe/resume/fork scenarios. * fix(kimi-code): forward create-session additional dirs from the v2 harness The in-process v2 print-mode harness dropped the SDK CreateSessionOptions additionalDirs when calling ISessionLifecycleService.create, so --add-dir never reached the v2 resolver. Pass it through. * fix(agent-core-v2): report video_upload telemetry for media reads Port the v1 video-upload telemetry wrapper into createVideoUploader: every upload emits a video_upload event with outcome (success/error), byte size, mime type, duration, and the caller's static props (model alias, protocol tags), and a throwing telemetry client never affects the upload outcome. The media-tools registrar supplies the sink and props from the bound model. Also restores two v1 rationale comments in ReadMediaFile (original-size reporting and the full_resolution hard refusal) that were dropped during the earlier port. --------- Co-authored-by: 7Sageer <7sageer@djwcb.cn> Co-authored-by: liruifengv <liruifeng1024@gmail.com> * refactor(agent-core-v2): remove the microCompaction domain - delete the microCompaction domain (service, wire model/op, config section, experimental flag) and its dedicated tests - stop truncating old tool results in the context projector and drop the projector's now-unused instantiation dependency - remove the domain from the layer map, package exports, and the DI x Scope dependency diagram - retarget the flag-registry test and skill examples at a neutral flag * fix(contextProjector): surface projection repairs via log warning - add ProjectionAnomaly + onAnomaly sink through the project / projectStrict passes (reorder, synthesize, orphan / duplicate drop, leading drop, merge, blank-text drop) so the pure projection reports every wire-repair it applies - AgentContextProjectorService injects ILogService and emits a single signature-deduped 'repaired the request to keep it wire-valid' warning, excluding trailing-tail synthesis, matching agent-core parity - cover the trace and its dedup in the projector tests * fix(agent-core-v2): fix cron killswitch, lost deliveries, id clashes - killswitch: read KIMI_DISABLE_CRON live by re-applying the ConfigService env overlay on every get(); CronCreate reads it via ISessionCronService instead of a value frozen at tool registration - delivery: resolve fire delivery on promptService.steer().launched so a rejected launch retains one-shot tasks for retry instead of deleting them; tick() is now async and awaits delivery before advancing cursors - ids: switch cron task ids to ULIDs (from 32-bit hex) so two sessions sharing a workspace cannot overwrite each other's persisted task; CronDelete and persistence accept both ULID and legacy 8-hex ids - display: CronCreate reports nextFireAt through the service so it honors KIMI_CRON_NO_JITTER and matches the scheduler and CronList - migration: adopt shape-valid tasks with no sessionId tag on loadFromStore and stamp the tag back to disk - persistence: create cron directories 0700 and files 0600 via FileStorageService dirMode/fileMode Gate SessionCronService startup on config.ready and resolve clocks after ready so config is never read before it is loaded; start() is now async. * feat(fs-watch): add workspace fs watch with v1-compatible WS delivery - os layer: add IHostFsWatchService over chokidar (raw create/modify/delete, .git ignored) - session layer: add ISessionFsWatchService, a workspace-confined, debounced, .gitignore-aware FsChangeEvent feed - kap-server: add FsWatchBridge pushing event.fs.changed over /api/v1/ws (watch_fs_add/remove, volatile, per-connection filter), byte-compatible with v1 - tests: os/session unit tests and kap-server fs-watch e2e * refactor(agent-core-v2): run external hooks through IHostProcessService - inject IHostProcessService into ExternalHooksRunnerService and thread it through runMatchedHooks to runHook instead of spawning node:child_process - route hook termination through the service's cross-platform process-tree kill - settle on the exit code plus drained stdout/stderr so fast-exiting hooks keep their trailing output - hide the child console window on Windows via the service default - update externalHooks tests for the new dependency * fix(agent-core-v2): strict-decode Edit reads, align with v1 - read the Edit target with errors:'strict' so a non-UTF-8 file fails the edit instead of being silently rewritten as U+FFFD (matches v1 kaos) - declare readWriteFile access since Edit reads before it writes, matching v1 - render edit.md directly instead of through renderPrompt: it has no template vars, and raw avoids treating literal {{ }} as a template - restore the replace_all usage example in edit.md (v1 #1102) - add a regression test asserting a non-UTF-8 file fails the edit and keeps its bytes untouched * fix(agent-core-v2): dedupe AgentMeta legacy field declarations * refactor(agent-core-v2): persist wire records natively in the v1 vocabulary Remove the persist-time v1 rewrite layer (serializeV1WireRecord): ops now write v1-shaped records directly, live-only state is declared persist:false on the op instead of being stripped at write time, and the swarm-exit reminder pop replays from the swarm_mode.exit record via a cross-model reducer. Fixes resumed sessions losing the todo list, drifting turn counters after retries, and removed reminders reappearing on resume. * refactor(agent-core-v2): move ReadTool status block to note side channel - ReadTool.finishReadResult now returns rendered lines as `output` only and rides the `<system>` status block on the model-only `note` side channel - drop the finishOutput helper that concatenated content and status - update read.test.ts expectations to assert `note` separately from `output` * refactor(agent-core-v2): split blob service helpers, rewrite tests - extract rewriteMediaUrls and blobref parse/format helpers to dedupe URL rewriting - move the byte-bounded LRU cache into a module-private ByteLruCache with focused unit tests, dropping the protected maxCacheSize test seam - rewrite blob service tests against the contract on in-memory storage, removing cache-internals cases * fix: make release-e2e scenarios pass under agent-core-v2 Three independent fixes for release-e2e failures that only appeared with the experimental v2 engine (KIMI_CODE_EXPERIMENTAL_FLAG): - agent-core-v2: register the KIMI_MODEL env overlay statically so it takes effect even when ModelService is not instantiated (the DI layer does not auto-instantiate Eager services). Fixes wire-llm-request-trace. - cli: omit the leading system.version meta line in stream-json prompt mode so the role sequence stays clean. Fixes stream-json-cron. - agent-core-v2: honor --skills-dir via a new explicit skill source seeded from the host. Fixes interactive-skills-dir. Cherry-picked from 2a7232737 (v2-migration), excluding the node-sdk V2Host change (not applicable on this branch). * refactor(agent-core-v2): drop replay-only wire ops Remove the three replay-only Ops that were kept for pre-alignment / 1.5 sessions, now that v2 persists natively in the v1 vocabulary: - turn.launch (replaced by turn.prompt) - todo.set (replaced by tools.update_store with key 'todo') - context.splice (replaced by context.append_message / append_loop_event) Also drop the dead code that handled them (transcript reducer, task-origin extraction, blob dehydration, harness helpers) and migrate the affected tests to the v1 record types. The live write path already emitted only v1 records, so wire.jsonl output is unchanged. * Revert "fix: make release-e2e scenarios pass under agent-core-v2" This reverts commitec9dae72ab. * fix(agent-core-v2): use a fresh TextDecoder per append-log read The module-level TextDecoder is stateful in stream mode: it buffers a trailing incomplete multi-byte sequence until the next decode. Sharing it across reads let leftover state from an earlier read that returned early (e.g. ensureWireMetadata bailing on the leading metadata record) leak into the next read and prepend a U+FFFD to its first line, corrupting the metadata envelope and breaking session fork with "corrupted line 1". Give each read its own TextDecoder so decoder state never leaks between reads. * fix(agent-core-v2): register KIMI_MODEL env overlay statically The KIMI_MODEL_* effective overlay was registered by ModelService on construction, but the DI layer does not auto-instantiate Eager services, so the overlay never took effect when nothing resolved IModelService. This broke the release-e2e wire-llm-request-trace scenario, where KIMI_MODEL_NAME must synthesize the env model and its thinking capability. Move registration to module load via a new configOverlayContributions collector, drained by ConfigRegistry on construction — mirroring the existing configSectionContributions pattern. ModelService no longer depends on IConfigRegistry. * docs(agent-core-v2): clarify live-only op semantics * fix(agent-core-v2): preserve oversized tool results * chore: remove full compaction complete data type * fix(agent-core-v2): align foreground output cap * fix(agent-core-v2): gate skill prompt injection * chore(skills): bundle review and test lenses into kc-review - add agent-core-review umbrella skill with slop and test sub-skills - move write-tests rules into agent-core-review/test and drop the standalone skill * feat(v2): auto-mint session ids and harden print-mode background drain - make CreateSessionOptions.sessionId optional; SessionLifecycleService.create and fork now mint `session_<lowercase-uuid>` via a shared createSessionId helper, so edge layers stop minting their own ids (drop randomUUID in the v2 harness, ulid in kap-server) - rework V2Session.waitForBackgroundTasksOnPrint to re-enumerate each round, suppress terminal notifications while waiting, and bound the drain by [task].print_wait_ceiling_s (default 1h) instead of a hardcoded 30s cap, so kimi -p can run long tasks to completion without being steered into a new turn - add v2-session unit tests; seed session/agent/bootstrap context in the tool-dedupe harness for the real executor * fix(agent-core-v2): refresh system prompt after compaction * docs(agent-core-review): limit kc-review skill to agent-core-v2 Clarify that the kc-review lenses apply only to packages/agent-core-v2 (the DI x Scope engine), not to the legacy packages/agent-core or other packages. * fix(agent-core-v2): align model-facing prompts * fix(agent-core): report EXIF-rotated image dimensions and raise edge cap to 3000px (#1460) * fix(agent-core): report EXIF-rotated image dimensions and raise edge cap to 3000px Image compression now reports original dimensions in the decoded (EXIF-rotated) space, matching the coordinate system of the sent image and of ReadMediaFile region readback; previously portrait JPEGs (orientation 5-8) got swapped width/height in captions. The longest-edge downscale cap rises from 2000px to 3000px, and the default jimp resize path is documented as the anti-aliased area-average one so it is not accidentally switched to a point-sampled interpolation mode. * test: shrink oversized image fixtures to fit CI timeouts The 3600x3600 fixtures introduced for the 3000px edge cap nearly doubled the pixel area jimp has to decode and deflate, pushing the slowest compression tests past the 5s vitest timeout on CI runners. 3600x1800 keeps every fixture over the cap while restoring roughly the workload of the old 2600x2600 fixtures that CI handled comfortably. * test: pin anti-aliased downscale quality with executable guards A 1px checkerboard probe pins the compressor to full-coverage averaging at integer and fractional ratios, with jimp's point-sampled BILINEAR mode kept as the executable aliasing counter-example (it collapses the 50%-gray pattern to solid black at 4:1). Also guards the other classic downscale bugs: transparent-pixel color bleed, mean-brightness drift, iterative recompression degradation, and zero-size collapse on extreme aspect ratios. * fix(agent-core): report decoded EXIF-rotated dimensions in ReadMediaFile notes The media note derived its original-dimensions line from the header sniff, which reports pre-rotation values for EXIF orientation 5-8 JPEGs. The sent image and region readback both live in the decoded (rotated) space, so portrait photos got axis-swapped coordinate guidance. Once a decode has happened — compression or crop — its dimensions now overwrite the sniffed ones. * fix(agent-core): improve handling of EXIF orientation in image dimensions and metadata * fix(agent-core): sniff EXIF orientation and step budget fallback through 2000px Two follow-ups to the EXIF and 3000px-cap changes: sniffImageDimensions now reads the JPEG EXIF Orientation tag (pure header parse, both byte orders) and reports display-space dimensions for orientations 5-8. Passthrough images — never decoded — previously kept the pre-rotation header size in compression results and media read notes, disagreeing with the decoded space that region readback uses. encodeWithinBudget steps the over-budget fallback through 2000px before the 1000px last resort. Raising the cap to 3000px had left a regression window: an image whose 2000px encode fits the byte budget was sent at 1000px where the old 2000px cap used to send it at 2000px. * fix(kimi-code): record pasted image dimensions in display space The TUI paste path recorded attachment and original dimensions from its raw header parser, which ignores EXIF orientation. For a portrait JPEG the submit-time caption then contradicted the sent image's aspect and region readback coordinates were axis-swapped. Dimensions now come from the compression result, which reports display space on both the compressed and passthrough paths; parseImageMeta remains only the format/mime gate. * feat(agent-core): add image compression and crop telemetry Every image ingestion path now reports an image_compress event — outcome (compressed / passthrough fast, guard, unsupported, unhelpful, error), input/output formats, byte and pixel sizes, EXIF transposition, and duration — and region readback reports an image_crop event with a failure classification and the region's share of the original area. Wiring is per call site via a new CompressImageOptions.telemetry option, so the outcome split and timing are measured inside the compressor while each caller only names its source: ReadMediaFile (tool construction, like GrepTool), MCP tool results (McpOutputOptions), server prompt ingestion (ICoreProcessService now exposes the host telemetry client), ACP prompts (session track adapter), and TUI paste (host.track adapter). Properties are numeric/enum only — never paths or content — and a throwing client can never affect the compression result. * fix(agent-core): run the full JPEG quality ladder at fallback sizes The fallback rescales encoded only at quality 20, so a JPEG whose ladder failed at the fitted size collapsed straight to the lowest quality even when the smaller size left budget headroom for a higher rung (the realistic window is the 1000px step, where the 4x pixel drop pays for q80/q60). Each fallback edge now walks the same q80-to-q20 ladder as the fitted size. * test: shrink heavy JPEG fixtures and add explicit timeouts The fallback-ladder test runs ~11 pure-JS JPEG encodes and the EXIF paste test decodes, rotates, and re-encodes a 6.5MP frame; both sat at the edge of the 5s vitest timeout on CI runners. Narrower fixtures cut the pixel area (the ladder test keeps its width above 2000px so the full fallback chain still runs) and explicit 15s timeouts absorb runner variance. * fix(server): scope prompt image compression telemetry to the session The prompt-ingestion image_compress events were emitted with the bare host telemetry client, while every agent-side source inherits a session-scoped client — so prompt_inline/prompt_file events could not be correlated with their session. The route now wraps the client with withTelemetryContext({ sessionId }) like rpc/core-impl does for session telemetry. * chore(changeset): consolidate image compression changesets One entry covering the cap raise and the EXIF dimension fix, listed for both the CLI and the SDK so the SDK changelog's compression description (previously pinned at 2000px) stays accurate. * fix: count goal creation turn (#1477) * feat(kosong): support structured response formats (#1397) * fix: clarify goal blocked audit guidance (#1481) * feat(agent-core): discard loaded tool schemas on compaction (#1471) Align progressive tool disclosure with the discard-on-compaction model: compaction no longer rebuilds loaded dynamic tool schemas. The boundary announcement re-lists every loadable name, the model re-selects what it still needs, and a from-memory call to a no-longer-loaded tool is rejected by preflight with select guidance. This removes the keep-all rebuild and its half-trigger budget heuristics entirely: the post-compaction floor is back to users + summary, which is structurally outside the auto-compaction trigger band, and the guard baseline degenerates to summary + reinjected reminders. Every downstream mechanism already treated the empty loaded set as its consistent base state (ledger scan, pending clear at the compaction boundary, deferred extras, preflight wording), so this is a strict simplification. Co-authored-by: fengchenchen <fengchenchen@moonshot.ai> * fix(kimi-code): exit 1 when a headless (-p) turn fails (#1483) Headless (`kimi -p`) failures could exit with code 0 when the event loop drained during the shutdown cleanup (e.g. telemetry's unref'd retry backoff when the network is blocked), because the rejection never reached the process.exit(1) call. Set the failure exit code before any await in both the run-prompt catch and the main catch, and keep the cleanup timeout ref'd so the loop stays alive long enough for the rejection to propagate. * feat(plugins): add Vercel plugin to marketplace (#1489) * feat(web): support Enter key to confirm archive and other dialogs (#1490) * feat(web): redesign cron reminder as a message bubble (#1480) * feat(web): redesign cron reminder as a message bubble Restyle the cron trigger notice as a right-aligned user-style message bubble that shows the scheduled prompt in full (wrapping across lines), with a small meta row beneath it for the schedule, status, job id and run time. Extract a shared MessageTime component used by both user messages and the cron reminder so the timestamp format and click-to-expand behavior stay consistent, and give the CronCreate/CronList/CronDelete tools distinct calendar icons. * refactor(web): render cron reminders only as standalone turns Remove the embedded cron block path from the web transcript projector so cron reminder fires always render through the standalone right-aligned bubble path. * chore(web): simplify cron redesign changeset * fix(web): composer model switch also updates global default model (#1491) * fix(web): composer model switch also updates global default model The composer model switcher still switches the active session's model via POST /sessions/{id}/profile (awaited, so the model pill reflects the result), and additionally fires POST /api/v1/config with { default_model } as a fire-and-forget side effect so new sessions inherit the chosen default. The config request is skipped when the model already matches the current default. * fix(web): route ModelPicker overlay selection through the default-model update The overlay opened from the composer's "More models" row (and /model) is a continuation of the same switch flow, so its selection now also bumps the global default model instead of only switching the active session. * fix(web): only persist the default model after a confirmed session switch setModel now returns whether the switch was accepted (true for the draft path), so the composer flow no longer writes a stale or invalid model alias into the global config when the session-level switch failed and rolled back. * feat(web): prefix skill slash commands with skill: to distinguish them from built-in commands (#1492) * ci: release packages (#1468) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix: surface provider auth error for unavailable models (#1506) * fix: surface provider auth error for unavailable models When an OAuth-managed model returns 401 after a forced token refresh, the token is valid but the provider rejected it for that model (the account lacks access). Emit provider.auth_error carrying the provider's message instead of auth.login_required with a misleading "OAuth login expired. Send /login" prompt. * fix(agent-core): preserve provider auth errors through compaction Treat provider.auth_error like auth.login_required in the compaction path so an auth rejection during compaction surfaces the provider's message instead of being wrapped as a generic compaction failure. * ci: release packages (#1507) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * docs(changelog): sync 0.23.3 and shorten OAuth error entry (#1509) * feat(kimi-web): add status-aware browser notifications (#1479) * feat(kimi-web): add approval notification storage key and i18n copy * feat(kimi-web): add approval notification helpers and tests * feat(kimi-web): wire approval notifications and guard completion alerts * fix(kimi-web): extract shouldNotifyCompletion helper and add tests * feat(kimi-web): add approval notification settings toggle * chore(kimi-web): add changeset and tidy notification module comment - Align approval notification tag with spec (kimi-approval-${approvalId}) - Update module header to describe all three notification kinds * fix(kimi-web): make notifications fire reliably - Key completion notification tags by turn (sid + promptId) and question tags by request id, so a stale notification left in the notification center no longer swallows every follow-up alert in the same session - Suppress notifications only while the window is actually focused, not merely visible (document.hasFocus() on top of visibilityState) - Play the attention sound when a tool needs approval, matching the completion and question sounds * chore(kimi-web): simplify changeset * fix(agent-core-v2): serialize concurrent model catalog refreshes Port v1 #1207's _refreshChain so a scheduled refresh and a manual one (or two overlapping manual ones) never race on reading/patching the persisted config. Applied to both refresh entry points: ModelCatalogService.refreshProviderModels (scheduler + all/single-provider) and OAuthService.refreshOAuthProviderModels (OAuth-only, a separate service in v2). * fix(agent-core-v2): dedupe workspace registry entries by root Port v1 #1221: collapse registered workspaces that share a root in list(), preferring the entry whose id matches the current canonical encodeWorkDirKey, so a legacy workspaces.json (v1-compatible) does not render the same folder twice through GET /workspaces. * fix(agent-core-v2): apply KIMI_CODE_CUSTOM_HEADERS and host identity headers Port v1's provider-manager outbound header logic to agent-core-v2 so `KIMI_CODE_CUSTOM_HEADERS` and host identity headers are applied to outbound LLM requests, closing the migration gap from #1186: - env `KIMI_CODE_CUSTOM_HEADERS` is the lowest-precedence header layer; - host identity headers (User-Agent + X-Msh-*) are sent for Kimi providers, only the User-Agent for every other provider — a Kimi provider routed through the Anthropic protocol still gets the full set, matching v1; - provider `customHeaders` always win on conflict. Host headers are seeded by the CLI via `createKimiDefaultHeaders` and a new `IHostRequestHeaders` App-scope token (defaulting to empty), so the model resolver can layer them without the host threading them through every call site. * chore(agent-core-review): rename skill from kc-review to agent-core-review * feat(kap-server): surface originating stack trace on error envelopes - add optional `stack` field to errEnvelope and the envelope schema/interface; omitted when undefined so the wire shape stays byte-identical for callers without a stack - thread `err.stack` through route error mappers plus the global and transport error handlers - preserve `details` on `session.undo_unavailable` while adding its stack - update tests to assert stacks are surfaced, reversing the prior no-leak contract * fix: align v2 media and task compaction handling * feat(agent-core-v2): sync shell mode and skill config parity (#1514) * feat(agent-core-v2): record shell command context - add ShellCommandOrigin and compaction handoff disposition - extract IAgentShellCommandService from AgentRPCService - keep AgentRPCService as a thin shell:run facade * fix(agent-core-v2): align skill priority and sync docs - restore project > user > plugin > builtin skill precedence - sync Skill tool description and parameter docs from v1 - update write-goal and custom-theme builtin skill copy * fix(agent-core-v2): restore undo and thinking telemetry - track conversation_undo after undoHistory - emit thinking_toggle with enabled/effort/from payload - add coverage for both telemetry events * feat(agent-core-v2): add skill directory config - add extraSkillDirs and mergeAllAvailableSkills config sections - introduce extra skill source and shared source priorities - align kap-server workspace skill preview with session catalog * feat(agent-core-v2): support explicit skill dirs - add ISkillCatalogRuntimeOptions for SDK-style explicit skill dirs - suppress default user/project discovery when explicitDirs are set - resolve explicit dirs per session workDir via explicitFileSkillSource * fix(agent-core-v2): fix configured skill dir resolution - expand ~ using OS home for configured skill dirs - honor explicitDirs in kap-server workspace skill preview * fix(agent-core-v2): await config ready before skill discovery - wait for config.ready before reading extraSkillDirs - wait for config.ready before reading mergeAllAvailableSkills - cover extra skill dir loading behind config readiness * fix(agent-core-v2): keep skill config live after changes - await config.ready in kap-server workspace skill preview - reload user and workspace skill sources when mergeAllAvailableSkills changes * fix(agent-core-v2): align goal budget handling * fix(agent-core-v2): forbid model goal pauses * fix(agent-core-v2): cap detached process output * fix(kimi-code): drain v2 print subagents before exit * fix: restore kap-server video upload compatibility * fix(agent-core-v2): charge only output tokens against goal token budgets Goal parity gap G2: v1 charges only per-step output tokens against a goal's tokenBudget, while v2 summed all four usage buckets (cache read, cache creation, other input, output), exhausting budgets orders of magnitude faster under prompt caching and skewing persisted tokensUsed counters. Align goal token accounting to output-only and drop the unused tokenUsageTotal helper. * fix: align server-v2 media file handling * feat(agent-core-v2): allow coder profile to use MCP tools * refactor(agent-core): introduce activity kernel and migrate turn lane - add `activity` domain: `IAgentActivityService` (Agent turn lane machine), `ISessionActivityKernel` (Session admission, PR1 placeholder), and the `ActivityLease` that owns the turn `AbortSignal` - turnService launches and cancels through the kernel lease; `Turn` now exposes `signal` instead of `abortController` - agentLifecycle.remove drives `beginDisposal`/`settled` and waits for the in-flight turn to drain before releasing the agent scope - add `activity.*` error codes; deprecate `turn.agent_busy` in favor of `activity.agent_busy` * refactor(sessionLegacy): remove fork/compact/abort/archive pass-throughs These four legacy session actions were thin delegations to the native v2 services (ISessionLifecycleService.fork/archive, IAgentFullCompactionService.begin, IAgentRPCService.cancel) with no v1-only projection to centralize. Drop them from ISessionLegacyService and call the native services directly from the kap-server sessions route. updateProfile, createChild, listChildren, undo and status stay in the adapter since they carry real v1 adaptation logic. * refactor(cli): run print-mode v2 on native agent-core-v2 services - add native v2 print runner (v2/run-v2-print.ts) that consumes agent-core-v2 DI services and awaits Turn.result directly - extract shared print-mode rendering into prompt-render.ts for v1 and v2 - remove the V2PromptHarness/V2Session shim and v2->v1 event translation - decouple initializeCliTelemetry from PromptHarness (homeDir/auth/track) - add IAgentPromptLegacyService.submitAndSettle for authoritative completion * refactor(session): serve v1 undo and children via native v2 services - make IAgentPromptService.undo throw session.undo_unavailable with a structured reason; move the precheck into contextMemory - add ISessionLifecycleService.createChild (fork + child markers) and ISessionIndex.list({ childOf }) - slim ISessionLegacyService to updateProfile/status (drop createChild, listChildren, undo) - rewire kap-server session routes to the native services and map SESSION_UNDO_UNAVAILABLE * refactor(cli): drop v1 sdk and telemetry deps from v2 print - run-v2-print: use core ITelemetryService + CloudAppender instead of kimi-telemetry; remove kimi-code-sdk import (auth via IOAuthToolkit, config path from bootstrap, hook result via structural type) - prompt-render: replace SDK HookResultEvent with a structural type so the shared renderer does not depend on the v1 SDK event shape - telemetry: revert initializeCliTelemetry to its original signature now that v2 no longer calls it; keep v1 callers and assertions untouched - update run-prompt and v2-run-print tests for the new wiring * feat(activity): add session lane machine and agent snapshot projector - implement SessionActivityKernel lane machine (restoring→active⇄quiescing→closing→disposed) with admission table, atomic quiesce+drain, beginClosing/settled, markActive - start AgentActivityService lane at initializing; add markReady driven by agentLifecycle.create after bootstrap - project LaneModel + EventBus facts into structured AgentActivitySnapshot (ActivityModel / setActivitySnapshot Op) with pending-approval and active-tool-call sets; emit agent.activity.updated - add IAgentTurnService.launchWithLease; goal continuation acquires the lane before appending its prompt - resolve pending interactions on turn.ended to avoid stranded awaiting_approval - fullCompaction registers a background activity and checks the activity lane - extract contextMemory publishSplice / isFullyUndoable / recoverFoldedLength helpers - kap-server: map activity snapshot into legacy status and sessionEventBroadcaster * fix(agent-core-v2): truncate over-long goal completion criteria Goal parity gap G11: v1 silently truncates a goal's completionCriterion to 4000 characters (the objective cap) before persisting, so an over-long criterion never fails creation and cannot bloat every goal reminder and record. v2 only trimmed whitespace and persisted arbitrary lengths verbatim. Cap the normalized criterion at MAX_GOAL_COMPLETION_CRITERION_LENGTH to match v1. * fix(agent-core-v2): add goal error catalog info metadata Align the GoalErrors domain with V1 by attaching the info block for the seven goal.* error codes (title, retryable, public, action hints) so errorInfo() surfaces them. Entries copied verbatim from the V1 error catalog. Gap: G43 * chore(nix): update pnpm deps hash * fix(agent-core-v2): retain queued steers when a turn ends cancelled or failed Align the prompt layer with V1's steer-buffer semantics: buffered steer input now survives a turn that ends cancelled or failed and is flushed into the next launched turn by the existing beforeStep hook, instead of being silently dropped. The turn-result observation in the prompt service existed only to perform that discard, so it is removed along with the now-trivial launch wrapper; explicit clear() still discards the queue. Gap: G24 * fix(agent-core-v2): remove ask-user background mode * fix(kap-server): align archived session restore * chore(lint): fix type-aware lint errors * chore(agent-core-v2): drop stray doResume debug log * fix(agent-core-v2): defer prompts and steers while a full compaction is in flight Align with V1's compaction gating: input arriving while a full compaction holds the context (and no turn is active) used to launch a turn immediately, appending assistant output that forced the in-flight compaction to cancel. The prompt service now buffers such input and replays it from a new onDidFinishCompaction hook that the compaction worker runs in a finally, so the buffer drains on completion, cancellation, and failure alike — the first deferred item launches a turn and the rest join the steer queue. The compaction service is resolved lazily instead of constructor- injected: materializing it during prompt-service construction reorders loop-hook registration and moves the full-compaction beforeStep hook ahead of the hooks that let a freshly launched prompt land in context before the auto-compaction check snapshots history. Gap: G23 * fix(agent-core-v2): re-inject the goal reminder after full compaction Align with V1: after a compaction rewrites the context, re-arm the per-turn context injectors and run them before the compaction is marked complete, so the first post-compaction request — including a replayed deferred prompt's — already carries the goal reminder the summary folded away. The injector service exposes injectAfterCompaction, which re-arms the new-turn flag and injects immediately; the compaction worker calls it after the system-prompt refresh and raises the post-compaction token floor to include the re-injected reminders (the pre-injection floor stays as the fallback when reinjection throws), so the nothing-new-since-compaction guard does not re-trigger against a shape that cannot shrink. The injector is resolved lazily from the compaction service to keep loop-hook registration order untouched across the dependency cascade. Matches V1 verbatim including the existing quirk where an idle manual compact yields a second reminder copy on the next turn's per-turn injection; the parity test pins that behavior. Gap: G14 * test(agent-core-v2): cover goal pause classification for provider errors Port the missing end-to-end coverage: goal-driven turn failures pause the goal with the exact per-class reason strings — provider rate limit, provider connection error, provider authentication error, provider safety policy block, and model configuration error (including the forced 'LLM not set' substitution). Failures are driven through a real turn with a throwing generate stub so the raw-error classification feeding the pause reason is exercised, not just the mapper. No source changes: the existing classification already matches the reference strings verbatim. Gap: G35 * feat: add progressive tool disclosure * feat(cli): gate print-mode v2 behind KIMI_MODEL_EXPERIMENT_FLAG - add KIMI_PRINT_V2_ENV / isPrintV2Enabled so `kimi -p` routes to the native agent-core-v2 runner through its own switch - keep `kimi server run` server-v2 routing on isKimiV2Enabled (KIMI_CODE_EXPERIMENTAL_FLAG), decoupling the two - update print-mode tests and comments to reference the new switch * feat(cli): add KIMI_MODEL_OUTPUT_FORMAT for print-mode default - resolve the effective `-p` format via resolveOutputFormat: the --output-format flag wins, then KIMI_MODEL_OUTPUT_FORMAT (prompt mode only), then text - ignore the env outside prompt mode and reject invalid values eagerly through the friendly validation path - apply the resolver on both the v1 and v2 print runners * fix(agent-core-v2): count the goal-creating turn as the first goal turn Goal parity gap G5: when the model creates or resumes a goal mid-turn, v1 counts that ordinary turn as goal turn 1 at turn end (with a budget re-check before the continuation driver takes over) and charges its remaining step output tokens against the token budget. v2 only flagged turns whose goal was already active at launch, leaving turnsUsed and tokensUsed off by one turn in the model-initiated flow. Adopt the live turn as a goal starter turn on activation: charge its post-creation step output, count it once at turn end via incrementTurn, and block instead of launching a continuation when that count exhausts the turn budget. * fix(agent-core-v2): remove model-initiated paused status from UpdateGoal Goal parity gap G6: v1 reserves pausing for the user and runtime — its UpdateGoal tool only accepts active/complete/blocked and rejects other statuses with an invalid-status error. v2 still carried a leftover 'paused' enum option, a model pauseGoal branch, and matching tool description wording from before v1 removed them. Drop the paused option, port v1's runtime invalid-status guard, and align the tool description with v1's. * fix(agent-core-v2): deliver goal outcome prompts through the UpdateGoal tool result Goal parity gap G7: when the model completes or blocks a goal, v1 returns the outcome prompt (stats plus final-message instructions) as the UpdateGoal tool result with stopTurn, keys the one-shot final- message continuation on that terminal tool result, and guards it with the per-turn step budget so a capped turn ends 'completed' instead of dying on max steps. v2 still used a pre-change leftover channel: terse tool outputs plus goal_completion_summary / goal_blocked_reason system reminders and a last-message-reminder continuation with no step-budget check. Return the outcome prompts as tool output, drop the reminder appends and their detection, key the continuation on the terminal UpdateGoal result observed via the tool executor hook, and mirror v1's hasStepBudgetRemaining guard. Also closes audit gaps G17 (max-steps death) and G27 (actor-conditional reminders). * fix(agent-core-v2): fail UpdateGoal as a tool error when no goal matches Goal parity gap G8 (with user modification): v1 returns friendly success-flagged no-op outputs when UpdateGoal targets a missing or non-active goal, while v2 either let GOAL_NOT_FOUND escape from resumeGoal or reported false success with stopTurn for complete and blocked on a non-active goal. Per the user's decision these cases now return error-flagged tool results in the same shape as the Edit tool's old-string-not-found failure - v1's message texts ('Goal not resumed: no current goal.', 'Goal not completed: no active goal.', 'Goal not blocked: no active goal.') with isError and no stopTurn, so the model sees a non-fatal failure and the turn continues normally. * fix(agent-core-v2): settle active goals when the continuation relaunch fails Goal parity gap P-B: the turn-ended subscriber that relaunches goal continuation turns discarded every rejection, so a failed launch (for example losing a race to a queued prompt) stranded the goal in status active with nothing driving it. Keep the event-driven per-turn continuation model but settle deterministically on failure: any rejection out of the turn-ended handling now pauses the active goal as actor system with reason 'Paused after goal continuation failure: <message>', emitting the normal goal.updated event; the settle itself never throws into the event bus. The busy-skip needs no settle: the turn service clears its active turn before publishing turn.ended, so the other live turn's own end reliably re-runs the relaunch check. * fix(agent-core-v2): restore the fork-cleared goal system reminder Goal parity gap G12: after a session fork, v1 tells the model the fork has no current goal so it ignores stale active-goal reminders copied from the source session; v2 cleared the goal silently through the forked wire op and dropped the reminder. Track the fork boundary in a derived (never persisted) wire model folded on both dispatch and replay: a forked record that clears a copied goal marks the reminder pending, and the post-replay pass appends v1's verbatim reminder text with origin goal_fork_cleared exactly once - the appended reminder record acknowledges the pending flag on later replays, so resumes never duplicate it. Forks of sessions without a goal append nothing. The forkGoal op itself stays pure. * fix(agent-core-v2): preserve turn result details (#1531) * feat(agent-core-v2): align defaultProvider fallback and clear-on-delete - ModelResolverService falls back to the top-level defaultProvider config when a model pins neither providerId/provider nor an inline baseUrl (v1 parity). - ProviderService.delete clears defaultProvider when removing the provider it points at, replacing v1's scattered call-site cleanups. - defaultProvider rides as an unregistered top-level scalar config section, mirroring defaultModel (no schema, generic snake/camel passthrough). * feat(agent-core-v2): synthesize legacy prompt lifecycle events - emit prompt.completed/aborted/steered on the per-agent IEventBus so the v1-compatible WS edge can forward them (v2 core only emits turn.ended) - bridge per-agent turn.ended into SessionInteractionService.cancelPendingForTurn via AgentLifecycleService (bus is Agent-scoped, no direct injection) - extract agent create() helpers: assertCanCreate, buildAgentScopeExtras, igniteEagerServices, bindBootstrap - finish assistant writer on PromptTranscriptWriter.flushAssistant - ungate live session status idle->running->idle e2e test (v2 backend pulls real status) * chore(agent-core-v2): align cron and skill prompts with agent-core Drop the KIMI_CRON_NO_JITTER / KIMI_CRON_NO_STALE notes from the CronCreate and CronList descriptions and the MAX_SKILL_QUERY_DEPTH sentence from the Skill description, matching agent-core (v1) where these were trimmed from the model-facing text in #1102. Code behavior is unchanged; the env bypasses and the depth cap still exist in both implementations. ULID/8-hex wording is left as-is for now. * chore(agent-core-v2): drop legacy 8-hex mention from cron prompts CronDelete and CronList descriptions now describe the task id as a ULID only, removing the "(or legacy 8-hex)" qualifier from the model-facing text. Code and tests are unchanged. * chore(agent-core-v2): reorganize tests to mirror src layout Move every file under test/ so its path mirrors the corresponding file under src/ one-to-one (agent/, session/, app/, os/, persistence/, _base/, activity/, wire/), and rename test files to match the basename of the source file they cover. Test-only infrastructure with no src counterpart (harness, snapshot, lint, dep-graph, tools/fixtures) stays at the top level. Rewrite imports after the move: src references use the #/ alias, while test-to-test and cross-package relative imports are recomputed for the new locations. No behavior changes. * feat(config): add default permission/plan mode and yolo alias - register `defaultPermissionMode` and `defaultPlanMode` config sections - apply `defaultPermissionMode` when creating the main agent - enter plan mode on fresh sessions when `defaultPlanMode` is true - fold `yolo: true` into `default_permission_mode` on kap-server config write, derive `yolo` on read (yolo stays wire sugar, never a persisted domain) * feat(agent-core-v2): add background mode to AskUserQuestion Align with v1: the model can pass `background: true` to get a task_id immediately while the question waits in the background for the user's answer; completion is delivered to the agent automatically through the task service's terminal notification. - New QuestionBackgroundTask (AgentTask kind 'question') that runs the question request on a detached task and settles completed/failed/killed. - AskUserQuestionTool gains the optional `background` schema field, the description suffix, an IAgentTaskService dependency, and a background execution branch whose output block matches v1 verbatim. - Tests: harness injects a task-service stub, two legacy 'no background' assertions are flipped to the v1-aligned behavior, and three background cases (immediate task_id, settle completed, abort killed) are added. * fix(agent-core-v2): synthesize default baseUrl for env-model provider Align with v1: when KIMI_MODEL_NAME is set without KIMI_MODEL_BASE_URL, the reserved __kimi_env__ provider now gets a per-type default baseUrl (kimi -> api.moonshot.ai/v1, openai -> api.openai.com/v1; anthropic is left unset so the SDK picks its default). Previously v2 left baseUrl empty and later threw "missing a base URL" for the openai env-model path, regressing v1's out-of-the-box behavior. An explicit KIMI_MODEL_BASE_URL still wins. * fix(agent-core-v2): restore UpdateGoal completion/blocked prompts and no-goal fallbacks Align with v1: completing or blocking a goal now returns the dynamic summary/blocked-reason prompt (buildGoalCompletionSummaryPrompt / buildGoalBlockedReasonPrompt, already present in outcome-prompts.ts but unused) instead of the static "Goal marked complete/blocked." text, and all three statuses report the "no active/current goal" fallback when there is nothing to transition. * feat(agent-core-v2): add [image] config section for image compression - add media-owned `image` config section (`max_edge_px`, `read_byte_budget`) with `KIMI_IMAGE_MAX_EDGE_PX` / `KIMI_IMAGE_READ_BYTE_BUDGET` env bindings (env > config.toml > default) - add Agent-scope `ImageConfigBridge` that pushes the env-resolved section into the image-compress resolver seam on load and on change, so all call sites honor config without per-call wiring - resolve `maxEdge` / read-byte-budget defaults in image-compress via the new seam; keep the support module config-agnostic - apply the read-image byte budget in ReadMediaFile's default downscale path (previously fell back to the 3.75 MB provider ceiling) * fix(agent-core-v2): align image compression defaults with v1 (#1508) Port v1 #1508 into v2: lower the longest-edge downscale cap back to 2000px (v2 was stuck on the 3000px it had ported from an earlier v1 change) and make it overridable, add the 256 KB read-image byte budget used by ReadMediaFile, and widen the over-budget fallback ladder to [2000, 1000, 768, 512, 384, 256]. - MAX_IMAGE_EDGE_PX 3000 -> 2000, with KIMI_IMAGE_MAX_EDGE_PX env and a config-pushed value resolved via resolveMaxImageEdgePx. - READ_IMAGE_BYTE_BUDGET=256KB with KIMI_IMAGE_READ_BYTE_BUDGET env and resolveReadImageByteBudget; ReadMediaFile's default compress path now uses it (region / full_resolution still honor IMAGE_BYTE_BUDGET). - Test expectations that hard-coded 3000px / 1500px updated to 2000 / 1000 to match the v1 behavior. The [image] config.toml section is intentionally not added: the env vars already cover the override path, and wiring a config section plus a runtime push would add v2-specific scaffolding beyond v1. * fix(agent-core-v2): restore HEIC/HEIF conversion guidance in ReadMediaFile Align with v1: a HEIC/HEIF read is now refused up front with an os-specific conversion command (sips / heif-convert / ImageMagick) so the unsupported format never reaches the provider (which would reject the whole session once it lands in history). The two guidance builders are ported verbatim from v1, and the check sits after the image-capability guard (using IHostEnvironment.osKind). Also give the existing EXIF-rotation test a longer timeout: it does heavy jimp encode/decode and was flaking around the default 5s boundary. * feat(agent-core-v2): wire startBtw into the agent RPC API Align with v1: expose `startBtw` on AgentAPI and delegate it to the existing ISessionBtwService (already implemented and DI-registered as SessionBtwService), so the /btw slash command can fork a side-question child agent once the server runs on v2. * chore(agent-core-v2): tidy misplaced and throwaway test files - Remove resume-debug.test.ts: a one-off diagnosis script with a hardcoded local path, not a real test. - Move streamTiming.test.ts to app/model/modelImpl.test.ts: it only exercises buildStreamTiming in app/model/modelImpl.ts. - Rename wire/store.test.ts to wire/wireServiceImpl.test.ts to match the module it covers (there is no store.ts in src). * fix(agent-core-v2): restore JSON Schema format validation in tool-args Align with v1: replace the hand-rolled subset validator with v1's Ajv- based implementation (draft-07/2019/2020 + ajv-formats), so tool-call argument validation once again honors the JSON Schema `format` keyword (and the full keyword set), not just the previously hard-coded subset. - args-validator.ts is now byte-identical to v1 (93 lines, replacing the 289-line hand-rolled subset). - Adds ajv@^8.18.0 and ajv-formats@^3.0.1 (same versions as v1) plus the pnpm-lock.yaml update. - The two call sites (compileToolArgsValidator -> validateToolArgs) keep working unchanged; a small test locks in format / required / additionalProperties / subset behavior. * fix(agent-core-v2): restore JSON Schema format validation in tool-args Align with v1: replace the hand-rolled subset validator with v1's Ajv- based implementation (draft-07/2019/2020 + ajv-formats), so tool-call argument validation once again honors the JSON Schema `format` keyword (and the full keyword set), not just the previously hard-coded subset. - args-validator.ts is now byte-identical to v1 (93 lines, replacing the 289-line hand-rolled subset). - Adds ajv@^8.18.0 and ajv-formats@^3.0.1 (same versions as v1) plus the pnpm-lock.yaml update. - The two call sites (compileToolArgsValidator -> validateToolArgs) keep working unchanged; a small test locks in format / required / additionalProperties / subset behavior. * chore(agent-core-v2): drop legacy 8-hex mention from CronDelete/CronList tool source Match the prompt change in5cc8e520f: the CronDelete parameter description and invalid-id error now say "ULID" only (not "ULID or legacy 8-hex"), and the CronList id doc comment likewise. The validation regex is unchanged so loading any legacy 8-hex tasks from disk still works. * chore(agent-core-v2): remove resume-roundtrip test It round-tripped restore over a hardcoded local dataset (kimi-code-mini-bench/.vitest-results) that is not in the repo, so it vacuously passed everywhere else. Removed at the original author's request. * test(agent-core-v2): raise timeout for slow image-compress invariant test The fuzz-style invariant test now drives the full v1 fallback ladder ([2000, 1000, 768, 512, 384, 256]) for over-budget inputs, which takes longer than the default 5s boundary in this environment. Give it 30s. * chore(agent-core-v2): rename ambiguous test files - agent/task/manager.test.ts -> taskManager.test.ts (no manager.ts in agent/task; disambiguate from taskService.test.ts). - app/cron/persist.test.ts -> cronTaskPersistenceService.test.ts (mirrors the module it covers; agent/task/persist.test.ts mirrors agent/task/persist.ts, so it is left as-is). - agent/contextMemory/message.test.ts -> message-history.test.ts (its describe is 'message history (IAgentContextMemoryService)'; the dir already names the domain). * fix(agent-core-v2): preserve usage for aborted steps * fix(agent-core-v2): resume-safe session reads and in-memory transcript - sessionLifecycle: get/list no longer return a session whose cold resume is still in flight, so callers never observe a half-initialized handle; resume remains the way to await a fully restored handle - messageLegacy: reduce the transcript from the main agent's in-memory wire journal instead of re-reading wire.jsonl; AgentWireRecordService now keeps the journal current with live dispatch so cold and live sessions both read a consistent, full transcript * chore(agent-core-v2): drop stale micro-compaction references in comments micro-compaction only exists in legacy agent-core; v2 has no such mechanism. Update two comments that still cited it: - fullCompactionService: the real reason not to project here is that llmRequester already projects once. - swarmService: context.spliced consumers no longer include micro-compaction bookkeeping. * fix(agent-core-v2): report skill discovery diagnostics * test(agent-core-v2): align plan mode parity fixtures * fix: distinguish task output timeout from cancellation * chore: fix test name * fix(agent-core-v2): flush the wire persist queue before the record log Sinced5e1d76fcevery wire append rides the async persist queue whenever a blob service is registered, but AgentWireRecordService.flush() only awaited the log store. Callers - including the session-close path - could complete a flush while records were still in flight on the queue, and record-order assertions in tests raced it. Await the wire service flush, which drains the persist queue, before flushing the log. * test(agent-core-v2): align the goal reminder boundary test with the continuation driver The per-turn-boundary reminder test predates the goal continuation driver and never passed: its second explicit prompt raced the auto-launched continuation turn, which correctly holds the turn lane. Treat the continuation turn as the second boundary and end it deterministically by completing the goal through UpdateGoal, keeping the once-per-boundary (never per-step) assertion. * fix(agent-core-v2): stop goal turns gracefully when a hard budget is exhausted Previously a goal whose hard budget was reached mid-turn was only flipped to blocked while the turn kept running unbounded: the loop continues unconditionally after a tool-calls step, and steer flushes or Stop hooks could extend the turn indefinitely past the budget. Now, when the over-budget step requested tool calls, a goal_budget_stop system reminder is appended after the tool results telling the model the goal is blocked (resumable via /goal resume), to stop immediately, that further tool calls will be rejected, and to write a brief final status message. The model gets exactly one grace step, during which tool calls are answered with a soft rejection instead of executing. After the grace step - or when the over-budget step had no pending tool calls - a new AfterStepContext.stopTurn flag ends the turn, honored by the loop with precedence over tool_calls and hook-set continue so nothing can extend past the stop. The grace grant also respects maxStepsPerTurn so it can never turn a budget stop into a max-steps turn failure. Turn launch is gated as well: a prompt arriving while the active goal is already over budget (e.g. after resuming an exhausted goal) blocks the goal before the turn is marked goal-driven, so turnsUsed no longer drifts, no spurious goal_continued telemetry fires, and the prompt runs as an ordinary turn with the blocked-goal note injected. This deliberately diverges from agent-core v1, which hard-stops with zero grace and answers a prompt on an exhausted goal with a synthetic model-less turn: budget overshoot is now bounded at one closing step in exchange for consumed tool results and a user-facing wrap-up message. * fix(agent-loop): stop looping on bare tool_calls signal - remap tool_calls finishReason to 'other' when the provider emitted no tool call structure - prevents re-issuing the model call until maxSteps on a bare tool_calls signal - add loop test covering the v1 'unknown' turn-lifecycle behavior * fix(kap-server): emit legacy background.task.* alias on /api/v1 ws The v2 engine emits background-task lifecycle as `task.started` / `task.terminated`, but v1 consumers (kimi-code TUI / `kimi -p`, node-sdk) only handle `background.task.*` and silently dropped every task event when talking to server-v2, while kimi-web handles the native spelling and has the legacy one registered as known-but-unhandled. Fan the legacy spelling out next to the native event in SessionEventBroadcaster, reusing the same volatility so replay, journal and the per-agent filter stay coherent between the two. kimi-web keeps the native event and ignores the alias; the native /api/v2 stream is left unchanged. Add a SessionEventBroadcaster test asserting both spellings are emitted. * feat(agent-core-v2): port /init command from v1 - add Session-scope ISessionInitService that spawns the coder subagent, mirrors the run onto the main agent, reloads AGENTS.md and appends an init-variant system reminder, then flushes records - add SESSION_INIT_FAILED error code and register the sessionInit domain in the layer map, package index and DI dependency graph - drop the stale "flat (no subdirectories)" rule from the agent-core-dev skill * feat(api-v2): add klient SDK and reflection-based channel registry - replace per-method actionMap (resource:action) with a channel registry: each Service registers once by decorator id and all methods are invoked by reflection - routes move from /api/v2/:sa to /api/v2/:service/:method across HTTP routes and the WS protocol - add @moonshot-ai/klient: typed core/session/agent client over the HTTP channel that reuses agent-core-v2 service interfaces - register klient in flake.nix and pnpm-lock; refresh kap-server tests, e2e, and the apiSurface snapshot * refactor(agent-core-v2): drive turns by draining a StepRequest queue - add StepRequest / StepRequestQueue: the loop drains one batch per step, folding mergeable requests (steers) into the driver's step; a step that ran tools enqueues a ContinuationStepRequest, a plain message enqueues none, so the turn completes when the queue empties - replace AfterStepContext.continue with explicit enqueue; a failed step is retried by head-inserting its driver request - move prompt's private steer queue onto the loop via PromptStepRequest / SteerStepRequest / RetryStepRequest, which materialize their context messages at pop time (image-compression captions reroute to reminders on materialization) - goal and externalHooks orchestrate continuations by enqueueing requests instead of setting ctx.continue; a request's message only lands when the loop pops it, so skipped or aborted launches leave no orphan messages - remove the now-unused CancellationError - delete the reworked turn tests (turn.test.ts, turn-ready.test.ts) and the cron test suites * refactor(agent-core-v2): translate provider errors at the model boundary - add translateProviderError in app/protocol/errors and apply it once in ModelImpl.request: raw provider failures become coded KimiErrors with the raw error preserved as cause and HTTP fields in details; abort shapes pass through untouched - move provider-error mapping out of _base/errors/serialize so _base no longer imports llmProtocol; toErrorPayload/fromErrorPayload now round-trip cause chains recursively, capped at depth 8 - move context.overflow from LoopErrors to ProtocolErrors (wire code unchanged); move LoopError and the max-steps helpers into loop/loop.ts - consolidate isAbortError into _base/utils/abort, dropping the duplicates in retry, cloudTransport, and the question/subagent task tools - add unwrapErrorCause; classify retryability and HTTP status on the unwrapped cause in llmRequester and full-compaction - align task-notification tests with enqueue-only delivery: drain the loop queue with one turn and assert on task.notified instead of prompt steer - protocol: add recursive cause to KimiErrorPayload with a lazy zod schema * docs(agent-core-dev): add commit-align workflow, drop DI dependency map - add commit-align.md subskill: triage one main-branch commit against v2 (aligned / partial / missing / not-applicable) and link it from SKILL.md - delete docs/di-scope-domains.puml and the rendered svg, and drop the keep-the-map-in-sync requirement from verify.md, align.md, commit-align.md, and packages/agent-core-v2/AGENTS.md * refactor(agent-core-v2): move turn lifecycle into agent loop - make loop admission, cancellation, and completion own turn execution - extract step retry into a loop error recovery service - preserve failed-step context and expose retry delay events * refactor(agent-core-v2): centralize loop turn scheduling - add queued turn and step lifecycle handles with explicit admission modes - move continuation and retry scheduling behind the loop service - consolidate legacy prompt scheduling into the prompt domain - align kap-server routes and tests with the new loop contract * feat(klient): add WebSocket transport for calls and event streams - add WsSocket: persistent /api/v2/ws transport with hello handshake, heartbeat answers, per-call timeouts, and auto-reconnect that re-subscribes active listens; bearer token rides the kimi-code.bearer.<token> subprotocol for browser compatibility - add WsKlient / WsChannel exposing core/session/agent scopes and listen(event, handler) over the shared socket - add Klient#ws() lazy singleton with WebSocketImpl injection - bind global fetch in HttpChannel to avoid "Illegal invocation" in browsers * refactor(agent-core-v2): unify hook names to on{Will,Did}Xxx convention - loop: beforeStep -> onWillBeginStep, afterStep -> onDidFinishStep - toolExecutor: onWillExecuteTool -> onBeforeExecuteTool (ToolWillExecuteContext -> ToolBeforeExecuteContext) - prompt: onWillSubmitPrompt -> onBeforeSubmitPrompt - permissionMode: onChanged -> onDidChangeMode - wireRecord: onRestoredRecord -> onDidRestoreRecord, onResumeEnded -> onDidFinishResume - terminal: onData -> onProcessData, onExit -> onProcessExit * feat(kap-server): synchronously refresh all providers before listing models - GET /api/v1/models now awaits refreshProviderModels({ scope: 'all' }) before returning the model list, so the response always reflects the latest provider model metadata - refresh failures are logged and swallowed, falling back to the persisted catalog instead of failing the request * refactor(agent-core-v2): convert one-way notification hooks to Events Replace fire-and-forget OrderedHookSlot hooks with Emitter/Event-based notifications for consumers that only observe, never intercept: - usage: hooks.onDidRecord -> onDidRecord event - permissionMode: hooks.onDidChangeMode -> onDidChangeMode event - fullCompaction: hooks.onDidFinishCompaction -> onDidFinishCompaction event - wireRecord: hooks.onDidFinishResume -> onDidFinishResume event - agentLifecycle: hooks.onDidStopAgentTask -> onDidStopAgentTask event, announced via new notifyAgentTaskStopped() called by mirrorAgentRun Interception-capable slots (onWillStartAgentTask, onWillCompact, onDidRestoreRecord) stay as ordered hooks. * fix(agent-core-v2): route FetchURL through the Moonshot fetch service when logged in When the managed Kimi provider has an oauth ref, WebFetchService builds a MoonshotFetchURLProvider (bearer token + host identity headers) with the local fetcher as fallback, re-reading login state on every call; logged-out setups keep the local fetcher. * fix(agent-core-v2): forward host identity headers with WebSearch requests WebSearchProviderService now passes the host's IHostRequestHeaders (User-Agent + X-Msh-* device identity) as default headers to the Moonshot search provider, mirroring v1's kimiRequestHeaders. * fix(cli): seed host identity headers into the experimental v2 server The v2 boot path (kimi server run with the experimental flag) now seeds the CLI's Kimi identity headers (User-Agent + X-Msh-* device identity) into the engine through kap-server's seeds option, so outbound model, WebSearch, and FetchURL requests carry the same identity as direct CLI runs. kap-server's own package version is 0.0.0, so the identity has to come from the CLI. * feat: validate workspace roots and auto-launch task notification turns - task: idle terminal notifications now use activeOrNewTurn admission, launching their own turn instead of waiting for the next user prompt (matches v1 turn.steer) - workspaceRegistry: createOrTouch rejects missing or non-directory roots with fs.path_not_found, so a phantom cwd never reaches session creation - kap-server: map FS_PATH_NOT_FOUND to protocol error 40409 on the session and RPC surfaces - server-e2e: migrate the v2 smoke test from the local ServerClient to the typed Klient - misc: switch loop/prompt clear() iteration to .slice(), align terminal event handler names, and tidy klient examples * fix(kap-server): emit idle/aborted session status on turn end The v1 WS broadcaster only re-emitted event.session.status_changed(running) on turn.started and never emitted the idle/aborted transition on turn.ended. kimi-web treats that event as the single source of session status (its turn.ended projector deliberately does not synthesize idle), so a session stuck at 'running' after the turn finished — most visibly for background tasks, where ISessionActivity keeps reporting non-idle while the detached task lives and even a REST pull never corrected it. Re-emit event.session.status_changed after turn.ended on the same dispatch queue, mapping reason cancelled/failed/blocked to aborted and otherwise idle (previous_status 'running'), matching v1's _computeStatus. Update the broadcaster tests and harden the wsV1Resync test helper so non-matching frames no longer strand a waiter's timeout. * feat(ws): add Service event streaming with waitUntil handshake - listen messages accept a service name; kap-server resolves the Service via resolveService and subscribes through its onUpperCase member - add listen_result acknowledgement and per-listen error reporting (onDidListenError) so failed subscriptions surface to the client - support onWill-style events: payload carries eventId/signal/waitUntil, the client replies with event_result and the server can event_cancel - klient proxy maps onUpperCase members to channel.listen; WsChannel shares one remote subscription across first/last listeners - channel.call now forwards the complete argument array * feat(agent-core-v2): add typed telemetry event registry - register business telemetry events with compile-time property contracts - redact sensitive values and reject invalid cloud properties - centralize cloud appender construction and version context * feat(kap-server): add channel introspection endpoint - add describeChannels() to channelRegistry: scope derived from the scoped DI registry, public methods/getters enumerated from the prototype chain (framework plumbing and events excluded) - export ChannelDescriptor / ChannelMethodDescriptor from the contract - serve GET /api/v2/channels so clients (kimi-inspect) can render a dynamic service browser without handwritten method lists - cover with rpc test, e2e channel registry test, and API surface snapshot * feat(kap-server): expose declared parameter names in channel descriptors - add `params` field to ChannelMethodDescriptor, parsed from function source - extract declared parameter list via Function#toString with paren-depth tracking - cover param introspection in rpc and server-e2e channel registry tests * fix: adapt v2 print runner and tests to enqueue prompt API - run-v2-print: drive turns via IAgentPromptService.enqueue() and handle.launched; detect hook-blocked prompts via handle.completion; read the LoopRunResult type discriminator in formatNativeTurnFailure - bootstrap stubs: add clientVersion required by IBootstrapService - v2-run-print test: mock enqueue, stub IBootstrapService for createCloudAppender, add track2 to the telemetry stub - node-sdk test: cover prompt.completed/aborted/steered in the exhaustive event switch * feat(agent-core-v2): introduce graded error taxonomy for os, storage, and wire layers - add `os.fs.*` codes with `HostFsError` and the `toHostFsError` boundary translator - add `os.process.*`, `storage.*`, and `wire.*` domains with coded error classes - register new codes in the protocol `KimiErrorCode` union - translate raw OS and parse failures into domain codes across services, persistence backends, and kap-server transport - rename `KimiError` to `Error2` in the v2 base errors - remove obsolete kimi-csdk init example * test(agent-core-v2): wait for MCP connectAll instead of a fixed tick The MCP initial-connect assertion used a single setTimeout(0) tick, but connectAll is gated on Promise.all([resolveSessionMcpConfig(...), enabledMcpServers()]); the session-config side walks the real filesystem (project-root search + mcp.json reads), which does not settle within one macrotask under CI load. Use vi.waitFor so the test is robust on CI. * fix(minidb): publish WAL value pointers only after the frame is durable In valueMode 'disk' the write path installed a disk ValueLoc using the predicted WAL offset before the frame's bytes were flushed (appendLoc returns the offset synchronously; the writev lands on a later tick). Under load a concurrent compaction snapshot could read a pointer past the WAL end and fail with a short read. Apply the record as an in-memory ref first and only publish the disk pointer after appended.done resolves, guarded against WAL rotation and stale record seqs. * fix(kap-server): report missing agents as agent.not_found - resolveScope now throws Error2 for missing session/agent instead of returning undefined, distinguishing agent.not_found from session.not_found - map AGENT_NOT_FOUND onto the session-not-found protocol envelope for v1 parity * refactor(cli): gate print-mode v2 on KIMI_CODE_EXPERIMENTAL_FLAG - remove KIMI_PRINT_V2_ENV / isPrintV2Enabled and the dedicated KIMI_CODE_EXPERIMENT_FLAG switch - route `kimi -p` to the agent-core-v2 runner via isKimiV2Enabled, the same master switch that gates server-v2 * fix(agent-core-v2): map hostFs/storage error codes at server boundaries - unwrap the HostFsError cause before matching EISDIR in FileEditService, restoring the "is not a file" edit output broken by the error taxonomy - map os.fs.* codes to the closest v1 wire codes in the kap-server fs route and /api/v2 transport instead of collapsing to INTERNAL_ERROR - map storage.io_failed / storage.locked to PERSISTENCE_FAILURE in the /api/v2 transport * fix(agent-core-v2): serialize config writes and reloads A User-target set/replace mutates raw/rawSnake, awaits persist(), then rebuilds effective, while a reload() replaces all three wholesale from disk. Without serialization a reload whose file read resolves inside a write's persist window (before the atomic rename lands) restores the stale pre-write state, so the write's post-persist rebuild drops the just-written domain from effective. This surfaced as POST /config responses missing the field they had just written when the startup model-catalog refresh's reload() raced the write. Run User-target writes and reloads through a promise chain so they can no longer interleave. * feat(agent-core-v2): align telemetry with the v1 wire format - rename tool_call_dedupe_detected to tool_call_dedup_detected - emit turn_ended on every turn end; add mode/provider/protocol tags and interrupt_reason to turn_started/turn_interrupted - enrich api_error with alias, protocol tags, and input_tokens - tag tool_call with dup_type via an executor-side map (avoids the executor/dedupe DI cycle) - rename compaction usage fields to input_tokens/output_tokens - add context_projection_repaired, session_started, and session_load_failed events * feat(agent-core-v2): add lifecycle transition machine - add guarded synchronous and asynchronous state transitions - support commit, rollback, cleanup, and compensation actions - cover transition conflicts, action ordering, and failure aggregation * fix(agent-core-v2): harden plugin load, install, and update check paths - Degrade plugin consumption reads to empty when installed.json fails to load, surface plugin.load_failed with a repair hint on management calls, and recover after an explicit reload; serialize the initial load and mutations so concurrent first callers share one load. - Clean up zip temp dirs on every failure path, report the original source in zip/github manifest errors, and roll back to the previous managed copy when an install or persist fails. - Restore managed Kimi endpoint env injection for stdio plugin MCP servers. - Check plugin updates concurrently with per-repo failure isolation and 10s timeouts, track branch installs by commit SHA, and stop false update reports for tag/SHA pins. - Throw plugin.not_found from getPluginInfo and the manager's not-installed paths. - Count plugin skills through the real skill discovery path. - Re-sync context injection positions after silent wire replay so cold resumes do not duplicate injections, and fire plugin session-start reminders only when the plugin skill source finishes refreshing. * fix(agent-core-v2): use the v2 coded error type for plugins * fix(cli): align print session with background completion API * fix(kap-server): stabilize catalog and session status updates - keep model catalog reads free of provider refresh side effects - broadcast deduplicated session lifecycle and interaction statuses - cover catalog loops and global session status fan-out * test: stabilize CI integration cleanup --------- Co-authored-by: _Kerman <kermanx@qq.com> Co-authored-by: 7Sageer <7sageer@djwcb.cn> Co-authored-by: qer <wbxl2000@outlook.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Kaiyi <me@kaiyi.cool> Co-authored-by: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Co-authored-by: STAR-QUAKE <99738745+starquakee@users.noreply.github.com> Co-authored-by: fengchenchen <fengchenchen@moonshot.ai> Co-authored-by: liruifengv <liruifeng1024@gmail.com>
11 KiB
Goal 功能拆分
本文把 agent-core 中 goal mode 的能力拆成三部分:
- 核心工作流:没有它就不能运行 goal。
- 统计 / token 数限制:让 goal 可度量、可限额、可审计。
- 用户交互相关:让用户可以安全启动、理解、控制和恢复 goal。
1. 核心工作流
核心工作流是 goal mode 的运行骨架。它负责创建结构化目标、维护状态机、把普通 turn 串成自治多轮执行,并让模型用机器可读状态结束或停放目标。
目标状态
同一个 main agent 同时最多只有一个当前 goal。goal 不是普通聊天文本,而是 runtime 持有的结构化状态,至少包含目标、可选完成标准、当前状态、停止原因和运行统计。
状态分为四类:
active:正在被 goal driver 推进。只有这个状态会自动运行下一轮。paused:暂停但保留目标。通常来自用户暂停、中断、进程恢复后降级、provider 或 runtime 错误。可以恢复。blocked:目标遇到真实阻塞但保留目标。通常来自模型判断需要外部输入、目标无法按当前表述完成、预算达到、prompt hook 阻止。可以恢复。complete:瞬时完成状态。runtime 发出完成事件后立即清除 goal,不长期持久化。
没有 cancelled 状态。取消就是清除 goal,并提醒模型忽略之前关于该目标的 active reminder。
创建和替换
创建 goal 时,runtime 需要校验目标不能为空、不能过长。已有 active、paused 或 blocked goal 时,默认拒绝创建新 goal,防止静默覆盖。只有用户或调用方明确要求替换时,才先清除旧 goal,再创建新 goal。
新 goal 创建后进入 active,写入持久记录,并发出 goal 更新事件。
多轮驱动
goal driver 的职责是把一个 active goal 推进成连续的普通 turn:
- turn 开始时如果 goal 已经是
active,进入 goal driver。 - 普通 turn 中如果模型创建了 goal,或把 paused/blocked goal 恢复成 active,当前 turn 结束后 goal driver 接管继续执行。
- driver 每次只运行一个普通 turn。
- 每个 turn 结束后读取 goal 状态。
- goal 仍是
active时,runtime 自动追加 continuation prompt 并启动下一轮。 - goal 变成
paused、blocked或被清除时,driver 停止。
模型如果不调用状态更新工具,且 goal 仍是 active,runtime 会继续下一轮。模型不能只靠自然语言说“完成了”来结束 goal,必须给出结构化状态信号。
Goal 注入
每个 goal turn 的边界,runtime 会把当前 goal 状态注入上下文。注入内容包括:
- 当前正在 goal mode。
- 目标和完成标准是什么。
- 目标文本是用户提供的数据,不能覆盖 system/developer 指令、工具 schema、权限规则或 host 控制。
- 当前状态和进度。
- 模型应该做简短自审,然后推进一个连贯工作切片。
- 简单、已完成、不可能、不安全、矛盾的目标,应在同一轮内直接标记 complete 或 blocked。
- 只有全部要求完成、验证通过、没有下一步有用动作时,才能标记 complete。
- 外部条件或用户输入阻塞时,应标记 blocked。
- 不要只做了计划、总结、第一版或部分结果就标记 complete。
goal 注入只在 turn / continuation 边界做,不在每个 model step 都做,避免上下文重复膨胀,也有利于 prompt cache。
paused 和 blocked goal 的注入更轻:
- paused:提醒模型目标存在但当前不应自治推进,除非用户明确要求继续。
- blocked:提醒模型目标被阻塞且当前不自治推进,除非用户要求处理或恢复。
Continuation prompt
当 goal 仍是 active,runtime 会追加一个系统触发输入,含义相当于“继续朝当前 active goal 工作”。它不只是简单续跑,还要求模型每轮重新判断:
- 是否已经完成。
- 是否遇到真实阻塞。
- 是否应该只推进一个合理切片后继续下一轮。
- 是否应该避免发散或启动无关工作。
- 除非真实阻塞,否则不要向用户要输入。
完成、阻塞和暂停
模型通过结构化状态更新控制 goal 生命周期:
complete:目标已满足,runtime 发出完成事件并清除 goal。blocked:遇到真实阻塞,runtime 保留 goal 并停止自治推进。paused:暂时放下 goal,runtime 保留 goal 并停止自治推进。active:恢复 paused 或 blocked goal。
状态更新工具的输入应保持窄,只表达机器状态。完成总结或阻塞原因由模型随后给用户说明。
当模型标记 complete 后,runtime 应再给模型一次收尾机会,生成简短最终回复,说明 goal 已完成、主要做了什么、跑了什么验证。
当模型标记 blocked 后,runtime 应再给模型一次收尾机会,说明具体阻塞、需要什么输入或变化才能继续。
如果当前 turn 已经没有 step 预算,不应为了收尾总结强行再跑一步,避免把“没法写总结”变成 turn 失败。
错误停车
goal mode 把技术运行失败视为可恢复停车:
- 用户中断当前 turn:goal 变 paused。
- provider rate limit:goal 变 paused。
- provider 连接错误、认证错误、API 错误:goal 变 paused。
- 模型配置错误:goal 变 paused。
- runtime 异常:goal 变 paused。
- provider safety filter:goal 变 paused。
业务、规则或外部条件阻塞则变 blocked:
- prompt hook 阻止目标。
- 模型判断无法继续。
- 预算达到。
- 需要用户或外部系统提供新条件。
持久化和恢复
goal 的创建、更新、完成、阻塞、清除应写入可恢复记录。session 恢复时,runtime 用记录重建 goal。
恢复时如果发现 goal 原来是 active,不应自动继续跑,而是降级为 paused。因为旧进程中的 active turn 不可能还活着,自动继续会造成重启后偷偷消耗资源。
paused 和 blocked 原样保留。complete 理论上不长期存在,因为完成后会清除。
fork session 时不继承源 session 的 goal,并提醒模型不要继续源 session 的旧目标。
2. 统计 / token 数限制
这一部分让 goal 可度量、可限额、可审计。没有它,goal 仍然可以运行,但不可控。
运行统计
goal 统计包括:
- continuation turn 数。
- token 数。
- active wall-clock 时间。
统计只在 goal 是 active 时增长。paused 和 blocked 期间不继续计数。
turn 统计在每个 goal turn 准备运行时增加,因此模型在某一轮里标记 complete 时,这一轮也计入最终统计。
token 统计在 model step 结束后累计。没有 active goal 时,不记入 goal。token 统计应以静默更新为主,不应每一步都刷 UI。
时间统计只计算 active pursuit 时间。进入 active 时开启计时区间,离开 active 时折算进累计时间;pause/resume 会形成新的 active 区间。
预算
goal 预算包括:
- turn budget。
- token budget。
- wall-clock budget。
默认没有预算。只有用户明确给出硬限制时才设置,例如“最多 20 轮”“不超过 500k token”“30 分钟内”。模糊表达如“尽快”“别花太久”不能设置预算,模型也不能自行发明预算。
时间预算需要合理范围。过短或过长应拒绝。turn 和 token 预算应规范化为正整数。
预算硬停
预算检查应发生在 goal turn 开始前和结束后。token budget 还应在 model step 后触发停止,避免超额后继续下一步。
一旦达到预算,runtime 应直接把 goal 标记为 blocked,原因是配置预算已达到。这个 blocked 仍可恢复,但如果预算不变,恢复后可能立刻再次 blocked。
预算引导和最终统计
当预算未接近时,模型提示应鼓励稳定推进。当任一预算达到 75% 以上时,提示应转为收敛,避免启动新的可选工作。
complete 和 blocked 的最终回复提示应包含 worked turns、elapsed time、tokens used 等统计信息。UI 事件也应带当前 snapshot 和变化类型。
telemetry 可以记录 goal 创建、预算设置、continuation、状态变化、清除等事件,但不应包含目标文本、停止原因等敏感内容。
3. 用户交互相关
这一部分让用户可以安全启动、理解、控制和恢复 goal。没有它,runtime 仍可能运行,但交互体验和安全边界不足。
生命周期控制
用户可以直接控制 goal:
- 创建。
- 查看。
- 暂停。
- 恢复。
- 取消。
这些操作可以不经过模型 turn。pause 把 active goal 变 paused;resume 把 paused 或 blocked goal 变 active;cancel 直接清除当前 goal。
resume 会清除旧停止原因,表示开始新的尝试。paused/blocked goal 不会因为用户发普通消息就自动继续。
模型发起 goal 的确认
模型可以代表用户创建 goal,但只有在用户明确要求启动 goal、自治工作,或宿主 goal-intake 提示要求时才应该这样做。普通请求不能被模型擅自升级成 goal。
模型发起 CreateGoal 时,非 auto 权限模式下应触发用户确认。确认菜单允许用户选择本次 goal 的运行权限模式。用户拒绝则 goal 不创建。
GetGoal、SetGoalBudget、UpdateGoal 只改 goal runtime 状态,默认可以更容易批准。真正写文件、跑 shell、访问敏感路径等仍走普通权限系统。
暂停、阻塞和取消后的提示
paused goal 的上下文提示应说明目标存在但当前不应继续做,除非用户明确要求继续。
blocked goal 的上下文提示应说明目标被阻塞且当前不自治推进,可以在用户要求时帮助解阻,否则正常处理当前请求。
cancel 后应追加提醒,让模型忽略旧 goal 的 active reminder,避免旧上下文诱导模型继续已经取消的目标。
完成和阻塞的用户回复
complete 后,goal 被清除,模型应给用户一条简短完成总结,说明完成了什么、做了什么验证。
blocked 后,goal 保留,模型应给用户一条简短阻塞说明,说明具体阻塞和继续所需输入、权限、外部条件或变更。
Tool 暴露和隔离
goal 工具只给 main agent。subagent 不应直接创建、恢复、结束主 goal。
没有 goal 时,模型不应看到 UpdateGoal 和 SetGoalBudget。有 goal 时才暴露这些控制工具。
goal ID 不应暴露给模型,因为它只是 runtime/UI 内部标识,没有用户语义。
辅助写 goal
write-goal 类能力用于帮助用户把粗糙意图整理成适合 goal mode 的完成契约。好的 goal 应明确:
- end state:什么条件必须变成真。
- proof:用什么可观察证据证明完成。
- boundaries:工作范围和禁止触碰的内容。
- loop:如何迭代推进。
- stop rule:什么情况下停止并报告,而不是强行继续。
预算是 opt-in,不应默认加入,也不应把 turn cap 写进目标文本。
UI 和会话语义
goal 创建、暂停、恢复、阻塞、完成、清除都应发出 goal updated 事件。lifecycle 变化和 completion 变化应区分。completion 是一次终局事件,然后 snapshot 变 null。blocked/paused 保留 snapshot,UI 可以继续展示可恢复 goal。
session 恢复时,active goal 会变 paused,避免重启后自动继续。fork session 时不继承 goal,并提醒模型不要继续源 session 的目标。