DeepSeek Harness TUI 工具结果卡片 Markdown 渲染:generic-card 与专用卡片的呈现边界

DeepSeek Harness TUI 工具结果卡片 Markdown 渲染:generic-card 与专用卡片的呈现边界 DeepSeek Harness TUI 工具结果卡片 Markdown 渲染generic-card 与专用卡片的呈现边界【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness工具在向用户呈现结果时往往会携带 Markdown 内容——包括用 fencedconsole代码块表达的“后台任务已受理”确认信息与执行错误。DeepSeek Harness 的 TUI 通过引入“共享 Markdown 主题 先渲染后截断”的决策让 generic 工具卡片的渲染词汇与对话内容保持一致同时为终端与 diff 卡片保留专用纯文本渲染路径。本文以 2026-07-23-tui-generic-card-markdown Agent Note 为骨架结合仓库中的工具呈现意图定义、Markdown 渲染管线与卡片模型源码说明这一决策的问题背景、实现方式、边界划分与验证手段。一、背景工具结果的“呈现意图”词汇表DeepSeek Harness 的 TUI 如何渲染一次工具调用并不是由 UI 逐个工具名特判决定的而是由工具自身通过ToolDefinition.presentCall/ToolDefinition.presentResult声明一个“呈现意图”render intent。这套 provider-neutral 词汇表定义在 presentation.tsToolCallView GenericCallView | TerminalCallView | DiffCallView即一次调用要么是默认的通用卡片generic要么是终端卡片terminal要么是 diff 卡片ToolResultView在此基础上增加了SearchResultView、ReadResultView、WebResultView并在generic、terminal、diff三种结果视图上镜像同一套card判别字段。其中GenericResultViewpresentation.ts只携带可选的替换标题title和 UI 面向的结果内容块content?: ContentBlock[]是“其余一切工具”的默认落点。而TerminalResultViewpresentation.ts则携带output、exitCode、signal三个结构化字段并明确写了一条回退规则不具备终端卡片能力的 UI会得到由bridge 从output派生出的 fenced console 回退工具本身不做双重编码。正是这条回退规则与 generic 卡片的内容模型组合在一起构成了本文讨论的问题generic 卡片允许携带任意 Markdown 内容块其中就包括这些 fencedconsole输出。二、问题纯文本渲染把 fence 标记暴露给用户当工具 presenter 在 generic-card 内容中放入 Markdown例如后台任务确认、执行错误信息里嵌入的 fencedconsole块时如果 TUI 把这些内容当作纯文本逐字渲染用户就会直接看到 围栏标记和可选的console语言标签。这在同一个 transcript 里会产生明显的不一致同样是 Markdown助手assistant与用户user的消息走的是完整 Markdown 渲染管线而工具卡片的结果区域却把 Markdown 源文本原样摊开。从仓库的代码可以看到这条“对话内容与工具内容”的渲染能力落差对话内容由 AssistantMarkdown.tsx 经MarkdownText走共享 Markdown 渲染器而工具卡片的结果文本在 tool-call-model.ts 的resultText()中只是简单展平text块逐字拼接、非文本块按 JSON 格式化再作为output字段交给卡片。于是“fence 标记可见”“语言标签可见”“fenced 正文没有代码着色”成为 TUI 中 generic 工具卡片的三个观感缺陷这与该 Agent Note 记录的 Problem 完全对应。三、决策共享 Markdown 主题渲染渲染先于截断针对上述问题决策分为两个互补的部分见 Agent Note。3.1 用共享 Markdown 主题渲染 generic-card 结果TUI 不再对 generic-card 的结果内容做纯文本渲染而是先复用与对话内容相同的共享 Markdown 主题渲染一遍再应用卡片自身的 head-and-tail 行数限制。共享主题要完成三件事隐藏 fence 语法——围栏标记本身不显示保留可选语言标签——console、ts、bash等信息串继续以语言标签形式呈现fenced 正文着色为代码——代码块内的内容按语法高亮展示。仓库中的共享渲染管线正是这样实现的。位于 render.tsx 的是一个直接 mdast→React 的 Markdown 渲染器其中的renderCoderender.tsx把 fenced code 节点交给CodeBlock组件而CodeBlockCodeBlock.tsx负责渲染一个带infostring 语言标签横幅、shiki 语法高亮与复制按钮的代码块。也就是说fence 标记不会进入最终 DOM语言信息则从node.lang落到代码块顶部的标签栏。这一渲染路径同时也是对话内容的 sanitize 路径render.tsx头部明确声明了“不可信输出策略”——链接与图片目标经过协议白名单仅http:、https:、mailto:图片额外要求绝对 HTTP(S) 地址raw HTML 一律作为字面文本渲染、不进入 DOMKaTeX 不启用受信命令render.tsx。generic 工具卡片复用这条路径意味着工具结果里的 Markdown 也自动获得了同样的安全护栏。3.2 渲染先于截断行数统计基于可见行决策的另一半是顺序先做 Markdown 渲染再应用折叠卡片的 head-and-tail 行数限制。原因在于如果先按 Markdown源行截断可能恰好把一段 fenced block 拦腰切断留下残缺的代码块折叠卡片的行数统计与边界描述的是用户最终看到的终端行而不是 Markdown 源行——渲染前后行数并不一致例如一段多行 fenced 代码被折叠成“语言标签 高亮正文”后可见行数与源码行数不同。因此必须先渲染、再按可见输出计算头部与尾部行数。这个“按行数/字节预算保留头部与尾部”的思路在仓库中并非孤例工具端对模型返回内容的截断正是由dsh-output-retention的TextRetainer以head/tail/headTail三种窗口完成的见 output-retention 包说明卡片端的折叠行数限制与之语义一致只是作用在渲染后的可见行上。四、边界划分generic、terminal、diff 与 raw input 各司其职“给 generic 卡片开启 Markdown 渲染”并不意味着所有工具卡片都变成 Markdown。决策明确保留了三条边界Terminal 卡片保留专用纯文本渲染器。终端卡片有自己的一整套语义化渲染以 cwd 为头部、命令为标题、输出区展示捕获的 stdout/stderr并把退出状态渲染为独立的 pill。其数据模型在 terminal-card-model.ts 中定义parseExitStatusL249-L255从 shell 工具渲染器追加的[exit code: N]/[killed by signal: X]标记契约见 shell/render.ts中恢复退出码与信号terminalCardModelL267-L307负责把 raw Tool 块派生为终端卡片 props。在 ToolRow.tsx 中终端卡片的maxLines被设为Infinity即不做折叠截断——因为终端输出本身具有专用格式与滚动语义。Diff 卡片保留专用渲染。文件变更走DiffBlock拥有自己的 diff 行着色与上下文行号语义不能被通用 Markdown 的标点解释所污染。generic-card 的 raw input 保持字面量。决策特别强调generic 卡片的原始输入raw input仍然逐字显示因为它代表的是工具参数而不是 presenter 创作的散文。对应到代码中deriveBodytool-call-model.ts对参数做JSON.stringify展平展示这部分内容刻意不走 Markdown 解释。这些边界最终体现在分发组件 GenericToolCard.tsx 中它同时派生toolRowModel、terminalCardModel、readCardModel、diffCardModel、searchCardModel、webCardModel把不同意图交给ToolRow里对应的专用渲染分支只有落在 generic 输出槽位的内容才走共享 Markdown 主题。五、备选方案与取舍Agent Note 记录了三个被否决的备选方案它们分别从“生产者侧修复”“渲染范围”“截断顺序”三个角度尝试过解决各有其不可取之处在 Bash presenter 中剥离 fence 标记。这只修复了一个生产者——其他工具放入 generic 卡片的 Markdown 仍然不渲染而且会让 presenter 反向依赖 TUI 的渲染行为职责倒挂。把所有工具卡片一律渲染为 Markdown。终端输出与 diff 有专用格式化且其中可能包含必须保持字面量的 Markdown 标点例如终端输出里的*、#、反引号一旦被解释就会失真。在 Markdown 渲染前先应用折叠卡片限制。按源行截断会切断 fenced block且折叠计数与可见行数不一致行数提示会与实际显示错位。这三个被否定的方向从反面印证了最终决策按意图intent区分渲染路径而不是按内容格式一刀切顺序上让“渲染”始终先于“截断”。六、后果与验证决策落地后的行为约束同样记录在 Agent Note 的 Consequences 一节词汇与安全路径统一generic 工具卡片与对话内容使用同一套 Markdown 词汇与 sanitize 路径。工具结果中的链接、图片、代码块获得与聊天内容相同的白名单与字面 HTML 处理标点语义变化generic 卡片中的 Markdown 标点会被解释而非总是字面显示需要逐字呈现终端输出避免标点被误读的工具应当改用terminal card 意图——这正是ToolCallView词汇表存在的意义测试固定行为聚焦的 TUI 测试固定了三条行为——fence 被隐藏、语言标签被保留、正文文本正确呈现同时keyless 终端状态快照通过一组组装的 TUI transcript 覆盖该行为。关于快照测试方法本身仓库中有更完整的方法论记录2026-07-18-tui-terminal-state-snapshots Agent Note 说明了为何不直接固定Terminal.write()片段差分渲染可能改变写边界而不改变画面、为何不用组件行快照ANSI 解析、光标、视图port 行为无法覆盖而是让生产 TUI 挂载在 headless 终端模拟器上、等同步帧稳定后读取语义化终端状态行列、缓冲区坐标、生命周期、样式范围并强调所有检查点强制主题无关性仅使用 ANSI 0–15 调色板、无背景色——这正是“keyless”快照能够稳定描述卡片渲染结果的原因。七、总结DeepSeek Harness TUI 对工具结果卡片的处理遵循一条清晰的主线以工具声明的呈现意图generic / terminal / diff为分发依据以共享 Markdown 渲染管线为 generic 内容的标准答案以“渲染先于截断”保证折叠行数的语义正确以专用的 raw input 呈现保住工具参数的逐字保真。对使用者而言这条决策的实操含义很直接在工具 presenter 中面向 UI 书写 generic 结果内容时可以放心使用与助手消息一致的 Markdown 词汇包括 fencedconsole代码块、链接、强调它们会获得同样安全、同样美观的呈现需要输出保持逐字保真的场景终端输出、diff应声明对应的card: terminal/card: diff意图而不是依赖 generic 卡片的内容格式折叠卡片的行数限制作用于渲染后的可见行因此任何基于“行数”的展示预算都应放在渲染语义上理解。理解这一决策就能在扩展新工具或调试卡片呈现时准确判断“为什么这段内容这样显示”“应该改 presenter 还是改 UI”从而遵循仓库既有的呈现边界行事。【免费下载链接】deepseek-harnessDeepSeek Harness: Everything is a Plugin.项目地址: https://gitcode.com/gh_mirrors/de/deepseek-harness创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考