dsh-plugin-wsl-env 0.15.0 → 0.16.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +87 -14
  2. package/CONTRIBUTING.md +25 -33
  3. package/README.md +10 -10
  4. package/SECURITY.md +6 -4
  5. package/SUPPORT.md +8 -8
  6. package/agent/native/linux-aarch64/dsh-wsl-agent +0 -0
  7. package/agent/native/linux-x86_64/dsh-wsl-agent +0 -0
  8. package/agent/wsl-agent.sh +4 -5
  9. package/cordis.patch.yml +8 -1
  10. package/docs/ARCHITECTURE.md +42 -35
  11. package/docs/COMPATIBILITY.md +121 -0
  12. package/docs/CONFIGURATION.md +254 -273
  13. package/docs/LIMITATIONS.md +61 -26
  14. package/docs/MIGRATION.md +16 -13
  15. package/docs/PARITY.md +4 -3
  16. package/docs/PERFORMANCE.md +11 -9
  17. package/docs/QUICKSTART.md +46 -21
  18. package/docs/README.zh.md +7 -7
  19. package/docs/RELEASING.md +257 -0
  20. package/docs/REQUIREMENTS.zh.md +77 -0
  21. package/docs/ROADMAP-1.0.md +105 -61
  22. package/docs/ROADMAP.md +15 -9
  23. package/docs/UPSTREAM-FILE-REFERENCE-SEAM.md +3 -3
  24. package/docs/UPSTREAM-FSIO-EXPORT.md +7 -7
  25. package/docs/UPSTREAM-OPEN-IN-APP.md +57 -0
  26. package/docs/UPSTREAM-SESSION-NOTICE.md +4 -4
  27. package/docs/VALUE.zh.md +144 -0
  28. package/docs/adr/0002-native-agent.md +348 -0
  29. package/docs/root-fs-routing.md +186 -0
  30. package/docs/screenshots/picker.png +0 -0
  31. package/docs/screenshots/preview.png +0 -0
  32. package/docs/screenshots/sandbox.png +0 -0
  33. package/docs/screenshots/terminal.png +0 -0
  34. package/lib/agent-exec.d.ts +2 -3
  35. package/lib/agent-shared.js +1 -0
  36. package/lib/agent.d.ts +19 -8
  37. package/lib/agent.js +73 -13
  38. package/lib/fs-routing.d.ts +2 -1
  39. package/lib/fs-routing.js +13 -1
  40. package/lib/fs-substrate.d.ts +11 -9
  41. package/lib/fs-substrate.js +6 -9
  42. package/lib/fsio-agent.d.ts +2 -3
  43. package/lib/fsio-agent.js +2 -3
  44. package/lib/index.d.ts +20 -13
  45. package/lib/index.js +18 -11
  46. package/lib/session-runtime.d.ts +5 -3
  47. package/lib/session-runtime.js +5 -4
  48. package/locale/en.json +1 -1
  49. package/locale/zh.json +1 -1
  50. package/package.json +13 -4
  51. package/scripts/diagnose.sh +1 -1
  52. package/scripts/doctor.mjs +3 -3
package/CHANGELOG.md CHANGED
@@ -6,16 +6,51 @@ 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
  ## [Unreleased]
18
18
 
19
+ ## [0.16.0] - 2026-10-31
20
+
21
+ The schema-validated routing row: the root-plane router stops silently
22
+ ignoring a mistyped or wrong-typed distro key, and the first pass of the
23
+ community-optimization plan stamps the routing documentation and the 1.0
24
+ roadmap with dated, current evidence.
25
+
26
+ ### Changed
27
+
28
+ - **`fs-routing` row config is now validated at load time.**
29
+ `WslRoutingFileSystem.Config` no longer blindly reuses
30
+ `LocalFileSystem.Config`: it is a `z.intersect` of the host row's schema and
31
+ the distro-side keys (`distro`, `wslPath`, `distroCwd`, `sandbox`,
32
+ `maskWindowsDrive`, `restrictToDistro`, `watchMaxDepth`). The Loader now
33
+ refuses to mount the row when a key fails its schema — previously a typo or
34
+ a wrong type was silently ignored and the distro side ran on defaults. A
35
+ `null` field value still falls back to the schema default, which matches how
36
+ the Loader treats optional keys. Composition tests mount the row through the
37
+ real Loader to assert both the accepted defaults and the rejected values.
38
+
39
+ ### Fixed
40
+
41
+ - `docs/root-fs-routing.md` claimed a 12-probe suite with no routing probe, a
42
+ unit file outside the test lists, and a 10/10 composition test. All three
43
+ had drifted: the suite is 15 probes with `--include-fs` and includes
44
+ `fs-routing.sh`, `test/fs-routing.test.mjs` entered both test lists on
45
+ 2026-10-19 (OI-7), and the composition test is 12/12. A header note stamps
46
+ the 2026-10-31 review.
47
+ - `docs/ROADMAP-1.0.md` still described WSL-101 as implemented in the working
48
+ tree; it shipped in 0.13.0, and the status now records which acceptance leg
49
+ has real-machine evidence and which leg (concurrent two-distro) is still
50
+ missing. WSL-104's "routing probe has never been executed" claim is replaced
51
+ by the dated 2026-10-31 single-machine 15/0 result; the metrics row's probe
52
+ count moves to the current 15-with-fs breakdown.
53
+
19
54
  ## [0.15.0] - 2026-10-27
20
55
 
21
56
  The compatibility-matrix release: the support table and its evidence, the
@@ -64,6 +99,44 @@ fixes the run surfaced, and the interop stance in one configuration key.
64
99
  fixture, and the picker probe now carries that fixture against the real
65
100
  composition. The LIMITATIONS entry retires.
66
101
 
102
+ ### Fixed
103
+
104
+ - **The nightly sandbox probe no longer crashes when the runner workspace sits
105
+ outside the C: drive.** The probe's bwrap-profile builder imported
106
+ `lib/bwrap.js` by a raw Windows path; on a self-hosted runner whose
107
+ workspace is on another drive the ESM loader rejects the `d:` scheme and
108
+ the first nightly run failed. The import now goes through
109
+ `pathToFileURL` (same hardening applied to the watcher probe).
110
+
111
+ - **The nightly probes job now tests the WSL topology, not the presence of
112
+ the binary.** The scheduled runs land on GitHub-hosted `windows-latest`
113
+ runners — no self-hosted runner carries that label, so the hosted fleet
114
+ picks the job up — and a hosted Windows image ships `wsl.exe` on PATH with
115
+ no distro behind it. The old `command -v wsl.exe` guard therefore always
116
+ passed, and the sandbox leg died inside wsl.exe itself: the last run
117
+ aborted on the `wslpath` translation with wsl.exe's own UTF-16 "no
118
+ installed distributions" surfacing as null bytes and exit 127 under the
119
+ step's `-e -o pipefail`. The guard now requires the default distro to
120
+ actually launch (`WSL_UTF8=1 wsl.exe -e true`); where it does not, the job
121
+ reports `skip all` — the honest no-op CONTRIBUTING now states. Where a
122
+ default distro does launch, the sandbox probe runs inside it through
123
+ `wsl.exe -e`, which is where the script's header and CONTRIBUTING already
124
+ place it: bubblewrap exists only there, so the leg could never pass from
125
+ Git Bash. The leg also picks up the `USER` resolution its sibling got in
126
+ the preceding fix (that hardening claimed `sandbox.sh` but changed only
127
+ `session-sandbox.sh`; a non-login `wsl.exe -e` shell needs it too). The
128
+ probe holds all 14 expectations under `env -u USER` in a distro.
129
+
130
+ ### Added
131
+
132
+ - **`scripts/open-wsl-vscode.ps1` — open a WSL folder in VS Code through
133
+ Remote-WSL.** Opening the UNC share in VS Code is the degraded mode: files
134
+ cross 9p, the terminal is PowerShell, extensions run on Windows. The
135
+ helper translates a WSL UNC path (or a Linux path + distro) into the
136
+ `vscode-remote://wsl+<distro>` folder URI, so VS Code's server runs inside
137
+ the distro — local ext4, distro shell, distro extensions. The quickstart
138
+ documents it next to the workspace-opening step.
139
+
67
140
  ### Changed
68
141
 
69
142
  - **The doctor gains a cross-distro sessions view (WSL-309) and marks live
@@ -281,7 +354,7 @@ entry points.
281
354
  complete `preset-wsl` block, applies `distro`/`sessionSandbox`/`sandbox`/
282
355
  `maskWindowsDrive`/`agentBackend`, and `--check` fails loudly with the exact
283
356
  block to paste when a layer carries the nested id patch. See
284
- [docs/CONFIGURATION.md](docs/CONFIGURATION.md#overriding-the-rows-inside-preset-wsl).
357
+ [docs/CONFIGURATION.md](docs/CONFIGURATION.md#configure-wsl-shell-and-wsl-fs).
285
358
 
286
359
  - **Session-notice upstream payload ready.** The exact title, body and `gh`
287
360
  command for the session-visible notice proposal are in
@@ -582,7 +655,7 @@ from it.
582
655
  longer the mechanism. The three deliberate deviations from the shipped copy
583
656
  — the `wsl-env` realm wrapping every row, the fixed shell-tool choice, and
584
657
  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
658
+ [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
659
  machine-local generator the block used to name: it takes the shipped file and
587
660
  prints the row, and its output is the committed block byte for byte.
588
661
  - `docs/UPSTREAM-TERMINAL-FONT.md` closes: the `0.2.1-rc.2` line made the
@@ -2184,7 +2257,7 @@ could not work, and one where the cause of a failure was invisible.
2184
2257
  is re-probed on the next command, and concurrent first commands share one probe. The
2185
2258
  policy lives in [`lib/probe-cache.js`](lib/probe-cache.js), which is pure, so the
2186
2259
  regression is pinned on every platform including CI
2187
- ([`test/probe-cache.test.mjs`](test/probe-cache.test.mjs)).
2260
+ ([`test/probe-cache.test.mjs`](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/test/probe-cache.test.mjs)).
2188
2261
  - `restrictToDistro` refused another distro's share with `FS_SANDBOX_DENIED`. The
2189
2262
  file-tool layer turns that code into a denial marker plus an escalation offer
2190
2263
  (`mapError` in `dsh-tool-fs`), and no wider permission can lift this fence: it is a
@@ -2259,7 +2332,7 @@ Repository and release engineering. No runtime behaviour changed.
2259
2332
 
2260
2333
  ### Added
2261
2334
 
2262
- - `npm run lint:style` ([`test/style.mjs`](test/style.mjs)): a dependency-free gate
2335
+ - `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
2336
  over the rules `.editorconfig` states, plus three packaging checks — every `files`
2264
2337
  entry still matches tracked content, every `exports` target exists, and exactly one
2265
2338
  readme candidate sits at the package root. That last one is the regression guard
@@ -2269,14 +2342,14 @@ Repository and release engineering. No runtime behaviour changed.
2269
2342
  spawning half of `lib/wsl.js` and the four service modules are covered by the
2270
2343
  probes instead, which is why the function threshold is lower than the others.
2271
2344
  - `.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
2345
+ ([`scripts/check-release-tag.mjs`](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/scripts/check-release-tag.mjs)), gated, then
2273
2346
  published with `--provenance` through npm trusted publishing, and the GitHub
2274
2347
  Release is created from the changelog section
2275
- ([`scripts/changelog-section.mjs`](scripts/changelog-section.mjs)). No npm token is
2348
+ ([`scripts/changelog-section.mjs`](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/scripts/changelog-section.mjs)). No npm token is
2276
2349
  stored in the repository.
2277
2350
  - The governance files a public repository is expected to carry:
2278
2351
  [`SECURITY.md`](SECURITY.md), [`CONTRIBUTING.md`](CONTRIBUTING.md),
2279
- [`SUPPORT.md`](SUPPORT.md), [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md),
2352
+ [`SUPPORT.md`](SUPPORT.md), [`CODE_OF_CONDUCT.md`](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/CODE_OF_CONDUCT.md),
2280
2353
  `CODEOWNERS`, a pull-request template, bug-report and contact-link issue templates,
2281
2354
  and a Dependabot configuration for the pinned workflow actions.
2282
2355
  - [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) describes the plugin as it is
@@ -2285,16 +2358,16 @@ Repository and release engineering. No runtime behaviour changed.
2285
2358
  has to be reconstructed from a changelog and a historical record.
2286
2359
  - [`docs/RELEASING.md`](docs/RELEASING.md): the release checklist, including the
2287
2360
  operational facts that cost time to learn.
2288
- - [`docs/archive/README.en.md`](docs/archive/README.en.md): an English index of the
2361
+ - [`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
2362
  Chinese archive.
2290
2363
  - `.nvmrc` names the Node version the coverage job uses.
2291
2364
 
2292
2365
  ### Changed
2293
2366
 
2294
2367
  - 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
2368
+ [`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
2369
  (`%USERPROFILE%`, `%LOCALAPPDATA%`, `WSL_DISTRO_NAME`) and
2297
- [`test/probe/env.mjs`](test/probe/env.mjs) does the same for the Node-side probes,
2370
+ [`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
2371
  which learn their own location from `import.meta.url`. The YAML overlays are now
2299
2372
  templates with `@NAME@` placeholders, and the scripts substitute real values into
2300
2373
  generated copies that `.gitignore` keeps out of the tree. The two profile layers
@@ -2516,7 +2589,7 @@ and the package installs as a bundle that contributes its own profile layer.
2516
2589
  - The README is now **current state only**. The five sections describing designs
2517
2590
  that this one replaced — the per-process `DSH_WSL` switch and the three
2518
2591
  abandoned tool-naming routes — moved to
2519
- [docs/archive/design-history.zh.md](docs/archive/design-history.zh.md), which
2592
+ [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
2593
  keeps their original numbering and records where each conclusion lives now.
2521
2594
  The README renumbered accordingly; its §0.1 explains the split. The one part of
2522
2595
  the old desktop section that is still current (regenerating the generated
@@ -2593,7 +2666,7 @@ Documentation that a reader would have acted on, and that was no longer true:
2593
2666
  current description of the plugin while its conclusions had been superseded
2594
2667
  three times over — most sharply by the sandbox work below — which is exactly
2595
2668
  the ambiguity a second README creates. It is now
2596
- [docs/archive/engineering-record.zh.md](docs/archive/engineering-record.zh.md):
2669
+ [docs/archive/engineering-record.zh.md](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/docs/archive/engineering-record.zh.md):
2597
2670
  the same content, renamed out of the README slot and banner-headed as history,
2598
2671
  with README.md the single statement of current state. Nothing was deleted from
2599
2672
  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 (the exit-4
95
- relay's unknown entry kind is the other site, deliberately left visible). It
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 twelve probes once the app has exited. And `ci.yml` has a
230
- `probes` job you dispatch by hand: `sandbox` runs whenever WSL is present, then
231
- `mode`, `terminal`, `picker` and `missing-wsl` where the harness app is
232
- installed. It is named for a self-hosted Windows runner although its `runs-on`
233
- carries the hosted `windows-latest` label, and it is not a green no-op on a
234
- hosted runner — the `sandbox` leg fails, not skips, where the default distro has
235
- no bubblewrap.
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. Two lags in those lists today are hygiene to fix in
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`. Five files import such packages at runtime today —
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 — and a sixth
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. Two shapes. The harness probes
273
- (`run.sh`, `terminal.sh`, `sandbox-shell.sh`, `picker.sh`, `fs-routing.sh`,
274
- `missing-wsl.sh`, `sandbox-off.sh`) boot a throwaway profile whose setup steps
275
- are in the header of `test/probe/run.sh`; the six others reuse the profile
276
- `run.sh` creates rather than making their own. The distro probes (`substrate.sh`, `watch.sh`, `agent.sh`,
277
- `exec.sh`) boot nothing: they run from a distro terminal and drive the resident
278
- agent through the `wsl.exe` interop transport. `sandbox.sh` and `mode.sh` sit
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
- Counts: 33 unit test files under `test/` — 32 of them registered in the
325
- `test:unit` list, see the OI-7 note above — which need the pinned packages
326
- installed, and over 1000 `assert` call sites across them; 19 filesystem-probe
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
  [![npm](https://img.shields.io/npm/v/dsh-plugin-wsl-env)](https://www.npmjs.com/package/dsh-plugin-wsl-env)
7
7
  [![license](https://img.shields.io/npm/l/dsh-plugin-wsl-env)](LICENSE)
8
8
 
9
- A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin that runs a session inside a WSL 2 distribution on Windows.
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. Commands, file operations, the folder picker and the GUI terminal use the distro. No helper service or daemon is installed, no port is opened, and confined commands run under `bubblewrap`.
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) — five minutes, prerequisites, recipes and the honest compatibility table.
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 read and write the distro's real files on ext4.
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 -- <distro>` remains the narrower tool-install helper.
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`, each command gets a private `/tmp` instead of the distro's real one.
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, which narrows the hole but does not close it. The complete fix is distro-level: set `[interop] enabled=false` in `/etc/wsl.conf`. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#sandbox) and [SECURITY.md](SECURITY.md).
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 | install `ripgrep` with `pnpm run bootstrap -- <distro> --install` |
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 -- --check <patch>`; it fails with the complete block to paste |
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) | five-minute first run, recipes, compatibility |
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`) narrows this:
92
- confined profiles shadow `/mnt` with an empty tmpfs, so the Windows executables
93
- cannot be launched from there. It does not close the hole — a Windows executable
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#the-strict-profile).
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 | the `0.2.1-rc.3`-or-newer packages the `peerDependencies` floors declare; an app on a newer harness installs unchanged |
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 upstream packages are a release candidate, so the peer versions are pinned
64
- exactly. One example is `@deepseek-ai/dsh-fs: 0.2.1-rc.2`. An upstream change can
65
- require a new release of this plugin. The exact pins are what make that break
66
- loudly instead of quietly.
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 -- <distro>` (read-only; safe to paste) |
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 |
@@ -50,9 +50,8 @@
50
50
  # of its stderr (reason: notfound notdir perm loop exists stale io),
51
51
  # which the host's adapter maps onto the FsError codes the tool layer
52
52
  # knows. A `replace` write carrying an expected version re-verifies it
53
- # against a fresh stat one syscall before the rename and refuses with
54
- # `stale` on a mismatch — the concurrent writer wins, the model's
55
- # stale write does not land.
53
+ # against a fresh stat before the rename and refuses with `stale` on
54
+ # a mismatch; a later concurrent write can still be overwritten.
56
55
  #
57
56
  # Confinement is NOT this script's business: the host wraps argv in the bwrap
58
57
  # profile (lib/sandbox.js) before the frame is sent, so the agent executes
@@ -510,8 +509,8 @@ sweep_staging() {
510
509
  fs_write() {
511
510
  # $1 = base64 path, $2 = base64 mode (`-` keeps none), $3 = base64
512
511
  # `replace` or `no-replace`, $4 = expected version (`-` for none) — the
513
- # version string this write was based on, re-verified one syscall before the
514
- # rename so a concurrent writer wins instead of being clobbered — and $5 =
512
+ # version string this write was based on, re-verified before the rename;
513
+ # a later concurrent write can still be overwritten — and $5 =
515
514
  # content STILL base64. Mirrors fsio's POSIX publication: a private 0700
516
515
  # sibling staging dir, a 0600 temp, fsync, chmod, then an atomic rename — or
517
516
  # a hard link that refuses to replace for guarded creates.
package/cordis.patch.yml CHANGED
@@ -191,7 +191,8 @@
191
191
  # the distro's shell and fs (the realm is declared on the group);
192
192
  # 2. `tool-bash` is enabled and `tool-pwsh` disabled outright, where the
193
193
  # shipped copy derives the choice from `process.platform`;
194
- # 3. the identity: row `preset-wsl`, preset `wsl`, `order: 2`.
194
+ # 3. the identity and display metadata: row `preset-wsl`, preset `wsl`,
195
+ # `name`/`description` for the mode selector, `order: 2`.
195
196
  # Regenerate it when an app update changes the preset: take
196
197
  # `packages/bundle/web-app/presets/standard.patch.yml` from the harness tree
197
198
  # the peer pins track and run `node scripts/build-preset-wsl.mjs <that
@@ -202,6 +203,12 @@
202
203
  name: '@deepseek-ai/dsh-agent-preset'
203
204
  config:
204
205
  id: wsl
206
+ # Display metadata: `wsl` is not a registry built-in id, so the mode
207
+ # selector falls back to the raw id and "no description" unless the
208
+ # declaration carries its own copy (registry policy: user-authored
209
+ # metadata is never localized — write it in the deployment language).
210
+ name: WSL 模式
211
+ description: 在 WSL 的 Linux 实例中打开项目工作区:命令执行、shell 脚本和文件访问均在 Linux 空间内完成,以获得全套 Linux shell 工具链,受限命令经 bubblewrap 沙箱隔离。
205
212
  order: 2
206
213
  plugins:
207
214
  - id: wsl-env