dsh-plugin-wsl-env 0.8.0 → 0.10.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 +488 -12
- package/CONTRIBUTING.md +125 -62
- package/README.md +99 -68
- package/SECURITY.md +18 -2
- package/SUPPORT.md +26 -10
- package/agent/native/linux-aarch64/dsh-wsl-agent +0 -0
- package/agent/native/linux-x86_64/dsh-wsl-agent +0 -0
- package/agent/wsl-agent.sh +83 -16
- package/cordis.patch.yml +50 -35
- package/docs/ARCHITECTURE.md +203 -55
- package/docs/CONFIGURATION.md +185 -18
- package/docs/LIMITATIONS.md +125 -45
- package/docs/PARITY.md +68 -38
- package/docs/PEER-PARITY.md +103 -33
- package/docs/README.zh.md +105 -64
- package/docs/UPSTREAM-FSIO-EXPORT.md +159 -50
- package/docs/UPSTREAM-TERMINAL-TITLE.md +56 -14
- package/examples/preset-wsl-session-sandbox.cordis.patch.yml +178 -0
- package/lib/agent-protocol.d.ts +10 -4
- package/lib/agent-protocol.js +10 -3
- package/lib/agent.d.ts +84 -0
- package/lib/agent.js +167 -15
- package/lib/bwrap.d.ts +8 -0
- package/lib/bwrap.js +4 -1
- package/lib/fs-routing.d.ts +40 -4
- package/lib/fs-routing.js +80 -12
- package/lib/fsio-agent.js +1 -1
- package/lib/index.d.ts +134 -0
- package/lib/index.js +202 -9
- package/lib/native-agent.d.ts +124 -0
- package/lib/native-agent.js +198 -0
- package/lib/ports.d.ts +13 -2
- package/lib/ports.js +18 -5
- package/lib/preflight.d.ts +91 -0
- package/lib/preflight.js +117 -0
- package/lib/probe-cache.d.ts +12 -1
- package/lib/probe-cache.js +11 -3
- package/lib/sandbox-core.d.ts +70 -0
- package/lib/sandbox-core.js +187 -18
- package/lib/session-runtime.d.ts +161 -0
- package/lib/session-runtime.js +220 -0
- package/lib/session-scope.d.ts +124 -0
- package/lib/session-scope.js +191 -0
- package/lib/shell-env.js +10 -3
- package/lib/watcher.d.ts +25 -12
- package/lib/watcher.js +120 -32
- package/lib/workspace-files-route.d.ts +8 -4
- package/lib/workspace-files-route.js +42 -14
- package/lib/workspace-files-wsl.js +25 -8
- package/lib/wsl.js +10 -4
- package/locale/preflight/en.json +6 -0
- package/locale/preflight/zh.json +6 -0
- package/package.json +44 -27
- package/scripts/diagnose.sh +92 -0
package/CONTRIBUTING.md
CHANGED
|
@@ -14,14 +14,22 @@ the conventions this repository uses.
|
|
|
14
14
|
|
|
15
15
|
## Toolchain
|
|
16
16
|
|
|
17
|
-
The runtime package ships
|
|
17
|
+
The runtime package ships no `dependencies` field at all, on purpose: it is
|
|
18
18
|
loaded into the DSH process and everything it needs arrives as a peer package.
|
|
19
19
|
Running the plugin from a checkout has no install step.
|
|
20
20
|
|
|
21
|
-
Developing and running the gates do have dependencies. The
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
before the gates pass.
|
|
21
|
+
Developing and running the gates do have dependencies. The tests import pinned
|
|
22
|
+
`@deepseek-ai/*` packages — the composition test runs the loader's real patch
|
|
23
|
+
algorithm, and the boot test starts a row through the real Loader — so a fresh
|
|
24
|
+
checkout needs one `pnpm install` before the gates pass.
|
|
25
|
+
|
|
26
|
+
Those pins are deliberate, and they move with the harness rather than on their
|
|
27
|
+
own: the `@deepseek-ai/dsh-*` peers sit at `0.2.1-rc.2`, `@deepseek-ai/cordis`
|
|
28
|
+
at `~4.0.5` and `@deepseek-ai/schemastery` at `~3.18.5`. The plugin subclasses
|
|
29
|
+
peer classes and mirrors peer protocol, so raising one pin alone is how the
|
|
30
|
+
seams drift: bump them in the commit that matches a harness release, re-run the
|
|
31
|
+
comparison `docs/PEER-PARITY.md` describes, and let the gate chain and the
|
|
32
|
+
probes confirm the result.
|
|
25
33
|
|
|
26
34
|
pnpm runs the scripts in this repository, pinned through the `packageManager`
|
|
27
35
|
field (corepack resolves it). Treat `pnpm run <script>` as the reference
|
|
@@ -40,24 +48,24 @@ Exactly three dependencies run install scripts (`@deepseek-ai/dsh-subprocess-loc
|
|
|
40
48
|
|
|
41
49
|
## The type gate
|
|
42
50
|
|
|
43
|
-
`lint:types` runs `tsc --noEmit` under `strict` with `checkJs
|
|
44
|
-
|
|
45
|
-
own `.d.ts
|
|
46
|
-
`declare module '@deepseek-ai/cordis'` augmentations the service
|
|
47
|
-
so `ctx.fs`, `ctx.shell`, `ctx.sandboxPolicy` and friends are
|
|
48
|
-
nothing itself and ships nowhere.
|
|
49
|
-
|
|
50
|
-
The
|
|
51
|
-
|
|
52
|
-
`
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
51
|
+
`lint:types` runs `tsc --noEmit` under `strict` with `checkJs` over what is NOT
|
|
52
|
+
generated — `test/**` and the ambient types — and the types come from the peer
|
|
53
|
+
packages' own `.d.ts`: nothing is invented twice. `types/dsh-services.d.ts`
|
|
54
|
+
pulls in the `declare module '@deepseek-ai/cordis'` augmentations the service
|
|
55
|
+
packages ship, so `ctx.fs`, `ctx.shell`, `ctx.sandboxPolicy` and friends are
|
|
56
|
+
typed; it declares nothing itself and ships nowhere.
|
|
57
|
+
|
|
58
|
+
The probe scripts under `test/probe/` are inside the gate as well. The sources
|
|
59
|
+
under `src/` are checked by the build program itself (`strict`,
|
|
60
|
+
`noEmitOnError`), which is strictly stronger than checking their emitted
|
|
61
|
+
JavaScript. The shipped artifact was checked first because it is where protocol
|
|
62
|
+
drift with the pinned peers hurts; the tests and probes came next, and the
|
|
63
|
+
errors they surfaced were real — a test that pinned a raw number into an argv,
|
|
64
|
+
a couple of hand-written JSDoc contracts wider than the code (`wslErrorCode`,
|
|
65
|
+
`gitSpawnRewrite`) and narrower than their seams, and one
|
|
66
|
+
accepted-but-never-enforced byte-window cap in the distro-routed file service,
|
|
67
|
+
found because a fake hands the confinement seam an `"full"` enforcement the core
|
|
68
|
+
itself never reports.
|
|
61
69
|
|
|
62
70
|
Three patterns keep the check honest:
|
|
63
71
|
|
|
@@ -69,9 +77,12 @@ Three patterns keep the check honest:
|
|
|
69
77
|
at the injection point — the cast states which contract the double stands in
|
|
70
78
|
for, instead of silencing the mismatch with `any`.
|
|
71
79
|
- `@ts-expect-error` is allowed exactly when the suppressed error names a real
|
|
72
|
-
upstream seam gap, carries the reason inline, and the gap is written up in
|
|
73
|
-
`docs/UPSTREAM-*.md
|
|
74
|
-
|
|
80
|
+
upstream seam gap, carries the reason inline, and the gap is written up — in a
|
|
81
|
+
`docs/UPSTREAM-*.md` tracking doc when the fix is an upstream ask (the
|
|
82
|
+
`LocalBashExecutor.spawnSpec` seam has one), or in the source comment itself
|
|
83
|
+
when the honest fix needs a protocol decision this package owns (the exit-4
|
|
84
|
+
relay's unknown entry kind is the other site, deliberately left visible). It
|
|
85
|
+
is a published finding, not a silencer.
|
|
75
86
|
|
|
76
87
|
## The build
|
|
77
88
|
|
|
@@ -119,15 +130,25 @@ Rules that keep this honest:
|
|
|
119
130
|
2. `pnpm run build`, then `pnpm test`. There is no exclude list to maintain and
|
|
120
131
|
no registration step: the build and `lint:build` derive everything from
|
|
121
132
|
`src/`, and `lib/` is generated output — never edit it by hand.
|
|
133
|
+
3. If the module gets a row of its own on the Plugins page, add its display
|
|
134
|
+
metadata as `locale/<name>/en.json` and `locale/<name>/zh.json` — a
|
|
135
|
+
`meta.title` / `meta.description` pair each — and expose both through
|
|
136
|
+
`exports` (`./<name>/locale/en.json`, `./<name>/locale/zh.json`), which is
|
|
137
|
+
what that page reads before falling back to the package `description`. The
|
|
138
|
+
bundle's own pair is `locale/en.json` / `locale/zh.json`, and `subprocess`,
|
|
139
|
+
`picker`, `shell-env` and `auto-preset` carry theirs; `files` already ships
|
|
140
|
+
`locale/**/*.json`, and `lint:style` fails an `exports` target that does not
|
|
141
|
+
exist.
|
|
122
142
|
|
|
123
143
|
## The development loop
|
|
124
144
|
|
|
125
145
|
1. Clone the repository inside the distro. Most contributors develop there.
|
|
126
146
|
2. Make the change.
|
|
127
147
|
3. Run the gates below.
|
|
128
|
-
4.
|
|
129
|
-
|
|
130
|
-
|
|
148
|
+
4. If you changed anything under `lib/`, run `pnpm run sync:windows` first and
|
|
149
|
+
then restart the DSH app. The app loads the Windows mirror (see below), so a
|
|
150
|
+
restart without the sync just reloads the old code; a running process also
|
|
151
|
+
caches ES modules.
|
|
131
152
|
|
|
132
153
|
### The runtime mirror
|
|
133
154
|
|
|
@@ -151,26 +172,28 @@ terminal if you would rather not see the prompt.
|
|
|
151
172
|
|
|
152
173
|
| Command | Covers | Needs a harness |
|
|
153
174
|
|---|---|---|
|
|
154
|
-
| `pnpm test` | style checks, syntax pass, type check, and unit tests | no |
|
|
175
|
+
| `pnpm test` | style checks, syntax pass, build consistency, type check, and unit tests | no |
|
|
155
176
|
| `pnpm run test:coverage` | unit tests with coverage | no |
|
|
156
177
|
| `pnpm run lint:style` | style and lint rules | no |
|
|
157
|
-
| `pnpm run lint:
|
|
178
|
+
| `pnpm run lint:build` | the build-freshness check: every source has its artifacts and the committed bytes match a real build | no |
|
|
179
|
+
| `pnpm run lint:types` | the TypeScript check over `test/**` and the ambient types (`tsc --noEmit`) | no |
|
|
158
180
|
| `pnpm run probe` | filesystem behaviour against a real distro | yes |
|
|
159
181
|
| `pnpm run probe:sandbox` | what bubblewrap confines and what it does not | no |
|
|
160
182
|
| `pnpm run probe:sandbox-shell` | the confined executor through a real boot | yes |
|
|
161
183
|
| `pnpm run probe:terminal` | the terminal provider | yes |
|
|
162
|
-
| `pnpm run probe:substrate` | the agent filesystem substrate over a real `wsl.exe` transport |
|
|
163
|
-
| `pnpm run probe:watch` | the in-distro watcher over a real directory |
|
|
164
|
-
| `pnpm run probe:agent` | the resident-vs-one-shot fallback parity legs |
|
|
165
|
-
| `pnpm run probe:exec` | the agent-backed execution handle: timeout, kill, cwd failure |
|
|
184
|
+
| `pnpm run probe:substrate` | the agent filesystem substrate over a real `wsl.exe` transport | no; inside the distro |
|
|
185
|
+
| `pnpm run probe:watch` | the in-distro watcher over a real directory | no; inside the distro |
|
|
186
|
+
| `pnpm run probe:agent` | the resident-vs-one-shot fallback parity legs | no; inside the distro |
|
|
187
|
+
| `pnpm run probe:exec` | the agent-backed execution handle: timeout, kill, cwd failure | no; inside the distro |
|
|
166
188
|
| `pnpm run probe:missing-wsl` | how a `wslPath` that cannot start is reported | yes |
|
|
167
189
|
| `pnpm run probe:picker` | the picker's root level, its refusals and its cap | yes |
|
|
168
190
|
| `pnpm run probe:mode` | which POSIX-mode facts survive the share | no; Windows Node |
|
|
169
191
|
| `pnpm run probe:sandbox-off` | the documented opt-out on both providers | yes |
|
|
170
192
|
|
|
171
193
|
Every script above is wired in `package.json`. `pnpm test` runs `lint:style`,
|
|
172
|
-
`test:syntax`, `lint:types` and `test:unit` in that order.
|
|
173
|
-
at publish time, so a broken gate stops a
|
|
194
|
+
`test:syntax`, `lint:build`, `lint:types` and `test:unit` in that order.
|
|
195
|
+
`prepublishOnly` runs `pnpm test` again at publish time, so a broken gate stops a
|
|
196
|
+
release before the upload.
|
|
174
197
|
|
|
175
198
|
CI runs `pnpm test` on Node 22 and 24, on both `ubuntu-latest` and
|
|
176
199
|
`windows-latest`, and `pnpm run test:coverage` on Node 24. The Windows runners are
|
|
@@ -181,6 +204,19 @@ The probes marked "yes" need Windows, WSL2, a mounted harness profile and a link
|
|
|
181
204
|
checkout, which a hosted runner does not have. Run them on your own machine and
|
|
182
205
|
paste the output into the pull request.
|
|
183
206
|
|
|
207
|
+
The rows marked "no; inside the distro" (`substrate`, `watch`, `agent`, `exec`)
|
|
208
|
+
still need Windows and WSL2, but no harness boot and no profile: run them from a
|
|
209
|
+
distro terminal with Node available, and they reach `wsl.exe` through interop.
|
|
210
|
+
|
|
211
|
+
Two conveniences for the real-machine set. `test/probe/run-all-when-closed.sh
|
|
212
|
+
--include-fs` runs all twelve probes once the app has exited. And `ci.yml` has a
|
|
213
|
+
`probes` job you dispatch by hand: `sandbox` runs whenever WSL is present, then
|
|
214
|
+
`mode`, `terminal`, `picker` and `missing-wsl` where the harness app is
|
|
215
|
+
installed. It is named for a self-hosted Windows runner although its `runs-on`
|
|
216
|
+
carries the hosted `windows-latest` label, and it is not a green no-op on a
|
|
217
|
+
hosted runner — the `sandbox` leg fails, not skips, where the default distro has
|
|
218
|
+
no bubblewrap.
|
|
219
|
+
|
|
184
220
|
## Adding a test
|
|
185
221
|
|
|
186
222
|
Choose the layer by what the test needs.
|
|
@@ -190,44 +226,70 @@ Choose the layer by what the test needs.
|
|
|
190
226
|
| Unit test | `test/*.test.mjs` | pure logic: path translation, listing, shell argument building, terminal helpers |
|
|
191
227
|
| Probe | `test/probe/` | behaviour that needs a real distro, a real profile, or bubblewrap |
|
|
192
228
|
|
|
193
|
-
Unit tests run under `node --test`.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
`test
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
229
|
+
Unit tests run under `node --test`. The `test:unit` and the `test:coverage` lists
|
|
230
|
+
in `package.json` name their files explicitly; a test that only touches this
|
|
231
|
+
repository belongs in both. Two lags in those lists today are hygiene to fix in
|
|
232
|
+
passing, not a pattern to copy: each names one file twice
|
|
233
|
+
(`test/fsio-text.test.mjs` in `test:unit`, `test/fs.boot.test.mjs` in
|
|
234
|
+
`test:coverage`), and `test/fs-routing.test.mjs` is in neither
|
|
235
|
+
(`docs/REQUIREMENTS.zh.md` tracks that as OI-7).
|
|
236
|
+
|
|
237
|
+
A test that imports a pinned `@deepseek-ai/*` package goes in the same lists and
|
|
238
|
+
needs no guarding — CI runs `pnpm install --frozen-lockfile` over the locked tree
|
|
239
|
+
before `pnpm test`. Five files import such packages at runtime today —
|
|
240
|
+
`test/provider.test.mjs` is what covers the assembled service rows, and
|
|
241
|
+
`test/composition.test.mjs` runs the loader's real patch algorithm — and a sixth
|
|
242
|
+
(`test/terminal-activity.test.mjs`) reaches a peer only through a JSDoc type
|
|
243
|
+
import, which `checkJs` still resolves.
|
|
244
|
+
|
|
245
|
+
Where a peer-free seam exists, prefer it. `test/sandbox.test.mjs` runs against
|
|
246
|
+
`lib/sandbox-core.js`, with a stand-in for the peer's `SandboxUnavailableError`
|
|
247
|
+
contract, which leaves only the one-line peer binding in `lib/sandbox.js`. That
|
|
248
|
+
line is covered by the harness probes, which boot the plugin and so reach
|
|
249
|
+
`WslSandbox` through `lib/index.js` — not by `probe:sandbox`, which reads only
|
|
250
|
+
the argument builder out of `lib/bwrap.js`. A test that cannot run everywhere
|
|
251
|
+
must skip itself — print a `SKIP` line and exit 0 — the way that file does on
|
|
252
|
+
`win32`, where its fake `wsl.exe` (a POSIX script) cannot execute.
|
|
253
|
+
|
|
254
|
+
Probes are shell scripts, plus the small plugin files they load: the `*-probe.mjs`
|
|
255
|
+
and `*-probe.yml` files in the same directory. Two shapes. The harness probes
|
|
256
|
+
(`run.sh`, `terminal.sh`, `sandbox-shell.sh`, `picker.sh`, `missing-wsl.sh`,
|
|
257
|
+
`sandbox-off.sh`) boot a throwaway profile whose setup steps are in the header of
|
|
258
|
+
`test/probe/run.sh`; the five others reuse the profile `run.sh` creates rather
|
|
259
|
+
than making their own. The distro probes (`substrate.sh`, `watch.sh`, `agent.sh`,
|
|
260
|
+
`exec.sh`) boot nothing: they run from a distro terminal and drive the resident
|
|
261
|
+
agent through the `wsl.exe` interop transport. `sandbox.sh` and `mode.sh` sit
|
|
262
|
+
outside both groups — no harness boot, no profile.
|
|
204
263
|
|
|
205
264
|
Keep probe output idempotent. Several probes delete their scratch files at the
|
|
206
265
|
start, because a leftover file from an earlier run changes the result.
|
|
207
266
|
|
|
208
267
|
## What has been verified
|
|
209
268
|
|
|
210
|
-
`pnpm test`
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
269
|
+
`pnpm test` runs the whole unit suite once the one `pnpm install` above has
|
|
270
|
+
fetched the pinned packages: the files that import them statically exercise the
|
|
271
|
+
services through the loader and their declared types, and `test:syntax` keeps a
|
|
272
|
+
`node --check` pass over every shipped module. What no test on a Linux leg can
|
|
273
|
+
reach is the Windows-plus-WSL2 topology itself: a real `wsl.exe`, a real distro,
|
|
274
|
+
a real harness boot. That is the gap the probes close; the archived record's
|
|
275
|
+
§15.4 documents the five rounds of misdiagnosis it once caused.
|
|
215
276
|
|
|
216
277
|
`test/probe/sandbox.sh` needs no harness: it uses the same arguments
|
|
217
278
|
`lib/sandbox.js` builds and asserts what bubblewrap does and does not confine,
|
|
218
279
|
recording the interop escape as `INFO` because a Linux sandbox cannot govern a
|
|
219
|
-
Windows process. The
|
|
280
|
+
Windows process. The harness probes boot a throwaway profile bound to the distro:
|
|
220
281
|
|
|
221
282
|
- the filesystem probe asserts the write path and the write checks — a write outside
|
|
222
283
|
the policy root and a write in `read-only` mode both return `FS_SANDBOX_DENIED`,
|
|
223
|
-
while `danger-full-access` is
|
|
284
|
+
while `danger-full-access` is the approved escalation and its write is asserted
|
|
285
|
+
to land;
|
|
224
286
|
- the shell probe drives all three modes through `ctx.shell` and checks the refusal
|
|
225
287
|
classification the tool layer returns;
|
|
226
288
|
- the terminal probe asserts the distro, the starting directory, and the `DSH_*`
|
|
227
289
|
variables forwarded through `WSLENV`.
|
|
228
290
|
|
|
229
|
-
The setup steps for
|
|
230
|
-
`
|
|
291
|
+
The setup steps for that throwaway profile are in the header of
|
|
292
|
+
`test/probe/run.sh`; the harness probes share the one profile it creates.
|
|
231
293
|
|
|
232
294
|
On Windows 11 + WSL2 (Ubuntu 26.04) the following has been verified:
|
|
233
295
|
|
|
@@ -242,11 +304,12 @@ On Windows 11 + WSL2 (Ubuntu 26.04) the following has been verified:
|
|
|
242
304
|
outside the session workspace is refused inside the distro and the following
|
|
243
305
|
wider-permission request succeeds.
|
|
244
306
|
|
|
245
|
-
Counts:
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
307
|
+
Counts: 28 unit test files under `test/` — 27 of them registered in the
|
|
308
|
+
`test:unit` list, see the OI-7 note above — which need the pinned packages
|
|
309
|
+
installed, and over 1000 `assert` call sites across them; 19 filesystem-probe
|
|
310
|
+
assertions; 15 shell-probe checks; 9 picker checks; 6 missing-executable checks;
|
|
311
|
+
5 mode checks; 5 opt-out checks and 10 sandbox expectations plus the recorded
|
|
312
|
+
escape; and 3 terminal assertions.
|
|
250
313
|
|
|
251
314
|
## Commit and pull request conventions
|
|
252
315
|
|