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 +285 -143
- package/cordis.patch.yml +8 -0
- package/docs//347/224/250/346/210/267/346/211/213/345/206/214.md +256 -222
- package/lib/index.js +342 -4
- package/lib/ui.js +159 -2
- package/lib/util.js +9 -0
- package/package.json +25 -4
- package/docs/max-thinking.gif +0 -0
package/README.md
CHANGED
|
@@ -1,216 +1,337 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-oc-tui
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/dsh-oc-tui)
|
|
6
|
+
[](https://awesome-dsh-plugin.com/zh/p/rayafriandion/dsh-oc-tui/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://nodejs.org)
|
|
6
9
|
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
|
|
82
|
+
```sh
|
|
83
|
+
npm install -g dsh-oc-tui
|
|
25
84
|
```
|
|
26
85
|
|
|
27
|
-
|
|
86
|
+
### Version channels
|
|
28
87
|
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
99
|
+
### One-command installers
|
|
36
100
|
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
115
|
+
### From a checkout or tarball
|
|
42
116
|
|
|
43
117
|
```sh
|
|
44
|
-
|
|
45
|
-
dsh --profile tui
|
|
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
|
-
|
|
122
|
+
`dsh plugin` anchors relative paths to the directory you invoke it from before forwarding to pnpm.
|
|
53
123
|
|
|
54
|
-
### `
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
|
177
|
+
| Environment variable | Effect |
|
|
67
178
|
| --- | --- |
|
|
68
|
-
| `DSH_TUI_PROFILE` | Default profile
|
|
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 |
|
|
86
|
-
| Ctrl+Enter / Shift+Enter / Alt+Enter |
|
|
87
|
-
| Ctrl+C |
|
|
88
|
-
| Ctrl+P |
|
|
89
|
-
| Ctrl+E |
|
|
90
|
-
| Tab | cycle the thinking intensity
|
|
91
|
-
| Ctrl+N |
|
|
92
|
-
| Ctrl+D | delete the focused session
|
|
93
|
-
| Ctrl+L |
|
|
94
|
-
| Up / Down |
|
|
95
|
-
| Left / Right |
|
|
96
|
-
| PgUp / PgDn |
|
|
97
|
-
| Esc |
|
|
98
|
-
| y / n |
|
|
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
|
|
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
|
-
###
|
|
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
|
|
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
|
-
|
|
211
|
+
### Interactive prompts
|
|
109
212
|
|
|
110
|
-
|
|
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
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
134
|
-
dsh plugin --profile tui add dsh-oc-tui
|
|
135
|
-
```
|
|
233
|
+
### Thinking intensity
|
|
136
234
|
|
|
137
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
243
|
+
### Context meter and telemetry
|
|
148
244
|
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
249
|
+
### Settings
|
|
156
250
|
|
|
157
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
259
|
+
### In-app updates
|
|
169
260
|
|
|
170
|
-
|
|
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
|
-
|
|
263
|
+
- `Update available → x.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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
188
|
-
- `lib/ui.js` is the responsive view model
|
|
189
|
-
- `lib/metrics.js` folds durable step/chunk/message events into
|
|
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
|
-
-
|
|
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
|
-
|
|
199
|
-
|
|
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
|
|
301
|
+
**The install is a build, so edit → build → install.** 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
|
-
|
|
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
|
|
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
|
-
-
|
|
230
|
-
- `dsh tui` as a bare subcommand needs a shell alias — the stock launcher hardcodes only `web` and `plugin
|
|
231
|
-
-
|
|
232
|
-
-
|
|
233
|
-
-
|
|
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
|
|
239
|
-
lib/startup.js
|
|
240
|
-
lib/term.js
|
|
241
|
-
lib/ui.js
|
|
242
|
-
lib/metrics.js
|
|
243
|
-
lib/interrupt.js
|
|
244
|
-
lib/web-settings.js
|
|
245
|
-
lib/updates.js
|
|
246
|
-
lib/markdown.js
|
|
247
|
-
lib/util.js
|
|
248
|
-
bin/dsh-oc-tui.js
|
|
249
|
-
install.sh
|
|
250
|
-
install.ps1
|
|
251
|
-
cordis.patch.yml
|
|
252
|
-
docs/用户手册.md
|
|
253
|
-
tests/smoke.test.mjs
|
|
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'
|