fix(cli): Bound ACP textual tool-result payloads - #8450
Conversation
|
Re-run at an unchanged head ( Template looks good ✓ Problem: observed and measured, not theoretical. The deterministic fake-MCP reproduction showed a single 499,999-byte tool result producing a 1,000,385-byte ACP frame on Direction: aligned. Phase 1 bounded the model-facing response and deliberately excluded ACP display events; this closes that exclusion at the live and replay delivery boundaries while keeping canonical transcripts, model input, and offline export lossless. The one awareness flag stands: the capped fields sit on the ACP wire surface external clients render — schema-compatible but intentionally lossy above 65,536 JSON UTF-8 bytes. That product decision is documented in the design doc, tracked in #8447/#8091, and has had two senior reviewers over exactly this surface. Size: not applicable (cli-only change, no core paths). At the current head: 410 production lines (401 projector + 9 wiring), 697 test lines, 66 docs lines. Approach: scope remains right — one projector applied at the live Risk: Moving on to code review. 🔍 中文说明本次 re-run 针对未变化的 head( 模板完整 ✓ 问题:已观测且有实测数据,不是理论问题。确定性 fake-MCP 复现显示:一个 499,999 字节的工具结果在 方向:对齐。Phase 1 限制了模型侧响应并刻意排除 ACP display event;本 PR 在 live 和 replay 交付边界补上这个排除项,同时保持 canonical transcript、模型输入和离线 export 无损。提醒仍然存在:被限制的字段位于外部客户端渲染的 ACP wire 表面——schema 兼容,但超过 65,536 JSON UTF-8 字节后有意有损。这个产品决策有设计文档记录、在 #8447/#8091 中跟踪,且已有两位资深审查者审视过这一表面。 规模:不适用(仅 cli 改动,未触及核心路径)。当前 head:410 行生产代码(401 行 projector + 9 行接线)、697 行测试、66 行文档。 方案:范围仍然合理——一个 projector 应用于 live 风险: 进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewRe-reviewed at What I verified against the tree and diff on this pass, beyond the prior pass's checks:
The tests pin the change: the 65,535/65,536/65,537 boundary cases, the 1,191/1,192 block-count pair, the exemption and idempotence regressions all fail if the projection is removed or loosened — this is not a suite that passes with the diff reverted. The Two senior reviewers have been over this surface at the equivalent pre-rebase commit: @wenshao's measurement-based review (300-case seeded fuzz of the extracted projector) found no correctness bug — his findings are design/robustness/observability; @yiliang114 approved twice with independent fuzz verification, and both approvals were dismissed only by the author's pushes, never by a finding. Their remaining items are tracked below as follow-ups, not blockers. sequenceDiagram
participant P1 as Tool producer
participant P2 as Session.sendUpdate (live)
participant P3 as Replay collector
participant P4 as Text projector
participant P5 as ACP client
participant P6 as Canonical transcript
P1->>P6: record lossless tool result
P1->>P2: tool_call_update with text result
P2->>P4: project eligible text fields
P4-->>P5: bounded update frame
P6->>P3: session load or paged replay
P3->>P4: project eligible text fields
P4-->>P5: bounded replay frame
Files changed (9 of 9 shown)
Test evidenceEvidence below is the PR's own CI on the reviewed commit, read through the API — I do not run PR code in this environment. CI has now settled at this head. Everything is green except one check:
Not verified: the frame-level before/after numbers (1,000,385 → 131,347 bytes) remain the author's macOS measurements until the sandboxed verify report below posts; the unit suite pins the algorithm, and two independent reviewers fuzz-verified the byte accounting, but the end-to-end frame claim itself is author-reported.
One row per check name (latest run); failures sort first. / 每个检查名一行(取最新一次运行),失败项排在最前。 Sandboxed verification is in flight on this very run — the Non-blocking follow-ups, unchanged from the last pass and still open:
中文说明代码审查在 本次在树与 diff 上核实的内容(在上次核实之外):
测试钉住了改动本身:65,535/65,536/65,537 字节边界用例、1,191/1,192 块数成对用例、豁免与幂等回归,在移除或放宽投影时都会失败——这不是"去掉 diff 也一样绿"的套件。该 commit 上的 两位资深审查者在等价 pre-rebase commit 上看过这一表面:@wenshao 的实测型审查(300 例固定种子 fuzz)未发现正确性 bug;@yiliang114 以独立 fuzz 验证两次批准,其批准均只因作者推送而被 dismiss。其余事项记录为后续跟进,不构成阻塞。 (时序图与文件清单见上方英文部分。) 测试证据以下证据来自被审 commit 的 PR 自有 CI(通过 API 读取)——本环境不运行 PR 代码。该 head 的 CI 现已结束:除一项外全部为绿。唯一失败的
未验证:frame 级 before/after 数字(1,000,385 → 131,347 字节)在下方沙箱 verify 报告发布之前仍是作者在 macOS 上的测量;单元测试钉住了算法本身,两位独立审查者 fuzz 验证过字节计数,但端到端 frame 结论仍为作者自述。 沙箱验证正在本次运行中进行——当前 非阻塞跟进事项与上次一致且仍未处理:可观测性(wenshao #2)、设计文档一致性(wenshao #5)、 — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 4/5 — clean re-review at an unchanged head; the only thing withholding approval is one red CI check that this PR demonstrably did not cause, plus the verify report still in flight. CI has settled since the last pass, so the honest state of play: the code review found nothing new to doubt — I re-verified the wiring and the blast radius independently this pass ( Stepping back: my independent baseline for this problem is still the shape this PR takes — a pure projector at the single live delivery chokepoint and the shared replay collector, strict eligibility gating, idempotent by construction. The production code under review is identical to what two senior reviewers already passed at the equivalent pre-rebase commit, and @wenshao has now approved this exact commit ( Why I am not approving in this pass, despite the verdict: the bot's approval attests to the state of the reviewed commit, and that commit's required unit-suite check is red. Even with the failure classified as an unrelated environmental flake, approving now would attest to a green that does not exist — and the repo's own finalize workflow holds approvals to the same standard. The unblock is mechanical: re-run the failed job (the author has write access, as does any maintainer). The standing approve-on-green marker below means no further What keeps this from a 5: the same open non-blocking follow-ups from @wenshao's review — observability (a projected frame is currently silent), the design-doc consistency note, the Verdict: approve once CI is green on the reviewed commit. ✅ 中文说明回顾全局:我为这个问题独立拟的方案仍然是本 PR 的形态——在唯一 live 交付 chokepoint 和共享 replay collector 上做纯投影、严格资格门控、构造上幂等。本次我独立重新核实了接线与影响面( 本次不发出批准的原因:机器人的批准是对被审 commit 状态的背书,而该 commit 的必选单测检查目前是红色。即使该失败已被定性为无关的环境性 flake,现在批准也是在为一个不存在的绿色背书——仓库自己的 finalize 工作流对批准也是同一标准。解锁动作是机械的:重跑失败的 job(作者与维护者都有 write 权限)。下方常驻的 approve-on-green 标记意味着无需再次 不到 5 分的原因:@wenshao 审查中仍未处理的非阻塞跟进事项——可观测性、设计文档一致性说明、 结论:在被审 commit 的 CI 全绿后批准。✅(CI 全绿后的批准动作由 finalize 工作流执行。) — Qwen Code · qwen3.8-max Reviewed at |
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. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship — CI landed green after the review. ✅
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
中文说明
已审查——无阻断问题。 建议见行内评论。
— qwen3.8-max via Qwen Code /review (v0.21.4)
|
Follow-up for the review suggestions on cc1cedd:\n\n| Thread | Resolution |\n| --- | --- |\n| Oversized-field scanning | Accepted: added budget-aware early exit and removed per-code-unit object allocation. |\n| A2UI serverId coverage | Accepted: added a serverId-only exemption regression. |\n| Water-fill ordering | Accepted: added asymmetric exact-budget saturation coverage. |\n| Low-budget truncation guard | Accepted: changed the fallback to a budget-fitting surrogate-safe prefix. |\n| Shared detached-copy helper | Not taken: extracting a three-line idiom across CLI and Core would add coupling while the budgeting and cut-point algorithms intentionally differ. |\n\nValidation completed on the current head: 530 focused tests, 74 fast-path tests, formatting/lint, build/typecheck, bundle, deterministic Unicode/projection fuzz, and two stable fake-MCP wire captures. No additional code change was needed in this monitoring cycle. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
中文说明
已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
— qwen3.8-max via Qwen Code /review (v0.21.3)
|
Review follow-up for 6c16210:\n\n| Feedback | Decision | Result |\n| --- | --- | --- |\n| Reuse the Core allocator | Not taken | The Core helper is module-private and character-budget based; exporting it would widen the public cross-package surface and couple two different contracts. |\n| Narrow the A2UI exemption | Not taken | Whole-update A2UI exemption is an explicit contract here. A leading-bracket heuristic is not equivalent to the daemon parser, and generic A2UI/frame bounds remain out of scope. |\n| Bound pathological multi-block scanning | Fixed | Added cumulative early-stop plus per-block maximum-allocation caps, with a 600-block regression test. |\n\nValidation:\n- Related ACP projector/session/replay/export/fast-path tests: 607 passed.\n- Prettier and targeted ESLint: passed.\n- Build, typecheck, and bundle: passed.\n- Fixed-seed projector fuzz: 300 samples passed.\n- Exact-vs-capped allocator comparison: 97,102 feasible trials with zero divergence.\n- 600 × 40,000-byte benchmark median: approximately 32.1 ms before and 9.0 ms after on the same machine.\n- Two consecutive post-fix open-ended audit passes found no remaining actionable issue.\n\nAll three review threads have been answered and will be resolved. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
中文说明
已审查。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。
— qwen3.8-max via Qwen Code /review (v0.21.3)
|
@qwen-code /takeover |
|
🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. Remove the 中文说明🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix review round — no code change needed (PR #8450)Every actionable finding in this round's feedback was already handled by earlier commits on this branch, and each claim was re-verified against the current head ( Finding dispositions
Round 3 of the automated review reported zero findings, and there are no failed or still-red checks. VerificationCommands actually run this round (no commit was made; focused re-verification of the already-fixed findings):
中文说明Autofix 审查轮次 — 无需代码变更(PR #8450)本轮反馈中的每个可处理发现都已由本分支上的早期提交处理完毕,并且每条修复声明都已在当前 head( 发现处理结果
自动审查的第 3 轮报告零发现,且没有失败或仍然红色的检查。 验证本轮实际运行的命令(未产生提交;仅对已修复发现做Focused复核):
Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
What this PR does
This PR adds an internal ACP transport projection for canonical textual tool-call fields in every status. Eligible pure-text
contentarrays and primitive-stringrawOutputvalues are bounded independently to 65,536 UTF-8 bytes as serialized JSON fields, using exact JSON escape accounting, deterministic 20/80 head-tail previews, and bounded multi-block allocation. Live updates and every replay collector are covered, while A2UI and structured or rich content remain unchanged.The projection is immutable and idempotent. Canonical transcripts, model-facing responses, artifact metadata, ACP schemas, public APIs, and offline exports are not changed. The design documentation records the contract and updates the older statement that immediate ACP display events are always unchanged.
Why it's needed
Phase 1 bounds model-facing tool responses, but ACP display construction can still duplicate a complete tool result in both
contentandrawOutputafter model finalization. A deterministic fake-MCP reproduction produced a 1,000,385-byte ACP frame for one 499,999-byte result onorigin/main. This PR limits the eligible display fields at the actual live and replay delivery boundaries without making transport loss canonical.Reviewer Test Plan
How to verify
Run the focused projector, live Session, replay collector, and offline export tests from
packages/cli. Confirm the JSON-byte threshold cases pass at 65,535, 65,536, and 65,537 bytes; Unicode, escape, surrogate, multi-block, A2UI, rich-content, idempotence, and structural-sharing cases remain correct; live and replay fields are each at most 65,536 bytes; and offline export preserves the original length and SHA-256.Run the root build and typecheck, then bundle and run the serve fast-path closure check. The candidate should build without changing the fast-path bundle closure. Repeating the deterministic fake-MCP exchange should retain both fields, produce parseable JSON, and keep the ordinary textual fixture frame below 256 KiB.
Evidence (Before & After)
The same fake OpenAI response and stdio fake MCP tool returned 499,999 ASCII bytes to both bundles. Only lengths and SHA-256 values were captured; no result body was printed.
origin/main(be4f5e0963)cc1ceddfde)contentJSON UTF-8 bytesrawOutputJSON UTF-8 bytesSource fixture: 499,999 bytes, SHA-256
146e22690b10e8ea73a95f10a7399f26318528775e4479cc9f078f42e79c78a4. The offline export regression preserves the original source length and SHA-256. The candidate frame result applies only to this ordinary textual fixture and is not a universal frame-size contract.Tested on
Environment (optional)
macOS 26.4.1, Node.js 24.12.0, npm 10.9.8, local production bundles, sandbox disabled.
Risk & Scope
Linked Issues
Refs #8447
Refs #7306
Refs #8091
中文说明
本 PR 做了什么
本 PR 为所有状态的 ACP tool-call update 中的规范文本字段增加内部 transport projection。符合条件的纯文本
content数组与 primitive stringrawOutput会分别限制在字段 JSON 序列化后 65,536 UTF-8 字节以内,使用精确的 JSON 转义字节计算、确定性的 20/80 头尾预览以及有界的多 block 预算分配。live update 和所有 replay collector 都受到覆盖,A2UI、结构化或富内容保持不变。投影过程不可变且幂等。canonical transcript、模型侧响应、artifact metadata、ACP schema、公共 API 和离线 export 均不改变。设计文档记录了该契约,并修正了旧文档中“即时 ACP display event 始终不变”的表述。
为什么需要
Phase 1 已限制模型侧工具响应,但 ACP display 会在模型 finalization 之后仍将完整工具结果同时放入
content和rawOutput,形成重复放大。确定性的 fake-MCP 复现在origin/main上仅一个 499,999 字节结果就产生了 1,000,385 字节 ACP frame。本 PR 在实际 live 和 replay 交付边界限制符合条件的 display 字段,同时避免让 transport 有损结果进入 canonical 数据。Reviewer 测试计划
如何验证
在
packages/cli运行 projector、live Session、replay collector 和 offline export 的聚焦测试。确认 65,535、65,536、65,537 字节阈值用例通过;Unicode、转义、代理对、多 block、A2UI、富内容、幂等和结构共享用例保持正确;live 与 replay 字段分别不超过 65,536 字节;offline export 保留原始长度和 SHA-256。运行根目录 build 和 typecheck,然后执行 bundle 与 serve fast-path 闭包检查。候选版本应正常构建且不改变 fast-path bundle 闭包。重复确定性的 fake-MCP 交换后,两个字段都应继续存在,JSON 可解析,普通文本 fixture frame 小于 256 KiB。
证据(修改前后)
两个 bundle 使用完全相同的 fake OpenAI 响应和 stdio fake MCP 工具,返回 499,999 ASCII 字节。只采集长度和 SHA-256,没有打印结果正文。
origin/main(be4f5e0963)cc1ceddfde)contentJSON UTF-8 字节rawOutputJSON UTF-8 字节源 fixture 为 499,999 字节,SHA-256 是
146e22690b10e8ea73a95f10a7399f26318528775e4479cc9f078f42e79c78a4。offline export 回归测试保留原始源长度和 SHA-256。候选 frame 结果只适用于这一普通文本 fixture,不是通用 frame 大小契约。测试平台
环境(可选)
macOS 26.4.1、Node.js 24.12.0、npm 10.9.8、本地 production bundle、未启用 sandbox。
风险与范围
关联 Issue
Refs #8447
Refs #7306
Refs #8091