# Agent Note: 保证 Code Mode 结果卡片内容完整

Status: implemented
Archived: 2026-07-26

[English](2026-07-20-code-mode-result-card-completeness.md) | 中文

## 问题

外层 `run_code` 工具会持久化完整的渲染内容，但其 UI 展示器忽略了这些内容，转而根据仅含日志的 `presentationMeta` 投影重新构建卡片正文。仅有结果的运行看似正确，是因为展示器正文为空时，消费方会回退到 `tool/result.content`。只要程序输出一条日志，展示器就会提供非空内容，回退随即停止，返回值便会从完成态卡片中消失。当已捕获的日志使陈旧投影变为非空时，输出落盘策略最终生成的头尾预览也会受到同一职责拆分的影响。

嵌套 Code 调用从不生成自己的卡片。因此，仅仅为了重建这一张不完整卡片而给外层调用生成元数据，还掩盖了每次外层调用只生成一张卡片的预期边界。

## 决策

规范的工具注册表流水线负责最终面向模型的外层内容。成功时，`run_code` 输出渲染器先渲染已捕获的日志，然后渲染返回值或显式的无输出标记。运行时失败和执行前策略拒绝由 `ToolRegistry` 归一化为错误内容，过程中不会调用该渲染器。Post-execute 阻断发生在成功渲染之后，并把结果替换为错误内容；其他 post-execute 策略与输出落盘决策可以在持久化之前替换内容。

`run_code` 不提供 `presentResult`。既有的通用结果回退机制会保留待完成的程序标题，并渲染原始的最终 `tool/result.content`；这一持久、可回放且经过 post-policy 处理的投影是卡片中结果内容的唯一来源。宿主 API 代理因此不提供单独的结果视图，而不会在 `event.data.content` 与 `view.view.content` 中重复序列化同一内容。冗余的仅含日志的 `presentationMeta` 投影继续保持移除状态。

嵌套分发保持不变。带有 `exec.parent` 标记的调用会发出 `tool/code-dispatch` 事件（携带完整渲染内容），但不会生成与 `tool/call` 或 `tool/result` 对应的界面卡片，因此一次外层 `run_code` 调用仍然只会生成一张卡片。

## 测试

工具单元测试通过规范注册表覆盖仅有日志、仅有结果、日志与结果并存、无输出、结果落盘和失败的结果，然后固定持久内容以及结果展示器不存在这一事实。宿主 mux 回归测试使用仅有调用的展示器，证明结果帧恰好携带一次原始内容，且不含视图。这些案例证明陈旧元数据无法替换最终内容，同时不会让宿主重复该内容。

无密钥的 ACP（Agent Client Protocol）后端快照与 TUI Code Mode 快照会执行一个外层程序：程序进行两次嵌套 bash 调用，记录 `captured output`，并返回 `CODE_ONE+CODE_TWO`。ACP 持久化日志固定完整结果；TUI 界面只显示一张完成态外层卡片，其中包含这两行内容，且没有嵌套卡片。

## 备选方案

**把返回值追加到日志元数据：**不予采纳。元数据会与渲染器重复，并且需要为每一种 JSON 根另行维护稳定的格式化契约；post-policy 内容替换或输出落盘预览仍然会被遗漏。

**把展示元数据与 `result.content` 合并：**不予采纳。渲染内容已经包含日志；合并会造成重复，还需要依赖脆弱的去重逻辑。

**通过通用结果展示器转发 `result.content`：**不予采纳。持久事件已经携带该内容，UI 消费方也已有通用的原始内容回退机制。宿主 mux 会在事件旁序列化工具拥有的结果视图，因此转发仅仅是为了重建该回退机制，却会在一个帧中重复渲染内容；仅默认 worker 在渲染前允许 64 MiB 的可变载荷预算。

**为每次嵌套分发创建一张卡片：**不予采纳。中间值有意只存在于执行期间，永远不面向模型。多张卡片会暴露实现轨迹，而不是模型与用户调用的单次 Code Mode 操作。

## 影响

TUI 与 JSON-RPC／Web 通过通用结果回退机制显示与模型接收及回放持久化相同的完整内容，其中包括 post-policy 输出落盘预览。宿主 API 保留待完成的程序标题，同时不在单独的视图负载中重复原始结果。新的 `run_code` 结果不再携带可选的日志元数据，但无需提升会话格式版本：现有记录仍然有效，因为展示逻辑会读取其中持久化的渲染内容。
