@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.
- package/README.md +205 -68
- package/lib/agent-manager.d.ts +125 -0
- package/lib/agent-manager.js +298 -0
- package/lib/agent-manager.js.map +1 -0
- package/lib/agents.d.ts +30 -0
- package/lib/agents.js +468 -0
- package/lib/agents.js.map +1 -0
- package/lib/append-system.d.ts +2 -0
- package/lib/append-system.js +1 -1
- package/lib/append-system.js.map +1 -1
- package/lib/dsh-events.d.ts +12 -0
- package/lib/editor.d.ts +58 -0
- package/lib/editor.js +92 -0
- package/lib/editor.js.map +1 -1
- package/lib/footer.d.ts +8 -0
- package/lib/footer.js +8 -0
- package/lib/footer.js.map +1 -1
- package/lib/hotkeys.d.ts +93 -0
- package/lib/hotkeys.js +305 -0
- package/lib/hotkeys.js.map +1 -0
- package/lib/index.js +342 -38
- package/lib/index.js.map +1 -1
- package/lib/keymap.d.ts +177 -0
- package/lib/keymap.js +189 -0
- package/lib/keymap.js.map +1 -0
- package/lib/live-widgets.d.ts +82 -29
- package/lib/live-widgets.js +222 -92
- package/lib/live-widgets.js.map +1 -1
- package/lib/messages.d.ts +4 -2
- package/lib/messages.js +8 -3
- package/lib/messages.js.map +1 -1
- package/lib/panels.d.ts +177 -0
- package/lib/panels.js +331 -0
- package/lib/panels.js.map +1 -0
- package/lib/provider-catalog.d.ts +31 -8
- package/lib/provider-catalog.js +63 -8
- package/lib/provider-catalog.js.map +1 -1
- package/lib/selectors.d.ts +12 -1
- package/lib/selectors.js +5 -2
- package/lib/selectors.js.map +1 -1
- package/lib/session.d.ts +36 -0
- package/lib/session.js +171 -10
- package/lib/session.js.map +1 -1
- package/lib/settings.d.ts +1 -2
- package/lib/settings.js +29 -2
- package/lib/settings.js.map +1 -1
- package/lib/subagent-policy.d.ts +58 -0
- package/lib/subagent-policy.js +137 -0
- package/lib/subagent-policy.js.map +1 -0
- package/lib/subagent-viewer.d.ts +49 -0
- package/lib/subagent-viewer.js +523 -0
- package/lib/subagent-viewer.js.map +1 -0
- package/lib/theme/index.js +4 -0
- package/lib/theme/index.js.map +1 -1
- package/lib/theme/palette.d.ts +21 -3
- package/lib/theme/palette.js +27 -6
- package/lib/theme/palette.js.map +1 -1
- package/lib/theme-settings.d.ts +39 -4
- package/lib/theme-settings.js +69 -12
- package/lib/theme-settings.js.map +1 -1
- package/lib/tui.d.ts +32 -12
- package/lib/tui.js +109 -36
- package/lib/tui.js.map +1 -1
- package/package.json +3 -4
- package/patches/@earendil-works__pi-tui.patch +206 -2
- package/scripts/dev-install.mjs +87 -0
- 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.
|
|
46
|
-
|
|
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**:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
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
|
|
153
|
-
|
|
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
|
|
159
|
-
|
|
160
|
-
dsh plugin --profile tui add /path/to/aiwayds-dsh-tui-pi-0.
|
|
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
|
-
|
|
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
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
181
|
-
|
|
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/ (
|
|
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
|
-
|
|
228
|
-
`
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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:
|
|
271
|
-
|
|
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
|
-
|
|
282
|
-
|
|
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/ (
|
|
428
|
+
test/*.test.mjs unit tests, node --test against lib/ (296 across 24 files)
|
|
299
429
|
```
|
|
300
430
|
|
|
301
|
-
##
|
|
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
|
|
305
|
-
add-provider flow; overlay chrome
|
|
306
|
-
(immediate apply, external-change
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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;
|