dsh-oc-tui 0.1.5 → 0.2.0-pre.13
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 +149 -16
- package/bin/mcp-sse-bridge.mjs +499 -0
- package/docs//347/224/250/346/210/267/346/211/213/345/206/214.md +560 -431
- package/dsh-plugin.json +1 -1
- package/lib/caps.js +83 -0
- package/lib/i18n.js +283 -0
- package/lib/image.js +326 -0
- package/lib/index.js +811 -147
- package/lib/locales/ar.js +561 -0
- package/lib/locales/de.js +530 -0
- package/lib/locales/en.js +548 -0
- package/lib/locales/es.js +530 -0
- package/lib/locales/fr.js +530 -0
- package/lib/locales/id.js +532 -0
- package/lib/locales/it.js +530 -0
- package/lib/locales/ja.js +530 -0
- package/lib/locales/ko.js +530 -0
- package/lib/locales/nl.js +530 -0
- package/lib/locales/pl.js +544 -0
- package/lib/locales/pt-BR.js +530 -0
- package/lib/locales/ru.js +544 -0
- package/lib/locales/tr.js +530 -0
- package/lib/locales/vi.js +532 -0
- package/lib/locales/zh-CN.js +531 -0
- package/lib/locales/zh-TW.js +531 -0
- package/lib/markdown.js +447 -184
- package/lib/mcp-skill.js +93 -0
- package/lib/mcp.js +838 -0
- package/lib/mermaid-ascii.js +138 -0
- package/lib/mermaid.js +137 -0
- package/lib/rewind.js +15 -14
- package/lib/splash-opencode.js +108 -0
- package/lib/startup.js +16 -10
- package/lib/term.js +242 -10
- package/lib/ui.js +1252 -406
- package/lib/updates.js +66 -48
- package/lib/util.js +85 -27
- package/lib/web-settings.js +428 -50
- package/package.json +18 -5
- package/skills/mcp-import/SKILL.md +216 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dsh-oc-tui
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**The `0.2.0` line is the visual-experience update, and it is out as a pre-release now** (`0.2.0-pre.11`, on npm's `next` channel): real image rendering, native Mermaid art, and full Markdown run through this plugin's own terminal engine. It needed no `chafa`, `mmdc` or `mdcat` wrapper — the rendering stack is pure npm (`sharp` decodes and scales, `lovely-mermaid` lays out Mermaid) — and which dsh (the harness) builds it serves is spelled out in the compatibility note below. If you have suggestions for specific visual features, please share them in the original issue thread or open a new issue with a title containing `Visual Experience Suggestion`
|
|
4
4
|
|
|
5
5
|
**_Thank you for your support of the `dsh-oc-tui` plugin!_**
|
|
6
6
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
[](LICENSE)
|
|
14
14
|
[](https://nodejs.org)
|
|
15
15
|
|
|
16
|
-
`dsh-oc-tui` renders the harness's durable event stream in your terminal — streaming replies, tool
|
|
16
|
+
`dsh-oc-tui` renders the harness's durable event stream in your terminal — streaming replies, tool activity, 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.
|
|
17
17
|
|
|
18
18
|
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.
|
|
19
19
|
|
|
@@ -31,7 +31,10 @@ Published on **npm** as [`dsh-oc-tui`](https://www.npmjs.com/package/dsh-oc-tui)
|
|
|
31
31
|
- [Interactive prompts](#interactive-prompts)
|
|
32
32
|
- [Thinking intensity](#thinking-intensity)
|
|
33
33
|
- [Session stats and the context meter](#session-stats-and-the-context-meter)
|
|
34
|
+
- [The status rail](#the-status-rail)
|
|
34
35
|
- [Settings](#settings)
|
|
36
|
+
- [Language](#language)
|
|
37
|
+
- [MCP servers](#mcp-servers)
|
|
35
38
|
- [In-app updates](#in-app-updates)
|
|
36
39
|
- [How it works](#how-it-works)
|
|
37
40
|
- [@dsh-std interop](#dsh-std-interop)
|
|
@@ -47,14 +50,21 @@ Published on **npm** as [`dsh-oc-tui`](https://www.npmjs.com/package/dsh-oc-tui)
|
|
|
47
50
|
| --- | --- |
|
|
48
51
|
| **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. |
|
|
49
52
|
| **Live streaming** | Assistant text and reasoning stream token by token; thinking renders in its own collapsible box that stays collapsed while streaming. |
|
|
50
|
-
| **
|
|
53
|
+
| **Real image rendering** | Images in the transcript render as real pixels — kitty graphics, iTerm2 inline images, or sixel, probed from the terminal itself (works over SSH), with a truecolor half-block preview on terminals that support no protocol. Pasted images, stored attachments, and markdown images all flow through one pipeline. See [Graphics rendering](#graphics-rendering). |
|
|
54
|
+
| **Mermaid diagrams** | A ` ```mermaid ` fence renders as a real diagram through a provider chain — local `mmdc` if installed, otherwise mermaid.ink (gated by a setting) — into the same image pipeline; offline it degrades to highlighted source. |
|
|
55
|
+
| **Full markdown** | Tables in rounded boxes, nested and task lists, syntax-highlighted code (190+ languages), and clickable OSC 8 hyperlinks, rendered from markdown-it's CommonMark + GFM token stream. |
|
|
56
|
+
| **Flat transcript** | No frames around the conversation: your turns open with a brand-blue `❯` pointer and hang under it, the assistant answers as a full-width column of text under its `dsh · time` label, and every block measures itself in text — so wide runes never push a border out of alignment. The header is one row: brand and session title on the left, the workspace path with its git branch on the right; the model is not repeated there (the status row / rail names it). |
|
|
57
|
+
| **Tool activity** | One row per tool: a status mark, the action in the tool's own accent (`read` cyan, `run` gold, `edit` light blue, `todo` mint), and its target. Click the row to expand the full result under a `⎿` gutter. |
|
|
51
58
|
| **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). |
|
|
52
59
|
| **Inline approvals** | `approval/request` prompts are answered with `y` / `n` without leaving the UI; the box shows the action and the reason, with room for origin, risk and details when a `@dsh-std` request carries them. |
|
|
53
60
|
| **Session stats** | One stats strip above the composer — turns/steps, LLM and tool wall time, average TTFT, decode throughput, cache-hit rate, and billed input/output tokens — folded from durable events. See [Session stats and the context meter](#session-stats-and-the-context-meter). |
|
|
54
61
|
| **Stats window** | Click the strip or the context meter, or type `/stats`, for the full session-statistics and token-usage breakdown. |
|
|
55
62
|
| **Context meter** | Live context occupancy (`ctx ▓▓░░ 32K/128K 25%`), with the system/tools/messages composition in the same window. |
|
|
63
|
+
| **Status rail (wide windows)** | From 140 columns a right-hand rail carries the expanded status — model, thinking intensity, context, session figures — plus the session's current goal (`/goal`) and its subagent activity, scrollable with `Shift+↑/↓` or the wheel. Narrow windows keep the bottom status row. See [The status rail](#the-status-rail). |
|
|
56
64
|
| **Thinking intensity** | `Tab` cycles the current model's real reasoning levels; `Ctrl+E` opens a slider. The level is applied per request and persisted. |
|
|
57
65
|
| **Shared settings** | The same host settings namespaces the Web UI uses — general, sessions, per-provider model configuration, credentials — persisted to `$DSH_HOME/settings.yaml`. |
|
|
66
|
+
| **17 languages** | Every string the TUI draws — chrome, transcript, dialogs, toasts, command help — comes from a catalog, so the whole interface follows one setting: `Ctrl+P → Language`, or the environment when no preference is stored. English, Chinese (simplified and traditional), Japanese, Korean, German, French, Spanish, Portuguese (Brazil), Italian, Russian, Polish, Dutch, Turkish, Vietnamese, Indonesian and Arabic. See [Language](#language). |
|
|
67
|
+
| **MCP servers** | A `Ctrl+P → MCP` tab manages MCP servers from the TUI itself: add or edit `stdio` / `HTTP` / `SSE` servers, remove them, and watch each connection state with the tools it registered. Scope is per user or per project directory, and the bundled `mcp-import` skill lets the model add servers for you. MCP is optional — with no server, a failing server, or no MCP client in the host at all, the TUI runs exactly as before. See [MCP servers](#mcp-servers). |
|
|
58
68
|
| **In-app updates** | Detect and switch versions of `@deepseek-ai/dsh` and `dsh-oc-tui` from inside the TUI, with Windows-safe deferred installs. |
|
|
59
69
|
| **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. |
|
|
60
70
|
|
|
@@ -102,17 +112,24 @@ npm install -g dsh-oc-tui
|
|
|
102
112
|
|
|
103
113
|
### Version channels
|
|
104
114
|
|
|
105
|
-
|
|
115
|
+
npm carries two channels for this package: **`latest`** holds the stable releases, **`next`** the pre-releases (`0.2.0-pre.x`, `-rc`, `-alpha`, `-beta`). Untagged installs — `dsh plugin … add -w dsh-oc-tui` and `npm install -g dsh-oc-tui` above — therefore still give you the newest stable version, never a release candidate. The [awesome-dsh-plugin](https://awesome-dsh-plugin.com/zh/p/rayafriandion/dsh-oc-tui/) marketplace entry links to the same package, so an install started from it lands on the stable channel too.
|
|
106
116
|
|
|
107
|
-
|
|
117
|
+
To run a pre-release, name the tag: `dsh plugin` forwards the spec to pnpm untouched, and pnpm resolves dist-tags.
|
|
108
118
|
|
|
109
|
-
|
|
119
|
+
```sh
|
|
120
|
+
dsh plugin --profile tui add -w dsh-oc-tui@next # newest pre-release
|
|
121
|
+
npm install -g dsh-oc-tui@next
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Unreleased work in this repository carries no dist-tag at all, so install it from a packed tarball or a checkout (see [From a checkout or tarball](#from-a-checkout-or-tarball)):
|
|
110
125
|
|
|
111
126
|
```sh
|
|
112
127
|
npm pack # -> dsh-oc-tui-<version>.tgz
|
|
113
128
|
dsh plugin --profile tui add -w ./dsh-oc-tui-<version>.tgz
|
|
114
129
|
```
|
|
115
130
|
|
|
131
|
+
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.
|
|
132
|
+
|
|
116
133
|
### One-command installers
|
|
117
134
|
|
|
118
135
|
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.
|
|
@@ -212,13 +229,14 @@ It prefers the `dsh` on `PATH` and falls back to `npx --yes @deepseek-ai/dsh`. I
|
|
|
212
229
|
| `Ctrl+D` | In Settings → Manage sessions: delete the focused session (press twice to confirm). |
|
|
213
230
|
| `Ctrl+L` | Clear the transcript view. |
|
|
214
231
|
| `Up` / `Down` | Move the caret across a multi-line prompt; on the first/last row, step through input history. |
|
|
232
|
+
| `Shift+Up` / `Shift+Down` | Scroll the status rail (wide windows); without the rail they act as `Up` / `Down`. |
|
|
215
233
|
| `Left` / `Right` | Move the caret within the input box. |
|
|
216
234
|
| `PgUp` / `PgDn` | Scroll the transcript. |
|
|
217
235
|
| `Esc` | Close the session stats window, the thinking slider, or help; cancel an approval; cancel a running turn; clear the prompt you are typing. |
|
|
218
236
|
| `Esc Esc` | Idle with an empty prompt: open the rewind picker. |
|
|
219
237
|
| `y` / `n` | Answer an inline approval prompt. |
|
|
220
238
|
|
|
221
|
-
**Mouse.** The wheel scrolls the transcript
|
|
239
|
+
**Mouse.** The wheel scrolls the transcript — or the status rail when the pointer rests over it, 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.
|
|
222
240
|
|
|
223
241
|
### Slash commands
|
|
224
242
|
|
|
@@ -282,15 +300,88 @@ The whole strip is a click target. Clicking it — or the context meter at the r
|
|
|
282
300
|
|
|
283
301
|
Figures come from the same sources as the Web UI, projection-first with the plugin's own fold as the fallback: `tokenUsage`, `contextPressure`, and `contextBreakdown` are mounted by `dsh-base`'s token-meter row, while `sessionStats` is mounted only by the web app bundle — so the TUI folds the durable `step` / `chunk` / `message` / `tool` events by the same rules. A missing projection falls back for that figure alone, and a figure nobody can supply stays hidden instead of printing a zero.
|
|
284
302
|
|
|
303
|
+
### The status rail
|
|
304
|
+
|
|
305
|
+
From **140 columns** a right-hand rail takes over the status figures, OpenCode-style: the session title pins to the top, scrollable sections fill the middle — `MODEL` (model, provider), `THINKING` (current level and its real range), `CONTEXT` (occupancy bar, tokens, billing, composition), `SESSION` (the stats strip's figures, expanded), `GOAL`, `SUBAGENTS` — and the running builds (`dsh x.y.z · tui a.b.c`) pin to the bottom. Sections with no data drop out whole.
|
|
306
|
+
|
|
307
|
+
`GOAL` and `SUBAGENTS` read the harness's native surfaces: the `goal` projection registered by `dsh-goal` (objective, phase, rounds, blocker reason), and `ctx.subagents.listDescendants()` for the delegation tree rooted at the open session, refreshed on the scoped `subagent/start` / `subagent/end` edges. Both services are optional — a composition without them leaves those sections empty.
|
|
308
|
+
|
|
309
|
+
When the sections outgrow the window a scrollbar appears in the rail's right edge; `Shift+↑` / `Shift+↓` or the wheel over the rail scrolls it. Clicking the `CONTEXT` section opens the session stats window (the seat the bottom-row meter vacates). Below 140 columns the rail disappears and the pre-rail layout — model on the status row, the stats strip above the composer — returns unchanged.
|
|
310
|
+
|
|
285
311
|
### Settings
|
|
286
312
|
|
|
287
313
|
`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):
|
|
288
314
|
|
|
289
315
|
- **Main** — General (busy-Enter behaviour, permission preset), Sessions (new session, manage sessions), System (provider API hints, update-manager shortcut, settings file path).
|
|
290
316
|
- **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.
|
|
317
|
+
- **MCP** — see [MCP servers](#mcp-servers).
|
|
291
318
|
- **Update** — see [In-app updates](#in-app-updates).
|
|
292
319
|
|
|
293
|
-
Only providers you actually added (present in your user settings layer) are listed; a provider that was never added stays hidden. **Default preset** is shown only when the profile mounts an agent-preset roster, because the choice list is that roster — and this plugin deliberately mounts none (see the compatibility note above), so on a stock `tui` profile the row is absent rather than empty. Where a roster *is* mounted, 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.
|
|
320
|
+
Only providers you actually added (present in your user settings layer) are listed; a provider that was never added stays hidden. **Default preset** is shown only when the profile mounts an agent-preset roster, because the choice list is that roster — and this plugin deliberately mounts none (see the compatibility note above), so on a stock `tui` profile the row is absent rather than empty. Where a roster *is* mounted, 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, the Web UI's `locale`) are not shown because they have no effect in the TUI; the TUI's own **Language** row is a separate namespace (`tui-locale`) — see [Language](#language).
|
|
321
|
+
|
|
322
|
+
### Language
|
|
323
|
+
|
|
324
|
+
Every user-facing string the TUI draws is looked up in a catalog at paint time: the title screen, header, sidebar, transcript labels, tool rows, composer, status row and rail, settings chrome and values, help overlay, rewind picker, stats window, question and approval boxes, toasts, and the `/help` command descriptions. The language is one setting — **`Ctrl+P` → Main → Language** — with 17 choices plus `System`, labelled in their own script (`简体中文`, `日本語`, `Português (Brasil)`, `العربية`, …). Choosing one repaints immediately; nothing restarts. The choice is stored wherever the running dsh keeps settings — the profile's `cordis.patch.yml` as the `tui-app` row's `locale` field on dsh 0.2.0, or `$DSH_HOME/settings.yaml` as `tui-locale.preference` on 0.1.x — and `System` stores nothing at all, so a later change of OS language is followed.
|
|
325
|
+
|
|
326
|
+
With `System` selected (the default) the boot resolves the language in this order, first hit wins:
|
|
327
|
+
|
|
328
|
+
1. `DSH_TUI_LOCALE`
|
|
329
|
+
2. `LANGUAGE`
|
|
330
|
+
3. `LC_ALL` / `LC_MESSAGES` / `LANG`
|
|
331
|
+
4. the OS locale (on Windows, the user's display language)
|
|
332
|
+
5. `en`
|
|
333
|
+
|
|
334
|
+
Values may be POSIX style (`de_DE.UTF-8`), BCP 47 (`pt-BR`), or a bare language (`zh`, `fr`); matching is case-insensitive, and an alias resolves to the nearest catalog it actually has (`zh`/`zh-Hans` → `zh-CN`, `zh-Hant`/`zh-HK` → `zh-TW`, `pt`/`pt-PT` → `pt-BR`, `in` → `id`). A language with no catalog — Norwegian, say — falls back to English rather than to a neighbour it does not resemble. `DSH_TUI_LOCALE=en` also pins the `--help` text, which is printed before the settings file is read.
|
|
335
|
+
|
|
336
|
+
English is the source catalog: it defines the key set, every other locale must cover all of it (a test enforces parity in both directions), and any key a locale is missing falls back to English rather than showing a raw key. Plural families go through `Intl.PluralRules`, so Russian and Arabic get their full CLDR forms while English and Chinese use the two or one they have. Product and protocol tokens (`DeepSeek Harness`, `dsh`, `MCP`, `stdio`, `Ctrl+P`, `Tab`, `Esc`, `Enter`, `git`, `npm`, …) stay literal in every locale.
|
|
337
|
+
|
|
338
|
+
Adding a language is one file: `lib/locales/<id>.js` exporting a flat `{ 'area.key': 'text' }` object, plus an entry in `LOCALES` in `lib/i18n.js`. `npm test` then checks its key coverage, placeholder parity, and plural forms against English.
|
|
339
|
+
|
|
340
|
+
### MCP servers
|
|
341
|
+
|
|
342
|
+
`Ctrl+P → MCP` is the TUI's own MCP page. Servers are mounted on the **current session's agent** through the host's MCP client, so the page shows live connection state once a session is open (on the title screen the configuration is still listed, just not connected).
|
|
343
|
+
|
|
344
|
+
`Enter` on a server edits it, `Add MCP server` starts a new one, and `Delete` (or `Backspace`) on a server row asks y/n before removing the configuration and disconnecting. The form holds:
|
|
345
|
+
|
|
346
|
+
| Field | Meaning |
|
|
347
|
+
| --- | --- |
|
|
348
|
+
| Name | The tool namespace: 1–32 letters, digits, `-` or `_`. Fixed while editing, because renaming a server is removing one and adding another. |
|
|
349
|
+
| Type | `Local command` (a child process over stdio), `HTTP` (Streamable HTTP), or `SSE` (the legacy HTTP+SSE transport). |
|
|
350
|
+
| Timeout (ms) | Per tool call or resource request; defaults to `30000`. |
|
|
351
|
+
| Command / Arguments / Environment (JSON) | For a local command. Arguments split on whitespace and group with double quotes, or you can paste a JSON array. The environment is a JSON object whose values may be strings, numbers, or booleans. |
|
|
352
|
+
| URL / Headers (JSON) | For `HTTP` and `SSE`. |
|
|
353
|
+
| Scope | `User` (`$DSH_HOME/mcp.json`, every session) or `Path` (a `<path>/.dsh/mcp.json`, sessions whose working directory is inside `<path>`), followed by the `Path` row itself. |
|
|
354
|
+
| Enabled | `off` keeps the configuration but does not connect. |
|
|
355
|
+
|
|
356
|
+
`Save` writes the record and mounts it right away; a local command inherits the session's working directory.
|
|
357
|
+
|
|
358
|
+
State rows read `● connected` (no readable tool list), `● N tools`, `… connecting`, `⚠ <reason>`, `○ disabled`, or `○ not connected`, and the tools one server registered are listed indented underneath it. Below the list, the `Config files` rows name every file that was read and whether it exists — the answer to "why is this server here".
|
|
359
|
+
|
|
360
|
+
**Scope is a property of the file, not a field of the record.** The effective list for a session is the user file plus every `<dir>/.dsh/mcp.json` between the session's working directory and the filesystem root; a nearer file wins a duplicate name. Editing a server and switching its scope moves the record to the other file. Servers are plain JSON:
|
|
361
|
+
|
|
362
|
+
```json
|
|
363
|
+
{
|
|
364
|
+
"version": 1,
|
|
365
|
+
"servers": {
|
|
366
|
+
"github": {
|
|
367
|
+
"type": "stdio",
|
|
368
|
+
"command": "npx",
|
|
369
|
+
"args": ["-y", "@modelcontextprotocol/server-github"],
|
|
370
|
+
"env": { "GITHUB_TOKEN": "…" },
|
|
371
|
+
"timeoutMs": 30000,
|
|
372
|
+
"enabled": true
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
The TUI re-reads those files while the MCP page is open and remounts what changed, so a server edited in another window — or imported by the model through the bundled **`mcp-import` skill** — appears and connects without restarting the session. The skill ships with the plugin and teaches the model how to add, fix, disable, or remove a server for you.
|
|
379
|
+
|
|
380
|
+
Environment values and headers are **plain text** in those files, and the page says so on its `Secrets` row: there is no credential vault here and no OAuth.
|
|
381
|
+
|
|
382
|
+
**MCP is optional by design.** No server configured, a server that refuses to connect, a host whose MCP client is missing, or a config file that will not parse all cost exactly one `⚠` row — sessions, input, and every other feature keep working.
|
|
383
|
+
|
|
384
|
+
**SSE.** The host's MCP client supports stdio and Streamable HTTP only, so the `SSE` type runs `bin/mcp-sse-bridge.mjs` — a zero-dependency stdio↔legacy-SSE proxy shipped with the plugin — as the child process. The host's tool bridge, reconnect policy, and per-call timeout then apply exactly as they do to a local command.
|
|
294
385
|
|
|
295
386
|
### In-app updates
|
|
296
387
|
|
|
@@ -329,6 +420,24 @@ The applier writes the outcome to `$DSH_HOME/tui-dsh-install.json`, which the Up
|
|
|
329
420
|
- 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.
|
|
330
421
|
- `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.
|
|
331
422
|
|
|
423
|
+
## Graphics rendering
|
|
424
|
+
|
|
425
|
+
Images and mermaid diagrams render as real pixels. On boot the TUI probes the terminal itself — DA1 attributes, an XTVERSION name query, a kitty graphics query, and a cell-size query — and every probe is an escape sequence the *client* terminal answers, so detection works unchanged over SSH where environment variables do not propagate.
|
|
426
|
+
|
|
427
|
+
| Terminal | Protocol used |
|
|
428
|
+
| --- | --- |
|
|
429
|
+
| kitty, Ghostty | kitty graphics (transmitted once per image, moved by reference — free on scroll) |
|
|
430
|
+
| WezTerm | kitty graphics, or iTerm2 inline images |
|
|
431
|
+
| iTerm2, Konsole, mintty | iTerm2 inline images / sixel |
|
|
432
|
+
| Windows Terminal ≥ 1.22 | sixel |
|
|
433
|
+
| GNOME Terminal, older xterm, anything else | truecolor half-block preview (▀, every font has the glyphs) |
|
|
434
|
+
|
|
435
|
+
- **Sources.** A pasted image (bytes are already in hand), a stored attachment on resume/replay (read back through the attachment store), a `data:` URL, and a standalone markdown image (``), plus tool-result images such as screenshots.
|
|
436
|
+
- **Budgets.** Images scale to fit the transcript width and a row cap (Settings → Graphics → Image height cap, default 20 rows); GIFs render their first frame.
|
|
437
|
+
- **Mermaid.** A ` ```mermaid ` fence draws as **native box-drawing art** whenever the grammar is one the built-in engine knows (flowchart, sequence, class, state, ER, pie, mindmap, timeline, gitGraph) — text in the transcript, sharp at any font size, selectable and searchable, with emoji and CJK labels measured the way the terminal draws them. Only a grammar the engine does not draw falls back to the provider chain: local `mmdc` (mermaid-cli) if it is on `PATH`, otherwise the mermaid.ink service (10 s timeout, disk-cached) — which sends the diagram text to a third party, hence the setting. A diagram wider than the pane reports the width it needs and shows its source instead of a raster. `Settings → Graphics → Mermaid rendering` offers `auto` / `local` / `off`; `off` keeps every diagram as its highlighted source, and every failure degrades to source, never a broken frame.
|
|
438
|
+
- **Settings.** `Graphics rendering` (`auto`/`off`), `No-protocol fallback` (`halfblock`/`chip`), `Image height cap (rows)`, `Mermaid rendering` (`auto`/`local`/`off`). Changes apply the moment they are saved.
|
|
439
|
+
- **Dependencies.** Rendering is pure npm: `sharp` decodes and scales (prebuilt binaries on all three platforms), [`lovely-mermaid`](https://www.npmjs.com/package/lovely-mermaid) lays out mermaid diagrams as terminal text, and the kitty/iTerm2/sixel encoders are part of the plugin. No external binaries are required; `mmdc` is optional.
|
|
440
|
+
|
|
332
441
|
## @dsh-std interop
|
|
333
442
|
|
|
334
443
|
The package carries a [`dsh-plugin.json`](dsh-plugin.json) manifest and a facet at `lib/facet.js`, so a `@dsh-std` host can discover it without running any code. **What ships today is that static manifest and the preflight surface; the runtime protocol support is currently gated off by an upstream limitation.** The plugin is designed as a **host and Presentation provider** in that ecosystem, not a protocol consumer — its facet is wired to publish `presentation.dsh/v1alpha1` `UserInteraction` / `Notification` / `CopyText` and `commands.dsh/v1alpha1` `CommandRuntime` for other components to drive. Its one `requires.contracts` entry is the `commands.dsh/v1alpha1` `Command` resource, declared **`optional: true`**. The entry is retained for a future consumer and is inert today — nothing calls `protocols.client(...)`, and its own command line is parsed from the cordis `ctx.commands` service, so the TUI behaves identically whether or not a Command provider exists. An unsatisfiable non-optional requirement would be a hard activation failure rather than a warning, and the `optional` flag is what keeps the declaration honest about that. But a Community v0.15 manifest **cannot declare protocol supports**, and `@dsh-std/lifecycle`'s `implement` — which *is* `stageProtocol` — refuses to stage a support the facet has not declared, so the facet stages **nothing**. The protocol shims are written and test-covered; they go live unchanged once upstream closes the gap. See [docs/dsh-std-接入说明.md](docs/dsh-std-接入说明.md#阻断性发现本插件当前不发布任何协议-support) for the layer-by-layer evidence.
|
|
@@ -339,6 +448,9 @@ The package carries a [`dsh-plugin.json`](dsh-plugin.json) manifest and a facet
|
|
|
339
448
|
|
|
340
449
|
## Development
|
|
341
450
|
|
|
451
|
+
AI agents working in this checkout should read [AGENTS.md](AGENTS.md) first — it
|
|
452
|
+
records the build/install loop as a rule rather than a suggestion.
|
|
453
|
+
|
|
342
454
|
```sh
|
|
343
455
|
npm run check # node --check over lib/, bin/
|
|
344
456
|
npm test # standalone smoke tests (no dsh needed)
|
|
@@ -350,11 +462,17 @@ npm test # standalone smoke tests (no dsh needed)
|
|
|
350
462
|
npm pack # -> dsh-oc-tui-<version>.tgz
|
|
351
463
|
dsh plugin --profile tui remove -w dsh-oc-tui # detach the old copy FIRST
|
|
352
464
|
Remove-Item .\*.tgz # then drop the stale tarball
|
|
353
|
-
npm pack
|
|
465
|
+
npm pack # repack the current tree
|
|
466
|
+
Copy-Item .\dsh-oc-tui-<version>.tgz $env:USERPROFILE\.dsh\profiles\tui\
|
|
354
467
|
dsh plugin --profile tui add -w .\dsh-oc-tui-<version>.tgz
|
|
468
|
+
Remove-Item $env:USERPROFILE\.dsh\profiles\tui\dsh-oc-tui-<version>.tgz
|
|
355
469
|
```
|
|
356
470
|
|
|
357
|
-
|
|
471
|
+
Three things that will bite you:
|
|
472
|
+
|
|
473
|
+
- **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`.
|
|
474
|
+
- **The tarball has to be reachable from the profile directory while you add it.** dsh hands package specs to pnpm with `cwd` pinned to the profile directory, and pnpm 9.15.9 then looks for the tarball *by name inside the profile* rather than at the absolute path it was given: the `add` fails with `ENOENT: no such file or directory, open '…\.dsh\profiles\tui\dsh-oc-tui-<version>.tgz'`. Copy it in first; dsh writes the absolute path into the profile manifest, so the copy comes straight back out afterwards.
|
|
475
|
+
- **Remove before adding, even at the same version.** dsh appends a bundle to `dsh.profile.bundles` only for dependencies that were *not* already installed, so adding over an existing copy installs the files and silently skips the bundle row — the profile then boots dsh-base with no TUI.
|
|
358
476
|
|
|
359
477
|
Verify the swap actually landed — the version string proves nothing:
|
|
360
478
|
|
|
@@ -368,6 +486,12 @@ foreach ($rel in @('lib\index.js','lib\ui.js','lib\util.js','lib\term.js','lib\m
|
|
|
368
486
|
}
|
|
369
487
|
```
|
|
370
488
|
|
|
489
|
+
The reverse failure is just as silent and worse: current files with no `dsh-oc-tui` row in `dsh.profile.bundles` means dsh-base boots with no TUI. Confirm the layer composes:
|
|
490
|
+
|
|
491
|
+
```sh
|
|
492
|
+
dsh --profile tui --dump-config | grep -A2 tui-app
|
|
493
|
+
```
|
|
494
|
+
|
|
371
495
|
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.
|
|
372
496
|
|
|
373
497
|
For a zero-install bootstrap that skips packaging entirely, create the profile once and point its patch at this checkout:
|
|
@@ -408,27 +532,34 @@ More detail, in Chinese: [docs/用户手册.md](docs/用户手册.md).
|
|
|
408
532
|
|
|
409
533
|
## Known limitations
|
|
410
534
|
|
|
411
|
-
- IME composition is not exposed by the zero-dependency terminal engine yet. Pasted images are: a bracketed paste of raw image bytes, a `data:image/...;base64,...` URL, a local image path, or an image URL
|
|
535
|
+
- IME composition is not exposed by the zero-dependency terminal engine yet. Pasted images are: a bracketed paste of raw image bytes, a `data:image/...;base64,...` URL, a local image path, or an image URL — each becomes an attachment and renders in the transcript (see [Graphics rendering](#graphics-rendering)); pasting text nothing recognizes asks the terminal for its clipboard (OSC 52).
|
|
412
536
|
- 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.
|
|
413
537
|
- `dsh tui` as a bare subcommand needs a shell alias — the stock launcher hardcodes only `web` and `plugin`.
|
|
414
|
-
- Harness slash commands need a live session; on the title screen the TUI tells you to start
|
|
538
|
+
- Harness slash commands need a live session; on the title screen the TUI tells you to start a session first.
|
|
415
539
|
- 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.
|
|
416
540
|
- `--resume`, Settings → Manage sessions, the context meter, and the stats strip depend on services mounted by `@deepseek-ai/dsh-base` (`sessionQuery`, `sessionProjections`); a hand-built profile must provide them. The `sessionStats` projection is a web-app-layer row, so the TUI folds those figures from the session log itself when no profile mounts it.
|
|
417
541
|
- An update restart only waits for the TUI that scheduled it: the "no other dsh process" check runs *before* you confirm, so a `dsh web` window opened afterwards can still overlap the install. Close other dsh windows first.
|
|
418
542
|
- Two `@dsh-std` presentation surfaces have no caller today, because the runtime protocol support is gated off (see [@dsh-std interop](#dsh-std-interop)). A standard `secret-input` request would open a masked prompt with `minLength`/`maxLength` bounds, and a standard `ApprovalRequest` is the only thing that populates the approval box's `Origin` / `Risk` / `Details` rows. Both paths are written and integration-tested; neither can be reached from a normal `dsh --profile tui` session, which is why the harness-shaped approval box shows only the action and the summary.
|
|
543
|
+
- Graphics: an image whose top rows are scrolled out of the transcript window shows as blank placeholder rows until it fits fully again (kitty placements cannot anchor off-screen); GIFs animate their first frame only; sixel/iTerm2 payloads are re-emitted when an image's row moves, which is fine locally and throttled on slow SSH links.
|
|
419
544
|
|
|
420
545
|
## Layout
|
|
421
546
|
|
|
422
547
|
```
|
|
423
548
|
lib/index.js plugin entry: agents, events, input, commands, approvals, user questions
|
|
424
549
|
lib/startup.js command-line provider (tuiStartup service)
|
|
425
|
-
lib/term.js terminal engine (raw mode, screen, key decoding)
|
|
426
|
-
lib/
|
|
550
|
+
lib/term.js terminal engine (raw mode, screen, key decoding, image slab emission)
|
|
551
|
+
lib/caps.js terminal capability probing (DA1, XTVERSION, kitty graphics, cell size)
|
|
552
|
+
lib/image.js image pipeline (decode/scale + halfblock, sixel, kitty, iTerm2 encoders)
|
|
553
|
+
lib/mermaid.js mermaid provider chain (mmdc → mermaid.ink → highlighted fallback), tried only for grammars the built-in engine cannot draw
|
|
554
|
+
lib/mermaid-ascii.js native terminal art for mermaid fences (box-drawing layout, tried before the image path)
|
|
555
|
+
lib/ui.js responsive view model + renderer (flat transcript, tool rows, question modal)
|
|
556
|
+
lib/splash-opencode.js title-screen art (OpenCode-style layout, DeepSeek gradient mark)
|
|
427
557
|
lib/metrics.js whole-session stats + token usage fold (web stats strip / tokenUsage port)
|
|
558
|
+
lib/rewind.js rewind picker: conversation fork from the event log plus fenced file restore
|
|
428
559
|
lib/interrupt.js Ctrl+C lifecycle state
|
|
429
|
-
lib/web-settings.js shared WebUI settings projection
|
|
560
|
+
lib/web-settings.js shared WebUI settings projection (incl. the tui-graphics namespace)
|
|
430
561
|
lib/updates.js in-app update manager (npm registry, download + staging, install verification)
|
|
431
|
-
lib/markdown.js markdown -> styled lines
|
|
562
|
+
lib/markdown.js markdown-it token stream -> styled lines (tables, hljs, links)
|
|
432
563
|
lib/util.js text/display helpers
|
|
433
564
|
lib/bridge.js live-TUI registry the @dsh-std facet forwards through
|
|
434
565
|
lib/facet.js @dsh-std facet entry (interop shell; never starts the TUI)
|
|
@@ -447,3 +578,5 @@ tests/smoke.test.mjs standalone smoke tests
|
|
|
447
578
|
## License
|
|
448
579
|
|
|
449
580
|
[LGPL-3.0-or-later](LICENSE).
|
|
581
|
+
|
|
582
|
+
**Third-party.** [`lovely-mermaid`](https://www.npmjs.com/package/lovely-mermaid) 0.3.3 (Apache-2.0, © Alexey Zaytsev / ccch1mneyyy) lays out mermaid diagrams as terminal text; its license ships in its package, and its source is at [github.com/ccch1mneyyy/lovely-mermaid](https://github.com/ccch1mneyyy/lovely-mermaid).
|