@xmoon76/dsh-pi-tui 0.3.4 → 0.3.5
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.en.md +648 -0
- package/README.md +557 -466
- package/dist/builtins.mjs +1 -13
- package/dist/extension/advanced.d.mts +1 -1
- package/dist/extension/unstable.d.mts +1 -1
- package/dist/extensions.d.mts +9 -6
- package/dist/extensions.mjs +1 -1
- package/dist/index.d.mts +2 -12
- package/dist/index.mjs +2 -2
- package/dist/{public-types-CZz5_arj.d.mts → public-types-_pe9ZH00.d.mts} +74 -8
- package/dist/{src-B1uP5j0d.mjs → src-B93JMbsv.mjs} +13497 -2012
- package/package.json +11 -6
- package/README.zh-CN.md +0 -469
- package/scripts/advanced-plugin-smoke.mjs +0 -314
- package/scripts/bench.mts +0 -302
- package/scripts/client-boundary-baseline.json +0 -82
- package/scripts/client-boundary-gate.mjs +0 -165
- package/scripts/e2e-session-lease.sh +0 -388
- package/scripts/examples-plugin-smoke.mjs +0 -320
- package/scripts/extension-fixture-smoke.mjs +0 -219
- package/scripts/naming-gate.mjs +0 -158
- package/scripts/phase4-plugin-smoke.mjs +0 -318
- package/scripts/pre-push-stages.mjs +0 -116
- package/scripts/preview-prompt.mts +0 -62
- package/scripts/preview-role-styles.mts +0 -97
- package/scripts/tarball-smoke.mjs +0 -393
- package/scripts/unstable-plugin-smoke.mjs +0 -300
- package/scripts/vim-plugin-smoke.mjs +0 -315
- /package/dist/{theme-registry-CjbtRmhd.d.mts → keybinding-registry-CjbtRmhd.d.mts} +0 -0
package/README.md
CHANGED
|
@@ -1,533 +1,624 @@
|
|
|
1
1
|
# dsh-pi-tui
|
|
2
2
|
|
|
3
|
-
English
|
|
3
|
+
[English](README.en.md) | 简体中文
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@xmoon76/dsh-pi-tui)
|
|
6
|
+
[](LICENSE)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
基于 Pi TUI 的 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 终端前端。
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
`dsh-pi-tui` 作为独立的 dsh bundle 安装到 profile 中,提供流式对话、工具调用、会话管理、Subagent、历史搜索、Shell、审批与设置等终端交互。模型、工具、Session、权限、Skills、Plan、Goal、Subagent 等运行时能力仍由 DeepSeek Harness 提供。
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
```sh
|
|
13
|
+
dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui
|
|
14
|
+
dsh --profile pi-tui
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
## 功能
|
|
20
|
+
|
|
21
|
+
### 对话与工具
|
|
22
|
+
|
|
23
|
+
* 流式 Markdown 输出
|
|
24
|
+
* Thinking 折叠与展开
|
|
25
|
+
* Tool Call 卡片及运行状态
|
|
26
|
+
* Tool / System 详情折叠
|
|
27
|
+
* Transcript 全文搜索
|
|
28
|
+
* 长会话历史折叠
|
|
29
|
+
* Context、Token、模型和运行状态显示
|
|
30
|
+
* Approval 与 `ask_user_question` 交互
|
|
31
|
+
* Plan Review
|
|
32
|
+
* Todo / Goal 状态展示
|
|
33
|
+
* 可读的终端窗口标题
|
|
34
|
+
* Compaction / prune 后不会出现重复的幽灵 Tool Card
|
|
35
|
+
|
|
36
|
+
`Ctrl+O` 控制工具和系统详情;在全屏 Focus 下它整体展开最近几个 Thought root,或全部收起。`Alt+T` 单独控制 Thinking。
|
|
37
|
+
|
|
38
|
+
### Focus Mode
|
|
39
|
+
|
|
40
|
+
`/focus` 可以把运行中的 Thinking、Tool Call 和中间回复聚合为一个实时更新的 Thought 区块。
|
|
41
|
+
|
|
42
|
+
需要查看过程时可以展开,关闭 Focus 后恢复普通 Transcript 展示。全屏 Focus 中可以按 Thought root 批量展开/收起,也可以单独点击卡片;切换或缩放时会保留 viewport。Focus 只影响界面投影,不修改 Session 中保存的事件。
|
|
43
|
+
|
|
44
|
+
### Session
|
|
45
|
+
|
|
46
|
+
支持 DSH 持久化 Session,包括:
|
|
47
|
+
|
|
48
|
+
* 新建和恢复 Session
|
|
49
|
+
* Session 切换
|
|
50
|
+
* 重命名
|
|
51
|
+
* Fork
|
|
52
|
+
* Rewind
|
|
53
|
+
* Session lineage
|
|
54
|
+
* Transcript 导出
|
|
55
|
+
|
|
56
|
+
使用:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
/sessions
|
|
60
|
+
/fork
|
|
61
|
+
/rewind
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
空闲且编辑器为空时也可以快速按两次 `Esc` 打开 Rewind。
|
|
65
|
+
|
|
66
|
+
Rewind 会从选中的历史 User Turn 创建新的 Child Session,并把对应 Prompt 放回编辑器。原 Session 不会被修改。
|
|
67
|
+
|
|
68
|
+
### 输入历史
|
|
69
|
+
|
|
70
|
+
`Ctrl+R` 打开输入历史搜索。
|
|
71
|
+
|
|
72
|
+
支持三个范围:
|
|
73
|
+
|
|
74
|
+
* Current session
|
|
75
|
+
* Current directory
|
|
76
|
+
* All directories
|
|
77
|
+
|
|
78
|
+
历史结果包含 Prompt、工作目录、时间和 Session 信息。选中历史后只恢复到编辑器,不会立即发送。
|
|
79
|
+
|
|
80
|
+
普通的 `↑` / `↓` 仍用于快速浏览最近输入。
|
|
81
|
+
|
|
82
|
+
### Subagent 与后台任务
|
|
83
|
+
|
|
84
|
+
`/tasks` 提供当前 Session 的任务浏览器。
|
|
85
|
+
|
|
86
|
+
Subagent 按完整 lineage 显示,包括嵌套创建的 descendant:
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
main
|
|
90
|
+
├─ subagent A
|
|
91
|
+
│ └─ subagent B
|
|
92
|
+
└─ subagent C
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
浏览器会区分:
|
|
15
96
|
|
|
16
|
-
|
|
97
|
+
* `continuable`
|
|
98
|
+
* `one-shot`
|
|
99
|
+
* running / inactive
|
|
100
|
+
* nested descendant
|
|
101
|
+
* 后台 Job
|
|
17
102
|
|
|
18
|
-
|
|
103
|
+
已经结束的 one-shot Subagent 仍可以打开并查看持久化 Transcript。
|
|
19
104
|
|
|
20
|
-
|
|
105
|
+
对于当前 Session 的直接 `continuable` Child,可以进入交互式 Viewer,并直接向该 Subagent 发送后续消息。Child 使用自己的 Transcript、Draft 和运行状态,不会修改主 Session 的输入。
|
|
21
106
|
|
|
22
|
-
|
|
23
|
-
operating manual). In one line: the **repository root is the published
|
|
24
|
-
`@xmoon76/dsh-pi-tui` bundle** (the only published package — its manifest
|
|
25
|
-
declares `dsh.bundle.patch` and the `exports` point at the built `dist/`),
|
|
26
|
-
and `packages/pi-tui/` is the vendored `@moonshot-ai/pi-tui` fork (rescoped
|
|
27
|
-
to `@xmoon76/pi-tui`, private, never published — its divergence ledger lives
|
|
28
|
-
in `packages/pi-tui/AGENTS.md`), bundled into the root package's build
|
|
29
|
-
output.
|
|
107
|
+
更深层的 nested Subagent 默认以只读方式查看。
|
|
30
108
|
|
|
31
|
-
|
|
109
|
+
### Shell
|
|
32
110
|
|
|
33
|
-
|
|
34
|
-
version `0.1.1-rc.1` or later on the same compat line (the TUI consumes the
|
|
35
|
-
split credential events and the `ctx.authorization` seam of that release).
|
|
36
|
-
- Node >= 22.19 (`^22.19.0 || >=24`, same range as dsh). Running from source
|
|
37
|
-
needs Node with native TypeScript support (>= 23.6) or the tsx ESM hook
|
|
38
|
-
(`node --import tsx/esm`, how dsh's own source launch works).
|
|
39
|
-
- [pnpm](https://pnpm.io) only when installing from source.
|
|
111
|
+
编辑器支持两种 Shell 模式:
|
|
40
112
|
|
|
41
|
-
|
|
113
|
+
```text
|
|
114
|
+
! git status
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
执行本地命令,并把输出提交到当前 Session。
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
!! git status
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
只在本地执行,输出不会进入模型上下文。
|
|
124
|
+
|
|
125
|
+
`!` / `!!` 是独立的编辑器模式,而不是普通文本前缀。进入 Shell 模式后 Prompt 和补全行为会同步切换。
|
|
126
|
+
|
|
127
|
+
Shell 卡片默认只显示有限的输出预览,`Ctrl+O` 可以展开完整保留内容——全屏 Focus 除外:那里 `Ctrl+O` 负责 Thought root 的整体开关,Shell 卡片保持折叠。
|
|
128
|
+
|
|
129
|
+
### 文件引用与图片
|
|
130
|
+
|
|
131
|
+
输入 `@` 可以搜索和补全工作区文件:
|
|
132
|
+
|
|
133
|
+
```text
|
|
134
|
+
@src/index.ts
|
|
135
|
+
@"path with spaces/file.ts"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
能够解析的相对路径会在提交时转换为明确的文件路径。
|
|
139
|
+
|
|
140
|
+
支持通过 `Ctrl+V` 添加剪贴板图片,并使用 DSH Attachment 能力保存到 Session。
|
|
141
|
+
|
|
142
|
+
### 模型与运行设置
|
|
143
|
+
|
|
144
|
+
TUI 使用 DSH 提供的模型和设置服务。
|
|
145
|
+
|
|
146
|
+
常用入口:
|
|
42
147
|
|
|
43
|
-
|
|
44
|
-
|
|
148
|
+
```text
|
|
149
|
+
/model
|
|
150
|
+
/settings
|
|
151
|
+
/login
|
|
152
|
+
/permission
|
|
153
|
+
/plan
|
|
154
|
+
/goal
|
|
155
|
+
/compact
|
|
156
|
+
/footer
|
|
157
|
+
/statusline
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
模型切换、Reasoning Effort、权限 Preset、Plan 和 Goal 都沿用 DSH 对应的运行时语义。
|
|
161
|
+
|
|
162
|
+
`/settings` 中的 `Icon style` 可切换 TUI 结构图标的风格:`Emoji`(默认,
|
|
163
|
+
彩色)、`Symbols`(紧凑的单格终端符号)、`Minimal`(隐藏装饰性图标,只
|
|
164
|
+
保留状态/交互标记);切换立即生效并持久化。
|
|
165
|
+
|
|
166
|
+
其他插件注册到 `ctx.commands` 的 Slash Command 也会被自动发现。
|
|
167
|
+
|
|
168
|
+
### Footer 自定义
|
|
169
|
+
|
|
170
|
+
状态行是一个**可组合表面**——常见场景无需插件或 shell。
|
|
171
|
+
|
|
172
|
+
`/settings` → Status line(或 `dsh-pi-tui` 设置文档中的 `footer` 键)
|
|
173
|
+
选择预设:
|
|
174
|
+
|
|
175
|
+
| 值 | 含义 |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `default`(旧名 `full`) | 经典两行 Footer(状态 + 统计) |
|
|
178
|
+
| `compact` | 仅状态行(隐藏统计行) |
|
|
179
|
+
| `custom` | 版本化 `footerLayout`(见下) |
|
|
180
|
+
| `command` | 用户配置的命令渲染状态表面(见下) |
|
|
181
|
+
|
|
182
|
+
前三个值可在 `/settings` 面板选择;`command` **不在面板中**——它只能
|
|
183
|
+
通过 USER 层设置文档(`footer: "command"` + `footerCommand`)启用,
|
|
184
|
+
`/settings` 的 Status line 行只有 `default / compact / custom` 三个选项。
|
|
185
|
+
|
|
186
|
+
`/footer` 是层级式交互配置器:先选行(Row Selector),再编辑该行的
|
|
187
|
+
条目——`↑/↓` 在整行条目间顺序移动(Left/Right 只是视觉分组),
|
|
188
|
+
`←/→` 左右换侧,`Space` 移除,`A` 打开可搜索的 Add Picker(按
|
|
189
|
+
label / id / 描述过滤,选中项下方显示描述),`M` 进入 Move Mode
|
|
190
|
+
排序,`Enter` 打开 Item Editor(Style 候选以条目的真实渲染作示例;
|
|
191
|
+
Tone 语义色;Advanced 编辑 prefix / suffix / importance 并可一键
|
|
192
|
+
Reset)。预览由真实 Footer 引擎合成,与 contextual help 一起固定在
|
|
193
|
+
面板顶部,任何终端尺寸下都不会随列表滚动消失。Row Selector 页 `S`
|
|
194
|
+
保存(持久化),`Esc` 逐页返回、在首页关闭且不影响当前生效布局。
|
|
195
|
+
无会话时也可使用。
|
|
196
|
+
|
|
197
|
+
Add Picker 的末尾还可以选择 `+ Create Custom Text`,创建用户自定义的静态文本条目。创建后可编辑文本、默认语义色、显示名称,也可以删除;条目定义只从 USER 层读取并持久化。定义 Tone 与布局中的放置 Tone 分开,条目仍可在 `/footer` 中显示/隐藏、移动和排序。
|
|
198
|
+
|
|
199
|
+
`footerLayout` 是嵌套设置对象(schemaVersion 1,1–2 行,左/右区域,
|
|
200
|
+
分隔符,有限 formatter,语义 tone,prefix/suffix,importance)。
|
|
201
|
+
`/footer` 配置器可交互地构建它;YAML 形状如下:
|
|
202
|
+
|
|
203
|
+
```yaml
|
|
204
|
+
footer: custom
|
|
205
|
+
footerLayout:
|
|
206
|
+
schemaVersion: 1
|
|
207
|
+
rows:
|
|
208
|
+
- left:
|
|
209
|
+
- id: agent-preset
|
|
210
|
+
format: compact
|
|
211
|
+
- id: model
|
|
212
|
+
- id: project
|
|
213
|
+
- id: context
|
|
214
|
+
format: full
|
|
215
|
+
- id: cache-hit
|
|
216
|
+
- id: token-usage
|
|
217
|
+
format: io
|
|
218
|
+
- id: performance
|
|
219
|
+
format: speed
|
|
220
|
+
- id: version
|
|
221
|
+
format: tui
|
|
222
|
+
right:
|
|
223
|
+
- id: focus-mode
|
|
224
|
+
separator:
|
|
225
|
+
text: " │ "
|
|
226
|
+
tone: textDim
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
内置 format 是有限集合,继续使用现有的 `format` 字段(不新增第二套
|
|
230
|
+
style schema):Model 为 `badge` / `plain` / `compact`;Permission preset
|
|
231
|
+
为 `badge` / `plain` / `compact`;Plan state 为 `badge` / `plain`;Working
|
|
232
|
+
directory 为 `short` / `basename` / `full`;Git branch 为 `plain` / `label`;
|
|
233
|
+
Context 为 `bar` / `percent` / `full`;Token usage 为 `io` / `total` /
|
|
234
|
+
`compact`;Cache hit 为 `full` / `compact`;Performance 为 `full` / `speed` /
|
|
235
|
+
`latency`(平均首 token 时间);Turns/steps 为 `both` / `turns` / `steps`;
|
|
236
|
+
Version 保留 `tui` / `dsh` / `both`。省略 `format` 时仍使用各条目的旧默认值。
|
|
237
|
+
|
|
238
|
+
内置条目 id:`agent-preset`、`model`、`reasoning`、
|
|
239
|
+
`permission-preset`、`sandbox-mode`、`approval-policy`、`plan-state`、
|
|
240
|
+
`focus-mode`、`focused-seat`、`view-scope`、`cwd`、`project`、
|
|
241
|
+
`git-branch`、`run-state`、`queue`、`tasks`、`agents`、`todo`、
|
|
242
|
+
`context`、`cache-hit`、`token-usage`、`performance`、`turns-steps`、
|
|
243
|
+
`stats-line`、`version`、`ext:*`(旧扩展段)。非法的 `footerLayout`
|
|
244
|
+
会警告一次并回退到默认布局——TUI 始终能启动。
|
|
245
|
+
|
|
246
|
+
`footer: command` 把状态表面交给用户配置的命令(Claude/Kimi 风格):
|
|
247
|
+
当前状态快照以 JSON 序列化到命令的 stdin(schemaVersion 1——不含
|
|
248
|
+
secret、凭据、提示词),命令的 stdout(经过净化:仅保留 SGR 颜色与
|
|
249
|
+
OSC 8 超链接)渲染状态表面。Host 的指令表面(如 Ctrl+C 退出提示)
|
|
250
|
+
始终叠加在最上层。
|
|
251
|
+
|
|
252
|
+
```yaml
|
|
253
|
+
footer: command
|
|
254
|
+
footerCommand:
|
|
255
|
+
schemaVersion: 1
|
|
256
|
+
command: "~/.config/dsh/statusline.sh"
|
|
257
|
+
timeoutMs: 300 # 默认 300,最大 1000
|
|
258
|
+
refreshIntervalMs: 1000 # 最小 1000
|
|
259
|
+
maxRows: 1 # 1..2
|
|
260
|
+
```
|
|
45
261
|
|
|
46
|
-
|
|
262
|
+
**安全:** 只有当命令位于你的设置文档的 USER 层时才会被执行。
|
|
263
|
+
仓库/项目提供的 `footerCommand` 永远不会被执行——命令模式被禁用并
|
|
264
|
+
回退到原生布局。命令按 `refreshIntervalMs` 周期刷新,每次最多输出 2 行;失败(空输出、非零退出、超时)自动回退到原生布局。
|
|
265
|
+
|
|
266
|
+
### 扩展 Footer 条目
|
|
267
|
+
|
|
268
|
+
插件可以通过 Stable 扩展 API(`@xmoon76/dsh-pi-tui/extensions`)贡献
|
|
269
|
+
**可配置的 Footer 条目**:在 `chrome.footer.item` 槽位注册一个
|
|
270
|
+
`FooterItemContribution`——包含 label 与纯数据 `segment`(带样式的
|
|
271
|
+
span;Host 会剥离任何终端控制序列,插件永远不能直接给终端上样式)。
|
|
272
|
+
用户可在 `/footer` 中像内置条目一样开关、排序、左右放置。注册前请
|
|
273
|
+
先 feature-detect `slot.chrome.footer.item` 能力(该能力在任何 surface
|
|
274
|
+
存在之前就已声明)。条目的配置身份是规范键 `ext:<owner>/<id>`,其中
|
|
275
|
+
owner 是插件的稳定名称——**跨 HMR 稳定**:引用已卸载插件条目的布局
|
|
276
|
+
保留引用,插件重载后自动恢复。npm scoped 插件名(`@scope/name`)合法:
|
|
277
|
+
其 `/` 在键中按 `encodeURIComponent` 百分号编码(`ext:%40scope%2Fname/<id>`);
|
|
278
|
+
id 本身不得包含 `/`。旧的 `chrome.footer.status` 槽位不变:
|
|
279
|
+
其 segment 聚合为单一的 `ext:*` 条目。完整作者指南:
|
|
280
|
+
[docs/extension-api.md](docs/extension-api.md)。
|
|
281
|
+
|
|
282
|
+
## 常用按键
|
|
283
|
+
|
|
284
|
+
| 按键 | 功能 |
|
|
285
|
+
| ------------- | ---------------------- |
|
|
286
|
+
| `Enter` | 提交输入 |
|
|
287
|
+
| `Ctrl+Enter` | Agent 忙碌时把草稿入队(与 Enter 相反) |
|
|
288
|
+
| `Shift+Enter` | 换行 |
|
|
289
|
+
| `Esc` | 取消当前交互 / 中断运行 |
|
|
290
|
+
| `Esc Esc` | 空闲时打开 Rewind |
|
|
291
|
+
| `Ctrl+C` | 中断 / 清空当前输入 |
|
|
292
|
+
| `Ctrl+D` | 退出 TUI(等同 `/exit`) |
|
|
293
|
+
| `Ctrl+S` | Steer:把队列消息和草稿一起发送到正在运行的回合 |
|
|
294
|
+
| `Ctrl+T` | 切换 Todo 面板 |
|
|
295
|
+
| `Ctrl+R` | 搜索输入历史 |
|
|
296
|
+
| `Ctrl+F` | 搜索 Transcript |
|
|
297
|
+
| `Ctrl+O` | 展开 / 折叠工具和系统详情;全屏 Focus 下整体切换 Thought root |
|
|
298
|
+
| `Alt+T` | 展开 / 折叠 Thinking |
|
|
299
|
+
| `Ctrl+G` | 使用 `$VISUAL`/`$EDITOR` 编辑输入 |
|
|
300
|
+
| `Ctrl+V` | 粘贴图片 |
|
|
301
|
+
| `Tab` | 补全斜杠命令与文件路径 |
|
|
302
|
+
| `@` | 文件补全 |
|
|
303
|
+
| `!` | 进入 Shell 模式 |
|
|
304
|
+
| `!!` | 进入 Local-only Shell 模式 |
|
|
305
|
+
|
|
306
|
+
完整按键和命令以 TUI 中的 `/help` 为准。表中的快捷键是默认值;用户自定义后,以 `/help` 和 `/keybindings` 显示的生效键位为准。
|
|
307
|
+
|
|
308
|
+
### 自定义快捷键
|
|
309
|
+
|
|
310
|
+
Host 快捷键是语义 action(`app.*`),通过 context-aware keymap 解析——
|
|
311
|
+
UI(页脚提示、`/help`、`/keybindings`)始终显示**生效**的按键,因此
|
|
312
|
+
改键后所有提示自动更新。在 `dsh-pi-tui` settings 命名空间中配置,
|
|
313
|
+
然后用 `/keybindings reload` 应用(显式 reload——改设置后执行 reload
|
|
314
|
+
即生效,无需重启):
|
|
315
|
+
|
|
316
|
+
```yaml
|
|
317
|
+
dsh-pi-tui:
|
|
318
|
+
keybindings:
|
|
319
|
+
app.input.steer: ctrl+s # 单个按键
|
|
320
|
+
app.permission.cycle: [shift+tab, ctrl+shift+p] # 多个按键
|
|
321
|
+
app.history.search: ctrl+r
|
|
322
|
+
app.transcript.toggleThinking: false # 禁用该 action 的按键
|
|
323
|
+
leader: ctrl+x # M6:leader 序列
|
|
324
|
+
bindings:
|
|
325
|
+
app.tasks.open: <leader>t
|
|
326
|
+
```
|
|
47
327
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
328
|
+
- 普通可打印键永远不能绑定到 Host action(会吞掉输入);坏配置只是
|
|
329
|
+
警告,绝不会导致启动失败(fail-soft)。
|
|
330
|
+
- 任何用户声明都会**替换**该 action 的内置默认键:`app.input.steer:
|
|
331
|
+
ctrl+x` 让 Ctrl+X steer、Ctrl+S 不再 steer;仅 leader 的
|
|
332
|
+
`app.todo.toggle: <leader>t` 让 Leader T 成为唯一切换触发(Ctrl+T
|
|
333
|
+
失效);`['ctrl+z', '<leader>s']` 同时保留两个用户触发;`false`
|
|
334
|
+
移除该 action 的全部触发。若 effective editor-owned submit key 会在
|
|
335
|
+
leader machine 之前消费某个 completion(例如 `<leader>enter`),该死序列会被
|
|
336
|
+
拒绝而不会被展示。
|
|
337
|
+
- `DSH_PI_TUI_SAFE_KEYBINDINGS=1` 忽略所有用户覆盖(仅使用内置默认)。Safe mode
|
|
338
|
+
开启时整个 `/keybindings` 编辑器只读,避免保存只会在关闭 safe mode 后才发现的
|
|
339
|
+
冲突配置。
|
|
340
|
+
- 编辑器中未自定义 action 且仍然 effective 的默认按键可选择。`Add shortcut` 会把
|
|
341
|
+
这些仍生效的默认键与新键一起写入;已被 shadow 的 definition default 仅作参考,替换
|
|
342
|
+
或删除一个仍生效的默认键会保留其余 sibling。action 已有用户声明后,仍按上文规则
|
|
343
|
+
替换内置按键集合。
|
|
344
|
+
- `/help` 仍是按键优先的只读帮助;`/keybindings` 是按 action 优先的
|
|
345
|
+
可编辑 Keyboard Shortcuts Editor:按类别分组,搜索 action ID/描述/当前键和
|
|
346
|
+
默认键,并标记 customized、conflict、Unbound、Disabled 和 fixed 状态。
|
|
347
|
+
独立的 Leader key 行还可设置全局 leader key。
|
|
348
|
+
- `/settings` 只有一个 `Keyboard shortcuts` 入口,打开与 `/keybindings` 相同的
|
|
349
|
+
编辑器和持久化控制器。
|
|
350
|
+
- 录制器读取真实终端按键,通过 `parseKey` 规范化为 `KeyId`;保存前会拒绝
|
|
351
|
+
无法匹配、吞输入、终端歧义或已知冲突的按键。普通录制器按 `Esc` 立即取消;
|
|
352
|
+
Host interrupt action 的 direct recorder 使用短暂双击窗口:一次 `Esc` 取消,
|
|
353
|
+
两次 `Esc` press event 才录入物理 Escape。repeat/release 不算第二次,不再有单字母
|
|
354
|
+
快捷方式;物理 Escape 保留给 Host 生命周期路径。
|
|
355
|
+
- 条件 affordance 会在编辑器中单独标注(例如空编辑器任务浏览器的
|
|
356
|
+
`Down (conditional)`),不会伪装成普通已配置快捷键。
|
|
357
|
+
- `/keybindings conflicts` 列出冲突(同键 + 作用域重叠 + 同优先级——绝不
|
|
358
|
+
静默 last-write-wins);`/keybindings reload` 重新读取设置(fail-soft:坏配置会
|
|
359
|
+
被诊断并跳过,读取异常才会给出错误提示——都不会崩溃,keymap 保留
|
|
360
|
+
last-known-good 配置);`/keybindings reset` 通过 settings 服务清除覆盖,并
|
|
361
|
+
立即重建运行中的 keymap。
|
|
362
|
+
- 子代理查看器按 action id 阻止父级 action,因此改键后的父级快捷键
|
|
363
|
+
在查看器内依然被阻止。
|
|
364
|
+
- 条件 affordance 是**累加**的:绑定 `app.tasks.open: ctrl+x` 是**增加**
|
|
365
|
+
一个触发——空编辑器的 `↓` 任务浏览器仍然有效;只有 `false` 才会
|
|
366
|
+
移除某 action 的全部触发。
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
## 安装
|
|
370
|
+
|
|
371
|
+
### 环境要求
|
|
372
|
+
|
|
373
|
+
* DeepSeek Harness
|
|
374
|
+
* Node.js `^22.19.0 || >=24`
|
|
375
|
+
|
|
376
|
+
项目当前跟随 DeepSeek Harness `0.1.1-rc.x` 版本线开发。
|
|
377
|
+
|
|
378
|
+
### npm
|
|
379
|
+
|
|
380
|
+
推荐使用单独的 `pi-tui` profile:
|
|
52
381
|
|
|
53
382
|
```sh
|
|
54
|
-
# install the bundle into the pi-tui profile (creates the profile if needed)
|
|
55
383
|
dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui
|
|
56
|
-
|
|
57
|
-
# run it
|
|
58
384
|
dsh --profile pi-tui
|
|
59
385
|
```
|
|
60
386
|
|
|
61
|
-
|
|
62
|
-
|
|
387
|
+
恢复已有 Session:
|
|
388
|
+
|
|
389
|
+
```sh
|
|
390
|
+
dsh --profile pi-tui --session <session-id>
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
安装包已经包含运行所需的 Pi TUI fork,不需要额外安装内部的 TUI package。
|
|
394
|
+
|
|
395
|
+
### 更新
|
|
396
|
+
|
|
397
|
+
```sh
|
|
398
|
+
dsh plugin --profile pi-tui -- update @xmoon76/dsh-pi-tui
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
查看已安装插件:
|
|
402
|
+
|
|
403
|
+
```sh
|
|
404
|
+
dsh plugin --profile pi-tui -- list
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
卸载:
|
|
63
408
|
|
|
64
|
-
|
|
409
|
+
```sh
|
|
410
|
+
dsh plugin --profile pi-tui -- remove @xmoon76/dsh-pi-tui
|
|
411
|
+
```
|
|
65
412
|
|
|
66
|
-
|
|
67
|
-
the package `exports` point at the built files), so build before installing
|
|
68
|
-
from a clone:
|
|
413
|
+
## 从源码运行
|
|
69
414
|
|
|
70
415
|
```sh
|
|
71
416
|
git clone https://github.com/XMoon/dsh-pi-tui
|
|
72
417
|
cd dsh-pi-tui
|
|
418
|
+
|
|
73
419
|
pnpm install
|
|
74
|
-
pnpm build
|
|
420
|
+
pnpm build
|
|
421
|
+
```
|
|
75
422
|
|
|
76
|
-
|
|
77
|
-
# to refresh (see "Update / uninstall" below)
|
|
78
|
-
dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui@file:$PWD
|
|
423
|
+
使用 `file:` 安装:
|
|
79
424
|
|
|
80
|
-
|
|
81
|
-
dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui@
|
|
425
|
+
```sh
|
|
426
|
+
dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui@file:$PWD
|
|
82
427
|
```
|
|
83
428
|
|
|
84
|
-
|
|
429
|
+
`file:` 会在安装时复制当前构建结果。修改源码后需要重新 build 并重新 add。
|
|
430
|
+
|
|
431
|
+
持续开发可以使用 `link:`:
|
|
85
432
|
|
|
86
433
|
```sh
|
|
87
|
-
dsh plugin --profile pi-tui --
|
|
88
|
-
dsh --profile pi-tui
|
|
434
|
+
dsh plugin --profile pi-tui-dev -- add @xmoon76/dsh-pi-tui@link:$PWD
|
|
435
|
+
dsh --profile pi-tui-dev
|
|
89
436
|
```
|
|
90
437
|
|
|
91
|
-
|
|
438
|
+
之后重新执行:
|
|
92
439
|
|
|
93
440
|
```sh
|
|
94
|
-
|
|
95
|
-
dsh plugin --profile pi-tui -- update @xmoon76/dsh-pi-tui
|
|
96
|
-
# file: source installs copy at add time — rebuild + re-add to refresh
|
|
97
|
-
# (link: installs track the repo live and need only `pnpm build`):
|
|
98
|
-
pnpm build && dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui@file:$PWD
|
|
99
|
-
|
|
100
|
-
dsh plugin --profile pi-tui -- remove @xmoon76/dsh-pi-tui
|
|
441
|
+
pnpm build
|
|
101
442
|
```
|
|
102
443
|
|
|
103
|
-
|
|
444
|
+
即可让开发 Profile 使用新的构建结果。
|
|
445
|
+
|
|
446
|
+
## DeepSeek Harness 集成
|
|
447
|
+
|
|
448
|
+
`dsh-pi-tui` 只实现终端交互层。
|
|
449
|
+
|
|
450
|
+
以下能力由 DeepSeek Harness 提供:
|
|
451
|
+
|
|
452
|
+
* Agent Loop
|
|
453
|
+
* LLM / Provider
|
|
454
|
+
* Session Persistence
|
|
455
|
+
* Tools
|
|
456
|
+
* Skills
|
|
457
|
+
* Approval
|
|
458
|
+
* Permission Presets
|
|
459
|
+
* Plan Mode
|
|
460
|
+
* Goal
|
|
461
|
+
* Jobs
|
|
462
|
+
* Subagents
|
|
463
|
+
* Credentials
|
|
464
|
+
* Settings
|
|
465
|
+
|
|
466
|
+
因此 TUI 不需要维护独立的模型配置、Session 格式或 Agent Runtime。
|
|
467
|
+
|
|
468
|
+
它可以和其他 DSH Surface 使用同一套运行时数据:
|
|
104
469
|
|
|
105
470
|
```sh
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
pnpm typecheck
|
|
110
|
-
node --expose-gc scripts/bench.mts # performance baseline (optional)
|
|
471
|
+
dsh --profile web
|
|
472
|
+
dsh --profile headless
|
|
473
|
+
dsh --profile pi-tui
|
|
111
474
|
```
|
|
112
475
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
| Tier | Entry | Contract |
|
|
146
|
-
|---|---|---|
|
|
147
|
-
| Stable | `@xmoon76/dsh-pi-tui/extensions` | compatibility-oriented; additive-first; existing semantics never silently change; removal requires a planned breaking change |
|
|
148
|
-
| Advanced | `@xmoon76/dsh-pi-tui/extensions/advanced` | experimental; minor releases may break; a migration note is required; no long-term shims |
|
|
149
|
-
| Unstable | `@xmoon76/dsh-pi-tui/extensions/unstable` | NO compatibility guarantee; implementation may change anytime |
|
|
150
|
-
|
|
151
|
-
All tiers reuse the SAME shared extension runtime: caller-fiber ownership,
|
|
152
|
-
surface lifecycle, invalidation, capability discovery. Do not fork a second
|
|
153
|
-
ownership/lifecycle model per tier. The tiers have grown since Phase 1:
|
|
154
|
-
|
|
155
|
-
- **Advanced** (`ADVANCED_API_LEVEL = 1`, Phase 2 + Phase 4): normalized
|
|
156
|
-
input capture, focused interactive surfaces (interactive managed
|
|
157
|
-
overlays), advanced editor control, the imperative UI broker
|
|
158
|
-
(select/confirm/input/notify), custom interactive UI and the host-state
|
|
159
|
-
facade (theme/title/working/tools-expanded) — still Host-mediated,
|
|
160
|
-
never raw terminal bytes. Author guide: `docs/extension-advanced.md`;
|
|
161
|
-
Pi capability reference: `docs/extension-capability-matrix.md`.
|
|
162
|
-
- **Unstable** (`UNSTABLE_API_LEVEL = 1`, Phase 3): raw input
|
|
163
|
-
interception (observe/consume/rewrite, exclusive raw ownership), the
|
|
164
|
-
Host emergency fail-safe (triple-Esc), and a selected low-level surface
|
|
165
|
-
seam — NO compatibility guarantee; a broken plugin can disrupt Host
|
|
166
|
-
behavior. Author guide: `docs/extension-unstable.md`.
|
|
167
|
-
- **Real-plugin validation (Phase 5):** the tier selection is proven by
|
|
168
|
-
real consumers in `examples/plugins/` — a
|
|
169
|
-
production-class vim modal editor (Advanced editor SDK), a
|
|
170
|
-
questionnaire form (Advanced imperative UI broker) and an interactive
|
|
171
|
-
shell (Unstable raw seam). The "which tier should I use?" decision
|
|
172
|
-
tree: `docs/plugin-authoring.md`.
|
|
173
|
-
|
|
174
|
-
A plugin imports only the public entry:
|
|
476
|
+
## Extension API
|
|
477
|
+
|
|
478
|
+
除作为 TUI 使用外,`dsh-pi-tui` 还提供版本化的 Extension API,供其他 Cordis / DSH 插件扩展终端界面。
|
|
479
|
+
|
|
480
|
+
目前分为三个入口:
|
|
481
|
+
|
|
482
|
+
| Entry | 用途 | 稳定性 |
|
|
483
|
+
| ----------------------------------------- | ------------ | -------- |
|
|
484
|
+
| `@xmoon76/dsh-pi-tui/extensions` | 常规扩展 | Stable |
|
|
485
|
+
| `@xmoon76/dsh-pi-tui/extensions/advanced` | 高级交互能力 | Advanced |
|
|
486
|
+
| `@xmoon76/dsh-pi-tui/extensions/unstable` | Low-level 能力 | Unstable |
|
|
487
|
+
|
|
488
|
+
可扩展的内容包括:
|
|
489
|
+
|
|
490
|
+
* Header / Footer
|
|
491
|
+
* Input Widget
|
|
492
|
+
* Slash Command
|
|
493
|
+
* Theme
|
|
494
|
+
* Setting
|
|
495
|
+
* Autocomplete
|
|
496
|
+
* Keybinding
|
|
497
|
+
* Message Renderer
|
|
498
|
+
* Tool Renderer
|
|
499
|
+
* Overlay
|
|
500
|
+
* Interactive UI
|
|
501
|
+
* Editor Control
|
|
502
|
+
* Replacement Editor
|
|
503
|
+
|
|
504
|
+
插件只需要依赖公开入口,不需要 import `TuiApp`、`TuiMainScreen` 等内部实现。
|
|
505
|
+
|
|
506
|
+
简单示例:
|
|
175
507
|
|
|
176
508
|
```ts
|
|
177
|
-
import {
|
|
509
|
+
import {
|
|
510
|
+
PI_TUI_EXTENSIONS_SERVICE,
|
|
511
|
+
type PiTuiExtensionService,
|
|
512
|
+
} from '@xmoon76/dsh-pi-tui/extensions'
|
|
178
513
|
|
|
179
514
|
export const name = 'my-plugin'
|
|
180
515
|
export const inject = ['tuiStartup', PI_TUI_EXTENSIONS_SERVICE]
|
|
181
516
|
|
|
182
517
|
export function apply(ctx: Context): void {
|
|
183
|
-
const service = ctx.get(
|
|
184
|
-
|
|
185
|
-
|
|
518
|
+
const service = ctx.get(
|
|
519
|
+
PI_TUI_EXTENSIONS_SERVICE,
|
|
520
|
+
) as PiTuiExtensionService
|
|
521
|
+
|
|
522
|
+
if (!service.api().capabilities.has('slot.chrome.header.badge')) {
|
|
523
|
+
return
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
service.register(
|
|
186
527
|
'chrome.header.badge',
|
|
187
|
-
{
|
|
188
|
-
|
|
528
|
+
{
|
|
529
|
+
id: 'my-badge',
|
|
530
|
+
order: 100,
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
text: 'my-plugin',
|
|
534
|
+
tone: 'info',
|
|
535
|
+
},
|
|
189
536
|
)
|
|
190
537
|
}
|
|
191
538
|
```
|
|
192
539
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
| Slot | Semantics | Contribution |
|
|
196
|
-
|---|---|---|
|
|
197
|
-
| `chrome.header.badge` | list | a short `[badge]` after the host title |
|
|
198
|
-
| `input.dock.item` | list | a dock line above the todo panel |
|
|
199
|
-
| `chrome.footer.status` | list | a footer segment (host owns width/truncation) |
|
|
200
|
-
| `input.widget.above` / `input.widget.below` | list | a bounded widget around the editor (M4 component kit) |
|
|
201
|
-
|
|
202
|
-
Contributions are **plain data**, not render functions: a plugin supplies
|
|
203
|
-
`HeaderBadge` / `DockItem` / `FooterSegment` / `InputWidget` values (text +
|
|
204
|
-
semantic tone spans, or a structured `ExtensionView` tree for widgets) and
|
|
205
|
-
the host owns rendering, ANSI compilation, width budgets and truncation.
|
|
206
|
-
There is deliberately no `render(context)` callback in v1 — plugins never
|
|
207
|
-
hold a rendering context, so a contribution can never capture or mutate
|
|
208
|
-
host internals.
|
|
209
|
-
|
|
210
|
-
Since `0.2.0` the widget slots (`input.widget.above` / `input.widget.below`)
|
|
211
|
-
accept a bounded component kit: `ExtensionView` is a structured view tree
|
|
212
|
-
(`text` / `markdown` / `spacer` / `stack` / `frame` / `rows` views with
|
|
213
|
-
semantic style tokens) that the host compiles into private components. A
|
|
214
|
-
plugin can add helper rows above or below the editor — for example a status
|
|
215
|
-
widget or a quick-reference line — without touching the root layout, the
|
|
216
|
-
editor, or focus. The host owns the row budgets: under height pressure the
|
|
217
|
-
lowest-importance widgets collapse first, and the editor always survives.
|
|
540
|
+
详细文档:
|
|
218
541
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
542
|
+
* [Extension API](docs/extension-api.md)
|
|
543
|
+
* [Extension tiers](docs/extension-tiers.md)
|
|
544
|
+
* [Advanced API](docs/extension-advanced.md)
|
|
545
|
+
* [Unstable API](docs/extension-unstable.md)
|
|
546
|
+
* [Plugin authoring](docs/plugin-authoring.md)
|
|
547
|
+
* [Capability matrix](docs/extension-capability-matrix.md)
|
|
548
|
+
|
|
549
|
+
## 开发
|
|
550
|
+
|
|
551
|
+
```sh
|
|
552
|
+
pnpm install
|
|
553
|
+
pnpm build
|
|
554
|
+
pnpm typecheck
|
|
555
|
+
pnpm test
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
测试包括 Pi TUI fork、自身 TUI 行为以及 Extension API 的 fixture / smoke test。
|
|
559
|
+
|
|
560
|
+
终端渲染和输入路由使用 `@xterm/headless` 做自动化验证,因此大部分 UI 测试不依赖真实 TTY 或模型连接。
|
|
561
|
+
|
|
562
|
+
性能基线:
|
|
563
|
+
|
|
564
|
+
```sh
|
|
565
|
+
node --expose-gc scripts/bench.mts
|
|
229
566
|
```
|
|
230
567
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
- `registerCommand(contribution)` — slash-command OWNERSHIP metadata
|
|
234
|
-
(`execution: 'local' | 'submission'`): a plugin-declared local command
|
|
235
|
-
always executes directly (never steered by the busy-Enter preference);
|
|
236
|
-
a submission command flows through the session policy. Actual execution
|
|
237
|
-
stays in the host's commands service; `/name args...` keeps
|
|
238
|
-
`rawInput` verbatim. Name conflicts are reported, never guessed.
|
|
239
|
-
- `registerTheme(contribution)` — a named semantic palette selectable
|
|
240
|
-
from the /settings theme picker; the owner's unload falls back to the
|
|
241
|
-
built-in palette (a selected plugin theme never dangles).
|
|
242
|
-
- `registerSetting(contribution)` — a settings row appended to the
|
|
243
|
-
/settings panel (label + current value + choices + optional rejection);
|
|
244
|
-
the host owns the panel.
|
|
245
|
-
- `registerAutocomplete(contribution)` — an autocomplete provider
|
|
246
|
-
consulted after the host's own provider returns null (deterministic
|
|
247
|
-
order, per-provider isolation, latest-only commit).
|
|
248
|
-
- `registerKeybinding(contribution)` — normalized-key → semantic-action
|
|
249
|
-
binding, routed by the host's InputRouter (M6). A plugin declares a key
|
|
250
|
-
with the public `NormalizedKey` shape (key + ctrl/alt/shift/super) and
|
|
251
|
-
a semantic action from the host's list (`submit-draft`, `queue-draft`,
|
|
252
|
-
`steer-draft`, `cancel-activity`, `open-search`, `toggle-fullscreen`,
|
|
253
|
-
`cycle-permission`). The host normalizes ALL terminal input (Kitty
|
|
254
|
-
CSI-u, modifyOtherKeys, legacy sequences) — a plugin never sees raw
|
|
255
|
-
escape data. Reserved host lifecycle keys (Ctrl+C/D/S/F/O/T/G/J,
|
|
256
|
-
Ctrl+Enter, Enter, Esc) cannot be claimed; plain printable keys never
|
|
257
|
-
fire a binding (typing always wins); bindings are non-capturing and
|
|
258
|
-
fire LAST in the precedence ladder (after questions, approvals,
|
|
259
|
-
overlays and the editor). The action executes through the host's own
|
|
260
|
-
paths — submission/session safety is never bypassed.
|
|
261
|
-
- `registerMessageRenderer(contribution)` — a TRANSCRIPT message renderer
|
|
262
|
-
(M7, chain slot): receives a semantic `MessagePresentationSnapshot`
|
|
263
|
-
(immutable; never the mutable message or the container) and returns an
|
|
264
|
-
`ExtensionView` or `undefined` (abdicate → the next renderer → the host
|
|
265
|
-
fallback). Kind-scoped renderers apply to one message kind.
|
|
266
|
-
- `registerToolRenderer(contribution)` — a TOOL card renderer (M7, keyed
|
|
267
|
-
slot): presents the card for ONE tool name from a
|
|
268
|
-
`ToolPresentationSnapshot` (callId, toolName, status, arguments,
|
|
269
|
-
result, expanded); the winner (lowest priority) abdicates to the next
|
|
270
|
-
renderer, then the host fallback. A priority tie on the same tool name
|
|
271
|
-
is an explicit error.
|
|
272
|
-
Renderers never stall the transcript: a throwing renderer is isolated
|
|
273
|
-
and the chain continues, and the message cache embeds the renderer
|
|
274
|
-
identity + registry revision, so an HMR/unload rebuilds exactly the
|
|
275
|
-
affected components.
|
|
276
|
-
- `showOverlay(view, options)` — a MANAGED overlay lease (M8): the plugin
|
|
277
|
-
supplies an `ExtensionView` + sizing hints; the host mounts it through
|
|
278
|
-
its overlay broker (modal stacking, focus, fullscreen migration). The
|
|
279
|
-
returned lease is generation-scoped (the surface's final dispose closes
|
|
280
|
-
every still-owned lease), close() is idempotent, and hide()/show()
|
|
281
|
-
toggle visibility without closing. A plugin can never mount a raw
|
|
282
|
-
component or steal focus — the host owns the terminal and the overlay
|
|
283
|
-
stack.
|
|
284
|
-
The [extension API v1 author guide](docs/extension-api.md) records the
|
|
285
|
-
import rules, the full surface table, the lifecycle/render contracts, the
|
|
286
|
-
M11 deprecation policy and the stability contract.
|
|
287
|
-
|
|
288
|
-
The M10 acceptance fixture (plan §15): the repo ships a vim-mode fixture
|
|
289
|
-
(`test/fixtures/vim-plugin/`) that validates the editor-extension seam — the
|
|
290
|
-
packed public SDK is consumable by a third-party Cordis plugin, its
|
|
291
|
-
replacement editor receives SEMANTIC `EditorInputEvent`s (never raw terminal
|
|
292
|
-
bytes), and editor `create()`/`dispose()` work — importing ONLY
|
|
293
|
-
`@xmoon76/dsh-pi-tui/extensions`. It is NOT a production Vim and NOT a
|
|
294
|
-
Stable-API completeness proof: modal-mode behavior (insert/normal) is not
|
|
295
|
-
part of the Stable contract, and the other public surfaces (commands,
|
|
296
|
-
themes, settings, autocomplete, keybindings, renderers, overlays, widgets)
|
|
297
|
-
have their own dedicated tests. Its CI gate forbids `@xmoon76/pi-tui`,
|
|
298
|
-
`src/tui-app` and repository-relative internal paths: if a STABLE plugin
|
|
299
|
-
ever needs a private import, the SDK is missing a capability (there is no
|
|
300
|
-
`unsafeGetTuiApp()` escape hatch).
|
|
301
|
-
|
|
302
|
-
- `registerEditor(contribution)` — the EDITOR SDK (M9, plan §14):
|
|
303
|
-
single-winner by priority (a tie is an explicit error); the winner
|
|
304
|
-
occupies the editor seat through the host's ATOMIC handoff (create →
|
|
305
|
-
transfer draft/cursor → mount → focus → dispose old). A creation throw
|
|
306
|
-
keeps the current editor working; winner unload restores the next
|
|
307
|
-
winner / the host default editor WITH the draft preserved. The plugin
|
|
308
|
-
editor receives an `EditorHost` (surfaceId, generation, getSnapshot,
|
|
309
|
-
replaceText, dispatch of semantic actions submit/queue-submit/steer/
|
|
310
|
-
open-external-editor, subscribe, invalidate) — but the host still owns
|
|
311
|
-
busy-Enter, Ctrl+Enter, local-command classification, paste protection,
|
|
312
|
-
approval/question capture, session guard/lock, external editor and
|
|
313
|
-
exit: a plugin editor can never bypass those.
|
|
314
|
-
|
|
315
|
-
Lifecycle is host-owned: registrations are disposed when the plugin's Cordis
|
|
316
|
-
fiber unloads (HMR, disable), regular and fullscreen both refresh, and
|
|
317
|
-
`handle.invalidate()/replace()` re-render through the active screen. The
|
|
318
|
-
`@xmoon76/dsh-pi-tui/builtins` entry is the Loader-only first-party
|
|
319
|
-
contributor (version badge, turn/step counters, todo-summary dock item) —
|
|
320
|
-
not a stable third-party SDK. Raw terminal access, pre-host input
|
|
321
|
-
interception and full input ownership are NOT part of the Stable tier (see
|
|
322
|
-
the Advanced/Unstable roadmap).
|
|
323
|
-
|
|
324
|
-
## Slash commands (selection)
|
|
325
|
-
|
|
326
|
-
- `/sessions [query]` — open the session picker: search-as-you-type over
|
|
327
|
-
session ids, titles, and workspaces, rows grouped by workspace with live
|
|
328
|
-
`filtered/total` counts, and titles loaded in the background as they are
|
|
329
|
-
read. Enter switches to the selected session.
|
|
330
|
-
- `/search <query>` — full-text search over persisted session logs, then
|
|
331
|
-
switch to a hit.
|
|
332
|
-
- `/title [title]` — with an argument, set the current session's title
|
|
333
|
-
(pins it against automatic generation; titles appear in the `/sessions`
|
|
334
|
-
picker); **without an argument, regenerate the title from the
|
|
335
|
-
conversation — this overwrites the current title, including one you
|
|
336
|
-
pinned earlier** (`/rename` is an alias).
|
|
337
|
-
- `/tasks` — the merged task browser: background jobs AND subagents in one
|
|
338
|
-
searchable list (type to filter rows by kind/label/status — `subagent`,
|
|
339
|
-
`bash`, `failed`…). Subagent rows show their mode as part of the label
|
|
340
|
-
(`subagent · <label> · continuable` / `… · one-shot`) — you know before
|
|
341
|
-
Enter whether the viewer will be interactive — and the durable descendant
|
|
342
|
-
tree renders as indented rows (`├─` per depth), so a subagent's subagent
|
|
343
|
-
is visible here without entering its parent. `Enter` opens the detail
|
|
344
|
-
(child viewer for a subagent — nested rows read-only — status viewer for
|
|
345
|
-
a job), `i` interrupts the selected subagent. `/subagents` is an alias.
|
|
346
|
-
- `/yolo` — switch to `danger-full-access` (alias of `/permission danger-full-access`).
|
|
347
|
-
- `/status` — show the current session's stats and identity (turn counts,
|
|
348
|
-
token usage, workspace, installed dsh version).
|
|
349
|
-
- `/rewind` — fork this conversation from an earlier user turn: the picker
|
|
350
|
-
lists every completed user prompt (newest first), and choosing one creates
|
|
351
|
-
a new child session whose history ends right before that turn, then
|
|
352
|
-
restores the selected prompt into the editor for editing. The original
|
|
353
|
-
session is never modified and stays reachable through `/sessions`;
|
|
354
|
-
**workspace and external side effects (files, shell, APIs) are not
|
|
355
|
-
reverted.** The same picker opens with `Esc Esc` on an empty editor.
|
|
356
|
-
- `/preset`, `/model`, `/settings`, `/export`, `/fork` — see
|
|
357
|
-
`dsh --profile pi-tui`'s command autocomplete (`/` + Tab).
|
|
358
|
-
|
|
359
|
-
## Keybindings (selection)
|
|
360
|
-
|
|
361
|
-
- `Esc Esc` — conversation rewind (on an empty editor while idle): open the
|
|
362
|
-
rewind picker and fork from an earlier user turn. One `Esc` while the
|
|
363
|
-
agent is busy still cancels the active turn/tool/shell command; with a
|
|
364
|
-
non-empty draft the double-Esc keeps its historical cancel behavior.
|
|
365
|
-
- `Ctrl+F` — toggle transcript search (the `/search <query>` overlay; a
|
|
366
|
-
second press closes it).
|
|
367
|
-
- `Shift+Tab` — cycle the permission preset (read-only → workspace-write →
|
|
368
|
-
danger-full-access); the footer's mode slot badges every preset
|
|
369
|
-
(`[workspace-write]` / `[read-only]` / `[custom]`, with `[yolo]` flagging
|
|
370
|
-
the no-approval mode).
|
|
371
|
-
- `Ctrl+S` — steer: with queued messages, sends the whole queue (plus the
|
|
372
|
-
draft, if any) into the running turn at once; otherwise sends the draft
|
|
373
|
-
alone. An idle agent starts a fresh turn with everything.
|
|
374
|
-
- `Alt+↑` — dequeue: pull every queued message back into the editor draft.
|
|
375
|
-
- `Ctrl+O` — expand/collapse the most recent turns' collapsible entries
|
|
376
|
-
(tool and system cards). The same master switch expands the local
|
|
377
|
-
`!`/`!!` shell cards (see Local shell below). In Focus Mode (regular
|
|
378
|
-
surface) it derives a full reveal of the recent Thoughts; the
|
|
379
|
-
fullscreen Focus secondary cards are mouse-owned and Ctrl+O does not
|
|
380
|
-
expand them. Thinking detail has its own owner — see `Alt+T`.
|
|
381
|
-
- `Alt+T` — collapse/expand every Thinking block (compact: the latest
|
|
382
|
-
reasoning line as a preview; full: the whole reasoning body). Thinking
|
|
383
|
-
blocks are disclosure, never visibility: a block stays present
|
|
384
|
-
whenever the model produced reasoning and the current projection
|
|
385
|
-
contains it, in Focus ON/OFF and both surfaces alike. In fullscreen a
|
|
386
|
-
click toggles a single Thinking card on top of this bulk level, and
|
|
387
|
-
`Ctrl+O` never touches Thinking detail (it owns only tool/system
|
|
388
|
-
cards). `/settings` → `Thinking detail` is the same state.
|
|
389
|
-
- `Alt+K` — dismiss the SETTLED local `!`/`!!` shell cards from the live
|
|
390
|
-
view; a RUNNING card is never dismissed and the process is not
|
|
391
|
-
cancelled (Esc owns that). See Local shell below.
|
|
392
|
-
- `Ctrl+T` — toggle the full todo list (in fullscreen, clicking the panel
|
|
393
|
-
expands it to the full list and back); the dock above the editor always
|
|
394
|
-
shows the todo summary and background tasks, and queued input renders
|
|
395
|
-
between them.
|
|
396
|
-
- `@` — file/folder mentions in the editor: `@` + Tab completes files from the
|
|
397
|
-
whole workspace (fd-backed when `fd` is on PATH, with a built-in recursive
|
|
398
|
-
fallback otherwise). The literal `@path` is submitted and the model reads
|
|
399
|
-
the file itself. With background work running, an empty editor's `↓` opens
|
|
400
|
-
the same merged task browser as `/tasks`:
|
|
401
|
-
- **subagent rows** (the durable descendant tree, labeled with their
|
|
402
|
-
catalog mode: `continuable` or `one-shot`, indented by depth with a
|
|
403
|
-
`├─` connector — a subagent's subagent is visible right here, in
|
|
404
|
-
stable pre-order) — `Enter` opens the child's viewer:
|
|
405
|
-
a **direct (depth-1) continuable** child's viewer is **interactive**
|
|
406
|
-
— type a follow-up and press Enter, and it is delivered as the
|
|
407
|
-
child's NEXT turn through `ctx.subagents.followup` (FIFO — a running
|
|
408
|
-
child is never interrupted or steered; an inactive child cold-resumes
|
|
409
|
-
automatically), while `Esc` returns to your session. A **one-shot**
|
|
410
|
-
child's viewer stays **read-only**, and so does any **nested
|
|
411
|
-
(depth > 1)** descendant — even a continuable one (`<mode> · nested ·
|
|
412
|
-
read-only from this parent`, the real mode always shown): the root
|
|
413
|
-
may only continue its own direct children. A FINISHED one-shot child stays in the
|
|
414
|
-
browser — `inactive` is live-store presence, not an outcome — and
|
|
415
|
-
Enter opens its persisted transcript. While a viewer is open the
|
|
416
|
-
footer switches to the CHILD's own identity (`[subagent ·
|
|
417
|
-
continuable]` badge, label, activity, cwd, the child's own
|
|
418
|
-
turns/steps/stats) and returns to your session's on exit. `i`
|
|
419
|
-
interrupts the selected child. They never register job records, so
|
|
420
|
-
this browser is their only glanceable home.
|
|
421
|
-
- **job rows** (bash and one-shot subagent jobs) — `Enter` shows the status
|
|
422
|
-
viewer only: a bash job's output read cursor belongs to the model's
|
|
423
|
-
`job_output`, and a one-shot subagent job record carries no child session
|
|
424
|
-
id, so a subagent job's transcript is reached through `/tasks` by picking
|
|
425
|
-
the child by its label.
|
|
426
|
-
The footer badge shows `[N tasks running · M agents · ↓ view]` while any
|
|
427
|
-
background work is live. The queue pane above the editor shows pending
|
|
428
|
-
input: user messages as `❯` rows, everything else (job notices, subagent
|
|
429
|
-
reports, injected instructions) as `⏳` notices that fold into a
|
|
430
|
-
`+N more` line beyond five — and disappear once the agent has received
|
|
431
|
-
them.
|
|
432
|
-
|
|
433
|
-
## Launch options
|
|
434
|
-
|
|
435
|
-
The TUI's startup row adds `--preset <id>` — the agent preset a fresh
|
|
436
|
-
session starts on (falls back to `$DSH_PI_TUI_PRESET`, then the saved
|
|
437
|
-
settings default). It exists because `/preset` only applies to a blank (not
|
|
438
|
-
yet created) session, so launch-time selection is the other half of choosing
|
|
439
|
-
a preset. All other flags are the dsh runner's own (`--session <id>`, …).
|
|
440
|
-
|
|
441
|
-
## Session lifecycle
|
|
442
|
-
|
|
443
|
-
Opening the TUI with no `--session` creates **no session at all**: the first
|
|
444
|
-
user message (text, slash command, `Ctrl+S` steer, or `!` shell) starts it
|
|
445
|
-
lazily. `--session <id>` still resumes immediately, and a local `!!` command
|
|
446
|
-
runs without needing a session.
|
|
447
|
-
|
|
448
|
-
## Local shell (`!` / `!!`)
|
|
449
|
-
|
|
450
|
-
`! command` runs the command locally and submits the completed command +
|
|
451
|
-
output to the session as an ordinary user message; `!! command` runs purely
|
|
452
|
-
locally — the card is the only record (never sent to the model). The card
|
|
453
|
-
is COLLAPSED by default so a long log cannot fill the TUI: a running card
|
|
454
|
-
shows the newest 5 lines, a settled card at most 20 visual rows (long
|
|
455
|
-
lines wrap and count as several), each with an honest hidden-line marker.
|
|
456
|
-
`Ctrl+O` (the same master switch as the recent-turn fold) expands the card
|
|
457
|
-
to the retained buffer — live while the command still streams; `Alt+K`
|
|
458
|
-
quick-dismisses the SETTLED cards (a running card is never dismissed, the
|
|
459
|
-
process is not cancelled, and an already-submitted `!` context payload is
|
|
460
|
-
untouched).
|
|
461
|
-
|
|
462
|
-
## Verified in the P0 spike
|
|
463
|
-
|
|
464
|
-
- Vendored pi-tui: the fork's own suite passes under `node --test` (run it as
|
|
465
|
-
the sync gate after every re-vendor; the count is deliberately not copied here —
|
|
466
|
-
`packages/pi-tui/package.json` is the single source of version facts).
|
|
467
|
-
- `TuiApp` renders, accepts editor input, and handles Ctrl+C on a headless xterm.
|
|
468
|
-
- The whole import chain (pi-tui, tui-app, `@deepseek-ai/dsh-cmdline`, commander)
|
|
469
|
-
loads under the tsx ESM hook — the dsh source-launch contract.
|
|
470
|
-
- Native modifier-key addons are optional: on Linux the loader returns `undefined`
|
|
471
|
-
without attempting a load, and the non-TTY stdin path is guarded.
|
|
472
|
-
|
|
473
|
-
## Diagnostic log
|
|
474
|
-
|
|
475
|
-
The TUI writes its own diagnostics to stderr and a log file (`ctx.logger` is
|
|
476
|
-
invisible in this process — no exporter):
|
|
477
|
-
|
|
478
|
-
- Default file: `$DSH_HOME/logs/pi-tui-<pid>.log` (default `~/.dsh/logs/`);
|
|
479
|
-
- Line format: `[tui] <ISO time> <level> <message> k=v ...`;
|
|
480
|
-
- Default level `info`: key lifecycle events only (boot/resume/switch/exit,
|
|
481
|
-
divergence-guard warnings, errors); `debug` additionally logs every guard
|
|
482
|
-
check before a send.
|
|
483
|
-
|
|
484
|
-
Configuration (environment variables):
|
|
485
|
-
|
|
486
|
-
| Variable | Meaning | Default |
|
|
487
|
-
|---|---|---|
|
|
488
|
-
| `DSH_PI_TUI_LOG` | Log file path; `off` disables file logging | `$DSH_HOME/logs/pi-tui-<pid>.log` |
|
|
489
|
-
| `DSH_PI_TUI_LOG_LEVEL` | `debug` / `info` / `warn` / `error` | `info` |
|
|
490
|
-
|
|
491
|
-
Troubleshooting example:
|
|
568
|
+
项目日常开发使用单独的 `pi-tui-dev` Profile 进行自测:
|
|
492
569
|
|
|
493
570
|
```sh
|
|
494
|
-
|
|
495
|
-
|
|
571
|
+
dsh plugin --profile pi-tui-dev -- add @xmoon76/dsh-pi-tui@link:$PWD
|
|
572
|
+
dsh --profile pi-tui-dev
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
## 项目结构
|
|
576
|
+
|
|
577
|
+
仓库根目录是发布到 npm 的 `@xmoon76/dsh-pi-tui` bundle。
|
|
578
|
+
|
|
579
|
+
Pi TUI fork 位于:
|
|
580
|
+
|
|
581
|
+
```text
|
|
582
|
+
packages/pi-tui/
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
它作为内部依赖参与构建,并随根 package 一起打包,不单独要求用户安装。
|
|
586
|
+
|
|
587
|
+
具体的 upstream 来源、版本和本地差异以:
|
|
588
|
+
|
|
589
|
+
```text
|
|
590
|
+
packages/pi-tui/package.json
|
|
591
|
+
packages/pi-tui/AGENTS.md
|
|
496
592
|
```
|
|
497
593
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
projection, cold/warm rebuilds, streaming frames, theme switches, and heap;
|
|
526
|
-
the saved baseline lives in `docs/perf-baseline.md`. Unchanged transcript
|
|
527
|
-
messages reuse their rendered components, so the warm per-frame rebuild
|
|
528
|
-
does not grow with history.
|
|
594
|
+
为准。
|
|
595
|
+
|
|
596
|
+
贡献者相关的仓库结构和开发约定见 [AGENTS.md](AGENTS.md)。
|
|
597
|
+
|
|
598
|
+
## 文档
|
|
599
|
+
|
|
600
|
+
| 文档 | 内容 |
|
|
601
|
+
| ------------------------------------------------------ | ---------------------------- |
|
|
602
|
+
| [docs/README.md](docs/README.md) | 文档索引 |
|
|
603
|
+
| [docs/architecture.md](docs/architecture.md) | 架构和模块职责 |
|
|
604
|
+
| [docs/input-history.md](docs/input-history.md) | 输入历史 |
|
|
605
|
+
| [docs/surface-decisions.md](docs/surface-decisions.md) | TUI 交互设计决策 |
|
|
606
|
+
| [docs/concurrency.md](docs/concurrency.md) | Session 并发 |
|
|
607
|
+
| [docs/failure-model.md](docs/failure-model.md) | Async failure / cancellation |
|
|
608
|
+
| [docs/perf-baseline.md](docs/perf-baseline.md) | 性能基线 |
|
|
609
|
+
| [docs/extension-api.md](docs/extension-api.md) | Extension API |
|
|
610
|
+
| [AGENTS.md](AGENTS.md) | Contributor operating manual |
|
|
611
|
+
|
|
612
|
+
## Changelog
|
|
613
|
+
|
|
614
|
+
中文:
|
|
615
|
+
|
|
616
|
+
[CHANGELOG.md](CHANGELOG.md)
|
|
617
|
+
|
|
618
|
+
English:
|
|
619
|
+
|
|
620
|
+
[CHANGELOG.en.md](CHANGELOG.en.md)
|
|
529
621
|
|
|
530
622
|
## License
|
|
531
623
|
|
|
532
|
-
MIT
|
|
533
|
-
(Copyright (c) 2025 Mario Zechner; Moonshot AI fork).
|
|
624
|
+
[MIT](LICENSE)
|