* feat(vis): surface background tasks and cron jobs
The visualizer read every wire/state/blob artifact a session persists but
ignored the two on-demand families agent-core also writes under the session
directory: background tasks (tasks/<id>.json + output.log) and cron jobs
(cron/<id>.json). Neither is reconstructable from the wire, so there was no
way to inspect what a session spawned in the background or scheduled.
Server:
- task-store / cron-store read-only readers mirroring agent-core's on-disk
layout, id-validation guard, and legacy snake_case task normalization
- GET /:id/tasks, /:id/tasks/:taskId/output (byte-window paged via an exact
nextOffset cursor), and /:id/cron routes
- re-export the public background-task types from agent-core; mirror the
non-exported CronTask shape with a fixture-backed drift test
Web:
- Tasks tab: process/agent/question kinds with status, timing, kind-specific
fields, raw JSON, and a progressively paged output.log viewer
- Cron tab: expression, prompt, recurring/one-shot, created/last-fired
- count badges on both tabs
Tests: +20 (lib + route), all 113 vis-server tests green; web typecheck and
build clean.
* feat(agent-core): persist step retries and tool progress summary
Two transient signals were only ever emitted as live-only loop events, so
nothing survived in the agent record for post-hoc analysis:
- step retries: chatWithRetry gains an onRetry callback; turn-step collects
the recovered attempts and attaches them to step.end as an optional
`retries` array (previously only the live `step.retrying` event).
- tool progress: tool-call distills a tool's sparse status/percent updates
into a bounded `progress` summary (updateCount / lastStatus / maxPercent)
on tool.result. Streamed stdout/stderr is excluded — it would bloat the
wire and is already reflected in the result output.
Both are additive optional fields, so the wire protocol version is unchanged
and existing records keep loading. New public types: LoopStepRetryRecord,
LoopToolProgressSummary.
* feat(vis): add execution-analysis timeline and surface retries/progress
Turn the debugger from a flat record viewer into an analysis tool.
New Timeline tab: folds the wire into turns → steps → tool calls (client-side,
no extra round-trip) and derives the metrics the raw list hides — per-turn /
per-step / per-tool duration, per-turn token cost, a context-window fill
sparkline with cache-hit rate, a tool usage table, idle-gap detection, and a
config-change timeline.
Inline elsewhere:
- Wire rows show tool.call → tool.result elapsed time; tool.result detail
shows truncation, output size, retries, and the progress summary.
- Issues drawer gains tool-error, truncation, filtered, max_tokens, and
retried categories.
- Tasks tab links agent-kind tasks to the subagent's wire.
Wires up vitest for the web package and adds analysis/issues unit tests.
* feat(vis): import debug zips with a logs view and imported-session filtering
A `/export-debug-zip` bundle is just `manifest.json` plus a flattened session
directory, which vis already knows how to read. Importing one therefore lights
up every existing tab for a session that lives on someone else's machine.
Server:
- zip-import: yauzl extraction with zip-slip path guards and entry-count /
uncompressed-size caps for untrusted uploads.
- import-store: extract a bundle into <home>/imported/<imp_…>/, validate it
has a main wire, and record an import-meta.json sidecar.
- session-store resolves imp_-prefixed ids against imported/, so wire /
context / tasks / cron / blobs / logs all work on imported sessions; agent
homedirs are re-derived locally (the bundle holds foreign absolute paths).
- POST /api/imports (raw zip body) and GET /api/sessions/:id/logs (structured
log lines — also available for local sessions).
Web:
- session rail: import button + all/local/imported filter + imported badge.
- new Logs tab: virtualized, level filter, search, session/global toggle.
- manifest card atop the State tab for imported sessions.
SessionSummary/SessionDetail gain `imported` + `importMeta`. Tests cover
extraction, the zip-slip guard, list merge, reading an imported wire through
the existing route, and log parsing.
* fix(vis): read tasks/cron from agent homedirs and stop persisting tool status text
Addresses review feedback on the debug-tooling changes:
- Background tasks and cron jobs are persisted under each agent's homedir
(<session>/agents/<id>/tasks and /cron), not the session root. The Tasks and
Cron tabs read the session root, so they showed nothing for normal sessions.
Both routes now aggregate across detail.agents homedirs; task entries carry
the owning agentId. The route-test fixtures were writing to the wrong
(session-root) location too — corrected to the real agents/main layout so
they actually exercise the path.
- tool.result progress no longer keeps free-form status text, only updateCount
and maxPercent. A tool's status string can contain sensitive data (e.g. an
MCP OAuth authorization URL) that must not leak into persisted wire files or
exported debug bundles.
* fix(vis): stop the main content area from overflowing horizontally
The <main> flex child lacked min-w-0, so it defaulted to min-width:auto and
refused to shrink below its content's intrinsic width. Tabs that lay out in
normal flow with flex-wrap rows (the Timeline tab) then got unbounded width,
never wrapped, and blew the layout out to thousands of pixels wide. Adding
min-w-0 lets the column shrink to the available width so its content wraps,
truncates, or scrolls within its own container.
* fix(vis): resolve local global log path and imported-state agent fallback
- Logs tab: for non-imported sessions the shared global log lives at
<KIMI_CODE_HOME>/logs/kimi-code.log, not under the session dir (that path is
only used inside exported bundles). The route now reads the home path for
local sessions, so the global-log toggle works for them.
- Imported detail: a bundle's state.json is best-effort and may omit the
agents map. When the inventory is empty, fall back to discovering agents
from disk so routes that require an agent (wire/context) still resolve main.
* fix(vis): harden imported manifest and task parsing against corrupt input
An imported debug zip is untrusted, so a syntactically valid but type-corrupt
file could crash whole views:
- manifest.json: a non-string field (e.g. workspaceDir: 123) flowed into
SessionSummary.workDir, where the session rail calls .split('/') and crashed
the entire list. readManifest/readImportMeta now sanitize declared string
fields, keeping only strings.
- task JSON: a record that passed the shape guard but held a non-string legacy
field (e.g. stop_reason: 5) threw in normalization, failing GET /tasks with a
500 and hiding all of a session's tasks. optionalNonEmptyString now tolerates
non-strings, and listBackgroundTasks skips any record that still fails to
normalize — honouring the reader's documented silently-skips contract.
* fix(vis): discover rotated logs; keep tool progress on thrown failures
- Logs tab: the diagnostic log can rotate (kimi-code.log.1, .2, …) and an
exported bundle may contain only the archives. The route now discovers the
active file plus its rotated siblings and concatenates them oldest-first, so
a rotated-away log still surfaces (covered by node-sdk's rotated-export case).
- agent-core: a tool that reported sparse progress and then threw lost its
progress summary, because the catch path built the error tool.result without
it. Thread progressSummary through that path too, matching the success and
malformed-return paths.
* fix(vis): skip type-corrupt agent entries in imported state
readImportedDetail's empty-inventory fallback never ran when a bundle's
state.json had a non-empty but type-corrupt agents map (e.g.
`{ "agents": { "main": null } }`): inventoryAgents dereferenced the null entry
and threw, so readSessionDetail returned 500 instead of recovering main from
the on-disk agents/main/wire.jsonl. inventoryAgents now skips non-object
entries, letting the disk-discovery fallback take over.
* fix(vis): reset timeline agent on session change; preserve context on zero-usage steps
- Timeline tab kept the previously-selected agent id across session navigation,
so a subagent selection would 404 against the next session. Reset it to main
on sessionId change, mirroring WireTab/ContextTab.
- A zero-usage step.end (e.g. a content-filtered response) reset the
context-window fill to 0, pushing a false drop into the Timeline chart and the
Context tab. agent-core's ContextMemory keeps the prior count in that case;
the analysis lib and the context projector now do the same.
* revert: drop agent-core retries/tool-progress persistence
These were the only changes in this branch that touched agent-core. They
persisted two previously live-only signals (step retries, tool progress) to
the wire purely so the visualizer could display them — marginal features that
did not justify modifying the core loop or extending the wire surface.
Reverts the agent-core loop/type/export changes (restored to main, keeping
#1209) and its changeset, and removes the vis-side rendering and types that
consumed step.end.retries / tool.result.progress. The rest of vis is unchanged
and reads only data agent-core already persists.
* 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(agent-core): re-export wire record types for in-monorepo consumers
* chore(vis): purge legacy wire protocol code
* feat(vis): introduce single-source agent-record types
* test(vis): add fixture session and builder helper
* feat(vis): implement new session store reader
* feat(vis): wire new session list/detail routes
* refactor(vis): drop legacy path config
* feat(vis): adapt session list page to new DTO
* feat(vis): implement per-agent wire reader
* feat(vis): rewrite wire route for new protocol
* feat(vis): rewrite wire type metadata for new protocol
* feat(vis): rewrite wire row + headline for new record union
* feat(vis): wire tab detail panel + multi-agent selector
* feat(vis): rebuild wire issues detection for new protocol
* feat(vis): implement context projector
* feat(vis): rewrite context route on projector
* feat(vis): rebuild context tab for new ContextMessage shape
* feat(vis): implement agent tree builder
* feat(vis): rewrite agents route
* feat(vis): rebuild subagents tab around state.json.agents
* feat(vis): rebuild state tab on raw state.json
* chore(vis): purge residual legacy field references
* vis: rewrite complete on new agent-core protocol
* fix(vis): adapt to wire protocol 1.1 with flattened tool calls
* fix(vis): populate workDir from session index
* fix(vis): return broken-state sessions from detail lookup
* fix(vis): tolerate per-session wire read failures during listing
* fix(vis): read wire files from canonical session path
* feat(vis): restore session detail page with full tab layout
* feat(vis): wire subagent context tab to real ContextTab
* fix(vis): sync wire and context tab agentId with prop changes
* feat(vis): auto-pick a free dev port when 3001 is busy
* feat(vis): accept v1.0 wire files via agent-core migration chain
* refactor(vis): default API to 5174 and pick a non-colliding vite port
* feat(vis): make long strings expandable with copy in JsonViewer
* fix(vis): stop gating session health on protocol version
* refactor(vis): split wire records into raw + projected; best-effort unknown protocol
* feat(vis): pair tool.call with tool.result via inline cross-reference and hover highlight
* feat(vis): open session folder and copy its path from the detail header
* fix(vis): reconstruct assistant and tool messages from loop events
* fix(vis): keep system prompt bubble within the message column width
* feat(vis): collapse tool result bubble by default in context view
* feat(vis): expose broken_main_wire in the session health filter
* fix(vis): reset wire and context tab agent when navigating sessions
* fix(vis): fall back to a generic headline for unknown wire record types
* fix(vis): emit compaction summary as an assistant message with origin
* fix(vis): harden session-store reads for broken wires, broken state, and path-traversal agent ids
* feat(vis): inline image previews for image_url content parts