@aiwayds/dsh-tui-pi 0.20.0 → 0.21.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 +13 -10
- package/README.zh.md +7 -4
- package/lib/append-system.d.ts +12 -3
- package/lib/append-system.js +33 -7
- package/lib/append-system.js.map +1 -1
- package/lib/ask-user.d.ts +79 -61
- package/lib/ask-user.js +117 -112
- package/lib/ask-user.js.map +1 -1
- package/lib/custom-provider.d.ts +119 -0
- package/lib/custom-provider.js +243 -0
- package/lib/custom-provider.js.map +1 -0
- package/lib/dsh-events.d.ts +15 -0
- package/lib/dsh-events.js +12 -0
- package/lib/dsh-events.js.map +1 -1
- package/lib/index.js +45 -17
- package/lib/index.js.map +1 -1
- package/lib/live-widgets.js +4 -0
- package/lib/live-widgets.js.map +1 -1
- package/lib/login.js +32 -2
- package/lib/login.js.map +1 -1
- package/lib/messages.d.ts +5 -3
- package/lib/messages.js +7 -2
- package/lib/messages.js.map +1 -1
- package/lib/provider-catalog.d.ts +7 -0
- package/lib/provider-catalog.js +1 -0
- package/lib/provider-catalog.js.map +1 -1
- package/lib/queue-panel.d.ts +19 -0
- package/lib/queue-panel.js +27 -1
- package/lib/queue-panel.js.map +1 -1
- package/lib/session.js +12 -3
- package/lib/session.js.map +1 -1
- package/lib/sessions.d.ts +16 -0
- package/lib/sessions.js +94 -9
- package/lib/sessions.js.map +1 -1
- package/lib/settings.d.ts +9 -0
- package/lib/settings.js +12 -1
- package/lib/settings.js.map +1 -1
- package/lib/subagent-policy.d.ts +14 -4
- package/lib/subagent-policy.js +40 -14
- package/lib/subagent-policy.js.map +1 -1
- package/lib/subagent-viewer.js +14 -2
- package/lib/subagent-viewer.js.map +1 -1
- package/lib/tui.d.ts +14 -0
- package/lib/tui.js +11 -2
- package/lib/tui.js.map +1 -1
- package/package.json +1 -1
- package/templates/APPEND_SYSTEM.md +2 -0
package/README.md
CHANGED
|
@@ -116,7 +116,7 @@ Running subagent activity is shown in the **last-request area below the editor**
|
|
|
116
116
|
↳ ⠼ Subagent B 10s 任务 · 562 token · 6.0s
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-
Each line shows: spinner + agent **name**, retries (`↻N≤M`), compact **current-context usage** (`X/Y` — the child's latest request's billed input+output plus a CJK estimate of messages after it, over its context window; NOT the cumulative token spend, which only grows), rounds (`round N/M` — the assistant-message count against the cap, `M` only when `maxRounds > 0`), elapsed. No provider shown, no box, no header — just one line per running child.
|
|
119
|
+
Each line shows: spinner + agent **name**, retries (`↻N≤M`), compact **current-context usage** (`X/Y` — the child's latest request's billed input+output plus a CJK estimate of messages after it, over its context window; NOT the cumulative token spend, which only grows), rounds (`round N/M` — the assistant-message count against the cap, `M` only when `maxRounds > 0`), elapsed, and a `⚡` marker when a policy injection (maxRounds wrap-up, steer) reached the child. No provider shown, no box, no header — just one line per running child.
|
|
120
120
|
|
|
121
121
|
Both **spawn-driven** and **fork-driven** children are tracked — dsh creates both through `childSessionMeta`, which writes `origin: 'subagent'` + a `delegationDepth` budget together, so header discovery recognises either marker (a budget-without-origin header is admitted as a defensive fallback and labelled `fork <id8>`; current dsh does not produce that shape). Non-children stay off the board by **value**, not by field presence: the jsonl persistence backend materialises `delegationDepth: 0` on every restored header, so the gate requires a budget `> 0`. User-facing session forks (a forked *conversation*: `Session.fork` sets `parentSession` + `seedLength`, no budget) are deliberately kept off the subagent board and stay resumable via `/resume` — whose filter (`isResumableSessionHeader`) excludes exactly the delegated children (`origin: 'subagent'` or budget > 0).
|
|
122
122
|
|
|
@@ -143,7 +143,7 @@ Icons: `☑` completed, `◐` in-progress, `☐` pending. A settled child drops
|
|
|
143
143
|
Two caps (`/agents` → `l` to configure):
|
|
144
144
|
|
|
145
145
|
- **`maxAgents`** (default 4, `0` = unlimited) — spawns are denied when the cap is hit.
|
|
146
|
-
- **`maxRounds`** (default 75, `0` = unlimited) — after a child's assistant messages (one per LLM round-trip — the "rounds") reach the cap, the TUI
|
|
146
|
+
- **`maxRounds`** (default 75, `0` = unlimited) — after a child's assistant messages (one per LLM round-trip — the "rounds") reach the cap, the TUI injects one wrap-up directive and never force-stops: a running child receives it at its next step boundary (`steer` — the very next LLM round-trip), an idle child as its own next turn. The injection is visible: the compact line, the Ctrl+G picker row and the viewer header show a `⚡` marker, and the transcript renders the injected message as `⚡ <text>` — so a wrap-up the child LLM ignored can be told apart from one that never fired.
|
|
147
147
|
|
|
148
148
|
---
|
|
149
149
|
|
|
@@ -168,7 +168,7 @@ Inside a subagent, a committed compaction is visible too: DCP appends one `user/
|
|
|
168
168
|
A user-editable markdown file whose content is appended to the **system prompt of the main agent this TUI creates** — borrows pi's `~/.pi/agent/APPEND_SYSTEM.md` convention, dsh side: `$DSH_HOME/APPEND_SYSTEM.md` (default `~/.dsh/APPEND_SYSTEM.md`, honors the same `$DSH_HOME` override as the rest of dsh).
|
|
169
169
|
|
|
170
170
|
- **Hot-applied** — the section provider reads the file at every prompt assembly, so editing the file picks up on the **next request**: no restart, no watcher, no `/reload`.
|
|
171
|
-
- **Auto-seeded on first run** — when the file is missing, the TUI seeds it once at startup from the shipped template `templates/APPEND_SYSTEM.md` (the English orchestrator-identity template: identity, core rules, execution workflow). An existing file is yours — the TUI never overwrites user content.
|
|
171
|
+
- **Auto-seeded on first run** — when the file is missing, the TUI seeds it once at startup from the shipped template `templates/APPEND_SYSTEM.md` (the English orchestrator-identity template: identity, core rules, execution workflow — including the registered-subagents vocabulary rule: "subagent" means the registered subagents only). An existing file is yours — the TUI never overwrites user content; it only appends the marked todo-lifecycle section and (idempotently, by phrase match) the subagents rule when a file does not phrase them yet.
|
|
172
172
|
- **TUI-owned section** — a marked block (`<!-- dsh-tui-pi:todo-lifecycle -->`) is appended once and then maintained idempotently so the model clears its `todo/write` list when every item is done. A marked file is left byte-identical on later startups.
|
|
173
173
|
- **Legacy migration** — the same todo block used to be delivered through `~/.dsh/AGENTS.md`. On startup the TUI strips that block once (no-op when absent), so the guidance is never duplicated.
|
|
174
174
|
- **Empty / unreadable = no section** — if the file is missing or can't be read, the section is silently dropped. No error, no TUI startup failure.
|
|
@@ -199,12 +199,12 @@ There's no slash command to toggle the feature — it's always on, controlled by
|
|
|
199
199
|
|
|
200
200
|
### Ask User Question
|
|
201
201
|
|
|
202
|
-
While the model is mid-turn it can pause and ask you structured questions via the `ask_user_question` tool (`@deepseek-ai/dsh-tool-ask-user`, mounted by this profile's bundle patch). The TUI hosts the answering side: a
|
|
202
|
+
While the model is mid-turn it can pause and ask you structured questions via the `ask_user_question` tool (`@deepseek-ai/dsh-tool-ask-user`, mounted by this profile's bundle patch). The TUI hosts the answering side: a bordered panel pins itself directly above the chat input (the Todos-panel slot — no floating popup), takes the keyboard while open, the tool call stays pending until you answer, and your answers flow back to the model as a normal tool result.
|
|
203
203
|
|
|
204
|
-
- **One
|
|
204
|
+
- **One panel, all questions flattened** — every question renders as a header row followed by its supporting `detail` text (when the model supplies any), option rows, plus a `Type something.` sentinel row for free text. Single-select replaces on Enter; multi-select toggles (`[+]` marks).
|
|
205
205
|
- **Single-question fast path** — a lone single-select question submits immediately on Enter: picking an option or committing typed free text both submit right away (a question without options is answered by typing alone). A lone multiSelect question instead gets a `⏎ Confirm answers` row so you can pick several options before submitting.
|
|
206
206
|
- **Multi-question review page** — with ≥ 2 questions a `⏎ Confirm answers` row hops to a review listing every answer, each row editable in place; `Submit answers` commits (Enter on it while an answer is missing flashes a hint instead of failing silently). After committing free text on one question the cursor hops to the next unanswered one.
|
|
207
|
-
- **Double-Esc declines** — two Esc presses within 200 ms return a declined envelope (the model reads it as a normal reply that no answer was given); holding Esc does not accidentally fire (key auto-repeat below a minimum gap is ignored), and
|
|
207
|
+
- **Double-Esc declines** — two Esc presses within 200 ms return a declined envelope (the model reads it as a normal reply that no answer was given); holding Esc does not accidentally fire (key auto-repeat below a minimum gap is ignored), and the tool call being aborted settles as declined too. While the panel is open it owns the keyboard exactly like an open overlay: Esc never arms the running-task stop, and app keys (Ctrl+L/G/O, Tab) yield to the panel.
|
|
208
208
|
- **Conservative-use guidance** — a system-prompt section nudges the model to ask only when it genuinely needs you (1–3 questions, 2–4 options each), so the TUI doesn't turn into a questionnaire.
|
|
209
209
|
- **Keyboard** — `↑↓` navigate · `Enter` select/toggle/confirm · type into the sentinel for free text · `Esc` twice to decline.
|
|
210
210
|
|
|
@@ -219,7 +219,7 @@ Inspired by [juicesharp/rpiv-ask-user-question](https://github.com/juicesharp/rp
|
|
|
219
219
|
| `/model` | Two-stage provider/model picker (then thinking level). Live switch, persisted; in-panel keys: `f` favorite · `h` hide · `/` filter (favorites/hidden persisted via settings). |
|
|
220
220
|
| `/think` | Reasoning-effort picker for the current model (`Off`/`High`/`Max`). |
|
|
221
221
|
| `/session` | Read-only info panel: id, cwd, model, token usage, event count. |
|
|
222
|
-
| `/resume` | Pick a persisted session, validate its log, then restore it. |
|
|
222
|
+
| `/resume` | Pick a persisted session, validate its log, then restore it. Ordered by last update (log-file mtime), newest first; the `Updated` column shows the effective time. |
|
|
223
223
|
| `/new` | Detach the current session; the next prompt opens a fresh one. |
|
|
224
224
|
| `/settings` | Text-based settings browser (namespaces, schema walk, inline editors, secrets masked). |
|
|
225
225
|
| `/export` | Write the current session log as JSONL (`~/Downloads/dsh-session-<id>.jsonl`). |
|
|
@@ -229,6 +229,8 @@ Inspired by [juicesharp/rpiv-ask-user-question](https://github.com/juicesharp/rp
|
|
|
229
229
|
| `/agents` | Manage agent markdown files + subagent limits (`maxAgents`, `maxRounds`). |
|
|
230
230
|
| `/subagents` | Pick a running/recent subagent and watch its live transcript; `Enter` inside the viewer steers the child (see Subagents). |
|
|
231
231
|
| `/reload` | Hot-reload the plugin from source (after `pnpm build`) without restarting dsh. |
|
|
232
|
+
| `/login` | Log in to a provider: pick from the directory (or `/login openai` to jump), enter one API key. The **Custom provider…** entry (`/login custom`) opens a six-field form for any OpenAI/Anthropic-compatible gateway pi-ai does not ship — route id, display name, protocol, base URL, model list, API key — and writes the same hand-declared route the web Models page composes. |
|
|
233
|
+
| `/logout` | Pick a logged-in provider and remove both the stored key and its provider profile. |
|
|
232
234
|
| `/hotkeys` | Keybinding browser and live editor. |
|
|
233
235
|
|
|
234
236
|
Anything that is not a resolvable command falls through to the model as an ordinary prompt.
|
|
@@ -390,7 +392,7 @@ dsh --profile tui # or: dsh-tui-pi (bin shim)
|
|
|
390
392
|
```sh
|
|
391
393
|
pnpm check # tsc --noEmit
|
|
392
394
|
pnpm build # emit lib/
|
|
393
|
-
pnpm test # unit tests, node --test against lib/ (
|
|
395
|
+
pnpm test # unit tests, node --test against lib/ (757 tests, pretest builds)
|
|
394
396
|
```
|
|
395
397
|
|
|
396
398
|
Local type-checking symlinks `node_modules/@deepseek-ai/*` to the installed
|
|
@@ -421,8 +423,9 @@ src/
|
|
|
421
423
|
footer.ts PowerlineFooter (7 segments + clock)
|
|
422
424
|
editor.ts CwdBorderEditor (top border: cwd + git branch)
|
|
423
425
|
subagent-policy.ts maxAgents guard + maxRounds wrap-up injection
|
|
426
|
+
(steer-when-running; ⚡-marked, visible in the viewer)
|
|
424
427
|
subagent-viewer.ts Ctrl+G picker + live transcript panel + Enter steer injection
|
|
425
|
-
ask-user.ts Ask User Question
|
|
428
|
+
ask-user.ts Ask User Question docked panel: pure state reducers +
|
|
426
429
|
framed overlay UI + ctx.userQuestions provider
|
|
427
430
|
steer-flow.ts Steer / follow-up decision layer: routed delivery with
|
|
428
431
|
race fallback, queue actions (remove / promote), notices
|
|
@@ -431,7 +434,7 @@ src/
|
|
|
431
434
|
queue-panel.ts Ctrl+O pending-message queue: d remove · s steer now,
|
|
432
435
|
live-refreshed overlay
|
|
433
436
|
theme/ GitHub light/dark palettes + terminal detection
|
|
434
|
-
test/*.test.mjs unit tests (
|
|
437
|
+
test/*.test.mjs unit tests (757 across 44 files)
|
|
435
438
|
```
|
|
436
439
|
|
|
437
440
|
---
|
package/README.zh.md
CHANGED
|
@@ -113,7 +113,7 @@ dsh ▸ volc-ark-plan ▸ deepseek-v4-flash ▸ high ▸ 48.7k/1.0M(4.6%) ▸
|
|
|
113
113
|
↳ ⠼ Subagent B 10s 任务 · 562 token · 6.0s
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
每行显示:spinner + 代理**名称**,重试次数(`↻N≤M`),当前上下文占用(`X/Y` —— 子代理最近一次请求的 billed input+output 加上其后消息的 CJK 估算,除以它的上下文窗口;**不是**只增不减的累计 token 消耗),rounds(`round N/M` —— assistant 消息数对上限,`maxRounds > 0` 时才显示 `/M
|
|
116
|
+
每行显示:spinner + 代理**名称**,重试次数(`↻N≤M`),当前上下文占用(`X/Y` —— 子代理最近一次请求的 billed input+output 加上其后消息的 CJK 估算,除以它的上下文窗口;**不是**只增不减的累计 token 消耗),rounds(`round N/M` —— assistant 消息数对上限,`maxRounds > 0` 时才显示 `/M`),耗时,以及策略注入(maxRounds 收尾、steer)到达后出现的 `⚡` 标记。不显示 provider,无边框,无标题。
|
|
117
117
|
|
|
118
118
|
**spawn 派生**与 **fork 派生**两类子代理都会被追踪——dsh 通过 `childSessionMeta` 同时写入 `origin: 'subagent'` 和 `delegationDepth` 预算,头部识别对两种标记都认得(只有预算没有 origin 的头部作为防御性兜底也会被接纳,标记为 `fork <id8>`;当前 dsh 不会产生这种形态)。非子代理会话按**值**而非字段有无被挡在板外:jsonl 持久化后端在每条恢复的头部上都会物化 `delegationDepth: 0`,所以闸门要求预算严格 `> 0`。面向用户的会话 fork(fork 出的*对话*:`Session.fork` 只设 `parentSession` + `seedLength`,不带预算)刻意不进子代理面板,仍可通过 `/resume` 恢复——`/resume` 的过滤器(`isResumableSessionHeader`)恰好排除被委派的子代理(`origin: 'subagent'` 或预算 > 0)。
|
|
119
119
|
|
|
@@ -138,7 +138,7 @@ dsh ▸ volc-ark-plan ▸ deepseek-v4-flash ▸ high ▸ 48.7k/1.0M(4.6%) ▸
|
|
|
138
138
|
两个限制项(通过 `/agents` → `l` 配置):
|
|
139
139
|
|
|
140
140
|
- **`maxAgents`**(默认 4,`0` = 无限制)—— 超过上限时拒绝新的子代理创建。
|
|
141
|
-
- **`maxRounds`**(默认 75,`0` = 无限制)—— 子代理的 assistant 消息数(每次 LLM 往返计 1 round)达到上限后,TUI
|
|
141
|
+
- **`maxRounds`**(默认 75,`0` = 无限制)—— 子代理的 assistant 消息数(每次 LLM 往返计 1 round)达到上限后,TUI 注入一条收尾指令,从不强制终止:运行中的子代理在**下一步边界**收到(`steer`,即下一次 LLM 往返),空闲的作为自己的下一个 turn。注入可见:紧凑行、Ctrl+G 选择器行和查看器头部显示 `⚡`,对话记录里注入消息渲染为 `⚡ <文本>`——子代理 LLM 无视收尾指令与注入从未发生,现在可以区分。
|
|
142
142
|
|
|
143
143
|
---
|
|
144
144
|
|
|
@@ -163,7 +163,7 @@ dsh plugin --profile tui add @aiwayds/dsh-dcp
|
|
|
163
163
|
一份用户可编辑的 markdown 文件,内容会**追加到 TUI 创建的主代理的系统提示末尾** —— 借鉴 pi 的 `~/.pi/agent/APPEND_SYSTEM.md` 约定,dsh 侧对应 `$DSH_HOME/APPEND_SYSTEM.md`(默认 `~/.dsh/APPEND_SYSTEM.md`,沿用 dsh 其余部分共用的 `$DSH_HOME` 覆盖)。
|
|
164
164
|
|
|
165
165
|
- **热应用** —— section 提供者在每次组装提示词时读盘,改完文件**下一次请求**即生效:无需重启、无需 watcher、无需 `/reload`。
|
|
166
|
-
- **首次启动自动播种** —— 文件不存在时,TUI 在启动时一次性从随包模板 `templates/APPEND_SYSTEM.md
|
|
166
|
+
- **首次启动自动播种** —— 文件不存在时,TUI 在启动时一次性从随包模板 `templates/APPEND_SYSTEM.md`(英文版协调者身份模板:身份、核心规则、执行工作流,含「subagent 仅指已注册子代理」的用语铁律)创建。已有文件归用户所有 —— TUI 永远不会覆盖用户内容;只在缺失标记的 todo-lifecycle 段、或尚未出现 subagents 铁律措辞(按短语匹配,幂等)时追加对应段落。
|
|
167
167
|
- **TUI 自有段落** —— 一段带标记的 block(`<!-- dsh-tui-pi:todo-lifecycle -->`)只在缺失时追加一次,并保持幂等,确保模型在所有 todo 都完成时清空 `todo/write` 列表。已带标记的文件后续启动原样保留。
|
|
168
168
|
- **旧版迁移** —— 同一段 todo block 早期通过 `~/.dsh/AGENTS.md` 下发。启动时 TUI 一次性把它剥掉(无标记时 no-op),避免重复下发。
|
|
169
169
|
- **空 / 读不到 = 不挂载该 section** —— 文件缺失或读不了时该 section 被静默丢弃,无报错、不影响 TUI 启动。
|
|
@@ -199,7 +199,7 @@ EOF
|
|
|
199
199
|
| `/model` | 两阶段选择 provider/model(然后选推理等级),实时切换并持久化。面板内按键:`f` 收藏 · `h` 隐藏 · `/` 过滤(收藏/隐藏经 settings 持久化)。 |
|
|
200
200
|
| `/think` | 当前模型的推理强度选择(`Off`/`High`/`Max`)。 |
|
|
201
201
|
| `/session` | 只读信息面板:id、cwd、模型、token 用量、事件计数。 |
|
|
202
|
-
| `/resume` |
|
|
202
|
+
| `/resume` | 选择已保存的会话,验证日志后恢复。按最后更新时间排序(日志文件 mtime),新的在上;`Updated` 列显示生效时间。 |
|
|
203
203
|
| `/new` | 分离当前会话;下一次输入开启新会话。 |
|
|
204
204
|
| `/settings` | 文本式设置浏览器(命名空间、schema 遍历、内联编辑器、密钥脱敏)。 |
|
|
205
205
|
| `/export` | 将当前会话日志导出为 JSONL(默认 `~/Downloads/dsh-session-<id>.jsonl`)。 |
|
|
@@ -209,6 +209,8 @@ EOF
|
|
|
209
209
|
| `/agents` | 管理 agent markdown 文件 + 子代理限制(`maxAgents`、`maxRounds`)。 |
|
|
210
210
|
| `/subagents` | 选择运行中/最近的子代理,查看其实时对话。 |
|
|
211
211
|
| `/reload` | 从源码热重载插件(`pnpm build` 后执行,无需重启 dsh)。 |
|
|
212
|
+
| `/login` | 登录 provider:从目录选择(或 `/login openai` 直达),输入一个 API key。**Custom provider…** 条目(`/login custom`)打开六字段表单,接入 pi-ai 未收录的任意 OpenAI/Anthropic 兼容网关 —— 路由 id、显示名、协议、base URL、模型列表、API key —— 写出与 Web Models 页相同的 hand-declared 路由。 |
|
|
213
|
+
| `/logout` | 选择已登录的 provider,同时删除存储的 key 和 provider 配置。 |
|
|
212
214
|
| `/hotkeys` | 快捷键浏览器和实时编辑。 |
|
|
213
215
|
|
|
214
216
|
不是已注册命令的内容会作为普通提示词发送给模型。
|
|
@@ -383,6 +385,7 @@ src/
|
|
|
383
385
|
footer.ts PowerlineFooter(7 分段 + 时钟)
|
|
384
386
|
editor.ts CwdBorderEditor(顶部边框:cwd + git 分支)
|
|
385
387
|
subagent-policy.ts maxAgents 守卫 + maxRounds 收尾请求注入
|
|
388
|
+
(运行中走 steer;⚡ 标记,查看器可见)
|
|
386
389
|
subagent-viewer.ts Ctrl+G 选择器 + 实时对话面板
|
|
387
390
|
theme/ GitHub light/dark 配色 + 终端检测
|
|
388
391
|
test/*.test.mjs 单元测试(569 个,覆盖 38 个文件)
|
package/lib/append-system.d.ts
CHANGED
|
@@ -26,6 +26,14 @@ export declare const TODO_LIFECYCLE_MARKER = "<!-- dsh-tui-pi:todo-lifecycle -->
|
|
|
26
26
|
* an all-completed list would stay pinned above the chat input.
|
|
27
27
|
*/
|
|
28
28
|
export declare const TODO_LIFECYCLE_SECTION = "<!-- dsh-tui-pi:todo-lifecycle -->\n## Todo list lifecycle (dsh-tui-pi)\n\nThe UI renders your todo list as a fixed panel above the chat input. Keep\nitems `pending` or `in_progress` while they are not done. When EVERY todo is\ncompleted \u2014 no pending or in-progress items remain \u2014 write an EMPTY todo list\n(`todos: []`) so the panel clears. Never leave a fully-completed list behind.\n";
|
|
29
|
+
/**
|
|
30
|
+
* The subagent vocabulary rule (user iron rule, 2026-08-24): "subagent" means
|
|
31
|
+
* the registered subagents only. Shipped inside the template's Core rules for
|
|
32
|
+
* fresh installs; appended idempotently to pre-template files that lack it.
|
|
33
|
+
*/
|
|
34
|
+
export declare const SUBAGENTS_RULE_SECTION = "## Subagents\n\nWhen the user says \"subagent\", they mean the registered subagents only; never use unregistered subagents.\n";
|
|
35
|
+
/** Idempotency phrase for the subagents rule (matches template and section). */
|
|
36
|
+
export declare const SUBAGENTS_RULE_PHRASE = "registered subagents only";
|
|
29
37
|
/** Default harness home: `$DSH_HOME` or `~/.dsh`. */
|
|
30
38
|
export declare function dshHome(): string;
|
|
31
39
|
/** The user-editable append file path (`$DSH_HOME/APPEND_SYSTEM.md`). */
|
|
@@ -49,12 +57,13 @@ export declare function readAppendSystem(path?: string): string;
|
|
|
49
57
|
* marked todo-lifecycle section: a missing file is seeded from
|
|
50
58
|
* `templates/APPEND_SYSTEM.md` (the English orchestrator template) followed
|
|
51
59
|
* by the marked section; an existing file is user-owned — only the marked
|
|
52
|
-
* section is appended when its marker is missing,
|
|
53
|
-
* untouched
|
|
60
|
+
* section is appended when its marker is missing, a marked file is left
|
|
61
|
+
* untouched, and the subagents vocabulary rule is appended to any file that
|
|
62
|
+
* does not phrase it yet (template ships it as a Core rule).
|
|
54
63
|
* @param path - target file (injectable for tests).
|
|
55
64
|
* @param templatePath - the shipped template (injectable for tests).
|
|
56
65
|
* @returns an error message on failure, undefined on success (including the
|
|
57
|
-
* no-op case where the marker
|
|
66
|
+
* no-op case where the marker and the rule are already present).
|
|
58
67
|
*/
|
|
59
68
|
export declare function ensureAppendSystemFile(path?: string, templatePath?: string): Promise<string | undefined>;
|
|
60
69
|
/**
|
package/lib/append-system.js
CHANGED
|
@@ -38,6 +38,27 @@ items \`pending\` or \`in_progress\` while they are not done. When EVERY todo is
|
|
|
38
38
|
completed — no pending or in-progress items remain — write an EMPTY todo list
|
|
39
39
|
(\`todos: []\`) so the panel clears. Never leave a fully-completed list behind.
|
|
40
40
|
`;
|
|
41
|
+
/**
|
|
42
|
+
* The subagent vocabulary rule (user iron rule, 2026-08-24): "subagent" means
|
|
43
|
+
* the registered subagents only. Shipped inside the template's Core rules for
|
|
44
|
+
* fresh installs; appended idempotently to pre-template files that lack it.
|
|
45
|
+
*/
|
|
46
|
+
export const SUBAGENTS_RULE_SECTION = `## Subagents
|
|
47
|
+
|
|
48
|
+
When the user says "subagent", they mean the registered subagents only; never use unregistered subagents.
|
|
49
|
+
`;
|
|
50
|
+
/** Idempotency phrase for the subagents rule (matches template and section). */
|
|
51
|
+
export const SUBAGENTS_RULE_PHRASE = 'registered subagents only';
|
|
52
|
+
/**
|
|
53
|
+
* Append the subagents rule unless the content already carries it (the shipped
|
|
54
|
+
* template embeds it as a Core rule; a hand-edited file may phrase it anywhere
|
|
55
|
+
* — the case-insensitive phrase check accepts both).
|
|
56
|
+
*/
|
|
57
|
+
function withSubagentsRule(content) {
|
|
58
|
+
if (content.toLowerCase().includes(SUBAGENTS_RULE_PHRASE))
|
|
59
|
+
return content;
|
|
60
|
+
return `${content.replace(/\s+$/u, '')}\n\n${SUBAGENTS_RULE_SECTION}`;
|
|
61
|
+
}
|
|
41
62
|
/** Default harness home: `$DSH_HOME` or `~/.dsh`. */
|
|
42
63
|
export function dshHome() {
|
|
43
64
|
return process.env.DSH_HOME ?? join(homedir(), '.dsh');
|
|
@@ -78,12 +99,13 @@ export function readAppendSystem(path = appendSystemPath()) {
|
|
|
78
99
|
* marked todo-lifecycle section: a missing file is seeded from
|
|
79
100
|
* `templates/APPEND_SYSTEM.md` (the English orchestrator template) followed
|
|
80
101
|
* by the marked section; an existing file is user-owned — only the marked
|
|
81
|
-
* section is appended when its marker is missing,
|
|
82
|
-
* untouched
|
|
102
|
+
* section is appended when its marker is missing, a marked file is left
|
|
103
|
+
* untouched, and the subagents vocabulary rule is appended to any file that
|
|
104
|
+
* does not phrase it yet (template ships it as a Core rule).
|
|
83
105
|
* @param path - target file (injectable for tests).
|
|
84
106
|
* @param templatePath - the shipped template (injectable for tests).
|
|
85
107
|
* @returns an error message on failure, undefined on success (including the
|
|
86
|
-
* no-op case where the marker
|
|
108
|
+
* no-op case where the marker and the rule are already present).
|
|
87
109
|
*/
|
|
88
110
|
export async function ensureAppendSystemFile(path = appendSystemPath(), templatePath = appendSystemTemplatePath()) {
|
|
89
111
|
let existing;
|
|
@@ -93,10 +115,14 @@ export async function ensureAppendSystemFile(path = appendSystemPath(), template
|
|
|
93
115
|
catch {
|
|
94
116
|
existing = '';
|
|
95
117
|
}
|
|
96
|
-
if (existing.includes(TODO_LIFECYCLE_MARKER))
|
|
97
|
-
|
|
118
|
+
if (existing.includes(TODO_LIFECYCLE_MARKER)) {
|
|
119
|
+
// Marker section already maintained — only the subagents rule may be new.
|
|
120
|
+
if (existing.toLowerCase().includes(SUBAGENTS_RULE_PHRASE))
|
|
121
|
+
return undefined;
|
|
122
|
+
return writeAtomically(path, withSubagentsRule(existing));
|
|
123
|
+
}
|
|
98
124
|
if (existing !== '') {
|
|
99
|
-
return writeAtomically(path, `${existing.replace(/\s+$/u, '')}\n\n${TODO_LIFECYCLE_SECTION}`);
|
|
125
|
+
return writeAtomically(path, withSubagentsRule(`${existing.replace(/\s+$/u, '')}\n\n${TODO_LIFECYCLE_SECTION}`));
|
|
100
126
|
}
|
|
101
127
|
// Fresh file: seed from the shipped template, then the marked section. A
|
|
102
128
|
// missing template (broken tarball) degrades to the marked section alone.
|
|
@@ -108,7 +134,7 @@ export async function ensureAppendSystemFile(path = appendSystemPath(), template
|
|
|
108
134
|
template = '';
|
|
109
135
|
}
|
|
110
136
|
const seed = template === '' ? TODO_LIFECYCLE_SECTION : `${template.replace(/\s+$/u, '')}\n\n${TODO_LIFECYCLE_SECTION}`;
|
|
111
|
-
return writeAtomically(path, seed);
|
|
137
|
+
return writeAtomically(path, withSubagentsRule(seed));
|
|
112
138
|
}
|
|
113
139
|
/**
|
|
114
140
|
* Migration: strip the TUI-owned todo-lifecycle block from `~/.dsh/AGENTS.md`
|
package/lib/append-system.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"append-system.js","sourceRoot":"","sources":["../src/append-system.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,uEAAuE;AACvE,MAAM,CAAC,MAAM,qBAAqB,GAAG,oCAAoC,CAAA;AAEzE;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,qBAAqB;;;;;;;CAO7D,CAAA;AAED,qDAAqD;AACrD,MAAM,UAAU,OAAO;IACrB,OAAO,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAA;AACxD,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,gBAAgB,CAAC,OAAe,OAAO,EAAE;IACvD,OAAO,IAAI,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAA;AACvC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,kBAAkB,CAAC,CAAA;AAChG,CAAC;AAED,oEAAoE;AACpE,SAAS,YAAY,CAAC,OAAe,OAAO,EAAE;IAC5C,OAAO,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAA;AAChC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe,gBAAgB,EAAE;IAChE,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAA;IACX,CAAC;AACH,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"append-system.js","sourceRoot":"","sources":["../src/append-system.ts"],"names":[],"mappings":"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"}
|
package/lib/ask-user.d.ts
CHANGED
|
@@ -2,52 +2,62 @@
|
|
|
2
2
|
* Ask the human a question while the model is mid-turn.
|
|
3
3
|
*
|
|
4
4
|
* Wires the upstream `ctx.userQuestions` capability seam (`dsh-user-questions`)
|
|
5
|
-
* with a terminal-side UI:
|
|
6
|
-
*
|
|
5
|
+
* with a terminal-side UI: a DOCKED panel pinned above the chat input (the
|
|
6
|
+
* Todos-panel slot of the widgets dock — not a floating overlay) that pauses
|
|
7
|
+
* the tool call until the human answers, then feeds the canonical
|
|
7
8
|
* `AskUserQuestionAnswer` envelope back to `dsh-tool-ask-user` as a normal
|
|
8
9
|
* tool result.
|
|
9
10
|
*
|
|
10
|
-
* Layout — one
|
|
11
|
+
* Layout — one bordered dock panel, all questions flattened (numbered rows, a
|
|
11
12
|
* divider between each question block and its options, and a scroll window
|
|
12
|
-
* so the cursor row is always visible even when the
|
|
13
|
+
* so the cursor row is always visible even when the dock is height-capped):
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
15
|
+
* ┌────────────────────────────────────┐
|
|
16
|
+
* │ ● Questions (2) │
|
|
17
|
+
* │ SELECTION │
|
|
18
|
+
* │ ───────────────────────────── │ (table chrome)
|
|
19
|
+
* │ Fruit Where should we deploy? │ (header — not selectable)
|
|
20
|
+
* │ ───────────────────────────── │ (divider)
|
|
21
|
+
* │ ▸ ● 1. staging │ (cursor + inline selection mark)
|
|
22
|
+
* │ ○ 2. production │
|
|
23
|
+
* │ red and round │ (option description, own muted line)
|
|
24
|
+
* │ 3. Type something. │ (sentinel row — inline input)
|
|
25
|
+
* │ │ (blank separator before confirm)
|
|
26
|
+
* │ ⏎ Confirm answers │ (when ≥ 2 questions, or any multiSelect)
|
|
27
|
+
* └────────────────────────────────────┘
|
|
28
|
+
*
|
|
29
|
+
* While the panel is open it owns the keyboard: it is mounted into the dock
|
|
30
|
+
* slot between the live widgets and the editor, takes focus through
|
|
31
|
+
* `tui.setFocus`, and `setModalActive(true)` routes the app keymap exactly
|
|
32
|
+
* like an open overlay (Esc/Ctrl+C/app keys yield to the panel — Esc never
|
|
33
|
+
* arms the running-task stop from inside a modal). On close the panel
|
|
34
|
+
* unmounts, clears the modal flag, and focus returns to the current editor
|
|
35
|
+
* through `restoreFocus`.
|
|
25
36
|
*
|
|
26
37
|
* The body renders through a scroll window sized from the live terminal
|
|
27
|
-
* height (`askUserMaxVisibleForRows`;
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
38
|
+
* height (`askUserMaxVisibleForRows`; the dock budget subtracts the editor,
|
|
39
|
+
* footer and a transcript floor, and falls back to `ASK_USER_MAX_VISIBLE`
|
|
40
|
+
* when the terminal row count is unknown): pi-tui would lay a taller dock
|
|
41
|
+
* out at full height and squeeze the transcript to nothing, so the window
|
|
42
|
+
* caps the body. The window slides with the cursor and the footer gains a
|
|
43
|
+
* `(n/m)` position readout while content overflows.
|
|
33
44
|
*
|
|
34
|
-
* Single-question single-select
|
|
45
|
+
* Single-question single-select panel: Enter on an option (or committing a
|
|
35
46
|
* filled sentinel) submits immediately. A multiSelect question — even a lone
|
|
36
47
|
* one — gets a Confirm row instead of auto-submitting, so the user can pick
|
|
37
|
-
* several options first. Multi-question
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* the result anyway.
|
|
48
|
+
* several options first. Multi-question panel: Enter on the Confirm row hops
|
|
49
|
+
* to the review page (all answers listed, each editable in place). Esc
|
|
50
|
+
* double-press within 200 ms declines — but terminal key auto-repeat (holding
|
|
51
|
+
* Esc) is ignored below `ESC_REPEAT_GUARD_MS`, so a long press cannot
|
|
52
|
+
* accidentally fire the decline. The provider returns the "declined" envelope
|
|
53
|
+
* and the model reads it as a normal user reply. An aborted request signal
|
|
54
|
+
* (`request.signal`) settles declined too — we resolve the declined envelope
|
|
55
|
+
* instead of rejecting ASK_ABORTED because the upstream service already
|
|
56
|
+
* screens entry-time aborts and an aborted step discards the result anyway.
|
|
47
57
|
*
|
|
48
58
|
* Pure logic lives in the top of this file (initial state, answer envelope,
|
|
49
|
-
* declined envelope, double-Esc state machine, row-layout math) so it can
|
|
50
|
-
*
|
|
59
|
+
* declined envelope, double-Esc state machine, row-layout math) so it can be
|
|
60
|
+
* unit-tested without a TTY. The component below owns the TUI render +
|
|
51
61
|
* keyboard handling; the install function at the bottom registers the
|
|
52
62
|
* provider under `ctx.userQuestions` (a Cordis effect, single active
|
|
53
63
|
* provider in the tree).
|
|
@@ -55,7 +65,7 @@
|
|
|
55
65
|
* Inspired by juicesharp/rpiv-ask-user-question
|
|
56
66
|
* (https://github.com/juicesharp/rpiv-ask-user-question).
|
|
57
67
|
*/
|
|
58
|
-
import { type TUI } from '@earendil-works/pi-tui';
|
|
68
|
+
import { type Component, type TUI } from '@earendil-works/pi-tui';
|
|
59
69
|
import type { AskUserQuestionAnswer, AskUserQuestionItem, AskUserQuestionOption } from '@deepseek-ai/dsh-user-questions';
|
|
60
70
|
import type { Context } from '@deepseek-ai/cordis';
|
|
61
71
|
import { type TuiTheme } from './theme/index.ts';
|
|
@@ -77,23 +87,21 @@ export declare const INCOMPLETE_HINT = "Answer every question first";
|
|
|
77
87
|
/**
|
|
78
88
|
* Fallback visible body-line cap for the questions/review panes, used only
|
|
79
89
|
* when the terminal row count is unknown (fake TUIs in tests, exotic
|
|
80
|
-
* terminals).
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* window from the real terminal height via `askUserMaxVisibleForRows`.
|
|
90
|
+
* terminals). Matches the 24-row dock budget: 24 − 8 reserved (editor 3 +
|
|
91
|
+
* footer 1 + status 1 + transcript floor 3) − 10 panel chrome lines = 6.
|
|
92
|
+
* The live path derives the window from the real terminal height via
|
|
93
|
+
* `askUserMaxVisibleForRows`.
|
|
85
94
|
*/
|
|
86
|
-
export declare const ASK_USER_MAX_VISIBLE =
|
|
95
|
+
export declare const ASK_USER_MAX_VISIBLE = 6;
|
|
87
96
|
/**
|
|
88
|
-
* Derive the scroll-window size from the terminal height
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* degrades to ASK_USER_MAX_VISIBLE.
|
|
97
|
+
* Derive the scroll-window size from the terminal height: the dock stacks
|
|
98
|
+
* (it does not float over the transcript like the old overlay did), so the
|
|
99
|
+
* panel budget is the terminal height minus the reserved dock rows, minus
|
|
100
|
+
* the panel's own chrome. At the 24-row e2e floor this yields exactly
|
|
101
|
+
* ASK_USER_MAX_VISIBLE (6); larger terminals scale up. Without a usable row
|
|
102
|
+
* count it degrades to ASK_USER_MAX_VISIBLE.
|
|
95
103
|
*/
|
|
96
|
-
export declare function askUserMaxVisibleForRows(termRows: number | undefined
|
|
104
|
+
export declare function askUserMaxVisibleForRows(termRows: number | undefined): number;
|
|
97
105
|
/** A row in the multi-question view. */
|
|
98
106
|
export interface FlatRow {
|
|
99
107
|
kind: 'question-header' | 'option' | 'sentinel' | 'confirm';
|
|
@@ -237,28 +245,38 @@ export declare function renderReviewView(theme: TuiTheme, state: AskUserState, w
|
|
|
237
245
|
/** Options for assembling the panel + provider function. */
|
|
238
246
|
export interface AskUserPanelDeps {
|
|
239
247
|
tui: TUI;
|
|
240
|
-
/** Live theme getter — re-read on every render so a mid-
|
|
248
|
+
/** Live theme getter — re-read on every render so a mid-panel hot-swap applies (borders included). */
|
|
241
249
|
theme: () => TuiTheme;
|
|
242
|
-
/** Re-focus the current editor on
|
|
250
|
+
/** Re-focus the current editor on panel close. */
|
|
243
251
|
restoreFocus: () => void;
|
|
252
|
+
/**
|
|
253
|
+
* Mount the panel component into the dock slot above the chat input (the
|
|
254
|
+
* Todos-panel slot); returns the unmount function. The panel is NOT an
|
|
255
|
+
* overlay — it renders pinned above the editor like the live widgets.
|
|
256
|
+
*/
|
|
257
|
+
mount: (component: Component) => () => void;
|
|
258
|
+
/**
|
|
259
|
+
* Declare the docked modal's keyboard ownership. While active, the app
|
|
260
|
+
* keymap treats the panel exactly like an open overlay (app keys and the
|
|
261
|
+
* Esc/Ctrl+C chains yield to the focused panel) and `refocusEditor` must
|
|
262
|
+
* not steal focus from it.
|
|
263
|
+
*/
|
|
264
|
+
setModalActive: (active: boolean) => void;
|
|
244
265
|
/** Injectable clock for tests; defaults to Date.now. */
|
|
245
266
|
now?: () => number;
|
|
246
|
-
/** Width and height of the framed overlay. */
|
|
247
|
-
width?: `${number}%` | number;
|
|
248
|
-
maxHeight?: `${number}%` | number;
|
|
249
267
|
}
|
|
250
268
|
/** Result promise from `openAskUserPanel`. Declined carries the canonical decline envelope. */
|
|
251
269
|
export type AskUserResult = AskUserQuestionAnswer;
|
|
252
270
|
/**
|
|
253
|
-
* Open the AskUser
|
|
271
|
+
* Open the AskUser docked panel for one set of questions.
|
|
254
272
|
*
|
|
255
273
|
* `signal` is the caller's abort signal (the tool execution's). Already-aborted
|
|
256
|
-
* settles declined WITHOUT
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
274
|
+
* settles declined WITHOUT mounting a panel; a live signal closes the panel
|
|
275
|
+
* and settles declined when it fires. We resolve the declined envelope rather
|
|
276
|
+
* than rejecting with the upstream ASK_ABORTED code on purpose: the upstream
|
|
277
|
+
* service already screens entry-time aborts, and a step aborted after this
|
|
278
|
+
* point discards the tool result anyway — resolving keeps the pending promise
|
|
279
|
+
* from ever hanging either way.
|
|
262
280
|
*/
|
|
263
281
|
export declare function openAskUserPanel(deps: AskUserPanelDeps, questions: readonly AskUserQuestionItem[], signal?: AbortSignal): Promise<AskUserResult>;
|
|
264
282
|
/**
|