@aiwayds/dsh-tui-pi 1.0.0 → 1.0.2

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 CHANGED
@@ -2,223 +2,108 @@
2
2
 
3
3
  # dsh-tui-pi
4
4
 
5
- pi-style terminal UI for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) — a plugin suite that turns dsh into a pi-like coding agent experience.
5
+ pi-style terminal UI for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) — a plugin suite that turns dsh into a pi-like coding-agent experience: pi-tui look & feel, dsh slash commands, GitHub light/dark themes and a powerline footer.
6
6
 
7
- **Compatibility:** tested against dsh `0.1.1-rc.2`. Slash-command execution goes
8
- through an `executeCommand()` compat shim (`src/commands.ts`) that probes the
9
- arity of `dsh-commands`' `execute()` at runtime — supporting both the pre-rc.8
10
- 3-arg form `(agent, line, signal)` and the current 4-arg form
11
- `(agent, line, images, signal)`, unchanged since `0.1.0-rc.8`. Verified by unit
12
- tests plus a live tmux e2e smoke.
13
-
14
- > 中文说明: [README.zh.md](README.zh.md)
15
-
16
- ## Screenshot
7
+ **Compatibility:** tested against dsh `0.1.1-rc.2`; slash commands keep working across dsh-commands' `execute()` signature change (pre-rc.8 3-arg → current 4-arg).
17
8
 
18
9
  https://github.com/user-attachments/assets/6a7e00bb-1fd0-4bc5-9070-457f1e9fa54d
19
10
 
20
- A live terminal recording of a session (MP4, 1.5× speed) — todos, running
21
- subagents, think/tool panels and the powerline footer in action.
22
- ([Interactive playback on asciinema](https://asciinema.org/a/BE212ZO8x1zEZyZn))
11
+ *A live recording of a session (MP4, 1.5× speed) — todos, running subagents, think/tool panels and the powerline footer in action.*
23
12
 
24
- ### Layout overview
13
+ ## Features
25
14
 
26
- ```
27
- ┌─────────────────────────────────────────────────────────────────────┐
28
- │ Transcript (scrollable) │
29
- │ ┌─────────────────────────────────────────────────────────────┐ │
30
- │ │ 💭 thinking — reasoning in progress │ │
31
- │ └─────────────────────────────────────────────────────────────┘ │
32
- │ ⚙ bash python scripts/demo.py … ✔ bash │
33
- │ ↳ 生成 2 个 todo, 每个 todo 起一个 10s 的 subagent │
34
- │ ↳ ⠼ Workhorse 10s 任务 · 1.2k token · 19.0s │
35
- └─────────────────────────────────────────────────────────────────────┘
36
- ┌─ ● Todos (0/8) ────────────────────────────────────────────────────┐
37
- │ ├─ ☑ 调研 dsh-tui-pi 斜杠命令/补全机制 │
38
- │ ├─ ◐ 调研 harness ctx.skills API │
39
- │ └─ ☐ 实现 /skill:<name> 补全并触发 skill │
40
- └─────────────────────────────────────────────────────────────────────┘
41
- ∴ working… │
42
- ~/github (Full access) │ ⎇ main │
43
- [ 请输入指令… ] │
44
- ↳ 第一 打slash 命令的时候 显示 /skill:<skill name> 选择后使用 │
45
- ↳ ⠼ 牛马狗 · 1.5m/1m · 635.7s │
46
- dsh ▸ volc-ark-plan ▸ deepseek-v4-flash ▸ high ▸ 48.7k/1.0M(4.6%) │
47
- ▸ ⚡ CH85.4% ▸ 15 msgs ▸ 11 tools 00:02:13 │
48
- Esc ×2: stop · Ctrl+C ×2: quit · Ctrl+G: subagents · ↑↓: history │
49
- └─────────────────────────────────────────────────────────────────────┘
50
- │ │ │
51
- │ │ └─ Footer (powerline)
52
- │ └─ Running subagents (last-request area)
53
- └─ Todos panel (bordered, above editor)
54
- ```
15
+ > Each item links to its section below — one line for what it gives you.
55
16
 
56
- ---
17
+ - [**Footer — live session overview**](#footer) — provider/model, context pressure and the session cache-hit rate at a glance, always in view.
18
+ - [**Think & tool panels**](#think-and-tool-panels) — reasoning and tool activity stay out of the transcript, so the conversation reads clean.
19
+ - [**Subagents**](#subagents) — every running subagent gets a status line; watch and steer it live.
20
+ - [**Ask User Question**](#ask-user-question) — the model can pause and ask you structured questions, answered without leaving the TUI.
21
+ - [**Feishu integration demo**](#feishu-integration-demo) — dsh-tui-pi on the desktop and Feishu/Lark on the phone driving (and answering for) the same dsh session.
22
+ - [**Dynamic context pruning (DCP)**](#dynamic-context-pruning-dcp) — context stays within limits automatically, with zero LLM calls.
23
+ - [**Persistent context**](#persistent-context) — your ground rules ride along on every request, hot-applied with no restart.
24
+ - [**Model profiles & favorites**](#model-profiles-and-favorites) — switch a whole model setup per project and keep the picker small.
25
+ - [**Sessions & resume**](#sessions-and-resume) — sessions stay tidy automatically and resume in a few keystrokes.
26
+ - [**Themes**](#themes) — GitHub light/dark palettes, hot-switchable; `auto` follows your terminal.
57
27
 
58
- ## Features
28
+ ---
59
29
 
60
- ### Footer
30
+ ## Footer
61
31
 
62
- A powerline-style status bar pinned at the bottom of the screen, showing live session state at a glance:
32
+ Every number that matters for a session — provider/model route, thinking level, context use, message and tool counts, plus a live clock — sits in one powerline bar pinned at the bottom, with the editor's top border showing your cwd and git branch. You see cost pressure (context %, cache-hit %) and activity without ever leaving the terminal.
63
33
 
64
34
  ```
65
35
  dsh ▸ volc-ark-plan ▸ deepseek-v4-flash ▸ high ▸ 48.7k/1.0M(4.6%) ▸ ⚡ CH85.4% ▸ 15 msgs ▸ 11 tools 00:02:13
66
36
  ```
67
37
 
68
- Seven segments read O(1) maintained counters (never re-scan the session log):
69
-
70
- | Segment | Content |
71
- |---|---|
72
- | **Provider** | current `provider/model` route |
73
- | **Model** | model short-name |
74
- | **Thinking** | reasoning effort level (`off` / `high` / `max`) |
75
- | **Context** | `used / max (percent%)` |
76
- | **Cache-hit** | `CHxx%` — prompt-cache hit rate |
77
- | **Messages** | total user + assistant messages |
78
- | **Tools** | total tool invocations |
79
- | **Clock** | live right-aligned HH:MM:SS (tick every second) |
80
-
81
- Segments are rendered with [U+E0B0](https://www.nerdfonts.com/cheat-sheet) powerline arrows; the palette is hot-swappable with the current theme.
82
-
83
- The editor's top border shows the working directory and git branch:
84
-
85
- ```
86
- ~/github (Full access) │ ⎇ main
87
- ```
38
+ **Cache-hit (`CHxx%`)** is the session's cache-hit rate — the share of the session's total billed input traffic that was served from the prompt cache. It is cumulative over the whole session (a provider/model switch does not reset it) and appears only once the session has actually billed any cached tokens. (Layout: [ARCHITECTURE.md](ARCHITECTURE.md).)
88
39
 
89
40
  ---
90
41
 
91
- ### Think & Tool Blocks
42
+ ## Think and tool panels
92
43
 
93
- In-flight thinking and tool calls render as fixed **panels pinned above the chat input** (they never appear in the scrollable transcript):
94
-
95
- ```
96
- ┌─ 💭 thinking ──────────────────────────────────────────────┐
97
- │ Actually, I can check list_agents or wait… │
98
- └────────────────────────────────────────────────────────────┘
99
- ⚙ bash python scripts/demo.py … ✔ bash
100
- ```
101
-
102
- Key behavior:
103
-
104
- - **One panel per type** — a single `ThinkPanel` and a single `ToolPanel` exist for the whole run; each event refreshes the panel in place, so there's no transcript churn.
105
- - **Empty = hidden** — when nothing is active, the panel renders zero rows and disappears.
106
- - **`dsh-tui.panelHeight`** (default `1`): one borderless row (block id + elapsed + last content line, right-truncated); `5`/`7`/`10` renders a boxed panel; `all` prints the full body.
107
- - **Delegation tools** (`use_agent`, `subagent`, `workflow`, `ralph`) never open a tool block — their children appear as running-agent lines (see Subagents).
44
+ Live reasoning and tool calls render as fixed panels pinned **above the chat input** instead of scrolling into the transcript, so the conversation thread stays readable. Panels appear only while something is active, and their height is configurable (`dsh-tui.panelHeight`: `1` line, `5`/`7`/`10` boxed rows, or `all`).
108
45
 
109
46
  ---
110
47
 
111
- ### Subagents
48
+ ## Subagents
112
49
 
113
- Running subagent activity is shown in the **last-request area below the editor** as compact, one-line-per-child status rows:
114
-
115
- ```
116
- ↳ 创建 2 个 todo, 每个 todo 起一个 10s 的 subagent
117
- ↳ ⠼ Subagent A 10s 任务 · 1.2k token · 19.0s
118
- ↳ ⠼ Subagent B 10s 任务 · 562 token · 6.0s
119
- ```
50
+ Running subagents show as compact one-line status rows below the editor name, context use, rounds, elapsed — so delegation is visible without opening anything. `Ctrl+G` (or `/subagents`) opens a live transcript viewer; press `Enter` inside it to steer a child with a message. The `● Todos` panel keeps your task tree pinned above the input. Caps (`maxAgents`, `maxRounds`, configured via `/agents` → `l`) keep runaway delegation in check.
120
51
 
121
- Each line shows: spinner + agent **name**, retries (`↻N≤M`), compact **current-context usage** (`X/Y` — the child's latest request's billed input+output plus a CJK estimate of messages after it, over its context window; NOT the cumulative token spend, which only grows), rounds (`round N/M` — the assistant-message count against the cap, `M` only when `maxRounds > 0`), elapsed, and a `⚡` marker when a policy injection (maxRounds wrap-up, steer) reached the child. No provider shown, no box, no header — just one line per running child.
52
+ ---
122
53
 
123
- Both **spawn-driven** and **fork-driven** children are tracked — dsh creates both through `childSessionMeta`, which writes `origin: 'subagent'` + a `delegationDepth` budget together, so header discovery recognises either marker (a budget-without-origin header is admitted as a defensive fallback and labelled `fork <id8>`; current dsh does not produce that shape). Non-children stay off the board by **value**, not by field presence: the jsonl persistence backend materialises `delegationDepth: 0` on every restored header, so the gate requires a budget `> 0`. User-facing session forks (a forked *conversation*: `Session.fork` sets `parentSession` + `seedLength`, no budget) are deliberately kept off the subagent board and stay resumable via `/resume` — whose filter (`isResumableSessionHeader`) excludes exactly the delegated children (`origin: 'subagent'` or budget > 0).
54
+ ## Ask User Question
124
55
 
125
- #### Todos
56
+ While mid-turn, the model can pause and ask you structured questions via the `ask_user_question` tool; the answering side is a docked panel above the input — no window juggling. One question at a time with tabs for the rest, `Ctrl+T` folds the panel away, double-`Esc` declines. Free text, multi-select, a multi-question review page, bracketed-paste and right-click / `Ctrl+Shift+C` system-clipboard paste are all supported.
126
57
 
127
- The `● Todos (done/total)` tree is a bordered panel pinned **above the chat input** (never scrolls with the transcript):
58
+ See the ask-question flow in action:
128
59
 
129
- ```
130
- ┌─ ● Todos (0/8) ──────────────────────────────────────────┐
131
- │ ├─ ☑ Todo 1: research subagent spawn API │
132
- │ ├─ ◐ Todo 2: implement /skill:<name> autocomplete │
133
- │ └─ ☐ Todo 3: add settings panel skills branch │
134
- └───────────────────────────────────────────────────────────┘
135
- ```
60
+ https://github.com/user-attachments/assets/aa36be36-a508-4f53-ba85-efe0394dab11
136
61
 
137
- Icons: `☑` completed, `◐` in-progress, `☐` pending. A settled child drops off; when empty, both the todo panel and agent lines collapse to zero rows.
62
+ ---
138
63
 
139
- #### Viewer & limits
64
+ ## Feishu integration demo
140
65
 
141
- `Ctrl+G` (or `/subagents`) opens an 80% picker over tracked children running ones first, then the five most recently settled. Enter opens a live transcript viewer (refreshes ~3×/s with tail-follow).
66
+ dsh-tui-pi on the desktop and Feishu/Lark on the phone driving (and answering for) the same dsh session:
142
67
 
143
- **Steering**: inside the transcript viewer, `Enter` opens a multi-line steer input (`Enter` send · `Shift+Enter` newline · `Esc` cancel). The message is delivered as a plugin-sourced user message, routed by the child's live state: a running child gets it injected at its next step boundary (`steer`), an idle-but-unfinished child gets it queued as its own follow-up turn; a child that has already ended never opens the box — the viewer explains "This subagent has ended — steering unavailable" instead. A failed send keeps the draft with an inline error so it can be retried; a successful one returns to the transcript with a short notice. These viewer keys are hardcoded and not remappable via keybindings.json.
68
+ https://github.com/user-attachments/assets/177e8839-523b-487e-b3d1-6d725cd8aba5
144
69
 
145
- Two caps (`/agents` → `l` to configure):
70
+ https://github.com/user-attachments/assets/c0d7092f-deda-4443-b75a-2bc93bd30d86
146
71
 
147
- - **`maxAgents`** (default 4, `0` = unlimited) — spawns are denied when the cap is hit.
148
- - **`maxRounds`** (default 75, `0` = unlimited) — after a child's assistant messages (one per LLM round-trip — the "rounds") reach the cap, the TUI injects one wrap-up directive and never force-stops: a running child receives it at its next step boundary (`steer` — the very next LLM round-trip), an idle child as its own next turn. The injection is visible: the compact line, the Ctrl+G picker row and the viewer header show a `⚡` marker, and the transcript renders the injected message as `⚡ <text>` — so a wrap-up the child LLM ignored can be told apart from one that never fired.
72
+ Demos courtesy of the [dsh-feishu Demos issue](https://github.com/fan56/dsh-feishu/issues/1).
149
73
 
150
74
  ---
151
75
 
152
- ### DCP (Dynamic Context Pruning)
76
+ ## Dynamic context pruning (DCP)
153
77
 
154
- [DCP](https://github.com/fan56/dsh-dcp) is a standalone zero-LLM compaction plugin for dsh it automatically trims context to stay within limits without calling an LLM to summarize.
155
-
156
- `dsh-tui-pi` lists `@aiwayds/dsh-dcp` as a dependency, but **does not mount it** — dsh-dcp ships its own `cordis.patch.yml` (since `@aiwayds/dsh-dcp@0.2.0`). To activate:
78
+ Context stays within the model window automatically: [dsh-dcp](https://github.com/fan56/dsh-dcp) compacts the session **without calling an LLM to summarize**. Mount it once and it runs transparently — the footer's context segment follows the shrink, and inside a subagent each committed compaction shows as a `🧹` notice in the viewer.
157
79
 
158
80
  ```sh
159
81
  dsh plugin --profile tui add @aiwayds/dsh-dcp
160
82
  ```
161
83
 
162
- Once mounted, DCP runs transparently in the background. The footer's **context** segment prices the current occupancy — the latest request's billed context plus a CJK estimate of messages after it — so after a compaction the next request lands smaller and the display follows it down (the percent is capped at 100, the window being a hard ceiling). The **cache-hit** segment reflects the current provider/model route's cache reuse — the rate is computed per route segment and restarts (hiding until the next billed message) when the provider or model changes.
163
-
164
- Inside a subagent, a committed compaction is visible too: DCP appends one `user/message` **notice** row per compaction on the child's own log, and the Ctrl+G transcript renders it with a `🧹` marker (distinct from the generic `ⓘ`), with the picker rows carrying the per-child compaction count (`🧹 N×` in the description). Both DCP's `roundInterval` and the TUI's `maxRounds` count the **same** thing — `assistant/message` events, one per LLM round-trip — but act differently: the TUI queues one wrap-up request once a child's count reaches `maxRounds`, while DCP compacts (prunes context) at the next idle boundary once a session's count reaches `roundInterval`. One triggers work, the other frees context.
165
-
166
84
  ---
167
85
 
168
- ### APPEND_SYSTEM.md
169
-
170
- A user-editable markdown file whose content is appended to the **system prompt of the main agent this TUI creates** — borrows pi's `~/.pi/agent/APPEND_SYSTEM.md` convention, dsh side: `$DSH_HOME/APPEND_SYSTEM.md` (default `~/.dsh/APPEND_SYSTEM.md`, honors the same `$DSH_HOME` override as the rest of dsh).
171
-
172
- - **Hot-applied** — the section provider reads the file at every prompt assembly, so editing the file picks up on the **next request**: no restart, no watcher, no `/reload`.
173
- - **Auto-seeded on first run** — when the file is missing, the TUI seeds it once at startup from the shipped template `templates/APPEND_SYSTEM.md` (the English orchestrator-identity template: identity, core rules, execution workflow — including the registered-subagents vocabulary rule: "subagent" means the registered subagents only). An existing file is yours — the TUI never overwrites user content; it only appends the marked todo-lifecycle section and (idempotently, by phrase match) the subagents rule when a file does not phrase them yet.
174
- - **TUI-owned section** — a marked block (`<!-- dsh-tui-pi:todo-lifecycle -->`) is appended once and then maintained idempotently so the model clears its `todo/write` list when every item is done. A marked file is left byte-identical on later startups.
175
- - **Legacy migration** — the same todo block used to be delivered through `~/.dsh/AGENTS.md`. On startup the TUI strips that block once (no-op when absent), so the guidance is never duplicated.
176
- - **Empty / unreadable = no section** — if the file is missing or can't be read, the section is silently dropped. No error, no TUI startup failure.
86
+ ## Persistent context
177
87
 
178
- #### Scope: main agent only
88
+ `$DSH_HOME/APPEND_SYSTEM.md` (default `~/.dsh/APPEND_SYSTEM.md`, pi convention) is appended to the **main agent's** system prompt and hot-applied — edit the file and the next request sees it, no restart. The TUI seeds it from a template on first run, keeps its marked todo-lifecycle section idempotent, and never overwrites your content. Subagents are deliberately left untouched.
179
89
 
180
- The section is registered on the main agent's **scoped** agent context (`installAppendSystem` in `src/session.ts`) — it lands in that agent's own prompt-scope layer, which subagent scopes never merge. An orchestrator identity ("dispatch sub-agents, never execute yourself") riding on the children would defeat its own purpose, so children see nothing from this file. The mechanism is the same one `dsh-subagent-registry` uses for per-child personas.
181
-
182
- #### Example
183
-
184
- ```sh
185
- # Auto-seeded on first run from templates/APPEND_SYSTEM.md — open and edit.
186
- $EDITOR ~/.dsh/APPEND_SYSTEM.md
187
-
188
- # Or replace with your own from scratch (the TUI still keeps its marked
189
- # todo-lifecycle section — it gets re-appended when missing).
190
- cat > ~/.dsh/APPEND_SYSTEM.md <<'EOF'
191
- # Project ground rules
90
+ ---
192
91
 
193
- - Always run `pnpm test` before claiming a task is done.
194
- - Prefer dispatching `workhorse` for multi-step investigations.
195
- EOF
196
- ```
92
+ ## Model profiles and favorites
197
93
 
198
- There's no slash command to toggle the feature it's always on, controlled by the file's contents.
94
+ `/profile-switch` swaps a whole setup — default model, thinking level and every subagent's model in one pick; `p` pins a profile to the current directory so every new session in that tree loads it. `/model` favorites and hidden lists keep the picker small. Manage profiles with `/profile-cfg` (roster, edit, save-current, rename, delete).
199
95
 
200
96
  ---
201
97
 
202
- ### Ask User Question
98
+ ## Sessions and resume
203
99
 
204
- While the model is mid-turn it can pause and ask you structured questions via the `ask_user_question` tool (`@deepseek-ai/dsh-tool-ask-user`, mounted by this profile's bundle patch). The TUI hosts the answering side: a bordered panel pins itself directly above the chat input (the Todos-panel slot no floating popup), takes the keyboard while open, the tool call stays pending until you answer, and your answers flow back to the model as a normal tool result.
100
+ `/resume` restores any recent session in a few keystrokes (ordered by last update), `/new` starts fresh, `/export` writes the session log as JSONL (`~/Downloads/dsh-session-<id>.jsonl`). A startup janitor (`dsh-tui.retention.*`) prunes old session logs so the store never grows without bound; the resume picker shows only the working set (`dsh-tui.resume.*`). Both are configurable in `~/.dsh/settings.yaml` with env overrides.
205
101
 
206
- Watch the ask-user-question flow in action:
207
-
208
- https://github.com/user-attachments/assets/aa36be36-a508-4f53-ba85-efe0394dab11
102
+ ---
209
103
 
210
- - **One question at a time, tabs for the rest** — with several questions the panel shows exactly ONE question block (header row + supporting `detail` text, option rows, plus a `Type something.` sentinel row for free text); a tab strip under the title (`[1] · 2✓ · 3` — brackets mark the focused tab, ✓ an answered one) folds the other questions away. `←`/`→` (and Tab/Shift-Tab) switch tabs; answering a single-select tab auto-advances to the next unanswered one (or onto the Confirm row once everything is answered). Single-select replaces on Enter; multi-select toggles (`●`/`○` marks) and never auto-advances.
211
- - **Ctrl+T folds the panel to a 3-line strip** — the questions panel can block the transcript it stacks on while you think; Ctrl+T collapses it to borders + one summary line (phase, tab position, answered count, how to expand) and the same key unfolds it. While folded only the toggle and the Esc chain act; folding mid-edit commits the buffer like the ↑↓ arrow-exit does.
212
- - **Single-question fast path** — a lone single-select question submits immediately on Enter: picking an option or committing typed free text both submit right away (a question without options is answered by typing alone). A lone multiSelect question instead gets a `⏎ Confirm answers` row so you can pick several options before submitting.
213
- - **Multi-question review page** — with ≥ 2 questions a `⏎ Confirm answers` row hops to a review listing every answer, each row editable in place (jumping back re-focuses that question's tab); `Submit answers` commits (Enter on it while an answer is missing flashes a hint instead of failing silently).
214
- - **Double-Esc declines** — two Esc presses within 200 ms return a declined envelope (the model reads it as a normal reply that no answer was given); holding Esc does not accidentally fire (key auto-repeat below a minimum gap is ignored), and the tool call being aborted settles as declined too. While the panel is open it owns the keyboard exactly like an open overlay: Esc never arms the running-task stop, and app keys (Ctrl+L/G/O, Tab) yield to the panel.
215
- - **Conservative-use guidance** — a system-prompt section nudges the model to ask only when it genuinely needs you (1–3 questions, 2–4 options each), so the TUI doesn't turn into a questionnaire.
216
- - **Keyboard** — `←→` switch question tabs · `↑↓` navigate · `Enter` select/toggle/confirm · type into the sentinel for free text · `Ctrl+T` fold/unfold the panel · `Esc` twice to decline.
217
- - **Paste (bracketed-paste) into the free-text sentinel** — terminals that advertise `?2004` deliver multi-line clipboard content as one bracketed-paste chunk; the sentinel collapses every run of `CR`/`LF`/`CRLF` into a single space, drops C0/C1 control bytes, expands tabs to four spaces, and caps the buffer at 16 KiB — so a pasted paragraph lands as one space-separated run, never a stack of newlines.
218
- - **Right-click while editing = direct paste from the system clipboard** — the TUI short-circuits SGR right-click presses *before* they reach pi-tui (which would otherwise start a selection drag) and, on `wl-paste`/`xclip`/`xsel`/`pbpaste`/PowerShell, copies the system clipboard into the sentinel's buffer; the helper is fire-and-forget, a missing clipboard tool degrades to a silent no-op, and the OSC 52 write-back runs in parallel so the host terminal's own paste works without the local tool.
219
- - **`Ctrl+Shift+C` copies the sentinel buffer to the system clipboard** — the kitty-CSI-u encoding `\x1b[<codepoint>;<modifier>u` (modifier 6 = ctrl+shift+1) writes the buffer through OSC 52 *and* `pbcopy`/`wl-copy`/`xclip`/`xsel`/`clip` in parallel; a small `Copied` hint appears under the panel for confirmation, an empty buffer is a silent no-op. *Known limitation:* Apple Terminal maps `Ctrl+Shift+C` to its own copy action before the kitty sequence reaches us, so the in-app copy fires as plain `Ctrl+C` = exit-edit; in tmux, run `set-clipboard on` (and the matching `set-option` for the pane) so the OSC 52 sequence passes through to the host terminal.
104
+ ## Themes
220
105
 
221
- Inspired by [juicesharp/rpiv-ask-user-question](https://github.com/juicesharp/rpiv-ask-user-question).
106
+ GitHub light/dark palettes, hot-switched with `/theme`; `auto` detects your terminal and follows live light/dark switches. `DSH_TUI_THEME=light|dark` pins a scheme, `DSH_TUI_TRANSPARENT=1` makes the canvas see-through, `DSH_TUI_MOUSE=buttons|all|off` tunes mouse tracking.
222
107
 
223
108
  ---
224
109
 
@@ -226,26 +111,32 @@ Inspired by [juicesharp/rpiv-ask-user-question](https://github.com/juicesharp/rp
226
111
 
227
112
  | Command | What it does |
228
113
  |---|---|
229
- | `/model` | Two-stage provider/model picker (then thinking level). Live switch, persisted; in-panel keys: `f` favorite · `h` hide · `/` filter (favorites/hidden persisted via settings). |
230
- | `/think` | Reasoning-effort picker for the current model (`Off`/`High`/`Max`). |
231
- | `/session` | Read-only info panel: id, cwd, model, token usage, event count. |
232
- | `/resume` | Pick a persisted session, validate its log, then restore it. Ordered by last update (log-file mtime), newest first; the `Updated` column shows the effective time. |
114
+ | `/model` | Two-stage provider/model picker + thinking level; `f` favorite, `h` hide, `/` filter (persisted). |
115
+ | `/think` | Reasoning-effort picker (`Off`/`High`/`Max`). |
116
+ | `/session` | Read-only info: id, cwd, model, token usage, event count. |
117
+ | `/resume` | Pick a persisted session (newest first), validate its log, restore it. |
233
118
  | `/new` | Detach the current session; the next prompt opens a fresh one. |
234
- | `/settings` | Text-based settings browser (namespaces, schema walk, inline editors, secrets masked). |
235
- | `/export` | Write the current session log as JSONL (`~/Downloads/dsh-session-<id>.jsonl`). |
119
+ | `/settings` | Text-based settings browser (namespaces, schema walk, secrets masked). |
120
+ | `/export` | Write the current session log as JSONL. |
236
121
  | `/permission` | Permission-preset picker (read-only / workspace-write / danger-full-access). |
237
- | `/theme` | Color-scheme picker (`auto` / `light` / `dark`). Applies immediately. |
238
- | `/preset` | Agent-preset picker; `<name>` switches directly, `next` cycles forward (same as `Tab`). |
239
- | `/profile-switch` | Switch the model profile — one named snapshot of the default model + think level and every subagent's model/think level. Enter applies it to the live selection, the persisted default and the agent markdown files. `p` pins the current directory to a profile (writes `.dsh-profile`, nearest file up the tree wins): every NEW session in that tree auto-loads it, without touching the global default. Ships `work` / `personal` / `other`; profiles live in `$DSH_HOME/model-profiles.json`. |
240
- | `/profile-cfg` | Configure model profiles: edit the default model / think / per-agent models (all pickers, model picks use the same favorites/hidden table as `/model`), `s` save the current configuration into the profile, `n` new, `r` rename, `d` delete, `v` review. |
122
+ | `/theme` | Color-scheme picker (`auto`/`light`/`dark`), applies immediately. |
123
+ | `/preset` | Agent-preset picker; `<name>` switches directly, `next` cycles (same as `Tab`). |
124
+ | `/profile-switch` | Apply a model profile to the live selection, the persisted default and the agent files; `p` pins the cwd. |
125
+ | `/profile-cfg` | Manage profiles: edit default model / think / per-agent models, `s` save current, `n` new, `r` rename, `d` delete. |
241
126
  | `/agents` | Manage agent markdown files + subagent limits (`maxAgents`, `maxRounds`). |
242
- | `/subagents` | Pick a running/recent subagent and watch its live transcript; `Enter` inside the viewer steers the child (see Subagents). |
243
- | `/reload` | Hot-reload the plugin from source (after `pnpm build`) without restarting dsh. |
244
- | `/login` | Log in to a provider: pick from the directory (or `/login openai` to jump), enter one API key. The **Custom provider…** entry (`/login custom`) opens a six-field form for any OpenAI/Anthropic-compatible gateway pi-ai does not ship — route id, display name, protocol, base URL, model list, API key — and writes the same hand-declared route the web Models page composes. |
245
- | `/logout` | Pick a logged-in provider and remove both the stored key and its provider profile. |
127
+ | `/subagents` | Pick a running/recent subagent and watch its live transcript; `Enter` steers it. |
128
+ | `/skills` | Manage user skills (installed and available). |
129
+ | `/reload` | Hot-reload the plugin from source after `pnpm build`. |
130
+ | `/login` | Log in to a provider (or `/login openai`); **Custom provider…** adds any OpenAI/Anthropic-compatible gateway. |
131
+ | `/logout` | Remove a provider's stored key and profile. |
246
132
  | `/hotkeys` | Keybinding browser and live editor. |
247
133
 
248
- Anything that is not a resolvable command falls through to the model as an ordinary prompt.
134
+ Model-list auto-sync for hand-declared (baseURL) providers is no longer a
135
+ built-in command: the separate `@aiwayds/dsh-model-sync` plugin (a default
136
+ dependency of this package) keeps those routes' model lists up to date on its
137
+ own schedule.
138
+
139
+ Anything else falls through to the model as an ordinary prompt; dsh-native commands (`plan`, `compact`, `feedback`, `goal`, …) work unchanged.
249
140
 
250
141
  ---
251
142
 
@@ -254,299 +145,85 @@ Anything that is not a resolvable command falls through to the model as an ordin
254
145
  | Key | Action |
255
146
  |---|---|
256
147
  | `Enter` | Send the prompt |
257
- | `Esc` | **Double-press to stop** single press arms (500ms window); popup open closes popup instead; idle (no task running) → no-op |
258
- | `Ctrl+C` | Mid-turn: first press cancels turn, second quits. Idle: clears editor / quits. **Held-key auto-repeat never quits.** |
259
- | `Ctrl+D` | Quit (only when editor is empty) |
260
- | `Ctrl+L` | Open model/think picker |
261
- | `Ctrl+G` | Open subagent picker (while children are running); inside the transcript viewer, `Enter` opens the steer input (viewer keys hardcoded) |
262
- | `Tab` | Cycle agent presets (footer brand shows the current one as `dsh(<name>)`) |
263
- | `↑` / `↓` | Browse submitted-message history (shell-style, 500 entries) |
264
-
265
- ### Custom keybindings
266
-
267
- Remap any app key through `~/.dsh/keybindings.json` — a partial JSON map of app keys to key ids (`ctrl+letter`, `alt+letter`, named keys). Edit by hand or use `/hotkeys` to change interactively (live-applied, no restart).
268
-
269
- ---
270
-
271
- ## Agent presets
272
-
273
- The TUI starts with the `standard` agent preset selected when the deployment supplies one; otherwise the first-scanned entry is selected. This is a local selection only: before you interact with `/preset` or press `Tab`, no `meta.agentPreset` is sent at session create, so the server-side default (`agent-presets.default`) governs. The footer brand segment reflects the local selection (`dsh(<name>)`); a switch applies to the next blank session.
274
-
275
- ---
148
+ | `Esc` | **Double-press to stop** (single press arms; a popup open closes it instead) |
149
+ | `Ctrl+C` | Mid-turn: first press cancels, second quits; idle: clears editor / quits. Held-key auto-repeat never quits. |
150
+ | `Ctrl+D` | Quit (only when the editor is empty) |
151
+ | `Ctrl+L` | Open the model/think picker |
152
+ | `Ctrl+G` | Open the subagent picker (viewer `Enter` opens steer) |
153
+ | `Ctrl+O` | Pending-message queue (s steer now · d remove) |
154
+ | `Tab` | Cycle agent presets |
155
+ | `↑` / `↓` | Browse submitted-message history |
276
156
 
277
- ## Themes
278
-
279
- GitHub light / GitHub dark palettes, hot-switchable at runtime:
280
-
281
- - `/theme` — live picker; the whole screen repaints including background.
282
- - `DSH_TUI_THEME=light|dark` — env pin that wins over preference.
283
- - `DSH_TUI_TRANSPARENT=1` — see-through canvas (terminal background shows through).
284
- - `DSH_TUI_MOUSE=buttons|all|off` — terminal mouse tracking mode (default `buttons`: clicks/wheel/drag keep working, idle pointer movement reports nothing; `all` = pi-tui's all-motion tracking, whose bursts can leak into the editor under cmux; `off` = no mouse).
285
- - `auto` mode detects the terminal and follows live light/dark switches.
286
-
287
- The full-screen canvas background ships inside the package — a write-stream
288
- decorator (`src/canvas-terminal.ts`) paints every erase sequence with the
289
- theme color via BCE, no patched dependencies.
157
+ Remap any app key through `~/.dsh/keybindings.json` (a partial JSON map, live-applied) or interactively with `/hotkeys`.
290
158
 
291
159
  ---
292
160
 
293
- ## Session management
294
-
295
- Two knobs clusters govern the session store, both living under the
296
- `dsh-tui` settings namespace (`~/.dsh/settings.yaml`) with an environment
297
- escape hatch each:
298
-
299
- ```yaml
300
- dsh-tui:
301
- # Startup janitor for ~/.dsh/sessions — DELETES whole session log
302
- # directories outside the window. Runs once per process at startup.
303
- retention:
304
- maxCount: 100 # keep at most this many sessions; <= 0 DISABLES the janitor
305
- maxAgeDays: 7 # delete logs untouched for more than this many days (> 0)
306
- minIdleHours: 24 # count-rule-only idle guard in hours (>= 0)
307
-
308
- # /resume display filter — only HIDES picker rows, never deletes.
309
- # Resolved fresh at every picker open (a settings change applies to
310
- # the next /resume, no restart).
311
- resume:
312
- maxAgeDays: 7 # only sessions with log activity inside this window (> 0)
313
- minBytes: 20480 # minimum compressed on-disk log size for a row (>= 0)
314
- ```
161
+ ## Configuration
315
162
 
316
- Precedence per field: an explicit value in settings.yaml > the
317
- `DSH_TUI_RETENTION_MAX_COUNT` / `DSH_TUI_RETENTION_MAX_AGE_DAYS` /
318
- `DSH_TUI_RETENTION_MIN_IDLE_HOURS` and `DSH_TUI_RESUME_MAX_AGE_DAYS` /
319
- `DSH_TUI_RESUME_MIN_BYTES` environment variables > the defaults above.
320
- An invalid settings value surfaces a transient notice via the shared
321
- notice bridge (silently dropped when no TUI sink is registered — headless
322
- runs never print it) and falls to the next level; an invalid env value — a typo
323
- never widens or guts the policy. `maxCount` and `minBytes` must be
324
- integers at every layer (a fractional cap or byte floor is garbage, not a
325
- window).
326
-
327
- **Disabling retention entirely** — for a long-lived process (a remote
328
- bridge, a headless cron run) that read-attaches old sessions the default
329
- window would prune:
163
+ Session-store knobs under the `dsh-tui` settings namespace in `~/.dsh/settings.yaml` (each also has an env override, `DSH_TUI_RETENTION_*` / `DSH_TUI_RESUME_*`; precedence: settings.yaml > env > default):
330
164
 
331
165
  ```yaml
332
166
  dsh-tui:
333
- retention:
334
- maxCount: 0 # or: DSH_TUI_RETENTION_MAX_COUNT=0
167
+ retention: # startup janitor for ~/.dsh/sessions — DELETES old logs. Once per startup.
168
+ maxCount: 100 # <= 0 disables the janitor
169
+ maxAgeDays: 7
170
+ minIdleHours: 24
171
+ resume: # /resume display filter — only HIDES picker rows, never deletes.
172
+ maxAgeDays: 7
173
+ minBytes: 20480
335
174
  ```
336
175
 
337
- Timing: **retention runs once at startup** (never mid-session; a
338
- `/reload` does not re-run it — the next cold start does), while the
339
- **resume filter applies at every `/resume` open**. The two `7`s default
340
- to the same "one week is the working set" decision but serve different
341
- masters — retention deletes logs, the resume filter only hides rows.
176
+ Other knobs: `dsh-tui.panelHeight` (think/tool panel height), `dsh-tui.iconSet` (`auto`/`nerdfont`/`plain` — powerline glyphs adapt to your font; install a Nerd Font with `node scripts/install-font.mjs`), `~/.dsh/keybindings.json` (key remaps).
342
177
 
343
178
  ---
344
179
 
345
- ## Fonts
346
-
347
- The TUI's only Private-Use-Area glyph is the powerline segment separator
348
- (U+E0B0) in the footer — no default terminal font ships it, so a terminal
349
- without a Nerd/Powerline font shows a tofu box. The `dsh-tui.iconSet`
350
- setting (`auto` | `nerdfont` | `plain`, default `auto`) adapts the risky
351
- glyphs (U+E0B0, ⏹, ⭘) to the terminal:
352
-
353
- - `auto` — powerline glyphs when a Nerd/Powerline font is detected at
354
- startup, safe Unicode stand-ins (`▸ ■ ●`) otherwise.
355
- - `nerdfont` — always the powerline glyphs (you already set the font).
356
- - `plain` — always the safe stand-ins, no font required.
357
-
358
- **Install the bundled font in one shot** (install + point the terminal at
359
- it, preserving your font size):
360
-
361
- ```sh
362
- node scripts/install-font.mjs
363
- ```
364
-
365
- It copies `assets/fonts/dsh-tui-pi-nerd.ttf` (a ~170KB subset: ASCII +
366
- U+E0B0 + every symbol the TUI renders) into the user font directory and
367
- best-effort flips the terminal: macOS iTerm2 (PlistBuddy, default bookmark),
368
- Linux GNOME Terminal (`gsettings`) and kitty/alacritty/wezterm (config
369
- file, backed up first). Terminal.app is intentionally skipped (its font is
370
- a binary blob) — set it by hand. Every step is wrapped: a failure logs a
371
- warning and moves on, never touching your config destructively.
372
-
373
- **Or set the font by hand** — any Nerd Font family as the terminal's main
374
- font (e.g. JetBrainsMono Nerd Font, Hack Nerd Font, or the bundled `DSH TUI
375
- Nerd` after installing it): iTerm2 → Settings → Profiles → Text → Font;
376
- Terminal.app → Settings → Profiles → Text; kitty → `font_family`; alacritty
377
- → `[font] family`; wezterm → `wezterm.font("…")`. Then `auto` resolves to
378
- the powerline glyphs on the next start.
379
-
380
- ---
381
-
382
- ## Install (local)
383
-
384
- The `tui` profile installs this plugin from the npm registry — its
385
- `package.json` pins `"@aiwayds/dsh-tui-pi": "<version>"`, resolved by pnpm
386
- like any other dependency. After a release, upgrade the profile with:
387
-
388
- ```sh
389
- node scripts/dev-upgrade.mjs # latest
390
- node scripts/dev-upgrade.mjs 0.15.1 --dry-run # preview the plan first
391
- ```
392
-
393
- The script verifies the version exists on the registry, updates ONLY the
394
- `"@aiwayds/dsh-tui-pi"` key in `~/.dsh/profiles/tui/package.json`
395
- (formatting-preserving read-modify-write), runs `pnpm install` there, then
396
- checks the installed copy reports the target version. It never touches
397
- `~/.dsh/settings.yaml` or `.credentials.yaml`. Restart dsh (or `/reload`
398
- inside the TUI) to load the new copy.
399
-
400
- ## Install (npm)
401
-
402
- Install the full dsh plugin suite into a fresh profile:
180
+ ## Install and launch
403
181
 
404
182
  ```sh
405
183
  dsh plugin --profile tui add @aiwayds/dsh-tui-pi
406
- dsh plugin --profile tui add @aiwayds/dsh-subagent-registry
407
- dsh plugin --profile tui add @aiwayds/dsh-dcp
184
+ dsh plugin --profile tui add @aiwayds/dsh-subagent-registry # optional
185
+ dsh plugin --profile tui add @aiwayds/dsh-dcp # optional
186
+ dsh --profile tui # launch (or: dsh-tui-pi)
408
187
  ```
409
188
 
410
- Then launch:
411
-
412
- ```sh
413
- dsh --profile tui
414
- ```
415
-
416
- **What happens automatically:**
417
-
418
- - dsh registers all three plugins in `dsh.profile.bundles` (via `reconcilePlugins`).
419
- - dsh sets `autoInstallPeers: false` in the profile's `pnpm-workspace.yaml`.
420
- - On first boot, dsh calls `healProfilesModuleFallback` to create symlinks
421
- under `~/.dsh/profiles/node_modules/@deepseek-ai/*` → the global dsh
422
- closure (`$(which dsh)/../../node_modules/@deepseek-ai`). This gives all
423
- plugins a single `@deepseek-ai/cordis` instance — no manual closure setup
424
- is needed.
425
- - `compaction-basic` is disabled by `@aiwayds/dsh-dcp`'s patch; dsh-dcp
426
- takes over as the compaction backend.
427
-
428
- **What does NOT happen automatically:**
429
-
430
- - Nothing patch-related anymore: since 0.8.0 the repo and the npm package
431
- run the same pristine `@earendil-works/pi-tui` — the canvas background is
432
- painted by our own write-stream decorator (BCE), which ships in the
433
- package and needs no `pnpm-workspace.yaml` entries in consumer profiles.
434
-
435
- ### Troubleshooting
436
-
437
- | Symptom | Cause | Fix |
438
- |---|---|---|
439
- | `Cannot find package '<name>' imported from ~/.dsh/profiles/...` | A bundle's `cordis.patch.yml` `name` field doesn't match the scoped package name. | Update the plugin; all `@aiwayds/*` plugins now use `name: '@aiwayds/<pkg>'` in their patch. |
440
- | `Cannot find package '@deepseek-ai/dsh-client-schema-form'` on npm-installed dsh | The npm-distributed dsh closure is missing that package (upstream packaging gap — [deepseek-harness discussion #3471](https://github.com/deepseek-ai/deepseek-harness/discussions/3471)). | Fixed for this plugin since 0.8.1 (helpers vendored, no import of the missing package). Other plugins needing it: `cd ~/.dsh/profiles/<profile> && pnpm add @deepseek-ai/dsh-client-schema-form@next`. |
441
- | `Cannot read properties of undefined (reading 'prepare')` | Duplicate `@deepseek-ai/cordis` module instances (two physical copies in the profile tree). | See iron rule 8 in AGENTS.md. Delete physical `~/.dsh/profiles/tui/node_modules/@deepseek-ai` copies and let dsh heal the fallback: `rm -rf ~/.dsh/profiles/tui/node_modules/@deepseek-ai && dsh --profile tui` (the heal recreates them as symlinks). |
442
- | pnpm `Peer dependencies that should be installed: @deepseek-ai/...` warning | A plugin declares `@deepseek-ai/*` as regular `dependencies` instead of `peerDependencies`. | Update the plugin (all `@aiwayds/*` dsh plugins use optional peerDeps). The warning is harmless — pnpm doesn't auto-install optional peers. |
443
- | pnpm `Ignored build scripts: @aiwayds/dsh-tui-pi@...` warning | pnpm 10 blocks build scripts by default; the tui-pi postinstall (`link-dsh-closure.mjs`) was skipped. | This is expected and **harmless** — the postinstall only matters for the repo dev flow, not npm consumers. dsh handles closure linking via `healProfilesModuleFallback`. |
444
-
445
- ---
446
-
447
- ## Use
189
+ Everything that used to need manual patching — the canvas background, the `@deepseek-ai` module closure, the compaction backend — now happens automatically. Upgrade an existing profile after a release:
448
190
 
449
191
  ```sh
450
- dsh --profile tui # or: dsh-tui-pi (bin shim)
192
+ node scripts/dev-upgrade.mjs # latest
193
+ node scripts/dev-upgrade.mjs 1.0.2 --dry-run # preview the plan first
451
194
  ```
452
195
 
453
196
  ---
454
197
 
455
- ## Companion plugins (optional)
456
-
457
- - **[@aiwayds/dsh-ask-router](https://www.npmjs.com/package/@aiwayds/dsh-ask-router)**
458
- (ships as a default dependency). Owns the single `ctx.userQuestions`
459
- provider slot and fans every `ask_user_question` out to the interaction
460
- surfaces bound to the asking session — first answer wins, losing surfaces
461
- dismiss automatically. Activate it by listing `@aiwayds/dsh-ask-router`
462
- in the profile's `bundles` **before any UI bundle**; without it the TUI
463
- panel simply owns questions by itself.
464
- - **[@aiwayds/dsh-feishu](https://github.com/fan56/dsh-feishu)** (optional).
465
- Drives an existing dsh session from Feishu/Lark on the phone: round cards,
466
- interactive `/resume` picker, and an ask-user **card surface** that joins
467
- the router's fan-out — ask on the desktop, answer on the phone, or both
468
- render and the first answer wins. Install into the same profile when you
469
- want phone-side participation; skip it for desktop-only setups. Never
470
- install the router into a **web** profile (the upstream web apiproxy
471
- registers its own provider and does not tolerate duplicates).
472
-
473
- ### Feishu integration demo
474
-
475
- The dsh-feishu companion in action — dsh-tui-pi on the desktop and
476
- Feishu/Lark on the phone driving (and answering for) the same dsh session:
477
-
478
- https://github.com/user-attachments/assets/177e8839-523b-487e-b3d1-6d725cd8aba5
198
+ ## Companion plugins
479
199
 
480
- https://github.com/user-attachments/assets/c0d7092f-deda-4443-b75a-2bc93bd30d86
481
-
482
- Demos courtesy of the [dsh-feishu Demos issue](https://github.com/fan56/dsh-feishu/issues/1).
200
+ - [@aiwayds/dsh-ask-router](https://www.npmjs.com/package/@aiwayds/dsh-ask-router) — ships as a default dependency; fans every `ask_user_question` out to all answering surfaces (TUI panel, Feishu card) and the first answer wins. List it in the profile's `bundles` before the UI bundles to activate.
201
+ - [@aiwayds/dsh-feishu](https://github.com/fan56/dsh-feishu) — optional; drives the same dsh session from Feishu/Lark on your phone, including an ask-user card surface. Install into the same profile for phone-side participation.
483
202
 
484
203
  ---
485
204
 
486
- ## Dev
205
+ ## Development
487
206
 
488
207
  ```sh
489
208
  pnpm check # tsc --noEmit
490
209
  pnpm build # emit lib/
491
- pnpm test # unit tests, node --test against lib/ (757 tests, pretest builds)
210
+ pnpm test # unit tests, node --test against lib/ (pretest builds; 1020 tests across 55 files)
492
211
  ```
493
212
 
494
- Local type-checking symlinks `node_modules/@deepseek-ai/*` to the installed
495
- dsh closure (`/opt/homebrew/lib/node_modules/@deepseek-ai/dsh/node_modules`);
496
- those symlinks stay out of any tarball. `scripts/link-dsh-closure.mjs` (the
497
- package's `postinstall`) re-creates every link after each `pnpm install`.
498
-
499
- **pi-tui**: pristine `@earendil-works/pi-tui` 0.84.2 from npm — no patches,
500
- no fork. The full-screen canvas background is our own write-stream decorator
501
- (`src/canvas-terminal.ts`, BCE).
502
-
503
- ---
504
-
505
- ## Layout
506
-
507
- ```
508
- bin/dsh-tui-pi launcher shim (exec dsh --profile tui)
509
- cordis.patch.yml bundle patch: mounts the plugin as `tui-pi`
510
- src/
511
- index.ts cordis plugin entry: command registration, footer,
512
- git watcher, clock, bridge, theme hot-swap, shutdown
513
- tui.ts alt-screen tree, transcript ScrollView, dock, canvas bg
514
- session.ts DshSessionBridge: agent create, followup, resume,
515
- O(1) incremental stats, subagent tracker
516
- live-widgets.ts Todos panel + running-agent activity lines
517
- messages.ts TranscriptRenderer: session events → pi-tui components,
518
- streaming setText, height-configurable panels
519
- footer.ts PowerlineFooter (7 segments + clock)
520
- editor.ts CwdBorderEditor (top border: cwd + git branch)
521
- subagent-policy.ts maxAgents guard + maxRounds wrap-up injection
522
- (steer-when-running; ⚡-marked, visible in the viewer)
523
- subagent-viewer.ts Ctrl+G picker + live transcript panel + Enter steer injection
524
- ask-user.ts Ask User Question docked panel: pure state reducers +
525
- framed overlay UI + ctx.userQuestions provider
526
- steer-flow.ts Steer / follow-up decision layer: routed delivery with
527
- race fallback, queue actions (remove / promote), notices
528
- route-dialog.ts Submit routing dialog (queue as follow-up vs steer now):
529
- pure key reducer + framed overlay
530
- queue-panel.ts Ctrl+O pending-message queue: d remove · s steer now,
531
- live-refreshed overlay
532
- theme/ GitHub light/dark palettes + terminal detection
533
- test/*.test.mjs unit tests (757 across 44 files)
534
- ```
213
+ `pi-tui` runs pristine from npm — no patches, no fork. See [AGENTS.md](AGENTS.md) for the iron rules and quality gates.
535
214
 
536
215
  ---
537
216
 
538
- ## Changelog
217
+ ## Documentation
539
218
 
540
- See [CHANGELOG.md](CHANGELOG.md) for the release history.
219
+ - [ARCHITECTURE.md](ARCHITECTURE.md) full design: process model, layers, data flow.
220
+ - [HANDOFF.md](HANDOFF.md) — session history and current state (Chinese).
221
+ - [CHANGELOG.md](CHANGELOG.md) — release history.
222
+ - [AGENTS.md](AGENTS.md) — working conventions and quality gates for contributors.
223
+ - [docs/](docs/) — design notes (steer/follow-up flow, showcase drafts, …).
541
224
 
542
225
  ---
543
226
 
544
227
  ## Credits
545
228
 
546
- - [Ask User Question](#ask-user-question) is inspired by
547
- [juicesharp/rpiv-ask-user-question](https://github.com/juicesharp/rpiv-ask-user-question) —
548
- the interaction design (numbered option list with a free-text sentinel,
549
- multi-question review page, decline gesture; since reworked into a
550
- one-question-at-a-time tab view with a fold-away strip) was adapted to this
551
- TUI's docked-panel and dsh `userQuestions` provider architecture. All code
552
- here is original.
229
+ The [Ask User Question](#ask-user-question) interaction is inspired by [juicesharp/rpiv-ask-user-question](https://github.com/juicesharp/rpiv-ask-user-question) (adapted to this TUI's docked-panel and dsh `userQuestions` provider architecture; all code here is original).