Repository navigation
feat(generated-ui): check a view's initial calculation before display - #15790
Conversation
Run the generated JavaScript's initialization once in the same bounded QuickJS interpreter before returning a snapshot, using the markup's initial input values and a stand-in ui API. Syntax errors, exceptions from the initial calculation, initial updates to missing element IDs and markup the view rejects now return a VIEW_ERROR tool result that names the problem, with line and column, so the model can correct the document instead of reporting a broken card as ready. The App names ordinary script errors instead of a generic runtime-limit message, reading only native error data properties so guest getters and toString hooks never run. When a view fails because of its document, the card offers to send the error to the session for correction through the existing host message review. The writer skill documents the check and that Intl is unavailable and toLocaleString ignores locales and options.
yiliang114
left a comment
There was a problem hiding this comment.
Review of #15790 — feat(generated-ui): check a view's initial calculation before display
Summary
This PR adds pre-flight script checking to generated_ui_show. Before returning a snapshot, the server now runs the generated JavaScript's initialization in the same bounded QuickJS interpreter the view uses, catching syntax errors, runtime exceptions, updates to missing IDs, and unsupported markup — all surfaced as VIEW_ERROR tool results the model can correct.
Security & Correctness
The check runs inside the same bounded QuickJS interpreter with the same 250ms limit and no host, file, network, or tool access. The DESCRIBE helper that reads error details is evaluated before any generated code, captures native references, walks the prototype chain only to documented native error prototypes, and reads only own data properties (typeof descriptor.value === 'string') — guest getters, toString hooks, and Proxies never execute. Non-native thrown objects and resource exhaustion correctly keep the generic message. The error message is sanitized (control/Unicode formatting characters stripped, truncated to 160 chars) before display.
Structure validation (createShowResult) still runs first, so document-level issues are caught before the script check. If the QuickJS module fails to load, checkDocument silently passes (the view runtime still validates at display time).
The "Ask the assistant to fix" flow correctly distinguishes document errors from host/browser unavailability (HOST_FAILURE regex), only offers the button when canReview is true, and prevents concurrent submissions. The send function extracted from continueWithParameters shares the same error handling.
Findings
No P0, P1, P2, or P3 findings. The change is focused, well-tested (227 existing + new check.test.ts + updated integration tests), and CI is green on all code-compiling lanes (Test ubuntu/Node 22.x, Lint & Static, Integration Tests, web-shell E2E Smoke).
Notes
- The
readMarkupregex-based HTML parser has known limitations documented in the PR description and README ("unusual markup can differ slightly from what the browser starts with"). The markup it parses is generated by the model using a small, controlled subset, so this tradeoff is appropriate. DESCRIBEandBRIDGEare large embedded JavaScript strings — consistent with the pre-existing pattern in this file.
Verdict
APPROVE. No blocking issues. This is a well-executed improvement that closes a real gap: broken generated scripts that the model incorrectly reports as ready.
qwen-code-dev-bot
left a comment
There was a problem hiding this comment.
APPROVE — 审查版本 head af4f2d61(提交前后两次读取一致,单 commit)。
- 历史 Review:仅 yiliang114 一条 APPROVE,无 P0–P3 finding、无未解决 thread,与本次独立检查结论一致。
- CI:head 上唯一 required check
Lint & Static (ubuntu-latest, Node 22.x)success;Test (ubuntu-latest, Node 22.x)、web-shell E2E Smoke、Integration Tests (no-AK)、TUI parity、OpenTUI no-flicker、Managed Agent gate均 success(含针对已构建 bundle 的 stdioVIEW_ERROR集成用例)。非 required 的precheck-pr / precheck仍 pending,属自动化上下文,不构成门禁阻塞。 - 独立检查未发现新 Critical:
- 服务端执行边界成立:
checkDocument走UiRuntime,构造函数即设setMemoryLimit(32MB)/setMaxStackSize(256KB)/ 中断处理器,round()每轮 250ms 上限并在出错时dispose(),while(true){}会返回 time limit;guest 侧只有 BRIDGE 与 DESCRIBE,无 host/fs/network/tool 通道;模块加载失败时 fail-open,展示期仍由视图校验。 - 错误信息不泄露宿主:
formatScriptError只接受 7 种原生错误名(QuickJSInternalError与非 Error 抛出仍走笼统提示),仅从generated-ui.js:line:col提取位置,消息剥离控制/格式字符并截断 160 字符。 - 规则与浏览器面一致:
check.ts的ID正则、INPUT_TYPES、控件必须带 id、select[multiple]拒绝,与surface.ts的校验逐条对应,不会把视图能接受的文档误判为VIEW_ERROR(服务端未覆盖的 element/input 数量上限属 fail-open 方向)。 mcp.ts中createShowResult仍先执行,结构问题优先报INVALID_DOCUMENT;main.ts的修复入口只在canReview为真、非 host 失败时可用,pending/accepted后禁用,不绕过宿主消息审阅。
- 服务端执行边界成立:
非阻塞小问题(可后续处理,不影响合入):check.ts 的 decode() 直接 String.fromCodePoint(Number(code)),遇到越界数字字符引用(如 �、�)会抛 RangeError;该调用在 checkDocument 的 try 之外,会让工具回调 reject,被 MCP SDK 兜底成 isError: true + Invalid code point …,即浏览器本可正常显示的文档拿到一条无指导的错误。建议把 fromCodePoint 包一层降级为原样文本。
|
Thanks for the PR! Template looks good ✓ Problem: observed, not theoretical. A real Web Shell run produced a card whose generated script had one extra Direction: aligned. This closes a feedback loop the feature was missing. The tool result is the model's only signal about whether a card worked, and today it says "success" for a card that cannot render. It is part of tracked issue #14019 (open, One thing a maintainer should accept consciously rather than inherit: the server process now executes model-generated JavaScript. It previously never did — the README said so explicitly, and this PR rewrites that sentence. The execution runs inside the same QuickJS WASM interpreter with the same 32 MB / 256 KB stack / 250 ms bounds and no host, file, network or tool bindings, and it fails open (a WASM load failure skips the check and leaves display-time validation intact), so I don't read this as a sandbox-policy change. But "the MCP server runs generated code" is a posture change that deserves a deliberate yes. Size: not applicable — no core paths. All 11 files sit under Approach: the shape is right, and it matches what I proposed from the description before reading the diff — reuse the existing bounded runtime with a stand-in Two questions worth sitting with, neither a blocker:
The Risk: no elevated risk signals — the high-risk path scan matched nothing in this diff. Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题: 已观测到的真实故障,不是理论性加固。一次真实的 Web Shell 运行中,生成的脚本多了一个 方向: 对齐。这个 PR 补上了该功能缺失的反馈回路——工具结果是模型判断卡片是否可用的唯一信号,而现在它对一张无法渲染的卡片报告"成功"。它属于已跟踪的 issue #14019(open、 有一点需要维护者明确认可、而不是被动继承:服务器进程现在开始执行模型生成的 JavaScript。此前它从不执行——README 明确这么写,本 PR 也改写了这句话。执行发生在同一个 QuickJS WASM 解释器内,内存 32 MB / 栈 256 KB / 250 ms 的限制完全一致,且没有宿主、文件、网络或工具绑定;并且是失败即放行(WASM 加载失败会跳过检查,展示时校验依然生效),所以我不认为这是沙箱策略变更。但"MCP 服务器会运行生成代码"是一次姿态变化,值得一个明确的 yes。 规模: 不适用——未触及核心路径。11 个文件全部位于 方案: 结构是对的,也与我在读 diff 之前仅凭描述给出的方案一致——复用已有的受限 runtime,配一个替身 两个值得考虑的问题,都不是阻塞项:
写作技能里新增的 风险: 无升级风险信号——高风险路径扫描在本 diff 中没有命中任何文件。 进入代码审查 🔍 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
Code reviewNo critical blockers. The design is sound and the security-sensitive part is handled with more care than I expected. Notes below, all non-blocking, in the order I'd want them addressed. 1.
Both need unusual markup, and the failure mode is recoverable (the error names the problem, the model corrects, the second attempt succeeds), so I am not calling this a blocker. But The other divergences all run the safe way — no 512-node cap, 2. Initial values can differ from the browser's in two specific ways. Both are covered in general terms by Risk & Scope; naming them exactly so they can be judged: 3. 4. What I checked and think is right, because a review that only lists doubts is not useful:
sequenceDiagram
participant P1 as MCP client
participant P2 as generated_ui_show handler
participant P3 as contract validation
participant P4 as readMarkup
participant P5 as QuickJS runtime
participant P6 as checkPatches
P1->>P2: show document with html css and javascript
P2->>P3: createShowResult
P3-->>P2: normalized fields and size limits ok
P2->>P4: readMarkup on html
P4-->>P2: ids and controls and initial input values or a markup problem
P2->>P5: initialize with javascript and initial inputs
P5-->>P2: patches or a thrown error named by DESCRIBE
P2->>P6: checkPatches against markup
P6-->>P2: missing id or non-input value target or clean
P2-->>P1: VIEW_ERROR naming the problem or the snapshot as before
TestingEvidence carried here: the PR's own CI check conclusions on the reviewed commit, read through the API. This was an unattended CI run, so the review is static by rule — I did not build or execute anything from this PR. Why this CI run does cover the change: the
Of the 88 check-runs on this commit, 21 succeeded, 58 were skipped and 8 were cancelled. Every cancelled one is a bot-orchestration job ( Not verified, and why:
Sandboxed verification would settle exactly that gap: 中文说明代码审查没有阻塞性问题。整体设计是合理的,安全敏感的部分处理得比我预期的更细致。以下都是非阻塞项,按我希望被处理的顺序排列。 1.
两者都需要少见的标记,且失败模式可恢复(错误会指明问题,模型修正后第二次即可成功),所以我不认为这是阻塞项。但 其余差异都朝安全方向偏——没有 512 节点上限、 2. 初始值在两个具体点上可能与浏览器不同。 Risk & Scope 已经笼统提到,这里点明以便判断: 3. 4. 我核对过并认为正确的部分,因为只列疑问的评审没有价值:
测试本次提供的证据: 在所审 commit 上,通过 API 读取的 PR 自身 CI 检查结论。这是一次无人值守的 CI 运行,按规则评审是静态的——我没有构建或执行本 PR 的任何代码。 为什么这次 CI 确实覆盖了本改动: CI 表格见上方英文部分(未重复)。该 commit 上共 88 个 check-run:21 成功、58 跳过、8 取消。所有取消项都是机器人编排 job( 未验证的部分及原因:
沙箱验证正好能补上这个缺口: — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
|
Confidence: 4/5 — the design is right and the hostile-input handling is better than this feature strictly needed; the reservation is a second copy of the markup rules that nothing pins in the acceptance direction. Stepping back: the problem is real and it is the worst failure mode a generated-UI feature can have. The tool result is the only channel through which the model learns whether a card worked, and before this PR that channel said "success" for a card that could not render — so the model confidently told the user the card was ready. Everything else in the diff follows from closing that loop. The approach matches what I proposed from the description alone before reading any code — reuse the existing bounded runtime with a stand-in The part that tries hardest is The "Ask the assistant to fix this view" action is the most arguable piece of scope, and I have landed on keeping it. It is the runtime half of the same feedback loop, it reuses the existing host message review instead of opening a new channel, and the extracted On the posture question from Stage 1 — the server now executes model-generated JavaScript. I flagged it so it would be a deliberate yes rather than an inherited one. It stays inside the same interpreter, the same bounds and the same absence of host bindings, it fails open, and the README sentence that claimed otherwise was corrected instead of left to go stale. A maintainer has also already approved this exact commit, so that yes has been given by a human. CI is green on the reviewed commit with no pending PR workflow runs, and the green Approving. The follow-ups I would like, none of them blocking: a test pinning the acceptance direction of 中文说明置信度:4/5 —— 设计方向正确,对恶意输入的处理甚至好于这个功能严格需要的程度;我的保留意见是标记规则出现了第二份副本,而"应接受"这个方向没有任何测试固定。 退一步看:问题是真实的,而且是生成式 UI 功能可能存在的最糟糕失效模式。工具结果是模型得知卡片是否可用的唯一通道,而本 PR 之前,这个通道对一张无法渲染的卡片报告"成功"——于是模型自信地告诉用户卡片已经就绪。diff 里的其他一切都是在补这个回路。 方案与我在读任何代码之前、仅凭描述给出的提议一致——复用已有的受限 runtime,配一个替身 最费力的部分是 "请助手修复此视图"这个操作是范围上最值得争论的部分,我的结论是保留它。它是同一个反馈回路的运行时那一半,复用了已有的宿主消息审阅而没有新开通道,并且抽取出的 关于 Stage 1 提出的姿态问题——服务器现在会执行模型生成的 JavaScript。我把它标出来,是为了让它成为一个明确的 yes,而不是被继承的默认。它仍在同一个解释器、同样的限制、同样没有宿主绑定的范围内运行,失败即放行,而且那句原本声称"从不执行"的 README 被改正了,而不是留着变陈旧。也已有一位维护者批准了这个确切的 commit,所以这个 yes 已经由人给出。 所审 commit 上 CI 全绿,没有待完成的 PR workflow run,而且绿色的 予以批准。我希望看到的后续工作,均非阻塞:为 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
qwen-code-review-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
|
Released in v0.26.3. |
What this PR does
Before returning a snapshot, the bundled Generated UI server now runs the generated JavaScript's initialization once in the same bounded QuickJS interpreter the view uses, with a stand-in
uiAPI and the markup's initial input values. Syntax errors, exceptions from the initial calculation, initial updates to element IDs that do not exist, and markup the view would reject (duplicate or invalid IDs, controls without IDs, unsupported input types, multiple selects) return aVIEW_ERRORtool result that names the problem, with line and column where available, so the model corrects the document instead of reporting a broken card as ready.In the card, ordinary script errors are named (for example
Script error: ReferenceError: 'total' is not defined (line 3, column 7).) instead of the generic "Generated JavaScript failed or exceeded a runtime limit". The description reads only a native error's own data properties, so guest getters,toStringhooks and Proxies never run; resource exhaustion keeps the generic message. When a view fails because of its document, the card offers Ask the assistant to fix this view, which sends the error to the session through the existing host message review. The writer skill documents the check and notes thatIntlis unavailable andtoLocaleStringignores locales and options.Why it's needed
In a real Web Shell run (qwen3.8-max), a subscription comparison card failed with only "Generated JavaScript failed or exceeded a runtime limit." The generated script had one extra
), the tool result reported success, and the model told the user "The interactive comparison card is ready". Nothing checked the script before display, the error did not say what was wrong, and the model never learned about it. Silent formatting differences (toLocaleStringwith options) produced wrong amounts without any error.Reviewer Test Plan
How to verify
npm run build --workspace=@qwen-code/generated-ui) and callgenerated_ui_showondist/bundled-main.jsover stdio with a document whose JavaScript has a syntax error, references an undefined name in its initial calculation, or callsui.texton an ID that is not in the markup. Each returnsisError: truewith aVIEW_ERROR:text naming the problem; a valid document returns the snapshot as before.experimental.generatedUion, a view whose event handler throws showsScript error: …in the card status and enables Ask the assistant to fix this view; clicking it opens the usual message review with the error text.check.test.ts).Evidence (Before & After)
The JavaScript from the failing real run, sent to
generated_ui_showon the built server over stdio:main)isError: false, snapshot returned; the card later shows "Generated JavaScript failed or exceeded a runtime limit."isError: true,VIEW_ERROR: Script error: SyntaxError: expecting ',' (line 1, column 439). Nothing was displayed. Correct the document and call generated_ui_show again with all five fields.Tests:
integrations/generated-ui227 passed (newcheck.test.ts, runtime error naming including hostile thrown objects and redefined error names, the fix action inmain.test.ts, and aVIEW_ERRORcase in the stdio integration test against the built bundle);scripts/tests/generated-ui-assets.test.js5 passed; typecheck and lint clean.Tested on
Environment (optional)
Node 24, package build plus the stdio server; the real-run failure came from a local
qwen servebundle with qwen3.8-max.Risk & Scope
VIEW_ERROR.Linked Issues
Part of #14019.
中文说明
这个 PR 做了什么
内置 Generated UI 服务器在返回快照之前,会用视图所用的同一个受限 QuickJS 解释器,把生成 JavaScript 的初始化运行一次,使用替身
uiAPI 和标记中的初始输入值。语法错误、初始计算抛出的异常、初始更新引用了不存在的元素 ID、以及视图会拒绝的标记(重复或非法 ID、没有 ID 的控件、不支持的输入类型、多选 select),都会返回一个指出问题的VIEW_ERROR工具结果,能给出行号和列号时会一并给出。这样模型会修正文档,而不是把一张坏掉的卡片报告为已就绪。在卡片中,普通脚本错误会被具体指出(例如
Script error: ReferenceError: 'total' is not defined (line 3, column 7).),不再只显示笼统的"Generated JavaScript failed or exceeded a runtime limit"。错误描述只读取原生错误对象自身的数据属性,因此不会触发生成代码的 getter、toString钩子或 Proxy;资源耗尽仍使用笼统提示。当视图因文档本身出错时,卡片提供 请助手修复此视图,通过现有的宿主消息审阅把错误发送到会话。写作技能补充说明了这项检查,并指出Intl不可用、toLocaleString会忽略地区和选项。为什么需要
在一次真实的 Web Shell 运行(qwen3.8-max)中,订阅对比卡片只显示了"Generated JavaScript failed or exceeded a runtime limit."。生成的脚本多了一个
),工具结果却报告成功,模型还对用户说"The interactive comparison card is ready"。展示前没有任何检查,错误信息没有说明问题所在,模型也无从得知。带选项的toLocaleString这类格式差异则会在没有任何报错的情况下算出错误的金额。审阅者测试计划
如何验证
npm run build --workspace=@qwen-code/generated-ui),通过 stdio 对dist/bundled-main.js调用generated_ui_show,传入的 JavaScript 分别包含语法错误、在初始计算中引用未定义的名字、或对标记中不存在的 ID 调用ui.text。每种情况都返回isError: true,文本以VIEW_ERROR:开头并指出问题;合法文档仍像以前一样返回快照。experimental.generatedUi的 Web Shell 中,事件处理函数抛错的视图会在卡片状态中显示Script error: …,并启用 请助手修复此视图;点击后打开常规的消息审阅,内容为错误文本。check.test.ts覆盖)。证据(修改前后)
把那次真实失败中的 JavaScript,通过 stdio 发给构建后服务器的
generated_ui_show:main)isError: false,返回快照;卡片随后显示"Generated JavaScript failed or exceeded a runtime limit."isError: true,VIEW_ERROR: Script error: SyntaxError: expecting ',' (line 1, column 439). Nothing was displayed. Correct the document and call generated_ui_show again with all five fields.测试:
integrations/generated-ui227 个通过(新增check.test.ts;运行时错误命名,包括恶意抛出对象和被改写的错误名;main.test.ts中的修复操作;以及针对构建产物的 stdio 集成测试中的VIEW_ERROR用例);scripts/tests/generated-ui-assets.test.js5 个通过;类型检查与 lint 通过。测试平台
环境(可选)
Node 24,包构建加 stdio 服务器;真实运行中的失败来自本地
qwen servebundle 与 qwen3.8-max。风险与范围
VIEW_ERROR拒绝。关联 Issue
属于 #14019 的一部分。