@rjfan/dsh-presence 0.18.0 → 0.20.1
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 +170 -16
- package/README.zh-CN.md +114 -15
- package/client.js +1 -1
- package/index.js +139 -84
- package/package.json +1 -1
- package/src/api.js +6 -1
- package/src/core/connection.mjs +125 -31
- package/src/destructive.js +265 -12
- package/src/intercept.js +150 -0
- package/src/job-store.js +292 -0
- package/src/observed-store.js +136 -19
- package/src/pool.js +444 -97
- package/src/tools.js +1458 -258
- package/src/wait.js +352 -0
package/README.md
CHANGED
|
@@ -22,6 +22,14 @@ forwards for coding agents — with **nothing installed on the remote host**.
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
+
> ⚠️ **A local session is not bound to a remote workspace.** Opening a remote workspace in
|
|
26
|
+
> Presence does not turn a local agent session into a remote one. When your session's
|
|
27
|
+
> working directory is outside the presence mounts root, `presence_exec` and every other
|
|
28
|
+
> `presence_*` tool require an explicit `host` (or `PRESENCE_HOST`); without one they refuse
|
|
29
|
+
> with the session's cwd instead of silently running on the last-activated machine. Sessions
|
|
30
|
+
> started inside a remote workspace resolve to that machine automatically. See
|
|
31
|
+
> [Known limits](#known-limits).
|
|
32
|
+
|
|
25
33
|
## Why
|
|
26
34
|
|
|
27
35
|
Running an agent on every remote machine means installing it N times, upgrading it N
|
|
@@ -44,6 +52,10 @@ connection per host**. The remote side is still plain `sshd`: SFTP, exec and a P
|
|
|
44
52
|
- **Native remote tools** — `rg`/`grep` runs on the target, commands run in the target shell; path, permission and encoding semantics are the remote ones.
|
|
45
53
|
- **Local-like edits** — atomic writes, mode preservation, ambiguity refusal, read-before-write guard, `presence_edit` with dry-run diffs.
|
|
46
54
|
- **Real workspaces** — a remote directory becomes a normal DSH workspace with a file tree and terminals that open **on the remote machine**.
|
|
55
|
+
- **Sessions are scoped (0.20 hotfix)** — a local session no longer silently borrows the last-activated remote workspace: `presence_*` needs an explicit `host` or `PRESENCE_HOST`, and the system prompt tells each session whether it is local or remote.
|
|
56
|
+
- **Conditions instead of sleeps** — `presence_wait` blocks on a file (`exists`/`minBytes`/`mtimeAfter`), a grep match count, a process (`pid`/`pattern`), a port state or a job id; a timeout is `met:false`, not an error.
|
|
57
|
+
- **Jobs that survive a restart** — background jobs are persisted to `~/.dsh/presence/jobs.json` with pid/pgid. `logPath` is the job log to tail; `commandLogPath` is the command's own log and equals `logPath` on current kernels (a separate wrapper log only exists when a kernel records one) — use `commandLogPath ?? logPath`. After a restart their state is `unknown` until probed — never a false `running`, and an exit code may be missing.
|
|
58
|
+
- **Batch transfer and append** — `presence_import`/`presence_export` take `paths[]` (1–50) or a glob and return per-file results; `presence_write { mode:"append" }` appends without rewriting the file.
|
|
47
59
|
- **Recoverable deletes** — destructive shell commands dry-run first; `presence_trash` moves paths to a remote trash with restore.
|
|
48
60
|
- **`@` completes remote files** — inside a remote workspace, file-reference completion indexes the machine itself (`rg --files`, `find` fallback), not a local mirror.
|
|
49
61
|
- **File operations, not just reads** — `presence_mkdir` / `move` / `copy` / `chmod` plus a right-click menu in the file tree; deletes go to the recoverable trash.
|
|
@@ -52,12 +64,50 @@ connection per host**. The remote side is still plain `sshd`: SFTP, exec and a P
|
|
|
52
64
|
- **Job notifications** — a toast (and desktop notification) when a background job finishes, plus an optional local `notifyCommand` for daemons and phones.
|
|
53
65
|
- **Two shells, one kernel** — the DSH plugin and a standalone MCP server (`presence-mcp`, 20 core tools) share the same kernel.
|
|
54
66
|
|
|
67
|
+
## Screenshots
|
|
68
|
+
|
|
69
|
+
<table>
|
|
70
|
+
<tr>
|
|
71
|
+
<td width="50%"><img src="docs/social/screenshot/setting.png" alt="Settings — Presence hosts and workspaces" /></td>
|
|
72
|
+
<td width="50%"><img src="docs/social/screenshot/explorer.png" alt="Remote workspace: file tree and editor" /></td>
|
|
73
|
+
</tr>
|
|
74
|
+
<tr>
|
|
75
|
+
<td align="center"><sub><b>Settings</b> — every host and folder is a local tab</sub></td>
|
|
76
|
+
<td align="center"><sub><b>Remote workspace</b> — the file tree and editor are on the host</sub></td>
|
|
77
|
+
</tr>
|
|
78
|
+
<tr>
|
|
79
|
+
<td width="50%"><img src="docs/social/screenshot/@file.png" alt="@ completes remote file paths" /></td>
|
|
80
|
+
<td width="50%"><img src="docs/social/screenshot/terminal.png" alt="A terminal on the remote host" /></td>
|
|
81
|
+
</tr>
|
|
82
|
+
<tr>
|
|
83
|
+
<td align="center"><sub><b>@ files</b> — completion indexes the remote machine itself</sub></td>
|
|
84
|
+
<td align="center"><sub><b>Terminal</b> — the shell runs on the host, not locally</sub></td>
|
|
85
|
+
</tr>
|
|
86
|
+
</table>
|
|
87
|
+
|
|
88
|
+
### A real trajectory
|
|
89
|
+
|
|
90
|
+
These three figures are rendered from a recorded 22-call session: train an MNIST
|
|
91
|
+
classifier twice on a remote 64-core host, compare two learning rates, and export
|
|
92
|
+
the result. Every row is one tool call with its measured duration; red rows are
|
|
93
|
+
real failures kept verbatim.
|
|
94
|
+
|
|
95
|
+
<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>
|
|
96
|
+
|
|
97
|
+
<p align="center"><img src="docs/social/figures/figure-2.png" width="100%" alt="Trajectory 2/3: comparing the two runs and archiving" /></p>
|
|
98
|
+
|
|
99
|
+
<p align="center"><img src="docs/social/figures/figure-3.png" width="100%" alt="Time composition: Presence vs bare SSH" /></p>
|
|
100
|
+
|
|
101
|
+
<sub>Figure 3 stacks end-to-end time into think / wait / tool. The same task through
|
|
102
|
+
bare <code>ssh</code>/<code>scp</code> takes 452.5 s versus 272.8 s, with a 10.4×
|
|
103
|
+
higher median interaction latency.</sub>
|
|
104
|
+
|
|
55
105
|
## Quick start
|
|
56
106
|
|
|
57
107
|
### 1. Install the DSH plugin
|
|
58
108
|
|
|
59
109
|
```sh
|
|
60
|
-
dsh plugin add @rjfan/dsh-presence@0.
|
|
110
|
+
dsh plugin add @rjfan/dsh-presence@0.20.1
|
|
61
111
|
```
|
|
62
112
|
|
|
63
113
|
From a source checkout:
|
|
@@ -72,6 +122,10 @@ Open **Settings → Presence → New**, choose a host from `~/.ssh/config` (or p
|
|
|
72
122
|
manual `ssh` command), then pick a remote folder. It appears as a workspace; the
|
|
73
123
|
left sidebar, file tree, terminal and agent tools all use that remote directory.
|
|
74
124
|
|
|
125
|
+
A session started in that workspace is remote. A session started anywhere else (for
|
|
126
|
+
example a normal local project) stays local: it must pass `host` explicitly — or set
|
|
127
|
+
`PRESENCE_HOST` — before calling `presence_exec` / `presence_*`.
|
|
128
|
+
|
|
75
129
|
### 3. Work
|
|
76
130
|
|
|
77
131
|
```text
|
|
@@ -146,23 +200,27 @@ Measured on a Linux host:
|
|
|
146
200
|
|
|
147
201
|
## Tools
|
|
148
202
|
|
|
149
|
-
The DSH plugin exposes
|
|
150
|
-
workspace
|
|
203
|
+
The DSH plugin exposes 25 tools. Inside a remote-workspace session relative paths
|
|
204
|
+
resolve against that workspace and `host` is optional; a **local** session must pass
|
|
205
|
+
`host` explicitly (or configure `PRESENCE_HOST`) — there is no fallback to the globally
|
|
206
|
+
last-activated workspace. Argument aliases are accepted at execute time
|
|
207
|
+
(`path`/`file_path`/`filePath`, `old`/`oldString`, `new`/`newString`, `cwd`/`workdir`, …),
|
|
208
|
+
so an agent's habitual spelling still works.
|
|
151
209
|
|
|
152
210
|
<details>
|
|
153
211
|
<summary><b>Show all tools</b></summary>
|
|
154
212
|
|
|
155
213
|
| Tool | What it does |
|
|
156
214
|
| --- | --- |
|
|
157
|
-
| `presence_hosts` | List SSH hosts, connection state and default host (`format:"json"` supported). |
|
|
215
|
+
| `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. `default` is session-scoped: this session's mounted host or `PRESENCE_HOST`, never a global last-active workspace. |
|
|
158
216
|
| `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` |
|
|
217
|
+
| `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`); text reports start with `host: <alias> remote cwd: <abs>`; destructive commands dry-run first — `confirm:true` runs the whole command, `confirm:"partial"` runs only the safe segments. A local session must pass `host`. |
|
|
218
|
+
| `presence_jobs` | Background jobs: `list`, `status`, `wait` (with `timedOut`), `stop` (with `alive`/`verified`/`portReleased`), `purge`. Jobs persist to `~/.dsh/presence/jobs.json` with `pid`/`pgid`; `logPath` is the job log to tail (the command's stdout/stderr on current kernels) and `commandLogPath` the command's own log — equal to `logPath` on current kernels, so use `commandLogPath ?? logPath`. After a restart, state is `unknown` until a liveness probe succeeds and the exit code may be missing. `/presence/api/state` job rows also carry `pid`, `pgid`, `commandLogPath` and `persisted`. |
|
|
219
|
+
| `presence_wait` | Block until exactly one remote condition is true: `file {path, exists?, minBytes?, mtimeAfter?}`, `grep {path, pattern, count?}`, `process {pid\|pattern, alive?}`, `port {port, state:"open"\|"closed"}` or `job {jobId}` (a job condition needs no `host` — the registry knows where it ran). Returns `{ met, timedOut, elapsedMs, observed, condition, polls }`; a timeout is `met:false` and **never an error**. `timeoutMs` default 120 s (cap 1 h), `pollMs`/`intervalMs` default 1000 ms. |
|
|
161
220
|
| `presence_read` | Read a remote file with line numbers, `offset`/`limit`, or `raw:true` for verbatim content. |
|
|
162
221
|
| `presence_read_many` | Read 1–50 remote files in one call, with per-file byte/line caps. |
|
|
163
|
-
| `presence_write` | Atomic write; preserves mode; returns real mode, size and full sha256; `ifMatchSha256` optimistic lock. |
|
|
222
|
+
| `presence_write` | Atomic write; preserves mode; returns real mode, size and full sha256; `ifMatchSha256` optimistic lock; `mode:"append"` appends at EOF and reports appended bytes + total size (`ifMatchSha256` is overwrite-only and append rejects it); `mode` also accepts an octal string such as `"755"` to set permissions on an overwrite. |
|
|
164
223
|
| `presence_edit` | Exact-text replacement or line-range replacement, with compact diff, `dryRun`, JSON fields. |
|
|
165
|
-
| `presence_ls` | List a directory; `recursive`, `totalBytes`, JSON output. |
|
|
166
224
|
| `presence_stat` | Batch `stat` for 1–100 paths: type, size, mode, mtime, optional sha256; missing paths return `exists:false`. |
|
|
167
225
|
| `presence_mkdir` | Create a remote directory (recursive by default); idempotent when it already exists. |
|
|
168
226
|
| `presence_move` | Rename or move a path inside the same machine; refuses to replace an existing file unless `overwrite:true`. |
|
|
@@ -172,8 +230,8 @@ workspace; `host` is optional inside a workspace.
|
|
|
172
230
|
| `presence_tail` | Read the last N lines of a remote file, or follow it for a bounded time (`follow:true`, `timeoutMs`). |
|
|
173
231
|
| `presence_search` | Remote `rg`/`grep` search, count-only mode, JSON matches. |
|
|
174
232
|
| `presence_glob` | Find remote files by glob with size/mtime. |
|
|
175
|
-
| `presence_import` | Local → remote binary-safe upload with mode and sha256. |
|
|
176
|
-
| `presence_export` | Remote → local real file (content-addressed), for preview or native apps. |
|
|
233
|
+
| `presence_import` | Local → remote binary-safe upload with mode and sha256; batch `paths[]` (1–50) and/or a local `glob` into a `to` directory, with one result per file. |
|
|
234
|
+
| `presence_export` | Remote → local real file (content-addressed), for preview or native apps; batch `paths[]` or a remote `glob`; the local name defaults to the basename and a collision becomes `name-<sha8>.ext`; one result per file. |
|
|
177
235
|
| `presence_forward` | Forward a remote port to `127.0.0.1`; open/list/close with JSON. |
|
|
178
236
|
| `presence_port_probe` | Check listeners and TCP reachability for up to 20 ports; `expect:open|closed`, polling. |
|
|
179
237
|
| `presence_http_probe` | Wait for port → forward → local HTTP assertions → close → verify the local port was released. |
|
|
@@ -182,19 +240,81 @@ workspace; `host` is optional inside a workspace.
|
|
|
182
240
|
|
|
183
241
|
</details>
|
|
184
242
|
|
|
243
|
+
### Batch transfer and append
|
|
244
|
+
|
|
245
|
+
```text
|
|
246
|
+
presence_export { paths: ["/data/a.pt", "/data/b.pt"] } # files:[{remotePath, localPath, bytes, sha256}]
|
|
247
|
+
presence_export { glob: "/data/ckpt-*.pt" } # every match; name-<sha8>.ext on collision
|
|
248
|
+
presence_import { glob: "dist/*.whl", to: "/data/wheels" } # local → remote, one result per file
|
|
249
|
+
presence_write { path: "/data/train.log", mode: "append", content: "epoch 6 done\n" }
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Single-path calls keep the existing `path`/`to` behaviour, so none of this is required for
|
|
253
|
+
one file.
|
|
254
|
+
|
|
255
|
+
## Which tool for which path
|
|
256
|
+
|
|
257
|
+
| Where the path lives | Use | Why |
|
|
258
|
+
| --- | --- | --- |
|
|
259
|
+
| 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 |
|
|
260
|
+
| remote absolute path outside the workspace (`/data`, `/scratch`, GPFS) | `presence_read` / `write` / `edit` / `stat` / `search` / `glob` (list a directory with `presence_exec`) | the built-in tools only see the mount, not the rest of the machine |
|
|
261
|
+
| 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 |
|
|
262
|
+
|
|
263
|
+
In a **local** session (cwd outside the mounts root) the built-in tools only touch local
|
|
264
|
+
files; every `presence_*` call needs `host` explicitly, or configure `PRESENCE_HOST` once.
|
|
265
|
+
|
|
266
|
+
## Long-running tasks
|
|
267
|
+
|
|
268
|
+
`presence_exec` waits for the command, so it is the wrong tool for a training run, a
|
|
269
|
+
build or a server. The recipe:
|
|
270
|
+
|
|
271
|
+
```text
|
|
272
|
+
1. presence_exec { command: "python train.py --lr 3e-4", background: true, label: "train-3e-4" }
|
|
273
|
+
→ { id: "j7", pid: 4242, pgid: 4242, logPath: ".../job.log", commandLogPath: ".../job.out" }
|
|
274
|
+
2. presence_jobs { action: "wait", jobId: "j7" } # or presence_wait { job: { jobId: "j7" } }
|
|
275
|
+
3. presence_tail { path: <logPath>, follow: true } # watch progress
|
|
276
|
+
presence_wait { grep: { path: <logPath>, pattern: "epoch 6", count: 1 } } # readiness
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
- `presence_jobs { action: "status" }` gives a snapshot; `stop` terminates the process
|
|
280
|
+
group and verifies the port was released. Jobs persist to `~/.dsh/presence/jobs.json`:
|
|
281
|
+
after a DSH restart a job shows `unknown` until a liveness probe succeeds (never a false
|
|
282
|
+
`running`) and its exit code may be missing.
|
|
283
|
+
- `logPath` is the job log to tail — the command's stdout/stderr on current kernels.
|
|
284
|
+
`commandLogPath` is the command's own log; it equals `logPath` unless the kernel records
|
|
285
|
+
a separate wrapper log, so tail `commandLogPath ?? logPath`.
|
|
286
|
+
- `presence_wait` replaces hand-written sleep+tail loops: wait for a port
|
|
287
|
+
(`presence_wait { port: { port: 8000, state: "open" } }`), a checkpoint
|
|
288
|
+
(`presence_wait { file: { path: "/data/ckpt.pt", minBytes: 1000000 } }`), a log line or
|
|
289
|
+
a process. A timeout returns `met:false` — a normal answer, not an error.
|
|
290
|
+
- Interactive work: `presence_terminal { action: "open", session: "train" }` — the
|
|
291
|
+
tmux-backed session survives a reconnect.
|
|
292
|
+
- **Do not** start `nohup`, `tmux new-session` or a trailing `&` inside a foreground
|
|
293
|
+
`presence_exec` for long jobs: when the login-node session ends, the cluster may clean
|
|
294
|
+
up what it left behind. `background: true` already detaches through `setsid`; on
|
|
295
|
+
scheduler-managed clusters start the job with the cluster's own launcher
|
|
296
|
+
(`rlaunch -d …`, `salloc`/`sbatch`) so the scheduler owns it.
|
|
297
|
+
|
|
185
298
|
## Safety and recovery
|
|
186
299
|
|
|
187
300
|
- **Destructive commands never run unconfirmed.** `rm -r/-f`, `git clean -fdx`,
|
|
188
301
|
`find -delete`, `shred`, `truncate` and `dd of=` are first replayed with
|
|
189
302
|
no-op shims to expand their real targets; unset variables and protected paths
|
|
190
303
|
(workspace root, home, `/`, `.git`) are refused. A second call with
|
|
191
|
-
`confirm:true` is required to execute.
|
|
304
|
+
`confirm:true` is required to execute. A rejected mixed command also lists the
|
|
305
|
+
segments that are safe on their own (`Safe segments you can run separately:`);
|
|
306
|
+
`confirm:"partial"` runs only those. Removing one file with a literal
|
|
307
|
+
`rm -f <path>` inside the workspace is exempt — it cannot remove a tree.
|
|
192
308
|
- **Prefer `presence_trash`** for anything under a workspace: it moves the path to
|
|
193
309
|
`~/.dsh/presence-trash/<timestamp>/` and returns a restore hint.
|
|
194
310
|
- **Writes are atomic** and keep the original file mode; `presence_edit` refuses
|
|
195
311
|
ambiguous anchors instead of guessing.
|
|
196
312
|
- **Read before write**: built-in `read`/`write`/`edit` share a read-before-write
|
|
197
|
-
guard
|
|
313
|
+
guard. Path keys are canonicalised across `presence://`, mount paths, workspace-relative
|
|
314
|
+
and absolute remote paths, and successful `presence_read` / `presence_write` /
|
|
315
|
+
`presence_edit` calls count as a read (a write counts too). `presence_exec` running
|
|
316
|
+
`cat file` does **not** count — the guard cannot see shell output; read with
|
|
317
|
+
`presence_read` or the built-in `read` before editing.
|
|
198
318
|
|
|
199
319
|
## Configuration
|
|
200
320
|
|
|
@@ -202,8 +322,9 @@ The plugin reads these environment variables on the host process:
|
|
|
202
322
|
|
|
203
323
|
| Variable | Default | Meaning |
|
|
204
324
|
| --- | --- | --- |
|
|
205
|
-
| `PRESENCE_HOST` | — | Default SSH host (
|
|
325
|
+
| `PRESENCE_HOST` | — | Default SSH host for **local** sessions (explicit opt-in; also used by the MCP server). Remote-workspace sessions ignore it in favour of their mounted host. |
|
|
206
326
|
| `PRESENCE_WORKSPACE` | — | `host:/abs/path` to bind a remote workspace. |
|
|
327
|
+
| `PRESENCE_JOBS_FILE` | `~/.dsh/presence/jobs.json` | Override the persisted background-job registry path. |
|
|
207
328
|
| `PRESENCE_PASSWORD_<ALIAS>` | — | Password for a host; `<ALIAS>` is uppercase with non-alphanumerics replaced by `_`. |
|
|
208
329
|
| `PRESENCE_MAX_CHANNELS` | 8 | Concurrent channels per connection. |
|
|
209
330
|
| `PRESENCE_CONNECT_TIMEOUT_MS` | 25000 | Connection attempt timeout. |
|
|
@@ -221,11 +342,19 @@ npm install --prefix .dev --ignore-scripts rollup @rollup/plugin-node-resolve \
|
|
|
221
342
|
@rollup/plugin-commonjs @rollup/plugin-terser codemirror @codemirror/language-data \
|
|
222
343
|
@codemirror/theme-one-dark
|
|
223
344
|
node scripts/build-client.mjs
|
|
224
|
-
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/package.ps1 -Version 0.
|
|
225
|
-
# → dist/presence-plugin-0.
|
|
345
|
+
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/package.ps1 -Version 0.20.1
|
|
346
|
+
# → dist/presence-plugin-0.20.1.zip
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
After a DSH update, prove the plugin still activates against the shipped runtime
|
|
350
|
+
(Electron-as-Node; this is what caught the 0.2.1-rc.5 tool-schema tightening):
|
|
351
|
+
|
|
352
|
+
```powershell
|
|
353
|
+
$env:ELECTRON_RUN_AS_NODE = '1'
|
|
354
|
+
& 'C:\...\DeepSeek Harness.exe' scripts\host-compat-check.mjs # ACTIVATED OK with 25 tools
|
|
226
355
|
```
|
|
227
356
|
|
|
228
|
-
Tests
|
|
357
|
+
Tests need one SSH-reachable host unless noted:
|
|
229
358
|
|
|
230
359
|
```sh
|
|
231
360
|
node scripts/regression.mjs server # transport, symlinks, timeouts, binary
|
|
@@ -233,13 +362,38 @@ node scripts/job-probe.mjs server # jobs + port forwarding
|
|
|
233
362
|
node scripts/feature-probe.mjs server # wait, sessions, pgid, readiness, search
|
|
234
363
|
node scripts/import-probe.mjs server # local→remote binary, mode, sha256
|
|
235
364
|
node scripts/safety-probe.mjs server # destructive guard + observation store
|
|
365
|
+
node scripts/p0-guard-probe.mjs # guard split/partial/single-rm (no host needed)
|
|
366
|
+
node scripts/p1-docs-probe.mjs # README/zh/RELEASE anchors (no host needed)
|
|
236
367
|
```
|
|
237
368
|
|
|
238
369
|
## Known limits
|
|
239
370
|
|
|
371
|
+
- **Local sessions.** A local session calling `presence_*` without `host` now raises
|
|
372
|
+
`this session is local (cwd=...); pass an explicit host ...`. That is the intended 0.20
|
|
373
|
+
behavior, not a bug: the old silent fallback to the last-activated remote workspace could
|
|
374
|
+
run commands on another session's machine. Pass `host` per call, or set `PRESENCE_HOST`
|
|
375
|
+
once as the explicit opt-in default; a local session still never inherits another
|
|
376
|
+
session's workspace, and `presence_hosts`' `default` is session-scoped for the same reason.
|
|
377
|
+
- The read-before-write guard cannot observe shell output: `presence_exec { command:
|
|
378
|
+
"cat file" }` (or `sed -n`) does **not** mark the file as read; `presence_read` and the
|
|
379
|
+
built-in `read` do, and `presence_write`/`presence_edit` count as reads. A guarded
|
|
380
|
+
built-in `edit` after only `exec cat` can still fail.
|
|
240
381
|
- The mount path (`~/.dsh/presence/mounts/...`) is virtual: it works through
|
|
241
382
|
`ctx.fs` / agent tools, but native apps (Explorer, Office, host Python) cannot
|
|
242
383
|
see it — use `presence_export` first.
|
|
384
|
+
- `presence_exec` defaults to a 300 s timeout; longer work must use `background: true`
|
|
385
|
+
(foreground output over the cap is spilled to a remote file, and `tailLines` keeps the
|
|
386
|
+
end of a long report). Background jobs persist to `~/.dsh/presence/jobs.json`; after a
|
|
387
|
+
DSH restart their state is `unknown` until a liveness probe succeeds — never assume a
|
|
388
|
+
persisted job is still running, and its exit code may be missing. Tail
|
|
389
|
+
`commandLogPath ?? logPath` (`logPath` holds the command output on current kernels).
|
|
390
|
+
- A login node may clean up processes left behind when a session closes: do not rely on
|
|
391
|
+
`nohup` / `tmux new-session` / `&` started inside a foreground `presence_exec` for long
|
|
392
|
+
jobs. Use `background: true` (detached with `setsid`) or `presence_terminal` (tmux),
|
|
393
|
+
and let the cluster scheduler own the job where required (`rlaunch -d`, `salloc`/`sbatch`).
|
|
394
|
+
- Argument aliases are accepted (`path`/`file_path`/`filePath`, `old`/`oldString`,
|
|
395
|
+
`new`/`newString`, `replaceAll`/`replace_all`, `dryRun`/`dry_run`, `cwd`/`workdir`);
|
|
396
|
+
the canonical name is what the tool schema documents.
|
|
243
397
|
- SSH `MaxSessions` (default 10) bounds concurrent channels; the pool uses at most 8.
|
|
244
398
|
- PTY terminals follow the connection; cross-reconnect shells need `tmux` on the target
|
|
245
399
|
(used automatically by `presence_terminal { session }`).
|
package/README.zh-CN.md
CHANGED
|
@@ -22,6 +22,12 @@
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
+
> ⚠️ **本地会话不会绑定远端工作区。** 在 Presence 里打开一个远程工作区,并不会把本地 agent
|
|
26
|
+
> 会话变成远端会话。当会话工作目录不在 presence 挂载根下时,`presence_exec` 及其他
|
|
27
|
+
> `presence_*` 工具必须显式传 `host`(或配置 `PRESENCE_HOST`);否则会带着当前 cwd 报错,
|
|
28
|
+
> 而不是静默跑到上一次激活的机器上。在远程工作区内启动的会话则自动解析到该机器。
|
|
29
|
+
> 详见[已知边界](#已知边界)。
|
|
30
|
+
|
|
25
31
|
## 为什么不是「agent 跑在远端」
|
|
26
32
|
|
|
27
33
|
在每台服务器上装 agent,意味着装 N 次、升级 N 次、逐台配代理;让本地 agent 每次
|
|
@@ -43,6 +49,10 @@ Presence 让 agent 留在本机,**通过每台机器一条常驻 SSH 连接把
|
|
|
43
49
|
- **用目标机的原生工具** —— `rg`/`grep` 在目标机跑,命令在目标 shell 里执行;路径、权限、编码都是远端的语义。
|
|
44
50
|
- **接近本地的编辑** —— 原子写、保留 mode、锚点歧义拒绝、读后写守卫、`presence_edit` 的 dry-run diff。
|
|
45
51
|
- **真正的远程工作区** —— 远端目录注册成普通 DSH 工作区,文件树、终端都**开在远端机器上**。
|
|
52
|
+
- **会话有作用域(0.20 热修)** —— 本地会话不再静默借用上一次激活的远程工作区:`presence_*` 必须显式传 `host` 或配置 `PRESENCE_HOST`;系统提示会告诉每个会话自己是本地还是远程工作区。
|
|
53
|
+
- **条件等待,不用 sleep** —— `presence_wait` 等文件(`exists`/`minBytes`/`mtimeAfter`)、grep 命中数、进程(`pid`/`pattern`)、端口状态或任务 id;超时返回 `met:false`,不是错误。
|
|
54
|
+
- **任务跨重启存活** —— 后台任务持久化到 `~/.dsh/presence/jobs.json`,带 pid/pgid;`logPath` 是要 tail 的任务日志,`commandLogPath` 是命令自己的日志(当前内核里等于 `logPath`;只有内核提供独立包装日志时才不同),用 `commandLogPath ?? logPath`;重启后状态是 `unknown` 直到探活,不会假装 `running`,退出码可能缺失。
|
|
55
|
+
- **批量传输与追加写** —— `presence_import`/`presence_export` 支持 `paths[]`(1–50)或 glob,逐文件返回;`presence_write { mode:"append" }` 追加写。
|
|
46
56
|
- **可恢复的删除** —— 危险 shell 命令先 dry-run;`presence_trash` 移入远端回收站并可恢复。
|
|
47
57
|
- **`@` 能补全远端文件** —— 远程工作区里,文件引用补全直接索引目标机(`rg --files`,回退 `find`),不是本地镜像。
|
|
48
58
|
- **文件操作不只是读** —— `presence_mkdir` / `move` / `copy` / `chmod`,文件树右键菜单直接操作;删除走可恢复回收站。
|
|
@@ -56,7 +66,7 @@ Presence 让 agent 留在本机,**通过每台机器一条常驻 SSH 连接把
|
|
|
56
66
|
### 1. 安装 DSH 插件
|
|
57
67
|
|
|
58
68
|
```sh
|
|
59
|
-
dsh plugin add @rjfan/dsh-presence@0.
|
|
69
|
+
dsh plugin add @rjfan/dsh-presence@0.20.1
|
|
60
70
|
```
|
|
61
71
|
|
|
62
72
|
从源码安装:
|
|
@@ -71,6 +81,9 @@ dsh plugin add /path/to/dsh-presence
|
|
|
71
81
|
`ssh` 命令),再选一个远端目录。它会以工作区出现:左侧文件树、终端和 agent 工具
|
|
72
82
|
都使用这个远端目录。
|
|
73
83
|
|
|
84
|
+
在该工作区里启动的会话就是远程会话;在其它地方(例如普通本地项目)启动的会话仍是本地
|
|
85
|
+
会话:调用 `presence_exec` / `presence_*` 前必须显式传 `host`,或配置 `PRESENCE_HOST`。
|
|
86
|
+
|
|
74
87
|
### 3. 开始用
|
|
75
88
|
|
|
76
89
|
```text
|
|
@@ -143,22 +156,25 @@ Linux 主机上的实测:
|
|
|
143
156
|
|
|
144
157
|
## 工具
|
|
145
158
|
|
|
146
|
-
DSH 插件提供
|
|
159
|
+
DSH 插件提供 25 个工具。在远程工作区会话里,相对路径相对该工作区、`host` 可省略;**本地**
|
|
160
|
+
会话必须显式传 `host`(或配置 `PRESENCE_HOST`)—— 不会回退到全局最后激活的工作区。执行时
|
|
161
|
+
接受参数别名(`path`/`file_path`/`filePath`、`old`/`oldString`、`new`/`newString`、
|
|
162
|
+
`cwd`/`workdir` 等),习惯写法都能用。
|
|
147
163
|
|
|
148
164
|
<details>
|
|
149
165
|
<summary><b>展开全部工具</b></summary>
|
|
150
166
|
|
|
151
167
|
| 工具 | 说明 |
|
|
152
168
|
| --- | --- |
|
|
153
|
-
| `presence_hosts` | 列出 SSH 主机、连接状态和默认主机(支持 `format:"json"
|
|
169
|
+
| `presence_hosts` | 列出 SSH 主机、连接状态和默认主机(支持 `format:"json"`);`workspace:true` 标记当前会话的机器,`degraded` 行带最后一次错误。`default` 是会话作用域:当前会话的挂载主机或 `PRESENCE_HOST`,绝不是全局最后激活的工作区。 |
|
|
154
170
|
| `presence_connect` | 建立/复用连接并返回远端身份。 |
|
|
155
|
-
| `presence_exec` | 执行远端命令;`pipefail`、`background`、`waitForPort`、结构化 JSON、`
|
|
156
|
-
| `presence_jobs` | 后台任务:`list`、`status`、`wait`(含 `timedOut`)、`stop`(`alive`/`verified`/`portReleased`)、`purge`。 |
|
|
171
|
+
| `presence_exec` | 执行远端命令;`pipefail`、`background`、`waitForPort`、结构化 JSON、`saveStdout`;超时默认 300 秒;`progress` 归一化 `\r` 进度条;超长输出落盘到远端文件并返回路径(`tailLines`);文本报告以 `host: <alias> remote cwd: <abs>` 开头;危险命令先 dry-run —— `confirm:true` 执行整条命令,`confirm:"partial"` 只执行安全分段。本地会话必须传 `host`。 |
|
|
172
|
+
| `presence_jobs` | 后台任务:`list`、`status`、`wait`(含 `timedOut`)、`stop`(`alive`/`verified`/`portReleased`)、`purge`。任务持久化到 `~/.dsh/presence/jobs.json`,含 `pid`/`pgid`;`logPath` 是要 tail 的任务日志(当前内核里即命令 stdout/stderr),`commandLogPath` 是命令自己的日志(当前内核里等于 `logPath`,用 `commandLogPath ?? logPath`)。重启后状态为 `unknown` 直到探活成功,退出码可能缺失。`/presence/api/state` 的 job 行还带 `pid`、`pgid`、`commandLogPath`、`persisted`。 |
|
|
173
|
+
| `presence_wait` | 阻塞直到某个远端条件成立(五选一):`file {path, exists?, minBytes?, mtimeAfter?}`、`grep {path, pattern, count?}`、`process {pid\|pattern, alive?}`、`port {port, state:"open"\|"closed"}`、`job {jobId}`(job 条件不需要 `host`,注册表知道它跑在哪台机器)。返回 `{ met, timedOut, elapsedMs, observed, condition, polls }`;超时是 `met:false`,**不会报错**。`timeoutMs` 默认 120 秒(上限 1 小时),`pollMs`/`intervalMs` 默认 1000 ms。 |
|
|
157
174
|
| `presence_read` | 读远端文件,带行号、`offset`/`limit`,`raw:true` 返回逐字节原样内容。 |
|
|
158
175
|
| `presence_read_many` | 一次读 1–50 个文件,支持每文件字节/行数上限。 |
|
|
159
|
-
| `presence_write` | 原子写;保留 mode;返回真实 mode、字节数和完整 sha256;`ifMatchSha256`
|
|
176
|
+
| `presence_write` | 原子写;保留 mode;返回真实 mode、字节数和完整 sha256;`ifMatchSha256` 乐观锁;`mode:"append"` 在 EOF 追加,返回追加字节数和总大小(`ifMatchSha256` 仅 overwrite 有效,append 传入会直接报错);`mode` 也可传八进制字符串(如 `"755"`)设置 overwrite 权限。 |
|
|
160
177
|
| `presence_edit` | 精确文本替换或行号区间替换,带紧凑 diff、`dryRun` 和 JSON 字段。 |
|
|
161
|
-
| `presence_ls` | 列目录;`recursive`、`totalBytes`、JSON 输出。 |
|
|
162
178
|
| `presence_stat` | 批量 `stat` 1–100 个路径:类型、大小、mode、mtime、可选 sha256;缺失路径返回 `exists:false`。 |
|
|
163
179
|
| `presence_mkdir` | 创建远端目录(默认递归);已存在时幂等成功。 |
|
|
164
180
|
| `presence_move` | 同一台机器内重命名/移动;目标已存在时除非 `overwrite:true` 否则拒绝。 |
|
|
@@ -168,8 +184,8 @@ DSH 插件提供 24 个工具。相对路径相对当前远程工作区;在工
|
|
|
168
184
|
| `presence_tail` | 读远端文件最后 N 行,或在限定时间内跟随(`follow:true`、`timeoutMs`)。 |
|
|
169
185
|
| `presence_search` | 远端 `rg`/`grep` 搜索,`countOnly`,JSON matches。 |
|
|
170
186
|
| `presence_glob` | 远端 glob 找文件,带 size/mtime。 |
|
|
171
|
-
| `presence_import` | 本地 → 远端二进制安全上传,可设 mode,返回 sha256
|
|
172
|
-
| `presence_export` | 远端 →
|
|
187
|
+
| `presence_import` | 本地 → 远端二进制安全上传,可设 mode,返回 sha256;支持批量 `paths[]`(1–50)和/或本地 `glob` 到 `to` 目录,逐文件返回结果。 |
|
|
188
|
+
| `presence_export` | 远端 → 本地真实文件(内容寻址),供预览或原生程序打开;支持批量 `paths[]` 或远端 `glob`;默认本地文件名取 basename,重名变成 `name-<sha8>.ext`;逐文件返回结果。 |
|
|
173
189
|
| `presence_forward` | 远端端口转发到 `127.0.0.1`;open/list/close 支持 JSON。 |
|
|
174
190
|
| `presence_port_probe` | 检查最多 20 个端口的监听与 TCP 可达性;`expect:open|closed` 可轮询。 |
|
|
175
191
|
| `presence_http_probe` | 等端口 → 开转发 → 本地 HTTP 断言 → 关转发 → 验证本地端口已释放。 |
|
|
@@ -178,14 +194,68 @@ DSH 插件提供 24 个工具。相对路径相对当前远程工作区;在工
|
|
|
178
194
|
|
|
179
195
|
</details>
|
|
180
196
|
|
|
197
|
+
### 批量传输与追加写
|
|
198
|
+
|
|
199
|
+
```text
|
|
200
|
+
presence_export { paths: ["/data/a.pt", "/data/b.pt"] } # files:[{remotePath, localPath, bytes, sha256}]
|
|
201
|
+
presence_export { glob: "/data/ckpt-*.pt" } # 全部匹配;重名变成 name-<sha8>.ext
|
|
202
|
+
presence_import { glob: "dist/*.whl", to: "/data/wheels" } # 本地 → 远端,逐文件结果
|
|
203
|
+
presence_write { path: "/data/train.log", mode: "append", content: "epoch 6 done\n" }
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
单文件仍走原来的 `path`/`to`,上面的写法都不是必需的。
|
|
207
|
+
|
|
208
|
+
## 路径与工具怎么选
|
|
209
|
+
|
|
210
|
+
| 路径在哪 | 用什么 | 说明 |
|
|
211
|
+
| --- | --- | --- |
|
|
212
|
+
| 工作区挂载目录下(`~/.dsh/presence/mounts/...`) | 内置 `read` / `write` / `edit` / `grep` / `glob` | 都直达远端机器;`grep`/`glob` 会自动路由过去 |
|
|
213
|
+
| 工作区之外的远端绝对路径(`/data`、`/scratch`、GPFS) | `presence_read` / `write` / `edit` / `stat` / `search` / `glob`(列目录用 `presence_exec`) | 内置工具只看得到挂载目录,看不到整台机器 |
|
|
214
|
+
| 本地文件(预览或 Office) | 先 `presence_export`,再 `present` / 原生打开 | 挂载路径是虚拟的,只有真实本地文件能被原生程序打开 |
|
|
215
|
+
|
|
216
|
+
**本地**会话(cwd 不在挂载根下)里内置工具只操作本地文件;每个 `presence_*` 调用都需要显式
|
|
217
|
+
`host`,或一次性配置 `PRESENCE_HOST`。
|
|
218
|
+
|
|
219
|
+
## 长时间任务
|
|
220
|
+
|
|
221
|
+
`presence_exec` 会等命令结束,所以训练、构建、起服务都不该直接用它。推荐流程:
|
|
222
|
+
|
|
223
|
+
```text
|
|
224
|
+
1. presence_exec { command: "python train.py --lr 3e-4", background: true, label: "train-3e-4" }
|
|
225
|
+
→ { id: "j7", pid: 4242, pgid: 4242, logPath: ".../job.log", commandLogPath: ".../job.out" }
|
|
226
|
+
2. presence_jobs { action: "wait", jobId: "j7" } # 或 presence_wait { job: { jobId: "j7" } }
|
|
227
|
+
3. presence_tail { path: <logPath>, follow: true } # 看进度
|
|
228
|
+
presence_wait { grep: { path: <logPath>, pattern: "epoch 6", count: 1 } } # 等就绪
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
- `presence_jobs { action: "status" }` 取快照;`stop` 结束进程组并验证端口已释放。任务持久化到
|
|
232
|
+
`~/.dsh/presence/jobs.json`:DSH 重启后先显示 `unknown`,直到探活成功(不会假装 `running`),
|
|
233
|
+
退出码可能缺失。
|
|
234
|
+
- `logPath` 是要 tail 的任务日志(当前内核里就是命令输出);`commandLogPath` 是命令自己的日志,
|
|
235
|
+
没有独立包装日志时等于 `logPath`,所以用 `commandLogPath ?? logPath`。
|
|
236
|
+
- `presence_wait` 取代手写 sleep+tail 循环:等端口
|
|
237
|
+
(`presence_wait { port: { port: 8000, state: "open" } }`)、等 checkpoint
|
|
238
|
+
(`presence_wait { file: { path: "/data/ckpt.pt", minBytes: 1000000 } }`)、等日志行或等进程。
|
|
239
|
+
超时返回 `met:false`,是正常结果而不是错误。
|
|
240
|
+
- 交互式任务:`presence_terminal { action: "open", session: "train" }`,tmux 会话跨重连存活。
|
|
241
|
+
- **不要**在前台 `presence_exec` 里用 `nohup`、`tmux new-session` 或结尾 `&` 跑长任务:登录节点
|
|
242
|
+
会话结束时,集群可能清理残留进程。`background: true` 本身用 `setsid` 脱离;调度器管理的集群
|
|
243
|
+
请用集群自己的启动器(`rlaunch -d …`、`salloc`/`sbatch`),让调度器持有任务。
|
|
244
|
+
|
|
181
245
|
## 安全与恢复
|
|
182
246
|
|
|
183
247
|
- **危险命令不会直接执行。** `rm -r/-f`、`git clean -fdx`、`find -delete`、`shred`、
|
|
184
248
|
`truncate`、`dd of=` 会先用 no-op shim 重放以展开真实目标;未设置变量和受保护路径
|
|
185
|
-
(工作区根、家目录、`/`、`.git`)直接拒绝。必须再带 `confirm:true`
|
|
249
|
+
(工作区根、家目录、`/`、`.git`)直接拒绝。必须再带 `confirm:true` 才会执行。被拒绝的
|
|
250
|
+
混合命令还会列出可单独执行的安全分段(`Safe segments you can run separately:`),
|
|
251
|
+
`confirm:"partial"` 只执行这些分段;工作区内对单个文件的字面量 `rm -f <路径>` 免检
|
|
252
|
+
(它不可能删掉一棵树)。
|
|
186
253
|
- **工作区内删除优先 `presence_trash`**:移动到 `~/.dsh/presence-trash/<时间戳>/` 并返回恢复提示。
|
|
187
254
|
- **写入是原子的**,保留原文件 mode;`presence_edit` 锚点不唯一时报错而不是猜。
|
|
188
|
-
- **读后写守卫**:内置 `read`/`write`/`edit`
|
|
255
|
+
- **读后写守卫**:内置 `read`/`write`/`edit` 共享读后写守卫。路径 key 会在 `presence://`、挂载路径、
|
|
256
|
+
工作区相对路径、远端绝对路径之间归一;`presence_read` / `presence_write` / `presence_edit` 成功都算
|
|
257
|
+
「已读」(写也算)。`presence_exec` 里 `cat file` **不算** —— 守卫看不到 shell 输出;编辑前请用
|
|
258
|
+
`presence_read` 或内置 `read`。
|
|
189
259
|
|
|
190
260
|
## 配置
|
|
191
261
|
|
|
@@ -193,8 +263,9 @@ DSH 插件提供 24 个工具。相对路径相对当前远程工作区;在工
|
|
|
193
263
|
|
|
194
264
|
| 变量 | 默认 | 含义 |
|
|
195
265
|
| --- | --- | --- |
|
|
196
|
-
| `PRESENCE_HOST` | — |
|
|
266
|
+
| `PRESENCE_HOST` | — | **本地**会话的默认 SSH 主机(显式 opt-in;MCP server 也用)。远程工作区会话优先使用自己的挂载主机。 |
|
|
197
267
|
| `PRESENCE_WORKSPACE` | — | `host:/abs/path`,绑定一个远程工作区。 |
|
|
268
|
+
| `PRESENCE_JOBS_FILE` | `~/.dsh/presence/jobs.json` | 覆盖后台任务持久化文件路径。 |
|
|
198
269
|
| `PRESENCE_PASSWORD_<ALIAS>` | — | 某台机器的密码;`<ALIAS>` 大写、非字母数字换成 `_`。 |
|
|
199
270
|
| `PRESENCE_MAX_CHANNELS` | 8 | 每连接并发通道数。 |
|
|
200
271
|
| `PRESENCE_CONNECT_TIMEOUT_MS` | 25000 | 连接尝试超时。 |
|
|
@@ -212,11 +283,19 @@ npm install --prefix .dev --ignore-scripts rollup @rollup/plugin-node-resolve \
|
|
|
212
283
|
@rollup/plugin-commonjs @rollup/plugin-terser codemirror @codemirror/language-data \
|
|
213
284
|
@codemirror/theme-one-dark
|
|
214
285
|
node scripts/build-client.mjs
|
|
215
|
-
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/package.ps1 -Version 0.
|
|
216
|
-
# → dist/presence-plugin-0.
|
|
286
|
+
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/package.ps1 -Version 0.20.1
|
|
287
|
+
# → dist/presence-plugin-0.20.1.zip
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
DSH 升级后,用 Electron-as-Node 对着真实 Harness 运行时验证插件仍能激活
|
|
291
|
+
(0.2.1-rc.5 的工具 schema 收紧就是这一步发现的):
|
|
292
|
+
|
|
293
|
+
```powershell
|
|
294
|
+
$env:ELECTRON_RUN_AS_NODE = '1'
|
|
295
|
+
& 'C:\...\DeepSeek Harness.exe' scripts\host-compat-check.mjs # ACTIVATED OK with 25 tools
|
|
217
296
|
```
|
|
218
297
|
|
|
219
|
-
验证脚本需要一台可 SSH
|
|
298
|
+
验证脚本需要一台可 SSH 的主机(标注“不需要真机”的除外):
|
|
220
299
|
|
|
221
300
|
```sh
|
|
222
301
|
node scripts/regression.mjs server # 传输层、符号链接、超时、二进制
|
|
@@ -224,12 +303,32 @@ node scripts/job-probe.mjs server # 后台任务 + 端口转发
|
|
|
224
303
|
node scripts/feature-probe.mjs server # wait、会话、pgid、就绪、搜索
|
|
225
304
|
node scripts/import-probe.mjs server # 本地→远端二进制、mode、sha256
|
|
226
305
|
node scripts/safety-probe.mjs server # 危险命令护栏 + 观测状态
|
|
306
|
+
node scripts/p0-guard-probe.mjs # 危险命令分段/单文件豁免(不需要真机)
|
|
307
|
+
node scripts/p1-docs-probe.mjs # README/zh/RELEASE 锚点(不需要真机)
|
|
227
308
|
```
|
|
228
309
|
|
|
229
310
|
## 已知边界
|
|
230
311
|
|
|
312
|
+
- **本地会话(有意行为)。** 本地会话不传 `host` 调 `presence_*` 现在会报
|
|
313
|
+
`this session is local (cwd=...); pass an explicit host ...` —— 这是 0.20 有意的行为,不是 bug:
|
|
314
|
+
旧逻辑会静默复用上一次激活的远程工作区,可能在别的会话的机器上执行命令。每次调用传 `host`,
|
|
315
|
+
或一次性配置 `PRESENCE_HOST` 作为显式默认值;本地会话仍然不会继承别的工作区,
|
|
316
|
+
`presence_hosts` 的 `default` 同样是会话作用域。
|
|
317
|
+
- 读后写守卫看不到 shell 输出:`presence_exec { command: "cat file" }`(或 `sed -n`)**不会**把
|
|
318
|
+
文件标记为已读;`presence_read` 和内置 `read` 才算,`presence_write`/`presence_edit` 也算已读。
|
|
319
|
+
只 `exec cat` 过再让内置 `edit` 编辑,仍可能触发守卫。
|
|
231
320
|
- 挂载路径(`~/.dsh/presence/mounts/...`)是虚拟的:对 `ctx.fs` / agent 工具有效,
|
|
232
321
|
但 Explorer、Office、宿主 Python 看不到 —— 需要先用 `presence_export`。
|
|
322
|
+
- `presence_exec` 超时默认 300 秒;更长的任务必须用 `background: true`(前台输出超限会
|
|
323
|
+
落盘到远端文件,`tailLines` 保留长报告末尾)。后台任务持久化到
|
|
324
|
+
`~/.dsh/presence/jobs.json`;DSH 重启后状态是 `unknown` 直到探活成功 —— 不要把持久化记录
|
|
325
|
+
当成还在运行,退出码可能缺失。要 tail 的日志用 `commandLogPath ?? logPath`(当前内核里
|
|
326
|
+
`logPath` 就是命令输出)。
|
|
327
|
+
- 登录节点可能在会话结束时清理残留进程:不要在前台 `presence_exec` 里用 `nohup` /
|
|
328
|
+
`tmux new-session` / `&` 跑长任务。用 `background: true`(`setsid` 脱离)或
|
|
329
|
+
`presence_terminal`(tmux);集群上让调度器持有任务(`rlaunch -d`、`salloc`/`sbatch`)。
|
|
330
|
+
- 参数别名可用(`path`/`file_path`/`filePath`、`old`/`oldString`、`new`/`newString`、
|
|
331
|
+
`replaceAll`/`replace_all`、`dryRun`/`dry_run`、`cwd`/`workdir`);schema 里记录的是规范名。
|
|
233
332
|
- sshd 的 `MaxSessions`(默认 10)限制并发通道;连接池最多用 8 个。
|
|
234
333
|
- PTY 终端跟随连接存活;跨重连的 shell 需要目标机有 `tmux`(`presence_terminal { session }` 自动使用)。
|
|
235
334
|
- ProxyJump / ProxyCommand 会被解析并提示,但目前不用于实际拨号。
|