qwen-code/scripts
Shaojin Wen 715ef938f5
feat(cli): serve the Web Shell UI from qwen serve (#5392)
* feat(cli): serve the Web Shell UI from `qwen serve`

`qwen serve` now serves the built Web Shell SPA at its root on the same
origin as the API, so a released binary exposes the browser terminal
without the dev-only Vite server (the `npm run dev:daemon` two-process
setup is unchanged for development).

- New `webShellStatic.ts` mounts `/`, `/assets/*` and an SPA deep-link
  fallback. The fallback uses the same document-navigation discriminator
  as the Vite dev proxy so it never shadows API JSON 404s.
- The static shell is registered BEFORE bearerAuth (a browser can't attach
  a token to a `<script>` subresource or an address-bar navigation; the
  shell carries no secrets and every API route stays token-gated). HTML
  responses set CSP + X-Frame-Options + Referrer-Policy + no-cache.
- `--open` launches the browser at the daemon URL (with `?token=` when set)
  once the listener is up, guarded by `shouldLaunchBrowser()`.
- `--no-web` opts out for an API-only daemon.
- Bundle / npm publish / standalone packaging now ship `dist/web-shell/`.
  Missing assets degrade to API-only with a breadcrumb, never a hard fail.

Tests: +6 cases in server.test.ts (root shell, assets, SPA fallback,
non-navigation 404 passthrough, security headers, --no-web off).

* fix(cli): address review on Web Shell serving

Review fixes for #5392 (qwen-code-ci-bot):

- [Critical] SPA fallback no longer shadows /health or /demo on non-loopback
  binds — those paths fall through to their own routes / bearerAuth instead
  of receiving index.html.
- [Critical] --open trims the bearer token before putting it in the browser
  URL, matching runQwenServe's own trimming, so a trailing newline from
  `$(cat token.txt)` no longer makes every API call 401.
- --open is wrapped in its own try/catch so a failed browser launch can't
  take down the already-listening daemon; it normalizes wildcard binds
  (0.0.0.0 / ::) to loopback, and only fires when the UI is actually mounted
  (new RunHandle.webShellMounted).
- resolveWebShellDir() now requires BOTH index.html and assets/, so a partial
  build degrades to API-only instead of serving a shell whose chunks 404.
- runQwenServe logs a positive "Web Shell UI served from <dir>" breadcrumb,
  and warns that on a non-loopback bind without --allow-origin the shell is
  read-only (same-origin POSTs are blocked by the CORS wall).
- Document the --open token-in-process-list exposure in help text + a stderr
  note when a token is forwarded.
- Tests: POST method guard, sec-fetch navigation signal, /health not shadowed,
  sendFile 500 path, plus isDocumentNavigation and resolveWebShellDir units.

* fix(cli): harden Web Shell asset resolution and send-error logging

Second-round review (claude /qreview on the initial commit):

- resolveWebShellDir() now walks up from this module to find a sibling
  packages/web-shell/dist, covering the transpiled layouts the previous
  fixed `..` depth missed — per-package `tsc` output and the integration
  daemon harness (packages/cli/dist/index.js), which would otherwise resolve
  to nonexistent paths and silently run API-only.
- sendFile failures are no longer silent: log the error (matching the /demo
  handler — previously the only 5xx path that emitted nothing) and res.end()
  a half-streamed response instead of leaving the client on a 200 with a
  partial body.

The remaining comment (open-browser inside the boot try) was already fixed
in 2487c90, where the --open block gained its own try/catch.

* fix(cli): pass --open token via URL fragment + add auth-contract tests

Third-round review (qwen3.7-max /review):

- --open now puts the token in the URL fragment (#token=) instead of a query
  param, and the Web Shell reads it from the fragment first (falling back to
  ?token= for the dev launcher / hand-built URLs). A fragment is never sent to
  the server, so the token stays out of access logs and Referer headers. It is
  still visible in the browser-launcher's argv, so the stderr note stays and a
  one-time-code exchange remains the real fix for multi-user hosts (follow-up).
- Add a server test pinning the "shell served before bearerAuth, API still
  token-gated" contract (GET / → 200 without auth, /capabilities → 401 with a
  token set), plus front-end getDaemonToken fragment/query precedence tests.

The token-trim comment in this pass was already addressed in 2487c90.

* fix(cli): read --open token from RunHandle.resolvedToken; doc + test polish

Fourth-round review (qwen3.7-max /review), all suggestions:

- --open now reads the server's resolved (trimmed) token from
  RunHandle.resolvedToken instead of re-deriving it from argv/env. Removes the
  duplicated QWEN_SERVER_TOKEN literal + trim logic and any drift risk; the
  browser token is by construction what the daemon authenticates against.
- Simplify webShellMounted to !!webShellDir (serveWebShell===false already
  forces webShellDir to undefined, so the extra conjunct was dead).
- Docs: the --open row now documents the #token= fragment transport (was
  ?token=) and why a fragment is used.
- Tests: add removeDaemonTokenFromUrl coverage (strip from fragment / query /
  both, preserve non-token hash params, no-op when absent) and the missing
  afterEach import.

* fix(cli): register Web Shell SPA fallback after API routes

Fifth-round review (claude /qreview):

- The SPA fallback no longer sits before bearerAuth. It now runs after every
  API route (just before the error handler), so authed routes — and their
  401s — always win, and only genuine 404 misses fall through to the shell.
  A navigation with an attacker-controlled `Accept: text/html` to
  /capabilities (or /health on a non-loopback bind) no longer coaxes the 200
  shell out of a gated endpoint, and the fragile exact-match /health,/demo
  denylist (which trailing-slash variants slipped past) is gone.
  registerWebShell is split into mountWebShellAssets (/, /assets — still
  pre-auth so a browser can load the shell + subresources without a header)
  and mountWebShellSpaFallback (post-auth). The contract test now sends
  Accept: text/html to /capabilities and asserts 401 — it would have been 200
  before this change (the test was passing only because it omitted Accept).
- verifyBundleArtifacts (the publish gate) now requires dist/web-shell, so a
  build that skipped the web-shell workspace (e.g. npm ci --ignore-scripts
  bypassing the root prepare) fails packaging loudly instead of silently
  shipping an API-only CLI whose GET / 404s.

* fix(cli): return a clean 404 for missing Web Shell assets

Sixth-round review (qwen3.7-max /review):

A missing /assets/* (e.g. a stale hashed chunk after a redeploy renamed it)
now returns 404 instead of falling through to the SPA fallback and answering a
browser navigation with a 200 index.html. Implemented with an explicit /assets
404 handler after express.static rather than serve-static's `fallthrough:
false` — the latter forwards a 404 error to the catch-all error handler, which
would turn it into a 500. Test added.

* test(cli): cover --open + Web Shell signals; add shell security headers

Seventh-round review (qwen-code-ci-bot):

- [Critical] Extract the --open browser-launch logic into the exported
  maybeOpenWebShellBrowser() and unit-test it: --open / webShellMounted /
  shouldLaunchBrowser gating, wildcard-host -> loopback rewrite, token in the
  URL fragment (not query), and the never-throws error catch.
- [Critical] Assert RunHandle.webShellMounted (false under --no-web) and
  resolvedToken (trimmed / undefined) in runQwenServe.test.ts; also cover
  --web/--no-web and --open arg parsing.
- Drop dead code: target.hostname === '::' is unreachable (Node's URL returns
  the IPv6 wildcard as '[::]', which is already handled).
- Add defense-in-depth headers to the shell response: base-uri 'none' in the
  CSP (does not fall back to default-src), X-Content-Type-Options: nosniff,
  and a restrictive Permissions-Policy.
- Add serve-debug-gated logging for /assets 404s and SPA-fallback hits so a
  white-screen shell / routing misconfig has a diagnostic trail.

* fix(test): satisfy the Web Shell release gate in package-assets fixture

Eighth-round review (claude /qreview) — this is the actual CI failure.

The verifyBundleArtifacts Web Shell gate (requiring dist/web-shell, added in
this PR) broke scripts/tests/package-assets.test.js, which merge-main pulled
in: its createBundleArtifacts fixture only created cli.js / vendor / bundled,
so preparePackage exited 1 at the new gate before the test's assertions ran —
red on all three Test jobs. Add the web-shell artifacts (index.html +
assets/) to the fixture. The gate itself is intentional (it stops an API-only
package from shipping).
2026-06-19 19:41:33 +08:00
..
installation feat(daemon): merge daemon-mode feature batch into main (#4490) 2026-06-12 00:34:49 +08:00
lib feat(core): support QWEN_HOME env var to customize config directory (#2953) 2026-05-09 15:51:52 +08:00
tests feat(cli): serve the Web Shell UI from qwen serve (#5392) 2026-06-19 19:41:33 +08:00
acp-http-smoke.mjs feat(daemon): merge daemon-mode feature batch into main (#4490) 2026-06-12 00:34:49 +08:00
benchmark-api-latency.mjs feat(cli): add API preconnect to reduce first-call latency (#3318) 2026-04-27 06:54:55 +08:00
build-hosted-installation-assets.js fix(installer): auto-detect SYSTEM account and default PATH scope to machine (#4903) 2026-06-10 21:02:10 +08:00
build-standalone-release.js feat(installer): add standalone archive installation (#3776) 2026-05-11 13:25:48 +08:00
build.js feat(channel): add QQ Bot (QQ机器人) channel adapter (#5202) 2026-06-19 06:32:52 +08:00
build_package.js fix(build): clean stale outputs before tsc --build to prevent TS5055 (#4453) 2026-05-23 23:06:31 +08:00
build_sandbox.js fix(sandbox): fall back to 'latest' tag when image name has no colon (#2962) 2026-04-18 09:07:05 +08:00
build_vscode_companion.js Sync upstream Gemini-CLI v0.8.2 (#838) 2025-10-23 09:27:04 +08:00
check-build-status.js Merge tag 'v0.3.0' into chore/sync-gemini-cli-v0.3.0 2025-09-11 16:26:56 +08:00
check-desktop-isolation.js feat(desktop): Add desktop app package with Qwen ACP SDK integration (#3778) 2026-06-11 21:57:20 +08:00
check-i18n.ts fix(i18n): Correct zh-TW translations to match Traditional Chinese conventions (#4129) 2026-05-15 15:26:12 +08:00
check-lockfile.js Sync upstream Gemini-CLI v0.8.2 (#838) 2025-10-23 09:27:04 +08:00
clean.js feat(desktop): Add desktop app package with Qwen ACP SDK integration (#3778) 2026-06-11 21:57:20 +08:00
cli-entry.js fix(cli,core): harden OOM prevention — idempotent compaction tests, explicit GC, debug log defaults (#4914) 2026-06-14 10:40:53 +08:00
copy_bundle_assets.js feat(cli): serve the Web Shell UI from qwen serve (#5392) 2026-06-19 19:41:33 +08:00
copy_files.js revert: remove unused script modifications 2026-02-10 14:34:36 +08:00
create-standalone-package.js feat(cli): serve the Web Shell UI from qwen serve (#5392) 2026-06-19 19:41:33 +08:00
create_alias.sh fix: ambiguous literals (#461) 2025-08-27 15:23:21 +08:00
daemon-dev.js feat(daemon): merge daemon-mode feature batch into main (#4490) 2026-06-12 00:34:49 +08:00
desktop-openwork-sync.ts feat(acp): support desktop qwen integration (#4728) 2026-06-09 19:09:44 +08:00
dev.js fix(cli,core): harden OOM prevention — idempotent compaction tests, explicit GC, debug log defaults (#4914) 2026-06-14 10:40:53 +08:00
esbuild-shims.js perf(cli): code-split lowlight to cut startup V8 parse cost (#4070) 2026-05-15 17:26:18 +08:00
generate-changelog.js feat(ci): add auto-generated CHANGELOG.md synced from releases (#4872) (#4881) 2026-06-10 14:04:43 +08:00
generate-git-commit-info.js # 🚀 Sync Gemini CLI v0.2.1 - Major Feature Update (#483) 2025-09-01 14:48:55 +08:00
generate-settings-schema.ts feat: add commit attribution with per-file AI contribution tracking (#3115) 2026-05-08 09:55:58 +08:00
get-release-version.js refactor: extract shared release helper utilities (#3834) 2026-05-05 10:15:17 +08:00
lint.js Align automated PR review with bundled skill (#4843) 2026-06-08 20:52:53 +08:00
local_telemetry.js Merge tag 'v0.3.0' into chore/sync-gemini-cli-v0.3.0 2025-09-11 16:26:56 +08:00
measure-flicker.mjs fix(cli): bound SubAgent display by visual height to prevent flicker (#3721) 2026-04-29 22:34:55 +08:00
pre-commit.js Sync upstream Gemini-CLI v0.8.2 (#838) 2025-10-23 09:27:04 +08:00
prepare-package.js feat(cli): serve the Web Shell UI from qwen serve (#5392) 2026-06-19 19:41:33 +08:00
release-script-utils.js feat(installer): add standalone hosted install and uninstall flow (#3828) 2026-05-21 11:57:10 +08:00
sandbox_command.js feat(core): support QWEN_HOME env var to customize config directory (#2953) 2026-05-09 15:51:52 +08:00
sign-release.sh feat(cli): add standalone auto-update support (#4629) 2026-06-04 22:53:12 +08:00
start.js fix(cli,core): harden OOM prevention — idempotent compaction tests, explicit GC, debug log defaults (#4914) 2026-06-14 10:40:53 +08:00
sync-computer-use-schemas.ts feat(computer-use): configurable screenshot max dimension (setting + env) (#5122) 2026-06-15 15:25:27 +08:00
telemetry.js feat(core): support QWEN_HOME env var to customize config directory (#2953) 2026-05-09 15:51:52 +08:00
telemetry_gcp.js fix(mcp): update OAuth client names and improve MCP commands 2026-02-08 10:46:48 +08:00
telemetry_utils.js feat(core): support QWEN_HOME env var to customize config directory (#2953) 2026-05-09 15:51:52 +08:00
test-rewind-e2e.sh fix(test): update rewind E2E Test 1 assertion after isRealUserTurn fix (#3622) 2026-04-26 06:49:42 +08:00
test-windows-paths.js chore: consistently import node modules with prefix (#3013) 2025-08-25 20:11:27 +00:00
unused-keys-only-in-locales.json feat: add /diff command and git diff statistics utility (#3491) 2026-05-10 11:15:59 +08:00
upload-aliyun-oss-assets.js fix(release): move constants above entry point to avoid TDZ error (#4398) 2026-05-23 22:21:33 +08:00
verify-installation-release.js feat(installer): verify release assets + switch public docs to standalone entrypoint (#3855) 2026-06-04 17:23:04 +08:00
version.js fix(ci): resolve TS5055 release build failure since May 19 (#4383) 2026-05-21 16:01:35 +08:00
workspaces.js feat(desktop): Add desktop app package with Qwen ACP SDK integration (#3778) 2026-06-11 21:57:20 +08:00