@rjfan/dsh-presence 0.18.0 → 0.19.0
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 +94 -9
- package/README.zh-CN.md +47 -9
- package/client.js +1 -1
- package/index.js +7 -1
- package/package.json +1 -1
- package/src/core/connection.mjs +125 -31
- package/src/destructive.js +265 -12
- package/src/pool.js +136 -11
- package/src/tools.js +646 -137
package/README.md
CHANGED
|
@@ -52,12 +52,50 @@ connection per host**. The remote side is still plain `sshd`: SFTP, exec and a P
|
|
|
52
52
|
- **Job notifications** — a toast (and desktop notification) when a background job finishes, plus an optional local `notifyCommand` for daemons and phones.
|
|
53
53
|
- **Two shells, one kernel** — the DSH plugin and a standalone MCP server (`presence-mcp`, 20 core tools) share the same kernel.
|
|
54
54
|
|
|
55
|
+
## Screenshots
|
|
56
|
+
|
|
57
|
+
<table>
|
|
58
|
+
<tr>
|
|
59
|
+
<td width="50%"><img src="docs/social/screenshot/setting.png" alt="Settings — Presence hosts and workspaces" /></td>
|
|
60
|
+
<td width="50%"><img src="docs/social/screenshot/explorer.png" alt="Remote workspace: file tree and editor" /></td>
|
|
61
|
+
</tr>
|
|
62
|
+
<tr>
|
|
63
|
+
<td align="center"><sub><b>Settings</b> — every host and folder is a local tab</sub></td>
|
|
64
|
+
<td align="center"><sub><b>Remote workspace</b> — the file tree and editor are on the host</sub></td>
|
|
65
|
+
</tr>
|
|
66
|
+
<tr>
|
|
67
|
+
<td width="50%"><img src="docs/social/screenshot/@file.png" alt="@ completes remote file paths" /></td>
|
|
68
|
+
<td width="50%"><img src="docs/social/screenshot/terminal.png" alt="A terminal on the remote host" /></td>
|
|
69
|
+
</tr>
|
|
70
|
+
<tr>
|
|
71
|
+
<td align="center"><sub><b>@ files</b> — completion indexes the remote machine itself</sub></td>
|
|
72
|
+
<td align="center"><sub><b>Terminal</b> — the shell runs on the host, not locally</sub></td>
|
|
73
|
+
</tr>
|
|
74
|
+
</table>
|
|
75
|
+
|
|
76
|
+
### A real trajectory
|
|
77
|
+
|
|
78
|
+
These three figures are rendered from a recorded 22-call session: train an MNIST
|
|
79
|
+
classifier twice on a remote 64-core host, compare two learning rates, and export
|
|
80
|
+
the result. Every row is one tool call with its measured duration; red rows are
|
|
81
|
+
real failures kept verbatim.
|
|
82
|
+
|
|
83
|
+
<p align="center"><img src="docs/social/figures/figure-1.png" width="100%" alt="Trajectory 1/3: staging the data and launching the first run" /></p>
|
|
84
|
+
|
|
85
|
+
<p align="center"><img src="docs/social/figures/figure-2.png" width="100%" alt="Trajectory 2/3: comparing the two runs and archiving" /></p>
|
|
86
|
+
|
|
87
|
+
<p align="center"><img src="docs/social/figures/figure-3.png" width="100%" alt="Time composition: Presence vs bare SSH" /></p>
|
|
88
|
+
|
|
89
|
+
<sub>Figure 3 stacks end-to-end time into think / wait / tool. The same task through
|
|
90
|
+
bare <code>ssh</code>/<code>scp</code> takes 452.5 s versus 272.8 s, with a 10.4×
|
|
91
|
+
higher median interaction latency.</sub>
|
|
92
|
+
|
|
55
93
|
## Quick start
|
|
56
94
|
|
|
57
95
|
### 1. Install the DSH plugin
|
|
58
96
|
|
|
59
97
|
```sh
|
|
60
|
-
dsh plugin add @rjfan/dsh-presence@0.
|
|
98
|
+
dsh plugin add @rjfan/dsh-presence@0.19.0
|
|
61
99
|
```
|
|
62
100
|
|
|
63
101
|
From a source checkout:
|
|
@@ -147,17 +185,19 @@ Measured on a Linux host:
|
|
|
147
185
|
## Tools
|
|
148
186
|
|
|
149
187
|
The DSH plugin exposes 24 tools. Relative paths resolve against the active remote
|
|
150
|
-
workspace; `host` is optional inside a workspace.
|
|
188
|
+
workspace; `host` is optional inside a workspace. Argument aliases are accepted at
|
|
189
|
+
execute time (`path`/`file_path`/`filePath`, `old`/`oldString`, `new`/`newString`,
|
|
190
|
+
`cwd`/`workdir`, …), so an agent's habitual spelling still works.
|
|
151
191
|
|
|
152
192
|
<details>
|
|
153
193
|
<summary><b>Show all tools</b></summary>
|
|
154
194
|
|
|
155
195
|
| Tool | What it does |
|
|
156
196
|
| --- | --- |
|
|
157
|
-
| `presence_hosts` | List SSH hosts, connection state and default host (`format:"json"` supported). |
|
|
197
|
+
| `presence_hosts` | List SSH hosts, connection state and default host (`format:"json"` supported); `workspace:true` marks this session's machine and `degraded` rows carry the last error. |
|
|
158
198
|
| `presence_connect` | Open/reuse a host connection and report the remote identity. |
|
|
159
|
-
| `presence_exec` | Run a remote command; `pipefail`, `background`, `waitForPort`, structured JSON, `
|
|
160
|
-
| `presence_jobs` | Manage background jobs: `list`, `status`, `wait` (with `timedOut`), `stop` (with `alive`/`verified`/`portReleased`)
|
|
199
|
+
| `presence_exec` | Run a remote command; `pipefail`, `background`, `waitForPort`, structured JSON, `saveStdout`; timeout defaults to 300 s; `progress` normalizes `\r` progress bars; long output spills to a remote file whose path is returned (`tailLines`); destructive commands dry-run first — `confirm:true` runs the whole command, `confirm:"partial"` runs only the safe segments. |
|
|
200
|
+
| `presence_jobs` | Manage background jobs: `list`, `status`, `wait` (with `timedOut`), `stop` (with `alive`/`verified`/`portReleased`). |
|
|
161
201
|
| `presence_read` | Read a remote file with line numbers, `offset`/`limit`, or `raw:true` for verbatim content. |
|
|
162
202
|
| `presence_read_many` | Read 1–50 remote files in one call, with per-file byte/line caps. |
|
|
163
203
|
| `presence_write` | Atomic write; preserves mode; returns real mode, size and full sha256; `ifMatchSha256` optimistic lock. |
|
|
@@ -182,13 +222,46 @@ workspace; `host` is optional inside a workspace.
|
|
|
182
222
|
|
|
183
223
|
</details>
|
|
184
224
|
|
|
225
|
+
## Which tool for which path
|
|
226
|
+
|
|
227
|
+
| Where the path lives | Use | Why |
|
|
228
|
+
| --- | --- | --- |
|
|
229
|
+
| under the workspace mount (`~/.dsh/presence/mounts/...`) | built-in `read` / `write` / `edit` / `grep` / `glob` | they reach the remote machine; `grep`/`glob` are routed to it automatically |
|
|
230
|
+
| remote absolute path outside the workspace (`/data`, `/scratch`, GPFS) | `presence_read` / `write` / `edit` / `ls` / `stat` / `search` / `glob` | the built-in tools only see the mount, not the rest of the machine |
|
|
231
|
+
| local file, for preview or Office | `presence_export` first, then `present` / native open | the mount path is virtual; only a real local file can be opened natively |
|
|
232
|
+
|
|
233
|
+
## Long-running tasks
|
|
234
|
+
|
|
235
|
+
`presence_exec` waits for the command, so it is the wrong tool for a training run, a
|
|
236
|
+
build or a server. The recipe:
|
|
237
|
+
|
|
238
|
+
```text
|
|
239
|
+
1. presence_exec { command: "python train.py --lr 3e-4", background: true, label: "train-3e-4" }
|
|
240
|
+
→ { id: "j7", pid: 4242, logPath: "/tmp/presence-jobs/....log" }
|
|
241
|
+
2. presence_jobs { action: "wait", jobId: "j7" } # blocks; no sleep+poll loop
|
|
242
|
+
3. presence_tail { path: "/tmp/presence-jobs/....log", follow: true } # watch progress
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
- `presence_jobs { action: "status" }` gives a snapshot; `stop` terminates the process
|
|
246
|
+
group and verifies the port was released.
|
|
247
|
+
- Interactive work: `presence_terminal { action: "open", session: "train" }` — the
|
|
248
|
+
tmux-backed session survives a reconnect.
|
|
249
|
+
- **Do not** start `nohup`, `tmux new-session` or a trailing `&` inside a foreground
|
|
250
|
+
`presence_exec` for long jobs: when the login-node session ends, the cluster may clean
|
|
251
|
+
up what it left behind. `background: true` already detaches through `setsid`; on
|
|
252
|
+
scheduler-managed clusters start the job with the cluster's own launcher
|
|
253
|
+
(`rlaunch -d …`, `salloc`/`sbatch`) so the scheduler owns it.
|
|
254
|
+
|
|
185
255
|
## Safety and recovery
|
|
186
256
|
|
|
187
257
|
- **Destructive commands never run unconfirmed.** `rm -r/-f`, `git clean -fdx`,
|
|
188
258
|
`find -delete`, `shred`, `truncate` and `dd of=` are first replayed with
|
|
189
259
|
no-op shims to expand their real targets; unset variables and protected paths
|
|
190
260
|
(workspace root, home, `/`, `.git`) are refused. A second call with
|
|
191
|
-
`confirm:true` is required to execute.
|
|
261
|
+
`confirm:true` is required to execute. A rejected mixed command also lists the
|
|
262
|
+
segments that are safe on their own (`Safe segments you can run separately:`);
|
|
263
|
+
`confirm:"partial"` runs only those. Removing one file with a literal
|
|
264
|
+
`rm -f <path>` inside the workspace is exempt — it cannot remove a tree.
|
|
192
265
|
- **Prefer `presence_trash`** for anything under a workspace: it moves the path to
|
|
193
266
|
`~/.dsh/presence-trash/<timestamp>/` and returns a restore hint.
|
|
194
267
|
- **Writes are atomic** and keep the original file mode; `presence_edit` refuses
|
|
@@ -221,11 +294,11 @@ npm install --prefix .dev --ignore-scripts rollup @rollup/plugin-node-resolve \
|
|
|
221
294
|
@rollup/plugin-commonjs @rollup/plugin-terser codemirror @codemirror/language-data \
|
|
222
295
|
@codemirror/theme-one-dark
|
|
223
296
|
node scripts/build-client.mjs
|
|
224
|
-
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/package.ps1 -Version 0.
|
|
225
|
-
# → dist/presence-plugin-0.
|
|
297
|
+
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/package.ps1 -Version 0.19.0
|
|
298
|
+
# → dist/presence-plugin-0.19.0.zip
|
|
226
299
|
```
|
|
227
300
|
|
|
228
|
-
Tests
|
|
301
|
+
Tests need one SSH-reachable host unless noted:
|
|
229
302
|
|
|
230
303
|
```sh
|
|
231
304
|
node scripts/regression.mjs server # transport, symlinks, timeouts, binary
|
|
@@ -233,6 +306,7 @@ node scripts/job-probe.mjs server # jobs + port forwarding
|
|
|
233
306
|
node scripts/feature-probe.mjs server # wait, sessions, pgid, readiness, search
|
|
234
307
|
node scripts/import-probe.mjs server # local→remote binary, mode, sha256
|
|
235
308
|
node scripts/safety-probe.mjs server # destructive guard + observation store
|
|
309
|
+
node scripts/p0-guard-probe.mjs # guard split/partial/single-rm (no host needed)
|
|
236
310
|
```
|
|
237
311
|
|
|
238
312
|
## Known limits
|
|
@@ -240,6 +314,17 @@ node scripts/safety-probe.mjs server # destructive guard + observation store
|
|
|
240
314
|
- The mount path (`~/.dsh/presence/mounts/...`) is virtual: it works through
|
|
241
315
|
`ctx.fs` / agent tools, but native apps (Explorer, Office, host Python) cannot
|
|
242
316
|
see it — use `presence_export` first.
|
|
317
|
+
- `presence_exec` defaults to a 300 s timeout; longer work must use `background: true`
|
|
318
|
+
(foreground output over the cap is spilled to a remote file, and `tailLines` keeps the
|
|
319
|
+
end of a long report). Background jobs are tracked per session, not persisted across a
|
|
320
|
+
DSH restart.
|
|
321
|
+
- A login node may clean up processes left behind when a session closes: do not rely on
|
|
322
|
+
`nohup` / `tmux new-session` / `&` started inside a foreground `presence_exec` for long
|
|
323
|
+
jobs. Use `background: true` (detached with `setsid`) or `presence_terminal` (tmux),
|
|
324
|
+
and let the cluster scheduler own the job where required (`rlaunch -d`, `salloc`/`sbatch`).
|
|
325
|
+
- Argument aliases are accepted (`path`/`file_path`/`filePath`, `old`/`oldString`,
|
|
326
|
+
`new`/`newString`, `replaceAll`/`replace_all`, `dryRun`/`dry_run`, `cwd`/`workdir`);
|
|
327
|
+
the canonical name is what the tool schema documents.
|
|
243
328
|
- SSH `MaxSessions` (default 10) bounds concurrent channels; the pool uses at most 8.
|
|
244
329
|
- PTY terminals follow the connection; cross-reconnect shells need `tmux` on the target
|
|
245
330
|
(used automatically by `presence_terminal { session }`).
|
package/README.zh-CN.md
CHANGED
|
@@ -56,7 +56,7 @@ Presence 让 agent 留在本机,**通过每台机器一条常驻 SSH 连接把
|
|
|
56
56
|
### 1. 安装 DSH 插件
|
|
57
57
|
|
|
58
58
|
```sh
|
|
59
|
-
dsh plugin add @rjfan/dsh-presence@0.
|
|
59
|
+
dsh plugin add @rjfan/dsh-presence@0.19.0
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
从源码安装:
|
|
@@ -143,17 +143,19 @@ Linux 主机上的实测:
|
|
|
143
143
|
|
|
144
144
|
## 工具
|
|
145
145
|
|
|
146
|
-
DSH 插件提供 24 个工具。相对路径相对当前远程工作区;在工作区里 `host`
|
|
146
|
+
DSH 插件提供 24 个工具。相对路径相对当前远程工作区;在工作区里 `host` 可省略。执行时接受
|
|
147
|
+
参数别名(`path`/`file_path`/`filePath`、`old`/`oldString`、`new`/`newString`、
|
|
148
|
+
`cwd`/`workdir` 等),习惯写法都能用。
|
|
147
149
|
|
|
148
150
|
<details>
|
|
149
151
|
<summary><b>展开全部工具</b></summary>
|
|
150
152
|
|
|
151
153
|
| 工具 | 说明 |
|
|
152
154
|
| --- | --- |
|
|
153
|
-
| `presence_hosts` | 列出 SSH 主机、连接状态和默认主机(支持 `format:"json"
|
|
155
|
+
| `presence_hosts` | 列出 SSH 主机、连接状态和默认主机(支持 `format:"json"`);`workspace:true` 标记当前会话的机器,`degraded` 行带最后一次错误。 |
|
|
154
156
|
| `presence_connect` | 建立/复用连接并返回远端身份。 |
|
|
155
|
-
| `presence_exec` | 执行远端命令;`pipefail`、`background`、`waitForPort`、结构化 JSON、`
|
|
156
|
-
| `presence_jobs` | 后台任务:`list`、`status`、`wait`(含 `timedOut`)、`stop`(`alive`/`verified`/`portReleased
|
|
157
|
+
| `presence_exec` | 执行远端命令;`pipefail`、`background`、`waitForPort`、结构化 JSON、`saveStdout`;超时默认 300 秒;`progress` 归一化 `\r` 进度条;超长输出落盘到远端文件并返回路径(`tailLines`);危险命令先 dry-run —— `confirm:true` 执行整条命令,`confirm:"partial"` 只执行安全分段。 |
|
|
158
|
+
| `presence_jobs` | 后台任务:`list`、`status`、`wait`(含 `timedOut`)、`stop`(`alive`/`verified`/`portReleased`)。 |
|
|
157
159
|
| `presence_read` | 读远端文件,带行号、`offset`/`limit`,`raw:true` 返回逐字节原样内容。 |
|
|
158
160
|
| `presence_read_many` | 一次读 1–50 个文件,支持每文件字节/行数上限。 |
|
|
159
161
|
| `presence_write` | 原子写;保留 mode;返回真实 mode、字节数和完整 sha256;`ifMatchSha256` 乐观锁。 |
|
|
@@ -178,11 +180,39 @@ DSH 插件提供 24 个工具。相对路径相对当前远程工作区;在工
|
|
|
178
180
|
|
|
179
181
|
</details>
|
|
180
182
|
|
|
183
|
+
## 路径与工具怎么选
|
|
184
|
+
|
|
185
|
+
| 路径在哪 | 用什么 | 说明 |
|
|
186
|
+
| --- | --- | --- |
|
|
187
|
+
| 工作区挂载目录下(`~/.dsh/presence/mounts/...`) | 内置 `read` / `write` / `edit` / `grep` / `glob` | 都直达远端机器;`grep`/`glob` 会自动路由过去 |
|
|
188
|
+
| 工作区之外的远端绝对路径(`/data`、`/scratch`、GPFS) | `presence_read` / `write` / `edit` / `ls` / `stat` / `search` / `glob` | 内置工具只看得到挂载目录,看不到整台机器 |
|
|
189
|
+
| 本地文件(预览或 Office) | 先 `presence_export`,再 `present` / 原生打开 | 挂载路径是虚拟的,只有真实本地文件能被原生程序打开 |
|
|
190
|
+
|
|
191
|
+
## 长时间任务
|
|
192
|
+
|
|
193
|
+
`presence_exec` 会等命令结束,所以训练、构建、起服务都不该直接用它。推荐流程:
|
|
194
|
+
|
|
195
|
+
```text
|
|
196
|
+
1. presence_exec { command: "python train.py --lr 3e-4", background: true, label: "train-3e-4" }
|
|
197
|
+
→ { id: "j7", pid: 4242, logPath: "/tmp/presence-jobs/....log" }
|
|
198
|
+
2. presence_jobs { action: "wait", jobId: "j7" } # 阻塞等待,不要 sleep+poll
|
|
199
|
+
3. presence_tail { path: "/tmp/presence-jobs/....log", follow: true } # 看进度
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
- `presence_jobs { action: "status" }` 取快照;`stop` 结束进程组并验证端口已释放。
|
|
203
|
+
- 交互式任务:`presence_terminal { action: "open", session: "train" }`,tmux 会话跨重连存活。
|
|
204
|
+
- **不要**在前台 `presence_exec` 里用 `nohup`、`tmux new-session` 或结尾 `&` 跑长任务:登录节点
|
|
205
|
+
会话结束时,集群可能清理残留进程。`background: true` 本身用 `setsid` 脱离;调度器管理的集群
|
|
206
|
+
请用集群自己的启动器(`rlaunch -d …`、`salloc`/`sbatch`),让调度器持有任务。
|
|
207
|
+
|
|
181
208
|
## 安全与恢复
|
|
182
209
|
|
|
183
210
|
- **危险命令不会直接执行。** `rm -r/-f`、`git clean -fdx`、`find -delete`、`shred`、
|
|
184
211
|
`truncate`、`dd of=` 会先用 no-op shim 重放以展开真实目标;未设置变量和受保护路径
|
|
185
|
-
(工作区根、家目录、`/`、`.git`)直接拒绝。必须再带 `confirm:true`
|
|
212
|
+
(工作区根、家目录、`/`、`.git`)直接拒绝。必须再带 `confirm:true` 才会执行。被拒绝的
|
|
213
|
+
混合命令还会列出可单独执行的安全分段(`Safe segments you can run separately:`),
|
|
214
|
+
`confirm:"partial"` 只执行这些分段;工作区内对单个文件的字面量 `rm -f <路径>` 免检
|
|
215
|
+
(它不可能删掉一棵树)。
|
|
186
216
|
- **工作区内删除优先 `presence_trash`**:移动到 `~/.dsh/presence-trash/<时间戳>/` 并返回恢复提示。
|
|
187
217
|
- **写入是原子的**,保留原文件 mode;`presence_edit` 锚点不唯一时报错而不是猜。
|
|
188
218
|
- **读后写守卫**:内置 `read`/`write`/`edit` 共享读后写守卫;`presence_write` 也会记账,两套 API 可混用。
|
|
@@ -212,11 +242,11 @@ npm install --prefix .dev --ignore-scripts rollup @rollup/plugin-node-resolve \
|
|
|
212
242
|
@rollup/plugin-commonjs @rollup/plugin-terser codemirror @codemirror/language-data \
|
|
213
243
|
@codemirror/theme-one-dark
|
|
214
244
|
node scripts/build-client.mjs
|
|
215
|
-
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/package.ps1 -Version 0.
|
|
216
|
-
# → dist/presence-plugin-0.
|
|
245
|
+
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/package.ps1 -Version 0.19.0
|
|
246
|
+
# → dist/presence-plugin-0.19.0.zip
|
|
217
247
|
```
|
|
218
248
|
|
|
219
|
-
验证脚本需要一台可 SSH
|
|
249
|
+
验证脚本需要一台可 SSH 的主机(标注“不需要真机”的除外):
|
|
220
250
|
|
|
221
251
|
```sh
|
|
222
252
|
node scripts/regression.mjs server # 传输层、符号链接、超时、二进制
|
|
@@ -224,12 +254,20 @@ node scripts/job-probe.mjs server # 后台任务 + 端口转发
|
|
|
224
254
|
node scripts/feature-probe.mjs server # wait、会话、pgid、就绪、搜索
|
|
225
255
|
node scripts/import-probe.mjs server # 本地→远端二进制、mode、sha256
|
|
226
256
|
node scripts/safety-probe.mjs server # 危险命令护栏 + 观测状态
|
|
257
|
+
node scripts/p0-guard-probe.mjs # 危险命令分段/单文件豁免(不需要真机)
|
|
227
258
|
```
|
|
228
259
|
|
|
229
260
|
## 已知边界
|
|
230
261
|
|
|
231
262
|
- 挂载路径(`~/.dsh/presence/mounts/...`)是虚拟的:对 `ctx.fs` / agent 工具有效,
|
|
232
263
|
但 Explorer、Office、宿主 Python 看不到 —— 需要先用 `presence_export`。
|
|
264
|
+
- `presence_exec` 超时默认 300 秒;更长的任务必须用 `background: true`(前台输出超限会
|
|
265
|
+
落盘到远端文件,`tailLines` 保留长报告末尾)。后台任务按会话跟踪,不跨 DSH 重启持久化。
|
|
266
|
+
- 登录节点可能在会话结束时清理残留进程:不要在前台 `presence_exec` 里用 `nohup` /
|
|
267
|
+
`tmux new-session` / `&` 跑长任务。用 `background: true`(`setsid` 脱离)或
|
|
268
|
+
`presence_terminal`(tmux);集群上让调度器持有任务(`rlaunch -d`、`salloc`/`sbatch`)。
|
|
269
|
+
- 参数别名可用(`path`/`file_path`/`filePath`、`old`/`oldString`、`new`/`newString`、
|
|
270
|
+
`replaceAll`/`replace_all`、`dryRun`/`dry_run`、`cwd`/`workdir`);schema 里记录的是规范名。
|
|
233
271
|
- sshd 的 `MaxSessions`(默认 10)限制并发通道;连接池最多用 8 个。
|
|
234
272
|
- PTY 终端跟随连接存活;跨重连的 shell 需要目标机有 `tmux`(`presence_terminal { session }` 自动使用)。
|
|
235
273
|
- ProxyJump / ProxyCommand 会被解析并提示,但目前不用于实际拨号。
|