# Agent Note: 轮次尾部 IconActions 要求轮次已完成

Status: implemented

[English](2026-08-05-turn-tail-actions-require-a-completed-turn.md) | 中文

## 问题

assistant IconActions 此前只从已定稿的 transcript（文本记录）推导：每个轮次中最后一条含内容文本的 assistant 拥有该行。这个量只有在轮次关闭后才稳定。轮次仍在产出步骤时，模型在工具调用前写下的叙述就是当时该轮次的最后一条内容 assistant，于是它在工具执行期间取得该行，等下一步的文本落定又把它交出去。读者会看到复制、分支和时钟出现在一句中间叙述下方，把流程推开一行 28px，然后消失。该行在这个状态下本身也是残缺的：分支控件已经通过 `turnEnds` 判定为禁用，`Ran for` 标签已经通过 `turnTimings` 判定为不显示，只有复制可用。

[已归档的消息 chrome 决策](../../archived/feature/2026-07-29-web-message-icon-actions-and-clock.md)一直声称轮次中间的叙述不带 chrome，但推导过程从未拿到能让这句话成立的完成信号。

## 决策

`assistantActionsSeqs` 接收 `ConversationSnapshot.turnEnds`，只在事件窗口中存在该轮次 `turn/end` 时才授予该行。已完成轮次内部的归属不变，仍是其最后一条含内容文本的 assistant。仍在产出步骤的轮次不授予任何座位，因此其叙述不会挂载该行；轮次关闭时，座位在已定稿答案下方一次性出现。

这与分支控件和运行时长标签使用的完成事实相同，因此同一行的三个部分现在口径一致。轮次是否完成读自持久的 `turn/end` 事件，而不是从 `running`、流式 partial 或在途工具调用推断，与[已完成轮次尾部决策](2026-08-02-message-fork-actions-require-completed-turn-tail.md)一致。任何结束原因类别都会关闭轮次，因此已中止轮次冻结的尾部保留其操作栏，而崩溃遗留的开放轮次会在加载时由日志修复补上 `turn/end`。

`hasContentText` 移入 `chat-flow.ts`，由 `AssistantMarkdown` 导入，使归属门控与挂载门控无法各自漂移。

## 考虑过的替代方案

**用 `running` 加流式 partial 或第一个在途工具调用指认开放轮次，据此扣留。** 这一做法曾在最初的变更中短暂存在，随后被删除。它推断完成状态而不是读取完成状态，还需要一个特例，避免轮次已被接受但尚未产出第一步时把上一条回答的座位取走；这正是已完成轮次尾部决策为分支控件否决过的推断。`turnEnds` 按轮次回答同一个问题，不需要推断，也不需要特例。

**轮次进行中保留该行，只把控件置为不可用。** 不予采纳：轮次中间的叙述不是一个降级的答案，它根本不是答案。复制仍然会写入一句中间文本，该行在轮次结束时仍然要移动到真正的尾部。

**让每个已定稿的内容节点长期保留该行。** 在此重新否决，理由与最初的决策相同：在每一步下重复复制、分支和时钟会使流程显得杂乱。它也解决不了本次报告的问题，因为分支控件只有落在尾部才有意义。

## 后果

运行中的轮次在触发它的用户气泡之下不再有任何消息操作栏，而更早的每个已完成轮次仍保留各自的座位；座位在 `turn/end` 到达时一次性出现，此刻已定稿答案下方会多出一行 28px。`turn/end` 落在加载窗口之外的轮次不授予座位，而翻页不会造成这种情况，因为一个轮次的结束事件排在它自己的节点之后。`apps/web/tests/turn-tail-actions.e2e.ts` 通过组装后的应用钉住两种状态：`hang` sidecar 作用在第二次模型调用上，把一个首步先叙述再调用 bash 的轮次挂住，两份预期输出分别记录挂起中的流程和停止之后的流程。包级测试直接覆盖该推导以及运行中轮次的渲染结果。
