@xmoon76/dsh-pi-tui 0.3.4 → 0.3.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmoon76/dsh-pi-tui",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "The dsh-pi-tui bundle: a TUI surface for DeepSeek Harness profiles (dsh --profile pi-tui)",
5
5
  "license": "MIT",
6
6
  "author": "XMoon <xmoonox@gmail.com>",
@@ -54,10 +54,11 @@
54
54
  "files": [
55
55
  "dist",
56
56
  "config",
57
- "scripts",
57
+ "scripts/repair-session.mjs",
58
+ "scripts/repair-core.mjs",
58
59
  "cordis.patch.yml",
59
60
  "README.md",
60
- "README.zh-CN.md",
61
+ "README.en.md",
61
62
  "LICENSE"
62
63
  ],
63
64
  "publishConfig": {
@@ -168,12 +169,19 @@
168
169
  "smoke:advanced": "node scripts/advanced-plugin-smoke.mjs",
169
170
  "smoke:phase4": "node scripts/phase4-plugin-smoke.mjs",
170
171
  "smoke:unstable": "node scripts/unstable-plugin-smoke.mjs",
172
+ "smoke:pi2dsh": "node scripts/pi2dsh-compat-smoke.mjs",
171
173
  "smoke:vim": "node scripts/vim-plugin-smoke.mjs",
174
+ "gate:pi-surface-compat": "node --test test/pi-component-compat.test.ts",
172
175
  "smoke:examples": "node scripts/examples-plugin-smoke.mjs",
173
176
  "pack:release": "rm -f xmoon76-dsh-pi-tui-*.tgz && pnpm pack --pack-destination .",
177
+ "release:notes": "node scripts/release-notes.mjs",
174
178
  "smoke:pack": "node scripts/tarball-smoke.mjs",
175
- "verify:prepush": "pnpm typecheck:fork && pnpm test:fork && pnpm test:docs && node scripts/naming-gate.mjs && pnpm gate:boundary && pnpm audit --prod --audit-level high && pnpm pack:release",
176
- "verify:prepush:nofork": "pnpm test:docs && node scripts/naming-gate.mjs && pnpm gate:boundary && pnpm audit --prod --audit-level high && pnpm pack:release",
177
- "gate:boundary": "node scripts/client-boundary-gate.mjs"
179
+ "dev:doctor": "node scripts/dev-doctor.mjs",
180
+ "dev:bootstrap": "node scripts/dev-bootstrap.mjs",
181
+ "dev:shell": "node scripts/dev-shell.mjs",
182
+ "verify:prepush": "pnpm typecheck:fork && pnpm test:fork && pnpm test:docs && node scripts/naming-gate.mjs && pnpm gate:boundary && pnpm gate:keybindings && pnpm audit --prod --audit-level high && pnpm pack:release",
183
+ "verify:prepush:nofork": "pnpm test:docs && node scripts/naming-gate.mjs && pnpm gate:boundary && pnpm gate:keybindings && pnpm audit --prod --audit-level high && pnpm pack:release",
184
+ "gate:boundary": "node scripts/client-boundary-gate.mjs",
185
+ "gate:keybindings": "node --import tsx/esm scripts/check-host-keybindings.mts"
178
186
  }
179
187
  }
package/README.zh-CN.md DELETED
@@ -1,469 +0,0 @@
1
- # dsh-pi-tui
2
-
3
- [English](README.md) | **简体中文**
4
-
5
- 发布历史:[CHANGELOG.zh-CN.md](CHANGELOG.zh-CN.md) · [English Changelog](CHANGELOG.md)
6
-
7
- 为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`dsh`) 提供的第三方 TUI 模式,构建于 [pi-tui](https://github.com/MoonshotAI/kimi-code/tree/main/packages/pi-tui) 的内置(vendored)fork 之上。
8
-
9
- 运行 `dsh --profile pi-tui` 即可获得终端界面,替代浏览器界面(`dsh --profile web`)或一次性模式(`dsh --profile headless`)。
10
-
11
- > **状态:可用。** 该 TUI 覆盖了主会话流程——输入 → 会话事件、审批、
12
- > 命令、会话切换与全文搜索——以及预设(presets)、技能(skills)、
13
- > 模型/设置菜单和斜杠命令。渲染与输入路由由无头测试
14
- > (`@xterm/headless`)验证,无需 TTY 或模型连接。
15
-
16
- ## 截图
17
-
18
- ![dsh-pi-tui 运行在终端中](https://raw.githubusercontent.com/XMoon/dsh-pi-tui/main/docs/dsh-pi-tui.png)
19
-
20
- ## 目录结构
21
-
22
- 完整的仓库布局见 [AGENTS.md](AGENTS.md)(贡献者操作手册)。一句话概括:
23
- **仓库根目录就是发布的 `@xmoon76/dsh-pi-tui` 包**(唯一发布的包——其
24
- 清单声明 `dsh.bundle.patch`,`exports` 指向构建后的 `dist/`),而
25
- `packages/pi-tui/` 是 `@moonshot-ai/pi-tui` 的内置 fork(重命名为
26
- `@xmoon76/pi-tui`,私有,永不发布——其分叉修改清单见
27
- `packages/pi-tui/AGENTS.md`),构建时被打进根包产物。
28
-
29
- ## 前置要求
30
-
31
- - 一个支持 profile 的 DeepSeek Harness 安装(`dsh` 已在 `PATH` 中),版本
32
- `0.1.1-rc.1` 或同一兼容线上的更新版本——TUI 消费该版本的拆分凭据事件
33
- 与 `ctx.authorization` 服务。
34
- - Node >= 22.19(`^22.19.0 || >=24`,与 dsh 相同范围)。从源码运行需要
35
- 原生支持 TypeScript 的 Node(>= 23.6)或 tsx ESM 钩子
36
- (`node --import tsx/esm`,dsh 自身源码启动的方式)。
37
- - 仅从源码安装时需要 [pnpm](https://pnpm.io)。
38
-
39
- ## 安装
40
-
41
- `dsh plugin` 会在目标 profile 目录内运行 pnpm,因此常用的 pnpm 动词
42
- (`add`、`remove`、`update`、`list`)都可用。
43
-
44
- ### 方式 A — 从 npm registry 安装(推荐)
45
-
46
- 发布的包是自包含的:内置的 pi-tui fork 被打进了构建产物,所以只需安装
47
- `@xmoon76/dsh-pi-tui` 一个包(`@xmoon76/pi-tui` 在本仓库保持私有,就像
48
- kimi-code 保持 `@moonshot-ai/pi-tui` 私有一样):
49
-
50
- ```sh
51
- # 把包安装到 pi-tui profile(必要时会创建该 profile)
52
- dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui
53
-
54
- # 运行
55
- dsh --profile pi-tui
56
- ```
57
-
58
- 任何在清单中声明了 `dsh.bundle` 的依赖都会自动加入 profile 的层栈
59
- (layer stack)——无需手动配置 `cordis.patch.yml`。
60
-
61
- ### 方式 B — 从源码安装
62
-
63
- 构建产物不提交(`dist/` 对两个包都在 gitignore 中,且包的 `exports`
64
- 指向构建后的文件),所以从克隆安装前需要先构建:
65
-
66
- ```sh
67
- git clone https://github.com/XMoon/dsh-pi-tui
68
- cd dsh-pi-tui
69
- pnpm install
70
- pnpm build # pi-tui tsdown (packages/pi-tui/dist/) + 根 tsdown (dist/, 打入 pi-tui)
71
-
72
- # file: — 添加时把包复制进 profile;重新构建后需重新 add 以刷新
73
- #(见下方"更新 / 卸载")
74
- dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui@file:$PWD
75
-
76
- # link: — 使用实时符号链接;`pnpm build` 的产物会被直接读取
77
- dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui@link:$PWD
78
- ```
79
-
80
- ### 验证安装
81
-
82
- ```sh
83
- dsh plugin --profile pi-tui -- list # 应包含 @xmoon76/dsh-pi-tui
84
- dsh --profile pi-tui # 启动的是 TUI 而不是 Web GUI
85
- ```
86
-
87
- ### 更新 / 卸载
88
-
89
- ```sh
90
- # registry 安装:
91
- dsh plugin --profile pi-tui -- update @xmoon76/dsh-pi-tui
92
- # file: 源码安装在 add 时复制——重新构建并重新 add 以刷新
93
- #(link: 安装实时跟踪仓库,只需 `pnpm build`):
94
- pnpm build && dsh plugin --profile pi-tui -- add @xmoon76/dsh-pi-tui@file:$PWD
95
-
96
- dsh plugin --profile pi-tui -- remove @xmoon76/dsh-pi-tui
97
- ```
98
-
99
- ## 开发
100
-
101
- ```sh
102
- pnpm install
103
- pnpm build # pi-tui tsdown (packages/pi-tui/dist/) + 根 tsdown (dist/, 打入 pi-tui)
104
- pnpm test # pi-tui 自带套件 (node --test) + dsh-pi-tui 无头测试
105
- pnpm typecheck
106
- node --expose-gc scripts/bench.mts # 性能基线(可选)
107
- ```
108
-
109
- 测试通过 `@xterm/headless` 驱动 UI(见
110
- `test/virtual-terminal.ts`),因此无需 TTY 或模型连接
111
- 即可验证渲染与输入路由。
112
-
113
- ### 开发历史(dogfooding)
114
-
115
- 本项目最初在浏览器界面(`dsh --profile web`)上开发,后来转为用自己开发
116
- 自己:自 2026 年 8 月 15 日起,所有修复与功能都在这个 TUI 内部完成,就像
117
- 本 README 与代码库的维护一样。开发循环运行在专用的 `pi-tui-dev`
118
- profile 上,使用方式 B 的 `link:` 说明符安装
119
- (`dsh plugin --profile pi-tui-dev -- add @xmoon76/dsh-pi-tui@link:$PWD`)
120
- ——实时符号链接,`pnpm build` 后无需重新 add 即可生效——而 `pi-tui`
121
- profile 保持安装已发布的 registry 包用于真实使用。
122
-
123
- ## 扩展(早期,稳定化中)
124
-
125
- 自 `0.2.0` 起,该 bundle 携带一个小的、带版本号的扩展面,让第三方
126
- Cordis 插件无需接触 TUI 内部即可贡献 chrome。它**处于早期、稳定化中**:
127
- 下面的能力是当前集合;API 版本(`1`)只在破坏性变更时递增,插件必须
128
- **按能力特性检测**,而不是解析包版本。
129
-
130
- 所有扩展插件仍是标准 DeepSeek Harness / Cordis 插件,使用 `name` /
131
- `inject` / `apply(ctx)`,统一依赖唯一的 `piTuiExtensions` 服务;三层
132
- 只是该单一 Service 上的能力 facade,而非独立的插件系统或 runtime。
133
-
134
- 扩展面分为三个层级:插件只导入**公开入口**——绝不导入 Stable 入口的
135
- 内部(`TuiApp`、`TuiMainScreen`、`TuiAltScreen`)或仓库相对路径。
136
-
137
- | 层级 | 入口 | 契约 |
138
- |---|---|---|
139
- | Stable | `@xmoon76/dsh-pi-tui/extensions` | 面向兼容;只增不改;既有语义永不静默变更;删除需计划内破坏性变更 |
140
- | Advanced | `@xmoon76/dsh-pi-tui/extensions/advanced` | 实验性;minor 版本可破坏;需迁移说明;不做长期 shim |
141
- | Unstable | `@xmoon76/dsh-pi-tui/extensions/unstable` | 不保证兼容;实现可随时变更 |
142
-
143
- 所有层级复用同一个共享 Extension Runtime:caller-fiber 所有权、surface
144
- 生命周期、失效机制、能力发现。不要按层级复制第二套所有权/生命周期
145
- 模型。第一阶段之后各层级已逐步落地:
146
-
147
- - **Advanced**(`ADVANCED_API_LEVEL = 1`,Phase 2 + Phase 4):规范化输入捕获、
148
- 聚焦交互表面(交互式托管 overlay)、高级编辑器控制、命令式 UI broker
149
- (select/confirm/input/notify)、自定义交互 UI 与 host-state facade
150
- (theme/title/working/tools-expanded)——仍由 Host 中介,绝不接触 raw
151
- terminal 字节。作者指南:`docs/extension-advanced.md`;Pi 能力参考:
152
- `docs/extension-capability-matrix.md`。
153
- - **Unstable**(`UNSTABLE_API_LEVEL = 1`,Phase 3):raw 输入拦截
154
- (observe/consume/rewrite、exclusive raw 所有权)、Host 紧急 fail-safe
155
- (三连 Esc)与精选低层 surface seam——不保证兼容;损坏的插件可能
156
- 破坏 Host 行为。作者指南:`docs/extension-unstable.md`。
157
- - **真实插件验证(Phase 5):**层级选择由真实消费者验证,见
158
- `examples/plugins/`——生产级 vim 模态编辑器
159
- (Advanced editor SDK)、questionnaire 表单(Advanced 命令式 UI
160
- broker)与交互式 shell(Unstable raw seam)。"该用哪一层?"决策树:
161
- `docs/plugin-authoring.md`。
162
-
163
- 插件只导入公开入口:
164
-
165
- ```ts
166
- import { PI_TUI_EXTENSIONS_SERVICE, type PiTuiExtensionService } from '@xmoon76/dsh-pi-tui/extensions'
167
-
168
- export const name = 'my-plugin'
169
- export const inject = ['tuiStartup', PI_TUI_EXTENSIONS_SERVICE]
170
-
171
- export function apply(ctx: Context): void {
172
- const service = ctx.get(PI_TUI_EXTENSIONS_SERVICE) as PiTuiExtensionService
173
- if (!service.api().capabilities.has('slot.chrome.header.badge')) return
174
- service.register<{ text: string; tone?: 'info' | 'warning' | 'error' | 'success' }>(
175
- 'chrome.header.badge',
176
- { id: 'my-badge', order: 100, description: 'A header badge from my plugin.' },
177
- { text: 'my-badge', tone: 'info' },
178
- )
179
- }
180
- ```
181
-
182
- 当前扩展点(v1):
183
-
184
- | Slot | 语义 | 贡献 |
185
- |---|---|---|
186
- | `chrome.header.badge` | list | host 标题后的短 `[badge]` |
187
- | `input.dock.item` | list | todo 面板上方的一行 dock |
188
- | `chrome.footer.status` | list | 一个 footer 段(host 拥有宽度/截断) |
189
- | `input.widget.above` / `input.widget.below` | list | 编辑器周围的有界 widget(M4 组件套件) |
190
-
191
- 贡献是**纯数据**,不是 render 函数:插件提供 `HeaderBadge` / `DockItem` /
192
- `FooterSegment` / `InputWidget` 值(文本 + 语义 tone spans,或 widget 的
193
- 结构化 `ExtensionView` 树),渲染、ANSI 编译、宽度预算与截断全部由 host
194
- 负责。v1 刻意没有 `render(context)` 回调——插件从不持有渲染上下文,
195
- 贡献永远无法捕获或修改 host 内部。
196
-
197
- 自 `0.2.0` 起,`input.widget.above` / `input.widget.below` 两个 widget slot
198
- 接受有界组件套件:`ExtensionView` 是结构化视图树(`text` / `markdown` /
199
- `spacer` / `stack` / `frame` / `rows` 视图 + 语义样式 token),由 host 编译
200
- 成私有组件。插件可以在编辑器上方或下方添加辅助行——例如状态 widget
201
- 或快捷参考行——而不接触根布局、编辑器或焦点。行预算由 host 拥有:
202
- 高度不足时先折叠低 importance 的 widget,编辑器永远存活。
203
-
204
- ```ts
205
- service.register<InputWidget>('input.widget.below', {
206
- id: 'my-widget',
207
- order: 100,
208
- }, {
209
- view: {
210
- kind: 'text',
211
- spans: [{ text: 'my-plugin ready', tone: 'success' }],
212
- },
213
- })
214
- ```
215
-
216
- 自 M5 起,扩展面还覆盖注册表(计划 §10):
217
-
218
- - `registerCommand(contribution)` — 斜杠命令的**所有权**元数据
219
- (`execution: 'local' | 'submission'`):插件声明的 local 命令始终直接
220
- 执行(永不被 busy-Enter 偏好 steer);submission 命令走会话投递策略。
221
- 实际执行仍在 host 的 commands 服务中;`/name args...` 保持 `rawInput`
222
- 原样。名称冲突被报告,绝不猜测。
223
- - `registerTheme(contribution)` — 一个具名语义调色板,可在 /settings
224
- 主题选择器中选择;owner 卸载时回退到内置调色板(选中的插件主题
225
- 绝不悬空)。
226
- - `registerSetting(contribution)` — 追加到 /settings 面板的设置行
227
- (标签 + 当前值 + 选项 + 可选拒绝);面板由 host 拥有。
228
- - `registerAutocomplete(contribution)` — 自动补全 provider,在 host 自身
229
- provider 返回 null 之后按确定性顺序咨询(逐 provider 隔离、
230
- latest-only commit)。
231
- - `registerKeybinding(contribution)` — 归一化按键 → 语义动作的绑定,
232
- 由 host 的 InputRouter(M6)路由。插件用公开的 `NormalizedKey` 形状
233
- (key + ctrl/alt/shift/super)声明按键和 host 动作列表中的语义动作
234
- (`submit-draft`、`queue-draft`、`steer-draft`、`cancel-activity`、
235
- `open-search`、`toggle-fullscreen`、`cycle-permission`)。host 统一归一化
236
- 所有终端输入(Kitty CSI-u、modifyOtherKeys、legacy 序列)——插件永远
237
- 看不到原始 escape 数据。保留的 host 生命周期按键(Ctrl+C/D/S/F/O/T/G/J、
238
- Ctrl+Enter、Enter、Esc)不可被占用;纯可打印按键永不触发绑定(输入
239
- 永远优先);绑定是非捕获的,在优先级梯子的最后触发(question、approval、
240
- overlay、编辑器之后)。动作通过 host 自己的路径执行——提交/会话安全
241
- 永不被绕过。
242
- - `registerMessageRenderer(contribution)` — TRANSCRIPT 消息渲染器(M7,
243
- chain slot):接收语义 `MessagePresentationSnapshot`(不可变;绝不是可变
244
- message 或容器),返回 `ExtensionView` 或 `undefined`(弃权 → 下一个
245
- 渲染器 → host 回退)。按 kind 限定的渲染器只作用于对应消息类型。
246
- - `registerToolRenderer(contribution)` — 工具卡片渲染器(M7,keyed slot):
247
- 从 `ToolPresentationSnapshot`(callId、toolName、status、arguments、
248
- result、expanded)呈现一个工具名的卡片;winner(最低 priority)弃权后
249
- 落到下一个渲染器,再到 host 回退。同一工具名的 priority 平局是显式
250
- 错误。渲染器绝不会卡住 transcript:抛错的渲染器被隔离、链继续,
251
- 消息缓存内嵌渲染器身份 + 注册表 revision,因此 HMR/unload 只重建
252
- 受影响的组件。
253
- - `showOverlay(view, options)` — 受管 overlay 租约(M8):插件提供
254
- `ExtensionView` + 尺寸提示;host 通过其 overlay broker 挂载(模态堆叠、
255
- 焦点、fullscreen 迁移)。返回的租约是 generation 限定的(表面最终
256
- dispose 会关闭所有仍在持有的租约),close() 幂等,hide()/show() 切换
257
- 可见性而不关闭。插件永远不能挂载原始组件或抢焦点——终端和 overlay
258
- 栈由 host 拥有。
259
- [扩展 API v1 作者指南](docs/extension-api.md)记录了导入规则、完整
260
- surface 表、生命周期/渲染契约、M11 弃用策略与稳定性契约。
261
-
262
- M10 验收 fixture(计划 §15):仓库附带一个 vim-mode fixture
263
- (`test/fixtures/vim-plugin/`),用于验证编辑器扩展接缝——第三方 Cordis
264
- 插件可消费打包后的公开 SDK,其替换编辑器通过**语义化** `EditorInputEvent`
265
- 接收输入(绝不接触原始终端字节),编辑器的 `create()`/`dispose()` 正常
266
- 工作——只导入 `@xmoon76/dsh-pi-tui/extensions`。它**不是**生产级 Vim,
267
- 也**不是** Stable API 完整性的证明:模态模式行为(insert/normal)不属于
268
- Stable 契约,其余公开能力(命令、主题、设置、自动补全、按键绑定、渲染器、
269
- overlay、widget)都有各自的独立测试。它的 CI 门禁禁止 `@xmoon76/pi-tui`、
270
- `src/tui-app` 和仓库相对内部路径:如果 Stable 插件需要私有导入,说明 SDK
271
- 缺 capability(没有 `unsafeGetTuiApp()` 逃生舱)。
272
-
273
- - `registerEditor(contribution)` — 编辑器 SDK(M9,计划 §14):按 priority
274
- 单选(平局是显式错误);winner 通过 host 的**原子交接**占据编辑器座席
275
- (create → 转移 draft/cursor → mount → focus → dispose 旧编辑器)。
276
- create 抛错时当前编辑器继续工作;winner 卸载恢复下一个 winner / host
277
- 默认编辑器,**保留 draft**。插件编辑器收到 `EditorHost`(surfaceId、
278
- generation、getSnapshot、replaceText、语义动作 dispatch submit/
279
- queue-submit/steer/open-external-editor、subscribe、invalidate)——但
280
- host 仍然拥有 busy-Enter、Ctrl+Enter、local 命令分类、粘贴保护、
281
- approval/question 捕获、会话 guard/lock、外部编辑器和退出:插件编辑器
282
- 永远不能绕过这些。
283
-
284
- 生命周期由 host 拥有:插件 Cordis fiber 卸载(HMR、禁用)时注册自动清理,
285
- regular/fullscreen 都会刷新,`handle.invalidate()/replace()` 通过活动屏幕
286
- 重渲染。`@xmoon76/dsh-pi-tui/builtins` 入口是仅 Loader 使用的一方贡献者
287
- (版本徽标、轮次/步骤计数器、todo 摘要 dock 项)——不是稳定的第三方
288
- SDK。原始终端访问、pre-host 输入拦截与完整输入所有权**不属于 Stable
289
- 层级**(见 Advanced/Unstable 路线图)。
290
-
291
- ## 斜杠命令(节选)
292
-
293
- - `/sessions [query]` — 打开会话选择器:对会话 id、标题和工作区进行
294
- 边输入边搜索,行按工作区分组并实时显示 `filtered/total` 计数,标题在
295
- 后台按需加载。回车切换到所选会话。
296
- - `/search <query>` — 在持久化的会话日志中全文搜索,然后跳转到命中项。
297
- - `/title [title]` — 带参数时设置当前会话的标题(固定标题,防止自动
298
- 生成;标题会出现在 `/sessions` 选择器中);**不带参数时根据对话重新
299
- 生成标题——这会覆盖当前标题,包括你之前固定的标题**(`/rename` 是
300
- 别名)。
301
- - `/tasks` — 合并任务浏览器:后台 job 与子代理在同一个可搜索列表中
302
- (直接输入过滤行——`subagent`、`bash`、`failed`…)。子代理行把 mode
303
- 写进主标签(`subagent · <label> · continuable` / `… · one-shot`),
304
- 按下 `Enter` 之前就能知道查看器是否可交互;durable 后代树以缩进行
305
- 渲染(`├─` 按深度缩进),子代理的子代理无需进入父节点就能在这里
306
- 看到。`Enter` 打开详情(子代理为子查看器——嵌套行只读——job 为
307
- 状态查看器),`i` 中断选中的子代理。`/subagents` 是别名。
308
- - `/yolo` — 切换到 `danger-full-access`(`/permission danger-full-access`
309
- 的别名)。
310
- - `/status` — 显示当前会话的统计与身份信息(回合数、token 用量、
311
- 工作区、已安装的 dsh 版本)。
312
- - `/rewind` — 从更早的用户回合 fork 当前对话:选择器列出每个已完成
313
- 的用户提示(最新在上),选中一个会创建一个新的子会话,其历史恰好
314
- 止于该回合之前,并把选中的提示回填到编辑器供修改。原会话从不被
315
- 修改,仍可通过 `/sessions` 回到;**工作区与外部副作用(文件、shell、
316
- API)不会回滚。** 空编辑器下按 `Esc Esc` 也会打开同一个选择器。
317
- - `/preset`、`/model`、`/settings`、`/export`、`/fork` —
318
- 见 `dsh --profile pi-tui` 的命令自动补全(`/` + Tab)。
319
-
320
- ## 快捷键(节选)
321
-
322
- - `Esc Esc` — 对话 rewind(空闲且编辑器为空时):打开 rewind 选择器,
323
- 从更早的用户回合 fork。agent 忙碌时按一次 `Esc` 仍只取消当前
324
- 回合/工具/shell 命令;草稿非空时双击 Esc 保持原有的取消语义。
325
- - `Ctrl+F` — 切换转录搜索(`/search <query>` 覆盖层;再按一次关闭)。
326
- - `Shift+Tab` — 循环切换权限预设(read-only → workspace-write →
327
- danger-full-access);页脚的模式槽位会为每个预设显示徽章
328
- (`[workspace-write]` / `[read-only]` / `[custom]`,`[yolo]` 标记免审批
329
- 模式)。
330
- - `Ctrl+S` — steer:有排队消息时,把整个队列(加上草稿,若有)一次性
331
- 送入正在运行的回合;否则只发送草稿。空闲的 agent 会用全部内容开启
332
- 新回合。
333
- - `Alt+↑` — 出队:把所有排队消息拉回编辑器草稿。
334
- - `Ctrl+O` — 展开/折叠最近回合的可折叠条目(工具、system 卡片);
335
- 同一个主开关也展开本地 `!`/`!!` shell 卡片(默认折叠——见下文
336
- "本地 shell")。在 Focus 模式(常规表面)下它派生展开最近几个
337
- Thought 的完整过程;全屏 Focus 的二级卡片由鼠标控制,Ctrl+O 不会
338
- 展开它们。Thinking 的 detail 有自己的拥有者——见 `Alt+T`。
339
- - `Alt+T` — 折叠/展开所有 Thinking 块(折叠:最新一行推理作为
340
- preview;展开:完整推理正文)。Thinking 块是 disclosure,不是
341
- visibility:只要模型产生过 reasoning 且当前 projection 包含该块,
342
- 它就始终存在——Focus 开/关、两种表面完全一致。全屏下点击可在该
343
- bulk 级别之上单独切换单个 Thinking 卡片;`Ctrl+O` 绝不触碰
344
- Thinking detail(它只拥有 tool/system 卡片)。`/settings` 的
345
- `Thinking detail` 与它共享同一状态。
346
- - `Alt+K` — 从实时视图快速清除**已结束**的本地 `!`/`!!` shell 卡片;
347
- **运行中**的卡片绝不会被清除,进程也不会被取消(那是 Esc 的职责)。
348
- 见下文"本地 shell"。
349
- - `Ctrl+T` — 切换完整待办列表(全屏时点击面板可在完整列表与折叠之间
350
- 切换);编辑器上方的 dock 始终显示待办摘要与后台任务,排队的输入渲染
351
- 在两者之间。
352
- - `@` — 编辑器中的文件/文件夹提及:`@` + Tab 从整个工作区补全文件
353
- (`fd` 在 PATH 上时以其为后端,否则使用内置的递归回退)。字面
354
- `@path` 会被提交,由模型自行读取文件。有后台工作时,空编辑器的 `↓`
355
- 会打开与 `/tasks` 相同的合并任务浏览器:
356
- - **子代理行**(durable 后代树,标签携带目录 mode:`continuable` 或
357
- `one-shot`,按深度缩进并带 `├─` 连接符——子代理的子代理在这里
358
- 直接可见,顺序为稳定的 pre-order)——`Enter` 打开子代理查看器:
359
- **直接(深度 1)continuable** 子代理的查看器是**可交互的**——输入
360
- 后续消息并回车,它会作为子代理的**下一回合**通过
361
- `ctx.subagents.followup` 投递(FIFO——运行中的子代理不会被中断或
362
- steer;非活跃的子代理会自动冷恢复),`Esc` 返回主会话。**one-shot**
363
- 子代理的查看器保持**只读**,任何**嵌套(深度 > 1)**后代也一样
364
- ——即使它是 continuable(`<mode> · nested · read-only from this
365
- parent`,始终显示真实 mode):根会话只能继续自己的直接子代理。已结束的 one-shot
366
- 子代理仍留在浏览器里——`inactive` 只是 live-store 的存在状态,
367
- 不是结果——Enter 依然能打开其持久化 transcript。查看器打开期间,
368
- 页脚切换为**子代理自己的身份**(`[subagent · continuable]` 徽章、
369
- 标签、activity、cwd 与子代理自己的 turns/steps/stats),退出后恢复
370
- 主会话页脚。`i` 中断选中的子代理。它们从不注册 job 记录,所以此
371
- 浏览器是它们唯一可一览的归宿。
372
- - **job 行**(bash 与一次性子代理 job)——`Enter` 只显示状态查看器:
373
- bash job 的输出读取游标属于模型的 `job_output`,而一次性子代理 job
374
- 记录不带子会话 id,因此子代理 job 的转录需通过 `/tasks` 按标签
375
- 挑选子代理访问。
376
- 只要有后台工作处于活动状态,页脚徽章就会显示
377
- `[N tasks running · M agents · ↓ view]`。编辑器上方的队列窗格显示
378
- 待处理输入:用户消息为 `❯` 行,其余(任务通知、子代理汇报、注入的
379
- 指令)为 `⏳` 通知行,超过五条折叠为 `+N more`——并在主 agent 接收
380
- 后消失。
381
-
382
- ## 启动选项
383
-
384
- TUI 的启动行新增了 `--preset <id>`——新会话启动时使用的 agent 预设
385
- (回退到 `$DSH_PI_TUI_PRESET`,然后是保存的设置默认值)。它存在的原因是
386
- `/preset` 只对空白(尚未创建)会话生效,所以启动时选择是选择预设的
387
- 另一半。其余标志都是 dsh runner 自有的(`--session <id>`,……)。
388
-
389
- ## 会话生命周期
390
-
391
- 不带 `--session` 打开 TUI **不会创建任何会话**:第一条用户消息(文本、
392
- 斜杠命令、`Ctrl+S` steer 或 `!` shell)才会惰性启动会话。`--session <id>`
393
- 仍然立即恢复会话,本地 `!!` 命令无需会话即可运行。
394
-
395
- ## 本地 shell(`!` / `!!`)
396
-
397
- `! command` 在本地运行命令,并把完成后的命令 + 输出作为普通用户消息
398
- 提交给会话;`!! command` 完全本地运行——卡片是唯一记录(绝不发给模型)。
399
- 卡片**默认折叠**,长日志不会塞满 TUI:运行中的卡片显示最新 5 行,
400
- 已结束的卡片最多显示 20 个视觉行(超长行会换行并按多行计),各自带
401
- 诚实的隐藏行数标记。`Ctrl+O`(与折叠最近工具回合的同一个主开关)把
402
- 卡片展开到 retained buffer——命令仍在跑时实时跟随;`Alt+K` 快速清除
403
- **已结束**的卡片(运行中的卡片绝不会被清除,进程不会被取消,已经
404
- 提交的 `!` context payload 不受影响)。
405
-
406
- ## 在 P0 spike 中验证过的事项
407
-
408
- - 内置 pi-tui:fork 自带的套件在 `node --test` 下通过(每次重新
409
- vendor 后把它作为同步门禁运行;计数特意不抄在这里——
410
- `packages/pi-tui/package.json` 是版本事实的唯一来源)。
411
- - `TuiApp` 能在 headless xterm 上渲染、接受编辑器输入并处理 Ctrl+C。
412
- - 整个导入链(pi-tui、tui-app、`@deepseek-ai/dsh-cmdline`、commander)
413
- 能在 tsx ESM 钩子下加载——即 dsh 源码启动契约。
414
- - 原生修饰键扩展是可选的:在 Linux 上加载器返回 `undefined` 而不尝试
415
- 加载,非 TTY 的 stdin 路径有守卫。
416
-
417
- ## 诊断日志
418
-
419
- TUI 把自身诊断写到 stderr 和一个日志文件(`ctx.logger` 在此进程中不可见
420
- ——没有 exporter):
421
-
422
- - 默认文件:`$DSH_HOME/logs/pi-tui-<pid>.log`(默认 `~/.dsh/logs/`);
423
- - 行格式:`[tui] <ISO time> <level> <message> k=v ...`;
424
- - 默认级别 `info`:只记录关键生命周期事件(启动/恢复/切换/退出、
425
- 跨进程 guard 警告、错误);`debug` 额外记录每次发送前的 guard 检查。
426
-
427
- 配置(环境变量):
428
-
429
- | 变量 | 含义 | 默认值 |
430
- |---|---|---|
431
- | `DSH_PI_TUI_LOG` | 日志文件路径;`off` 关闭文件日志 | `$DSH_HOME/logs/pi-tui-<pid>.log` |
432
- | `DSH_PI_TUI_LOG_LEVEL` | `debug` / `info` / `warn` / `error` | `info` |
433
-
434
- 排障示例:
435
-
436
- ```sh
437
- DSH_PI_TUI_LOG_LEVEL=debug dsh --profile pi-tui
438
- tail -f ~/.dsh/logs/pi-tui-*.log
439
- ```
440
-
441
- ## 安全与运维说明
442
-
443
- - **每个会话只有一个界面。** dsh 没有跨进程的会话协调:一个会话同时
444
- 在两个 dsh 进程中打开(TUI + web,或两个 TUI)会损坏其日志。TUI 会
445
- 拒绝打开已被另一个存活 dsh 进程持有的会话(日志旁的 `owner.lock`
446
- 文件,带 pid/starttime 探测以处理崩溃残留的陈旧锁——第二个界面在
447
- **打开时**就被阻止,而不是在损坏发生之后)。对于写入,TUI 会检测到
448
- 另一个写入者并阻止发送;再次按下同一个操作(Enter 提交、Ctrl+S
449
- steer,草稿不变)会强制通过——编辑过的草稿、换过的按键、新的文件
450
- 修订或会话切换都会使强制失效。绝不要在一个会话上运行两个界面
451
- (完整契约:`docs/concurrency.md`)。
452
- - **会话修复。** `node_modules/@xmoon76/dsh-pi-tui/scripts/repair-session.mjs`
453
- 修复损坏的日志(`--scan` 只读列出损坏;`--yes` 应用修复并强制备份)。
454
- 撕裂(截断)的尾部会在最后一个完整帧处截断,并报告精确的字节核算;
455
- 指向重复 seq 的引用绝不自动解决——修复会拒绝并要求
456
- `--duplicate-reference=first|last|segment`。修复后的日志在备份被认为
457
- 多余之前,会用 dsh 读取器自身的布局检查重新验证。(完整修复契约,
458
- 包括帧布局约束:`docs/repair-session.md`。)
459
- - **退出。** `/exit`(`/quit` 的别名)以 10 秒硬超时刷新会话:卡死的
460
- 提供方无法困住 TUI。如果刷新失败或超时,终端会打印警告(尾部可能
461
- 未持久化),进程仍然退出。
462
- - **性能。** `scripts/bench.mts`(非默认)测量摄取、投影、冷/热重建、
463
- 流式帧、主题切换与堆;保存的基线见 `docs/perf-baseline.md`。未变化的
464
- 转录消息会复用其渲染组件,因此热路径的每帧重建不会随历史增长。
465
-
466
- ## 许可证
467
-
468
- MIT。`packages/pi-tui` 保留其上游 MIT 许可证与作者署名
469
- (Copyright (c) 2025 Mario Zechner;Moonshot AI fork)。