dsh-plugin-wsl-env 0.1.5 → 0.1.6
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 +42 -0
- package/CONTRIBUTING.md +8 -4
- package/README.md +3 -0
- package/docs/ARCHITECTURE.md +8 -4
- package/docs/LIMITATIONS.md +5 -0
- package/docs/README.zh.md +3 -0
- package/lib/auto-preset.js +2 -2
- package/lib/preset-choice.js +28 -0
- package/lib/wsl.js +5 -0
- package/package.json +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,48 @@ reference below points at that record's numbering.
|
|
|
12
12
|
|
|
13
13
|
## [Unreleased]
|
|
14
14
|
|
|
15
|
+
## [0.1.6] - 2026-10-02
|
|
16
|
+
|
|
17
|
+
Three probes that close coverage gaps a closure review named, the documentation of a
|
|
18
|
+
`/tmp` asymmetry, and one clearer error message. No behaviour changed otherwise.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- `npm run probe:picker` drives the directory picker in a throwaway boot: the root
|
|
23
|
+
level's crumbs, the Windows home, every installed distro, the documented
|
|
24
|
+
`directory-unreadable` refusal for a relative path and for a directory that does not
|
|
25
|
+
exist, and `maxEntries` with its `truncated` flag. Nothing ran `lib/picker.js` before
|
|
26
|
+
this — the unit tests import only `lib/listing.js`.
|
|
27
|
+
- `npm run probe:missing-wsl` boots a profile whose `wslPath` cannot start and pins how
|
|
28
|
+
that is reported: the executable is named, `wslPath` is suggested, the failure is not
|
|
29
|
+
dressed up as a sandbox refusal, and no sandbox facts are claimed for a command that
|
|
30
|
+
never ran. The other fail-closed state — `wsl.exe` works but the distro has no
|
|
31
|
+
`bubblewrap` — is asserted in `test/sandbox.test.mjs`, which drives
|
|
32
|
+
`WslSandbox.confine` against a failing probe, because driving that end to end needs a
|
|
33
|
+
distro without `bubblewrap`.
|
|
34
|
+
- `npm run probe:mode` runs `test/probe/mode-probe.mjs`, which measures which POSIX-mode
|
|
35
|
+
facts survive the share. The file was cited by `docs/LIMITATIONS.md` and wired into
|
|
36
|
+
no script at all.
|
|
37
|
+
- `test/preset-choice.test.mjs` covers the session-preset decision, now that the rule
|
|
38
|
+
is the pure `lib/preset-choice.js`. The frame timing that applies the decision still
|
|
39
|
+
needs the harness, and the test says so.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- `runCapture` names the cause when the executable itself cannot start: the message now
|
|
44
|
+
points at `wslPath` and at WSL being installed, the same way it already did for a
|
|
45
|
+
token denied by the Windows sandbox. `npm run probe:missing-wsl` asserts it.
|
|
46
|
+
|
|
47
|
+
### Documentation
|
|
48
|
+
|
|
49
|
+
- `docs/LIMITATIONS.md` states the `/tmp` split: a confined command's `/tmp` is a fresh
|
|
50
|
+
tmpfs while the file tools' fence grants the distro's *real* `/tmp`, so both sides can
|
|
51
|
+
write but neither sees the other's files. Hand a file between them through the session
|
|
52
|
+
workspace. `docs/ARCHITECTURE.md` describes the same distinction where it explains the
|
|
53
|
+
fence, including why the Windows temp directory is deliberately not granted to a
|
|
54
|
+
distro session.
|
|
55
|
+
|
|
56
|
+
|
|
15
57
|
## [0.1.5] - 2026-10-02
|
|
16
58
|
|
|
17
59
|
Three defects from a design-closure review. Two are cases where a documented remedy
|
package/CONTRIBUTING.md
CHANGED
|
@@ -69,6 +69,9 @@ 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:missing-wsl` | how a `wslPath` that cannot start is reported | yes |
|
|
73
|
+
| `npm run probe:picker` | the picker's root level, its refusals and its cap | yes |
|
|
74
|
+
| `npm run probe:mode` | which POSIX-mode facts survive the share | no; Windows Node |
|
|
72
75
|
|
|
73
76
|
Every script above is wired in `package.json`. `npm test` runs `lint:style`,
|
|
74
77
|
`test:syntax` and `test:unit` in that order. `prepublishOnly` runs `npm test` again
|
|
@@ -144,10 +147,11 @@ On Windows 11 + WSL2 (Ubuntu 26.04) the following has been verified:
|
|
|
144
147
|
outside the session workspace is refused inside the distro and the following
|
|
145
148
|
wider-permission request succeeds.
|
|
146
149
|
|
|
147
|
-
Counts:
|
|
148
|
-
which need the `dsh-sandbox` peer and skip
|
|
149
|
-
filesystem-probe assertions; 12 shell-probe checks;
|
|
150
|
-
|
|
150
|
+
Counts: 55 unit assertions on a bare checkout (50 plus the 5 preset-decision
|
|
151
|
+
checks), plus 5 in `test/sandbox.test.mjs` which need the `dsh-sandbox` peer and skip
|
|
152
|
+
themselves without it; 19 filesystem-probe assertions; 12 shell-probe checks; 9 picker
|
|
153
|
+
checks; 6 missing-executable checks; 5 mode checks and 10 sandbox expectations plus the
|
|
154
|
+
recorded escape; and 3 terminal assertions.
|
|
151
155
|
|
|
152
156
|
## Commit and pull request conventions
|
|
153
157
|
|
package/README.md
CHANGED
|
@@ -100,6 +100,9 @@ npm run probe:sandbox # measure inside the distro what bubblewrap does an
|
|
|
100
100
|
npm run probe # filesystem probe against a real distro (Windows + WSL only)
|
|
101
101
|
npm run probe:sandbox-shell # boot a real harness and drive the confined executor
|
|
102
102
|
npm run probe:terminal # open a PTY through the terminal provider
|
|
103
|
+
npm run probe:missing-wsl # boot a profile whose wslPath cannot start (Windows + WSL only)
|
|
104
|
+
npm run probe:picker # list the picker's root level, refusals and its cap (Windows + WSL only)
|
|
105
|
+
npm run probe:mode # which POSIX-mode facts survive the share (Windows node only)
|
|
103
106
|
```
|
|
104
107
|
|
|
105
108
|
There is nothing to install first: the package has no dependencies, and every module under test imports only Node builtins. CI runs `npm test` on Node 20, 22 and 24, on Linux and Windows, and `npm run test:coverage` on Node 24.
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -124,9 +124,12 @@ behaviour and the error messages match a Linux host. The function
|
|
|
124
124
|
| `workspace-write` | the above, plus the session workspace is writable and `/tmp` is a temporary mount |
|
|
125
125
|
| `danger-full-access` | no sandbox; used for an approved wider-permission request |
|
|
126
126
|
|
|
127
|
-
`WslFileSystem` checks writes against the same policy and the same writable list
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
`WslFileSystem` checks writes against the same policy and the same writable list,
|
|
128
|
+
with one deliberate translation: for a session inside a distro the temp root is the
|
|
129
|
+
distro's own `/tmp`, and the Windows temp directory is *not* granted, because it lies
|
|
130
|
+
outside the execution world the policy describes. That avoids a mismatch where `bash`
|
|
131
|
+
could write `/tmp` but the write tool could not. The two `/tmp`s are still different
|
|
132
|
+
directories — the command's is a fresh tmpfs — see [LIMITATIONS.md](LIMITATIONS.md). The fence is complete for the model's reach: `writeText` and `editText` are
|
|
130
133
|
the only mutation calls the model-facing file tools make on the filesystem seam,
|
|
131
134
|
so checking those two checks everything the model can mutate. Both providers
|
|
132
135
|
report their mode through `sandboxMode`. The tool layer and the Permissions
|
|
@@ -181,7 +184,8 @@ step with the checkout, and `npm run sync:windows` runs that script.
|
|
|
181
184
|
| Syntax pass | [`test/syntax.mjs`](../test/syntax.mjs) | no |
|
|
182
185
|
| Unit tests | `test/*.test.mjs` | no |
|
|
183
186
|
| Sandbox probe | [`test/probe/sandbox.sh`](../test/probe/sandbox.sh) | no; it applies the profile arguments directly |
|
|
184
|
-
| Harness probes | [`test/probe/run.sh`](../test/probe/run.sh), `terminal.sh`, `sandbox-shell.sh` | yes: Windows, WSL2, a mounted profile, and a linked checkout |
|
|
187
|
+
| Harness probes | [`test/probe/run.sh`](../test/probe/run.sh), `terminal.sh`, `sandbox-shell.sh`, `picker.sh`, `missing-wsl.sh` | yes: Windows, WSL2, a mounted profile, and a linked checkout |
|
|
188
|
+
| Host probes | [`test/probe/mode.sh`](../test/probe/mode.sh) | Windows Node, no harness |
|
|
185
189
|
| Probe plugins | the `*-probe.mjs` and `*-probe.yml` files in `test/probe/` | loaded by the scripts above |
|
|
186
190
|
|
|
187
191
|
The unit tests import only Node builtins, so they run on any platform. CI runs them
|
package/docs/LIMITATIONS.md
CHANGED
|
@@ -9,6 +9,11 @@ boundary; one that is not is a bug report waiting to happen.
|
|
|
9
9
|
- **`bubblewrap` must be installed**, or both providers fail immediately. The
|
|
10
10
|
usability probe re-runs after a failure, so installing bubblewrap while the app
|
|
11
11
|
runs is believed on the next command; only a success is cached.
|
|
12
|
+
- **A confined command's `/tmp` is not the write tool's `/tmp`.** `workspace-write`
|
|
13
|
+
mounts a fresh tmpfs at `/tmp` for the command — that is what makes it ephemeral —
|
|
14
|
+
while the file tools' fence grants the distro's *real* `/tmp`. Both are writable, so
|
|
15
|
+
neither side is refused, but a file the write tool puts in `/tmp` is invisible to
|
|
16
|
+
`bash` and vice versa. Hand a file between them through the session workspace.
|
|
12
17
|
- A distro that is stopped or unregistered while the app runs leaves the UNC share
|
|
13
18
|
unreachable: the shell tools report `wsl.exe`'s `WSL_E_*` code with the reason, but
|
|
14
19
|
the file tools can only report a file-level error, because the share itself is
|
package/docs/README.zh.md
CHANGED
|
@@ -100,6 +100,9 @@ npm run probe:sandbox # 在发行版里实测 bubblewrap 能约束什么
|
|
|
100
100
|
npm run probe # 文件系统探针,需要真实发行版(仅 Windows + WSL)
|
|
101
101
|
npm run probe:sandbox-shell # 启动真实 harness,驱动受限执行器
|
|
102
102
|
npm run probe:terminal # 通过终端 provider 打开一个 PTY
|
|
103
|
+
npm run probe:missing-wsl # 启动一个 wslPath 无法启动的 profile(仅 Windows + WSL)
|
|
104
|
+
npm run probe:picker # 列出选择器的根级、拒绝路径与上限(仅 Windows + WSL)
|
|
105
|
+
npm run probe:mode # 哪些 POSIX 权限事实能穿过共享层(仅需 Windows node)
|
|
103
106
|
```
|
|
104
107
|
|
|
105
108
|
这个包没有依赖,被测模块只 import Node 内置模块,所以不需要先安装任何东西。CI 在 Linux 和 Windows 上用 Node 20、22、24 运行 `npm test`,并在 Node 24 上运行 `npm run test:coverage`。
|
package/lib/auto-preset.js
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
import { appendFileSync } from "node:fs";
|
|
34
34
|
import z from "@deepseek-ai/schemastery";
|
|
35
35
|
import { isWslUnc } from "./paths.js";
|
|
36
|
+
import { shouldAdoptWslPreset } from "./preset-choice.js";
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
39
|
* Set `DSH_WSL_TRACE` to a file path to record every decision this plugin makes.
|
|
@@ -106,14 +107,13 @@ export async function apply(ctx, config = {}) {
|
|
|
106
107
|
// registry default (i.e. the operator did not ask for one). `undefined` means
|
|
107
108
|
// the creating caller mounted nothing at all — the headless runner does this
|
|
108
109
|
// — which is likewise "no explicit choice".
|
|
109
|
-
if (!isWslUnc(cwd)) return;
|
|
110
110
|
let fallback;
|
|
111
111
|
try {
|
|
112
112
|
fallback = ctx.agentPresets.defaultId;
|
|
113
113
|
} catch {
|
|
114
114
|
fallback = void 0;
|
|
115
115
|
}
|
|
116
|
-
if (
|
|
116
|
+
if (!shouldAdoptWslPreset({ cwd, current, fallback })) return;
|
|
117
117
|
trace(` -> acting: fallback=${String(fallback)} isWsl=${isWslUnc(cwd)}`);
|
|
118
118
|
|
|
119
119
|
// Deferred on purpose — see the module comment.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a newly created session should adopt the WSL preset.
|
|
3
|
+
*
|
|
4
|
+
* The rule is deliberately narrow, and it is the whole of "a session whose workspace
|
|
5
|
+
* is inside a distro gets the distro environment automatically":
|
|
6
|
+
*
|
|
7
|
+
* - the workspace must be a WSL UNC path, so a Windows folder keeps its own
|
|
8
|
+
* environment;
|
|
9
|
+
* - the session must not already carry an explicit choice. `undefined` counts as
|
|
10
|
+
* "no choice", because a caller that mounts nothing at all (the headless runner)
|
|
11
|
+
* is not asking for the host environment either; anything else must equal the
|
|
12
|
+
* registry's default id, which is what "the operator did not pick one" looks like
|
|
13
|
+
* after composition.
|
|
14
|
+
*
|
|
15
|
+
* Pure, so the decision is unit-testable without a boot — the frame timing that
|
|
16
|
+
* applies it still needs the harness.
|
|
17
|
+
*
|
|
18
|
+
* @param {{ cwd?: string, current?: string, fallback?: string }} input - the new
|
|
19
|
+
* session's workspace, the preset it currently has, and the registry default.
|
|
20
|
+
* @returns true when the WSL preset should be selected for this session.
|
|
21
|
+
*/
|
|
22
|
+
import { isWslUnc } from "./paths.js";
|
|
23
|
+
|
|
24
|
+
export function shouldAdoptWslPreset({ cwd, current, fallback } = {}) {
|
|
25
|
+
if (!isWslUnc(cwd)) return false;
|
|
26
|
+
if (current === void 0) return true;
|
|
27
|
+
return current === fallback;
|
|
28
|
+
}
|
package/lib/wsl.js
CHANGED
|
@@ -40,10 +40,15 @@ export function runCapture(argv, signal) {
|
|
|
40
40
|
// is unhelpful, so name the cause when it is recognisable.
|
|
41
41
|
const detail = `${stdout ?? ""}${stderr ?? ""}`.replace(/\u0000/g, "").trim();
|
|
42
42
|
const denied = /E_ACCESSDENIED/i.test(detail) || error.code === "EACCES";
|
|
43
|
+
const missing = error.code === "ENOENT";
|
|
43
44
|
error.message = `${file} failed: ${detail || error.message}${
|
|
44
45
|
denied
|
|
45
46
|
? " (WSL is unreachable from a sandboxed process: DSH's Windows ACL sandbox runs commands under a restricted low-integrity token. Compose the non-sandboxing providers.)"
|
|
46
47
|
: ""
|
|
48
|
+
}${
|
|
49
|
+
missing
|
|
50
|
+
? ` (the executable "${file}" could not be started: check \`wslPath\` in this row, and that WSL is installed)`
|
|
51
|
+
: ""
|
|
47
52
|
}`;
|
|
48
53
|
reject(error);
|
|
49
54
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-wsl-env",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Run a DeepSeek Harness session against a WSL distro: a ctx.shell executor that runs commands inside the distro and a ctx.fs backend over its UNC share",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -46,12 +46,15 @@
|
|
|
46
46
|
],
|
|
47
47
|
"scripts": {
|
|
48
48
|
"test": "npm run lint:style && npm run test:syntax && npm run test:unit",
|
|
49
|
-
"test:coverage": "node --test --experimental-test-coverage --test-coverage-lines=85 --test-coverage-branches=85 --test-coverage-functions=70 test/paths.test.mjs test/listing.test.mjs test/shell.test.mjs test/terminal.test.mjs test/probe-cache.test.mjs",
|
|
49
|
+
"test:coverage": "node --test --experimental-test-coverage --test-coverage-lines=85 --test-coverage-branches=85 --test-coverage-functions=70 test/paths.test.mjs test/listing.test.mjs test/shell.test.mjs test/terminal.test.mjs test/probe-cache.test.mjs test/preset-choice.test.mjs",
|
|
50
50
|
"test:syntax": "node test/syntax.mjs",
|
|
51
|
-
"test:unit": "node --test test/paths.test.mjs test/listing.test.mjs test/shell.test.mjs test/terminal.test.mjs test/probe-cache.test.mjs test/sandbox.test.mjs",
|
|
51
|
+
"test:unit": "node --test test/paths.test.mjs test/listing.test.mjs test/shell.test.mjs test/terminal.test.mjs test/probe-cache.test.mjs test/preset-choice.test.mjs test/sandbox.test.mjs",
|
|
52
52
|
"lint:style": "node test/style.mjs",
|
|
53
53
|
"prepublishOnly": "npm test",
|
|
54
54
|
"probe": "bash test/probe/run.sh",
|
|
55
|
+
"probe:missing-wsl": "bash test/probe/missing-wsl.sh",
|
|
56
|
+
"probe:mode": "bash test/probe/mode.sh",
|
|
57
|
+
"probe:picker": "bash test/probe/picker.sh",
|
|
55
58
|
"probe:terminal": "bash test/probe/terminal.sh",
|
|
56
59
|
"probe:sandbox": "bash test/probe/sandbox.sh",
|
|
57
60
|
"probe:sandbox-shell": "bash test/probe/sandbox-shell.sh",
|