dsh-plugin-wsl-env 0.1.10 → 0.3.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 +268 -0
- package/README.md +21 -2
- package/agent/wsl-agent.sh +498 -0
- package/cordis.patch.yml +7 -0
- package/docs/ARCHITECTURE.md +37 -1
- package/docs/CONFIGURATION.md +12 -1
- package/docs/LIMITATIONS.md +70 -5
- package/docs/PARITY.md +70 -0
- package/docs/PEER-PARITY.md +60 -0
- package/docs/README.zh.md +31 -20
- package/docs/UPSTREAM-FSIO-EXPORT.md +87 -0
- package/lib/agent-confined.js +81 -0
- package/lib/agent-errors.js +33 -0
- package/lib/agent-exec.js +204 -0
- package/lib/agent-protocol.js +174 -0
- package/lib/agent-shared.js +32 -0
- package/lib/agent.js +564 -0
- package/lib/bwrap.js +74 -0
- package/lib/fs-publish.js +42 -0
- package/lib/fs-substrate.js +285 -0
- package/lib/fsio-agent.js +481 -0
- package/lib/fsio-text.js +153 -0
- package/lib/index.js +430 -12
- package/lib/ports.js +61 -0
- package/lib/sandbox.js +35 -28
- package/lib/shell-env.js +28 -2
- package/lib/subprocess.js +15 -0
- package/lib/terminal-route.js +31 -0
- package/lib/watcher.js +127 -0
- package/package.json +13 -4
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,274 @@ reference below points at that record's numbering.
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`maskWindowsDrive` on `wsl-shell` and `wsl-fs`** — the mitigation knob for
|
|
18
|
+
the interop hole the sandbox has always documented. The confined profile
|
|
19
|
+
mounts an empty tmpfs over `/mnt`, so the Windows drive's files cannot be
|
|
20
|
+
read or exfiltrated by a "sandboxed" command and its executables cannot be
|
|
21
|
+
launched through interop (the path by which such a command reaches your full
|
|
22
|
+
Windows token). Honest limits, stated wherever the key is: binfmt interop
|
|
23
|
+
dispatches on file content, so a command that can write the workspace can
|
|
24
|
+
still write an executable there and run it — enforcement stays `partial`,
|
|
25
|
+
the result's sandbox fact now carries `windowsDrive: "masked" | "visible"`
|
|
26
|
+
so the model can see which posture it runs under, and the only complete
|
|
27
|
+
closure is distro-level (`[interop] enabled=false` in `wsl.conf`). Default
|
|
28
|
+
`false`: the drive stays visible unless you ask for the mask.
|
|
29
|
+
|
|
30
|
+
## [0.3.0] - 2026-10-03
|
|
31
|
+
|
|
32
|
+
One theme: the file tools move into the distro. The `substrate: "agent"` I/O
|
|
33
|
+
substrate and its kernel-enforced confined writes land together with the
|
|
34
|
+
enterprise review that hardened them — the watchdog that killed the shared
|
|
35
|
+
agent for a queue it never chose to join, the output captures nothing bounded,
|
|
36
|
+
a root listing that sheared the first character off every child's name, and an
|
|
37
|
+
overwrite guard that stopped at the host.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- **One file carries one version string, whichever op answered.** `stat` and
|
|
42
|
+
`list` built their versions from different producers: the stat op used the
|
|
43
|
+
distro's `stat` directives (uutils `stat` rounds timestamps to microseconds)
|
|
44
|
+
while the list walk used `find -printf` (nanosecond-exact), so the same file
|
|
45
|
+
yielded two different version strings and a list-derived guard failed
|
|
46
|
+
against its own stat. `fs_stat` now reads through the same `find -printf`
|
|
47
|
+
the list walk uses — one producer, one string — and `find -L`'s habit of
|
|
48
|
+
reporting a dangling link as the link itself is refused the way the `stat`
|
|
49
|
+
it replaced was, so a dangling link still stats to null like the peer's.
|
|
50
|
+
|
|
51
|
+
- **An overwrite or edit now re-verifies its version distro-side, one syscall
|
|
52
|
+
before the rename.** The module header claimed the mutation guard "survives
|
|
53
|
+
to the write", but the no-replace link guarded only `createIfAbsent`: an
|
|
54
|
+
overwrite or edit checked the version host-side and then published with an
|
|
55
|
+
unconditional `mv -f`, so a concurrent writer (an editor save, `git stash`,
|
|
56
|
+
another session) that landed between the model's stat and the publish was
|
|
57
|
+
silently clobbered with no `FS_STALE_VERSION`. The `write` op now carries
|
|
58
|
+
the version the write was based on (a `replaceIfVersion` guard's version,
|
|
59
|
+
or the stat an edit was read against), and the agent re-verifies it with a
|
|
60
|
+
fresh stat from the SAME producer `fs_stat` uses — refusing with
|
|
61
|
+
`FS_STALE_VERSION` ("file changed since it was read", or "file no longer
|
|
62
|
+
exists") while the concurrent writer's content stays untouched and no
|
|
63
|
+
staging debris is left behind. The window narrows from a host round trip to
|
|
64
|
+
kernel-adjacent — the honest wording, here and in the module headers,
|
|
65
|
+
LIMITATIONS.md and ARCHITECTURE.md, is that only the create case is closed
|
|
66
|
+
outright.
|
|
67
|
+
|
|
68
|
+
- **The agent command path now enforces the output budget it always
|
|
69
|
+
advertised.** `maxOutputBytes` rode the config schema but nothing else: the
|
|
70
|
+
agent captured a command's whole stdout and stderr and base64ed both into
|
|
71
|
+
one RES line — multi-gigabyte output meant multi-gigabyte dash command
|
|
72
|
+
substitutions in the distro and a multi-gigabyte host line, with the result
|
|
73
|
+
claiming `truncated: false` all the way. The EXEC frame now carries the
|
|
74
|
+
caller's per-stream budget (`wsl-shell`'s `maxOutputBytes`, wired from the
|
|
75
|
+
agent execution path); the agent cuts each stream at it — only the first
|
|
76
|
+
bytes are encoded, so distro AND host memory are bounded — and the RES line
|
|
77
|
+
reports which streams were cut, which the handle surfaces as honest
|
|
78
|
+
per-stream `truncated` flags (a guarded-create synthesis and frames without
|
|
79
|
+
a budget still read `false`). The host also defends itself against a broken
|
|
80
|
+
or hostile agent: the unbounded readline accumulator is replaced with a
|
|
81
|
+
bounded line splitter that kills the transport when one stdout line exceeds
|
|
82
|
+
the protocol cap instead of buffering it whole.
|
|
83
|
+
|
|
84
|
+
- **A short-budget request queued behind a long one no longer kills the shared
|
|
85
|
+
agent — and an in-flight command is never silently re-run.** Two defects
|
|
86
|
+
shared one root: the host armed each request's watchdog at SUBMISSION and
|
|
87
|
+
let it kill the one `wsl.exe` carrying every pending request, and the
|
|
88
|
+
rebuild relay then replayed all carried frames verbatim. In the default
|
|
89
|
+
configuration the 10 s `DSH_WSL_PORTS` poller (8 s budget, shared agent) did
|
|
90
|
+
exactly this against any command longer than ~21 s: the poll's watchdog
|
|
91
|
+
killed the transport, the command died mid-flight, and the rebuild
|
|
92
|
+
**re-executed it from scratch** — both callers seeing success (measured: a
|
|
93
|
+
killed `sleep 40` ran twice). Three changes close it: the agent answers
|
|
94
|
+
`ACK|<id>` when it dequeues a request and the host arms the watchdog there,
|
|
95
|
+
so queue time never spends a budget (wire protocol v3 — sync the runtime
|
|
96
|
+
copy and restart the app, an old agent fails the handshake loudly instead of
|
|
97
|
+
misbehaving quietly); the rebuild relays only provably read-only FS frames
|
|
98
|
+
(`stat`/`lstat`/`list`/`realpath`/`read`), failing an in-flight EXEC or
|
|
99
|
+
`write` with "its state is unknown — inspect the distro before retrying"
|
|
100
|
+
instead of re-running it; and an abort now addresses the request's CURRENT
|
|
101
|
+
id, so a relayed request's KILL (and map cleanup) can no longer miss. The
|
|
102
|
+
watchdog-vs-queue experiment now ends with the long command executed exactly
|
|
103
|
+
once and the transport alive.
|
|
104
|
+
- **A root listing kept every child's name whole.** `listChildren` sliced each
|
|
105
|
+
`find` record at `parent.length + 1`, which against the bare `/` parent
|
|
106
|
+
sheared the first character off every name (`/etc` → `tc`), and a write to a
|
|
107
|
+
listed child then created `/tc`. The exact prefix is stripped instead, and
|
|
108
|
+
the child join owes its own slash, so `listDir` on
|
|
109
|
+
`\\wsl.localhost\<distro>\` names `/etc`, `/home`, `/usr` correctly.
|
|
110
|
+
|
|
111
|
+
### Added
|
|
112
|
+
|
|
113
|
+
- **`substrate: "agent"` — the filesystem's in-distro I/O substrate.** `wsl-fs`
|
|
114
|
+
gains a `substrate` key, default `agent`: the file tools are served through
|
|
115
|
+
the resident in-distro agent instead of the 9p share — identities are
|
|
116
|
+
distro-side `realpath`s, symlinks resolve where they live (no retry
|
|
117
|
+
machinery, `/etc/os-release` just works), mode bits are native, and a
|
|
118
|
+
mutation guard survives to the write's no-replace publication — the
|
|
119
|
+
check-then-write window the share documents closes, exactly as on a Linux
|
|
120
|
+
host. `substrate: "share"` is the opt-out that keeps the previous behaviour
|
|
121
|
+
byte for byte. Both substrates fence writes with the same host-side policy
|
|
122
|
+
check, so this changes fidelity and posture, not enforcement. Known
|
|
123
|
+
differences are in LIMITATIONS.md (notably: new files publish at 0600, the
|
|
124
|
+
peer's own POSIX semantics, where the share took the umask 0644), and an
|
|
125
|
+
agent outage fails with `FS_IO_ERROR` naming the substrate rather than
|
|
126
|
+
degrading to share semantics. Layers: FS operation frames on the agent wire
|
|
127
|
+
protocol (v3, `agent/wsl-agent.sh`), fsio-over-agent mechanics
|
|
128
|
+
(`lib/fsio-agent.js`), provider-shaped orchestration (`lib/fs-substrate.js`),
|
|
129
|
+
and the provider branch (`lib/index.js`). The pure text mechanics are
|
|
130
|
+
replicated from upstream pending the fsio export proposed in
|
|
131
|
+
docs/UPSTREAM-FSIO-EXPORT.md. Probed live: `npm run probe:substrate`.
|
|
132
|
+
- **Stage two: kernel-enforced confined writes.** On the agent substrate, a
|
|
133
|
+
mutation under a confined policy rides its own long-lived resident — one
|
|
134
|
+
agent per (distro, mode, workspace), spawned inside the bwrap profile that
|
|
135
|
+
binds exactly what the mode grants (`lib/agent-confined.js`, `argvPrefix` on
|
|
136
|
+
the agent wire). The kernel now refuses what the host-side check would have,
|
|
137
|
+
so a check bug cannot become a write outside the workspace; a mount-table
|
|
138
|
+
refusal surfaces as `FS_SANDBOX_DENIED`, the command path's dialect, and a
|
|
139
|
+
distro without usable bwrap refuses confined writes with the bootstrap
|
|
140
|
+
command instead of downgrading. Escalated (`danger-full-access`) writes ride
|
|
141
|
+
the plain resident, and a policy change simply addresses a different key —
|
|
142
|
+
the previous resident retires through its idle timer. Probed live: the
|
|
143
|
+
confined write inside the workspace lands, and the one outside it is refused
|
|
144
|
+
by the kernel (`probe:substrate`).
|
|
145
|
+
|
|
146
|
+
### Fixed
|
|
147
|
+
|
|
148
|
+
- **Every mutation on the agent substrate failed: the confined resident's command
|
|
149
|
+
had no program** — `confinedAgent()` built the resident's `argvPrefix` from the
|
|
150
|
+
raw profile arguments, so the transport handed `wsl.exe` a command beginning at
|
|
151
|
+
`--ro-bind` instead of `bwrap --ro-bind`. The resident exited 1 during its
|
|
152
|
+
handshake, and every write, edit and guarded create on `substrate: "agent"`
|
|
153
|
+
failed with `FS_IO_ERROR: the distro file substrate is unavailable: agent exited
|
|
154
|
+
during handshake (code=1 signal=none)` while reads kept working. Both confinement
|
|
155
|
+
sites now assemble the command through the import-free `lib/bwrap.js`, which is
|
|
156
|
+
what `test/agent.test.mjs` asserts; the same bug stayed invisible because
|
|
157
|
+
`test/probe/substrate.sh` inlined a *third*, correct copy of those arguments —
|
|
158
|
+
it now imports the production builder, so the probe fails with the provider.
|
|
159
|
+
- **`WslFileSystem` could not build its agent substrate at all: a constructor
|
|
160
|
+
field shadowed the method of the same name** — the constructor's
|
|
161
|
+
`this.agentSubstrate = undefined` (the cache) overwrote the prototype's
|
|
162
|
+
`async agentSubstrate()` (the accessor), so the first file operation that needed
|
|
163
|
+
the agent threw `this.agentSubstrate is not a function`, which is how a GUI
|
|
164
|
+
session failed its turn in the Desktop app. The cache is now
|
|
165
|
+
`this.agentSubstrateInstance`, named after the sibling `this.agentInstance`, and
|
|
166
|
+
`lib/index.js`'s instance fields no longer collide with any class member.
|
|
167
|
+
- **A schemastery member the pinned peer does not publish took the `wsl` preset
|
|
168
|
+
down in the Desktop app** — `WslFileSystem.Config` declared
|
|
169
|
+
`substrate: z.enum(["share", "agent"])`, and `@deepseek-ai/schemastery` 3.18.4
|
|
170
|
+
(what every `@deepseek-ai/dsh-*` peer pins as `~3.18.4`) publishes no `enum`. A
|
|
171
|
+
Config schema is a class field, so the call ran while `lib/index.js` was still
|
|
172
|
+
being imported: the loader logs the `TypeError` and leaves that row with no
|
|
173
|
+
fiber, which the preset audit renders as `never started` — naming neither the
|
|
174
|
+
member nor the file, and only for `wsl-shell` and `wsl-fs`, the two rows that
|
|
175
|
+
load `lib/index.js`. The preset therefore failed to activate, so the app
|
|
176
|
+
reported `2 row(s) did not activate` for those two ids. Now
|
|
177
|
+
`z.union(["share", "agent"])`: same `agent` default, and a bad value still
|
|
178
|
+
fails with `expected "share" | "agent" but got "bogus"`. Measured against the
|
|
179
|
+
app's own runtime, both entries import; `test/syntax.mjs` now checks every
|
|
180
|
+
`z.<member>` in `lib/` against the pinned peer's surface, which fails on
|
|
181
|
+
`z.enum` and passes on `z.union`.
|
|
182
|
+
- **`truncated` missing from the agent path's output streams** — the bash tool's
|
|
183
|
+
canonical result copies the field unconditionally (`canonicalBashResult`), so an
|
|
184
|
+
`undefined` failed the wire's lossless-JSON snapshot and EVERY shell tool call
|
|
185
|
+
returned "tool bash returned invalid output: value is not lossless JSON". The
|
|
186
|
+
agent path now always reports `truncated: false` (it never truncates; there are
|
|
187
|
+
no spill files). Regression assertion in `test/agent-exec.test.mjs`.
|
|
188
|
+
- **The agent execution path failed every shell call whose request carried an
|
|
189
|
+
absolute UNC workdir** — which is every call a GUI session makes, so the
|
|
190
|
+
harness's own agent session lost its bash tool until the fix. The one-shot
|
|
191
|
+
path converts the spec's workdir inside `argv()` (`toLinuxPath`); the agent
|
|
192
|
+
path carried the workdir as a raw frame field, so the raw UNC reached the
|
|
193
|
+
in-distro `chdir`, the synthesized relay text fired `throwIfWorkdirMissing`,
|
|
194
|
+
and the call died with "could not enter the working directory". The conversion
|
|
195
|
+
now happens at the call site (regression test in `test/shell.test.mjs`).
|
|
196
|
+
|
|
197
|
+
### Added
|
|
198
|
+
|
|
199
|
+
- **Phase 5: per-session terminal routing.** `spawnTerminal` now routes on the
|
|
200
|
+
request's directory coordinates (`lib/terminal-route.js`, `subprocess-wsl.hostSessions`,
|
|
201
|
+
default on): a WSL-folder session gets the distro shell, a Windows-folder session gets
|
|
202
|
+
a host `powershell.exe` in its own directory — no more distro terminal at
|
|
203
|
+
`/mnt/<drive>/…` for host sessions. The shell menu stays the single configured profile,
|
|
204
|
+
and every non-`wsl.exe` consumer is untouched. Still limited (LIMITATIONS.md): the
|
|
205
|
+
shell menu is one entry, and directory-less launches land in the distro.
|
|
206
|
+
- **Phase 7: developer-loop recipes.** Both READMEs gain a Recipes section: Git
|
|
207
|
+
credential sharing through the Windows Credential Manager, `/mnt/c` 9p
|
|
208
|
+
performance guidance, and the `WSLENV` passthrough policy (prefix-admitted
|
|
209
|
+
`DSH_*`, `/p` translation, `PATH` deliberately excluded).
|
|
210
|
+
- **Phase 6: listening-port visibility.** `DSH_WSL_PORTS` joins the managed
|
|
211
|
+
`DSH_*` facts: the ports with a listener inside the distro, comma-separated,
|
|
212
|
+
refreshed every `portsRefreshMs` (default 10s) through the resident agent by
|
|
213
|
+
reading `/proc/net/tcp{,6}` directly — no `ss`, nothing to install. The
|
|
214
|
+
registry's resolve is synchronous by contract, so the snapshot is served
|
|
215
|
+
stale-but-recent; a dev server started moments ago appears on the next
|
|
216
|
+
refresh. Scoped as before: only sessions whose workspace is inside a distro
|
|
217
|
+
see it. Probed live: a real HTTP server appears in the next snapshot.
|
|
218
|
+
- **Phase 4: bootstrap.** `npm run bootstrap -- [distro] [--install]` checks the
|
|
219
|
+
distro for bubblewrap (required) plus ripgrep and inotifywait (the optional
|
|
220
|
+
backends later phases prefer), prints the exact copy-paste install command for
|
|
221
|
+
the distro's package family (apt/dnf/pacman/zypper), and with `--install` runs
|
|
222
|
+
it as the distro's root through `wsl.exe -u root` — no sudo prompt inside the
|
|
223
|
+
distro, nothing touches the Windows side. The `SANDBOX_UNAVAILABLE` message now
|
|
224
|
+
names the same command instead of a bare `sudo apt install`. Probed live:
|
|
225
|
+
detect mode on a distro that already has bubblewrap.
|
|
226
|
+
- **Phase 3: shell execution through the agent.** `WslShellExecutor` now runs commands
|
|
227
|
+
over the resident agent when it is up (`wsl-shell.agent`, default on): one long-lived
|
|
228
|
+
in-distro process instead of a `wsl.exe` per command, with the timeout enforced
|
|
229
|
+
INSIDE the distro (TERM, then KILL after the grace) instead of against the Windows
|
|
230
|
+
process tree. The confinement argv, the execution-handle shape, the result
|
|
231
|
+
decoration (runner failures, denials, `enforcement: partial`) and the workdir
|
|
232
|
+
failure are all identical to the one-shot path — an out-of-service agent falls back
|
|
233
|
+
to it per call, permanently after the agent's rebuild budget. Probed end-to-end:
|
|
234
|
+
`npm run probe:exec` (confined completion, verbatim shell parsing, in-distro
|
|
235
|
+
timeout, relay-shaped cwd failure, kill). Documented deviation: the agent path has
|
|
236
|
+
no live output streaming — reads after settlement carry the whole stream, and no
|
|
237
|
+
spill files (the protocol line already holds it in memory).
|
|
238
|
+
- **Phase 2 (second half, first slice): in-distro publication through the agent.**
|
|
239
|
+
`WslFileSystem` now carries an optional resident agent (`wsl-fs.agent`, default on):
|
|
240
|
+
when available, publication — the replaced file's mode copied onto the staged temp,
|
|
241
|
+
then the atomic rename — is ONE `exec` inside the distro (`lib/fs-publish.js`)
|
|
242
|
+
instead of a `wsl.exe` chmod plus a 9p rename. Any agent failure falls back to the
|
|
243
|
+
legacy two-step path for that call; the flag changes round trips, never what is
|
|
244
|
+
published. Probed: the publication script's mode preservation and creation
|
|
245
|
+
behaviour run in `test/probe/agent.sh` (now 10 checks).
|
|
246
|
+
- **Phase 2 (first half): real `watch()`.** `WslFileSystem.watch` now observes from
|
|
247
|
+
inside the distro — a long-lived `wsl.exe` `find -newer` poll loop (`lib/watcher.js`)
|
|
248
|
+
firing the seam's coarse invalidation callback — instead of refusing with
|
|
249
|
+
`FS_IO_ERROR`. Activates on a READY barrier from the loop (a write racing the first
|
|
250
|
+
tick is never invisible), sees creations and deletions (both bump the parent's
|
|
251
|
+
mtime), and cleans up on close. Documented blind spot: files created with an
|
|
252
|
+
mtime older than the stamp (`cp -p`, `tar -x`). Probed end-to-end:
|
|
253
|
+
`npm run probe:watch`. The native-ext4 migration of the remaining fs operations
|
|
254
|
+
stays with Phase 3 of the plan.
|
|
255
|
+
- **Phase 1 of the Remote-WSL parity plan (`docs/PARITY.md`): the resident in-distro
|
|
256
|
+
agent.** `agent/wsl-agent.sh` is a dependency-free POSIX shell peer spawned once per
|
|
257
|
+
distro; `lib/agent.js` owns the lifecycle (handshake, lazy start, idle shutdown, one
|
|
258
|
+
rebuild, permanent fallback to the shipped one-shot `wsl.exe` path) and
|
|
259
|
+
`lib/agent-protocol.js` the line protocol (base64 payloads, NUL-safe argv). Not wired
|
|
260
|
+
into the seams yet — the executor and filesystem migrations are the next phases.
|
|
261
|
+
Probed end-to-end against a real distro: `test/probe/agent.sh` (8 checks, including
|
|
262
|
+
binary-safe payloads, an in-distro timeout kill, and a 2 MB single-line response).
|
|
263
|
+
Unit tests cover the codecs and the lifecycle state machine with a fake transport.
|
|
264
|
+
|
|
265
|
+
### Added
|
|
266
|
+
|
|
267
|
+
- `docs/PARITY.md`: the plan to reach VS Code Remote-WSL-grade experience — a resident
|
|
268
|
+
in-distro agent as the core, with the `fs` and `shell` seams migrated onto it and the
|
|
269
|
+
current 9p/one-shot paths kept as the documented fallback — plus the Phase-0 contract
|
|
270
|
+
audit that shaped it (watch is a coarse invalidation callback; search spawns the
|
|
271
|
+
packaged ripgrep through `ctx.subprocess`, so the interception point is the
|
|
272
|
+
`subprocess-wsl` subclass).
|
|
273
|
+
|
|
274
|
+
### Changed
|
|
275
|
+
|
|
276
|
+
- The package description is rewritten in Chinese to state what the plugin does first —
|
|
277
|
+
it makes a WSL distro the session's execution environment — then covers the execution,
|
|
278
|
+
UI, and sandbox aspects in one clause each.
|
|
279
|
+
- Chinese text now says “WSL 子系统” instead of “WSL 发行版”, the phrasing Chinese readers
|
|
280
|
+
more commonly use for a WSL distro (`docs/README.zh.md` and the two archived Chinese
|
|
281
|
+
documents; 99 occurrences).
|
|
282
|
+
|
|
15
283
|
## [0.1.10] - 2026-10-02
|
|
16
284
|
|
|
17
285
|
Metadata and one documentation addition. No behaviour change.
|
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ Step 3 should print a layer named `# == dsh-plugin-wsl-env`, and `- id: terminal
|
|
|
28
28
|
|
|
29
29
|
Then open a folder under `\\wsl.localhost\<distro>\...` in the GUI. The picker lists every installed distro at its root level, and **New terminal** opens a shell in the distro.
|
|
30
30
|
|
|
31
|
-
You also need **bubblewrap inside the distro**: `
|
|
31
|
+
You also need **bubblewrap inside the distro**: run `npm run bootstrap -- <distro> --install` (drop `--install` for a read-only check), or paste `wsl.exe -d <distro> -u root -- apt-get install -y bubblewrap`. Without it every command fails closed, see [Sandbox](#sandbox).
|
|
32
32
|
|
|
33
33
|
Uninstall: `dsh plugin --profile wsl remove dsh-plugin-wsl-env`. Upgrade: run the same `add` command again.
|
|
34
34
|
|
|
@@ -56,6 +56,7 @@ Override a row by id in `$DSH_HOME/profiles/<name>/cordis.patch.yml`. The keys w
|
|
|
56
56
|
| `wsl-fs` | `distro` | `''` | as above |
|
|
57
57
|
| | `restrictToDistro` | `true` | refuse a path in **another distro**'s share; `/mnt/c` is inside this distro and is not affected. Refused with `FS_OUTSIDE_DISTRO`, which is not a sandbox denial and cannot be lifted by wider permissions |
|
|
58
58
|
| | `sandbox` | `true` | check `writeText` and `editText` against the policy |
|
|
59
|
+
| | `substrate` | `agent` | which I/O substrate serves the file tools: `agent` — the resident in-distro agent, where reads, writes and identities run on ext4 (native symlinks and mode bits; the write guard survives to publication, kernel-enforced under a confined policy); `share` — the opt-out: the Windows-side host stack over the 9p share. Both fence writes with the same policy; see [LIMITATIONS.md](docs/LIMITATIONS.md) for what changes |
|
|
59
60
|
| `directory-picker-wsl` | `includeHostHome` | `true` | also list the Windows home directory |
|
|
60
61
|
| `subprocess-wsl` | `distro` | `''` | which distro the GUI terminal opens in |
|
|
61
62
|
|
|
@@ -77,11 +78,28 @@ Commands are confined by `bubblewrap` inside the distro, and file writes are che
|
|
|
77
78
|
|
|
78
79
|
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#sandbox) for the design, and [docs/LIMITATIONS.md](docs/LIMITATIONS.md) for everything the plugin does not do.
|
|
79
80
|
|
|
81
|
+
## Recipes
|
|
82
|
+
|
|
83
|
+
- **Git credential sharing**: let git inside the distro use the Windows-side Git
|
|
84
|
+
Credential Manager — `git config --global credential.helper "/mnt/c/Program\ Files/Git/mingw64/bin/git-credential-manager.exe"`
|
|
85
|
+
(adjust the path to your Windows Git install; WSL2's localhost forwarding is
|
|
86
|
+
platform behaviour, so ports a dev server listens on inside the distro are
|
|
87
|
+
reachable from Windows directly).
|
|
88
|
+
- **Paths and performance**: the model sees and operates on Linux paths
|
|
89
|
+
(`/home/...`) on the distro's own ext4. `/mnt/c` reaches the Windows disk over
|
|
90
|
+
9p — noticeably slow for many small files; keep heavy-IO projects on the
|
|
91
|
+
distro filesystem. `npm run bootstrap -- <distro>` also reports whether
|
|
92
|
+
ripgrep and inotifywait (the search and watch backends) are in place.
|
|
93
|
+
- **WSLENV passthrough**: WSL imports only the variables listed in `WSLENV`.
|
|
94
|
+
This plugin admits the managed `DSH_*` namespace by prefix, translating the
|
|
95
|
+
two Windows-path ones (`DSH_HOME`, `DSH_PROFILE_DIR`) with `/p`. `PATH` is
|
|
96
|
+
deliberately never forwarded — it would shadow the distro's own PATH.
|
|
97
|
+
|
|
80
98
|
## Troubleshooting
|
|
81
99
|
|
|
82
100
|
| Symptom | Cause | Fix |
|
|
83
101
|
|---|---|---|
|
|
84
|
-
| every command reports `SANDBOX_UNAVAILABLE` | `bubblewrap` is not installed in the distro | run `
|
|
102
|
+
| every command reports `SANDBOX_UNAVAILABLE` | `bubblewrap` is not installed in the distro | run `npm run bootstrap -- <distro> --install`, or set `sandbox: false` on both providers |
|
|
85
103
|
| a command or write is refused outside the session folder | expected behaviour of `workspace-write` | accept the wider-permission offer, or open a session on the folder you need |
|
|
86
104
|
| writes are refused even inside the workspace | the session is in `read-only` mode | switch the Permissions selector |
|
|
87
105
|
| `dsh plugin add` warns that no layer was activated | the dependency was already installed, so `add` had nothing to record | run `dsh plugin --profile wsl remove dsh-plugin-wsl-env`, then add it again |
|
|
@@ -101,6 +119,7 @@ npm run probe:sandbox # measure inside the distro what bubblewrap does an
|
|
|
101
119
|
npm run probe # filesystem probe against a real distro (Windows + WSL only)
|
|
102
120
|
npm run probe:sandbox-shell # boot a real harness and drive the confined executor
|
|
103
121
|
npm run probe:terminal # open a PTY through the terminal provider
|
|
122
|
+
npm run probe:substrate # drive the agent filesystem substrate over a real wsl.exe transport (from inside the distro)
|
|
104
123
|
npm run probe:missing-wsl # boot a profile whose wslPath cannot start (Windows + WSL only)
|
|
105
124
|
npm run probe:picker # list the picker's root level, refusals and its cap (Windows + WSL only)
|
|
106
125
|
npm run probe:mode # which POSIX-mode facts survive the share (Windows node only)
|