deepseek-harness-tui
v0.3.0
Published
Interactive terminal UI for deepseek-harness, installed as a dsh profile bundle
Downloads
1,482
Maintainers
Readme
deepseek-harness-tui
English | 简体中文
deepseek-harness 的交互式终端 UI,以 dsh profile bundle 的形式安装。它直接渲染在终端主屏上——不用备用屏 (alternate screen),退出后对话仍留在终端回滚缓冲区里——并且与它驱动的 agent 运行在同一个进程内。

亮点
- 为 agent 而生的对话视图——流式回答、带实时输出的工具卡片;连续的只读调用
折叠成一行(
Thought for 8s, searched for 3 patterns, read 2 files), Ctrl+O 随时展开回卡片。 - 运行中随时介入——turn 进行时编辑器保持可用:Enter 追加引导(steer),
Esc 或 Ctrl+C 取消,取消时排队中的输入会原样退还。agent 还没读到的那条会自己
说明(
插入指令 · 待认领),提示行给出还排着几条,/status列出它们。输入框 为空时按 Up 把最新一条排队消息取回来改;改完再发是排到队尾,所以连着按 Up 可以把整个队列过一遍。用/config可以把 Enter 改成「排到下一轮」, Ctrl+Enter 则对单条消息走相反的那一档(见 插话与排队)。 - 一个键切模式——Shift+Tab 在 normal → auto-accept → plan 间循环;提示行
上方的徽标与
/permission、/plan永远一致,因为按键写的是同一套服务。 - 会话可持久——
/resume恢复任意历史会话,/rewindfork 回到更早的 prompt 且原会话完好保留,/search全文检索本会话所有消息。 - 模型与 provider 就地管理——
/model选择路由和推理力度(可只对本会话生效, 也可存为默认);/login存 key 前先对端点校验,密钥只进凭据存储。 - 终端自适应——亮/暗/无色主题实时预览、中英文界面(
/lang)、按键可重绑、@文件引用通过fd尊重.gitignore。 - 可脚本化——
--print无 UI 跑一个任务、答案输出到 stdout,模型、preset、 各项 flag 与交互模式含义完全一致。
一个正在流式输出的 turn:agent 的 read 调用渲染出工具卡片,随后是思考过程、
逐 token 到达的回答——下方编辑器仍然可用,随时等你引导(Enter)或取消
(Esc):

快速开始
需要先安装 dsh CLI——本包是 dsh 插件,不是独立程序:
npm install -g @deepseek-ai/dsh
dsh plugin --profile tui add deepseek-harness-tui
dsh --profile tuiplugin 命令会把本包装进一个新的 tui profile(dsh-base +
deepseek-harness-tui),下次启动即生效。

用法
dsh --profile tui # 启动交互式 TUI
dsh --profile tui "fix the failing test" # 启动并发送首条 prompt
dsh --profile tui --continue # 继续最近一个会话
dsh --profile tui --resume <sessionId> # 恢复指定会话
dsh --profile tui --preset code # 以 "code" agent preset 启动
dsh --profile tui -m deepseek-official/deepseek-v4-flash # 覆盖模型
dsh --profile tui --print "run the tests" # 跑一个任务,答案输出到 stdout| Flag | 作用 |
|---|---|
| -m, --model <provider/model> | 本次运行的模型选择 |
| --preset <id> | 新会话按此 agent preset 组装;恢复的会话保持它自己日志里记录的 preset |
| -r, --resume <sessionId> | 按 id 恢复会话 |
| -c, --continue | 继续本工作区最近的会话 |
| -p, --print <task> | 无 UI 跑一个任务:答案写到 stdout,只有 turn 完整结束退出码才是 0;工具审批固定为 never,因为没有人可问 |
| -h, --help | 显示帮助 |
| [prompt...] | 首条 prompt,UI 就绪后发送 |
stdin 和 stdout 都必须是 TTY,否则拒绝启动。--print 是唯一例外——它不渲染
任何东西,所以可以跑在管道里,而管道也是它唯一有用的地方。其余 flag 与交互模式
含义完全一致:--print 同样可以作用于 --resume 或 --continue 的会话,
模型和 preset 也听命令行其余部分的。
按键
下面每个键都是终端默认真实绑定的键。空输入框按 ?、/hotkeys、/help
打印的是同一份列表,从按键注册表生成——部署改了绑定,三处看到的都是新键。
| 按键 | 作用 |
|---|---|
| Enter | 发送;turn 运行中是插话还是排队,取决于 /config(见插话与排队) |
| Ctrl+Enter | 对这一条走「运行中按 Enter」的相反档:Enter 插话时它排队,Enter 排队时它插话。需要终端能把它和 Enter 区分开——Kitty 键盘协议或 xterm 的 modifyOtherKeys 会把它编码成独立按键,其余终端物理上只发得出 \r,于是退化成普通 Enter 正常发送 |
| Shift+Enter / Alt+Enter / Ctrl+J | 换行;行尾 \ 再回车效果相同,照顾发不出 Shift+Enter 的终端 |
| Up / Down | 光标在输入框首行时翻 prompt 历史,其余行移动光标;输入框为空且还有排队消息时,Up 把最新那条取回编辑器修改 |
| Tab | 接受补全 |
| @ | 引用文件 |
| / | 运行命令;/skill:<name> 加载技能 |
| ? | 空输入框时显示快捷键帮助;不会打进草稿 |
| Ctrl+R | 反向搜索 prompt 历史;历史跨进程保存在 $DSH_HOME/history.jsonl,DSH_SKIP_PROMPT_HISTORY=1 停写(见 prompt 历史) |
| Ctrl+G | 搜索本会话消息;Ctrl+F 保留给编辑器的前进一格 |
| Shift+Tab | 循环模式:normal → auto-accept → plan → normal。normal 和 auto-accept 是 workspace-write 与 auto-accept 两个权限 preset(同一沙箱,问不问审批之差);plan 是计划模式,循环在 workspace-write 上进入。danger-full-access 不在循环里——它靠 /permission 进入,已处于其上的会话保持不变,按键只切计划模式 |
| Ctrl+N | 展开/收起计划;Ctrl+Y 保留给编辑器的 kill-ring 粘贴 |
| Ctrl+O | 循环工具卡片:预览、完整、隐藏 |
| Ctrl+T | 显示/隐藏思考块——关闭时思考随所在 step 流式出现并消失;开启时每个 step 都保留,含历史。模型无论如何都在推理;showReasoning: false 连同此键一起关闭 |
| Ctrl+X | 复制最后一条回答 |
| Alt+E | 用 $EDITOR 编辑草稿,保存后取回;先看 $VISUAL,再看 $EDITOR,最后在 PATH 上找 nano/vim/vi。发不出 Alt 的终端用 /editor |
| Ctrl+L | 重绘 |
| Esc | 取消 turn(并退还排队的输入);有草稿时再按清空草稿;空输入框再按打开 Rewind |
| Ctrl+C | 运行中取消(排队的输入照 Esc 的规矩退还),输入中清空草稿,空闲时连按两次退出;第三次按下直接离开无法取消的 turn |
| Ctrl+D | 空输入框时退出 |
| Shift+Ctrl+D | 会话调试面板——身份、生命周期、屏幕、按键解析结果 |
当某个界面持有键盘时
| 界面 | 按键 |
|---|---|
| 面板(/help、/hotkeys、/palette、/status、/mcp、/doctor、/subagents、/jobs) | Up/Down 滚动 · PgUp/PgDn 翻页 · g/G 或 Home/End 到顶/到底 · Esc 或 Ctrl+C 关闭 |
| 提问 | Up/Down 移动 · 1-9 直接作答 · Space 勾选(多选)· "Type something." 行输入自定义答案 · PgUp/PgDn 翻长详情 · Enter 提交 · Esc 或 Ctrl+C 取消 |
| 权限审批 | Up/Down 移动 · 数字键直接作答该行 · Enter 确认 · Esc 或 Ctrl+C 拒绝。只有能被记住的授权才会多出第 5 行(命令行工具则打开规则供编辑) |
| 历史搜索(Ctrl+R) | 输入即匹配 · Ctrl+R 跳上一条更旧的匹配 · Tab 或 Esc 取回编辑器 · Enter 直接发送 · Ctrl+C 或清空查询恢复草稿 |
| 会话搜索(/search、Ctrl+G) | 输入即过滤 · Up/Down 移动 · PgUp/PgDn 翻页 · Enter 打开该消息 · Esc 依次退出消息、清空查询、关闭 |
| 模型选择器(/model) | 输入即过滤 · Up/Down 移动 · Left/Right 或 Shift+Tab 调推理力度 · Enter 存为默认 · Ctrl+S 仅本会话生效 · Esc 先清过滤再关闭 |
| 恢复选择器(/resume) | 输入即搜索 · Up/Down 移动 · PgUp/PgDn 翻页 · Tab 在本工作区/全部之间切换 · Enter 恢复 · Esc 先清搜索再关闭 |
| Rewind(/rewind) | Up/Down 移动 · PgUp/PgDn 翻页 · Home/End 首/末 · Enter 回到那条 prompt · Esc 关闭 |
| 插件(/plugins) | 输入即过滤 · Up/Down 移动 · PgUp/PgDn 翻页 · Enter 展开条目 · Esc 关闭 |
| 技能(/skills) | 输入即过滤 · Up/Down 移动 · PgUp/PgDn 翻页 · Enter 阅读技能(Up/Down 滚动 · g/G 或 Home/End 到顶/到底)· Esc 依次退出技能、清空过滤、关闭 |
| 设置(/config) | Up/Down 移动 · Enter 翻开关、步进选项或进子菜单 · Left/Right 步进选项 · Esc 关闭 |
| 主题选择器(/theme) | Up/Down 逐个在背后屏幕上预览 · Enter 保留 · Esc 恢复打开时的主题 |
| Provider 登录(/login、/provider add) | Up/Down 移动 · Space 勾选模型 · Enter 继续 · Ctrl+U 清空输入 · Esc 取消整个流程 |
Ctrl+C 是唯一永不可重绑的键:它是离开终端的最后手段。其余绑定均可配置——见
下文 keybindings。
命令
| 命令 | 作用 |
|---|---|
| /help | 快捷键与命令 |
| /hotkeys | 只看快捷键 |
| /model [[provider/]model] | 切换模型并存为默认;不带参数打开选择器,也可只对本会话生效 |
| /preset [<preset> \| copy <preset> <new-id>] | 查看、切换或复制本会话的 agent preset |
| /config | 本终端自己的设置——Ctrl+T 思考固定、会话打开时的工具卡片阶段、运行中按 Enter 是插话还是排队、主题——就地修改并保存到下次会话 |
| /theme [auto\|light\|dark\|no-color] | 本终端的配色;不带参数打开选择器 |
| /login [provider] | 给 provider 配 API key:选一条已配置的或适配器提供的路由,粘贴 key,先对端点校验再存储。key 进凭据存储;settings 只记录变量名 |
| /provider [add] | 列出已配置的 provider 和 /login 可配置的;add 依次填写名称、端点、协议、key 和端点报告的模型 |
| /copy [N] | 复制一条回答到系统剪贴板;不带参数是最后一条,/copy 2 是上一条 |
| /editor | 在 $EDITOR 里编辑当前输入;编辑器运行期间终端交给它,退出后整屏重绘 |
| /new | 在本工作区开一个空白会话;当前会话保留全部历史、仍可恢复 |
| /clear | 清空对话视图;会话日志不变 |
| /rename [名字] | 给本会话起个名字;起了名字就固定住,自动命名不再改它。不带参数则重新生成标题,并继续交给自动命名维护 |
| /compact | 把更早的对话历史压缩成一段摘要;屏幕上的对话保留,模型侧只留摘要。不接受参数 |
| /lang [en\|zh] | 查看或切换界面语言;选择会记住到下次会话 |
| /palette | 本终端渲染的全部颜色与属性角色 |
| /export [path \| clipboard] | 把本会话日志写入文件并报告路径(覆盖已有文件前会先确认);写 clipboard 则把会话以 Markdown 放到系统剪贴板(远程 SSH 下走一次 OSC 52 写入,超过 100000 个字符会截断,提示里会说明) |
| /plugins | 搜索并查看 Loader 的插件条目 |
| /search [query] | 搜索本会话消息;参数会预填面板查询框 |
| /rewind | 回到本会话更早的 prompt |
| /resume [session] | 列出本工作区可恢复的会话;参数会预填选择器搜索框 |
| /skills | 搜索本会话的技能并完整阅读 |
| /subagents | 本会话下的子代理树:标签、一次性还是可继续、运行中还是未运行,以及 /resume 要用的子会话 id。面板开着时会自动刷新;profile 没挂子代理注册表时会明说 |
| /jobs | 后台任务:类型、标签、状态、生产方给的细节,以及各自跑了多久。面板开着时跟随注册表刷新;profile 没挂后台任务注册表时会明说 |
| /status | 会话诊断、排队消息、系统提示词、已注册工具 |
| /mcp | 本 agent 各工具来自哪个 MCP 服务器及其工具列表;profile 没挂 MCP 时告诉你怎么挂 |
| /doctor | 检查 Node 版本、终端、模型路由,以及缺了会静默降级的服务 |
| /exit、/quit | 当前 turn 到达空闲后退出 |
| /skill:<name> [instructions] | 把技能加载进对话 |
| /reload | 实验性(开发用):重读 Loader 配置文件并应用差异,仅空闲时可用。仅在 experimentalCommands 开启时注册 |
以上是本 bundle 自己的命令。profile 挂载的其他插件会在其上注册各自的命令,
/help 列出的才是当前会话真正可用的全集。
/details 已退役。它把两个不相关的开关塞进一套要背下来才能用的参数语法
([collapsed|expanded|hidden] [reasoning [on|off]]),而且两个都不跨进程记忆。
它做的两件事各归各处:工具卡片阶段就是 Ctrl+O 当场循环的东西,思考显示是一项
长期偏好——现在都是 /config 里的行,旁边就是打开 /theme 的主题行。它的
detailsDialogWidth 更名为 settingsDialogWidth,参数补全是 /theme 的四个值。
/config 和 /theme 的修改立即生效,并写入 harness 自己 settings 文档
($DSH_HOME/settings.yaml)的 tui 段——用的正是 /model 保存默认模型的那个
可选 settings 服务。/config 的每一行都实时读值,面板开着时按 Ctrl+O,下面的
工具卡片行会跟着动。宿主没挂该服务时,所有开关本会话内照常工作,只是退出即忘。
/lang 切换的是本终端自己的界面元素——命令列表、各面板(/help、/status、
/config、/search、/skills、/subagents、/jobs、/mcp、/doctor、
/plugins)、提示行与状态行、对话框及其按钮、这些界面写出的通知——在英文
(默认)与中文之间切换;对话内容永远不会被翻译。少数命令回执无论语言如何仍是英文:/model、/preset、
/resume 打印它们自己的报告文本,对话视图折叠的 turn 结局通知("Turn
cancelled."、"The model reached its output-token limit.")来自会话日志而非
消息表。
语言选择写入 Host 的 locale settings 段(有 settings provider 时,与 web
客户端读的是同一份偏好),否则写入 $DSH_HOME/tui-locale.json
(~/.dsh/tui-locale.json)。
插话与排队
turn 运行时编辑器保持可用,所以 Enter 永远是「发送」。运行中「发送」到底是什么
意思,由 /config 里的运行中按 Enter 决定:
- 插话当前轮次(默认)把这条交给正在跑的 driver,它在下一个 step 边界读到
——正在写的这段回答会被打断。提示行上方的 echo 带
插入指令徽章。 - 排到下一轮则是先寄存:正在跑的这一轮不受打扰地跑完,这条消息开启它之后
的新一轮。认领之前它的 echo 带
排队中徽章,认领之后就是一条普通的 prompt 行——因为它确实变成了一轮的开场提问。
Ctrl+Enter 只对当前这一条走相反的那一档,不改设置。它需要终端能把它和 Enter
区分开——Kitty 键盘协议或 xterm 的 modifyOtherKeys;其它终端上 Ctrl+Enter
物理上就是一个 \r,谁也认不出来,于是按普通 Enter 正常发送。
两档都只是「排进队列」而不是「立刻被回答」,所以队列提供的一切对两者都成立:
提示行给出条数(2 条排队中)、/status 按认领边界列出内容、输入框为空时 Up
把它取回来编辑(见 prompt 历史)、取消 turn 时 Esc 或 Ctrl+C
把文本退还编辑器。这项选择按用户存在 $DSH_HOME/settings.yaml;harness 的
web 端有它自己的一份同名选择,默认是排队,而本终端默认插话——这是它一直以来
的行为。
prompt 历史
上方向键和 Ctrl+R 能取回本进程启动之前输入过的 prompt。每条提交的 prompt——
以及被 Esc 或 Ctrl+C 清掉的草稿(含取消时退还回来的队列),那也是你可能想要
回来的东西——都会追加到
$DSH_HOME/history.jsonl(~/.dsh/history.jsonl),挂载时读回:最新的在前,
本会话自己的排在其他会话之前,且只取在本工作区里输入过的。超过 1024 个字符的
prompt 在行内只留 200 字符预览,正文移到旁边的 history-cache/;两者都以
0600 写入。文件超过 1 MB 后压实一次,只保留最新 1000 条;没人再引用的正文
文件在一周后删除。
turn 运行时 Up 还有第二个用处。输入框为空、还有排队消息、且没有打开补全菜单 时,它把最新那条排队消息从 agent inbox 取出来放回编辑器——提示行上方那条 echo 随之消失,计数减一。取回来之后它就是一条普通草稿:Enter 重新发出去,落到队尾; Esc 丢掉,文本进 prompt 历史。想改更早的一条,就把手上这条发回去再按一次 Up, 这样能把整个队列过一遍。三个条件缺一个,Up 就还是原来的 Up:正在写的草稿不会 被覆盖,队列为空时照旧翻历史。
存的是发出去的原文,明文:粘在输入框里的密钥或客户名会一直留在磁盘上,直到
文件被删掉。浮层里的输入框不是 prompt,不会被记录——/login 的 API key 不会
进这个文件。设 DSH_SKIP_PROMPT_HISTORY=1(或 true/yes/on)可以只停写、
不停读;要清掉已经记下的,删掉 $DSH_HOME 下的 history.jsonl 和
history-cache/。
上下文压力
prompt 行上的 ${context} 报告本会话用掉了模型窗口的多少——已用 78% 上下文,
暗色——直到窗口变紧。剩 25% 起改报另一个数:黄色的 上下文剩 22%;剩 10% 及以下
转红色。一次测量同时喂给颜色和数字,所以两个数不会打架,屏幕上也只会出现其中一个。
transcript 里每档还会写一行,因为正在读长回答的人不会盯着 prompt 行:黄色的
上下文快满了——窗口还剩 25%,以及剩 10% 以下时红色的 上下文几乎用尽。每档最多
写一行,并且只有读数重新回到该阈值之上 3 个百分点才会重新武装,所以读数在边界上来
回跳不会把同一条警告重复贴出来。本会话的 preset 挂了压缩服务时,这一行让你执行
/compact,没挂时让你用 /new;而当前这一轮还在运行时,它会说等这一轮结束再压缩
——/compact 需要会话空闲。
两个阈值是常量,不是配置项:25% 特意高于 @deepseek-ai/dsh-compaction-basic 默认
配置下自动压缩的 20%,所以黄行是你还能自己决定压不压的最后时刻;红行意味着自动压缩
这条路缺席、被关掉或者失败了。
权限授权
权限审批框的第 2 行("本会话内不再询问")只记在内存里,窗口关掉就没了。第 5 行
("本项目内不再询问")写入 $DSH_HOME/approvals.json(~/.dsh/approvals.json),
重启或 /resume 之后依然有效:
{
"version": 1,
"projects": {
"/home/you/code/app": {
"allow": ["edit", "bash(npm run:*)", "bash(git status)", "edit [danger-full-access]"]
}
}
}规则按会话打开时的工作区分组,所以在一个仓库里给的授权不会花在另一个仓库上,也
不会有任何东西被写进仓库本身。规则有三种:裸工具名(edit——该工具的每次询问都
放行)、命令前缀(bash(npm run:*)——匹配 npm run build、npm run test,但绝不
匹配 npm run-evil)、单条精确命令(bash(git status))。命令按词比较,模型多打
几个空格不会改变规则是否命中。规则永远不覆盖一行里跑多条命令的情况:只要带上
;、&&、|、重定向、反引号、括号或换行,即使第一条命令被允许也照样弹框;命令
指定了项目目录之外的工作目录时同样弹框,审批框里会写明是哪个目录。想撤销授权,把
对应条目从文件里删掉即可——projects 下其余内容(包括本版本不写的键)原样保留。
规则末尾的 [mode] 是这条授权当时对应的 sandbox 权限。宿主在调用被沙箱拒绝后会带
着更高权限重问一次(「escalate sandbox to danger-full-access: …」),而一条规则只回
答与自己同类的请求:在 workspace-write 下存的规则不会替之后的 danger-full-access
作答,普通调用下存的规则也不会回答任何提权请求。第 5 行的文案会写明它将不再询问的
权限档位。
命令行工具问的是「这条命令能不能跑」,所以它的第 5 行给的是一条规则而不是整工具放
行:审批框会预填(npm run build 预填成 npm run:*,git status 预填成
git status:*),Enter 保存,而先改一改正是它的用意——改成 npm:* 覆盖得更宽,改
成 npm run build 就只覆盖这一条。把输入框清空则只允许这一次、不存任何规则。这里
刻意没有「本项目内放行所有 shell 命令」这一行;遇到任何规则都匹配不上的命令——复合
命令,或 sudo/env/bash -c 这类裸包装——第 5 行直接不出现,只剩原来的四个选项。
终端看不清这次调用时也一样(后台命令、入参不是合法 JSON 等):持久授权只对审批框
真正展示过的内容开放,绝不只凭一个工具名发出去。
写文件类的工具会把这次改动直接画在审批框里:文件路径加 old→new 的 diff,内容取自
这次调用自己的入参(不读磁盘)。改动太长会被截断以保住下面的选项行——预算按实际占用
的屏幕行数计算(含长行折行与多文件),所以终端多宽都不会把选项挤掉;截断标记会写明
还有多少行没显示,调用真正跑起来之后,transcript 里的工具卡片有完整 diff。改动量
超过 maxDiffEditLength 时按整文件替换渲染并标注为近似;终端拿不到这次调用的内容
时,弹的就是原来那个不带 diff 的审批框。
@ 文件引用
宿主装了 fd(Debian/Ubuntu 上叫 fdfind)时,@ 通过它列出工作区,补全因此
尊重 .gitignore、.ignore 和 .fdignore。没有 fd 时由内置遍历器接管,按名
跳过构建产物——.git、node_modules、dist、build、out、coverage、
.cache、.next、.nuxt、.turbo、.venv、__pycache__、target——查询没
写扩展名时还会隐藏 *.log 和 *.tsbuildinfo。fileSearchCommand 可固定二进制
路径、设 "" 强制用遍历器;fileSearchExcludedDirectories 改遍历器跳过的目录。
命令参数同样有补全:/model 提供所有已公布的 provider/model,/preset 提供
roster 里的 preset 和 copy 动词,/theme 提供四个取值,/resume 提供本工作区
最近的会话。
界面
- 对话——主视图:流式消息、工具卡片、计划、状态行,以及带上下文行的输入框。
连续的只读调用——read、grep、glob、
ls/cat型 shell 命令、MCP 查询——折叠 为一行(Thought for 8s, searched for 3 patterns, read 2 files)而不是一卡 一行;Ctrl+O 把这一段展开回卡片。写入的调用永远不会入组——cat a > b写了b,动词说什么都没用;失败的调用留在组里并把圆点染红,因为读者看不见的失败 比一行承认失败更糟。该行的每个片段在每种语言里都是完整短语,而不是渲染时拼接 的动词加名词,中文因此有自己的语序、量词和逗号。 - 该行上的思考——这一段把思考作为首个分句报告在旁边(
Thinking for 12s, read 2 files…),模型还在思考时随时钟递增。这是默认对话视图唯一陈述思考 时长的地方:思考块本身保持自己的规则,随写下它的 step 一起消失(Ctrl+T 固定它,Ctrl+O 展开时回来)。每个分句有自己的时态——思考还在进行时文件已经 读完——时长出现在哪一行就留在哪一行,所以一段以回答而非下一次工具调用收尾的 思考会原地落定,而不是从屏幕上消失。在这一段的第一个调用报出文件、模式或命令 之前,行下的⎿行显示思考的最新一行;showReasoning: false和其他地方一样 不让这行出现,而时长——不引用任何内容——保留。 - 工作流运行——一次
workflow工具调用折叠成「运行 / 阶段 / 成员」三级: 运行行给出名称与成员数,每个阶段一行表头,每个成员一行状态与耗时。展开到哪一 级由运行状态决定而不是开关——只要某个阶段里还有成员不是「已完成」,这个阶段就 保留成员行;成员全部完成的运行收成一行,Ctrl+O 再展开。没有阶段的成员和阶段名 为空的成员是两个不同的组,因为它们在日志里本来就是两回事。turn 结束时仍未收到 结果的运行读作「已中断」,未结算的成员一同中断:它们不会再有结果,而一行还在 说「运行中」就是在说反话。 - Rewind——
/rewind,或空输入框连按 Esc:回到更早的 prompt。宿主能 fork 会话时对话随之移动、原会话仍可恢复;否则只是把那条 prompt 放回编辑器。文件 永远不会被恢复——dsh 不做文件快照。 - 恢复——
/resume [session]:挑选并恢复历史会话,范围是本工作区,Tab 切到 全部。每行是标题加上多久前碰过和日志多大(2 hours ago · 354.1KB);当前 所在的会话不列出,因为恢复到自己不是一个去处。id 可被搜索框匹配但不打印在 行上。什么都没输且无可列时,面板直说没有其他会话可恢复,而不是报告一次落空 的搜索——空列表就是答案,不是查询失败。离开终端时会打印找回刚离开会话的 命令,走的那一刻回来的路就在屏幕上。 - 会话搜索——
/search [query],或 Ctrl+G:本会话的每条消息,输入即过滤, 命中处就地展示,整条消息一个 Enter 即达。做成面板而不是跳转,是因为输入框 上方的对话属于终端的回滚缓冲区,任何程序都无法替你滚动它。 - 插件——
/plugins:搜索并查看 Loader 的条目。 - 技能——
/skills:搜索本会话组装的技能并阅读正文;/skill:<name>再把它 加载进对话。 - 设置——
/config:本终端自己决定的偏好——思考固定、会话打开时的工具卡片 阶段、主题——外加语言和模型两行,它们只读、注明改它们该用哪条命令。 - 主题——
/theme,或/config里那一行:auto(跟随终端报告)、light、dark、no-color,移动时在选择器背后的屏幕上实时预览,Esc 离开则恢复原样。 - Provider 登录——
/login [provider]:给一条路由配 API key。列表包含 settings 已配置的和适配器目录自带的,后者正是一台 settings 空白的机器也能 连上 DeepSeek 官方端点的原因。key 永不回显——输入框画点——有端点可校验时 先校验再存:401 或 403 什么都不存;端点答不上来的 key 只有明确说"是"才存; 目录路由的端点在适配器内部、本终端看不见,直接存,因为本来就无从问起。回执 只把端点真正应答过的 key 称为已校验;其余存储的 key 一律报告为未校验而不是 可用。密钥进凭据存储自己的文件;settings 只记录变量名。/provider列出同样 两组,/provider add引导一条适配器没听说过的路由走完名称、端点、协议、凭据 变量、key 和端点报告的模型。 - 子代理——
/subagents:本会话下的委派树,每个子代理一行——标签、是一次性 委派还是可继续的会话、记录是活着还是只在持久化里,以及/resume要用的子会话 id。这些行来自子代理目录而不是对话视图,所以上一个进程里委派出去的子代理同样 在树里;subagent/start与subagent/end只负责告诉打开着的面板「该重读目录 了」。刚刚 spawn 的子代理可能晚一次刷新才出现:目录从子代理写下自己的 descriptor 那一刻起才列出它。/status用一行给出同一棵树。 - 状态——
/status:会话诊断、排队消息、系统提示词、已注册工具。排队消息 一节按 agent 将要读取的顺序列出它收到但还没读的输入,每条一行(过长省略), 并标出停在哪个边界:[插话]打断正在写的这段回答,[下一轮]单独占一轮。 它和提示行那个计数(2 条排队中)读的是同一个 agent inbox,所以别的 host 插进这个会话的输入也在其中。 - 后台任务——
/jobs:本会话丢到后台还在跑的活。带run_in_background的bash调用,或者发出去就不等的委派,都会立刻把控制权还给模型然后继续跑;它们 当前的状态只存在于后台任务注册表里,所以这些行就来自那里。每一行给出生产方 类型、生产方起的标签(命令本身、委派描述)、生命周期状态与随之而来的细节 (失败 · exit code: 1),以及已经跑了多久——运行中的排在前面且最早的在最上, 然后是已结束的,最近结束的在前。面板开着且确实有任务在跑时,运行中那行的秒表 才走;都结束了就停。提示行只带一个计数(2 个后台任务运行中),让后台有活这件 事一眼可见,而不用在屏幕上多放一块秒表;/status用一行给出同样两个数。 - MCP——
/mcp:本会话每个工具来自哪个 MCP 服务器,从工具注册名mcp__<server>__<tool>反推出来,因为 harness 没有可查询的注册表。它天生 只读——终端没有连接、重启或认证服务器的句柄——profile 没有 MCP 行时告诉你 怎么挂一个,而不是给你看一张空列表。 - Doctor——
/doctor:会话跑在什么之上(/status描述的是会话本身)—— Node 版本、终端、模型路由,以及缺了会静默降级的服务。每项检查一行:结论、 观察到的事实、该做的那一件事。 - 帮助——
/help:按键与斜杠命令。
配置
bundle 行(tui-runner)上的值,全部可选。
| 键 | 默认 | 含义 |
|---|---|---|
| welcome | — | 启动 banner 下额外的一行暗色文字;完全不设时改为字标扫入动画 |
| sessionId | main | 本终端驱动的共享 agent/会话标识 |
| initialSkill | — | 作为会话第一个 turn 自动调用的技能,等同输入 /skill:<name>;供启动器设置,不面向人 |
| initialDraft | — | 编辑器打开时预填的未发送文本;由 rewind handoff 设置 |
| experimentalCommands | false | 注册开发者命令(目前是 /reload) |
| showReasoning | true | 本对话视图是否允许渲染推理文本;false 在所有阶段隐藏思考块、不让折叠行的 ⎿ 提示引用模型原文(时长保留),并连同 Ctrl+T 和 /config 的思考显示行一起关闭 |
| markdownRenderer | claude | claude(本 bundle 的渲染器)或 pi(pi-tui 的 Markdown);claude 渲染抛错后本进程余下时间回退到 pi |
| maxToolOutputLines | 6 | 折叠工具卡片头/尾预览保留的正文行数 |
| maxDiffEditLength | 1000 | 推导精确行级 diff 时探索的增删行数上限 |
| maxPromptChars | 10000 | 单条提交输入的字符上限;超出的中段会被丢弃并留下 ... [N characters truncated] ... 标记与一条提示,0 表示不截断 |
| maxQuestionOptions | 8 | 提问面板一次可见的选项数 |
| maxModelOptions | 8 | 模型选择器一次可见的模型数 |
| maxResumeOptions | 8 | 恢复选择器一次可见的会话数 |
| resumeScanConcurrency | 4 | 一次恢复扫描的冷投影并发读数 |
| questionDialogWidth | 200 | 提问面板宽度(列),受终端约束 |
| questionDialogMaxHeight | 20 | 提问面板最大高度(行) |
| modelDialogWidth | 76 | 模型选择器宽度(列) |
| modelDialogMaxHeight | 20 | 模型选择器最大高度(行) |
| settingsDialogWidth | 72 | /theme 选择器宽度(列) |
| fileSearchMaxResults | 20 | 一次 @ 查询展示的模糊候选数 |
| fileSearchMaxEntries | 10000 | 一个 @ 工作区索引保留的路径数 |
| fileSearchExcludedDirectories | 见上文 | 遍历器跳过的目录名 |
| fileSearchCommand | — | fd 的路径或名称;不设则在 PATH 上发现,"" 禁用 |
| externalEditor | — | Alt+E 与 /editor 交给哪个编辑器;不设则依次读 $VISUAL/$EDITOR 再在 PATH 上发现,"" 关闭该功能。GUI 编辑器需要自带等待参数(code -w),已知的那些会自动补上 |
| showHardwareCursor | false | 在编辑器 IME 标记处显示终端硬件光标 |
| updateCheck | true | 每天最多问一次 npm registry 有没有更新的版本,有则提示一条;从不自动安装,任何失败都不出声 |
| title | DeepSeek Harness | UI 挂载期间的终端窗口标题 |
| theme.color | true | 应用内置 ANSI 配色 |
| theme.truecolor | 自动检测 | banner 的 24 位品牌渐变;不设时读 COLORTERM |
| theme.leftPrompt | ${cwd}${git/worktree}${model}${token_meter/cache_hit_rate}${context} | 编辑器上方左对齐模板 |
| theme.rightPrompt | ${queued}${jobs} | 编辑器上方右对齐模板 |
| theme.inputPrompt | ❯ | 编辑器首行前缀 |
| theme.inputPlaceholder | press enter to steer and esc to cancel | agent 运行时空编辑器的占位文本 |
| keybindings | — | 按键覆盖,按 action id 键入 |
提示行模板以 ${name} 插值本 bundle 注册的值——cwd、git/worktree、
model、context、token_meter/cache_hit_rate、goal、queued、jobs、
symbol、indicator——某个值当前不可用时,挨着它的分隔符一并省去。context 报的是"已用"
还是"剩余"取决于窗口有多满,见上下文压力。
除 Ctrl+C 外的绑定均可配置:在 bundle 行上设 keybindings
({ "app.history.search": "alt+r" }),按 action id 键入,值为一个或多个
pi-tui 按键 id。本 bundle 的 id 有 app.mode.cycle、app.tools.cycle、
app.history.search、app.transcript.search、app.todos.toggle、
app.thinking.toggle、app.message.copy、app.screen.redraw、app.cancel、
app.exit;pi-tui 编辑器自己的绑定也可以同样方式移动。Shift+Ctrl+D 会报告
每个 id 解析成了什么键、哪个键被两个 action 同时认领、哪个键被 app.* action
从 pi-tui 编辑器手里拿走——按键"没反应"时第一个该查的就是它。
有两个键故意不用读者可能期待的那个,因为 app.* 绑定在编辑器看到按键之前就被
应答:搜索是 Ctrl+G 而不是 Ctrl+F(pi-tui 的 tui.editor.cursorRight),计划
开关是 Ctrl+N 而不是 Ctrl+Y(pi-tui 的 tui.editor.yank)。把它们重绑到编辑器
的键上,编辑器的习惯就永远没了。
Shift+Tab 拿来做模式循环是安全的,理由相同:pi-tui 在这一族里只绑了 tab,
它的编辑器不认识 Shift+Tab。/model 选择器里的 Shift+Tab(步进推理力度)是
另一个作用域、照常工作——任何浮层持屏时,应用层监听器在第一个分支前就返回,
对话框先看到按键。
循环本身不写任何自己的状态:normal 和 auto-accept 通过
ctx.permissionPresets 选择(auto-accept 条目由本 bundle 的
cordis.patch.yml 加进表里),计划模式通过 ctx.planMode——所以
/permission、/plan、恢复的日志和按键报告的是同一个状态。开启的模式会在
输入框上方说明——⏸ plan mode on、⏵⏵ auto-accept on——徽标旁标注循环它的
键。两个徽标可以同时在场,通过 /permission auto-accept 加 /plan 而非按键
到达;此时提示只跟着最后一个,因为同一个键在两行叠着重复会被读成要按两个键。
没有组装 preset 表或计划模式的部署保留它有的档位:按键循环挂载了的东西,
无可循环时直说。
开发
pnpm install
pnpm typecheck
pnpm test
pnpm build
dsh plugin --profile tui add link:./path/to/dsh-tui # 开发用 live-link
dsh --profile tui在 tui-runner 行上设 experimentalCommands: true,编辑配置文件时可用
/reload。
本插件是一个 bundle 里的两个 Cordis 插件:
dsh-tui/startup——解析 TUI 自己的命令行,提供tuiStartup服务。dsh-tui——runner:持有 pi-tui 渲染循环、进程内 agent 会话、审批应答器和 用户提问 provider。
数据单向流动。dsh 总线上的事件(session/event、agent/status)由每会话的
读模型折叠成不可变节点列表;带 key 的 reconciler 把列表变成 pi-tui 组件,版本
已应用过的节点全部复用,一阵流式 chunk 只重绘一个 assistant step 而不是整个
对话。TUI 运行在进程内,直接调用 ctx.agents / ctx.approval /
ctx.commands——没有 SDK、没有 ACP 传输、没有 React。
许可证
MIT
