@fishwithcat/dsh-tui-app 0.0.0-stage → 0.2.2-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +79 -0
- package/README.md +245 -2
- package/README.zh.md +246 -0
- package/cordis.patch.yml +67 -0
- package/lib/index.js +7021 -0
- package/lib/startup.js +54 -0
- package/lib/types/ansi.d.ts +109 -0
- package/lib/types/app.d.ts +480 -0
- package/lib/types/clipboard.d.ts +61 -0
- package/lib/types/commands.d.ts +101 -0
- package/lib/types/config.d.ts +35 -0
- package/lib/types/diagnostics.d.ts +23 -0
- package/lib/types/diff.d.ts +37 -0
- package/lib/types/images.d.ts +37 -0
- package/lib/types/index.d.ts +28 -0
- package/lib/types/interactions.d.ts +132 -0
- package/lib/types/measurement.d.ts +18 -0
- package/lib/types/session.d.ts +151 -0
- package/lib/types/shell.d.ts +111 -0
- package/lib/types/startup.d.ts +34 -0
- package/lib/types/subagents.d.ts +75 -0
- package/lib/types/tool-view.d.ts +39 -0
- package/lib/types/transcript.d.ts +216 -0
- package/lib/types/views.d.ts +572 -0
- package/lib/types/watch/client.d.ts +70 -0
- package/lib/types/watch/history.d.ts +25 -0
- package/lib/types/watch/protocol.d.ts +146 -0
- package/lib/types/watch/record.d.ts +50 -0
- package/lib/types/watch/server.d.ts +172 -0
- package/lib/types/watch/viewer.d.ts +128 -0
- package/locale/de.json +6 -0
- package/locale/en.json +6 -0
- package/locale/es.json +6 -0
- package/locale/fr.json +6 -0
- package/locale/ja.json +6 -0
- package/locale/pt.json +6 -0
- package/locale/ru.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +110 -4
package/README.zh.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "dsh 的交互式终端界面:在单个进程内驱动一个 Agent、一个会话,提供全屏终端交互,适合通过 SSH 工作或偏好终端而非浏览器的使用者。"
|
|
3
|
+
kind: "package-bundle"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @deepseek-ai/dsh-tui-app
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
`dsh-tui-app` 是 dsh 的交互式终端界面。运行 `dsh` 即在当前目录启动一个 Agent,其模型、沙箱与审批默认值与其他界面一致,工具为 base 工具外加 `apply_patch`,但以全屏终端应用呈现:可滚动的对话记录、位于固定页脚之上的输入框,以及报告工作区、上下文占用、路由模型、token 吞吐量、token 总量、缓存命中率和委派子代理数量的状态行。Agent 运行在同一进程内。它唯一的监听器是首个本地 subagent 启动时的回环查看服务,使另一个终端可以读取该子会话的对话记录与实时流,而无需创建第二个 Agent。主要边界:每次调用只有一个会话,没有浏览器与文件侧栏。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [使用本包](#use-this-package)
|
|
17
|
+
- [理解实现](#understand-the-implementation)
|
|
18
|
+
- [进一步探索](#further-exploration)
|
|
19
|
+
- [模型体验](#model-experience)
|
|
20
|
+
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
|
21
|
+
- [开发备注](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## 使用本包
|
|
27
|
+
|
|
28
|
+
在应作为工作区的目录中打开终端界面:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
dsh
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
不带 profile 的 `dsh` 即启动本 bundle;`dsh --profile tui` 显式指定它,并接受同一组参数。`DSH_DEFAULT_PROFILE` 可为裸调用选择其他默认 profile(`DSH_DEFAULT_PROFILE=web dsh`),将其设为空字符串则恢复上游「每次启动都必须指定 profile」的要求。没有交互式终端时应用会拒绝启动,因此管道或重定向调用会明确报错,而不是等待无法读取的按键。
|
|
35
|
+
|
|
36
|
+
### 组合、提问与中断
|
|
37
|
+
|
|
38
|
+
输入提示并按 Enter 提交。Agent 工作期间,输入框切换为 steer 模式:Enter 提交的文本会在运行的 turn 的下一个 step 被消费,Esc 则中断该 turn 以及会话委派的所有存活 subagent 后代。委派工作会比 Agent 自身的 turn 更晚结束,因此只要还有 subagent 处于 turn 中,页脚会持续显示 `running`,输入框也会继续提供取消按键。手动 `/compact` 不经过 turn 生命周期,因此在压缩进行期间,页脚会把 `idle` 换成 `compacting`,随后再以通知报告最终结果。PageUp/PageDown、鼠标滚轮与终端搜索可在不退出应用的情况下滚动对话记录;当视图滚离最新一行时,状态栏会给出提示。
|
|
39
|
+
|
|
40
|
+
在 alternate-screen 模式下,拖动选中文本并松开鼠标即可自动复制。本机 macOS 会话使用 `pbcopy`,包括 Terminal.app;写入失败时显示 `Copy failed`。其他平台及 SSH 会话需要终端支持 OSC 52 剪贴板写入,该路径无法确认写入成功。终端转发 Command+C 时,也可按该键再次复制选中的文本。如果终端拦截 Command+C,请使用终端原生选择手势(macOS 上通常为 Option 加拖动),再按 Command+C;inline 模式也使用原生选择来复制。Ctrl+C 仍用于中断或退出。
|
|
41
|
+
|
|
42
|
+
按 `Ctrl+V` 可把系统剪贴板中的图片附到草稿上。输入框把每个已持有的图片显示为 `[Image #1]` 标记,其行为与键入文本一致:移动或删除标记即移动或删除对应图片。该粘贴直接读取剪贴板,因此终端自身的文本粘贴仍保留原有按键。读取进行期间页脚会显示 `pasting image…`,因此较慢的平台读取器不会让界面看起来冻结。
|
|
43
|
+
|
|
44
|
+
按 `Shift+Tab` 可切换当前会话 Agent 的 plan 模式。plan 模式开启时,页脚会在生命周期状态旁显示 `plan` 标记,空输入框也会列出该按键。turn 运行期间的切换从下一个 step 生效,与 `/plan` 完全一致;未挂载 plan 模式的部署会明确报告,而不会改变会话。Agent 完成规划后通过 `exit_plan_mode` 提交计划,review 会把该计划以 markdown 的形式显示在「批准/继续规划」选项之上,并占据固定页脚与输入框之上的全部行,使长计划在任意终端尺寸下都可读,可用方向键、PageUp/PageDown 与滚轮滚动,因为 plan 模式策略要求计划经该工具提交,而不是作为普通回复粘贴。选择「继续规划」会保持 plan 模式开启并把 turn 交还给用户,因此 Agent 会等待你的下一条消息,而不是立即修改计划。
|
|
45
|
+
|
|
46
|
+
| 按键 | 作用 |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `Enter` | 提交提示;若 turn 正在运行则作为 steer |
|
|
49
|
+
| `Esc` | 中断正在运行的 turn 及会话的存活 subagent |
|
|
50
|
+
| `Shift+Tab` | 切换 plan 模式 |
|
|
51
|
+
| `Ctrl+V` | 把系统剪贴板中的图片附到草稿(Windows 与 WSL 上为 `Alt+V`,因为那里的终端占用了 Ctrl+V) |
|
|
52
|
+
| `Ctrl+C` | 取消正在运行的 turn 及会话的存活 subagent,或停止本地 `!` 命令;两者都未运行时退出 |
|
|
53
|
+
| `Ctrl+D` | 退出 |
|
|
54
|
+
| `PageUp` / `PageDown` | 滚动对话记录,或问题详情溢出时的详情 |
|
|
55
|
+
| `↑` / `↓` | 问题详情溢出时滚动它;否则移动其选择器 |
|
|
56
|
+
| `←` / `→` | 当问题详情占用方向键时移动选择器的选中项 |
|
|
57
|
+
| `Space` | 在多选问题中勾选或取消勾选一个选项 |
|
|
58
|
+
| `Ctrl+L` | 全量重绘 |
|
|
59
|
+
|
|
60
|
+
退出时——无运行时按 Ctrl+C、按 Ctrl+D,或执行 `/quit`——应用会恢复终端,并把会话 id 以 `dsh --resume <session-id>` 命令的形式打印出来,以便之后的调用继续同一段对话。恢复时——启动时执行该命令,或在输入框执行 `/resume`——应用会把已存储的对话恢复到对话记录中,然后才接受下一条提示,因此此前的提示、回复与工具行都会重新出现在屏幕上,而不只是留在模型上下文中。`/resume` 选择器与 `/sessions` 用会话标题——第一条人类消息的开头词语、生成的标题或显式重命名——作为标识,并在其旁显示 id、开始时间与工作区。仅当部署挂载了会话持久化后端时才会打印该提示;没有后端时 `--resume` 无可打开的对象。
|
|
61
|
+
|
|
62
|
+
### 查看子代理
|
|
63
|
+
|
|
64
|
+
本地 subagent 启动后,TUI 会先发布回环连接记录,然后以普通提示显示查看命令:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
· watch: dsh watch <session-id>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
旧写法 `dsh tui --watch <session-id>` 仍可使用。
|
|
71
|
+
|
|
72
|
+
`/subagents` 通过子代理服务打开一个固定面板,列出当前会话委派的所有后代子代理,并给出各自的执行状态与查看命令。该读取既覆盖常驻子代理,也覆盖已存储的子代理;无法读取的子代理目录会显示为诊断行。面板在目录事实或生命周期边沿到达时刷新,因此在面板打开期间启动或结束的子代理会自动加入或离开列表;列表不写入对话记录,主 Agent 在下方继续输出也不会移动面板。列表超出面板高度时可用 Up/Down 或 PageUp/PageDown 滚动;按 Enter、Esc 或 Ctrl+C 关闭面板,不会取消主 Agent。
|
|
73
|
+
|
|
74
|
+
在另一个终端执行该命令即可查看子会话。查看端是只读的:没有输入框,不发送提示、不审批工具,也不取消子代理。它显示已提交的对话记录,以及正在生成的文字、思考内容、工具调用与审批审计行,因此响应进行到一半时打开也能看到已经生成的部分。PageUp/PageDown、鼠标滚轮、选择复制、配色与配置的屏幕模式都与交互界面一致。Esc、Ctrl+C、Ctrl+D 仅退出查看。单次模型响应结束后连接仍保持打开。子代理本次执行结束后保留画面,连接仍在时继续显示该子会话的后续执行;连接中途断开时保留最后的可信画面并提示断开,不自动重连。
|
|
75
|
+
|
|
76
|
+
对话记录与底部区域之间留一行空白,并用横线分隔。底部显示子会话 ID、执行状态和连接状态:`online`、`history` 或 `disconnected`。第二行根据子会话已提交的事件显示可用的上下文占用、生成速度、累计计费 token 和缓存命中率,在线和历史模式均支持;断线后保留最后的统计值。没有在线记录,或记录对应的服务已不存在时,查看端通过持久化读句柄打开已存储日志并标记 `history`;它不取得写锁、不恢复 Agent,也不发起模型请求。错误令牌、不兼容协议或损坏记录会明确报错,而不是静默转成历史模式。两个终端必须使用同一用户、同一 `$DSH_HOME`,并具有兼容的 TUI 持久化配置。
|
|
77
|
+
|
|
78
|
+
### 命令
|
|
79
|
+
|
|
80
|
+
以已知 `/command` 开头的行会执行该命令,而不会到达模型。其他斜杠文本原样传给 Agent:`/ponytail full` 通过共享技能加载器调用已安装且允许用户调用的技能,未知技能名则保留为普通文本。应用命令和已注册命令优先于同名技能。下列会话命令由本应用自己实现;其他已注册命令——`/compact`、`/goal`、`/plan` 以及部署提供的命令——都从命令注册表发现,并针对当前 Agent 派发。
|
|
81
|
+
|
|
82
|
+
| 命令 | 作用 |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `/help` | 列出本应用的命令与全部已注册命令 |
|
|
85
|
+
| `/new` | 落盘当前会话,新建一个,然后关闭原会话 |
|
|
86
|
+
| `/sessions` | 按标题与时间倒序列出已存储会话 |
|
|
87
|
+
| `/subagents` | 打开固定面板,列出已委派的子代理及其执行状态与查看命令 |
|
|
88
|
+
| `/resume [id]` | 按 id 恢复已存储会话,或从选择器中挑选;指定当前会话时保持现状 |
|
|
89
|
+
| `/model [provider/model]` | 从实时目录中选择模型,或直接切换 |
|
|
90
|
+
| `/effort [id]` | 选择路由模型声明的推理强度,或直接切换 |
|
|
91
|
+
| `/questions` | 回答前台等待窗口已关闭的问题;从仍在等待的调用中选择 |
|
|
92
|
+
| `/quit` | 退出 |
|
|
93
|
+
|
|
94
|
+
### 本地 shell 命令
|
|
95
|
+
|
|
96
|
+
以 `!` 开头的行在本机执行,不会到达 Agent:`!git status` 通过已挂载的 shell 执行器运行,沙箱策略与 Agent 的 `bash` 工具相同,但不弹审批。每行都是一个全新的 shell,工作目录为会话当前目录,因此 `cd` 与导出的变量不会带到下一行。命令按执行器上限(默认 600 秒)设限,超过每流上限的输出只保留尾部,并在行内给出 spill 文件路径。运行期间页脚显示 `● shell`。
|
|
97
|
+
|
|
98
|
+
该行仅存在于本地:命令与输出不会写入会话记录,也不会发送给模型,并会在 `/new`、`/resume` 与退出时消失,因此后续 turn 无法引用它们。`\!ls` 会把字面文本 `!ls` 发送给 Agent。
|
|
99
|
+
|
|
100
|
+
同一时刻只运行一条本地命令:它运行期间,输入框会拒绝新的提示与 `!` 行并保留草稿;turn 或压缩运行期间,`!` 行同样被拒绝。Ctrl+C 会停止正在运行的命令,保留已经产出的输出,且不影响 turn;收尾行报告 `[exit code: N]`、`[cancelled]` 或 `[timed out after 600s]`,沙箱拒绝会指出拦截它的模式。本地命令运行期间斜杠命令仍然可用。未挂载 shell 执行器的部署会拒绝 `!` 行并说明原因。
|
|
101
|
+
|
|
102
|
+
### 设置
|
|
103
|
+
|
|
104
|
+
本应用仅有的部署设置决定绘制位置与调色板:
|
|
105
|
+
|
|
106
|
+
| 字段 | 默认值 | 含义 |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| `screen` | `'alternate'` | `'alternate'` 在备用屏幕中绘制对话记录并使用应用自己的滚动窗口;`'inline'` 绘制到普通屏幕,把历史留给终端自身的回滚缓冲。 |
|
|
109
|
+
| `colorScheme` | `'auto'` | 调色板选择。`'auto'` 读取终端导出的背景信号,读不到时保持深色;`'dark'` 与 `'light'` 固定调色板。 |
|
|
110
|
+
|
|
111
|
+
生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-tui-app)是全部可接受字段及其 JSDoc 的完整来源。bundle 补丁中的 `DSH_TUI_SCREEN` 与 `DSH_TUI_COLOR_SCHEME` 提供这些默认值。
|
|
112
|
+
|
|
113
|
+
-----
|
|
114
|
+
|
|
115
|
+
<a id="understand-the-implementation"></a>
|
|
116
|
+
## 理解实现
|
|
117
|
+
|
|
118
|
+
<details>
|
|
119
|
+
<summary>实现细节——点击展开</summary>
|
|
120
|
+
|
|
121
|
+
应用拥有一个 `TuiSession` 与一个终端界面。它先等待组合完成(`ctx.get('loader')?.await()`),以确保 Agent 的 scoped 工具与适配器已挂载,再通过核心注册表创建或恢复 Agent,然后订阅持久的 `session/event` 日志、实时的 `agent/assistant-stream` 流、`agent/status`,以及用于报告委派工作、供页脚与取消按键跟踪的 `subagent/start` / `subagent/end`。恢复会话中较早的事件早于这些订阅,因此应用会通过 [`TuiSession.history()`](src/session.ts) 从持久化后端读回它们——读到 Session 的首个实时序列号为止,避免恢复后追加的事件被折叠两次——并在接受输入前将其折叠进对话记录;若读取期间有新事件到达,会先缓冲,待已存储行折叠完后再应用,从而保持日志顺序。
|
|
122
|
+
|
|
123
|
+
渲染器拥有终端期间,已挂载插件写入 console 的内容会被捕获并作为普通通知行追加。直接写入会落在光标所在行,而差分渲染器在该行自身内容变化前不会重绘它。
|
|
124
|
+
|
|
125
|
+
### 渲染
|
|
126
|
+
|
|
127
|
+
[`src/transcript.ts`](src/transcript.ts) 把这些事件折叠为有序行——提示、assistant 消息、保留的 reasoning、带最终结果的工具调用、审批决定与应用通知——并通过修订计数器使其渲染行失效;而视图只重新渲染该修订真正改动的行:它按行身份与视口宽度缓存每一行的渲染结果,因此一次流式增量只渲染实时行,而不是整份对话记录。提示会先渲染其图片标记、再渲染文本,每个标记按内容顺序对应一张已附图片,因为终端没有缩略图,而持久内容块才是所附内容的记录。消息行带有角色侧栏:提示以 `›` 开头,其后的每一行都在提示色的 `│` 竖线下续行;assistant 消息以 `◆` 开头,并在自身颜色的 `│` 竖线下续行,因此即使没有颜色,两种角色也能区分。注入的上下文是模型输入而非对话,因此只有生产者声明了一行式 `notice` 形式时才产生行:工作区指令、技能目录与运行时上下文消息只留在会话日志中,而模型切换、plan 模式变化与 goal 以应用通知呈现。两种屏幕策略下页脚都位于输入框之下,共两行:第一行是工作区与 Agent 状态,其右侧右对齐路由模型名及其推理档位;第二行是「下一次请求相对路由模型容量的占用」,其右下角是整段会话的 token 数字。一旦本会话委派过子代理,占用之后会跟随一个 `● subagents (运行中/总数)` 标记——`●` 表示仍有子代理激活在运行,`○` 表示已记录的子代理全部结束——它读取的子代理目录与 `/subagents` 列出的是同一份;尚未得知占用时该标记单独显示。这些数字分别是解码吞吐量(每秒 token 数)、计费的提示词加输出 token 总量,以及计费提示词 token 中由缓存读取提供的占比;每项在有数据前都不显示,速率是 Web 统计条所报的整段会话平均值而非实时瞬时值,终端宽度不足时这一组会先于占用被丢弃。plan 模式开启时会在 Agent 状态旁显示 `plan` 标记,由 `Shift+Tab` 通过 `ctx.planMode` 切换。快捷键提示是空输入框的 placeholder 而非页脚的一行:应用包装了输入框,使其内容行在用户键入第一个字符前显示这些提示,因为编辑器组件本身不渲染 placeholder。占用与容量取自已挂载 `ctx.tokenMeter` 的 `contextPressure` 投影,总量取自其 `tokenUsage` 投影,吞吐量取自本 bundle 插入的 `sessionStats` 投影;`(auto)` 标记取自 `ctx.compaction.autoCompactionEnabled`;只有当部署注册了多个 provider 时,模型标签才会带上 provider。[`src/views.ts`](src/views.ts) 使用 [`@earendil-works/pi-tui`](https://www.npmjs.com/package/@earendil-works/pi-tui) 组件把行转为终端行,并把每一行截断到视口宽度,因为渲染器会把超宽行视为组件缺陷;工具标题还会被压成单行,因为一行只拥有一个终端行:多行参数否则会打印出自身的换行,使该行越出转录区压到固定页脚上。选择器或提示以普通输出而非对话框的形式呈现——一行普通标题、一行空行和主体——在备用屏幕布局中紧贴输入框上方,并把每一行填充到视口宽度,因此转录内容不会从旁边透出。滚动由备用屏幕渲染器负责:对话记录是它的主滚动视图,因此 PageUp/PageDown 与滚轮移动对话记录,而页脚与输入框保持固定。停止渲染器时会把整份对话记录写回普通屏幕,因此退出后输出仍可选择。`stop()` 随后落盘会话,并把 resume 命令写到 stderr:写在恢复屏幕之后,因为此时界面已不再拥有屏幕;且仅在挂载了持久化后端时写出。工具声明的 Host 呈现决定其行:折叠事件时通过 `ctx.tools` 解析出的调用与结果视图,会分别渲染为 `card: 'diff'` 的统一 diff(含工具自己的标题与 `+新增 -删除` 计数)、`card: 'terminal'` 的命令及其描述、工作目录、输出与退出状态标记、`card: 'read'` 的带行号窗口、`card: 'search'` 的分组匹配或路径列表及其截断信号、`card: 'web'` 的来源引用或抓取摘要,以及 `card: 'generic'` 的标题与关键输入。卡片主体超过限定行数后折叠;已结算的结果视图替换待定卡片,结果视图缺省时保留调用时卡片,失败的变更则回退为普通的名称加摘要行并显示错误文本。工具行的普通结果主体以调色板的工具结果灰渲染,而非 assistant 主体的终端前景色。调色板跟随终端背景:`colorScheme` 固定深色或浅色,`auto` 读取终端的 `COLORFGBG` 背景信号,读不到时默认深色。 切换会话时会释放对话记录视图缓存的行和 Markdown 组件。 模型的思考内容在流式输出结束后仍保留显示,恢复会话时也会从会话日志中还原。
|
|
128
|
+
|
|
129
|
+
### 交互接缝
|
|
130
|
+
|
|
131
|
+
应用依赖审批服务记录持久的工具授权,并应答 Agent 会暂停等待的两个接缝。`ctx.on('approval/request', …)` 为本应用自己的 Agent 及所有存活子代理后代提供「允许一次/始终允许/拒绝」,并在标题中标出子代理的 descriptor 标签;其他 Agent 的请求会被委托,因此同时挂载其他界面的组合仍由自己的应答者负责。「始终允许」会在应用自己的根 Agent 上记录按工具的授权,因此该选择覆盖本会话及所有存活子代理;后续同工具询问不再弹出提示,审计行显示 `allowed always`,恢复会话后授权仍然有效。提示会串行处理,并发请求按到达顺序排队;被跳过或取消的提示解析为 `cancelled`,审批服务本就把它当作 fail-closed。每次询问与决定还会折叠为一行持久的 `approval · <工具> — <结果>` 对话记录:本应用自己的决定出现在主转录中,子代理的决定出现在其 watch 查看器中。`ctx.on('user-questions/request', …)` 把问题选项渲染为选择器——当问题设置 `multiSelect` 时渲染为可勾选列表,Space 切换当前高亮项的勾选状态,Enter 按列表顺序提交已勾选的标签,未勾选任何项时不作任何操作——或在问题未声明选项时渲染自由文本输入。两类选项都会在末尾追加一行 `Other…`,选中它则改为打开该自由文本输入:单选时输入内容会替换原选择,多选时勾选的标签会与输入内容一并保留。Escape 以空选择跳过当前问题,让批次继续推进;Ctrl+C 则以 `ASK_ABORTED` 让提问的工具失败。plan review 选择「继续规划」——或以任何其他方式未作答——会以 `ASK_CANCELLED` 失败,plan 模式将其理解为用户收回了该 turn;而从 `Other…` 输入的反馈则会送达模型。问题的 `detail` 会以 markdown 渲染在该控件之上,视口大小取决于控件未占用的行数;带 detail 的提示会占据固定页脚与输入框之上的全部行,而没有 detail 的提示仍受选择器自身的高度上限约束,短列表因此不会占满高终端。详情溢出时,Up/Down、PageUp/PageDown 与滚轮滚动它,Left/Right 移动选择器,因此 plan review 会显示模型提交的计划,而不只是批准选项。选择器本身不安装鼠标处理器,因此误点击不会移动高亮或确认选项,滚轮会翻动提示背后的执行历史;只有溢出的详情才把滚轮留给自己的视口。同一时刻只有一个提示占用键盘。问题标题最多换行三行——问题上方是带批次位置与 header 的暗色标签——限时窗口的倒计时固定在其首行。
|
|
132
|
+
|
|
133
|
+
`ask_user_question` 以定时形式运行。应用从服务认领该调用的前台等待窗口,并在标题中倒数剩余秒数;用户未在窗口内回答——包括主动取消——会把该工具结算为 pending 而非失败,因此问题仍可回答。`/questions` 列出本会话仍欠答案的每个调用,用与实时提问相同的提示逐题呈现,并把完成的答案批次交给服务;服务将其作为用户消息 steer 进 Agent,对话记录会把该消息显示为用户的回复。已结算调用的对话记录行会逐题列出问题与用户选择的标签(未作答则标记为跳过),仍在等待的定时调用则标明等待中,而不是显示结果 JSON。
|
|
134
|
+
|
|
135
|
+
### 剪贴板图片
|
|
136
|
+
|
|
137
|
+
Ctrl+V 通过平台自带的读取器读取系统剪贴板([`src/clipboard.ts`](src/clipboard.ts)):macOS 经 `osascript` 的 JavaScript 自动化运行时直接读取 `NSPasteboard`,Windows 与 WSL 用 PowerShell,Wayland 用 `wl-paste`,X11 用 `xclip`。读取器把字节落到临时文件,读取后即删除;它声明的媒体类型只是声明——附件服务会对照解码后的字节校验。页脚从按下按键到字节就绪期间显示 `pasting image…`,因为平台读取器是子进程,大尺寸剪贴板图片可能要将近一秒才能落地。字节以标记为键留在内存中。提交时若引用了标记,应用先解析精确路由模型声明的输入模态,再通过 `ctx.attachments.admitPromptContent(…)` 准入该批次,并把提示文本与其后已准入的图片块作为一条用户消息交给 Agent。被拒绝时——没有附件存储、模型排除图片输入或准入失败——应用恢复草稿,而不是发送残缺的提示。
|
|
138
|
+
|
|
139
|
+
### 基于 base 的补丁面
|
|
140
|
+
|
|
141
|
+
补丁叠加在 `dsh-base` 之上,不添加任何 Host 或浏览器行。它重述其他界面设置的编码 persona,插入启动 provider 与应用本身,并把 base 的面向模型的行保留在 host 平面:本界面是单会话的,其 Agent 进程级组合这些行,而非按会话组合。启动 provider([`src/startup.ts`](src/startup.ts))注入 `ctx.cmdlineArgs`([`dsh-cmdline`](../../boot/cmdline/README.zh.md)),解析 `--resume`、`--provider`、`--model` 与 `--watch`,并提供 `tuiStartup`;应用行注入该服务,因此 `--help` 与被拒绝的调用完全不会挂载终端界面。`--watch` 与会话参数互斥,并启动 [`src/watch/viewer.ts`](src/watch/viewer.ts) 而不是交互应用。首个本地 subagent 会启动由 [`src/watch/server.ts`](src/watch/server.ts) 拥有的进程内 Node HTTP 监听器;它只服务该 TUI 的本地 subagent 树,并随 TUI 退出。补丁还以 `mode: timed` 形式挂载 `tool-ask-user`,因为 base 层提供提问服务但不提供提问工具。补丁把 `tool-fs.applyPatch` 设为 `true`,在 `edit` 旁加入 `apply_patch`;`dsh-base` 为其他界面保持该工具关闭。补丁还把 `session-log-deepseek` 设为 `enabled: false`,因此本 fork 让 Session 日志留在本机,而不会向官方 DeepSeek 请求附带上游的 `dsh_session_log` 后缀;需要该后缀的部署可通过 `--patch` 覆盖层或 profile 自带的 `cordis.patch.yml` 重新启用该行。补丁还把 `subagent.delegatedApproval` 设为 `inherit`,因此本地子代理继承父会话当前生效的审批策略,应用的子代理应答者可以决定其请求;需要无人值守委派的部署可恢复包默认值 `never`。
|
|
142
|
+
|
|
143
|
+
### 源码索引
|
|
144
|
+
|
|
145
|
+
| 文件 | 职责 |
|
|
146
|
+
|---|---|
|
|
147
|
+
| [`src/index.ts`](src/index.ts) | `tui-app` 插件:启动器事实、启动与失败退出 |
|
|
148
|
+
| [`src/startup.ts`](src/startup.ts) | `tui-startup` provider:参数族与 `--help` |
|
|
149
|
+
| [`src/app.ts`](src/app.ts) | 界面构建、事件接线、输入路由与收尾 |
|
|
150
|
+
| [`src/diagnostics.ts`](src/diagnostics.ts) | 界面拥有终端期间的 console 捕获 |
|
|
151
|
+
| [`src/session.ts`](src/session.ts) | Agent 创建/恢复、历史读取、提示提交、路由切换 |
|
|
152
|
+
| [`src/transcript.ts`](src/transcript.ts) | 把事件折叠为可渲染行 |
|
|
153
|
+
| [`src/diff.ts`](src/diff.ts) | 纯文件 diff 行模型 |
|
|
154
|
+
| [`src/tool-view.ts`](src/tool-view.ts) | Host 工具呈现桥接层 |
|
|
155
|
+
| [`src/images.ts`](src/images.ts) | 输入框图片标记与提交时的折叠 |
|
|
156
|
+
| [`src/clipboard.ts`](src/clipboard.ts) | 各平台剪贴板图片读取器与其暂存文件 |
|
|
157
|
+
| [`src/views.ts`](src/views.ts) | 对话记录、状态栏与模态面板组件 |
|
|
158
|
+
| [`src/commands.ts`](src/commands.ts) | 斜杠命令目录、派发与模型目录 |
|
|
159
|
+
| [`src/interactions.ts`](src/interactions.ts) | 审批与用户提问应答者 |
|
|
160
|
+
| [`src/ansi.ts`](src/ansi.ts) | 语义样式与组件主题 |
|
|
161
|
+
| [`src/watch/protocol.ts`](src/watch/protocol.ts) | NDJSON 线协议与连接记录校验 |
|
|
162
|
+
| [`src/watch/record.ts`](src/watch/record.ts) | 私有运行时记录的发布与清理 |
|
|
163
|
+
| [`src/watch/server.ts`](src/watch/server.ts) | 回环流服务、子会话状态与有界客户端队列 |
|
|
164
|
+
| [`src/watch/client.ts`](src/watch/client.ts) | 带令牌的流读取与线数据校验 |
|
|
165
|
+
| [`src/watch/history.ts`](src/watch/history.ts) | 查看端的历史读句柄访问 |
|
|
166
|
+
| [`src/watch/viewer.ts`](src/watch/viewer.ts) | 只读查看界面及其页脚 |
|
|
167
|
+
| [`cordis.patch.yml`](cordis.patch.yml) | 基于 `dsh-base` 的终端补丁 |
|
|
168
|
+
| — | 不发布运行时 invariant 伴随模块;应用不注册任何注册表,查看记录是可丢弃的运行时数据而非 Session 状态。 |
|
|
169
|
+
| [`tests/tui-app.spec.ts`](tests/tui-app.spec.ts) | 输入路由、剪贴板粘贴、渲染、模态应答与退出 |
|
|
170
|
+
| [`tests/diagnostics.spec.ts`](tests/diagnostics.spec.ts) | console 格式化、纯文本净化与方法恢复 |
|
|
171
|
+
| [`tests/transcript.spec.ts`](tests/transcript.spec.ts) | 事件折叠、页脚、placeholder 输入框与行宽边界 |
|
|
172
|
+
| [`tests/clipboard.spec.ts`](tests/clipboard.spec.ts) | 各平台读取器路径与暂存文件适配器 |
|
|
173
|
+
| [`tests/images.spec.ts`](tests/images.spec.ts) | 输入框标记解析 |
|
|
174
|
+
| [`tests/startup.spec.ts`](tests/startup.spec.ts) | 在真实 Loader 树上的命令行解析 |
|
|
175
|
+
| [`tests/watch.spec.ts`](tests/watch.spec.ts) | 记录发布、鉴权流、连续性校验、历史回退与查看界面 |
|
|
176
|
+
|
|
177
|
+
### invariant 归属
|
|
178
|
+
|
|
179
|
+
不发布 invariant 伴随模块:应用不贡献任何注册表,查看记录是带仅所有者可读令牌的可丢弃运行时数据,其可观察契约是终端界面,而该界面由测试通过替换 Terminal 驱动。
|
|
180
|
+
|
|
181
|
+
</details>
|
|
182
|
+
|
|
183
|
+
-----
|
|
184
|
+
|
|
185
|
+
<a id="further-exploration"></a>
|
|
186
|
+
## 进一步探索
|
|
187
|
+
|
|
188
|
+
以下页面可深入了解共享核心、同类界面与终端库。
|
|
189
|
+
|
|
190
|
+
- [Bundle 包地图](../README.zh.md)——构建在同一核心之上的各个界面。
|
|
191
|
+
- [dsh-base](../base/README.zh.md)——本界面运行的共享核心。
|
|
192
|
+
- [dsh-headless](../headless/README.zh.md)——面向脚本与 CI 的一次性同类界面。
|
|
193
|
+
- [dsh-web-app](../web-app/README.zh.md)——面向多轮工作的浏览器同类界面。
|
|
194
|
+
- [dsh-cmdline](../../boot/cmdline/README.zh.md)——启动器如何把命令行交给应用。
|
|
195
|
+
- [交互式委派审批](../../../.agents/notes/implemented/architecture/2026-10-22-interactive-delegated-approvals.zh.md)——本界面为何应答存活后代,以及策略如何写入。
|
|
196
|
+
- [生成的配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-tui-app)——全部可接受配置字段及其来源声明。
|
|
197
|
+
|
|
198
|
+
-----
|
|
199
|
+
|
|
200
|
+
<a id="model-experience"></a>
|
|
201
|
+
## 模型体验
|
|
202
|
+
|
|
203
|
+
### 交互式编码会话
|
|
204
|
+
|
|
205
|
+
#### 模型看到什么
|
|
206
|
+
|
|
207
|
+
组合启用了 `apply_patch`,因此每次请求都会在 base 文件工具旁携带该工具的 schema 与其系统提示指导。应用把输入的提示作为普通用户消息提交,并把模型自身的输出渲染回来;`/help`、`/quit`、`/new`、`/sessions`、`/resume` 与 `/model` 永远不会到达模型。切换模型会追加共享的模型选择通知,与其他界面完全一致。`Shift+Tab` 通过与 `/plan` 相同的 service 进入或退出 plan 模式,因此模型看到的是与手输命令相同的 plan 策略段落与用户切换通知。剪贴板粘贴会像其他界面的上传一样,把已准入图片作为持久的图片引用加入同一条消息;输入框中的 `[Image N]` 标记只是输入框文本,永远不会到达模型。
|
|
208
|
+
|
|
209
|
+
#### Token 影响
|
|
210
|
+
|
|
211
|
+
提示与回复承担其常规 token 成本。`apply_patch` 的 schema 与指导会计入每次请求的输入 token。终端渲染的任何内容——页脚、工具行折叠或滚动窗口——都不会增加请求或 token;占用、吞吐量与 token 总量数字读取自本地测量投影,而非额外的 provider 调用。切换 plan 模式本身不新增请求,只改变下一次请求的 plan 策略段落。当路由模型声明了图片计价时,所附图片按该计价计入,因此页脚反映的是模型实际会收到的请求。
|
|
212
|
+
|
|
213
|
+
#### KV Cache 影响
|
|
214
|
+
|
|
215
|
+
profile 额外加入的 `apply_patch` schema 与指导属于稳定的请求前缀,不随 turn 变化;除此之外应用不向请求前缀添加内容,只把用户输入的提示送入已组合的树中。
|
|
216
|
+
|
|
217
|
+
## 已知限制与延期工作
|
|
218
|
+
|
|
219
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
这些限制说明终端界面不做什么。它们是本 bundle 的当前约束,而不是浏览器界面的待办列表。
|
|
223
|
+
|
|
224
|
+
- **每次调用一个 Agent**——应用只拥有一个会话;切换到其他 Agent 意味着通过 `/new` 或 `/resume` 先打开替代会话、再关闭当前会话,因此打开失败时当前会话仍然可用。
|
|
225
|
+
- **「始终允许」是应用级且限于会话的**——在任何提示上选择它(包括子代理的提示)都会在根 Agent 上记录授权,并覆盖本会话剩余时间内的所有存活子代理;本界面没有撤销控制。
|
|
226
|
+
- **子代理审批需要存活的父链**——应用只应答自己的 Agent 与存活后代;父链已不存在,或 profile 固定 `delegatedApproval: never` 的子代理不会弹出终端提示。
|
|
227
|
+
- **「继续规划」会等待消息**——终端 review 没有自由文本反馈字段,因此选择「继续规划」会关闭 review 并把 turn 交还给用户,同时保持 plan 模式开启;调整内容就是用户的下一条提示,而不是随 review 带回的答案。
|
|
228
|
+
- **提示高度在打开时固定**——面板只在打开时按终端尺寸计算一次,因此在 review 期间调整终端大小不会改变它;需要新高度时请关闭并重新打开该提示。
|
|
229
|
+
- **图片只来自剪贴板**——输入框只附加从系统剪贴板读到的 PNG、JPEG、WebP 与 GIF 字节:macOS 经 `osascript` 读取 `NSPasteboard`,Windows 与 WSL 用 PowerShell,Wayland 用 `wl-paste`,X11 用 `xclip`。缺少这些读取器的主机会把粘贴报告为空剪贴板;没有文件选择器、拖放或非图片附件。
|
|
230
|
+
- **迟到回复是后续消息**——定时工具已返回其 pending 结果,因此 `/questions` 把答案作为新的用户消息 steer 进 Agent;已记录的调用不会被恢复。
|
|
231
|
+
- **工具输出会被折叠**——工具结果只显示前若干行加剩余行数;完整输出留在会话日志中,而不在屏幕上。
|
|
232
|
+
- **卡片以文本渲染**——每种 Host 卡片都以终端行呈现:读取窗口不带语法高亮,generic 卡片的 `kind` 图标与 `locations` 跟随不会绘制,终端卡片回放的是已捕获输出而非实时会话。
|
|
233
|
+
- **占用是估算值**——页脚百分比锚定最近一次 provider 报告的提示规模,并对表层此后的增减做启发式重新计价;它是给用户看的参考,不是计费或准入依据。
|
|
234
|
+
- **退出由启动器拥有**——与所有界面一样,应用只能通过 `dsh` profile 启动,因为只有启动器提供有界退出请求。
|
|
235
|
+
- **没有录制会话快照**——无密钥快照框架通过 stdio 驱动随附 profile,而本界面拥有一个终端;它的验收是包测试加一次伪终端运行,而不是快照夹具,因此终端布局的回归需要扩展界面测试,而不是重新录制快照。
|
|
236
|
+
- **查看功能仅限本机第一版**——只能查看 TUI 启动的本地 subagent,包括嵌套 subagent。Web、headless、SDK 与远程机器会话不在范围内;查看端不自动重连;写入端不转发工具内部 stdout。原进程崩溃前已生成但尚未提交到 Session 日志的 token 无法恢复;查看端只保留已经显示的内容并报告中断。
|
|
237
|
+
|
|
238
|
+
<a id="dev-note"></a>
|
|
239
|
+
### 开发备注
|
|
240
|
+
|
|
241
|
+
<details>
|
|
242
|
+
<summary>维护者的工作上下文——点击展开</summary>
|
|
243
|
+
|
|
244
|
+
无。
|
|
245
|
+
|
|
246
|
+
</details>
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# The dsh terminal application over dsh-base: one interactive TUI driving a
|
|
2
|
+
# single in-process Agent. It mounts no Host, HTTP server, Web runtime, or
|
|
3
|
+
# browser plugin, and it leaves the base's model-facing rows in the host plane
|
|
4
|
+
# because this surface is single-session (see dsh-base's README).
|
|
5
|
+
|
|
6
|
+
- id: system-prompt
|
|
7
|
+
config:
|
|
8
|
+
personaPrefix: >-
|
|
9
|
+
You are a coding agent powered by the {{model}} model.
|
|
10
|
+
|
|
11
|
+
# The terminal surface is where multi-file, multi-region edits are most useful:
|
|
12
|
+
# the transcript already renders diff cards for the tool. Base keeps it off.
|
|
13
|
+
- id: tool-fs
|
|
14
|
+
config:
|
|
15
|
+
applyPatch: true
|
|
16
|
+
|
|
17
|
+
# This fork keeps Session logs on the machine. The base row now contributes the
|
|
18
|
+
# `dsh_session_log` request field by default, which sends a Session-log suffix
|
|
19
|
+
# with official DeepSeek requests; patch it off here and re-enable per
|
|
20
|
+
# deployment with a `--patch` overlay or the profile's own `cordis.patch.yml`.
|
|
21
|
+
- id: session-log-deepseek
|
|
22
|
+
config:
|
|
23
|
+
enabled: false
|
|
24
|
+
|
|
25
|
+
# This surface answers approvals for its own Agent and every live descendant, so
|
|
26
|
+
# in-process children may ask the user instead of failing closed. A child whose
|
|
27
|
+
# parent runs under `never` still inherits the deterministic rejection.
|
|
28
|
+
- id: subagent
|
|
29
|
+
config:
|
|
30
|
+
delegatedApproval: inherit
|
|
31
|
+
|
|
32
|
+
- insert:
|
|
33
|
+
# The model-facing question tool in its timed form: the app owns a bounded
|
|
34
|
+
# countdown, and a window that closes unanswered returns the tool's pending
|
|
35
|
+
# result so `/questions` can still submit the answer later. The base layer
|
|
36
|
+
# mounts the question service but not this tool.
|
|
37
|
+
- id: tool-ask-user
|
|
38
|
+
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
39
|
+
config:
|
|
40
|
+
mode: timed
|
|
41
|
+
|
|
42
|
+
# Whole-log decode wall time and output tokens for the footer's throughput
|
|
43
|
+
# figure (the sessionStats projection key); the projection registry itself
|
|
44
|
+
# is a base-layer row.
|
|
45
|
+
- id: session-stats
|
|
46
|
+
name: '@deepseek-ai/dsh-session-stats'
|
|
47
|
+
|
|
48
|
+
# An ordinary provider for this app's parsed flags. Its plugin-level
|
|
49
|
+
# injection waits for cmdlineArgs; `--help` provides nothing, so no TUI is
|
|
50
|
+
# built for a help invocation.
|
|
51
|
+
- id: tui-startup
|
|
52
|
+
name: '@deepseek-ai/dsh-tui-app/startup'
|
|
53
|
+
|
|
54
|
+
# The interactive runner. It reads its invocation from the provider above
|
|
55
|
+
# and owns the terminal for the life of the process.
|
|
56
|
+
- id: tui-app
|
|
57
|
+
name: '@deepseek-ai/dsh-tui-app'
|
|
58
|
+
inject: [tuiStartup]
|
|
59
|
+
config:
|
|
60
|
+
screen: !!js process.env.DSH_TUI_SCREEN ?? 'alternate'
|
|
61
|
+
colorScheme: !!js process.env.DSH_TUI_COLOR_SCHEME ?? 'auto'
|
|
62
|
+
|
|
63
|
+
# The terminal app holds one Agent and the terminal for the life of the
|
|
64
|
+
# process, so its layers apply once at startup like the other applications that
|
|
65
|
+
# own work once they start.
|
|
66
|
+
- id: hmr
|
|
67
|
+
disabled: true
|