Conversation
…ides - make InstantiationService.services private to prevent external mutation\n- add serviceOverrides to DaemonStartOptions for test injection\n- refactor e2e tests to use startup overrides instead of post-boot container hacks
- add per-session prompt queue: concurrent submits return queued status\n- add list/steer REST endpoints and protocol schemas\n- synthesize prompt.steered lifecycle event\n- add debug endpoint to inject active prompts for e2e testing\n- add daemon-e2e queue + steer invariant test\n- fix WS ping frame handling in daemon-e2e client
…p and expand DI lifecycle primitives - remove defaultServicesModule() and services/src/module.ts; consume getSingletonServiceDescriptors() directly\n- update daemon service registrations and bootstrap to use registry descriptors\n- add DisposableMap, DisposableSet, disposable tracking, and disposeOnReturn to agent-core DI\n- update AGENTS.md with new registration patterns
- add Dockerfile and run-docker-e2e.sh for isolated docker-based e2e testing\n- add docker:e2e npm script with workspace-scoped runner names\n- add undoSession method to DaemonClient and HttpClient\n- add live and mocked tests for undoSession\n- update AGENTS.md and README.md with docker:e2e usage docs
…iring
UI/UX:
- Onboarding: welcome + language/theme prefs (Modern default, applied on start)
- Modern is the default theme; chat surface white; floating dock (input on top,
status controls as functional pill-buttons below, ctx far right)
- Single-line composer with smaller send; empty-chat hint vertically centered
- Global connecting splash on first load; overlay drift fixes (viewport-unit sizing)
- Wide-screen reading-column cap; font-size pass (chrome up, chat = session list)
- Merged ~/files + ~/diff into one tab (Changed|All, list/tree)
- ThinkingBlock capped to ~3.5 lines with auto-scroll-to-latest
- Workspace rail title + branch on second line
Design system:
- Radius scale tokens (--r-xs/sm/md/lg = 6/8/12/16); unified component radii
- Moved Modern per-component overrides to global style.css (scoped :global() did
not win the cascade — input/tabs/cards were silently un-styled)
- docs/design-system.html: tokens + live component reference
Backend wiring:
- POST /sessions/{id}/profile for model + runtime (thinking/permission/plan)
- GET /sessions/{id}/status; :compact / :fork; agent.status.updated
- Historical replay no longer re-streams (messagesToTurns dedup)
… drop web tests - Font: switcher now applies site-wide (--mono + --sans); default Inter + font-smoothing - Theme accent color toggle (Kimi blue / mono Vercel style) - Hide system-injected user messages via origin in message metadata (TUI parity) - Fix historical-session re-stream (markstream smooth-streaming gated on live streaming) - Fix auto-scroll-to-bottom on opening a session (stick-to-bottom window over async load + late markdown render) - Default-collapse thinking blocks in historical sessions - Markdown badge images render at natural size - Plus in-progress workspace/sidebar/rail/files/tasks UI work Removes the apps/kimi-web test suite (WIP; restorable from history at e609a07).
- add sinon mocking, spying, and stubbing to TestInstantiationService (mock, spy, stubPromise, stubInstance)\n- add createServices factory for disposable test service collections\n- export IConstructorSignature for constructor signatures with DI service args\n- remove verbose JSDoc comments from DI core files\n- migrate test-instantiation tests from vitest vi.fn to sinon
…ove redundant JSDoc - remove extensive JSDoc comments from event, lifecycle, and instantiationService\n- add new disposable utilities: RefCountedDisposable, ReferenceCollection, AsyncReferenceCollection, ImmortalReference, MandatoryMutableDisposable\n- change dispose() to throw collected errors instead of swallowing via onUnexpectedError\n- use combinedDisposable in Event.any and simplify Emitter internals\n- refactor InstantiationService.dispose to use centralized dispose() helper\n- update tests to match new error-throwing behavior
- add scenario 10 verifying REST + WS contract for steering queued prompts\n- cover debug active-prompt injection, queue assertions, steer response,\n prompt.steered frame, content verification, and queue drain\n- update README scenario index
…ervices to shared package - Move filesystem, fileStore, logger and workspace service implementations from daemon to shared services package\n- Update daemon routes and services to import from shared package\n- Move chokidar and ignore dependencies to services package\n- Rename daemon loggerService.ts to pinoLoggerService.ts
…1Routes module - Extract inline /api/v1 route setup from start.ts into registerApiV1Routes\n- Centralize health check, meta, auth, sessions, messages, and all other route registrations\n- Reduce start.ts size and improve separation of concerns
…ract Workspace names, path lines and session titles each derived their left edge from unrelated magic numbers (terminal: session 5px left of the workspace name; modern: 1px right because the .se inset margin was not compensated; path line 2px off). Define one --sb-pad-x/--sb-gutter/ --sb-gap contract on .side and derive all three from it; drop the dead :root block in SessionRow's scoped style. Verified in-browser: all three text edges at x=34 in both themes.
A 9-agent sweep of all 36 components found ~110 terminal-styled remnants still rendering under modern: sharp 0-4px corners on dialogs/menus/cards (approval+question cards, slash/mention menus, statusline popover, all six dialog shells, file/diff/task rows), --mono hardcoded on UI copy, 2px blue 'terminal stripe' dialog tops, and hardcoded colors bypassing the token system. Adds a grouped de-terminalization layer to style.css using the --r-* / --sans / --sh tokens; code, paths, commands and timestamps deliberately keep --mono. Also fixes real bugs the sweep surfaced: - var(--blue-soft) was referenced in Composer + style.css but defined nowhere, so the Plan pill active state and permission-row highlight rendered with no background; replaced with the existing --soft token - the modern .se row override also matched MobileTopBar's unrelated .se span, mis-spacing the mobile title path; now scoped to .sessions .se - #1565C0 hardcoded in DiffView/FileTree/ChangedTree ignored the html[data-accent=mono] grayscale remap; routed through var(--blue) - .gh-name hardcoded #000 instead of var(--ink) Verified in-browser (modern + terminal): dialogs, slash menu, settings popover, sidebar; terminal theme is untouched.
MobileSwitcherSheet: replace the old workspace-chips row + flat active-workspace session list with the desktop sidebar's design — collapsible workspace groups (folder icon + name + branch/path sub-line + per-group new-session button) over all workspaces, plus a '+ new workspace' top row. Session titles share an alignment contract (--m-pad/--m-gutter/--m-gap) with the group headers, mirroring the desktop --sb-* contract; the modern inset-pill override compensates its margin so titles stay on the alignment line. MobileSettingsSheet: add the desktop settings-popover capabilities that had no mobile counterpart — theme + accent segmented toggles, language switcher, and sign in/out. Tested e2e in a real browser via a same-origin 375px iframe driving the actual app + stub daemon: mobile shell activates, switcher shows 5 groups with correct session counts, group collapse toggles, session tap switches the active session and closes the sheet, scrim closes, theme/accent toggles flip html[data-theme] live, and 375/414/640 viewports show no horizontal overflow. (Initial 'stuck sheet' was the background-tab rAF freeze, not an app bug — verified by patching rAF.) vue-tsc passes.
Inside a hunk, a deleted SQL/Lua/Haskell comment line renders as '--- comment' in unified diff output and matched the '--- ' file-header pattern, flipping inHunk off and silently dropping the rest of the hunk from the ~/diff view. Only 'diff --git' can end a hunk now; the other header patterns are only honoured between hunks. Verified with a real SQL-comment deletion diff.
…field The daemon broadcasts approval payloads with tool_input_display (packages/protocol/src/approval.ts) but the client only read a non-existent 'display' field, so against the real daemon every approval card fell through to the generic one-liner: file-edit approvals showed no diff, shell approvals no command/cwd/danger info — users were approving actions blind. Read tool_input_display first and keep 'display' as a fallback for the stub daemon's older shape.
toolUse blocks were stamped status 'ok' the moment they appeared (unless awaiting approval), so every executing tool rendered a green check that could later flip to a cross — the ToolCall spinner state was unreachable in practice. Tools now start as 'running' and resolve to ok/error when their toolResult is absorbed; turns that were ended by a later message settle dangling tools back to 'ok' so aborted turns in old transcripts don't spin forever. Behaviour verified for live, historical-dangling, completed and error cases.
Three related silent-loss paths:
- Submitting while an image upload was in flight sent the prompt
WITHOUT the image and cleared the chips; the composer now refuses to
submit until uploads settle (text + chips stay put).
- Sending while the agent was busy enqueued only the prompt text —
attachments were dropped with no warning. Queue entries are now
structured {text, attachments} and the flush passes both through.
- A failed submit left the optimistic user message in the transcript
looking delivered (until a reload silently removed it), and a failed
queue flush dropped the prompt entirely. The optimistic message is
now rolled back in the catch, and a failed flush re-queues the prompt
at the head.
fetch() was issued with no signal anywhere, so a hung connection (the half-open TCP you get after a network change — the same scenario that kills the WS) left the promise pending for minutes. submitPrompt sets the per-session in-flight flag before awaiting, and that flag is only cleared on settle, so one hung submit silently routed every subsequent prompt into the queue until the browser's own socket timeout fired. AbortSignal.timeout(30s) turns the hang into the existing DaemonNetworkError path (with a jsdom-safe fallback), which already cleans up the in-flight state.
The only turn-end cleanup was a watch(activity) callback, and activity is computed from the ACTIVE session — so a session that finished while the user was looking at another one never had its in-flight flag cleared or its queue flushed. Switching back didn't help (idle → idle is not a watch transition): the session was bricked — permanent 'sending…' placeholder and every new prompt silently enqueued forever, recoverable only by a page reload that also discarded the queue. Cleanup + queue flush now run from the WS sessionStatusChanged → idle event for the session the event names (background sessions included); git/runtime status refreshes still only run for the on-screen session.
DaemonEventSocket had no reconnect logic at all (the close() docstring mentioned 'reconnect attempts' that never existed): one daemon restart, laptop sleep or network blip permanently killed all live updates — replies, approvals, questions and status changes never arrived again and the only recovery was a full page reload. connectEventsIfNeeded's eventConn guard made the loss unrecoverable from above. onclose now schedules connect() with exponential backoff + jitter (1s..30s, reset on a successful hello); the kept subscriptions map is replayed via client_hello on reconnect and a too-large seq gap is handled by the existing resync_required path. close() still stops everything. Verified against a live WS server: kill → backoff → reconnect → subscriptions re-sent with their lastSeq.
Three projection bugs that corrupted live streaming: - Every sidebar click re-subscribed the session, and the subscribe wrapper unconditionally projector.reset() — wiping the turn/prompt bindings, after which every remaining delta/tool event of the in-flight turn was silently discarded (turn.step.started hard-bailed on the missing promptId, so it never self-healed). Re-subscribing no longer resets; only the resync path (which reloads messages) does. - turn.step.started and tool.result now synthesize a promptId when the binding is missing (mid-turn join after reconnect/resync), mirroring turn.started, so the rest of the turn renders instead of vanishing. - The projector emitted messages/content by reference and then mutated them in place (slot.text += delta) while the reducer also appended the delta to the same object — doubling the first streamed chunk of every text/thinking block. Emits now clone the content objects.
Neither component is imported or rendered anywhere since the sidebar redesign (verified by repo-wide grep): 649 + 485 lines of unreachable UI. Worse than dead weight, both contained stale forks of live code — WorkspaceRail duplicated the settings popover that now lives in Sidebar.vue (already missing its codeFont/accent additions), and StatusLine duplicated the Composer-toolbar controls with a diverged permission color mapping — so edits could land in the dead copy and silently no-op. Also removes the five workspace.* i18n keys only the rail used and updates the comments that still pointed at StatusLine.
The follow-to-bottom gate was an atBottom position snapshot updated by scroll events, which broke in three ways users hit daily: - the scroll event fired by our own pin could observe a view that had already grown past the 80px threshold mid-stream (thinking / tool phases) and flip the gate off — the view stopped above the newest content and a 'new messages' pill appeared without any user scroll; - QuestionCard replaces the Composer in the bottom dock OUTSIDE the scroller, so its appearance (and the composer growing via queue strip / attachments / multiline input) shrank the scroll viewport without producing a single scroll or mutation event — nothing re-pinned and the newest message stayed hidden behind the dock; - sending a prompt while scrolled up only raised the pill. following is now an intent flag: it turns off ONLY when the user scrolls up out of the bottom zone (our own scrolls always move down, so an upward scrollTop is always user intent; sub-80px drifts never break it), and back on when they return, click the pill, send a prompt, answer a question, or switch session/tab. ResizeObservers on the dock, the scroller and the content column re-pin on pure layout changes (the QuestionCard case and image loads), without raising the pill; the 1200ms stick-window machinery is replaced by the flag. Also re-pins on visibilitychange (background tabs freeze rAF). Verified e2e against the stub daemon: full-stream follow stayed within 1px across thinking/tool/approval phases (155 samples); mid-stream scroll-up stopped following and raised the pill; sending and answering while scrolled up force-pinned; QuestionCard appearance kept the view pinned (max 1px); content-collapse clamp events did not break follow.
- Replace `@moonshot-ai/kimi-code-sdk` with `@moonshot-ai/agent-core` in protocol\n- Remove `@moonshot-ai/kimi-code-sdk` from services dependencies\n- Introduce internal `managedAuth` facade in services to replace `KimiAuthFacade`\n- Add compile-time assertions that neither package references the node SDK
- move event payloads and tool display schemas from agent-core into protocol\n- make agent-core depend on protocol instead of the reverse\n- remove alias workarounds in protocol build config\n- add changeset for agent-core, protocol, and kimi-code
A prompt queued while the agent is busy can carry image attachments
with no text; the queue strip rendered its bare text — an empty string,
so the row was just a blank button next to a remove cross. Queue items
now expose {text, attachmentCount}: image-only prompts render an
'image ×N' placeholder with a small badge, and any item carrying
images disables the load-back-into-input edit action (the uploaded
files can't be restored to the composer; editing would silently drop
them — remove stays available). Verified with component render tests
for the three shapes (image-only / text-only / text+images).
- fix swagger-ui resolution path to packages/server\n- add agent-core ./session/store package export\n- handle non-string action/message in lifecycle formatHuman\n- improve exec error message serialization\n- fix test error objects and mock return values\n- include missing event types in SDK type tests\n- update flake.nix dependency hash
- sort apps/kimi-code devDependencies alphabetically - add fast-json-stringify/lib/serializer and fast-json-stringify/lib/validator to optional runtime requires - add package.json placeholders for packages/daemon and packages/kimi-migration-legacy
…d docs - update existing changeset names and contents from daemon to server\n- rename changeset files to replace daemon with server\n- update services AGENTS.md and DI README references
Add a third UI theme "kimi" implementing the official Kimi design language (Quiet Utility) with token-exact values from kimi-design-skill tokens.json v0.2.0: - Interaction accent = kimiDark (black in light / white in dark); kimiBlue stays reserved for brand/data-viz (::selection only) - Gray user bubbles (bubbleGrayPc), flat tool cards and dialogs; shadows only on the composer (shadow.inputDefault) and floating menus (shadow.small); dark elevated surfaces use background.tertiary - PingFang SC for UI, Geist Mono for code (bundled JetBrains fallback) - Modern's de-terminalization rules are shared via :is(html[data-theme="modern"], html[data-theme="kimi"]) — identical specificity, so Modern renders exactly as before; kimi token blocks sit after the data-accent rules and pin the accent (the accent picker is hidden while kimi is active) Theme pickers (sidebar popover, mobile sheet) and onboarding now offer Modern/Kimi only. Terminal remains the CSS baseline and a persisted 'terminal' choice still loads; it's just no longer offered in the UI. Also: file-preview pane default width now follows half the window.
add pino-pretty to apps/kimi-code dependencies to fix server startup when pretty logging is enabled\nadd regression test verifying the dependency is declared\ninclude changeset for patch release
…ort startup failure - switch createServerLogger from pino transport target to in-process pretty stream\n- update changeset to cover @moonshot-ai/server\n- add test asserting pretty logger does not use ThreadStream\n- rename cli server test description
…rols - add terminal protocol schemas (REST and WebSocket controls)\n- add TerminalService with node-pty backend and frame buffering\n- add REST routes for terminal CRUD operations\n- add WebSocket terminal attach, input, resize, and close handlers\n- add e2e and unit tests for terminal lifecycle and I/O
- collect and embed server web assets into the native SEA binary\n- add web-assets manifest and collection scripts for native builds\n- make swagger/swagger-ui registration conditional in server start\n- enrich lifecycle commands with service URL, running state, log path, and notes\n- add --no-open flag to control auto-opening web UI after install\n- introduce ServiceUnavailableError and ProgramServiceManager\n- update service managers (launchd, systemd, schtasks) with new status fields
- add host field to LockContents and acquireLock options\n- record bind host in lock file during server startup\n- remove JSDoc block comments across service manager modules\n- update start.test.ts to assert host is persisted in lock
- Open the active server URL when `kimi server run` finds an existing local server\n- Distinguish background vs foreground server mode in conflict message\n- Show mode-appropriate stop command (`kimi server stop` or `pkill`)\n- Always include host in lock file contents\n- Add tests for already-running server handling
The pnpm patch for list-nested code blocks hanging in production builds is reverted in favor of an upstream fix; the verified recipe is archived in reports/markstream-nested-codeblock-fix.md. Upstream report: Simon-He95/markstream-vue#498
…ted sessions - add wire.jsonl transcript reader (readWireRecords, readWireTranscript, reduceWireRecords)\n- rewrite MessageService to use wire log instead of live context history\n- add createdAtMsOverride to toProtocolMessage for wire-derived timestamps\n- add transcript cache with LRU eviction and mtime-based invalidation\n- append unflushed live tail when memory is ahead of the wire file\n- degrade to live context view on wire read/parse failures\n- add comprehensive tests for compaction, undo, clear, blob rehydration
- add GET /sessions/{session_id}/skills and POST .../{skill_name}:activate routes\n- add skill protocol schemas (SkillDescriptor, list/activate DTOs) and error codes\n- add ISkillService/SkillService to list and activate skills via core RPC\n- map skill errors to protocol envelopes (40415, 40912)\n- add end-to-end tests covering list, activate, unknown session/skill, and unsupported action
Enable with ?debug=1 or localStorage kimi-web.debug=1. A ring buffer (1000 entries, redacted secrets, truncated payloads) records every REST call (method/path/requestId/status/envelope code/duration) and WS frame (lifecycle, outbound control frames, inbound events with session/seq/ offset) as a side channel — request ordering and error handling are unchanged. The panel offers a filterable timeline, per-entry JSON with copy, JSONL export, and per-session/event-type aggregation.
A user turn whose origin metadata marks it as a skill_activation (trigger user-slash) no longer dumps the raw XML block into the chat: the turn shows only the user-provided args plus an "activated skill" label with the skill name.
…tate The onboarding composer (workspace draft) has no backend session yet, so setModel() hit the early return and the pick was silently dropped. The pick is now remembered as draftModel: the status line reflects it immediately, and the first prompt passes it to createSession so the new session starts on the chosen model. Also keep the user's model when the daemon echoes model as '' — both in /status refreshes and in snapshot syncs racing an optimistic setModel — and guard the createSession echo the same way.
5 tasks
# Conflicts: # .gitignore
- unconditionally expose /openapi.json and /asyncapi.json\n- add --swagger flag to kimi server run\n- mount Swagger UI at /documentation only when --swagger is passed\n- generate AsyncAPI document from ws-control schemas\n- update tests and docs
- remove --host option from server run/install/web commands (force 127.0.0.1)\n- default foreground logs to silent; add styled ready banner when logs off\n- change foreground stop suggestion from pkill to kill -TERM <pid>\n- update service install plans, tests, and bilingual docs accordingly
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related Issue
No linked issue. This PR introduces the web client, daemon gateway, and supporting service foundation for browser-based Kimi Code sessions.
Summary
Adds a Vite/Vue Kimi web application backed by a local daemon REST/WebSocket gateway, wires the CLI
kimi weband daemon entry points, and moves daemon-facing capabilities into reusable services with focused e2e and unit coverage.1. Web Client Experience
Problem: Kimi Code currently has no browser UI for managing workspaces, sessions, prompts, tools, approvals, file diffs, and provider settings against a local daemon.
What was done:
apps/kimi-webwith Vue components, i18n, API clients, state projection/reduction, markdown/image rendering, composer controls, responsive desktop/mobile layouts, onboarding, login, session, and workspace flows.2. Daemon Gateway And CLI Entry Points
Problem: A web UI needs stable daemon endpoints and a simple way for CLI users to launch the daemon-backed browser workflow.
What was done:
daemonandwebsubcommands plus scripts for web asset copying and daemon development restart flows.3. Shared Services, Protocol Boundaries, And DI
Problem: Daemon-facing capabilities were spread across layers, making reuse and lifecycle management harder as web and daemon features expanded.
What was done:
4. Verification, Docs, And Release Metadata
Problem: The new daemon/web flow needs coverage for REST/WS behavior and release metadata for affected packages.
What was done:
Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.