@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.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
+ ![dsh-tui-pi 演示](./dsh-tui-pi-demo.gif)
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
- # The TUI ships the deterministic compaction backend @aiwayds/dsh-dcp: the
8
- # default LLM summarizer (compaction-basic) is disabled and dsh-dcp is mounted
9
- # alongside it, so every profile that bundles this TUI gets zero-LLM
10
- # compaction out of the box. The dependency is installed as `latest`.
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_-]*)?$/u.exec(upto);
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
- const descriptors = await this.list();
134
- if (descriptors.length === 0)
135
- return null;
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 items = descriptors
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
- const completed = `${before}${item.value} ${after}`;
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
@@ -1 +1 @@
1
- {"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
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);