@aiwayds/dsh-tui-pi 0.1.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/LICENSE +21 -0
- package/README.md +324 -0
- package/bin/dsh-tui-pi +5 -0
- package/cordis.patch.yml +9 -0
- package/lib/append-system.d.ts +66 -0
- package/lib/append-system.js +161 -0
- package/lib/append-system.js.map +1 -0
- package/lib/commands.d.ts +53 -0
- package/lib/commands.js +167 -0
- package/lib/commands.js.map +1 -0
- package/lib/dsh-events.d.ts +106 -0
- package/lib/dsh-events.js +30 -0
- package/lib/dsh-events.js.map +1 -0
- package/lib/editor.d.ts +28 -0
- package/lib/editor.js +70 -0
- package/lib/editor.js.map +1 -0
- package/lib/footer.d.ts +36 -0
- package/lib/footer.js +112 -0
- package/lib/footer.js.map +1 -0
- package/lib/frame.d.ts +35 -0
- package/lib/frame.js +75 -0
- package/lib/frame.js.map +1 -0
- package/lib/git.d.ts +17 -0
- package/lib/git.js +51 -0
- package/lib/git.js.map +1 -0
- package/lib/index.d.ts +15 -0
- package/lib/index.js +781 -0
- package/lib/index.js.map +1 -0
- package/lib/instructions.d.ts +29 -0
- package/lib/instructions.js +67 -0
- package/lib/instructions.js.map +1 -0
- package/lib/live-widgets.d.ts +85 -0
- package/lib/live-widgets.js +218 -0
- package/lib/live-widgets.js.map +1 -0
- package/lib/messages.d.ts +277 -0
- package/lib/messages.js +734 -0
- package/lib/messages.js.map +1 -0
- package/lib/permission.d.ts +27 -0
- package/lib/permission.js +48 -0
- package/lib/permission.js.map +1 -0
- package/lib/provider-catalog.d.ts +114 -0
- package/lib/provider-catalog.js +124 -0
- package/lib/provider-catalog.js.map +1 -0
- package/lib/quotes.d.ts +28 -0
- package/lib/quotes.js +144 -0
- package/lib/quotes.js.map +1 -0
- package/lib/reload.d.ts +23 -0
- package/lib/reload.js +171 -0
- package/lib/reload.js.map +1 -0
- package/lib/selectors.d.ts +48 -0
- package/lib/selectors.js +261 -0
- package/lib/selectors.js.map +1 -0
- package/lib/session.d.ts +157 -0
- package/lib/session.js +555 -0
- package/lib/session.js.map +1 -0
- package/lib/sessions.d.ts +73 -0
- package/lib/sessions.js +253 -0
- package/lib/sessions.js.map +1 -0
- package/lib/settings.d.ts +180 -0
- package/lib/settings.js +1328 -0
- package/lib/settings.js.map +1 -0
- package/lib/text.d.ts +22 -0
- package/lib/text.js +45 -0
- package/lib/text.js.map +1 -0
- package/lib/theme/index.d.ts +79 -0
- package/lib/theme/index.js +121 -0
- package/lib/theme/index.js.map +1 -0
- package/lib/theme/palette.d.ts +56 -0
- package/lib/theme/palette.js +154 -0
- package/lib/theme/palette.js.map +1 -0
- package/lib/theme-settings.d.ts +68 -0
- package/lib/theme-settings.js +223 -0
- package/lib/theme-settings.js.map +1 -0
- package/lib/tui.d.ts +70 -0
- package/lib/tui.js +206 -0
- package/lib/tui.js.map +1 -0
- package/lib/welcome.d.ts +91 -0
- package/lib/welcome.js +281 -0
- package/lib/welcome.js.map +1 -0
- package/package.json +50 -0
- package/patches/@earendil-works__pi-tui.patch +72 -0
- package/pnpm-workspace.yaml +2 -0
- package/templates/APPEND_SYSTEM.md +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-tui-pi contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# dsh-tui-pi
|
|
2
|
+
|
|
3
|
+
pi-style terminal UI for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh).
|
|
4
|
+
|
|
5
|
+
## Preview (ASCII mock-up)
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
9
|
+
│ 🐳: 两个 subagent 已在后台并行启动 (A: 5b19b15c, B: d2072ffd) │ ← transcript (scrolls)
|
|
10
|
+
│ ┌─ 💭 thinking ──────────────────────────────────────────┐ │
|
|
11
|
+
│ │ Actually, I can check list_agents or wait… │ │
|
|
12
|
+
│ └────────────────────────────────────────────────────────┘ │
|
|
13
|
+
│ ⚙ bash python scripts/demo.py … ✔ bash │ │
|
|
14
|
+
└──────────────────────────────────────────────────────────────┘
|
|
15
|
+
┌─ ● Todos (1/2) ────────────────────────────────────────────┐
|
|
16
|
+
│ ├─ ☑ Todo 1: 启动 subagent A 执行 10s 任务并收集结果 │ ← fixed widgets
|
|
17
|
+
│ └─ ◐ Todo 2: 启动 subagent B 执行 10s 任务并收集结果 │ above the input
|
|
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
|
+
∴ working… ← status
|
|
26
|
+
📁 ~/github (Full access) │ ⎇ main ← editor border
|
|
27
|
+
[ 请输入指令… ] ← input
|
|
28
|
+
↳ 创建 2 个 todo, 每个 todo 起一个 10s 的 subagent ← last request
|
|
29
|
+
dsh ▸ ☁ opencode-go ▸ 🤖 deepseek-v4-flash ▸ ● high ▸ 🧠 11.6k/1.0M (1.2%) ▸ ⚡ CH98.9% ▸ 💬 8 ▸ 🔧 4 00:00:14 ← footer
|
|
30
|
+
⌨ Enter: send · Ctrl+C: cancel / double: quit ← hints
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- **Look & feel**: pi coding agent interactive TUI, built on
|
|
34
|
+
`@earendil-works/pi-tui` 0.84.2 (pinned) — alt-screen scrollable transcript,
|
|
35
|
+
docked editor/status/footer, markdown messages, slash-command autocomplete.
|
|
36
|
+
- **Slash commands**: dsh's own, untouched. Autocomplete from
|
|
37
|
+
`ctx.commands.list(agent)`, executed via `ctx.commands.execute(agent, line, signal)`.
|
|
38
|
+
Verified: `/compact`, `/plan`, `/goal`, `/permission`, `/feedback`. The TUI
|
|
39
|
+
adds its own surface commands — `/model /think /session /resume /new
|
|
40
|
+
/settings /export /theme /reload` (see below).
|
|
41
|
+
- **Themes**: GitHub light / GitHub dark palettes (aligned with the
|
|
42
|
+
`cmux-theme.sh` GitHub terminal themes). Hot-switchable at runtime: pick one
|
|
43
|
+
with `/theme` (applies immediately), edit the `dsh-tui.theme` setting
|
|
44
|
+
(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).
|
|
47
|
+
- **Footer**: powerline segments ported from
|
|
48
|
+
[pi-powerline-footer](https://github.com/fan56/pi-powerline-footer) —
|
|
49
|
+
provider / model+thinking / context / cache-hit / msgs / tools with U+E0B0
|
|
50
|
+
arrows, right-aligned live clock, cwd+git-branch editor top border, and the
|
|
51
|
+
`↳ 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
|
|
60
|
+
(dsh side: `~/.dsh/APPEND_SYSTEM.md`): a user-editable file whose content is
|
|
61
|
+
appended to the system prompt of every agent the TUI creates — read at each
|
|
62
|
+
assembly, so edits apply to the next request without a restart. The TUI's
|
|
63
|
+
own `dsh-tui-pi:todo-lifecycle` guidance (marker
|
|
64
|
+
`<!-- dsh-tui-pi:todo-lifecycle -->`) lives in that file, telling the model
|
|
65
|
+
to write an empty todo list once everything is completed (idempotent,
|
|
66
|
+
atomic, best-effort; the panel-side all-completed hide remains as fallback).
|
|
67
|
+
|
|
68
|
+
## Commands
|
|
69
|
+
|
|
70
|
+
| Command | What it does |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `/model` | pick provider/model — every route the mounted llm services list (pi-ai's built-in catalog + llm-deepseek's static pair). Two-stage picker: choose the model, then a think level when the route exposes one (Esc on stage 2 abandons the whole pick). Live switch with footer sync, persisted as the default. |
|
|
73
|
+
| `/think` | reasoning-effort picker for the current model — `(provider default)` clears the override, then the route's efforts (Off / High / Max …). Live + persisted. |
|
|
74
|
+
| `/session` | read-only info panel: id, cwd, created, model, think level, status, message/tool counts, token usage, event count, parent session. |
|
|
75
|
+
| `/resume` | pick a persisted session (subagent children and the current session are filtered out), validate its log *before* touching the live agent, then restore it — transcript and footer stats rebuild from the stored events. |
|
|
76
|
+
| `/new` | detach the current session and clear the transcript; the next prompt opens a fresh one (the escape hatch when the current history must not follow, e.g. images in it). |
|
|
77
|
+
| `/settings` | text-based settings browser: namespaces grouped into categories (General / Models / Plugins / Agent Presets / Other), schema walk with drill-ins, cycle rows, inline editors (secrets masked), dict add-key, reset-to-defaults. Writes go through the settings mutate chain. |
|
|
78
|
+
| `/export` | write the current session log as JSONL — default `~/Downloads/dsh-session-<id>.jsonl`, or a path argument. |
|
|
79
|
+
| `/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
|
+
| `/theme` | color-scheme picker (auto / light / dark). The choice applies immediately and is persisted to `dsh-tui.theme`. |
|
|
81
|
+
| `/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`. |
|
|
82
|
+
|
|
83
|
+
Anything that is not a resolvable command falls through to the model as an
|
|
84
|
+
ordinary prompt, so dsh packages' commands (and future registrations) appear
|
|
85
|
+
automatically.
|
|
86
|
+
|
|
87
|
+
### Models: provider-first
|
|
88
|
+
|
|
89
|
+
`/settings → Models` does not expose the raw `llm-pi-ai` namespace. It lists
|
|
90
|
+
one row per configured provider — label (`displayName` ?? catalog name ??
|
|
91
|
+
route key), value column (first model / `N models` / `catalog` when pi-ai
|
|
92
|
+
serves the route), and one-line API-key state (`API key set` / `missing` /
|
|
93
|
+
`not configured`) probed from the process environment plus the credentials
|
|
94
|
+
document — with dedicated `DeepSeek (official)` and `Default model` rows and a
|
|
95
|
+
`+ Add provider…` action. The add flow mirrors pi's `/login`:
|
|
96
|
+
|
|
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);
|
|
100
|
+
2. enter exactly one API key — masked dot-row editor, the value never echoes
|
|
101
|
+
and never reaches the rendered output;
|
|
102
|
+
3. the commit double-writes like the web Models page: `llm-pi-ai.providers.<id>`
|
|
103
|
+
gets `{ apiKeyEnv: <ref> }` through the settings mutate chain (ref derived
|
|
104
|
+
by convention: route key uppercased, non-alphanumerics → `_`, `_API_KEY`
|
|
105
|
+
suffix, e.g. `opencode-go → OPENCODE_GO_API_KEY`) and the key is stored via
|
|
106
|
+
`ctx.credentials.set` — never in `settings.yaml`. Without a credentials
|
|
107
|
+
service the profile still commits and the UI says
|
|
108
|
+
`export <REF>=<key> to use it` instead.
|
|
109
|
+
|
|
110
|
+
### Theme hot-switch
|
|
111
|
+
|
|
112
|
+
Themes change live, no restart:
|
|
113
|
+
|
|
114
|
+
- `/theme` (or an edit through `/settings → General → dsh-tui`) commits the
|
|
115
|
+
preference to the `dsh-tui` settings namespace, which is registered
|
|
116
|
+
`applies: 'live'`. The namespace's watch hook pushes the commit to the
|
|
117
|
+
running TUI, which repaints everything on the next frame: transcript
|
|
118
|
+
(replayed from its operation buffer), editor border, footer hint, spinner.
|
|
119
|
+
- An **external edit** of `~/.dsh/settings.yaml` (`dsh-tui.theme: dark`)
|
|
120
|
+
hot-applies through the same watch path.
|
|
121
|
+
- `DSH_TUI_THEME=light|dark` **pins** the display regardless of preference —
|
|
122
|
+
it wins at startup and keeps winning; `/theme` still persists the
|
|
123
|
+
preference and honestly reports `Theme preference saved — display is pinned
|
|
124
|
+
by DSH_TUI_THEME=…` instead of claiming it applied.
|
|
125
|
+
- The choice survives restarts (`auto` falls back to terminal detection).
|
|
126
|
+
|
|
127
|
+
## APPEND_SYSTEM.md
|
|
128
|
+
|
|
129
|
+
dsh-tui-pi supports pi's `APPEND_SYSTEM.md` convention on the dsh side:
|
|
130
|
+
**`~/.dsh/APPEND_SYSTEM.md`** (`$DSH_HOME` or `~/.dsh`) is appended to the
|
|
131
|
+
system prompt of every agent this TUI creates. The file is read at each
|
|
132
|
+
prompt assembly, so **edits apply to the very next request** — no restart,
|
|
133
|
+
no reload, no watcher.
|
|
134
|
+
|
|
135
|
+
- **The file is not shipped with the source**: the repo contains no
|
|
136
|
+
`APPEND_SYSTEM.md` at its root — it lives in your `~/.dsh` and is yours to
|
|
137
|
+
edit freely (identity, persona rules, UI conventions — anything you want
|
|
138
|
+
the model to know). The English **template** the installer seeds from is
|
|
139
|
+
`templates/APPEND_SYSTEM.md` (the pi orchestrator-identity definition,
|
|
140
|
+
translated — content lives in that file, not in code).
|
|
141
|
+
- On first run the TUI **creates** the file from the template if missing and
|
|
142
|
+
**maintains one marked section** in it (`<!-- dsh-tui-pi:todo-lifecycle
|
|
143
|
+
-->`, telling the model to clear the todo list once everything is
|
|
144
|
+
completed). Your own content is never touched; the maintenance is
|
|
145
|
+
idempotent, atomic (tmp + rename) and best-effort.
|
|
146
|
+
- An empty or missing file contributes nothing to the prompt (the section is
|
|
147
|
+
dropped by the prompt renderer).
|
|
148
|
+
|
|
149
|
+
## Install (local)
|
|
150
|
+
|
|
151
|
+
```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
|
|
157
|
+
|
|
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
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Both paths auto-add `dsh-tui-pi` to the profile's `dsh.profile.bundles`.
|
|
164
|
+
|
|
165
|
+
## Use
|
|
166
|
+
|
|
167
|
+
```sh
|
|
168
|
+
dsh --profile tui # or: dsh-tui-pi (bin shim)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
- Type a prompt → Enter. Streaming reply renders live; tool calls render as
|
|
172
|
+
`⚙/✔/✘` 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.
|
|
179
|
+
- `/` 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.
|
|
182
|
+
|
|
183
|
+
## Performance rules (from the pi-turbo findings)
|
|
184
|
+
|
|
185
|
+
pi's TUI lags in long sessions because its footer re-scans the whole session
|
|
186
|
+
log on every render (O(n)) and a 1s clock tick recomputes everything.
|
|
187
|
+
dsh-tui-pi avoids both by construction:
|
|
188
|
+
|
|
189
|
+
- **Event-driven incremental state**: `session/event` listeners maintain an
|
|
190
|
+
append-only transcript model + running counters (tokens, messages, tools,
|
|
191
|
+
cache-hit rate). Render never re-scans the dsh session log.
|
|
192
|
+
- **Footer reads O(1) maintained values** — never derived in `render()`.
|
|
193
|
+
- **Clock tick only re-renders the footer line**; transcript components cache.
|
|
194
|
+
- **Live widgets tick at 100 ms** (`AGENT_TICK_MS`, unref'd timer): a tick
|
|
195
|
+
only re-setTexts the widget's single Text (O(agents)) and is a no-op while
|
|
196
|
+
no child runs — never a transcript re-scan.
|
|
197
|
+
- **Streaming strategy**: deltas accumulate in a plain Text via `setText` on
|
|
198
|
+
the same component (never remove+re-add per token); markdown renders once on
|
|
199
|
+
the assembled `assistant/message` (no per-token markdown parsing).
|
|
200
|
+
- **Configurable think/tool panels** (`dsh-tui.panelHeight`, default `'5'`):
|
|
201
|
+
`'5'/'7'/'10'` set the total panel rows (top border + header row + body rows
|
|
202
|
+
+ bottom border); `'all'` prints the full body, with bounded on-screen
|
|
203
|
+
content — a streaming reasoning panel boxes a 200-line live tail while
|
|
204
|
+
chunks are in flight (the assembled message renders everything) and a
|
|
205
|
+
settled tool result keeps at most 2000 lines (a `… (+N lines)` marker
|
|
206
|
+
reports the drop). No inner scroll — pi-tui 0.84.2 never lays out nested
|
|
207
|
+
components, so a nested ScrollView cannot obtain a viewport. Body lines are
|
|
208
|
+
clipped to one physical row *before* styling, so long output can never wrap
|
|
209
|
+
the panel past its configured rows.
|
|
210
|
+
- **Width safety**: every truncation goes through `clipToWidth` (src/text.ts)
|
|
211
|
+
— CJK full-width characters count 2 columns and graphemes are never split.
|
|
212
|
+
Bare `String.length` clipping is banned.
|
|
213
|
+
|
|
214
|
+
## Dev
|
|
215
|
+
|
|
216
|
+
```sh
|
|
217
|
+
pnpm check # tsc --noEmit
|
|
218
|
+
pnpm build # emit lib/
|
|
219
|
+
pnpm test # unit tests, node --test against lib/ (171 tests, pretest builds)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Local type-checking symlinks `node_modules/@deepseek-ai/*` to the installed
|
|
223
|
+
dsh closure (`/opt/homebrew/lib/node_modules/@deepseek-ai/dsh/node_modules`);
|
|
224
|
+
at runtime those imports resolve to the same module instances the running dsh
|
|
225
|
+
uses. Those symlinks stay out of any tarball (`files` ships lib/bin/patch only).
|
|
226
|
+
|
|
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
|
+
```
|
|
244
|
+
|
|
245
|
+
**pi-tui patch**: this plugin applies a small patch to the pinned
|
|
246
|
+
`@earendil-works/pi-tui` 0.84.2 (`pnpm.patchedDependencies` in
|
|
247
|
+
`pnpm-workspace.yaml`, patch in `patches/`): it adds an `unselectedText`
|
|
248
|
+
SelectListTheme hook (full-row background on unselected rows), wires the
|
|
249
|
+
previously dead `selectedPrefix` hook (accent arrow on the selected row), and
|
|
250
|
+
frames the editor's slash-autocomplete list in a `│` box. The patch travels in
|
|
251
|
+
the tarball (`files` includes `patches` + `pnpm-workspace.yaml`); the
|
|
252
|
+
link:-mounted dev workflow uses the already-patched copy in this repo's
|
|
253
|
+
`node_modules`.
|
|
254
|
+
|
|
255
|
+
## Layout
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
bin/dsh-tui-pi launcher shim (exec dsh --profile tui)
|
|
259
|
+
cordis.patch.yml bundle patch: mounts the plugin as `tui-pi`
|
|
260
|
+
src/
|
|
261
|
+
index.ts cordis plugin entry + wiring: command registration, footer,
|
|
262
|
+
git watcher, clock, bridge, theme hot-swap sink, shutdown
|
|
263
|
+
tui.ts TUI bootstrap: alt-screen tree, transcript ScrollView,
|
|
264
|
+
dock (status/editor/last-request/footer), editor rebuild
|
|
265
|
+
session.ts DshSessionBridge: lazy agent create, followup, resume,
|
|
266
|
+
replay, cancel, O(1) incremental stats, persistDefaultModel,
|
|
267
|
+
subagent tracker (tool-workflow + child events → live rows)
|
|
268
|
+
dsh-events.ts local types + guards for tool-workflow/subagent/llm-retry
|
|
269
|
+
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)
|
|
272
|
+
commands.ts CommandService: slash autocomplete + dual-channel dispatch
|
|
273
|
+
(registerLocal agentless direct / ctx.commands host path)
|
|
274
|
+
messages.ts TranscriptRenderer: session events → pi-tui components;
|
|
275
|
+
streaming setText, height-configurable panels, ReplayOp
|
|
276
|
+
buffer for theme-switch rebuilds
|
|
277
|
+
footer.ts PowerlineFooter (ported segment palette, 7 segments + clock)
|
|
278
|
+
editor.ts CwdBorderEditor (top border: 📁 cwd │ ⎇ branch)
|
|
279
|
+
git.ts GitBranchWatcher (polled, cached)
|
|
280
|
+
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)
|
|
283
|
+
reload.ts /reload hot-reload (cordis-plugin-hmr style partial reload)
|
|
284
|
+
text.ts clipToWidth / visibleWidth (grapheme-safe column clipping)
|
|
285
|
+
theme-settings.ts dsh-tui settings namespace (applies: 'live') + watch sink
|
|
286
|
+
+ preference read/write with conflict retry
|
|
287
|
+
selectors.ts /model (two-stage), /think, /theme and /permission picker
|
|
288
|
+
overlays
|
|
289
|
+
permission.ts permission display names (web-client conventions) + picker
|
|
290
|
+
option assembly (pure, unit-tested)
|
|
291
|
+
sessions.ts /session info panel + /resume persisted-session picker
|
|
292
|
+
settings.ts /settings browser: categories, schema walk, inline editors,
|
|
293
|
+
serialized mutate write chain, add-provider flow
|
|
294
|
+
theme/
|
|
295
|
+
palette.ts GitHub light/dark palettes + terminal-background detection
|
|
296
|
+
index.ts buildTheme: Editor/Markdown/SelectList/chat roles, POWERLINE
|
|
297
|
+
segment palette, resolveTheme (env > preference > detect)
|
|
298
|
+
test/*.test.mjs unit tests, node --test against lib/ (171 across 14 files)
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Status (2026-08-15)
|
|
302
|
+
|
|
303
|
+
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.
|
|
310
|
+
|
|
311
|
+
Known limitations (accepted, pi-tui 0.84.2 constraints):
|
|
312
|
+
|
|
313
|
+
- **SelectList unselected rows and the SettingsList search row have no
|
|
314
|
+
background**: unselected rows render as raw `prefix + value` and the search
|
|
315
|
+
input row is pushed without a theme hook — the popup backdrop is striped
|
|
316
|
+
rather than one solid surface.
|
|
317
|
+
- **On a 24-row terminal a popup can reach the dock rows**: overlays float
|
|
318
|
+
over the status/editor area (the frame adds 4 rows on top of the list cap),
|
|
319
|
+
so the popup bottom border and the status line share screen rows while a
|
|
320
|
+
popup is open. Cosmetic — popups are modal.
|
|
321
|
+
- **An overlay open at switch time does not follow a theme hot-switch**: the
|
|
322
|
+
transcript, dock and editor repaint immediately, but an open popup keeps the
|
|
323
|
+
palette it was built with until it closes (its submenus inherit the same
|
|
324
|
+
stale bundle).
|
package/bin/dsh-tui-pi
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Convenience launcher: boots the dsh `tui` profile that mounts @aiwayds/dsh-tui-pi.
|
|
3
|
+
# Requires the dsh CLI on PATH and @aiwayds/dsh-tui-pi installed into that profile
|
|
4
|
+
# (dsh plugin --profile tui add @aiwayds/dsh-tui-pi).
|
|
5
|
+
exec dsh --profile tui "$@"
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# dsh-tui-pi bundle patch: mounts the pi-style TUI plugin over the profile's
|
|
2
|
+
# base bundle under the stable plugin id `tui-pi`. The plugin renders the
|
|
3
|
+
# terminal UI in-process with @earendil-works/pi-tui and talks to the dsh tree
|
|
4
|
+
# directly (ctx.agents / ctx.commands / session events), keeping dsh's slash
|
|
5
|
+
# commands untouched.
|
|
6
|
+
|
|
7
|
+
- insert:
|
|
8
|
+
- id: tui-pi
|
|
9
|
+
name: '@aiwayds/dsh-tui-pi'
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* APPEND_SYSTEM.md support — pi's convention (`~/.pi/agent/APPEND_SYSTEM.md`),
|
|
3
|
+
* dsh side: `~/.dsh/APPEND_SYSTEM.md`. A user-editable file whose content is
|
|
4
|
+
* appended to the system prompt of every agent this TUI creates. The TUI
|
|
5
|
+
* registers a system-prompt section whose text provider reads the file at
|
|
6
|
+
* each assembly, so edits apply to the very next request — no restart, no
|
|
7
|
+
* watcher.
|
|
8
|
+
*
|
|
9
|
+
* The TUI seeds a fresh file at first run (`ensureAppendSystemFile`) from
|
|
10
|
+
* the shipped template `templates/APPEND_SYSTEM.md` (the user's pi
|
|
11
|
+
* orchestrator-identity definition in English — content lives in the FILE,
|
|
12
|
+
* not in code), then maintains its marked todo-lifecycle section. An
|
|
13
|
+
* existing file is user-owned — the TUI only maintains its marker section
|
|
14
|
+
* there, idempotently (marker `<!-- dsh-tui-pi:todo-lifecycle -->`),
|
|
15
|
+
* atomically (tmp + rename) and best-effort — a failure is contained and
|
|
16
|
+
* reported, never breaks TUI startup. `migrateAgentsMdTodoSection` removes
|
|
17
|
+
* the todo section's earlier incarnation from `~/.dsh/AGENTS.md` (the
|
|
18
|
+
* pre-APPEND_SYSTEM.md delivery channel) so the guidance is not delivered
|
|
19
|
+
* twice.
|
|
20
|
+
*/
|
|
21
|
+
/** Idempotency marker for the TUI-owned section of the append file. */
|
|
22
|
+
export declare const TODO_LIFECYCLE_MARKER = "<!-- dsh-tui-pi:todo-lifecycle -->";
|
|
23
|
+
/**
|
|
24
|
+
* The TUI-owned section: the model must clear the todo list when the work is
|
|
25
|
+
* done, because `todo/write` is a whole-list snapshot (last-write-wins) and
|
|
26
|
+
* an all-completed list would stay pinned above the chat input.
|
|
27
|
+
*/
|
|
28
|
+
export declare const TODO_LIFECYCLE_SECTION = "<!-- dsh-tui-pi:todo-lifecycle -->\n## Todo list lifecycle (dsh-tui-pi)\n\nThe UI renders your todo list as a fixed panel above the chat input. Keep\nitems `pending` or `in_progress` while they are not done. When EVERY todo is\ncompleted \u2014 no pending or in-progress items remain \u2014 write an EMPTY todo list\n(`todos: []`) so the panel clears. Never leave a fully-completed list behind.\n";
|
|
29
|
+
/** The user-editable append file path (`$DSH_HOME/APPEND_SYSTEM.md`). */
|
|
30
|
+
export declare function appendSystemPath(home?: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* The shipped English template (`templates/APPEND_SYSTEM.md`): the user's pi
|
|
33
|
+
* orchestrator-identity definition translated to English. The content lives
|
|
34
|
+
* in this FILE, not in code — installation seeds `~/.dsh/APPEND_SYSTEM.md`
|
|
35
|
+
* from it (a fresh file only; existing files are user-owned).
|
|
36
|
+
*/
|
|
37
|
+
export declare function appendSystemTemplatePath(): string;
|
|
38
|
+
/**
|
|
39
|
+
* Synchronous read for the system-prompt section provider (the provider
|
|
40
|
+
* signature is sync; the file is small and read once per assembly).
|
|
41
|
+
* @returns the file content, or '' when missing/unreadable — an empty
|
|
42
|
+
* section is dropped by the prompt renderer.
|
|
43
|
+
*/
|
|
44
|
+
export declare function readAppendSystem(path?: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Ensure the append file exists with the shipped template and the TUI's
|
|
47
|
+
* marked todo-lifecycle section: a missing file is seeded from
|
|
48
|
+
* `templates/APPEND_SYSTEM.md` (the English orchestrator template) followed
|
|
49
|
+
* by the marked section; an existing file is user-owned — only the marked
|
|
50
|
+
* section is appended when its marker is missing, and a marked file is left
|
|
51
|
+
* untouched.
|
|
52
|
+
* @param path - target file (injectable for tests).
|
|
53
|
+
* @param templatePath - the shipped template (injectable for tests).
|
|
54
|
+
* @returns an error message on failure, undefined on success (including the
|
|
55
|
+
* no-op case where the marker is already present).
|
|
56
|
+
*/
|
|
57
|
+
export declare function ensureAppendSystemFile(path?: string, templatePath?: string): Promise<string | undefined>;
|
|
58
|
+
/**
|
|
59
|
+
* Migration: strip the TUI-owned todo-lifecycle block from `~/.dsh/AGENTS.md`
|
|
60
|
+
* (the earlier delivery channel). No-op when the marker is absent; the file
|
|
61
|
+
* is deleted when the strip leaves it empty. The block is removed by exact
|
|
62
|
+
* content match, so a user-edited block is left alone (best-effort). Every
|
|
63
|
+
* write here is atomic and failure-contained.
|
|
64
|
+
* @returns an error message on failure, undefined on success or no-op.
|
|
65
|
+
*/
|
|
66
|
+
export declare function migrateAgentsMdTodoSection(path?: string): Promise<string | undefined>;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* APPEND_SYSTEM.md support — pi's convention (`~/.pi/agent/APPEND_SYSTEM.md`),
|
|
3
|
+
* dsh side: `~/.dsh/APPEND_SYSTEM.md`. A user-editable file whose content is
|
|
4
|
+
* appended to the system prompt of every agent this TUI creates. The TUI
|
|
5
|
+
* registers a system-prompt section whose text provider reads the file at
|
|
6
|
+
* each assembly, so edits apply to the very next request — no restart, no
|
|
7
|
+
* watcher.
|
|
8
|
+
*
|
|
9
|
+
* The TUI seeds a fresh file at first run (`ensureAppendSystemFile`) from
|
|
10
|
+
* the shipped template `templates/APPEND_SYSTEM.md` (the user's pi
|
|
11
|
+
* orchestrator-identity definition in English — content lives in the FILE,
|
|
12
|
+
* not in code), then maintains its marked todo-lifecycle section. An
|
|
13
|
+
* existing file is user-owned — the TUI only maintains its marker section
|
|
14
|
+
* there, idempotently (marker `<!-- dsh-tui-pi:todo-lifecycle -->`),
|
|
15
|
+
* atomically (tmp + rename) and best-effort — a failure is contained and
|
|
16
|
+
* reported, never breaks TUI startup. `migrateAgentsMdTodoSection` removes
|
|
17
|
+
* the todo section's earlier incarnation from `~/.dsh/AGENTS.md` (the
|
|
18
|
+
* pre-APPEND_SYSTEM.md delivery channel) so the guidance is not delivered
|
|
19
|
+
* twice.
|
|
20
|
+
*/
|
|
21
|
+
import { readFile, rename, unlink, writeFile } from 'node:fs/promises';
|
|
22
|
+
import { readFileSync } from 'node:fs';
|
|
23
|
+
import { homedir } from 'node:os';
|
|
24
|
+
import { dirname, join, resolve } from 'node:path';
|
|
25
|
+
import { fileURLToPath } from 'node:url';
|
|
26
|
+
/** Idempotency marker for the TUI-owned section of the append file. */
|
|
27
|
+
export const TODO_LIFECYCLE_MARKER = '<!-- dsh-tui-pi:todo-lifecycle -->';
|
|
28
|
+
/**
|
|
29
|
+
* The TUI-owned section: the model must clear the todo list when the work is
|
|
30
|
+
* done, because `todo/write` is a whole-list snapshot (last-write-wins) and
|
|
31
|
+
* an all-completed list would stay pinned above the chat input.
|
|
32
|
+
*/
|
|
33
|
+
export const TODO_LIFECYCLE_SECTION = `${TODO_LIFECYCLE_MARKER}
|
|
34
|
+
## Todo list lifecycle (dsh-tui-pi)
|
|
35
|
+
|
|
36
|
+
The UI renders your todo list as a fixed panel above the chat input. Keep
|
|
37
|
+
items \`pending\` or \`in_progress\` while they are not done. When EVERY todo is
|
|
38
|
+
completed — no pending or in-progress items remain — write an EMPTY todo list
|
|
39
|
+
(\`todos: []\`) so the panel clears. Never leave a fully-completed list behind.
|
|
40
|
+
`;
|
|
41
|
+
/** Default harness home: `$DSH_HOME` or `~/.dsh`. */
|
|
42
|
+
function dshHome() {
|
|
43
|
+
return process.env.DSH_HOME ?? join(homedir(), '.dsh');
|
|
44
|
+
}
|
|
45
|
+
/** The user-editable append file path (`$DSH_HOME/APPEND_SYSTEM.md`). */
|
|
46
|
+
export function appendSystemPath(home = dshHome()) {
|
|
47
|
+
return join(home, 'APPEND_SYSTEM.md');
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The shipped English template (`templates/APPEND_SYSTEM.md`): the user's pi
|
|
51
|
+
* orchestrator-identity definition translated to English. The content lives
|
|
52
|
+
* in this FILE, not in code — installation seeds `~/.dsh/APPEND_SYSTEM.md`
|
|
53
|
+
* from it (a fresh file only; existing files are user-owned).
|
|
54
|
+
*/
|
|
55
|
+
export function appendSystemTemplatePath() {
|
|
56
|
+
return resolve(dirname(fileURLToPath(import.meta.url)), '..', 'templates', 'APPEND_SYSTEM.md');
|
|
57
|
+
}
|
|
58
|
+
/** The user-global AGENTS.md path (the legacy delivery channel). */
|
|
59
|
+
function agentsMdPath(home = dshHome()) {
|
|
60
|
+
return join(home, 'AGENTS.md');
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Synchronous read for the system-prompt section provider (the provider
|
|
64
|
+
* signature is sync; the file is small and read once per assembly).
|
|
65
|
+
* @returns the file content, or '' when missing/unreadable — an empty
|
|
66
|
+
* section is dropped by the prompt renderer.
|
|
67
|
+
*/
|
|
68
|
+
export function readAppendSystem(path = appendSystemPath()) {
|
|
69
|
+
try {
|
|
70
|
+
return readFileSync(path, 'utf8');
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return '';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Ensure the append file exists with the shipped template and the TUI's
|
|
78
|
+
* marked todo-lifecycle section: a missing file is seeded from
|
|
79
|
+
* `templates/APPEND_SYSTEM.md` (the English orchestrator template) followed
|
|
80
|
+
* by the marked section; an existing file is user-owned — only the marked
|
|
81
|
+
* section is appended when its marker is missing, and a marked file is left
|
|
82
|
+
* untouched.
|
|
83
|
+
* @param path - target file (injectable for tests).
|
|
84
|
+
* @param templatePath - the shipped template (injectable for tests).
|
|
85
|
+
* @returns an error message on failure, undefined on success (including the
|
|
86
|
+
* no-op case where the marker is already present).
|
|
87
|
+
*/
|
|
88
|
+
export async function ensureAppendSystemFile(path = appendSystemPath(), templatePath = appendSystemTemplatePath()) {
|
|
89
|
+
let existing;
|
|
90
|
+
try {
|
|
91
|
+
existing = await readFile(path, 'utf8');
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
existing = '';
|
|
95
|
+
}
|
|
96
|
+
if (existing.includes(TODO_LIFECYCLE_MARKER))
|
|
97
|
+
return undefined;
|
|
98
|
+
if (existing !== '') {
|
|
99
|
+
return writeAtomically(path, `${existing.replace(/\s+$/u, '')}\n\n${TODO_LIFECYCLE_SECTION}`);
|
|
100
|
+
}
|
|
101
|
+
// Fresh file: seed from the shipped template, then the marked section. A
|
|
102
|
+
// missing template (broken tarball) degrades to the marked section alone.
|
|
103
|
+
let template;
|
|
104
|
+
try {
|
|
105
|
+
template = await readFile(templatePath, 'utf8');
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
template = '';
|
|
109
|
+
}
|
|
110
|
+
const seed = template === '' ? TODO_LIFECYCLE_SECTION : `${template.replace(/\s+$/u, '')}\n\n${TODO_LIFECYCLE_SECTION}`;
|
|
111
|
+
return writeAtomically(path, seed);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Migration: strip the TUI-owned todo-lifecycle block from `~/.dsh/AGENTS.md`
|
|
115
|
+
* (the earlier delivery channel). No-op when the marker is absent; the file
|
|
116
|
+
* is deleted when the strip leaves it empty. The block is removed by exact
|
|
117
|
+
* content match, so a user-edited block is left alone (best-effort). Every
|
|
118
|
+
* write here is atomic and failure-contained.
|
|
119
|
+
* @returns an error message on failure, undefined on success or no-op.
|
|
120
|
+
*/
|
|
121
|
+
export async function migrateAgentsMdTodoSection(path = agentsMdPath()) {
|
|
122
|
+
let content;
|
|
123
|
+
try {
|
|
124
|
+
content = await readFile(path, 'utf8');
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return undefined; // no AGENTS.md — nothing to migrate
|
|
128
|
+
}
|
|
129
|
+
if (!content.includes(TODO_LIFECYCLE_MARKER))
|
|
130
|
+
return undefined;
|
|
131
|
+
const next = content.trim() === TODO_LIFECYCLE_SECTION.trim()
|
|
132
|
+
? ''
|
|
133
|
+
: content.split(TODO_LIFECYCLE_SECTION).join('').replace(/\n{3,}/gu, '\n\n').trim();
|
|
134
|
+
if (next === '') {
|
|
135
|
+
try {
|
|
136
|
+
await unlink(path);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
return error instanceof Error ? error.message : String(error);
|
|
140
|
+
}
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
return writeAtomically(path, `${next}\n`);
|
|
144
|
+
}
|
|
145
|
+
/** Atomic write (tmp + rename); the tmp file is cleaned up on failure. */
|
|
146
|
+
async function writeAtomically(path, content) {
|
|
147
|
+
const tmp = `${path}.tmp`;
|
|
148
|
+
try {
|
|
149
|
+
await writeFile(tmp, content, 'utf8');
|
|
150
|
+
await rename(tmp, path);
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
try {
|
|
155
|
+
await unlink(tmp);
|
|
156
|
+
}
|
|
157
|
+
catch { /* already gone */ }
|
|
158
|
+
return error instanceof Error ? error.message : String(error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=append-system.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"append-system.js","sourceRoot":"","sources":["../src/append-system.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slash-command integration with dsh's own command registry.
|
|
3
|
+
*
|
|
4
|
+
* dsh-tui-pi never re-implements a command: autocomplete lists
|
|
5
|
+
* `ctx.commands.list(agent)` and submission routes through
|
|
6
|
+
* `ctx.commands.execute(agent, line, signal)`. Anything that is not a
|
|
7
|
+
* resolvable command falls through to the model as an ordinary prompt, so
|
|
8
|
+
* every command registered by dsh packages (plan, compact, feedback, export,
|
|
9
|
+
* permission, goal, …) works here unchanged and future registrations appear
|
|
10
|
+
* automatically.
|
|
11
|
+
*
|
|
12
|
+
* One exemption: TUI-owned commands that never touch the receiving agent
|
|
13
|
+
* (model pickers, settings browser, session info/resume) are dispatched
|
|
14
|
+
* locally when no live agent exists — dsh's execute path addresses an agent
|
|
15
|
+
* and would mint a throwaway session just to run them.
|
|
16
|
+
*/
|
|
17
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
18
|
+
import { type CommandDescriptor, type CommandResult } from '@deepseek-ai/dsh-commands';
|
|
19
|
+
import type { AutocompleteProvider } from '@earendil-works/pi-tui';
|
|
20
|
+
import type { DshSessionBridge } from './session.ts';
|
|
21
|
+
/** A TUI-owned command body that needs no receiving agent. */
|
|
22
|
+
export type LocalCommandHandler = (rawInput: string, signal: AbortSignal) => CommandResult | Promise<CommandResult>;
|
|
23
|
+
export declare class CommandService {
|
|
24
|
+
private readonly ctx;
|
|
25
|
+
private readonly bridge;
|
|
26
|
+
/** TUI-owned command bodies, dispatchable without a live agent. */
|
|
27
|
+
private readonly local;
|
|
28
|
+
constructor(ctx: Context, bridge: DshSessionBridge);
|
|
29
|
+
/**
|
|
30
|
+
* Register a local command body (the same handler the ctx.commands
|
|
31
|
+
* registration wraps). It is dispatched directly when no live agent
|
|
32
|
+
* exists, avoiding a throwaway session for agentless commands.
|
|
33
|
+
*/
|
|
34
|
+
registerLocal(name: string, handler: LocalCommandHandler): void;
|
|
35
|
+
/**
|
|
36
|
+
* Try to run `line` as a dsh slash command.
|
|
37
|
+
* @returns `handled: true` when the line was admitted as a command (the
|
|
38
|
+
* caller must not forward it to the model), with an optional `error` or
|
|
39
|
+
* success `text` to surface; `handled: false` when it is not a command.
|
|
40
|
+
*/
|
|
41
|
+
tryExecute(line: string, signal: AbortSignal): Promise<{
|
|
42
|
+
handled: boolean;
|
|
43
|
+
error?: string;
|
|
44
|
+
text?: string;
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* Effective command descriptors for the current agent. Warms the session on
|
|
48
|
+
* first use so `/` autocompletes before any prompt was sent.
|
|
49
|
+
*/
|
|
50
|
+
list(): Promise<readonly CommandDescriptor[]>;
|
|
51
|
+
/** pi-tui autocomplete provider over the live command registry. */
|
|
52
|
+
autocompleteProvider(): AutocompleteProvider;
|
|
53
|
+
}
|