@aiwayds/dsh-tui-pi 0.1.0 → 0.3.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 (67) hide show
  1. package/README.md +205 -68
  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 +468 -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 +8 -0
  16. package/lib/footer.js +8 -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 -38
  22. package/lib/index.js.map +1 -1
  23. package/lib/keymap.d.ts +177 -0
  24. package/lib/keymap.js +189 -0
  25. package/lib/keymap.js.map +1 -0
  26. package/lib/live-widgets.d.ts +82 -29
  27. package/lib/live-widgets.js +222 -92
  28. package/lib/live-widgets.js.map +1 -1
  29. package/lib/messages.d.ts +4 -2
  30. package/lib/messages.js +8 -3
  31. package/lib/messages.js.map +1 -1
  32. package/lib/panels.d.ts +177 -0
  33. package/lib/panels.js +331 -0
  34. package/lib/panels.js.map +1 -0
  35. package/lib/provider-catalog.d.ts +31 -8
  36. package/lib/provider-catalog.js +63 -8
  37. package/lib/provider-catalog.js.map +1 -1
  38. package/lib/selectors.d.ts +12 -1
  39. package/lib/selectors.js +5 -2
  40. package/lib/selectors.js.map +1 -1
  41. package/lib/session.d.ts +36 -0
  42. package/lib/session.js +171 -10
  43. package/lib/session.js.map +1 -1
  44. package/lib/settings.d.ts +1 -2
  45. package/lib/settings.js +29 -2
  46. package/lib/settings.js.map +1 -1
  47. package/lib/subagent-policy.d.ts +58 -0
  48. package/lib/subagent-policy.js +137 -0
  49. package/lib/subagent-policy.js.map +1 -0
  50. package/lib/subagent-viewer.d.ts +49 -0
  51. package/lib/subagent-viewer.js +523 -0
  52. package/lib/subagent-viewer.js.map +1 -0
  53. package/lib/theme/index.js +4 -0
  54. package/lib/theme/index.js.map +1 -1
  55. package/lib/theme/palette.d.ts +21 -3
  56. package/lib/theme/palette.js +27 -6
  57. package/lib/theme/palette.js.map +1 -1
  58. package/lib/theme-settings.d.ts +39 -4
  59. package/lib/theme-settings.js +69 -12
  60. package/lib/theme-settings.js.map +1 -1
  61. package/lib/tui.d.ts +32 -12
  62. package/lib/tui.js +109 -36
  63. package/lib/tui.js.map +1 -1
  64. package/package.json +3 -4
  65. package/patches/@earendil-works__pi-tui.patch +206 -2
  66. package/scripts/dev-install.mjs +87 -0
  67. package/scripts/link-dsh-closure.mjs +85 -0
package/README.md CHANGED
@@ -16,16 +16,12 @@ pi-style terminal UI for [DeepSeek Harness](https://github.com/deepseek-ai/deeps
16
16
  │ ├─ ☑ Todo 1: 启动 subagent A 执行 10s 任务并收集结果 │ ← fixed widgets
17
17
  │ └─ ◐ Todo 2: 启动 subagent B 执行 10s 任务并收集结果 │ above the input
18
18
  └─────────────────────────────────────────────────────────────┘
19
- ┌─ ● Agents ─────────────────────────────────────────────────┐
20
- │ ├─ ⠼ spawn Subagent A 10s 任务 · 1.2k token · 19.0s │
21
- │ │ ⎿ running sleep… │
22
- │ └─ ⠼ spawn Subagent B 10s 任务 · 562 token · 6.0s │
23
- │ ⎿ working… │
24
- └─────────────────────────────────────────────────────────────┘
25
19
  ∴ working… ← status
26
20
  📁 ~/github (Full access) │ ⎇ main ← editor border
27
21
  [ 请输入指令… ] ← input
28
22
  ↳ 创建 2 个 todo, 每个 todo 起一个 10s 的 subagent ← last request
23
+ ↳ ⠼ Subagent A 10s 任务 · 1.2k token · 19.0s ← running agents
24
+ ↳ ⠼ Subagent B 10s 任务 · 562 token · 6.0s ← (compact lines)
29
25
  dsh ▸ ☁ opencode-go ▸ 🤖 deepseek-v4-flash ▸ ● high ▸ 🧠 11.6k/1.0M (1.2%) ▸ ⚡ CH98.9% ▸ 💬 8 ▸ 🔧 4 00:00:14 ← footer
30
26
  ⌨ Enter: send · Ctrl+C: cancel / double: quit ← hints
31
27
  ```
@@ -42,21 +38,27 @@ dsh ▸ ☁ opencode-go ▸ 🤖 deepseek-v4-flash ▸ ● high ▸ 🧠 11.6k/1
42
38
  `cmux-theme.sh` GitHub terminal themes). Hot-switchable at runtime: pick one
43
39
  with `/theme` (applies immediately), edit the `dsh-tui.theme` setting
44
40
  (external edits hot-apply too), or pin with `DSH_TUI_THEME=light|dark` — the
45
- env var wins over every preference. Without a preference, terminal-background
46
- 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.
47
46
  - **Footer**: powerline segments ported from
48
47
  [pi-powerline-footer](https://github.com/fan56/pi-powerline-footer) —
49
48
  provider / model+thinking / context / cache-hit / msgs / tools with U+E0B0
50
49
  arrows, right-aligned live clock, cwd+git-branch editor top border, and the
51
50
  `↳ last-request` widget.
52
- - **Live todos & subagents**: bordered panels pinned **above the chat input** —
53
- a `● Todos (done/total)` tree (`☐`/`◐`/`☑` status icons) and a live
54
- `● Agents` board (spinner, provider + label, retries, token count + context
55
- percent, elapsed and the current tool) refreshed ~10×/s. Show while there is
56
- content, clear when done a settled child drops off the board and an empty
57
- panel collapses to zero rows. Subagents are tracked from the child sessions
58
- themselves (header `origin: subagent` + `parentSession`), so any spawn
59
- mechanism works. The TUI also supports pi's `APPEND_SYSTEM.md` convention
51
+ - **Live todos & subagents**: the `● Todos (done/total)` tree (`☐`/`◐`/`☑`
52
+ status icons) is a bordered panel pinned **above the chat input**; the
53
+ running subagent activity merges into the **last-request area below the
54
+ editor** as compact lines (` ↳ ` prefix, spinner + agent **name** first,
55
+ retries, token count + context percent, elapsed no provider) no box, no
56
+ header, just one line per running
57
+ child. Both refresh ~10×/s. Show while there is content, clear when done —
58
+ a settled child drops off and an empty panel/area collapses to zero rows.
59
+ Subagents are tracked from the child sessions themselves (header `origin:
60
+ subagent` + `parentSession`), so any spawn mechanism works. The TUI also
61
+ supports pi's `APPEND_SYSTEM.md` convention
60
62
  (dsh side: `~/.dsh/APPEND_SYSTEM.md`): a user-editable file whose content is
61
63
  appended to the system prompt of every agent the TUI creates — read at each
62
64
  assembly, so edits apply to the next request without a restart. The TUI's
@@ -78,7 +80,10 @@ dsh ▸ ☁ opencode-go ▸ 🤖 deepseek-v4-flash ▸ ● high ▸ 🧠 11.6k/1
78
80
  | `/export` | write the current session log as JSONL — default `~/Downloads/dsh-session-<id>.jsonl`, or a path argument. |
79
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"). |
80
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. |
81
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). |
82
87
 
83
88
  Anything that is not a resolvable command falls through to the model as an
84
89
  ordinary prompt, so dsh packages' commands (and future registrations) appear
@@ -94,9 +99,9 @@ serves the route), and one-line API-key state (`API key set` / `missing` /
94
99
  document — with dedicated `DeepSeek (official)` and `Default model` rows and a
95
100
  `+ Add provider…` action. The add flow mirrors pi's `/login`:
96
101
 
97
- 1. pick from the built-in directory (10 searchable catalog routes Anthropic,
98
- DeepSeek, Google Gemini, Groq, Mistral, OpenAI, OpenCode Go, OpenRouter,
99
- Together AI, xAI);
102
+ 1. pick from the directory every llm-pi-ai catalog route that takes an API
103
+ key (36 in the installed pi-ai 0.82.1), read live from the llm service with
104
+ a static fallback, the same directory as the web Models page;
100
105
  2. enter exactly one API key — masked dot-row editor, the value never echoes
101
106
  and never reaches the rendered output;
102
107
  3. the commit double-writes like the web Models page: `llm-pi-ai.providers.<id>`
@@ -116,13 +121,26 @@ Themes change live, no restart:
116
121
  `applies: 'live'`. The namespace's watch hook pushes the commit to the
117
122
  running TUI, which repaints everything on the next frame: transcript
118
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.
119
132
  - An **external edit** of `~/.dsh/settings.yaml` (`dsh-tui.theme: dark`)
120
133
  hot-applies through the same watch path.
121
134
  - `DSH_TUI_THEME=light|dark` **pins** the display regardless of preference —
122
135
  it wins at startup and keeps winning; `/theme` still persists the
123
136
  preference and honestly reports `Theme preference saved — display is pinned
124
137
  by DSH_TUI_THEME=…` instead of claiming it applied.
125
- - 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.
126
144
 
127
145
  ## APPEND_SYSTEM.md
128
146
 
@@ -149,18 +167,25 @@ no reload, no watcher.
149
167
  ## Install (local)
150
168
 
151
169
  ```sh
152
- # build once
153
- cd dsh-tui-pi && pnpm install && pnpm build
154
-
155
- # live development link (recommended; edits to src/ + pnpm build apply on next launch)
156
- dsh plugin --profile tui add link:/path/to/dsh-tui-pi
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
157
172
 
158
- # or an npm tarball
159
- npm pack # → aiwayds-dsh-tui-pi-0.1.0.tgz
160
- 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
161
176
  ```
162
177
 
163
- 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.
164
189
 
165
190
  ## Use
166
191
 
@@ -170,15 +195,112 @@ dsh --profile tui # or: dsh-tui-pi (bin shim)
170
195
 
171
196
  - Type a prompt → Enter. Streaming reply renders live; tool calls render as
172
197
  `⚙/✔/✘` cards.
173
- - Todos and subagent children the model spawns show in bordered panels pinned
174
- **above the chat input** (they never scroll with the transcript): a
175
- `● Todos (done/total)` tree and a live `● Agents` board — spinner, provider
176
- + label, retries (`↻N≤M`), tokens (+ context percent), elapsed, and the
177
- current tool (`⎿ running …`). A finished child drops off the board; when
178
- nothing is left the panels collapse away.
198
+ - Todos the model spawns show in a bordered panel pinned **above the chat
199
+ input** (never scrolls with the transcript): a `● Todos (done/total)` tree.
200
+ Subagent children render as **compact lines in the last-request area below
201
+ the editor** (` <last request>` then one line per running child) ` ↳ `
202
+ prefix, spinner + agent **name** first, retries (`↻N≤M`), tokens
203
+ (+ context percent), elapsed (no box, no `● Agents` header, no provider). A
204
+ finished child drops off; when nothing is left the panel and the activity
205
+ lines collapse away.
179
206
  - `/` opens slash-command autocomplete (Tab/arrows/Enter).
180
- - Ctrl+C quits — while the agent is mid-turn the first press cancels the turn
181
- (`⏹ 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.
182
304
 
183
305
  ## Performance rules (from the pi-turbo findings)
184
306
 
@@ -216,7 +338,7 @@ dsh-tui-pi avoids both by construction:
216
338
  ```sh
217
339
  pnpm check # tsc --noEmit
218
340
  pnpm build # emit lib/
219
- pnpm test # unit tests, node --test against lib/ (171 tests, pretest builds)
341
+ pnpm test # unit tests, node --test against lib/ (296 tests, pretest builds)
220
342
  ```
221
343
 
222
344
  Local type-checking symlinks `node_modules/@deepseek-ai/*` to the installed
@@ -224,23 +346,13 @@ dsh closure (`/opt/homebrew/lib/node_modules/@deepseek-ai/dsh/node_modules`);
224
346
  at runtime those imports resolve to the same module instances the running dsh
225
347
  uses. Those symlinks stay out of any tarball (`files` ships lib/bin/patch only).
226
348
 
227
- ⚠️ `pnpm install` regenerates the three type-check symlinks declared in
228
- `package.json` (`dsh-settings`, `dsh-client-schema-form`, `schemastery`)
229
- into local `.pnpm` copies, splitting the cordis module identity and breaking
230
- `pnpm check` after any install, re-link them:
231
-
232
- ```sh
233
- 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/
234
- ```
235
-
236
- `dsh-permission-presets` is a fourth, undeclared type-check link — the same
237
- extraneous-closure pattern as `cordis`/`dsh-agent` above: `pnpm install`
238
- never regenerates it (it is not in the dependency tree), but a wiped
239
- `node_modules` needs it re-created by hand:
240
-
241
- ```sh
242
- ln -sfn /opt/homebrew/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-permission-presets node_modules/@deepseek-ai/
243
- ```
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`.
244
356
 
245
357
  **pi-tui patch**: this plugin applies a small patch to the pinned
246
358
  `@earendil-works/pi-tui` 0.84.2 (`pnpm.patchedDependencies` in
@@ -261,14 +373,16 @@ src/
261
373
  index.ts cordis plugin entry + wiring: command registration, footer,
262
374
  git watcher, clock, bridge, theme hot-swap sink, shutdown
263
375
  tui.ts TUI bootstrap: alt-screen tree, transcript ScrollView,
264
- 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)
265
378
  session.ts DshSessionBridge: lazy agent create, followup, resume,
266
379
  replay, cancel, O(1) incremental stats, persistDefaultModel,
267
380
  subagent tracker (tool-workflow + child events → live rows)
268
381
  dsh-events.ts local types + guards for tool-workflow/subagent/llm-retry
269
382
  events (declaring packages not installed) + AgentView
270
- live-widgets.ts LiveWidgets: fixed Todos/Agents widgets pinned above the
271
- chat window (renderTodos/renderAgents/tickLive/setTheme)
383
+ live-widgets.ts LiveWidgets: Todos boxed above the input + running-agent
384
+ activity merged under the last-request line
385
+ (renderTodos/renderAgents/setLastRequest/tickLive/setTheme)
272
386
  commands.ts CommandService: slash autocomplete + dual-channel dispatch
273
387
  (registerLocal agentless direct / ctx.commands host path)
274
388
  messages.ts TranscriptRenderer: session events → pi-tui components;
@@ -278,8 +392,19 @@ src/
278
392
  editor.ts CwdBorderEditor (top border: 📁 cwd │ ⎇ branch)
279
393
  git.ts GitBranchWatcher (polled, cached)
280
394
  frame.ts FramedOverlay: shared top/bottom ─ border for every popup
281
- provider-catalog.ts built-in provider directory + deriveKeyRef + row views
282
- (pure data/functions for the Models add-provider flow)
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)
405
+ provider-catalog.ts built-in provider directory (36 llm-pi-ai catalog
406
+ routes, mirrors the web Models page) + deriveKeyRef + row
407
+ views (pure data/functions for the Models add-provider flow)
283
408
  reload.ts /reload hot-reload (cordis-plugin-hmr style partial reload)
284
409
  text.ts clipToWidth / visibleWidth (grapheme-safe column clipping)
285
410
  theme-settings.ts dsh-tui settings namespace (applies: 'live') + watch sink
@@ -291,22 +416,34 @@ src/
291
416
  sessions.ts /session info panel + /resume persisted-session picker
292
417
  settings.ts /settings browser: categories, schema walk, inline editors,
293
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)
294
423
  theme/
295
424
  palette.ts GitHub light/dark palettes + terminal-background detection
425
+ (rgbIsLight luminance)
296
426
  index.ts buildTheme: Editor/Markdown/SelectList/chat roles, POWERLINE
297
427
  segment palette, resolveTheme (env > preference > detect)
298
- test/*.test.mjs unit tests, node --test against lib/ (171 across 14 files)
428
+ test/*.test.mjs unit tests, node --test against lib/ (296 across 24 files)
299
429
  ```
300
430
 
301
- ## Status (2026-08-15)
431
+ ## Changelog
432
+
433
+ See [CHANGELOG.md](CHANGELOG.md) for the release history.
434
+
435
+ ## Status (0.2.0)
302
436
 
303
437
  All surface commands shipped and tmux-e2e verified: `/model /think /session
304
- /resume /new /settings /export /theme /reload`; provider-first Models with the
305
- add-provider flow; overlay chrome (backgrounds + borders); theme hot-switch
306
- (immediate apply, external-change watch, env pinning); graded Ctrl+C; live
307
- todos + subagent progress blocks; clean Ctrl+C exit. `pnpm check` clean,
308
- 171 unit tests green, e2e run confirmed the settings/credentials files are
309
- 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.
310
447
 
311
448
  Known limitations (accepted, pi-tui 0.84.2 constraints):
312
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;