dsh-plugin-wsl-env 0.5.0 → 0.7.2
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 +256 -0
- package/CONTRIBUTING.md +4 -0
- package/README.md +9 -6
- package/agent/wsl-agent.sh +41 -8
- package/cordis.patch.yml +48 -0
- package/docs/ARCHITECTURE.md +36 -22
- package/docs/CONFIGURATION.md +3 -2
- package/docs/LIMITATIONS.md +52 -54
- package/docs/PARITY.md +18 -0
- package/docs/README.zh.md +10 -7
- package/lib/fs-decisions.js +22 -25
- package/lib/fs-routing.js +176 -0
- package/lib/git-route.js +142 -0
- package/lib/index.js +92 -306
- package/lib/listing.js +47 -0
- package/lib/picker.js +121 -17
- package/lib/sandbox-core.js +157 -0
- package/lib/sandbox.js +11 -131
- package/lib/search-exec.js +114 -0
- package/lib/search-route.js +95 -0
- package/lib/subprocess.js +153 -16
- package/lib/terminal-activity.js +121 -0
- package/lib/watcher.js +63 -12
- package/lib/workspace-files-route.js +205 -0
- package/lib/workspace-files-wsl.js +341 -0
- package/lib/wsl.js +24 -41
- package/package.json +23 -3
- package/lib/fs-publish.js +0 -42
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,262 @@ reference below points at that record's numbering.
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
## [0.7.2] - 2026-10-05
|
|
16
|
+
|
|
17
|
+
One theme: the root-plane routing lands - and the composition that
|
|
18
|
+
silently dropped it and the workspace-files takeover is repaired and
|
|
19
|
+
pinned by a test that runs the plugin's real patch file through the
|
|
20
|
+
loader's real patch algorithm.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **The root-plane filesystem routes by coordinate.** The root `ctx.fs`
|
|
25
|
+
(behind the GUI's change feed and every un-mounted root consumer) is
|
|
26
|
+
replaced by a coordinate-routing variant (`lib/fs-routing.js`): distro UNC
|
|
27
|
+
identities are served by the resident agent - ext4 reads, and real watch
|
|
28
|
+
events for the change feed, which previously never fired on a 9p share -
|
|
29
|
+
while drive paths stay host-native. The root write fence retires with this
|
|
30
|
+
round: the root plane has no write consumers (the model's writes ride the
|
|
31
|
+
preset filesystems, which enforce their own containment). Design note:
|
|
32
|
+
`docs/root-fs-routing.md`.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- **The 0.7.1 composition never mounted what it announced.** The
|
|
37
|
+
`workspace-files` disable, the `fs-sandbox` disable, and both replacement
|
|
38
|
+
rows were written inside a bare `insert:` list — but a patch layer is a
|
|
39
|
+
flat list: an `insert` list is data to append, never nested patches. The
|
|
40
|
+
loader therefore mounted the two disable rows as duplicate-id anonymous
|
|
41
|
+
entries that *shadowed the real upstream rows* (`EntryGroup.update` keys
|
|
42
|
+
rows by id, last occurrence wins): the running app had no root `ctx.fs`
|
|
43
|
+
and no GUI file-tree service at all, while `fs-routing` and
|
|
44
|
+
`workspace-files-wsl` - trapped inside nested `insert:` lists - never
|
|
45
|
+
started. Opening a distro workspace failed accordingly. Both takeovers are
|
|
46
|
+
now top-level entries: disable by id, then append the replacement with a
|
|
47
|
+
bare insert. `test/composition.test.mjs` runs the plugin's actual patch
|
|
48
|
+
file through the loader's actual `applyEntryPatches` over stand-in
|
|
49
|
+
upstream rows, asserting every composed row is a real named entry, ids
|
|
50
|
+
stay unique, the shipped rows are disabled under their own names, and
|
|
51
|
+
both replacements mount - the shape cannot regress silently again.
|
|
52
|
+
- The terminal-activity live check marked its shell with a runtime
|
|
53
|
+
`export`, which `/proc/<pid>/environ` never shows (it reflects the
|
|
54
|
+
exec-time block only), so the probe could never count past one and the
|
|
55
|
+
busy assertion failed deterministically. The marker now rides the spawn
|
|
56
|
+
environment - what `wsl.exe` does when it imports `DSH_TERMINAL_ID`
|
|
57
|
+
before `--exec`.
|
|
58
|
+
|
|
59
|
+
## [0.7.1] - 2026-10-04
|
|
60
|
+
|
|
61
|
+
One theme: the remaining root-plane 9P consumers move inside the distro -
|
|
62
|
+
the GUI file tree and previews, and the workspace-changes git snapshots -
|
|
63
|
+
plus the test-infrastructure maturity (a REAL-composition boot test and
|
|
64
|
+
the pure text-mechanics refusals under direct unit test) that the
|
|
65
|
+
promotion work was built on.
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
|
|
69
|
+
- **git snapshot commands run the distro's git.** `workspace-changes`
|
|
70
|
+
snapshots a workspace by spawning the host git through
|
|
71
|
+
`ctx.subprocess.spawn` — on a distro workspace that was Windows git
|
|
72
|
+
scanning the full work tree across the 9p share, the most
|
|
73
|
+
metadata-heavy operation there is. Spawns whose working directory names
|
|
74
|
+
a distro and whose argv carries none of the absolute-output discovery
|
|
75
|
+
flags are rewritten into `wsl.exe -d <distro> --cd <linux dir> --exec
|
|
76
|
+
git …` (`lib/git-route.js`). The snapshot is safe to route because it
|
|
77
|
+
already runs in a fully isolated git environment (`GIT_INDEX_FILE` /
|
|
78
|
+
`GIT_OBJECT_DIRECTORY` / `GIT_ALTERNATE_OBJECT_DIRECTORIES` in the
|
|
79
|
+
tool's private scratch — the repository's index, object store, work
|
|
80
|
+
tree, and refs stay unchanged); the route translates those path-shaped
|
|
81
|
+
env values into distro coordinates and forwards them through `WSLENV`.
|
|
82
|
+
Verified against a live distro: the snapshot sequence completes with the
|
|
83
|
+
user's index, object store, and refs untouched.
|
|
84
|
+
- **The GUI file tree and previews are served from inside the distro.**
|
|
85
|
+
`workspace-files` - the root-plane consumer behind the GUI's file tree
|
|
86
|
+
and previews - read UNC workspaces through the 9p share. The plugin
|
|
87
|
+
replaces the composition row with a distro-routed variant
|
|
88
|
+
(`lib/workspace-files-wsl.js`): listings, stats, line-paged reads and
|
|
89
|
+
byte windows for a \\wsl.localhost\\<distro> workspace run through the
|
|
90
|
+
resident agent, with the upstream refusals mirrored (not-found,
|
|
91
|
+
outside-workspace, not-directory, not-regular-file, too-large,
|
|
92
|
+
not-text) and `absolutePath` synthesized in the UNC display form so
|
|
93
|
+
the GUI is unchanged. Windows-folder sessions keep the shipped
|
|
94
|
+
host-backed service; the watch stream delegates unchanged.
|
|
95
|
+
(`lib/workspace-files-route.js` holds the pure builders/parsers;
|
|
96
|
+
`test/workspace-files-wsl.test.mjs` covers every wire shape and refusal
|
|
97
|
+
over a fake agent runner.)
|
|
98
|
+
- **bootstrap installs and reports git.** The git snapshot routing needs
|
|
99
|
+
git inside the distro; `bootstrap --install` includes it in every
|
|
100
|
+
package manager's list and the report names it alongside ripgrep (the
|
|
101
|
+
snapshot backend for workspace-changes).
|
|
102
|
+
- **A REAL-composition boot test.** The plugin's fs row is booted through
|
|
103
|
+
the cordis Loader from a test-only yml composition and asserted against
|
|
104
|
+
a live distro read (`test/fs.boot.test.mjs`), alongside direct unit
|
|
105
|
+
tests for the pure text mechanics' refusal edges
|
|
106
|
+
(`test/fsio-text.test.mjs`).
|
|
107
|
+
|
|
108
|
+
## [0.7.0] - 2026-10-04
|
|
109
|
+
|
|
110
|
+
One theme: the 9p share is out of the model's reach. Two changes land together
|
|
111
|
+
because they close the same measured gap — the file-search spawn was the one
|
|
112
|
+
model-facing I/O path that still crossed the share, in its slow direction
|
|
113
|
+
(~2 s per search where the distro's own rg answers in single-digit
|
|
114
|
+
milliseconds; `\\wsl.localhost` metadata round trips at ~17 ms per file), and
|
|
115
|
+
the share substrate was the configured way back onto that path.
|
|
116
|
+
|
|
117
|
+
### Added
|
|
118
|
+
|
|
119
|
+
- **The directory picker lists distro levels from inside the distro.** A level
|
|
120
|
+
under `\\wsl.localhost\<distro>` is served by the resident agent's
|
|
121
|
+
`ls -1ALp` in one round trip — a one-shot `wsl.exe --exec ls` when the
|
|
122
|
+
resident is out, never the host's 9p walk, which paid one metadata round
|
|
123
|
+
trip per entry plus one more per symlink, in the share's slow direction.
|
|
124
|
+
`-L` folds the per-entry symlink stat the host walk used to pay; `-p`'s
|
|
125
|
+
trailing `/` is what the row parser keys on, and a broken link drops out
|
|
126
|
+
exactly as the old per-entry `stat` dropped it. `createDirectory` follows
|
|
127
|
+
the same route (`mkdir` through the agent, one-shot otherwise). The parse
|
|
128
|
+
is the pure `parseLsListing`/`lsListingArgv` pair in `lib/listing.js`
|
|
129
|
+
(`test/listing.test.mjs`); the accepted limitation — a filename containing
|
|
130
|
+
a newline would split into two rows — is documented in LIMITATIONS.md.
|
|
131
|
+
- **The file search runs the distro's own rg.** `WslSubprocessRuntime` now
|
|
132
|
+
overrides `spawn` beside `spawnTerminal`: a spawn of the packaged ripgrep
|
|
133
|
+
binary whose working directory names a distro (a `\\wsl.localhost\<distro>`
|
|
134
|
+
UNC — the form every distro workspace search carries) is rewritten into
|
|
135
|
+
`wsl.exe -d <distro> --cd <linux dir> --exec rg …` with every argument
|
|
136
|
+
forwarded verbatim, so the tool reads the distro rg's output format and exit
|
|
137
|
+
codes unchanged. The decision is the peer-free `lib/search-route.js`
|
|
138
|
+
(mirroring `terminal-route.js`), and the rewrite delegates to the shipped
|
|
139
|
+
spawn — the handle still wraps a real host process, so the consumer's output
|
|
140
|
+
budget, spill machinery and teardown all work unchanged. One guard rides
|
|
141
|
+
the rewrite: when the tool named no search path (its pattern rides
|
|
142
|
+
`--regexp=`; a bare `--` is how it spells a path), the rewrite appends
|
|
143
|
+
`-- .` — because `wsl.exe --exec` hands distro-side rg a relay fifo on
|
|
144
|
+
stdin, and rg's readable-stdin heuristic then searches stdin instead of the
|
|
145
|
+
directory. The packaged binary saw a non-readable NUL stdin under the old
|
|
146
|
+
path, which is why the heuristic never fired there. A drive-path
|
|
147
|
+
directory keeps the host binary, which is native there; a distro without rg
|
|
148
|
+
surfaces rg's own "command not found" (exit 127) instead of a silent fall
|
|
149
|
+
back to the share — `npm run bootstrap -- <distro> --install` installs it.
|
|
150
|
+
There is no 9p path left to fall back to, by design. (`test/search-route.test.mjs`.)
|
|
151
|
+
- **The search rides the warm resident, not a one-shot spawn.** The rewrite's
|
|
152
|
+
distro command is exec'd by the resident agent — one round trip (~10 ms)
|
|
153
|
+
instead of a `wsl.exe` process startup (~400 ms) per search. The tool's
|
|
154
|
+
handle surface is small (`done` plus `collected`), and `lib/search-exec.js`
|
|
155
|
+
builds exactly that facade: the agent's out state is the lazy fallback cue,
|
|
156
|
+
delegating to the one-shot `wsl.exe --exec` handle inside `done` — never an
|
|
157
|
+
error, never the share. The tool's output budget rides the agent's
|
|
158
|
+
per-stream capture cap; cancellation and `kill()` abort the agent exec and
|
|
159
|
+
settle as the killed outcome the one-shot path reports.
|
|
160
|
+
(`test/search-exec.test.mjs`.)
|
|
161
|
+
- **Idle distro terminals are reclaimed.** The controller reclaims an
|
|
162
|
+
unattended terminal after 2 h of observed idleness, but its observation is
|
|
163
|
+
host-side and a distro terminal can never answer it: the shipped
|
|
164
|
+
shell-activity integration is gated to interactive `bash`/`zsh` launched
|
|
165
|
+
directly on a POSIX host, every branch of `inspectActivity` settles on
|
|
166
|
+
`unknown` for a `wsl.exe` terminal, and `unknown` never accumulates idle —
|
|
167
|
+
so distro terminals had to be closed by hand. The terminal is now marked at
|
|
168
|
+
launch (a per-terminal `DSH_TERMINAL_ID` through the existing `WSLENV`
|
|
169
|
+
forwarding) and its `inspectActivity` counts the marked processes through
|
|
170
|
+
the resident agent: exactly one (the shell at its prompt) is idle, more
|
|
171
|
+
than one (a running command or a nested shell) is busy, and the agent
|
|
172
|
+
being out answers `unknown` — reclamation pauses, because a probe that
|
|
173
|
+
cannot answer must not authorize a close. A nested shell keeps the
|
|
174
|
+
terminal busy by design, as does anything a shell's startup files spawn
|
|
175
|
+
with the inherited marker. `terminalIdleReclaim: false` restores
|
|
176
|
+
close-by-hand. (`lib/terminal-activity.js`, `test/terminal-activity.test.mjs`.)
|
|
177
|
+
|
|
178
|
+
### Breaking changes
|
|
179
|
+
|
|
180
|
+
- **`substrate: "share"` is refused at construction.** The Windows-side share
|
|
181
|
+
backend was the file tools' opt-out onto the 9p share; the resident agent
|
|
182
|
+
serves every distro read and write, and no file tool crosses the share any
|
|
183
|
+
more. A profile still carrying the value fails to boot with the migration
|
|
184
|
+
(`shareSubstrateRefusal`): delete the `substrate` line or set it to
|
|
185
|
+
`"agent"`. The failure message for an agent that is out
|
|
186
|
+
(`substrateFailure`) no longer offers the share as a way out and names the
|
|
187
|
+
recovery path instead. The share-only plumbing (the symlink retry, the 9p
|
|
188
|
+
publication hooks, and the `agent` and `resolveSymlinks` keys that gated
|
|
189
|
+
them) is removed with this change — `lib/fs-publish.js` with it.
|
|
190
|
+
|
|
191
|
+
## [0.6.0] - 2026-10-04
|
|
192
|
+
|
|
193
|
+
One theme: the P2 backlog from the 2026-10-03 review, closed with the same
|
|
194
|
+
evidence bar as its P0/P1 — every fix carries a check that fails without it,
|
|
195
|
+
and the real-distro probes run green over the changed paths — plus the
|
|
196
|
+
import-time regression that backlog carried, caught at the first
|
|
197
|
+
sync-and-restart: `watchMaxDepth` was written `z.number().int()`, a member the
|
|
198
|
+
pinned schemastery does not publish, which took `wsl-shell` and `wsl-fs` down
|
|
199
|
+
in every distro workspace session. The key now uses `z.natural()`, and
|
|
200
|
+
`test/syntax.mjs` reads the chained half of the schemastery surface so the
|
|
201
|
+
next such member dies in CI instead of in the app.
|
|
202
|
+
|
|
203
|
+
### Fixed
|
|
204
|
+
|
|
205
|
+
- **The in-distro watcher no longer fails silently (review M10).** Four defects
|
|
206
|
+
in `lib/watcher.js`, fixed together because they share one loop:
|
|
207
|
+
the stamp file is removed on every way out (EXIT trap; TERM/INT/HUP routed
|
|
208
|
+
through a plain exit so the trap runs); a target that vanishes mid-watch now
|
|
209
|
+
ends the loop with a report through the error channel instead of arming
|
|
210
|
+
forever (`E` protocol line, unit- and probe-tested); the scan's depth is
|
|
211
|
+
boundable through the new `wsl-fs.watchMaxDepth` key (`0`, the default,
|
|
212
|
+
keeps the whole-tree walk); and the loop's stderr is drained continuously
|
|
213
|
+
with its tail kept for error context, so a chatty `sh` can no longer stall
|
|
214
|
+
the watch at the 64 KiB pipe limit.
|
|
215
|
+
- **`watchMaxDepth` no longer takes both preset rows down at import (a
|
|
216
|
+
regression the M10 entry above introduced, caught after the first
|
|
217
|
+
sync-and-restart).** The key was written `z.number().int().min(0)`, but the
|
|
218
|
+
pinned schemastery publishes no `int` on any schema instance, and a Config
|
|
219
|
+
schema is a class field — so `lib/index.js` threw
|
|
220
|
+
`TypeError: z.number(...).int is not a function` while it was still being
|
|
221
|
+
imported, the loader left `wsl-shell` and `wsl-fs` fiber-less, and the preset
|
|
222
|
+
audit reported exactly those two rows as `never started`: every session in a
|
|
223
|
+
distro workspace, new or historical, failed at the first message. The key is
|
|
224
|
+
now `z.natural().default(0)` — the pinned spelling of "integer >= 0", the
|
|
225
|
+
idiom `picker` and `auto-preset` already use — and `test/syntax.mjs` now
|
|
226
|
+
reads the chained half of the schemastery surface (a balanced walk that
|
|
227
|
+
survives nested call arguments, string literals and template
|
|
228
|
+
interpolations), so a member the peer does not publish fails the suite
|
|
229
|
+
instead of waiting for the Desktop app. Reproduced and verified in a
|
|
230
|
+
peer-stub harness: the `lib/index.js` import went from the `TypeError` to
|
|
231
|
+
clean with the fix, and the new check fails on the old line.
|
|
232
|
+
- **A wedged WSL service can no longer hang a tool call forever (review M11).**
|
|
233
|
+
Every `wsl.exe` resolution call in `lib/wsl.js` now runs under a deadline
|
|
234
|
+
(`DEFAULT_WSL_DEADLINE_MS`, 60 s — generous on purpose for cold boots, `0`
|
|
235
|
+
waits forever), and the deadline kill is named for what it is in the error,
|
|
236
|
+
with the `wsl.exe --shutdown` remedy. Separately, the agent execution path's
|
|
237
|
+
`confine()` now receives the request's abort signal, the same one the
|
|
238
|
+
one-shot path always passed.
|
|
239
|
+
- **A timed-out command can no longer leave descendants running (review M12).**
|
|
240
|
+
EXEC requests run as their own session and process group (`setsid`, detected
|
|
241
|
+
at startup with a documented fallback), and the in-distro timeout and KILL
|
|
242
|
+
frames signal the whole group — TERM, then KILL after the grace, descendants
|
|
243
|
+
included. Proven by a new real-script check: a command that backgrounds a
|
|
244
|
+
marked shell and outlives its budget leaves zero survivors.
|
|
245
|
+
- **Documentation drift cluster (review M9).** ARCHITECTURE.md's service table
|
|
246
|
+
claimed the share substrate was the default (it is `agent`), pointed at
|
|
247
|
+
`lib/sandbox.js` for `bwrapProfileArgs` (built in `lib/bwrap.js`), and kept
|
|
248
|
+
the long-gone `watch()` refusal in the error-code table; the `substrate`
|
|
249
|
+
schema docstring claimed watches run through the substrate (both substrates
|
|
250
|
+
ride the same `lib/watcher.js` poll loop); PARITY.md now records the rg
|
|
251
|
+
interception as designed-but-never-implemented instead of leaving it
|
|
252
|
+
implied; and the probe checklists (both READMEs, CONTRIBUTING, RELEASING,
|
|
253
|
+
the PR template, CI's comment) list the agent, watch, substrate and exec
|
|
254
|
+
probes that had shipped without being listed anywhere.
|
|
255
|
+
|
|
256
|
+
### Changed
|
|
257
|
+
|
|
258
|
+
- **Test structure (review M15).** The sandbox checks run everywhere now:
|
|
259
|
+
the probe-and-confine logic moved to the peer-free `lib/sandbox-core.js`
|
|
260
|
+
(one implementation, still shipped), `lib/sandbox.js` binds it to the peer's
|
|
261
|
+
`SandboxUnavailableError`, and the tests inject a stand-in with the same
|
|
262
|
+
`code` — CI no longer skips them. `FS_SANDBOX_DENIED`'s write-only
|
|
263
|
+
classification carries three unit checks (write denial classified, the same
|
|
264
|
+
signature ignored off the write path, ordinary permission failures keep
|
|
265
|
+
theirs). The probes stopped carrying inline bwrap profiles: `exec.sh` and
|
|
266
|
+
`sandbox.sh` derive their profiles from `lib/bwrap.js` itself, so a profile
|
|
267
|
+
change fails the probe instead of hiding behind a copy. `lib/wsl.js`'s
|
|
268
|
+
spawning half — previously probe-only — gained `test/wsl.test.mjs`.
|
|
269
|
+
`test/probe/agent.sh` is wired as `npm run probe:agent` like its siblings.
|
|
270
|
+
|
|
15
271
|
## [0.5.0] - 2026-10-04
|
|
16
272
|
|
|
17
273
|
One theme: telling the truth, in the reader's language. The Plugins page now
|
package/CONTRIBUTING.md
CHANGED
|
@@ -69,6 +69,10 @@ terminal if you would rather not see the prompt.
|
|
|
69
69
|
| `npm run probe:sandbox` | what bubblewrap confines and what it does not | no |
|
|
70
70
|
| `npm run probe:sandbox-shell` | the confined executor through a real boot | yes |
|
|
71
71
|
| `npm run probe:terminal` | the terminal provider | yes |
|
|
72
|
+
| `npm run probe:substrate` | the agent filesystem substrate over a real `wsl.exe` transport | yes; inside the distro |
|
|
73
|
+
| `npm run probe:watch` | the in-distro watcher over a real directory | yes; inside the distro |
|
|
74
|
+
| `npm run probe:agent` | the resident-vs-one-shot fallback parity legs | yes; inside the distro |
|
|
75
|
+
| `npm run probe:exec` | the agent-backed execution handle: timeout, kill, cwd failure | yes; inside the distro |
|
|
72
76
|
| `npm run probe:missing-wsl` | how a `wslPath` that cannot start is reported | yes |
|
|
73
77
|
| `npm run probe:picker` | the picker's root level, its refusals and its cap | yes |
|
|
74
78
|
| `npm run probe:mode` | which POSIX-mode facts survive the share | no; Windows Node |
|
package/README.md
CHANGED
|
@@ -57,11 +57,11 @@ Override a row by id in `$DSH_HOME/profiles/<name>/cordis.patch.yml`. The keys w
|
|
|
57
57
|
| `wsl-fs` | `distro` | `''` | as above |
|
|
58
58
|
| | `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 |
|
|
59
59
|
| | `sandbox` | `true` | check `writeText` and `editText` against the policy |
|
|
60
|
-
| | `substrate` | `agent` | which I/O substrate serves the file tools
|
|
60
|
+
| | `substrate` | `agent` | which I/O substrate serves the file tools. Only the resident in-distro agent remains: reads, writes and identities run on ext4 (native symlinks and mode bits; the write guard survives to publication, kernel-enforced under a confined policy). The former `"share"` opt-out — the Windows-side host stack over the 9p share — is refused at boot; no file tool crosses the share |
|
|
61
61
|
| `directory-picker-wsl` | `includeHostHome` | `true` | also list the Windows home directory |
|
|
62
62
|
| `subprocess-wsl` | `distro` | `''` | which distro the GUI terminal opens in |
|
|
63
63
|
|
|
64
|
-
[`cordis.patch.yml`](cordis.patch.yml) is the commented reference for every shipped value. [docs/CONFIGURATION.md](docs/CONFIGURATION.md) lists the rest, including `shell`, `loginShell`, `cwd`, `timeoutMs`, `
|
|
64
|
+
[`cordis.patch.yml`](cordis.patch.yml) is the commented reference for every shipped value. [docs/CONFIGURATION.md](docs/CONFIGURATION.md) lists the rest, including `shell`, `loginShell`, `cwd`, `timeoutMs`, `preferredDistro` and `maxEntries`; [examples/profile.cordis.patch.yml](examples/profile.cordis.patch.yml) is a machine-local layer to copy from.
|
|
65
65
|
|
|
66
66
|
## Recipes
|
|
67
67
|
|
|
@@ -74,7 +74,7 @@ Override a row by id in `$DSH_HOME/profiles/<name>/cordis.patch.yml`. The keys w
|
|
|
74
74
|
(`/home/...`) on the distro's own ext4. `/mnt/c` reaches the Windows disk over
|
|
75
75
|
9p — noticeably slow for many small files; keep heavy-IO projects on the
|
|
76
76
|
distro filesystem. `npm run bootstrap -- <distro>` also reports whether
|
|
77
|
-
ripgrep and inotifywait (the search and watch backends) are in place.
|
|
77
|
+
ripgrep, git and inotifywait (the search, snapshot and watch backends) are in place.
|
|
78
78
|
- **WSLENV passthrough**: WSL imports only the variables listed in `WSLENV`.
|
|
79
79
|
This plugin admits the managed `DSH_*` namespace by prefix, translating the
|
|
80
80
|
two Windows-path ones (`DSH_HOME`, `DSH_PROFILE_DIR`) with `/p`. `PATH` is
|
|
@@ -100,7 +100,7 @@ preset-wsl (the wsl agent preset; its services run in isolate realms)
|
|
|
100
100
|
|
|
101
101
|
**Why two levels.** `wsl-shell` and `wsl-fs` live inside the `wsl` agent preset, which `auto-preset` binds whenever a session's workspace is inside the distro: a Windows-folder session keeps the stock providers, a distro session gets the WSL ones — the environment is a property of the session, not of the process. The terminal controller is the exception: it resolves its execution world through the root context, which never sees a preset's isolate realms, so `subprocess-wsl` sits at the composition level instead.
|
|
102
102
|
|
|
103
|
-
**Commands and files.** A command runs as `wsl.exe -d <distro> --cd <linux dir> --exec <login shell> -lc <cmd>` inside a distro-side bubblewrap profile assembled with the same arguments as DSH's own Linux runner, so confinement semantics and error messages match a Linux host. The file tools read and write real distro files on
|
|
103
|
+
**Commands and files.** A command runs as `wsl.exe -d <distro> --cd <linux dir> --exec <login shell> -lc <cmd>` inside a distro-side bubblewrap profile assembled with the same arguments as DSH's own Linux runner, so confinement semantics and error messages match a Linux host. The file tools read and write real distro files on the resident in-distro agent — reads, writes and identities run on ext4 with native symlinks and mode bits — and the file-search spawn is rewritten the same way: a search over a distro workspace runs the distro's own `rg` (`wsl.exe --exec`), never the Windows binary over the 9p share. Writes are checked against the same policy the command sandbox enforces.
|
|
104
104
|
|
|
105
105
|
## Sandbox
|
|
106
106
|
|
|
@@ -129,8 +129,8 @@ See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#sandbox) for the design, and [do
|
|
|
129
129
|
| the terminal still opens `cmd.exe` | the `terminal-controller` row from the layer did not apply | check that `dsh --profile wsl --dump-config` shows `shell: { path: wsl.exe, name: WSL }` |
|
|
130
130
|
| changes to `lib/` have no effect | ES module cache | restart the app |
|
|
131
131
|
| `link:\\wsl.localhost\...` leaves a broken symlink | pnpm cannot link a UNC path | link a Windows path instead; developing inside the distro needs the runtime mirror, see [Development](#development) |
|
|
132
|
-
| `glob` and `grep` are slow | the Windows-
|
|
133
|
-
| the terminal reports `unknown` activity |
|
|
132
|
+
| `glob` and `grep` are slow | a distro search without rg inside the distro falls back to rg's own "command not found"; a Windows-folder search is native and unaffected | run `npm run bootstrap -- <distro> --install` (installs ripgrep); distro searches always run the distro's rg — they never walk the 9p share |
|
|
133
|
+
| the terminal reports `unknown` activity | only while the resident agent is out — distro terminals are observed from inside the distro (a `DSH_TERMINAL_ID` marker scanned in `/proc`: a shell alone is `idle`, a shell running anything is `busy`) | check the distro is running; idle terminals are reclaimed automatically after the controller's unattended timeout (2 h by default), and `terminalIdleReclaim: false` restores the close-by-hand posture |
|
|
134
134
|
| a result names an `FS_*` code | the code says what refused it, and what clears it | see the error-code table in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#error-codes) |
|
|
135
135
|
|
|
136
136
|
## Development
|
|
@@ -143,6 +143,9 @@ npm run probe # filesystem probe against a real distro (Windows +
|
|
|
143
143
|
npm run probe:sandbox-shell # boot a real harness and drive the confined executor
|
|
144
144
|
npm run probe:terminal # open a PTY through the terminal provider
|
|
145
145
|
npm run probe:substrate # drive the agent filesystem substrate over a real wsl.exe transport (from inside the distro)
|
|
146
|
+
npm run probe:watch # arm the in-distro watcher over a real directory (from inside the distro)
|
|
147
|
+
npm run probe:agent # the resident-vs-one-shot fallback parity legs (from inside the distro)
|
|
148
|
+
npm run probe:exec # the agent-backed execution handle: timeout, kill, cwd failure (from inside the distro)
|
|
146
149
|
npm run probe:missing-wsl # boot a profile whose wslPath cannot start (Windows + WSL only)
|
|
147
150
|
npm run probe:picker # list the picker's root level, refusals and its cap (Windows + WSL only)
|
|
148
151
|
npm run probe:mode # which POSIX-mode facts survive the share (Windows node only)
|
package/agent/wsl-agent.sh
CHANGED
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
# With a cap, stdout and stderr are each cut at that many bytes and
|
|
28
28
|
# the flags say which streams were cut; 0 means the whole capture.
|
|
29
29
|
# or ERR|<id>|<reason>|<b64 message> (reason: cwd)
|
|
30
|
+
# An EXEC request runs as its own session and process group (`setsid`),
|
|
31
|
+
# so a timeout or a KILL frame takes the whole descendant tree — not
|
|
32
|
+
# just the direct child, which is how a build tool's daemonized
|
|
33
|
+
# grandchild outlives the kill.
|
|
30
34
|
# FS|<id>|<op>|<timeout seconds|0>|<n args>
|
|
31
35
|
# followed by <n args> lines of base64, one argument each — arguments
|
|
32
36
|
# arrive still base64 so a write's content never passes through a shell
|
|
@@ -81,31 +85,58 @@ b64enc_file() {
|
|
|
81
85
|
|
|
82
86
|
kill_current() {
|
|
83
87
|
if [ -n "$CURRENT_PID" ]; then
|
|
84
|
-
|
|
88
|
+
kill_request TERM "$CURRENT_PID"
|
|
85
89
|
fi
|
|
86
90
|
}
|
|
87
91
|
|
|
92
|
+
# Signal one in-flight request, descendants included. With `setsid` (EXEC
|
|
93
|
+
# requests, below) the request is its own process-group leader, so the
|
|
94
|
+
# negative PID signals the WHOLE group — every descendant that did not make
|
|
95
|
+
# its own session. Without it the group does not exist, the negative-PID kill
|
|
96
|
+
# fails with ESRCH, and the direct kill keeps the old guarantee. A shell whose
|
|
97
|
+
# `kill` builtin cannot parse `-- -PID` at all also lands here.
|
|
98
|
+
kill_request() {
|
|
99
|
+
# $1 = signal name, $2 = request PID.
|
|
100
|
+
kill -s "$1" -- "-$2" 2>/dev/null || kill -s "$1" "$2" 2>/dev/null || :
|
|
101
|
+
}
|
|
102
|
+
|
|
88
103
|
shutdown() {
|
|
89
104
|
kill_current
|
|
90
105
|
exit 0
|
|
91
106
|
}
|
|
92
107
|
trap shutdown TERM INT HUP
|
|
93
108
|
|
|
109
|
+
# Whether EXEC requests can run as their own session. `setsid` makes the
|
|
110
|
+
# request process a session AND process-group leader (pid = pgid), which is
|
|
111
|
+
# what lets kill_request signal the whole descendant tree; the agent's own
|
|
112
|
+
# shell cannot do this portably — `set -m` in a non-interactive dash has been
|
|
113
|
+
# observed skipping the setpgid entirely, and a kill against a PID that is
|
|
114
|
+
# not a group leader is a no-op. A distro without `setsid` keeps working:
|
|
115
|
+
# requests then run in the agent's own group and the direct-child kill of the
|
|
116
|
+
# pre-setsid behaviour applies.
|
|
117
|
+
SETSID=
|
|
118
|
+
if command -v setsid >/dev/null 2>&1; then
|
|
119
|
+
SETSID=setsid
|
|
120
|
+
fi
|
|
121
|
+
|
|
94
122
|
timeout_watcher() {
|
|
95
|
-
# $1 = seconds, $2 = pid, $3 = grace seconds. TERM first
|
|
96
|
-
#
|
|
97
|
-
# the
|
|
123
|
+
# $1 = seconds, $2 = pid, $3 = grace seconds. TERM first — the whole group,
|
|
124
|
+
# so a command that ignores TERM takes its descendants with it when the
|
|
125
|
+
# KILL lands; a process that ignores TERM gets KILLed after the grace, so a
|
|
126
|
+
# stuck command cannot hang the request forever.
|
|
98
127
|
sleep "$1"
|
|
99
|
-
|
|
128
|
+
kill_request TERM "$2"
|
|
100
129
|
sleep "${3:-3}"
|
|
101
|
-
|
|
130
|
+
kill_request KILL "$2"
|
|
102
131
|
}
|
|
103
132
|
|
|
104
133
|
# Capture one request's output and answer with RES. $1 = request id, $2 =
|
|
105
134
|
# timeout seconds ('' or 0 = none), $3 = per-stream output cap in bytes (0 or
|
|
106
135
|
# empty = unlimited), $4 = launch mode: `exec` runs "$@" (argv words) as the
|
|
107
|
-
# request process
|
|
108
|
-
# the
|
|
136
|
+
# request process, in its own session via `setsid` when the distro has it —
|
|
137
|
+
# the timeout and KILL then signal the whole descendant group; `subshell` runs
|
|
138
|
+
# ( "$@" ) in a forked subshell, which is how the FS handlers — shell
|
|
139
|
+
# functions, not executables — run.
|
|
109
140
|
run_capture() {
|
|
110
141
|
local req_id timeout_s maxout mode rc watcher_pid out_cut err_cut
|
|
111
142
|
req_id=$1
|
|
@@ -121,6 +152,8 @@ run_capture() {
|
|
|
121
152
|
CURRENT_ID=$req_id
|
|
122
153
|
if [ "$mode" = subshell ]; then
|
|
123
154
|
( "$@" ) >"$OUT_FILE" 2>"$ERR_FILE" &
|
|
155
|
+
elif [ -n "$SETSID" ]; then
|
|
156
|
+
( exec setsid "$@" ) >"$OUT_FILE" 2>"$ERR_FILE" &
|
|
124
157
|
else
|
|
125
158
|
"$@" >"$OUT_FILE" 2>"$ERR_FILE" &
|
|
126
159
|
fi
|
package/cordis.patch.yml
CHANGED
|
@@ -102,6 +102,54 @@
|
|
|
102
102
|
|
|
103
103
|
- id: directory-picker-browse-surface
|
|
104
104
|
name: '@deepseek-ai/dsh-client-ui-directory-picker-browse'
|
|
105
|
+
|
|
106
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
107
|
+
# The distro-side takeovers of the two root-plane services.
|
|
108
|
+
#
|
|
109
|
+
# A patch layer is a flat list: an `id:` entry patches the shipped row, and a
|
|
110
|
+
# bare `insert:` list appends new rows as data. A disable written INSIDE an
|
|
111
|
+
# insert list is not a patch — it becomes a duplicate-id data row that shadows
|
|
112
|
+
# the real service row (the loader keys rows by id, last occurrence wins), and
|
|
113
|
+
# a nested `insert:` list is never interpreted at all. Both takeovers are
|
|
114
|
+
# therefore two top-level entries: disable the shipped row by id, then append
|
|
115
|
+
# the replacement with a bare insert. test/composition.test.mjs runs this file
|
|
116
|
+
# through the real patch algorithm to keep the shape honest.
|
|
117
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
118
|
+
|
|
119
|
+
# The GUI file tree and previews for a distro workspace are served from inside
|
|
120
|
+
# the distro (resident agent) instead of Windows-side 9P walks. The variant
|
|
121
|
+
# extends the shipped service — a distro UNC routes distro-side, a drive path
|
|
122
|
+
# calls super (host native), the watch stream delegates unchanged — so
|
|
123
|
+
# replacing the row is safe for Windows-folder sessions too.
|
|
124
|
+
- id: workspace-files
|
|
125
|
+
disabled: true
|
|
126
|
+
|
|
127
|
+
# The root-plane filesystem routes by coordinate: distro UNC identities are
|
|
128
|
+
# served by the resident agent (real watch events, ext4 reads), drive paths
|
|
129
|
+
# stay host-native. Replaces the root fs-sandbox row, whose write fence has no
|
|
130
|
+
# root-plane consumers (the model's writes ride the preset filesystems; see
|
|
131
|
+
# docs/root-fs-routing.md).
|
|
132
|
+
- id: fs-sandbox
|
|
133
|
+
disabled: true
|
|
134
|
+
|
|
135
|
+
- insert:
|
|
136
|
+
- id: workspace-files-wsl
|
|
137
|
+
name: 'dsh-plugin-wsl-env/workspace-files-wsl'
|
|
138
|
+
config:
|
|
139
|
+
maxBytes: 2097152
|
|
140
|
+
maxFileBytes: 33554432
|
|
141
|
+
maxLines: 5000
|
|
142
|
+
maxEntries: 2000
|
|
143
|
+
|
|
144
|
+
- id: fs-routing
|
|
145
|
+
name: 'dsh-plugin-wsl-env/fs-routing'
|
|
146
|
+
config:
|
|
147
|
+
distro: ''
|
|
148
|
+
wslPath: wsl.exe
|
|
149
|
+
sandbox: true
|
|
150
|
+
maskWindowsDrive: false
|
|
151
|
+
restrictToDistro: true
|
|
152
|
+
watchMaxDepth: 0
|
|
105
153
|
# Contributes the WSL environment's facts (distro / real shell / home) to the
|
|
106
154
|
# managed DSH_* namespace that dsh-shell-env injects into every model shell
|
|
107
155
|
# call. That is DSH's sanctioned channel for shell-environment facts.
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -19,8 +19,8 @@ smaller integrations.
|
|
|
19
19
|
| Service | Class | File | What it does |
|
|
20
20
|
|---|---|---|---|
|
|
21
21
|
| `ctx.shell` | `WslShellExecutor` | [`lib/index.js`](../lib/index.js) | Runs each command as `wsl.exe -d <distro> --cd <linux dir> --exec <login shell> -lc <cmd>`, wrapped in a distro-side `bwrap` sandbox. |
|
|
22
|
-
| `ctx.fs` | `WslFileSystem` | [`lib/index.js`](../lib/index.js) | Serves the file tools from real distro files
|
|
23
|
-
| `ctx.subprocess` | `WslSubprocessRuntime` | [`lib/subprocess.js`](../lib/subprocess.js) | Opens the GUI terminal inside the distro, in the session workspace. |
|
|
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 spawn into the distro's own rg (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
|
|
@@ -30,7 +30,7 @@ overridden.
|
|
|
30
30
|
|
|
31
31
|
| Subpath | Class or name | File | What it does |
|
|
32
32
|
|---|---|---|---|
|
|
33
|
-
| `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. |
|
|
33
|
+
| `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
34
|
| `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
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 `DSH_WSL_HOME` to the managed `DSH_*` namespace. |
|
|
36
36
|
|
|
@@ -89,7 +89,7 @@ Three coordinate systems appear in the code:
|
|
|
89
89
|
| System | Example | Used by |
|
|
90
90
|
|---|---|---|
|
|
91
91
|
| Linux inside the distro | `/home/you/proj/a.ts` | commands, `ctx.shell` |
|
|
92
|
-
| The distro's UNC share | `\\wsl.localhost\ubuntu\home\you\proj\a.ts` | the
|
|
92
|
+
| The distro's UNC share | `\\wsl.localhost\ubuntu\home\you\proj\a.ts` | the harness's path identities (`ctx.fs` target keys), the picker and terminal routing — a coordinate, not an I/O path: no file tool reads or writes through the share |
|
|
93
93
|
| The Windows filesystem | `C:\Users\you\...`, or `/mnt/c/Users/you/...` from inside | sessions opened on a Windows folder |
|
|
94
94
|
|
|
95
95
|
[`lib/paths.js`](../lib/paths.js) converts between them. It is pure: it imports no
|
|
@@ -98,22 +98,35 @@ path sits under a writable root. The filesystem fence uses them.
|
|
|
98
98
|
|
|
99
99
|
## The filesystem substrate
|
|
100
100
|
|
|
101
|
-
`wsl-fs` serves `ctx.fs` on one
|
|
102
|
-
`substrate: share
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
(
|
|
116
|
-
|
|
101
|
+
`wsl-fs` serves `ctx.fs` on one I/O substrate: the resident in-distro agent.
|
|
102
|
+
The former Windows-side share backend (`substrate: "share"`) is retired — a
|
|
103
|
+
profile carrying the value is refused at boot, and no file tool crosses the
|
|
104
|
+
9p share. The substrate's facts:
|
|
105
|
+
|
|
106
|
+
- **Reads, writes, identities** run on ext4 through the resident; symlinks and
|
|
107
|
+
mode bits are native (every identity is a distro-side `realpath`; the
|
|
108
|
+
publication rename is the kernel's).
|
|
109
|
+
- **Guarded writes** carry their intent to the publication: a `createIfAbsent`
|
|
110
|
+
publishes with a no-replace link, closing the check-then-write window for
|
|
111
|
+
creates outright; an overwrite or edit carries its version into the write op
|
|
112
|
+
and the agent re-verifies it one syscall before the rename — a concurrent
|
|
113
|
+
writer wins, the stale write refuses with `FS_STALE_VERSION`.
|
|
114
|
+
- **Kernel enforcement** is stage two: a mutation runs on a confined resident —
|
|
115
|
+
one long-lived agent per (distro, policy), whose bwrap profile binds exactly
|
|
116
|
+
what the mode grants — so the kernel refuses what the check would have, and a
|
|
117
|
+
check bug cannot become a write outside the workspace. Escalated writes
|
|
118
|
+
(`danger-full-access`) ride the plain resident. A kernel refusal carries
|
|
119
|
+
`FS_SANDBOX_DENIED`, the same dialect as the command path.
|
|
120
|
+
- **New files** publish 0600 — the host backend's own POSIX publication
|
|
121
|
+
semantics, as on a Linux host.
|
|
122
|
+
- **Agent outage** fails closed: `FS_IO_ERROR` naming the substrate and the
|
|
123
|
+
recovery path. There is no fallback I/O; a distro without usable bwrap
|
|
124
|
+
refuses confined mutations with the bootstrap command, the same closed
|
|
125
|
+
failure the command path has.
|
|
126
|
+
|
|
127
|
+
Writes fence with the same host-side policy check (`checkedTarget`) the
|
|
128
|
+
command path uses, and the substrate does not change what `sandboxMode`
|
|
129
|
+
reports. The wire protocol the substrate speaks is documented in
|
|
117
130
|
[`agent/wsl-agent.sh`](../agent/wsl-agent.sh); the orchestration layers are
|
|
118
131
|
[`lib/fsio-agent.js`](../lib/fsio-agent.js) (fsio's mechanics over the FS
|
|
119
132
|
frames), [`lib/fs-substrate.js`](../lib/fs-substrate.js) (the provider-shaped
|
|
@@ -146,7 +159,8 @@ wsl.exe -d <distro> --cd <linux dir> --exec bwrap \
|
|
|
146
159
|
|
|
147
160
|
These are the same arguments DSH's Linux runner uses (`dsh-sandbox-local`), so the
|
|
148
161
|
behaviour and the error messages match a Linux host. The function
|
|
149
|
-
`bwrapProfileArgs` in [`lib/
|
|
162
|
+
`bwrapProfileArgs` in [`lib/bwrap.js`](../lib/bwrap.js) builds them — the
|
|
163
|
+
peer-free builder every confinement site composes from.
|
|
150
164
|
|
|
151
165
|
| Mode | What a command inside the distro can do |
|
|
152
166
|
|---|---|
|
|
@@ -208,7 +222,7 @@ finds it here.
|
|
|
208
222
|
| `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 |
|
|
209
223
|
| `FS_NOT_OBSERVED` | `WslFileSystem.assertGuard` | 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 |
|
|
210
224
|
| `FS_STALE_VERSION` | `WslFileSystem.assertGuard` | the version the caller holds no longer matches: the file changed, or is gone | read it again and retry with the fresh version |
|
|
211
|
-
| `FS_IO_ERROR` | `WslFileSystem.watch` |
|
|
225
|
+
| `FS_IO_ERROR` | `WslFileSystem.watch` | the watch target is not a path inside the pinned distro, so there is nothing the in-distro poll loop can observe | watch a distro path; the watch itself runs where the files live (`lib/watcher.js`) |
|
|
212
226
|
| `FS_NOT_FOUND` | the host filesystem stack | the path does not exist — the ordinary answer, passed through | — |
|
|
213
227
|
|
|
214
228
|
Two failures come from `wsl.exe` itself rather than from a code of ours, and both used
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -23,9 +23,9 @@ are schema defaults, listed because they are the ones worth knowing.
|
|
|
23
23
|
| | `restrictToDistro` | `true` *(shipped)* | refuse a path in **another distro**'s share. `/mnt/c` is a directory *inside* the pinned distro, so it is not affected. The refusal carries `FS_OUTSIDE_DISTRO`; that is not a sandbox decision, so the file tools do not offer a wider permission for it. Set `false` to allow another distro's share |
|
|
24
24
|
| | `sandbox` | `true` | check `writeText` and `editText` against the policy |
|
|
25
25
|
| | `maskWindowsDrive` | `false` | as on `wsl-shell`: confined fs mutations publish through a resident whose profile shadows `/mnt` (same honest limits) |
|
|
26
|
-
| | `substrate` | `agent` *(shipped)* | which I/O substrate serves the file tools
|
|
27
|
-
| | `resolveSymlinks` | `true` | *(share substrate only)* follow Linux symlinks that the share cannot traverse, such as `/etc/os-release` and `/bin` — the agent substrate follows them natively |
|
|
26
|
+
| | `substrate` | `agent` *(shipped)* | which I/O substrate serves the file tools. Only the resident in-distro agent (`lib/fs-substrate.js`) exists: reads, writes and identities run on ext4 — native symlinks, native mode bits, and the mutation guard survives to the write's atomic publication, kernel-enforced by the confined resident when the policy is confined. The former `share` opt-out (the Windows-side host stack over the 9p share) is refused at boot; delete the line |
|
|
28
27
|
| | `cwd` | `''` | base directory for relative paths; empty means the distro user's home |
|
|
28
|
+
| | `watchMaxDepth` | `0` | how deep the in-distro watch scan walks; `0` walks the whole tree. Bound it (e.g. `4`) when the watched tree holds a `node_modules` — a change below the bound is seen only when another change above it fires the same callback |
|
|
29
29
|
| `directory-picker-wsl` | `preferredDistro` | `''` *(shipped)* | the distro listed first in the picker |
|
|
30
30
|
| | `includeHostHome` | `true` *(shipped)* | also list the Windows home directory |
|
|
31
31
|
| | `maxEntries` | `1000` *(shipped)* | maximum entries listed per directory |
|
|
@@ -33,6 +33,7 @@ are schema defaults, listed because they are the ones worth knowing.
|
|
|
33
33
|
| | `shell` | `''` | pin a shell; empty lets `wsl.exe` decide |
|
|
34
34
|
| | `loginShell` | `true` | whether a pinned shell uses login semantics |
|
|
35
35
|
| | `hostSessions` | `true` | give a session opened on a Windows folder a host `powershell.exe` in its own directory instead of the distro shell at `/mnt/<drive>/…`; `false` restores the composition-owned distro terminal for every session |
|
|
36
|
+
| | `terminalIdleReclaim` | `true` | let the controller reclaim an idle distro terminal: the launch is marked with a per-terminal `DSH_TERMINAL_ID`, and the handle's `inspectActivity` counts the marked processes through the resident agent — one (the shell at its prompt) is idle, more than one (a running command or a nested shell) is busy, `unknown` (agent out) pauses reclamation. `false` restores close-by-hand |
|
|
36
37
|
|
|
37
38
|
You can override the other keys in the same way; they keep their defaults. Those are
|
|
38
39
|
`wslPath`, `hostCwd` and `forwardEnv` on the shell and terminal rows,
|