dsh-oc-tui 0.1.1 → 0.1.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
@@ -1,216 +1,337 @@
1
- # Deepseek Harness opencode-like TUI Plugin
1
+ # dsh-oc-tui
2
2
 
3
- An opencode-inspired **terminal UI (TUI)** for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness), shipped as a dsh profile app plugin. It boots a chat client inside the dsh process: it creates/resumes agents through `ctx.agents`, renders the durable `session/event` stream (user messages, streaming assistant tokens, tool cards, todo lists), routes human input back via `agent.followup()`, and answers `approval/request` prompts inline.
3
+ **A terminal UI for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)** an opencode-inspired chat client that boots inside the `dsh` process as a profile app plugin.
4
4
 
5
- The UI follows a DeepSeek blue-white dark design language (brand blue `#4D6BFE` accents on a blue-tinted canvas, with a blue→white gradient on the DeepSeek Harness title): a responsive session rail, markdown transcript and tool activity, a bordered multiline composer with slash-command suggestions, and a telemetry footer for session tokens, average time to first token (TTFT), decode throughput, KV-cache hit rate, and a **context meter** showing the current context length over the context-length limit. Thinking and running tools (read/write/...) show flowing spinner animations, the status row carries a flowing wave, and the composer border becomes a flowing gold marching-ants frame (dashes chasing clockwise around the box) while the agent is working.
5
+ [![npm latest](https://img.shields.io/npm/v/dsh-oc-tui?label=npm&color=BF392B)](https://www.npmjs.com/package/dsh-oc-tui)
6
+ [![awesome-dsh-plugin](https://img.shields.io/badge/awesome--dsh--plugin-marketplace-BF392B)](https://awesome-dsh-plugin.com/zh/p/rayafriandion/dsh-oc-tui/)
7
+ [![License: LGPL-3.0-or-later](https://img.shields.io/badge/license-LGPL--3.0--or--later-blue.svg)](LICENSE)
8
+ [![Node](https://img.shields.io/badge/node-%3E%3D22-339933.svg)](https://nodejs.org)
6
9
 
7
- ![max thinking effect](docs/max-thinking.gif)
10
+ `dsh-oc-tui` renders the harness's durable event stream in your terminal — streaming replies, tool cards, todo lists, thinking blocks — and routes what you type back into the agent. Model routing, tool execution, approvals, commands, durable sessions, and credentials stay owned by DSH; this package owns terminal input and presentation.
8
11
 
9
- 中文用户手册见 [docs/用户手册.md](docs/用户手册.md)
12
+ Published on **npm** as [`dsh-oc-tui`](https://www.npmjs.com/package/dsh-oc-tui) and listed in the [**awesome-dsh-plugin**](https://awesome-dsh-plugin.com/zh/p/rayafriandion/dsh-oc-tui/) marketplace.
13
+
14
+ > 中文文档:[docs/用户手册.md](docs/用户手册.md)
15
+
16
+ ## Table of contents
17
+
18
+ - [Features](#features)
19
+ - [Requirements](#requirements)
20
+ - [Install](#install)
21
+ - [Quick start](#quick-start)
22
+ - [Usage](#usage)
23
+ - [Keybindings](#keybindings)
24
+ - [Slash commands](#slash-commands)
25
+ - [Interactive prompts](#interactive-prompts)
26
+ - [Thinking intensity](#thinking-intensity)
27
+ - [Context meter and telemetry](#context-meter-and-telemetry)
28
+ - [Settings](#settings)
29
+ - [In-app updates](#in-app-updates)
30
+ - [How it works](#how-it-works)
31
+ - [Development](#development)
32
+ - [Troubleshooting](#troubleshooting)
33
+ - [Known limitations](#known-limitations)
34
+ - [Layout](#layout)
35
+ - [License](#license)
36
+
37
+ ## Features
38
+
39
+ | | |
40
+ | --- | --- |
41
+ | **Durable sessions** | Create, resume, list, and delete sessions; the transcript is rebuilt from the persisted event log, so a resumed session looks exactly like the one you left. |
42
+ | **Live streaming** | Assistant text and reasoning stream token by token; thinking renders in its own collapsible box that stays collapsed while streaming. |
43
+ | **Tool activity** | Tool cards with a one-line summary (`read src/app.ts`, `run npm test`), flowing spinners while running, and markdown-rendered results. |
44
+ | **Interactive questions** | The model can pause and ask you — option lists, multi-select, free text, and a scrollable plan review — all inline in the terminal. See [Interactive prompts](#interactive-prompts). |
45
+ | **Inline approvals** | `approval/request` prompts are answered with `y` / `n` without leaving the UI. |
46
+ | **Telemetry footer** | Session tokens, average time to first token, decode throughput, and cache-hit rate, folded from durable events. |
47
+ | **Context meter** | Live context occupancy (`ctx ▓▓░░ 32K/128K 25%`) with a click-through composition breakdown. |
48
+ | **Thinking intensity** | `Tab` cycles the current model's real reasoning levels; `Ctrl+E` opens a slider. The level is applied per request and persisted. |
49
+ | **Shared settings** | The same host settings namespaces the Web UI uses — general, sessions, per-provider model configuration, credentials — persisted to `$DSH_HOME/settings.yaml`. |
50
+ | **In-app updates** | Detect and switch versions of `@deepseek-ai/dsh` and `dsh-oc-tui` from inside the TUI, with Windows-safe deferred installs. |
51
+ | **Zero-dependency terminal engine** | Raw mode, alternate screen, a diffing cell buffer, truecolor ANSI, CJK-aware widths, SGR + legacy X10 mouse decoding, and IME caret anchoring. |
10
52
 
11
53
  ## Requirements
12
54
 
13
- - Node.js >= 22, an installed `@deepseek-ai/dsh` CLI, and an interactive terminal (Windows Terminal / ConPTY, iTerm2, GNOME Terminal, ...).
14
- - `pnpm` on PATH for the one-time plugin install (`dsh plugin` forwards to pnpm).
15
- - A configured model route: the profile reuses `$DSH_HOME/settings.yaml` (`llm-pi-ai` providers or `llm-deepseek`) and `$DSH_HOME/.credentials.yaml` — the same setup the Web GUI uses.
55
+ | | |
56
+ | --- | --- |
57
+ | Node.js | >= 22 |
58
+ | dsh CLI | `@deepseek-ai/dsh` — e.g. `npm install -g @deepseek-ai/dsh` |
59
+ | pnpm | on `PATH`; `dsh plugin` forwards to it |
60
+ | Terminal | an interactive terminal (Windows Terminal / ConPTY, iTerm2, GNOME Terminal, …) |
61
+ | Model route | a usable route in `$DSH_HOME/settings.yaml` + `$DSH_HOME/.credentials.yaml` (the same setup the Web GUI uses) |
16
62
 
17
- ## Quick start
63
+ ```sh
64
+ dsh --version
65
+ pnpm --version
66
+ ```
67
+
68
+ **Compatibility.** Verified against dsh `0.1.2-rc.1` (and `0.1.1-rc.2`). DSH renamed parts of the session API in 0.1.2 — `Session.events` became `snapshotEvents()` — and this plugin reads whichever accessor the host provides, so one build serves both lines.
69
+
70
+ ## Install
71
+
72
+ ### From npm
73
+
74
+ The package is published on npm as [`dsh-oc-tui`](https://www.npmjs.com/package/dsh-oc-tui). Install it into the `tui` profile:
18
75
 
19
76
  ```sh
20
- # 1. One-time setup: create the tui profile and install this plugin into it.
21
- dsh plugin --profile tui add dsh-oc-tui
77
+ dsh plugin --profile tui add -w dsh-oc-tui
78
+ ```
79
+
80
+ Or install the launcher globally — that puts the `dsh-oc-tui` command on `PATH`, which then boots `dsh --profile tui`:
22
81
 
23
- # 2. Launch.
24
- dsh --profile tui
82
+ ```sh
83
+ npm install -g dsh-oc-tui
25
84
  ```
26
85
 
27
- The first command initializes `$DSH_HOME/profiles/tui` with `@deepseek-ai/dsh-base`, installs this package with pnpm, and appends it to the profile's `dsh.profile.bundles` because the package declares `dsh.bundle`. Nothing else needs editing.
86
+ ### Version channels
28
87
 
29
- Verify the layer without booting:
88
+ The **npm package** and the **[awesome-dsh-plugin](https://awesome-dsh-plugin.com/zh/p/rayafriandion/dsh-oc-tui/) marketplace entry** both ship **stable releases only** — pre-releases are never published to either. `npm install` therefore gives you the latest stable version, not a release candidate.
89
+
90
+ This README describes the current source tree, which can be ahead of the published release — a feature documented here is only guaranteed to exist in a stable build once that version is on npm.
91
+
92
+ To run a pre-release, or unreleased work from this repository, install it explicitly from source:
30
93
 
31
94
  ```sh
32
- dsh --profile tui --dump-config
95
+ npm pack # -> dsh-oc-tui-<version>.tgz
96
+ dsh plugin --profile tui add -w ./dsh-oc-tui-<version>.tgz
33
97
  ```
34
98
 
35
- The dump shows the `dsh-oc-tui` bundle layer after `@deepseek-ai/dsh-base`.
99
+ ### One-command installers
36
100
 
37
- ## Launch
101
+ The repository ships installers that check Node >= 22, make sure `pnpm` exists, install the plugin into the `tui` profile, and can also add the `dsh-oc-tui` launcher globally.
38
102
 
39
- ### `dsh --profile tui`
103
+ ```sh
104
+ # Linux / macOS
105
+ curl -fsSL https://raw.githubusercontent.com/rayafriandion/dsh-oc-tui/main/install.sh | bash
106
+ ```
107
+
108
+ ```powershell
109
+ # Windows (PowerShell)
110
+ powershell -ExecutionPolicy Bypass -Command "iwr https://raw.githubusercontent.com/rayafriandion/dsh-oc-tui/main/install.ps1 -OutFile install.ps1; & .\install.ps1"
111
+ ```
112
+
113
+ Run `./install.sh` / `.\install.ps1` from a checkout instead, and add `--launcher` / `-Launcher` to also put the `dsh-oc-tui` command on `PATH`. Other flags: `--local` (`-Local`) installs the current checkout, `--source <spec>` (`-Source <spec>`) uses a custom source, `--profile <name>` (`-Profile <name>`) targets another profile.
40
114
 
41
- The canonical launch is the dsh launcher itself:
115
+ ### From a checkout or tarball
42
116
 
43
117
  ```sh
44
- dsh --profile tui # open the title screen; first message creates a session
45
- dsh --profile tui --resume <sessionId> # resume a persisted session
46
- dsh --profile tui --model <modelId> # default model for new sessions
47
- dsh --profile tui --provider <route> # default provider route
48
- dsh --profile tui --no-sidebar # start without the sidebar
49
- dsh --profile tui --help # the TUI's own flags
118
+ npm pack # -> dsh-oc-tui-<version>.tgz
119
+ dsh plugin --profile tui add -w ./dsh-oc-tui-<version>.tgz
50
120
  ```
51
121
 
52
- The stock dsh launcher only hardcodes `web` and `plugin` as bare subcommands, so the profile flag is the intended shape for custom surfaces. If you want the exact string `dsh tui`, add a one-line shell alias (for example `doskey tui=dsh --profile tui $*` in CMD).
122
+ `dsh plugin` anchors relative paths to the directory you invoke it from before forwarding to pnpm.
53
123
 
54
- ### `dsh-oc-tui` convenience launcher
124
+ ### Why `-w`
125
+
126
+ The profile directory declares itself a pnpm workspace root (`pnpm-workspace.yaml` → `packages: [.]`), so pnpm refuses a bare `add` with `ERR_PNPM_ADDING_TO_ROOT`. `-w` makes the dependency land in the profile's own manifest — which is exactly what it is. `dsh plugin` then reconciles `dsh.profile.bundles` against what is installed.
127
+
128
+ ### What the install does
129
+
130
+ 1. `dsh plugin` initializes `$DSH_HOME/profiles/tui` on first use (`@deepseek-ai/dsh-base` plus an empty user patch layer).
131
+ 2. pnpm installs `dsh-oc-tui` into the profile's `node_modules`.
132
+ 3. Because the package declares `dsh.bundle.patch`, dsh appends `dsh-oc-tui` to `dsh.profile.bundles`.
133
+ 4. `dsh --profile tui` composes the base layer, this bundle's rows, and your own patch — no manual editing required.
55
134
 
56
- The package also ships a `dsh-oc-tui` bin. It is equivalent to `dsh --profile tui`, but it verifies that the `tui` profile actually has the plugin installed and prints the one-time setup command when it does not:
135
+ Verify without booting:
136
+
137
+ ```sh
138
+ dsh --profile tui --dump-config
139
+ ```
140
+
141
+ The dump shows a `# == dsh-oc-tui` layer containing `tui-startup`, `tui-app`, the `agent-presets` roster row, and the `tool-ask-user` row.
142
+
143
+ ## Quick start
144
+
145
+ ```sh
146
+ dsh --profile tui # title screen; your first message creates a session
147
+ dsh --profile tui --resume <sessionId> # resume a persisted session
148
+ dsh --profile tui --model <modelId> # default model for new sessions
149
+ dsh --profile tui --provider <route> # default provider route
150
+ dsh --profile tui --no-sidebar # start without the session rail
151
+ dsh --profile tui --help # the TUI's own flags
152
+ ```
153
+
154
+ The stock launcher hardcodes only `web` and `plugin` as bare subcommands, so `--profile tui` is the intended shape. Want the literal `dsh tui`? Add a shell alias:
155
+
156
+ ```powershell
157
+ function tui { dsh --profile tui @args } # PowerShell $PROFILE
158
+ ```
159
+
160
+ ```bat
161
+ doskey tui=dsh --profile tui $* :: CMD
162
+ ```
163
+
164
+ ### Convenience launcher
165
+
166
+ The package also ships a `dsh-oc-tui` binary that is equivalent to `dsh --profile tui`, but checks first that the profile actually has the plugin installed and prints the one-time install command when it does not.
57
167
 
58
168
  ```sh
59
169
  dsh-oc-tui # boot the tui profile
60
- dsh-oc-tui --profile mytui # boot a profile with a different name
170
+ dsh-oc-tui --profile mytui # boot a different profile
61
171
  dsh-oc-tui --help # launcher help
172
+ dsh-oc-tui --version # launcher version
62
173
  ```
63
174
 
64
- The launcher prefers an installed `dsh` on PATH and falls back to `npx --yes @deepseek-ai/dsh`. Install the bin globally with `npm install -g dsh-oc-tui` (or run it from a local checkout with `node bin/dsh-oc-tui.js`). Environment overrides:
175
+ It prefers the `dsh` on `PATH` and falls back to `npx --yes @deepseek-ai/dsh`. Install it with `npm install -g dsh-oc-tui`.
65
176
 
66
- | Variable | Effect |
177
+ | Environment variable | Effect |
67
178
  | --- | --- |
68
- | `DSH_TUI_PROFILE` | Default profile name when `--profile` is not given. |
179
+ | `DSH_TUI_PROFILE` | Default profile when `--profile` is absent (default `tui`). |
69
180
  | `DSH_TUI_SKIP_CHECK` | Set to `1` to skip the profile preflight (advanced installs). |
70
181
 
71
182
  ## Usage
72
183
 
73
- | Flag | Effect |
74
- | --- | --- |
75
- | `--resume <sessionId>` | Resume a persisted session by id. |
76
- | `--model <modelId>` | Default model id for new sessions. |
77
- | `--provider <provider>` | Default provider route for new sessions. |
78
- | `--sidebar` / `--no-sidebar` | Show (default) or hide the session sidebar. |
79
- | `--help` | Print the TUI's own flag help. |
80
-
81
184
  ### Keybindings
82
185
 
83
186
  | Key | Action |
84
187
  | --- | --- |
85
- | Enter | send message |
86
- | Ctrl+Enter / Shift+Enter / Alt+Enter | insert a newline |
87
- | Ctrl+C | clear a non-empty prompt, cancel a running turn, or press twice while idle to exit |
88
- | Ctrl+P | open the settings menu |
89
- | Ctrl+E | toggle the thinking slider below the input box |
90
- | Tab | cycle the thinking intensity (session page); switch the settings left menu (settings page) |
91
- | Ctrl+N | new session |
92
- | Ctrl+D | delete the focused session in Settings Manage sessions |
93
- | Ctrl+L | clear the transcript view |
94
- | Up / Down | move the caret across multi-line input; on the first/last row they step through input history |
95
- | Left / Right | move the caret left/right in the input box |
96
- | PgUp / PgDn | scroll the transcript |
97
- | Esc | close the context-meter panel / thinking slider / help, or cancel an approval prompt |
98
- | y / n | answer an inline approval prompt |
188
+ | `Enter` | Send the message. |
189
+ | `Ctrl+Enter` / `Shift+Enter` / `Alt+Enter` | Insert a newline. |
190
+ | `Ctrl+C` | Clear a non-empty prompt, cancel the running turn, or press twice while idle to exit. |
191
+ | `Ctrl+P` | Open Settings. |
192
+ | `Ctrl+E` | Toggle the thinking-intensity slider below the composer. |
193
+ | `Tab` | Session page: cycle the thinking intensity. Settings page: switch the left menu. |
194
+ | `Ctrl+N` | New session. |
195
+ | `Ctrl+D` | In Settings → Manage sessions: delete the focused session (press twice to confirm). |
196
+ | `Ctrl+L` | Clear the transcript view. |
197
+ | `Up` / `Down` | Move the caret across a multi-line prompt; on the first/last row, step through input history. |
198
+ | `Left` / `Right` | Move the caret within the input box. |
199
+ | `PgUp` / `PgDn` | Scroll the transcript. |
200
+ | `Esc` | Close the context-meter panel, the thinking slider, or help; cancel an approval. |
201
+ | `y` / `n` | Answer an inline approval prompt. |
99
202
 
100
- Mouse: the wheel scrolls the transcript (or the settings window when Settings is open — it no longer moves the settings focus). Hold the left button and drag across the transcript to select text, then press the right button to copy the selection to the clipboard.
203
+ **Mouse.** The wheel scrolls the transcript (or the Settings window while it is open). Hold the left button and drag across the transcript to select text, then press the right button to copy the selection.
101
204
 
102
- ### Commands
205
+ ### Slash commands
103
206
 
104
- `/help` `/settings` `/new` `/resume <id>` `/model <id>` `/provider <route>` `/clear` `/cancel` `/quit`
207
+ Built in: `/help` `/settings` `/new` `/resume <id>` `/model <id>` `/provider <route>` `/clear` `/cancel` `/quit` (`/exit` also works).
105
208
 
106
- Harness human commands (`/compact`, `/goal`, ...) are forwarded to `ctx.commands` and run without a model turn.
209
+ Harness commands `/compact`, `/goal`, `/plan`, … — are forwarded to `ctx.commands` and run without a model turn. They need a live session: on the title screen the TUI answers `/<name>: start a session first` instead of dropping the command silently.
107
210
 
108
- ## Install
211
+ ### Interactive prompts
109
212
 
110
- ### Automatic install script
213
+ **Approvals.** When a tool needs permission, the composer area shows `Approval · <tool> · y allow / n deny`. `y` allows once, `n` rejects, `Esc` cancels. The plugin also honours the effective permission preset, so an auto-approving preset does not prompt at all.
111
214
 
112
- The repo ships one-command installers for Linux/macOS and Windows. They check Node.js >= 22, ensure `pnpm` is present, install the plugin from GitHub into the `tui` profile, and can also install the `dsh-oc-tui` launcher globally.
215
+ **Questions.** The model can ask you directly through the `ask_user_question` tool. The tool is declared by this bundle's `tool-ask-user` row `dsh-base` mounts the `user-questions` service but not the tool, and a TUI session composes from the base rather than from an agent preset — and it is answered by a modal:
113
216
 
114
- ```sh
115
- # Linux / macOS
116
- curl -fsSL https://raw.githubusercontent.com/rayafriandion/dsh-oc-tui/main/install.sh | bash
117
-
118
- # Windows (PowerShell)
119
- powershell -ExecutionPolicy Bypass -Command "iwr https://raw.githubusercontent.com/rayafriandion/dsh-oc-tui/main/install.ps1 -OutFile install.ps1; & .\install.ps1"
120
- ```
121
-
122
- From a checkout you can run `./install.sh` (Linux/macOS) or `.\install.ps1` (Windows) directly. Add `--launcher` / `-Launcher` to also put the `dsh-oc-tui` command on PATH:
217
+ | Key | Action |
218
+ | --- | --- |
219
+ | `Up` / `Down` | Move between options (wrapping). |
220
+ | `Space` | Toggle the highlighted option (multi-select) or select it (single choice). |
221
+ | `Enter` | Continue: a single choice is selected and advances; on the free-text row it starts editing; in a multi-select it confirms the toggled set. |
222
+ | any printable key | Jump into the free-text row and start typing. |
223
+ | `PgUp` / `PgDn`, wheel | Scroll a long plan or detail pane. |
224
+ | `Esc` | Defer: decline to answer here (`Esc` while editing returns to the options). |
225
+ | `Ctrl+C` | Still cancels the running turn; the pending question is withdrawn. |
123
226
 
124
- ```sh
125
- ./install.sh --launcher # Linux/macOS
126
- .\install.ps1 -Launcher # Windows
127
- ```
227
+ Questions are staged one at a time, exactly as the Web UI composer stages them, and the answer encoding is identical: a free-text answer replaces the selection for a single-select question and accompanies it for a multi-select one.
128
228
 
129
- Other options: `--local` (`-Local`) installs the current checkout instead of GitHub, `--source <spec>` (`-Source <spec>`) uses a custom source (e.g. `dsh-oc-tui` once published to npm, or a tarball path), and `--profile <name>` (`-Profile <name>`) targets a non-default profile.
229
+ A question carrying the `plan-review` intent what `exit_plan_mode` sends renders the plan markdown in a scrollable pane above `Approve` / `Keep planning`. Answering `Approve` exits plan mode and the model continues; anything else keeps planning.
130
230
 
131
- ### From the npm registry
231
+ Deferring is deliberate, not a cancel: with no other answerer the tool reports `no user-questions answerer accepted the request`, which cannot be mistaken for a human choice.
132
232
 
133
- ```sh
134
- dsh plugin --profile tui add dsh-oc-tui
135
- ```
233
+ ### Thinking intensity
136
234
 
137
- ### From a local checkout or tarball
235
+ The effective level sits on the composer's top-right border as the bare level name, diagonally opposite the `provider · model` label.
138
236
 
139
- ```sh
140
- dsh plugin --profile tui add ./dsh-oc-tui
141
- # or a packed tarball:
142
- dsh plugin --profile tui add ./dsh-oc-tui-0.1.0.tgz
143
- ```
237
+ - `Tab` on the session page cycles the levels of the **current model** (wrapping strongest → weakest); `Shift+Tab` steps backwards.
238
+ - `Ctrl+E` opens a slider below the composer: `Tab` or `←`/`→` adjust and persist, `Esc` or `Ctrl+E` close it.
239
+ - Levels come from the provider adapter (`ctx.llm.resolveModelInfo`), so a boolean-thinking model shows exactly its two ends, DeepSeek's `Off`/`High`/`Max` shows those three, and a full-range model shows every advertised level — never a blanket `none → max` scale.
144
240
 
145
- `dsh plugin` anchors relative paths to your invoking directory before forwarding to pnpm.
241
+ The choice is applied to the session's requests through the `agent/request` waterfall and stored in `agent-default-model.reasoningEffort`.
146
242
 
147
- ### From GitHub
243
+ ### Context meter and telemetry
148
244
 
149
- ```sh
150
- dsh plugin --profile tui add github:you/dsh-oc-tui
151
- ```
245
+ The status row carries a live context-occupancy bar fed by the token-meter `contextPressure` projection — the same source as the Web UI's composer ring: current context length over the model's context window, shifting to the warning and error palette as occupancy climbs. Clicking it opens a breakdown panel (click again or `Esc` to close) with the occupancy reading and the heuristic composition shares — system prompt, tools, and messages — matching the Web UI's ContextMeter dialog. The meter hides itself when the profile has no token-meter projections.
152
246
 
153
- This package ships plain JavaScript, so a git install needs no build step. pnpm 10 may still require allowlisting the git dependency's package key under `allowBuilds` in the profile's `pnpm-workspace.yaml` if a build step is ever added.
247
+ The footer reports session tokens, average time to first token, decode throughput, and cache-hit rate, folded from durable step, chunk, and message events.
154
248
 
155
- ### What the install does
249
+ ### Settings
156
250
 
157
- 1. `dsh plugin` initializes `$DSH_HOME/profiles/tui` on first use (`@deepseek-ai/dsh-base` plus an empty user patch layer).
158
- 2. pnpm installs `dsh-oc-tui` into the profile's `node_modules`.
159
- 3. `dsh` appends `dsh-oc-tui` to `dsh.profile.bundles` because the package declares `dsh.bundle.patch`; the bundle patch inserts the `tui-startup` and `tui-app` rows.
160
- 4. `dsh --profile tui` composes `@deepseek-ai/dsh-base` + `dsh-oc-tui` and boots the UI.
251
+ `Ctrl+P` opens a settings menu over the same host settings namespaces as the Web UI, persisted through `ctx.settings` to `$DSH_HOME/settings.yaml`. A left menu splits it into three tabs (`Tab` or click to switch):
161
252
 
162
- To remove:
253
+ - **Main** — General (busy-Enter behaviour, default agent preset, permission preset), Sessions (new session, manage sessions), System (provider API hints, update-manager shortcut, settings file path).
254
+ - **Model** — the default provider/model/reasoning choice, then one group per provider holding its URL, API key, and model list. Pressing `Enter` on **Models** fetches the provider's advertised catalog (`ctx.llm.discoverModels`) and opens a checkbox window; pressing `Enter` on a listed model makes it the default route.
255
+ - **Update** — see [In-app updates](#in-app-updates).
163
256
 
164
- ```sh
165
- dsh plugin --profile tui remove dsh-oc-tui
166
- ```
257
+ Only providers you actually added (present in your user settings layer) are listed; a provider that was never added stays hidden. The default agent preset comes from the roster the profile mounts (the shipped presets plus any you authored under `$DSH_HOME/.agent-presets`) — note that a TUI session composes process-wide from the base, so the stored default applies where a session is created from a preset. Web-UI-only options (`ui-theme` appearance, `locale`) are not shown because they have no effect in the TUI.
167
258
 
168
- ## Settings
259
+ ### In-app updates
169
260
 
170
- The settings menu projects the same Host settings namespaces used by the WebUI and persists changes through `ctx.settings` to `$DSH_HOME/settings.yaml`. A left menu bar splits it into three tabs that `Tab` (or a click) switches: **Main** keeps the general settings — General (Busy Enter behavior, default agent and permission presets), Sessions (new session, session management), and System (including an `Update manager` shortcut to the Update tab); **Model** merges the former provider settings and model settings into one tree: the default provider/model/reasoning choice, then one block per provider holding its Provider URL, Provider API key, and Models; **Update** carries the in-app update manager (see below). Only providers you have actually added (present in your user settings layer) are listed — preset providers that were never added stay hidden. Under Models the saved selection is listed one row per model (Enter makes a listed model the default route); pressing Enter on the Models row auto-fetches the provider's advertised catalog (`ctx.llm.discoverModels` — the installed catalog for a known route, or an endpoint interrogation for a custom route) and opens a selection window of checkboxes where you choose which models to keep. The default agent preset is chosen from the roster the profile mounts (`agent-presets`): the shipped presets plus any you authored under `$DSH_HOME/.agent-presets`; TUI sessions keep composing process-wide from the base, so the stored default only applies where a session is created from a preset. WebUI-only options (`ui-theme` Appearance and `locale` Language) are intentionally not shown because they have no effect inside the TUI. Choice items open their option list with Enter - there is no inline left/right value cycling.
261
+ `Ctrl+P Update` shows the installed versions of `@deepseek-ai/dsh` and `dsh-oc-tui`, the latest npm dist-tag, and a status line that only ever targets **stable** releases:
171
262
 
172
- **Thinking intensity.** The effective level stays visible on the composer's top-right border as the bare level name (no "effort" caption), diagonally opposite the `provider · model` label. Press `Tab` on the session page to cycle the intensity through the current model's levels (wrapping strongest weakest); `Shift+Tab` steps backwards. Press `Ctrl+E` to open a slider below the input box; `Tab` or `←`/`→` move it and persist the choice, `Esc` or `Ctrl+E` closes it. The slider is driven by the current model's actual selectable levels reported by the provider adapter (`ctx.llm.resolveModelInfo`), so a boolean-thinking model shows exactly its two ends, a full-range model shows every level it advertises, and a partial model (for example DeepSeek's `Off`/`High`/`Max`) shows only those never a blanket `none max` scale. At the strongest available level, the track gradient and bright sweep move left to right, the empty track shimmers, and the top-right label receives a flowing gradient with a pulsing text arrow. The Settings → Model → Reasoning entry stays available as a list menu over the same levels. The chosen level is applied to the session's requests through the `agent/request` waterfall and stored in `agent-default-model.reasoningEffort`.
263
+ - `Update availablex.y.z` — a newer stable release exists.
264
+ - `Up to date` — nothing to do.
265
+ - `No stable release — pick from Versions` — the registry has no stable release yet; pick one manually.
266
+ - `Install damaged — reinstall below` — the global dsh tree is in a mixed old/new state; reinstall it.
173
267
 
174
- **Context meter.** The status row carries a live context-occupancy bar (`ctx ▓▓░░ 32K/128K 25%`) fed by the token-meter `contextPressure` projection, the same source as the Web UI's composer ring. It shows the current context length over the context-length limit once the provider reports both; the fill shifts toward the warning/error palette as occupancy climbs. Click the meter (or press `Esc` to close) to open a breakdown panel with the occupancy reading and the heuristic composition shares — system prompt, tools, and messages matching the Web UI's ContextMeter dialog. The meter stays hidden when the profile lacks the token-meter projections.
268
+ `Enter` on a package's **Versions** row opens the full registry list (newest first, `[latest]`/`[next]`/other tags and `(installed)` colour-coded) where you can pick any version including pre-releases for a y/n-confirmed install through `npm`/`dsh plugin`. `Check now` re-reads the registry; `Startup check` toggles the silent boot-time stable-release check. Installs run in the background, never block the UI, and need a restart to apply.
175
269
 
176
- **Update manager.** `Ctrl+P → Update` shows the installed versions of `@deepseek-ai/dsh` and `dsh-oc-tui`, their latest npm dist-tag, and a status line that only ever targets **stable** releases (pre-releases such as `0.1.1-rc.2` never prompt as an update — but the factual `Latest (npm tag)` row still reports them). Enter on a package's `Versions` row opens the full registry version list (newest first, `[latest]`/`[next]`/other tags plus `(installed)` color-coded) where you pick any version — including pre-releases — to switch to; a y/n confirm shows the exact target, and the install runs through `npm`/`dsh plugin` so your configured registry and mirror are respected. Installs complete in the background with a toast and require a **restart to apply**. `Check now` re-reads the registry, and `Startup check` toggles the silent boot-time check that toasts stable updates (default `on`). Checks and installs never block the UI; a spinner runs in the page subtitle.
270
+ <details>
271
+ <summary><strong>Windows: why dsh installs are deferred to exit</strong></summary>
177
272
 
178
- On Windows, updating dsh while any dsh process runs can *silently corrupt* the global install npm replaces the directory while the running process holds memory-mapped native DLLs, and still exits 0; the hybrid old/new tree then fails to boot. The updater guards this in three layers: **dsh installs are deferred to TUI exit time** (a detached helper waits for the TUI to close, runs the install, and records the outcome in `$DSH_HOME/tui-dsh-install.json`, which the Update page verifies on the next open), **the on-disk version is re-read and compared** after every direct install so a silent corruption surfaces as an `install corrupt` toast with repair instructions, and the dsh Status row flags an already-damaged install as `Install damaged — reinstall below`. On macOS/Linux there is no DLL lock, but the install is refused while other dsh processes are running.
273
+ On Windows, updating dsh while any dsh process runs can *silently corrupt* the global install: npm replaces the directory while the running process holds memory-mapped native DLLs, still exits 0, and the resulting old/new hybrid tree fails to boot. The updater guards this in three layers:
179
274
 
180
- ## Plugin model
275
+ 1. **dsh installs are deferred to TUI exit** — a detached helper waits for the TUI to close, runs the install, and records the outcome in `$DSH_HOME/tui-dsh-install.json`, which the Update page verifies on the next visit.
276
+ 2. **The on-disk version is compared** against the requested target after every direct install, so a silent corruption surfaces as an `install corrupt` toast with repair instructions.
277
+ 3. **An already-damaged install is flagged** in the Status row rather than reported as a bogus success.
181
278
 
182
- This package is a DeepSeek Harness Cordis plugin, not a standalone agent runtime. The optional `dsh-oc-tui` binary only launches `dsh --profile tui`; DSH continues to own model routing, agent execution, tools, approvals, commands, durable sessions, and credentials. The plugin owns terminal input and presentation.
279
+ macOS/Linux have no DLL lock, but an install is refused while other dsh processes are running.
280
+ </details>
183
281
 
184
282
  ## How it works
185
283
 
186
284
  - The plugin is a Cordis function plugin loaded by the `tui` profile. `lib/startup.js` parses the app's flags and provides the `tuiStartup` service; `lib/index.js` owns the UI loop.
187
- - `lib/term.js` is a zero-dependency terminal engine: raw mode, alternate screen, a diffing cell buffer, and a key decoder (truecolor ANSI, CJK-aware widths). It parks the (hidden) terminal cursor at the input caret so the OS IME anchors its composition window inside the composer, and it understands both SGR and legacy X10 mouse encodings so wheel/click bytes can never leak into the input text.
188
- - `lib/ui.js` is the responsive view model + renderer (DeepSeek blue-white theme, session rail, transcript, multiline composer, command suggestions, and telemetry footer). Rendered transcript lines are cached per block, only the visible window is materialised each frame, streaming paints are coalesced, and the live block is re-rendered on a short throttle — so render cost stays bounded and output speed does not degrade as the history grows. `thinking` content is shown inside a gray-emphasised box that stays collapsed while streaming, collapses by default once finished, and toggles on click; thinking and running tools animate with flowing spinners.
189
- - `lib/metrics.js` folds durable step/chunk/message events into session token, average TTFT, decode throughput, and disjoint-token cache-hit metrics.
285
+ - `lib/term.js` is a zero-dependency terminal engine: raw mode, alternate screen, a diffing cell buffer, and a key decoder (truecolor ANSI, CJK-aware widths). It parks the hidden terminal cursor at the input caret so the OS IME anchors its composition window inside the composer, and it understands both SGR and legacy X10 mouse encodings so wheel and click bytes can never leak into the input text.
286
+ - `lib/ui.js` is the responsive view model and renderer (DeepSeek blue-white theme, session rail, transcript, multiline composer, command suggestions, telemetry footer). Transcript lines are cached per block, only the visible window is materialised each frame, streaming paints are coalesced, and the live block re-renders on a short throttle — so render cost stays bounded as history grows. Thinking collapses to keep the transcript readable, and running tools and thinking blocks animate with flowing spinners.
287
+ - `lib/metrics.js` folds durable step/chunk/message events into token, TTFT, throughput, and cache-hit metrics.
190
288
  - `lib/interrupt.js` owns the clear/cancel/double-exit state machine used by stdin and `SIGINT`.
191
- - `lib/updates.js` isolates every npm/pnpm interaction for the Update settings tab: registry queries, version parsing/comparison (dependency-free semver core + pre-release), dsh install detection, and async installs — all through `child_process.spawn`, never `spawnSync`.
192
289
  - `lib/markdown.js` renders model output (headings, lists, quotes, code, inline spans) to styled lines.
193
- - Agents are created/resumed through `ctx.agents`, the transcript is rebuilt from `session.surface` on resume and fed live by `session/event` (including `assistant/chunk` streaming), model defaults come from `ctx.agentDefaultModel`, and approvals answer the `approval/request` waterfall inline.
290
+ - `lib/updates.js` isolates every npm/pnpm interaction for the Update tab registry queries, dependency-free semver comparison, dsh install detection, and installs all through `child_process.spawn`, never `spawnSync`.
291
+ - Agents are created and resumed through `ctx.agents`, the transcript is rebuilt from the session's durable log and fed live by `session/event` (including `assistant/chunk`), model defaults come from `ctx.agentDefaultModel`, and approvals answer the `approval/request` waterfall inline.
292
+ - `ask_user_question` is answered over the `user-questions/request` waterfall: a scoped Cordis waterfall where the modal either returns an answer or delegates with `next()`. An aborted request rejects so the service reports its own `ASK_ABORTED`; requests addressed to another agent are delegated untouched.
194
293
 
195
294
  ## Development
196
295
 
197
296
  ```sh
198
- node tests/smoke.test.mjs # standalone pure-module tests (no dsh needed)
199
- node --check lib/*.js # syntax
200
- node --check bin/*.js # launcher syntax
297
+ npm run check # node --check over lib/, bin/
298
+ npm test # standalone smoke tests (no dsh needed)
201
299
  ```
202
300
 
203
- The full end-to-end path (profile bootsessionlive LLM streaming commands clean exit) was verified through a pseudo-terminal (node-pty + ConPTY) on Windows.
301
+ **The install is a build, so edit buildinstall.** The profile contains a *tarball* copy of the plugin, and the profile's HMR root is the profile directory, not the plugin directory — editing this checkout changes nothing until you repack and reinstall:
302
+
303
+ ```sh
304
+ npm pack # -> dsh-oc-tui-<version>.tgz
305
+ dsh plugin --profile tui remove -w dsh-oc-tui # detach the old copy FIRST
306
+ Remove-Item .\*.tgz # then drop the stale tarball
307
+ npm pack
308
+ dsh plugin --profile tui add -w .\dsh-oc-tui-<version>.tgz
309
+ ```
204
310
 
205
- For a zero-install development bootstrap that avoids pnpm, create the profile once and point its `cordis.patch.yml` at this checkout with absolute module paths:
311
+ Detach before deleting: pnpm resolves the profile's existing `file:` dependency while adding, so a dependency pointing at a deleted tarball aborts the whole install with `ENOENT`.
312
+
313
+ Verify the swap actually landed — the version string proves nothing:
314
+
315
+ ```powershell
316
+ foreach ($rel in @('lib\index.js','lib\ui.js','lib\util.js','lib\term.js','lib\metrics.js',
317
+ 'lib\interrupt.js','lib\web-settings.js','lib\updates.js','lib\markdown.js',
318
+ 'lib\startup.js','bin\dsh-oc-tui.js','cordis.patch.yml')) {
319
+ $a = (Get-FileHash ".\$rel").Hash
320
+ $b = (Get-FileHash "$env:USERPROFILE\.dsh\profiles\tui\node_modules\dsh-oc-tui\$rel").Hash
321
+ if ($a -ne $b) { "DIFFERS: $rel" }
322
+ }
323
+ ```
324
+
325
+ Then boot it for real. Reaching the title screen is not enough — the session-open path is where host API breaks surface, so send a message. Test `--resume` separately, because it is an apply-time path that can lose a startup race the post-boot paths win.
326
+
327
+ For a zero-install bootstrap that skips packaging entirely, create the profile once and point its patch at this checkout:
206
328
 
207
329
  ```sh
208
330
  dsh --profile tui --dump-config # initializes the base profile once
209
331
  ```
210
332
 
211
- Then add to `$DSH_HOME/profiles/tui/cordis.patch.yml`:
212
-
213
333
  ```yaml
334
+ # $DSH_HOME/profiles/tui/cordis.patch.yml
214
335
  - insert:
215
336
  - id: tui-startup
216
337
  name: 'file:///D:/Projects/DeepSeekHarnessPlugins/dsh-oc-tui/lib/startup.js'
@@ -221,34 +342,55 @@ Then add to `$DSH_HOME/profiles/tui/cordis.patch.yml`:
221
342
  showReasoning: true
222
343
  ```
223
344
 
224
- The plugin's dsh imports resolve through the shared `$DSH_HOME/profiles/node_modules` fallback that dsh maintains, so no pnpm install into the plugin directory is required.
345
+ The plugin's dsh imports resolve through the shared `$DSH_HOME/profiles/node_modules` fallback that dsh maintains, so nothing has to be installed into the plugin directory.
346
+
347
+ ## Troubleshooting
348
+
349
+ | Symptom | Cause and fix |
350
+ | --- | --- |
351
+ | `pnpm failed in profile directory` / `ERR_PNPM_ADDING_TO_ROOT` | The profile is a pnpm workspace root; add `-w` to the `add`/`remove` command. |
352
+ | `ENOENT: … dsh-oc-tui-<v>.tgz` during install | The profile still references a tarball you deleted. `dsh plugin --profile tui remove -w dsh-oc-tui`, then add again. |
353
+ | `pnpm not found on PATH` | Install pnpm (`npm install -g pnpm`) and retry. |
354
+ | `--dump-config` has no TUI layer | The install did not complete, or the package name is misspelled. Re-run the `add` and check `dsh.profile.bundles`. |
355
+ | Exits immediately / no UI | stdin and stdout must both be a TTY — do not pipe or redirect. Then verify the model route and credentials exist. |
356
+ | `--resume` or Manage sessions unavailable | Both need the shared `sessionQuery` service; keep `@deepseek-ai/dsh-base` first in `dsh.profile.bundles`. |
357
+ | `no agent factory registered` on `--resume` | A startup race with the agent-loop row; current builds retry it. On an older build, run `/resume <id>` after boot instead. |
358
+ | Loader errors after updating dsh (`State`, `./internal`) | The global dsh install is a mixed old/new tree. Close every dsh process and reinstall: `npm install -g @deepseek-ai/dsh@<version>`. |
359
+ | Source edits have no effect | The installed copy is a tarball; repack and reinstall (see [Development](#development)). |
360
+
361
+ More detail, in Chinese: [docs/用户手册.md](docs/用户手册.md).
225
362
 
226
363
  ## Known limitations
227
364
 
228
365
  - IME composition and bracketed-paste image attachments are not exposed by the zero-dependency terminal engine yet.
229
- - Saved sessions are managed from Settings Manage sessions; the shared `sessionQuery` service is required for the list.
230
- - `dsh tui` as a bare subcommand needs a shell alias — the stock launcher hardcodes only `web` and `plugin` subcommands.
231
- - Editing the plugin source does not hot-reload (the profile's HMR root is the profile dir, not the plugin dir); restart the profile to pick up changes.
232
- - `--resume` and Settings Manage sessions require the shared `sessionQuery` service (mounted by `dsh-base`).
233
- - The deferred dsh install on Windows waits for the TUI that scheduled it, not for every dsh process on the machine — with several TUI windows (or a `dsh web`) open, close them before the exit-time install runs; if a concurrent install still corrupts the tree, the next Update page visit flags it (`Install damaged — reinstall below`) instead of reporting a bogus success.
366
+ - The plugin does not hot-reload: the profile's HMR root is the profile directory, so a running TUI keeps the copy it booted with.
367
+ - `dsh tui` as a bare subcommand needs a shell alias — the stock launcher hardcodes only `web` and `plugin`.
368
+ - Harness slash commands need a live session; on the title screen the TUI tells you to start one first.
369
+ - Deferring a question with `Esc` does not cancel the tool call — it delegates, and with no other answerer the tool call fails. Per-question skip (as the Web UI composer offers) is not implemented.
370
+ - `--resume`, Settings Manage sessions, and the context meter depend on services mounted by `@deepseek-ai/dsh-base` (`sessionQuery`, `sessionProjections`); a hand-built profile must provide them.
371
+ - The deferred dsh install on Windows waits for the TUI that scheduled it, not for every dsh process on the machine — close other TUI windows (and `dsh web`) before it runs.
234
372
 
235
373
  ## Layout
236
374
 
237
375
  ```
238
- lib/index.js plugin entry: agents, events, input, commands, approvals
239
- lib/startup.js command-line provider (tuiStartup service)
240
- lib/term.js terminal engine (raw mode, screen, key decoding)
241
- lib/ui.js responsive view model + renderer
242
- lib/metrics.js durable event telemetry fold
243
- lib/interrupt.js Ctrl+C lifecycle state
244
- lib/web-settings.js shared WebUI settings projection
245
- lib/updates.js in-app update manager (npm registry + installs)
246
- lib/markdown.js markdown -> styled lines
247
- lib/util.js text/display helpers
248
- bin/dsh-oc-tui.js convenience launcher for `dsh --profile tui`
249
- install.sh one-command installer (Linux/macOS)
250
- install.ps1 one-command installer (Windows)
251
- cordis.patch.yml bundle patch layer (TUI rows)
252
- docs/用户手册.md Chinese user manual
253
- tests/smoke.test.mjs standalone smoke tests
376
+ lib/index.js plugin entry: agents, events, input, commands, approvals, user questions
377
+ lib/startup.js command-line provider (tuiStartup service)
378
+ lib/term.js terminal engine (raw mode, screen, key decoding)
379
+ lib/ui.js responsive view model + renderer (includes the question modal)
380
+ lib/metrics.js durable event telemetry fold
381
+ lib/interrupt.js Ctrl+C lifecycle state
382
+ lib/web-settings.js shared WebUI settings projection
383
+ lib/updates.js in-app update manager (npm registry + installs)
384
+ lib/markdown.js markdown -> styled lines
385
+ lib/util.js text/display helpers
386
+ bin/dsh-oc-tui.js convenience launcher for `dsh --profile tui`
387
+ install.sh one-command installer (Linux/macOS)
388
+ install.ps1 one-command installer (Windows)
389
+ cordis.patch.yml bundle patch layer (TUI rows, agent-presets roster, ask-user tool)
390
+ docs/用户手册.md Chinese user manual
391
+ tests/smoke.test.mjs standalone smoke tests
254
392
  ```
393
+
394
+ ## License
395
+
396
+ [LGPL-3.0-or-later](LICENSE).
package/cordis.patch.yml CHANGED
@@ -22,3 +22,11 @@
22
22
  name: '@deepseek-ai/dsh-agent-presets'
23
23
  config:
24
24
  default: standard
25
+
26
+ # The model-facing `ask_user_question` tool. dsh-base mounts the
27
+ # `user-questions` service but NOT this tool — the agent presets mount it,
28
+ # and a TUI session composes process-wide from the base rather than from a
29
+ # preset, so the row must be declared here or the tool is simply absent.
30
+ # lib/index.js answers the `user-questions/request` waterfall with a modal.
31
+ - id: tool-ask-user
32
+ name: '@deepseek-ai/dsh-tool-ask-user'