dsh-remote 0.8.73 → 0.8.74
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 +81 -0
- package/README.md +67 -0
- package/lib/client.js +19 -2
- package/lib/host-api.js +808 -0
- package/lib/index.js +681 -309
- package/lib/tui/flow.js +130 -0
- package/lib/tui/format.js +332 -0
- package/lib/tui/i18n.js +344 -0
- package/lib/tui/index.js +322 -0
- package/lib/tui/provider.js +327 -0
- package/lib/tui/scene.js +996 -0
- package/lib/workspace-cleanup.js +337 -0
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -66,6 +66,7 @@ The rest:
|
|
|
66
66
|
- **Async long tasks** — `rw_sync`/`rw_push` with `async: true` return a `taskId` with progress/result/cancel.
|
|
67
67
|
- **Data lives under the harness home** — machines and mirrors follow `$DSH_HOME`; pre-0.6 data migrates automatically on first run.
|
|
68
68
|
- **No `dsh-workspace` core changes** — everything ships as a normal plugin.
|
|
69
|
+
- **Pages in the terminal UI too (dsh-tui)** — `/dsh-remote` opens a server management page (add/edit/delete, connect, browse remote directories, set as workspace), and the new-workspace picker can list remote project directories directly — again without patching dsh-tui. See "Use it from the TUI" below.
|
|
69
70
|
|
|
70
71
|
## Install
|
|
71
72
|
|
|
@@ -155,6 +156,81 @@ The classic case: dsh 0.1.0-rc.6 depends on `node-pty@1.1.0`, whose published pa
|
|
|
155
156
|
The plugin no longer installs or health-checks dsh for you. Install it on the remote the way you
|
|
156
157
|
install anything else, then point the plugin at it: Settings → Remote workspaces → the machine row →
|
|
157
158
|
**Edit** → Advanced → **remote dsh command** (or the global `webAttachCommand`).
|
|
159
|
+
|
|
160
|
+
## Use it from the TUI (dsh-tui)
|
|
161
|
+
|
|
162
|
+
The plugin ships a TUI adaptation layer: once installed into a `dsh-tui` profile, the same machine
|
|
163
|
+
registry, SSH pool and remote workspaces work in the terminal UI — **without patching dsh-tui itself**
|
|
164
|
+
(everything registers through its plugin seams).
|
|
165
|
+
|
|
166
|
+
### Install
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# On a machine that already runs dsh-tui this is the only step; a local checkout works too
|
|
170
|
+
dsh plugin --profile dsh-tui add dsh-remote
|
|
171
|
+
# or: dsh plugin --profile dsh-tui add /path/to/dsh-remote
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Restart dsh-tui afterwards. Web profiles are unaffected: without terminal seams the TUI layer stays
|
|
175
|
+
completely silent and registers nothing.
|
|
176
|
+
|
|
177
|
+
### `/dsh-remote` — the remote-server settings page, in your terminal
|
|
178
|
+
|
|
179
|
+
Type `/dsh-remote` to open a full-screen settings page (the command also shows up in `/` completion
|
|
180
|
+
and `/help`):
|
|
181
|
+
|
|
182
|
+
- `↑/↓` picks a machine, `Enter` opens the action menu; `a` add, `e` edit, `d` delete, `t` test
|
|
183
|
+
connection, `c` connect, `b` browse remote directories, `m` manage workspaces, `Esc`/`q` close
|
|
184
|
+
(inside a sub-view they step back to the list first).
|
|
185
|
+
- Add/edit: name, host, port, username, auth method (password / private key / SSH agent), password or
|
|
186
|
+
key path, group. Passwords are **write-only** and never echoed; leaving the field empty keeps the
|
|
187
|
+
stored value.
|
|
188
|
+
- `b` browses remote directories; `w` adopts the current one as a **workspace**: it creates the local
|
|
189
|
+
mirror, registers it as a DSH workspace (title `<machine>-<directory>`) and, per configuration,
|
|
190
|
+
switches the session there.
|
|
191
|
+
|
|
192
|
+
### Pick a remote directory when creating a workspace
|
|
193
|
+
|
|
194
|
+
- The `/workspace` picker lists every machine's recent remote projects next to local directories
|
|
195
|
+
(badge = machine name, description = `user@host:/remote/path`). Picking one opens that remote
|
|
196
|
+
workspace in a new session.
|
|
197
|
+
- URI form works too: `/workspace open dshremote://<machine-name-or-id>/<URL-encoded remote absolute path>`.
|
|
198
|
+
- Launch arguments work as well: `dst dshremote://<machine>/<remote path>`.
|
|
199
|
+
- How many entries each machine contributes is controlled by the configuration below.
|
|
200
|
+
|
|
201
|
+
### Clean up workspaces (delete a workspace or its chats)
|
|
202
|
+
|
|
203
|
+
`/dsh-remote` → pick a machine → `m` (manage workspaces) lists every local mirror of that machine:
|
|
204
|
+
|
|
205
|
+
- `d`: **delete the workspace permanently** — its chats, its workspace registration, the remote
|
|
206
|
+
path in every machine's recent list, and finally the local mirror directory
|
|
207
|
+
`$DSH_HOME/remote-workspaces/<host>-<user>-<port>/<name>`. Removing the recent entry matters:
|
|
208
|
+
otherwise the provider recreates the mirror from it on the next start.
|
|
209
|
+
- `x`: delete only the chats under that workspace (mirror and registration stay); chats still in
|
|
210
|
+
use are skipped and reported.
|
|
211
|
+
- `r`: only forget the remote path from the recent list / current workspace (no deletion) — stops
|
|
212
|
+
the automatic recreation.
|
|
213
|
+
- While a chat still uses the workspace, `d` is refused (stop background sessions with `Ctrl+X` or
|
|
214
|
+
exit the terminal that holds it). Deletion is irreversible and does **not** touch the remote
|
|
215
|
+
directory.
|
|
216
|
+
|
|
217
|
+
### Configuration
|
|
218
|
+
|
|
219
|
+
The TUI layer's five switches live on the same `cordis.patch.yml` row as every other
|
|
220
|
+
option — see the last five rows of the [Configuration](#configuration) table
|
|
221
|
+
(`tui` / `tuiProviderListRecent` / `tuiProviderRecentLimit` / `tuiAutoSwitchWorkspace` /
|
|
222
|
+
`tuiLang`).
|
|
223
|
+
|
|
224
|
+
### Notes
|
|
225
|
+
|
|
226
|
+
- A remote project is presented in the terminal as its **local mirror directory**: that is the
|
|
227
|
+
session cwd and where the `rw_*` tools operate; the remote path only appears in titles and
|
|
228
|
+
descriptions. The remote identity comes from `.dsh-remote-meta.json` inside the mirror.
|
|
229
|
+
- The workspace provider is a newer dsh-tui seam: on older dsh-tui builds (or profiles that do not
|
|
230
|
+
mount it) the `/dsh-remote` page and the workspace registry still work; only the picker entries are
|
|
231
|
+
missing.
|
|
232
|
+
- Design and host-API contract: [docs/tui.md](docs/tui.md).
|
|
233
|
+
|
|
158
234
|
## CLI defaults (optional)
|
|
159
235
|
|
|
160
236
|
Provide a default machine in `cordis.patch.yml`:
|
|
@@ -257,6 +333,11 @@ Deploying to a product profile is a separate, explicit action (`./sync.sh`) for
|
|
|
257
333
|
| `webAttachDshHome` | string | `''` | `DSH_HOME` exported on the remote when attaching; empty reuses the remote user's own harness home |
|
|
258
334
|
| `webAttachWaitSeconds` | int | `45` | how long to wait for a freshly started remote `dsh web` to print its launch token |
|
|
259
335
|
| `localTools` | string | `bootstrap` | which `rw_*` tools a LOCAL session (cwd outside every remote mirror) sees (issue #63): `bootstrap` keeps only the configuration loop `rw_machines`/`rw_connect`/`rw_pick_workspace` (the other 19 refuse a local session with "this session is LOCAL", so exposing them just wastes schema budget); `none` hides all `rw_*` (remote setup happens via the settings page only); `all` restores the legacy full surface. REMOTE sessions (cwd inside a mirror) always see everything; an invalid value degrades to `bootstrap` |
|
|
336
|
+
| `tui` | bool | `true` | master switch for the TUI (dsh-tui) layer; `false` registers no `/dsh-remote` page, workspace provider or status field |
|
|
337
|
+
| `tuiProviderListRecent` | bool | `true` | list each machine's recent remote projects in the TUI workspace picker |
|
|
338
|
+
| `tuiProviderRecentLimit` | int | `5` | recent remote projects listed per machine (1–20) |
|
|
339
|
+
| `tuiAutoSwitchWorkspace` | bool | `true` | switch the session right after "set as workspace" in the `/dsh-remote` page |
|
|
340
|
+
| `tuiLang` | string | `''` | language of the TUI layer's own copy: `''` = auto (zh, or en when `DSH_TUI_LANG=en`), `zh` / `en` pins it |
|
|
260
341
|
| `remoteSandbox` | string | `inherit` | which sandbox semantics remote operations follow (0.8.54+): `inherit` reads the mode the local `ctx.sandboxPolicy` resolved (read-only refuses remote writes/commands/forwards, workspace-write fences remote writes to the workspace root, danger-full-access allows everything); `off` disables the fence (legacy behaviour); an explicit mode name is also accepted. **This is containment, not a security boundary** — deploy hard boundaries on the remote side |
|
|
261
342
|
| `credentialFallback` | string | `refuse` | what to do when the OS secure store refuses a password (0.8.54+): `refuse` saves no password (the machine is still saved, the UI reports why, and nothing is written to machines.json in plaintext); `plain` restores the legacy plaintext fallback (with a warning). Turning encryption off explicitly (`backend: plain`) is unaffected |
|
|
262
343
|
|
package/README.md
CHANGED
|
@@ -56,6 +56,7 @@ DSH 的 Web 界面刻意只监听 `127.0.0.1`(CLI 为安全拒绝 `--host 0.0.
|
|
|
56
56
|
- **数据跟随 Harness 根目录** —— 机器清单与镜像在 `$DSH_HOME/remote-workspaces`;0.6 之前的数据首次启动自动迁移。
|
|
57
57
|
- **`~/.ssh/config` 是连接四字段的唯一来源**(`0.8.50+`,issue #50)—— 保存机器时把 主机/用户/端口/私钥 写入 `~/.ssh/config` 的受管块(`# dsh-remote: managed` 标记,只动自己的块;你手写的同名 Host 块绝不覆盖,会提示冲突)。之后**改这个文件立即生效**:任何 Host 块点名的主机(含字面 IP),连接都按文件解析(裸 `Host *` 是通用默认,不算点名);机器记录里的副本只做兜底,插件自己的状态(最近工作区/延迟等)仍存注册表。
|
|
58
58
|
- **不改动 `dsh-workspace` 官方代码** —— 全部作为普通插件实现。
|
|
59
|
+
- **终端界面(dsh-tui)里也有页面** —— `/dsh-remote` 打开服务器管理页(增删改/连接/浏览远端目录/设为工作区),新建工作区时可直接挑远程项目目录;同样不改 dsh-tui 本体。见下文「在 TUI 里使用」。
|
|
59
60
|
|
|
60
61
|
## 安装
|
|
61
62
|
|
|
@@ -143,6 +144,67 @@ dsh plugin add dsh-remote
|
|
|
143
144
|
指向它的绝对路径即可(设置 → 远程工作区 → 机器行「编辑」→ 高级配置 → 远端 dsh 命令,
|
|
144
145
|
或全局 `webAttachCommand`)。
|
|
145
146
|
|
|
147
|
+
## 在 TUI(dsh-tui)里使用
|
|
148
|
+
|
|
149
|
+
插件自带 TUI 适配层:装进 `dsh-tui` profile 后,同一份机器清单、SSH 池和远程工作区在
|
|
150
|
+
终端界面里也能用,**不修改 dsh-tui 本体**(全部通过它的插件接缝注册)。
|
|
151
|
+
|
|
152
|
+
### 安装
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# 已装好 dsh-tui 的机器只需这一条;本地开发可直接给绝对路径
|
|
156
|
+
dsh plugin --profile dsh-tui add dsh-remote
|
|
157
|
+
# 或:dsh plugin --profile dsh-tui add /path/to/dsh-remote
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
装完重启 dsh-tui 生效。Web profile 不受影响:TUI 层在缺少终端接缝时静默关闭,不注册任何东西。
|
|
161
|
+
|
|
162
|
+
### `/dsh-remote` —— 终端里的远程服务器设置页
|
|
163
|
+
|
|
164
|
+
输入 `/dsh-remote` 打开全屏设置页(命令同时出现在 `/` 补全与 `/help` 里):
|
|
165
|
+
|
|
166
|
+
- `↑/↓` 选机器,`Enter` 打开动作菜单;`a` 新增、`e` 编辑、`d` 删除、`t` 测试连接、
|
|
167
|
+
`c` 连接、`b` 浏览远端目录、`m` 工作区管理、`Esc`/`q` 关闭(子视图里先返回列表)。
|
|
168
|
+
- 新增/编辑:名称、主机、端口、用户名、认证方式(密码 / 私钥 / SSH agent)、密码或私钥路径、
|
|
169
|
+
分组。密码**只写不回显**,编辑时留空表示不修改。
|
|
170
|
+
- `b` 浏览远端目录,按 `w` 把当前目录**设为工作区**:自动建本地镜像、注册成 DSH 工作区
|
|
171
|
+
(标题形如 `机器名-目录名`),并按配置切换过去。
|
|
172
|
+
|
|
173
|
+
### 新建工作区时直接选远程目录
|
|
174
|
+
|
|
175
|
+
- `/workspace` 的选择器里除了本地目录,还会列出每台机器的最近远程项目(徽标是机器名,
|
|
176
|
+
说明是 `user@host:/远端路径`)。选中即在新会话里打开该远程工作区。
|
|
177
|
+
- 也可以直接给 URI:`/workspace open dshremote://<机器名或 id>/<URL 编码的远端绝对路径>`。
|
|
178
|
+
- 启动参数同样支持:`dst dshremote://<机器>/<远端路径>`。
|
|
179
|
+
- 列出的条目数量由下面的配置控制。
|
|
180
|
+
|
|
181
|
+
### 工作区清理(彻底删除工作区或其中对话)
|
|
182
|
+
|
|
183
|
+
`/dsh-remote` → 选中机器 → `m`(工作区管理)列出该机器全部本地镜像:
|
|
184
|
+
|
|
185
|
+
- `d`:**彻底删除该工作区** —— 依次删除它的全部对话、工作区登记、每台机器「最近工作区」
|
|
186
|
+
里的该远端路径,最后删掉本地镜像目录
|
|
187
|
+
`$DSH_HOME/remote-workspaces/<host>-<user>-<port>/<name>`。删最近项是必须的,
|
|
188
|
+
否则 provider 下次启动会按它把镜像重建回来。
|
|
189
|
+
- `x`:只删该工作区下的对话(保留镜像与登记);被占用的会话会跳过并在提示里报数。
|
|
190
|
+
- `r`:只把远端路径移出「最近列表 / 当前工作区」(不删镜像、不删对话),用来止住自动重建。
|
|
191
|
+
- 仍有会话在使用该工作区时 `d` 会被拒绝(先 `Ctrl+X` 停止后台会话,或退出对应终端);
|
|
192
|
+
删除不可恢复,且**不会**动远端目录(远端文件仍由你自己管理)。
|
|
193
|
+
|
|
194
|
+
### 配置
|
|
195
|
+
|
|
196
|
+
TUI 适配层的五个开关与其它配置项一样写在 `cordis.patch.yml` 的同一行里,见下文
|
|
197
|
+
[配置](#配置) 表的最后五行(`tui` / `tuiProviderListRecent` / `tuiProviderRecentLimit` /
|
|
198
|
+
`tuiAutoSwitchWorkspace` / `tuiLang`)。
|
|
199
|
+
|
|
200
|
+
### 说明
|
|
201
|
+
|
|
202
|
+
- 远程项目在终端里以**本地镜像目录**呈现:那才是会话 cwd,也是 `rw_*` 工具工作的地方;
|
|
203
|
+
远端路径只出现在标题/描述里。远程身份由镜像里的 `.dsh-remote-meta.json` 决定。
|
|
204
|
+
- 工作区 provider 是 dsh-tui 较新的接缝:旧版 dsh-tui(或没挂载该服务的 profile)上,
|
|
205
|
+
`/dsh-remote` 页面与工作区台账仍然可用,只是选择器里不会出现远程项目。
|
|
206
|
+
- 设计与宿主 API 契约见 [docs/tui.md](docs/tui.md)。
|
|
207
|
+
|
|
146
208
|
## 可选:CLI 默认机
|
|
147
209
|
|
|
148
210
|
可在 `cordis.patch.yml` 提供默认机:
|
|
@@ -246,6 +308,11 @@ scripts/dev-run.sh --status # 是否在运行
|
|
|
246
308
|
| `webAttachDshHome` | string | `''` | 远端启动时导出的 `DSH_HOME`;留空则复用远端用户自己的 harness home |
|
|
247
309
|
| `webAttachWaitSeconds` | int | `45` | 等待远端 `dsh web` 打印启动令牌的秒数 |
|
|
248
310
|
| `localTools` | string | `bootstrap` | 本地 session(cwd 不在远程镜像里)暴露哪些 `rw_*` 工具(issue #63):`bootstrap`=只暴露配置闭环 `rw_machines`/`rw_connect`/`rw_pick_workspace`(其余 19 个在本地 session 必然报 "this session is LOCAL",暴露只是白占 schema);`none`=完全不暴露 `rw_*`(远程配置只能走设置页);`all`=旧的 full 暴露。远程 session(cwd 在镜像里)不受影响,始终全量;非法值按 `bootstrap` 处理 |
|
|
311
|
+
| `tui` | bool | `true` | TUI 适配层(dsh-tui)总开关;`false` 时不注册 `/dsh-remote` 页面、工作区 provider 与状态行字段 |
|
|
312
|
+
| `tuiProviderListRecent` | bool | `true` | TUI 工作区选择器里是否列出每台机器最近的远程项目 |
|
|
313
|
+
| `tuiProviderRecentLimit` | int | `5` | 每台机器列出的最近远程项目数(1–20) |
|
|
314
|
+
| `tuiAutoSwitchWorkspace` | bool | `true` | 在 `/dsh-remote` 页面「设为工作区」后立即把会话切过去 |
|
|
315
|
+
| `tuiLang` | string | `''` | TUI 层文案语言:`''`=自动(中文;`DSH_TUI_LANG=en` 时英文),`zh` / `en` 固定 |
|
|
249
316
|
| `remoteSandbox` | string | `inherit` | 远程操作套用哪套 sandbox 语义(0.8.54+):`inherit`=读取本机 `ctx.sandboxPolicy` 已解析的模式(read-only 拒远端写入/命令/转发,workspace-write 把远端写入围在 workspace 根内,danger-full-access 全放行);`off`=不设围栏(旧行为);也可显式写某一模式。**这是遏制不是安全边界**,硬边界请在远端一侧做 |
|
|
250
317
|
| `credentialFallback` | string | `refuse` | 系统密钥库写密码失败时怎么办(0.8.54+):`refuse`=不保存该密码(机器照常保存,界面报出失败原因,绝不把明文写进 machines.json);`plain`=旧的明文回退(会警告)。主动关掉加密(`backend: plain`)不受影响 |
|
|
251
318
|
|
package/lib/client.js
CHANGED
|
@@ -39,6 +39,23 @@ window.__ModuleLoader__.load({
|
|
|
39
39
|
const Primitives = (() => { try { return require('@deepseek-ai/dsh-client-ui-primitives') } catch { return null } })()
|
|
40
40
|
const MarkdownText = (Primitives && typeof Primitives.MarkdownText === 'function') ? Primitives.MarkdownText : null
|
|
41
41
|
|
|
42
|
+
// Issue #93: floating layers must PORTAL to document.body, exactly like the
|
|
43
|
+
// host's own Modal/Menu (and like our context menu already does with an
|
|
44
|
+
// imperative append). An inline `position:fixed` overlay stays inside the
|
|
45
|
+
// slot's subtree, where the 0.2.2 conversation layout traps its pointer
|
|
46
|
+
// events in existing sessions — the dialog paints but every click falls
|
|
47
|
+
// through to the chat text beneath (the hero page has no such container,
|
|
48
|
+
// which is why new sessions "worked"). react-dom is in the host's shared
|
|
49
|
+
// module map; when it is not (older host, test harness), keep the inline
|
|
50
|
+
// overlay rather than lose the dialog altogether.
|
|
51
|
+
const ReactDOM = (() => { try { return require('react-dom') } catch { return null } })()
|
|
52
|
+
const portalize = (element) => (
|
|
53
|
+
ReactDOM && typeof ReactDOM.createPortal === 'function'
|
|
54
|
+
&& typeof document !== 'undefined' && document.body
|
|
55
|
+
? ReactDOM.createPortal(element, document.body)
|
|
56
|
+
: element
|
|
57
|
+
)
|
|
58
|
+
|
|
42
59
|
// ── i18n (issue #14) ────────────────────────────────────────────────────
|
|
43
60
|
// Dictionaries are keyed by stable ids, zh + en share the same key set.
|
|
44
61
|
// Lookup order: active locale → en fallback → the key itself (so a missing
|
|
@@ -2546,7 +2563,7 @@ window.__ModuleLoader__.load({
|
|
|
2546
2563
|
onClick: () => switchTab(t),
|
|
2547
2564
|
style: { ...buttonS, fontWeight: tab === t ? 700 : 400, padding: '5px 14px' },
|
|
2548
2565
|
}, lbl)
|
|
2549
|
-
return React.createElement('div', {
|
|
2566
|
+
return portalize(React.createElement('div', {
|
|
2550
2567
|
style: { position: 'fixed', inset: 0, zIndex: 9999, background: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 16 },
|
|
2551
2568
|
onMouseDown: backdrop.onMouseDown,
|
|
2552
2569
|
// Issue #52 defect 1: this was a bare onClick, so picking an autocomplete
|
|
@@ -2656,7 +2673,7 @@ window.__ModuleLoader__.load({
|
|
|
2656
2673
|
onCancel: () => setMkdirPrompt(false),
|
|
2657
2674
|
}) : null,
|
|
2658
2675
|
),
|
|
2659
|
-
)
|
|
2676
|
+
))
|
|
2660
2677
|
}
|
|
2661
2678
|
|
|
2662
2679
|
// ── better-sidebar integration (optional) ────────────────────────────────
|