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
@@ -5,11 +5,12 @@ boundary; one that is not is a bug report waiting to happen.
5
5
 
6
6
  ## The filesystem substrate (`wsl-fs`)
7
7
 
8
- The file tools run on one substrate: the resident in-distro agent, on ext4 —
9
- reads, writes and identities happen where the files live. The former
8
+ The WSL-routed file tools use one substrate: the resident in-distro agent —
9
+ reads, writes and identities happen on the filesystem backing the target path
10
+ (typically ext4 for a distro's own files, but other mounts are possible). The former
10
11
  Windows-side share backend (`substrate: "share"`) is retired: a profile still
11
- carrying the value is refused at boot with the migration, and no file tool
12
- crosses the 9p share. What to know about the one substrate there is:
12
+ carrying the value is refused at boot with the migration, and WSL-routed file
13
+ operations do not use the Windows-side 9p share. What to know about the substrate:
13
14
 
14
15
  - **A new file publishes with mode 0600.** That is the host backend's own POSIX
15
16
  publication semantics (a private 0600 staging file, renamed into place), which
@@ -27,17 +28,22 @@ crosses the 9p share. What to know about the one substrate there is:
27
28
  - **The mutation guard survives to the write.** `createIfAbsent` publishes with
28
29
  a no-replace link inside the distro, which closes the check-then-write window
29
30
  for creates outright. An overwrite or edit carries the version it was based
30
- on into the write op, and the agent re-verifies it against a fresh stat one
31
- syscall before the rename: a concurrent writer wins and the stale write
32
- refuses with `FS_STALE_VERSION`. The residual stat-then-rename pair is the
33
- same sliver a Linux-host session's own rename has.
34
- - **A write is durable against process death, not machine death.** The
35
- publication fsyncs the staged file before its atomic rename, but the
36
- containing directory is not fsynced afterwards: a kernel panic or power loss
37
- immediately after a successful write can lose the rename, and the file
38
- reverts to its pre-write content. The peer backend fsyncs its staged file the
39
- same way, but neither fsyncs the containing directory — the exposure is the
40
- peer's too, and it stays short of a crash-consistent store.
31
+ on into the write op, and the agent re-verifies it against a fresh stat before
32
+ the rename. A mismatch returns `FS_STALE_VERSION`, but a change between the
33
+ check and rename can still be overwritten; this is not an atomic
34
+ compare-and-swap.
35
+ - **A cancelled write can have an unknown outcome.** Cancellation is reported
36
+ before the agent necessarily finishes. A write already being published may
37
+ still take effect; inspect the file before retrying. The native agent refuses
38
+ queued writes when it processes their cancellation before they start. The
39
+ serial shell fallback removes requests cancelled while waiting on the host;
40
+ a request already sent to the agent may still take effect even if it has not
41
+ yet acknowledged the request.
42
+ - **A successful write is not guaranteed durable across a crash.** The native
43
+ agent syncs the staged file before publication; the shell agent attempts
44
+ `sync -f` but ignores failure. Neither syncs the containing directory after
45
+ the rename or link. A kernel panic or power loss can therefore lose data or
46
+ the published directory entry; the operation is not a crash-consistent store.
41
47
  - **A Linux filename containing a backslash cannot be addressed.** The UNC
42
48
  display form (`\\wsl.localhost\<distro>\...`) is the file's identity
43
49
  everywhere - the session header, the GUI, the caches - and the backslash is
@@ -128,7 +134,7 @@ crosses the 9p share. What to know about the one substrate there is:
128
134
  slower than a direct agent call, especially on large files; the model's own
129
135
  file operations are unaffected — they use the preset filesystem and the
130
136
  resident agent directly. Measured 2026-10-19 with
131
- [`test/probe/gui-preview.sh`](../test/probe/gui-preview.sh) (20 iterations on
137
+ [`test/probe/gui-preview.sh`](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/test/probe/gui-preview.sh) (20 iterations on
132
138
  this machine): the routed backend's `stat` p95 2.7 ms, `listDir` p95 5.1 ms,
133
139
  and a 1 MiB `readByteRange` p95 **63.5 ms/MiB** — the service's own round
134
140
  trips sit on top of those, and the 1 s/MiB budget has a wide margin. No
@@ -143,6 +149,20 @@ crosses the 9p share. What to know about the one substrate there is:
143
149
  shipped row's own caps (`maxBytes`, `maxFileBytes`, `maxLines`, `maxEntries`)
144
150
  can be overridden on the `workspace-files` row.
145
151
 
152
+ ## `@` completion
153
+
154
+ - **`@` references still walk the 9p share.** The shipped
155
+ `file-reference-local` service uses `node:fs/promises` from the Windows
156
+ harness process, so a WSL workspace pays a 9p `readdir`/`lstat` per
157
+ directory. On a 20-directory / 520-file tree the walk was **84.7 ms**
158
+ median over 9p versus **1.5 ms** on ext4 (~56×;
159
+ [`probe:file-reference`](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/test/probe/file-reference-9p.sh), 2026-10-19).
160
+ [`src/file-reference-wsl.ts`](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/src/file-reference-wsl.ts) is the distro-side
161
+ traversal (built and unit-tested) but is **not** in `cordis.patch.yml`: the
162
+ funnels it would replace are module-internal. The fallback is the 9p walk
163
+ until upstream exposes `FileReferenceTraversal`. Cost, seam and wire
164
+ trigger: [UPSTREAM-FILE-REFERENCE-SEAM.md](UPSTREAM-FILE-REFERENCE-SEAM.md).
165
+
146
166
  ## The sandbox
147
167
 
148
168
  - **The one-shot fallback has only the host deadline.** With `agent: false`, or
@@ -158,12 +178,14 @@ crosses the 9p share. What to know about the one substrate there is:
158
178
  pays the native preparation/cache cost (~70 ms once); `agentBackend: shell`
159
179
  forces the shell reads. v6's FS concurrency would only shave the native
160
180
  runtime's 7.7 ms/16 writes and is deferred.
161
- - **The native backend is the default; aarch64 is build-tested only.** The
162
- packaged x86_64 musl binary runs the 18-check suite in CI. The same job
163
- builds the aarch64 binary, but the runner cannot execute it, so its
164
- functional validation on real aarch64 hardware (or qemu) is still pending.
165
- `agentBackend: auto` still selects it on an aarch64 distro; `native` refuses
166
- if preparation fails, and `shell` forces the shell agent.
181
+ - **The native backend is the default; aarch64 is executed under qemu in CI.**
182
+ The `native packages` job builds static-musl x86_64 and aarch64 binaries and
183
+ runs the 18-check suite against both the freshly built aarch64 artifact and
184
+ the committed `agent/native/linux-aarch64/dsh-wsl-agent` under qemu-user.
185
+ Physical aarch64 WSL is out of scope (no Windows-on-ARM machine in reach);
186
+ see [COMPATIBILITY.md](COMPATIBILITY.md#scope-decisions).
187
+ `agentBackend: auto` still selects the packaged binary on an aarch64 distro;
188
+ `native` refuses if preparation fails, and `shell` forces the shell agent.
167
189
  - **The session runtime needs systemd and a lingering user manager.** The
168
190
  shipped preset sets `sessionSandbox: true`; a distro without systemd as PID 1,
169
191
  a reachable `systemd --user`, `systemd-run`, or `Linger` is refused on the
@@ -179,13 +201,15 @@ crosses the 9p share. What to know about the one substrate there is:
179
201
  Windows program, which escapes the Linux-side boundary. The plugin reports this as
180
202
  `enforcement: partial`. See [ARCHITECTURE.md](ARCHITECTURE.md#reported-enforcement).
181
203
  The `maskWindowsDrive` key on `wsl-shell`, `wsl-fs` and `fs-routing` narrows the
182
- hole: the confined profile shadows `/mnt` with an empty tmpfs, so the drive's files
183
- cannot be read or exfiltrated and its executables cannot be launched. It is not a
204
+ hole: the confined profile shadows `/mnt` with an empty tmpfs, so files and
205
+ executables at paths under `/mnt` are hidden. Other mounts, alternate access
206
+ paths and unrestricted file reads are not covered by this mask. It is not a
184
207
  closure — a command that can write the workspace can write an executable there and
185
208
  run it (binfmt interop dispatches on file content, not location) — so enforcement
186
209
  stays `partial`, the result's sandbox fact reports `windowsDrive: "masked"`, and
187
- the only complete closure remains distro-level (`[interop] enabled=false` in
188
- `wsl.conf`).
210
+ distro-level disabling of WSL interop (`[interop] enabled=false` in
211
+ `wsl.conf`) must be configured separately. On `wsl-shell`, an explicitly set
212
+ `interopVisibility` takes precedence over `maskWindowsDrive`.
189
213
  - **`bubblewrap` must be installed**, or both providers fail immediately. The
190
214
  usability probe re-runs after a failure, so installing bubblewrap while the app
191
215
  runs is believed on the next command; only a success is cached. The refusal
@@ -299,3 +323,14 @@ crosses the 9p share. What to know about the one substrate there is:
299
323
  `unknown` activity, which never accumulates idle.
300
324
  - Windows-folder host terminals (the `powershell.exe` branch) keep the shipped
301
325
  behaviour: `unknown` activity, no reclamation.
326
+
327
+ ## Host "Open in app"
328
+
329
+ - **Opening VS Code (or another Electron editor) from the host "Open in app"
330
+ menu can fail independently of this plugin.** The DSH Host process runs with
331
+ `ELECTRON_RUN_AS_NODE=1`. The open-in-app launcher inherits that variable, so
332
+ `Code.exe` starts as Node, exits 1, and the UI reports launch-failed. File
333
+ Explorer and other non-Electron launchers are unaffected; the failure is the
334
+ same on a Windows folder and on a WSL UNC workspace. This repository does
335
+ not own that spawn path. The record and the upstream fix are in
336
+ [UPSTREAM-OPEN-IN-APP.md](UPSTREAM-OPEN-IN-APP.md).
package/docs/MIGRATION.md CHANGED
@@ -20,10 +20,10 @@ still move in a **minor**. Within that freedom:
20
20
  - **Changing a key's meaning is a minor with a migration note**, because a profile
21
21
  written for the old meaning keeps booting. The note must say what changed, how
22
22
  to keep the old posture, and where the behavior differs.
23
- - **Peer pins are exact.** A new upstream peer line is tested by the
24
- [peer canary](../.github/workflows/peer-canary.yml) and lands as a minor with
25
- the delta in the release note (or as a patch if the plugin code needs no
26
- change).
23
+ - **Peer compatibility is ranged.** DSH peer dependencies declare a minimum;
24
+ development dependencies are pinned exactly. A new upstream peer line is
25
+ tested by the [peer canary](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/.github/workflows/peer-canary.yml); changes to
26
+ the tested baseline or supported range belong in the release note.
27
27
  - **Tool names and error codes are API.** `FS_OUTSIDE_DISTRO`,
28
28
  `SANDBOX_UNAVAILABLE`, `FS_STALE_VERSION` and the diagnostic scripts
29
29
  (`doctor`, `diagnose`, `preset`, `bootstrap`) keep their names and contracts;
@@ -62,7 +62,9 @@ the old meaning keeps booting, but now behaves differently. The migration:
62
62
  distro: default
63
63
  ```
64
64
 
65
- `pnpm run preset -- --distro default --out …` writes that block. The CHANGELOG
65
+ `pnpm run preset --distro default` prints that block for manual merging.
66
+ `--out` replaces the whole destination file, including unrelated overrides;
67
+ see [CONFIGURATION.md](CONFIGURATION.md#configure-wsl-shell-and-wsl-fs). The CHANGELOG
66
68
  entry for 0.12.0 states the change; this page is the durable copy. `doctor`
67
69
  reports the effective distro mode in its host/distro rows, and `--dump-config`
68
70
  shows the composed value.
@@ -76,21 +78,22 @@ shows the composed value.
76
78
  | `pnpm run test:coverage` | lines 85 / branches 84 / functions 70 | CI (Node 24, Ubuntu) |
77
79
  | Peer line tested | `peer-canary` workflow resolves `next` and runs the scratch upgrade | CI schedule/manual |
78
80
  | Packed install/upgrade/uninstall | `pnpm run e2e:install` (temp `DSH_HOME`, 0.11.0 → current, marker) | manual, this machine (10/10 on 2026-10-19) |
79
- | Real-machine probe suite | `test/probe/run-all-when-closed.sh` (needs the app closed) | manual; **not run in this environment** since the last batch's targeted probes |
81
+ | Real-machine probe suite | `test/probe/run-all-when-closed.sh --include-fs` (needs the app closed) | manual; 15 probes passed on this Windows/WSL machine on 2026-10-31 |
80
82
  | Clean Win11 + Ubuntu first run ≤ 5 min | a clean machine, a stopwatch | **not run** (the project's machine is already configured) |
81
- | aarch64 executed | the native suite on aarch64 WSL or qemu | **not run** (CI builds the binary only) |
83
+ | aarch64 executed | the native 18-check suite under qemu in CI | **run in CI** (physical aarch64 WSL is out of scope; see COMPATIBILITY.md) |
82
84
  | No open P0 bug for 30 days | issue tracker | time-based; cannot be asserted from a checkout |
83
85
 
84
- The last four rows are deliberately marked as unrun rather than assumed: a
85
- release note must not claim them, and [ROADMAP-1.0.md](ROADMAP-1.0.md#wsl-304--10-readiness)
86
- tracks them. A release that skips the manual rows still ships through the tag
87
- workflow, but its notes say which gates were executed.
86
+ The clean-machine first-run timing and the 30-day P0 window stay unrun until
87
+ someone measures them; aarch64 qemu is a CI gate. A release note must not
88
+ claim an unrun row, and [ROADMAP-1.0.md](ROADMAP-1.0.md#wsl-304--10-readiness)
89
+ tracks the 1.0 leftovers. A release that skips the remaining manual rows still
90
+ ships through the tag workflow, but its notes say which gates were executed.
88
91
 
89
92
  ## Verifying a migration locally
90
93
 
91
94
  ```bash
92
- pnpm run preset -- --distro default --check <profile>/cordis.patch.yml # no nested-row trap
93
- pnpm run doctor -- --json # effective environment facts
95
+ pnpm run preset --distro default --check <profile>/cordis.patch.yml # no nested-row trap
96
+ pnpm run doctor --json # effective environment facts
94
97
  dsh --profile <name> --dump-config # composed rows
95
98
  ```
96
99
 
package/docs/PARITY.md CHANGED
@@ -5,9 +5,10 @@ against, and the phase checklist with each phase's current status. The plan
5
5
  has landed; the rows below record where each phase ended up and what
6
6
  deviation, if any, still stands.
7
7
 
8
- Verified against: `dsh-plugin-wsl-env@0.8.0` / peer `0.2.0-rc.2` / agent
9
- protocol v4, 2026-10-06. Update this line when the peer pin or the agent
10
- protocol moves. The peer pin moved to `0.2.1-rc.2` on 2026-10-17 with every
8
+ Historical verification: `dsh-plugin-wsl-env@0.8.0` / peer `0.2.0-rc.2` / agent
9
+ protocol v4, 2026-10-06. These rows are not a fresh verification of the current
10
+ release or protocol; see [PEER-PARITY.md](PEER-PARITY.md) for the peer
11
+ comparison. The peer pin moved to `0.2.1-rc.2` on 2026-10-17 with every
11
12
  seam package's shipped artifact byte-identical to the ones these rows were
12
13
  measured against; the real-machine probes were not re-run in that move.
13
14
 
@@ -9,8 +9,8 @@ and how to reproduce them; it does not extrapolate beyond them.
9
9
 
10
10
  | | 9p share (`\\wsl.localhost\...`) | This plugin |
11
11
  |---|---|---|
12
- | File I/O | host stack crosses the VM boundary per operation | resident agent serves reads/writes on ext4 inside the distro |
13
- | Change feed | polling the share | inotify inside the distro (`watchMaxDepth: 0`) |
12
+ | File I/O | host stack crosses the VM boundary per operation | resident agent serves reads/writes inside the distro, on the target's backing filesystem |
13
+ | Change feed | polling the share | inotify for unbounded directories when available, polling otherwise |
14
14
  | Commands | host process | distro login shell through `wsl.exe` |
15
15
 
16
16
  The plugin's root filesystem routes every WSL path to the distro backend
@@ -26,7 +26,7 @@ Absolute numbers are machine-dependent; the ratio and the budgets are the point.
26
26
 
27
27
  | Scenario | 9p share | Agent (ext4) | Source |
28
28
  |---|---|---|---|
29
- | 16 concurrent shared reads | 342–614 ms (queue up to 583 ms) | **2.4–4.1 ms** after A7, first start ~70 ms once | [ADR 0002 fs queue](adr/0002-native-agent.md#fs-queue-measurement-a6-2026-10-18) |
29
+ | 16 concurrent shared reads, shell agent vs native agent | — | shell: 342–614 ms (queue up to 583 ms); native: **2.4–4.1 ms** after A7, first start ~70 ms once | [ADR 0002 fs queue](adr/0002-native-agent.md#fs-queue-measurement-a6-2026-10-18) |
30
30
  | Single FS op (host round trip) | — | p95 ≈ 22 ms | [ROADMAP-1.0.md](ROADMAP-1.0.md#7-metrics) |
31
31
  | GUI-preview backend: `stat` | — | p95 **2.7 ms** | `pnpm run probe:gui-preview` (20 iterations, 2026-10-19) |
32
32
  | GUI-preview backend: listing | — | p95 **5.1 ms** | same |
@@ -47,14 +47,16 @@ pnpm run probe:gui-preview # stat / listDir / 1 MiB page read, p50/p95
47
47
  node test/probe/fs-queue.mjs # the agent queue, sequential vs concurrent, with per-op latency
48
48
  ```
49
49
 
50
- Both need a working distro and Node; neither boots the harness. The probes print
51
- the raw samples' p50/p95/max and fail when a budget is exceeded, so a regression
52
- is a red probe rather than a claim in prose.
50
+ Both need WSL interop, a working distro and Node; neither boots the harness.
51
+ The queue measurement also needs systemd and a usable native binary; it prints
52
+ wall times and queue metrics but does not enforce latency budgets. The GUI-preview
53
+ probe reports p50/p95/max and fails when its budgets are exceeded.
53
54
 
54
55
  ## What is not measured
55
56
 
56
57
  - End-to-end GUI paint time (the browser and the service's own work are out of
57
58
  scope for these probes).
58
- - 9p numbers for large files or many small files beyond the A6 sample above;
59
- the A6 measurement is the recorded baseline, not a re-runnable benchmark.
60
- - aarch64; the binary is built but not executed in CI.
59
+ - 9p numbers for large files or many small files; A6 compared shell and native
60
+ agents, not the Windows-side 9p share.
61
+ - Physical aarch64 WSL latency (the native 18-check suite runs under qemu in
62
+ CI; that is functional correctness, not a host-round-trip measurement).
@@ -1,6 +1,6 @@
1
1
  # Quickstart
2
2
 
3
- Five minutes from a clean Windows + WSL 2 machine to a working WSL session.
3
+ Steps from a Windows + WSL 2 machine to a working WSL session.
4
4
  This page is the entry point; every claim links to the document that owns it.
5
5
 
6
6
  ## 1. Prerequisites
@@ -11,10 +11,11 @@ This page is the entry point; every claim links to the document that owns it.
11
11
  | A distro | one line in the list above | `wsl.exe --install -d Ubuntu` |
12
12
  | `bubblewrap` | `wsl.exe -d <distro> -- which bwrap` | `wsl.exe -d <distro> -u root -- apt-get install -y bubblewrap` |
13
13
  | systemd + Linger (for the session runtime) | `node <plugin>/scripts/doctor.mjs` (installed) or `pnpm run doctor` (checkout) | `[boot] systemd=true` in `/etc/wsl.conf`, `wsl --shutdown`, then `loginctl enable-linger "$USER"`; or set `sessionSandbox: auto`/`false` |
14
- | Node 22.19+ or 24 | only needed for source/development work | not needed to use the published plugin |
14
+ | Node `^22.19.0` or `>=24.0.0` | only needed for source/development work | not needed inside the distro to use the published plugin |
15
15
 
16
- The doctor (`scripts/doctor.mjs`, or `pnpm run doctor` from a checkout) covers all
17
- of the above in one pass and prints a copy-paste remedy per failure. It is read-only unless you pass `--fix` (stale scopes) or
16
+ The doctor (`scripts/doctor.mjs`, or `pnpm run doctor` from a checkout) checks
17
+ the environment and prints remedies for detected failures. Confirm the distro's
18
+ WSL version separately with `wsl.exe -l -v`. Doctor is read-only unless you pass `--fix` (stale scopes) or
18
19
  `--fix --install` (packages, after a confirmation).
19
20
 
20
21
  ## 2. Install
@@ -23,6 +24,8 @@ From a Windows PowerShell where the DSH CLI is available:
23
24
 
24
25
  ```powershell
25
26
  dsh plugin --profile <profile> add dsh-plugin-wsl-env
27
+ # 若解析到旧版本(发布当天的元数据缓存),显式指定:
28
+ # dsh plugin --profile <profile> add dsh-plugin-wsl-env@<latest>
26
29
  dsh --profile <profile> --dump-config # the plugin rows must be there
27
30
  ```
28
31
 
@@ -37,6 +40,23 @@ or an UNC path directly. The session's commands, file tools, terminal and previe
37
40
  all run in that distro; a Windows folder keeps the normal Windows environment.
38
41
  Two distros can be open at once, each with its own environment.
39
42
 
43
+ ### Open the workspace in VS Code (Remote-WSL)
44
+
45
+ Opening the WSL folder as a Windows UNC share works but is the degraded mode:
46
+ files cross 9p, the terminal is PowerShell, extensions run on Windows. The full
47
+ integration is VS Code's Remote-WSL — the server runs inside the distro, the
48
+ integrated terminal is the distro shell, and file operations are local ext4:
49
+
50
+ ```powershell
51
+ # from the plugin checkout:
52
+ scripts/open-wsl-vscode.ps1 -Path "\\wsl.localhost\ubuntu\home\me\proj"
53
+ # or by hand:
54
+ code --folder-uri "vscode-remote://wsl+ubuntu/home/me/proj"
55
+ ```
56
+
57
+ Requires the `code` shim on PATH (VS Code installer adds it) and the
58
+ Remote-WSL extension.
59
+
40
60
  ![The directory picker listing a WSL distro's folders](screenshots/picker.png)
41
61
 
42
62
  The terminal panel runs in the same distro — `uname -a` answers with the
@@ -55,10 +75,13 @@ distro kernel. If the first command fails, the error names the remedy; the
55
75
  [doctor](CONFIGURATION.md) and the [Troubleshooting table](../README.md#troubleshooting)
56
76
  cover the common cases.
57
77
 
58
- The sandbox is enforced by the kernel, not a promise: the probe below runs the
59
- shipped bwrap profiles live — writes inside the workspace land, writes outside
60
- are refused with `EROFS`, and the strict profile hides `/mnt` entirely
61
- ([`test/probe/sandbox.sh`](../test/probe/sandbox.sh)):
78
+ The probe below runs the shipped kernel-enforced bwrap profiles live — writes
79
+ inside the workspace land, tested writes outside are refused with `EROFS`, and
80
+ the strict profile hides `/mnt` entirely
81
+ ([`test/probe/sandbox.sh`](https://github.com/Big-Dao/dsh-plugin-wsl-env/blob/main/test/probe/sandbox.sh)):
82
+
83
+ This is partial isolation, not a Windows security boundary: Windows interop
84
+ can still launch executables outside `/mnt`. See [LIMITATIONS.md](LIMITATIONS.md).
62
85
 
63
86
  ![The sandbox probe: every expectation holds, the kernel refuses what the profile does not grant](screenshots/sandbox.png)
64
87
 
@@ -75,8 +98,9 @@ pnpm test
75
98
  pnpm dev # a dev server survives the command that started it with sessionSandbox: true
76
99
  ```
77
100
 
78
- The server's port appears in `DSH_WSL_PORTS` on the next snapshot (10 s by
79
- default); open it from Windows at `http://localhost:<port>`.
101
+ The server's port can appear in `DSH_WSL_PORTS` after a successful snapshot
102
+ (refreshed every 10 s by default); open a forwarded port from Windows at
103
+ `http://localhost:<port>`.
80
104
 
81
105
  ### Python
82
106
 
@@ -106,14 +130,16 @@ go build ./...
106
130
  go test ./...
107
131
  ```
108
132
 
109
- The module cache lives in the distro's `$GOPATH`, so builds stay on ext4.
133
+ The module cache normally lives under the distro user's `$GOPATH`; its actual
134
+ location and backing filesystem depend on the Go environment and mounts.
110
135
 
111
136
  ### Monorepo
112
137
 
113
138
  Open the repository root as the workspace. Each package's commands run from the
114
139
  same distro; `pnpm -r`, `turbo`, `nx` and `make` see one Linux filesystem. The
115
- file watcher uses inotify inside the distro (`watchMaxDepth: 0`). If a change is
116
- missed, see the watcher bounds in [CONFIGURATION.md](CONFIGURATION.md).
140
+ file watcher uses inotify for unbounded directories when `inotifywait` is
141
+ available; otherwise it falls back to polling. If a change is missed, see the
142
+ watcher bounds in [CONFIGURATION.md](CONFIGURATION.md).
117
143
 
118
144
  ## Compatibility
119
145
 
@@ -123,21 +149,20 @@ executed here.
123
149
 
124
150
  | Tier | Configuration | Status |
125
151
  |---|---|---|
126
- | 1 | Ubuntu 24.04 or Debian 12, systemd, x86_64 | Probed on Ubuntu (the project's development machine; the real-machine suite is manual). Debian is a Tier 1 target, probe pending |
127
- | 2 | Other systemd distros, aarch64 | The aarch64 native binary is built by CI but not executed here; aarch64 WSL or qemu is the pending leg |
152
+ | 1 | Ubuntu / Debian, systemd, x86_64 | Probed. Debian harness-level probes pass against a `wslfs-debian` profile (2026-10-26). |
153
+ | 2 | Fedora, openSUSE, Arch, Alpine; aarch64 native agent | Distro matrix in [COMPATIBILITY.md](COMPATIBILITY.md). aarch64 18-check runs under qemu in CI; physical aarch64 WSL is out of scope. |
128
154
  | Unsupported | WSL 1, Linux/macOS hosts, Windows without WSL 2 | Not supported; the plugin attaches to `wsl.exe` |
129
155
 
130
156
  With `sessionSandbox: auto` a distro without systemd/Linger still gets the
131
157
  per-command `bubblewrap` sandbox and the result reports the narrower scope;
132
- `true` refuses. See [CONFIGURATION.md](CONFIGURATION.md#the-strict-profile) for
158
+ `true` refuses. See [CONFIGURATION.md](CONFIGURATION.md#sandbox-and-preflight) for
133
159
  the sandbox trade-offs and [PERFORMANCE.md](PERFORMANCE.md) for the measured
134
160
  latency.
135
161
 
136
162
  ## Demo
137
163
 
138
- There is no GIF or screenshot in this repository yet: producing one needs a
139
- capture of the GUI, which this project has not recorded. The
140
- [Troubleshooting table](../README.md#troubleshooting) and the probe output
141
- (`pnpm run probe:sandbox`, `pnpm run probe:gui-preview`) are the reproducible
142
- evidence in the meantime; the absence is recorded in
164
+ The screenshots above show the picker, terminal, preview and sandbox probe.
165
+ There is no GIF yet. The [Troubleshooting table](../README.md#troubleshooting)
166
+ and the probe output (`pnpm run probe:sandbox`, `pnpm run probe:gui-preview`)
167
+ provide reproducible evidence; the remaining demo work is tracked in
143
168
  [ROADMAP-1.0.md](ROADMAP-1.0.md#wsl-302--discoverability-and-docs-entry).
package/docs/README.zh.md CHANGED
@@ -6,9 +6,9 @@
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
- 一个 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 插件,让会话在 Windows 的 WSL 2 发行版中运行。
9
+ 一个 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 插件,在 Windows 的 WSL 2 发行版中打开项目工作区。
10
10
 
11
- Harness 进程仍在 Windows 上。命令、文件操作、文件夹选择器和 GUI 终端都使用发行版环境。发行版内不安装辅助服务或守护进程,也不开放端口,受限命令在 `bubblewrap` 中运行。
11
+ Harness 进程仍在 Windows 上。命令执行、shell 脚本和文件访问都在发行版的 Linux 空间内完成,以获得全套 Linux shell 工具链。发行版内不安装辅助服务或守护进程,也不开放端口,受限命令在 `bubblewrap` 中运行。
12
12
 
13
13
  仅支持 Windows + WSL 2。
14
14
 
@@ -16,7 +16,7 @@ Harness 进程仍在 Windows 上。命令、文件操作、文件夹选择器和
16
16
 
17
17
  Windows 上的编程工具要在 WSL 项目中工作,有三条路:把整个工具装进发行版、连接发行版内的辅助服务,或由插件直接提供发行版环境。本插件是第三条。两类会话共用一套流程,同时保留发行版的真实工具和命令沙箱。
18
18
 
19
- - **真实的 Linux 环境。** 命令在发行版自己的登录 shell 中运行,`PATH`、`nvm`、`cargo`、`pyenv` 和 rc 文件都生效。文件工具直接读写发行版 ext4 上的真实文件。
19
+ - **真实的 Linux 环境。** 命令在发行版自己的登录 shell 中运行,`PATH`、`nvm`、`cargo`、`pyenv` 和 rc 文件都生效。文件工具通过发行版内的 agent 访问发行版路径,底层文件系统取决于路径。
20
20
  - **发行版内没有服务。** 没有辅助服务、没有守护进程、没有监听端口。唯一持久化的内容是 `~/.cache/dsh-wsl/agent/` 下按内容寻址的原生 agent 副本。
21
21
  - **默认有沙箱。** 受限命令在发行版内的 `bubblewrap` 中运行。缺少 `bubblewrap` 时命令直接失败,不会脱箱运行。
22
22
  - **按会话生效。** 打开发行版文件夹的会话获得发行版环境;打开 Windows 文件夹的会话保留 Windows 工具。两者可同时运行在一个 harness 进程中。
@@ -72,7 +72,7 @@ dsh --profile wsl # 启动
72
72
  wsl.exe -d <发行版> -u root -- apt-get install -y bubblewrap
73
73
  ```
74
74
 
75
- 在插件检出目录中,`pnpm run bootstrap -- <发行版>` 检查四个发行版工具,并按包管理器打印安装命令;加 `--install` 直接执行。
75
+ 在插件检出目录中,`pnpm run bootstrap <发行版>` 检查四个发行版工具,并按包管理器打印安装命令;加 `--install` 仅在缺少 bubblewrap 时执行该命令。若只缺少其他工具,请自行执行打印的命令。
76
76
 
77
77
  升级:再次执行 `add` 命令。卸载:`dsh plugin --profile wsl remove dsh-plugin-wsl-env`;之后可删除 `~/.cache/dsh-wsl`。如果你为了恢复预览在 profile 里加过 `workspace-files-wsl` 的临时覆盖,升级后请删掉:插件不再挂载该行。
78
78
 
@@ -135,11 +135,11 @@ wsl preset(每个会话)
135
135
  | `workspace-write` | 上面的权限,加上会话工作区;会话 runtime 下 `/tmp` 是发行版真实 `/tmp` |
136
136
  | `danger-full-access` | 不经过沙箱 |
137
137
 
138
- 设置 `sessionSandbox: false` 时,每条命令使用私有的 `/tmp`,而不是发行版的真实 `/tmp`。
138
+ 设置 `sessionSandbox: false` 时,`workspace-write` 命令使用私有的 `/tmp`,而不是发行版的真实 `/tmp`;`read-only` 不挂载可写的 `/tmp`。
139
139
 
140
140
  `bubblewrap` 是必需项。缺失或损坏时,受限命令报 `SANDBOX_UNAVAILABLE`,不会运行。在对应的行设置 `sandbox: false` 可关闭沙箱;模型会收到"该操作无沙箱"的说明。
141
141
 
142
- 隔离上报为 `partial`。发行版内的命令仍可通过 `/mnt/c` 启动 Windows 程序,`bubblewrap` 无法控制 Windows 进程。`maskWindowsDrive: true` 让受限视图看不到 `/mnt`,能收窄这个洞但不能关死。彻底关闭要在发行版层面设置 `[interop] enabled=false`(`/etc/wsl.conf`)。见 [docs/ARCHITECTURE.md](ARCHITECTURE.md#sandbox) 和 [SECURITY.md](../SECURITY.md)。
142
+ 隔离上报为 `partial`。发行版内的命令仍可通过 `/mnt/c` 启动 Windows 程序,`bubblewrap` 无法控制 Windows 进程。`maskWindowsDrive: true` 让受限视图看不到 `/mnt`,但会被 `interopVisibility: visible` 覆盖;遮罩只能收窄这个洞,不能关死。关闭 WSL interop 需要另行在发行版的 `/etc/wsl.conf` 中设置 `[interop] enabled=false`。见 [docs/ARCHITECTURE.md](ARCHITECTURE.md#sandbox) 和 [SECURITY.md](../SECURITY.md)。
143
143
 
144
144
  ## 常见问题
145
145
 
@@ -147,7 +147,7 @@ wsl preset(每个会话)
147
147
  |---|---|
148
148
  | 每条命令都报 `SANDBOX_UNAVAILABLE` | 在发行版内安装 `bubblewrap`,或设置 `sandbox: false` |
149
149
  | 会话在第一条命令前就被拒绝 | 启用 systemd 和 linger,或在 `wsl-shell`、`wsl-fs` 上设置 `sessionSandbox: false` |
150
- | 发行版内 `glob`、`grep` 很慢 | 运行 `pnpm run bootstrap -- <发行版> --install` 安装 `ripgrep` |
150
+ | 发行版内 `glob`、`grep` 很慢 | 运行 `pnpm run bootstrap <发行版>`,使用打印的安装命令安装 `ripgrep` |
151
151
  | `dsh plugin add` 没有输出 | 插件已安装;这是成功 |
152
152
  | 改了 `lib/` 但不生效 | 运行 `pnpm run sync:windows`,然后重启应用 |
153
153