Commit graph

463 commits

Author SHA1 Message Date
liruifengv
659062d11c
fix(tui): enable file path completion for / in shell mode (#1225)
Typing `/\' in shell mode (`!\') now triggers file path completion instead of the slash command menu, for both a bare leading `/\' and inline paths like `ls /\'. Hidden entries are skipped to match `/add-dir\', and accepting a completion no longer produces a double leading slash.
2026-06-30 17:54:24 +08:00
qer
a3f9cec8a9
fix(web): deduplicate workspaces shown in the sidebar (#1221)
Collapse registered workspaces that share a root in the daemon registry (preferring the canonical id) and in the web sidebar merge, so the same folder no longer renders as two identical, synchronously-selected entries.
2026-06-30 17:25:24 +08:00
liruifengv
ec51324230
feat(tui): open undo selector on double-Esc (#1220)
* feat(tui): open undo selector on double-Esc

Pressing Esc twice while idle now opens the undo selector, equivalent to running /undo with no arguments. Esc during streaming, compaction, or with a popup open keeps its cancel/close behavior and does not arm the double-press.

* fix(tui): disarm double-Esc undo on any intervening key

A pending double-Esc was only cleared by text changes, so a sequence like Esc, Ctrl-C, Esc within the window still opened the undo selector. Fire an onNonEscapeInput hook for every non-Escape key and clear the pending state there, so the shortcut only triggers for two consecutive Escape presses.
2026-06-30 15:42:27 +08:00
liruifengv
80e6888e34
fix(tui): open @ file mentions inside slash command arguments (#1223)
Typing @ in the middle of a slash command argument (for example `/goal Fix the @checkout docs`) was swallowed by the slash-argument completion guard before the @ mention branch ran, so the file list never opened. Run the @ mention branch ahead of the slash guards so file mentions take priority; plain slash-argument editing is still suppressed as before.
2026-06-30 15:35:19 +08:00
Kai
525fb146d6
feat(vis): full session debugging — tasks/cron, execution timeline, retries & tool progress (#1210)
* 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.
2026-06-30 13:40:37 +08:00
github-actions[bot]
b41f108584
ci: release packages (#1197)
Some checks are pending
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-30 12:37:57 +08:00
Haozhe
14d9e98903
feat(server): auto-refresh provider model catalog and push change events (#1207)
* feat(server): auto-refresh provider models and push change events

- add scheduled provider-model refresh in the daemon (configurable
  interval + refresh-on-start) plus manual endpoints:
  POST /providers:refresh and POST /providers/{id}:refresh
- publish global event.model_catalog.changed when a refresh changes
  the catalog so connected clients can resync
- extract the refresh orchestrator into @moonshot-ai/kimi-code-oauth so
  the CLI and server share managed/open-platform/custom-registry logic
- wire the web daemon client to the new refresh endpoints

* chore: add changeset for provider model auto-refresh

* fix(web): reload model and provider caches on catalog change events

When the daemon's scheduled refresh changes the catalog, the pushed
event.model_catalog.changed only advanced the websocket sequence, leaving
the web composer's model/provider refs stale until an unrelated reload.
Reload both caches when the event arrives.

* test(sdk): cover event.model_catalog.changed in event exhaustiveness
2026-06-30 12:29:10 +08:00
qer
636ccc40f1
fix(web): fix mobile Safari composer toolbar overlap and focus zoom (#1212)
* fix(web): keep composer visible above the mobile Safari toolbar and keyboard

* fix(web): scope the mobile Safari composer fix to the toolbar case

* fix(web): prevent page zoom when focusing the mobile composer
2026-06-30 12:15:14 +08:00
7Sageer
c82dcf9cd8
refactor(agent-core): use ripgrep for Glob tool (#1068)
* refactor(agent-core): use ripgrep for Glob tool

Glob now shares Grep's ripgrep subprocess plumbing: it respects .gitignore by default, supports brace patterns natively, adds an include_ignored option, and returns only files.

* fix(glob): address review findings on ripgrep migration

- Run rg with cwd pinned to the search root so glob patterns containing
  a slash (e.g. src/**/*.ts) match under an absolute search root.
- Keep include_dirs as a deprecated, ignored parameter so older calls
  are not rejected by parameter validation.
- Surface stdout truncation and drop half-written trailing paths when
  the rg output buffer is capped.
- Document that a bare pattern (e.g. *.ts) matches recursively, and sync
  user docs, the explore profile prompt, and the TUI summary to the new
  files-only / gitignore behavior.
- Add real-ripgrep integration tests covering sort order, recursion,
  brace patterns, and the absolute-search-root case.

* fix(glob): keep partial results on traversal errors

---------

Co-authored-by: hynor <hynor@users.noreply.github.com>
Co-authored-by: Kai <me@kaiyi.cool>
2026-06-29 17:40:58 +08:00
7Sageer
10ffb7d9f9
chore(telemetry): normalize telemetry property keys to snake_case (#1196)
- Rename camelCase telemetry keys to snake_case on compaction_finished, compaction_failed, micro_compaction_finished, and the tool error event (tokens_before, tokens_after, compacted_count, retry_count, thinking_level, error_type, input_tokens/output_tokens, and the micro compaction config/effect keys).

- Emit a fixed client-attribution key set (client_id/name/version/ui_mode, null when absent) from both session_started producers (core-impl and kimi-harness) so they share a stable schema.

- Drop the duplicate current/latest keys on update_prompted and the redundant ui_mode on server_started.

- Additive fields: login.method=oauth and question_answered.answered.

Telemetry-only change; no changeset.
2026-06-29 17:40:50 +08:00
liruifengv
0df1812502
fix: render provider HTML error messages instead of blank lines (#1191)
When a provider returns an HTML error page (e.g. nginx 413 Request Entity Too Large), the error message carried CRLF line endings and raw HTML. The trailing carriage returns made the TUI render the error line as blank. Extract the page title for the wire message and strip carriage returns before rendering.
2026-06-29 15:49:48 +08:00
github-actions[bot]
52e2719d17
ci: release packages (#1154)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-29 14:49:34 +08:00
liruifengv
04b3492e74
fix(tui): keep working tips out of the agent swarm progress line (#1189)
* fix(tui): keep working tips out of the agent swarm progress line

The activity loader is shared between the activity pane and the agent swarm progress status line. Tips were written into the loader, so they leaked into the swarm progress line and got squeezed against the bar. Keep the inline spinner text used by the swarm progress line free of tips, while the loader's own row in the activity pane still shows them.

* test(tui): stop moon loader timers in tests

MoonLoader starts a real setInterval in its constructor. Stop every loader created in the tests via afterEach so no live timer leaks past the file.
2026-06-29 14:21:16 +08:00
liruifengv
db5fbc53c0
fix(tui): stop full-screen redraw on input and slash panel toggle (#1188)
Remove the global clear-on-shrink setting so typing in the input box and opening or closing the slash panel no longer trigger a full-screen redraw.
2026-06-29 13:51:31 +08:00
liruifengv
97f9263c6f
fix(tui): clear debug timing status on undo (#1187) 2026-06-29 13:31:08 +08:00
7Sageer
49e93893a6
refactor: standardize telemetry property names (#1184)
Align outlier telemetry events with the conventions already used by
tool_call, api_error, permission_approval_result, and the plan events:

- duration / latency_ms / duration_s -> duration_ms
- success boolean -> outcome enum ('success' | 'error')
- bare type -> provider_type

The exit event switches from seconds to milliseconds to match the
duration_ms convention, so its numeric scale changes accordingly.
2026-06-29 12:34:38 +08:00
qer
1dab2c2268
feat(web): preserve side panel and scope input history per session (#1181)
Some checks are pending
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
CI / build (push) Waiting to run
CI / test (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat(web): preserve open side panel across session switches

* feat(web): scope composer input history to current session

* fix(web): suppress side panel open animation on session switch

* fix(web): preserve per-session scroll position on session switch

* chore: add changeset for per-session scroll position

* fix(web): preserve follow-bottom state per session

* fix(web): scope composer attachments to their session

* fix(web): restore saved scroll position on session switch
2026-06-29 01:52:08 +08:00
qer
fc3d69dbdc
feat(web): add completion sound and question notifications (#1179)
* feat(web): play a sound when a turn completes

Synthesize a short chime when a session finishes a turn. Opt-in via Settings -> Notifications (off by default); the audio context is unlocked on the first user gesture so it also plays while the tab is backgrounded.

* feat(web): notify and play a sound when a question needs an answer

Reuse the existing notification/sound toggles so they also fire when the agent asks a question (the awaiting-answer state). Generalize the Settings labels to cover both cases.

* fix(web): don't queue the chime on a suspended audio context

A suspended AudioContext has a frozen clock, so tones scheduled on it would play stale when the context later resumes (e.g. on the next click). Only schedule the chime when the context is actually running; if it is still suspended, try to unlock it for next time and skip this one.

* fix(web): gate question notifications behind explicit opt-in

Question notifications surface question text, so they must not fire for users who only opted into turn-completion alerts (which default on). Split question notifications into their own persisted preference that defaults off, with a separate Settings toggle. Completion notifications keep their existing default-on behavior.

* fix(web): show the question text in question notifications

Lead with the actionable question text in the desktop notification body, keeping the short header as context (e.g. 'Storage: Which database?'). Previously the header alone was shown, so users had to open the tab to learn what was being asked.
2026-06-28 21:02:00 +08:00
qer
c63edd5bf6
refactor(web): unify new-session creation behind the first message (#1167)
Some checks are pending
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
Remove the NewSessionDialog path so every new-session entry in the web UI enters the onboarding composer, creating the session only when the first message is sent. This removes the last web flow that produced an empty session.
2026-06-28 17:01:26 +08:00
qer
dfcfdfd9dd
feat(web): hide empty sessions from the session list (#1166)
* chore(web): remove the /sessions slash command

* feat(web): hide empty sessions from the session list

Add an optional exclude_empty parameter to the session list API; the web client passes it so unused "New Session" entries are hidden by default, with pagination and has_more computed on the filtered set.

* fix(protocol): add exclude_empty to the session list query schema

Keep the shared protocol schema in sync with the server route so clients using the protocol type see the new parameter.

* fix(protocol): keep exclude_empty off the child session list schema

listSessionChildrenQuerySchema aliased the main list schema, so it inherited exclude_empty even though the /sessions/{id}/children route does not filter by it. Split it so generated clients are not misled.
2026-06-28 13:38:46 +08:00
Haozhe
cf558cd742
feat(managed-kimi-code): support Anthropic-compatible protocol (#1170)
* fix(agent-core): recover from context overflow 413

- track provider-observed effective context limit after overflow
- compact with the reduced limit before retrying the turn
- treat large plain 413 responses as recoverable context overflow
- add CLI patch changeset

* feat(managed-kimi-code): support Anthropic-compatible protocol

- switch managed provider to anthropic when models declare anthropic protocol
- add base64 video content blocks to the kosong anthropic provider
- downgrade unsupported media parts to text placeholders by capability
- pass prompt cache key as Anthropic metadata.user_id for session affinity

* feat(agent-core): add protocol/type to request and video upload telemetry

- turn_started now carries `type` (configured provider wire type) and
  `protocol` (effective transport, i.e. alias.protocol ?? provider.type)
- new video_upload event reports mime type, size, latency and
  success/failure, plus type/protocol/model context
- ResolvedRuntimeProvider gains `type` and `protocol` fields
2026-06-28 13:04:03 +08:00
qer
f3b15322da
fix(web): replace the web composer attach plus icon with an image icon (#1165)
Some checks are pending
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
2026-06-28 02:18:11 +08:00
qer
ff6e8bbd7c
fix(web): clear composer draft synchronously on send (#1163)
When the first message of an empty session is submitted, the optimistic
user turn unmounts the empty-session composer before the post-flush text
watcher can persist the cleared draft. The docked composer then mounts
and reloads the stale text from localStorage.

Clear the persisted draft synchronously in the submit / steer / slash
command paths instead of relying on the text watcher, so the next mount
always starts empty.
2026-06-28 01:55:04 +08:00
qer
b0708464f4
feat(kimi-web): rework ask-user-question card as step-by-step wizard (#1162)
* chore(web): show five sessions per workspace

* feat(kimi-web): rework ask-user-question card as step-by-step wizard
2026-06-28 01:24:38 +08:00
qer
d968642384
chore(web): show five sessions per workspace (#1161) 2026-06-28 01:07:51 +08:00
qer
23a553bb91
feat(web): focus composer on /new and /clear, keep viewport scalable (#1159) 2026-06-28 00:48:19 +08:00
qer
54baf5d07f
chore(deps): upgrade web markdown renderer dependencies (#1155)
Some checks are pending
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* chore(deps): upgrade web markdown renderer dependencies

- katex: ^0.16.22 -> ^0.17.0

- markstream-vue: 1.0.3 -> ^1.0.4

- shiki: ^4.2.0 -> ^4.3.0

* chore(deps): update pnpm deps hash in flake.nix

Required after upgrading katex, markstream-vue, and shiki.
2026-06-27 18:18:04 +08:00
github-actions[bot]
da63403207
ci: release packages (#1124)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-26 19:17:49 +08:00
liruifengv
b0b2aee8c5
perf(tui): keep long conversations responsive (#1119)
* perf(tui): cache rendered message lines across frames

Cache render(width) output in the transcript container and message components, returning cached lines when content, theme, and width are unchanged. Removes the per-frame full-transcript re-render that caused the TUI to lag as history grew.

* perf(tui): bound transcript with sliding window and step merging

Keep the TUI responsive as conversations grow by bounding the live
transcript:

- Sliding window: keep only the most recent 50 turns in the component
  tree; older turns are destroyed (entry + component).
- Step merging: within each turn, keep only the most recent 30
  thinking / tool steps rendered; older ones collapse into a summary.
- Expand (Ctrl+O) only reaches the most recent 3 turns.

All thresholds are overridable via KIMI_CODE_TUI_* env vars; 0
disables the corresponding feature.

* chore: add changeset for tui transcript window

* chore(tui): remove KIMI_TUI_PERF render timing log
2026-06-26 18:32:32 +08:00
qer
f1c8175f9c
fix(tui): carry server token in /web and print it on exit (#1133)
* fix(tui): show the server token when handing off via /web

The /web slash command opened the session deep link without the bearer token, so the web UI was not authenticated and the token was never shown, unlike the kimi web subcommand. Resolve the persistent server token, append it as the #token= fragment so the browser signs in on load, and show it in green below the status line so it can be copied before the terminal exits.

* test(plugins-selector): add required hookCount to fixtures

PluginSummary/PluginInfo gained a required hookCount field, so the app's typecheck failed on fixtures that did not provide it. Add hookCount: 0 to the test summaries (none of these fixtures declare hooks).
2026-06-26 18:04:51 +08:00
qer
81ba48f455
feat(web): auto-grow composer and add expandable editing mode (#1121)
* feat(web): auto-grow composer and add expandable editing mode

- Grow the chat textarea with its content up to a 1/4-viewport cap.
- Add an expand toggle above the send button for a taller editor; in that
  mode Enter inserts a newline and Cmd/Ctrl+Enter or the button sends,
  then the editor collapses back.

* fix(web): reset expanded composer state on session change

The composer instance is reused across sessions (not keyed by session id), so the expanded preference leaked into the next session's draft, leaving it stuck in the tall editor with Enter inserting newlines. Collapse back when the active session changes.

* fix(web): match expand-toggle threshold to theme resting height

The modern/kimi global theme overrides the composer min-height to 40px (the scoped default is 56px), so a hard-coded 56px threshold kept the expand toggle hidden until a third line under the default theme. Read the computed min-height from the element instead.

* fix(web): recompute expand-toggle visibility after collapsing

While expanded the computed min-height is 70vh, so a multi-line draft measured there sets isGrown=false. Collapsing did not recompute it, hiding the toggle even though the collapsed draft was still multi-line. Recompute growth after every toggle via a shared helper. The expanded state itself is unchanged and stays at 70vh until toggled or sent.

* fix(web): collapse expanded editor on slash-command submit

Known slash commands return early from handleSubmit, above the post-send collapse, so sending an expanded /goal, /btw, /compact, or skill command left an empty 70vh editor. Collapse in the slash-command path too.

* fix(web): refocus textarea after toggling expand

Clicking the expand toggle leaves focus on the button, so subsequent keystrokes do not reach the textarea and Enter would activate the button again instead of inserting a newline. Return focus to the textarea after toggling.

* fix(web): refit textarea when collapsing after image-only sends

When the expanded editor collapses on an image-only send, the text is already empty so the draft watcher never re-runs autosize; the textarea kept the inline height measured at 70vh and the collapsed cap left an oversized empty box. Route all send/steer collapses through a helper that re-runs autosize after the 70vh min-height is removed.

---------

Co-authored-by: liruifengv <liruifeng1024@gmail.com>
2026-06-26 17:21:51 +08:00
Haozhe
0886bff2bc
feat(server): add --allowed-host flag for DNS-rebinding allowlist (#1128)
- add `kimi server run --allowed-host <host...>` (repeatable or
  comma-separated; leading dot matches a domain suffix) and thread it
  through daemon spawn into startServer
- merge CLI allowed hosts with KIMI_CODE_ALLOWED_HOSTS for both the HTTP
  and WebSocket Host checks
- include the rejected host and allow guidance in the 403 error message
2026-06-26 17:06:01 +08:00
Kai
e9a3b7c83a
feat(cli): add update alias for upgrade command (#1125)
Register a `kimi update` alias for the existing `kimi upgrade` command via commander's .alias(), so both forms run the same upgrade flow. Document the alias in the command reference and add a routing test.
2026-06-26 16:48:45 +08:00
7Sageer
e736349a7c
feat(feedback): support attaching logs and codebase (#1120)
* feat(feedback): support attaching logs and codebase

Add an attachment picker to /feedback (none / logs / logs + codebase).
Codebase uploads scan the working directory with sensitive files excluded
and are sent through a new multipart upload API on the oauth/node-sdk layers.

* fix(feedback): fall back to logs when codebase scan fails

* tiny fix

* fix(feedback): make diagnostic uploads partial-safe

* refactor(feedback): reuse harness session export and normalize upload url types

* docs(slash-commands): note optional feedback attachments

* refactor(feedback): reorganize feedback upload modules

Move the attachment orchestration out of tui/commands/info.ts into a
dedicated feedback/feedback-attachments.ts, and split the former
codebase-upload/attach.ts into a generic multipart uploader
(feedback/upload.ts) and an archive lifecycle module
(feedback/archive.ts). Both session and codebase archives now flow
through a single upload lifecycle, which also removes the temp-dir
leak that occurred when codebase packaging failed.

Rename FeedbackCodebaseArchive to FeedbackArchive and the
codebase-upload/ directory to codebase/ so module boundaries match
their actual responsibilities (scan + package only).
2026-06-26 16:15:08 +08:00
liruifengv
820d77ab4c
feat(tui): show hidden todo status breakdown in collapsed panel (#1122) 2026-06-26 15:50:58 +08:00
liruifengv
b51e13538d
ci: run unit tests on windows (#1037)
* ci: run unit tests on windows

* fix(migration-legacy): align workdir bucket key with agent-core

computeWorkdirBucket used a local node:path-based resolve that yields backslash-separated paths on Windows, while agent-core's encodeWorkDirKey uses pathe (forward slashes on every platform). The SHA-256 inputs diverged, so migrated sessions were written to a bucket that the session picker never reads, making them invisible on Windows.

Alias computeWorkdirBucket to encodeWorkDirKey so both sides stay byte-identical, drop the local slugify copy, and update the workdir-bucket test reference accordingly.

* test(acp-adapter): expect platform-native separators in e2e-fs path

The e2e-fs test asserted the fs/readTextFile wire path as the raw POSIX targetPath, but AcpKaos.toClientPath converts '/' to '\' when the inner LocalKaos reports pathClass 'win32' (Windows). On Windows the wire path became '\Users\test\x.ts' and the assertion failed.

Mirror toClientPath in the test: expect backslash separators on win32 and the raw path otherwise. Implementation is unchanged.

* test(sdk): normalize workDir and skillDir paths in session tests

SessionStore.create/list and the skill loader normalize paths through pathe (forward slashes). The SDK tests compared the resulting workDir and skill loaded-dir against raw mkdtemp / node:path strings, which use backslashes on Windows (and node:fs realpath also returns backslashes for the skill dir), failing three toMatchObject assertions.

Build the expected paths with agent-core's normalizeWorkDir so they match the internal pathe representation on every platform. The skill dir keeps its realpath() (the loader realpaths the root) and only normalizes separators.

* test(skill): normalize realpath to forward slashes in scanner tests

resolveSkillRoots normalizes every root.path through fs.realpath followed by replacing backslashes with forward slashes (scanner.ts). The scanner tests compared root.path against node:fs realpath directly, which returns backslashes on Windows, so twenty assertions failed (toEqual / toContain / toHaveLength) even though the resolved paths were identical.

Wrap realpath at the top of the test file to mirror the implementation's normalization, so every comparison uses the same forward-slash form on every platform.

* test: skip Unix-only permission tests on Windows

The Unix file-permission assertions (mode bits like 0o600 / 0o700 and chmod 000 making a path unreadable) have no equivalent on Windows, which uses ACLs; fs.chmod there can only toggle the read-only bit. These six tests failed on Windows with mismatched mode values or a missing 40411.

Skip them on win32 via it.skipIf(process.platform === 'win32'): oauth FileTokenStorage (0600 file, 0700 dir), agent-core BackgroundTaskPersistence (0700 tasks dir), agent-core createPerIdJsonStore (0700 subdir), migration-legacy atomicWrite (0600 file), and server fs:browse (chmod 000 -> 40411).

* test(tui): make platform-sensitive assertions cross-platform

The TUI implementations are already platform-aware (pathe-style paths, pathToFileURL, quoteShellArg cmd/POSIX quoting, Alt+V on Windows for paste expansion), but the tests hard-coded POSIX expectations and failed on Windows.

Align the assertions with the implementation's platform behavior: footer-goal-badge matches the '[goal' badge prefix instead of /goal/ (toolbar tips contain '/goal'); tool-call expects backslash relative paths on win32; plan-box builds the file:// URL via pathToFileURL; custom-editor sends Alt+V on win32 for paste expansion; file-mention-provider normalizes the expected description to forward slashes; kimi-tui-startup builds the resume command with quoteShellArg; kimi-tui-message-flow builds the expected install path with resolve().

* test: align path assertions with pathe on Windows

Several test suites asserted paths produced by node:path/node:os/node:fs against values that agent-core, node-sdk and kaos normalize through pathe (forward slashes). On Windows the two forms diverge (backslashes vs forward slashes), failing about 19 assertions.

Mirror the implementation's normalization in the assertions via a local toPosix helper (or agent-core's normalizeWorkDir), so expected paths use forward slashes on every platform: kaos LocalKaos, node-sdk export/list/resume/config/transport sessions, cli FileMentionProvider, and agent-core skill-session.

* test(native): build path expectations with node:path.resolve

paths.mjs builds every path with node:path.resolve, which yields backslash-separated absolute paths on Windows. The path-helpers tests asserted against template strings that mixed the backslash appRoot with forward-slash segments, so Object.is failed on Windows even though the strings looked identical.

Build the expectations with the same resolve(appRoot, ...) helper so the separators match on every platform.

* fix: make Windows CI tests pass across all packages

Fix the remaining Windows CI failures so the Windows test job can go green. The changes fall into a few categories:

- Path separators: agent-core/node-sdk/kaos normalize paths via pathe (forward slashes); align test expectations and a couple of implementations (native cache base, workspace registry) with that.

- Platform-only services: skip launchd/systemd manager suites on win32 (Windows uses schtasks).

- Process/signal lifecycle: skip or relax tests that rely on POSIX signals / SIGTERM semantics that Windows does not support.

- Hook shell syntax: rewrite hook test commands from POSIX shell (single quotes, semicolons, stderr redirects, if/then/fi) to node -e / .cjs files that run under cmd.exe.

- CRLF: make Bash tool description stripping tolerate CRLF line endings.

- Misc: realpath short-name divergence, port-retry timing, telemetry spawn, fs-watch timing, snapshot path normalization, etc.

* fix: remove unused basename import in workspaceRegistryService

Fix lint error (no-unused-vars): basename from node:path is no longer used after switching to posixBasename from pathe.

* fix: align resume harness pathClass and wait for banner state on Windows

Two more Windows CI fixes:

- createResumeNoSideEffectKaos now reports pathClass 'win32' on Windows so tool descriptions (e.g. Glob's Windows note) match the live agent in expectResumeMatches, fixing usage/description deep-equal drift.

- kimi-tui-startup once-banner test now waits for writeBannerDisplayState to land before asserting, since the atomic write can lag behind the render on Windows.

* fix: resolve remaining Windows unit test failures

Make the new Windows CI job green across agent-core, kaos, node-sdk and server:

- Align the resume harness kaos pathClass with the live agent so platform-conditional tool descriptions (Glob's Windows note) match in expectResumeMatches instead of drifting on win32.
- Rewrite hook commands in agent-core tests as cross-platform node one-liners; single-quote echo, >&2 and ';' do not work under cmd.exe.
- Add .gitattributes enforcing LF so raw-imported templates (e.g. the compaction instruction) produce byte-identical token counts on Windows and POSIX.
- Terminate the full process tree on Windows in both the hook runner and kaos (taskkill /T /F) so grandchildren cannot outlive their parent and keep the cwd locked.
- Normalize workDir path separators in two kimi-sdk session tests to match the stored canonical form.
- Avoid cmd.exe arg-quoting pitfalls in the kaos cmd.exe test, and run the Windows process-tree kill test from a script file with the pid path passed via argv.
- Give the first fs-git e2e test more time on Windows and retry the temp-dir cleanup; skip the fs-watch overflow-burst assertion on Windows where fs-event coalescing prevents the single-window spike.

* ci: retrigger checks

* fix: resolve remaining Windows failures after merging main

- Terminate the spawned git/gh process tree on Windows in FsGitService (taskkill /T /F on timeout) so a timed-out 'gh pr view' cannot leave a grandchild holding the workspace cwd, which made the fs-git e2e cleanup fail with EPERM.

- Give the fs:git_status e2e suite a longer timeout on Windows and retry the temp-dir cleanup longer to ride out the slower child-process teardown.

- Make the third-party plugin install trust test assert the resolved install path via node:path so it matches the Windows-resolved path (D:\tmp\...) as well as the POSIX one.

* fix: align workspace registry roots and harden fs-git cleanup on Windows

- workspace-registry test: compare normalized (forward-slash) roots, since the registry and session index both store workDir via pathe.resolve (forward slashes on every platform). realpath() yields backslashes on Windows and diverged from the stored root.

- fs-git e2e: bump the temp-dir cleanup retries and the afterEach timeout, since Windows child-process teardown after server.close() is asynchronous and can keep the workspace cwd locked for several seconds.

* test: stub openUrl in kimi-tui-message-flow feedback tests

The /feedback command falls back to openUrl(FEEDBACK_ISSUE_URL) when submission fails, which spawned a real browser window on every test run. Mock #/utils/open-url (matching the existing login/message-replay/server test convention) so the suite never opens a browser.

* test: harden fs-git e2e cleanup against Windows cwd locks

On Windows, git/gh child processes and the session core process can outlive server.close() and keep the temp workspace as their cwd, so rmSync fails with EPERM even after a long retry. Add rmSyncRobust that retries and, if the cwd is still locked, swallows EPERM/EBUSY on Windows — the OS reclaims the temp dir and a cleanup hiccup must not fail an otherwise-passing test.

* test: harden server e2e cleanup against async teardown races

server.close() does not fully await the server's asynchronous teardown, so on a loaded CI runner the temp home/workspace dirs can still be held or written to when the afterEach rmSync runs, failing with EPERM (Windows) or ENOTEMPTY (Linux). Use a rmSyncRobust helper (retry + swallow EPERM/EBUSY/ENOTEMPTY) in the fs-git and question e2e cleanup. Also fix a leftover `throw err` (renamed to `throw error`) that broke the typecheck.
2026-06-26 11:56:41 +08:00
github-actions[bot]
5f36e763ca
ci: release packages (#1061)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-26 02:29:53 +08:00
qer
6194d3fad3
perf(web): reuse sessions reference to stop sidebar re-render on streaming deltas (#1113) 2026-06-26 02:24:31 +08:00
qer
6a97d0bf43
feat(web): add a copy button to user messages (#1112)
* feat(web): add a copy button to user messages

Mirror the assistant per-message copy button on user turns, copying the message text with the same checkmark feedback.

* fix(web): place user message copy button with undo and time

Move the copy button out of the line-number role row into the modern bubble meta row, grouped with the undo and time buttons, with matching styling and tooltip.
2026-06-26 01:01:56 +08:00
qer
d554f9ac87
feat(web): show full accumulated subagent progress (#1109)
* feat(web): show full accumulated subagent progress

The subagent detail panel only showed the most recent 40 progress lines because the reducer truncated the accumulated output. Keep the full history so the panel reflects the entire process as it grows.

* fix(web): preserve subagent progress across task updates

The projector emits a taskCreated (without reducer-owned outputLines) right before every taskProgress, and the taskCreated branch replaced the task object outright, resetting outputLines to empty on each progress event. So the panel still only showed the latest chunk. Preserve the accumulated outputLines when replacing a task, and update the test to mirror the real taskCreated-before-taskProgress path.

* feat(web): clean up subagent progress text

Drop the noisy 'Started a step' line and summarize tool calls with a concise target (path / command / pattern) instead of the full JSON args, so the subagent progress panel shows what the subagent is actually doing.

* fix(web): strip numeric index from subagent tool result names

Some subagents name tool calls with a trailing index (e.g. Read_0, Bash_4), which surfaced in tool.result progress lines since the label did not resolve. Strip the index before resolving the label.

* fix(web): bound non-subagent task output and subagent progress text

Restore a tail cap for background bash/tool task output (which can grow without bound) while keeping subagent progress in full, and cap individual subagent tool.progress chunks so a single huge output cannot dominate the panel.

* feat(web): group and fold subagent progress output

Drop the noisy 'Finished' lines, group tool output under its call, and fold long output (first 5 + last 2 lines, expandable) so the subagent progress panel shows the call rhythm at a glance.
2026-06-26 00:22:12 +08:00
qer
18f7c34a07
feat(web): show edit diffs inline in tool call cards (#1103)
* feat(web): show edit diffs inline in tool call cards

Render a line-by-line diff inside Edit/Write tool cards in the web chat, reusing the existing diff-line style from the changes panel. The diff is built client-side from the tool input, so no protocol or server changes are needed; the header chip now shows real +/- counts.

* fix(web): extend diff line backgrounds through horizontal scroll

Diff rows were only as wide as the viewport, so the add/del background stopped where long lines overflowed and the area revealed by horizontal scroll had no color. Size each row to its content so the background paints the full line.

* fix(web): make all diff rows fill the longest line width

Size the diff container to the longest line and have every row fill it, so add/del backgrounds form one continuous band across the whole horizontal scroll instead of stopping at each line's own length.

* fix(web): resolve oxlint errors in diff line builder

Use Array.at(-1) and Array.from instead of index access and new Array(length), which oxlint flags as errors.

* fix(web): show tool output for failed edit/write calls

When an Edit/Write call fails, render the tool output (the failure reason) instead of the requested diff, so error cards explain why no change was applied.

* fix(web): fall back to tool output for replace_all and append edits

A single-pair diff misrepresents replace_all edits (which change many occurrences) and from-empty diffs misrepresent append writes (which extend an existing file). Skip the synthetic diff in those cases and render the truthful tool output instead.

* feat(web): open edit diffs in the right-side detail panel

Move the Edit/Write diff out of the inline tool-card body into the shared right-side detail layer, opened by clicking the card (like the subagent detail panel). The panel shows the line diff when it faithfully represents the operation, otherwise the tool output (replace_all, append, errors), so failed calls explain why no change was applied.

* feat(web): toggle detail panels closed when their trigger is clicked again

Clicking the same Edit/Write card, file link, or git-status area a second time now collapses the right-side detail panel, matching the existing thinking / compaction / subagent toggle behavior.

* fix(web): cap client-side edit diff to avoid freezing on large inputs

The line-level LCS allocates an (oldLines+1) x (newLines+1) matrix and runs eagerly when an Edit/Write card renders. For large edits (e.g. a 5k x 5k block, ~25M cells) this can freeze or exhaust the chat. Cap the matrix at 1M cells and fall back to the raw tool output beyond that.

* fix(web): keep the edit diff panel in sync with live tool state

Store only the tool id and re-derive the diff panel payload from the live tool call in the session turns, so a panel opened while the tool is still running reflects later status/output changes (e.g. an eventual error shows the failure output instead of the stale attempted diff).

* fix(web): do not render a synthetic diff for Write calls

Write only reports the new content, so the client cannot tell a new file from an overwrite of an existing one. A from-empty diff showed overwrites as all additions and no deletions, which is misleading. Fall back to the tool output for every Write (append already did).

* fix(web): also cap diff output rows, not just the LCS matrix

The matrix-size cap alone let through asymmetric edits (e.g. one line replaced by hundreds of thousands) whose small matrix still produced a huge row array that the chip computed on render. Cap each side's line count too, so the diff output is bounded.

* fix(web): keep edit/write cards expandable when the diff panel is unwired

Clicking an Edit/Write card opens the right-side diff panel, but the nested ChatPane in the side chat does not wire that event, so the click became a no-op and the card could no longer expand to show its output. Gate the panel behavior behind a toolDiffPanel prop (enabled only in the main conversation); elsewhere the cards expand inline as before.

* fix(web): close the tool diff panel when its target disappears

When a session resync removes the tool call an open diff panel is showing, toolDiffTarget becomes null but detailTarget stayed 'toolDiff', leaving an empty aside that Escape would not close. Gate sidePanelVisible on toolDiffVisible so the panel collapses once its target is gone.
2026-06-25 23:18:14 +08:00
qer
fe667d7c2e
fix(reload): re-inject plugin session-start reminder after /reload (#1086)
* fix(reload): re-inject plugin session-start reminder after /reload

Reload reloaded plugins and resumed the session, but the model kept seeing the stale plugin session-start reminder from before the reload, so plugin skill changes only took effect in a fresh session.

Append a fresh plugin_session_start reminder to the main agent after reload, gated on a new forcePluginSessionStartReminder flag that only the explicit /reload command sets, so config and experiment toggles that reuse the reload RPC do not spam the transcript.

* fix(reload): keep reload result fresh and neutralize stale plugin reminder

Append the plugin session-start reminder before constructing ResumeSessionResult so SDK callers reading getResumeState() see the refreshed plugin context instead of a pre-reload snapshot.

When a plugin with a prior plugin_session_start reminder is disabled or removed, append a neutralizing reminder so the model does not keep following stale plugin instructions.

* fix(reload): neutralize stale plugin reminder after compaction

A full compaction folds the discrete plugin_session_start reminder into a compaction_summary, so the origin-only scan no longer detects it. Also treat a compaction_summary in history as a signal to neutralize, so disabling or removing a plugin after compaction still emits a superseding 'no active plugin session starts' reminder.

* fix(reload): thread plugin reminder option through KimiHarness.reloadSession

KimiHarness.reloadSession is a public SDK entry point; forward forcePluginSessionStartReminder to both the active-session and RPC reload paths so SDK callers using the harness can opt into the refreshed plugin reminder too.
2026-06-25 23:16:55 +08:00
Haozhe
bf9b01e0d8
feat(server): make --host opt-in for LAN binding (#1105)
* feat(server): bind `kimi server` to 0.0.0.0 by default

- change DEFAULT_SERVER_HOST to 0.0.0.0 and default --insecure-no-tls on
  so a bare `kimi server run` clears the non-loopback TLS gate
- add LOCAL_SERVER_HOST (127.0.0.1) for connectable URLs when bound to the
  wildcard, used by lockConnectHost and the service status URL
- pin the OS-supervised install to --host 127.0.0.1 so the always-on
  service stays loopback-only

* feat(server): make --host opt-in for LAN binding

- default kimi server run/web binds 127.0.0.1 when --host is omitted
- treat bare --host as 0.0.0.0 while keeping --host <host> explicit
- update server help/banner text and host binding tests
2026-06-25 22:13:13 +08:00
liruifengv
2db5fc20ec
feat: add shell mode (!) to the CLI (#1079)
* feat: add shell mode (`!`) to the CLI

Add shell mode, letting users run shell commands directly from the prompt
with `!`. Output streams live into the transcript, supports backgrounding
(ctrl+b), cancellation (Esc / Ctrl+C), input queuing while running, and
enters the conversation context with resume support.

* feat(kimi-code): show shell mode label on editor border and add tip

Render a "! shell mode" label on the top-left of the editor border while the editor is in `!` bash mode, so the active mode is visible at a glance. Also add a rotating toolbar tip (`! to run a shell command`) to surface the feature.

* feat(kimi-code): refine shell mode queue, history, and display

- Keep `!` commands out of input history so they never resurface as bare text stripped of their `!`.

- Make `!` commands non-steerable: Ctrl-S skips them (they stay queued to run after the current task) and the steer hint is only shown when something is actually steerable.

- Render queued `!` commands with a `$` prompt and the shell-mode hue so they read as commands, not as text to send to the model.

- Echo executed shell commands with a `$` prompt instead of `!`.

* fix(kimi-code): sanitize shell output and harden rendering

Captured shell command output can contain terminal control sequences (colours, cursor moves, alternate-screen switches, OSC hyperlinks, carriage-return spinners, bells). pi-tui's Text passes strings straight to the terminal, so any unhandled sequence was executed by the terminal and fought with pi-tui's own cursor control, producing the blank-screen-plus-leftover-characters mess after running commands like pnpm dev or a nested TUI.

- Sanitize CSI (incl. private modes), OSC, single-char ESC and C0 control chars (keeping newline and tab) in both the finished/resume view (previously unsanitized) and the running tail.

- Make the sanitize, format, and ShellRunComponent render paths never-throw, and cap the live running buffer, so a misbehaving command cannot crash the TUI.

- Dispose transcript children on clear so ShellRunComponent's timer is released on /clear or session switch.

* fix(kimi-code): render shell command echo with $ instead of sparkles

The shell command echo is a 'user' transcript entry, so UserMessageComponent prefixed it with the USER_MESSAGE_BULLET (sparkles), producing 'sparkles $ command'.

Add an optional bullet override to UserMessageComponent / TranscriptEntry and set it to an empty string for the shell echo (both live and resume), so the '$ command' content sits at the leading column where the sparkles marker used to be. Normal user messages keep the sparkles bullet.

* fix(kimi-code): enter shell mode when pasting a !-prefixed command

The bash-mode trigger only handled the single ! keystroke, so a pasted !cmd was inserted as literal text in prompt mode and submitted as a normal message.

After pi-tui inserts pasted content, detect an empty-prompt buffer that now starts with !, switch to bash mode, and strip the leading ! so the buffer holds only the command, matching the typed ! path.

* fix(kimi-code): restore shell mode when recalling a queued command

recallLastQueued() dropped the queued item's mode, and the Up-arrow recall only restored the text. A queued ! command (queued while another command runs, which resets the editor to prompt mode) therefore came back as a normal prompt and was submitted as a message instead of a shell command.

Return the full QueuedMessage from recallLastQueued() and restore editor.inputMode (plus the onInputModeChange sync) from the recalled item's mode.

* feat(kimi-code): use violet as the shell mode color

Replace the claude-code-style magenta/rose shellMode token with a violet that is distinct from plan-mode blue, the user role amber, success green, error red, and the teal accent.

Custom themes that omit the token fall back to this new default via the base+overrides merge, so existing custom themes keep working unchanged.

* chore: refine the shell mode changeset

* docs: document shell mode

Add a Shell mode section to the interaction guide and list the ! and Ctrl+B shortcuts in the keyboard reference, in both English and Chinese.

* test(protocol): include shell events in volatile classification check

shell.output and shell.started were added as volatile event types for shell mode; update the snapshot test's volatile-type list and count accordingly.

* fix(agent-core): surface shell command failure reason with no output

When a ! shell command fails without producing stdout/stderr (non-zero exit with no output, timeout, spawn failure), the failure reason lived only in the tool result's output and the TUI showed '(no output)'. Fold it into stderr so the live view and replay show what went wrong.

* fix(kimi-code): decode CSI-u ! to enter shell mode

In terminals with the Kitty keyboard protocol (VSCode integrated terminal, Kitty), pressing ! arrives as a CSI-u sequence, so the raw normalized === '!' comparison never matched and shell mode could not be entered by typing !. Decode with printableChar before comparing, matching every other printable-key check in the TUI.

* fix(kimi-code): do not steer while a shell command is running

Ctrl-S steers queued input into the running turn, but a shell command is not an agent turn, so steering during streamingPhase === 'shell' would launch a turn before the command output is recorded. Keep Ctrl-S a no-op during shell runs; queued messages stay queued.

* fix(agent-core): escape bash tag delimiters in shell output

Shell command output is arbitrary text; if it contains a bash tag delimiter such as </bash-stdout>, the recorded pseudo-XML wrapper breaks and replay extracts the wrong slice. Escape the content when wrapping it in agent-core and unescape when extracting during replay, so output survives round-trip intact.

* docs: document the shellMode theme token

The shellMode color token was added to the palette but not propagated to its mirrors. Add it to the custom-theme docs token table, the theme JSON schema, and the custom-theme skill token list.

* feat(agent-core): reset background task deadline on detach

Add a resettable deadline timer to BackgroundManager and let tasks register a detach timeout; when a foreground task is moved to the background, its deadline resets to the background default counted from the detach moment.

Wire this into shell mode so ! commands run with a 3-minute foreground timeout and get 10 minutes once detached to the background, instead of staying bounded by the original 60-second foreground deadline.

* feat(agent-core): lower shell mode foreground timeout to 2 minutes
2026-06-25 21:24:53 +08:00
qer
3ea6ac278d
feat(web): render plan review card with plan body and approach choices (#1101)
* feat(web): render plan review card with plan body and approach choices

The ExitPlanMode plan_review approval in the web UI now renders the plan body as Markdown with one button per approach option, plus Revise and Reject-and-Exit, with the selected label threaded back to the server.

The approval header keeps APPROVAL REQUIRED and the minimize control on the title row and shows the plan path on a second line, and the plan body uses up to half the viewport height.

The ExitPlanMode tool card also gains a link to the plan file, currently hidden behind a flag until the server can read files outside the workspace.

* fix(web): hide misleading shortcut numbers on plan review actions

When a plan review has approach options, the option buttons already own [1]/[2]/[3]. Revise and Reject-and-Exit advertised the same numbers even though those keys approve an option, so hide their shortcut labels whenever options are present.
2026-06-25 19:25:23 +08:00
Haozhe
60dfb68a2d
feat(server): add bearer-token auth and safe host exposure (#1006)
* 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.
2026-06-25 17:57:56 +08:00
qer
d6e524682d
perf(web): page session list per workspace on first load (#1084)
* perf(web): page session list per workspace on first load

Load only the first page of sessions per workspace instead of draining every session up front, so the initial request count scales with the number of workspaces rather than the total number of sessions. The per-workspace "show more" button now fetches the next page on demand, and searching lazily loads the full list so results stay complete.

To keep per-workspace paging working for sessions created with cwd only, the workspace registry now also surfaces directories that have sessions but were never explicitly registered.

* fix(web): trust server hasMore for session pagination

Stop deriving per-workspace hasMore from the workspace session_count. After a local archive/delete the count is stale (archiveSession only removes the local session), so loadedCount < total stayed true after the server returned its final page and re-fetched empty pages forever. The server's page.hasMore is authoritative for whether more pages exist, so use it directly and keep session_count only as a label total.

* fix(web): fall back to global session walk when no workspaces listed

When /workspaces is unavailable or empty on older or partially-failing daemons while /sessions still works, the per-workspace initial load produced no sessions and the sidebar rendered blank. Reuse the existing global walk as a fallback in that case so history still shows.

* fix(agent-core): keep workspace deletion durable

Record deleted workspace ids as tombstones in the registry and skip them during derived registration. Deleting a workspace only removes its registry entry (session buckets stay on disk by design), so without the tombstone the next derived-registration scan recreated the workspace, making deletion non-durable for any workspace with history. An explicit re-add clears the tombstone.

* fix(web): track session paging cursor per workspace

Compute the load-more cursor from the end of the last fetched page instead of the oldest loaded session. A deep-linked older session appended out of band would otherwise become the cursor, so the next page started after it and skipped every session between the first page and the deep link.

* fix(web): load more sessions in the mobile switcher

The mobile switcher still used the old local display-expansion logic, but each workspace now starts with only the first page of sessions. Wire its show-more button to loadMoreSessions (matching the desktop sidebar) so workspaces with more sessions can page beyond the first page on mobile.

* fix(agent-core): align derived workspace id with its session bucket

Session buckets are keyed by normalizeWorkDir (resolve, not realpath), so registering a derived workspace via createOrTouch (which realpaths) produced a different workspace id for a symlinked cwd, and per-workspace session lookups then read the wrong bucket and returned empty. Register derived workspaces with the resolved bucket key instead.

* fix(agent-core): skip archived-only buckets in derived registration

A bucket that only contains archived sessions would otherwise be registered as an empty workspace on a plain GET /workspaces, surfacing an empty sidebar group that the old session-derived fallback (which ignored archived sessions) kept hidden. Skip derived buckets with no active sessions.

* refactor(agent-core): derive workspaces from the session index on the fly

Stop persisting derived workspaces into the registry. Persisting made the registry a second data source that drifted from the session store (symlinked cwds, archived-only buckets, deleted/unmounted roots), each producing a bug. Instead compute derived workspaces fresh in list() from the session index and resolve their ids in resolveRoot() via the same index, so the session store stays the single source of truth. The deletion tombstone is kept so explicitly removed workspaces are not re-derived.

* fix(agent-core): tombstone derived workspaces on delete

After deriving workspaces from the session index, derived ids are valid list results but absent from the registry file, so delete() threw before writing the tombstone and DELETE on a derived workspace 404ed and reappeared on the next list(). Resolve the derived id and write the tombstone for it too.

* fix(web): use local session count once a workspace is fully loaded

mergedWorkspaces kept the server session_count as a floor even after a workspace had no more pages, so archiving the last session left the header showing 1 until a reload. Once a workspace is fully loaded (hasMore === false) the local count is exact, so prefer it; keep the server count as a floor only while pages remain.
2026-06-25 17:12:34 +08:00
qer
0030f76c5c
feat(tui): confirm before installing third-party plugins (#1088)
* feat(tui): confirm before installing third-party plugins

* chore: add changeset for third-party plugin install confirmation

* docs: note third-party plugin install confirmation prompt

* fix: harden third-party plugin install confirmation
2026-06-25 13:48:23 +08:00
qer
f1fad7222c
fix: reduce streaming stutter in the web chat (#1085)
Some checks are pending
CI / test (push) Waiting to run
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix(web): coalesce streaming token updates into one render per frame

* fix(server): disable Nagle on WebSocket socket for lower streaming latency

* fix(web): flush pending streaming deltas before re-subscribing

* fix(web): flush pending streaming deltas before forgetting a session
2026-06-25 13:06:38 +08:00
liruifengv
8ee5c0ff81
fix(kimi-code): avoid terminal focus flicker on Linux Wayland (#1094)
The focus-driven clipboard image hint spawned wl-paste/xclip on every
terminal focus event. On Wayland this perturbs seat focus and re-triggers
the focus event, creating a feedback loop that made the terminal window
repeatedly gain and lose focus and broke IME input.

Limit the probe to macOS and Windows, which use the in-process native
module and do not perturb focus. Image paste is unaffected.

Resolve #1090
2026-06-25 12:59:01 +08:00