@aiwayds/dsh-tui-pi 0.4.2 → 0.5.0
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.md +270 -420
- package/README.zh.md +306 -0
- package/cordis.patch.yml +4 -11
- package/lib/commands.d.ts +12 -0
- package/lib/commands.js +62 -9
- package/lib/commands.js.map +1 -1
- package/lib/index.js +104 -5
- package/lib/index.js.map +1 -1
- package/lib/messages.d.ts +10 -2
- package/lib/messages.js +16 -5
- package/lib/messages.js.map +1 -1
- package/lib/selectors.d.ts +13 -0
- package/lib/selectors.js +48 -0
- package/lib/selectors.js.map +1 -1
- package/lib/session.d.ts +19 -0
- package/lib/session.js +81 -0
- package/lib/session.js.map +1 -1
- package/lib/settings.d.ts +18 -1
- package/lib/settings.js +343 -7
- package/lib/settings.js.map +1 -1
- package/lib/skills.d.ts +225 -0
- package/lib/skills.js +426 -0
- package/lib/skills.js.map +1 -0
- package/package.json +2 -1
package/README.zh.md
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# dsh-tui-pi
|
|
2
|
+
|
|
3
|
+
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) 的 pi 风格终端 UI 插件 —— 把 dsh 变成 pi 风格的编码代理体验。
|
|
4
|
+
|
|
5
|
+
> English version: [README.md](README.md)
|
|
6
|
+
|
|
7
|
+
## 截图
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
真实会话的终端录制——Todos、运行中的 subagent、思考/工具面板和 powerline footer 一览。([asciinema 交互播放](https://asciinema.org/a/BE212ZO8x1zEZyZn))
|
|
12
|
+
|
|
13
|
+
### 布局总览
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
17
|
+
│ 对话区(可滚动) │
|
|
18
|
+
│ ┌─────────────────────────────────────────────────────────────┐ │
|
|
19
|
+
│ │ 💭 thinking — 推理进行中 │ │
|
|
20
|
+
│ └─────────────────────────────────────────────────────────────┘ │
|
|
21
|
+
│ ⚙ bash python scripts/demo.py … ✔ bash │
|
|
22
|
+
│ ↳ 生成 2 个 todo, 每个 todo 起一个 10s 的 subagent │
|
|
23
|
+
│ ↳ ⠼ Workhorse 10s 任务 · 1.2k token · 19.0s │
|
|
24
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
25
|
+
┌─ ● Todos (0/8) ────────────────────────────────────────────────────┐
|
|
26
|
+
│ ├─ ☑ 调研 dsh-tui-pi 斜杠命令/补全机制 │
|
|
27
|
+
│ ├─ ◐ 调研 harness ctx.skills API │
|
|
28
|
+
│ └─ ☐ 实现 /skill:<name> 补全并触发 skill │
|
|
29
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
30
|
+
∴ working… │
|
|
31
|
+
~/github (Full access) │ ⎇ main │
|
|
32
|
+
[ 请输入指令… ] │
|
|
33
|
+
↳ 第一 打slash 命令的时候 显示 /skill:<skill name> 选择后使用 │
|
|
34
|
+
↳ ⠼ 牛马狗 · 1.5m/1m · 635.7s │
|
|
35
|
+
dsh ▸ volc-ark-plan ▸ deepseek-v4-flash ▸ high ▸ 48.7k/1.0M(4.6%) │
|
|
36
|
+
▸ ⚡ CH85.4% ▸ 15 msgs ▸ 11 tools 00:02:13 │
|
|
37
|
+
Esc ×2: stop · Ctrl+C ×2: quit · Ctrl+G: subagents · ↑↓: history │
|
|
38
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
39
|
+
│ │ │
|
|
40
|
+
│ │ └─ Footer(状态栏)
|
|
41
|
+
│ └─ 运行中的 subagent(last-request 区域)
|
|
42
|
+
└─ Todos 面板(有边框,固定在输入框上方)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 功能特性
|
|
48
|
+
|
|
49
|
+
### Footer 状态栏
|
|
50
|
+
|
|
51
|
+
底部固定显示当前会话的实时状态:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
dsh ▸ volc-ark-plan ▸ deepseek-v4-flash ▸ high ▸ 48.7k/1.0M(4.6%) ▸ ⚡ CH85.4% ▸ 15 msgs ▸ 11 tools 00:02:13
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
七个分段,全部从 O(1) 维护的计数器读取(从不扫描会话日志):
|
|
58
|
+
|
|
59
|
+
| 分段 | 内容 |
|
|
60
|
+
|---|---|
|
|
61
|
+
| **Provider** | 当前 `provider/model` 路由 |
|
|
62
|
+
| **Model** | 模型简称 |
|
|
63
|
+
| **Thinking** | 推理强度等级(`off` / `high` / `max`) |
|
|
64
|
+
| **Context** | `已用 / 上限 (百分比%)` |
|
|
65
|
+
| **Cache-hit** | `CHxx%` —— prompt 缓存命中率 |
|
|
66
|
+
| **Messages** | 用户 + 助手消息总数 |
|
|
67
|
+
| **Tools** | 工具调用总数 |
|
|
68
|
+
| **Clock** | 右对齐实时 HH:MM:SS(每秒刷新) |
|
|
69
|
+
|
|
70
|
+
分段用 [U+E0B0](https://www.nerdfonts.com/cheat-sheet) powerline 箭头渲染,配色随主题切换。
|
|
71
|
+
|
|
72
|
+
编辑器顶部边框显示工作目录和 git 分支:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
~/github (Full access) │ ⎇ main
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### 思考面板 & 工具面板
|
|
81
|
+
|
|
82
|
+
运行中的思考和工具调用渲染为**固定面板,固定在输入框上方**(不会出现在可滚动的对话区):
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
┌─ 💭 thinking ──────────────────────────────────────────────┐
|
|
86
|
+
│ Actually, I can check list_agents or wait… │
|
|
87
|
+
└────────────────────────────────────────────────────────────┘
|
|
88
|
+
⚙ bash python scripts/demo.py … ✔ bash
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
行为要点:
|
|
92
|
+
|
|
93
|
+
- **每种类型一个面板** —— 整个运行期间只有一个 `ThinkPanel` 和一个 `ToolPanel`;每次事件刷新同一个面板,不会产生对话区刷屏。
|
|
94
|
+
- **空 = 隐藏** —— 无活动时面板渲染 0 行并消失。
|
|
95
|
+
- **`dsh-tui.panelHeight`**(默认 `1`):1 行无边框(块标识 + 耗时 + 最后一行内容,右截断);`5`/`7`/`10` 带边框面板;`all` 输出全部内容。
|
|
96
|
+
- **委派工具**(`use_agent`、`subagent`、`workflow`、`ralph`)不打开工具面板 —— 它们的子进程在底部的运行子代理行中显示。
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### Subagents 子代理
|
|
101
|
+
|
|
102
|
+
运行中的子代理在**编辑器下方的 last-request 区域**显示为每行一条的紧凑状态:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
↳ 创建 2 个 todo, 每个 todo 起一个 10s 的 subagent
|
|
106
|
+
↳ ⠼ Subagent A 10s 任务 · 1.2k token · 19.0s
|
|
107
|
+
↳ ⠼ Subagent B 10s 任务 · 562 token · 6.0s
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
每行显示:spinner + 代理**名称**,重试次数(`↻N≤M`),token 数(+ 上下文占比),耗时。不显示 provider,无边框,无标题。
|
|
111
|
+
|
|
112
|
+
#### Todos 待办面板
|
|
113
|
+
|
|
114
|
+
`● Todos (done/total)` 树是一个有边框的面板,**固定在输入框上方**(不随对话区滚动):
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
┌─ ● Todos (0/8) ──────────────────────────────────────────┐
|
|
118
|
+
│ ├─ ☑ 调研 dsh-tui-pi 斜杠命令/补全机制 │
|
|
119
|
+
│ ├─ ◐ 调研 harness ctx.skills API │
|
|
120
|
+
│ └─ ☐ 实现 /skill:<name> 补全并触发 skill │
|
|
121
|
+
└───────────────────────────────────────────────────────────┘
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
图标:`☑` 已完成,`◐` 进行中,`☐` 待处理。子代理完成后从列表消失;当面板和子代理行都为空时,整个区域折叠隐藏。
|
|
125
|
+
|
|
126
|
+
#### 子代理查看器 & 限制
|
|
127
|
+
|
|
128
|
+
`Ctrl+G`(或 `/subagents`)打开 80% 宽度的子代理选择器 —— 运行中的排在前面,然后是最近完成的 5 个。Enter 打开实时对话查看器(~3×/s 刷新,自动跟随尾部)。
|
|
129
|
+
|
|
130
|
+
两个限制项(通过 `/agents` → `l` 配置):
|
|
131
|
+
|
|
132
|
+
- **`maxAgents`**(默认 4,`0` = 无限制)—— 超过上限时拒绝新的子代理创建。
|
|
133
|
+
- **`maxRounds`**(默认 50,`0` = 无限制)—— 子代理完成轮次达到上限后,TUI 排队发送一个收尾请求,从不强制终止。
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
### DCP 动态上下文裁剪
|
|
138
|
+
|
|
139
|
+
[DCP](https://github.com/fan56/dsh-dcp) 是 dsh 的零 LLM 上下文裁剪插件 —— 自动修剪上下文以保持在限制内,无需调用 LLM 做摘要。
|
|
140
|
+
|
|
141
|
+
`dsh-tui-pi` 将 `@aiwayds/dsh-dcp` 列为依赖,但**不自动挂载** —— dsh-dcp 自带 `cordis.patch.yml`(自 `@aiwayds/dsh-dcp@0.2.0` 起)。要启用:
|
|
142
|
+
|
|
143
|
+
```sh
|
|
144
|
+
dsh plugin --profile tui add @aiwayds/dsh-dcp
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
挂载后 DCP 在后台透明运行。Footer 中的 **Context** 和 **Cache-hit** 分段实时反映裁剪效果。
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 斜杠命令
|
|
152
|
+
|
|
153
|
+
| 命令 | 功能 |
|
|
154
|
+
|---|---|
|
|
155
|
+
| `/model` | 两阶段选择 provider/model(然后选推理等级),实时切换并持久化。 |
|
|
156
|
+
| `/think` | 当前模型的推理强度选择(`Off`/`High`/`Max`)。 |
|
|
157
|
+
| `/session` | 只读信息面板:id、cwd、模型、token 用量、事件计数。 |
|
|
158
|
+
| `/resume` | 选择已保存的会话,验证日志后恢复。 |
|
|
159
|
+
| `/new` | 分离当前会话;下一次输入开启新会话。 |
|
|
160
|
+
| `/settings` | 文本式设置浏览器(命名空间、schema 遍历、内联编辑器、密钥脱敏)。 |
|
|
161
|
+
| `/export` | 将当前会话日志导出为 JSONL(默认 `~/Downloads/dsh-session-<id>.jsonl`)。 |
|
|
162
|
+
| `/permission` | 权限预设选择器(read-only / workspace-write / danger-full-access)。 |
|
|
163
|
+
| `/theme` | 配色方案选择器(`auto` / `light` / `dark`),实时生效。 |
|
|
164
|
+
| `/agents` | 管理 agent markdown 文件 + 子代理限制(`maxAgents`、`maxRounds`)。 |
|
|
165
|
+
| `/subagents` | 选择运行中/最近的子代理,查看其实时对话。 |
|
|
166
|
+
| `/reload` | 从源码热重载插件(`pnpm build` 后执行,无需重启 dsh)。 |
|
|
167
|
+
| `/hotkeys` | 快捷键浏览器和实时编辑。 |
|
|
168
|
+
|
|
169
|
+
不是已注册命令的内容会作为普通提示词发送给模型。
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 快捷键
|
|
174
|
+
|
|
175
|
+
| 按键 | 功能 |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `Enter` | 发送提示词 |
|
|
178
|
+
| `Esc` | **双击停止** —— 单击进入等待窗口(500ms);弹窗打开时关闭弹窗;空编辑器时双击打开 `/session` |
|
|
179
|
+
| `Ctrl+C` | 对话中:第一次取消当前轮次,第二次退出。空闲时:第一次清空编辑器,第二次退出。**长按自动重复不会触发退出。** |
|
|
180
|
+
| `Ctrl+D` | 退出(仅在编辑器为空时) |
|
|
181
|
+
| `Ctrl+L` | 打开模型/推理强度选择器 |
|
|
182
|
+
| `Ctrl+G` | 打开子代理选择器(有运行中的子代理时) |
|
|
183
|
+
| `Tab` | 自动补全 |
|
|
184
|
+
| `↑` / `↓` | 浏览历史消息(shell 风格,保留 500 条) |
|
|
185
|
+
|
|
186
|
+
### 自定义快捷键
|
|
187
|
+
|
|
188
|
+
通过 `~/.dsh/keybindings.json` 重新映射任意按键 —— JSON 格式的按键映射表。可手动编辑后 `/reload`,或用 `/hotkeys` 交互式修改(实时生效,无需重启)。
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 主题
|
|
193
|
+
|
|
194
|
+
GitHub light / GitHub dark 配色方案,运行时热切换:
|
|
195
|
+
|
|
196
|
+
- `/theme` —— 实时选择器,整个屏幕重绘(含背景)。
|
|
197
|
+
- `DSH_TUI_THEME=light|dark` —— 环境变量钉选,优先于偏好设置。
|
|
198
|
+
- `DSH_TUI_TRANSPARENT=1` —— 透明画布(终端背景可见)。
|
|
199
|
+
- `auto` 模式自动检测终端并跟随实时明暗切换。
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## 安装(本地)
|
|
204
|
+
|
|
205
|
+
```sh
|
|
206
|
+
# 一步构建 + 打包 + 安装到 profile
|
|
207
|
+
node scripts/dev-install.mjs # pnpm build → pnpm pack → 刷新 profile 副本
|
|
208
|
+
|
|
209
|
+
# 或手动:
|
|
210
|
+
pnpm pack # → aiwayds-dsh-tui-pi-<version>.tgz
|
|
211
|
+
dsh plugin --profile tui add /path/to/aiwayds-dsh-tui-pi-<version>.tgz
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Profile 的 `package.json` 包含两个指向同一 tarball 的键:
|
|
215
|
+
`dsh-tui-pi`(dsh 通过此名称解析 bundle)和
|
|
216
|
+
`@aiwayds/dsh-tui-pi`(`cordis.patch.yml` 中的 loader 入口)。
|
|
217
|
+
|
|
218
|
+
## 安装(npm)
|
|
219
|
+
|
|
220
|
+
在全新 profile 里安装完整的 dsh 插件套件:
|
|
221
|
+
|
|
222
|
+
```sh
|
|
223
|
+
dsh plugin --profile tui add @aiwayds/dsh-tui-pi
|
|
224
|
+
dsh plugin --profile tui add @aiwayds/dsh-subagent-registry
|
|
225
|
+
dsh plugin --profile tui add @aiwayds/dsh-dcp
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
然后启动:
|
|
229
|
+
|
|
230
|
+
```sh
|
|
231
|
+
dsh --profile tui
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**自动完成的事:**
|
|
235
|
+
|
|
236
|
+
- dsh 将三个插件注册到 `dsh.profile.bundles`(通过 `reconcilePlugins`)。
|
|
237
|
+
- dsh 在 profile 的 `pnpm-workspace.yaml` 里设置 `autoInstallPeers: false`。
|
|
238
|
+
- 首次启动时 dsh 调用 `healProfilesModuleFallback`,在
|
|
239
|
+
`~/.dsh/profiles/node_modules/@deepseek-ai/*` 创建软链指向全局 dsh
|
|
240
|
+
闭包。所有插件共享同一个 `@deepseek-ai/cordis` 实例——无需手动建闭包。
|
|
241
|
+
- `@aiwayds/dsh-dcp` 的补丁禁用 `compaction-basic`,dsh-dcp 接管上下文压缩。
|
|
242
|
+
|
|
243
|
+
**不会自动完成的事:**
|
|
244
|
+
|
|
245
|
+
- pi-tui patchedDependencies(编辑器补全边框、SelectList 全行背景)
|
|
246
|
+
**不会**为 npm 消费者自动应用——需要在 profile 的
|
|
247
|
+
`pnpm-workspace.yaml` 里手动添加条目。这是**纯外观**问题:TUI 不打
|
|
248
|
+
补丁照样启动,只是未选中行不会渲染全行背景。
|
|
249
|
+
|
|
250
|
+
### 故障排查
|
|
251
|
+
|
|
252
|
+
| 症状 | 原因 | 修复 |
|
|
253
|
+
|---|---|---|
|
|
254
|
+
| `Cannot find package '<name>' imported from ~/.dsh/profiles/...` | 某个 bundle 的 `cordis.patch.yml` 的 `name` 字段与 scoped 包名不匹配 | 更新插件(所有 `@aiwayds/*` 插件已修复补丁 `name` 字段) |
|
|
255
|
+
| `Cannot read properties of undefined (reading 'prepare')` | profile 树里出现两个 `@deepseek-ai/cordis` 物理副本(模块重复安装) | 见 AGENTS.md 铁律 8。删掉物理副本:`rm -rf ~/.dsh/profiles/tui/node_modules/@deepseek-ai && dsh --profile tui`(dsh 会重新 heal 为软链) |
|
|
256
|
+
| pnpm 提示 `Peer dependencies that should be installed: @deepseek-ai/...` | 某个插件把 `@deepseek-ai/*` 放在 `dependencies` 而非 `peerDependencies` | 更新插件(所有 `@aiwayds/*` dsh 插件已改用 optional peerDeps),警告无害 |
|
|
257
|
+
| pnpm 提示 `Ignored build scripts: @aiwayds/dsh-tui-pi@...` | pnpm 10 默认阻止 build 脚本,tui-pi 的 postinstall 被跳过 | **正常且无害**——postinstall 只影响仓库开发流,npm 消费者由 dsh 的 `healProfilesModuleFallback` 处理闭包链接 |
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## 使用
|
|
262
|
+
|
|
263
|
+
```sh
|
|
264
|
+
dsh --profile tui # 或:dsh-tui-pi(bin shim)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## 开发
|
|
270
|
+
|
|
271
|
+
```sh
|
|
272
|
+
pnpm check # tsc --noEmit
|
|
273
|
+
pnpm build # 输出 lib/
|
|
274
|
+
pnpm test # 单元测试,node --test 对 lib/ 执行(296 个测试,pretest 自动构建)
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
本地类型检查通过 symlink `node_modules/@deepseek-ai/*` 指向已安装的 dsh 闭包(`/opt/homebrew/lib/node_modules/@deepseek-ai/dsh/node_modules`);这些 symlink 不会打入 tarball。`scripts/link-dsh-closure.mjs`(`postinstall`)在每次 `pnpm install` 后重新创建所有 symlink。
|
|
278
|
+
|
|
279
|
+
**pi-tui 补丁**:对 `@earendil-works/pi-tui` 0.84.2 的一个小补丁(`pnpm-workspace.yaml`,补丁文件在 `patches/`)添加了 `unselectedText` 和 `selectedPrefix` SelectListTheme 钩子以及编辑器自动补全边框。
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## 目录结构
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
bin/dsh-tui-pi 启动器 shim(执行 dsh --profile tui)
|
|
287
|
+
cordis.patch.yml bundle 补丁:将插件挂载为 `tui-pi`
|
|
288
|
+
src/
|
|
289
|
+
index.ts cordis 插件入口:命令注册、footer、git 监控、时钟、bridge、主题热切换、关闭
|
|
290
|
+
tui.ts alt-screen 树、对话区 ScrollView、dock、canvas 背景
|
|
291
|
+
session.ts DshSessionBridge:agent 创建、followup、resume、O(1) 增量统计、子代理追踪
|
|
292
|
+
live-widgets.ts Todos 面板 + 运行子代理活动行
|
|
293
|
+
messages.ts TranscriptRenderer:会话事件 → pi-tui 组件、流式 setText、可配置高度面板
|
|
294
|
+
footer.ts PowerlineFooter(7 分段 + 时钟)
|
|
295
|
+
editor.ts CwdBorderEditor(顶部边框:cwd + git 分支)
|
|
296
|
+
subagent-policy.ts maxAgents 守卫 + maxRounds 收尾请求注入
|
|
297
|
+
subagent-viewer.ts Ctrl+G 选择器 + 实时对话面板
|
|
298
|
+
theme/ GitHub light/dark 配色 + 终端检测
|
|
299
|
+
test/*.test.mjs 单元测试(296 个,覆盖 24 个文件)
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## 更新日志
|
|
305
|
+
|
|
306
|
+
见 [CHANGELOG.md](CHANGELOG.md)。
|
package/cordis.patch.yml
CHANGED
|
@@ -4,18 +4,11 @@
|
|
|
4
4
|
# directly (ctx.agents / ctx.commands / session events), keeping dsh's slash
|
|
5
5
|
# commands untouched.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
7
|
+
# dsh-dcp is NOT mounted here: it is a dependency of this package and ships
|
|
8
|
+
# its own bundle patch (@aiwayds/dsh-dcp/cordis.patch.yml). Adding dsh-dcp to
|
|
9
|
+
# the profile's bundles mounts it; mounting it here too would duplicate the
|
|
10
|
+
# entry id and crash the loader.
|
|
11
11
|
|
|
12
|
-
- id: compaction-basic
|
|
13
|
-
disabled: true
|
|
14
12
|
- insert:
|
|
15
13
|
- id: tui-pi
|
|
16
14
|
name: '@aiwayds/dsh-tui-pi'
|
|
17
|
-
- id: dsh-dcp
|
|
18
|
-
name: '@aiwayds/dsh-dcp'
|
|
19
|
-
config:
|
|
20
|
-
thresholdRatio: 0.7
|
|
21
|
-
language: zh
|
package/lib/commands.d.ts
CHANGED
|
@@ -50,4 +50,16 @@ export declare class CommandService {
|
|
|
50
50
|
list(): Promise<readonly CommandDescriptor[]>;
|
|
51
51
|
/** pi-tui autocomplete provider over the live command registry. */
|
|
52
52
|
autocompleteProvider(): AutocompleteProvider;
|
|
53
|
+
/**
|
|
54
|
+
* The live (optional) `ctx.skills` registry's user-invocable summaries,
|
|
55
|
+
* scoped to the current agent/cwd. An absent or failing service yields none.
|
|
56
|
+
*/
|
|
57
|
+
private listSkills;
|
|
58
|
+
/**
|
|
59
|
+
* Explicit `/skill:<name>` completion candidates for a `/skill:<prefix>`
|
|
60
|
+
* query. Only user-invocable skills appear.
|
|
61
|
+
*/
|
|
62
|
+
private skillCandidates;
|
|
63
|
+
/** Native `/name` skill candidates for the mixed `/` command list. */
|
|
64
|
+
private nativeSkillCandidates;
|
|
53
65
|
}
|
package/lib/commands.js
CHANGED
|
@@ -15,13 +15,16 @@
|
|
|
15
15
|
* and would mint a throwaway session just to run them.
|
|
16
16
|
*/
|
|
17
17
|
import { parseCommand } from '@deepseek-ai/dsh-commands';
|
|
18
|
+
import { buildNativeSkillCandidates, buildSkillCompletionCandidates, completionLabel, isExplicitSkillItem, mergeMixedSkillItems, skillCompletionQuery, } from "./skills.js";
|
|
18
19
|
/**
|
|
19
20
|
* Token ending at the cursor on one editor line, for command detection.
|
|
20
|
-
* Returns the leading slash token when the caret sits inside one.
|
|
21
|
+
* Returns the leading slash token when the caret sits inside one. The token
|
|
22
|
+
* charset admits `:` (after the first letter) so the TUI's `/skill:<name>`
|
|
23
|
+
* form reads as a single token and drives skill completion.
|
|
21
24
|
*/
|
|
22
25
|
function tokenAtCursor(line, cursorCol) {
|
|
23
26
|
const upto = line.slice(0, cursorCol);
|
|
24
|
-
const match = /(^|\s)\/([a-z][a-z0-9_
|
|
27
|
+
const match = /(^|\s)\/([a-z][a-z0-9_:-]*)?$/u.exec(upto);
|
|
25
28
|
if (match === null)
|
|
26
29
|
return undefined;
|
|
27
30
|
const start = match.index + (match[1] ?? '').length;
|
|
@@ -130,17 +133,34 @@ export class CommandService {
|
|
|
130
133
|
// Only complete the command name itself (leading token, no arguments yet).
|
|
131
134
|
if (at.start !== 0 && line.slice(0, at.start).trim() !== '')
|
|
132
135
|
return null;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
// A `/skill` or `/skill:<prefix>` token yields the explicit
|
|
137
|
+
// `/skill:<name>` candidates (a dedicated list, not the mixed one).
|
|
138
|
+
// skillCompletionQuery strips the token's leading `/`, so the
|
|
139
|
+
// canonical `/skill:da` token from tokenAtCursor matches here.
|
|
140
|
+
const skillQuery = skillCompletionQuery(at.token);
|
|
141
|
+
if (skillQuery !== undefined) {
|
|
142
|
+
const skillItems = await this.skillCandidates(skillQuery);
|
|
143
|
+
if (skillItems.length === 0)
|
|
144
|
+
return null;
|
|
145
|
+
return { items: skillItems, prefix: at.token };
|
|
146
|
+
}
|
|
147
|
+
// Generic `/` completion: commands and the user skills' native `/name`
|
|
148
|
+
// rows in one mixed list, sorted by display name and filtered by the
|
|
149
|
+
// token after the slash. This keeps skills interleaved with commands
|
|
150
|
+
// (never grouped under their `/skill:` prefix, which would cluster all
|
|
151
|
+
// of them in a single `s` bucket).
|
|
136
152
|
const query = at.token.slice(1).toLowerCase();
|
|
137
|
-
const
|
|
153
|
+
const descriptors = await this.list();
|
|
154
|
+
const commandItems = descriptors
|
|
138
155
|
.filter(d => query === '' || d.name.toLowerCase().startsWith(query))
|
|
139
156
|
.map(d => ({
|
|
140
157
|
value: `/${d.name}`,
|
|
141
|
-
label: `/${d.name}
|
|
158
|
+
label: completionLabel('command', `/${d.name}`),
|
|
142
159
|
description: d.description,
|
|
160
|
+
kind: 'command',
|
|
143
161
|
}));
|
|
162
|
+
const nativeSkillItems = await this.nativeSkillCandidates();
|
|
163
|
+
const items = mergeMixedSkillItems(commandItems, nativeSkillItems, query);
|
|
144
164
|
if (items.length === 0)
|
|
145
165
|
return null;
|
|
146
166
|
return { items, prefix: at.token };
|
|
@@ -152,16 +172,49 @@ export class CommandService {
|
|
|
152
172
|
return { lines, cursorLine, cursorCol };
|
|
153
173
|
const before = line.slice(0, at.start);
|
|
154
174
|
const after = line.slice(cursorCol);
|
|
155
|
-
|
|
175
|
+
// A completed explicit `/skill:<name>` is a full invocation — the
|
|
176
|
+
// trailing-space separator (which readies a command's arguments)
|
|
177
|
+
// would just be noise, so it is inserted exactly with the cursor at
|
|
178
|
+
// the end. Native `/name` skills and commands keep the space.
|
|
179
|
+
const isExplicit = isExplicitSkillItem(item);
|
|
180
|
+
const completed = `${before}${item.value}${isExplicit ? '' : ' '}${after}`;
|
|
156
181
|
const nextLines = lines.slice();
|
|
157
182
|
nextLines[cursorLine] = completed;
|
|
158
183
|
return {
|
|
159
184
|
lines: nextLines,
|
|
160
185
|
cursorLine,
|
|
161
|
-
cursorCol: before.length + item.value.length + 1,
|
|
186
|
+
cursorCol: before.length + item.value.length + (isExplicit ? 0 : 1),
|
|
162
187
|
};
|
|
163
188
|
},
|
|
164
189
|
};
|
|
165
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* The live (optional) `ctx.skills` registry's user-invocable summaries,
|
|
193
|
+
* scoped to the current agent/cwd. An absent or failing service yields none.
|
|
194
|
+
*/
|
|
195
|
+
async listSkills() {
|
|
196
|
+
const skills = this.ctx.get('skills');
|
|
197
|
+
if (skills === undefined)
|
|
198
|
+
return [];
|
|
199
|
+
const agent = this.bridge.getAgent();
|
|
200
|
+
const cwd = agent?.session.header.cwd ?? process.cwd();
|
|
201
|
+
try {
|
|
202
|
+
return await skills.list({ scope: agent, cwd });
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Explicit `/skill:<name>` completion candidates for a `/skill:<prefix>`
|
|
210
|
+
* query. Only user-invocable skills appear.
|
|
211
|
+
*/
|
|
212
|
+
async skillCandidates(afterColon) {
|
|
213
|
+
return buildSkillCompletionCandidates(await this.listSkills(), afterColon);
|
|
214
|
+
}
|
|
215
|
+
/** Native `/name` skill candidates for the mixed `/` command list. */
|
|
216
|
+
async nativeSkillCandidates() {
|
|
217
|
+
return buildNativeSkillCandidates(await this.listSkills());
|
|
218
|
+
}
|
|
166
219
|
}
|
|
167
220
|
//# sourceMappingURL=commands.js.map
|
package/lib/commands.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,EAAE,YAAY,EAA8C,MAAM,2BAA2B,CAAA;
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
|
package/lib/index.js
CHANGED
|
@@ -19,7 +19,8 @@ import { ensureAppendSystemFile, dshHome, migrateAgentsMdTodoSection } from "./a
|
|
|
19
19
|
import { TranscriptRenderer } from "./messages.js";
|
|
20
20
|
import { AGENT_TICK_MS, LiveWidgets } from "./live-widgets.js";
|
|
21
21
|
import { displayPermissionPreset } from "./permission.js";
|
|
22
|
-
import { pickEffort, pickModel, pickPermission, pickTheme } from "./selectors.js";
|
|
22
|
+
import { pickEffort, pickModel, pickPermission, pickSkill, pickTheme } from "./selectors.js";
|
|
23
|
+
import { parseSkillCommand, skillGesture } from "./skills.js";
|
|
23
24
|
import { DshSessionBridge, persistDefaultModel, stashSessionIdForReload, takeStashedSessionId } from "./session.js";
|
|
24
25
|
import { currentThemePreference, readFooterHintsPreference, readPanelHeightPreference, readThemePreference, registerThemeSettings, writeThemePreference, } from "./theme-settings.js";
|
|
25
26
|
import { openAgentManager } from "./agents.js";
|
|
@@ -480,6 +481,26 @@ export function apply(ctx) {
|
|
|
480
481
|
description: 'Browse subagents and inspect their live transcript',
|
|
481
482
|
handler: invocation => subagentsHandler(invocation.rawInput, invocation.signal),
|
|
482
483
|
}), 'dsh-tui-pi: /subagents');
|
|
484
|
+
// /skill: invoke a user skill. Skills are not commands (there is no
|
|
485
|
+
// ctx.skills enable/disable runtime API), so the TUI owns the surface:
|
|
486
|
+
// `/skill:data-analysis` and the bare `/skill` picker both dispatch the
|
|
487
|
+
// harness's own `/name ` gesture through the normal prompt path (see
|
|
488
|
+
// invokeSkill below). The command is registered in both channels for
|
|
489
|
+
// discoverability/parity; submit() intercepts skill lines before the
|
|
490
|
+
// generic dispatch (the colon form is not a valid command name).
|
|
491
|
+
const skillHandler = (rawInput, signal) => {
|
|
492
|
+
void runSkillCommand(`/skill${rawInput ?? ''}`, signal);
|
|
493
|
+
return { kind: 'success', text: undefined };
|
|
494
|
+
};
|
|
495
|
+
commands.registerLocal('skill', skillHandler);
|
|
496
|
+
ctx.effect(() => ctx.commands.register({
|
|
497
|
+
name: 'skill',
|
|
498
|
+
description: 'Invoke a user skill (shows a picker; or use /skill:<name>)',
|
|
499
|
+
handler: invocation => {
|
|
500
|
+
void runSkillCommand(`/skill${invocation.rawInput}`, invocation.signal);
|
|
501
|
+
return { kind: 'success', text: undefined };
|
|
502
|
+
},
|
|
503
|
+
}), 'dsh-tui-pi: /skill');
|
|
483
504
|
// /think: cycle the current model's reasoning effort without re-picking
|
|
484
505
|
// the model. A no-session /think still lands in the selection ref and
|
|
485
506
|
// survives the lazy session creation (bridge seeds only an empty ref).
|
|
@@ -729,6 +750,7 @@ export function apply(ctx) {
|
|
|
729
750
|
tui: ui.tui,
|
|
730
751
|
theme: ui.theme,
|
|
731
752
|
restoreFocus: () => ui.tui.setFocus(ui.editor),
|
|
753
|
+
agent: bridge.getAgent(),
|
|
732
754
|
onError: message => {
|
|
733
755
|
// Buffered notice: the settings browser outlives the write, so the
|
|
734
756
|
// line must survive a theme hot-swap (the doc.clear() rebuild).
|
|
@@ -949,7 +971,75 @@ export function apply(ctx) {
|
|
|
949
971
|
* "aborted due to timeout" — those run with a never-aborting signal
|
|
950
972
|
* instead.
|
|
951
973
|
*/
|
|
952
|
-
const MODAL_COMMANDS = new Set(['settings', 'model', 'think', 'session', 'resume', 'theme', 'permission', 'agents', 'subagents']);
|
|
974
|
+
const MODAL_COMMANDS = new Set(['settings', 'model', 'think', 'session', 'resume', 'theme', 'permission', 'agents', 'subagents', 'skill']);
|
|
975
|
+
/**
|
|
976
|
+
* Dispatch one user skill invocation (function 1). The skill is validated
|
|
977
|
+
* against `ctx.skills` (exists + user-invocable), then the user's line is
|
|
978
|
+
* echoed verbatim while the model receives the harness's own `/name `
|
|
979
|
+
* gesture through the normal prompt path — tool-skill's `agent/pre-step`
|
|
980
|
+
* scans user messages for that gesture and injects the rendered
|
|
981
|
+
* `<skill_content>` as instructions context. This reuses the authoritative
|
|
982
|
+
* injection chain (the web client's path) with zero duplicated injection
|
|
983
|
+
* code.
|
|
984
|
+
*/
|
|
985
|
+
const invokeSkill = async (name, echoLine) => {
|
|
986
|
+
const skills = ctx.get('skills');
|
|
987
|
+
if (skills === undefined) {
|
|
988
|
+
renderer.renderNotice('Skills are not available in this environment.', 'error');
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
const agent = bridge.getAgent();
|
|
992
|
+
const cwd = agent?.session.header.cwd ?? process.cwd();
|
|
993
|
+
let gesture;
|
|
994
|
+
try {
|
|
995
|
+
const skill = await skills.get(name, { scope: agent, cwd });
|
|
996
|
+
if (skill === undefined || !skill.invocation.userInvocable) {
|
|
997
|
+
renderer.renderNotice(`Skill "${name}" is unknown or not user-invocable.`, 'error');
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
gesture = skillGesture(name);
|
|
1001
|
+
}
|
|
1002
|
+
catch (error) {
|
|
1003
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1004
|
+
renderer.renderNotice(message, 'error');
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
// Echo the user's own line; the session echo of the translated gesture
|
|
1008
|
+
// is deduped against the gesture text (renderPromptEcho's sessionEcho).
|
|
1009
|
+
// A light marker rides the echo so the transcript says the skill loaded.
|
|
1010
|
+
renderer.renderPromptEcho(echoLine, gesture, `skill ${name} loaded`);
|
|
1011
|
+
try {
|
|
1012
|
+
await bridge.prompt(gesture);
|
|
1013
|
+
}
|
|
1014
|
+
catch (error) {
|
|
1015
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1016
|
+
renderer.renderNotice(message, 'error');
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
/**
|
|
1020
|
+
* Handle one `/skill`-shaped command line: `/skill:<name>` invokes the
|
|
1021
|
+
* named skill; a bare `/skill` opens the picker and invokes the selection.
|
|
1022
|
+
* A skill-shaped line that fails `parseSkillCommand` (arguments, a bad
|
|
1023
|
+
* name, or a case mismatch) is surfaced as a usage notice instead of
|
|
1024
|
+
* vanishing — the submit() interception already committed this line as a
|
|
1025
|
+
* skill, so it is never silently swallowed or forwarded to the model.
|
|
1026
|
+
*/
|
|
1027
|
+
const runSkillCommand = async (echoLine, _signal) => {
|
|
1028
|
+
const parsed = parseSkillCommand(echoLine);
|
|
1029
|
+
if (parsed === undefined) {
|
|
1030
|
+
renderer.renderNotice('usage: /skill[:<name>]', 'error');
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
if (parsed.kind === 'invoke') {
|
|
1034
|
+
await invokeSkill(parsed.name, echoLine);
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
const agent = bridge.getAgent();
|
|
1038
|
+
const picked = await pickSkill(ctx, ui.tui, ui.theme, agent, () => ui.tui.setFocus(ui.editor));
|
|
1039
|
+
if (picked === undefined)
|
|
1040
|
+
return;
|
|
1041
|
+
await invokeSkill(picked, `/skill:${picked}`);
|
|
1042
|
+
};
|
|
953
1043
|
/** Route one submitted line: dsh slash command first, model prompt second. */
|
|
954
1044
|
const submit = async (text) => {
|
|
955
1045
|
const line = text.trim();
|
|
@@ -959,6 +1049,18 @@ export function apply(ctx) {
|
|
|
959
1049
|
const tokens = line.startsWith('/') ? line.slice(1).split(/\s+/) : [];
|
|
960
1050
|
const name = tokens[0]?.toLowerCase();
|
|
961
1051
|
let executeLine = line;
|
|
1052
|
+
const signal = name !== undefined && MODAL_COMMANDS.has(name)
|
|
1053
|
+
? new AbortController().signal
|
|
1054
|
+
: AbortSignal.timeout(30_000);
|
|
1055
|
+
// Function 1: /skill:<name> and bare /skill both dispatch a user skill.
|
|
1056
|
+
// This must run before the generic command dispatch — `/skill:foo` is
|
|
1057
|
+
// not a valid command name for parseCommand, so the generic path would
|
|
1058
|
+
// otherwise fall through to the model untouched. The translation to the
|
|
1059
|
+
// harness's `/name ` gesture lives in runSkillCommand/invokeSkill.
|
|
1060
|
+
if (name === 'skill' || name?.startsWith('skill:')) {
|
|
1061
|
+
await runSkillCommand(line, signal);
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
962
1064
|
// Bare /permission (no arguments) opens the preset picker — UI sugar
|
|
963
1065
|
// over dsh's canonical command: a picked row is replayed as
|
|
964
1066
|
// `/permission <name>` through the normal execute path below, so the
|
|
@@ -995,9 +1097,6 @@ export function apply(ctx) {
|
|
|
995
1097
|
executeLine = `/permission ${picked}`;
|
|
996
1098
|
}
|
|
997
1099
|
}
|
|
998
|
-
const signal = name !== undefined && MODAL_COMMANDS.has(name)
|
|
999
|
-
? new AbortController().signal
|
|
1000
|
-
: AbortSignal.timeout(30_000);
|
|
1001
1100
|
let command;
|
|
1002
1101
|
try {
|
|
1003
1102
|
command = await commands.tryExecute(executeLine, signal);
|