fix(web-shell): proxy standalone/live daemon routes in vite dev - #10748
Conversation
The vite dev proxy is an allowlist; /standalone/sessions* and /live/* were missing, so those API calls fell through to the SPA fallback, which returns index.html for Accept: */*. Clients then failed with "Unexpected token '<' ... is not valid JSON" when listing, opening, or creating a standalone session (and on Live voice status calls). Add both prefixes to the proxy list. /live overlaps client/live/* source modules, so extend the existing source-file bypass exemption (used for /extensions and /session-catalog) to /live.
|
|
|
Thanks for the PR! Template looks good ✓ Problem: observed bug with real evidence, not theory. The dev-server proxy allowlist genuinely lacks Direction: dev-mode fix for the Web Shell dev server — squarely in scope, no product-direction question. Size: not a core-path change (package-root Approach: minimal and conventional — follows the established per-prefix allowlist pattern, and extending the client-source bypass to Risk: no elevated risk signals. Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题:真实观测到的 bug,有证据,不是理论问题。开发代理允许列表确实缺少 方向:Web Shell 开发服务器的开发模式修复,完全在项目范围内,无产品方向问题。 规模:非核心路径改动(packages/web-shell 包根的 vite.config.ts,单包改动)。12 行生产代码 + 35 行测试。 方案:最小且符合惯例——沿用既有的逐前缀允许列表模式;把客户端源码豁免扩展到 风险:无升级风险信号。 进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewNo blockers. The change is exactly the minimal fix: two new allowlist entries reusing the shared Things I verified while reading:
One standing observation, not blocking: the allowlist remains manual, as the PR itself notes — future daemon prefixes used by the shell need the same treatment. That is the established convention, and redesigning it here would be scope creep. TestingUnattended CI run — no PR code executed here; evidence is the PR's own CI on the reviewed commit. Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 The unit suite on ubuntu — which includes The author's live HTTP probes (dev server + daemon, macOS) are their own claim, not independently re-run here — starting the vite dev server would mean executing PR-derived code, which triage never does. Sandboxed verification would settle the remaining end-to-end gap: 中文说明代码审查无阻塞问题。改动正是最小修复:两个新的允许列表条目复用共享的 阅读时核实的内容:
一个长期观察(非阻塞):允许列表仍是手工维护——正如 PR 自述,未来 shell 用到的新 daemon 前缀需要同样处理;这是既有惯例,在本 PR 里重新设计反而越界。 测试无人值守 CI 运行——此处不执行任何 PR 代码;证据为被审提交上 PR 自身的 CI(见上表)。 ubuntu 上的单元测试套件(包含 作者的实时 HTTP 探测(开发服务器 + daemon,macOS)是其自述,未在此独立复跑——启动 vite 开发服务器属于执行 PR 代码,triage 从不这样做。沙盒验证可补齐剩余的端到端缺口: — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 5/5 — verified dev-mode bug, minimal fix following the established convention, tests pin the changed logic; only CI is still settling. Stepping back: my independent proposal for "the dev proxy doesn't forward the standalone/live daemon routes" was exactly this — add the two prefixes to the allowlist and exempt No reservations. Approval is deferred only because the unit suite is still running on the reviewed commit; the marker below approves automatically once CI lands green on that commit. 中文说明置信度:5/5 —— 已核实的开发模式 bug,遵循既有惯例的最小修复,测试钉住了变更逻辑;只剩 CI 还在跑。 整体来看:我对"开发代理未转发 standalone/live daemon 路由"的独立方案正是如此——把两个前缀加入允许列表,并把 没有保留意见。批准仅因被审提交上的单元测试套件仍在运行而顺延;下方标记会在该提交的 CI 全绿后自动批准。 — Qwen Code · qwen3.8-max Reviewed at |
🖼️ web-shell visual previewRendered against a mock daemon (no real backend): the PR base vs this PR head Screenshots · before / afterFull-resolution recordings (.webm) are attached to the workflow run. — Qwen Code · web-shell visuals |
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
|
Review pass at head Static verification
Unit: Live A/B (real
CI note: the Not approving — no maintainer/ci-bot approval on record yet. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
Not explored to full depth (tool budget reached): "agent 3c": none — but disclosed: npx vitest run client/vite-config.test.ts was not executed (no installed dependencies in the review worktree)..
Test Plan (not a blocker): 6 tests pass — this review observed 5498, 503 passed.
中文说明
已审查。 建议见行内评论。
未探索到全部深度(达到工具调用预算):"agent 3c":none — but disclosed: npx vitest run client/vite-config.test.ts was not executed (no installed dependencies in the review worktree).。
Test Plan(非阻断):6 tests pass — this review observed 5498, 503 passed。
— qwen3.8-max via Qwen Code /review (v0.22.3)
| // Live voice routes (`/live/status`, `/live/setup`, ...). The prefix | ||
| // overlaps `client/live/*` source modules; the bypass above exempts | ||
| // those source files from proxying. |
There was a problem hiding this comment.
[Suggestion] The new comment states that the bypass exempts client/live/* source files from proxying, but the exemption is qualified by the extension regex at line 20 (/\.(?:[cm]?[jt]sx?|css|map)(?:\?|$)/) — only code/CSS/map files are exempted. If a non-code asset is later added under client/live/ (e.g. import micIcon from './mic.svg' or a .json data import in one of the voice-UI components there), vite requests /live/mic.svg in dev, the regex does not match, bypass returns undefined, and the request is proxied to the daemon and 404s — dev-only broken UI while the production build works. A top-level live*.ts module hits the same trap the other way: it prefix-matches the /live proxy key, but the exemption requires the trailing-slash /live/, so its module load is proxied and blanks the page in dev only. Today nothing breaks (client/live/ holds only .ts/.tsx/.module.css, and no top-level live*/standalone* modules exist), which is what keeps the mismatch invisible — the comment reads as a guarantee the mechanism does not give.
Witness:
probe against the real bypass (scratch tree):
intact PR GET /live/useLiveVoice.ts -> served by vite
GET /live/LiveVoiceButton.module.css -> served by vite
GET /live/mic.svg -> undefined (proxied; daemon 404s)
GET /live/data.json -> undefined (proxied; daemon 404s)
GET /liveModels.ts -> undefined (proxied)
GET /live/status -> undefined (proxied; correct)
with fix /live/-scoped any-extension broadening -> /live/mic.svg and /live/data.json flip to served;
/live/status stays proxied; all 6 PR tests still pass
| // Live voice routes (`/live/status`, `/live/setup`, ...). The prefix | |
| // overlaps `client/live/*` source modules; the bypass above exempts | |
| // those source files from proxying. | |
| // Live voice routes (`/live/status`, `/live/setup`, ...). The prefix | |
| // overlaps `client/live/*` source modules; the bypass above exempts | |
| // those source modules (code/CSS/map extensions only) from proxying — | |
| // other asset types under `client/live/` need the exemption regex extended. |
If broadening the exemption rather than correcting the comment: the regex at packages/web-shell/vite.config.ts:20 is shared by the /extensions, /session-catalog/ and /live/ prefixes, so the broadening must be scoped to /live/ only and stay extension-anchored — exempting everything under /live/ would break the API routes this PR adds. If you take the broadening route, please add a case in client/vite-config.test.ts asserting bypass returns the URL for GET /live/mic.svg, and confirm it goes red when the broadened branch is removed.
中文说明
新注释称该旁路会把 client/live/* 源文件豁免于代理,但豁免实际上受第 20 行的扩展名正则(/\.(?:[cm]?[jt]sx?|css|map)(?:\?|$)/)限定——只有代码/CSS/map 文件被豁免。如果之后在 client/live/ 下的语音 UI 组件中新增非代码资源(例如 import micIcon from './mic.svg' 或 .json 数据导入),开发模式下 vite 会请求 /live/mic.svg,正则不匹配,bypass 返回 undefined,请求被代理到 daemon 并 404——只有开发模式 UI 损坏,生产构建正常。顶层 live*.ts 模块会以相反方式踩中同一个坑:它会前缀匹配 /live 代理键,但豁免要求带斜杠的 /live/,因此其模块加载会被代理走,页面只在开发模式下白屏。目前没有任何东西坏掉(client/live/ 只有 .ts/.tsx/.module.css,也不存在顶层 live*/standalone* 模块),这正是该不匹配不可见的原因——注释读起来像是机制并未提供的保证。
验证证据:对真实 bypass 的探测(临时树):未修改的 PR 下 GET /live/mic.svg、GET /live/data.json、GET /liveModels.ts 均返回 undefined(被代理,daemon 返回 404),而 /live/useLiveVoice.ts 与 /live/LiveVoiceButton.module.css 正常由 vite 服务;应用仅限 /live/ 的任意扩展名豁免后,探测结果翻转为服务 svg/json 资源,/live/status 仍被代理,且 PR 的 6 个测试全部仍然通过。
建议的修复:把注释改为与机制一致(见上方 suggestion 代码块);或者把 /live/ 的豁免放宽到任意文件扩展名(需加测试)。若选择放宽豁免:第 20 行的正则同时被 /extensions、/session-catalog/、/live/ 三个前缀共享,放宽必须只限定在 /live/,且必须继续以扩展名为锚——豁免 /live/ 下的一切会破坏本 PR 新增的 API 路由。同时请在 client/vite-config.test.ts 中新增断言 bypass 对 GET /live/mic.svg 返回该 URL 的用例,并确认删除放宽分支后该用例变红。
— qwen3.8-max via Qwen Code /review (v0.22.3)
ytahdn
left a comment
There was a problem hiding this comment.
APPROVE —— 纯静态审查,head aaaa61d。
What this PR does / PR 主旨
EN: A dev-mode-only fix. The Web Shell vite dev server forwards only an allowlist of daemon route prefixes; /standalone/sessions* and /live/* were missing, so in dev those API calls fell through to vite's SPA fallback (index.html for Accept: /) and the client hit "Unexpected token '<', ... is not valid JSON" when opening or creating a standalone session and on Live voice status calls. This adds /standalone and /live to the proxy allowlist and extends the existing client-source bypass (previously /extensions/, /session-catalog/) to /live/ so vite keeps serving client/live/* modules. The production daemon is unaffected.
中文:仅影响开发模式的修复。Web Shell 的 vite 开发服务器只转发白名单内的 daemon 路由前缀,缺了 /standalone/sessions* 与 /live/,导致 dev 下这些 API 落入 vite 的 SPA 回退(对 Accept:/* 返回 index.html),点击/新建 standalone 会话与 Live 语音状态调用时报 "Unexpected token '<', ... is not valid JSON"。本 PR 把 /standalone、/live 加入代理白名单,并把既有客户端源码豁免(原 /extensions/、/session-catalog/)扩展到 /live/,让 vite 继续服务 client/live/* 模块。生产 daemon 不受影响。
Findings / 核对情况
None blocking, and the design is exactly right for the collision it resolves. Because vite is configured with root:'client', the source directory client/live/ is reachable at the literal URL /live/* — which collides with the daemon's /live/* API routes. So the proxy prefix and the bypass exemption must be added together, and this PR does precisely that, mirroring the pre-existing pattern where /extensions (proxy key plus bypass exemption) and /session-catalog already work the same way. I confirmed client/live/ genuinely holds source modules (useLiveVoice.ts, LiveVoiceButton.tsx, a .module.css), so the /live/ exemption is not dead code.
无阻塞项,且针对它要解决的撞名问题,设计完全正确。因为 vite 配了 root:'client',源码目录 client/live/ 就以字面 URL /live/* 被访问——与 daemon 的 /live/* API 路由撞名。所以代理前缀与 bypass 豁免必须成对加入,本 PR 正是如此,完全沿用了 /extensions(代理项 + 豁免项)与 /session-catalog 的既有模式。我确认 client/live/ 确有源码模块(useLiveVoice.ts、LiveVoiceButton.tsx、一个 .module.css),故 /live/ 豁免并非死码。
The bypass predicate grouping is correct: GET && (extensions || session-catalog || live) && source-extension-regex — the added startsWith('/live/') stays inside the same parenthesized group, so there is no operator-precedence slip. The source-extension regex cleanly separates a source request like /live/useLiveVoice.ts (bypass, so vite serves it) from an API call like /live/status (no extension, so it proxies to the daemon), and the daemon's /live routes are JSON, never .ts/.js/.css, so nothing is wrongly bypassed. Prefix matching (http-proxy-middleware startsWith) keeps /standalone and /live free of collision with the existing /session, /list, /file, /voice keys — /standalone/sessions is not matched by /session because matching is prefix-only. The two added tests pin both directions (source module returns the url; API returns undefined to proxy) and match the implementation, so removing either half would fail.
bypass 判断的分组正确:新增的 startsWith('/live/') 仍在同一括号组内,无运算符优先级疏漏。源码后缀正则干净区分 /live/useLiveVoice.ts(豁免→vite 服务)与 /live/status(无后缀→代理→daemon),且 daemon 的 /live 路由都是 JSON、不会是 .ts/.js/.css,故不会误豁免。前缀匹配下 /standalone、/live 与既有 /session、/list、/file、/voice 键无冲突——/standalone/sessions 不会被 /session 命中,因为匹配仅按前缀。两条新增测试分别钉住两个方向(源码返回 url、API 返回 undefined 走代理)且与实现吻合,缺任一半即打红。
The bot's only comment on this head is one Suggestion about the inline comment wording (vite.config.ts:122), non-blocking; it also observed the full suite at 5498 files / 503 passed.
机器人在本 head 仅一条关于行内注释措辞的建议(vite.config.ts:122),非阻断;并观察到全量 5498 文件 / 503 通过。
CI / 持续集成
EN: At publish time on aaaa61d: Web-shell Visuals, Security Checks, Autofix and Triage green; Qwen Code CI still in_progress. Stated as observed; I did not poll or run anything (static review only). Dev-only config, so the CI risk surface here is the test additions, which are sound.
中文:发布时刻 Web-shell Visuals、Security Checks、Autofix、Triage 已绿,Qwen Code CI 仍运行中。如实记录,未轮询、未运行(纯静态)。仅 dev 配置,此处 CI 风险面就是新增测试,而其是可靠的。
Verdict / 结论
🔵 APPROVE. Minimal, correct, dev-only fix that follows the established per-prefix proxy + bypass convention and is pinned by tests covering both the source-serving and API-proxying paths. No Critical or Important finding. / 改动最小、正确、仅 dev 的修复,沿用既有的逐前缀代理 + 豁免惯例,测试覆盖源码服务与 API 代理两侧。无 Critical 或 Important 发现。建议 Qwen Code CI 在本 head 转绿后合并。
yiliang114
left a comment
There was a problem hiding this comment.
LGTM — dev-mode-only fix, tightly scoped:
- Adds the missing
/standaloneand/liveprefixes to the daemon proxy allowlist, matching the existing per-prefix pattern (and the documented SPA-fallback failure mode). - The
/live/source-module bypass extends the existing/extensions///session-catalog/exemption with the same GET + module-extension shape, soclient/live/*keeps being served by vite instead of proxied. - Tests pin both directions: source modules bypass (return the url), API fetches proxy (bypass returns undefined).
One note for the record: the Ubuntu unit-test check was still in progress at approval time; merge should wait for it to land green.

What this PR does
Adds the missing
/standaloneand/liveroute prefixes to the Web Shell vite dev-server proxy allowlist, and extends the existing client-source bypass exemption (previously covering/extensions/and/session-catalog/) to/live/so vite keeps servingclient/live/*source modules.Why it's needed
The vite dev proxy only forwards an explicit allowlist of daemon route prefixes.
/standalone/sessions*and/live/*were missing, so in dev mode those API calls fell through to vite's SPA fallback, which servesindex.htmlforAccept: */*requests. The client then failed JSON parsing withUnexpected token '<', "<!doctype "... is not valid JSON— most visibly when clicking a standalone session in the sidebar or creating a new one, and on Live voice status calls. The production daemon is unaffected (its SPA fallback only claims document navigations); this is a dev-mode-only breakage.Reviewer Test Plan
How to verify
main, run the daemon (qwen serve --port 4170 --workspace <dir>) and the Web Shell dev server (cd packages/web-shell && npm run dev), then open the dev URL and click a standalone session in the sidebar (or create a new standalone session) — an error toastUnexpected token '<', "<!doctype "... is not valid JSONappears.curl -s -i -H 'Accept: */*' http://localhost:5173/standalone/sessionsreturns200 text/html(the SPA shell) before this PR.GET /live/useLiveVoice.tsmust still be served by vite astext/javascript(client source, not proxied).cd packages/web-shell && npx vitest run client/vite-config.test.ts(6 tests pass).Evidence (Before & After)
Before (dev server on 5173, unpatched):
After (same daemon, vite started from this branch):
Tested on
Environment (optional)
Local
qwen servedaemon (port 4170) + Web Shell vite dev server; verified both via unit tests and live HTTP probes against a vite instance started from this branch.Risk & Scope
/acp,/language,/a2ui) are also unproxied but have no Web Shell callers today; left as-is.Linked Issues
N/A
中文说明
本 PR 做了什么
给 Web Shell 的 vite 开发服务器代理允许列表补上缺失的
/standalone和/live路由前缀,并把现有的客户端源码豁免规则(此前覆盖/extensions/和/session-catalog/)扩展到/live/,使 vite 继续正常服务client/live/*源码模块。为什么需要
vite 开发代理只转发显式列出的 daemon 路由前缀。
/standalone/sessions*和/live/*之前缺失,导致开发模式下这些 API 请求落入 vite 的 SPA 回退——它对Accept: */*的请求也返回index.html。客户端随后 JSON 解析失败,报出Unexpected token '<', "<!doctype "... is not valid JSON——最明显的场景是在侧栏点击 standalone 会话或新建会话,以及 Live 语音状态调用。生产 daemon 不受影响(其 SPA 回退只认领文档导航请求);这只是开发模式下的问题。评审者验证计划
如何验证
main上启动 daemon(qwen serve --port 4170 --workspace <dir>)和 Web Shell 开发服务器(cd packages/web-shell && npm run dev),打开开发地址,在侧栏点击一个 standalone 会话(或新建一个)——会出现Unexpected token '<', "<!doctype "... is not valid JSON的错误提示。curl -s -i -H 'Accept: */*' http://localhost:5173/standalone/sessions在本 PR 之前返回200 text/html(SPA 外壳页面)。GET /live/useLiveVoice.ts必须仍由 vite 以text/javascript服务(客户端源码,未被代理走)。cd packages/web-shell && npx vitest run client/vite-config.test.ts(6 个测试通过)。证据(修复前后)
修复前(5173 上的开发服务器,未打补丁):
修复后(同一 daemon,vite 从本分支启动):
测试平台
macOS 已验证;Windows / Linux 未测试。
环境(可选)
本地
qwen servedaemon(端口 4170)+ Web Shell vite 开发服务器;通过单元测试和从本分支启动的 vite 实例的实时 HTTP 探测双重验证。风险与范围
/acp、/language、/a2ui)同样未被代理,但今天 Web Shell 没有调用方;保持原样。关联 Issue
无