# Agent Note: 从首条消息自动命名终端

Status: implemented
Archived: 2026-07-26

[English](2026-07-21-tui-auto-pane-title.md) | 中文

> **已被取代**：见[标题归一 Agent Note](../simplification/2026-07-22-tui-titles-from-session-title-service.md)。TUI 本地的 `autoTitle` 生成已移除；标题来自日志承载的 session-title 服务，终端重命名消费 `session/title` 事件。

> **已被取代**（就默认值与恢复行为而言），见[自动标题默认开启 Agent Note](2026-07-21-tui-auto-title-default-on.md)：`autoTitle` 现默认开启，恢复会话会从已存储的首条消息重新推导标题，而非保留静态标题。下文的 OSC 0 路径、一次性门闩、模型概括形态、发出后不等待其返回的调用，以及每一条失败兜底，均仍然成立。

## Problem

TUI 的终端标题是一个所有会话共用的静态字符串（`title`，默认 `DeepSeek Harness`）。在 tmux 每个窗格或每个终端标签页各跑一个 agent（智能体）的用户看来，它们的标签全都一样，因此窗格一眼看去无从区分，标签栏也不携带任何关于各会话正在做什么的信号。

## Decision

- `TuiConfig` 新增布尔字段 `autoTitle`（默认 `false`）。开启后，TUI 会在全新会话的首条用户消息之后发起一次后台模型调用，并用一个简短的、模型生成的标签替换终端标题；静态 `title` 是替换前的初值，也是兜底。
- 该标签是模型概括，而非对提示词的截断。请求携带一段固定的任务指令（将该请求概括为两到五个小写单词、不含标点的简短标题）加上用户的首条消息，且不带工具；TUI 取回复的首个非空行并截断到 40 个字符（39 个字符加一个省略号）。
- 标题通过 `runtime.terminal.setTitle` 设置——静态 `title` 已经在用的同一条 OSC 0 路径。不引入任何新的终端控制面，终端写入仍归 pi-tui 所有。
- 该调用发出后不等待其返回，且每会话仅一次。一个 `titleSettled` 门闩守护它：`autoTitle` 关闭时它预先置为已结算、从不运行；在首条 `user/message` 已入日志的恢复会话中它预先结算，因此静态标题得以保留；仅含空白的首条消息被跳过且不消耗名额。任何失败、空回复、缺少 `llm` 服务、或缺少 agent 的 `provider` 或 `model`，都会让静态标题保持不动。一个专用的 `AbortController` 在关闭时取消尚在进行的请求。
- 标题调用直接抵达 `ctx.llm.stream`，而非经由 `agent.send`，因此它从不追加进会话或 transcript（文本记录），也无法扰动 agent loop（智能体循环）。
- 该功能默认关闭，仅在交互式产品配置（`examples/tui-agent/cordis.yml`）与脚本化 PTY fixture（测试前置数据）中开启。若在共享的 `dsh-tui-demo` schema 默认值里开启，会在不发送任何用户消息的无密钥回放与启动场景中多发一次模型调用。

## Alternatives considered

**截断首条用户消息，而非用模型生成标题。** 否决：用户选择的是简短的、模型制作的标签；截断后的原始提示词嘈杂、常以样板文字开头，且很少读起来像标题。

**重命名窗口（OSC 2）或 tmux 窗口。** 否决：OSC 0 只设置 `pane_title`，因此它标记窗格而不重命名、也不泄漏进用户的窗口标题；用户确认 OSC 是正确的手段。

**让该功能默认开启。** 否决：在共享的 demo schema 里开启会扰动无密钥回放与启动快照，并在每个全新会话上花掉一次模型调用；按部署选择性开启可让默认面保持惰性。

**并入日志支撑的会话标题工作（PR #451）。** 否决：那项改动是持久化到日志的会话元数据；本项是不做持久化的终端标签。让二者相互独立可使各自自成一体，并避免共享依赖。

**阻塞首轮直到标题就绪。** 否决：在发送用户消息前先等待标题，会给实际请求增加延迟；发出后不等待其返回可让重命名对该轮次不可见。

## Consequences

- 开启时，全新会话会多花一次无工具的模型调用，只带单条简短的用户消息和少量输出 token；默认关闭时它不产生任何开销。
- 由于标题调用会打上 `sessionId`，它与会话的 `llm-replay` 游标共享：在以回放支撑的快照场景中开启 `autoTitle` 会消耗一条录制脚本条目。这正是它默认关闭、且脚本化 PTY fixture 用按工具分支的适配器而非回放来回答该调用的原因。
- `packages/ui/tui/tests/tui.spec.ts` 用一个 mock `llm` 适配器固定该行为：生成的标题替换静态标题、过长输出以省略号截断、仅含空白的首条消息保留一次性名额、空回复或失败回复保留标题、恢复的会话从不触发，以及功能关闭 / 无服务 / 缺提供方 / 缺模型各路径都保留静态标题。一项关闭测试断言尚在进行的请求被中止。
- `examples/tui-agent/tests/tui-keyless-smoke.e2e.ts` 证明真实的经 Loader 启动的路径：脚本化适配器以固定字符串回答无工具的标题调用，对话场景断言 OSC 0 序列抵达 PTY。启动场景不发送用户消息，因此它们从不触发该调用。
