dsh-plugin-wsl-env 0.7.5 → 0.9.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 +345 -8
- package/CONTRIBUTING.md +221 -68
- package/README.md +50 -35
- package/SECURITY.md +18 -2
- package/SUPPORT.md +23 -7
- package/cordis.patch.yml +9 -0
- package/docs/ARCHITECTURE.md +200 -57
- package/docs/CONFIGURATION.md +180 -16
- package/docs/LIMITATIONS.md +91 -33
- package/docs/PARITY.md +66 -38
- package/docs/PEER-PARITY.md +95 -32
- package/docs/README.zh.md +49 -31
- package/docs/UPSTREAM-FSIO-EXPORT.md +159 -50
- package/docs/UPSTREAM-TERMINAL-TITLE.md +49 -14
- package/lib/agent-confined.d.ts +42 -0
- package/lib/agent-confined.js +25 -41
- package/lib/agent-errors.d.ts +31 -0
- package/lib/agent-errors.js +19 -16
- package/lib/agent-exec.d.ts +159 -0
- package/lib/agent-exec.js +151 -165
- package/lib/agent-protocol.d.ts +192 -0
- package/lib/agent-protocol.js +76 -100
- package/lib/agent-shared.d.ts +27 -0
- package/lib/agent-shared.js +12 -12
- package/lib/agent.d.ts +353 -0
- package/lib/agent.js +536 -552
- package/lib/auto-preset.d.ts +110 -0
- package/lib/auto-preset.js +157 -141
- package/lib/bwrap.d.ts +90 -0
- package/lib/bwrap.js +18 -18
- package/lib/file-reference-wsl.d.ts +160 -0
- package/lib/file-reference-wsl.js +192 -173
- package/lib/fs-decisions.d.ts +107 -0
- package/lib/fs-decisions.js +64 -60
- package/lib/fs-routing.d.ts +182 -0
- package/lib/fs-routing.js +173 -116
- package/lib/fs-substrate.d.ts +258 -0
- package/lib/fs-substrate.js +248 -248
- package/lib/fs.d.ts +10 -0
- package/lib/fs.js +5 -0
- package/lib/fsio-agent.d.ts +307 -0
- package/lib/fsio-agent.js +414 -410
- package/lib/fsio-text.d.ts +115 -0
- package/lib/fsio-text.js +104 -84
- package/lib/git-route.d.ts +100 -0
- package/lib/git-route.js +46 -47
- package/lib/index.d.ts +1003 -0
- package/lib/index.js +1106 -1003
- package/lib/listing.d.ts +114 -0
- package/lib/listing.js +68 -55
- package/lib/paths.d.ts +182 -0
- package/lib/paths.js +134 -109
- package/lib/picker.d.ts +266 -0
- package/lib/picker.js +371 -351
- package/lib/ports.d.ts +44 -0
- package/lib/ports.js +39 -29
- package/lib/preflight.d.ts +91 -0
- package/lib/preflight.js +94 -0
- package/lib/preset-choice.d.ts +33 -0
- package/lib/preset-choice.js +15 -7
- package/lib/probe-cache.d.ts +51 -0
- package/lib/probe-cache.js +43 -29
- package/lib/sandbox-core.d.ts +149 -0
- package/lib/sandbox-core.js +239 -87
- package/lib/sandbox.d.ts +48 -0
- package/lib/sandbox.js +4 -4
- package/lib/search-exec.d.ts +96 -0
- package/lib/search-exec.js +52 -79
- package/lib/search-route.d.ts +75 -0
- package/lib/search-route.js +30 -31
- package/lib/shell-env.d.ts +88 -0
- package/lib/shell-env.js +78 -74
- package/lib/shell.d.ts +10 -0
- package/lib/shell.js +5 -0
- package/lib/subprocess.d.ts +313 -0
- package/lib/subprocess.js +267 -262
- package/lib/terminal-activity.d.ts +98 -0
- package/lib/terminal-activity.js +29 -39
- package/lib/terminal-route.d.ts +27 -0
- package/lib/terminal-route.js +16 -8
- package/lib/watcher.d.ts +77 -0
- package/lib/watcher.js +120 -126
- package/lib/workspace-files-route.d.ts +132 -0
- package/lib/workspace-files-route.js +73 -81
- package/lib/workspace-files-wsl.d.ts +257 -0
- package/lib/workspace-files-wsl.js +311 -251
- package/lib/wsl.d.ts +266 -0
- package/lib/wsl.js +150 -155
- package/locale/preflight/en.json +6 -0
- package/locale/preflight/zh.json +6 -0
- package/package.json +78 -22
- package/scripts/diagnose.sh +92 -0
package/SUPPORT.md
CHANGED
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
| Linux side | WSL2 with at least one distro |
|
|
9
9
|
| DSH packages | the `0.2.0-rc.2`-era packages listed in `peerDependencies` |
|
|
10
10
|
| Plugin version | the latest published version only |
|
|
11
|
-
| Node |
|
|
11
|
+
| Node | 22.19+ (`^22.19.0`), or 24 and later (`>=24.0.0`) |
|
|
12
|
+
|
|
13
|
+
Node 20 is outside the range on purpose: the floor is the harness host's own, and
|
|
14
|
+
CI covers 22 and 24. `engines` in `package.json` is the authoritative statement,
|
|
15
|
+
not this table.
|
|
12
16
|
|
|
13
17
|
Outside Windows there is nothing for the plugin to attach to. It runs `ctx.shell`
|
|
14
18
|
inside the distro through `wsl.exe`. A Linux host, a macOS host, or WSL1 is not a
|
|
@@ -21,7 +25,10 @@ Open an issue in the repository:
|
|
|
21
25
|
https://github.com/Big-Dao/dsh-plugin-wsl-env/issues
|
|
22
26
|
|
|
23
27
|
Use it for bug reports and for questions about configuration. Search the open and
|
|
24
|
-
the closed issues first
|
|
28
|
+
the closed issues first, and check whether the answer is already written down:
|
|
29
|
+
[docs/LIMITATIONS.md](docs/LIMITATIONS.md) lists the known design boundaries, and
|
|
30
|
+
the Troubleshooting table in [README.md](README.md#troubleshooting) covers the
|
|
31
|
+
common failures (`SANDBOX_UNAVAILABLE`, a missing `rg`, and the rest).
|
|
25
32
|
|
|
26
33
|
For a security issue, follow [SECURITY.md](SECURITY.md). Do not open a public
|
|
27
34
|
issue for one.
|
|
@@ -31,8 +38,12 @@ issue for one.
|
|
|
31
38
|
- macOS, and Linux hosts that are not WSL2.
|
|
32
39
|
- Windows without WSL2.
|
|
33
40
|
- A modified DSH build, or a fork of the harness packages.
|
|
34
|
-
- A distro the plugin cannot recognize
|
|
35
|
-
|
|
41
|
+
- A distro the plugin cannot recognize, which is what its probes report: no
|
|
42
|
+
distro from `wsl.exe -l -q`, a `$HOME` that is empty or not an absolute path,
|
|
43
|
+
or no absolute shell from the `getent passwd` / `$SHELL` probes. The detection
|
|
44
|
+
lives in `src/wsl.ts`.
|
|
45
|
+
- A distro without `bwrap`. Commands there fail closed rather than run
|
|
46
|
+
unconfined.
|
|
36
47
|
- The upstream `@deepseek-ai/*` packages. Report those to their own projects.
|
|
37
48
|
- Third-party npm mirrors. Install from the registry the package was published to.
|
|
38
49
|
|
|
@@ -47,14 +58,19 @@ There is no long-term support branch. Fixes go to the latest version.
|
|
|
47
58
|
|
|
48
59
|
## What a good bug report contains
|
|
49
60
|
|
|
61
|
+
The one-command path: `pnpm run diagnose` (from a checkout of the plugin, or
|
|
62
|
+
`bash scripts/diagnose.sh`) collects most of the table below — Windows build,
|
|
63
|
+
WSL status, distro, kernel, package family, the four tools with their versions,
|
|
64
|
+
and a bwrap functional probe — as a paste-ready report. Run it first, then add
|
|
65
|
+
what it cannot see:
|
|
66
|
+
|
|
50
67
|
| Item | How to get it |
|
|
51
68
|
|---|---|
|
|
69
|
+
| The diagnose report | `pnpm run diagnose -- <distro>` (read-only; safe to paste) |
|
|
52
70
|
| Plugin version | `npm ls dsh-plugin-wsl-env`, or the git tag you built from |
|
|
53
71
|
| DSH version | the version of the harness you run |
|
|
54
|
-
| Distro and kernel | `wsl.exe -l -v`, and `uname -r` inside the distro |
|
|
55
|
-
| Windows build | `winver` |
|
|
56
72
|
| The composed configuration | `dsh --profile <name> --dump-config`, in particular the `dsh-plugin-wsl-env` layer and the rows it patches |
|
|
57
|
-
| Sandbox behaviour | the output of `npm run probe:sandbox`,
|
|
73
|
+
| Sandbox behaviour, in depth | the output of `npm run probe:sandbox`, run from inside the distro (`wsl.exe -d <distro>` first) — the probe calls `bwrap` and reads `lib/bwrap.js` there, so it needs both in the distro |
|
|
58
74
|
| What you expected and what happened | one paragraph each |
|
|
59
75
|
| A reproduction | the exact command, or the exact sequence of clicks |
|
|
60
76
|
|
package/cordis.patch.yml
CHANGED
|
@@ -162,6 +162,15 @@
|
|
|
162
162
|
- id: auto-preset
|
|
163
163
|
name: 'dsh-plugin-wsl-env/auto-preset'
|
|
164
164
|
|
|
165
|
+
# Session-start sandbox preflight: when a distro session opens, run the
|
|
166
|
+
# providers' own bwrap usability probe (shared cache, so the first confined
|
|
167
|
+
# command pays nothing) and surface a missing or broken bubblewrap as a
|
|
168
|
+
# warning with the full remedy — instead of the first command's fail-closed
|
|
169
|
+
# error being the discovery moment. Advisory only: it never installs and
|
|
170
|
+
# never changes the providers' fail-closed semantics.
|
|
171
|
+
- id: wsl-preflight
|
|
172
|
+
name: 'dsh-plugin-wsl-env/preflight'
|
|
173
|
+
|
|
165
174
|
- insert:
|
|
166
175
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
167
176
|
# preset-wsl — the WSL environment, scoped to agents that select this preset.
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -11,28 +11,56 @@ DSH exposes its capabilities as services. The model-facing tools (`bash`, `read`
|
|
|
11
11
|
`write`, `edit`, `glob`, `grep`) consume those services. They never touch a
|
|
12
12
|
filesystem or a shell directly.
|
|
13
13
|
|
|
14
|
-
This plugin supplies WSL-backed implementations of three of them,
|
|
15
|
-
smaller integrations.
|
|
14
|
+
This plugin supplies WSL-backed implementations of three of them, takes over two
|
|
15
|
+
root-plane services, and adds three smaller integrations.
|
|
16
16
|
|
|
17
17
|
## The three service providers
|
|
18
18
|
|
|
19
19
|
| Service | Class | File | What it does |
|
|
20
20
|
|---|---|---|---|
|
|
21
|
-
| `ctx.shell` | `WslShellExecutor` | [`lib/index.js`](../lib/index.js) | Runs each command
|
|
21
|
+
| `ctx.shell` | `WslShellExecutor` | [`lib/index.js`](../lib/index.js) | Runs each command in the distro's login shell over the resident in-distro agent (the default), wrapped in a distro-side `bwrap` sandbox; while the agent is out, or with `agent: false`, the same command falls back to one `wsl.exe -d <distro> --cd <linux dir> --exec <login shell> -lc <cmd>` process. |
|
|
22
22
|
| `ctx.fs` | `WslFileSystem` | [`lib/index.js`](../lib/index.js) | Serves the file tools from real distro files on the resident in-distro agent, on ext4 — the former Windows-side share substrate is retired (see below). |
|
|
23
|
-
| `ctx.subprocess` | `WslSubprocessRuntime` | [`lib/subprocess.js`](../lib/subprocess.js) | Opens the GUI terminal inside the distro, in the session workspace, and rewrites the file-search
|
|
23
|
+
| `ctx.subprocess` | `WslSubprocessRuntime` | [`lib/subprocess.js`](../lib/subprocess.js) | Opens the GUI terminal inside the distro, in the session workspace, and rewrites the file-search and git-snapshot spawns into the distro (see below). |
|
|
24
24
|
|
|
25
25
|
`WslShellExecutor` extends the shipped `LocalBashExecutor`, and `WslFileSystem`
|
|
26
26
|
extends the shipped `LocalFileSystem`. Only the parts that need a distro are
|
|
27
27
|
overridden.
|
|
28
28
|
|
|
29
|
+
## The two root-plane takeovers
|
|
30
|
+
|
|
31
|
+
Two of the shipped services are session-less — the GUI file tree and the root
|
|
32
|
+
`ctx.fs` — so they cannot be served from an agent preset. The layer replaces
|
|
33
|
+
their shipped rows outright, with subclasses that route by coordinate:
|
|
34
|
+
|
|
35
|
+
| Service | Class | File | What it does |
|
|
36
|
+
|---|---|---|---|
|
|
37
|
+
| `ctx.workspaceFiles` | `WorkspaceFilesWsl` | [`lib/workspace-files-wsl.js`](../lib/workspace-files-wsl.js) | The GUI file tree and previews for a `\\wsl.localhost\<distro>` workspace, served from inside the distro over the resident agent instead of Windows-side 9P walks. Extends the shipped service: a distro UNC routes distro-side, a drive path calls `super`, and the watch stream delegates unchanged — so replacing the row is safe for Windows-folder sessions too. Replaces the shipped `workspace-files` row. |
|
|
38
|
+
| `ctx.fs` (root plane) | `WslRoutingFileSystem` | [`lib/fs-routing.js`](../lib/fs-routing.js) | The root filesystem, routed by coordinate: a distro UNC identity is served by the resident agent (real watch events, ext4 reads), a drive path stays host-native. Replaces the shipped `fs-sandbox` row, whose write fence has no root-plane consumers — the model's writes ride the preset filesystems. [`root-fs-routing.md`](root-fs-routing.md) is the design. |
|
|
39
|
+
|
|
40
|
+
Both routes are composition-level for the mirror-image reason the terminal is
|
|
41
|
+
(see below): no session identity reaches them. Each takeover is a top-level
|
|
42
|
+
`disabled:` entry targeting the shipped row plus a bare `insert:` list holding
|
|
43
|
+
the replacement — because a `disabled` written *inside* an insert list is not a
|
|
44
|
+
patch but a duplicate-id data row, and a nested `insert:` is never interpreted at
|
|
45
|
+
all. [`cordis.patch.yml`](../cordis.patch.yml) carries the full row list, and
|
|
46
|
+
`test/composition.test.mjs` runs the file through the real patch algorithm to
|
|
47
|
+
keep the shape honest.
|
|
48
|
+
|
|
49
|
+
One further module ships built but deliberately unwired:
|
|
50
|
+
[`lib/file-reference-wsl.js`](../lib/file-reference-wsl.js), the distro-side
|
|
51
|
+
traversal strategy for `@` completion. It is built and tested, but **not** wired
|
|
52
|
+
into `cordis.patch.yml` — the seam it consumes is an upstream proposal
|
|
53
|
+
([`upstream/rfc-wsl-workspaces.md`](upstream/rfc-wsl-workspaces.md)), and
|
|
54
|
+
reaching the funnels without it would mean subclassing past module-internal
|
|
55
|
+
functions.
|
|
56
|
+
|
|
29
57
|
## The three smaller integrations
|
|
30
58
|
|
|
31
59
|
| Subpath | Class or name | File | What it does |
|
|
32
60
|
|---|---|---|---|
|
|
33
61
|
| `dsh-plugin-wsl-env/picker` | `WslDirectoryPicker` | [`lib/picker.js`](../lib/picker.js) | Adds the installed distros to the folder picker, next to the Windows home directory, and lists distro levels from inside the distro (the resident's `ls`, one-shot `wsl.exe` when it is out). |
|
|
34
62
|
| `dsh-plugin-wsl-env/auto-preset` | `auto-preset` | [`lib/auto-preset.js`](../lib/auto-preset.js) | Binds the `wsl` agent preset when a new session's workspace is inside a distro. |
|
|
35
|
-
| `dsh-plugin-wsl-env/shell-env` | `wsl-shell-env` | [`lib/shell-env.js`](../lib/shell-env.js) | Contributes `DSH_WSL_DISTRO`, `DSH_WSL_SHELL`, and `
|
|
63
|
+
| `dsh-plugin-wsl-env/shell-env` | `wsl-shell-env` | [`lib/shell-env.js`](../lib/shell-env.js) | Contributes `DSH_WSL_DISTRO`, `DSH_WSL_SHELL`, `DSH_WSL_HOME` and `DSH_WSL_PORTS` to the managed `DSH_*` namespace. The port list is a snapshot, refreshed every `portsRefreshMs` (10000 by default), so a server started moments ago may not be listed yet. |
|
|
36
64
|
|
|
37
65
|
Three modules support them and import no DSH package.
|
|
38
66
|
[`lib/paths.js`](../lib/paths.js) translates paths.
|
|
@@ -50,7 +78,9 @@ The environment is per session, not per process:
|
|
|
50
78
|
| Row | Mounted | Why there |
|
|
51
79
|
|---|---|---|
|
|
52
80
|
| `wsl-shell`, `wsl-fs` | inside `preset-wsl`, an agent preset with isolate realms | so one process can serve a Windows workspace and a distro workspace at the same time |
|
|
53
|
-
| `subprocess-wsl` | at the app level, called the composition | because no session identity reaches the terminal controller, see below |
|
|
81
|
+
| `subprocess-wsl` | at the app level, called the composition; the shipped `subprocess` row is disabled | because no session identity reaches the terminal controller, see below |
|
|
82
|
+
| `workspace-files-wsl` | at the app level; the shipped `workspace-files` row is disabled | the GUI file tree is session-less, so it cannot be served from a preset |
|
|
83
|
+
| `fs-routing` | at the app level; the shipped `fs-sandbox` row is disabled | the root `ctx.fs` is session-less, and its write fence has no root-plane consumers |
|
|
54
84
|
| `directory-picker-wsl`, `wsl-shell-env`, `auto-preset` | at the app level | they answer questions that are not scoped to a session |
|
|
55
85
|
|
|
56
86
|
The preset mount requires the isolate realms. Without them the registry rejects the
|
|
@@ -71,8 +101,15 @@ does not use it.
|
|
|
71
101
|
|
|
72
102
|
So the layer disables the shipped `subprocess` row and inserts
|
|
73
103
|
`dsh-plugin-wsl-env/subprocess` in its place at the app level. The subclass
|
|
74
|
-
overrides `spawnTerminal`
|
|
75
|
-
|
|
104
|
+
overrides two methods: `spawnTerminal` rewrites a `wsl.exe` launch into
|
|
105
|
+
`wsl.exe -d <distro> --cd <linux dir>`, and `spawn()` rewrites two kinds of
|
|
106
|
+
launches into the distro — the file-search tool's packaged ripgrep (agent-first,
|
|
107
|
+
with the one-shot `wsl.exe --exec` handle as the fallback when the agent is out)
|
|
108
|
+
and git snapshot commands. Both spawn decisions are pure coordinate functions
|
|
109
|
+
(`lib/search-route.js`, `lib/git-route.js`); every other `spawn()` — including
|
|
110
|
+
the one-shot fallback's own `wsl.exe -lc <command>` (the default agent path
|
|
111
|
+
carries the command over the resident instead of spawning) — reaches the shipped
|
|
112
|
+
implementation untouched, as do the pwsh executor and the LSP host.
|
|
76
113
|
|
|
77
114
|
The terminal's execution world follows the session (`hostSessions`, default on):
|
|
78
115
|
a WSL-folder session runs the distro shell, a Windows-folder session runs
|
|
@@ -124,9 +161,11 @@ profile carrying the value is refused at boot, and no file tool crosses the
|
|
|
124
161
|
refuses confined mutations with the bootstrap command, the same closed
|
|
125
162
|
failure the command path has.
|
|
126
163
|
|
|
127
|
-
Writes fence with the same host-side policy
|
|
128
|
-
|
|
129
|
-
|
|
164
|
+
Writes fence with the same host-side policy the command path's sandbox
|
|
165
|
+
describes — `WslFileSystem.checkedTarget`, over the same writable list; the
|
|
166
|
+
command path itself is confined by the distro-side profile and never calls it —
|
|
167
|
+
and the substrate does not change what `sandboxMode` reports. The wire protocol
|
|
168
|
+
the substrate speaks is documented in
|
|
130
169
|
[`agent/wsl-agent.sh`](../agent/wsl-agent.sh); the orchestration layers are
|
|
131
170
|
[`lib/fsio-agent.js`](../lib/fsio-agent.js) (fsio's mechanics over the FS
|
|
132
171
|
frames), [`lib/fs-substrate.js`](../lib/fs-substrate.js) (the provider-shaped
|
|
@@ -148,19 +187,26 @@ sandbox.
|
|
|
148
187
|
|
|
149
188
|
### What the plugin does instead
|
|
150
189
|
|
|
151
|
-
The plugin builds a `bubblewrap` profile on the Windows side and
|
|
152
|
-
|
|
190
|
+
The plugin builds a `bubblewrap` profile on the Windows side and hands the
|
|
191
|
+
resulting argv to the distro — over the resident agent by default, or to
|
|
192
|
+
`wsl.exe --exec` on the one-shot fallback. The confinement is created inside the
|
|
193
|
+
distro, and the argv is the same either way:
|
|
153
194
|
|
|
154
195
|
```text
|
|
155
196
|
wsl.exe -d <distro> --cd <linux dir> --exec bwrap \
|
|
156
197
|
--ro-bind / / --dev /dev --unshare-pid --proc /proc --die-with-parent \
|
|
157
|
-
[--tmpfs /tmp --bind <workspace> <workspace>] -- <shell> -lc <cmd>
|
|
198
|
+
[--tmpfs /tmp --bind <workspace> <workspace>] [--tmpfs /mnt] -- <shell> -lc <cmd>
|
|
158
199
|
```
|
|
159
200
|
|
|
160
201
|
These are the same arguments DSH's Linux runner uses (`dsh-sandbox-local`), so the
|
|
161
202
|
behaviour and the error messages match a Linux host. The function
|
|
162
203
|
`bwrapProfileArgs` in [`lib/bwrap.js`](../lib/bwrap.js) builds them — the
|
|
163
|
-
peer-free builder every confinement site composes from.
|
|
204
|
+
peer-free builder every confinement site composes from. The trailing
|
|
205
|
+
`[--tmpfs /mnt]` is the `maskWindowsDrive` option: when it is on, the Windows
|
|
206
|
+
drive is shadowed by an empty tmpfs in every confined profile, so its files
|
|
207
|
+
cannot be read or exfiltrated and its executables cannot be launched through
|
|
208
|
+
interop. The option is off by default; [CONFIGURATION.md](CONFIGURATION.md) lists
|
|
209
|
+
the row keys, and `lib/sandbox-core.js` states its honest ceiling.
|
|
164
210
|
|
|
165
211
|
| Mode | What a command inside the distro can do |
|
|
166
212
|
|---|---|
|
|
@@ -185,7 +231,19 @@ selector read that value.
|
|
|
185
231
|
distro can still run a Windows program through WSL interop, for example
|
|
186
232
|
`/mnt/c/.../*.exe`. Bubblewrap does not govern that process.
|
|
187
233
|
|
|
188
|
-
`
|
|
234
|
+
`maskWindowsDrive` is the one mitigation the profile can offer, and it is a
|
|
235
|
+
narrowing rather than a closure: it takes the drive's data and its executables
|
|
236
|
+
off the table, but `binfmt` interop dispatches on file *content*, so a command
|
|
237
|
+
that can write the workspace can still write a PE there and have it launched as
|
|
238
|
+
a Windows process. Either way the provider reports `partial` — the complete
|
|
239
|
+
closure is distro-level (`[interop] enabled=false` in `wsl.conf`). The mask is
|
|
240
|
+
reported on the result's sandbox fact as `windowsDrive: "masked"` (or
|
|
241
|
+
`"visible"`), but that is this plugin's own extra field: the pinned harness's
|
|
242
|
+
sandbox type and its model-facing notes carry `mode`, `denied`, `enforcement` and
|
|
243
|
+
`runnerFailed` only, so the field reaches the result and the tests, not the
|
|
244
|
+
model.
|
|
245
|
+
|
|
246
|
+
`pnpm run probe:sandbox` demonstrates the boundary on your machine and records it as
|
|
189
247
|
`INFO`.
|
|
190
248
|
|
|
191
249
|
### Failure mode
|
|
@@ -212,18 +270,37 @@ and `sandboxMode` returns `undefined`.
|
|
|
212
270
|
## Error codes
|
|
213
271
|
|
|
214
272
|
The plugin raises `FsError`s with a code the tool layer maps, and `wsl.exe`'s own
|
|
215
|
-
failures are named as well.
|
|
216
|
-
finds it here.
|
|
273
|
+
failures are named as well. Every `FsError` code the plugin itself raises appears
|
|
274
|
+
below — a model sees the code, and a reader asking what refused them finds it here.
|
|
275
|
+
Codes the shipped host stack raises behind a drive path pass through
|
|
276
|
+
`lib/fs-routing.js` unchanged and are the peer's, not this table's; the GUI file
|
|
277
|
+
tree's own wire refusals are a separate dialect, listed after the table.
|
|
217
278
|
|
|
218
279
|
| Code | Raised by | Meaning | What clears it |
|
|
219
280
|
|---|---|---|---|
|
|
220
281
|
| `SANDBOX_UNAVAILABLE` | `WslSandbox.confine` | the requested mode cannot be enforced: no usable `bwrap` in the distro | install `bubblewrap` there (a failed probe is re-run, so no restart is needed), or set `sandbox: false` |
|
|
221
|
-
| `FS_SANDBOX_DENIED` | `WslFileSystem.checkedTarget` | the file-effect policy refused the write: `read-only`, or a target outside the writable roots | a wider permission for that one call, or a session workspace that contains the target |
|
|
282
|
+
| `FS_SANDBOX_DENIED` | `WslFileSystem.checkedTarget`, and `lib/fsio-agent.js` (a write the confined resident's mount table refused, classified from the kernel's `read-only file system`) | the file-effect policy refused the write: `read-only`, or a target outside the writable roots — or the kernel refused it at the resident's profile | a wider permission for that one call, or a session workspace that contains the target |
|
|
222
283
|
| `FS_OUTSIDE_DISTRO` | `WslFileSystem.worldPath` | `restrictToDistro` is on and the path names **another distro**'s share — a configuration fence, not a sandbox decision | open a session in that distro, or set `restrictToDistro: false`. A wider permission does **not** lift it |
|
|
223
|
-
| `FS_NOT_OBSERVED` | `
|
|
224
|
-
| `FS_STALE_VERSION` | `
|
|
225
|
-
| `FS_IO_ERROR` | `WslFileSystem.watch` | the
|
|
226
|
-
| `FS_NOT_FOUND` | the host filesystem stack | the path does not exist — the ordinary answer
|
|
284
|
+
| `FS_NOT_OBSERVED` | `lib/fs-substrate.js` (`writeText`; the agent's `exists` conclusion, mapped in `lib/fsio-agent.js`) | a guarded create (`createIfAbsent`) targeted a file that exists and had not been read first | read the file, then overwrite with the version guard or edit it |
|
|
285
|
+
| `FS_STALE_VERSION` | `lib/fs-substrate.js` (`writeText`, `editText`; the agent's `stale` conclusion, mapped in `lib/fsio-agent.js`) | the version the caller holds no longer matches: the file changed, or is gone | read it again and retry with the fresh version |
|
|
286
|
+
| `FS_IO_ERROR` | the filesystem stack: `lib/fs-decisions.js` (the substrate is unavailable), `lib/fsio-agent.js` (a `perm`/`loop`/`io` reason or a malformed record), `lib/index.js` (a path that cannot be mapped, or a `SandboxUnavailableError`), and `WslFileSystem.watch` | the catch-all I/O failure — most often the resident agent is out or the distro returned an I/O error, the "agent outage fails closed" case above. A watch target outside the pinned distro is the narrow one | get the resident back (check `wsl.exe -l -v`, then restart the session); for a watch, aim it at a distro path — the watch itself runs where the files live (`lib/watcher.js`) |
|
|
287
|
+
| `FS_NOT_FOUND` | `lib/fsio-agent.js` and `lib/fs-substrate.js` (the agent's `notfound`/`notdir` conclusion, an empty path, or a missing edit target), and the host filesystem stack behind a drive path | the path does not exist — the ordinary answer | — |
|
|
288
|
+
| `FS_NOT_REGULAR_FILE` | `lib/fsio-agent.js`, `lib/fs-substrate.js` | the target is not a regular file where one is required: a directory, symlink or device on a read, or a non-file target on a write | point the call at a regular file |
|
|
289
|
+
| `FS_NOT_DIRECTORY` | `lib/fsio-agent.js` (`listChildren`) | the target is not a directory, so there are no children to list | list a directory |
|
|
290
|
+
| `FS_NOT_TEXT` | `lib/fsio-agent.js`, `lib/fsio-text.js` | the content is not UTF-8 text: a NUL sample (binary) or a decode failure | not a policy refusal — read text, or use a command for binary content |
|
|
291
|
+
| `FS_TOO_LARGE` | `lib/fsio-agent.js` | the content exceeds the byte cap the caller's operation carries | read a byte range (`WslFileSystem.readByteRange`) instead of the whole file, or raise the cap |
|
|
292
|
+
| `FS_ABORTED` | `lib/fsio-agent.js`, `lib/fsio-text.js` | the caller's signal was aborted before or during the operation | not a refusal — the caller cancelled; a fresh call clears it |
|
|
293
|
+
| `FS_EDIT_NOT_FOUND` | `lib/fsio-text.js` (`applyLiteralEdit`) | `old_string` was empty, or matched nothing in the file | give an `old_string` the file actually contains |
|
|
294
|
+
| `FS_AMBIGUOUS_EDIT` | `lib/fsio-text.js` (`applyLiteralEdit`) | `old_string` matched more than once and `replace_all` was not set | make `old_string` unique, or set `replace_all` |
|
|
295
|
+
|
|
296
|
+
The GUI file tree speaks a second dialect. `WorkspaceFilesWsl` refuses through the
|
|
297
|
+
typert wire's `RemoteError`s, not `FsError`s, so its codes are not in the table
|
|
298
|
+
above: `workspace-file/not-found`, `workspace-file/outside-workspace`,
|
|
299
|
+
`workspace-file/not-directory`, `workspace-file/not-regular-file`,
|
|
300
|
+
`workspace-file/not-text`, and `workspace-file/too-large` (one code for both the
|
|
301
|
+
full-file cap and the byte-window cap), plus `gateway/bad-request` for a malformed
|
|
302
|
+
page request (a non-integer offset, or a limit above `maxLines`). A drive-path
|
|
303
|
+
workspace calls the shipped service and gets the peer's own answers instead.
|
|
227
304
|
|
|
228
305
|
Two failures come from `wsl.exe` itself rather than from a code of ours, and both used
|
|
229
306
|
to be invisible:
|
|
@@ -250,7 +327,7 @@ distro checkout.
|
|
|
250
327
|
|
|
251
328
|
The Windows copy at `default-workspace/dsh-plugin-wsl` is a runtime mirror.
|
|
252
329
|
[`test/probe/sync-to-windows.sh`](../test/probe/sync-to-windows.sh) keeps it in
|
|
253
|
-
step with the checkout, and `
|
|
330
|
+
step with the checkout, and `pnpm run sync:windows` runs that script.
|
|
254
331
|
|
|
255
332
|
## Test layers
|
|
256
333
|
|
|
@@ -258,15 +335,35 @@ step with the checkout, and `npm run sync:windows` runs that script.
|
|
|
258
335
|
|---|---|---|
|
|
259
336
|
| Style and packaging checks | [`test/style.mjs`](../test/style.mjs) | no |
|
|
260
337
|
| Syntax pass | [`test/syntax.mjs`](../test/syntax.mjs) | no |
|
|
338
|
+
| Build consistency | [`test/build-freshness.mjs`](../test/build-freshness.mjs) | no; it re-runs the real build and byte-compares the committed `lib/`, so it also catches an orphan there |
|
|
339
|
+
| Type check | `tsc --noEmit` over `test/**` and `types/*.d.ts` (`src/` is checked by the build itself) | no |
|
|
261
340
|
| Unit tests | `test/*.test.mjs` | no |
|
|
262
341
|
| Sandbox probe | [`test/probe/sandbox.sh`](../test/probe/sandbox.sh) | no; it applies the profile arguments directly |
|
|
263
342
|
| Harness probes | [`test/probe/run.sh`](../test/probe/run.sh), `terminal.sh`, `sandbox-shell.sh`, `picker.sh`, `missing-wsl.sh`, `sandbox-off.sh` | yes: Windows, WSL2, a mounted profile, and a linked checkout |
|
|
264
|
-
|
|
|
343
|
+
| Distro probes | [`test/probe/substrate.sh`](../test/probe/substrate.sh), `watch.sh`, `agent.sh`, `exec.sh` | Windows and WSL2, but no harness boot and no profile: they run from a distro terminal with Node available, reaching `wsl.exe` through interop |
|
|
344
|
+
| Host probe | [`test/probe/mode.sh`](../test/probe/mode.sh) | Windows Node, no harness |
|
|
265
345
|
| Probe plugins | the `*-probe.mjs` and `*-probe.yml` files in `test/probe/` | loaded by the scripts above |
|
|
266
346
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
347
|
+
`pnpm test` runs the first five rows in order: `lint:style`, `test:syntax`,
|
|
348
|
+
`lint:build`, `lint:types`, `test:unit`.
|
|
349
|
+
[`test/probe/run-all-when-closed.sh`](../test/probe/run-all-when-closed.sh)
|
|
350
|
+
waits for the app to exit and then runs the whole real-machine suite once —
|
|
351
|
+
`--include-fs` prepends `run.sh`.
|
|
352
|
+
|
|
353
|
+
The unit tests come in two kinds: most import only Node builtins, while
|
|
354
|
+
[`test/composition.test.mjs`](../test/composition.test.mjs),
|
|
355
|
+
[`test/fs.boot.test.mjs`](../test/fs.boot.test.mjs),
|
|
356
|
+
[`test/fsio-agent.test.mjs`](../test/fsio-agent.test.mjs),
|
|
357
|
+
[`test/provider.test.mjs`](../test/provider.test.mjs) and
|
|
358
|
+
[`test/workspace-files-wsl.test.mjs`](../test/workspace-files-wsl.test.mjs) import
|
|
359
|
+
pinned `@deepseek-ai/*` development dependencies (the composition and boot tests run
|
|
360
|
+
the loader's real patch algorithm). One `pnpm install` reproduces that tree before
|
|
361
|
+
the first run; the runtime package itself still ships zero dependencies. CI runs
|
|
362
|
+
`pnpm test` on Linux and Windows, on Node 22 and 24, and `pnpm run test:coverage` on
|
|
363
|
+
Node 24 only — the threshold flags need Node 22.8. Node 20 is absent on purpose:
|
|
364
|
+
`engines` requires `^22.19.0 || >=24.0.0`, the harness host's own floor. The harness
|
|
365
|
+
probes are a manual step, because a hosted runner has no WSL distro and no profile to
|
|
366
|
+
mount.
|
|
270
367
|
|
|
271
368
|
The probe scripts share [`test/probe/env.sh`](../test/probe/env.sh), which derives
|
|
272
369
|
the Windows user, the distro name and the UNC spelling of the checkout from the
|
|
@@ -276,45 +373,91 @@ placeholders that the scripts substitute into generated copies.
|
|
|
276
373
|
|
|
277
374
|
## File layout
|
|
278
375
|
|
|
376
|
+
`lib/` is generated output: every module's source is `src/<name>.ts`, and
|
|
377
|
+
`pnpm run build` emits `lib/<name>.js` plus its `lib/<name>.d.ts`. Never edit
|
|
378
|
+
`lib/` by hand — `lint:build` fails when the committed bytes drift from a real
|
|
379
|
+
build, and it also fails on an orphan in either direction.
|
|
380
|
+
|
|
381
|
+
```text
|
|
382
|
+
src/<name>.ts the sources — edit THESE; each builds to lib/<name>.{js,d.ts}
|
|
383
|
+
agent/wsl-agent.sh the resident in-distro agent (POSIX sh, coreutils only)
|
|
384
|
+
cordis.patch.yml the bundle configuration layer (dsh.bundle), commented
|
|
385
|
+
examples/ one machine-local profile layer, for comparison
|
|
386
|
+
locale/ the Plugins-page display metadata (title/description)
|
|
387
|
+
scripts/ the build (build.mjs), the release helpers
|
|
388
|
+
(check-release-tag.mjs, changelog-section.mjs), and the
|
|
389
|
+
distro-side bootstrap installer (bootstrap.sh)
|
|
390
|
+
test/ unit tests, the style/build/type gates, and the probes
|
|
391
|
+
docs/ this file, the configuration and limitation references,
|
|
392
|
+
the release checklist, and the archived designs
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
The modules, in alphabetical order:
|
|
396
|
+
|
|
279
397
|
```text
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
398
|
+
src/agent-confined.ts the confined residents' factory (one per policy)
|
|
399
|
+
src/agent-errors.ts the error a resident outage rejects with
|
|
400
|
+
src/agent-exec.ts the agent-backed shell execution handle
|
|
401
|
+
src/agent-protocol.ts the wire protocol codecs (pure)
|
|
402
|
+
src/agent-shared.ts the per-distro resident shared by every provider
|
|
403
|
+
src/agent.ts the resident in-distro agent's host side
|
|
404
|
+
src/auto-preset.ts per-session environment selection
|
|
405
|
+
src/bwrap.ts the bwrap command line every confinement site
|
|
406
|
+
assembles (no imports, so a bare checkout can
|
|
407
|
+
assert it)
|
|
408
|
+
src/file-reference-wsl.ts the distro-side `@`-completion traversal — built,
|
|
409
|
+
tested, not yet wired (see above)
|
|
410
|
+
src/fs.ts the ctx.fs subpath entry point
|
|
411
|
+
src/fs-decisions.ts the filesystem provider's policy decisions (pure,
|
|
412
|
+
unit-testable in a bare checkout)
|
|
413
|
+
src/fs-routing.ts WslRoutingFileSystem, the root-plane ctx.fs
|
|
414
|
+
src/fs-substrate.ts the agent substrate's provider-shaped operations
|
|
415
|
+
src/fsio-agent.ts fsio's mechanics over the agent's FS frames
|
|
416
|
+
src/fsio-text.ts the peer's pure text mechanics, replicated pending
|
|
417
|
+
the upstream fsio export (UPSTREAM-FSIO-EXPORT.md)
|
|
418
|
+
src/git-route.ts which git spawns belong inside the distro (pure)
|
|
419
|
+
src/index.ts WslShellExecutor (ctx.shell) and WslFileSystem (ctx.fs)
|
|
420
|
+
src/listing.ts pure directory listing and breadcrumb helpers
|
|
421
|
+
src/paths.ts pure conversion between the three path formats
|
|
422
|
+
src/picker.ts WslDirectoryPicker (ctx.directoryPicker)
|
|
423
|
+
src/ports.ts the listening-port snapshot behind DSH_WSL_PORTS
|
|
424
|
+
src/preset-choice.ts whether a new session adopts the WSL preset (pure)
|
|
425
|
+
src/probe-cache.ts which probe verdicts may be remembered (pure)
|
|
426
|
+
src/sandbox.ts the distro-side bwrap confinement shared by both
|
|
427
|
+
providers, bound to the peer's failure type
|
|
428
|
+
src/sandbox-core.ts the probe-and-confinement core, free of DSH peers
|
|
429
|
+
src/search-exec.ts the agent-backed execution handle for a search
|
|
430
|
+
src/search-route.ts which execution world a search spawn belongs to (pure)
|
|
431
|
+
src/shell.ts the ctx.shell subpath entry point
|
|
432
|
+
src/shell-env.ts registers the DSH_WSL_* environment variables
|
|
433
|
+
src/subprocess.ts WslSubprocessRuntime (ctx.subprocess), the GUI
|
|
434
|
+
terminal window
|
|
435
|
+
src/terminal-activity.ts the distro-side activity probe for idle terminal
|
|
436
|
+
reclamation
|
|
437
|
+
src/terminal-route.ts which execution world a terminal launch belongs to (pure)
|
|
438
|
+
src/watcher.ts in-distro file watching for WslFileSystem.watch
|
|
439
|
+
src/workspace-files-route.ts pure builders and parsers for the routed
|
|
440
|
+
workspace-files service
|
|
441
|
+
src/workspace-files-wsl.ts WorkspaceFilesWsl (ctx.workspaceFiles), the GUI
|
|
442
|
+
file tree and previews
|
|
443
|
+
src/wsl.ts wsl.exe interop primitives (no DSH imports)
|
|
306
444
|
```
|
|
307
445
|
|
|
308
446
|
`package.json` publishes a subset of this tree: the `files` list is the contract, and
|
|
309
|
-
`
|
|
447
|
+
`pnpm run lint:style` fails if an entry stops matching tracked content.
|
|
310
448
|
|
|
311
449
|
## Where the reasoning lives
|
|
312
450
|
|
|
313
451
|
- [`CHANGELOG.md`](../CHANGELOG.md) records what changed in each release. It also
|
|
314
452
|
names the documentation a reader would have acted on that is no longer true.
|
|
453
|
+
- [`REQUIREMENTS.zh.md`](REQUIREMENTS.zh.md) is the requirements register: every
|
|
454
|
+
documented behaviour commitment numbered, with where it is specified and what
|
|
455
|
+
verifies it, plus the open items (Chinese, maintainer-facing, not packaged).
|
|
315
456
|
- [`archive/engineering-record.zh.md`](archive/engineering-record.zh.md) is the
|
|
316
457
|
full engineering record, in Chinese: measured contracts, debugging rounds, and
|
|
317
458
|
the conclusions as they stood then.
|
|
318
|
-
- [`archive/README.md`](archive/README.md)
|
|
319
|
-
|
|
459
|
+
- [`archive/README.md`](archive/README.md) and
|
|
460
|
+
[`archive/README.en.md`](archive/README.en.md) are two English indexes of the
|
|
461
|
+
same three archived files, the second so a reader who does not read Chinese can
|
|
462
|
+
still tell what is there; the records themselves are Chinese.
|
|
320
463
|
- [`README.md`](../README.md) is the only statement of current state.
|