fix(tools): make code result cards authoritative

This commit is contained in:
Tianyi Cui
2026-07-21 18:38:22 +08:00
parent f9b938a4a8
commit 449e3cf298
19 changed files with 1526 additions and 374 deletions
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-20-code-mode-result-card-completeness.md: 65aad02713498f5dbeff5ab3886da558326a0003
2026-07-20-code-mode-result-card-completeness.zh.md: 082e209544d4ea43f75bb979fc3aa490b426952e
2026-07-20-code-mode-result-card-completeness.md: 1fbdbdd311f359cbe2ab505210768b2136d56067
2026-07-20-code-mode-result-card-completeness.zh.md: 619c742e6adb2dc846a9c278e78938dd8e0559b1
@@ -6,7 +6,7 @@ English | [中文](2026-07-20-code-mode-result-card-completeness.zh.md)
## Problem
The outer `run_code` tool persisted complete rendered content, but its editor presenter ignored that content and rebuilt the card body from a logs-only `presentationMeta` projection. A result-only run appeared correct because an empty presenter body let ACP and TUI fall back to `tool/result.content`. Once the program emitted a log, the presenter supplied non-empty content, that fallback stopped, and the returned value disappeared from the completed card. Failure text and a spill policy's final head/tail preview were vulnerable to the same split ownership.
The outer `run_code` tool persisted complete rendered content, but its editor presenter ignored that content and rebuilt the card body from a logs-only `presentationMeta` projection. A result-only run appeared correct because an empty presenter body let ACP and TUI fall back to `tool/result.content`. Once the program emitted a log, the presenter supplied non-empty content, that fallback stopped, and the returned value disappeared from the completed card. A spill policy's final head/tail preview was vulnerable to the same split ownership whenever captured logs made the stale projection non-empty.
Nested Code calls never owned cards, so producing metadata for the outer call solely to reconstruct one incomplete card also obscured the intended one-card boundary.
@@ -14,13 +14,13 @@ Nested Code calls never owned cards, so producing metadata for the outer call so
The `run_code` output renderer remains the single owner of model-facing outer content. It renders captured logs followed by the return value, the explicit no-output marker, or the failure content produced by the canonical tool pipeline. Post-execute policy and spill may replace that content before it is persisted.
`run_code.presentResult` now forwards the final `result.content` into one generic result card. It deliberately omits the title so the pending card retains the program text. The existing logs metadata remains in `tool/result` for transcript compatibility, but the presenter no longer treats it as a second content source: `tool/result.content` is the durable, replayable, post-policy projection.
`run_code.presentResult` now forwards the final `result.content` into one generic result card. It deliberately omits the title so the pending card retains the program text. The redundant logs-only `presentationMeta` projection is removed: `tool/result.content` is the durable, replayable, post-policy projection and the card's only result-content source.
Nested dispatch remains unchanged. Calls marked by `exec.parent` emit bounded `tool/code-dispatch` diagnostics but no `tool/call` or `tool/result` surface cards, so one outer `run_code` invocation still produces exactly one card.
## Testing
Presenter unit coverage pins logs-only, result-only, logs-plus-result, no-output, failure, and spilled-result content. Every case proves stale metadata cannot replace the final content.
Presenter unit coverage pins logs-only, result-only, logs-plus-result, no-output, and spilled-result content. A separate integration-shaped unit drives a real runtime failure through the canonical registry result before presenting it. The successful cases prove stale metadata cannot replace final content; the failure case guards complete forwarding without claiming it reproduced the original metadata-triggered defect.
The keyless ACP and TUI Code Mode snapshots execute one outer program that performs two nested bash calls, logs `captured output`, and returns `CODE_ONE+CODE_TWO`. Both surfaces show one completed outer card containing both lines and no nested cards.
@@ -34,4 +34,4 @@ The keyless ACP and TUI Code Mode snapshots execute one outer program that perfo
## Consequences
ACP and TUI now display the same complete content the model receives and replay persists, including post-policy spill previews. The change adds or removes no event fields and requires no session-format bump. Existing and future replay records remain valid because the presenter ignores logs metadata when choosing card content and reads their durable rendered content.
ACP and TUI now display the same complete content the model receives and replay persists, including post-policy spill previews. New `run_code` results no longer carry the optional logs metadata, but this requires no session-format bump: existing records remain valid because the presenter ignores that field and reads their durable rendered content.
@@ -6,7 +6,7 @@ Status: implemented
## 问题
外层 `run_code` 工具会持久化完整的渲染内容,但编辑器的卡片展示逻辑忽略了这些内容,转而根据仅含日志的 `presentationMeta` 投影重新构建卡片正文。仅有结果的运行看似正确,是因为展示逻辑未提供正文时,ACP 和 TUI 会回退到 `tool/result.content`。只要程序输出一条日志,展示逻辑就会提供非空内容,回退随即停止,返回值便会从完成态卡片中消失。失败文本以及输出落盘策略最终生成的头尾预览也会受到同一职责拆分的影响。
外层 `run_code` 工具会持久化完整的渲染内容,但编辑器的卡片展示逻辑忽略了这些内容,转而根据仅含日志的 `presentationMeta` 投影重新构建卡片正文。仅有结果的运行看似正确,是因为展示逻辑未提供正文时,ACP 和 TUI 会回退到 `tool/result.content`。只要程序输出一条日志,展示逻辑就会提供非空内容,回退随即停止,返回值便会从完成态卡片中消失。当已捕获的日志使陈旧投影变为非空时,输出落盘策略最终生成的头尾预览也会受到同一职责拆分的影响。
嵌套 Code 调用从不生成自己的卡片。因此,仅仅为了重建这一张不完整卡片而给外层调用生成元数据,还掩盖了每次外层调用只生成一张卡片的预期边界。
@@ -14,13 +14,13 @@ Status: implemented
`run_code` 输出渲染器继续作为面向模型的外层内容的唯一所有者。它先渲染已捕获的日志,然后渲染返回值、显式的无输出标记,或规范工具流水线生成的失败内容。Post-execute 策略与输出落盘机制可以在内容持久化之前替换它。
`run_code.presentResult` 会把最终的 `result.content` 转交给一张通用结果卡片。它有意省略标题,使待完成卡片保留程序文本。现有日志元数据仍保留在 `tool/result` 中,以维持 transcript(文本记录)兼容性;但展示逻辑不再把它视为第二个内容来源`tool/result.content` 是持久、可回放且经过 post-policy 处理的投影。
`run_code.presentResult` 会把最终的 `result.content` 转交给一张通用结果卡片。它有意省略标题,使待完成卡片保留程序文本。多余的仅含日志的 `presentationMeta` 投影被移除`tool/result.content` 是持久、可回放且经过 post-policy 处理的投影,也是卡片中结果内容的唯一来源
嵌套分发保持不变。带有 `exec.parent` 标记的调用会发出有界的 `tool/code-dispatch` 诊断,但不会生成与 `tool/call``tool/result` 对应的界面卡片,因此一次外层 `run_code` 调用仍然只会生成一张卡片。
## 测试
展示逻辑的单元测试覆盖仅有日志、仅有结果、日志与结果并存、无输出、失败和结果落盘六种情况。每个用例都证明陈旧元数据无法替换最终内容。
展示逻辑的单元测试覆盖仅有日志、仅有结果、日志与结果并存、无输出和结果落盘时的内容。另一个具有集成测试形态的单元测试会触发真实的运行时失败,先让它经过规范注册表形成结果,再交给展示逻辑。成功场景证明陈旧元数据无法替换最终内容;失败场景则保护内容的完整转发,同时不声称它复现了最初由元数据触发的缺陷
无密钥的 ACP 与 TUI Code Mode 快照会执行一个外层程序:程序进行两次嵌套 bash 调用,记录 `captured output`,并返回 `CODE_ONE+CODE_TWO`。两个界面都只显示一张完成态外层卡片,其中包含这两行内容,且没有嵌套卡片。
@@ -34,4 +34,4 @@ Status: implemented
## 影响
ACP 和 TUI 会显示模型接收、回放持久化的同一份完整内容,其中包括 post-policy 输出落盘预览。该变更不增加或删除任何事件字段,也不需要提升会话格式版本。现有及未来的回放记录都保持有效,因为展示逻辑在选择卡片内容时会忽略日志元数据,转而读取记录中持久化渲染内容。
ACP 和 TUI 会显示模型接收、回放持久化的同一份完整内容,其中包括 post-policy 输出落盘预览。新的 `run_code` 结果不再携带可选的日志元数据,但无需提升会话格式版本:展示逻辑会忽略该字段并读取记录中持久化渲染内容,因此现有记录仍然有效