首页
/ DeepSeek Harness:统一 TUI 呈现与导航——一套终端阅读模型如何收敛调色板、工具卡片、上下文折叠与跨工作区 Resume

DeepSeek Harness:统一 TUI 呈现与导航——一套终端阅读模型如何收敛调色板、工具卡片、上下文折叠与跨工作区 Resume

2026-09-04 11:50:19作者:魏献源Searcher

本篇基于 DeepSeek Harness(gh_mirrors/de/deepseek-harness)仓库中的架构决策笔记 2026-07-28-consolidated-tui-presentation.md 展开:它记录了终端 UI(TUI)如何将多套彼此冲突的呈现规则——调色板角色别名、工具卡片框架重复、注入上下文被当作 XML 解析、/resume 仅限当前工作区——收敛为一套单一、可检查的"终端阅读模型"。读完后,你将理解 paletteSpec(scheme) 单表调色板的设计契约、工具卡片"状态标题 + 内收正文"的结构、Ctrl+O 三态折叠机制,以及跨工作区 resume 时 cwd 如何在进程替换前穿过 host 接缝,并掌握其配套测试策略与已知残留边界。

背景问题:局部修复为何会不断互相推翻

原 Agent Note 将问题归纳为四个独立症状,但它们共享同一个根因——缺少统一的终端阅读模型:

  1. 调色板角色互为别名或在浅色终端上颠倒强调muteddimaddedremoved 等角色语义重叠,导致组件各自为政地选择强调方式;
  2. 工具卡片的框架、输出和退出标记重复或争夺注意力:卡片内混用默认前景色、青色命令、dim 的 cwd、无样式 XML 与 dim 输出;
  3. 注入上下文被当作 XML 解析,无法可靠折叠<system-reminder> 包裹的其实是任意散文(含裸 &、比较符、占位尖括号),强行解析必然失败或改动模型可见文本;
  4. /resume 排除当前工作区之外的会话:即使启动器本来可以到达那些会话。

原文的核心论断是:每个症状看似局部,但持久决策只有一个终端阅读模型——精简且可检查的调色板、以状态为首且正文内收的卡片、与内容无关的记录折叠、感知工作区的导航。这个决策还直接回应了仓库中另一条决策:隐藏模式下把每轮 assistant 步骤折叠为一条消息的规则由 2026-07-29-tui-hidden-mode-assistant-fold.md 单独承载,两者共同构成"终端阅读模型"的完整版图。

调色板:一张表统治所有 SGR 序列

契约设计

统一后的调色板契约是:

  • paletteSpec(scheme)唯一的 SGR 开始码、结束码与用途表;
  • createPalette 从该表派生出所有样式包装器;
  • /palette 斜杠命令在运行中的终端里打印的正是同一张表,保证"你看到的就是代码里的";
  • 除固定的启动品牌渐变(见 2026-07-21-tui-banner-brand-gradient.md)外,组件不允许自行发出 SGR 序列
  • 每条结束码必须重置对应开始码设置的所有 SGR 组,杜绝"结束码不完整导致样式泄漏"这类静默失败。

角色合并与 dim 的相对化

重复角色被合并,规则明确:

原角色 合并去向 理由
muted dim 语义与 dim 完全重叠
added success diff 新增色与成功色同义
removed error diff 删除色与错误色同义
第二个强调色(未使用) 直接移除 没有消费方

其中 muted 被并入 dim 后,公开的 TuiTheme.muted 角色被整体移除,扩展方需要使用 dim——这是对下游插件的破坏性变更,但换来角色集合的无歧义。

dim 的关键细节在于 SGR 取值:两种配色方案(dark/light)下统一使用 2;39 开始、22;39 结束。39(默认前景色)加上 2(减淡)意味着"内收文本"是相对终端前景色变暗的,而不是某个固定的深灰——这样在浅色终端上不会退化成"背景接近深灰、对比度崩塌"的老问题。

TypeScript 层面的组合规则

颜色(color)与属性(attribute)在 TypeScript 类型中被分别标记。这带来两条机械约束:

  • 允许"属性 + 颜色"自由组合(例如减淡 + 青色);
  • 拒绝嵌套颜色——因为内层颜色的结束码会把外层颜色一并重置,造成静默丢色。

原 Note 明确把这称为"以少量编译期摩擦换取防止静默样式丢失":paletteSpecrenderUnknownXml 的契约变严格了,扩展作者写 TUI 组件时多一层类型阻力,但非法组合在编译期即被拒绝。

工具卡片:一个状态标题 + 一块内收正文

卡片结构

统一后的工具卡片只有两层结构:

Tool / <name>          ← 唯一的彩色状态标题(scan anchor)
─────────────────────
<正文,全部 dim>        ← 呈现器标题、终端命令、cwd 行、
                          输出、XML 文本、折叠标记

即:一行带颜色的 Tool / <name> 状态标题,下面一块统一的 dim 正文。卡片内不再区分"框架色/输出色"——所有非语义内容共享正文色调。两个明确的语义例外被保留:

  • diff 颜色保留:红/绿承载增删语义,是"窄语义例外";
  • 信号标记保留为错误样式

这条决策直接推翻了早期卡片实现(可参见相关决策链:2026-07-27-tui-tool-card-header.md 定义了卡片头,2026-07-27-tool-card-single-row-fields-inline.md 修复了单行字段换行)——旧卡片混用五种色调,而状态标题本身已经是扫描锚点,正文只剩"内收"一种职责。

renderUnknownXml 与退出标记的去重

工具结果中的未知 XML 文本通过 renderUnknownXml 渲染,该函数显式接收一个正文样式器(而非内部自选颜色),把"未知结果如何着色"也收编进正文色调契约。

退出/信号标记的处理是另一个易错点:面向模型的最终退出或信号标记(文本标记)与 TUI 的状态 pill 是同一状态的两个受众。处理规则是:

  • 终端呈现器(presenter)在返回 TerminalResultView.output 之前,解析并移除模型可见的末尾退出/信号标记;
  • TUI 把该结构化状态作为自己的 pill 只渲染一次
  • 截断、超时、沙箱行留在正文里,因为 pill 不表示这些事实。

由此模型与 UI 各拿到一份"单一表示",不再出现 pill 与文本标记重复显示。相关背景可参考 2026-07-24-tui-turn-end-stop-reason-notices.md

已知残留边界

原 Note 的 Consequences 明确记录了一个残留风险:终端呈现器仍会把"恰好与其退出标记语法精确匹配的最后一行输出"当作结构化状态处理,因此一条故意打印这种行的命令,会丢失其在卡片正文中的这一行。该残留在 dsh-tool-bash 技能中单独做了文档化。写测试脚本或调试 bash 工具输出时需要注意这一条边界。

注入上下文与折叠:按散文处理,按行数折叠

为什么不能按 XML 解析

注入上下文(injected context)曾被 XML 树渲染器处理,但 <system-reminder> 帧本质是"围绕任意散文的提示约定",正文中天然包含裸 &</> 比较符、占位尖括号。任何修复或转义要么是在猜测结构,要么改动了模型可见文本。因此决策是:

  • 注入上下文改由 ContextCardComponent 按普通散文(prose)呈现,不经过 XML 树渲染器;
  • 只剥离精确配对的外层 <system-reminder> 行——不匹配、单边(unpaired)或行内类似标签的文本原样保留
  • 面向模型的内容完全不变。

与内容无关的折叠

折叠(folding)发生在正文组装之后,使用共享的 preview 辅助函数,因此它只取决于行数,从不依赖解析是否成功、也不关心载荷里包含什么字符。这保证了含裸 &、尖括号的任意散文都能稳定折叠,不会出现"解析失败 → 折叠失效 → 卡片撑爆屏幕"的连锁反应。

Ctrl+O 三态循环

Ctrl+O 在三个状态间循环,各状态对卡片的处理规则不同:

状态 工具卡片 上下文卡片
collapsed 折叠 参与折叠
expanded 展开 展开
hidden 连同卡片自有的前导间距一起移除 回落到折叠态

上下文卡片在 hidden 状态下"回落折叠"而不是"一并隐藏",理由是:注入的是指令,不是可丢弃的工具流量——工具卡片承载的是可恢复的执行细节,隐藏后不影响理解;注入指令则必须保留。

hidden 阶段还叠加了第二层折叠规则:把每轮 assistant 的步骤折叠为一条消息。该规则由专门的 Agent Note 2026-07-29-tui-hidden-mode-assistant-fold.md 承载:每轮中第一个有可见内容的步骤独占该轮唯一的 Assistant 头,其余步骤作为无头续段渲染,纯工具步骤不占头也不留空段;折叠态是重算值而非存储值,因此会话、持久化格式均不变。

跨工作区 Resume:cwd 必须在进程替换前穿过 host 接缝

选择器行为

/resume 选择器总结全部会话记录,并拥有一个用 Tab 切换的"当前工作区 / 全部工作区"作用域:

  • 默认作用域为当前工作区;
  • 只在"全部工作区"作用域下追加工作区标签(当前工作区内加标签是噪音);
  • 拒绝没有 cwd 的记录——因为没有可进入的目录,选择即失败。

交接(handoff)机制

TuiResumeHost.handoff 接收两个输入:选中的 SessionId,以及 preflight 阶段重新读取的 cwd。交接顺序是刻意的:

  1. CLI 在销毁当前应用之前先切换目录;
  2. 目标目录不可达时在终端仍可控的窗口内失败(而不是在拆除过程中失败);
  3. execve 替换进程后自然继承所选工作区;
  4. 退出消息由启动器(launcher)提供,而不是让 TUI 去重建 launcher 语法——避免 TUI 侧硬编码 launcher 命令行格式。

被否决的替代方案中有一条值得注意:"保持 resume 限定单工作区,或在启动后推断 cwd"被拒绝,理由是:限定单工作区迫使用户手动重启,而"恢复的头部 cwd 不控制文件系统与 shell 的解析"——目标目录必须穿过 host 接缝、在进程替换之前落实。

替代方案与最终取舍

原 Note 完整列出了七个被否决的替代方案及其理由,是这份决策最有参考价值的部分:

替代方案 否决理由(要点)
为每个视觉症状保留独立笔记与局部修复 决策共享同一套阅读层级且反复互相推翻;单一负责人让最终规则可查,读者不必重建时间线
保留别名、靠约定约束呈现 别名暗示了不存在的区分;嵌套颜色重置或不完整 SGR 结束会静默失败;单表 + 类型使契约可检查、可机械校验
工具卡片内保留框架/输出颜色分层 真实卡片混用五种色调;状态标题已是扫描锚点,一块内收正文即可去噪;diff 颜色是唯一的窄语义例外
按 XML 解析或修复注入上下文 reminder 帧是围绕任意散文的提示约定;修复/转义要么猜结构、要么改动模型可见文本
让上下文卡片随工具卡片一起隐藏 上下文承载注入指令而非可恢复执行细节;hidden 阶段应只移除工具流量
resume 限定单工作区或启动后推断 cwd 前者强制手动重启;后者让恢复的头部 cwd 不控制文件系统/shell 解析;目标目录必须在进程替换前穿过 host 接缝
去掉 TUI 退出 pill 或去掉模型可见退出标记 pill 是可扫读的 UI 状态,文本标记是模型的状态信号;呈现器在构建结构化视图时消费标记,让两个受众各得一份表示

影响、约束与测试覆盖

对扩展作者的影响

  • 公开 TuiTheme.muted 角色被移除,扩展需改用 dim
  • paletteSpecrenderUnknownXml 契约更严格——以少量编译期摩擦换取防止静默样式丢失;
  • 组件一律不得自行发射 SGR(启动品牌渐变除外),/palette 是唯一的运行期调色板自省入口。

行为约束与风险

  • 跨工作区 resume 可以把所有路径解析型工具挪到另一个目录;cwd 缺失或不可达会阻止交接;
  • 全量选择器让"并发访问共享会话存储"更容易发生,跨进程会话锁被明确标记为独立的后续工作(未在本决策范围内);
  • bash 输出末行恰好命中退出标记语法时会被结构化消费(见上文残留边界)。

测试策略

原 Note 的 Testing 一节列出了覆盖矩阵,全部针对上述契约而非视觉像素:

  • TUI 单测 + 无按键终端快照:调色板枚举、明暗两方案的角色、合法/非法样式组合、统一 dim 的卡正文、语义 diff 颜色、无标记终端输出 + 单退出 pill、散文保留的上下文帧、与内容无关的折叠、Ctrl+O 三态循环、模型过滤、resume 的两种作用域;
  • CLI 交接测试:传递重读的 cwd;在拆除前拒绝目录进入失败;
  • Tool-bash 测试:把结果标记的"发射 → 解析 → 剥离"钉死为一次完整往返(round trip),防止两端各改各的。

小结

这份已归档的架构决策(Status: implemented,Archived: 2026-08-04)的核心价值在于用一个可检查的终端阅读模型替代了 N 个局部约定:

  1. 一张 paletteSpec(scheme) 表 + 类型化组合规则,让 SGR 契约在编译期与运行期(/palette)双重可验证;
  2. 工具卡片收敛为"彩色状态标题 + 统一 dim 正文",diff 红绿与错误信号是唯一例外;
  3. 注入上下文按散文处理、按行数折叠,摆脱对载荷字符的依赖;
  4. Ctrl+O 三态循环 + hidden 阶段的 assistant 折叠(由 2026-07-29-tui-hidden-mode-assistant-fold.md 承载)统一控制记录密度;
  5. 跨工作区 resume 通过 preflight 重读 cwd 与启动器提供的退出消息,把"选目录"落实在进程替换之前。

需要说明的是:该 Note 处于仓库 .agents/notes/archived/ 归档区,其指向的 TUI 呈现层实现(paletteSpeccreatePaletteContextCardComponentTuiResumeHost 等符号)在当前仓库快照中未检索到对应源码文件,本文所有实现细节均以该决策笔记及其引用的关联 Agent Note 为事实依据;若要跟踪这些契约的最新形态,建议从 docs/subsystems/ 下与 web-client、session 相关的子系统文档,以及 .agents/notes/ 目录中相邻的 TUI 决策链(如 2026-07-28-tui-chat-channel-module-split.md2026-07-24-configurable-tui-prompt-theme.md)继续阅读。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384