dsh-plugin-wsl-env 0.15.0 → 0.17.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 +144 -15
- package/CONTRIBUTING.md +25 -33
- package/README.md +10 -10
- package/SECURITY.md +6 -4
- package/SUPPORT.md +8 -8
- 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 +51 -12
- package/cordis.patch.yml +23 -10
- package/docs/ARCHITECTURE.md +56 -41
- package/docs/COMPATIBILITY.md +121 -0
- package/docs/CONFIGURATION.md +275 -273
- package/docs/LIMITATIONS.md +114 -38
- package/docs/MIGRATION.md +16 -13
- package/docs/PARITY.md +4 -3
- package/docs/PERFORMANCE.md +11 -9
- package/docs/QUICKSTART.md +46 -21
- package/docs/README.zh.md +7 -7
- package/docs/RELEASING.md +257 -0
- package/docs/REQUIREMENTS.zh.md +77 -0
- package/docs/ROADMAP-1.0.md +105 -61
- package/docs/ROADMAP.md +15 -9
- package/docs/UPSTREAM-FILE-REFERENCE-SEAM.md +3 -3
- package/docs/UPSTREAM-FSIO-EXPORT.md +7 -7
- package/docs/UPSTREAM-OPEN-IN-APP.md +57 -0
- package/docs/UPSTREAM-SESSION-NOTICE.md +4 -4
- package/docs/UPSTREAM-TERMINAL-TITLE.md +10 -12
- package/docs/VALUE.zh.md +144 -0
- package/docs/adr/0002-native-agent.md +348 -0
- package/docs/root-fs-routing.md +186 -0
- package/docs/screenshots/picker.png +0 -0
- package/docs/screenshots/preview.png +0 -0
- package/docs/screenshots/sandbox.png +0 -0
- package/docs/screenshots/terminal.png +0 -0
- package/lib/agent-exec.d.ts +7 -3
- package/lib/agent-protocol.d.ts +18 -5
- package/lib/agent-protocol.js +11 -6
- package/lib/agent-shared.js +1 -0
- package/lib/agent.d.ts +28 -9
- package/lib/agent.js +74 -14
- package/lib/fs-routing.d.ts +2 -1
- package/lib/fs-routing.js +13 -1
- package/lib/fs-substrate.d.ts +11 -9
- package/lib/fs-substrate.js +6 -9
- package/lib/fsio-agent.d.ts +2 -3
- package/lib/fsio-agent.js +2 -3
- package/lib/git-exec.d.ts +117 -0
- package/lib/git-exec.js +105 -0
- package/lib/git-route.d.ts +43 -13
- package/lib/git-route.js +54 -15
- package/lib/index.d.ts +20 -13
- package/lib/index.js +18 -11
- package/lib/session-runtime.d.ts +5 -3
- package/lib/session-runtime.js +5 -4
- package/lib/subprocess.d.ts +21 -7
- package/lib/subprocess.js +63 -9
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +13 -4
- package/scripts/diagnose.sh +1 -1
- package/scripts/doctor.mjs +23 -3
package/CHANGELOG.md
CHANGED
|
@@ -6,15 +6,106 @@ All notable changes to this project are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
The reasoning and the measurements behind each entry are in the archived
|
|
8
8
|
engineering record,
|
|
9
|
-
[docs/archive/engineering-record.zh.md](docs/archive/engineering-record.zh.md)
|
|
9
|
+
[docs/archive/engineering-record.zh.md](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/docs/archive/engineering-record.zh.md)
|
|
10
10
|
(Chinese, repository only — it is history, never a second README). A `§`
|
|
11
11
|
reference below points at that record's numbering unless it names another
|
|
12
12
|
document: the 0.1.0 entry's `design-history §` references are to
|
|
13
|
-
[docs/archive/design-history.zh.md](docs/archive/design-history.zh.md), which
|
|
13
|
+
[docs/archive/design-history.zh.md](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/docs/archive/design-history.zh.md), which
|
|
14
14
|
keeps the superseded designs under the section numbers they had while they
|
|
15
15
|
were part of the record.
|
|
16
16
|
|
|
17
|
-
## [
|
|
17
|
+
## [0.17.0] - 2026-11-01
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **Git snapshot spawns are now agent-first with a one-shot fallback.**
|
|
22
|
+
Protocol v6 adds a stdin payload to the EXEC frame (a seventh header field
|
|
23
|
+
flagging one trailing base64 line), which is what lets `workspace-changes`'
|
|
24
|
+
snapshot calls — including `check-ignore -z --stdin` — run on the warm
|
|
25
|
+
resident agent instead of a one-shot `wsl.exe`. Measured on `ubuntu`
|
|
26
|
+
(2026-11-01): ~232 ms per one-shot git call (nearly all `wsl.exe` process
|
|
27
|
+
startup) against ~1.6 ms on the resident — a 6-8 call snapshot saves well
|
|
28
|
+
over a second. The per-stream caps map onto the agent's single wire cap with
|
|
29
|
+
a client-side per-stream slice (`lib/git-exec.js`), and the environment
|
|
30
|
+
rides the agent's per-request `env` (`env --` argv prefix, no `SETENV`
|
|
31
|
+
pollution) carrying only the translated `GIT_*` names. Both agents speak v6
|
|
32
|
+
(the shell agent decodes the payload through command substitution, which
|
|
33
|
+
strips trailing newlines — NUL separators survive; the native binary
|
|
34
|
+
delivers bytes verbatim), so host library and shipped binaries upgrade
|
|
35
|
+
together; the doctor's handshake reports any mismatch.
|
|
36
|
+
|
|
37
|
+
- **WSL change review no longer requires Windows git.** When
|
|
38
|
+
`resolveExecutable('git')` fails on the host, the provider answers with a
|
|
39
|
+
distro sentinel (`/usr/bin/git`); every workspace-changes spawn — the
|
|
40
|
+
`rev-parse` discovery included — then routes into the distro, and the
|
|
41
|
+
snapshot facade translates the discovery call's absolute-path output lines
|
|
42
|
+
into UNC form (`translateDiscoveryOutput`). A machine with Windows git is
|
|
43
|
+
unchanged (host discovery + `safe.directory` whitelist). The
|
|
44
|
+
`resolveExecutable` override is scoped to the one name and only fires on
|
|
45
|
+
the super's failure.
|
|
46
|
+
|
|
47
|
+
- **The doctor probes the Windows git trust.** A new `git.win-safe` check
|
|
48
|
+
reports the `safe.directory` whitelist state through interop `git.exe`:
|
|
49
|
+
`ok` when the `\\wsl.localhost` share is whitelisted, a warn with the
|
|
50
|
+
copy-paste remedy when Windows git does not trust it, and an info (nothing
|
|
51
|
+
to do) when there is no Windows git — the distro fallback covers WSL change
|
|
52
|
+
review entirely.
|
|
53
|
+
|
|
54
|
+
### Documentation
|
|
55
|
+
|
|
56
|
+
- The architecture and upstream-promotion documents reflect the v6 protocol
|
|
57
|
+
and the shell-menu candidates; the promotion map records the measurement
|
|
58
|
+
that superseded the git one-shot decision.
|
|
59
|
+
- The full real-machine suite re-ran 15/0 on 2026-11-01 after the protocol
|
|
60
|
+
change landed, and again after the documentation refresh. The safe.directory
|
|
61
|
+
prerequisite for WSL change review is now documented with its one-time
|
|
62
|
+
remedy.
|
|
63
|
+
|
|
64
|
+
- **The shell menu now offers host shells instead of a single entry.**
|
|
65
|
+
`terminal-controller`'s `shellCandidates` grows from `[]` to
|
|
66
|
+
`[wsl.exe, powershell, cmd]` while the default profile stays
|
|
67
|
+
`shell: { path: wsl.exe, name: WSL }`. `wsl.exe` dedupes into the default's
|
|
68
|
+
`wsl` kind; `powershell` and `cmd` are launches this terminal really starts —
|
|
69
|
+
host sessions already run PowerShell outright (`hostSessions`), and a manually
|
|
70
|
+
selected shell is titled after itself, which moves the tab-title workaround
|
|
71
|
+
from a user-layer edit into the plugin's own composition
|
|
72
|
+
([#8769](https://github.com/deepseek-ai/deepseek-harness/discussions/8769)
|
|
73
|
+
remains the real fix for the default entry's title).
|
|
74
|
+
|
|
75
|
+
## [0.16.0] - 2026-10-31
|
|
76
|
+
|
|
77
|
+
The schema-validated routing row: the root-plane router stops silently
|
|
78
|
+
ignoring a mistyped or wrong-typed distro key, and the first pass of the
|
|
79
|
+
community-optimization plan stamps the routing documentation and the 1.0
|
|
80
|
+
roadmap with dated, current evidence.
|
|
81
|
+
|
|
82
|
+
### Changed
|
|
83
|
+
|
|
84
|
+
- **`fs-routing` row config is now validated at load time.**
|
|
85
|
+
`WslRoutingFileSystem.Config` no longer blindly reuses
|
|
86
|
+
`LocalFileSystem.Config`: it is a `z.intersect` of the host row's schema and
|
|
87
|
+
the distro-side keys (`distro`, `wslPath`, `distroCwd`, `sandbox`,
|
|
88
|
+
`maskWindowsDrive`, `restrictToDistro`, `watchMaxDepth`). The Loader now
|
|
89
|
+
refuses to mount the row when a key fails its schema — previously a typo or
|
|
90
|
+
a wrong type was silently ignored and the distro side ran on defaults. A
|
|
91
|
+
`null` field value still falls back to the schema default, which matches how
|
|
92
|
+
the Loader treats optional keys. Composition tests mount the row through the
|
|
93
|
+
real Loader to assert both the accepted defaults and the rejected values.
|
|
94
|
+
|
|
95
|
+
### Fixed
|
|
96
|
+
|
|
97
|
+
- `docs/root-fs-routing.md` claimed a 12-probe suite with no routing probe, a
|
|
98
|
+
unit file outside the test lists, and a 10/10 composition test. All three
|
|
99
|
+
had drifted: the suite is 15 probes with `--include-fs` and includes
|
|
100
|
+
`fs-routing.sh`, `test/fs-routing.test.mjs` entered both test lists on
|
|
101
|
+
2026-10-19 (OI-7), and the composition test is 12/12. A header note stamps
|
|
102
|
+
the 2026-10-31 review.
|
|
103
|
+
- `docs/ROADMAP-1.0.md` still described WSL-101 as implemented in the working
|
|
104
|
+
tree; it shipped in 0.13.0, and the status now records which acceptance leg
|
|
105
|
+
has real-machine evidence and which leg (concurrent two-distro) is still
|
|
106
|
+
missing. WSL-104's "routing probe has never been executed" claim is replaced
|
|
107
|
+
by the dated 2026-10-31 single-machine 15/0 result; the metrics row's probe
|
|
108
|
+
count moves to the current 15-with-fs breakdown.
|
|
18
109
|
|
|
19
110
|
## [0.15.0] - 2026-10-27
|
|
20
111
|
|
|
@@ -64,6 +155,44 @@ fixes the run surfaced, and the interop stance in one configuration key.
|
|
|
64
155
|
fixture, and the picker probe now carries that fixture against the real
|
|
65
156
|
composition. The LIMITATIONS entry retires.
|
|
66
157
|
|
|
158
|
+
### Fixed
|
|
159
|
+
|
|
160
|
+
- **The nightly sandbox probe no longer crashes when the runner workspace sits
|
|
161
|
+
outside the C: drive.** The probe's bwrap-profile builder imported
|
|
162
|
+
`lib/bwrap.js` by a raw Windows path; on a self-hosted runner whose
|
|
163
|
+
workspace is on another drive the ESM loader rejects the `d:` scheme and
|
|
164
|
+
the first nightly run failed. The import now goes through
|
|
165
|
+
`pathToFileURL` (same hardening applied to the watcher probe).
|
|
166
|
+
|
|
167
|
+
- **The nightly probes job now tests the WSL topology, not the presence of
|
|
168
|
+
the binary.** The scheduled runs land on GitHub-hosted `windows-latest`
|
|
169
|
+
runners — no self-hosted runner carries that label, so the hosted fleet
|
|
170
|
+
picks the job up — and a hosted Windows image ships `wsl.exe` on PATH with
|
|
171
|
+
no distro behind it. The old `command -v wsl.exe` guard therefore always
|
|
172
|
+
passed, and the sandbox leg died inside wsl.exe itself: the last run
|
|
173
|
+
aborted on the `wslpath` translation with wsl.exe's own UTF-16 "no
|
|
174
|
+
installed distributions" surfacing as null bytes and exit 127 under the
|
|
175
|
+
step's `-e -o pipefail`. The guard now requires the default distro to
|
|
176
|
+
actually launch (`WSL_UTF8=1 wsl.exe -e true`); where it does not, the job
|
|
177
|
+
reports `skip all` — the honest no-op CONTRIBUTING now states. Where a
|
|
178
|
+
default distro does launch, the sandbox probe runs inside it through
|
|
179
|
+
`wsl.exe -e`, which is where the script's header and CONTRIBUTING already
|
|
180
|
+
place it: bubblewrap exists only there, so the leg could never pass from
|
|
181
|
+
Git Bash. The leg also picks up the `USER` resolution its sibling got in
|
|
182
|
+
the preceding fix (that hardening claimed `sandbox.sh` but changed only
|
|
183
|
+
`session-sandbox.sh`; a non-login `wsl.exe -e` shell needs it too). The
|
|
184
|
+
probe holds all 14 expectations under `env -u USER` in a distro.
|
|
185
|
+
|
|
186
|
+
### Added
|
|
187
|
+
|
|
188
|
+
- **`scripts/open-wsl-vscode.ps1` — open a WSL folder in VS Code through
|
|
189
|
+
Remote-WSL.** Opening the UNC share in VS Code is the degraded mode: files
|
|
190
|
+
cross 9p, the terminal is PowerShell, extensions run on Windows. The
|
|
191
|
+
helper translates a WSL UNC path (or a Linux path + distro) into the
|
|
192
|
+
`vscode-remote://wsl+<distro>` folder URI, so VS Code's server runs inside
|
|
193
|
+
the distro — local ext4, distro shell, distro extensions. The quickstart
|
|
194
|
+
documents it next to the workspace-opening step.
|
|
195
|
+
|
|
67
196
|
### Changed
|
|
68
197
|
|
|
69
198
|
- **The doctor gains a cross-distro sessions view (WSL-309) and marks live
|
|
@@ -281,7 +410,7 @@ entry points.
|
|
|
281
410
|
complete `preset-wsl` block, applies `distro`/`sessionSandbox`/`sandbox`/
|
|
282
411
|
`maskWindowsDrive`/`agentBackend`, and `--check` fails loudly with the exact
|
|
283
412
|
block to paste when a layer carries the nested id patch. See
|
|
284
|
-
[docs/CONFIGURATION.md](docs/CONFIGURATION.md#
|
|
413
|
+
[docs/CONFIGURATION.md](docs/CONFIGURATION.md#configure-wsl-shell-and-wsl-fs).
|
|
285
414
|
|
|
286
415
|
- **Session-notice upstream payload ready.** The exact title, body and `gh`
|
|
287
416
|
command for the session-visible notice proposal are in
|
|
@@ -582,7 +711,7 @@ from it.
|
|
|
582
711
|
longer the mechanism. The three deliberate deviations from the shipped copy
|
|
583
712
|
— the `wsl-env` realm wrapping every row, the fixed shell-tool choice, and
|
|
584
713
|
the preset's identity — are unchanged, and the transformation is now
|
|
585
|
-
[scripts/build-preset-wsl.mjs](scripts/build-preset-wsl.mjs) rather than the
|
|
714
|
+
[scripts/build-preset-wsl.mjs](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/scripts/build-preset-wsl.mjs) rather than the
|
|
586
715
|
machine-local generator the block used to name: it takes the shipped file and
|
|
587
716
|
prints the row, and its output is the committed block byte for byte.
|
|
588
717
|
- `docs/UPSTREAM-TERMINAL-FONT.md` closes: the `0.2.1-rc.2` line made the
|
|
@@ -2184,7 +2313,7 @@ could not work, and one where the cause of a failure was invisible.
|
|
|
2184
2313
|
is re-probed on the next command, and concurrent first commands share one probe. The
|
|
2185
2314
|
policy lives in [`lib/probe-cache.js`](lib/probe-cache.js), which is pure, so the
|
|
2186
2315
|
regression is pinned on every platform including CI
|
|
2187
|
-
([`test/probe-cache.test.mjs`](test/probe-cache.test.mjs)).
|
|
2316
|
+
([`test/probe-cache.test.mjs`](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/test/probe-cache.test.mjs)).
|
|
2188
2317
|
- `restrictToDistro` refused another distro's share with `FS_SANDBOX_DENIED`. The
|
|
2189
2318
|
file-tool layer turns that code into a denial marker plus an escalation offer
|
|
2190
2319
|
(`mapError` in `dsh-tool-fs`), and no wider permission can lift this fence: it is a
|
|
@@ -2259,7 +2388,7 @@ Repository and release engineering. No runtime behaviour changed.
|
|
|
2259
2388
|
|
|
2260
2389
|
### Added
|
|
2261
2390
|
|
|
2262
|
-
- `npm run lint:style` ([`test/style.mjs`](test/style.mjs)): a dependency-free gate
|
|
2391
|
+
- `npm run lint:style` ([`test/style.mjs`](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/test/style.mjs)): a dependency-free gate
|
|
2263
2392
|
over the rules `.editorconfig` states, plus three packaging checks — every `files`
|
|
2264
2393
|
entry still matches tracked content, every `exports` target exists, and exactly one
|
|
2265
2394
|
readme candidate sits at the package root. That last one is the regression guard
|
|
@@ -2269,14 +2398,14 @@ Repository and release engineering. No runtime behaviour changed.
|
|
|
2269
2398
|
spawning half of `lib/wsl.js` and the four service modules are covered by the
|
|
2270
2399
|
probes instead, which is why the function threshold is lower than the others.
|
|
2271
2400
|
- `.github/workflows/release.yml`: a `v*` tag is checked against `package.json`
|
|
2272
|
-
([`scripts/check-release-tag.mjs`](scripts/check-release-tag.mjs)), gated, then
|
|
2401
|
+
([`scripts/check-release-tag.mjs`](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/scripts/check-release-tag.mjs)), gated, then
|
|
2273
2402
|
published with `--provenance` through npm trusted publishing, and the GitHub
|
|
2274
2403
|
Release is created from the changelog section
|
|
2275
|
-
([`scripts/changelog-section.mjs`](scripts/changelog-section.mjs)). No npm token is
|
|
2404
|
+
([`scripts/changelog-section.mjs`](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/scripts/changelog-section.mjs)). No npm token is
|
|
2276
2405
|
stored in the repository.
|
|
2277
2406
|
- The governance files a public repository is expected to carry:
|
|
2278
2407
|
[`SECURITY.md`](SECURITY.md), [`CONTRIBUTING.md`](CONTRIBUTING.md),
|
|
2279
|
-
[`SUPPORT.md`](SUPPORT.md), [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md),
|
|
2408
|
+
[`SUPPORT.md`](SUPPORT.md), [`CODE_OF_CONDUCT.md`](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/CODE_OF_CONDUCT.md),
|
|
2280
2409
|
`CODEOWNERS`, a pull-request template, bug-report and contact-link issue templates,
|
|
2281
2410
|
and a Dependabot configuration for the pinned workflow actions.
|
|
2282
2411
|
- [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) describes the plugin as it is
|
|
@@ -2285,16 +2414,16 @@ Repository and release engineering. No runtime behaviour changed.
|
|
|
2285
2414
|
has to be reconstructed from a changelog and a historical record.
|
|
2286
2415
|
- [`docs/RELEASING.md`](docs/RELEASING.md): the release checklist, including the
|
|
2287
2416
|
operational facts that cost time to learn.
|
|
2288
|
-
- [`docs/archive/README.en.md`](docs/archive/README.en.md): an English index of the
|
|
2417
|
+
- [`docs/archive/README.en.md`](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/docs/archive/README.en.md): an English index of the
|
|
2289
2418
|
Chinese archive.
|
|
2290
2419
|
- `.nvmrc` names the Node version the coverage job uses.
|
|
2291
2420
|
|
|
2292
2421
|
### Changed
|
|
2293
2422
|
|
|
2294
2423
|
- The probes no longer carry a user name, a home directory or a distro name.
|
|
2295
|
-
[`test/probe/env.sh`](test/probe/env.sh) derives them from the machine
|
|
2424
|
+
[`test/probe/env.sh`](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/test/probe/env.sh) derives them from the machine
|
|
2296
2425
|
(`%USERPROFILE%`, `%LOCALAPPDATA%`, `WSL_DISTRO_NAME`) and
|
|
2297
|
-
[`test/probe/env.mjs`](test/probe/env.mjs) does the same for the Node-side probes,
|
|
2426
|
+
[`test/probe/env.mjs`](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/test/probe/env.mjs) does the same for the Node-side probes,
|
|
2298
2427
|
which learn their own location from `import.meta.url`. The YAML overlays are now
|
|
2299
2428
|
templates with `@NAME@` placeholders, and the scripts substitute real values into
|
|
2300
2429
|
generated copies that `.gitignore` keeps out of the tree. The two profile layers
|
|
@@ -2516,7 +2645,7 @@ and the package installs as a bundle that contributes its own profile layer.
|
|
|
2516
2645
|
- The README is now **current state only**. The five sections describing designs
|
|
2517
2646
|
that this one replaced — the per-process `DSH_WSL` switch and the three
|
|
2518
2647
|
abandoned tool-naming routes — moved to
|
|
2519
|
-
[docs/archive/design-history.zh.md](docs/archive/design-history.zh.md), which
|
|
2648
|
+
[docs/archive/design-history.zh.md](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/docs/archive/design-history.zh.md), which
|
|
2520
2649
|
keeps their original numbering and records where each conclusion lives now.
|
|
2521
2650
|
The README renumbered accordingly; its §0.1 explains the split. The one part of
|
|
2522
2651
|
the old desktop section that is still current (regenerating the generated
|
|
@@ -2593,7 +2722,7 @@ Documentation that a reader would have acted on, and that was no longer true:
|
|
|
2593
2722
|
current description of the plugin while its conclusions had been superseded
|
|
2594
2723
|
three times over — most sharply by the sandbox work below — which is exactly
|
|
2595
2724
|
the ambiguity a second README creates. It is now
|
|
2596
|
-
[docs/archive/engineering-record.zh.md](docs/archive/engineering-record.zh.md):
|
|
2725
|
+
[docs/archive/engineering-record.zh.md](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/docs/archive/engineering-record.zh.md):
|
|
2597
2726
|
the same content, renamed out of the README slot and banner-headed as history,
|
|
2598
2727
|
with README.md the single statement of current state. Nothing was deleted from
|
|
2599
2728
|
the record; §0.1 names the conclusions that are now historical.
|
package/CONTRIBUTING.md
CHANGED
|
@@ -91,9 +91,8 @@ Three patterns keep the check honest:
|
|
|
91
91
|
upstream seam gap, carries the reason inline, and the gap is written up — in a
|
|
92
92
|
`docs/UPSTREAM-*.md` tracking doc when the fix is an upstream ask (the
|
|
93
93
|
`LocalBashExecutor.spawnSpec` seam has one), or in the source comment itself
|
|
94
|
-
when the honest fix needs a protocol decision this package owns
|
|
95
|
-
|
|
96
|
-
is a published finding, not a silencer.
|
|
94
|
+
when the honest fix needs a protocol decision this package owns. The current
|
|
95
|
+
exception is the `spawnSpec` seam; it is a published finding, not a silencer.
|
|
97
96
|
|
|
98
97
|
## The build
|
|
99
98
|
|
|
@@ -194,6 +193,7 @@ terminal if you would rather not see the prompt.
|
|
|
194
193
|
| `pnpm run probe` | filesystem behaviour against a real distro | yes |
|
|
195
194
|
| `pnpm run probe:sandbox` | what bubblewrap confines and what it does not | no |
|
|
196
195
|
| `pnpm run probe:sandbox-shell` | the confined executor through a real boot | yes |
|
|
196
|
+
| `pnpm run probe:interop-shell` | resident and one-shot interop for drive and copied executables, with and without the `/mnt` mask | yes |
|
|
197
197
|
| `pnpm run probe:terminal` | the terminal provider | yes |
|
|
198
198
|
| `pnpm run probe:substrate` | the agent filesystem substrate over a real `wsl.exe` transport | no; inside the distro |
|
|
199
199
|
| `pnpm run probe:watch` | the in-distro watcher over a real directory | no; inside the distro |
|
|
@@ -226,13 +226,15 @@ still need Windows and WSL2, but no harness boot and no profile: run them from a
|
|
|
226
226
|
distro terminal with Node available, and they reach `wsl.exe` through interop.
|
|
227
227
|
|
|
228
228
|
Two conveniences for the real-machine set. `test/probe/run-all-when-closed.sh
|
|
229
|
-
--include-fs` runs all
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
229
|
+
--include-fs` runs all fifteen probes once the app has exited. The interop shell
|
|
230
|
+
matrix uses the `wslfs` profile, systemd and a lingering user manager; it fails
|
|
231
|
+
when Windows interop or its temporary marker cannot be verified. And `ci.yml` has a
|
|
232
|
+
scheduled and manually dispatched `probes` job: `sandbox` runs whenever a default distro
|
|
233
|
+
actually launches, then `mode`, `terminal`, `picker` and `missing-wsl` where the
|
|
234
|
+
harness app is installed. It is named for a self-hosted Windows runner although
|
|
235
|
+
its `runs-on` carries the GitHub-hosted `windows-latest` label; when the hosted
|
|
236
|
+
runner has no usable distro the job is a green no-op (`skip all`). Where a default
|
|
237
|
+
distro launches without bubblewrap, the `sandbox` leg fails, not skips.
|
|
236
238
|
|
|
237
239
|
## Adding a test
|
|
238
240
|
|
|
@@ -245,19 +247,13 @@ Choose the layer by what the test needs.
|
|
|
245
247
|
|
|
246
248
|
Unit tests run under `node --test`. The `test:unit` and the `test:coverage` lists
|
|
247
249
|
in `package.json` name their files explicitly; a test that only touches this
|
|
248
|
-
repository belongs in both.
|
|
249
|
-
passing, not a pattern to copy: each names one file twice
|
|
250
|
-
(`test/fsio-text.test.mjs` in `test:unit`, `test/fs.boot.test.mjs` in
|
|
251
|
-
`test:coverage`), and `test/fs-routing.test.mjs` is in neither
|
|
252
|
-
(`docs/REQUIREMENTS.zh.md` tracks that as OI-7).
|
|
250
|
+
repository belongs in both. Keep the lists synchronized without duplicate entries.
|
|
253
251
|
|
|
254
252
|
A test that imports a pinned `@deepseek-ai/*` package goes in the same lists and
|
|
255
253
|
needs no guarding — CI runs `pnpm install --frozen-lockfile` over the locked tree
|
|
256
|
-
before `pnpm test`.
|
|
254
|
+
before `pnpm test`. For example,
|
|
257
255
|
`test/provider.test.mjs` is what covers the assembled service rows, and
|
|
258
|
-
`test/composition.test.mjs` runs the loader's real patch algorithm
|
|
259
|
-
(`test/terminal-activity.test.mjs`) reaches a peer only through a JSDoc type
|
|
260
|
-
import, which `checkJs` still resolves.
|
|
256
|
+
`test/composition.test.mjs` runs the loader's real patch algorithm.
|
|
261
257
|
|
|
262
258
|
Where a peer-free seam exists, prefer it. `test/sandbox.test.mjs` runs against
|
|
263
259
|
`lib/sandbox-core.js`, with a stand-in for the peer's `SandboxUnavailableError`
|
|
@@ -269,14 +265,13 @@ must skip itself — print a `SKIP` line and exit 0 — the way that file does o
|
|
|
269
265
|
`win32`, where its fake `wsl.exe` (a POSIX script) cannot execute.
|
|
270
266
|
|
|
271
267
|
Probes are shell scripts, plus the small plugin files they load: the `*-probe.mjs`
|
|
272
|
-
and `*-probe.yml` files in the same directory.
|
|
273
|
-
|
|
274
|
-
`missing-wsl.sh
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
outside both groups — no harness boot, no profile.
|
|
268
|
+
and `*-probe.yml` files in the same directory. Harness probes including `run.sh`,
|
|
269
|
+
`terminal.sh`, `sandbox-shell.sh`, `interop-shell.sh`, `picker.sh`,
|
|
270
|
+
`fs-routing.sh`, `missing-wsl.sh` and `sandbox-off.sh` use the throwaway profile
|
|
271
|
+
whose setup is documented in the header of `test/probe/run.sh`. The distro probes
|
|
272
|
+
(`substrate.sh`, `watch.sh`, `agent.sh`, `exec.sh`) boot no harness: they run
|
|
273
|
+
from a distro terminal and drive the resident agent through the `wsl.exe` interop
|
|
274
|
+
transport. `sandbox.sh` and `mode.sh` need neither a harness boot nor a profile.
|
|
280
275
|
|
|
281
276
|
Keep probe output idempotent. Several probes delete their scratch files at the
|
|
282
277
|
start, because a leftover file from an earlier run changes the result.
|
|
@@ -321,12 +316,9 @@ On Windows 11 + WSL2 (Ubuntu 26.04) the following has been verified:
|
|
|
321
316
|
outside the session workspace is refused inside the distro and the following
|
|
322
317
|
wider-permission request succeeds.
|
|
323
318
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
assertions; 15 shell-probe checks; 9 picker checks; 6 missing-executable checks;
|
|
328
|
-
5 mode checks; 5 opt-out checks and 10 sandbox expectations plus the recorded
|
|
329
|
-
escape; and 3 terminal assertions.
|
|
319
|
+
The current unit and coverage test lists are in `package.json`; probe assertions
|
|
320
|
+
and prerequisites are in the individual scripts. Verification results describe
|
|
321
|
+
the tested environment and are not a substitute for rerunning the applicable gates.
|
|
330
322
|
|
|
331
323
|
## Good first issues and the contributor path
|
|
332
324
|
|
package/README.md
CHANGED
|
@@ -6,19 +6,19 @@
|
|
|
6
6
|
[](https://www.npmjs.com/package/dsh-plugin-wsl-env)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
|
|
9
|
-
A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin that
|
|
9
|
+
A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin that opens the project workspace inside a WSL 2 distribution on Windows.
|
|
10
10
|
|
|
11
|
-
The harness process stays on Windows.
|
|
11
|
+
The harness process stays on Windows. Command execution, shell scripts and file access all run in the distro's Linux environment for the full Linux shell toolchain. No helper service or daemon is installed, no port is opened, and confined commands run under `bubblewrap`.
|
|
12
12
|
|
|
13
13
|
Windows + WSL 2 only.
|
|
14
14
|
|
|
15
|
-
**New here?** Start with [docs/QUICKSTART.md](docs/QUICKSTART.md) —
|
|
15
|
+
**New here?** Start with [docs/QUICKSTART.md](docs/QUICKSTART.md) — prerequisites, recipes and the compatibility table.
|
|
16
16
|
|
|
17
17
|
## Why this plugin
|
|
18
18
|
|
|
19
19
|
A Windows coding tool that works on WSL projects has three options: run the whole tool inside the distro, connect to a helper server inside the distro, or let the plugin provide the distro environment. This plugin is the third option. One workflow covers both kinds of session, with the distro's real tools and the command sandbox intact.
|
|
20
20
|
|
|
21
|
-
- **Real Linux environment.** Commands run in the distro's own login shell. Its `PATH`, `nvm`, `cargo`, `pyenv` and rc files apply. File tools
|
|
21
|
+
- **Real Linux environment.** Commands run in the distro's own login shell. Its `PATH`, `nvm`, `cargo`, `pyenv` and rc files apply. File tools access distro paths through an in-distro agent; the backing filesystem depends on the path.
|
|
22
22
|
- **No service in the distro.** There is no helper server, no daemon and no listening port. The only persisted artifact is a content-addressed copy of the native agent in `~/.cache/dsh-wsl/agent/`.
|
|
23
23
|
- **Sandboxed by default.** Confined commands run under `bubblewrap` inside the distro. If `bubblewrap` is missing, the command fails; it does not run unconfined.
|
|
24
24
|
- **Per-session.** A session opened on a distro folder gets the distro environment. A session opened on a Windows folder keeps the normal Windows tools. Both can run in one harness process.
|
|
@@ -74,7 +74,7 @@ Install `bubblewrap` before the first command:
|
|
|
74
74
|
wsl.exe -d <distro> -u root -- apt-get install -y bubblewrap
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
From a checkout, `pnpm run doctor` is the one-command environment check: Windows/WSL versions, the distro and its shell, bubblewrap usability, systemd / the user manager / Linger, the optional tools, peer versions, the agent cache, a fresh-agent handshake, stale session scopes and the known profile-patch trap. Each failure prints one copy-paste remedy; `--json` is for scripts, and `--fix` sweeps stale scopes (package installation needs `--fix --install` and an explicit confirmation). `pnpm run diagnose` is its read-only alias. `pnpm run bootstrap
|
|
77
|
+
From a checkout, `pnpm run doctor` is the one-command environment check: Windows/WSL versions, the distro and its shell, bubblewrap usability, systemd / the user manager / Linger, the optional tools, peer versions, the agent cache, a fresh-agent handshake, stale session scopes and the known profile-patch trap. Each failure prints one copy-paste remedy; `--json` is for scripts, and `--fix` sweeps stale scopes (package installation needs `--fix --install` and an explicit confirmation). `pnpm run diagnose` is its read-only alias. `pnpm run bootstrap <distro>` remains the narrower tool-install helper.
|
|
78
78
|
|
|
79
79
|
Upgrade by running the `add` command again. Uninstall with `dsh plugin --profile wsl remove dsh-plugin-wsl-env`; the agent cache under `~/.cache/dsh-wsl` can then be deleted. If you added a local `workspace-files-wsl` workaround to your profile to restore previews, remove it after upgrading: the plugin no longer mounts that row.
|
|
80
80
|
|
|
@@ -137,11 +137,11 @@ wsl preset (per session)
|
|
|
137
137
|
| `workspace-write` | the above, plus the session workspace; `/tmp` is the distro's real `/tmp` with the session runtime |
|
|
138
138
|
| `danger-full-access` | no sandbox |
|
|
139
139
|
|
|
140
|
-
With `sessionSandbox: false`,
|
|
140
|
+
With `sessionSandbox: false`, a `workspace-write` command gets a private `/tmp` instead of the distro's real one. `read-only` does not mount a writable `/tmp`.
|
|
141
141
|
|
|
142
142
|
`bubblewrap` is required. If it is missing or broken, confined commands report `SANDBOX_UNAVAILABLE` and do not run. Setting `sandbox: false` on the owning row disables confinement; the model is told that the operation is unsandboxed.
|
|
143
143
|
|
|
144
|
-
Enforcement is reported as `partial`. A command inside the distro can still start a Windows executable through `/mnt/c`, and `bubblewrap` cannot control Windows processes. `maskWindowsDrive: true` hides `/mnt` from the confined view
|
|
144
|
+
Enforcement is reported as `partial`. A command inside the distro can still start a Windows executable through `/mnt/c`, and `bubblewrap` cannot control Windows processes. `maskWindowsDrive: true` hides `/mnt` from the confined view unless overridden by `interopVisibility: visible`; this narrows the hole but does not close it. Disabling WSL interop requires a separate distro-level change: set `[interop] enabled=false` in `/etc/wsl.conf`. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#sandbox) and [SECURITY.md](SECURITY.md).
|
|
145
145
|
|
|
146
146
|
## Troubleshooting
|
|
147
147
|
|
|
@@ -149,9 +149,9 @@ Enforcement is reported as `partial`. A command inside the distro can still star
|
|
|
149
149
|
|---|---|
|
|
150
150
|
| every command reports `SANDBOX_UNAVAILABLE` | install `bubblewrap` in the distro, or set `sandbox: false` |
|
|
151
151
|
| the session refuses before the first command | run `pnpm run doctor`; enable systemd and linger, or set `sessionSandbox: false` on `wsl-shell` and `wsl-fs` |
|
|
152
|
-
| `glob` and `grep` are slow in the distro |
|
|
152
|
+
| `glob` and `grep` are slow in the distro | run `pnpm run bootstrap <distro>` and use its printed install command to install `ripgrep` |
|
|
153
153
|
| `dsh plugin add` prints nothing | the plugin is already installed; this is success |
|
|
154
|
-
| a profile patch for `wsl-shell`/`wsl-fs` changes nothing | those rows are nested inside `preset-wsl`. Run `pnpm run preset --
|
|
154
|
+
| a profile patch for `wsl-shell`/`wsl-fs` changes nothing | those rows are nested inside `preset-wsl`. Run `pnpm run preset --check <patch>`; it fails with the complete block to paste |
|
|
155
155
|
| changes under `lib/` have no effect | run `pnpm run sync:windows`, then restart the app |
|
|
156
156
|
|
|
157
157
|
More error codes and refusals are in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#error-codes). Everything the plugin does not do is in [docs/LIMITATIONS.md](docs/LIMITATIONS.md).
|
|
@@ -174,7 +174,7 @@ Real-machine probes run on Windows + WSL only. See [CONTRIBUTING.md](CONTRIBUTIN
|
|
|
174
174
|
|
|
175
175
|
| Document | Content |
|
|
176
176
|
|---|---|
|
|
177
|
-
| [docs/QUICKSTART.md](docs/QUICKSTART.md) |
|
|
177
|
+
| [docs/QUICKSTART.md](docs/QUICKSTART.md) | first run, recipes, compatibility |
|
|
178
178
|
| [docs/PERFORMANCE.md](docs/PERFORMANCE.md) | ext4 agent vs 9p, measured latency, how to reproduce |
|
|
179
179
|
| [docs/CONFIGURATION.md](docs/CONFIGURATION.md) | every configuration key and how to override it |
|
|
180
180
|
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | services, path routing, sandbox, error codes |
|
package/SECURITY.md
CHANGED
|
@@ -88,14 +88,16 @@ This is a documented design boundary, not a vulnerability by itself. The command
|
|
|
88
88
|
distro, where `bubblewrap` and `node` must be installed; the README states it in
|
|
89
89
|
its Sandbox section.
|
|
90
90
|
|
|
91
|
-
The strict profile (`maskWindowsDrive: true` with `sandbox: true
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
The strict profile (`maskWindowsDrive: true` with `sandbox: true`, without an
|
|
92
|
+
overriding `interopVisibility: visible`) narrows this:
|
|
93
|
+
confined profiles shadow `/mnt` with an empty tmpfs, so executables at paths
|
|
94
|
+
under `/mnt` cannot be launched from there. Other mounts and unrestricted file
|
|
95
|
+
reads are not covered. It does not close the hole — a Windows executable
|
|
94
96
|
written into the workspace still runs, because WSL's `binfmt_misc` dispatches on
|
|
95
97
|
content, not location — so the reported enforcement stays `partial`. The complete
|
|
96
98
|
closure is distro-level (`[interop] enabled=false` in `/etc/wsl.conf`). The three
|
|
97
99
|
states and their evidence are tabulated in
|
|
98
|
-
[docs/CONFIGURATION.md](docs/CONFIGURATION.md#
|
|
100
|
+
[docs/CONFIGURATION.md](docs/CONFIGURATION.md#windows-interop).
|
|
99
101
|
|
|
100
102
|
A report is still welcome if you find an escape that does not depend on interop,
|
|
101
103
|
if the masked profile is bypassable from `/mnt`, or if the plugin ever reports
|
package/SUPPORT.md
CHANGED
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
|---|---|
|
|
7
7
|
| Operating system | Windows 10 and Windows 11 |
|
|
8
8
|
| Linux side | WSL2 with at least one distro, and systemd as PID 1 with a lingering user manager (`loginctl enable-linger`) for the shipped session runtime. Set `sessionSandbox: false` on the preset's `wsl-shell` to keep the per-command sandbox instead |
|
|
9
|
-
| DSH packages |
|
|
9
|
+
| DSH packages | `peerDependencies` declares a `0.2.1-rc.3` floor; newer versions require compatibility validation |
|
|
10
|
+
| Plugin version | the latest published version only |
|
|
11
|
+
| Node | 22.19+ (`^22.19.0`), or 24 and later (`>=24.0.0`) |
|
|
10
12
|
|
|
11
13
|
The DSH row is checked weekly: `.github/workflows/peer-canary.yml` resolves the
|
|
12
14
|
`next` dist-tag of every pinned peer, installs that line into a scratch copy and
|
|
13
15
|
runs `pnpm test` plus the composition boot test. A successful run prints the
|
|
14
16
|
exact version delta in its summary; update the row above from that summary. A
|
|
15
17
|
red run means the plugin has not been validated against the new line yet.
|
|
16
|
-
| Plugin version | the latest published version only |
|
|
17
|
-
| Node | 22.19+ (`^22.19.0`), or 24 and later (`>=24.0.0`) |
|
|
18
18
|
|
|
19
19
|
Node 20 is outside the range on purpose: the floor is the harness host's own, and
|
|
20
20
|
CI covers 22 and 24. `engines` in `package.json` is the authoritative statement,
|
|
@@ -60,10 +60,10 @@ issue for one.
|
|
|
60
60
|
|
|
61
61
|
## Compatibility expectations
|
|
62
62
|
|
|
63
|
-
The
|
|
64
|
-
exactly
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
The DSH peer ranges start at `0.2.1-rc.3`; development dependencies pin that
|
|
64
|
+
version exactly for testing. Cordis and Schemastery use caret peer ranges.
|
|
65
|
+
The accepted range does not guarantee compatibility with every newer release:
|
|
66
|
+
an upstream change can require a new release of this plugin.
|
|
67
67
|
|
|
68
68
|
There is no long-term support branch. Fixes go to the latest version.
|
|
69
69
|
|
|
@@ -93,7 +93,7 @@ what it cannot see:
|
|
|
93
93
|
|
|
94
94
|
| Item | How to get it |
|
|
95
95
|
|---|---|
|
|
96
|
-
| The diagnose report | `pnpm run diagnose
|
|
96
|
+
| The diagnose report | `pnpm run diagnose <distro>` (read-only; review and redact before sharing) |
|
|
97
97
|
| Plugin version | `npm ls dsh-plugin-wsl-env`, or the git tag you built from |
|
|
98
98
|
| DSH version | the version of the harness you run |
|
|
99
99
|
| The composed configuration | `dsh --profile <name> --dump-config`, in particular the `dsh-plugin-wsl-env` layer and the rows it patches |
|
|
Binary file
|
|
Binary file
|
package/agent/wsl-agent.sh
CHANGED
|
@@ -24,8 +24,12 @@
|
|
|
24
24
|
# SETENV|<key>|<b64 value> -> export the variable for later requests;
|
|
25
25
|
# <key> must be a POSIX identifier, else
|
|
26
26
|
# ERR|protocol
|
|
27
|
-
# EXEC|<id>|<b64 cwd>|<timeout seconds|0>|<n args>|<output cap bytes|0>
|
|
28
|
-
# followed by <n args> lines of base64, one argv word each
|
|
27
|
+
# EXEC|<id>|<b64 cwd>|<timeout seconds|0>|<n args>|<output cap bytes|0>|<stdin flag|0>
|
|
28
|
+
# followed by <n args> lines of base64, one argv word each, and — when
|
|
29
|
+
# the stdin flag is 1 — one more base64 line: the child's entire stdin.
|
|
30
|
+
# The agent feeds those bytes to the child and closes its stdin, so the
|
|
31
|
+
# child sees EOF at the payload's end. Absent or 0 means the child reads
|
|
32
|
+
# an empty stdin and immediate EOF (the v5 shape).
|
|
29
33
|
# -> ACK|<id> the moment the request is dequeued for execution — the host
|
|
30
34
|
# arms its stuck-request watchdog on this line, so time spent queued
|
|
31
35
|
# behind an earlier request never counts against a budget
|
|
@@ -50,9 +54,8 @@
|
|
|
50
54
|
# of its stderr (reason: notfound notdir perm loop exists stale io),
|
|
51
55
|
# which the host's adapter maps onto the FsError codes the tool layer
|
|
52
56
|
# knows. A `replace` write carrying an expected version re-verifies it
|
|
53
|
-
# against a fresh stat
|
|
54
|
-
#
|
|
55
|
-
# stale write does not land.
|
|
57
|
+
# against a fresh stat before the rename and refuses with `stale` on
|
|
58
|
+
# a mismatch; a later concurrent write can still be overwritten.
|
|
56
59
|
#
|
|
57
60
|
# Confinement is NOT this script's business: the host wraps argv in the bwrap
|
|
58
61
|
# profile (lib/sandbox.js) before the frame is sent, so the agent executes
|
|
@@ -63,7 +66,14 @@
|
|
|
63
66
|
|
|
64
67
|
set -u
|
|
65
68
|
|
|
66
|
-
|
|
69
|
+
# v6: EXEC gains an optional seventh header field flagging one stdin payload
|
|
70
|
+
# line after the argv words. One documented limitation, the same one SETENV
|
|
71
|
+
# and every argv word already carry: command substitution strips TRAILING
|
|
72
|
+
# NEWLINES from the decoded payload, so a stdin feed ending in `\n` reaches
|
|
73
|
+
# the child without it (NUL bytes — `check-ignore -z` separators — survive).
|
|
74
|
+
# Every known consumer parses line-oriented stdin that tolerates a missing
|
|
75
|
+
# final newline.
|
|
76
|
+
PROTO_VERSION=6
|
|
67
77
|
TMPDIR_AGENT=$(mktemp -d "${TMPDIR:-/tmp}/wsl-agent.XXXXXX")
|
|
68
78
|
OUT_FILE="$TMPDIR_AGENT/out"
|
|
69
79
|
ERR_FILE="$TMPDIR_AGENT/err"
|
|
@@ -255,16 +265,23 @@ run_capture() {
|
|
|
255
265
|
fi
|
|
256
266
|
fi
|
|
257
267
|
CURRENT_ID=$req_id
|
|
268
|
+
# Protocol v6 stdin: when the EXEC dispatch staged a payload file, the child
|
|
269
|
+
# reads its bytes and sees EOF at the file's end; otherwise /dev/null keeps
|
|
270
|
+
# the child off the agent's own stdin (the host pipe).
|
|
271
|
+
spawn_in=/dev/null
|
|
272
|
+
if [ -n "${EXEC_STDIN:-}" ]; then
|
|
273
|
+
spawn_in=$EXEC_STDIN
|
|
274
|
+
fi
|
|
258
275
|
# The inflight marker pauses the lease watchdog for this request and
|
|
259
276
|
# refreshes the temp dir's mtime for the boot sweep; removing it after the
|
|
260
277
|
# answer re-arms both.
|
|
261
278
|
: >"$TMPDIR_AGENT/inflight"
|
|
262
279
|
if [ "$mode" = subshell ]; then
|
|
263
|
-
( "$@" ) >"$spawn_out" 2>"$spawn_err" &
|
|
280
|
+
( "$@" ) >"$spawn_out" 2>"$spawn_err" <"$spawn_in" &
|
|
264
281
|
elif [ -n "$SETSID" ]; then
|
|
265
|
-
( exec setsid "$@" ) >"$spawn_out" 2>"$spawn_err" &
|
|
282
|
+
( exec setsid "$@" ) >"$spawn_out" 2>"$spawn_err" <"$spawn_in" &
|
|
266
283
|
else
|
|
267
|
-
"$@" >"$spawn_out" 2>"$spawn_err" &
|
|
284
|
+
"$@" >"$spawn_out" 2>"$spawn_err" <"$spawn_in" &
|
|
268
285
|
fi
|
|
269
286
|
CURRENT_PID=$!
|
|
270
287
|
# Dispatched: the child exists now, so the host may arm its execution
|
|
@@ -510,8 +527,8 @@ sweep_staging() {
|
|
|
510
527
|
fs_write() {
|
|
511
528
|
# $1 = base64 path, $2 = base64 mode (`-` keeps none), $3 = base64
|
|
512
529
|
# `replace` or `no-replace`, $4 = expected version (`-` for none) — the
|
|
513
|
-
# version string this write was based on, re-verified
|
|
514
|
-
#
|
|
530
|
+
# version string this write was based on, re-verified before the rename;
|
|
531
|
+
# a later concurrent write can still be overwritten — and $5 =
|
|
515
532
|
# content STILL base64. Mirrors fsio's POSIX publication: a private 0700
|
|
516
533
|
# sibling staging dir, a 0600 temp, fsync, chmod, then an atomic rename — or
|
|
517
534
|
# a hard link that refuses to replace for guarded creates.
|
|
@@ -676,7 +693,18 @@ while IFS= read -r line; do
|
|
|
676
693
|
timeout_s=${rest%%|*}
|
|
677
694
|
rest=${rest#*|}
|
|
678
695
|
nargs=${rest%%|*}
|
|
679
|
-
|
|
696
|
+
rest=${rest#*|}
|
|
697
|
+
maxout=${rest%%|*}
|
|
698
|
+
# Protocol v6: an optional seventh field flags one stdin payload line
|
|
699
|
+
# after the argv words. Absent (a stale peer) or 0 keeps the v5 shape —
|
|
700
|
+
# the child reads an empty stdin and immediate EOF.
|
|
701
|
+
stdin_flag=0
|
|
702
|
+
case $rest in
|
|
703
|
+
*\|*) stdin_flag=${rest#*|} ;;
|
|
704
|
+
*) stdin_flag=0 ;;
|
|
705
|
+
esac
|
|
706
|
+
case $stdin_flag in ''|*[!01]*) stdin_flag=0 ;; esac
|
|
707
|
+
EXEC_STDIN=
|
|
680
708
|
cwd=$(b64dec "$cwd_b64")
|
|
681
709
|
if ! cd "$cwd" 2>/dev/null; then
|
|
682
710
|
printf 'ERR|%s|cwd|%s\n' "$req_id" "$(printf '%s' "$cwd" | base64 | tr -d '\n')"
|
|
@@ -705,9 +733,20 @@ while IFS= read -r line; do
|
|
|
705
733
|
printf 'ERR|%s|protocol|%s\n' "$req_id" "$(printf '%s' "an argv payload failed to decode" | base64 | tr -d '\n')"
|
|
706
734
|
continue
|
|
707
735
|
fi
|
|
736
|
+
if [ "$stdin_flag" = 1 ]; then
|
|
737
|
+
IFS= read -r stdin_b64 || exit 0
|
|
738
|
+
if ! b64dec_strict "$stdin_b64"; then
|
|
739
|
+
printf 'ERR|%s|protocol|%s\n' "$req_id" "$(printf '%s' "the stdin payload failed to decode" | base64 | tr -d '\n')"
|
|
740
|
+
continue
|
|
741
|
+
fi
|
|
742
|
+
EXEC_STDIN="$TMPDIR_AGENT/stdin.$req_id"
|
|
743
|
+
printf '%s' "$DEC" >"$EXEC_STDIN"
|
|
744
|
+
fi
|
|
708
745
|
# Dispatched: run_capture prints the ACK once the child exists, so the
|
|
709
746
|
# host's watchdog and USR1 cancel both target a real process.
|
|
710
747
|
run_capture "$req_id" "$timeout_s" "$maxout" exec "$@"
|
|
748
|
+
[ -n "$EXEC_STDIN" ] && rm -f -- "$EXEC_STDIN"
|
|
749
|
+
EXEC_STDIN=
|
|
711
750
|
;;
|
|
712
751
|
FS\|*)
|
|
713
752
|
header=${line#FS|}
|