dsh-plugin-wsl-env 0.9.0 → 0.10.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/CHANGELOG.md +417 -0
- package/CONTRIBUTING.md +2 -2
- package/README.md +84 -68
- package/SUPPORT.md +3 -3
- package/agent/native/linux-aarch64/dsh-wsl-agent +0 -0
- package/agent/native/linux-x86_64/dsh-wsl-agent +0 -0
- package/agent/wsl-agent.sh +83 -16
- package/cordis.patch.yml +41 -35
- package/docs/ARCHITECTURE.md +9 -1
- package/docs/CONFIGURATION.md +7 -4
- package/docs/LIMITATIONS.md +53 -18
- package/docs/PARITY.md +3 -1
- package/docs/PEER-PARITY.md +18 -11
- package/docs/README.zh.md +97 -74
- package/docs/UPSTREAM-FSIO-EXPORT.md +3 -3
- package/docs/UPSTREAM-TERMINAL-TITLE.md +9 -2
- package/examples/preset-wsl-session-sandbox.cordis.patch.yml +178 -0
- package/lib/agent-protocol.d.ts +10 -4
- package/lib/agent-protocol.js +10 -3
- package/lib/agent.d.ts +84 -0
- package/lib/agent.js +167 -15
- package/lib/bwrap.d.ts +8 -0
- package/lib/bwrap.js +4 -1
- package/lib/fs-routing.d.ts +40 -4
- package/lib/fs-routing.js +80 -12
- package/lib/fsio-agent.js +1 -1
- package/lib/index.d.ts +134 -0
- package/lib/index.js +191 -3
- package/lib/native-agent.d.ts +124 -0
- package/lib/native-agent.js +198 -0
- package/lib/ports.d.ts +13 -2
- package/lib/ports.js +18 -5
- package/lib/preflight.js +24 -1
- package/lib/session-runtime.d.ts +161 -0
- package/lib/session-runtime.js +220 -0
- package/lib/session-scope.d.ts +124 -0
- package/lib/session-scope.js +191 -0
- package/lib/shell-env.js +10 -3
- package/lib/watcher.d.ts +25 -12
- package/lib/watcher.js +120 -32
- package/lib/workspace-files-route.d.ts +8 -4
- package/lib/workspace-files-route.js +42 -14
- package/lib/workspace-files-wsl.js +25 -8
- package/package.json +34 -25
package/cordis.patch.yml
CHANGED
|
@@ -187,9 +187,20 @@
|
|
|
187
187
|
# Because a preset's plugins mount under a REGISTRY-owned scope (not this row's
|
|
188
188
|
# ctx), the realm has to be declared here, inside config.plugins.
|
|
189
189
|
#
|
|
190
|
-
# Generated from the shipped standard preset
|
|
191
|
-
#
|
|
192
|
-
#
|
|
190
|
+
# Generated from the shipped standard preset so the embedded plan-mode
|
|
191
|
+
# section and every nested group survive byte-for-byte: this copy is
|
|
192
|
+
# `packages/bundle/web-app/presets/standard.patch.yml` of the
|
|
193
|
+
# `dsh-v0.2.1-rc.2` source tree, changed in exactly three ways —
|
|
194
|
+
# 1. every shipped row mounts inside `wsl-env`, so the preset's tools see
|
|
195
|
+
# the distro's shell and fs (the realm is declared on the group);
|
|
196
|
+
# 2. `tool-bash` is enabled and `tool-pwsh` disabled outright, where the
|
|
197
|
+
# shipped copy derives the choice from `process.platform`;
|
|
198
|
+
# 3. the identity: row `preset-wsl`, preset `wsl`, `order: 2`.
|
|
199
|
+
# Regenerate it when an app update changes the preset: take
|
|
200
|
+
# `packages/bundle/web-app/presets/standard.patch.yml` from the harness tree
|
|
201
|
+
# the peer pins track and run `node scripts/build-preset-wsl.mjs <that
|
|
202
|
+
# file>`, then replace this row to the end of the file. The script applies
|
|
203
|
+
# changes 1 and 2 itself, so only change 3 is yours.
|
|
193
204
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
194
205
|
- id: preset-wsl
|
|
195
206
|
name: '@deepseek-ai/dsh-agent-preset'
|
|
@@ -209,12 +220,15 @@
|
|
|
209
220
|
# asked of the distro itself. Pin a directory here to override it —
|
|
210
221
|
# a value that is right on one machine is wrong on every other.
|
|
211
222
|
#
|
|
212
|
-
#
|
|
213
|
-
#
|
|
214
|
-
#
|
|
215
|
-
#
|
|
216
|
-
#
|
|
217
|
-
#
|
|
223
|
+
# Commands run in one persistent systemd user scope per session
|
|
224
|
+
# (ADR 0001): a background dev server survives the command that
|
|
225
|
+
# started it, `/tmp` is the distro's real `/tmp`, and `ps` sees the
|
|
226
|
+
# session. This needs systemd as PID 1 (`[boot] systemd=true` in
|
|
227
|
+
# /etc/wsl.conf) and a lingering user manager (`loginctl
|
|
228
|
+
# enable-linger <user>`); the preflight refuses with those remedies
|
|
229
|
+
# when one is missing. Set `sessionSandbox: false` for the old
|
|
230
|
+
# per-command shape. Bubblewrap is still required for the mount
|
|
231
|
+
# profile; without it the providers fail closed.
|
|
218
232
|
- id: wsl-shell
|
|
219
233
|
name: 'dsh-plugin-wsl-env'
|
|
220
234
|
config:
|
|
@@ -222,6 +236,13 @@
|
|
|
222
236
|
loginShell: true
|
|
223
237
|
timeoutMs: 120000
|
|
224
238
|
maxTimeoutMs: 600000
|
|
239
|
+
sessionSandbox: true
|
|
240
|
+
# auto: the native session-runtime agent when a verified
|
|
241
|
+
# source is available (packaged binary, or the
|
|
242
|
+
# DSH_WSL_AGENT_NATIVE development override); otherwise the
|
|
243
|
+
# shipped shell agent. native fails the first command instead
|
|
244
|
+
# of falling back; shell forces the shell agent.
|
|
245
|
+
agentBackend: auto
|
|
225
246
|
- id: wsl-fs
|
|
226
247
|
name: 'dsh-plugin-wsl-env/fs'
|
|
227
248
|
config:
|
|
@@ -234,15 +255,19 @@
|
|
|
234
255
|
# share: the opt-out — the Windows-side host fs stack over
|
|
235
256
|
# the 9p share.
|
|
236
257
|
substrate: agent
|
|
258
|
+
# Confined writes ride the session runtime (ADR 0001), so they
|
|
259
|
+
# share the command's real /tmp and workspace mounts.
|
|
260
|
+
sessionSandbox: true
|
|
237
261
|
- id: persona
|
|
238
262
|
name: '@deepseek-ai/dsh-persona'
|
|
239
263
|
config:
|
|
240
|
-
suffix: Your working directory is {{cwd}}.
|
|
241
264
|
prefix: You are a coding agent powered by the {{model}} model.
|
|
242
265
|
- id: agent-instructions
|
|
243
266
|
name: '@deepseek-ai/dsh-agent-instructions'
|
|
244
267
|
config:
|
|
245
268
|
maxBytes: 65536
|
|
269
|
+
- id: time-context
|
|
270
|
+
name: '@deepseek-ai/dsh-time-context'
|
|
246
271
|
- id: tool-bash
|
|
247
272
|
name: '@deepseek-ai/dsh-tool-bash'
|
|
248
273
|
disabled: false
|
|
@@ -257,6 +282,8 @@
|
|
|
257
282
|
sampleOverCapGlobResults: false
|
|
258
283
|
- id: tool-jobs
|
|
259
284
|
name: '@deepseek-ai/dsh-tool-jobs'
|
|
285
|
+
- id: tool-schedule
|
|
286
|
+
name: '@deepseek-ai/dsh-tool-schedule'
|
|
260
287
|
- id: skill-filesystem
|
|
261
288
|
name: '@deepseek-ai/dsh-skill-filesystem'
|
|
262
289
|
- id: tool-skill
|
|
@@ -275,7 +302,7 @@
|
|
|
275
302
|
name: '@deepseek-ai/dsh-plan-mode'
|
|
276
303
|
config:
|
|
277
304
|
section: |
|
|
278
|
-
You are in plan mode. Stay in plan mode until
|
|
305
|
+
You are in plan mode. Stay in plan mode until the user approves your plan through exit_plan_mode or switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
|
|
279
306
|
|
|
280
307
|
Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
|
|
281
308
|
|
|
@@ -319,41 +346,17 @@
|
|
|
319
346
|
provider: spawn
|
|
320
347
|
toolName: subagent
|
|
321
348
|
modelSelectionSettings: true
|
|
322
|
-
backgroundMode: continuable
|
|
323
349
|
- id: tool-subagent-fork
|
|
324
350
|
name: '@deepseek-ai/dsh-tool-subagent'
|
|
325
351
|
config:
|
|
326
352
|
provider: fork
|
|
327
353
|
toolName: subagent_fork
|
|
328
|
-
backgroundMode: continuable
|
|
329
|
-
- id: tool-subagent-codex
|
|
330
|
-
name: '@deepseek-ai/dsh-tool-subagent'
|
|
331
|
-
disabled: true
|
|
332
|
-
config:
|
|
333
|
-
provider: codex
|
|
334
|
-
toolName: subagent_codex
|
|
335
|
-
backgroundMode: one-shot
|
|
336
|
-
maxDepth: provider-managed
|
|
337
|
-
- id: tool-subagent-claude-code
|
|
338
|
-
name: '@deepseek-ai/dsh-tool-subagent'
|
|
339
|
-
disabled: true
|
|
340
|
-
config:
|
|
341
|
-
provider: claude-code
|
|
342
|
-
toolName: subagent_claude_code
|
|
343
|
-
backgroundMode: one-shot
|
|
344
|
-
maxDepth: provider-managed
|
|
345
354
|
- id: workflow-ptc
|
|
346
355
|
name: '@deepseek-ai/dsh-workflow-ptc'
|
|
347
356
|
config:
|
|
348
357
|
provider: spawn
|
|
349
358
|
- id: tool-workflow
|
|
350
359
|
name: '@deepseek-ai/dsh-tool-workflow'
|
|
351
|
-
- id: tool-ralph
|
|
352
|
-
name: '@deepseek-ai/dsh-tool-ralph'
|
|
353
|
-
disabled: true
|
|
354
|
-
config:
|
|
355
|
-
subagentProvider: spawn
|
|
356
|
-
maxRounds: 64
|
|
357
360
|
- id: tool-ask-user
|
|
358
361
|
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
359
362
|
- id: tool-todo
|
|
@@ -370,3 +373,6 @@
|
|
|
370
373
|
- id: tool-plugin-manager
|
|
371
374
|
name: '@deepseek-ai/dsh-plugin-manager/tools'
|
|
372
375
|
disabled: true
|
|
376
|
+
- id: tool-plugin-market
|
|
377
|
+
name: '@deepseek-ai/dsh-plugin-market/tools'
|
|
378
|
+
disabled: true
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -198,6 +198,14 @@ wsl.exe -d <distro> --cd <linux dir> --exec bwrap \
|
|
|
198
198
|
[--tmpfs /tmp --bind <workspace> <workspace>] [--tmpfs /mnt] -- <shell> -lc <cmd>
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
+
By default the profile wraps the session's resident agent ONCE, under
|
|
202
|
+
`systemd-run --user --scope`, and the command argv is the shell line itself:
|
|
203
|
+
`bwrap … --die-with-parent --bind /tmp /tmp --bind <workspace> <workspace> -- sh agent.sh`.
|
|
204
|
+
There is no `--unshare-pid` and no per-command tmpfs, so background processes
|
|
205
|
+
survive, `/tmp` is the distro's real `/tmp`, and `ps` sees the session. The
|
|
206
|
+
per-command argv above is what `sessionSandbox: false` (and the one-shot
|
|
207
|
+
fallback) still runs.
|
|
208
|
+
|
|
201
209
|
These are the same arguments DSH's Linux runner uses (`dsh-sandbox-local`), so the
|
|
202
210
|
behaviour and the error messages match a Linux host. The function
|
|
203
211
|
`bwrapProfileArgs` in [`lib/bwrap.js`](../lib/bwrap.js) builds them — the
|
|
@@ -211,7 +219,7 @@ the row keys, and `lib/sandbox-core.js` states its honest ceiling.
|
|
|
211
219
|
| Mode | What a command inside the distro can do |
|
|
212
220
|
|---|---|
|
|
213
221
|
| `read-only` | read the whole distro; a fresh `/dev` is mounted writable, so `/dev/null` and `/dev/shm` work, and nothing else does |
|
|
214
|
-
| `workspace-write` | the above, plus the session workspace is writable
|
|
222
|
+
| `workspace-write` | the above, plus the session workspace is writable; `/tmp` is the distro's real `/tmp` in the session runtime, and a fresh tmpfs in the per-command profile (`sessionSandbox: false`) |
|
|
215
223
|
| `danger-full-access` | no sandbox; used for an approved wider-permission request |
|
|
216
224
|
|
|
217
225
|
`WslFileSystem` checks writes against the same policy and the same writable list,
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -26,6 +26,8 @@ has its own table at the end of this page.
|
|
|
26
26
|
| | `shell` | `''` | pin a shell inside the distro; empty means the user's login shell |
|
|
27
27
|
| | `loginShell` | `true` *(shipped)* | use `<shell> -lc`, which sources your profile, instead of a bare `-c`. Applies only to the POSIX family; a non-POSIX shell (fish, csh) still gets a bare `-c` |
|
|
28
28
|
| | `agent` | `true` | run commands over the resident in-distro agent (one long-lived process; in-distro timeout); the one-shot `wsl.exe` path is fallen back to only when the agent is out of service. Anything else — a cwd refusal, a sandbox refusal — is the command's own verdict and is raised, not retried on the other path |
|
|
29
|
+
| | `sessionSandbox` | `true` *(shipped)* | run this session's commands in one persistent systemd user scope (ADR 0001): a background dev server survives the command that started it, `/tmp` is the distro's real `/tmp` under `workspace-write`, and `ps` sees the session. The executor holds the runtime for the session and stops the scope from the preset's disposer, which reaps the cgroup. Requires `agent: true`, systemd as PID 1, a reachable user manager and `Linger` (`loginctl enable-linger <user>`); the preflight refuses with the remedy when systemd, the user manager, `systemd-run` or `Linger` is missing (a success is cached; a failure is re-probed, so following the remedy needs no app restart). On in the shipped preset; set `false` for the per-command shape |
|
|
30
|
+
| | `agentBackend` | `auto` | the session runtime's in-distro agent. `auto` uses the native agent when a verified source is available — the packaged per-arch static-musl binary at `agent/native/linux-<arch>/dsh-wsl-agent` (x86_64 and aarch64; runs on glibc and musl distros), or the `DSH_WSL_AGENT_NATIVE` development override — and otherwise the shipped shell agent; `native` fails the first command when it cannot prepare instead of silently falling back; `shell` forces the shell agent. Confined writes share the runtime, so they follow the same selection. The native backend is the default (static musl x86_64/aarch64); the shell agent is the fallback, and `native` refuses instead of falling back |
|
|
29
31
|
| | `sandbox` | `true` | confine commands with `bubblewrap`; `false` disables the sandbox. Needs a usable `bwrap` inside the distro and fails closed without one (`SANDBOX_UNAVAILABLE`, never an unconfined run) — see [Turning the sandbox off](#turning-the-sandbox-off) |
|
|
30
32
|
| | `maskWindowsDrive` | `false` | shadow `/mnt` with an empty tmpfs in every confined profile: the Windows drive's files cannot be read or exfiltrated and its executables cannot be launched through interop — the hole that lets a "sandboxed" command reach your full Windows token. A narrowing, not a closure (a command can still write an executable into the workspace and run it — binfmt dispatches on content, not location), so enforcement stays `partial` and the result's sandbox fact reports `windowsDrive: "masked"`. The complete closure is distro-level: `[interop] enabled=false` in `wsl.conf` |
|
|
31
33
|
| | `cwd` | `''` | default working directory; empty means the distro user's home |
|
|
@@ -34,15 +36,16 @@ has its own table at the end of this page.
|
|
|
34
36
|
| | `restrictToDistro` | `true` *(shipped)* | refuse a path in **another distro**'s share. `/mnt/c` is a directory *inside* the pinned distro, so it is not affected. The refusal carries `FS_OUTSIDE_DISTRO`; that is not a sandbox decision, so the file tools do not offer a wider permission for it. Set `false` to allow another distro's share |
|
|
35
37
|
| | `sandbox` | `true` | check `writeText` and `editText` against the policy. Needs a usable `bwrap` in the distro too, and fails closed the same way: a distro without one refuses confined mutations (`FS_IO_ERROR`, with the install guidance) rather than downgrading them |
|
|
36
38
|
| | `maskWindowsDrive` | `false` | as on `wsl-shell`: confined fs mutations publish through a resident whose profile shadows `/mnt` (same honest limits) |
|
|
39
|
+
| | `sessionSandbox` | `true` *(shipped)* | run confined mutations in the same session runtime the shell uses for the same `(distro, workspace, policy)` — one agent, one real `/tmp`, one workspace mount view; requires the same systemd/linger setup and the preflight refuses with the remedy. Set `false` for the per-policy confined resident. The app-level `fs-routing` backend is not session-owned and keeps this off |
|
|
37
40
|
| | `substrate` | `agent` *(shipped)* | which I/O substrate serves the file tools. Only the resident in-distro agent (`lib/fs-substrate.js`) exists: reads, writes and identities run on ext4 — native symlinks, native mode bits, and the mutation guard survives to the write's atomic publication, kernel-enforced by the confined resident when the policy is confined. The former `share` opt-out (the Windows-side host stack over the 9p share) is refused at boot; delete the line. The comment shipped beside this key in `cordis.patch.yml` still calls `share` the opt-out — that comment is out of date, and the boot refusal is what runs |
|
|
38
41
|
| | `cwd` | `''` | base directory for relative paths; empty means the distro user's home |
|
|
39
|
-
| | `watchMaxDepth` | `0` | how deep the in-distro watch scan walks; `0` walks the whole tree
|
|
42
|
+
| | `watchMaxDepth` | `0` | how deep the in-distro watch scan walks; `0` walks the whole tree and enables the inotify backend. A positive value keeps the polling scan (inotify cannot bound its walk); a change below the bound is seen only when another change above it fires the same callback |
|
|
40
43
|
| `fs-routing` | `distro` | `''` *(shipped)* | which distro the root-plane filesystem routes to; empty resolves WSL's default. **A row of its own**, with its own copy of the distro knobs — changing `wsl-fs` does not change this one, nor the other way round |
|
|
41
44
|
| | `wslPath` | `wsl.exe` *(shipped)* | path to `wsl.exe`; used here only to resolve the default distro |
|
|
42
45
|
| | `sandbox` | `true` *(shipped)* | fence routed mutations by `ctx.sandboxPolicy`; same `bwrap` requirement and fail-closed refusal as `wsl-fs`. The routing row reports no `sandboxMode` — `undefined` whether the fence is on or off |
|
|
43
46
|
| | `maskWindowsDrive` | `false` *(shipped)* | as on `wsl-shell`, for routed mutations |
|
|
44
47
|
| | `restrictToDistro` | `true` *(shipped)* | refuse a path in another distro's share (`FS_OUTSIDE_DISTRO`) |
|
|
45
|
-
| | `watchMaxDepth` | `0` *(shipped)* | how deep the routed watch scan walks; `0` walks the whole tree |
|
|
48
|
+
| | `watchMaxDepth` | `0` *(shipped)* | how deep the routed watch scan walks; `0` walks the whole tree and enables the inotify backend; a positive value keeps the polling scan |
|
|
46
49
|
| | `distroCwd` | `''` | the routed backend's default workdir; empty means the distro user's home. `cwd` belongs to the host backend only — the distro side reads `distroCwd` — while `diffBasisMaxBytes` is shared: it bounds the overwrite diff on both sides |
|
|
47
50
|
| `workspace-files-wsl` | `maxBytes` | `2097152` *(shipped)* | byte cap on one page of a text read, and the default byte window of a byte read |
|
|
48
51
|
| | `maxFileBytes` | `33554432` *(shipped)* | full-file byte cap for the byte reader |
|
|
@@ -138,7 +141,7 @@ above. They all keep their defaults:
|
|
|
138
141
|
|---|---|---|---|
|
|
139
142
|
| `wsl-shell` (in `preset-wsl`) | `wslPath` | `wsl.exe` | path to `wsl.exe`, for an installation that is not on `PATH` |
|
|
140
143
|
| | `hostCwd` | unset → `%SystemRoot%` | the Windows directory the `wsl.exe` process itself starts in |
|
|
141
|
-
| | `forwardEnv` | `[]` | extra host environment variable names to forward into the distro through `WSLENV`. This is the one key here that widens what crosses the boundary: `NO_COLOR`, `TERM`, `PAGER`, `GIT_PAGER`, `LANG`, `LC_ALL` and every managed `DSH_*` name ride along without it, and `PATH` is deliberately never forwarded |
|
|
144
|
+
| | `forwardEnv` | `[]` | extra host environment variable names to forward into the distro through `WSLENV`. This is the one key here that widens what crosses the boundary: `NO_COLOR`, `TERM`, `PAGER`, `GIT_PAGER`, `LANG`, `LC_ALL` and every managed `DSH_*` name ride along without it, and `PATH` is deliberately never forwarded. It applies to the **one-shot `wsl.exe` path**; the resident agent's environment stays pinned to the managed `DSH_*` namespace and deliberately does not forward these names, so a secret listed here does not widen every model command in the session |
|
|
142
145
|
| `wsl-fs` (in `preset-wsl`) | `wslPath` | `wsl.exe` | as above; used only to resolve the default distro |
|
|
143
146
|
| | `diffBasisMaxBytes` | `10485760` | UTF-8 byte limit per side of an overwrite diff; a larger file publishes without a diff |
|
|
144
147
|
| `fs-routing` | `cwd`, `diffBasisMaxBytes` | `process.cwd()`, `10485760` | `cwd` configures the host side only (the distro side reads `distroCwd`); `diffBasisMaxBytes` is shared by both — it also bounds the routed backend's overwrite diff |
|
|
@@ -226,4 +229,4 @@ behind the fail-closed choices above are in [LIMITATIONS.md](LIMITATIONS.md).
|
|
|
226
229
|
| Key | Default | Meaning |
|
|
227
230
|
| --- | --- | --- |
|
|
228
231
|
| `distro` | `''` | the distro the `DSH_WSL_*` facts describe |
|
|
229
|
-
| `portsRefreshMs` | `10000` | how often the `DSH_WSL_PORTS` listening-port snapshot refreshes through the resident agent |
|
|
232
|
+
| `portsRefreshMs` | `10000` | how often the `DSH_WSL_PORTS` listening-port snapshot refreshes through the resident agent. A failed scan keeps the last successful list (and a debug line), so a transient agent outage does not read as “no ports”; only a successful empty scan means none were listening |
|
package/docs/LIMITATIONS.md
CHANGED
|
@@ -71,14 +71,20 @@ crosses the 9p share. What to know about the one substrate there is:
|
|
|
71
71
|
when `bubblewrap` itself is missing, so a distro that has bwrap but not rg
|
|
72
72
|
needs the printed line run by hand. A search whose directory is a Windows
|
|
73
73
|
folder keeps the host binary, which is native there.
|
|
74
|
-
- **`watch()`
|
|
75
|
-
the
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
`
|
|
74
|
+
- **`watch()` prefers inotify, with a polling fallback.** When `inotifywait`
|
|
75
|
+
is installed and the target is an unbounded directory, the watcher uses
|
|
76
|
+
recursive inotify watches and coalesces events to one callback per 0.5s.
|
|
77
|
+
Otherwise — a file target, a positive `maxDepth`, or no `inotify-tools` — it
|
|
78
|
+
is the `find -newer` poll loop (a `stat` fingerprint for a file), so the
|
|
79
|
+
coarse-invalidation caveats below apply.
|
|
80
|
+
- **With `sessionSandbox: false`, a confined command's `/tmp` is not the write
|
|
81
|
+
tool's `/tmp`.** `workspace-write` mounts a fresh tmpfs at `/tmp` for the
|
|
82
|
+
command — that is what makes it ephemeral — while the file tools' fence grants
|
|
83
|
+
the distro's *real* `/tmp`. Both are writable, so neither side is refused, but a
|
|
84
|
+
file the write tool puts in `/tmp` is invisible to `bash` and vice versa. Hand
|
|
85
|
+
a file between them through the session workspace. The shipped preset turns
|
|
86
|
+
the session runtime on, and `wsl-fs` shares it for confined writes, so
|
|
87
|
+
commands and writes bind the same real `/tmp` and workspace.
|
|
82
88
|
- A distro that is stopped or unregistered while the app runs takes the file
|
|
83
89
|
tools' substrate with it: reads and writes report the substrate-unavailable
|
|
84
90
|
error naming the cause. A death with nothing in flight costs nothing — the
|
|
@@ -87,20 +93,49 @@ crosses the 9p share. What to know about the one substrate there is:
|
|
|
87
93
|
rebuild, and when that rebuild cannot complete — the distro is still down, or
|
|
88
94
|
gone — the resident is permanently out: every later file operation fails the
|
|
89
95
|
same way until the app restarts.
|
|
90
|
-
- `watch()` observes from inside the distro
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
the
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
- `watch()` observes from inside the distro, so callbacks are coarse
|
|
97
|
+
invalidation, not per-file notifications. With inotify they arrive as events
|
|
98
|
+
(coalesced to 0.5s); with the polling fallback they arrive on the poll
|
|
99
|
+
cadence, not instantly. In the polling path a creation is seen even when the
|
|
100
|
+
new file's own mtime is old: creating it bumps the containing directory's
|
|
101
|
+
mtime, and the scan includes the watched directory itself (deletions are seen
|
|
102
|
+
the same way). What escapes the polling stamp is a change that leaves every
|
|
103
|
+
scanned mtime older than it — an in-place `cp -p` onto an existing file (new
|
|
104
|
+
content, a copied-back mtime), or a `tar -x` whose archive restores the
|
|
105
|
+
containing directory's own mtime. That blind spot does not apply to the
|
|
106
|
+
inotify path. A FILE target is watched by its `mtime:size` fingerprint: a
|
|
107
|
+
rename-over or an in-place write fires, a removal reports the target as gone,
|
|
108
|
+
and a change that preserves both mtime and size escapes it. Watching a 9p
|
|
109
|
+
share from the Windows side remains unsupported — the loop runs where the
|
|
110
|
+
kernel can actually report change.
|
|
100
111
|
- `editText` reads the whole file into memory before writing it back.
|
|
101
112
|
|
|
102
113
|
## The sandbox
|
|
103
114
|
|
|
115
|
+
- **The one-shot fallback has only the host deadline.** With `agent: false`, or
|
|
116
|
+
when the resident is out of service, a command runs through a fresh
|
|
117
|
+
`wsl.exe`. The host kills `wsl.exe` at the timeout, but WSL does not relay
|
|
118
|
+
that kill into the distro, so the Linux-side process tree can outlive the
|
|
119
|
+
call. The session runtime path — the shipped default — is cleaned by its
|
|
120
|
+
scope. A systemd-scoped one-shot (with explicit environment pass-through) is
|
|
121
|
+
not shipped; prefer `agent: true` and the session runtime for long commands.
|
|
122
|
+
- **The native backend is the default; aarch64 is build-tested only.** The
|
|
123
|
+
packaged x86_64 musl binary runs the 18-check suite in CI. The same job
|
|
124
|
+
builds the aarch64 binary, but the runner cannot execute it, so its
|
|
125
|
+
functional validation on real aarch64 hardware (or qemu) is still pending.
|
|
126
|
+
`agentBackend: auto` still selects it on an aarch64 distro; `native` refuses
|
|
127
|
+
if preparation fails, and `shell` forces the shell agent.
|
|
128
|
+
- **The session runtime needs systemd and a lingering user manager.** The
|
|
129
|
+
shipped preset sets `sessionSandbox: true`; a distro without systemd as PID 1,
|
|
130
|
+
a reachable `systemd --user`, `systemd-run`, or `Linger` is refused on the
|
|
131
|
+
first command with the remedy. Set `sessionSandbox: false` on the preset's
|
|
132
|
+
`wsl-shell` to keep the per-command shape. A **hard crash** of the app can
|
|
133
|
+
leave a stale `dsh-wsl-*.scope` and its background processes behind; a
|
|
134
|
+
graceful session end stops the scope (validated). The session preflight
|
|
135
|
+
sweeps stale scopes when the next WSL session opens: it stops a
|
|
136
|
+
`dsh-wsl-*.scope` only when its cgroup has no agent process (`wsl-agent.sh`
|
|
137
|
+
or the native `dsh-wsl-agent`), so another live session's runtime is never
|
|
138
|
+
touched.
|
|
104
139
|
- **The sandbox does not govern WSL interop.** A confined command can still run a
|
|
105
140
|
Windows program, which escapes the Linux-side boundary. The plugin reports this as
|
|
106
141
|
`enforcement: partial`. See [ARCHITECTURE.md](ARCHITECTURE.md#reported-enforcement).
|
package/docs/PARITY.md
CHANGED
|
@@ -7,7 +7,9 @@ deviation, if any, still stands.
|
|
|
7
7
|
|
|
8
8
|
Verified against: `dsh-plugin-wsl-env@0.8.0` / peer `0.2.0-rc.2` / agent
|
|
9
9
|
protocol v4, 2026-10-06. Update this line when the peer pin or the agent
|
|
10
|
-
protocol moves.
|
|
10
|
+
protocol moves. The peer pin moved to `0.2.1-rc.2` on 2026-10-17 with every
|
|
11
|
+
seam package's shipped artifact byte-identical to the ones these rows were
|
|
12
|
+
measured against; the real-machine probes were not re-run in that move.
|
|
11
13
|
|
|
12
14
|
## What parity means here
|
|
13
15
|
|
package/docs/PEER-PARITY.md
CHANGED
|
@@ -7,12 +7,18 @@ same refusal dialect on both substrates. The **read path** holds end to end;
|
|
|
7
7
|
the **edit path** carries two declared divergences, listed at the end. This
|
|
8
8
|
file records the verification against the real peers.
|
|
9
9
|
|
|
10
|
-
- Peers: `@deepseek-ai/dsh-fs-local@0.2.
|
|
11
|
-
`package.json` pins), plus `@deepseek-ai/dsh-fs@0.2.
|
|
10
|
+
- Peers: `@deepseek-ai/dsh-fs-local@0.2.1-rc.2` (the exact version
|
|
11
|
+
`package.json` pins), plus `@deepseek-ai/dsh-fs@0.2.1-rc.2` for the
|
|
12
12
|
`FsVersion` brand the local package imports. Divergence 2 is measured
|
|
13
|
-
against a third peer, `@deepseek-ai/dsh-bash-local@0.2.
|
|
13
|
+
against a third peer, `@deepseek-ai/dsh-bash-local@0.2.1-rc.2` — the shell
|
|
14
14
|
executor carries the collect-budget/spill machinery; `spill` and
|
|
15
15
|
`maxSpillBytes` do not appear in `dsh-fs-local` at all.
|
|
16
|
+
- The `0.2.1-rc.2` move leaves every line reference below intact: all three
|
|
17
|
+
peers' shipped `lib/index.js` are **byte-identical** to the `0.2.0-rc.2`
|
|
18
|
+
ones (`cmp` clean; `dsh-fs-local`'s is still 909 lines), so only the
|
|
19
|
+
tarballs' digests changed. `dsh-fs@0.2.1-rc.2` did drop its unpublished
|
|
20
|
+
`./invariant` export subpath; this plugin imports neither it nor
|
|
21
|
+
`dsh-invariants`.
|
|
16
22
|
- What "not imported" means: the peer's **fsio module** is not reachable from
|
|
17
23
|
the package's exports, so `lib/fsio-text.js` reimplements it and imports
|
|
18
24
|
nothing from it (the export is proposed in `docs/UPSTREAM-FSIO-EXPORT.md`).
|
|
@@ -27,23 +33,24 @@ file records the verification against the real peers.
|
|
|
27
33
|
|
|
28
34
|
```sh
|
|
29
35
|
mkdir -p /tmp/peer-audit && cd /tmp/peer-audit
|
|
30
|
-
npm pack @deepseek-ai/dsh-fs-local@0.2.
|
|
31
|
-
openssl dgst -sha512 -binary deepseek-ai-dsh-fs-local-0.2.
|
|
32
|
-
tar xzf deepseek-ai-dsh-fs-local-0.2.
|
|
36
|
+
npm pack @deepseek-ai/dsh-fs-local@0.2.1-rc.2 @deepseek-ai/dsh-fs@0.2.1-rc.2 --pack-destination .
|
|
37
|
+
openssl dgst -sha512 -binary deepseek-ai-dsh-fs-local-0.2.1-rc.2.tgz | base64 -w0
|
|
38
|
+
tar xzf deepseek-ai-dsh-fs-local-0.2.1-rc.2.tgz # -> package/lib/index.js
|
|
33
39
|
wc -l package/lib/index.js # expect 909
|
|
34
40
|
```
|
|
35
41
|
|
|
36
42
|
The tarballs this audit compared, by content — the same values
|
|
37
|
-
`npm view <pkg>@0.2.
|
|
43
|
+
`npm view <pkg>@0.2.1-rc.2 dist.integrity` reports:
|
|
38
44
|
|
|
39
45
|
```
|
|
40
|
-
@deepseek-ai/dsh-fs-local@0.2.
|
|
41
|
-
@deepseek-ai/dsh-fs@0.2.
|
|
46
|
+
@deepseek-ai/dsh-fs-local@0.2.1-rc.2 sha512-IZPJ12/EpV6fu3CCWtW4w4GthXAKLFjplm4Feoep2HAum7J1aGzHZwiw7ipsPCZiA5/TyK3lDUUAGZ/pcXgRcw==
|
|
47
|
+
@deepseek-ai/dsh-fs@0.2.1-rc.2 sha512-Z6d2ZKsAiJc0FaiES8wC66vuUQMGohl9tCc2p3bAsPFYkJ0+L2zTViQ9Y5BArtRe9Br8qhNjNgLlN0jb/uwv1g==
|
|
42
48
|
```
|
|
43
49
|
|
|
44
50
|
Each row of the table below names the peer's `lib/index.js` line and the
|
|
45
51
|
literal both sides must produce. Verified 2026-10-03; the read/edit split
|
|
46
|
-
re-verified 2026-10-06.
|
|
52
|
+
re-verified 2026-10-06; the whole table re-checked against `0.2.1-rc.2`,
|
|
53
|
+
2026-10-17.
|
|
47
54
|
|
|
48
55
|
## Verdict
|
|
49
56
|
|
|
@@ -86,7 +93,7 @@ declared extensions, listed at the end.
|
|
|
86
93
|
2. **Per-stream truncation flags.** The EXEC frame carries an output budget;
|
|
87
94
|
the RES line reports which streams the agent cut, surfaced as honest
|
|
88
95
|
`truncated` flags. The collect-budget/spill machinery of the shell peer,
|
|
89
|
-
`@deepseek-ai/dsh-bash-local@0.2.
|
|
96
|
+
`@deepseek-ai/dsh-bash-local@0.2.1-rc.2` (`lib/index.js:30`, `:110`), is the
|
|
90
97
|
spill-file path we deliberately do not run.
|
|
91
98
|
3. **The transport around the ops** (ACK, KILL, the confined resident) is
|
|
92
99
|
protocol this package owns; the peer has no agent.
|