TUI presents a reason for every turn-end kind
TUI 为每种轮次结束 kind 呈现原因
The TUI rendered transcript notices for `error`, `aborted`, `max-tokens`, `rejected`, and `interrupted` turn ends, but a `disposed` turn end and any plugin-added `TurnEndReasonMap` kind rendered nothing. When such a turn ended — live or replayed from a persisted log — the agent stopped working with no visible reason, breaking the product expectation that every stop is explained to the user.
English
Problem
The TUI rendered transcript notices for error, aborted, max-tokens, rejected, and interrupted turn ends, but a disposed turn end and any plugin-added TurnEndReasonMap kind rendered nothing. When such a turn ended — live or replayed from a persisted log — the agent stopped working with no visible reason, breaking the product expectation that every stop is explained to the user.
Decision
The turn/end case in packages/ui/tui/src/index.ts switches on the reason's discriminant and covers every kind: completed stays silent because the settled assistant message and its Completed timing header already present that outcome; disposed appends Turn stopped: the agent was disposed.; and the merge-extensible default appends Turn ended: <kind>. so an unknown plugin-added outcome still names why the agent stopped. All other kinds keep their existing notices.
Alternatives considered
A notice for completed turns too. Rejected as noise: every ordinary response would gain a redundant line, and the assistant message plus its frozen timing header already mark the completion.
Suppressing the disposed turn-end notice live because agent/disposed also appends Agent "<id>" was disposed. Rejected: the two notices state different facts (this turn was cut short vs. the agent is gone), and the turn-end notice is the only one that survives replay of a persisted log, where the live agent/disposed emission does not recur.
Keeping the default branch silent (the prior behavior). Rejected: a merge-extensible kind unknown to the TUI is exactly the case where the user has no other way to learn why the agent stopped.
Consequences
- A turn never ends without a user-visible reason in the TUI: every non-
completedturn/endkind appends a transcript notice, including unknown plugin-added kinds by name. - Live disposal during a running turn shows two notices (the turn-end notice plus
agent/disposed); a replayed log shows the turn-end notice alone. - The
errors-and-helpsnapshot pins thedisposedand unknown-kind notices alongside the existing failure and interruption notices.
中文
问题
TUI 会为 error、aborted、max-tokens、rejected、interrupted 这几种轮次结束渲染 transcript(文本记录)通知,但 disposed 轮次结束和任何插件新增的 TurnEndReasonMap kind 不渲染任何内容。此类轮次结束时,无论实时发生还是从持久化日志回放,agent(智能体)都会在没有任何可见原因的情况下停止工作,违背了「每次停止都要向用户解释」的产品预期。
决策
packages/ui/tui/src/index.ts 中的 turn/end 分支按 reason 的判别字段做 switch,覆盖每一种 kind:completed 保持沉默,因为已定稿的助手消息及其 Completed 计时头部已经呈现了这一结果;disposed 追加 Turn stopped: the agent was disposed.;merge 扩展的 default 分支追加 Turn ended: <kind>.,让未知的插件新增结果仍能点明 agent 停止的原因。其余各 kind 保留现有通知。
备选方案
为 completed 轮次也加一条通知。 否决,属于噪音:每次普通响应都会平添一行冗余内容,而助手消息加上已冻结的计时头部本就标示了完成。
因为 agent/disposed 也会追加 Agent "<id>" was disposed.,就在实时场景下抑制 disposed 轮次结束通知。 否决:两条通知陈述的是不同事实(前者说明这一轮被中途截断,后者说明 agent 已不复存在),而且只有轮次结束通知在回放持久化日志时得以保留,实时发出的 agent/disposed 不会在回放中重现。
让 default 分支保持沉默(沿用先前行为)。 否决:TUI 不认识的 merge 扩展 kind,恰恰是用户没有其他途径得知 agent 为何停止的情形。
后果
- 在 TUI 中,轮次结束永远不会缺少用户可见的原因:每种非
completed的turn/endkind 都会追加一条 transcript 通知,未知的插件新增 kind 也会按名称列明。 - 轮次运行期间实时 dispose(资源释放)会显示两条通知(轮次结束通知加上
agent/disposed);回放日志则只显示轮次结束通知。 errors-and-help快照把disposed通知和未知 kind 通知连同现有的失败与中断通知一并固定下来。