@aiwayds/dsh-tui-pi 0.20.1 → 0.22.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 +62 -8
- package/README.zh.md +7 -4
- package/assets/deepseek-icon.svg +4 -0
- 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 +139 -16
- 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 +26 -17
- package/lib/messages.js +36 -17
- 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/retention.d.ts +236 -0
- package/lib/retention.js +345 -0
- package/lib/retention.js.map +1 -0
- package/lib/session.d.ts +11 -0
- package/lib/session.js +33 -3
- package/lib/session.js.map +1 -1
- package/lib/sessions.d.ts +138 -5
- package/lib/sessions.js +264 -17
- 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/skills-manager.d.ts +69 -6
- package/lib/skills-manager.js +217 -99
- package/lib/skills-manager.js.map +1 -1
- package/lib/startup-info.d.ts +139 -0
- package/lib/startup-info.js +238 -0
- package/lib/startup-info.js.map +1 -0
- 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/theme-settings.d.ts +48 -0
- package/lib/theme-settings.js +109 -7
- package/lib/theme-settings.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.
|
|
@@ -275,6 +277,57 @@ theme color via BCE, no patched dependencies.
|
|
|
275
277
|
|
|
276
278
|
---
|
|
277
279
|
|
|
280
|
+
## Session management
|
|
281
|
+
|
|
282
|
+
Two knobs clusters govern the session store, both living under the
|
|
283
|
+
`dsh-tui` settings namespace (`~/.dsh/settings.yaml`) with an environment
|
|
284
|
+
escape hatch each:
|
|
285
|
+
|
|
286
|
+
```yaml
|
|
287
|
+
dsh-tui:
|
|
288
|
+
# Startup janitor for ~/.dsh/sessions — DELETES whole session log
|
|
289
|
+
# directories outside the window. Runs once per process at startup.
|
|
290
|
+
retention:
|
|
291
|
+
maxCount: 100 # keep at most this many sessions; <= 0 DISABLES the janitor
|
|
292
|
+
maxAgeDays: 7 # delete logs untouched for more than this many days (> 0)
|
|
293
|
+
minIdleHours: 24 # count-rule-only idle guard in hours (>= 0)
|
|
294
|
+
|
|
295
|
+
# /resume display filter — only HIDES picker rows, never deletes.
|
|
296
|
+
# Resolved fresh at every picker open (a settings change applies to
|
|
297
|
+
# the next /resume, no restart).
|
|
298
|
+
resume:
|
|
299
|
+
maxAgeDays: 7 # only sessions with log activity inside this window (> 0)
|
|
300
|
+
minBytes: 20480 # minimum compressed on-disk log size for a row (>= 0)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Precedence per field: an explicit value in settings.yaml > the
|
|
304
|
+
`DSH_TUI_RETENTION_MAX_COUNT` / `DSH_TUI_RETENTION_MAX_AGE_DAYS` /
|
|
305
|
+
`DSH_TUI_RETENTION_MIN_IDLE_HOURS` and `DSH_TUI_RESUME_MAX_AGE_DAYS` /
|
|
306
|
+
`DSH_TUI_RESUME_MIN_BYTES` environment variables > the defaults above.
|
|
307
|
+
An invalid settings value warns once on stderr and falls to the next
|
|
308
|
+
level; an invalid env value falls back silently to the default — a typo
|
|
309
|
+
never widens or guts the policy. `maxCount` and `minBytes` must be
|
|
310
|
+
integers at every layer (a fractional cap or byte floor is garbage, not a
|
|
311
|
+
window).
|
|
312
|
+
|
|
313
|
+
**Disabling retention entirely** — for a long-lived process (a remote
|
|
314
|
+
bridge, a headless cron run) that read-attaches old sessions the default
|
|
315
|
+
window would prune:
|
|
316
|
+
|
|
317
|
+
```yaml
|
|
318
|
+
dsh-tui:
|
|
319
|
+
retention:
|
|
320
|
+
maxCount: 0 # or: DSH_TUI_RETENTION_MAX_COUNT=0
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Timing: **retention runs once at startup** (never mid-session; a
|
|
324
|
+
`/reload` does not re-run it — the next cold start does), while the
|
|
325
|
+
**resume filter applies at every `/resume` open**. The two `7`s default
|
|
326
|
+
to the same "one week is the working set" decision but serve different
|
|
327
|
+
masters — retention deletes logs, the resume filter only hides rows.
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
278
331
|
## Fonts
|
|
279
332
|
|
|
280
333
|
The TUI's only Private-Use-Area glyph is the powerline segment separator
|
|
@@ -421,8 +474,9 @@ src/
|
|
|
421
474
|
footer.ts PowerlineFooter (7 segments + clock)
|
|
422
475
|
editor.ts CwdBorderEditor (top border: cwd + git branch)
|
|
423
476
|
subagent-policy.ts maxAgents guard + maxRounds wrap-up injection
|
|
477
|
+
(steer-when-running; ⚡-marked, visible in the viewer)
|
|
424
478
|
subagent-viewer.ts Ctrl+G picker + live transcript panel + Enter steer injection
|
|
425
|
-
ask-user.ts Ask User Question
|
|
479
|
+
ask-user.ts Ask User Question docked panel: pure state reducers +
|
|
426
480
|
framed overlay UI + ctx.userQuestions provider
|
|
427
481
|
steer-flow.ts Steer / follow-up decision layer: routed delivery with
|
|
428
482
|
race fallback, queue actions (remove / promote), notices
|
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 个文件)
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<!-- DeepSeek official whale logo (brand blue #4D6BFE). Source: DeepSeek (deepseek.com) brand asset. Kept in-repo for future branding/README use; not referenced by code yet. © DeepSeek. -->
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50" fill="none">
|
|
3
|
+
<path d="M48.8354 10.0479C48.3232 9.79199 48.1025 10.2798 47.8032 10.5278C47.7007 10.6079 47.6143 10.7119 47.5273 10.8076C46.7793 11.624 45.9048 12.1597 44.7622 12.0957C43.0923 12 41.666 12.5356 40.4058 13.8398C40.1377 12.2319 39.2476 11.272 37.8926 10.6558C37.1836 10.3359 36.4668 10.0156 35.9702 9.31982C35.6235 8.82373 35.5293 8.27197 35.356 7.72754C35.2456 7.3999 35.1353 7.06396 34.7651 7.00781C34.3633 6.94385 34.2056 7.2876 34.0479 7.57568C33.418 8.75195 33.1733 10.0479 33.1973 11.3599C33.2524 14.312 34.4736 16.6641 36.8999 18.3359C37.1758 18.5278 37.2466 18.7197 37.1597 19C36.9946 19.5757 36.7974 20.1357 36.624 20.7119C36.5137 21.0801 36.3486 21.1597 35.9624 21C34.6309 20.4321 33.481 19.5918 32.4644 18.5757C30.7393 16.8721 29.1792 14.9917 27.2334 13.52C26.7764 13.1758 26.3193 12.856 25.8467 12.5518C23.8618 10.584 26.1069 8.96777 26.627 8.77588C27.1704 8.57568 26.8159 7.8877 25.0591 7.896C23.3022 7.90381 21.6953 8.50391 19.647 9.30371C19.3477 9.42383 19.0322 9.51172 18.7095 9.58398C16.8501 9.22363 14.9199 9.14355 12.9033 9.37598C9.10596 9.80762 6.07275 11.6396 3.84326 14.7681C1.16455 18.5278 0.53418 22.7998 1.30664 27.2559C2.11768 31.9521 4.46582 35.8398 8.07373 38.8799C11.8159 42.0322 16.1255 43.5762 21.041 43.2803C24.0269 43.104 27.3516 42.6963 31.1016 39.4561C32.0469 39.936 33.0396 40.1279 34.686 40.272C35.9546 40.3921 37.1758 40.208 38.1211 40.0078C39.6021 39.688 39.4995 38.2881 38.9639 38.0322C34.623 35.9678 35.5762 36.8081 34.71 36.1279C36.9155 33.4639 40.2402 30.6958 41.54 21.728C41.6426 21.0161 41.5557 20.5679 41.54 19.9917C41.5322 19.6396 41.6108 19.5039 42.0049 19.4639C43.0923 19.3359 44.1479 19.0317 45.1167 18.4878C47.9292 16.9199 49.064 14.3438 49.3315 11.2559C49.3711 10.7837 49.3237 10.2959 48.8354 10.0479ZM24.3262 37.8398C20.1196 34.4639 18.0791 33.3521 17.2358 33.3999C16.4482 33.4482 16.5898 34.3682 16.7632 34.9678C16.9443 35.5601 17.1812 35.9683 17.5117 36.4878C17.7402 36.832 17.8979 37.3442 17.2832 37.728C15.9282 38.584 13.5728 37.4399 13.4624 37.3838C10.7207 35.7358 8.42822 33.5601 6.81348 30.584C5.25342 27.7197 4.34766 24.6479 4.19775 21.3677C4.1582 20.5757 4.38672 20.2959 5.15869 20.1519C6.17529 19.96 7.22314 19.9199 8.23926 20.0718C12.5327 20.7119 16.1885 22.6719 19.2529 25.7759C21.002 27.5439 22.3252 29.6558 23.6885 31.7202C25.1377 33.9121 26.6978 36 28.6831 37.7119C29.3843 38.312 29.9434 38.7681 30.479 39.104C28.8643 39.2881 26.1699 39.3281 24.3262 37.8398ZM26.3433 24.6001C26.3433 24.248 26.6191 23.9678 26.9658 23.9678C27.0444 23.9678 27.1152 23.9839 27.1782 24.0078C27.2651 24.04 27.3438 24.0879 27.4067 24.1602C27.5171 24.272 27.5801 24.4321 27.5801 24.6001C27.5801 24.9521 27.3042 25.2319 26.9575 25.2319C26.6108 25.2319 26.3433 24.9521 26.3433 24.6001ZM32.6064 27.8799C32.2046 28.0479 31.8027 28.1919 31.4165 28.208C30.8179 28.2397 30.1641 27.9922 29.8096 27.688C29.2583 27.2158 28.8643 26.9521 28.6987 26.1279C28.6279 25.7759 28.6675 25.2319 28.7305 24.9199C28.8721 24.248 28.7144 23.8159 28.2495 23.4238C27.8716 23.104 27.3911 23.0161 26.8633 23.0161C26.666 23.0161 26.4849 22.9277 26.3511 22.856C26.1304 22.7441 25.9492 22.4639 26.1226 22.1201C26.1777 22.0078 26.4458 21.7358 26.5088 21.688C27.2256 21.272 28.0527 21.4077 28.8169 21.7197C29.5259 22.0161 30.0615 22.5601 30.834 23.3281C31.6216 24.2559 31.7632 24.5117 32.2124 25.208C32.5669 25.752 32.8901 26.312 33.1104 26.9521C33.2446 27.3521 33.0713 27.6802 32.6064 27.8799Z" fill="#4D6BFE" fill-rule="nonzero"/>
|
|
4
|
+
</svg>
|
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
|
/**
|