dsh-plugin-wsl-env 0.12.0 → 0.13.1
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 +195 -0
- package/CONTRIBUTING.md +28 -6
- package/README.md +13 -6
- package/SECURITY.md +11 -1
- package/SUPPORT.md +29 -2
- package/cordis.patch.yml +9 -2
- package/docs/ARCHITECTURE.md +7 -5
- package/docs/CONFIGURATION.md +75 -16
- package/docs/LIMITATIONS.md +47 -7
- package/docs/MIGRATION.md +98 -0
- package/docs/PARITY.md +1 -1
- package/docs/PERFORMANCE.md +60 -0
- package/docs/QUICKSTART.md +125 -0
- package/docs/README.zh.md +6 -4
- package/docs/ROADMAP-1.0.md +780 -0
- package/docs/ROADMAP.md +4 -0
- package/docs/UPSTREAM-FILE-REFERENCE-SEAM.md +80 -0
- package/docs/UPSTREAM-FSIO-EXPORT.md +3 -2
- package/docs/UPSTREAM-LSP-SEAM.md +90 -0
- package/docs/UPSTREAM-SESSION-NOTICE.md +95 -0
- package/lib/agent-errors.d.ts +19 -0
- package/lib/agent-errors.js +22 -0
- package/lib/agent-exec.d.ts +9 -2
- package/lib/agent-exec.js +4 -4
- package/lib/agent.d.ts +68 -5
- package/lib/agent.js +129 -22
- package/lib/distro-route.d.ts +75 -0
- package/lib/distro-route.js +86 -0
- package/lib/fs-routing.d.ts +4 -1
- package/lib/index.d.ts +166 -37
- package/lib/index.js +358 -74
- package/lib/preflight.d.ts +46 -3
- package/lib/preflight.js +90 -15
- package/lib/shell-env.d.ts +9 -3
- package/lib/shell-env.js +128 -43
- package/lib/subprocess.d.ts +12 -3
- package/lib/subprocess.js +21 -3
- package/package.json +23 -6
- package/scripts/diagnose.sh +9 -88
- package/scripts/doctor.mjs +548 -0
- package/scripts/profile-preset.mjs +254 -0
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,201 @@ were part of the record.
|
|
|
16
16
|
|
|
17
17
|
## [Unreleased]
|
|
18
18
|
|
|
19
|
+
## [0.13.1] - 2026-10-19
|
|
20
|
+
|
|
21
|
+
A patch release restoring `wsl-shell-env`: the component mounts again, and the
|
|
22
|
+
managed `DSH_*` facts it contributes now reach commands on the default
|
|
23
|
+
resident-agent path.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **Upstream proposals filed.** The session-notice seam
|
|
28
|
+
([#10263](https://github.com/deepseek-ai/deepseek-harness/discussions/10263)),
|
|
29
|
+
the `spawnSpec` seam (#10261), the fsio export (#10262), the UNC-workspace bug
|
|
30
|
+
(#10260) and the WSL-workspaces RFC (#10264) now point at their discussions;
|
|
31
|
+
the local drafts carry the links.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **`wsl-shell-env` no longer fails to activate.** The row sweeps the
|
|
36
|
+
live-agent list at mount to warm facts for sessions that opened before it,
|
|
37
|
+
but declared only `shellEnv` in `inject` and read `ctx.agents` as a property.
|
|
38
|
+
Cordis 4 refuses that read — `cannot get property "agents" without inject` —
|
|
39
|
+
so `apply()` rejected and the plugin manager listed the component as 异常
|
|
40
|
+
(`包含的组件 → 异常`), with the whole contribution missing from shell calls.
|
|
41
|
+
The sweep now reads the service through `ctx.get("agents")`, the sanctioned
|
|
42
|
+
access for a service this row does not inject: activation no longer depends
|
|
43
|
+
on an agent loop, and the sweep still runs when one is composed.
|
|
44
|
+
`wsl-preflight` carried the same property read inside a `try`/`catch`, where
|
|
45
|
+
the throw silently disabled its mount sweep; it gets the same fix.
|
|
46
|
+
`test/shell-env.test.mjs` and the real-cordis check in
|
|
47
|
+
`test/preflight.test.mjs` mount both rows against the guard.
|
|
48
|
+
|
|
49
|
+
- **The managed `DSH_*` namespace now reaches commands on the default
|
|
50
|
+
resident-agent path.** `ShellExecSpec.dshEnv` was forwarded only by the
|
|
51
|
+
one-shot `wsl.exe` fallback; `executeViaAgent` dropped it before
|
|
52
|
+
`agent.exec` — after the mount fix above, the row registered its facts but
|
|
53
|
+
no model shell call could see `DSH_HOME`, `DSH_SESSION_ID` or any
|
|
54
|
+
`DSH_WSL_*` value (`env | grep '^DSH_'` was empty). The handle now carries
|
|
55
|
+
the merged map (`env` first, `dshEnv` last — the same order the one-shot
|
|
56
|
+
path uses) into the agent's `SETENV` frames. `test/agent-exec.test.mjs` and
|
|
57
|
+
`test/provider.test.mjs` pin the frame, and both checks fail against the
|
|
58
|
+
previous build.
|
|
59
|
+
|
|
60
|
+
## [0.13.0] - 2026-10-19
|
|
61
|
+
|
|
62
|
+
This release ships the P0/P1 hardening batch: session-scoped `auto`
|
|
63
|
+
sandboxing, resident-agent recovery with doctor coverage, the packed-install
|
|
64
|
+
E2E, measured GUI-preview and `@`-reference probes, and the docs/community
|
|
65
|
+
entry points.
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
|
|
69
|
+
- **`pnpm run preset` — the `preset-wsl` override helper.** `wsl-shell` and
|
|
70
|
+
`wsl-fs` sit inside `preset-wsl`'s `config.plugins`, so a patch that targets
|
|
71
|
+
them by id is ignored with only a loader warning. The helper prints the
|
|
72
|
+
complete `preset-wsl` block, applies `distro`/`sessionSandbox`/`sandbox`/
|
|
73
|
+
`maskWindowsDrive`/`agentBackend`, and `--check` fails loudly with the exact
|
|
74
|
+
block to paste when a layer carries the nested id patch. See
|
|
75
|
+
[docs/CONFIGURATION.md](docs/CONFIGURATION.md#overriding-the-rows-inside-preset-wsl).
|
|
76
|
+
|
|
77
|
+
- **Session-notice upstream payload ready.** The exact title, body and `gh`
|
|
78
|
+
command for the session-visible notice proposal are in
|
|
79
|
+
`docs/upstream/proposal-session-notice.md`; it remains unfiled (a maintainer
|
|
80
|
+
action).
|
|
81
|
+
|
|
82
|
+
- **Configuration stability and migration policy.** `docs/MIGRATION.md` records
|
|
83
|
+
the below-1.0 freeze (new keys preserve behavior, removals refuse at boot with
|
|
84
|
+
a migration, meaning changes get a note), the deprecation path with the
|
|
85
|
+
`substrate: "share"` and `distro: ''` cases, and the release-gate table with
|
|
86
|
+
the manual legs (probe suite, clean machine, aarch64) marked unrun.
|
|
87
|
+
RELEASING and SUPPORT link it.
|
|
88
|
+
|
|
89
|
+
- **Upstream promotion pack documented.** `docs/upstream/promotion-pack.md`
|
|
90
|
+
records the `packages/wsl/*` layout (ten capability packages plus an
|
|
91
|
+
integration package), the seam/RFC index, what the composition boot test
|
|
92
|
+
covers and its gaps, and the install proof. Promotion stays packaging and
|
|
93
|
+
proof, not a dependency.
|
|
94
|
+
|
|
95
|
+
- **`@`-reference traversal cost measured; seam deferred.** The Windows walk
|
|
96
|
+
over the WSL share is ~56× the ext4 walk on a 520-entry tree (84.7 ms vs
|
|
97
|
+
1.5 ms) and is now reproducible with `pnpm run probe:file-reference`.
|
|
98
|
+
`docs/UPSTREAM-FILE-REFERENCE-SEAM.md` records the three-function seam, the
|
|
99
|
+
ready (unwired) plugin consumer, and the decision to keep the 9p walk until
|
|
100
|
+
upstream exposes the funnel.
|
|
101
|
+
|
|
102
|
+
- **Community loop: issue forms, triage targets and a contributor path.**
|
|
103
|
+
Feature-request and setup-report forms (the setup form requires
|
|
104
|
+
`doctor --json`), a chooser that disables blank issues, SUPPORT's P1
|
|
105
|
+
three-business-day target and two-to-four-week cadence, and a
|
|
106
|
+
`good first issue` path in CONTRIBUTING.
|
|
107
|
+
|
|
108
|
+
- **Quickstart and performance entry pages.** `docs/QUICKSTART.md` is the
|
|
109
|
+
five-minute path (prerequisites, install, first session, Node/Python/Rust/Go
|
|
110
|
+
and monorepo recipes, honest Tier 1/Tier 2 table); `docs/PERFORMANCE.md`
|
|
111
|
+
records the 9p-vs-agent numbers and the GUI-preview baseline with reproduction
|
|
112
|
+
commands. The visual demo is explicitly still missing.
|
|
113
|
+
|
|
114
|
+
- **GUI-preview latency has a measured baseline.** `pnpm run probe:gui-preview`
|
|
115
|
+
drives the routed backend's `stat`, `listDir` and 1 MiB `readByteRange` with
|
|
116
|
+
p50/p95 budgets (stat 100 ms, listing 500 ms, page 1 s/MiB). On this machine:
|
|
117
|
+
p95 2.7 ms / 5.1 ms / 63.5 ms per MiB. No optimization follows; the
|
|
118
|
+
`workspace-files` row is untouched.
|
|
119
|
+
|
|
120
|
+
- **LSP seam researched and deferred.** `docs/UPSTREAM-LSP-SEAM.md` records that
|
|
121
|
+
the harness ships `dsh-lsp`/`dsh-lsp-stdio`/`dsh-tool-lsp` but the 0.2.1-rc.2
|
|
122
|
+
base composition mounts none of them, and that a WSL provider needs a
|
|
123
|
+
preset-scoped subprocess provider plus URI translation. The prototype is
|
|
124
|
+
deferred behind an explicit trigger; no LSP claim is made in the README.
|
|
125
|
+
|
|
126
|
+
- **Peer-upgrade canary.** A weekly workflow resolves the `next` dist-tag of
|
|
127
|
+
every pinned peer, tests that line in a scratch copy (`pnpm test` + the
|
|
128
|
+
composition boot test), and publishes the exact version delta in the run
|
|
129
|
+
summary; a moved line that fails fails the run. `pnpm run canary:peers`
|
|
130
|
+
performs the check locally.
|
|
131
|
+
|
|
132
|
+
- **The strict profile is documented and probed.** `maskWindowsDrive: true` with
|
|
133
|
+
`sandbox: true` hides `/mnt` from confined profiles; CONFIGURATION describes
|
|
134
|
+
the trade-offs and the visible/masked/disabled interop matrix, and
|
|
135
|
+
`probe:sandbox` now asserts the masked leg (empty `/mnt`, unreachable Windows
|
|
136
|
+
binary, writable workspace). Enforcement stays `partial`; the binfmt content
|
|
137
|
+
escape and the distro-level closure are stated.
|
|
138
|
+
|
|
139
|
+
- **Install/upgrade/uninstall E2E.** `pnpm run e2e:install` packs the tree,
|
|
140
|
+
installs it into a fresh `%TEMP%` `DSH_HOME`, composes `--dump-config`, removes
|
|
141
|
+
it, and upgrades from the previous minor with a marker proving the profile
|
|
142
|
+
layer survived. `test/packaging.test.mjs` keeps the packed file list honest in
|
|
143
|
+
`pnpm test`.
|
|
144
|
+
|
|
145
|
+
- **The doctor probes the resident agent.** `agent.handshake` starts the
|
|
146
|
+
shipped shell agent in the distro, reads its HELLO (pid, protocol, script
|
|
147
|
+
digest) and stops it, so a missing or stale agent script is a named check
|
|
148
|
+
instead of a first-command failure. It also reports the agent cache and the
|
|
149
|
+
live/stale `dsh-wsl-*.scope` counts.
|
|
150
|
+
|
|
151
|
+
- **`pnpm run doctor` — one command for the environment prerequisites.** Reports
|
|
152
|
+
Windows/WSL versions, the distro and its login shell, bubblewrap usability,
|
|
153
|
+
systemd / the user manager / `systemd-run` / Linger, the optional tools, peer
|
|
154
|
+
versions, the native-agent cache, stale `dsh-wsl-*.scope` units and the
|
|
155
|
+
profile-patch trap that silently ignores `wsl-shell`/`wsl-fs` overrides. Each
|
|
156
|
+
failure carries one copy-paste remedy; `--json` is the machine shape, and
|
|
157
|
+
`--fix` only sweeps stale scopes unless `--install` plus a confirmation allows
|
|
158
|
+
a package install. `diagnose` is now the read-only alias.
|
|
159
|
+
|
|
160
|
+
### Changed
|
|
161
|
+
|
|
162
|
+
- **`sessionSandbox: auto` — the session runtime is an optimization, not a
|
|
163
|
+
prerequisite.** `wsl-shell` and `wsl-fs` accept `true`, `false` or `"auto"`;
|
|
164
|
+
`auto` uses the systemd user scope when it is available and otherwise falls
|
|
165
|
+
back to the per-command `bwrap` profile — still confined, never unconfined —
|
|
166
|
+
reporting `sessionScope: "session" | "per-command"` in the command result.
|
|
167
|
+
A failed verdict is re-probed at most once a minute, so installing systemd or
|
|
168
|
+
enabling Linger upgrades without an app restart. `true` keeps refusing with
|
|
169
|
+
the existing remedy; `false` is unchanged.
|
|
170
|
+
|
|
171
|
+
- **The session preflight also checks the session-runtime prerequisites.** It
|
|
172
|
+
now probes systemd as PID 1, the user manager, `systemd-run` and Linger —
|
|
173
|
+
not only `bubblewrap` — when a distro session opens, and publishes the same
|
|
174
|
+
advisory to the session's runtime context as well as the log. `sessionSandbox:
|
|
175
|
+
false` on the `wsl-preflight` row skips the session half. DSH 0.2.1 has no
|
|
176
|
+
session-notice seam; the ask is recorded in
|
|
177
|
+
[docs/UPSTREAM-SESSION-NOTICE.md](docs/UPSTREAM-SESSION-NOTICE.md) and the
|
|
178
|
+
boundary in [docs/LIMITATIONS.md](docs/LIMITATIONS.md#session-start-advisories).
|
|
179
|
+
Advisory only: nothing is installed and fail-closed behavior is unchanged.
|
|
180
|
+
|
|
181
|
+
- **The distro follows the workspace.** `distro: ''`/`'auto'` now selects the
|
|
182
|
+
distro from the request workdir or target UNC; `'default'` forces WSL's
|
|
183
|
+
default, and an explicit name still pins. Shell, files, root-plane routing,
|
|
184
|
+
the GUI terminal and `shell-env` all use the same rule, and the shared agent,
|
|
185
|
+
substrate, home and session-runtime state are cached per distro. A pinned
|
|
186
|
+
profile still refuses a foreign UNC with `FS_OUTSIDE_DISTRO`. This changes the
|
|
187
|
+
previous meaning of `distro: ''` (WSL's default): set `'default'` to keep that
|
|
188
|
+
posture.
|
|
189
|
+
|
|
190
|
+
### Fixed
|
|
191
|
+
|
|
192
|
+
- **A dead resident revives with bounded backoff instead of staying dead for
|
|
193
|
+
the process lifetime.** A failure that spends the one permitted rebuild now
|
|
194
|
+
leaves the agent `dead` with a revival scheduled (2 s, 4 s, 8 s); the next
|
|
195
|
+
call after the delay gets a fresh transport and its own single rebuild, and
|
|
196
|
+
nothing from the dead generation is replayed. After three deaths the state is
|
|
197
|
+
permanent and the refusal names the restart remedy. `status()` exposes the
|
|
198
|
+
live state, pid, last error and pending count for diagnostics.
|
|
199
|
+
|
|
200
|
+
- **A cancelled command's signal can no longer kill the next one.** The
|
|
201
|
+
out-of-band USR1 carries no request id: a signal meant for a timed-out
|
|
202
|
+
command could land after the agent had moved on, and the next command then
|
|
203
|
+
died with exit 138 but reported `completed`. A host timeout no longer sends
|
|
204
|
+
the signal at all (the in-distro watcher is the one that kills that
|
|
205
|
+
command), and an explicit cancel now waits briefly and is dropped once the
|
|
206
|
+
request answered or a newer request is running.
|
|
207
|
+
|
|
208
|
+
- **A missing workdir is reported with its path on the native agent.** The
|
|
209
|
+
native agent reports the failed spawn as reason `spawn`, without naming the
|
|
210
|
+
directory; the executor now treats that as its own error class and retries
|
|
211
|
+
the command on the one-shot path, whose `chdir(<path>) failed` warning names
|
|
212
|
+
the directory.
|
|
213
|
+
|
|
19
214
|
## [0.12.0] - 2026-10-19
|
|
20
215
|
|
|
21
216
|
The GUI file service keeps the shipped `workspace-files` row: its server-only
|
package/CONTRIBUTING.md
CHANGED
|
@@ -172,11 +172,14 @@ terminal if you would rather not see the prompt.
|
|
|
172
172
|
|
|
173
173
|
| Command | Covers | Needs a harness |
|
|
174
174
|
|---|---|---|
|
|
175
|
-
| `pnpm test` | style checks, syntax pass, build consistency, type check, and unit tests | no |
|
|
175
|
+
| `pnpm test` | style checks, relative links, syntax pass, build consistency, type check, and unit tests | no |
|
|
176
|
+
| `pnpm run lint:links` | relative Markdown links in README, CONTRIBUTING, AGENTS and `docs/**` | no |
|
|
176
177
|
| `pnpm run test:coverage` | unit tests with coverage | no |
|
|
177
178
|
| `pnpm run lint:style` | style and lint rules | no |
|
|
178
179
|
| `pnpm run lint:build` | the build-freshness check: every source has its artifacts and the committed bytes match a real build | no |
|
|
179
180
|
| `pnpm run lint:types` | the TypeScript check over `test/**` and the ambient types (`tsc --noEmit`) | no |
|
|
181
|
+
| `pnpm run e2e:install` | packed-tarball install/upgrade/uninstall into a temporary `DSH_HOME` | yes; Windows + WSL |
|
|
182
|
+
| `pnpm run doctor` | the environment prerequisites, peers and caches (`--json` for scripts; `diagnose` is the read-only alias) | no; inside the distro |
|
|
180
183
|
| `pnpm run probe` | filesystem behaviour against a real distro | yes |
|
|
181
184
|
| `pnpm run probe:sandbox` | what bubblewrap confines and what it does not | no |
|
|
182
185
|
| `pnpm run probe:sandbox-shell` | the confined executor through a real boot | yes |
|
|
@@ -187,11 +190,14 @@ terminal if you would rather not see the prompt.
|
|
|
187
190
|
| `pnpm run probe:exec` | the agent-backed execution handle: timeout, kill, cwd failure | no; inside the distro |
|
|
188
191
|
| `pnpm run probe:missing-wsl` | how a `wslPath` that cannot start is reported | yes |
|
|
189
192
|
| `pnpm run probe:picker` | the picker's root level, its refusals and its cap | yes |
|
|
193
|
+
| `pnpm run probe:gui-preview` | the routed backend's stat/listing/1 MiB page-read latency (p50/p95 budgets) | no; inside the distro |
|
|
194
|
+
| `pnpm run probe:file-reference` | the `@`-reference traversal: 9p walk vs the distro's ext4 | no; inside the distro |
|
|
195
|
+
| `pnpm run probe:fs-routing` | the root-plane router: distro coordinate, host plane, cross-distro fence | yes |
|
|
190
196
|
| `pnpm run probe:mode` | which POSIX-mode facts survive the share | no; Windows Node |
|
|
191
197
|
| `pnpm run probe:sandbox-off` | the documented opt-out on both providers | yes |
|
|
192
198
|
|
|
193
199
|
Every script above is wired in `package.json`. `pnpm test` runs `lint:style`,
|
|
194
|
-
`test:syntax`, `lint:build`, `lint:types` and `test:unit` in that order.
|
|
200
|
+
`lint:links`, `test:syntax`, `lint:build`, `lint:types` and `test:unit` in that order.
|
|
195
201
|
`prepublishOnly` runs `pnpm test` again at publish time, so a broken gate stops a
|
|
196
202
|
release before the upload.
|
|
197
203
|
|
|
@@ -253,10 +259,10 @@ must skip itself — print a `SKIP` line and exit 0 — the way that file does o
|
|
|
253
259
|
|
|
254
260
|
Probes are shell scripts, plus the small plugin files they load: the `*-probe.mjs`
|
|
255
261
|
and `*-probe.yml` files in the same directory. Two shapes. The harness probes
|
|
256
|
-
(`run.sh`, `terminal.sh`, `sandbox-shell.sh`, `picker.sh`, `
|
|
257
|
-
`sandbox-off.sh`) boot a throwaway profile whose setup steps
|
|
258
|
-
`test/probe/run.sh`; the
|
|
259
|
-
than making their own. The distro probes (`substrate.sh`, `watch.sh`, `agent.sh`,
|
|
262
|
+
(`run.sh`, `terminal.sh`, `sandbox-shell.sh`, `picker.sh`, `fs-routing.sh`,
|
|
263
|
+
`missing-wsl.sh`, `sandbox-off.sh`) boot a throwaway profile whose setup steps
|
|
264
|
+
are in the header of `test/probe/run.sh`; the six others reuse the profile
|
|
265
|
+
`run.sh` creates rather than making their own. The distro probes (`substrate.sh`, `watch.sh`, `agent.sh`,
|
|
260
266
|
`exec.sh`) boot nothing: they run from a distro terminal and drive the resident
|
|
261
267
|
agent through the `wsl.exe` interop transport. `sandbox.sh` and `mode.sh` sit
|
|
262
268
|
outside both groups — no harness boot, no profile.
|
|
@@ -311,6 +317,22 @@ assertions; 15 shell-probe checks; 9 picker checks; 6 missing-executable checks;
|
|
|
311
317
|
5 mode checks; 5 opt-out checks and 10 sandbox expectations plus the recorded
|
|
312
318
|
escape; and 3 terminal assertions.
|
|
313
319
|
|
|
320
|
+
## Good first issues and the contributor path
|
|
321
|
+
|
|
322
|
+
The repository labels self-contained changes `good first issue`. Typical shapes:
|
|
323
|
+
|
|
324
|
+
- a recipe or troubleshooting row in `README.md` or `docs/QUICKSTART.md`
|
|
325
|
+
(the link check gates it; one commit);
|
|
326
|
+
- a probe report fixture under `test/probe/` that follows an existing
|
|
327
|
+
`*-report.txt` shape;
|
|
328
|
+
- an issue-template wording fix, a cross-reference, or a missing error-code row
|
|
329
|
+
in `docs/ARCHITECTURE.md`.
|
|
330
|
+
|
|
331
|
+
To claim one, comment on the issue. Before opening a PR run `pnpm test` (and
|
|
332
|
+
`pnpm run test:coverage` for a `lib/` change), then make one logical commit whose
|
|
333
|
+
body records the check results — the conventions below. Probes need a real
|
|
334
|
+
Windows + WSL machine; unit tests, docs and packaging changes do not.
|
|
335
|
+
|
|
314
336
|
## Commit and pull request conventions
|
|
315
337
|
|
|
316
338
|
- Write an imperative subject line: `Fix the npm readme`, not `Fixed` or `Fixes`.
|
package/README.md
CHANGED
|
@@ -12,6 +12,8 @@ The harness process stays on Windows. Commands, file operations, the folder pick
|
|
|
12
12
|
|
|
13
13
|
Windows + WSL 2 only.
|
|
14
14
|
|
|
15
|
+
**New here?** Start with [docs/QUICKSTART.md](docs/QUICKSTART.md) — five minutes, prerequisites, recipes and the honest compatibility table.
|
|
16
|
+
|
|
15
17
|
## Why this plugin
|
|
16
18
|
|
|
17
19
|
A Windows coding tool that works on WSL projects has three options: run the whole tool inside the distro, connect to a helper server inside the distro, or let the plugin provide the distro environment. This plugin is the third option. One workflow covers both kinds of session, with the distro's real tools and the command sandbox intact.
|
|
@@ -72,7 +74,7 @@ Install `bubblewrap` before the first command:
|
|
|
72
74
|
wsl.exe -d <distro> -u root -- apt-get install -y bubblewrap
|
|
73
75
|
```
|
|
74
76
|
|
|
75
|
-
From a checkout, `pnpm run
|
|
77
|
+
From a checkout, `pnpm run doctor` is the one-command environment check: Windows/WSL versions, the distro and its shell, bubblewrap usability, systemd / the user manager / Linger, the optional tools, peer versions, the agent cache, a fresh-agent handshake, stale session scopes and the known profile-patch trap. Each failure prints one copy-paste remedy; `--json` is for scripts, and `--fix` sweeps stale scopes (package installation needs `--fix --install` and an explicit confirmation). `pnpm run diagnose` is its read-only alias. `pnpm run bootstrap -- <distro>` remains the narrower tool-install helper.
|
|
76
78
|
|
|
77
79
|
Upgrade by running the `add` command again. Uninstall with `dsh plugin --profile wsl remove dsh-plugin-wsl-env`; the agent cache under `~/.cache/dsh-wsl` can then be deleted. If you added a local `workspace-files-wsl` workaround to your profile to restore previews, remove it after upgrading: the plugin no longer mounts that row.
|
|
78
80
|
|
|
@@ -80,6 +82,7 @@ Upgrade by running the `add` command again. Uninstall with `dsh plugin --profile
|
|
|
80
82
|
|
|
81
83
|
Open a distro folder as the workspace, for example `\\wsl.localhost\ubuntu\home\you\project`.
|
|
82
84
|
|
|
85
|
+
- Opening a folder in any distro runs there, even when it is not WSL's default.
|
|
83
86
|
- Commands run in the distro's login shell. `/home/you/x` and `\\wsl.localhost\ubuntu\home\you\x` are the same file.
|
|
84
87
|
- **New terminal** opens a shell in the same distro and directory.
|
|
85
88
|
- The model sees listening ports and can return the URL of a dev server. WSL 2 forwards localhost to Windows.
|
|
@@ -91,18 +94,18 @@ Settings live on named rows in `$DSH_HOME/profiles/<name>/cordis.patch.yml`. The
|
|
|
91
94
|
|
|
92
95
|
| Row | Key | Default | Purpose |
|
|
93
96
|
|---|---|---|---|
|
|
94
|
-
| `wsl-shell` | `distro` | `''` | distro
|
|
97
|
+
| `wsl-shell` | `distro` | `''` | distro mode; empty/`auto` follows the workspace, `default` forces WSL's default |
|
|
95
98
|
| | `sandbox` | `true` | confine commands with `bubblewrap` |
|
|
96
99
|
| | `sessionSandbox` | `true` | run the session's commands in one systemd user scope |
|
|
97
100
|
| | `agentBackend` | `auto` | `auto`, `native` or `shell` for the in-distro agent |
|
|
98
101
|
| | `maskWindowsDrive` | `false` | hide `/mnt` from confined commands |
|
|
99
|
-
| `wsl-fs` | `distro` | `''` | distro
|
|
100
|
-
| | `restrictToDistro` | `true` | refuse paths that belong to another distro |
|
|
102
|
+
| `wsl-fs` | `distro` | `''` | distro mode; empty/`auto` follows each target path |
|
|
103
|
+
| | `restrictToDistro` | `true` | under a fixed distro, refuse paths that belong to another distro |
|
|
101
104
|
| | `sandbox` | `true` | check confined writes against the policy |
|
|
102
105
|
| | `sessionSandbox` | `true` | run confined writes in the session runtime |
|
|
103
106
|
| | `substrate` | `agent` | in-distro agent; `share` is rejected at startup |
|
|
104
107
|
| `directory-picker-wsl` | `includeHostHome` | `true` | also list the Windows home directory |
|
|
105
|
-
| `subprocess-wsl` | `distro` | `''` | distro
|
|
108
|
+
| `subprocess-wsl` | `distro` | `''` | distro mode; empty/`auto` follows the launch directory |
|
|
106
109
|
|
|
107
110
|
`wsl-shell` and `wsl-fs` are inside the shipped `wsl` preset, so overriding them by id does not work. Override the whole `preset-wsl` row instead; [docs/CONFIGURATION.md](docs/CONFIGURATION.md) has the recipe and the remaining keys. [`cordis.patch.yml`](cordis.patch.yml) is the commented reference for every shipped value.
|
|
108
111
|
|
|
@@ -145,9 +148,10 @@ Enforcement is reported as `partial`. A command inside the distro can still star
|
|
|
145
148
|
| Symptom | Fix |
|
|
146
149
|
|---|---|
|
|
147
150
|
| every command reports `SANDBOX_UNAVAILABLE` | install `bubblewrap` in the distro, or set `sandbox: false` |
|
|
148
|
-
| the session refuses before the first command | enable systemd and linger, or set `sessionSandbox: false` on `wsl-shell` and `wsl-fs` |
|
|
151
|
+
| the session refuses before the first command | run `pnpm run doctor`; enable systemd and linger, or set `sessionSandbox: false` on `wsl-shell` and `wsl-fs` |
|
|
149
152
|
| `glob` and `grep` are slow in the distro | install `ripgrep` with `pnpm run bootstrap -- <distro> --install` |
|
|
150
153
|
| `dsh plugin add` prints nothing | the plugin is already installed; this is success |
|
|
154
|
+
| a profile patch for `wsl-shell`/`wsl-fs` changes nothing | those rows are nested inside `preset-wsl`. Run `pnpm run preset -- --check <patch>`; it fails with the complete block to paste |
|
|
151
155
|
| changes under `lib/` have no effect | run `pnpm run sync:windows`, then restart the app |
|
|
152
156
|
|
|
153
157
|
More error codes and refusals are in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#error-codes). Everything the plugin does not do is in [docs/LIMITATIONS.md](docs/LIMITATIONS.md).
|
|
@@ -170,10 +174,13 @@ Real-machine probes run on Windows + WSL only. See [CONTRIBUTING.md](CONTRIBUTIN
|
|
|
170
174
|
|
|
171
175
|
| Document | Content |
|
|
172
176
|
|---|---|
|
|
177
|
+
| [docs/QUICKSTART.md](docs/QUICKSTART.md) | five-minute first run, recipes, compatibility |
|
|
178
|
+
| [docs/PERFORMANCE.md](docs/PERFORMANCE.md) | ext4 agent vs 9p, measured latency, how to reproduce |
|
|
173
179
|
| [docs/CONFIGURATION.md](docs/CONFIGURATION.md) | every configuration key and how to override it |
|
|
174
180
|
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | services, path routing, sandbox, error codes |
|
|
175
181
|
| [docs/LIMITATIONS.md](docs/LIMITATIONS.md) | what the plugin does not do |
|
|
176
182
|
| [docs/ROADMAP.md](docs/ROADMAP.md) | current plan and deferred work |
|
|
183
|
+
| [docs/ROADMAP-1.0.md](docs/ROADMAP-1.0.md) | the 1.0 and ecosystem plan |
|
|
177
184
|
| [docs/VALUE.zh.md](docs/VALUE.zh.md) | value and trade-offs in Chinese |
|
|
178
185
|
| [CONTRIBUTING.md](CONTRIBUTING.md) | development loop, gates, probes |
|
|
179
186
|
| [SECURITY.md](SECURITY.md) | private vulnerability reporting |
|
package/SECURITY.md
CHANGED
|
@@ -88,8 +88,18 @@ This is a documented design boundary, not a vulnerability by itself. The command
|
|
|
88
88
|
distro, where `bubblewrap` and `node` must be installed; the README states it in
|
|
89
89
|
its Sandbox section.
|
|
90
90
|
|
|
91
|
+
The strict profile (`maskWindowsDrive: true` with `sandbox: true`) narrows this:
|
|
92
|
+
confined profiles shadow `/mnt` with an empty tmpfs, so the Windows executables
|
|
93
|
+
cannot be launched from there. It does not close the hole — a Windows executable
|
|
94
|
+
written into the workspace still runs, because WSL's `binfmt_misc` dispatches on
|
|
95
|
+
content, not location — so the reported enforcement stays `partial`. The complete
|
|
96
|
+
closure is distro-level (`[interop] enabled=false` in `/etc/wsl.conf`). The three
|
|
97
|
+
states and their evidence are tabulated in
|
|
98
|
+
[docs/CONFIGURATION.md](docs/CONFIGURATION.md#the-strict-profile).
|
|
99
|
+
|
|
91
100
|
A report is still welcome if you find an escape that does not depend on interop,
|
|
92
|
-
or if the plugin ever reports
|
|
101
|
+
if the masked profile is bypassable from `/mnt`, or if the plugin ever reports
|
|
102
|
+
`full` while interop is reachable.
|
|
93
103
|
|
|
94
104
|
## Credentials
|
|
95
105
|
|
package/SUPPORT.md
CHANGED
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
| Operating system | Windows 10 and Windows 11 |
|
|
8
8
|
| Linux side | WSL2 with at least one distro, and systemd as PID 1 with a lingering user manager (`loginctl enable-linger`) for the shipped session runtime. Set `sessionSandbox: false` on the preset's `wsl-shell` to keep the per-command sandbox instead |
|
|
9
9
|
| DSH packages | the `0.2.1-rc.2`-era packages listed in `peerDependencies` |
|
|
10
|
+
|
|
11
|
+
The DSH row is checked weekly: `.github/workflows/peer-canary.yml` resolves the
|
|
12
|
+
`next` dist-tag of every pinned peer, installs that line into a scratch copy and
|
|
13
|
+
runs `pnpm test` plus the composition boot test. A successful run prints the
|
|
14
|
+
exact version delta in its summary; update the row above from that summary. A
|
|
15
|
+
red run means the plugin has not been validated against the new line yet.
|
|
10
16
|
| Plugin version | the latest published version only |
|
|
11
17
|
| Node | 22.19+ (`^22.19.0`), or 24 and later (`>=24.0.0`) |
|
|
12
18
|
|
|
@@ -22,9 +28,14 @@ supported configuration.
|
|
|
22
28
|
|
|
23
29
|
Open an issue in the repository:
|
|
24
30
|
|
|
25
|
-
https://github.com/Big-Dao/dsh-plugin-wsl-env/issues
|
|
31
|
+
https://github.com/Big-Dao/dsh-plugin-wsl-env/issues/new/choose
|
|
32
|
+
|
|
33
|
+
The chooser offers a **feature request** and a **setup report**; the setup
|
|
34
|
+
report requires the `doctor --json` output, which is what makes a first-run
|
|
35
|
+
problem diagnosable without a round trip. For a security issue, use the private
|
|
36
|
+
advisory link instead (see [SECURITY.md](SECURITY.md)).
|
|
26
37
|
|
|
27
|
-
Use
|
|
38
|
+
Use issues for bug reports and for questions about configuration. Search the open and
|
|
28
39
|
the closed issues first, and check whether the answer is already written down:
|
|
29
40
|
[docs/LIMITATIONS.md](docs/LIMITATIONS.md) lists the known design boundaries, and
|
|
30
41
|
the Troubleshooting table in [README.md](README.md#troubleshooting) covers the
|
|
@@ -56,6 +67,22 @@ loudly instead of quietly.
|
|
|
56
67
|
|
|
57
68
|
There is no long-term support branch. Fixes go to the latest version.
|
|
58
69
|
|
|
70
|
+
## Triage and release cadence
|
|
71
|
+
|
|
72
|
+
- **Roadmap.** The public plan is [docs/ROADMAP-1.0.md](docs/ROADMAP-1.0.md);
|
|
73
|
+
the open issues #5–#22 map to its WSL-1xx/2xx/3xx blocks.
|
|
74
|
+
- **Triage target.** A `priority:P1` report gets its first response within three
|
|
75
|
+
business days, best effort. Security reports follow the response targets in
|
|
76
|
+
[SECURITY.md](SECURITY.md).
|
|
77
|
+
- **Cadence.** Releases ship when there is a change worth shipping, roughly every
|
|
78
|
+
two to four weeks; the tag workflow and its gates are in
|
|
79
|
+
[docs/RELEASING.md](docs/RELEASING.md), and the configuration-stability and
|
|
80
|
+
migration rules are in [docs/MIGRATION.md](docs/MIGRATION.md). There is no
|
|
81
|
+
long-term support branch.
|
|
82
|
+
- **Good first issues.** The `good first issue` label marks changes that need no
|
|
83
|
+
prior context; [CONTRIBUTING.md](CONTRIBUTING.md#good-first-issues-and-the-contributor-path)
|
|
84
|
+
lists the usual shapes and how to claim one.
|
|
85
|
+
|
|
59
86
|
## What a good bug report contains
|
|
60
87
|
|
|
61
88
|
The one-command path: `pnpm run diagnose` (from a checkout of the plugin, or
|
package/cordis.patch.yml
CHANGED
|
@@ -66,6 +66,8 @@
|
|
|
66
66
|
- id: subprocess-wsl
|
|
67
67
|
name: 'dsh-plugin-wsl-env/subprocess'
|
|
68
68
|
config:
|
|
69
|
+
# auto: follow the launch directory's UNC distro; "default" forces WSL's
|
|
70
|
+
# default, and a name pins. Empty is auto.
|
|
69
71
|
distro: ''
|
|
70
72
|
|
|
71
73
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
@@ -135,6 +137,8 @@
|
|
|
135
137
|
- id: fs-routing
|
|
136
138
|
name: 'dsh-plugin-wsl-env/fs-routing'
|
|
137
139
|
config:
|
|
140
|
+
# auto: route each UNC target to its own distro; "default" forces WSL's
|
|
141
|
+
# default, and a name pins. Empty is auto.
|
|
138
142
|
distro: ''
|
|
139
143
|
wslPath: wsl.exe
|
|
140
144
|
sandbox: true
|
|
@@ -223,6 +227,8 @@
|
|
|
223
227
|
- id: wsl-shell
|
|
224
228
|
name: 'dsh-plugin-wsl-env'
|
|
225
229
|
config:
|
|
230
|
+
# auto: follow the request workdir's UNC distro; "default"
|
|
231
|
+
# forces WSL's default, and a name pins. Empty is auto.
|
|
226
232
|
distro: ''
|
|
227
233
|
loginShell: true
|
|
228
234
|
timeoutMs: 120000
|
|
@@ -237,14 +243,15 @@
|
|
|
237
243
|
- id: wsl-fs
|
|
238
244
|
name: 'dsh-plugin-wsl-env/fs'
|
|
239
245
|
config:
|
|
246
|
+
# auto: follow each target path's UNC distro; "default"
|
|
247
|
+
# forces WSL's default, and a name pins. Empty is auto.
|
|
240
248
|
distro: ''
|
|
241
249
|
restrictToDistro: true
|
|
242
250
|
# agent (the default): reads/writes/identities run inside the
|
|
243
251
|
# distro over the resident agent (lib/fs-substrate.js) —
|
|
244
252
|
# native symlinks and mode bits, the write guard survives to
|
|
245
253
|
# publication, and confined mutations are kernel-enforced.
|
|
246
|
-
# share
|
|
247
|
-
# the 9p share.
|
|
254
|
+
# "share" is retired; a profile carrying it is refused at boot.
|
|
248
255
|
substrate: agent
|
|
249
256
|
# Confined writes ride the session runtime (ADR 0001), so they
|
|
250
257
|
# share the command's real /tmp and workspace mounts.
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -66,7 +66,7 @@ functions.
|
|
|
66
66
|
|---|---|---|---|
|
|
67
67
|
| `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). |
|
|
68
68
|
| `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. |
|
|
69
|
-
| `dsh-plugin-wsl-env/shell-env` | `wsl-shell-env` | [`lib/shell-env.js`](../lib/shell-env.js) | Contributes `DSH_WSL_DISTRO`, `DSH_WSL_SHELL`, `DSH_WSL_HOME` and `DSH_WSL_PORTS` to the managed `DSH_*` namespace. The port list is a snapshot, refreshed every `portsRefreshMs` (10000 by default), so a server started moments ago may not be listed yet. |
|
|
69
|
+
| `dsh-plugin-wsl-env/shell-env` | `wsl-shell-env` | [`lib/shell-env.js`](../lib/shell-env.js) | Contributes `DSH_WSL_DISTRO`, `DSH_WSL_SHELL`, `DSH_WSL_HOME` and `DSH_WSL_PORTS` to the managed `DSH_*` namespace, choosing the session workspace's distro (default/pinned facts are probed at mount; other distros are warmed when their session appears). The port list is a snapshot per active distro, refreshed every `portsRefreshMs` (10000 by default), so a server started moments ago may not be listed yet. |
|
|
70
70
|
|
|
71
71
|
Three modules support them and import no DSH package.
|
|
72
72
|
[`lib/paths.js`](../lib/paths.js) translates paths.
|
|
@@ -87,7 +87,7 @@ The environment is per session, not per process:
|
|
|
87
87
|
| `subprocess-wsl` | at the app level, called the composition; the shipped `subprocess` row is disabled | because no session identity reaches the terminal controller, see below |
|
|
88
88
|
| `workspace-files` | shipped, not replaced | its client half is the browser `file` resource provider the Sidebar previews need; it reads WSL paths through the routed root filesystem |
|
|
89
89
|
| `fs-routing` | at the app level; the shipped `fs-sandbox` row is disabled | the root `ctx.fs` is session-less, and its write fence has no root-plane consumers |
|
|
90
|
-
| `directory-picker-wsl`, `wsl-shell-env`, `auto-preset` | at the app level | they answer questions that are not scoped to a session |
|
|
90
|
+
| `directory-picker-wsl`, `wsl-shell-env`, `auto-preset` | at the app level | they answer questions that are not scoped to a single preset; `shell-env` resolves its facts per session workspace |
|
|
91
91
|
|
|
92
92
|
The preset mount requires the isolate realms. Without them the registry rejects the
|
|
93
93
|
providers with `Preset services require isolate realms: shell, fs`.
|
|
@@ -295,10 +295,10 @@ own dialect, described after the table.
|
|
|
295
295
|
|---|---|---|---|
|
|
296
296
|
| `SANDBOX_UNAVAILABLE` | `WslSandbox.confine` | the requested mode cannot be enforced: no usable `bwrap` in the distro | install `bubblewrap` there (a failed probe is re-run, so no restart is needed), or set `sandbox: false` |
|
|
297
297
|
| `FS_SANDBOX_DENIED` | `WslFileSystem.checkedTarget`, and `lib/fsio-agent.js` (a write the confined resident's mount table refused, classified from the kernel's `read-only file system`) | the file-effect policy refused the write: `read-only`, or a target outside the writable roots — or the kernel refused it at the resident's profile | a wider permission for that one call, or a session workspace that contains the target |
|
|
298
|
-
| `FS_OUTSIDE_DISTRO` | `WslFileSystem.worldPath` | `restrictToDistro` is on and the path names **another distro**'s share — a configuration fence, not a sandbox decision
|
|
298
|
+
| `FS_OUTSIDE_DISTRO` | `WslFileSystem.worldPath` | `distro` is fixed (`default` or a name), `restrictToDistro` is on, and the path names **another distro**'s share — a configuration fence, not a sandbox decision. With `distro: ''`/`auto` a UNC target is served by its own distro, so the fence has nothing foreign to refuse | set `distro` to `''`/`auto` (the default), or open a session in that distro. A wider permission does **not** lift it |
|
|
299
299
|
| `FS_NOT_OBSERVED` | `lib/fs-substrate.js` (`writeText`; the agent's `exists` conclusion, mapped in `lib/fsio-agent.js`) | a guarded create (`createIfAbsent`) targeted a file that exists and had not been read first | read the file, then overwrite with the version guard or edit it |
|
|
300
300
|
| `FS_STALE_VERSION` | `lib/fs-substrate.js` (`writeText`, `editText`; the agent's `stale` conclusion, mapped in `lib/fsio-agent.js`) | the version the caller holds no longer matches: the file changed, or is gone | read it again and retry with the fresh version |
|
|
301
|
-
| `FS_IO_ERROR` | the filesystem stack: `lib/fs-decisions.js` (the substrate is unavailable), `lib/fsio-agent.js` (a `perm`/`loop`/`io` reason or a malformed record), `lib/index.js` (a path that cannot be mapped, or a `SandboxUnavailableError`), and `WslFileSystem.watch` | the catch-all I/O failure — most often the resident agent is out or the distro returned an I/O error, the "agent outage fails closed" case above. A watch target outside the
|
|
301
|
+
| `FS_IO_ERROR` | the filesystem stack: `lib/fs-decisions.js` (the substrate is unavailable), `lib/fsio-agent.js` (a `perm`/`loop`/`io` reason or a malformed record), `lib/index.js` (a path that cannot be mapped, or a `SandboxUnavailableError`), and `WslFileSystem.watch` | the catch-all I/O failure — most often the resident agent is out or the distro returned an I/O error, the "agent outage fails closed" case above. A watch target outside the selected distro is the narrow one | get the resident back (check `wsl.exe -l -v`, then restart the session); for a watch, aim it at a distro path — the watch itself runs where the files live (`lib/watcher.js`) |
|
|
302
302
|
| `FS_NOT_FOUND` | `lib/fsio-agent.js` and `lib/fs-substrate.js` (the agent's `notfound`/`notdir` conclusion, an empty path, or a missing edit target), and the host filesystem stack behind a drive path | the path does not exist — the ordinary answer | — |
|
|
303
303
|
| `FS_NOT_REGULAR_FILE` | `lib/fsio-agent.js`, `lib/fs-substrate.js` | the target is not a regular file where one is required: a directory, symlink or device on a read, or a non-file target on a write | point the call at a regular file |
|
|
304
304
|
| `FS_NOT_DIRECTORY` | `lib/fsio-agent.js` (`listChildren`) | the target is not a directory, so there are no children to list | list a directory |
|
|
@@ -351,7 +351,7 @@ step with the checkout, and `pnpm run sync:windows` runs that script.
|
|
|
351
351
|
| Type check | `tsc --noEmit` over `test/**` and `types/*.d.ts` (`src/` is checked by the build itself) | no |
|
|
352
352
|
| Unit tests | `test/*.test.mjs` | no |
|
|
353
353
|
| Sandbox probe | [`test/probe/sandbox.sh`](../test/probe/sandbox.sh) | no; it applies the profile arguments directly |
|
|
354
|
-
| Harness probes | [`test/probe/run.sh`](../test/probe/run.sh), `terminal.sh`, `sandbox-shell.sh`, `picker.sh`, `missing-wsl.sh`, `sandbox-off.sh` | yes: Windows, WSL2, a mounted profile, and a linked checkout |
|
|
354
|
+
| Harness probes | [`test/probe/run.sh`](../test/probe/run.sh), `terminal.sh`, `sandbox-shell.sh`, `picker.sh`, `fs-routing.sh`, `missing-wsl.sh`, `sandbox-off.sh` | yes: Windows, WSL2, a mounted profile, and a linked checkout |
|
|
355
355
|
| Distro probes | [`test/probe/substrate.sh`](../test/probe/substrate.sh), `watch.sh`, `agent.sh`, `exec.sh` | Windows and WSL2, but no harness boot and no profile: they run from a distro terminal with Node available, reaching `wsl.exe` through interop |
|
|
356
356
|
| Host probe | [`test/probe/mode.sh`](../test/probe/mode.sh) | Windows Node, no harness |
|
|
357
357
|
| Probe plugins | the `*-probe.mjs` and `*-probe.yml` files in `test/probe/` | loaded by the scripts above |
|
|
@@ -416,6 +416,8 @@ src/auto-preset.ts per-session environment selection
|
|
|
416
416
|
src/bwrap.ts the bwrap command line every confinement site
|
|
417
417
|
assembles (no imports, so a bare checkout can
|
|
418
418
|
assert it)
|
|
419
|
+
src/distro-route.ts the pure distro-mode selector (pinned / workspace /
|
|
420
|
+
default) shared by shell, fs, preflight and env
|
|
419
421
|
src/file-reference-wsl.ts the distro-side `@`-completion traversal — built,
|
|
420
422
|
tested, not yet wired (see above)
|
|
421
423
|
src/fs.ts the ctx.fs subpath entry point
|