dsh-ssh-tui 0.7.3 → 0.7.4
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.en.md +98 -27
- package/README.md +77 -27
- package/docs/remote-ops.md +27 -1
- package/docs/terminals.md +17 -4
- package/docs/windows.md +128 -0
- package/lib/attach.js +14 -1
- package/lib/attach.js.map +1 -1
- package/lib/commands.js +1 -0
- package/lib/commands.js.map +1 -1
- package/lib/copy-text.js +2 -0
- package/lib/copy-text.js.map +1 -1
- package/lib/diag.js +3 -0
- package/lib/diag.js.map +1 -1
- package/lib/dialogs.js.map +1 -1
- package/lib/display-sock.js +313 -2
- package/lib/display-sock.js.map +1 -1
- package/lib/dsh-compat.js +46 -0
- package/lib/dsh-compat.js.map +1 -1
- package/lib/i18n/en.js +40 -14
- package/lib/i18n/en.js.map +1 -1
- package/lib/i18n/index.js +5 -0
- package/lib/i18n/index.js.map +1 -1
- package/lib/i18n/zh.js +40 -14
- package/lib/i18n/zh.js.map +1 -1
- package/lib/index.js +75 -15
- package/lib/index.js.map +1 -1
- package/lib/line-mode.js +4 -0
- package/lib/line-mode.js.map +1 -1
- package/lib/paint.js +32 -1
- package/lib/paint.js.map +1 -1
- package/lib/picker.js +104 -15
- package/lib/picker.js.map +1 -1
- package/lib/plan.js +8 -0
- package/lib/plan.js.map +1 -1
- package/lib/platform.js +81 -0
- package/lib/platform.js.map +1 -1
- package/lib/preset-rows.js +54 -24
- package/lib/preset-rows.js.map +1 -1
- package/lib/question-wait.js +418 -0
- package/lib/question-wait.js.map +1 -0
- package/lib/selection.js +26 -8
- package/lib/selection.js.map +1 -1
- package/lib/term-text.js +79 -8
- package/lib/term-text.js.map +1 -1
- package/lib/terminal-input.js +13 -0
- package/lib/terminal-input.js.map +1 -1
- package/lib/tui.js +597 -51
- package/lib/tui.js.map +1 -1
- package/lib/types/attach.d.ts +8 -0
- package/lib/types/commands.d.ts +3 -0
- package/lib/types/diag.d.ts +2 -0
- package/lib/types/dialogs.d.ts +16 -0
- package/lib/types/display-sock.d.ts +78 -2
- package/lib/types/dsh-compat.d.ts +44 -12
- package/lib/types/i18n/index.d.ts +13 -3
- package/lib/types/paint.d.ts +21 -0
- package/lib/types/picker.d.ts +22 -0
- package/lib/types/platform.d.ts +22 -0
- package/lib/types/preset-rows.d.ts +18 -10
- package/lib/types/question-wait.d.ts +221 -0
- package/lib/types/selection.d.ts +12 -4
- package/lib/types/settings-subagent.d.ts +3 -3
- package/lib/types/subagent-model.d.ts +3 -3
- package/lib/types/term-text.d.ts +14 -0
- package/lib/types/terminal-input.d.ts +10 -0
- package/lib/types/transcript-types.d.ts +22 -0
- package/lib/types/tui.d.ts +126 -3
- package/lib/types/update-check.d.ts +19 -4
- package/lib/types/workspace-changes.d.ts +135 -0
- package/lib/update-check.js +25 -8
- package/lib/update-check.js.map +1 -1
- package/lib/workspace-changes.js +120 -0
- package/lib/workspace-changes.js.map +1 -0
- package/package.json +80 -42
package/README.en.md
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
[](https://dshfind.com/en/plugins/cyjyyd/dsh-ssh-tui?ref=badge)
|
|
7
7
|
|
|
8
8
|
A DeepSeek Harness terminal for jump hosts, headless servers, and high-latency
|
|
9
|
-
SSH. Plain ANSI and incremental redraws. No browser required.
|
|
9
|
+
SSH. Plain ANSI and incremental redraws. No browser required. **When the SSH
|
|
10
|
+
session drops mid-turn, the Host keeps the turn; reconnecting with the same
|
|
11
|
+
command attaches back to it, so the session is not lost.**
|
|
10
12
|
|
|
11
13
|
中文部署指南:[README.md](README.md)
|
|
12
14
|
|
|
@@ -14,6 +16,20 @@ If you mostly work over SSH — a jump host, a test box, a keyboard-only
|
|
|
14
16
|
session — start here. A local desktop terminal with themes and layout
|
|
15
17
|
you already like can stay as it is.
|
|
16
18
|
|
|
19
|
+
How this differs from what already exists:
|
|
20
|
+
|
|
21
|
+
- **Official headless**: the same task prints only its last reply to stdout; the
|
|
22
|
+
rest stays in the session log.
|
|
23
|
+
- **Official `dsh-ssh` (from 0.1.6)**: the Harness runs locally and files and
|
|
24
|
+
processes run remotely, which needs a helper installed ahead of time. Use this
|
|
25
|
+
plugin when you are already SSH'd onto the machine. The two stack; neither
|
|
26
|
+
replaces the other.
|
|
27
|
+
- **Other terminal skins** (`dsh-TUI` and the like): a good-looking local
|
|
28
|
+
terminal, competing on themes and layout. This plugin competes on incremental
|
|
29
|
+
redraws over a slow link, a Host that survives the drop, and a plain line mode.
|
|
30
|
+
|
|
31
|
+
How to get back in after a drop is under [After an SSH drop](#after-an-ssh-drop).
|
|
32
|
+
|
|
17
33
|
A SuperGrok / X Premium subscription goes through the standalone plugin
|
|
18
34
|
[dsh-llm-xai-oauth](https://github.com/cyjyyd/dsh-llm-xai-oauth) (headless, web, or this TUI). It reuses a local grok-bridge token; no xAI API key.
|
|
19
35
|
|
|
@@ -69,8 +85,12 @@ Reproducible, no model in the loop: `npm run screenshots:slow` writes
|
|
|
69
85
|
`docs/screenshots/slow-link.json`. This capture is 14 paints, about
|
|
70
86
|
**18.0 KB**, **8.8 s** at 2 kB/s. Byte ledger for this event sequence.
|
|
71
87
|
|
|
72
|
-
0.7 highlights: drag-select any part of a model reply to copy it (
|
|
73
|
-
clipboard; a tool card still expands on click
|
|
88
|
+
0.7 highlights: drag-select any part of a model reply to copy it (hold the button, or Shift;
|
|
89
|
+
OSC 52 into your local clipboard; a tool card still expands on click; an SSH session copies
|
|
90
|
+
into the local terminal and no longer warns that it cannot) · a reply is a selectable card too:
|
|
91
|
+
one `↑` on an empty input lands on the newest reply-or-card (marked `▶`, in screen order;
|
|
92
|
+
`Alt+4` selects the latest reply), `/copy` then takes that one as written rather than the
|
|
93
|
+
wrapped screen text, and `Enter` opens it full-screen · the footer is one priority-ordered chip
|
|
74
94
|
strip, and `⚠` opens `/doctor` · the quota bar is on screen from the first frame and names
|
|
75
95
|
its window (`5Hr`/`1Wk`/`1Mo`, smallest window by default, `?%` with a 15-second retry until
|
|
76
96
|
a reading arrives) · `/mode` groups presets and filters with `/` · the compact view names
|
|
@@ -82,8 +102,10 @@ pins the palette (truecolor / 256 / 8 / none).
|
|
|
82
102
|
## Requirements
|
|
83
103
|
|
|
84
104
|
- Node.js >= 22.19
|
|
85
|
-
- `@deepseek-ai/dsh` CLI: `npm i -g @deepseek-ai/dsh` (CI covers `0.1.5-rc.1` / `0.1.5-rc.3` / `0.1.7-rc.1`: `0.1.5-rc.1` runs typecheck and the unit suite, the other
|
|
105
|
+
- `@deepseek-ai/dsh` CLI: `npm i -g @deepseek-ai/dsh` (this repo develops on `0.1.7-rc.1`; CI covers `0.1.5-rc.1` / `0.1.5-rc.3` / `0.1.7-rc.1` / `0.1.7-rc.2`: `0.1.5-rc.1` runs typecheck and the unit suite, the other three add the real-PTY probes, and every non-default leg rewrites the manifest with `scripts/ci-pin-line.mjs` before installing from scratch. `0.1.5-alpha.1` / `0.1.5-alpha.2` / `0.1.3-alpha.2` share the same handle API + `agent/assistant-stream` shims as the 0.1.5-rc line. **`0.1.2-rc` and older are no longer supported** — if the install is refused, upgrade to `0.1.5-rc` or `0.1.7-rc` first. `0.1.3-alpha.1` exists only as a GitHub tag and was never published to npm)
|
|
86
106
|
- pnpm (used by `dsh plugin` to manage profile dependencies)
|
|
107
|
+
- an ANSI terminal (SSH directly, or PowerShell / Windows Terminal on Windows)
|
|
108
|
+
- **Windows**: install, troubleshooting and how to read `/doctor` are in [docs/windows.md](docs/windows.md)
|
|
87
109
|
|
|
88
110
|
## Install
|
|
89
111
|
|
|
@@ -114,7 +136,7 @@ dsh --profile headless "Reply with exactly: tui-install-ok. Do not use tools."
|
|
|
114
136
|
dsh --profile tui
|
|
115
137
|
```
|
|
116
138
|
|
|
117
|
-
From a checkout: `bash scripts/smoke-headless.sh
|
|
139
|
+
From a checkout: `node scripts/smoke-headless.mjs` (or `bash scripts/smoke-headless.sh`; prints an outcome summary, never a token).
|
|
118
140
|
|
|
119
141
|
### After an SSH drop
|
|
120
142
|
|
|
@@ -130,6 +152,22 @@ dsh --profile tui --resume # picker (live hosts first)
|
|
|
130
152
|
dsh --profile tui --resume <session-id> # attach if live, else resume the log
|
|
131
153
|
```
|
|
132
154
|
|
|
155
|
+
The picker gives every live session one of two words and the Host's pid:
|
|
156
|
+
**attachable** (one keystroke in) or **occupied** (another window has it). When it
|
|
157
|
+
says occupied, `--resume <session-id>` is refused rather than kicking that window:
|
|
158
|
+
take it over from the **picker** instead — select the row and confirm with `y` /
|
|
159
|
+
Enter, and the other window exits.
|
|
160
|
+
|
|
161
|
+
**A cut link does not block you.** When SSH dies the server usually does not
|
|
162
|
+
know yet: sshd waits for TCP keepalive (which can be hours), the launcher stays
|
|
163
|
+
connected to the display channel, and the lock still reads `attached`. So the
|
|
164
|
+
Host asks the **terminal itself** (a cursor round trip through the relay — see
|
|
165
|
+
`scripts/tui-cut-probe.mjs`). A terminal that cannot answer means that window is
|
|
166
|
+
gone, the picker says **attachable**, one keystroke attaches, and so does
|
|
167
|
+
`--resume <session-id>`. Only a terminal that confirms it is still there needs
|
|
168
|
+
the takeover question above — and that answer is resolved *before* the list is
|
|
169
|
+
painted, so a row never says occupied and then changes its mind.
|
|
170
|
+
|
|
133
171
|
A second Host on the same `sessionId` is refused (it would steal the jsonl
|
|
134
172
|
and approvals). Locks live under `$DSH_HOME/tui-locks/`; the display socket
|
|
135
173
|
under `$DSH_HOME/tui-socks/`. A leftover lock from a crash is stolen if the
|
|
@@ -155,8 +193,11 @@ New sessions inherit the directory you launched from. Resuming a session
|
|
|
155
193
|
`目录:srv` (last path segment); click it to print the full path.
|
|
156
194
|
|
|
157
195
|
On start, if npm has a newer `dsh-ssh-tui`, a first-launch picker offers
|
|
158
|
-
**Update now / Later / Skip this version**. Update now
|
|
159
|
-
`dsh plugin --profile tui add dsh-ssh-tui
|
|
196
|
+
**Update now / Later / Skip this version**. Update now installs the exact
|
|
197
|
+
version just looked up (`dsh plugin --profile tui add dsh-ssh-tui@<version>`),
|
|
198
|
+
not `@latest`: pnpm resolves `@latest` a second time, and a release still inside
|
|
199
|
+
its `minimumReleaseAge` window is then skipped silently — the install succeeds
|
|
200
|
+
and you are left on the old one. It asks you to restart afterwards.
|
|
160
201
|
Set `DSH_TUI_NO_UPDATE_CHECK=1` to skip. `/status` also shows the plugin version, the link chip, the quota window, and whether the subagent model is in the same family as the parent route.
|
|
161
202
|
|
|
162
203
|
From git:
|
|
@@ -164,9 +205,12 @@ From git:
|
|
|
164
205
|
```sh
|
|
165
206
|
git clone https://github.com/cyjyyd/dsh-ssh-tui.git
|
|
166
207
|
cd dsh-ssh-tui
|
|
167
|
-
|
|
208
|
+
node scripts/install.mjs # installs into the `tui` profile
|
|
168
209
|
```
|
|
169
210
|
|
|
211
|
+
`bash scripts/install.sh` is a thin wrapper around that script, so it does the
|
|
212
|
+
same thing where bash exists. On Windows run the Node form — there is no bash.
|
|
213
|
+
|
|
170
214
|
Or manually:
|
|
171
215
|
|
|
172
216
|
```sh
|
|
@@ -175,6 +219,14 @@ npm run build
|
|
|
175
219
|
dsh plugin --profile tui add "link:$(pwd)"
|
|
176
220
|
```
|
|
177
221
|
|
|
222
|
+
The PowerShell equivalent, since `$(pwd)` is a POSIX substitution:
|
|
223
|
+
|
|
224
|
+
```powershell
|
|
225
|
+
npm install --no-audit --no-fund
|
|
226
|
+
npm run build
|
|
227
|
+
dsh plugin --profile tui add "link:$((Get-Location).Path)"
|
|
228
|
+
```
|
|
229
|
+
|
|
178
230
|
### The preset roster `/mode` needs
|
|
179
231
|
|
|
180
232
|
A terminal profile built on `dsh-base` composes no preset roster (only the Web
|
|
@@ -191,8 +243,8 @@ Three ways to repair it (idempotent, pick one):
|
|
|
191
243
|
1. **In-app**: `/mode fix` writes the block below and tells you to restart.
|
|
192
244
|
This is the generic path — an npm install and the in-app "Update now" both go
|
|
193
245
|
through `dsh plugin add` and never run the repository scripts.
|
|
194
|
-
2. From a checkout: `
|
|
195
|
-
(default `tui`).
|
|
246
|
+
2. From a checkout: `node scripts/profile-rows.mjs [profile]`
|
|
247
|
+
(default `tui`; `bash scripts/ensure-profile-rows.sh` is the same command).
|
|
196
248
|
3. By hand, in `$DSH_HOME/profiles/<profile>/cordis.patch.yml`:
|
|
197
249
|
|
|
198
250
|
```yaml
|
|
@@ -213,7 +265,7 @@ this block lists the roster row twice, and the second mount fails with
|
|
|
213
265
|
|
|
214
266
|
Without the row, the TUI prints a boot line ("No agent-preset roster is
|
|
215
267
|
composed…"), `/mode` reports the patch path plus the `/mode fix` entry point,
|
|
216
|
-
and `scripts/verify.sh` says the same. Preset identity is per module instance,
|
|
268
|
+
and `node scripts/verify.mjs` (`scripts/verify.sh`) says the same. Preset identity is per module instance,
|
|
217
269
|
so a dsh install tree carrying two copies of `@deepseek-ai/dsh-scope` (an
|
|
218
270
|
npm-nested checkout can) fails the mount with `refusing to compose an unscoped
|
|
219
271
|
context`; a global `npm i -g` install is not affected.
|
|
@@ -221,7 +273,7 @@ context`; a global `npm i -g` install is not affected.
|
|
|
221
273
|
For the optional **智能路由模式 (routing-suite)** mode, also run:
|
|
222
274
|
|
|
223
275
|
```sh
|
|
224
|
-
|
|
276
|
+
node scripts/install-routing-suite.mjs
|
|
225
277
|
```
|
|
226
278
|
|
|
227
279
|
The script adds `dsh-routing-suite`, registers its preset for the `/mode`
|
|
@@ -238,8 +290,8 @@ dsh --profile tui
|
|
|
238
290
|
Verify and uninstall:
|
|
239
291
|
|
|
240
292
|
```sh
|
|
241
|
-
|
|
242
|
-
|
|
293
|
+
node scripts/verify.mjs
|
|
294
|
+
node scripts/uninstall.mjs
|
|
243
295
|
```
|
|
244
296
|
|
|
245
297
|
## First-launch setup
|
|
@@ -348,14 +400,14 @@ You can reopen the wizard at any time with:
|
|
|
348
400
|
|
|
349
401
|
| Key | Action |
|
|
350
402
|
| --- | --- |
|
|
351
|
-
| `Enter` | send; while running, steer; with empty input, toggle the selected card. Oversized tool bodies open a dedicated inspect view
|
|
403
|
+
| `Enter` | send; while running, steer; with empty input, toggle the selected card, or open the full view when the selected row is a reply (`Esc` returns). Oversized tool bodies open a dedicated inspect view too |
|
|
352
404
|
| `Tab` | complete the highlighted slash command |
|
|
353
|
-
| `↑` / `↓` | empty input
|
|
354
|
-
| `Ctrl+R` | expand the latest card; once
|
|
405
|
+
| `↑` / `↓` | empty input with cards in the session: walk up from the newest **reply or card** in screen order (the `▶` marker is only ever on the selected row); with no card at all (plain Q&A, or right after `/clear`) the key keeps its history meaning — select a reply there with `Alt+4` or `Ctrl+N`/`Ctrl+P`. Otherwise history (↓ past the newest item restores the live draft) |
|
|
406
|
+
| `Ctrl+R` | expand the latest card; once something is selected, expand or collapse all (a selected reply keeps its selection) |
|
|
355
407
|
| `Ctrl+T` | fold the input box (display-only) |
|
|
356
|
-
| `Alt+1` / `2` / `3` / `4` | jump to latest thinking / plan / subagent / reply |
|
|
357
|
-
| `/find [kind] query` | search and jump to the full matching message (`thinking` `plan` `subagent` `reply` `prompt` `tool`). `Ctrl+/` or `Alt+/` opens it |
|
|
358
|
-
| `/copy` | copy the
|
|
408
|
+
| `Alt+1` / `2` / `3` / `4` | jump to latest thinking / plan / subagent / reply (a reply is selected, ready for `/copy`) |
|
|
409
|
+
| `/find [kind] query` | search and jump to the full matching message (`thinking` `plan` `subagent` `reply` `prompt` `tool`), which is also selected. `Ctrl+/` or `Alt+/` opens it |
|
|
410
|
+
| `/copy` | copy the selected card or reply **as written** to the local clipboard (latest reply if none is selected; OSC 52). The selection survives, so pressing it twice copies the same thing. Inside a full view the copy key (`Ctrl+Shift+C`) takes **the body on screen** — a tool body, a changes diff, or the reply as written — and the overlay echoes the confirmation |
|
|
359
411
|
| `Ctrl+G` / `Alt+N` | next search hit; `Alt+P` previous |
|
|
360
412
|
| `Esc` | drop selection → scroll to bottom → cancel the running turn |
|
|
361
413
|
| `Ctrl+C` | cancel the running turn; press twice when idle to exit |
|
|
@@ -623,8 +675,11 @@ skips token chunks and lays out only the visible tail on the first paint.
|
|
|
623
675
|
|
|
624
676
|
Each paint is one `stdout.write` of dirty rows only, so a jump host or
|
|
625
677
|
corporate proxy does not see one SSH packet per line. Local ttys use 80 ms.
|
|
626
|
-
Over SSH the TUI probes CSI 6n
|
|
627
|
-
|
|
678
|
+
Over SSH the TUI probes CSI 6n and picks 80 / 160 / 250 / 400 ms from the
|
|
679
|
+
round-trip. It keeps measuring — 8 s after the attach, then every 20 s (a
|
|
680
|
+
measurement that moved is confirmed 5 s later) — and both the chip and the paint
|
|
681
|
+
tier follow the median of the last three, so a jittery entry cannot pin either
|
|
682
|
+
at the slowest tier. `DSH_TUI_PAINT_MS` always wins (40–1000). The stats line
|
|
628
683
|
starts with `SSH ●●●○ 90ms` (1 pip red, 2 yellow, 3+ green). The probe
|
|
629
684
|
does not write into the transcript.
|
|
630
685
|
|
|
@@ -639,13 +694,16 @@ Find your symptom; each answer is what to do, not a change log.
|
|
|
639
694
|
|
|
640
695
|
- **`dsh-ssh-tui: both stdin and stdout must be TTYs`** — start it from a real terminal or
|
|
641
696
|
SSH session; a pipe, CI, or `&` background job will not do.
|
|
697
|
+
- **Windows install, the scripts, and how to read `/doctor`** — [docs/windows.md](docs/windows.md)
|
|
698
|
+
has a 30-second install, a "what to check first when it will not start" table, and what the
|
|
699
|
+
`●` / `⚠` / `✖` marks in a `/doctor` report mean.
|
|
642
700
|
- **Windows: `host display socket did not appear`** — upgrade
|
|
643
701
|
(`dsh plugin --profile tui add dsh-ssh-tui@latest`); older builds waited 15 seconds and
|
|
644
702
|
timed out on the named pipe. If it still fails, attach `/diag` to an issue.
|
|
645
703
|
- **Windows: the in-app update reports `spawn dsh ENOENT`** — an older updater spawned a
|
|
646
|
-
bare `dsh`, which on Windows is a `dsh.cmd` shim. Run
|
|
647
|
-
|
|
648
|
-
from then on
|
|
704
|
+
bare `dsh`, which on Windows is a `dsh.cmd` shim. Run
|
|
705
|
+
`dsh plugin --profile tui add dsh-ssh-tui@latest` once from the command line; the in-app
|
|
706
|
+
update works from then on, and it installs the version it looked up rather than `@latest`.
|
|
649
707
|
- **pnpm refuses to run the build script of a git dependency** — add the key pnpm prints to
|
|
650
708
|
`allowBuilds` in the profile's `pnpm-workspace.yaml`, then reinstall.
|
|
651
709
|
- **After an upgrade `/mode` reports a missing service, or the preset tools vanish** — run
|
|
@@ -686,8 +744,17 @@ Find your symptom; each answer is what to do, not a change log.
|
|
|
686
744
|
painter asks for the narrow text form; a font without the glyph still makes the terminal
|
|
687
745
|
fall back to a wider colour emoji.
|
|
688
746
|
- **The screen cannot keep up on a slow link** — `DSH_TUI_PAINT_MS` sets the paint interval
|
|
689
|
-
(40–1000 ms: smaller is snappier and sends more); unset, it follows the round-trip
|
|
690
|
-
|
|
747
|
+
(40–1000 ms: smaller is snappier and sends more); unset, it follows the round-trip,
|
|
748
|
+
which is re-measured while the session runs (80 / 160 / 250 / 400 ms, median of the last
|
|
749
|
+
three) — see `scripts/tui-rtt-probe.mjs`.
|
|
750
|
+
- **The model “thinks, then says it is done”** — that is an **empty turn from upstream**: some
|
|
751
|
+
gateways map a Gemini/Claude thought part onto `reasoning_content` and then finish with
|
|
752
|
+
`finish_reason: stop` and no content at all, so the harness assembles a reply that is only
|
|
753
|
+
thinking and the turn legitimately ends. The TUI now says so in the transcript
|
|
754
|
+
(“upstream ended this turn after thinking only … it is not really done — press Enter or send
|
|
755
|
+
another message to continue”) instead of letting 完成 read as an answer. Measured on one
|
|
756
|
+
gateway (`google-ai-pro` / `gemini-3.8-flash-high`): about **29%** of turns, with the rest of
|
|
757
|
+
the same session replying normally.
|
|
691
758
|
- **Screen reader, or you want a log** — start with `DSH_TUI_LINE_MODE=1`: plain appended
|
|
692
759
|
lines, no cursor control, safe to `tee`.
|
|
693
760
|
- **Title bar or bell does nothing** — the terminal needs OSC 0 and BEL; `DSH_TUI_NO_BELL=1`
|
|
@@ -695,6 +762,10 @@ Find your symptom; each answer is what to do, not a change log.
|
|
|
695
762
|
- **Whole-row backgrounds are too loud on a dark terminal** — `DSH_TUI_COLOR_DEPTH=none`
|
|
696
763
|
drops them; diffs still read through `+`/`-`.
|
|
697
764
|
|
|
765
|
+
- **Box drawing and dots render as garbage** — the console code page is not UTF-8. The UI
|
|
766
|
+
redraws its chrome in ASCII automatically (rules as `-`, status dots as `*`, the warning as
|
|
767
|
+
`!`) and `/diag` says so on its terminal line; `chcp 65001` or Windows Terminal restores the
|
|
768
|
+
glyphs. `DSH_TUI_ASCII=1` forces ASCII, `=0` forces Unicode. See [docs/windows.md](docs/windows.md).
|
|
698
769
|
- **Terminal compatibility** — what each terminal is allowed and promised (Windows Terminal,
|
|
699
770
|
conhost, GNOME, XFCE, Konsole, xterm, tmux, screen, the Linux console) is tabulated in
|
|
700
771
|
[docs/terminals.md](docs/terminals.md); `/diag` prints the verdict it used, and
|
package/README.md
CHANGED
|
@@ -6,13 +6,21 @@
|
|
|
6
6
|
[](https://dshfind.com/zh/plugins/cyjyyd/dsh-ssh-tui?ref=badge)
|
|
7
7
|
|
|
8
8
|
给跳板机、无桌面服务器、高延迟 SSH 用的 DeepSeek Harness 终端。纯 ANSI、增量重绘,
|
|
9
|
-
|
|
9
|
+
不需要浏览器。**SSH 掉线时,正在跑的回合留在 Host 里,重连用同一条命令接回,会话不丢。**
|
|
10
10
|
|
|
11
11
|
English: [README.en.md](README.en.md)
|
|
12
12
|
|
|
13
13
|
如果你主要在 SSH 里写代码——公司跳板、测试机、只有键盘的会话——可以从这里开始。
|
|
14
14
|
本机桌面终端若更在意主题和布局,也可以继续用你已经习惯的界面。
|
|
15
15
|
|
|
16
|
+
和现成的东西不重叠:
|
|
17
|
+
|
|
18
|
+
- **官方 headless**:同一条任务只把最后一条回复打到 stdout,过程全在日志里。
|
|
19
|
+
- **官方 `dsh-ssh`(0.1.6 起)**:本机跑 Harness、远端跑文件和进程,要预装 helper。人已经 SSH 在那台机器上时用本插件;两者可以叠用,不是替代。
|
|
20
|
+
- **其它终端皮肤**(`dsh-TUI` 等):本机漂亮终端,比主题和布局。本插件比的是弱网下的增量重绘、掉线后 Host 留下、纯文本行模式。
|
|
21
|
+
|
|
22
|
+
掉线之后怎么接回来,见下面的[「SSH 断了之后」](#ssh-断了之后)。
|
|
23
|
+
|
|
16
24
|
已经在付 SuperGrok / X Premium 的话,用独立插件 [dsh-llm-xai-oauth](https://github.com/cyjyyd/dsh-llm-xai-oauth) 把订阅接进 dsh(headless / web / 本 TUI 都能用),复用本机 grok-bridge token,不需要 xAI API Key。
|
|
17
25
|
|
|
18
26
|
本插件已被 [dshfind 插件目录](https://dshfind.com/zh/plugins/cyjyyd/dsh-ssh-tui) 收录:
|
|
@@ -88,7 +96,11 @@ dsh --profile tui
|
|
|
88
96
|
标题(还没出现就保持「处理中」),运行中的工具摘要在 `└` 下自动折行(最多 3 行,末行
|
|
89
97
|
加省略号),带计时和 Esc 中断;回复开始流式输出时自动让位。
|
|
90
98
|
|
|
91
|
-
- 0.7
|
|
99
|
+
- 0.7 起:模型回复可**拖选自由复制**(按住鼠标拖过一段,走 OSC 52 写回本机剪贴板;按住 Shift 拖选
|
|
100
|
+
结果相同;工具卡仍是点击展开)。回复也是**可选中卡片**:空输入 `↑` 落到最新的一条回复或卡片(`▶` 标记,
|
|
101
|
+
走屏幕顺序;`Alt+4` 直接选中最新回复),
|
|
102
|
+
`/copy` 复制的是选中的那一条(原文,不是折行后的屏幕文本),`Enter` 打开全文;
|
|
103
|
+
SSH 会话里复制落到本机终端,不再误报「本终端不接收 OSC 52」;
|
|
92
104
|
底栏收敛成一条带优先级的芯片带(先丢文字后丢组,`⚠` 可点击打开 `/doctor`);**额度条常驻**并标注窗口
|
|
93
105
|
(`5Hr`/`1Wk`/`1Mo`,默认显示最小窗口,未取到时显示 `?%` 并每 15 秒重试);`/mode` 分组显示并可用 `/` 过滤;
|
|
94
106
|
极简视图逐文件列 `+/-`;工具 diff 为**行级**、只高亮变化字符、≥100 列时并排显示;
|
|
@@ -98,10 +110,10 @@ dsh --profile tui
|
|
|
98
110
|
## 环境要求
|
|
99
111
|
|
|
100
112
|
- Node.js ≥ 22.19
|
|
101
|
-
- DeepSeek Harness CLI:`npm i -g @deepseek-ai/dsh
|
|
113
|
+
- DeepSeek Harness CLI:`npm i -g @deepseek-ai/dsh`(本仓库默认开发线是 `0.1.7-rc.1`,CI 覆盖 `0.1.5-rc.1` / `0.1.5-rc.3` / `0.1.7-rc.1` / `0.1.7-rc.2`:`0.1.5-rc.1` 跑 typecheck 与单元套件,其余三条连真 PTY 探针一起跑;非默认腿由 `scripts/ci-pin-line.mjs` 改写 manifest 后从零安装。`0.1.5-alpha.1` / `0.1.5-alpha.2` / `0.1.3-alpha.2` 与 0.1.5-rc 线共用同一套 handle API + `agent/assistant-stream` 兼容层。**`0.1.2-rc` 及更早的线不再支持**,装不上请先升到 `0.1.5-rc` 或 `0.1.7-rc`。`0.1.3-alpha.1` 只在 GitHub 有 tag,npm 未发布,无法本地装包验证)
|
|
102
114
|
- pnpm(`dsh plugin` 通过 pnpm 管理 profile 依赖)
|
|
103
115
|
- 支持 ANSI 的终端(推荐 SSH 直连;Windows 用 PowerShell / Windows Terminal)
|
|
104
|
-
- Windows
|
|
116
|
+
- Windows:安装、排障与 `/doctor` 的读法见 [docs/windows.md](docs/windows.md)。Host 与显示端之间的本地通道使用命名管道
|
|
105
117
|
`\\.\pipe\dsh-tui-<DSH_HOME 摘要 8 位>-<会话名>-<会话摘要 8 位>`(Windows 只能监听命名管道,
|
|
106
118
|
不能监听 `.sock` 文件;名字里同时带 DSH_HOME 与会话 id 的摘要,所以不同 home、不同会话都不会撞名,
|
|
107
119
|
结束进程即自动回收)。Host 的 stderr 记录在 `%USERPROFILE%\.dsh\tui-socks\<会话名>-<摘要>.err`,
|
|
@@ -150,6 +162,17 @@ dsh --profile tui --resume # 选择器(活进程优先接入
|
|
|
150
162
|
dsh --profile tui --resume <session-id> # 有活进程则接入,否则从日志恢复
|
|
151
163
|
```
|
|
152
164
|
|
|
165
|
+
选择器里每个活着的会话只标两个词:**可接入**(一键接入)或**已占用**(别的窗口在用)。
|
|
166
|
+
后面跟的是 Host 的 pid。被标成「已占用」时 `--resume <session-id>` 会被拒绝而不是把对方踢掉——
|
|
167
|
+
要接管就在**选择器里**选中它,再按 `y` / Enter 确认(那个窗口随即退出)。
|
|
168
|
+
|
|
169
|
+
**断线残留不会挡住你**:SSH 被切断时服务端常常还不知道(sshd 要等 TCP keepalive 才发现,可能几小时),
|
|
170
|
+
那个窗口的 launcher 仍然连着显示通道,锁里写的还是 `attached`。所以 Host 会反过来向**终端本身**要答案
|
|
171
|
+
(经 relay 做一次光标往返,见 `scripts/tui-cut-probe.mjs`):终端答不上来就说明那个窗口已经没了,
|
|
172
|
+
选择器直接标「可接入」,一键接入;`--resume <session-id>` 也直接接入。
|
|
173
|
+
只有终端确认还活着时才需要上面的接管确认。这个判断在**首帧之前**完成,所以列表不会先显示「已占用」
|
|
174
|
+
再改口。
|
|
175
|
+
|
|
153
176
|
同一 `sessionId` 不能同时开第二份 Host(会抢 jsonl 和审批)。锁在
|
|
154
177
|
`$DSH_HOME/tui-locks/`,显示通道在 `$DSH_HOME/tui-socks/`。进程死后残留锁会在
|
|
155
178
|
下次启动时核对 pid,已死则自动从日志接管。调试可设 `DSH_TUI_NO_SESSION_LOCK=1`。
|
|
@@ -163,6 +186,11 @@ dsh --profile tui --resume <session-id> # 有活进程则接入,否则从
|
|
|
163
186
|
倍以上的(那是别人请求的回复,相对判定所以 `ssh localhost` 的 2ms 链路照样算得出来)。
|
|
164
187
|
代价是每次接入多约 0.4–0.6 秒探测时间,换来的是 50ms 链路不再出现 2ms / 1900ms 的跳变。
|
|
165
188
|
|
|
189
|
+
**链路会一直重测,不是只测一次**:接入 8 秒后补测一次,之后每 20 秒一次(变化超过 50ms 时
|
|
190
|
+
5 秒后再确认一次),footer 芯片与绘制档位取**最近三次的中位数**——单次抖动(或接入那一刻正好
|
|
191
|
+
在抖)不会把芯片和绘制预算钉在最慢一档,真正的变化两次就能定下来。终端完全不答 DSR 时退避到
|
|
192
|
+
10 分钟一次。见 `scripts/tui-rtt-probe.mjs`(CI 的 0.1.7 腿)。
|
|
193
|
+
|
|
166
194
|
这些回复也不可能再进输入框:relay 整条 stdin 管道常驻过滤(含跨 read 拆分的),Host 键
|
|
167
195
|
处理前再过滤一次;连 Host 启动那几百毫秒里敲的键也会被暂存、接入后补发,不再被丢掉。
|
|
168
196
|
`DSH_TUI_DEBUG=1` 时会打印每次采样与丢弃原因。
|
|
@@ -182,22 +210,25 @@ Host 在后台跑完这一轮;审批和提问等接上后再弹。空闲断线
|
|
|
182
210
|
完全没有显示器且一直空闲的兜底仍由 `DSH_TUI_DETACHED_IDLE_MS`(默认 6 小时)负责。可选:用 tmux 包一层。
|
|
183
211
|
常驻与接管的可复制配方(tmux / screen / systemd --user / 长任务)见 [`docs/remote-ops.md`](docs/remote-ops.md);重连后转录里的「已重连 N 次 · 断开 Xs」与「离开 …」两行的语义也在那里。
|
|
184
212
|
|
|
185
|
-
启动时若 npm 上有更新,会弹出选单(类似 Codex / Claude Code 首启):**现在更新 / 稍后 /
|
|
213
|
+
启动时若 npm 上有更新,会弹出选单(类似 Codex / Claude Code 首启):**现在更新 / 稍后 / 跳过此版本**。选「现在更新」安装的是刚才查到的那个版本号(`dsh plugin --profile tui add dsh-ssh-tui@<版本>`),不写 `@latest`:pnpm 对 `@latest` 会再解析一次,刚发布、还在 `minimumReleaseAge` 窗口里的版本会被静默跳过、装成旧的还显示成功。完成后提示退出再启动。`DSH_TUI_NO_UPDATE_CHECK=1` 可关掉。`/status` 里也能看到当前插件版本、链路芯片、额度窗口,以及子代理模型是否与父路由同族。
|
|
186
214
|
|
|
187
|
-
仓库内也可:`bash scripts/smoke-headless.sh
|
|
215
|
+
仓库内也可:`node scripts/smoke-headless.mjs`(`bash scripts/smoke-headless.sh` 等价;记录出口摘要,不打印 token)。
|
|
188
216
|
|
|
189
217
|
### 方式一:从 git clone 安装
|
|
190
218
|
|
|
191
219
|
```bash
|
|
192
220
|
git clone https://github.com/cyjyyd/dsh-ssh-tui.git
|
|
193
221
|
cd dsh-ssh-tui
|
|
194
|
-
|
|
222
|
+
node scripts/install.mjs # 默认安装到 tui profile(bash scripts/install.sh 等价)
|
|
195
223
|
```
|
|
196
224
|
|
|
225
|
+
Windows 没有 bash,用同一条 `node scripts/install.mjs`;完整的 Windows 步骤、
|
|
226
|
+
「装不上先看什么」和 `/doctor` 的读法见 [docs/windows.md](docs/windows.md)。
|
|
227
|
+
|
|
197
228
|
安装到其它 profile(例如自定义 `work` profile):
|
|
198
229
|
|
|
199
230
|
```bash
|
|
200
|
-
|
|
231
|
+
node scripts/install.mjs work
|
|
201
232
|
```
|
|
202
233
|
|
|
203
234
|
脚本会依次:安装依赖 → 构建 `lib/` → 通过 `dsh plugin --profile <name> add link:<repo>`
|
|
@@ -216,7 +247,8 @@ bash scripts/install.sh work
|
|
|
216
247
|
|
|
217
248
|
1. **运行中的应用内修复**:`/mode fix` 写入下面这段并提示重启。npm 安装和
|
|
218
249
|
「现在更新」走的是 `dsh plugin add`,不会执行仓库脚本,所以这是最通用的一条。
|
|
219
|
-
2. 仓库安装:`
|
|
250
|
+
2. 仓库安装:`node scripts/profile-rows.mjs [profile]`(默认 `tui`;
|
|
251
|
+
`bash scripts/ensure-profile-rows.sh` 是它的薄封装,两者等价)。
|
|
220
252
|
3. 手动在该 profile 的 `$DSH_HOME/profiles/<profile>/cordis.patch.yml` 里加入:
|
|
221
253
|
|
|
222
254
|
```yaml
|
|
@@ -234,7 +266,7 @@ profile(例如同时装了 `@deepseek-ai/dsh-web-app` 的 profile)。改完
|
|
|
234
266
|
会以 `service "agentPresets" has been registered` 失败),先把 profile 补丁里的这段删掉。
|
|
235
267
|
|
|
236
268
|
名单缺席时,TUI 启动会打一行提示(中文界面:「未挂载 agent-presets 名单…」),
|
|
237
|
-
`/mode` 会打印补丁路径和 `/mode fix` 修复入口,`scripts/verify.sh`
|
|
269
|
+
`/mode` 会打印补丁路径和 `/mode fix` 修复入口,`node scripts/verify.mjs`(`scripts/verify.sh` 等价)也会给出提示。
|
|
238
270
|
另外,preset 的 scope 身份按模块实例判定:一个 dsh 安装树里若存在两份
|
|
239
271
|
`@deepseek-ai/dsh-scope`(npm 嵌套安装的 checkout 可能如此),名单挂载会以
|
|
240
272
|
`refusing to compose an unscoped context` 失败;全局安装(`npm i -g`)不受影响。
|
|
@@ -248,7 +280,15 @@ npm run build
|
|
|
248
280
|
dsh plugin --profile tui add "link:$(pwd)"
|
|
249
281
|
```
|
|
250
282
|
|
|
251
|
-
Windows(PowerShell
|
|
283
|
+
Windows(PowerShell)不用 bash,直接跑 Node 脚本(`scripts/*.sh` 只是转去调用它们的薄封装):
|
|
284
|
+
|
|
285
|
+
```powershell
|
|
286
|
+
cd dsh-ssh-tui
|
|
287
|
+
node scripts/install.mjs # 装依赖、构建、链接进 tui profile、挂上 /mode 需要的行
|
|
288
|
+
node scripts/verify.mjs # 检查组合是否生效
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
不想用脚本时,三条命令等价:
|
|
252
292
|
|
|
253
293
|
```powershell
|
|
254
294
|
cd dsh-ssh-tui
|
|
@@ -261,8 +301,8 @@ dsh plugin --profile tui add "link:$((Get-Location).Path)"
|
|
|
261
301
|
|
|
262
302
|
```bash
|
|
263
303
|
dsh plugin --profile work add dsh-ssh-tui
|
|
264
|
-
#
|
|
265
|
-
|
|
304
|
+
# 或仓库脚本(bash scripts/install-npm.sh 等价)
|
|
305
|
+
node scripts/install-npm.mjs work
|
|
266
306
|
```
|
|
267
307
|
|
|
268
308
|
### 智能路由模式(dsh-routing-suite)
|
|
@@ -270,8 +310,8 @@ bash scripts/install-npm.sh work
|
|
|
270
310
|
需要“智能路由模式”时,安装 `dsh-routing-suite` 并注册其 preset:
|
|
271
311
|
|
|
272
312
|
```bash
|
|
273
|
-
|
|
274
|
-
|
|
313
|
+
node scripts/install-routing-suite.mjs # 默认 tui profile
|
|
314
|
+
node scripts/install-routing-suite.mjs work # 其它 profile(bash scripts/install-routing-suite.sh 等价)
|
|
275
315
|
```
|
|
276
316
|
|
|
277
317
|
脚本会执行 `dsh plugin --profile <name> add dsh-routing-suite`,并把包内的
|
|
@@ -300,16 +340,16 @@ dsh --profile tui --no-color
|
|
|
300
340
|
|
|
301
341
|
| 键 | 作用 |
|
|
302
342
|
| --- | --- |
|
|
303
|
-
| `Enter` |
|
|
343
|
+
| `Enter` | 发送;运行中则插入指示;空输入且已选中卡片时展开/收起,选中的是回复则打开全文(`Esc` 返回)。工具正文超出窗口也单独全览 |
|
|
304
344
|
| `1..9` / `Enter` | 回答 `ask_user_question` 提问:`1..9` 直接选,`Enter` 取当前高亮项(默认第一项),`Esc` 才取消 |
|
|
305
|
-
| `↑` / `↓` |
|
|
306
|
-
| `Ctrl+R` |
|
|
345
|
+
| `↑` / `↓` | 空输入且会话里已有卡片时:在当前屏幕顺序的最新一条(**回复或卡片**)开始往上走,标记 `▶` 只出现在选中的那一条;没有卡片时(纯问答、或刚 `/clear`)仍是有输入时的历史召回——那种会话用 `Alt+4` 或 `Ctrl+N`/`Ctrl+P` 选回复。有输入:历史(↓ 越过最新一条会回到当前草稿) |
|
|
346
|
+
| `Ctrl+R` | 展开最新一条卡片;已选中时全部展开或全部收起(选中的回复不会被抢走焦点,打开覆盖层时该键交给覆盖层) |
|
|
307
347
|
| `Ctrl+T` | 折叠输入框(只影响显示) |
|
|
308
|
-
| `Alt+1` / `2` / `3` / `4` | 跳到最新思考 / 计划 / 子代理 /
|
|
309
|
-
| `/find [类] 关键字` |
|
|
348
|
+
| `Alt+1` / `2` / `3` / `4` | 跳到最新思考 / 计划 / 子代理 / 回复(回复会被选中,可直接 `/copy`) |
|
|
349
|
+
| `/find [类] 关键字` | 搜索并跳到该条完整消息(反色高亮),命中的那条同时成为选中项。类:`思考` `计划` `子代理` `回复` `提示词`。`Ctrl+/` 或 `Alt+/` 打开 |
|
|
310
350
|
| `Ctrl+G` / `Alt+N` | 下一条搜索结果;`Alt+P` 上一条 |
|
|
311
|
-
| `/copy` |
|
|
312
|
-
| 鼠标左键 | 点击卡片标题展开/收起;点 markdown 链接则复制 URL |
|
|
351
|
+
| `/copy` | 把选中的卡片或回复按**原文**写入本机剪贴板(无选中则最近一条回复;支持 OSC 52 的终端/tmux)。复制后保留选中,连按两次拿到的是同一条。在「全文」覆盖层里按复制键(`Ctrl+Shift+C`)复制的是**屏幕上那份正文**——工具卡是工具全文、改动卡是那份 diff、回复是原文;覆盖层底部会回显「已复制 …(全文)」 |
|
|
352
|
+
| 鼠标左键 | 点击卡片标题展开/收起;点 markdown 链接则复制 URL;**拖过回复**按屏幕所见复制(不含选中标记 `▶`) |
|
|
313
353
|
| `PgUp` / `PgDn`、滚轮 | 转录回看 |
|
|
314
354
|
| `Esc` | 取消选择 → 回底部 → 取消当前轮次 |
|
|
315
355
|
| `Ctrl+C` | 中断当前轮次;空闲连按两次退出 |
|
|
@@ -471,7 +511,7 @@ DeepSeek 上、B 会话跑在 xAI 上;只记软件级默认值的话,resume
|
|
|
471
511
|
## 验证
|
|
472
512
|
|
|
473
513
|
```bash
|
|
474
|
-
|
|
514
|
+
node scripts/verify.mjs # 检查 profile 组合与 CLI 语法(bash scripts/verify.sh 等价)
|
|
475
515
|
npm test # 单元 + 集成(含屏幕网格护栏、重连接管、选择器首帧)
|
|
476
516
|
python3 scripts/pty-acceptance.py # 真 PTY:模拟 40ms SSH 链路 + 滞留的光标回复
|
|
477
517
|
node scripts/tui-probe.mjs # 真 PTY:真 dsh --profile tui 走一遍启动/缩放//diag/打字//exit
|
|
@@ -509,8 +549,8 @@ PROBE_HOME=$H node scripts/tui-probe.mjs --session <id> # 只读式驱动已
|
|
|
509
549
|
## 卸载
|
|
510
550
|
|
|
511
551
|
```bash
|
|
512
|
-
|
|
513
|
-
|
|
552
|
+
node scripts/uninstall.mjs # 默认 tui profile(bash scripts/uninstall.sh 等价)
|
|
553
|
+
node scripts/uninstall.mjs work # 指定 profile
|
|
514
554
|
```
|
|
515
555
|
|
|
516
556
|
卸载只移除 profile 中的插件依赖与 bundle 层,不会删除会话数据。
|
|
@@ -568,10 +608,12 @@ npm run build
|
|
|
568
608
|
### 启动与安装
|
|
569
609
|
|
|
570
610
|
- **`dsh-ssh-tui: both stdin and stdout must be TTYs`**:必须在真实终端 / SSH 会话里启动;管道、CI、`&` 后台都不行。
|
|
611
|
+
- **Windows 安装、脚本与 `/doctor` 的读法**:[docs/windows.md](docs/windows.md) 有 30 秒安装、
|
|
612
|
+
「装不上先看什么」的对照表,以及 `/doctor` 报告里 `●` / `⚠` / `✖` 各代表什么。
|
|
571
613
|
- **Windows 报 `host display socket did not appear`**:升级到最新版:
|
|
572
614
|
`dsh plugin --profile tui add dsh-ssh-tui@latest`。仍失败请附 `/diag` 输出提 issue。
|
|
573
615
|
- **Windows 上应用内更新报 `spawn dsh ENOENT`**:旧的更新器直接 `spawn dsh`,而 Windows 上装的是 `dsh.cmd` 垫片;
|
|
574
|
-
|
|
616
|
+
在命令行跑一次 `dsh plugin --profile tui add dsh-ssh-tui@latest`,之后应用内更新正常(它装的是查到的版本号,不再写 `@latest`)。
|
|
575
617
|
- **pnpm 拒绝 git 依赖的构建脚本**:把 pnpm 打印的 key 加进 profile 的 `pnpm-workspace.yaml` 的 `allowBuilds`,再重装。
|
|
576
618
|
- **升级后 `/mode` 报「服务不可用」、preset 工具消失**:敲 `/doctor`。它逐项判定部署组合(补丁能否解析、
|
|
577
619
|
名单与 code-runtime 是否组合、有没有行被挂载两次、dsh 版本是否在兼容表内、是否装着两份 `@deepseek-ai/dsh-scope`),
|
|
@@ -597,11 +639,19 @@ npm run build
|
|
|
597
639
|
- **中文 / emoji 挤压相邻字符**:换一款覆盖这些字形的等宽字体(如 Noto Sans Mono CJK)。程序按 2 格预算这些符号
|
|
598
640
|
并请求文本字形;字体缺字时终端会回退到彩色 emoji 字形,视觉上仍可能偏宽。
|
|
599
641
|
- **弱网下画面跟不上**:`DSH_TUI_PAINT_MS` 控制发画间隔(40–1000 ms,越小越快也越费流量);
|
|
600
|
-
|
|
642
|
+
不设时按测得的 RTT 自动取 80 / 160 / 250 / 400 ms;链路每 20 秒重测(刚变化过就 5 秒后再测一次),
|
|
643
|
+
取最近三次的中位数,所以接入那一刻的抖动不会把绘制档位钉在最慢一档。
|
|
644
|
+
- **模型“只思考一下就说完成”**:这是**上游返回了空回合**——有些网关把 Gemini/Claude 的 thought 部分映射成
|
|
645
|
+
`reasoning_content`,然后以 `finish_reason: stop` 收尾却没有任何正文,harness 如实组装成“只含思考的助手消息”,
|
|
646
|
+
回合就合法结束了。TUI 现在会显式写一行「上游只返回了思考…这不是真正的完成(finish_reason: stop);按 Enter 或再输入一句即可继续」,
|
|
647
|
+
不再让你以为它答完了。实测某网关(`google-ai-pro` / `gemini-3.8-flash-high`)约 **29%** 的回合如此(同会话其它回合正常)。
|
|
601
648
|
- **要给屏幕阅读器或日志用**:`DSH_TUI_LINE_MODE=1` 启动纯行模式——只追加纯文本行、不发光标控制,可直接 `tee` 存档。
|
|
602
649
|
- **标题栏 / 铃声不生效**:终端需支持 OSC 0 与 BEL;`DSH_TUI_NO_BELL=1` 可关闭铃声。
|
|
603
650
|
- **深色终端下整行底色太抢眼**:`DSH_TUI_COLOR_DEPTH=none` 去掉底色,diff 仍用 `+`/`-` 区分。
|
|
604
651
|
|
|
652
|
+
- **框线、圆点显示成乱码**:控制台代码页不是 UTF-8。界面会自动改画 ASCII(横线 `-`、状态点 `*`、
|
|
653
|
+
警告 `!`),`/diag` 的「终端」一行会注明;想要原字形就 `chcp 65001` 或改用 Windows Terminal。
|
|
654
|
+
`DSH_TUI_ASCII=1` 强制 ASCII,`=0` 强制 Unicode。详见 [docs/windows.md](docs/windows.md)。
|
|
605
655
|
- **终端兼容性**:每个终端允许发什么、承诺什么(Windows Terminal / conhost / GNOME / XFCE / Konsole /
|
|
606
656
|
xterm / tmux / screen / Linux 控制台)见 [docs/terminals.md](docs/terminals.md);
|
|
607
657
|
判定依据写在 `/diag` 的「终端」一行,判定错了用 `DSH_TUI_TERM_CAPS` 覆盖。
|
package/docs/remote-ops.md
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
| **空闲**(没有轮次在跑) | launcher 交还终端并退出,会话日志已落盘。回来 `--resume` 从日志恢复,**没有 Host 在等你** |
|
|
13
13
|
| **忙碌**(思考 / 回复 / 工具 / 子代理在跑),默认策略 `pause` | 取消当前轮次、flush 日志,**Host 留下**并持有会话写锁,等你回来接入 |
|
|
14
14
|
| **忙碌**,`/disconnect continue` | 不取消,Host 在后台把这一轮跑完;你回来时结果已经在转录里(可能还带着子代理的进度) |
|
|
15
|
-
| 你开了第二个窗口去接同一个会话 |
|
|
15
|
+
| 你开了第二个窗口去接同一个会话 | 如果那个会话正被别的窗口接入,选择器会把它标成「已占用 · pid N」:选中它先问一句,按 `y` / Enter 才接管——新窗口接管显示,旧窗口退出(`replaced`)。`--resume <session-id>` 跳过选择器,所以这种情况**会被拒绝**,不会静默踢掉对方;断线留下的 Host(标「可接入」)仍是一键接入。**不要为同一个会话起第二份 Host** |
|
|
16
|
+
| 断线残留(SSH 悄悄断了,服务端还没发现) | launcher 还连着显示通道、锁里还写着 `attached`,但那个窗口其实已经没了(sshd 要等 TCP keepalive 才发现,可能几小时)。Host 会经 relay 向**终端本身**做一次光标往返:答不上来就判定为残留——选择器直接标「可接入」,`--resume <session-id>` 与 Enter 都直接接入,不再需要接管确认。这个判断在**首帧之前**完成,所以列表不会先显示「已占用」再改口 |
|
|
16
17
|
|
|
17
18
|
会话写锁是内核锁:同一时刻只有一个写者。这是为什么"再开一个窗口"和"再起一份 Host"是两件不同的事——前者是接管,后者会失败(`already owned by an active write handle`)。
|
|
18
19
|
|
|
@@ -41,6 +42,31 @@
|
|
|
41
42
|
- **摘要是增量**:只统计这次离开期间发生的事,不重复会话的总量。
|
|
42
43
|
- 只有真发生过的部分才会出现;链路只是抖一下,就只有第一行。
|
|
43
44
|
- 断线期间到达的**未识别审批会被拒绝**并把理由写进转录(模型据此调整)。这是有意的:挂起的轮次会撞上 idle-exit 兜底,最后既没跑完也没人确认。
|
|
45
|
+
- 有提问还在等时,摘要会多一句「这个问题等了你 Xm」,从它开始等算起,而不是从断线算起。
|
|
46
|
+
|
|
47
|
+
## 3.1 提问在等、而你不在
|
|
48
|
+
|
|
49
|
+
模型问了一个问题、当时没有人看着屏幕时,回合会停在那里。过去这只在**你重连之后**才看得出,所以一次等了很久的提问要到回来才发现。现在有两层,都不需要新依赖。
|
|
50
|
+
|
|
51
|
+
**标记文件,不用配置。** 等待一开始就在 `$DSH_HOME/tui-socks/` 写一个 `<会话>.waiting`,内容是会话 id、在等的提问数、开始时间和提问原文,权限 `0600`。回到跳板机、还没开 TUI 时 `ls` 就能看见。提问被回答、取消,或 Host 退出时删掉,所以留下的文件一定对应一个还活着的等待。
|
|
52
|
+
|
|
53
|
+
等待中的提问也算「忙」:Host 不会被 60 秒的空闲退出杀掉,否则提问会跟着进程一起消失。
|
|
54
|
+
|
|
55
|
+
**一条命令,可选。** `ssh-tui.notify`(或 `DSH_TUI_NOTIFY`,环境变量优先)放一条命令,提问开始等待时跑一次。消息从标准输入进去,细节在环境变量里:`DSH_TUI_NOTIFY_SESSION`、`DSH_TUI_NOTIFY_COUNT`、`DSH_TUI_NOTIFY_QUESTION`、`DSH_TUI_NOTIFY_WAITED_MS`、`DSH_TUI_NOTIFY_RESUME`(接回这条会话的命令)。10 秒不返回就杀掉,失败不进转录、不拖回合。只在**开始等待时**发一次,不是每分钟一次。
|
|
56
|
+
|
|
57
|
+
不用手写 settings。`/notify` 直接配,三种走邮件的方式:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
/notify 查看当前配置
|
|
61
|
+
/notify off 关闭
|
|
62
|
+
/notify mail you@example.com 本机 mail / mailx
|
|
63
|
+
/notify smtp smtp.example.com from@a.c you@b.c alice 密码
|
|
64
|
+
/notify local from@a.c you@b.c 本机邮局,127.0.0.1:25
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`smtp` 的端口默认 587,写在主机后面就换(`smtp.example.com:465`);账号和密码省略时按不登录发送。`local` 的端口默认 25,要换就放第一个(`/notify local 2525 from to`)。密码存在 `ssh-tui.notifySmtpPassword`,不进命令字符串,也不出现在转录里;发送时经 `DSH_TUI_NOTIFY_SMTP_PASSWORD` 交给命令。SMTP 和本机邮局都用 python3 标准库的 `smtplib`,跳板机有 Node 就有它。
|
|
68
|
+
|
|
69
|
+
`DSH_TUI_NOTIFY` 仍可用,适合一条自己写的命令。企业微信等待下一轮。
|
|
44
70
|
|
|
45
71
|
## 4. 配方
|
|
46
72
|
|
package/docs/terminals.md
CHANGED
|
@@ -100,14 +100,27 @@ Windows 与 Linux 的差异只在**改键**文件里:`/keys` 写出的键名
|
|
|
100
100
|
- **旧版 Windows 控制台(无 VT 处理)**:Windows 10 之前的 conhost 不认这些转义序列,
|
|
101
101
|
用 `DSH_TUI_NO_ALT_SCREEN=1` 或 `DSH_TUI_TERM_CAPS=no-alternateScreen,no-mouse` 退到最朴素模式。
|
|
102
102
|
- **代码页不是 UTF-8 的 Windows 控制台**:框线与 emoji 会花。TUI 不改你的控制台代码页
|
|
103
|
-
|
|
103
|
+
(那是全局状态),而是把界面骨架改画成 ASCII(横线 `-`、状态点 `*`、警告 `!`,判定见
|
|
104
|
+
`src/platform.ts` 的 `asciiFallbackEnabled`)。`/diag` 的「终端」一行会注明;
|
|
105
|
+
想要原字形用 `chcp 65001` 或改用 Windows Terminal。`DSH_TUI_ASCII=1` 强制 ASCII,`=0` 强制 Unicode。
|
|
104
106
|
- **`/copy` 与拖选都依赖 OSC 52**:VTE 系(GNOME / XFCE / MATE / Tilix / Terminator)、conhost、screen、
|
|
105
|
-
Linux 控制台都不接收;Konsole 要 24.12+;xterm / tmux 需要自身配置。这些终端上 TUI
|
|
106
|
-
|
|
107
|
+
Linux 控制台都不接收;Konsole 要 24.12+;xterm / tmux 需要自身配置。这些终端上 TUI 每会话提示一次。
|
|
108
|
+
**SSH 会话不提示**:能力表读的是远端 tty,而 OSC 52 写到的是你本机的终端,复制实际落到本机剪贴板,
|
|
109
|
+
再警告就是误报。提示里说的退路是终端自己的选择:按住 Shift 时多数终端不转发鼠标、留给原生选择;
|
|
110
|
+
少数终端照样转发,那种情况下 Shift+拖选 与直接拖选做同一件事(SGR 的按钮码带上 Shift 的 +4)。
|
|
111
|
+
- **选中标记 `▶` 不算正文**:被选中的回复第一行前面有 `▶ `(`▶` 按本项目的宽度表算两格,所以是三格;
|
|
112
|
+
ASCII 回退下它是 `> `,仍是三格),这一行仍然可以拖选。反色高亮与复制都从第四格开始算,粘出来的文本里
|
|
113
|
+
没有标记;起点落在标记上也不算「没选中」——拖过它就会带上该行的正文。被选中的那条回复按**窄三格**排版,
|
|
114
|
+
所以和未选中时相比会重新折行——代价是换来"标记不会吃掉首行末尾的字"。
|
|
115
|
+
- **覆盖层里的复制键**:`Ctrl+Shift+C` 在 kitty / CSI-u 终端上会到达 TUI 并复制「全文」;但把该键折叠成
|
|
116
|
+
`Ctrl+C` 的老终端上,它只是 Ctrl+C(在覆盖层里等于关闭)。那里用 `ssh-tui.keys` 把 `copy` 改绑到别的键
|
|
117
|
+
——改绑后的键在覆盖层内同样生效(旧版本只在没有对话框时查改绑表,覆盖层里按不动)。
|
|
107
118
|
- **旧版 Windows 控制台的括号粘贴**:`?2004` 到 2022-11(Windows 11 22H2)才进 conhost,
|
|
108
119
|
更早的 PowerShell/cmd 没有;粘贴仍然可用(多行 burst 会被当成一次粘贴),只是没有括号标记。
|
|
109
120
|
- **tmux / screen 里的鼠标**:需要 tmux `set -g mouse on`、screen 亦然;TUI 会照发,转发与否由它们决定。
|
|
110
|
-
- **不是 UTF-8 的 locale
|
|
121
|
+
- **不是 UTF-8 的 locale**:自动改画 ASCII 骨架。`LC_ALL` / `LC_CTYPE` / `LANG` 写明了非 UTF-8
|
|
122
|
+
字符集(或裸 `C` / `POSIX`)即触发,与 Windows 代码页走同一条回退;没写字符集的 locale 不动,
|
|
123
|
+
因为一条 UTF-8 的 SSH 会话经常什么都不导出。中文翻译不会被替换成 ASCII——那种控制台请同时 `/language en`。
|
|
111
124
|
- **`/diag` 的终端行来自 Host 的环境**:重新接入(reattach)后它描述的是 Host 启动时所处的终端。
|
|
112
125
|
退出时的「离开备用屏」序列因此**无条件发出**——它落在没进过备用屏的终端上会被忽略,
|
|
113
126
|
而漏发会把用户留在无法滚动的屏幕里。
|