# Agent Note: Web 轮次运行时长与悬停显示的时间附属元素

Status: implemented

[English](2026-08-03-web-turn-run-time.md) | 中文

## 问题

Web 聊天界面会显示消息的到达时间，却不显示 agent（智能体）处理这条消息花了多久。长轮次除静态活动标签外没有任何实时进度信号，轮次结束后也无法从 UI 中还原实际耗时。与此同时，始终可见的时钟行给每条消息都增加了视觉噪音。

## 决策

轮次实际耗时（wall time）采用日志中已有的 `turn/start` 和 `turn/end` 时间戳，不新增任何会话事件。客户端 Session 将加载窗口内的每对边界归并到 `turnTimings` 中；轮次结束后，承载操作图标的 assistant 页脚把 `endTime - startTime` 渲染为本地化的 `Ran for {duration}` 标签。运行中的 `TurnStatus` 时钟采用最新一条没有结束时间的计时记录，因此重新加载会保留已用时长，steering（中途引导）不会重置计时，重试也从自身的日志边界开始。两处读数共用同一个本地化格式化器，并向下取整到整秒。该时钟在 15 秒后才出现，并从实时区域中隐藏，因此屏幕阅读器会播报活动状态而不会重复播报每次时钟跳动。

时钟与运行时长这类时间附属元素（time chrome）在悬停时才显示：消息容器通过 `data-time-hover-root` 属性显式启用该行为，`MessageIconActions.module.css` 在容器处于 `:hover`/`:focus-within` 时以淡入方式显示时间标签。该规则限定在 `@media (hover: hover)` 之内，触屏设备因此保持标签始终可见；显隐通过 opacity（而非 display）实现，布局保持稳定。复制与分支图标始终可见。

## 考虑过的替代方案

**从消息节点推导计时。** 渲染后的 transcript（文本记录）中可以取得最近的用户或 steering 时间戳，但这会错误计算重试轮次，并让轮次中途的 steering 重置实时时钟。已有的轮次边界事件无需改变日志格式即可提供权威时间戳。

**将实时时钟锚定到组件挂载时刻。** 更简单，但轮次进行中重新加载会让时钟从零重新计时，并与最终的页脚标签不一致。仅当 `turn/start` 位于已加载窗口之外时，才回退到挂载时刻。

**将整个操作行隐藏至悬停时才显示。** 复制与分支是值得让用户发现的操作入口，而整行级别的显隐切换有布局偏移的风险。只有被动的时间文本由悬停控制显隐。

## 后果

轮次时长在运行中和结束后都可见，且不需要新的会话事件；两处读数共用精确的日志边界和格式化方式。结束后的时长包括最后一条 assistant 文本之后、直至 `turn/end` 的活动；若 `turn/start` 位于已加载窗口之外，则不显示标签。未交互时，时间附属元素不再与消息内容争夺注意力，持续跳动的时钟也只保留视觉呈现，不会被重复播报。
