* chore(serve): remove the /demo debug page The daemon has shipped a real browser UI for a while: `resolveWebShellDir()` finds the bundled Web Shell assets and `mountWebShellAssets()` serves them at `/`, so `qwen serve` already opens onto a full client. `/demo` stayed behind as a 663-line inline-HTML console covering the same ground with none of the reach — nobody drives the daemon through it, and `npm run dev:daemon` starts the Web Shell dev server rather than the demo page. Keeping it around costs more than the dead code. It is the only file in the tree that pairs an event log with daemon HTTP, so work that starts as a Web Shell observation lands there instead: #8762 was found while running `/review` through the Web Shell and was fixed entirely inside the demo page's rendering, with "no Web Shell changes" in its own risk note. Deleting the page removes that decoy. Nothing is lost for protocol-level debugging: `GET /session/:id/events` streams the same raw frames the Events tab printed. `/health` shared `routes/health-demo.ts` with the demo handler, so the module is now `routes/health.ts` / `createHealthRoutes()` and drops its `getPort` dependency. The rate-limit exemption, the boot breadcrumb, and the daemon docs lose their `/demo` arms; the loopback self-origin shim regression test already asserted through `/health` and only needed its title corrected. * test(serve): pin the removed /demo contract and the pre-auth surface Review follow-up. Three of the removal hunks shipped ungated, and two doc sentences the removal rewrote were describing the pre-auth surface wrong — both before and after the edit. Deleting the `/demo` route took its assertions with it, so nothing failed if the handler came back: the Web Shell suite only exercised a generic deep link, and the rate-limit exemption could be widened again with the suite still green. `/demo` is now pinned as what it became — an ordinary unknown path: a non-navigation request 404s, a browser navigation is answered by the SPA fallback like any other deep link, and once a token is configured (with or without `--require-auth`) that navigation is refused with 401, because the fallback sits behind the bearer. The rate-limit test pins that `/health` is the only exempt GET, so re-adding a second pre-auth page to the predicate fails instead of silently escaping the limiter. Each new assertion was checked by reverting the hunk it guards and confirming it goes red. The `--allow-origin '*'` warning and both `--allow-origin` doc paragraphs enumerated `/health` as the residual tokenless surface and said nothing about the Web Shell static assets, which are mounted before the bearer in every launch mode and stay reachable even under `--require-auth` — the enumeration also claimed `/health` stays pre-auth on non-loopback binds, where it is registered behind the bearer and 401s. A probe across all three launch modes established the actual matrix; the warning and the docs now match it and name `--no-web` as the way to remove the residual browser surface. The warning text is asserted by a test for the first time. * fix(serve): correct Web Shell doc claims and re-pin the pre-auth CORS wall Review follow-up. The removal rewrote the daemon docs around the Web Shell, and three of the rewritten claims did not match what the runtime actually does: §1 never said how the bearer reaches the browser (with auth on, the plain URL loads a shell whose every API call 401s), §8 called the shell writable on any bind (on a non-loopback bind without `--allow-origin` its POSTs hit the CORS wall and 403), and §8 served `/session/:id` without the document-navigation qualifier its own code enforces. The §9 call-chain diagram also still listed the deleted `/demo` route, the developer flag references had no `--web`/`--no-web` row despite the new guidance pointing at the flag, and both design docs listed the JSON body parser ahead of post-auth `/health` while `createServeApp()` registers them the other way round. The deleted `/demo` CORS test was also the only assertion that a pre-auth page sits behind the Origin wall — every surviving Origin test targets an API path. Re-pin it for the shell root so a mount-order regression fails instead of exposing the pre-auth HTML surface cross-origin. * fix(serve): finish demo rename sweep and scope pre-auth shell claims to loopback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
25 KiB
Daemon Developer Documentation
This is the developer-facing technical documentation for qwen-code daemon mode: the qwen serve HTTP daemon, the @qwen-code/acp-bridge package, the workspace-scoped MCP transport pool, multi-client permission mediation, typed daemon event schema v1, the TypeScript SDK daemon client, and the adapters that connect to the daemon.
It complements, rather than replaces, these existing docs:
| Existing doc | Audience | Source of truth for |
|---|---|---|
../../users/qwen-serve.md |
Operators | User quickstart, flags, threat model |
../qwen-serve-protocol.md |
Protocol implementers | HTTP route catalog, request/response shapes, error codes |
../examples/daemon-client-quickstart.md |
SDK users | End-to-end TypeScript walkthrough |
../daemon-client-adapters/ |
Adapter authors | Legacy client adapter design docs |
14-cli-tui-adapter.md |
Adapter authors | Client adapter design notes |
../../design/f2-mcp-transport-pool.md |
F2 maintainers | Workspace MCP transport pool design v2.2 |
If you want to start a daemon and use it, read qwen-serve.md first. If you want to build a client against the wire format, read qwen-serve-protocol.md. If you want to understand, extend, or debug the daemon internals, read this set.
Reading order
Pick the path that matches your goal:
- Start and verify a daemon first:
20 -> 17 -> 19. - New contributor:
01 -> 02 -> 03 -> 08 -> 09 -> 10 -> 11 -> 12. - Adding a new client adapter:
01 -> 09 -> 10 -> 13 -> (14 / 15 / 16). - Working on the MCP pool or budget:
01 -> 03 -> 05 -> 06. - Working on permissions:
01 -> 03 -> 04 -> 12. - Debugging a production daemon:
19 -> 18 -> 17 -> 20.
Document set
Foundation
01-architecture.md- system architecture, process topology, package map, and all seven top-level sequence diagrams.
Server core
02-serve-runtime.md-runQwenServebootstrap, Express app, middleware chain, graceful shutdown.03-acp-bridge.md-@qwen-code/acp-bridgepackage internals, session multiplexing, channel factory, ACP child spawn.04-permission-mediation.md-MultiClientPermissionMediator, four policies, N1 timeout invariant, cancel sentinel.05-mcp-transport-pool.md-McpTransportPool(F2), pool entries, reverse index, restart, drain.06-mcp-budget-guardrails.md-WorkspaceMcpBudget, modes (off/warn/enforce), hysteresis, refused-batch coalescing.07-workspace-filesystem.md-WorkspaceFileSystemsandbox, path policy, audit,BridgeFileSystemcontract.08-session-lifecycle.md- create / attach / load / resume,X-Qwen-Client-Id, heartbeat, eviction, metadata.09-event-schema.md- typed event schema v1: all 53 known event types with payloads, reducers, forward compatibility.10-event-bus.md-EventBus, monotonic IDs, ring replay,Last-Event-ID, slow-client backpressure,client_evicted.11-capabilities-versioning.md- capability registry, protocol version, schema version, conditional advertisement.12-auth-security.md- bearer middleware, host allowlist, CORS deny, mutation gate,--require-auth,/healthexemption, device flow.
Clients
13-sdk-daemon-client.md- TypeScript SDK:DaemonClient,DaemonSessionClient,DaemonAuthFlow, SSE parser, event reducers,ui/*transcript layer.14-cli-tui-adapter.md- shared UI transcript layer and the legacy CLI TUI daemon adapter relationship.15-channel-adapters.md-DaemonChannelBridgeshared base plus DingTalk, WeChat (Weixin), Telegram, Feishu per-channel adapters.16-vscode-ide-adapter.md-DaemonIdeConnection, loopback-only enforcement, webview bridging.
Reference appendices
17-configuration.md- env vars, CLI flags,settings.jsonkeys that affect the daemon.18-error-taxonomy.md- typed errors per layer with remediation.19-observability.md-QWEN_SERVE_DEBUG, debugging recipes, telemetry gaps.20-quickstart-operations.md- shortest startup path, curl checks, route map, and embedded invocation recipes.
Glossary
- ACP - Agent Client Protocol. JSON-RPC over stdio spoken between the daemon bridge and the ACP child process. This is not the HTTP protocol that clients use against the daemon.
- ACP child - the
qwen --acpchild that hosts one workspace's agent runtime. Production attempts to preheat the primary bridge and retries on first use after failure; a trusted secondary starts its child on demand, while an untrusted secondary does not. The owning bridge multiplexes sessions and clients onto that child. - acp-bridge - the
@qwen-code/acp-bridgepackage (packages/acp-bridge/). Owns session multiplexing, the permission mediator, the event bus, and the channel factory. - BridgeClient -
packages/acp-bridge/src/bridgeClient.ts. Wraps one ACPClientSideConnection, and handlesrequestPermission,sendPrompt, andcancelSession. - Channel factory - pluggable strategy for spawning or attaching to an ACP child. The default
spawnChannelrunsqwen --acpas a subprocess;inMemoryChannelruns it in-process for tests. - DaemonClient -
packages/sdk-typescript/src/daemon/DaemonClient.ts. The TypeScript SDK HTTP-level facade over the daemon. - DaemonSessionClient -
packages/sdk-typescript/src/daemon/DaemonSessionClient.ts. Session-scoped wrapper that trackslastSeenEventIdfor SSE replay. - EventBus -
packages/acp-bridge/src/eventBus.ts. Per-session in-memory pub/sub with monotonic IDs, a bounded ring, and per-subscriber backpressure. - F1 / F2 / F3 / F4 - internal milestones tracked in #4175. F1: bridge extraction and
BridgeFileSystem. F2: workspace-scoped MCP transport pool. F3: multi-client permission mediation. F4: protocol completion and daemon client surfaces. - MCP - Model Context Protocol. Servers expose tools, resources, and prompts; the daemon ACP child connects to them.
- McpTransportPool -
packages/core/src/tools/mcp-transport-pool.ts. F2 workspace-scoped pool sharing one MCP transport per server name and config fingerprint. - Mediator policy - one of
first-responder,designated,consensus, orlocal-only. Decides how multi-client permission votes resolve. - Originator client id - the
X-Qwen-Client-Idof the client that initiated the prompt currently requesting permission. Thedesignatedpolicy only accepts votes from this id. - PoolEntry -
packages/core/src/tools/mcp-pool-entry.ts. One entry inMcpTransportPool: one MCP transport, a refcount of attached sessions, and an idle drain timer. - Session scope -
single(one ACP session shared by all clients) orthread(one session per conversation thread). The default issingle. - SSE - Server-Sent Events. The daemon outbound event channel (
GET /session/:id/events). - Workspace - a directory registered at daemon boot, restored from the registration store, or added dynamically.
workspaceCwdis the legacy primary default;workspaces[]is the catalog of isolated runtimes and their trust/removal metadata.
Implementation source anchors
Use these anchors when moving from the docs into the latest main code:
| Surface | Implementation anchors | Primary docs |
|---|---|---|
| Bootstrap and HTTP assembly | packages/cli/src/serve/run-qwen-serve.ts, packages/cli/src/serve/server.ts, packages/cli/src/serve/routes/health.ts, packages/cli/src/serve/web-shell-static.ts |
02, 20 |
| ACP bridge and session multiplexing | packages/acp-bridge/src/bridge.ts, packages/acp-bridge/src/bridgeTypes.ts, @qwen-code/acp-bridge |
03, 08 |
| Permission mediation | packages/acp-bridge/src/permissionMediator.ts, fromLoopback: boolean, policy.* |
04, 12 |
| MCP transport pool | packages/core/src/tools/mcp-transport-pool.ts, mcp-pool-key.ts, pid-descendants.ts, session-mcp-view.ts, /mcp refresh, MCPCallInterruptedError |
05, 06 |
| MCP budget guardrails | packages/core/src/tools/mcp-workspace-budget.ts, ServeMcpBudgetStatusCell.scope, budgets[] |
06 |
| Workspace filesystem | packages/cli/src/serve/fs/, assertTrustedForIntent(trusted, intent), meta.matchedIgnore, includeIgnored |
07 |
| Event schema and SSE writer | packages/sdk-typescript/src/daemon/events.ts, packages/cli/src/serve/routes/sse-events.ts, formatSseFrame, packages/cli/src/acp-integration/session/emitters/tool-call-emitter.ts, ToolCallEmitter.resolveToolProvenance, tool_call.provenance, serverId |
09, 10 |
| Event resync | state_resync_required, awaitingResync, RESYNC_PASSTHROUGH_TYPES, asKnownDaemonEvent, unrecognizedKnownEventCount |
09, 10 |
| Capabilities | packages/cli/src/serve/capabilities.ts, mcp_server_restart_refused.reason, MCP_RESTART_REFUSED_REASONS.has |
11 |
| Auth and device flow | packages/cli/src/serve/auth.ts, packages/cli/src/serve/auth/device-flow.ts |
12 |
| TypeScript SDK daemon client | packages/sdk-typescript/src/daemon/{DaemonClient,DaemonSessionClient,DaemonAuthFlow,sse,events,types}.ts, MCP_RESTART_DEFAULT_TIMEOUT_MS |
13 |
| Shared UI transcript layer | DaemonUiEventType, DaemonSessionProvider, packages/webui/src/daemon/ |
13, 14, ../daemon-ui/README.md |
| Channels and IDE adapters | packages/channels/, packages/vscode-ide-companion/src/services/daemonIdeConnection.ts |
15, 16 |
What is intentionally out of scope
- Java / Python SDK daemon clients - only the TypeScript SDK ships a daemon client today. Doc 13 is TypeScript-only.
- Web UI product details - the shared transcript layer and web UI daemon entry points are covered here, but product UI layout is tracked in
docs/developers/daemon-ui/and adapter design notes. - Zed extension (
packages/zed-extension/) - it launchesqwen --acpover stdio directly and bypasses the daemon. - Experimental in-process hosting -
--no-http-bridgestill falls back to http-bridge today; a stable in-process serve mode would need new docs when it lands.
Current daemon mode coverage
Server core coverage
| Area | Current state | Primary docs |
|---|---|---|
| Bootstrap / listen path | qwen serve lazy-loads runQwenServe, validates auth/workspace/budget/settings, builds an Express app, then calls app.listen and blocks forever until signal. |
02, 20 |
| Auth / network guardrails | Loopback defaults to no bearer; non-loopback requires bearer; --require-auth extends bearer to loopback and /health; Host allowlist and default CORS deny are active. |
12, 17 |
| Session lifecycle | POST /session, load, resume, metadata patch, heartbeat, eviction, idle reaping, prompt pending limits, and graceful close are documented. |
08, 10 |
| ACP bridge | Single ACP child multiplexed by default; sessionScope supports single and thread; BridgeFileSystem, context filename, env overrides, and channel idle timeout are wired. |
03, 07 |
| MCP pool / budget | Workspace MCP pool is on by default unless QWEN_SERVE_NO_MCP_POOL=1; guardrail events and restart semantics are documented. |
05, 06 |
| Permissions | F3 mediator supports first-responder, designated, consensus, and local-only; invalid settings fail explicitly. |
04, 12 |
Wire protocol
| Area | Current state | Primary docs |
|---|---|---|
| HTTP routes | The route catalog lives in qwen-serve-protocol.md; this daemon set only references it and explains implementation ownership. |
../qwen-serve-protocol.md, 20 |
| Event schema | EVENT_SCHEMA_VERSION = 1; 53 known event types; id-less subscriber synthetic frames; _meta.serverTimestamp stamped by EventBus.publish() (with formatSseFrame() fallback for synthetic frames). |
09, 10 |
| Capabilities | SERVE_PROTOCOL_VERSION = 'v1'; 75 registered tags; 13 conditional tags. |
11 |
| Session shell | POST /session/:id/shell exists behind --enable-session-shell, bearer auth, and session-bound X-Qwen-Client-Id; capability tag is conditional. |
11, 17, 20 |
| Rate limiting | Optional per-tier HTTP rate limit is exposed by CLI flags/env and conditional capability tag. | 11, 17 |
Clients / SDK
| Area | Current state | Primary docs |
|---|---|---|
| TypeScript SDK daemon client | DaemonClient, DaemonSessionClient, DaemonAuthFlow, SSE parser, event reducers, feature preflight, and UI transcript exports are documented. |
13 |
| Shared UI transcript layer | SDK daemon/ui/* normalizes daemon events into 42 UI semantic event types, reduces them into transcript blocks, and provides renderers/conformance helpers. |
14, ../daemon-ui/README.md, ../daemon-ui/MIGRATION.md |
| Web UI daemon consumer | packages/webui/src/daemon/ consumes the SDK transcript store through React providers and adapters. |
14, ../daemon-client-adapters/web-ui.md |
| CLI TUI / channels / VS Code | Legacy paths still exist; migration to shared transcript primitives is documented as follow-up work, not completed behavior. | 14, 15, 16 |
Reference and operations
| Area | Current state | Primary docs |
|---|---|---|
| Configuration | Full qwen serve flags, env vars, settings.json, ServeOptions, BridgeOptions, and important constants are collected in one page. |
17 |
| Quickstart / operations | Shortest startup path, launch recipes, curl checks, Web Shell auth behavior, route split, shutdown behavior, and embedded invocation recipes are covered. | 20 |
| Errors | Boot-time explicit failures, route errors, bridge errors, EventBus errors, filesystem errors, and mediator errors are summarized with remediation. | 18 |
| Observability | QWEN_SERVE_DEBUG, curl recipes, useful events, telemetry gaps, and investigation checklists are documented. |
19 |
Historical or deprecated surfaces
| Surface | Status |
|---|---|
docs/developers/daemon-client-adapters/tui.md |
Historical draft for the old DaemonTuiAdapter spike; current shared UI transcript architecture is in doc 14. |
packages/cli/src/ui/daemon/daemon-tui-adapter.ts |
Legacy experimental adapter still in-tree. New shared UI work should prefer SDK daemon/ui/*. |
--no-http-bridge |
Accepted for compatibility but falls back to http-bridge and prints stderr. |
Forward compatibility
- Event schema v1 is additive. New known event types must be appended to
DAEMON_KNOWN_EVENT_TYPE_VALUES; old SDKs must treat unknown types as forward-compatible. - Capability tags are behavior contracts. New behavior needs a new tag, especially if clients might preflight it before calling a route.
sessionScope: 'thread'is the current per-conversation-thread split; avoid reintroducing older client-scoped wording.- Envelope
_metaand ACP payloaddata._metaare distinct. Tool-call provenance lives under the ACP payload; server emit timestamps live on the SSE envelope.
Version provenance
This doc set reflects the daemon mode surface currently merged into main, including the follow-up work from #4412. It intentionally describes current behavior instead of earlier F-series planning snapshots.