dsh-tui
面向 DeepSeek Harness 的交互式终端界面。它把 Harness 已有的 Agent、模型路由、工具、会话持久化、审批和提问能力放进一个长期存在的 TUI 工作区中:无需启动浏览器,也不另造一套后端。
界面使用 OpenTUI 与 SolidJS 渲染,重点不是做一张“终端海报”,而是让日常工作中的输入框、对话、工具活动、上下文占用和会话恢复始终清楚可见。

> 图中 552k/1.0m 来自 Harness 的 contextPressure 投影。TUI 不写死 1M:容量和占用都以当前模型路由实际报告的数据为准。
它解决什么问题
DeepSeek Harness 已经拥有完整的 Agent 与工具运行时,但纯命令行输出很难同时回答下面这些问题:
- 我现在在哪个目录、哪个会话里?
- 当前使用的模型是什么,上下文还剩多少?
- 模型是在回复、思考,还是运行 Shell / 其他工具?
- 哪条工具指令失败了,完整参数和输出在哪里?
- Agent 正在等审批,还是在等一个选项答案?
- 终端变窄后,关键信息是否仍然看得见?
dsh-tui 提供一个单会话、常驻输入框的终端工作区:
- 模型回复始终以 Markdown 展示,不折叠最终答案。
- Reasoning 与工具活动默认压成一行,可单独展开,也可用
Ctrl+O全部展开。 - 输入框始终固定在底部;审批或提问到来时,它会切换为
APPROVE/ANSWER状态。 - 模型、上下文、MCP 和插件数量来自 Harness 运行时,不使用猜测值。
- 会话继续写入标准
$DSH_HOME/sessions,可以在 TUI、Web 或其他 Harness 界面之间复用。 - 80×24 到超宽终端使用同一套响应式布局;空间不足时按优先级减信息,不截断关键字段。
界面概览
1. 首页
空会话显示一张由真彩色半块字符绘制的倾斜黑洞。它有四档完整资源,而不是把一张大图强行裁切:220×60、160×45、100×34 和 80×24 都有对应尺寸。第一条消息出现后,开屏立即让位给对话。
首页下方的三行输入区从上到下分别是:
1. 当前输入状态和辅助操作; 2. 永久存在的输入行; 3. 模式、模型、上下文、MCP 与插件状态。
2. 对话与折叠活动

用户消息、模型回复、Reasoning、工具调用和系统消息是不同的语义行。折叠后的活动行仍保留最有用的信息:
thought:Reasoning 的开头摘要与词数;shell:解析后的命令与running / ok / failed状态;- 其他工具:工具名、代表性参数与状态;
- 模型回复:完整 Markdown,不参与折叠。
按 Ctrl+O 后,会展开当前对话中的 Reasoning、Shell 命令、工具参数和捕获输出:

TUI 只展示模型适配器真实发出的 reasoning 块。若提供方没有公开 Reasoning,就不会伪造“思考过程”。
3. 会话选择
在有历史会话的目录运行 dsh-tui 时,会先出现当前目录专属的恢复面板:

它只列出创建于当前工作目录的会话。这样可以保持“先 cd 到项目,再继续这个项目的对话”的使用习惯,不会把其他目录的历史混在一起。
4. 窄终端

80×24 是设计和测试覆盖的紧凑基线。此时仍保留输入框、模型与上下文;MCP、插件、会话 ID、提示项和黑洞尺寸会按可用空间逐级缩减。更窄的终端会继续隐藏低优先级装饰,但不保证拥有完整的视觉层级。
快速开始
环境要求
| 依赖 | 要求 | 原因 |
|---|---|---|
| DeepSeek Harness | dsh 命令可在 PATH 中找到 | dsh-tui 是 Harness profile,不是独立 Agent 后端 |
| Node.js | 26.1 或更高 | OpenTUI 原生渲染依赖实验性的 node:ffi |
| pnpm | 可在 PATH 中找到 | dsh plugin 使用 pnpm 管理 profile 依赖 |
| 终端 | 真实 TTY;建议支持 True Color、Unicode 和鼠标 | 交互输入、半块图像和真彩色状态依赖终端能力 |
Node 26 启用 --experimental-ffi 时会打印一条 ExperimentalWarning,这是当前 OpenTUI 启动的正常提示。
安装到 tui profile
从 GitHub 安装:
~~~sh dsh plugin --profile tui add github:Zhen-WushuiLingchun/dsh-tui ~~~
建议在正式环境固定提交:
~~~sh dsh plugin --profile tui add github:Zhen-WushuiLingchun/dsh-tui#<commit-sha> ~~~
从本地 checkout 安装:
~~~sh cd dsh-tui dsh plugin --profile tui add . ~~~
也可以安装已经打包、包含 lib/ 的 tarball:
~~~sh dsh plugin --profile tui add ./dsh-tui-0.4.0.tgz ~~~
dsh plugin 会在 profile 不存在时创建 $DSH_HOME/profiles/tui,加入 @deepseek-ai/dsh-base,再把本包声明的 bundle patch 叠加进去。它不会修改 Harness 的 Web 或 headless profile。
#### Git 安装被 allowBuilds 拦截
Git 依赖需要在安装阶段运行 prepare 生成 lib/。pnpm 10 默认可能阻止依赖执行构建脚本,并在错误中指出 profile 的 pnpm-workspace.yaml。确认源码可信后,在该文件中允许本包:
~~~yaml allowBuilds: dsh-tui: true ~~~
然后重新执行安装命令。allowBuilds 是“允许安装时执行代码”的授权;请审查源码并固定提交。若不希望开放安装脚本,可以改用预构建 tarball 或未来发布的 npm 包。
启动方式
推荐使用随包提供的 dsh-tui 启动器,因为它会自动给子进程注入 --experimental-ffi:
~~~sh dsh-tui dsh-tui --resume <session-id> dsh-tui --list dsh-tui --help ~~~
把 bundle 安装进 profile 并不会自动把 profile 内的 bin 加进系统 PATH。在源码 checkout 中可以显式建立全局链接:
~~~sh pnpm link --global dsh-tui ~~~
如果不想建立全局链接,也可以直接启动 profile。
PowerShell:
~~~powershell $env:NODE_OPTIONS = '--experimental-ffi' dsh --profile tui ~~~
Bash / Zsh:
~~~sh NODE_OPTIONS=--experimental-ffi dsh --profile tui ~~~
dsh-tui 启动器本质上只做两件事:添加 FFI 标志,然后执行 dsh --profile tui <原参数>。因此所有模型、凭据、工具和会话配置仍由 Harness 管理。
日常使用
启动参数
| 命令 | 作用 |
|---|---|
dsh-tui | 当前目录有历史会话时打开选择器,否则新建会话 |
dsh-tui --resume <id> | 跳过选择器,直接恢复指定会话 |
dsh-tui --list | 列出持久化会话后退出,不挂载交互界面 |
dsh-tui --help | 显示本应用参数 |
会话选择器
| 按键 | 作用 |
|---|---|
↑ / ↓ | 移动选中项,首尾循环 |
Ctrl+P / Ctrl+N | 上移 / 下移 |
Enter | 恢复选中会话 |
n | 新建会话 |
q / Esc | 退出 |
对话快捷键
| 按键或操作 | 作用 |
|---|---|
Enter | 发送当前输入 |
Ctrl+O | 展开或折叠全部 Reasoning 与工具活动 |
| 鼠标点击活动行 | 只展开或折叠这一行 |
PageUp / PageDown | 每次滚动半个视口 |
| 鼠标滚轮 | 滚动对话 |
Ctrl+D | 结束输入并退出 |
Ctrl+C | 终止当前 TUI 进程 |
新的流式输出会让视图回到最新消息。输入框始终保留焦点,方向键不会被对话滚动抢走。
斜杠命令
| 命令 | 作用 |
|---|---|
/exit、/quit、/q | 保存并退出 |
/list、/sessions | 在终端输出持久化会话列表 |
/new | 新建并切换到一个会话 |
/resume <id> | 切换到指定持久化会话 |
/help、/h | 在对话中显示帮助 |
其他内容都按普通用户消息提交,包括无法识别的斜杠文本。
审批与问题
当工具请求审批时,输入框会变为 APPROVE:
y/yes:仅允许这一次;c/cancel:取消请求;- 其他输入或空输入:拒绝。
当 Agent 调用 Harness 的 Ask User 能力时,输入框会变为 ANSWER:
- 单选:输入选项编号,例如
2; - 多选:用空格或逗号分隔,例如
1 3或1,3; - 自由文本题:直接输入文字。
根 Agent 和其拥有的子 Agent 共用这个终端回答者,因此它们的审批和问题都会回到同一个输入框。
如何理解底部状态区
示例:
~~~text ▌ MESSAGE ctrl+o details ▌ › ask anything · /help for commands ▌ chat · deepseek-v4-pro · 55% ctx · 552k/1.0m · ⊙ 2 mcp · ⚙ 47 plugins ~~~
| 字段 | 数据来源 | 行为 |
|---|---|---|
chat / working… / needs you | TUI 当前阶段 | 请求运行、等待用户时立即变化 |
| 模型名 | agentDefaultModel.currentSelection() | 宽终端显示 provider/model,窄终端显示短名 |
| 上下文 | contextPressure 投影 | 优先 projectedTokens,否则使用 pressureTokens;contextWindow 是分母 |
| MCP 数量 | Loader 中启用的 @deepseek-ai/dsh-mcp-client 实例 | 已知为零时明确显示 0 mcp |
| 插件数量 | Loader 中启用且非分组的条目 | Loader 不可读时省略,不伪造零 |
以 Harness 当前内置的 deepseek-official/deepseek-v4-pro 为例,路由报告 1,000,000 token 上下文,因此会显示 …/1.0m。如果其他模型报告 128K、256K 或没有公开容量,TUI 会分别显示真实容量或 ctx pending,不会把 DeepSeek 的 1M 套到其他模型上。
上下文颜色分段:
- 低于 70%:绿色;
- 70%–89%:琥珀色;
- 90% 及以上:红色;
- 请求尚未产生用量或服务不可用:
ctx pending/ctx —。
架构
它在 Harness 中的位置
dsh-tui 是一个外部 bundle layer。它复用 @deepseek-ai/dsh-base,只新增终端入口与终端渲染,不启动浏览器、HTTP Host 或 Web Runtime。
~~~mermaid flowchart TD Launcher["dsh-tui 启动器<br/>注入 --experimental-ffi"] --> CLI["dsh --profile tui"] CLI --> Profile["Profile 组合"] Base["@deepseek-ai/dsh-base<br/>Agent · 模型 · 工具 · 会话"] --> Profile Bundle["dsh-tui/cordis.patch.yml<br/>startup + runner"] --> Profile UserPatch["profile / home / --patch 覆盖层"] --> Profile Profile --> Startup["tui-startup<br/>解析 --resume / --list"] Startup --> Runner["tui-runner<br/>创建或恢复一个 Agent"] Runner --> Harness["Harness 服务<br/>sessions · approvals · questions · projections · loader"] Harness --> Events["session/event"] Events --> Fold["foldEvent<br/>事件折叠为语义行"] Fold --> Host["响应式 UiState Host"] Host --> OpenTUI["SolidJS + OpenTUI<br/>真实终端渲染"] ~~~
Profile 的生效顺序由 Harness 管理:bundle 按 dsh.profile.bundles 顺序应用,然后是 profile patch、home 级 patch 和命令行 --patch。后面的层可以覆盖前面的配置。Harness 的 patch 对目标行的 config 是整体替换,不是深度合并;自定义配置时需要写完整的目标配置值。
一条消息如何到达屏幕
~~~mermaid sequenceDiagram participant U as 用户 participant T as dsh-tui participant A as Harness Agent participant S as Session / Projection participant V as OpenTUI
U->>T: Enter 提交输入 T->>T: parseCommand alt 斜杠命令 T->>S: 新建、恢复、列出或退出 else 普通消息 T->>A: createUserMessage + followup A-->>S: assistant/chunk、tool/call、tool/result... S-->>T: session/event T->>T: foldEvent 更新语义行 T->>S: 读取 contextPressure T->>V: 更新 UiState V-->>U: 增量重绘终端 end ~~~
TUI 不修改系统提示词之外的请求结构,不给每轮增加额外前缀,也不实现独立 Token 估算。因此它本身不会改变 KV Cache 命中方式;上下文数字由 Harness 的 token-meter 和模型路由负责。
源码分层
| 文件 | 职责 |
|---|---|
bin/dsh-tui.js | 跨 Windows / POSIX 启动 dsh,注入 FFI 标志并透传参数 |
cordis.patch.yml | 在 dsh-base 之上插入启动参数提供者、Code Runtime 与 TUI Runner |
src/startup.ts | 用 Commander 解析 --resume、--list 和帮助 |
src/index.ts | 会话生命周期、输入命令、Agent 提交、审批、问题和 Harness 事件订阅 |
src/stream.ts | 将持久化会话事件折叠为 user / assistant / thinking / tool / system 行 |
src/telemetry.ts | 从 projection 与 loader 读取上下文、MCP、插件数量并格式化 |
src/ui.tsx | SolidJS/OpenTUI 组件、输入框、Markdown、折叠行为和响应式布局 |
src/hole.ts | 四档内嵌真彩色半块黑洞资源;运行时不依赖外部图片 |
scripts/preview.mjs | 用真实测试渲染器输出空白、聊天、展开、审批等场景 |
为什么使用 SolidJS 变换
OpenTUI 的 Solid 渲染依赖响应式属性 getter,不能把 TSX 当成普通 React JSX 编译。tsdown.config.ts 使用 Babel:
babel-preset-solid以generate: universal、moduleName: @opentui/solid编译 TSX;- 将
solid-js与solid-js/store映射到客户端构建; - 把
@deepseek-ai/*标记为运行时外部依赖,由当前dsh安装提供。
这样 Git / tarball 安装只构建本包的 lib/*.mjs,Harness 核心 API 则始终跟随实际启动它的安装,而不是把另一份 Harness 复制进 profile。
兼容性
Harness 兼容
- 模型:不限制为 DeepSeek 模型。只要模型通过 Harness Adapter 运行,回复、工具和状态都走同一事件流;Reasoning 与上下文容量按 Adapter 是否提供对应数据决定。
- 工具与 Skills:继承
dsh-base的工具面,包括 Shell、文件、Skills、工作流、目标和子 Agent。TUI 不维护第二份工具注册表。 - MCP:MCP 仍由 Harness profile 配置。TUI 只显示启用实例数量,不私自启动服务器。
- 会话:使用标准 Session 与 SessionPersistence 服务,
--resume不依赖 TUI 私有格式。 - 审批与提问:实现 Harness 的 approval 事件和 userQuestions provider;所有决定仍由 Harness 写入会话。
- 配置覆盖:profile 自己的
cordis.patch.yml、home patch 和--patch仍可覆盖 bundle。
终端与平台兼容
| 场景 | 状态 |
|---|---|
| Windows PowerShell / Windows Terminal | 已验证;启动器通过 cmd.exe /d /s /c 调用 dsh |
| Linux / macOS | 启动器走标准 dsh 子进程;受 Node 26 与 OpenTUI 对该平台的支持约束 |
| 80×24 | 自动化布局与视觉测试覆盖的紧凑基线 |
| 100×34、160×45、220×60 | 自动化截图与布局测试覆盖 |
| 非 TTY / CI 管道 | --list、--help 可用;交互界面需要真实 TTY |
| 非 True Color 终端 | 可以启动,但终端可能量化黑洞和状态色 |
| 不完整 Unicode 字体 | 半块图形、图标或框线可能显示异常;建议 Cascadia Mono、JetBrains Mono 等 |
已知限制
- 同一时刻只展示一个会话;
/new和/resume会切换当前会话,不提供多窗格。 - 根 Agent 与子 Agent 的审批/问题共用同一个终端输入框,没有按子会话拆分的提示路由。
- Reasoning 完全取决于提供方;无法显示提供方没有发送的私有思考。
- OpenTUI 当前要求 Node 26 的实验性 FFI,启动警告暂时无法消除。
- DeepSeek Harness 仍处于快速演进阶段;若 Harness 升级后插件无法挂载,应重新安装本 bundle,使依赖解析和构建产物与新版本对齐。
配置与扩展
安装后的 profile 位于:
~~~text $DSH_HOME/profiles/tui/ ├── package.json ├── cordis.yml ├── cordis.patch.yml └── node_modules/ ~~~
如果没有设置 DSH_HOME,Harness 默认使用 ~/.dsh。应把个人覆盖写入 profile 自己的 cordis.patch.yml,不要直接编辑 node_modules/dsh-tui/cordis.patch.yml;后者在更新包时会被替换。
本 bundle 默认:
- 复用
@deepseek-ai/dsh-base; - 使用 coding persona;
- 保留 Harness Code Mode 的
DSH_TOOLS_MODE开关; - 插入 Code Runtime、
tui-startup和tui-runner; - 关闭共享 HMR 行;
- 不插入 Web Host、HTTP Server 或浏览器插件。
开发与验证
~~~sh pnpm install pnpm run prepare pnpm test ~~~
测试覆盖:
- 参数解析与启动选择器;
- Harness 会话事件折叠;
- 上下文、插件与 MCP 遥测;
- 80 / 100 / 160 / 220 列布局;
- Markdown、Reasoning、工具折叠、审批与问题;
- 首页黑洞在各档尺寸下的颜色、暗区和延伸结构;
- 有消息后首页图消失、输入框持续存在。
输出实际终端字符帧:
~~~sh pnpm preview pnpm preview chat pnpm preview expanded pnpm preview picker pnpm preview approval pnpm preview question pnpm preview working ~~~
prepare 在独立 checkout 中可能提示无法解析 @deepseek-ai/dsh-*。这些包被有意保留为运行时外部依赖,由 Harness 的 profile module fallback 提供;只要构建完成且在实际 dsh profile 中启动成功,这些 warning 不代表产物缺失。
发布前建议至少执行:
~~~sh pnpm run prepare pnpm test npm pack --dry-run --json ~~~
并在 80×24、100×34 与一个宽终端中分别检查空会话、普通聊天和 Ctrl+O 展开状态。
常见问题
dsh-tui:command not found
Bundle 已经安装进 profile,但启动器不一定在系统 PATH。可在 checkout 中执行 pnpm link --global,或者使用带 NODE_OPTIONS=--experimental-ffi 的 dsh --profile tui。
Cannot find module node:ffi
Node 版本过低,或者直接运行 dsh --profile tui 时没有添加 --experimental-ffi。确认:
~~~sh node --version ~~~
版本应为 26.1 或更高。优先使用 dsh-tui 启动器。
为什么显示 ctx pending
新会话在第一次模型请求前没有用量样本,这是正常状态。若完成请求后仍然 pending,检查 profile 是否挂载 token-meter / session projection,以及当前模型路由是否报告 contextWindow。
为什么没有 thought 行
当前模型适配器没有发出 reasoning 块。TUI 不会根据最终回答反推或编造思考内容。
黑洞颜色不对或字符错位
确认终端启用了 True Color,并使用等宽、包含 ▀ / ▝ 等 Unicode 字形的字体。终端强制 16 色、字体 fallback 或非 1:2 字符比例都会改变图像观感。
更新 Harness 后启动失败
先确认当前 dsh 版本和 profile 实际安装的 bundle,再重新解析:
~~~sh dsh plugin --profile tui update dsh-tui ~~~
如果使用 Git 提交固定版本,改为新的已验证提交后重新执行 add。不要通过复制另一份 @deepseek-ai/* 到本包来绕过模块错误,这会造成同一进程中存在两套 Harness 服务类型。
安全说明
- Git 安装的
prepare会执行本仓库代码;仅对可信来源启用allowBuilds。 - MCP 服务器是 profile 配置的外部进程,可能在 Agent 沙箱之外运行;启用前应审查命令与权限。
- TUI 的批准输入
y只产生allowed-once,不会自行创建永久授权。 - API Key、模型端点和凭据继续由 Harness 管理,本包不读取或保存独立凭据文件。
License
[MIT](LICENSE)