dsh-plugin-wsl-env 0.2.0 → 0.4.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 +294 -0
- package/README.md +2 -0
- package/agent/wsl-agent.sh +413 -27
- package/cordis.patch.yml +7 -0
- package/docs/ARCHITECTURE.md +37 -1
- package/docs/CONFIGURATION.md +4 -1
- package/docs/LIMITATIONS.md +67 -1
- package/docs/PARITY.md +5 -4
- package/docs/PEER-PARITY.md +60 -0
- package/docs/README.zh.md +2 -0
- package/docs/UPSTREAM-FSIO-EXPORT.md +87 -0
- package/lib/agent-confined.js +87 -0
- package/lib/agent-exec.js +22 -12
- package/lib/agent-protocol.js +65 -7
- package/lib/agent.js +279 -64
- package/lib/bwrap.js +91 -0
- package/lib/fs-decisions.js +132 -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 +271 -26
- package/lib/paths.js +14 -0
- package/lib/sandbox.js +32 -27
- package/lib/shell-env.js +6 -1
- package/package.json +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -12,8 +12,302 @@ reference below points at that record's numbering.
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
## [0.4.0] - 2026-10-03
|
|
16
|
+
|
|
17
|
+
One theme: the enterprise review's remediation cycle completes. The
|
|
18
|
+
security-decision layer gains its own peer-free module and unit tests, the
|
|
19
|
+
coverage gate measures every unit file again, the fallback invariant is probed
|
|
20
|
+
for real, and the file tools pick up the audit's fixes — a killed write no
|
|
21
|
+
longer strands state, large reads stream instead of staging, `/tmp` writes
|
|
22
|
+
land where readers read, cross-distro workdirs are refused, the resident's
|
|
23
|
+
Windows environment is pinned, and `maskWindowsDrive` offers the interop
|
|
24
|
+
mitigation.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **The filesystem provider's policy decisions are a peer-free module with
|
|
29
|
+
their own unit tests.** The security-decision layer — which paths are
|
|
30
|
+
refused for naming another distro, which mode denies a mutation outright,
|
|
31
|
+
which write guards refuse which intents, how a substrate failure maps onto
|
|
32
|
+
the surfaced refusal, and whether a mutation routes to the confined
|
|
33
|
+
resident — used to live inline in `lib/index.js`, a file no unit test can
|
|
34
|
+
import. It is now `lib/fs-decisions.js` (pure in, refusal descriptor out,
|
|
35
|
+
byte-identical messages), with `test/fs-decisions.test.mjs` covering every
|
|
36
|
+
branch, and `lib/index.js` delegating at one `FsError` choke point.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- **The fallback invariant is now probed, not just promised.** PARITY.md's
|
|
41
|
+
hard rule — "`agent: false` must behave like the pre-agent release" — cited
|
|
42
|
+
a probe leg that did not exist, and spelled the key as `wsl.agent: 'off'`
|
|
43
|
+
when it is a boolean. `test/probe/agent.sh` now runs the same operations
|
|
44
|
+
through the resident AND through the one-shot `wsl.exe` path and compares
|
|
45
|
+
outcomes: exit code and stdout bytes (including `$`-metacharacters), stderr
|
|
46
|
+
and working directory, and NUL/CRLF binary safety. All three legs pass
|
|
47
|
+
against a real distro; the doc now names the key correctly and describes
|
|
48
|
+
what the probe actually compares.
|
|
49
|
+
|
|
50
|
+
- **The coverage gate measures every unit file again.** `test:coverage` listed
|
|
51
|
+
six of the sixteen test files — the thresholds (85/85/70) were met by the
|
|
52
|
+
narrowed set while the full suite sat at 82.45% branches, so branch
|
|
53
|
+
regressions anywhere outside the six were invisible to the gate that claimed
|
|
54
|
+
to guard them. The gate now runs the whole unit suite (95.83% lines / 85.95%
|
|
55
|
+
branches / 91.01% functions), which got there the honest way: new tests for
|
|
56
|
+
the seams the audit found dark — the deny-dialect classification,
|
|
57
|
+
aborted-signal mapping, the transport env pin, handshake mismatch, protocol
|
|
58
|
+
garbage, cwd rejections, pre-aborted signals, idle retirement, mid-flight
|
|
59
|
+
over-cap buffers, failed rebuilds, watcher teardown and error paths, the
|
|
60
|
+
cross-distro workdir predicate, staging sweeps, setuid publication, and the
|
|
61
|
+
real-`/tmp` consistency the file tools now have.
|
|
62
|
+
|
|
63
|
+
- **Reading a large file no longer copies its remainder to a temp file for
|
|
64
|
+
every window.** `fs_read` staged `tail -c +offset` to disk before `head`
|
|
65
|
+
took the requested bytes — so a full read of a 1 GB file performed ~1000
|
|
66
|
+
window round trips AND ~500 GB of aggregate temp-file writes inside the
|
|
67
|
+
distro, and a window that took over 30 seconds died as an opaque
|
|
68
|
+
"distro I/O failure". The remainder now streams through a `tail | head`
|
|
69
|
+
pipe (the request's own bound is the only staging), read failures still
|
|
70
|
+
classify through the same stderr dialect, and windowed reads are verified
|
|
71
|
+
byte-exact including the final partial window.
|
|
72
|
+
- **A request the transport cannot deliver now fails its caller instead of
|
|
73
|
+
stranding it.** `writeLines` used to drop a frame silently when the
|
|
74
|
+
`wsl.exe` process was gone (a thin race between exit handling and the next
|
|
75
|
+
request), leaving a pending entry nothing would ever answer — the caller
|
|
76
|
+
waited forever when it had no timeout, and the watchdog had no process
|
|
77
|
+
left to kill. An undeliverable frame now rejects with
|
|
78
|
+
`AgentUnavailableError` ("the agent exited before the request could be
|
|
79
|
+
sent; it never ran"), the rebuild relay fails undeliverable re-sends the
|
|
80
|
+
same way, and `close()` no longer writes `SHUTDOWN` into an ended stdin
|
|
81
|
+
(whose async write-after-end error could crash the process during its own
|
|
82
|
+
shutdown).
|
|
83
|
+
|
|
84
|
+
- **A confined write to `/tmp` now lands in the distro's real `/tmp`, where
|
|
85
|
+
every reader reads.** The fence grants `/tmp` as a writable root, but the
|
|
86
|
+
confined resident's profile mounted an ephemeral tmpfs over it — so the
|
|
87
|
+
write reported success into a private tmpfs, the read tools (unconfined)
|
|
88
|
+
could not see it, the post-write stat produced a `missing:` version that
|
|
89
|
+
made the next edit fail with `FS_STALE_VERSION`, and the file evaporated
|
|
90
|
+
when the resident retired. The file-writes resident's profile now binds the
|
|
91
|
+
real `/tmp` read-write (`realTmp`, on by default for the confined factory):
|
|
92
|
+
the write lands where the fence said it would, reads and edits agree, and
|
|
93
|
+
user terminals see the file. Commands keep their ephemeral per-run tmpfs —
|
|
94
|
+
the fresh-temp-area property (and its anti-poisoning guarantee) is
|
|
95
|
+
unchanged, so a command still cannot see files the write tools placed in
|
|
96
|
+
`/tmp` — the documented command-vs-tools split, now safe in both
|
|
97
|
+
directions. Verified live: confined write → plain read returns the content;
|
|
98
|
+
a confined `cat` still reports it absent, as documented.
|
|
99
|
+
|
|
100
|
+
- **A killed write no longer leaves its staging directory in your files.** A
|
|
101
|
+
`KILL`/`SIGTERM` mid-write could not be trapped, so the private 0700
|
|
102
|
+
staging dir (`.<name>.<pid>.<rand>.tmpdir`) sat in the target's own
|
|
103
|
+
directory forever — and listings showed the debris to the model. The EXIT
|
|
104
|
+
trap now removes the in-flight staging along with the agent's temp dir, and
|
|
105
|
+
each write sweeps the target directory's leftovers whose creating agent's
|
|
106
|
+
PID (it rides the name) is no longer running — a live agent's staging is
|
|
107
|
+
left alone, PID reuse included.
|
|
108
|
+
- **A write to a typo'd path refuses instead of materializing the chain.**
|
|
109
|
+
The staging `mkdir -p` silently created every missing parent directory and
|
|
110
|
+
reported success, where the peer's Node-fs publication fails `ENOENT` —
|
|
111
|
+
masking model errors as wins. Parents are no longer created: the refusal is
|
|
112
|
+
`FS_NOT_FOUND`, an existing directory stays fine, and a concurrent creator
|
|
113
|
+
of the directory still wins.
|
|
114
|
+
- **Setuid, setgid and sticky bits survive an overwrite or edit.** The mode
|
|
115
|
+
was masked to `0o777` at the stat parse and again at the write, so a
|
|
116
|
+
`4755` binary became `755` and a `1777` directory file lost its sticky bit
|
|
117
|
+
— while LIMITATIONS claimed the bits were kept. The full mode now rides
|
|
118
|
+
stat and write end-to-end (`0o7777`), matching the share substrate's
|
|
119
|
+
`chmod --reference` behaviour.
|
|
120
|
+
|
|
121
|
+
- **A shell command whose workdir names another WSL distro is refused, where
|
|
122
|
+
it used to run in the pinned distro against whatever tree shares the path.**
|
|
123
|
+
`toLinuxPath` strips a `\\wsl.localhost\<name>\…` UNC's distro name, and the
|
|
124
|
+
shell executor never compared it — so a `debian` workspace's commands ran
|
|
125
|
+
inside the pinned `ubuntu`, silently, while the fs side refused the same
|
|
126
|
+
workspace with `FS_OUTSIDE_DISTRO` ("open a session in that distro" — which
|
|
127
|
+
the user had done). The shell now refuses with the fs side's wording, and
|
|
128
|
+
the shell-env facts stay silent for a foreign-distro session instead of
|
|
129
|
+
presenting another distro's home and shell as this session's.
|
|
130
|
+
|
|
131
|
+
- **The resident agent no longer inherits the Windows environment's WSLENV
|
|
132
|
+
forwarding.** Spawned bare, `wsl.exe` carried this process's whole
|
|
133
|
+
environment with it, and whatever the user's `WSLENV` names — dev machines
|
|
134
|
+
routinely route secrets (`GITHUB_TOKEN/u` is the documented example) — was
|
|
135
|
+
forwarded into the distro, where every model command inherited it. The
|
|
136
|
+
transport's Windows env is now pinned: the essentials `wsl.exe` itself
|
|
137
|
+
needs, the managed `DSH_*` facts, `WSL_UTF8=1`, and a `WSLENV` that names
|
|
138
|
+
only the managed entries with their original flags. Verified live: a secret
|
|
139
|
+
injected on the Windows side with a forwarding flag appears zero times in
|
|
140
|
+
the distro environment. `forwardEnv` remains one-shot-only today — the
|
|
141
|
+
resident path never forwarded it, and the pin keeps that posture instead of
|
|
142
|
+
widening it silently.
|
|
143
|
+
|
|
144
|
+
### Added
|
|
145
|
+
|
|
146
|
+
- **`maskWindowsDrive` on `wsl-shell` and `wsl-fs`** — the mitigation knob for
|
|
147
|
+
the interop hole the sandbox has always documented. The confined profile
|
|
148
|
+
mounts an empty tmpfs over `/mnt`, so the Windows drive's files cannot be
|
|
149
|
+
read or exfiltrated by a "sandboxed" command and its executables cannot be
|
|
150
|
+
launched through interop (the path by which such a command reaches your full
|
|
151
|
+
Windows token). Honest limits, stated wherever the key is: binfmt interop
|
|
152
|
+
dispatches on file content, so a command that can write the workspace can
|
|
153
|
+
still write an executable there and run it — enforcement stays `partial`,
|
|
154
|
+
the result's sandbox fact now carries `windowsDrive: "masked" | "visible"`
|
|
155
|
+
so the model can see which posture it runs under, and the only complete
|
|
156
|
+
closure is distro-level (`[interop] enabled=false` in `wsl.conf`). Default
|
|
157
|
+
`false`: the drive stays visible unless you ask for the mask.
|
|
158
|
+
|
|
159
|
+
## [0.3.0] - 2026-10-03
|
|
160
|
+
|
|
161
|
+
One theme: the file tools move into the distro. The `substrate: "agent"` I/O
|
|
162
|
+
substrate and its kernel-enforced confined writes land together with the
|
|
163
|
+
enterprise review that hardened them — the watchdog that killed the shared
|
|
164
|
+
agent for a queue it never chose to join, the output captures nothing bounded,
|
|
165
|
+
a root listing that sheared the first character off every child's name, and an
|
|
166
|
+
overwrite guard that stopped at the host.
|
|
167
|
+
|
|
168
|
+
### Fixed
|
|
169
|
+
|
|
170
|
+
- **One file carries one version string, whichever op answered.** `stat` and
|
|
171
|
+
`list` built their versions from different producers: the stat op used the
|
|
172
|
+
distro's `stat` directives (uutils `stat` rounds timestamps to microseconds)
|
|
173
|
+
while the list walk used `find -printf` (nanosecond-exact), so the same file
|
|
174
|
+
yielded two different version strings and a list-derived guard failed
|
|
175
|
+
against its own stat. `fs_stat` now reads through the same `find -printf`
|
|
176
|
+
the list walk uses — one producer, one string — and `find -L`'s habit of
|
|
177
|
+
reporting a dangling link as the link itself is refused the way the `stat`
|
|
178
|
+
it replaced was, so a dangling link still stats to null like the peer's.
|
|
179
|
+
|
|
180
|
+
- **An overwrite or edit now re-verifies its version distro-side, one syscall
|
|
181
|
+
before the rename.** The module header claimed the mutation guard "survives
|
|
182
|
+
to the write", but the no-replace link guarded only `createIfAbsent`: an
|
|
183
|
+
overwrite or edit checked the version host-side and then published with an
|
|
184
|
+
unconditional `mv -f`, so a concurrent writer (an editor save, `git stash`,
|
|
185
|
+
another session) that landed between the model's stat and the publish was
|
|
186
|
+
silently clobbered with no `FS_STALE_VERSION`. The `write` op now carries
|
|
187
|
+
the version the write was based on (a `replaceIfVersion` guard's version,
|
|
188
|
+
or the stat an edit was read against), and the agent re-verifies it with a
|
|
189
|
+
fresh stat from the SAME producer `fs_stat` uses — refusing with
|
|
190
|
+
`FS_STALE_VERSION` ("file changed since it was read", or "file no longer
|
|
191
|
+
exists") while the concurrent writer's content stays untouched and no
|
|
192
|
+
staging debris is left behind. The window narrows from a host round trip to
|
|
193
|
+
kernel-adjacent — the honest wording, here and in the module headers,
|
|
194
|
+
LIMITATIONS.md and ARCHITECTURE.md, is that only the create case is closed
|
|
195
|
+
outright.
|
|
196
|
+
|
|
197
|
+
- **The agent command path now enforces the output budget it always
|
|
198
|
+
advertised.** `maxOutputBytes` rode the config schema but nothing else: the
|
|
199
|
+
agent captured a command's whole stdout and stderr and base64ed both into
|
|
200
|
+
one RES line — multi-gigabyte output meant multi-gigabyte dash command
|
|
201
|
+
substitutions in the distro and a multi-gigabyte host line, with the result
|
|
202
|
+
claiming `truncated: false` all the way. The EXEC frame now carries the
|
|
203
|
+
caller's per-stream budget (`wsl-shell`'s `maxOutputBytes`, wired from the
|
|
204
|
+
agent execution path); the agent cuts each stream at it — only the first
|
|
205
|
+
bytes are encoded, so distro AND host memory are bounded — and the RES line
|
|
206
|
+
reports which streams were cut, which the handle surfaces as honest
|
|
207
|
+
per-stream `truncated` flags (a guarded-create synthesis and frames without
|
|
208
|
+
a budget still read `false`). The host also defends itself against a broken
|
|
209
|
+
or hostile agent: the unbounded readline accumulator is replaced with a
|
|
210
|
+
bounded line splitter that kills the transport when one stdout line exceeds
|
|
211
|
+
the protocol cap instead of buffering it whole.
|
|
212
|
+
|
|
213
|
+
- **A short-budget request queued behind a long one no longer kills the shared
|
|
214
|
+
agent — and an in-flight command is never silently re-run.** Two defects
|
|
215
|
+
shared one root: the host armed each request's watchdog at SUBMISSION and
|
|
216
|
+
let it kill the one `wsl.exe` carrying every pending request, and the
|
|
217
|
+
rebuild relay then replayed all carried frames verbatim. In the default
|
|
218
|
+
configuration the 10 s `DSH_WSL_PORTS` poller (8 s budget, shared agent) did
|
|
219
|
+
exactly this against any command longer than ~21 s: the poll's watchdog
|
|
220
|
+
killed the transport, the command died mid-flight, and the rebuild
|
|
221
|
+
**re-executed it from scratch** — both callers seeing success (measured: a
|
|
222
|
+
killed `sleep 40` ran twice). Three changes close it: the agent answers
|
|
223
|
+
`ACK|<id>` when it dequeues a request and the host arms the watchdog there,
|
|
224
|
+
so queue time never spends a budget (wire protocol v3 — sync the runtime
|
|
225
|
+
copy and restart the app, an old agent fails the handshake loudly instead of
|
|
226
|
+
misbehaving quietly); the rebuild relays only provably read-only FS frames
|
|
227
|
+
(`stat`/`lstat`/`list`/`realpath`/`read`), failing an in-flight EXEC or
|
|
228
|
+
`write` with "its state is unknown — inspect the distro before retrying"
|
|
229
|
+
instead of re-running it; and an abort now addresses the request's CURRENT
|
|
230
|
+
id, so a relayed request's KILL (and map cleanup) can no longer miss. The
|
|
231
|
+
watchdog-vs-queue experiment now ends with the long command executed exactly
|
|
232
|
+
once and the transport alive.
|
|
233
|
+
- **A root listing kept every child's name whole.** `listChildren` sliced each
|
|
234
|
+
`find` record at `parent.length + 1`, which against the bare `/` parent
|
|
235
|
+
sheared the first character off every name (`/etc` → `tc`), and a write to a
|
|
236
|
+
listed child then created `/tc`. The exact prefix is stripped instead, and
|
|
237
|
+
the child join owes its own slash, so `listDir` on
|
|
238
|
+
`\\wsl.localhost\<distro>\` names `/etc`, `/home`, `/usr` correctly.
|
|
239
|
+
|
|
240
|
+
### Added
|
|
241
|
+
|
|
242
|
+
- **`substrate: "agent"` — the filesystem's in-distro I/O substrate.** `wsl-fs`
|
|
243
|
+
gains a `substrate` key, default `agent`: the file tools are served through
|
|
244
|
+
the resident in-distro agent instead of the 9p share — identities are
|
|
245
|
+
distro-side `realpath`s, symlinks resolve where they live (no retry
|
|
246
|
+
machinery, `/etc/os-release` just works), mode bits are native, and a
|
|
247
|
+
mutation guard survives to the write's no-replace publication — the
|
|
248
|
+
check-then-write window the share documents closes, exactly as on a Linux
|
|
249
|
+
host. `substrate: "share"` is the opt-out that keeps the previous behaviour
|
|
250
|
+
byte for byte. Both substrates fence writes with the same host-side policy
|
|
251
|
+
check, so this changes fidelity and posture, not enforcement. Known
|
|
252
|
+
differences are in LIMITATIONS.md (notably: new files publish at 0600, the
|
|
253
|
+
peer's own POSIX semantics, where the share took the umask 0644), and an
|
|
254
|
+
agent outage fails with `FS_IO_ERROR` naming the substrate rather than
|
|
255
|
+
degrading to share semantics. Layers: FS operation frames on the agent wire
|
|
256
|
+
protocol (v3, `agent/wsl-agent.sh`), fsio-over-agent mechanics
|
|
257
|
+
(`lib/fsio-agent.js`), provider-shaped orchestration (`lib/fs-substrate.js`),
|
|
258
|
+
and the provider branch (`lib/index.js`). The pure text mechanics are
|
|
259
|
+
replicated from upstream pending the fsio export proposed in
|
|
260
|
+
docs/UPSTREAM-FSIO-EXPORT.md. Probed live: `npm run probe:substrate`.
|
|
261
|
+
- **Stage two: kernel-enforced confined writes.** On the agent substrate, a
|
|
262
|
+
mutation under a confined policy rides its own long-lived resident — one
|
|
263
|
+
agent per (distro, mode, workspace), spawned inside the bwrap profile that
|
|
264
|
+
binds exactly what the mode grants (`lib/agent-confined.js`, `argvPrefix` on
|
|
265
|
+
the agent wire). The kernel now refuses what the host-side check would have,
|
|
266
|
+
so a check bug cannot become a write outside the workspace; a mount-table
|
|
267
|
+
refusal surfaces as `FS_SANDBOX_DENIED`, the command path's dialect, and a
|
|
268
|
+
distro without usable bwrap refuses confined writes with the bootstrap
|
|
269
|
+
command instead of downgrading. Escalated (`danger-full-access`) writes ride
|
|
270
|
+
the plain resident, and a policy change simply addresses a different key —
|
|
271
|
+
the previous resident retires through its idle timer. Probed live: the
|
|
272
|
+
confined write inside the workspace lands, and the one outside it is refused
|
|
273
|
+
by the kernel (`probe:substrate`).
|
|
274
|
+
|
|
15
275
|
### Fixed
|
|
16
276
|
|
|
277
|
+
- **Every mutation on the agent substrate failed: the confined resident's command
|
|
278
|
+
had no program** — `confinedAgent()` built the resident's `argvPrefix` from the
|
|
279
|
+
raw profile arguments, so the transport handed `wsl.exe` a command beginning at
|
|
280
|
+
`--ro-bind` instead of `bwrap --ro-bind`. The resident exited 1 during its
|
|
281
|
+
handshake, and every write, edit and guarded create on `substrate: "agent"`
|
|
282
|
+
failed with `FS_IO_ERROR: the distro file substrate is unavailable: agent exited
|
|
283
|
+
during handshake (code=1 signal=none)` while reads kept working. Both confinement
|
|
284
|
+
sites now assemble the command through the import-free `lib/bwrap.js`, which is
|
|
285
|
+
what `test/agent.test.mjs` asserts; the same bug stayed invisible because
|
|
286
|
+
`test/probe/substrate.sh` inlined a *third*, correct copy of those arguments —
|
|
287
|
+
it now imports the production builder, so the probe fails with the provider.
|
|
288
|
+
- **`WslFileSystem` could not build its agent substrate at all: a constructor
|
|
289
|
+
field shadowed the method of the same name** — the constructor's
|
|
290
|
+
`this.agentSubstrate = undefined` (the cache) overwrote the prototype's
|
|
291
|
+
`async agentSubstrate()` (the accessor), so the first file operation that needed
|
|
292
|
+
the agent threw `this.agentSubstrate is not a function`, which is how a GUI
|
|
293
|
+
session failed its turn in the Desktop app. The cache is now
|
|
294
|
+
`this.agentSubstrateInstance`, named after the sibling `this.agentInstance`, and
|
|
295
|
+
`lib/index.js`'s instance fields no longer collide with any class member.
|
|
296
|
+
- **A schemastery member the pinned peer does not publish took the `wsl` preset
|
|
297
|
+
down in the Desktop app** — `WslFileSystem.Config` declared
|
|
298
|
+
`substrate: z.enum(["share", "agent"])`, and `@deepseek-ai/schemastery` 3.18.4
|
|
299
|
+
(what every `@deepseek-ai/dsh-*` peer pins as `~3.18.4`) publishes no `enum`. A
|
|
300
|
+
Config schema is a class field, so the call ran while `lib/index.js` was still
|
|
301
|
+
being imported: the loader logs the `TypeError` and leaves that row with no
|
|
302
|
+
fiber, which the preset audit renders as `never started` — naming neither the
|
|
303
|
+
member nor the file, and only for `wsl-shell` and `wsl-fs`, the two rows that
|
|
304
|
+
load `lib/index.js`. The preset therefore failed to activate, so the app
|
|
305
|
+
reported `2 row(s) did not activate` for those two ids. Now
|
|
306
|
+
`z.union(["share", "agent"])`: same `agent` default, and a bad value still
|
|
307
|
+
fails with `expected "share" | "agent" but got "bogus"`. Measured against the
|
|
308
|
+
app's own runtime, both entries import; `test/syntax.mjs` now checks every
|
|
309
|
+
`z.<member>` in `lib/` against the pinned peer's surface, which fails on
|
|
310
|
+
`z.enum` and passes on `z.union`.
|
|
17
311
|
- **`truncated` missing from the agent path's output streams** — the bash tool's
|
|
18
312
|
canonical result copies the field unconditionally (`canonicalBashResult`), so an
|
|
19
313
|
`undefined` failed the wire's lossless-JSON snapshot and EVERY shell tool call
|
package/README.md
CHANGED
|
@@ -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
|
|
|
@@ -118,6 +119,7 @@ npm run probe:sandbox # measure inside the distro what bubblewrap does an
|
|
|
118
119
|
npm run probe # filesystem probe against a real distro (Windows + WSL only)
|
|
119
120
|
npm run probe:sandbox-shell # boot a real harness and drive the confined executor
|
|
120
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)
|
|
121
123
|
npm run probe:missing-wsl # boot a profile whose wslPath cannot start (Windows + WSL only)
|
|
122
124
|
npm run probe:picker # list the picker's root level, refusals and its cap (Windows + WSL only)
|
|
123
125
|
npm run probe:mode # which POSIX-mode facts survive the share (Windows node only)
|