- kimi server run / kimi web now boot kap-server (agent-core-v2 engine)
unconditionally; the KIMI_CODE_EXPERIMENTAL_FLAG gate on the server
path is gone (the kimi -p print-mode gate stays)
- move the OS service manager (svc: launchd/systemd/schtasks) from
packages/server into packages/kap-server and export it there
- repoint the CLI server subcommands, tests, and dev scripts at
kap-server; relabel the web dev backend presets default/multi
- delete packages/server and update workspace bookkeeping (flake.nix,
pnpm-lock.yaml, changeset ignore docs, AGENTS.md, agent-core-dev skill)
* 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 kaos 021786f5 so 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 commit ec9dae72ab.
* 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 in 5cc8e520f: 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
Since d5e1d76fc every 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>
* feat(web): use sidebar fold/unfold icons for sidebar toggle
* feat(web): move settings entry to a sidebar footer row
* feat(web): fully collapse sidebar with animated width transition
* feat(web): redesign sidebar colors, spacing and macos desktop chrome
* feat(desktop): center traffic lights on the 48px header row
* fix(web): restore webkit thin scrollbars and unify sidebar icon sizes
* feat(web): add Kbd keycap component and justify sidebar search shortcut
* style(web): rework sidebar palette and pin a resident sidebar toggle
* fix(desktop): sync window appearance with web UI theme so dimmed traffic lights stay visible
* feat(web): adopt Kimi design icons in the sidebar via a local icon collection
* style(web): mute workspace group title color in the sidebar
* style(web): refine sidebar typography, unify shortcut keycaps, float workspace row actions
* style(web): cap sidebar draggable width at 480px
* style(web): derive sidebar row height from type and padding, float the kebab
* chore: add changeset for sidebar UI polish
* fix(nix): update pnpmDeps hash
* style(web): put the sidebar collapse button inside the header on non-mac
* fix(nix): update pnpmDeps hash
* feat(kosong): classify HTTP 413 request-body-too-large as a dedicated error type
* feat(agent-core): lower default image downscale cap to 2000px and make it configurable
* feat(agent-core): strip media to text markers and retry when the compaction request is too large
* feat(agent-core): cap model-initiated image reads with a configurable byte budget
* feat(agent-core): resend with degraded media when the provider rejects the request body as too large
* test(agent-core): add explicit timeouts to encode-heavy image budget tests
* feat: add WebP decoding support with wasm integration
- Introduced a new WebP decoding module using @jsquash/webp's wasm decoder.
- Implemented functions to decode WebP images and check for animated WebP formats.
- Updated image compression tests to include scenarios for WebP handling, including encoding and decoding.
- Enhanced error handling for API request size limits to accommodate various error messages.
- Updated pnpm lockfile to include new dependencies for WebP encoding and decoding.
* chore(changeset): consolidate this PR's entries into one
* fix(nix): update pnpmDeps hash for merged lockfile
* feat(agent-core): refuse HEIC/HEIF reads with platform-matched conversion guidance
* refactor(web): migrate icons to unplugin-icons
Replace the hand-written gen-icon-data.mjs + @iconify/utils runtime
rendering with unplugin-icons build-time imports. The public API
(<Icon name>, iconSvg, IconName, SIZE_PX, NAME_TO_REMIX) is unchanged;
127+ call sites are untouched.
- add unplugin-icons@^23.0.0 (devDep) + Vite Icons() plugin (compiler: vue3)
- rewrite src/lib/icons.ts: static ~icons/ri/* imports (component + ?raw)
for 56 distinct Remix icons across 59 IconName entries
- Icon.vue renders <component :is> with unknown-name fallback
- append ICON_GROUPS export for DesignSystemView catalog
- DesignSystemView: v-for catalog, remove legacy-script references
- delete gen-icon-data.mjs, gen-icon-catalog.mjs, icon-data.ts,
gen:icons script
- remove @iconify/vue and @iconify/utils from dependencies; move
@iconify-json/ri to devDependencies
- drop Icon.vue from check-style ICON_EXEMPT (no hand-written <svg>)
* refactor(web): drop unused NAME_TO_REMIX icon mapping
NAME_TO_REMIX was a Record<IconName, string> table introduced to map
internal icon names to their ri: ids. After the unplugin-icons migration
it has no production consumers — only icons.test.ts imported it (for two
drift tests) and DesignSystemView mentioned it in descriptive copy. The
ICONS table already conveys the same ri: id via each entry's paired
component + ?raw imports (e.g. RiFolderOpenLine / RawFolderOpenLine).
- remove NAME_TO_REMIX const from src/lib/icons.ts (-63 lines)
- remove NAME_TO_REMIX import + describe block from icons.test.ts
- update DesignSystemView §02 copy: describe the import-pair idiom and
stop claiming ICON_GROUPS is sourced from NAME_TO_REMIX
* chore: add changeset for web icon migration
* chore(nix): bump pnpmDeps hash for unplugin-icons
Adding unplugin-icons changed pnpm-lock.yaml, so the fixed-output
pnpmDeps derivation hash is stale. Update to the hash reported by the
Nix Build CI run.
This package only ever contained a package.json with no sources, dependencies, or scripts, and nothing in the repo imports it (the CLI uses @moonshot-ai/migration-legacy instead). Remove the directory and drop its entries from flake.nix and the changeset config, then refresh the lockfile.
* refactor(web): replace hand-written icons with Remix Icon
Generate a tree-shaken Remix Icon subset at build time via @iconify/utils + @iconify-json/ri, keeping the <Icon>/iconSvg() API.
Add a chat-new icon for the new-chat buttons and reveal the workspace 'new chat in group' button on hover. Unify the message copy and undo buttons (matching hover style and tooltip, drop the undo hover label, align sizes). Switch the mobile switcher kebab to the horizontal dots icon and tweak sidebar search colors. Regenerate the design-system icon catalog.
* fix(web): address PR review feedback
Restore accessible names (aria-label) on the message copy and undo buttons. Keep the workspace add button reachable for keyboard users by revealing it on header focus-within. Update the nix pnpmDeps hash for the newly added icon dependencies.
* fix(web): address follow-up review feedback
Keep the workspace add/more buttons focusable without hover by revealing them via opacity instead of display:none, so keyboard and non-hover users can reach the control.
Drop explicit .ts extensions in icon imports to satisfy oxlint, and read the design-system icon catalog directly from the generated icon data.
* chore(kimi-code): upgrade pi-tui to 0.78.1 and adapt native helpers
Bump @earendil-works/pi-tui from ^0.74.0 to ^0.78.1. pi-tui 0.75.5 replaced its koffi-based Windows VT input with a bundled native helper, and 0.76.0 added a darwin native helper for Terminal.app Shift+Enter.
- SEA build: teach native-deps to collect pi-tui's per-target .node files, drop the koffi registry, and add a native-file-only collect mode so only package.json + the target .node ship (28 -> 2 files).
- Redirect pi-tui's absolute-path native require() into the native-asset cache through the Module._load hook, and extend the native smoke test to actually load the helper.
- npm package: ship pi-tui's native/ directory so macOS Terminal.app Shift+Enter and Windows Shift+Tab keep working for npm installs.
* chore: add changeset for pi-tui upgrade
* chore: vendor @earendil-works/pi-tui 0.80.2
Fork the upstream pi-tui source into packages/pi-tui for local modification. Pristine snapshot of @earendil-works/pi-tui@0.80.2; the apps/kimi-code dependency on ^0.78.1 from npm is left unchanged.
* feat(kimi-code): integrate vendored @moonshot-ai/pi-tui
Replace the npm @earendil-works/pi-tui dependency with the vendored @moonshot-ai/pi-tui workspace package so the fork can be modified locally.
- Point apps/kimi-code imports and native-deps at @moonshot-ai/pi-tui.
- Make pi-tui source-first (exports -> src, publishConfig.exports -> dist, mirroring node-sdk) and strict-clean: bracket access for process.env / named capture groups, an override modifier, and non-null assertions for noUncheckedIndexedAccess.
- Bump the root tsconfig target to ES2024 and enable allowImportingTsExtensions (needed for pi-tui's /v regex and .ts imports, which node --test requires).
- Add packages/pi-tui to flake.nix workspaces and exclude the vendored source from oxlint.
* fix(pi-tui): export package.json for native asset resolution
The SEA native-asset collector resolves the package root via
require.resolve('@moonshot-ai/pi-tui/package.json'). The vendored
package's exports field only exposed ".", which blocked the
"./package.json" subpath and broke build:native:sea with
ERR_PACKAGE_PATH_NOT_EXPORTED.
* chore(kimi-code): sync pi-tui native prebuilds at build time
Copy packages/pi-tui/native prebuilds into apps/kimi-code/native
during build instead of tracking a manual copy in git. Only the
.node prebuilds are copied (not the C sources); the directory is
now a build artifact covered by .gitignore.
* fix(pi-tui): avoid destructive full redraw during streaming
When the first changed line is above the viewport, the differential
renderer fell back to fullRender(true), which clears scrollback and
yanks the user's viewport. On Windows Terminal this jumps to the
absolute top (microsoft/Terminal#20370).
Clamp the diff to the visible viewport when content length is
unchanged (spinner tick / markdown reflow above the viewport), so
streaming no longer triggers a full redraw in those cases. Length
changes still fall back to fullRender to reset the viewport.
* fix(kimi-code): update pi-tui imports in files merged from main
Two files added on main (effort-selector, plugin-command) still
imported the old @earendil-works/pi-tui package name; point them at
the vendored @moonshot-ai/pi-tui.
* chore(nix): update pnpmDeps hash after lockfile regen
* fix(pi-tui): clamp above-viewport diff even when content shrinks
Previously, when the first changed line was above the viewport and
content length changed (e.g. spinner removed at end of streaming), the
renderer fell back to fullRender(true), which clears scrollback and
yanks the viewport to the absolute top on Windows Terminal.
Always clamp the diff to the visible viewport instead, preserving the
user's scroll position. Stale bytes remain in scrollback but are not
visible.
* fix(kimi-code): keep activity placeholder to avoid streaming shrink
When streaming ends, removing the activity spinner shrank the content
by two rows, which (combined with transient→final code highlighting
above the viewport) triggered a destructive full redraw. Keep a one-row
placeholder in the activity pane when idle so the content does not
fully shrink.
* chore: refine streaming scroll changeset wording
* chore: remove obsolete pi-tui native helpers changeset
* chore: add pi-tui changesets and document the pi-tui changelog rule
Add changesets for the fork integration, package manifest export, and
viewport clamp fix so the vendored pi-tui keeps its own changelog.
Update the gen-changesets skill to treat @moonshot-ai/pi-tui as a
special internal package that lists itself instead of the CLI, with a
separate CLI changeset only when the change is user-visible there.
* chore(nix): update pnpmDeps hash after merging main
* fix(changeset): drop private kimi-code-docs from google-genai changeset
* feat(agent-core): compress oversized images before sending to the model
Downsample images to a 2000px longest-edge and per-image byte budget at the
single prompt-ingestion chokepoint (the prompt/steer RPC) and on tool results
(ReadMediaFile, MCP), so every client transport — CLI, web, desktop, ACP, SDK —
is covered uniformly inside the core. PNG screenshots stay lossless and only
degrade to JPEG when the byte budget cannot otherwise be met. Best-effort: the
original image is sent unchanged if compression fails.
* fix(agent-core): serialize prompt/steer RPCs to avoid a turn-claim race
The prompt/steer RPC handlers await image compression before turn.launch()
synchronously claims the active turn, so two overlapping calls could both
compress first — letting the faster-to-compress one win the turn and strand the
other on agent_busy. Run these two RPCs through a per-agent serialization chain
so they claim in submit order; cancel and the other RPCs stay immediate.
* fix: update flake.nix pnpmDeps hash for the jimp dependency
Adding jimp to the workspace changed pnpm-lock.yaml, so the pnpmDeps
fixed-output hash was stale and the nix build failed. Update it to the value
the CI nix build reported.
* fix(agent-core): guard image compression against decompression bombs
A tiny-byte, huge-dimension image (e.g. a solid 30000x30000 PNG) would be fully
decoded into a multi-gigabyte bitmap by Jimp before any resize — an OOM vector
the byte budget never catches. Skip compression when the sniffed pixel count
exceeds MAX_DECODE_PIXELS (~100 MP), before the decode; oversized images pass
through uncompressed as they did before compression existed.
* fix(agent-core): cap decode byte size before compressing images
Compression runs before downstream size caps (e.g. the 10MB MCP per-part
limit), so a huge or invalid base64 image from an MCP tool was Buffer.from-
decoded — and handed to Jimp — just to be dropped afterward. Add a
MAX_DECODE_BYTES ceiling (64MB, overridable) checked before the base64 decode
and before Jimp, the byte-side complement to the pixel-count guard; oversized
payloads pass through uncompressed.
* refactor(agent-core): compress images at ingestion, not on the turn RPC
Move image compression off the prompt/steer RPC path and back to each ingestion
site (CLI paste, server upload resolution, ACP conversion; ReadMediaFile and MCP
already compressed at their producers). Compressing on the RPC control path put
an async step before the synchronous turn-claim, which spawned a series of
races: prompt/steer interleaving, and — with a cancel arriving mid-compression —
an ineffective abort that let a cancelled prompt launch anyway.
Treating compression as a pure input-stage transform (done while the content
part is built, before it ever enters the agent loop) removes those races
structurally: rpc.prompt/steer are plain synchronous handlers again, and the
serialization/cancel-window machinery is gone. Records stay compressed, resume
stays consistent, and coverage degrades gracefully (a new client that skips
compression just sends a larger image, as before this feature).
* fix: compress inline base64 prompts and honor ACP cancels mid-compression
Two contained ingestion-site follow-ups:
- server: resolvePromptMediaFiles now also compresses images submitted as an
inline `{ kind: 'base64' }` source, not just uploaded files, so the REST
inline-base64 path gets the same downsampling.
- acp-adapter: AcpSession tracks a pending-abort flag while prompt() awaits
image compression (before any turn exists). A session/cancel in that window
flips it, so the prompt returns `cancelled` instead of launching a turn the
client already stopped.
* fix(acp-adapter): cover all concurrent pre-turn prompts on cancel
The pending-abort marker was a single session field, so with two
`session/prompt` requests compressing large inline images at once the later
one overwrote it and a `session/cancel` could mark only one — the other
launched after the client had cancelled. Track a token per in-flight prompt in
a set and flip them all on cancel so every pre-turn prompt is covered.
* chore(node-sdk): declare jimp as a devDependency
The SDK re-exports the image compressor, whose lazy `import('jimp')` (inside
the bundled agent-core code) is inlined into the published dist. jimp was
resolved only transitively via agent-core, so declare it as an explicit build
input here — matching the CLI — to make the bundling reliable rather than
phantom. It stays a devDependency: jimp is bundled, not a runtime dependency.
* feat(kimi-desktop): add Electron desktop client wrapping kimi-web
New apps/kimi-desktop — a thin Electron shell + process manager around
the existing web UI. It reuses kimi-code's shared daemon: it runs the
bundled SEA's `server run` (the same ensureDaemon reuse-or-spawn flow as
`kimi web`), reads ~/.kimi-code/server/lock for the real origin, and
loads the SEA-served kimi-web same-origin. The daemon is left running on
quit so the CLI / browser / TUI keep sharing it.
- main process: ensure-server (run SEA, read lock, confirm healthz),
sea-path (dev vs packaged), window + native menu + window-state +
loading/error screens
- packaging: electron-builder config; before-pack stages the
matching-platform SEA into <resources>/bin/<target>
- CI: desktop-build workflow builds unsigned mac/win/linux installers,
each runner building its own SEA
- workspace wiring: register in flake.nix, allow electron postinstall
(onlyBuiltDependencies), root dev:desktop + typecheck entries
v1 is unsigned, default icon, no auto-update.
* feat(kimi-desktop): sign + notarize macOS builds
Unsigned macOS builds are blocked by Gatekeeper ("app is damaged") once
transferred to another Mac. Add Developer ID signing + Apple notarization,
mirroring the TUI native build:
- build/entitlements.mac.plist: hardened-runtime entitlements (allow-jit,
disable-library-validation for koffi/clipboard, etc.) applied to the app
and — via entitlementsInherit — the nested SEA backend
- electron-builder.config.cjs (replaces .yml): hardenedRuntime + entitlements;
signing and notarization are env-driven (CSC_* + KIMI_DESKTOP_NOTARIZE +
APPLE_API_* ), so the same config builds unsigned locally or signed+notarized
- desktop-build CI: sign-macos input reuses the existing macos-keychain-setup
action + APPLE_* secrets, notarizes via the notary API key
- README: document signing, the Developer-ID requirement, and the
"don't rename the .app" gotcha
Verified locally that electron-builder signs both the app and the nested SEA
with hardened runtime + the entitlements, and the signed app still launches and
serves the web UI. Notarization itself needs a Developer ID cert (CI / a machine
that has one).
* feat(kimi-desktop): rename product to Kimi Code Desktop
productName / window title / menu label / error-screen text all use
"Kimi Code Desktop" so the bundle name matches its executable (a
mismatch from manual renaming is itself reported as "damaged").
* ci(kimi-desktop): build and attach desktop installers in the release pipeline
Make desktop-build.yml reusable (workflow_call) and invoke it from the
release workflow, mirroring the native-build pipeline, so each release
also attaches signed+notarized macOS, Windows and Linux desktop
installers to the GitHub Release.
* feat(kimi-desktop): brand the desktop as an internal testing build
- Add an inline 'internal testing build' tag next to the Kimi Code brand
in the sidebar header, shown only inside the desktop app.
- Use a hidden native title bar on macOS with the traffic lights folded
into the sidebar header, and pin the window title to the product name.
- Ship the Kimi app icon for macOS and Linux builds.
Desktop detection is runtime (a query hint from the Electron shell,
persisted in sessionStorage) so the branding appears even when the
window is served by an already-running shared daemon.
* docs(kimi-desktop): update v1 scope now that the app icon ships
* feat(kimi-desktop): add the Kimi app icon for Windows builds
* chore(nix): update pnpmDeps hash after lockfile refresh
* ci(kimi-desktop): build desktop on release but do not attach to GitHub Release
The desktop build is an internal-testing artifact (branded as such), so
keep it as a CI artifact for internal download instead of publishing it
to the public GitHub Release.
* chore(kimi-desktop): mark installers as internal pre-release builds
Rename the packaged artifacts to KCD-Internal-<version>-<arch>.<ext> and
bump the version to the 0.1.1-internal.0 pre-release, so a leaked or
forwarded installer file is not mistaken for an official public release.
* feat(kimi-desktop): strengthen the internal-build tag wording
Change the sidebar tag to 'Internal testing · do not distribute' /
'内部测试 · 禁止外传' so the no-distribution intent is explicit.
* feat(kimi-desktop): tweak internal-build tag to '仅供内部测试'
* fix(kimi-desktop): pass the server token to the web UI on launch
Read the daemon's persistent bearer token from <KIMI_CODE_HOME>/server.token
and carry it in the URL fragment (#token=), matching how 'kimi web' opens
the Web UI. Without this, a fresh launch (no saved credential) boots the
web UI without a token, hits 401, and falls into the manual token dialog
even though the desktop started the daemon itself.
Addresses review feedback on the desktop URL.
* test(server): add API surface snapshot guardrail
Boot startServer on port 0 and snapshot the documented v1 route table derived from /openapi.json paths, plus the reachability of doc/meta endpoints (/healthz, /openapi.json, /asyncapi.json, /). Gives later auth/--host phases an intentional diff when routes change. M0 makes no production behavior change.
* test(server): add e2e server harness with token support
Add test/helpers/serverHarness.ts: boot() wraps startServer with an isolated lock + home dir and returns a handle (server, address, baseUrl, wsUrl, token, close) plus authedFetch/authedWs that carry Authorization: Bearer <token> (and the kimi-code.bearer.<token> WS subprotocol). serviceOverrides is the generic DI seam later phases use to inject a fixed-token auth service; IAuthTokenService is not referenced yet. closeAll() tears down every booted server and socket. M0 makes no production behavior change; typecheck-only gate.
* feat(server): add privateFiles 0600 atomic write/read utility
* feat(server): add per-start tokenStore
* feat(server): add env-based bcrypt password hash utility
* feat(server): add IAuthTokenService DI seam
* feat(server): add global onRequest auth hook with bypass + redaction
* fix(server): stop reflecting Host header in /asyncapi.json
* feat(server): add WS bearer subprotocol constant and parser
* feat(server): enforce bearer token auth on WS upgrade
* feat(server): add Host header allowlist middleware
* feat(server): add Origin/CORS middleware
* feat(server): wire Host/Origin checks into HTTP and WS
* feat(server): wire token auth, Host/Origin, and WS auth into start.ts
* fix(server): create lock file with 0600 permissions
* fix(server): suppress debug routes on non-loopback binds
* feat(kimi-code): read server token and send Authorization on CLI calls
* feat(kimi-code): inject server token into /web URL fragment
* feat(server): add bindClassify for loopback/lan/public classification
* feat(kimi-code): register --host flag and pass it through the daemon
* feat(server): require password and TLS opt-out on non-loopback binds
* feat(server): rate-limit repeated auth failures on non-loopback binds
* feat(server): disable shutdown and terminals on public binds by default
* feat(server): add security response headers on non-loopback binds
* test(server): cover LAN/public host-exposure hardening end to end
* docs(server): add deployment security and threat-model guide
* changeset: minor kimi-code for server auth and host exposure
* feat(kimi-web): add server bearer-token auth support
* fix: repair CI for server auth and host exposure
- Replace native @node-rs/bcrypt with pure-JS bcryptjs so the ESM CLI
bundle and the SEA native bundle both build without native-addon
require issues (node-rs/bcrypt broke the ESM smoke and the SEA
check-bundle allowlist).
- Remove dead cleanup references (stopSpinner, authLogoBlinkTimer) in
apps/kimi-web App.vue that failed vue-tsc.
- Fix lint: drop empty spread fallbacks in the e2e auth-header merge,
void the intentionally-async WS upgrade listener, add missing
assertions to satisfy jest/expect-expect, and convert a ternary
statement to if/else.
- Send the bearer token in the snapshot perf/smoke tests so they pass
under the new global auth hook.
- Refresh the pnpmDeps hash in flake.nix for the updated lockfile.
* feat(server): persist bearer token and add rotate-token command
- persist the server bearer token in <home>/server.token (0600) and reuse it across restarts instead of per-start server-<pid>.token
- add `kimi server rotate-token` to regenerate the token; the token store reloads on mtime/inode change so rotation applies without restart
- print the token and Vite-style Local/Network URLs in the startup banner
- allow non-loopback binds with bearer-token-only auth (password now optional) and update SECURITY.md
- surface daemon boot failures immediately with the exit reason and log tail instead of waiting for the spawn timeout
* feat(server): print full token URLs and re-print links after rotate
- Drop the ready-panel border so token URLs print in full for copying; keep the Kimi sprite beside the title.
- Re-print Local/Network access links after `server rotate-token` (host/port from the lock).
- Extract shared access-URL helpers into access-urls.ts.
- Unify link and token colors between the banner and rotate-token.
* feat(server): dim URL #token= fragment and de-highlight token
- Render the `#token=…` fragment in a dim gray so the host/port stands out in the banner and rotate-token links.
- De-highlight the standalone token; set it off with surrounding whitespace instead of color.
- Add splitTokenFragment helper.
* refactor(cli): polish server ready banner and rotate-token output
- move version onto the ready banner title line; drop the separate
Ready:/Version: rows and the startup-time metric
- reorder rotate-token output so the new token sits between the
invalidation note and the access links
- update server CLI tests for the new layout
* feat(server): warn on reuse and refine ready banner
- Warn when `server run` reuses an already-running daemon (its options are not applied) and show the running server's actual URLs.
- Show a `Network: off use --host 0.0.0.0 to enable` hint on loopback binds.
- Move the version onto the title line and drop the startup-time metric.
* fix(web): relabel auth dialog to token and cover full page
- Relabel the server auth dialog from "password" to "token"; the server accepts the bearer token, with the password only as a fallback.
- Make the auth dialog overlay fully opaque so it covers the whole page instead of revealing the login page underneath.
* fix: resolve CI failures on web auth PR
- Replace chalk.yellow named color with chalk.hex(darkColors.warning)
in the server reuse notice to satisfy the chalk named color guard.
- Update pnpmDeps hash in flake.nix to match the regenerated
pnpm-lock.yaml so the Nix build succeeds.
- Retry rmSync in ws-broadcast e2e teardown to ride out EBUSY /
ENOTEMPTY races while the server flushes files after close().
* test(server): update API surface snapshot for warnings route
The feat/web-auth branch adds GET /api/v1/sessions/{session_id}/warnings
(packages/server/src/routes/sessions.ts), so the API surface guardrail
snapshot needs to record the new documented v1 route.
* test(kimi-web): keep only pure logic unit tests
Remove jsdom/component Vitest coverage from apps/kimi-web, keep server-e2e as the e2e path, and add focused pure-logic Vitest coverage for diff parsing, file path links, tool summaries, turn grouping, and todo derivation.
* build(nix): update pnpm deps hash
- delete packages/daemon package.json
- drop daemon from flake.nix workspace paths/names and pnpm-lock.yaml
- remove dead daemon-e2e Dockerfile gitignore negation
- update stale daemon references in DiffView and PromptDispatchLogEntry comments
* docs(reports): collapse P3 plan into a single final-solution doc
Drop the per-step TDD/commit scaffolding; keep the substance as one final
approach per area (what it does, files to touch, key types/events/projection,
component responsibilities, verification, risks, sequencing).
* fix(kimi-web): normalize chat block spacing
Group consecutive tool cards structurally so chat block spacing is applied consistently without leaking card borders or shadows.
* feat(web): land P3 — goal / swarm / subagent + terminal + view split
Implements the locked P3 design end-to-end:
- subagent lifecycle projection (spawned→started→suspended→completed/failed) +
inline Agent / AgentGroup cards; swarm progress card (multi-column) derived
from swarmIndex; goal dock strip (expandable) from goal.updated; plan/goal/
swarm activation badges in the composer status line.
- terminal as a view (xterm + WS terminal_* frames with since_seq replay) and a
tab/view-dimension split (usePaneLayout tree + ViewGroup + SplitLayout, VSCode
editor-group style), persisted to localStorage.
Adds swarm-groups / subagent-goal / agent-group-turns unit tests and stub-daemon
seeds. 98 tests pass; vue-tsc + oxlint clean; production build OK.
Accepted by review (see reports/web-p3-acceptance.md); no blocking issues.
* docs(reports): P3 landing acceptance review
Comprehensive acceptance of the P3 landing (f5a7f21c): per-area verdicts, the
terminal 'map' crash explained as a stale-stub test artifact, non-blocking
recommendations, and verification record (98 tests, vue-tsc/oxlint clean, prod
build, in-browser smoke). No serious issues found; no code changed per the
'only fix serious issues' instruction.
* fix(terminal): make node-pty load and spawn in packaged + pnpm-dev builds
Two distinct PTY failures:
- 'Failed to load native module: pty.node' (npx/published daemon): node-pty was
transitively bundled via @moonshot-ai/services (alwaysBundle), inlining its JS
while its native binary can't be bundled and wasn't shipped. Mark node-pty
external in tsdown (neverBundle) and declare it as a runtime dependency of
@moonshot-ai/kimi-code so npm/npx installs it with its prebuilt pty.node.
- 'posix_spawnp failed' (local pnpm dev): node-pty's prebuilds/*/spawn-helper
loses its +x bit through pnpm's store extraction. Add a root postinstall
(scripts/fix-node-pty-perms.mjs) that restores the executable bit; verified it
fixes a reproducible spawn failure.
Also harden defaultShell() to fall back on an empty (not just unset) $SHELL.
Note: the SEA standalone binary still needs node-pty's pty.node + spawn-helper
wired into scripts/native/native-deps.mjs (not addressed here; npx path covers
the reported case).
* fix(web): use a real monospace font + tighter line height in the terminal
xterm's fontFamily takes a literal font string, so 'var(--mono)' never resolved
and the terminal fell back to courier with loose metrics — the wrong-looking
font and spacing. Pass the actual JetBrains Mono stack, await document.fonts
before xterm measures the cell (so the variable font isn't mismeasured), tighten
lineHeight 1.25 → 1.1, and pin letterSpacing 0.
* style(web): drop the staggered line-in animation on expanded tool-call output
Remove the per-line kimi-line-in stagger on `.box.open .bb > div` (modern/kimi
themes) and its keyframes — expanding a tool card no longer animates each output
line in.
* feat(web): move the tool-call summary into the card when expanded
Previously the command/summary always sat on the header. Now it shows on the
header only while collapsed; expanding hides it from the header and renders it
at the top of the card body (above the output) — so it appears exactly once and
the expanded header stays clean. Re-adds the .bb-summary style and a mount test.
* feat(web): show the full, un-truncated summary in the expanded tool card
The expanded body has room to wrap, so it shouldn't keep the header's '…'
clip. Add a `full` flag to toolSummary that skips the length clip and use it for
the .bb-summary; the collapsed header keeps the clipped form (CSS ellipsis still
guards overflow). Extends the mount test to cover full-vs-clipped.
* revert(web): keep the sending moon until the turn ends
Reverts 980ff9d4: dropping the moon the instant the first token streamed wasn't
wanted. Remove the assistantDelta/messageUpdated clear so sendingBySession is
again cleared only on turn end (onSessionIdle), restoring the prior behavior,
and delete the now-moot sending-moon test.
* style(web): bump composer textarea font-size to 14px
The composer input (.ph) under the modern/kimi themes was 13px while the
terminal-theme baseline is 14px. Unify on 14px so the textarea text matches
the rest of the composer.
* fix(web): dedupe the daemon echo of an image steer (no double user bubble)
Steering an image while a turn was running rendered TWO user bubbles and the
steer text looked like it never landed. Two causes:
1. The reducer matched the daemon's user-message echo to our optimistic copy by
exact content equality. Image content serializes differently on each side
(our {source:{kind:'file',fileId}} vs the daemon's resolved URL/base64), so
the echo never matched and appended a duplicate. Match by prompt_id first
(stamped on the optimistic message at submit), falling back to content.
2. Optimistic message ids were msg_opt_<Date.now()>. A queued send + a steer in
the same millisecond collided on one id, so the prompt_id stamp landed on the
wrong message. Use a monotonic counter for a unique id per optimistic message.
steerPrompt now also stamps the real prompt_id onto its optimistic echo, like
submitPromptInternal already did.
* fix(web): don't flash the chat pane when opening an empty session
Selecting a never-opened session set sessionLoading=true until its snapshot
arrived, so the chat pane (loading spinner) rendered for a beat before the
empty-composer. A session the daemon reports as empty (messageCount 0) has
nothing to load — keep sessionLoading false for it so the empty-composer shows
immediately. Non-empty sessions still show the loading state.
* fix(web): auto-scroll to the latest content after a mid-stream refresh
Refreshing while a turn was streaming left two things parked above the live
output:
- The thinking block's inner 5-line window stayed at its TOP. Its scroll watcher
only re-pins when already at the bottom, but a refresh delivers the whole
thinking text at once with scrollTop 0. Pin a streaming block to its latest
line on mount.
- The transcript could stop short of the bottom: the first scroll runs before
markdown highlighting/images lay out and grow the content. Re-pin on the next
couple of frames (only while still following) so a refresh ends at the latest
content.
* fix(web): stop subagent turns from fragmenting the parent transcript
A subagent runs under the parent session id and streams its own turn / step /
delta / tool frames over the SAME session channel, each tagged with the
subagent's agentId. The web projector folded them into the parent transcript,
which produced the reported bug: empty 'skeleton' assistant bubbles (a subagent
turn.step.started opened a parent assistant message the main agent never filled)
and fragmented snippets (subagent deltas appended to the parent).
Skip transcript-building frames whose agentId is a non-main subagent, mirroring
the server's InFlightTurnTracker (which already tracks only main-agent
activity). Subagent progress is unaffected — it flows through the
subagent.* -> task -> AgentCard path, which is intentionally not gated.
* feat(web): remove the floating todo/background-task overlay
The wide-screen float-stack pinned a todo card + running-tasks card to the
top-right of the chat. Drop the overlay entirely (and the now-unused
TasksCard.vue) — todos and background tasks live in their own ~/todo and ~/tasks
tabs, so the overlay was a redundant, transcript-covering duplicate.
* feat(web): show all background tasks in the tasks tab, scroll on overflow
The tasks tab capped the list at 5 rows and showed '… +N more', hiding the rest
even with plenty of room. Render every task and let the list scroll internally
once it overflows the pane, so nothing is silently dropped.
* feat(web): running spinner + unread blue dot left of the session title
The gutter slot left of each session title (which kept the title aligned under
the workspace name) now carries a status indicator instead of being an empty
spacer:
- a small SVG spinner (Kimi-blue arc) while the session is running, replacing
the old absolutely-positioned pulse dot;
- an unread blue dot when a BACKGROUND session finished a turn the user hasn't
opened yet. Tracked via unreadBySession (set on idle for a non-active session,
cleared when the session is selected).
* feat(web): unify archive/remove wording + keep the confirm within the title
- Clarify the two list-removal actions: a session is 'Archive' (归档), a
workspace is 'Remove workspace' (移除工作区) — the workspace menu used the bare
'Delete', which read as the same action as the session archive.
- Keep the session row's archive-confirm strip aligned under the title: the
leading gutter slot now persists in the confirm state, so the confirm row
starts at the title's left boundary instead of spilling to the row edge.
* feat(web): new-conversation button + workspace picker on the empty composer
- Add a compose button in the sidebar header (top-left) that starts a new
conversation in the active workspace. It wires up the previously-dead 'create'
emit (handleCreateSession → openWorkspaceDraft).
- On the empty composer, add a workspace picker below the hint so a new
conversation can be started in any workspace without leaving the screen
(switching enters that workspace's draft via openWorkspaceDraft).
* feat(web): add a Fork entry to the session row menu
Forking already worked via the /fork command and the daemon's :fork route, but
had no discoverable affordance. Add a 'Fork session' item to each session row's
kebab menu; forkSession() now takes an optional session id so any row (not just
the active one) can be forked.
* feat(web): recall sent messages with ArrowUp/ArrowDown in the composer
Shell-style history: ArrowUp on the first line of the composer walks back
through previously sent messages; ArrowDown on the last line walks forward and
finally restores the live draft. Editing the text leaves history-browsing, and
the edge-line guards keep multi-line cursor movement intact. Submitting (or
steering) a message appends it to the history (consecutive duplicates skipped).
* feat(web): capture console.log/info/debug + reusable log export
The client trace only captured console.error/warn. Capture every console level
(log/info/debug too) when tracing is enabled, so the exported troubleshooting
log reflects the full front-end console. Extract the JSONL download into a
reusable downloadTraceLog() (the debug panel now calls it; a settings 'Export
log' action can reuse it).
* feat(web): extract settings into a dedicated Settings page
Settings used to live in the sidebar account popover (a cramped fixed dropdown
that mixed appearance, language, account and the daemon endpoint). Move them
into a dedicated SettingsDialog modal opened from the header gear:
- Appearance (theme / colour scheme / accent), Language
- Account (provider, add workspace, reopen onboarding, sign in/out)
- Advanced (daemon endpoint, Export log — reuses downloadTraceLog)
The sidebar popover and its anchoring/positioning code are removed; the gear now
just emits openSettings. A Notifications section is added next (T14).
* feat(web): browser notification when a turn completes (with a settings toggle)
When a session finishes a turn and the user isn't already watching it (page
hidden, or a different session is active), fire a browser system notification
titled with the session, clicking it focuses the window and opens the session.
Opt-in via a new Notifications toggle in the Settings page; enabling it requests
OS permission and the preference is persisted (stays off if the user blocks it).
* feat(web): modes selector (plan/goal/swarm) + fix swarm double-render
- The plan pill at the composer's bottom-left becomes a 'Modes' popover that
groups Plan (a working client toggle) with Goal and Swarm. Each shows its
activated state (plan on / goal active / swarm n/m), and goal/swarm focus
their card in the chat when active. The menu is position:fixed so the composer
input row can't paint over it.
- Fix the swarm 'two blocks' bug: a multi-member swarm rendered BOTH inline as an
AgentGroup AND as its SwarmCard. messagesToTurns now skips the inline block for
swarm members (same membership test as buildSwarmGroups), so the swarm shows
once — its special card in the chat flow.
Note: starting a goal/swarm from the web needs a daemon REST endpoint (the goal
RPC isn't exposed over REST and the daemon doesn't interpret slash commands in
prompts); display + activation state are wired here.
* feat(web): add a chat context header (workspace/session, git, open, copy, PR)
A thin bar above the chat shows the workspace / session breadcrumb, the git
branch with ahead/behind + changed-file count, an 'open in editor' action
(daemon fs:open on the workspace root), and a 'copy all conversation' action
(reuses ChatPane.copyConversation). It also has a GitHub PR slot that renders
when PR data is available — the daemon doesn't expose PR status yet, so it's
wired but currently passed null. Hidden on mobile and for the empty composer.
* feat(web): default path + fuzzy recursive search in the add-workspace browser
- Open the folder browser at the path kimi-web is working in (the active
workspace root, falling back to $HOME) instead of always at $HOME.
- The filter becomes an fzf-style search: typing runs a bounded, debounced
RECURSIVE subsequence-fuzzy walk under the current folder (capped depth/dirs/
results, cancellable) and lists matching directories by relative path. The
result list keeps a fixed height, so the dialog never resizes while searching.
- Collapse the paste-an-absolute-path field behind a secondary 'enter a path'
toggle (auto-expanded when the daemon can't browse).
* chore(web): remove the non-functional /undo slash command
/undo had no daemon endpoint — it only pushed an 'undo not implemented' warning,
so it was a dead menu entry. Remove it from the slash list, the command router,
and the client. The full slash-command review with deletion suggestions for the
remaining commands is in reports/web-goal2-fixes.md (T17).
* docs(reports): results report for the second web TODO sweep (19 items)
* test(web): provide browser storage in vitest under node 24
* docs(reports): add web goal2 acceptance notes
* feat: show shortPath over branch in sidebar workspace header
* feat(kimi-code-web): use rounded chat bubble icon for new session button
* feat(kimi-code-web): add workspace creation in empty composer and tidy settings dialog
* feat: add manual swarm and goal activation to web ui
- Extend protocol schemas with swarm_mode, goal_objective, goal_control
- Add stub diff-dispatch in PromptService for new runtime controls
- Wire swarm/goal state through useKimiWebClient and daemon events
- Add Swarm toggle and Goal create/pause/resume/cancel in Composer modes menu
- Update StatusPanel and MobileSettingsSheet with swarm indicator/toggle
- Add bilingual i18n strings and update fixtures/tests
* feat: remove copy-conversation button from view-tabs
- Drop showCopyConversation / copyConversationCopied props from TabBar and ViewGroup
- Remove the share-conversation button markup and styles from TabBar
- Clean up related i18n strings in en/zh sidebar locales
- Keep the existing ChatHeader copy-all button and internal copy state unchanged
* feat: reorder chat-header layout and simplify git status styling
- Move Copy all button next to the workspace/session title on the left
- Move git branch/status and Open-in-editor to the right
- Shorten editor button label via new openInEditorShort i18n key
- Render ahead/behind/changes as plain colored text without pills
- Update en/zh header locale files
* style: make chat-header action buttons borderless icon + text
- Remove border, background, border-radius, and padding from .ch-act
- Keep label collapse on narrow widths, drop obsolete padding override
* feat: move copy-all to kebab menu and add session actions in chat-header
* feat: redesign chat-header open button with open-in menu
* feat: align chat-header diff stats with git ++/-- red/green style
* style(web): thinner, fainter scrollbars across all components
* fix(web): re-pin chat to bottom when a turn finishes streaming
* fix(web): keep the working moon spinning after a refresh mid-stream
* fix(web): subagent card margins in bubble layout + expandable task/result detail
* feat(web): rebuild subagent cards from the transcript so they survive a refresh
* fix(web): stop code blocks getting stuck on the loading skeleton
markstream's CodeBlock shows a skeleton while !stream && loading, and its
loading prop defaults to true. We never set it, so every settled code block
waited on shiki to highlight before showing anything; a screenful of code
(long session / fast burst) overwhelms shiki and the skeletons get stuck,
leaving the whole page blank. Pin loading:false so blocks render their
plain-text fallback immediately and upgrade to highlighted when ready.
* fix(web): tick running task timers + make task rows expandable to view output
* fix(web): dedupe image-steer echo via a loose (text+image-count) match
The daemon's messageCreated echo can land before submitPrompt stamps the
prompt_id onto the optimistic copy, and an image serializes differently
(file ref vs resolved URL), so neither the prompt_id nor exact-content match
fired and the echo rendered as a SECOND user bubble. Add a loose fallback
matching on text + image-count so the echo reconciles regardless of order.
* fix(web): let ↑/↓ walk all the way through input history once browsing
Recalling a multi-line entry left the caret on its last line, and the
'ArrowUp only on the first line' gate then refused to recall further, so
history only ever went one step back. Once browsing (historyIndex set), walk
history directly regardless of caret line; typing still exits browsing.
* feat(web): minimize button on question/approval cards + stack option label/desc
- Add a minimize toggle so a blocking question/approval can collapse to a thin
header bar instead of covering the chat; number-key shortcuts are gated while
collapsed so an unseen option can't be picked.
- Stack each option's label above its description (was squeezed side-by-side
into many thin lines when the description was long).
Note: there is no question/approval timeout in the codebase (ask-user waits
indefinitely), so the '10 minute' request is a no-op.
* feat(web): archive-confirm text matches title size; workspace remove always hides
- Bump the 'archive session?' confirm label to the session-title size (14px)
so it lines up with the title instead of reading as a smaller note.
- 'Remove workspace' now always hides the sidebar entry, even when it still has
sessions: record the root in a persisted hidden set so mergedWorkspaces stops
re-deriving it from session cwds. History/sessions are untouched; re-adding
the same path un-hides it.
* feat(web): persist unsent composer drafts per session in localStorage
The composer text is saved under a per-session key as you type and restored
when you switch back to that session or reload the page; sending/steering
clears it. New-session drafts use a '__new__' key.
* feat(web): implement undo + edit-and-resend the last user message
- Wire the daemon POST /sessions/{id}:undo endpoint: client.undo(count) reverts
the last turn(s) and re-syncs the snapshot. Restore the /undo slash command.
- Add an 'edit & resend' button on the latest user message: it undoes the last
exchange and refills the composer with that message's text for editing.
* fix(web): reflect the agent's plan mode in the composer toggle
The agent reports plan mode via agent.status.updated (e.g. it auto-entered plan
mode for a 'make a plan' prompt), but the projector only forwarded swarmMode, so
the composer's plan toggle never lit up. Carry planMode on sessionUsageUpdated,
sync it into state, and also read it from GET /status — mirroring swarmMode.
* fix(web): hide an empty {} argument from the tool-call title (kept in details)
An empty tool argument was rendered as a noisy '{}' in the collapsed tool-card
header. toolSummary now returns '' for empty args in header (non-full) mode while
the expanded body still shows it.
* feat(web): show file/media preview as a split pane (peer of chat/files)
On desktop, opening a preview from a chat link/media now splits the layout and
shows it as a 'preview' view at the chat/files level (a transient tab in that
group, closeable via the group's close button) instead of a separate right-side
panel — matching the split buttons. Mobile keeps the full-screen side panel; the
preview view isn't persisted across reloads.
* docs(reports): results report for the third web TODO sweep (16 items)
* chore(kimi-web): temporarily hide open-in-app header menu
* docs: design doc for temporarily disabling swarm and goal modes in web composer
* feat(web): gray out swarm and goal modes with not-supported label
* docs: design doc for composer queue bubble + expanded panel
* feat(kimi-web): move undo button out of bubble with new icon and confirm step
* fix: inherit split layout attributes
* fix(kimi-web): smooth moon spinner speed
* feat(web): wire swarm and goal controls to agent-core
- enable swarm toggle and goal input/pause/resume/cancel controls in Composer\n- add goal error codes and agent-core-to-protocol route mappings\n- wire enterSwarm/exitSwarm and create/pause/resume/cancelGoal RPCs in PromptService\n- bootstrap swarmMode from agent-core state and track it in the shadow\n- update tests for swarm/goal dispatch and session status serialization
* fix(tui): only show provider refresh status for added models
Skip removed / metadata-only provider updates when reporting model list changes.\n\n add: test to enforce the behavior.
* feat(tasks): add background task command and output polling to web
- include command field on protocol/server tasks\n- support withOutput/outputBytes on task get endpoint\n- poll running task output and fetch final output in web client\n- show bash command and terminal output in TasksPane with copy buttons
* feat(web,server): wire open-in app menu to daemon endpoint
- add /fs:open-in endpoint and command builders for vscode, cursor, finder, iterm, terminal\n- expose installed open_in_apps via meta response\n- filter OpenInMenu by available apps and remove antigravity target\n- support optional line number when opening files in apps\n- add unit tests for open-in launch commands
* feat(kimi-web): expose git diff line stats in chat header
- add additions/deletions to fs:git_status protocol and daemon response\n- compute aggregate diff stats with git diff --numstat HEAD\n- render +N/-N counter and detached HEAD label in chat header\n- update tests and stub daemon fixtures
* fix(kimi-web): hide terminal tab temporarily
* feat(kimi-web): add UI font size setting
* fix(kimi-web): repin chat after tail layout settles
* feat(kimi-web): show live subagent progress
* fix(kimi-web): align plaintext colors in dark mode
* feat(kimi-web): stream running bash output
* fix(kimi-web): avoid shiki overload on large messages
* feat(kimi-web): preselect recommended questions
* feat(kimi-web): add conversation outline nav
* fix(kimi-web): tighten mobile layouts
* feat(kimi-web): animate undo removal
* feat(kimi-web): reorganize bottom dock
* fix(web): lengthen session row running spinner arc
* feat(kimi-web): turn goal mode into a toolbar toggle
Turn the Modes menu's 'Goal' row from a dedicated input form into a
switch that arms the main composer. When goal mode is on, the composer
placeholder prompts for an objective and the next submitted prompt
is sent as a goal via updateSession({ goalObjective }).
The armed state is surfaced in the composer toolbar the same way as
Plan and Swarm: the Modes pill shows a 'Goal' tag and the menu switch
is highlighted. An active (agent-driven) goal continues to expose
Pause / Resume controls in the menu.
Also includes minor bottom-dock spacing alignment changes to keep
goal chips, workbar, and composer visually consistent.
* feat(config): add config API endpoint with redaction support
add GET/POST /api/v1/config routes\nadd ConfigService and config protocol schemas\nredact api_key in config response\nadd web client bindings and config event handling\nadd e2e tests for config routes and ws-broadcast
* fix: keep web tool calls collapsed by default
* feat(kimi-web): split dock work panel into bash, subagent, and todos tabs
- Replace the single Background tasks tab with separate Bash and
Subagent tabs in the bottom dock work panel.
- Add i18n labels for the new dock tabs in both en and zh.
- Filter task lists by kind and reuse TasksPane for each tab.
- Align left edges of Bash/Subagent/Todos tab bodies and match
the dock panel width/background to the Goal card style.
- Hide TasksPane header title when rendered inside the dock to
avoid duplicate headings.
- Make the dock tab header show only the current tab name instead
of clickable buttons.
- Hide Goal card title summary on expand while keeping layout
alignment for status/progress/chevron.
- Update unit tests for the three-chip dock behavior.
- Add changesets for the dock split and alignment fixes.
* feat(kimi-web): beta proportional conversation outline with viewport indicator and hover tooltip
* fix(kimi-web): avoid streaming markdown placeholders
* fix(kimi-web): hide legacy conversation outline when beta TOC is off
* chore(kimi-web): rename beta settings section to Experimental / 实验性
* fix(web): scale UI font size consistently
Apply the web font size preference across readable text using the shared UI font scale, keep fixed icon glyph sizes pinned, and raise the default font size to 15px.
* fix(web): remove task tabs from tab bar
* feat(web): refresh OAuth model metadata for always-thinking models
* fix(kimi-web): use 'Sub Agent' and 'Mode' in English labels
* fix(kimi-web): keep swarm subagents across background-task refreshes
REST /tasks lists only the main agent's background-task store and never
returns foreground swarm subagents (kind 'subagent'), which arrive purely
through the WS event stream. Both the 1s output poll and the session-load
task fetch rebuilt tasksBySession from that REST list, so a plain replace
dropped the subagents on every refresh and the next event re-added them —
flickering the swarm/subagent cards, their live "currently doing" line,
and the dock "running" count about once per second.
Add keepLiveSubagents() to carry WS-owned subagent tasks across the REST
refresh (REST stays authoritative for the background tasks it does return)
and use it at both rebuild sites.
* fix(kimi-web): hide completed swarm cards from conversation bottom stack
- Filter out swarm groups whose members are all completed/failed.
- Preserve markstream-vue .table-node styles without overriding its layout.
- Add unit tests for swarm stack visibility.
* feat(session): add session-level abort and expose current_prompt_id in snapshot
- add POST /sessions/{sid}:abort to cancel running turns without prompt_id\n- expose current_prompt_id in in-flight turn snapshot\n- wire session-level abort fallback in kimi-web stop button\n- add IPromptService.abortBySession and getCurrentPromptId\n- update protocol, server, services, and web tests
* fix(server): allow aborting queued prompts and add server-e2e send/cancel coverage
add server-e2e scenario (12-send-and-cancel) and vitest cases for send prompt / cancel prompt flows, including repeated ESC idempotency\nsupport aborting queued prompts in PromptService.abort and add abortSession helper to DaemonClient/HttpClient\nhandle SSE transport case in MCP server mapping and fix related typecheck issues\nadd changeset for @moonshot-ai/services and @moonshot-ai/kimi-code
* fix(kimi-web): prevent file preview scroll jump when opening a file at a line
* fix(kimi-web): show just now for sessions created less than a minute ago
* fix(kimi-web): keep sidebar logo intact and hide product name on narrow sidebars
* fix(kimi-web): preserve markdown code gutter
* feat(web): carry message createdAt into ChatTurn
* feat(web): add formatMessageTime utility
* feat(i18n): localize yesterday label for message timestamps
* feat(web): render timestamp below user query bubble
* fix(web): move user query timestamp outside the bubble
* fix(web): place timestamp on same line as undo action
* fix(web): swap timestamp and undo positions, reveal undo text on hover
* feat(web): make timestamp a button that toggles full date time
* feat(web): update sidebar branding and enlarge session tags; clean up changesets
- Replace "Kimi Code Web" + "BETA" with "Kimi Code" + version pill
- Enlarge session pending tags to match the title font size
- Ignore internal private packages in changeset config
- Remove stale changesets that only affected ignored packages
- Document web release flow in README
* style(web): equalize timestamp and undo button heights and alignment
* feat(web): make workspace names bolder to distinguish from session titles
* feat(kimi-web): rename themes to Explore/Native and remove accent selector
* style(web): nudge undo icon up by 1px
* style(web): align both meta actions to the right
* style(web): remove gap between undo and timestamp buttons
* style(web): nudge undo icon up by another 0.5px
* feat(web): tune workspace name font-weight to 500
* fix(kimi-web): center tag/question text and limit shell-cmd height in approval card
* style(kimi-web): remove icons from session pending tags
* feat(kimi-web): limit recent workspaces in empty-composer picker
* feat(kimi-web): rename sidebar new-workspace button to new-chat and adjust empty conversation title
* style(kimi-web): refine sidebar typography, spacing and font settings
* style(kimi-web): unify Composer typography with sidebar
- Use --ui-font-size for queue text and --ui-font-size-xs for queue labels/bubbles.
- Remove mono font-family from composer queue/bubble elements.
- Normalize perm/mode/model pill text color to --text.
- Set placeholder color to --muted.
* style(kimi-web): keep model pill color dimmed
Revert the model selector pill text color from --text back to --dim
so it stays visually secondary, matching the original design intent.
* style(kimi-web): adjust ChatHeader git status spacing and badge layout
* style(kimi-web): polish composer dock chip styles
* feat(kimi-web): refresh session list relative times on a 30s clock
* fix(kimi-web): decode base64 file content in the preview pane
* feat(kimi-web): show per-session answer/approve tags in the sidebar
* feat(kimi-web): pop the KAP debug panel out into a separate window
* feat(kimi-web): open subagent detail in the side panel; inline agent-live
* style(kimi-web): align DiffView focus outline with KMBlue
* feat(kimi-web): surface goal protocol errors; ignore global config-changed events
* feat(kimi-web): support video attachments in user messages end-to-end
* feat(kimi-web): add /swarm and /goal slash commands
* feat(kimi-web): add /btw side chat backed by child sessions
* style(kimi-web): pin user message bubble font size to 15px
* style(kimi-web): use Lucide PR icon and text-only git status in header
* fix(kimi-web): update undo tooltip copy and reduce hover delay
* fix(kimi-web): auto-scroll to bottom on send, session switch, and tab switch
- Scroll side-chat panel to bottom after sending and while streaming
- Reset scroll baseline on session switch to avoid stale lastScrollTop
- Reset scroll baseline when returning from files tab to chat
- Reset scroll baseline after user sends a message
- Include @moonshot-ai/kimi-code so CLI rebuilds bundle the updated web app
* fix(kimi-web): drop duplicate config-changed case shadowing the real handler
A stopgap no-op `case 'event.config.changed'` (added before the config
feature landed) ended up earlier in the switch than the real configChanged
mapper after merging origin/feat/web, silently swallowing config events.
Remove the no-op so the proper handler runs.
* style(kimi-web): unify PR badge with git status pills and drop changes count
* style(kimi-web): remove unused changes computed in ChatHeader
* fix: keep packaged web build in sync
Build kimi-web before copying packaged web assets and surface the build version plus short commit in the settings dialog.
* fix(web): support slash command input tails
* fix(web): scope composer dock to chat tab
* fix(web): route btw through side-channel agents
* feat(web): open changed files from git status
* feat(web): copy final assistant summary
* feat(web): add tabbed model picker
* fix(web): keep composer input height fixed
* fix(web): preview composer attachments
* feat(web): open workspace links in files tab
* fix(web): stabilize subagent progress
* docs(web): design tab split workflow
* feat(web): connect daemon config settings
* fix: resolve CI failures on feat/web
- add missing 'event.config.changed' case in exhaustive switch test\n- fix oxlint errors (unused import, string spread, unsafe stringification)\n- update protocol test fixtures for additions/deletions, open_in_apps, swarm_mode\n- fix services mcp transport switch exhaustiveness for sse\n- update nix pnpm deps hash
* fix(server): suppress debug logs by default
- route BridgeClientAPI and PromptService debug logs through ILogService instead of console.error\n- lower SessionClientsService debug logs from info to debug\n- add changeset for @moonshot-ai/services, @moonshot-ai/server and @moonshot-ai/kimi-code
* fix(kimi-web): remove model picker top blue bar and widen dialog
- Remove the inset blue box-shadow from the model picker header.
- Increase the default dialog width from 620px to 760px.
* fix(kimi-web): replace model picker checkmark with icon
- Swap the textual checkmark for a proper SVG check icon in ModelPicker,
matching the icon used in the composer model dropdown.
* fix(kimi-web): hide the Open in app menu
- Remove OpenInMenu usage from ChatHeader and the prop/event plumbing
through ConversationPane and App.
- Remove the now-obsolete test case in files-tab-no-git.test.ts.
* feat(kimi-web): scope composer dock to chat tab and polish BTW side chat
- Move the composer dock into the chat tab only so it no longer appears in
split file, task, preview, or BTW panes.
- Render the BTW side chat as a split side pane scoped to the active session,
and keep its messages out of the main conversation transcript.
- Remove the side-chat panel header, relabel the tab to Side chat / 侧边聊天,
and use the shared moon spinner while waiting for the first token.
- Suppress the generic Started a step progress text for side-channel agents.
* feat(server): expose live session status via HTTP and WebSocket
- add status field to session status response schema and event.session.status_changed\n- compute session lifecycle status in SessionService from approvals, questions, prompts, and turns\n- broadcast event.session.status_changed globally to all WebSocket connections\n- re-export new event types from agent-core\n- add e2e and unit tests for status computation and broadcasting
* fix(kimi-web): label sidebar session removal as Archive
* feat(kimi-web): make tab/split chrome accessible
TabBar is now a real ARIA tablist: role=tab buttons with aria-selected,
roving tabindex, Left/Right/Home/End keyboard nav, focus-visible styling,
and aria-controls wired to each ViewGroup's tabpanel (role=tabpanel +
aria-labelledby).
ViewGroup split-right/split-down/close buttons get localized aria-labels
(no longer English title-only) and a 28x28 hit area.
* fix(kimi-web): move auth banner into layout flow
The onboarding/auth banner was position:fixed over the top of the
conversation column, covering the desktop ChatHeader and the mobile
top bar. Wrap the app grid in a flex-column shell and render the banner
as the shell's first in-flow child so it reserves its own height above
both the sidebar/header and the mobile top bar instead of overlapping
navigation.
* fix(kimi-web): enlarge and label header/sidebar icon buttons
Unify icon-button hit areas and keyboard affordances:
- ChatHeader kebab: 28x28 target, aria-label + aria-expanded/haspopup,
focus-visible ring.
- Sidebar workspace kebab (.gh-more): 24x24 target, aria-label, stays
visible on keyboard focus (it was hover-only), focus ring.
- Sidebar per-workspace add (.gh-add): aria-label + larger tap target.
- Focus rings on the settings button and new-chat/new-workspace buttons.
* feat(kimi-web): give overlay dialogs modal focus management
Add useDialogFocus(): records the opener, moves focus into the dialog on
open, and restores focus to the opener on close. Wire it plus
aria-modal="true" / tabindex="-1" into ModelPicker, LoginDialog and
ProviderManager (Escape-to-close was already present). ModelPicker keeps
focusing its search box on open. Covered by a model-picker focus test.
SettingsDialog is intentionally left for a follow-up to avoid colliding
with in-flight settings work.
* feat(kimi-web): consistent rules for the right-side detail layer
The transient detail panels (thinking, compaction summary, subagent
detail, mobile file/media preview) now share one set of rules:
- the aside is a labelled role=complementary region (aria-hidden when
collapsed),
- Escape closes whichever panel is open — handled in App on the capture
phase so it takes precedence over the conversation's "Esc interrupts a
run" handler instead of firing both,
- every close button has an aria-label (not just a title) and a
focus-visible ring; thinking/subagent close targets bumped to 28x28,
- FilePreview toolbar buttons get focus-visible rings too.
* fix(kimi-web): calm the diff line colors
Added/removed diff lines washed the entire row in green/red (12% tint +
fully coloured text), which competed with reading the code. Drop the
background to a faint 7% tint plus a left accent bar, color only the +/-
sign, and let the code text keep the normal ink color so the content —
not the color wash — is what stands out.
* docs(web): record tab/split convergence + UI audit follow-through
Implementation note for the tab/split work: the final three-layer view
model (persistent chat/files tabs, transient preview/btw tabs, right-side
detail layer), the transient-view routing rules, a per-task status table,
which audit suggestions were absorbed vs intentionally skipped, and why
the SettingsDialog focus item is deferred (concurrent in-flight rewrite).
* feat(kimi-web): add side-tab navigation to SettingsDialog
* feat(session): persist session archive state and add include_archive list filter
- Replace deleteSession with archiveSession RPC and REST endpoint\n- Persist archived flag in session state and filter archived sessions by default\n- Add optional include_archive query parameter to list archived sessions\n- Expose archived flag on session responses through protocol and web types\n- Rename web session delete events/handlers to archive
* feat(kimi-web): give SettingsDialog modal focus management
Completes the dialog-focus baseline (task 8): wire useDialogFocus +
aria-modal/tabindex into SettingsDialog now that the side-tab rewrite has
landed. Focus moves into the dialog on open and returns to the opener on
close; covered by a settings-dialog focus test.
* refactor(workspace): centralize registry into a single workspaces.json
- replace per-bucket workspace.json files with one workspaces.json registry\n- serialize registry reads/writes through an opQueue to avoid races\n- delete now removes only the registry entry, leaving the session bucket intact\n- update workspace e2e tests and scenario for the new storage model
* feat(workspace): add workspace lifecycle WS events
- publish event.workspace.created/updated/deleted from the registry service\n- broadcast them to every connection via the __global__ watermark\n- add protocol types/schemas and frontend mapping for real-time workspace sync\n- cover with protocol, broadcast, and exhaustive-switch tests
* feat(fs): add fs:mkdir action for creating directories
- add fsMkdir request/response schemas and FS_ALREADY_EXISTS (40919) error code
- implement IFsService.mkdir guarded by resolveSafePath, returning the created directory entry
- register the mkdir action in the fs route dispatcher with EEXIST/ENOENT mapping
- add protocol schema tests and server e2e coverage
* style(kimi-web): set fixed heights for all modal dialogs
* feat(kimi-web): add collapsible sidebar
* refactor(web): temporarily hide new workspace button in sidebar
* feat(kimi-web): add starred models support to model picker and composer dropdown
- Persist starred model ids in localStorage via useKimiWebClient.
- Pin starred models to the top of the All tab in ModelPicker.
- Show a Starred section in the Composer quick-switch dropdown, including models from other providers.
- Render a star glyph on each starred model row in both pickers.
- Add --star CSS variable with a brighter yellow across themes.
- Add tests for starred model ordering and Composer dropdown rendering.
* test(kimi-web): cover chat dock composer alignment
* feat(server): expose GitHub pull request in git status and web header
Add a pullRequest field to the session fs:git_status response, looked up via gh pr view with a 5s timeout, GH_NO_UPDATE_NOTIFIER/GH_PROMPT_DISABLED, and a 60s per-cwd cache.\nNormalize gh state to open/merged/closed and fail soft to null so git status never breaks.\nWire the web chat header PR badge to the active session.
* feat(kimi-web): surface 5-state session status with a separate busy flag
The session view-model collapsed every lifecycle state to running|idle,
so awaiting-input and aborted sessions were indistinguishable and the
spinner span while a session was actually waiting on the user.
Session now carries the real `status` (idle/running/awaitingApproval/
awaitingQuestion/aborted) plus a separate `busy` flag (running + a real
task in flight). SessionRow spins only when busy, shows awaiting tags
from status as a fallback for background sessions, and a distinct aborted
tag; SessionsDialog and MobileSwitcherSheet distinguish the states too.
(The producers in useKimiWebClient already landed via an earlier commit;
this adds the Session type, the UI, and labels so the tree type-checks.)
* fix(kimi-web): persist unread dots across a page reload
unreadBySession was pure in-memory state seeded empty on every load, with
no localStorage persistence and no server-side read cursor — so a browser
refresh dropped every sidebar unread dot. Persist the `true` entries to
localStorage (compact: only unread sessions are stored) and seed the map
from storage on init; opening a session clears the flag and the stored
entry. Covered by a reload test in the session-cache suite.
Note: also carries pre-existing empty-session-flash test edits that were
already part of this file's working state.
* style(kimi-web): redraw collapse/expand sidebar icons
Use an indent-style glyph: three lines with a directional chevron, mirrored between the collapse (left) and expand (right) states.
* feat: add server-hosted web UI and document its packages
- wire kimi-web into root and CI typecheck (vue-tsc) and refresh the Nix pnpm hash
- consolidate per-feature changesets into a single server-hosted-web-ui entry
- make agentEventProjector.shortJson resilient to stringify failures and adjust tests
- add AGENTS.md for kimi-web and server; add READMEs for server and services
- expand root AGENTS.md project map and clarify the flake.nix workspace-sync rule
* test(kimi-web): cover empty-session flash + draft-send paths
Tests and changeset for the empty-session-flash fix (the sessionsKnownEmpty
/ sessionLoading logic itself already landed in an earlier commit):
selecting a locally-created session shows the empty composer with no
loading flash, an existing session reported as empty still loads its
snapshot (messageCount is not trusted), and sending straight from the
draft composer does not flash the empty state.
* chore: ignore generated docs and reports
* style(apps/kimi-web): remove app shell top border
* fix(build): build kimi-web assets before native SEA build
The native SEA build embeds the Kimi web SPA from apps/kimi-code/dist-web (see scripts/native/02-sea-blob.mjs) and fails when that directory is missing. Build kimi-web and stage its assets via copy-web-assets.mjs before running build:native:sea.
- flake.nix: add the web build + asset copy to buildPhase so `nix build` works.
- _native-build.yml: add the same prep step before the SEA build so CI release / manual native bundles keep working.
Fixes "Kimi web build output was not found at .../dist-web" in the nix build and the CI native build stage.
* feat(web): hide context indicator on empty session composer
* feat(kimi-web): unify detail panel layout
* fix(web): improve dark toc tooltip contrast
* fix(web): keep markdown code blocks mounted
* fix(web): tighten dark color contrast
* fix(web): close dock cards on outside click
* feat(web): show session content summaries
* fix(web): report web client telemetry
* refactor(services): merge @moonshot-ai/services into agent-core
- move services/src/** into agent-core/src/services/** and delete the standalone @moonshot-ai/services package
- re-export service contracts/implementations from agent-core src/index.ts
- update all server, test, and kimi-web imports to @moonshot-ai/agent-core
- enable experimentalDecorators in tsconfig and adjust dev/build configs
- sync workspace registry (changeset config, flake.nix, pnpm-lock)
* refactor(server): remove Swagger UI and --swagger flag
- drop @fastify/swagger-ui and the dev-only --swagger option
- keep /openapi.json via @fastify/swagger (bundled in the SEA)
- simplify the native SEA build (no swagger-ui external or asset copy)
- update tests, docs, and lockfile
* feat(server): add on-demand daemon for kimi web with idle shutdown
- make kimi web non-blocking by spawning or reusing a single detached daemon per device (via ~/.kimi-code/server/lock), auto-picking a free port on conflict
- daemon self-exits after a 1-minute grace once the last web WebSocket client disconnects (new onConnectionCountChange hook + createIdleShutdownHandler)
- add getLiveLock helper for daemon discovery
- hide kimi server install/uninstall/start/stop/restart/status (service-ization) for now; implementation preserved for later re-exposure
* fix(web): improve mobile dialog layouts
* fix(ci): resolve lint, typecheck, and nix build failures
- add startBtw to IPromptService test mocks (agent-core, server)
- remove useless spread in PromptService session cleanup
- add assertion to concurrent-connection WS handshake test
- bind idle onConnectionCountChange callback in server run
- type getSessionSnapshot mock via vi.mocked in kimi-web test
- update pnpmDeps hash in flake.nix
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(web): distinguish native markdown links
* fix(vis-web): enable experimentalDecorators for typecheck
vis-web type-checks agent-core source (via source exports), whose services use legacy parameter decorators for DI. Without experimentalDecorators, tsc reports TS1206 "Decorators are not valid here" and the CI typecheck job fails.
* Revert "feat(web): show session content summaries"
This reverts commit 8de58eacbc.
* fix(web): align active toc-bubble highlight with bubble edges
* fix(web): hide conversation toc when chat pane is too narrow
* feat(server): add `kimi server ps` to list active clients
- add GET /api/v1/connections endpoint backed by IConnectionRegistry
- record connection metadata (connectedAt, remoteAddress, userAgent) on WsConnection
- add connection wire schema in @moonshot-ai/protocol
- add kimi server ps CLI command with table and --json output
* feat(kimi-web): add queue chip to chat dock workbar
* fix(agent-core): hide console window when spawning git/gh on Windows
On Windows, spawning a console-subsystem executable (gh.exe / git.exe)
from the background Kimi server creates a visible console window that
flashes on screen. Set windowsHide: true (a no-op on POSIX) to suppress it.
* feat(server): add startup and ws connection telemetry for kimi web
- bootstrap telemetry in `kimi web`/`kimi server run` via initializeServerTelemetry (ui_mode=web, honors telemetry=false)
- wire the real client into KimiCore so agent-core events carry the enriched context
- emit server_started after the server listens; flush telemetry on shutdown
- emit ws_connected/ws_disconnected from WSGateway via WSGatewayOptions.telemetry
- re-export loadRuntimeConfigSafe/resolveConfigPath from the SDK for host config reads
* feat(web): dynamic page title based on session or workspace
* fix(web): show recently active sessions at the top of the web session list
* feat(web): show running indicator in dynamic page title
* feat(kimi-web): show elapsed time for completed assistant turns
* fix(kimi-web): resolve TDZ error on App mount
* refactor(kimi-web): align turn duration and support multi-tab timing
* feat: display per-turn wall-clock duration in web chat
* feat(web): use animated spinner in page title while running
* feat(server): add kill command and background server run
- add `kimi server kill` to stop the running daemon (graceful API + forced PID kill)
- add `POST /api/v1/shutdown` so the server can terminate itself
- make `kimi server run` start in the background and print the ready banner
- route `kimi web` through the same path as `server run` so it prints the banner too
* fix(server): remove duplicate startBtw key in prompt e2e test
Resolves eslint no-dupe-keys and TS1117 errors that broke the lint and typecheck CI jobs.
* chore: bundle Inter font locally
* fix(nix): update pnpmDeps hash for new font dependency
The local Inter font dependency changed pnpm-lock.yaml, so refresh the fixed-output derivation hash to match what the nix builder computes. Resolves the nix build .#kimi-code CI failure.
* fix(server): restore web client telemetry and stabilize skills cleanup
Restore forwarding of x-kimi-client-* headers into session creation telemetry, which was dropped during the services-to-agent-core merge and left the new-session telemetry test with empty records.\n\nRetry the skills e2e sandbox cleanup to ride out ENOTEMPTY races when the core process flushes files into the sandboxed home after close().
* chore: remove trailing blank lines
* chore(changeset): remove consumed changeset files
These 20 changeset files were applied during the version bump and are no longer needed.
* chore: clean up web release changesets
* docs: clean up kimi web readme
* chore: scope package lint to cli release
* chore: remove temporary design docs and preview files from PR
Remove files that were not intended for submission:
- docs HTML research/design archives
- docs/superpowers specs added during design phase
- apps/kimi-web icon preview pages and one-off test script
* chore(kimi-web): remove dev stub daemon
The real server package is now available; the throwaway stub daemon
is no longer needed for development.
* test(server-e2e): accept aborted image prompt scenario
* chore: update flake.nix workspace paths and add new changesets
- Added new packages: daemon, server-e2e, and kimi-migration-legacy to the workspacePaths in flake.nix.
- Introduced new changeset for "@moonshot-ai/kimi-code-sdk" to add host-side config helpers.
- Removed outdated changesets related to server-hosted web UI and server web APIs.
* feat(server): daemonize by default and fall back to port +1
- kimi server run now spawns a background daemon by default; --foreground keeps the terminal attached
- default server port moves from 7878 to 58627 across CLI, web, e2e, and docs
- listenWithPortRetry retries on port + 1 when a third party holds the port (capped at 100)
- lock gains updatePort so status/kill/ps find the daemon on its real bound port
* fix(cli): resolve oxlint unbound-method errors in server run
---------
Signed-off-by: qer <wbxl2000@outlook.com>
Co-authored-by: qer <wbxl2000@outlook.com>
Co-authored-by: Claude <noreply@anthropic.com>
* feat: polish vis
* feat: add 'kimi vis' command for session visualization
* fix(vis): drop metadata app_version/resumed removed upstream
#786 stopped recording resume version metadata, so those fields no
longer exist on the metadata wire record. The vis-into-typecheck wiring
caught the stale field reads after merging main; drop them from the
metadata headline.
* fix(vis): drop unnecessary return-await in startVisServer
oxlint typescript-eslint(return-await) flags returning an awaited
promise outside try/catch; return the promise directly.
* fix(vis): green CI — tolerate unbuilt embedded asset + bump nix pnpmDeps hash
- handleVis: wrap the embedded-SPA dynamic import in try/catch. The value
module is generated at build time (prebuild); in contexts without a build
(tests run pnpm test, not build) only the .d.ts type stub exists, so the
runtime import throws. Tolerate it and fall back to filesystem serving.
- flake.nix: update the fetchPnpmDeps hash after adding the vis-web /
vis-server / vite-plugin-singlefile dependencies.
* refactor(vis): drop redundant alwaysBundle in tsdown config
#775's single-entry build (codeSplitting: false) already bundles
everything not declared in dependencies/peerDependencies. hono /
@hono/node-server (transitive via vis-server) and @moonshot-ai/vis-server
(a devDependency) are all undeclared there, so they bundle by default —
the explicit alwaysBundle was redundant. Verified the emitted main.mjs is
still fully self-contained and 'kimi vis' serves.
* fix(vis): address review — context-token resets, IPv6 url, marker-safe indexing
- contextTokens now mirrors agent-core on lifecycle records: 0 on
context.clear, tokensAfter on context.apply_compaction (was only
updated from step.end.usage, leaving a stale live fill after a
clear/compaction).
- start.ts brackets IPv6 hosts in the returned url (http://[::1]:port/);
hostForUrl moved to config.ts and shared with the startup banner.
- compaction slice + micro-compaction blanking now index over real
history entries only, so synthetic undo/clear UI markers no longer
offset agent-core's compactedCount / cutoff.
* chore: add changeset for kimi vis command
* fix(vis): cross-platform single-file build + history-count micro clamp
- build-vis-asset.mjs sets VIS_SINGLEFILE via the spawn env and runs
'vite build' directly (cross-platform), instead of the POSIX-inline-env
'build:single' script that broke on Windows cmd; removed the now-unused
build:single script. Fixes the win32 build path (the asset generator
runs in the kimi-code prebuild + native bundle).
- context.undo now clamps the micro-compaction cutoff by history-entry
count (excluding synthetic undo/clear markers) instead of messages.length,
mirroring agent-core undo() -> microCompaction.reset(_history.length); a
surviving marker no longer leaves the cutoff one too high and wrongly
blanks a later-appended tool result.
* fix(vis): run the single-file build through a shell for Windows pnpm
The win32 native binary is built on Windows runners
(.github/workflows/_native-build.yml), which run this generator. pnpm's
launcher there is pnpm.cmd, which a bare argv exec can't resolve without
a shell. Use execSync with a single command string so the platform shell
(cmd on Windows) resolves the shim; a command string (not an args array)
avoids the args+shell deprecation. Args are static.
* fix(vis): show model-facing tool result content in the context view
agent-core normalizes tool results via toolResultOutputForModel before
they enter history (error -> '<system>ERROR: ...' prefix, empty ->
'<system>Tool output is empty.' sentinel). The projector was using the
raw ev.result.output, so the Context tab's model view showed content the
model never saw for failed/empty tool calls. Replicate that normalization
(the upstream helper is module-private) so the projected tool message
matches what the model received.
* feat: honor HTTP_PROXY/HTTPS_PROXY/NO_PROXY for all outbound traffic
Install a global undici dispatcher at CLI startup so every in-process fetch
(LLM APIs, MCP HTTP, web tools, telemetry, sign-in, update checks) honors the
standard proxy variables, and propagate NODE_USE_ENV_PROXY to spawned stdio
MCP child processes. Loopback hosts always bypass the proxy; an invalid proxy
URL is reported and ignored rather than aborting startup.
* feat: support SOCKS proxies via ALL_PROXY
Recognize SOCKS proxies (socks5/socks5h/socks4/socks alias) from ALL_PROXY or a
socks-scheme HTTP(S)_PROXY, routing traffic through a custom undici connector
backed by the socks client (reusing undici's own TLS handling for https).
HTTP(S) proxies keep precedence; NO_PROXY and loopback are honored for the SOCKS
path too. Child stdio MCP node processes honor HTTP(S) proxies via
NODE_USE_ENV_PROXY; SOCKS applies to the main process only.
* fix: address proxy review comments (env masking, child NO_PROXY, nix hash)
- Resolve HTTP(S)_PROXY explicitly via the first non-blank casing so a blank
lowercase var can no longer mask a populated uppercase one (the dispatcher
installed but went direct), and coerce a SOCKS-scheme value sitting in an
HTTP(S) var to '' so it is never handed to EnvHttpProxyAgent.
- Reconcile a child's NO_PROXY override across both casings using the first
non-blank value run through resolveNoProxy, so a per-server config override
is not shadowed by the injected lowercase value, keeps the loopback bypass,
and passes '*' through verbatim.
- Update flake.nix pnpmDeps hash for the added socks/undici dependencies.
* fix(proxy): honor http ALL_PROXY, match port-qualified NO_PROXY, note child Node version
- Honor an http-scheme ALL_PROXY as the catch-all fallback for both http and
https (scheme-specific HTTP(S)_PROXY still wins), so an ALL_PROXY-only setup
no longer installs a no-op dispatcher and connects direct.
- Make the SOCKS-path NO_PROXY matcher port-aware: a `host:port` entry now
matches only that port (with IPv6-safe parsing for `::1` / `[::1]:443`).
- Document that child stdio MCP proxying via NODE_USE_ENV_PROXY only applies on
Node versions that support it (>= 22.21 / >= 24.5).
* fix(proxy): IPv6 + wildcard NO_PROXY and per-server child proxy edges
- Strip IPv6 brackets from a SOCKS proxy host (e.g. ALL_PROXY=socks5://[::1]:1080)
so the socks client connects to the bare address.
- Add the bracketed [::1] to the loopback bypass: undici's EnvHttpProxyAgent
only exempts IPv6 loopback when the NO_PROXY entry is bracketed (it mis-parses
bare ::1). The SOCKS-path matcher normalizes brackets on both sides.
- Match *.domain wildcard (and host:port) NO_PROXY entries in the SOCKS matcher.
- Compute the child stdio proxy env from the MERGED env so a proxy declared only
in a server's config.env also enables NODE_USE_ENV_PROXY.
* fix(proxy): synthesize HTTP(S)_PROXY from ALL_PROXY for child processes
proxyEnvForChild now hands spawned stdio MCP children the resolved
HTTP_PROXY/HTTPS_PROXY (in both casings), synthesizing them from an http-scheme
ALL_PROXY when no scheme-specific variable is set. Node's --use-env-proxy reads
HTTP_PROXY/HTTPS_PROXY (not ALL_PROXY), so an ALL_PROXY-only parent now proxies
the child consistently with the main process. Shared resolveHttpProxyUrls helper
is reused by createProxyDispatcher and proxyEnvForChild.
* chore(changeset): tighten proxy changeset wording
* fix(nix): expose ripgrep and fd to wrapped kimi
- make wrapper available to the Nix build
- wrap kimi binary with ripgrep and fd on PATH
- include rg and fd in the dev shell packages
* chore(nix): add changeset for ripgrep and fd
This commit scaffolds the @moonshot-ai/acp-adapter package and introduces
the full ACP (Agent Communication Protocol) server implementation for
Kimi Code CLI, including:
- Scaffold @moonshot-ai/acp-adapter workspace package with build skeleton
- `kimi acp` CLI subcommand and stdout-safe logging
- ACP version negotiation and AgentSideConnection wrapper
- Auth gate for session creation
- Session lifecycle: new, list, load with history replay
- Prompt content conversion (text, image, embedded resources, resource links)
- Assistant streaming with thinking support and end-turn handling
- Tool call streaming (started, delta, progress) with result conversion (text / diff)
- Approval handling with diff/text display blocks mapped to ACP options
- Kaos read/write interface (AcpKaos) for unsaved buffer access
- Session mode (yolo/auto) and model management
- Config options builder with thinking toggle
- MCP server forwarding from ACP to harness
- Agent plan updates and available commands updates
- AskUserQuestion bridged to session/request_permission
- Plan review options surfaced through requestPermission
- Error mapping, ext_method stubs, and graceful shutdown
- IDE integration guide (Zed + JetBrains)
- End-to-end tests against ACP TS SDK client
* chore(flake): simplify nix build and add ci validation
- Replace dynamic pnpm-workspace.yaml parsing with hardcoded workspacePaths
and workspaceNames to reduce format assumptions
- Remove update-pnpm-deps script and kimi-code-pnpm-deps package; use
lib.fakeHash for standard hash mismatch workflow
- Remove nodejs_latest fallback in nodejsFor, hardcode to nodejs_24
- Add nix-build CI workflow that posts hash-mismatch details to PR comments
- Remove unused Nix installation step from release.yml
- Add workspace maintenance note to AGENTS.md