dsh-native-tui
经典、稳健的 DeepSeek Harness TUI。
它把会话、供应商和模型管理放进终端,保留键盘操作与原生滚动,布局保持紧凑。这里的 “经典”指传统终端式交互;“稳健”来自锁定的 rc.6 依赖和持久会话重放,所有交互都走 官方 Harness service / event / presenter 合同。

这是社区项目,不是 DeepSeek 官方发行版,也不代表 DeepSeek。启动字标沿用上游 DeepSeek Harness TUI 曾实际发布的官方终端字标:
DEEPSEEK使用官方蓝色渐变,HARNESS使用终端前景色。实现依据是上游提交d04ec5a, 上游与本项目均采用 MIT 许可证。
项目包名是 @ablemind/dsh-plugin-tui,Cordis 插件入口名是 tui-runner。它作为普通
函数插件挂在 dsh-base 上,接管当前终端,不改 DeepSeek Harness 内核。rc.6 自带的
dsh-headless 明确没有 interactive follow-up surface;这个插件补上交互终端界面。
核心能力
- 会话管理:新建、继续最近会话、按列表恢复、按 ID 恢复、重命名;历史与实时消息走同一套事件渲染。
- 供应商管理:
/provider查看 active / dormant 状态、凭据来源和模型目录,可遮罩录入 API Key,并响应官方配置刷新事件。 - 模型管理:
/model按供应商分组选择模型,接着选择 reasoning effort;结果写回默认设置,下一轮生效。 - 经典终端交互:键盘优先、低装饰、原生 scrollback;全屏与内联模式可切换,支持 dark / light / mono。
- Harness 原生能力:工具 presenter、Code Mode、Workflow、子代理、Jobs、HITL 与上下文压缩都从官方服务和事件读取。
设计语言整体沿用 ablework/dpagt/dpcli 的 Python TUI(Textual 版),数据来源换成
harness 自己已经拥有的三样东西。
与 Python 版的关系
照搬的(这些是已经验证过的产品判断,没有理由重做):
- 无图标、无消息边框;用户消息与工具行靠背景色块区分,assistant 裸 Markdown
- 工具渐进披露:运行时只露最近 3 条语义行,轮次结束折成
工具 × N;失败与 中止行在折叠后仍然可见 - braille spinner(
⠋⠙⠹…@80ms)+ 当前工具名 + 已耗时 +(Esc 打断) - 输入框只有上下两条全宽
─,无左右竖边;>提示符常显;!开头进本机 shell 态(品牌红) - Claude Code 形状的三段底栏:左 = 模型 + 上下文占用条(>70% 黄 / ≥85% 红), 中 = 目录短形式 + git 分支,右 = 权限模式 + token 计量;宽屏一行、窄屏两行, 降级顺序固定,模型与上下文占用永不下车
- 三套配色
dark/light/mono,色值逐条搬过来,/theme持久化
换掉的(Python 版当年只能自己猜,这里 harness 已经给了权威来源):
| 数据 | Python 版 | 这里 |
|---|---|---|
| 工具叫什么、动了哪个文件、diff 是什么 | 名字表 + 参数键启发式 | ToolDefinition.presentCall / presentResult 声明的 ToolCallView / ToolResultView;启发式只作为未声明 presenter 的工具(主要是 MCP)的兜底 |
| 上下文占用、token 明细 | 自己从流帧累加 _last_usage(其注释记着一次压缩后仪表卡死的 bug) |
tokenUsage / contextPressure / contextBreakdown 三个会话投影,从持久日志折出来 |
| transcript 从哪来 | 自建 SSE 帧 | ctx.on('session/event'),用户输入也从事件回显而不是从按键回显 —— 重放渲染与实时一致 |
结构上唯一的实质差异:Python 版是 Textual 全屏应用,可以事后改任意 widget,
代价是丢掉终端自身的选中/滚动(它的 docstring 里列了三条并行的复制通道来补)。
这里反过来:transcript 就是普通 stdout 滚动区,选中、滚轮、复制全是终端原生的;
只有会变的东西(运行中的工具组、流式 assistant 尾巴、状态行、底栏、输入框)
待在底部固定区,轮次结束时把最终形态打一次进滚动区。因此 “展开某个历史工具行”
不是原地改写,而是 /open 重新打印一遍。
布局
dsh-native-tui/
├── package.json @ablemind/dsh-plugin-tui — dsh.bundle.patch + rc.6 依赖
├── cordis.patch.yml 组合包 patch:直接叠在 dsh-base 上,与 dsh-headless / dsh-web-app 平级
├── tsdown.config.ts src/index.ts → lib/index.js(ESM,@deepseek-ai/* 全部 external)
└── src/
├── ansi.ts Span/Line 样式模型、东亚宽字符测宽、截断/填充/换行、SGR 渲染(顶替 rich.Text)
├── theme.ts 三套配色 + 解析 + 持久化(顶替 tui_theme.py + palette.py)
├── format.ts preview / 脱敏 / token 数字格式 / 路径收缩(对应 tui/format.py)
├── markdown.ts 极简 Markdown → 样式行(顶替 rich.Markdown + pygments)
├── screen.ts 滚动区追加 + 底部固定区重绘(核心渲染器,Textual 的替代品)
├── composer.ts raw mode 行编辑、历史、`/` 与 `@` 补全、`!` 态
├── tools.ts ToolRow 语义行 + 渐进披露 + diff;ToolGroup 三行活动窗(对应 tui/tools.py)
├── events.ts 可回放的 workflow / compaction / context / inbox 投影
├── subagents.ts 直接消费 ctx.subagents 官方目录与会话投影
├── statusbar.ts spinner / 提示行 / 三段底栏 / `/context` 明细(对应 tui/statusbar.py)
└── index.ts Cordis 插件:建 agent、分发 session 事件、命令、按键
安装
需要 Node.js 22+。克隆仓库后运行安装器:
git clone https://github.com/hanlinlibham/dsh-native-tui.git
cd dsh-native-tui
./install.sh
$EDITOR ~/.config/ablemind/config.env
dsh .
安装器先执行完整检查,再按仓库锁文件安装 @deepseek-ai/dsh@0.1.0-rc.6 和插件,位置是
~/.local/share/ablemind,创建 abletui profile,再把启动器放到
~/.local/bin/dsh。重复运行就是升级;已有配置不会被覆盖。配置文件至少填写
DEEPSEEK_API_KEY,也可固定 model / effort、权限、Code Mode、主题和全屏模式。
如果机器已有别的 ~/.local/bin/dsh,安装器会停止,不会覆盖。若 PATH 中另一个
dsh 排在前面,安装器也会提示你把 ~/.local/bin 提到前面。
日常入口和其他 coding agent 一样:
dsh . # 新会话
dsh --continue . # 当前项目最近一次
dsh --resume . # 列表选择历史会话
dsh --resume=SESSION_ID . # 精确恢复
这个 dsh 仍保留官方入口。dsh plugin ...、dsh web ...、dsh --profile NAME ...
会原样交给安装器固定的 DeepSeek Harness CLI;裸 dsh 或传入目录时才启动 TUI。
开发安装
npm install && npm run check # typecheck + 测试 + 构建 lib/index.js
dsh plugin --profile abletui add "$PWD" # 首次会自动建 profile
profile manifest 落成这样,和 headless / web 同构:
"dsh": { "profile": { "bundles": [
"@deepseek-ai/dsh-base", "@ablemind/dsh-plugin-tui" ] } }
显式指定 profile
cd 任意项目目录
dsh --profile abletui
没有启动器、没有 --patch、没有 cwd overlay:process.cwd() 就是 agent 的工作目录。
这一段的历史:2026-08-16 之前这个包是 checkout 里的 .ts 源码插件,靠
lab-plugins/ 拷贝 + tsx 源码启动加载,于是进程只能从 checkout 里跑(先 cd 到
项目再起会在 import 阶段炸 does not provide an export named 'FiberState'),
工作目录只能靠一张生成的 overlay 传,还得配一个 tui.sh 启动器。四个补丁同一个
根因。改成"编译产物 + dsh.bundle + npm 依赖"之后一次性全消失——
@deepseek-ai/dsh-*@0.1.0-rc.6 已经发到 npm(rc.5 时代挡路的
@deepseek-ai/dsh-type-meta 在 rc.6 已经不是依赖了),全量 typecheck 对着发布
接口零报错。
要连 dsh 本体也不依赖 checkout:npm i -g @deepseek-ai/dsh@0.1.0-rc.6。
用源码 checkout 起也行(cd dsh && node --import tsx/esm apps/cli/src/bin.ts --profile abletui),只是那样进程 cwd 又被钉回 checkout。
DEEPSEEK_API_KEY 由 app-boot 从进程 cwd 的 .env 读,所以要么放进项目根的
.env,要么直接 export 到环境里。
配置项(cordis.patch.yml 的 config:)
| 键 | 含义 |
|---|---|
fullscreen |
占满终端(alt screen,自带 PgUp/PgDn 滚动,退出时把 transcript 回放到普通屏)。默认开;DSH_TUI_FULLSCREEN=0 退回内联模式(滚动与拖选交给终端) |
cwd |
agent 的工作目录(会话 meta.cwd、底栏路径与 git chip、! shell、@ 补全根)。通常不用填——默认就是进程 cwd;只在宿主把进程钉死在别处时才需要(比如源码 checkout 启动) |
theme |
dark / light / mono;不填则用 ~/.dsh/tui-settings.json 里 /theme 存的 |
contextLength |
适配器还没报出容量前,底栏占用条用的窗口大小 |
settingsFile |
配色持久化路径 |
bangTimeout |
! 本机命令的超时秒数(默认 30) |
sessionMode |
new / continue / resume;启动器自动设置 |
resumeSession |
要精确恢复的 session id;启动器自动设置 |
historyFile |
输入历史 JSONL,默认 $DSH_HOME/tui-history.jsonl(未设时 ~/.dsh/...) |
provider / model / reasoningEffort |
可选的启动模型路由;provider/model 必须一起配置 |
按键与命令
| 按键 | 行为 |
|---|---|
Enter |
发送;补全打开时先接受候选 |
Alt+Enter 或行尾 \ |
换行(raw mode 读不到 Shift+Enter,这里不假装能) |
↑ ↓ |
历史;补全打开时是选候选 |
Ctrl+R |
按当前输入倒序搜索持久历史 |
Shift+Tab |
打开权限预设弹窗 |
Tab |
接受候选 |
Esc |
关补全;没有补全时打断当前轮 |
PgUp / PgDn |
全屏模式下翻阅历史(内联模式交给终端自己滚) |
Ctrl+O |
transcript(所有工具行常开第二层) |
Ctrl+L |
清屏 |
Ctrl+C |
运行中→打断;有输入→清空;空输入连按两次→退出 |
Ctrl+D |
空输入时退出 |
Ctrl+A/E/U/K/W |
行首 / 行尾 / 删到行首 / 删到行尾 / 删词 |
命令:/help /new /clear /resume /rename /context /agents /jobs /todos /tools /open /verbose /theme /model /permission /provider /mcp /quit,
外加未列入补全的 /dump(打印最近一条工具行的完整脱敏 args/result)。
挂上 @ablemind/dsh-plugin-peers 之后还有 /peers(同工作区其它对话)和 /quad(开或复用 tmux 四席)。
/context 是 “底部 token 细节” 的展开版:system / tools / messages 三项构成
(启发式估算),加上末次请求、下次预计、窗口容量、累计输入/缓存读/缓存写/输出。
构成三项与占用不相加——前者是固定密度估算,后者锚定提供方实报,这个差是
token-meter 有意保留的。
非用户的上下文注入与官方 Web UI 一样默认只显示一行来源;/context show
展开最近一条,/context show #<seq> 展开指定编号。
Harness 能力在终端上的消费
- Code Mode:
rootCallId / parentCallId折成真正的递归工具树;根调用才计入工具 × N,每层子调用继续走同一套presentCall / presentResult。 - Workflow:按
runId折叠 run/member 生命周期,运行中在固定区更新,结束后进入 scrollback;历史尾先到时会等待更早的 run-start。 - 子代理:不把 child transcript 灌入主对话。
/agents读官方子代理目录,显示 one-shot/continuable、存活、token、活跃耗时和后代。 - 压缩:关联 start/summary/replacement/end,显示 summary、被遮蔽项/token、路由和耗时;
compaction/prune保持官方 log-only 计量语义。 - 上下文注入:非用户
user/message按 durable source 分为 inject/recall,指令文件、 skill、plugin 与 session-reference 都不再静默丢弃;对齐官方 Web UI 默认折叠,需要时再用/context show [#seq]显式展开。 - HITL:approval 绑定
callId;提问支持完整 batch、skip、多选+自定义、精确 plan-review 识别,并区分用户取消与 harness abort。 - Jobs / 产出:TUI 挂上官方 jobs controller,
/jobs output|kill <id>直接走 registry 的 read/kill 合同;每轮成功 mutation 按 tool render intent 折出去重的产出文件,不靠工具名猜测。
Known Limitations and Deferred Work
- 跨会话全文检索尚未接入。
/resume能选择并重放已有会话,但没有/history或基于sessionQuery的跨会话搜索;中文 FTS 分词与字面扫描降级也因此仍是延期工作。 - 没有通用插件、Skill 与设置清单。
/mcp只按当前注册工具分组,尚无/pluginsLoader inventory、/skillsregistry 列表或通用只读/settings;/provider只管理供应商相关配置。 - 没有会话导出。
/copy只用 OSC 52 复制最近一条助手回答,尚无/exportJSONL/Markdown。 - spill notice 仍按普通工具文本显示。完整结果路径仍在文本里,但尚未折成“已溢出 N 字节 → path”的专用定位行。
- 固定区假设自己是唯一写 stdout 的人。同 profile 里别的插件(或 logger)直接
往 stdout 写,会把底部区顶上去一次,下一次重绘才恢复。真要共存,得给
logger 换一个走
screen.printRaw的 sink。 - 历史工具行不可原地展开。滚动区是追加式的,
/open是重新打印而不是改写;/dump查看最近一条完整脱敏载荷。 /model弹窗切换。借鉴 AbleMind Code v0.2.0:←→ 切 provider 分组,↑↓ 选择模型;支持模型后的第二级 reasoning effort,下一轮请求生效并写回默认选择。/provider供应商管理。借鉴 AbleMind Code v0.2.0 的双栏弹窗:↑↓ 选择供应商,右侧查看 active/dormant、配置、凭据来源和模型目录;k遮罩录入 Key,首次配置会像官方 Web 一样先用 path mutation 建立 credential reference,再把秘密只写入ctx.credentials。页面状态按 provider directory × redacted settings × credential describe 合并,并随三类官方 invalidation 自动刷新;r也可手动刷新。- MCP 是配置期能力。rc.6 没有运行时 manager service,
/mcp如实列出当前已注册的mcp__*工具;增减服务后需重启,TUI 不伪造 toggle/reload。 - 图片与 Mermaid:会话图片按官方 attachment 元数据列出,
/file可预览文本与识别图片; 跨终端没有统一的图形协议,因此不把 Kitty/iTerm 私有协议冒充通用渲染。Mermaid 源码按 Markdown code fence 展示,浏览器图形画布仍属于 Web 插件。 - 多字节候选宽度:补全列表按候选条数截窗,不按像素宽度,超宽候选会被行截断。
@补全只补路径文本,不挂附件。补出来的@path就是发给模型的普通文本 (模型可以自己去读),没有走dsh-attachment的内容寻址存储。web 面的ui-input-trigger那套内联引用语义这里还没有对应物。- 渲染节流 33ms。流式 chunk 实测超过 100/s,每帧全量重绘固定区会撕裂;按键走 leading edge 所以输入仍是即时的,但极端情况下最后一帧可能滞后一帧。
No comments yet. Be the first to write one.