@aiwayds/dsh-tui-pi 0.1.1 → 0.4.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.
Files changed (62) hide show
  1. package/README.md +175 -41
  2. package/lib/agent-manager.d.ts +125 -0
  3. package/lib/agent-manager.js +298 -0
  4. package/lib/agent-manager.js.map +1 -0
  5. package/lib/agents.d.ts +30 -0
  6. package/lib/agents.js +489 -0
  7. package/lib/agents.js.map +1 -0
  8. package/lib/append-system.d.ts +2 -0
  9. package/lib/append-system.js +1 -1
  10. package/lib/append-system.js.map +1 -1
  11. package/lib/dsh-events.d.ts +12 -0
  12. package/lib/editor.d.ts +58 -0
  13. package/lib/editor.js +92 -0
  14. package/lib/editor.js.map +1 -1
  15. package/lib/footer.d.ts +46 -0
  16. package/lib/footer.js +65 -0
  17. package/lib/footer.js.map +1 -1
  18. package/lib/hotkeys.d.ts +93 -0
  19. package/lib/hotkeys.js +305 -0
  20. package/lib/hotkeys.js.map +1 -0
  21. package/lib/index.js +342 -61
  22. package/lib/index.js.map +1 -1
  23. package/lib/keymap.d.ts +243 -0
  24. package/lib/keymap.js +245 -0
  25. package/lib/keymap.js.map +1 -0
  26. package/lib/live-widgets.d.ts +59 -49
  27. package/lib/live-widgets.js +174 -117
  28. package/lib/live-widgets.js.map +1 -1
  29. package/lib/panels.d.ts +177 -0
  30. package/lib/panels.js +331 -0
  31. package/lib/panels.js.map +1 -0
  32. package/lib/selectors.d.ts +12 -1
  33. package/lib/selectors.js +1 -1
  34. package/lib/selectors.js.map +1 -1
  35. package/lib/session.d.ts +25 -0
  36. package/lib/session.js +173 -12
  37. package/lib/session.js.map +1 -1
  38. package/lib/settings.d.ts +1 -2
  39. package/lib/subagent-policy.d.ts +88 -0
  40. package/lib/subagent-policy.js +189 -0
  41. package/lib/subagent-policy.js.map +1 -0
  42. package/lib/subagent-viewer.d.ts +49 -0
  43. package/lib/subagent-viewer.js +523 -0
  44. package/lib/subagent-viewer.js.map +1 -0
  45. package/lib/theme/index.js +4 -0
  46. package/lib/theme/index.js.map +1 -1
  47. package/lib/theme/palette.d.ts +21 -3
  48. package/lib/theme/palette.js +27 -6
  49. package/lib/theme/palette.js.map +1 -1
  50. package/lib/theme-settings.d.ts +71 -9
  51. package/lib/theme-settings.js +145 -21
  52. package/lib/theme-settings.js.map +1 -1
  53. package/lib/tui.d.ts +23 -9
  54. package/lib/tui.js +110 -7
  55. package/lib/tui.js.map +1 -1
  56. package/package.json +3 -4
  57. package/patches/@earendil-works__pi-tui.patch +206 -2
  58. package/scripts/dev-install.mjs +87 -0
  59. package/scripts/link-dsh-closure.mjs +85 -0
  60. package/templates/agents/ArtyDuck.md +11 -0
  61. package/templates/agents/oldfox.md +33 -0
  62. package/templates/agents/workhorse.md +34 -0
package/README.md CHANGED
@@ -38,8 +38,11 @@ dsh ▸ ☁ opencode-go ▸ 🤖 deepseek-v4-flash ▸ ● high ▸ 🧠 11.6k/1
38
38
  `cmux-theme.sh` GitHub terminal themes). Hot-switchable at runtime: pick one
39
39
  with `/theme` (applies immediately), edit the `dsh-tui.theme` setting
40
40
  (external edits hot-apply too), or pin with `DSH_TUI_THEME=light|dark` — the
41
- env var wins over every preference. Without a preference, terminal-background
42
- detection (COLORFGBG).
41
+ env var wins over every preference. The app paints its own canvas, so a
42
+ switch recolors the whole screen (background included) even inside
43
+ multiplexers; without a preference, `auto` detects the terminal background
44
+ (COLORFGBG + a live OSC 11 / CSI 996n query) and follows the terminal's
45
+ light/dark switches in real time.
43
46
  - **Footer**: powerline segments ported from
44
47
  [pi-powerline-footer](https://github.com/fan56/pi-powerline-footer) —
45
48
  provider / model+thinking / context / cache-hit / msgs / tools with U+E0B0
@@ -77,7 +80,10 @@ dsh ▸ ☁ opencode-go ▸ 🤖 deepseek-v4-flash ▸ ● high ▸ 🧠 11.6k/1
77
80
  | `/export` | write the current session log as JSONL — default `~/Downloads/dsh-session-<id>.jsonl`, or a path argument. |
78
81
  | `/permission` | permission-preset picker (whatever the deployment table advertises — read-only / workspace-write / danger-full-access). Select a preset to apply it through dsh's canonical `/permission <name>` command, or Esc to keep the current one. The editor's top border shows the live preset badge (danger-full-access → "Full access"). |
79
82
  | `/theme` | color-scheme picker (auto / light / dark). The choice applies immediately and is persisted to `dsh-tui.theme`. |
83
+ | `/agents` | manage agent definition markdown files (name/model/thinking/deep per agent) **and the subagent limits** — `l` from the table opens the limits panel: `maxAgents` (concurrent live children, default 4) and `maxRounds` (completed turns before the TUI queues a wrap-up request, default 50; both `0 = unlimited`). Limits are read live at every spawn/turn decision; writes go to the `dsh-tui` settings namespace and hot-apply. Also the initial view when no agent files exist yet. |
84
+ | `/subagents` | the command twin of `Ctrl+G`: pick a running (or recently settled) subagent and watch its live transcript in the 80% viewer — status, rounds against the cap, tokens, tool calls. |
80
85
  | `/reload` | hot-reload the plugin from the current source (after `pnpm build`) without restarting dsh — the TUI and the live agent are torn down; the session log persists and can be rejoined with `/resume`. |
86
+ | `/hotkeys` | keybinding browser: the effective app-key table (custom overrides starred) plus the keybindings file path — see [Custom keybindings](#custom-keybindings). |
81
87
 
82
88
  Anything that is not a resolvable command falls through to the model as an
83
89
  ordinary prompt, so dsh packages' commands (and future registrations) appear
@@ -115,13 +121,26 @@ Themes change live, no restart:
115
121
  `applies: 'live'`. The namespace's watch hook pushes the commit to the
116
122
  running TUI, which repaints everything on the next frame: transcript
117
123
  (replayed from its operation buffer), editor border, footer hint, spinner.
124
+ - **The whole screen changes, background included.** The TUI paints its own
125
+ canvas (a patched pi-tui paints every rendered row with the palette's
126
+ canvas color), so a light→dark switch recolors the entire surface — the
127
+ terminal's own background never shows through, which is what makes the
128
+ switch look broken inside multiplexers like cmux/gostty where the pane
129
+ background belongs to the terminal, not the app. Set
130
+ `DSH_TUI_TRANSPARENT=1` to go back to the see-through canvas and keep your
131
+ terminal theme visible.
118
132
  - An **external edit** of `~/.dsh/settings.yaml` (`dsh-tui.theme: dark`)
119
133
  hot-applies through the same watch path.
120
134
  - `DSH_TUI_THEME=light|dark` **pins** the display regardless of preference —
121
135
  it wins at startup and keeps winning; `/theme` still persists the
122
136
  preference and honestly reports `Theme preference saved — display is pinned
123
137
  by DSH_TUI_THEME=…` instead of claiming it applied.
124
- - The choice survives restarts (`auto` falls back to terminal detection).
138
+ - The choice survives restarts. `auto` detects the terminal: the synchronous
139
+ startup guess reads `COLORFGBG`; a background refinement then asks the
140
+ terminal itself (CSI `?996n` color-scheme query, falling back to an OSC 11
141
+ background-color query — both answered by Ghostty/cmux, kitty and iTerm),
142
+ and while `auto` stays selected the TUI follows live light/dark switches of
143
+ the terminal (CSI 997 push notifications) and repaints on the next frame.
125
144
 
126
145
  ## APPEND_SYSTEM.md
127
146
 
@@ -148,18 +167,25 @@ no reload, no watcher.
148
167
  ## Install (local)
149
168
 
150
169
  ```sh
151
- # build once
152
- cd dsh-tui-pi && pnpm install && pnpm build
170
+ # build + pack + install into the profile in one step (the recommended dev loop)
171
+ node scripts/dev-install.mjs # = pnpm build pnpm pack → refresh the profile's copies
153
172
 
154
- # live development link (recommended; edits to src/ + pnpm build apply on next launch)
155
- dsh plugin --profile tui add link:/path/to/dsh-tui-pi
156
-
157
- # or an npm tarball
158
- npm pack # → aiwayds-dsh-tui-pi-0.1.0.tgz
159
- dsh plugin --profile tui add /path/to/aiwayds-dsh-tui-pi-0.1.0.tgz
173
+ # or, manually, like a real user would:
174
+ pnpm pack # aiwayds-dsh-tui-pi-0.2.0.tgz
175
+ dsh plugin --profile tui add /path/to/aiwayds-dsh-tui-pi-0.2.0.tgz
160
176
  ```
161
177
 
162
- Both paths auto-add `dsh-tui-pi` to the profile's `dsh.profile.bundles`.
178
+ The profile's `package.json` carries **both** keys pointing at the tarball
179
+ `dsh-tui-pi` (dsh resolves the bundle by this name) and
180
+ `@aiwayds/dsh-tui-pi` (the loader entry in `cordis.patch.yml` imports this
181
+ name) — and its `pnpm-workspace.yaml` declares the pi-tui
182
+ `patchedDependencies` (see `~/.dsh/profiles/tui/`). At runtime
183
+ `@deepseek-ai/*` resolve through dsh's shared
184
+ `~/.dsh/profiles/node_modules` fallback to the installed closure, so the
185
+ profile never needs the repo's `node_modules`. Known quirk: pnpm does not
186
+ re-read a changed `file:` tarball while its `node_modules` entry exists —
187
+ `scripts/dev-install.mjs` removes the two installed copies before installing,
188
+ which forces the refresh.
163
189
 
164
190
  ## Use
165
191
 
@@ -178,8 +204,103 @@ dsh --profile tui # or: dsh-tui-pi (bin shim)
178
204
  finished child drops off; when nothing is left the panel and the activity
179
205
  lines collapse away.
180
206
  - `/` opens slash-command autocomplete (Tab/arrows/Enter).
181
- - Ctrl+C quits — while the agent is mid-turn the first press cancels the turn
182
- (`⏹ canceling current turn…`), any further press quits.
207
+
208
+ ## Keyboard shortcuts
209
+
210
+ App-level keys (key mappings mirror [pi](https://github.com/badlogic/pi-mono)):
211
+
212
+ | Key | Action |
213
+ | --- | --- |
214
+ | `Enter` | send the prompt |
215
+ | `Esc` | **stop the current task — as a deliberate double-press** — priority chain: a popup that is open closes itself first (Esc inside a popup *never* stops the running task); the editor's autocomplete closes; a mid-turn agent waits for a second `Esc` within 500ms to cancel the whole task (parent + subagents, `⏹ canceling current turn…`; the first press only arms the window and shows a hint); a non-empty editor does **nothing** (anti-misfire); on an **empty** editor a second `Esc` within 500ms opens `/session` |
216
+ | `Ctrl+C` | mid-turn: first press cancels the running turn, second press (within 500ms) quits; idle: first press clears the editor, second press quits. With a popup open it cancels the popup instead. **Held-key auto-repeat never quits** — repeats under 80ms apart are swallowed, and the double-press quit is confirmed for 200ms (a follow-up repeat aborts it, a human-speed re-press fires it immediately) |
217
+ | `Ctrl+D` | quit — only when the editor is **empty**, like pi's `app.exit`; with text it is the regular delete-character-forward |
218
+ | `Ctrl+L` | open the model/think picker (pi's `app.model.select`) |
219
+ | `Ctrl+G` | open the subagent picker while subagents run (see `/subagents`); idle the key falls through untouched. dsh's own mapping — pi spends this key on an external editor we don't have, remap in `keybindings.json` if you miss it |
220
+ | `Tab` | autocomplete |
221
+
222
+ Editor keys (movement/deletion/undo) come from the pi-tui `Editor` default
223
+ bindings — no dsh code involved: `←→` / `Ctrl+B`/`Ctrl+F` move, `Alt+←→` /
224
+ `Ctrl+←→` / `Alt+B`/`Alt+F` word-move, `Home`/`End` / `Ctrl+A`/`Ctrl+E` line
225
+ edges, `PageUp`/`PageDown` scroll, `Backspace` / `Delete`/`Ctrl+D` delete,
226
+ `Ctrl+W`/`Alt+Backspace` delete word back, `Alt+D` delete word forward,
227
+ `Ctrl+U`/`Ctrl+K` delete to line start/end, `Ctrl+-` undo, `Ctrl+Y`/`Alt+Y`
228
+ yank, `Shift+Enter`/`Ctrl+J` newline. `↑` / `↓` follow this interplay: on the
229
+ first line `↑` moves the cursor to the line start, whereas on an **empty**
230
+ editor (or with the cursor at the line start) it browses history instead (next
231
+ paragraph).
232
+
233
+ `↑` / `↓` browse the submitted-message history shell-style. `↑` recalls the
234
+ most recent prompt and walks further back; `↓` moves forward again, and past
235
+ the newest entry it restores your in-progress draft. Browsing starts when the
236
+ cursor is at the start of the first line — including on an empty editor — and
237
+ the text you were leaving is preserved as the draft, so `↓` all the way back
238
+ hands you the draft again. The history holds up to **500 entries**, dropping
239
+ the oldest ones beyond that; it survives a theme hot-swap (the editor is
240
+ rebuilt on switch). The history is in-memory for the current TUI run — it does
241
+ **not** survive `/reload` or a restart. Submitted slash commands (`/theme`,
242
+ `/hotkeys`, …) are recorded too. Recalling a multi-line entry places the
243
+ cursor at its start; `↓` then walks the entry's lines before advancing the
244
+ history — but a single-line entry round-trips identically (do not fight the
245
+ pi-tui behavior).
246
+
247
+ Not supported yet (documented status): `Ctrl+O` collapse tool output, `Ctrl+X`
248
+ copy the last assistant message, `Alt+Enter` follow-up queue, `Ctrl+V` paste
249
+ image, `Ctrl+Z` suspend, `Ctrl+P`/`Ctrl+Shift+P` model cycle, `Shift+Tab` think
250
+ cycle, `Ctrl+T` collapse thinking.
251
+
252
+ ### Subagent viewer & fine-grained control
253
+
254
+ `Ctrl+G` (or `/subagents`) opens an 80% picker over the tracked children —
255
+ running ones first (spinner, mode, rounds against `maxRounds`, tokens,
256
+ elapsed), then the five most recently settled. Enter opens the transcript
257
+ viewer: one readable line per buffered child event (user/assistant messages,
258
+ tool calls paired with truncated results, turns, todos), refreshing ~3x/s
259
+ with tail-follow (scroll up to detach, reach the bottom to re-attach), and a
260
+ truncation note when the per-child 2000-event ring buffer dropped its head.
261
+ `Esc` closes; a deliberate double-`x` within 500ms closes too.
262
+
263
+ Two caps steer delegation (configure in `/agents` → `l`, both live-read at
264
+ every decision):
265
+
266
+ - **`maxAgents`** (default 4, `0` = unlimited) — a `tools.guard` denies
267
+ model-facing spawn tools (`subagent`, `subagent_fork`, `workflow`, `ralph`,
268
+ `use_agent`) once that many children run, with the running labels in the
269
+ deny reason so the model can wait or `list_agents`. The cap is approximate
270
+ under a burst of parallel spawns; workflow fan-out (which bypasses the tool
271
+ pipeline) is pruned after the fact on `subagent/start`.
272
+ - **`maxRounds`** (default 50, `0` = unlimited) — when a child's completed
273
+ turns reach the cap, the TUI queues one wrap-up request
274
+ ("总结和结束这个任务,汇报情况。") as its next turn — it never interrupts
275
+ work underway, never repeats per child, and never re-awakens a child that
276
+ already settled. There is deliberately **no force stop**.
277
+
278
+ ### Custom keybindings
279
+
280
+ The five app-level keys are remappable through `$DSH_HOME/keybindings.json`
281
+ (`~/.dsh/keybindings.json` by default) — pi's
282
+ `~/.pi/agent/keybindings.json` convention. The file is a **partial** map of
283
+ the app keys to pi-tui key ids; anything missing keeps its default. Key id
284
+ format: `modifier+key`, modifiers `ctrl`/`shift`/`alt`/`super` (combined with
285
+ `+`), key a letter/digit/symbol or a named key (`escape`, `enter`, `tab`,
286
+ `space`, `backspace`, `delete`, `home`, `end`, `pageUp`, `pageDown`, arrows,
287
+ `f1`–`f24` …).
288
+
289
+ ```json
290
+ {
291
+ "escape": "ctrl+x",
292
+ "ctrlC": "alt+c",
293
+ "ctrlD": "ctrl+w",
294
+ "modelPicker": "ctrl+m"
295
+ }
296
+ ```
297
+
298
+ The file is read when the TUI starts. You can edit it by hand (then
299
+ `/reload`) — or use `/hotkeys`, which shows the effective table in the same
300
+ select-panel style as `/agents`: each app key is a row (custom overrides
301
+ starred), `Enter` prompts for a new key id (empty input resets the key to its
302
+ default), and a commit **writes the file and applies the change live** — no
303
+ `/reload` needed. Invalid entries never block: they warn and keep the default.
183
304
 
184
305
  ## Performance rules (from the pi-turbo findings)
185
306
 
@@ -217,7 +338,7 @@ dsh-tui-pi avoids both by construction:
217
338
  ```sh
218
339
  pnpm check # tsc --noEmit
219
340
  pnpm build # emit lib/
220
- pnpm test # unit tests, node --test against lib/ (185 tests, pretest builds)
341
+ pnpm test # unit tests, node --test against lib/ (296 tests, pretest builds)
221
342
  ```
222
343
 
223
344
  Local type-checking symlinks `node_modules/@deepseek-ai/*` to the installed
@@ -225,23 +346,13 @@ dsh closure (`/opt/homebrew/lib/node_modules/@deepseek-ai/dsh/node_modules`);
225
346
  at runtime those imports resolve to the same module instances the running dsh
226
347
  uses. Those symlinks stay out of any tarball (`files` ships lib/bin/patch only).
227
348
 
228
- ⚠️ `pnpm install` regenerates the three type-check symlinks declared in
229
- `package.json` (`dsh-settings`, `dsh-client-schema-form`, `schemastery`)
230
- into local `.pnpm` copies, splitting the cordis module identity and breaking
231
- `pnpm check` after any install, re-link them:
232
-
233
- ```sh
234
- ln -sfn /opt/homebrew/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/{dsh-settings,dsh-client-schema-form,schemastery} node_modules/@deepseek-ai/
235
- ```
236
-
237
- `dsh-permission-presets` is a fourth, undeclared type-check link — the same
238
- extraneous-closure pattern as `cordis`/`dsh-agent` above: `pnpm install`
239
- never regenerates it (it is not in the dependency tree), but a wiped
240
- `node_modules` needs it re-created by hand:
241
-
242
- ```sh
243
- ln -sfn /opt/homebrew/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-permission-presets node_modules/@deepseek-ai/
244
- ```
349
+ `scripts/link-dsh-closure.mjs` (the package's `postinstall`) re-creates every
350
+ `node_modules/@deepseek-ai/*` link to the global dsh closure after **each**
351
+ `pnpm install`/`patch-commit`, and is a no-op without a global dsh install.
352
+ That keeps a single `@deepseek-ai/cordis` module identity in the type graph
353
+ (declaring any of them in `package.json` would make pnpm install a second
354
+ local copy and break the cordis `settings` augmentation). A wiped
355
+ `node_modules` self-heals on the next `pnpm install`.
245
356
 
246
357
  **pi-tui patch**: this plugin applies a small patch to the pinned
247
358
  `@earendil-works/pi-tui` 0.84.2 (`pnpm.patchedDependencies` in
@@ -262,7 +373,8 @@ src/
262
373
  index.ts cordis plugin entry + wiring: command registration, footer,
263
374
  git watcher, clock, bridge, theme hot-swap sink, shutdown
264
375
  tui.ts TUI bootstrap: alt-screen tree, transcript ScrollView,
265
- dock (status/editor/last-request/footer), editor rebuild
376
+ dock (status/editor/last-request/footer), editor rebuild,
377
+ app-owned canvas background (patched pi-tui)
266
378
  session.ts DshSessionBridge: lazy agent create, followup, resume,
267
379
  replay, cancel, O(1) incremental stats, persistDefaultModel,
268
380
  subagent tracker (tool-workflow + child events → live rows)
@@ -280,6 +392,16 @@ src/
280
392
  editor.ts CwdBorderEditor (top border: 📁 cwd │ ⎇ branch)
281
393
  git.ts GitBranchWatcher (polled, cached)
282
394
  frame.ts FramedOverlay: shared top/bottom ─ border for every popup
395
+ panels.ts select-panel framework: TablePanel/FieldPanel/ViewerPanel/
396
+ PanelHost + padCell/columnWidths/ListController
397
+ keymap.ts pure key-action decision (resolveKeyAction) — Esc/Ctrl+C/
398
+ Ctrl+D/Ctrl+L/Ctrl+G chains with double-press guards
399
+ hotkeys.ts keybindings.json contract + validation + /hotkeys manager
400
+ agent-manager.ts agent markdown files: parse/validate/write-back engine +
401
+ ~/.zcode/agents seeding, `deep` policy
402
+ agents.ts /agents table + fields window + subagent limits panel
403
+ subagent-policy.ts maxAgents guard + maxRounds wrap-up injection (read live)
404
+ subagent-viewer.ts Ctrl+G picker + live transcript panel (300 ms tick)
283
405
  provider-catalog.ts built-in provider directory (36 llm-pi-ai catalog
284
406
  routes, mirrors the web Models page) + deriveKeyRef + row
285
407
  views (pure data/functions for the Models add-provider flow)
@@ -294,22 +416,34 @@ src/
294
416
  sessions.ts /session info panel + /resume persisted-session picker
295
417
  settings.ts /settings browser: categories, schema walk, inline editors,
296
418
  serialized mutate write chain, add-provider flow
419
+ welcome.ts startup whale banner (WHALE_ART + PIXEL_FONT glyphs)
420
+ quotes.ts startup quote pool
421
+ append-system.ts APPEND_SYSTEM.md support + todo-lifecycle section
422
+ maintenance (idempotent, atomic)
297
423
  theme/
298
424
  palette.ts GitHub light/dark palettes + terminal-background detection
425
+ (rgbIsLight luminance)
299
426
  index.ts buildTheme: Editor/Markdown/SelectList/chat roles, POWERLINE
300
427
  segment palette, resolveTheme (env > preference > detect)
301
- test/*.test.mjs unit tests, node --test against lib/ (185 across 15 files)
428
+ test/*.test.mjs unit tests, node --test against lib/ (296 across 24 files)
302
429
  ```
303
430
 
304
- ## Status (2026-08-15)
431
+ ## Changelog
432
+
433
+ See [CHANGELOG.md](CHANGELOG.md) for the release history.
434
+
435
+ ## Status (0.2.0)
305
436
 
306
437
  All surface commands shipped and tmux-e2e verified: `/model /think /session
307
- /resume /new /settings /export /theme /reload`; provider-first Models with the
308
- add-provider flow; overlay chrome (backgrounds + borders); theme hot-switch
309
- (immediate apply, external-change watch, env pinning); graded Ctrl+C; live
310
- todos + subagent progress blocks; clean Ctrl+C exit. `pnpm check` clean,
311
- 185 unit tests green, e2e run confirmed the settings/credentials files are
312
- restored byte-for-byte.
438
+ /resume /new /settings /export /theme /reload /agents /subagents /hotkeys`;
439
+ provider-first Models with the add-provider flow; overlay chrome
440
+ (backgrounds + borders); theme hot-switch (immediate apply, external-change
441
+ watch, env pinning) with an app-owned canvas background that recolors the
442
+ whole screen; terminal-following `auto` theme; subagent viewer with live
443
+ rounds/tokens/elapsed; subagent `maxAgents`/`maxRounds` limits; pi-aligned
444
+ keybindings with double-press guards; live todos + subagent progress blocks;
445
+ clean Ctrl+C exit. `pnpm check` clean, 296 unit tests green, e2e run
446
+ confirmed the settings/credentials files are restored byte-for-byte.
313
447
 
314
448
  Known limitations (accepted, pi-tui 0.84.2 constraints):
315
449
 
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Agent definitions as markdown files — the dsh terminal counterpart of the
3
+ * pi fun-agent / zcode "one file per agent" convention.
4
+ *
5
+ * An agent is a `<agents-dir>/<name>.md` file with a `---` frontmatter block
6
+ * and a markdown body that doubles as the agent's system prompt:
7
+ *
8
+ * ---
9
+ * name: oldfox
10
+ * display_name: 老法师
11
+ * description: "顾问角色:review、挑刺、保证健壮性。"
12
+ * color: red
13
+ * model: volc-ark-plan/glm-5.3
14
+ * thinking: high
15
+ * deep: 1
16
+ * ---
17
+ * You are 老法师 — 顾问…
18
+ *
19
+ * Frontmatter keys are parsed loosely (`key: value`, optional surrounding
20
+ * quotes); `name` is required, `description` feeds the picker subtitle,
21
+ * `model` is a dsh `provider/model` route (the picker rewrites it), and
22
+ * `deep` caps how many levels of subagents this agent may spawn (default 1,
23
+ * 0 = never, no unlimited — there is no spelling for unbounded depth).
24
+ * The body is kept verbatim.
25
+ */
26
+ /** One agent's frontmatter-derived metadata (the editable surface). */
27
+ export interface AgentMeta {
28
+ /** File basename without `.md` — required, the stable agent id. */
29
+ name: string;
30
+ /** Optional display name, shown before `name` in the picker. */
31
+ displayName?: string;
32
+ /** Optional one-line summary shown as the picker subtitle. */
33
+ description?: string;
34
+ /** Optional 8-color label (red/blue/green/yellow/purple/orange/pink/cyan). */
35
+ color?: string;
36
+ /** dsh model route (`provider/model`); absent = inherit the default. */
37
+ model?: string;
38
+ /** Reasoning effort id (off/low/medium/high/max); absent = inherit. */
39
+ thinking?: string;
40
+ /** Max subagent spawn depth: default 1, 0 = never spawn children. */
41
+ deep: number;
42
+ }
43
+ /** A parsed agent file: metadata + the raw system-prompt body. */
44
+ export interface AgentFile {
45
+ path: string;
46
+ meta: AgentMeta;
47
+ body: string;
48
+ }
49
+ /** One parse outcome: a usable agent, or a broken file with a reason. */
50
+ export type AgentParseResult = {
51
+ ok: true;
52
+ agent: AgentFile;
53
+ } | {
54
+ ok: false;
55
+ error: string;
56
+ };
57
+ /** The dsh agents directory (`~/.dsh/agents`, under the dsh home). */
58
+ export declare function agentsDir(): string;
59
+ /**
60
+ * The legacy agents directory from the first shipped layout (`~/dsh/agents`,
61
+ * no dot). Kept only for one-time migration — see `migrateLegacyAgentsDir`.
62
+ */
63
+ export declare function legacyAgentsDir(): string;
64
+ /**
65
+ * One-time migration from the legacy `~/dsh/agents` layout into
66
+ * `~/.dsh/agents`: when the target directory holds no agents but the legacy
67
+ * one does, every legacy file is copied over. Idempotent.
68
+ */
69
+ export declare function migrateLegacyAgentsDir(targetDir?: string, legacyDir?: string): number;
70
+ /** zcode agent files are the one-time seeding source (model converted). */
71
+ export declare function zcodeAgentsDir(): string;
72
+ /**
73
+ * Parse one agent markdown file. Tolerates CRLF, quotes, and non-key lines
74
+ * inside the frontmatter; `name` is required, `deep` must be a non-negative
75
+ * integer when present (absent defaults to 1).
76
+ */
77
+ export declare function parseAgentMarkdown(text: string, path: string): AgentParseResult;
78
+ /** Render an agent back to markdown (frontmatter + blank line + body). */
79
+ export declare function renderAgentMarkdown(meta: AgentMeta, body: string): string;
80
+ /** List agents under `dir` (top level only), broken files reported aside. */
81
+ export declare function listAgentFiles(dir: string): {
82
+ agents: AgentFile[];
83
+ broken: Array<{
84
+ path: string;
85
+ error: string;
86
+ }>;
87
+ };
88
+ /**
89
+ * Frontmatter field updates for one agent file. `undefined` leaves the key
90
+ * untouched; `null` removes the line (the "inherit" spelling — the agent
91
+ * falls back to its default); a concrete value writes/replaces the line.
92
+ */
93
+ export interface FrontmatterUpdates {
94
+ model?: string | null;
95
+ thinking?: string | null;
96
+ deep?: number | null;
97
+ }
98
+ /**
99
+ * Apply frontmatter updates in place: existing lines are rewritten in place,
100
+ * missing keys are inserted before the closing fence, `null` removes the
101
+ * line. CRLF is preserved; a no-op returns without touching the file.
102
+ * Resolves with an error message, or `undefined` on success.
103
+ */
104
+ export declare function updateAgentFrontmatter(path: string, updates: FrontmatterUpdates): string | undefined;
105
+ /**
106
+ * Convert a zcode `custom:<uri-encoded providerId>:<modelName>` model value
107
+ * to a dsh `provider/model` route for providers we know. Unknown providers
108
+ * return `undefined` (caller keeps the original line); a value without the
109
+ * `custom:` prefix is assumed to be dsh format already.
110
+ */
111
+ export declare function convertZcodeModel(model: string): string | undefined;
112
+ /**
113
+ * One-time seeding: when `targetDir` holds no agents yet, copy every
114
+ * parseable zcode agent into it with the model converted to a dsh route.
115
+ * Idempotent — once any agent exists, nothing is written.
116
+ */
117
+ export declare function seedFromZcode(targetDir: string, sourceDir?: string): {
118
+ seeded: number;
119
+ errors: Array<{
120
+ file: string;
121
+ error: string;
122
+ }>;
123
+ };
124
+ /** Short file label for diagnostics (kept importable for callers). */
125
+ export declare function agentFileLabel(path: string): string;