# Agent Note: 独立的全屏 TUI 入口

Status: implemented
Archived: 2026-08-04

[English](2026-07-17-dedicated-full-screen-tui-front-door.md) | 中文

## 问题

可复用的 TUI 包（package）仍然保留实现，但 [`dsh` 不再将其作为应用入口交付](../simplification/2026-08-03-explicit-config-dsh-entrypoint.md)。本记录继续负责包边界和终端行为；后续记录负责产品组合。

在本入口引入时，面向行的 agent 负责 pipe 与普通终端，但全屏 coding 界面必须负责原始输入、差分绘制、光标状态、浮层和终端恢复。把这两类契约合并到一个 UI 插件中，会迫使面向 stream 的路径依赖仅适用于 TTY 的生命周期。后续的[移除重复 agent 决策](../simplification/2026-07-20-remove-stdio-and-echo-agents.md)移除了这个面向行 agent；本 Note 继续负责 TUI 设计。

交互通道必须继续作为 Cordis 插件，使用与其他入口相同的 agent（智能体）、会话、工具和用户交互服务。它需要恢复持久历史、跟随压缩替换、显示工具自有的呈现内容，并在启动失败和资源释放时恢复终端。独立聊天应用或第二套 agent 组合会在插件图之外重复实现这些行为。

## 决策

DeepSeek Harness 将 [`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) 作为独立的 Cordis 插件交付。该插件只负责终端输入与呈现；agent 生命周期、会话持久化、工具执行以及模型可见的提问工具仍由不同组合项负责。插件要求 stdin 和 stdout 均为 TTY；条件不满足时会失败，不会静默切换为逐行输出。

该包是终端入口，而不是完整应用。宿主在已配置 agent 之前挂载 `@deepseek-ai/dsh-tui`，并围绕它组合后端、工具和策略。产品 CLI 目前不交付终端组合；非交互任务使用 headless 模式，Web 是已安装产品中面向人的界面，而 ACP 仍是独立的自动化协议。

宿主提供其预创建 agent 使用的同一个新建或恢复 `SessionId`。TUI 等待相符的根 agent 出现，然后才进入全屏模式。因此，相符的 `agent-loop/config-start-failed` 事件会在接管屏幕前报告。

### 会话投影与交互

TUI 从追加来源的会话事件重建 transcript（文本记录），因此恢复后的历史会保留读者已经看到的每条消息；被压缩的范围不再与模型可见会话保持一致，而是留在一行标记之后仍可阅读（[追加来源的 transcript](../bug-fix/2026-07-29-human-transcript-append-origin.md)）。TUI 渲染 Markdown 文本与推理（其中围栏代码块隐藏 Markdown 标记、保留一个暗色的可选语言标签，并使用代码配色的正文）、token 用量、最新 `todo/write` 计划，以及各工具定义通过 `presentCall` 和 `presentResult` 方法生成的工具卡片。较长的工具卡片正文会保留可配置的头尾预览，并显示隐藏行数；一个终端控制可以展开或收起全部卡片。进行中的分片与工具调用会更新同一组组件，随后由完成事件收束状态。

agent 空闲时，编辑器输入调用 `agent.send()`；轮次运行中则调用 `agent.steer()`。取消、推理显隐、工具卡片展开、重绘、清空 transcript 和退出都只是终端控制。`/exit` 和 `/quit` 共用同一条退出路径：先取消进行中的轮次，等待 agent 空闲，然后恢复并关闭终端。空闲态页脚根据 `tokenMeter` 得出上下文占用率，并显示所选模型和显式选定的推理强度；agent 运行期间，该摘要会替换为带已用时长的活动指示和 Escape 中断提示。`/status` 在这两种状态下均可用，并会追加一份仅在终端显示的详细快照，其中包括会话标识与时间戳、所选模型、推理强度（或默认状态）及推理显隐状态、从事件日志归并得出的生命周期计数、与页脚一致的去重用量分项和 KV 缓存命中率，以及 `tokenMeter` 给出的上下文用量和所选模型公布的容量。插件注册共享的 `userInteraction` 提供方，在左下角宽幅键盘操作面板中呈现排队的问题，面板显示批次进度、带编号的选项和对齐的描述；面板的操作提示只列出在当前选项数量下有意义的操作，仅有一个选项时不显示导航项；agent 行为和答案日志仍由既有服务负责。

`/model` 命令将建议性的 `ctx.llm` 目录呈现为键盘选择器，并且只更改当前 TUI 会话的目标；带参数的形式仍可直接选择目标。选择器在列表上方设有一个过滤框：输入内容会按对每行 `provider/model` 标签、模型名称和描述的大小写不敏感子串匹配来缩小行集，并在原先高亮行仍通过过滤时保持其选中状态；Escape 会先清空非空的过滤内容，再次按 Escape 才取消选择器。每个模型行都持有适配器公布的推理强度顺序和默认值：按 Shift+Tab 可循环切换该行的推理强度；如果适配器没有公布默认值，循环中还会包含提供方默认行为；没有可选元数据的模型则保持不变。agent 作用域内的 prompt 组装和请求两条 waterfall（瀑布式事件）会为每个步骤快照一次同一个提供方/模型/推理强度目标，因此即使命令在组装期间到达，`{{provider}}` / `{{model}}` 插值与请求路由也不会分裂。系统通过日志中最新的请求头恢复已经使用过的目标；未被请求使用的选择只保留在当前进程中。

### 终端所有权

在模型输出、会话数据、工具呈现、问题、配置或诊断信息进入 pi-tui 或终端标题前，`displayText()` 会把换行之外的 C0 和 C1 控制字符显示为十六进制转义文本。只有 TUI 和 pi-tui 可以生成 ANSI 控制序列。

内置配色仅使用标准 16 色 ANSI 前景色和 SGR 属性，正文文字和背景沿用终端默认值，选中项使用反显。因此，宿主终端可以直接按浅色或深色主题重映射界面，无需 TUI 专用主题设置；`color: false` 会移除样式。

## 验证

已实现的 [TUI 终端状态快照 Agent Note](../testing/2026-07-18-tui-terminal-state-snapshots.md) 规定包验证契约：直接行为测试和语义终端快照。交付该入口的部署负责其组装后 transcript 和进程／PTY 验收。包 README 负责记录配置、命令、模型可见效果和当前限制。

## 曾考虑的替代方案

- **把 readline 与全屏模式都保留在 `@deepseek-ai/dsh-stdio` 中**：不予采纳，因为逐行输出和差分 TTY 渲染具有不同的依赖、输入规则、日志所有权和资源清理义务。拆分为独立包可以让管道安全契约保持精简、明确。
- **当任一进程流不是 TTY 时，让 TUI 插件静默降级**：不予采纳，因为回退会掩盖部署错误并改变交互语义。宿主可以选择其他入口；明确挂载的 TUI 会快速失败。
- **把 TUI 接线与测试保留在 readline `repl-agent` 叶节点下**：当时不予采纳，因为一个叶节点会代表两个不同入口。后续移除产品入口时删除了该应用接线，但保留了包边界。
- **在 `/model` 运行时修改 `agent.options`**：不予采纳，因为创建选项无法在异步 prompt 组装与请求路由之间提供原子边界。agent 作用域内的 waterfall 会在保持创建输入不可变的同时，为每个 step 快照一次选中的字段组合。

## 后果

- 挂载 TUI 的部署会获得带状态的 Markdown、卡片、计划和提问界面，无需再对齐第二套终端协议。
- TUI 会引入 pi-tui 依赖并严格要求 TTY；非 TTY 部署使用 Headless app 或结构化协议。
- 会话投影使恢复与持久会话保持一致，但只有一个已配置会话拥有 transcript 和编辑器。
- 工具包通过既有呈现方法扩展终端卡片，无需在 TUI 中增加工具专用分支。
- 模型和推理强度选择使用适配器公布的元数据，但不会把目录成员关系变成请求校验；未使用的选择不属于持久化状态。
