社区发布文稿 · 待 review · GIF 已换成真实录制

DSH-TUI-PI —— 让 dsh 在终端里住下来

DeepSeek Harness 的全功能 TUI:流式对话、powerline 状态栏、实时思考/工具/待办面板、子代理双视图、主题热切换——全部以纯 cordis 插件实现,不 fork、不 patch 上游一行代码。

它不是孤零零一个界面包,而是一套自研插件组合拳:TUI 本体之外,还有子代理注册中心和确定性上下文压缩两个配套插件,装齐了才是完整形态。

📦 https://github.com/fan56/dsh-tui-pi

dsh-tui-pi 终端演示 GIF
上图是一段真实终端录制:todos、运行中的子代理、think/tool 面板和 powerline 底栏都在动。另有 asciinema 版

界面一览

dsh --profile tui
┌──────────────────────────────────────────────────────────────┐
│ ~/github (Full access) │ ⎇ main        ◄─ 编辑框上沿信息条     │
│ ┌──────────────────────────────────────────────────────┐     │
│ │              对话流(可滚动,只留干净问答正文)           │    │
│ │   ▸ You    这个报错帮我看看                             │    │
│ │   ● AI     ## 排查结论                                 │    │
│ │            1. 根因是 ……                                │    │
│ └──────────────────────────────────────────────────────┘     │
│ ● Todos (1/3)          ◄─ 待办面板(做完自动收起)             │
│ 💭 thinking · 12.4s    ◄─ 思考面板                            │
│ ⚙ read session.ts      ◄─ 工具面板                            │
│ ❯ _                                                    │
├──────────────────────────────────────────────────────────────┤
│ ├─ ⠋ 牛马狗 · 21k/1m · round 2/75 · 13.6s · reading files…   │
│ └─ ⠋ 小黄鸭 · …                       ◄─ 运行中的子代理行      │
│ ● 正在请求…                                                  │
├──────────────────────────────────────────────────────────────┤
│ dsh(standard)▸☁️deepseek▸🤖gpt-5◕▸🧠32k/128k(25%)▸💬12▸🔧34▸21:03:44
│ ⌨ Enter 发送 · Esc×2 停止 · Ctrl+G 子代理 · Tab preset        │
└──────────────────────────────────────────────────────────────┘

设计取舍:过程细节(think/tool/todo)从对话流里抽出来钉在固定位置,主对话流永远只有干净的问答正文;状态则全部沉到屏幕边缘的底栏与信息条。

UI 拆解

Powerline 底栏

底栏是经典 powerline 样式,所有数字都是 O(1) 维护的计数器——渲染路径从不扫描会话日志,再长的会话也不掉帧:

powerline footer
 dsh(standard) ▸ ☁️ deepseek ▸ 🤖 gpt-5 ◕ ▸ 🧠 32k/128k (25%) ▸ 💬 12 msgs ▸ 🔧 34 tools ▸        21:03:44
 └品牌+preset──┘ └─提供商──┘  └模型+思考等级┘ └─上下文用量(分档变色)─┘ └消息数─┘ └工具数┘ └时钟┘

上下文用量按阈值分四档变色:50% 内正常、50%/70%/90% 逐级转橙转红,快满了瞄一眼就知道。编辑框上沿还有一条信息行:当前目录、权限徽标、git 分支:

editor info line
 ~/github (Full access) │ ⎇ main
 └─cwd──┘ └─权限徽标──┘     └分支

底栏下面还有一条可开关的快捷键提示行,7 个段落各自可在设置里单独关掉,全关后一行都不占:

footer hints
 ⌨ Enter: send · Esc ×2: stop · Ctrl+C ×2: quit · Ctrl+D: quit (empty) · Ctrl+G: subagents · Tab: preset · ↑↓: history

Think / Tool 面板

思考与工具各一块面板,钉在输入框上方,高度一个设置搞定(dsh-tui.panelHeight):

dsh-tui.panelHeight
 '1'(默认)  💭 thinking · 12.4s · 正在分析调用栈……
              └ 一行极简:标识 + 耗时 + 最新一行,超宽右截断

 '5'|'7'|'10' ┌─ 💭 thinking ──────────────────┐
              │ 盒装面板,正文 N-1 行            │
              └────────────────────────────────┘

 'all'       全量展示;流式思考保留 200 行活动尾部,
             工具结果封顶 2000 行,超出显示 … (+N lines)

工具面板头部自带状态: 执行中 / 成功 / 失败 + 工具名 + 操作对象 + 耗时,成败还有不同底色;派活类操作(use_agent 等)不占工具面板,它们有自己的视图(见下)。面板自绘、随宽度自适应重排,换主题只需重画一帧。

Todo 面板

任务清单以带边框表格呈现在输入框正上方,编号、勾选态、内容三列,三种状态三种颜色:

todos panel
 ● Todos (1/3)
 ─────────────────────────────────
 #   ✓  Task
 1   ☑  定位根因
 2   ◐  补回归测试          ◄ 进行中
 3   ☐  更新文档

列表为空或全部完成时整个面板自动收起,不留空壳。

子代理:运行行 + 洞察视图

每个运行中的子代理在输入框下方占一行,旋转指示器 + 名称 + 当前上下文用量 + 轮次 + 已耗时 + 最新输出尾巴,一眼看清谁在干什么:

running agents line
 ● 帮我把这个 bug 修了                        ◄ 最近一次请求(常驻)
 ├─ ⠋ 牛马狗 · ↻1 · 21k/1m · round 2/75 · 13.6s · reading src/session.ts…
 └─ ⠋ 小黄鸭 · 8k/1m · round 1 · 4.2s · 已识别报错堆栈中的……

Ctrl+G(或 /subagents)进入洞察视图,两层结构:

/subagents viewer
 Ctrl+G ──► ● Sub-agents 选择层(运行中在前,最近完成 5 个)
               │  <Xk tok> · 🧹 N次压缩 · 耗时 —— 每行的摘要栏
               ▼ Enter
            ● 单个子代理的实时转录层
               🐳 回复  ⚙ 工具调用  ✔✘ 结果  ▎用户  🧹 DCP压缩  ☑ todos
               尾部跟随刷新,向上滚动即脱离跟随翻历史

并发上限、轮次上限都能调(/agents 里配置:默认并发 4、每轮 75 轮)。

Reset 的四种姿势

「重来」在这个 TUI 里分场景各有其键:

场景 操作 行为
停掉正在跑的任务 Esc 两连(500ms 内) 连同子代理一起停
中断本轮回复 Ctrl+C(运行中) 取消当前 turn
清空输入框 Ctrl+C(空闲时) 编辑器文本一键清零
开新会话 /new 脱离当前会话,清屏清面板

另外 /settings 浏览器里每组配置都有 Reset to defaults 行(二次确认),/hotkeys 编辑器里把按键清空即恢复默认绑定。

命令全家福

TUI 自带命令全部双通道注册(无会话时也能直接执行),dsh 内置命令则原样透传、绝不重新发明:

命令 说明
/model 选模型(含思考等级)
/think 切换当前模型的推理等级
/agents 管理本地 markdown 定义的 agent(模型、思考等级、派生深度)
/subagents 浏览子代理并查看其实时转录
/preset [name] 切换 agent preset(Tab 也可循环)
/theme 切配色方案,立即生效
/session 查看当前会话信息(id、模型、统计)
/resume 恢复一个已持久化的会话
/export [path] 导出会话日志为 JSONL
/new 开新会话
/settings 浏览器式配置编辑(命名空间、取值、重置)
/skills 管理用户技能(已装 + 可装)
/login [provider] 配置 provider API key(可搜索目录 + 打码密钥输入)
/logout 登出 provider(密钥与配置一并移除,模型立刻消失)
/permission 权限预设选择器
/model-sync 发现手写 baseURL provider 的可用模型并入配置
/hotkeys 查看/自定义快捷键(~/.dsh/keybindings.json)
/reload 从源码热重载 TUI

其中登录/登出是完整的图形化流程:/login 是可搜索的 provider 目录,API key 输入框逐字符打码显示;/logout 会列出所有存有凭据的 provider 供选择摘除。技能、计划、压缩等 dsh 原生命令以及你自己装的 skill 都会自动出现在补全列表里。

Agent Preset

Tab 循环切换,/preset 弹选择器,/preset <名字> 直达,/preset next 下一个;当前 preset 直接显示在底栏品牌段上:

preset cycle
 Tab ──►  code ⇄ cordis ⇄ minimal ⇄ [standard] ──► 下一个会话生效
                               底栏同步显示 dsh(standard)

启动时默认选中 standard;没碰过 preset 时不下发任何预设参数,服务端配置的默认值说了算。

主题引擎

GitHub light / dark 双配色;auto 模式跟随终端明暗,运行中切换也实时跟进:

/theme hot-switch chain
/theme ──► settings 变更 ──► applyThemeRef
                                 ├─► 历史消息 ReplayOp 整屏换装
                                 └─► 画布背景换色 + 输入区重建
                                      (一帧节流完成,无残影)

画布背景逐行涂色(BCE),在 cmux/gostty 这类终端里切主题也不会留下冻住的默认底色;想要透明背景,DSH_TUI_TRANSPARENT=1 一键退回。

子代理:注册中心 + 三件套

配套插件 dsh-subagent-registryhttps://github.com/fan56/dsh-subagent-registry)把本地定义的自定义 agent 注册成可调用的 subagent:扫一个 agents 目录,每个 markdown 就是一个人设,主模型通过统一的 use_agent 工具按名字派活。招牌能力是断点续跑——跑了一半被中断的子代理,下次调用会从已保存的部分成果接着来,而不是从头再来。

我日常挂三个:

名字 一句话定义
🦆 小黄鸭 rubber-duck 多模态视觉 agent:替你看截图、图表、报错界面并输出结构化结论,也能把数据画成图——主模型是 text-only 时它就是眼睛
🐕 牛马狗 workhorse 干活的主力:写代码、调查、测试、部署全包,受安全门约束——不出 $HOME、不碰危险命令
🧙 老法师 oldfox 解决难题的顾问:分析、排障、review、挑刺,专门给方案和代码把关,不做设计不动手

它们在 TUI 里是一等公民:运行行、洞察视图、🧹 压缩账目全都照常工作。

上下文压缩:DCP

配套插件 dsh-dcphttps://github.com/fan56/dsh-dcp)把默认的「LLM 总结式压缩」换成确定性的上下文剪枝:压缩全程零模型调用,同样输入永远得到同样输出,路径/命令/报错原文/未完成 todo 这些硬事实逐字保留。实测约 8 万 token 的历史压到几百 token。TUI 里每次压缩以 🧹 标记呈现,子代理各自压了几次也有账可查。

中文安全

截断统一走宽度词汇表:CJK 全角 = 2 列、grapheme 永不劈半、ANSI 先裁后染。中文内容随便贴,不会错位串行。

质量

  • 零上游补丁:所有能力纯插件挂载,升级 dsh 不用重新合代码
  • 544 个测试(37 个文件)持续护航;e2e 覆盖 24 行小终端场景
  • 同系列还有网关网络错误的有界重试插件 dsh-llm-net-retry(https://github.com/fan56/dsh-llm-net-retry),按需取用

安装

install
$ dsh plugin --profile tui add @aiwayds/dsh-tui-pi
$ dsh --profile tui

更多配置与排障见 👉 https://github.com/fan56/dsh-tui-pi