Commit graph

35 commits

Author SHA1 Message Date
qer
e91a616f21
fix(web): dedupe optimistic user message against snapshot resync (#1620)
* fix(web): dedupe optimistic user message against snapshot resync

* fix(web): match snapshot messages by identity
2026-07-13 21:54:08 +08:00
liruifengv
098623ed9f
chore(web): drop the /help, /model, /provider, and /permission slash commands (#1615)
* chore(web): drop the /help, /model, /provider, and /permission slash commands

* chore: drop the changeset
2026-07-13 20:52:19 +08:00
qer
f338fcdac4
fix(web): restore swarm member list after page refresh (#1589)
* fix(web): restore swarm member list after page refresh

* chore: add changeset for swarm roster refresh fix
2026-07-13 20:40:52 +08:00
qer
5a208cb041
fix(web): auto-enable default thinking effort when switching to an effort-capable model (#1475)
Some checks are pending
CI / typecheck (push) Waiting to run
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (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 / Desktop release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* fix(web): auto-enable default thinking effort when switching to an effort-capable model

* chore: add changeset

* fix(web): preserve thinking off when reselecting current model
2026-07-12 18:44:39 +08:00
qer
d2c2c33f3e
feat(web): type absolute paths directly in the workspace picker (#1556)
* feat(web): type absolute paths directly in the workspace picker

The add-workspace dialog's fuzzy-search box now doubles as an absolute
path entry: input starting with "/" or "~" is validated live (missing
path / missing parent get specific errors plus prefix-matched
candidates), and a valid path live-follows the folder browser so the
existing "Open this folder" button submits it. Enter accepts the first
candidate or opens a valid path; Esc clears the box. The collapsed
paste-path section at the bottom is removed, and the degraded
(no-browse) mode reuses the same box with format-only validation.

* fix(web): recognize Windows absolute paths and gate Open button in path mode

Two review fixes on the workspace picker's path entry:

- PATH_LIKE now also matches Windows drive (C:\x, C:/x) and UNC
  (\\srv\x) forms, matching node:path.isAbsolute on the daemon side.
  Without this, Windows users in degraded (no-browse) mode had no way to
  submit a path at all. Parent-dir splitting and trailing-separator
  trimming are now separator-aware (drive roots are preserved).
- "Open this folder" is disabled while path mode has no validated target.
  Previously, after typing a valid prefix and then an invalid path, the
  button still submitted the stale followed prefix.

* fix(web): submit the typed lexical root when adding a workspace in path mode

fs:browse canonicalizes via realpath, but workspace/session ids are based
on the lexical root. For a symlinked cwd (/tmp/project -> /private/tmp/
project on macOS), live-follow stored the resolved target in currentPath
and "Open this folder" emitted it, so sessions under the typed cwd would
not group under the workspace. Keep the typed normalized path for the add
action and use the browse result only to populate the visible browser.

* fix(web): handle workspace path input edge cases
2026-07-12 14:35:25 +08:00
liruifengv
170ae44205
style(web): polish the session sidebar (#1519)
* 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
2026-07-09 19:39:20 +08:00
Luyu Cheng
ad30a1c632
style(web): polish web UI typography and controls (#1502)
* style(web): polish sidebar and tool typography

- Use UI font and medium weight for sidebar and composer controls

- Add reusable shortcut and tool output blocks

- Cap long tool output at 50 lines with a scrollbar

- Update sidebar show-more copy and muted styling

* style(web): refine workspace picker sizing

* style(web): align composer mode menus

* style(web): tune list and question typography

* style(web): reuse shortcut keys in approvals

* style(web): size workspace picker from content

* feat(web): localize chat status labels

* style(web): refine composer toolbar controls

* style(web): use complete Inter variable font

* style(web): tune sidebar workspace typography

* style(web): polish composer and workspace picker

* style(web): refine markdown and thinking typography

* chore: add web UI polish changeset

* fix(web): pin Inter package for Nix build

* style(web): polish goal tool calls

* style: polish goal mode display

* fix: layer latest message pill below menus

* style: align goal strip content
2026-07-09 14:55:58 +08:00
liruifengv
735922c291
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
2026-07-09 12:30:15 +08:00
qer
b0809ddac8
feat(web): prefix skill slash commands with skill: to distinguish them from built-in commands (#1492) 2026-07-08 15:03:30 +08:00
liruifengv
2ad0120c2a
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
2026-07-08 13:42:22 +08:00
liruifengv
ee385456d0
refactor(web): migrate icons to unplugin-icons (#1467)
* 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.
2026-07-07 16:44:56 +08:00
liruifengv
2374bc41c3
feat(kimi-web): render cron fire notices in the web chat (#1426)
* feat(kimi-web): render cron fire notices as in-transcript cards

Show scheduled-reminder fires as distinct notice cards in the web chat,
both live and after a reload, instead of hiding them. Each card carries
a humanized schedule, a dimmed job id, and a collapsible prompt body.

* fix(kimi-web): isolate cron notice prompt id and route prefixed events

- Give synthesized cron messages a fresh promptId so a fire mid-turn
  cannot be reconciled into the optimistic user echo and hide it.
- Add cron.fired to KNOWN_AGENT_CORE_TYPES so event.-prefixed frames
  reach the projector and render live too.

* fix(kimi-web): truncate long one-line cron prompts when collapsed

Slice the first line to the collapse limit with an ellipsis when a
single-line prompt exceeds it, so the collapsed state actually truncates
and the expand toggle is not a no-op for common one-line reminders.

* fix(kimi-web): keep cron notices from reconciling into optimistic user echoes

Skip optimistic-echo reconciliation for user messages whose origin is
cron_job or cron_missed: their prompt text can coincide with a still-
optimistic user message, and the loose content match would otherwise
replace the user's turn with the cron notice instead of appending.

* fix(kimi-web): keep in-turn cron injections from breaking tool results

A cron injection steered into an active turn lands inside that turn's
message sequence, between a tool use and its result. Treating it as a
hard user-turn boundary flushed the pending assistant group, so the next
tool result had no group to fold into and the tool rendered without
output. Embed such in-turn cron notices as a block inside the assistant
group instead, and only render a cron at a turn boundary as its own turn.

* fix(kimi-web): only embed cron notices while a tool is in flight

Embedding whenever a group was pending was too broad: on REST snapshots
without prompt ids the whole transcript shares one group, so an idle cron
fire merged into the previous assistant answer and its own reply kept
going in that group. Embed only while the group has a running tool (a
cron sandwiched between a tool use and its result); flush to its own
turn otherwise.

* fix(kimi-web): omit synthetic prompt id on cron notices

The synthesized cron message carried a cron_pr_ promptId that the web
client caches into promptIdBySession for Stop/abort. Because it is not a
real daemon prompt id, it clobbered the active promptId, so Stop first
aborted a nonexistent prompt and only recovered via the error fallback.
Omit the promptId; the reducer already skips optimistic-echo
reconciliation for cron-origin messages, so it is not needed for de-dup.
2026-07-06 22:25:19 +08:00
liruifengv
ac5b5e4cbf
fix(kimi-web): keep tool components from jumping on expand or collapse (#1433)
* fix(kimi-web): keep tool components from jumping on expand or collapse

Also show the scroll-to-bottom button whenever scrolled up, and render a
fallback icon for tools without a dedicated glyph.

* fix(kimi-web): preserve bottom follow during content-only resizes

Late-loading media can grow after scrollKey has run; keep chasing the
bottom on content growth, and only suppress follow during the pinned
expand/collapse window.
2026-07-06 22:19:02 +08:00
qer
c5e3e80041
feat(web): render AgentSwarm as an inline tool card (#1425)
* feat(web): render AgentSwarm as an inline tool card

Replace the bottom SwarmCard footer and the messagesToTurns live-skip
with one dedicated inline tool card for AgentSwarm. The card shows a
phase overview plus a per-subagent accordion: live progress while it
runs, parsed aggregated result once it completes (and after a refresh
that has already dropped the live tasks).

Refresh and resync keep member identity metadata (swarmIndex,
parentToolCallId, subagentType, runInBackground) stable across skeleton
task replacement in the reducer, and the .content-wrap flex layout is
hardened against the overflow compression that previously displaced the
footer.

* fix(web): handle swarm review feedback

- SwarmTool: when AgentSwarm fails before producing a structured
  agent_swarm_result (e.g. argument validation), render the raw tool
  output instead of the "waiting for subagents" placeholder so the
  failure cause is visible.
- resolveSwarmMembers: source live members from the AppTask store keyed
  by parentToolCallId rather than buildSwarmGroups, which filters out
  single-member groups. A resume-only AgentSwarm now streams its live
  progress before the final result arrives. The badge counter still
  relies on buildSwarmGroups's filter.

* fix(web): carry streamed subagent text into swarm rows

Swarm subagents that stream normal assistant output accumulate it on
AppTask.text (text-kind taskProgress), not outputLines. The new live
member map was dropping `text`, so a still-composing subagent rendered
an empty / stale row until the structured result arrived.

- Add `text` to SwarmMember and thread it through buildSwarmGroups and
  swarmMembersByToolCall.
- SwarmTool: prefer member.text for both the row activity preview and
  the expanded body; fall back to outputLines / summary.
- Tests cover text propagation through both helpers.

* fix(web): merge swarm result rows and fall back to raw output

Address the two latest swarm review comments:

- Rows: when a parsed agent_swarm_result coexists with live AppTasks
  (which the detail panel also depends on), the inline card previously
  only rendered the live members. Interrupted swarms can carry
  state="not_started" / outcome="aborted" result entries for items that
  never spawned a task; those rows were dropped until a refresh cleared
  the live tasks. Extract the row model into buildSwarmCardRows and
  merge result-only aborted/not-started rows with the live member rows.
- Fallback: when the tool is no longer running but produced no
  structured result (argument validation, parser miss, or legacy
  legacy transcript), render the raw tool output instead of
  "Waiting for subagents…" so the final text / failure cause is
  visible to the user.

* fix(web): parse swarm result subagent bodies defensively

Producer writes subagent body unescaped, so a subagent that analyzes or
emits an AgentSwarm snippet can include a literal "</subagent>" inside
its body. The non-greedy regex treated that as the row close and truncated
the body in the result-only path (post-refresh where the AppTask store is
gone).

Rewrite the parser to scan opening tags, then resolve each row's body as
everything up to the last "</subagent>" before the next row's opening tag
(or document end), preserving embedded close-tag strings. Add tests for a
literal "</subagent>" within a single body and across sibling rows.

* fix(web): only count top-level subagent result tags

A subagent body that contains a literal `<subagent ...>` tag — for
example emitting an AgentSwarm/XML snippet — was being pre-collected as
another result row, splitting the real body and producing duplicate /
bogus subagents after refresh where the AppTask store is gone.

Rewrite parseSubagents with a depth-tracking tokenizer: scan every
`<subagent ...>` / `</subagent>` token in order, push a real row frame
only at the outermost level, and treat openings / closings while nested
inside another body as body text. Drop the now-inaccurate "literal
</subagent> without matching open" regression tests; replace with tests
that verify a balanced nested snippet stays inside the parent body and
does not register as a separate row.
2026-07-06 22:05:05 +08:00
qer
578f7d334c
fix(web): reconcile session from snapshot on reopen (#1409)
* fix(web): reconcile session from snapshot on reopen

* fix(web): discard stale snapshot when a newer prompt races reopen

* fix(web): harden reopen snapshot against first-open and optimistic-send races

* fix(web): keep evicted reopens subscribed when a snapshot races

* fix(web): let resync snapshots bypass the reopen staleness guard

* fix(web): force-apply the snapshot after an undo

* fix(web): gate session reopen on durable seq instead of updatedAt

* refactor(web): always rebuild reopened sessions from a snapshot

* fix(web): sharpen reopen snapshot discard and skip rebuilds mid-stream

* refactor(web): unconditionally apply session snapshots, drop the staleness guard

* fix(web): preserve loaded older messages when reopen snapshots apply
2026-07-06 21:30:25 +08:00
qer
c5c6282f44
feat(web): render AskUserQuestion result as an option list (#1391)
Some checks are pending
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / test-pi-tui (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 / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Desktop release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat(web): render AskUserQuestion result as an option list

Parse the AskUserQuestion tool result and echo the full option list, highlighting the chosen option(s) and dimming the rest, instead of dumping raw JSON. Handles single/multi select, free-text Other answers, and the dismissed state. Answers are zipped back to the input questions by index since the input carries no ids.

* fix(web): drop the stray indent in the tool-call card body

The expanded body of every tool-call card carried a hard-coded 36px left indent (a magic number meant to align with the header name). Drop it so expanded content aligns with the header's 11px padding instead. The design-system preview page mirrored the same rule and is updated to match.

* fix(web): align markdown diff block with the design system

Restyle the local ```diff renderer in chat Markdown to match the ~/diff panel: code text keeps the normal ink colour, the +/- sign carries the add/del colour, and rows use a soft background with an inset accent bar instead of dyeing the text green/red. The header and copy button now match the standard code-block chrome (height, hover, focus ring).

* fix(web): match the markdown diff block chrome to code blocks

Make the local markdown diff renderer's shell identical to a regular code block: swap the text copy button for the same icon button, inherit the header's text-xs sizing, and keep the container / header / code-area padding, background, radius and shadow in lockstep with .code-block-container and .code-block-header. The diff-specific row tinting (soft background, inset accent bar, coloured sign) is kept since that is what makes it a diff.

* fix(web): fall back to raw output for non-answer AskUserQuestion results

Background launches and error cases return plain-text tool output (task_id/status lines, or a failure reason), not the { answers } JSON the card expects. The card used to render an empty, fully-unselected option list in those cases, hiding the task id or error. Now the card only renders the option list when the output parses as the answer payload; otherwise it shows the raw output. Addresses review feedback (P2).

* fix(web): localize AskUserQuestion result labels

The card hard-coded user-visible strings (Dismissed, answer/answers, Answered, the (+N more) summary), so Chinese-locale transcripts mixed in English. Move them into the en/zh tools locale files and read them via t(). Addresses review feedback (P2).
2026-07-06 02:36:31 +08:00
qer
26b90225d2
feat(web): support multi-level thinking effort selection (#1344)
Some checks are pending
CI / build (push) Waiting to run
CI / test (push) Waiting to run
CI / test-pi-tui (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 / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Desktop release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
* feat(web): support multi-level thinking effort selection

Surface each model's declared reasoning efforts (support_efforts /
default_effort) in the web model picker as a segmented control,
replacing the on/off toggle. ThinkingLevel is now an open string, the
model catalog carries the effort metadata to the web app, and the
mobile settings sheet and /thinking command cycle through the
available levels.

* fix(web): preserve persisted thinking level before models load

coerceThinkingForModel returned 'on' for any non-'off' level when the
active model was still undefined (catalog not loaded yet), which
rewrote a persisted/default effort like 'high' to 'on' and silently
dropped the model's declared effort on later prompts. Keep the
requested level as-is until loadModels() re-runs coercion with the
real model.

Addresses Codex P1 review on modelThinking.ts.

* fix(web): coerce stale thinking level against the active model

When selecting another session, the persisted thinking level can be a
boolean 'on'/'off' carried over from a previous model. Deriving the
pill suffix and active segment from that raw value could show
"thinking: off" on an always-on model or hide the concrete effort
behind a bare "thinking" tag. Coerce the level against the current
model before deriving display state.

Addresses Codex P2 review on Composer.vue.

* fix(web): submit coerced thinking level for the prompt's target model

The send paths (submitPromptInternal / steerPrompt) previously submitted
rawState.thinking verbatim, so a value carried over from another session
(e.g. 'max' from an effort model) was sent to a model that doesn't
declare it, even though the composer already showed the coerced default.
Coerce the level against the target model before submitting so the first
turn runs with the level the UI displays.

Addresses Codex P2 review on Composer.vue.

* fix(web): coerce stale thinking in /thinking and mobile settings

Two more surfaces derived their active state from the raw persisted
level, which is stale when the saved level came from a different model:

- /thinking slash command: indexing the raw value (e.g. 'on' from a
  boolean model) into an effort model's segments returned -1 and jumped
  to 'off' instead of advancing from the model's default effort.
- Mobile settings sheet: clamping the raw prop fell back to the first
  segment, showing/selecting 'off' or the first effort while the
  composer and prompt submission coerce to the model default.

Coerce the level against the active model in both places, matching the
composer and send-path behavior.

Addresses Codex P2 reviews on App.vue and MobileSettingsSheet.vue.
2026-07-04 00:57:35 +08:00
qer
c3653a1c50
refactor(web): show an up arrow on the composer send button (#1301)
* refactor(web): show an up arrow on the composer send button

* docs(web): regenerate design-system catalog with the send up-arrow
2026-07-02 15:56:48 +08:00
qer
6a469b3e07
refactor(web): replace hand-written icons with Remix Icon (#1293)
* 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.
2026-07-02 14:20:17 +08:00
qer
b905dd4910
feat(web): redesign web UI and add design system (#1258)
* feat: redesign web ui & add design system

* feat(web): add motion to redesigned UI and add changesets

Animate toast enter/leave, dialog open, and workspace-list and tool-row expand/collapse instead of snapping, and add the changesets covering the web redesign.

* fix(web): remove undo message exit animation

* fix(web): route agent tools to AgentTool and focus dialog on open

- toolRegistry matched the raw 'agent' name, but normalizeToolName folds
  agent/subagent into 'task', so agent calls fell through to GenericTool
  and lost the inline Open button for the subagent detail panel.
- Dialog's focus watcher only fired on change; callers that mount with
  open already true (Login, Settings, ...) never moved focus into the
  modal. Run it immediately so initial focus and restore-on-close work.

* feat(web): add logo long-press design-system easter egg

Hold the sidebar logo for 3 seconds to open a dialog showing the design system page. Also trim and rebalance the redesign changesets.

* fix(web): silence Sidebar v-show warning by making it single-root

Nest the design-system Teleport inside the sidebar <aside> so the component has a single element root. App applies v-show to Sidebar, which needs an element root to attach to; the fragment root logged a "non-element root node" warning on every reactive update and the collapse did not take effect.

* fix(web): thinking toggle, tool-group i18n, agent detail button

- Show the default-thinking switch as on whenever thinking is effectively enabled (enabled !== false), matching the core resolver.

- Route the grouped tool-call header and status through vue-i18n.

- Hide the subagent "Open detail" button when no matching task exists (e.g. a completed foreground subagent after a refresh).

* fix(web): use strict equality in agent detail button guard

oxlint eqeqeq flagged the loose != null check; resolveAgentTaskId returns string | undefined, so compare with !== undefined.

* chore(web): remove stray design mockups and screenshots

Remove the design exploration mockups, screenshots, prompts, and notes that were accidentally committed under apps/kimi-web/design. Keep design-system.html, which the sidebar logo easter egg still references.

* fix(web): keep sessions on continuation failure, treat absent thinking as on

- Keep sessions already loaded from earlier pages when a continuation page fetch fails, instead of replacing the workspace with an empty page.

- Treat an absent thinking config as enabled in the settings toggle, matching the core resolver (thinking is on unless explicitly disabled).

* feat(web): open design-system easter egg on 10 logo clicks

Replace the 3-second long-press trigger with 10 consecutive clicks on the Kimi mark; the count resets after a short idle. The long-press was unreliable because pointerleave cancelled the timer on any drift.

* fix(web): treat cancelled swarm members as finished

phaseForTask now lets a terminal status (completed/failed/cancelled) override a stale subagentPhase, so a cancelled swarm member no longer stays live and suppresses the finished AgentSwarm card.

* feat(web): use 1s long-press for design-system easter egg

Switch the logo easter egg back to a long-press, shortened to 1 second, and make it reliable this time: use pointer capture plus touch-action:none so a slight drift no longer cancels the hold.

* fix(web): use plain Spinner for activity notices

ActivityNotice renders for non-chat loading states (e.g. compaction), so it must use Spinner per the design-system rule that reserves MoonSpinner for the chat first-response state.

* docs(web): add a11y guidance to design system

* docs(web): drop stale design README link

* fix(web): restore model search focus and define panel header weight

- Bind the model picker's search Input to searchRef so useDialogFocus moves focus into it on open instead of the dialog's close button.

- Use the defined --weight-semibold token for panel header titles (--weight-bold is not declared, so the shorthand was invalid).

* fix(web): let any open dialog own Escape over the side panel

Track open design-system Dialog instances in a shared count and include it in App.vue's anyOverlayOpen, so a dialog whose open state lives outside App.vue (such as the sidebar session search) captures Escape before the background side panel closes.

* fix(web): base dock-work flag on filtered dock task lists

Foreground subagents are excluded from the dock task lists, so a session whose only task is a foreground subagent no longer renders an empty workbar above the composer.

* fix(web): create subagent task before forwarding text deltas

A client that subscribes from a snapshot after subagent.spawned already fired never received the lifecycle taskCreated; the reducer only applies taskProgress to existing tasks, so assistant text deltas were dropped and the live subagent detail stayed blank. Emit taskCreated (via patchSubagent) before the text progress, mirroring the tool-progress path.

* fix(web): keep plan, swarm, and goal mode toggles per session

Plan, swarm, and goal modes were stored as global scalars on the web client and a single localStorage key each, so they leaked across sessions. Bind them to the active session via per-session maps, persist per session, and apply server status/events to the originating session so background sessions keep independent state.

* fix(web): keep subagent detail reachable for synthesized tasks

When the web client subscribes after a subagent already spawned, the synthesized subagent task has no parentToolCallId, so the Agent tool's Open-detail button was hidden and the panel would not open. Fall back to the single unmapped subagent task when resolving the detail target in both the button visibility and the panel open paths.

* fix(web): keep session kebab menu from being clipped

Teleport the SessionRow kebab menu to body and anchor it with fixed positioning so the collapsing group-sessions list's overflow:hidden no longer clips the dropdown.

* fix(web): apply staged modes to the created session by id

When starting the first prompt, apply the staged plan/swarm/goal modes to the just-created session's per-session maps by id instead of via the activeSessionId-based setters, so a session switch during the selectSession await can't drop the modes for this session or pollute another.

* refactor(web): unify confirmation dialogs into a single modal

Replace the inconsistent confirmation patterns (native confirm(),
two-step menu arming, hand-rolled inline strips, bare buttons) with one
modal ConfirmDialog driven by a global useConfirmDialog() composable,
and consolidate the duplicated confirm/cancel i18n keys.

* feat(web): inline message queue with separate stop button

- Send button always sends/enqueues; interrupt moves to a separate red Stop
  button shown only while running, so the two can no longer be confused.
- Queued prompts now render inline at the tail of the transcript (after the
  running turn) instead of behind the dock panel: click to edit, remove, drag
  the grip to reorder, with image thumbnails and a "next up" marker.
- Remove the dock queue panel and the QueuePane component; Steer stays on
  Ctrl/Cmd+S.

* chore: add changeset for web queue UX

* feat(web): prompt reliability, sidebar menu, and composer/markdown polish

- Fix spurious errors when question/approval/task actions were already complete
- Add loading feedback to question and approval prompts; block double-clicks
- Make the question "Other" option selectable by row click and let Enter advance/submit
- Consolidate workspace section actions into an overflow menu
- Tighten markdown prose line-height and block spacing
- Recall input history only when the caret is at text start
2026-07-01 20:47:12 +08:00
qer
882cf355a9
fix(web): persist workspace rename and hide provider manager (#1234)
* fix(web): persist workspace rename via the daemon update API

* fix(web): hide the unshipped provider manager

* fix(web): fall back to a local override for derived workspace renames

* fix(web): preserve workspace name override across registration
2026-07-01 02:03:19 +08:00
qer
210cedb3bf
chore: add KCD client for test (#1230)
* 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.
2026-06-30 21:46:51 +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
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
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
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
884b65a040
fix(web): coalesce snapshot reloads on resync (#1087)
Avoid concurrent session snapshot requests when resync_required fires repeatedly, while still allowing one queued rerun after the in-flight reload settles.
2026-06-25 11:49:05 +08:00
qer
98d3e5b71d
feat(web): stabilize and drag-reorder workspaces in the sidebar (#1047)
* feat(web): stabilize and drag-reorder workspaces in the sidebar

* fix(web): preserve dragged workspace order after refresh

* fix(web): float session to top of its group on new message

* fix(web): align workspace drop order with insertion marker

* fix(web): allow dropping a workspace after the last item

* fix(web): use reordered workspaces for active fallback

* fix(web): honor drag order for next-workspace fallback on removal
2026-06-24 12:06:32 +08:00
qer
ac1882fe28
feat(web): persist collapsed workspace groups to localStorage (#1045) 2026-06-23 22:45:24 +08:00
qer
9d197e0f67
fix(web): make clipboard copy work over plain HTTP (#1044)
* fix(web): make clipboard copy work over plain HTTP

The Clipboard API (navigator.clipboard) is only exposed in secure contexts. When the web UI is served over plain HTTP, every copy action threw synchronously and silently failed. Route all copy call sites through a helper that falls back to execCommand('copy') in insecure contexts, and surface success or failure feedback to the user.

* test: address review feedback and a flaky goal-badge test

- clipboard test: drop the jsdom environment and mock the small navigator/document surface in the default node environment, per the kimi-web "pure logic tests only" rule.

- footer-goal-badge test: assert the absence of the "[goal" badge instead of the bare "goal" substring, which could match a rotating working tip ("/goal ...") and fail depending on Date.now().
2026-06-23 22:21:10 +08:00
qer
83384ee6d4
fix(web): persist input history so recall works after the first message (#1015)
* fix(web): persist input history so recall works after the first message

The composer has two mutually-exclusive instances: the empty-session
composer and the docked composer. The first message of a new session is
sent by the empty composer, which unmounts as soon as the first turn
appears; the docked composer then mounted with an empty in-memory history,
so ArrowUp did nothing until a second message was sent. The history was
also lost on every page reload.

Persist the history to localStorage as a single global list and re-read it
on mount. Global (not per-session) because a new session has no id until
after the first submit, so per-session keys would not line up across the
empty -> docked handoff. Caps the list at 200 entries.

Adds persistence-focused unit tests (surviving a remount, the 200-entry
cap, and a malformed stored value).

* fix(web): record slash commands in input history too

Move the history.push call ahead of the slash-command branch so that known
commands (with or without args, e.g. /goal <task> or /model) are recorded
and can be recalled with ArrowUp, instead of only plain messages. Steer
already pushed; only the submit slash path was missing it.

* fix(web): record menu-selected slash commands in history

Bare slash commands picked from the slash menu (e.g. /model, /login) go
through selectSlashCommand and emit directly, never reaching handleSubmit,
so they were not recorded even after the typed-slash fix. Push the command
name before emitting. acceptsInput commands are still recorded later by
handleSubmit together with their argument.
2026-06-23 18:07:00 +08:00
qer
d4ae02d82e
fix(web): keep sidebar unread dots in sync across browser tabs (#978) 2026-06-22 23:36:58 +08:00
qer
3b9938b4c3
refactor(web): consolidate localStorage access and split appearance/notification modules (#973) 2026-06-22 17:21:15 +08:00
qer
a74a6b7f6b
fix(web): improve slash menu and skill editing experience (#878)
* fix(web): keep slash skills editable

* fix(web): wrap long slash menu entries

* fix(web): rank exact slash matches before substrings
2026-06-18 14:30:29 +08:00
Haozhe
9a8fea5c85
feat(web): introduce Kimi web app and daemon gateway (#625)
* 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>
2026-06-17 20:53:46 +08:00