qwen-code/docs/design/serve-server-final-split.md
Shaojin Wen 95e17691a9
chore(serve): remove the /demo debug page (#8805)
* 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>
2026-08-10 13:31:15 +00:00

3.8 KiB

serve server.ts final split

Goal

Continue the staged packages/cli/src/serve/server.ts split without changing daemon behavior. This pass moves the remaining inline REST handlers, small middleware helpers, capability construction, device-flow registry setup, and rate-limiter setup into focused internal modules. createServeApp() remains the composition point for daemon state, middleware order, route registration, ACP transport mount, Web Shell fallback, and final error handling.

Middleware And Route Order

The assembly order is part of the daemon contract and must stay visually auditable in createServeApp():

  1. same-origin Origin stripping
  2. CORS and host allowlist
  3. pre-auth /health on allowed loopback setups
  4. access logging
  5. Web Shell static assets
  6. bearer auth
  7. rate limit
  8. post-auth /health when required
  9. JSON body parser and JSON parser error mapper
  10. daemon telemetry
  11. REST route groups
  12. ACP HTTP and WebSocket routes
  13. Web Shell fallback
  14. final error handler

Extracted Boundaries

server/self-origin.ts, server/access-log.ts, server/rate-limiter-setup.ts, and server/error-handlers.ts own small middleware/setup blocks that previously lived inline in createServeApp(). They are intentionally thin and keep the same registration order in server.ts.

server/serve-features.ts owns the language-code list, voice transcription capability cache, and advertised feature envelope input construction. Its cache invalidation function is still called by workspace settings reload/change paths.

server/device-flow-registry.ts owns default Qwen OAuth provider registration, event sink wiring, audit stderr breadcrumbs, and app.locals registry installation.

routes/capabilities.ts owns GET /capabilities.

routes/workspace-mcp-control.ts owns MCP restart/manage/runtime add/remove mutations.

routes/workspace-lifecycle.ts owns /workspace/init and /workspace/reload.

routes/workspace-tools.ts owns /workspace/tools/:name/enable.

Each route module receives only the dependencies it needs. None of the new modules import server.ts, which keeps dependency direction one-way and avoids cycles.

Remaining In server.ts

server.ts still owns app creation, bound-workspace canonicalization, bridge/filesystem/workspace construction, mutation gate creation, route ordering, ACP HTTP/WebSocket mount, Web Shell static/fallback placement, and the compatibility exports consumed by existing callers.

The file is not required to drop below 200 lines in this PR. The acceptance criterion is that it has no inline REST endpoint handlers and reads as an assembly file whose behavioral ordering can be reviewed in one place.

Non-goals

This pass does not change response bodies, status codes, headers, SSE frames, ACP behavior, auth gates, rate-limit tiers, device-flow semantics, or error taxonomy. It does not remove status.ts, event-bus.ts, or in-memory-channel.ts compatibility shims. It does not rename historical docs or introduce a Router framework or a single god context for routes.

Audit Notes

Round 1 checked architecture boundaries and kept the existing registerXRoutes(app, deps) pattern instead of adding a Router abstraction.

Round 2 checked dependency direction and moved device-flow/runtime setup behind helpers without letting any route module import server.ts.

Round 3 checked failure paths and kept bridge error mapping, JSON body parser errors, strict mutation gates, and client-id validation call sites behavior-preserving.

Round 4 checked compatibility and retained public exports from server.ts for run-qwen-serve.ts, ACP HTTP callers, and tests.

Round 5 checked testing strategy and uses focused server.test.ts, route tests, ACP HTTP tests, typecheck, build, lint, inline endpoint grep, and git diff --check.