Skip to content

feat(web): introduce Kimi web app and daemon gateway#625

Open
sailist wants to merge 529 commits into
mainfrom
feat/web
Open

feat(web): introduce Kimi web app and daemon gateway#625
sailist wants to merge 529 commits into
mainfrom
feat/web

Conversation

@sailist

@sailist sailist commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

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 web and 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:

  • Added apps/kimi-web with Vue components, i18n, API clients, state projection/reduction, markdown/image rendering, composer controls, responsive desktop/mobile layouts, onboarding, login, session, and workspace flows.
  • Added UI polish for message copying, image attachments, tool-call display, auto-scroll behavior, model name truncation, mobile toolbar/send button behavior, and modern theme layout.

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:

  • Added daemon REST/WS gateway routes for sessions, prompts, files, workspaces, approvals, questions, tasks, tools, auth, OAuth, metadata, model catalog, and static web assets.
  • Added CLI daemon and web subcommands plus scripts for web asset copying and daemon development restart flows.
  • Added OpenAPI route support and timeout, reconnect, and resync behavior needed by the web client.

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:

  • Added shared service abstractions for file store, filesystem, prompt, message, session, auth/OAuth, model catalog, task, question, tool, event, logger, and workspace services.
  • Added DI/lifecycle primitives and test helpers in agent-core, and inverted protocol/core dependencies so protocol-facing code stays isolated from agent-core internals.

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:

  • Added daemon e2e scenarios, Docker workflow support, and focused tests for prompt queue steering, replay, sessions, service wiring, route validation, and daemon APIs.
  • Added changesets for user-visible package changes and updated Kimi command reference docs.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

@changeset-bot

changeset-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7f8c558

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

❌ Nix build failed

Hash mismatch in pnpmDeps:

Hash
specified sha256-vtn1XBvcmcDQv6pfc+XQWv5JXDc9m14QL7X2QRnLTdA=
got sha256-u+u5Vm6UgrMW/SwiBoSz2WhKp8GOehk4p6euwlinwFI=

Please update flake.nix with the got hash.

@pkg-pr-new

pkg-pr-new Bot commented Jun 10, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@7f8c558
npx https://pkg.pr.new/@moonshot-ai/kimi-code@7f8c558

commit: 7f8c558

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 59d44f9b2b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/kimi-code/src/cli/sub/daemon.ts Outdated
port: options.port,
logLevel: options.logLevel,
debugEndpoints: options.debugEndpoints,
webAssetsDir: daemonWebAssetsDir(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include web assets for native daemon startup

In native builds, the workflow runs build:native:* and package:native, and scripts/native/package.mjs zips only the executable, so there is no dist-web directory beside a package root for the SEA binary. Passing daemonWebAssetsDir() here makes startDaemon assert filesystem web assets before listening, so native users running kimi daemon or kimi web hit the “Kimi web assets were not found” startup failure even though the command is now exposed.

Useful? React with 👍 / 👎.

if (input.apiKey !== undefined) body['api_key'] = input.apiKey;
if (input.baseUrl !== undefined) body['base_url'] = input.baseUrl;
if (input.defaultModel !== undefined) body['default_model'] = input.defaultModel;
const data = await this.http.post<WireProvider>('/providers', body);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Implement provider mutation endpoints before calling them

When the ProviderManager tries to add a provider, this client posts to /api/v1/providers, but the real daemon route file only registers GET /providers and GET /providers/{provider_id}; I also searched the repo for provider routes and found no POST/DELETE/refresh handlers. As a result the UI's add provider flow always receives a missing-route error instead of saving the provider.

Useful? React with 👍 / 👎.

wbxl2000 added 21 commits June 13, 2026 14:48
High-fidelity 'this is what it looks like' mockup built from the user's choices:
goal as an expandable dock strip, swarm/subagent as inline TUI-style chat cards
(referencing agent-swarm-progress / AgentGroup), plan/goal/swarm activation
badges in the status line, and a VSCode-style flexible terminal split (single
tab first). Records the locked decisions in the design doc; subagent placement
flagged as pending final confirmation.
- Status checkmarks now render as SVG icons (swarm counts, member summaries,
  agent sub-tools) instead of text glyphs.
- Swarm card lays members in a multi-column grid (multiple per row, TUI-style)
  instead of one per row.
- Clarify the flexible terminal split: it is a tab/view-dimension split (VSCode
  editor groups holding chat/tasks/todo/files/terminal), NOT a split inside the
  terminal tab. Updated the mockup and the locked-decision record accordingly.
File/event/component/test-level plan for the locked P3 design: subagent
lifecycle projection + inline agent cards, inline swarm card, goal dock strip,
plan/goal/swarm activation badges, terminal tab, and tab/view-dimension split.
TDD with real test code for the projector/reducer/pure-logic layers; phased,
each phase independently shippable.
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).
Group consecutive tool cards structurally so chat block spacing is applied consistently without leaking card borders or shadows.
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.
Comprehensive acceptance of the P3 landing (f5a7f21): 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.
…ilds

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).
…inal

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.
…l 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.
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.
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.
Reverts 980ff9d: 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.
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.
…bble)

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.
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.
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.
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.
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.
…flow

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.
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).
wbxl2000 and others added 21 commits June 17, 2026 15:11
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.
- 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
- 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
Resolves eslint no-dupe-keys and TS1117 errors that broke the lint and typecheck CI jobs.
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.
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().
These 20 changeset files were applied during the version bump and are no longer needed.
@wbxl2000 wbxl2000 requested review from Copilot and wbxl2000 and removed request for Copilot June 17, 2026 11:16
wbxl2000 added 5 commits June 17, 2026 19:18
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
The real server package is now available; the throwaway stub daemon
is no longer needed for development.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants