dsh-plugin-wsl-env 0.10.0 → 0.12.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 +66 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +117 -137
- package/cordis.patch.yml +8 -17
- package/docs/ARCHITECTURE.md +23 -24
- package/docs/CONFIGURATION.md +7 -6
- package/docs/LIMITATIONS.md +31 -0
- package/docs/README.zh.md +115 -136
- package/docs/ROADMAP.md +79 -0
- package/lib/agent-shared.js +33 -5
- package/lib/agent.d.ts +103 -0
- package/lib/agent.js +144 -8
- package/lib/index.js +3 -1
- package/lib/paths.d.ts +15 -0
- package/lib/paths.js +28 -0
- package/package.json +4 -12
- package/lib/workspace-files-route.d.ts +0 -136
- package/lib/workspace-files-route.js +0 -225
- package/lib/workspace-files-wsl.d.ts +0 -257
- package/lib/workspace-files-wsl.js +0 -418
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,72 @@ were part of the record.
|
|
|
16
16
|
|
|
17
17
|
## [Unreleased]
|
|
18
18
|
|
|
19
|
+
## [0.12.0] - 2026-10-19
|
|
20
|
+
|
|
21
|
+
The GUI file service keeps the shipped `workspace-files` row: its server-only
|
|
22
|
+
replacement dropped the browser `file` resource provider and broke Sidebar
|
|
23
|
+
previews. The shipped row now serves WSL paths through `fs-routing`. The
|
|
24
|
+
`wsl-shell.maskWindowsDrive` wiring fix ships with it.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **`wsl-shell.maskWindowsDrive` reaches the per-command sandbox.** The shell
|
|
29
|
+
executor built its `WslSandbox` without the configured key. With
|
|
30
|
+
`sessionSandbox: false` the per-command profile therefore left `/mnt`
|
|
31
|
+
visible after the operator asked for the mask; with `sessionSandbox: true`
|
|
32
|
+
the runtime still masked the drive, but the decorated result reported
|
|
33
|
+
`windowsDrive: "visible"`. The executor now passes the value, matching
|
|
34
|
+
`wsl-fs` and `fs-routing`.
|
|
35
|
+
|
|
36
|
+
- **Sidebar previews work: the GUI file service keeps the shipped
|
|
37
|
+
`workspace-files` row.** The former `workspace-files-wsl` takeover replaced
|
|
38
|
+
that row with a server-only subclass, which dropped the browser `file`
|
|
39
|
+
resource provider and made every Sidebar preview report "The file resource
|
|
40
|
+
service is unavailable." The shipped row now serves WSL paths through
|
|
41
|
+
`fs-routing`, so the server and client halves stay paired. The trade-off is
|
|
42
|
+
GUI-only: a listing, `stat` or page read takes roughly four to six agent round
|
|
43
|
+
trips instead of one, and a deep page read streams the preceding lines through
|
|
44
|
+
the pipe. The model's file tools, search and `@` references are unaffected.
|
|
45
|
+
|
|
46
|
+
## [0.11.0] - 2026-10-18
|
|
47
|
+
|
|
48
|
+
The shared read agent is the native binary (A7): unconfined reads now use the
|
|
49
|
+
packaged static-musl agent with the shell as the fallback, cutting 16
|
|
50
|
+
concurrent reads from 342-614 ms to 2.4-8.2 ms. The A6 FS measurement and
|
|
51
|
+
metrics ship with it.
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
- **The shared read agent is native (A7).** `WslAgent` gained an async
|
|
56
|
+
`prepare()` hook; `sharedAgent` uses it to select the packaged native binary
|
|
57
|
+
through `agentBackend` (`auto`/`native`), with the shell as the fallback
|
|
58
|
+
(`shell`, a failed preparation, or a native HELLO/runtime failure; the reads
|
|
59
|
+
never fail closed). The A6 probe measures 16 concurrent shared reads at
|
|
60
|
+
2.4-8.2 ms, down from 342-614 ms on the shell. The native shared agent also
|
|
61
|
+
serves the picker, the subprocess activity probes and the non-session
|
|
62
|
+
command path, so those inherit EXEC concurrency.
|
|
63
|
+
|
|
64
|
+
- **`WslAgent.metrics()`.** Per-kind queue wait (sent -> ACK), execution time
|
|
65
|
+
(ACK -> settle), in-flight peak, rejected and cancelled counters; the
|
|
66
|
+
47-check lifecycle suite covers the counters and the reset.
|
|
67
|
+
|
|
68
|
+
- **`test/probe/fs-queue.mjs`.** A live measurement of both FS agents. The A6
|
|
69
|
+
run showed the shared shell reads were the batch bottleneck (16 concurrent
|
|
70
|
+
`stat`/`read`/`list` at 342-614 ms, queue up to 583 ms) while native FS ops
|
|
71
|
+
execute in 0.2-0.3 ms; the A7 run measures the native shared reads at
|
|
72
|
+
2.4-8.2 ms. v6's lock removal only takes 16 native writes from 7.7 ms to
|
|
73
|
+
~5 ms, so it stays deferred.
|
|
74
|
+
|
|
75
|
+
### Changed
|
|
76
|
+
|
|
77
|
+
- **The plan after the A6 measurement: A7 (native shared reads) landed, v6
|
|
78
|
+
deferred.** [`docs/ROADMAP.md`](docs/ROADMAP.md) records the done, next and
|
|
79
|
+
deferred steps; ADR 0002, LIMITATIONS and the READMEs describe the native
|
|
80
|
+
shared reads and the v6 deferral.
|
|
81
|
+
|
|
82
|
+
- `test/probe/fs-queue.mjs` and the README/LIMITATIONS/ROADMAP describe the
|
|
83
|
+
native shared reads; `test/agent-shared.test.mjs` covers the shell selection.
|
|
84
|
+
|
|
19
85
|
## [0.10.0] - 2026-10-18
|
|
20
86
|
|
|
21
87
|
The native in-distro agent is the default session-runtime backend: `agentBackend: auto`
|
package/CONTRIBUTING.md
CHANGED
|
@@ -304,7 +304,7 @@ On Windows 11 + WSL2 (Ubuntu 26.04) the following has been verified:
|
|
|
304
304
|
outside the session workspace is refused inside the distro and the following
|
|
305
305
|
wider-permission request succeeds.
|
|
306
306
|
|
|
307
|
-
Counts:
|
|
307
|
+
Counts: 33 unit test files under `test/` — 32 of them registered in the
|
|
308
308
|
`test:unit` list, see the OI-7 note above — which need the pinned packages
|
|
309
309
|
installed, and over 1000 `assert` call sites across them; 19 filesystem-probe
|
|
310
310
|
assertions; 15 shell-probe checks; 9 picker checks; 6 missing-executable checks;
|
package/README.md
CHANGED
|
@@ -6,199 +6,179 @@
|
|
|
6
6
|
[](https://www.npmjs.com/package/dsh-plugin-wsl-env)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin that runs a session inside a WSL 2 distribution on Windows.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
The harness process stays on Windows. Commands, file operations, the folder picker and the GUI terminal use the distro. No helper service or daemon is installed, no port is opened, and confined commands run under `bubblewrap`.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
[Troubleshooting](#troubleshooting) · [Development](#development) · [Documentation](#documentation)
|
|
13
|
+
Windows + WSL 2 only.
|
|
15
14
|
|
|
16
|
-
##
|
|
15
|
+
## Why this plugin
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
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.
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
```
|
|
19
|
+
- **Real Linux environment.** Commands run in the distro's own login shell. Its `PATH`, `nvm`, `cargo`, `pyenv` and rc files apply. File tools read and write the distro's real files on ext4.
|
|
20
|
+
- **No service in the distro.** There is no helper server, no daemon and no listening port. The only persisted artifact is a content-addressed copy of the native agent in `~/.cache/dsh-wsl/agent/`.
|
|
21
|
+
- **Sandboxed by default.** Confined commands run under `bubblewrap` inside the distro. If `bubblewrap` is missing, the command fails; it does not run unconfined.
|
|
22
|
+
- **Per-session.** A session opened on a distro folder gets the distro environment. A session opened on a Windows folder keeps the normal Windows tools. Both can run in one harness process.
|
|
23
|
+
- **Integrated with the GUI.** The folder picker lists installed distros, the terminal opens a shell in the distro, and the model can see listening ports.
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
### Why Linux, not PowerShell
|
|
28
26
|
|
|
29
|
-
|
|
27
|
+
Most coding agents learn from Linux-first material, and most open-source toolchains are built and tested on Linux first. Asking an agent to work through a Windows shell adds a translation layer on every call:
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
- **Training prior.** Examples, CI scripts, Dockerfiles and most public answers use POSIX shells and GNU tools. A model's first instinct is `bash`, not PowerShell.
|
|
30
|
+
- **Toolchain support.** Node, Python, Rust, Go, C/C++ and most build systems are validated on Linux first. Windows support is often a port with different quoting, paths, line endings and file locking.
|
|
31
|
+
- **Shell semantics.** PowerShell and `cmd` differ from POSIX in quoting, escaping, pipelines, environment variables, exit codes and path separators. Mixing the two produces errors that look like model mistakes.
|
|
32
|
+
- **Environment drift.** Windows setups vary by PowerShell version, execution policy, PATH and installed tools. Linux environments are easier to reproduce and containerize.
|
|
33
|
+
|
|
34
|
+
The result is more failed commands, more retries and more time spent on syntax instead of the task. Generated code is affected too: shell snippets, build commands and CI-style one-liners are usually written for POSIX, and the distro is where they run unchanged. A real Linux login shell removes that translation layer. Modern PowerShell and Windows-native toolchains narrow the gap, but the default prior and most project scripts still assume POSIX.
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
This plugin keeps the GUI on Windows and moves only the execution environment into the distro, so the agent gets the shell that its training and the toolchain both assume.
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
## How it compares
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
| | Desktop remote (VS Code, ZCode) | This plugin |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| Inside the distro | a helper server | one cached agent binary |
|
|
43
|
+
| Network | a listening port | none |
|
|
44
|
+
| Command sandbox | none | `bubblewrap` |
|
|
45
|
+
| Removal | delete the server | delete the cache |
|
|
40
46
|
|
|
41
|
-
|
|
47
|
+
A desktop remote reconnects faster and is more mature. A CLI agent installed inside WSL is simpler if it covers your work. This plugin is for the case where the harness stays on Windows and commands must be sandboxed.
|
|
42
48
|
|
|
43
|
-
|
|
49
|
+
## Requirements
|
|
44
50
|
|
|
45
|
-
|
|
51
|
+
- Windows 10 or 11 with WSL 2 and at least one distro.
|
|
52
|
+
- `bubblewrap` in the distro. It is required.
|
|
53
|
+
- systemd in the distro for the default session sandbox: set `[boot] systemd=true` in `/etc/wsl.conf`, restart WSL, then run `loginctl enable-linger "$USER"` once. Without systemd, set `sessionSandbox: false` on both `wsl-shell` and `wsl-fs`.
|
|
54
|
+
- Optional: `ripgrep` for search, `git` for change snapshots, `inotify-tools` for file watching.
|
|
46
55
|
|
|
47
|
-
|
|
56
|
+
No Node.js is required inside the distro.
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
- **Commands run where you expect.** They run inside the distro, in your own login shell — so your `PATH`, `nvm`, `cargo`, `pyenv` and rc files all apply. The shell comes from the distro's configuration; it is not hardcoded to bash.
|
|
51
|
-
- **Files are the real files.** `/home/you/x` and `\\wsl.localhost\ubuntu\home\you\x` are the same file. `/mnt/c/...` reaches the Windows disk as usual.
|
|
52
|
-
- **The terminal too.** Right sidebar, *New terminal*: a shell inside the distro, in the session's folder.
|
|
53
|
-
- **Port visibility.** The model sees which ports are listening inside the distro (refreshed about every 10 seconds) and can return the exact URL of a dev server it starts. WSL2 forwards localhost to Windows, so the URL opens in the browser directly.
|
|
54
|
-
- **Permissions work like on a Linux host.** The Permissions selector offers `read-only`, `workspace-write` (the default) and `danger-full-access`. When something is refused, the model is offered one retry with the smallest permission that would work. Only `danger-full-access` runs without the sandbox.
|
|
58
|
+
## Install
|
|
55
59
|
|
|
56
|
-
|
|
60
|
+
Run these commands in a Windows terminal:
|
|
57
61
|
|
|
58
|
-
|
|
62
|
+
```powershell
|
|
63
|
+
dsh wsl --from-default-profile web --dump-config # create the profile
|
|
64
|
+
dsh plugin --profile wsl add dsh-plugin-wsl-env # install the plugin and its settings
|
|
65
|
+
dsh --profile wsl --dump-config # verify: prints "# == dsh-plugin-wsl-env"
|
|
66
|
+
dsh --profile wsl # start
|
|
67
|
+
```
|
|
59
68
|
|
|
60
|
-
|
|
61
|
-
2. **Connect from a desktop app.** VS Code (Remote-WSL, and its open-source mirror [open-remote-wsl](https://github.com/jeanp413/open-remote-wsl)) and agent desktops like [ZCode](https://github.com/zai-org/ZCode) keep the app on Windows and set up a helper server inside the distro. Battle-tested. The cost: a server folder (`~/.vscode-server`, `~/.zcode/server`), a port, and no sandbox for commands.
|
|
62
|
-
3. **This plugin.** The harness stays on Windows. Nothing is installed in the distro, no port is opened, and commands run inside a bubblewrap sandbox. If something is missing, the session says what to install the moment it opens.
|
|
69
|
+
Install `bubblewrap` before the first command:
|
|
63
70
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
| opens a port | yes | no |
|
|
68
|
-
| command sandbox | none | bubblewrap + systemd session scope |
|
|
69
|
-
| after uninstall | remove the server manually | nothing left |
|
|
70
|
-
| after a WSL upgrade | server may need repair | the next command recovers automatically |
|
|
71
|
+
```powershell
|
|
72
|
+
wsl.exe -d <distro> -u root -- apt-get install -y bubblewrap
|
|
73
|
+
```
|
|
71
74
|
|
|
72
|
-
|
|
75
|
+
From a checkout, `pnpm run bootstrap -- <distro>` checks the four distro tools and prints the install command for the distro's package manager. Add `--install` to run it.
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
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.
|
|
75
78
|
|
|
76
|
-
|
|
79
|
+
## Using it
|
|
77
80
|
|
|
78
|
-
|
|
79
|
-
|---|---|---|---|
|
|
80
|
-
| `wsl-shell` | `distro` | `''` | which distro to use; empty means WSL's default distro |
|
|
81
|
-
| | `sandbox` | `true` | run commands inside the `bubblewrap` sandbox; `false` turns it off |
|
|
82
|
-
| | `sessionSandbox` | `true` | run the session's commands in one persistent systemd user scope: background dev servers survive, `/tmp` is shared, `ps` sees the session. Needs systemd as PID 1, a reachable user manager and `Linger`; the preflight refuses with the remedy. `false` restores the per-command sandbox |
|
|
83
|
-
| | `agentBackend` | `auto` | the session runtime's agent: `auto` uses the native in-distro agent when a verified source exists (packaged static-musl binary at `agent/native/linux-<arch>/dsh-wsl-agent` (x86_64/aarch64) or the `DSH_WSL_AGENT_NATIVE` override), else the shell agent; `native` refuses instead of falling back; `shell` forces the shell agent. Covers commands and confined writes. The native backend is the default; the shell agent is the fallback |
|
|
84
|
-
| `wsl-fs` | `distro` | `''` | same as above |
|
|
85
|
-
| | `restrictToDistro` | `true` | refuse paths that belong to a **different** distro. (`/mnt/c` belongs to this distro, so it is not affected.) The refusal is `FS_OUTSIDE_DISTRO`; wider permissions cannot lift it |
|
|
86
|
-
| | `sandbox` | `true` | check file writes against the same policy |
|
|
87
|
-
| | `sessionSandbox` | `true` | run confined file writes in the same session runtime as commands (one real `/tmp`, one workspace mount view); needs the same systemd/linger setup; `false` uses the per-policy confined resident |
|
|
88
|
-
| | `substrate` | `agent` | how file operations reach the distro. Only one value remains: the in-distro agent, so reads and writes run on the distro's own filesystem, with native symlinks and permissions. The former `"share"` option used the 9p share and is rejected at startup |
|
|
89
|
-
| `directory-picker-wsl` | `includeHostHome` | `true` | also list the Windows home directory in the picker |
|
|
90
|
-
| `subprocess-wsl` | `distro` | `''` | which distro the GUI terminal opens in |
|
|
81
|
+
Open a distro folder as the workspace, for example `\\wsl.localhost\ubuntu\home\you\project`.
|
|
91
82
|
|
|
92
|
-
|
|
83
|
+
- Commands run in the distro's login shell. `/home/you/x` and `\\wsl.localhost\ubuntu\home\you\x` are the same file.
|
|
84
|
+
- **New terminal** opens a shell in the same distro and directory.
|
|
85
|
+
- The model sees listening ports and can return the URL of a dev server. WSL 2 forwards localhost to Windows.
|
|
86
|
+
- The Permissions selector offers `read-only`, `workspace-write` (default) and `danger-full-access`. Only `danger-full-access` runs without the sandbox.
|
|
93
87
|
|
|
94
|
-
|
|
88
|
+
## Configure
|
|
95
89
|
|
|
96
|
-
|
|
90
|
+
Settings live on named rows in `$DSH_HOME/profiles/<name>/cordis.patch.yml`. The main values:
|
|
97
91
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
92
|
+
| Row | Key | Default | Purpose |
|
|
93
|
+
|---|---|---|---|
|
|
94
|
+
| `wsl-shell` | `distro` | `''` | distro to use; empty means the WSL default |
|
|
95
|
+
| | `sandbox` | `true` | confine commands with `bubblewrap` |
|
|
96
|
+
| | `sessionSandbox` | `true` | run the session's commands in one systemd user scope |
|
|
97
|
+
| | `agentBackend` | `auto` | `auto`, `native` or `shell` for the in-distro agent |
|
|
98
|
+
| | `maskWindowsDrive` | `false` | hide `/mnt` from confined commands |
|
|
99
|
+
| `wsl-fs` | `distro` | `''` | distro for file operations |
|
|
100
|
+
| | `restrictToDistro` | `true` | refuse paths that belong to another distro |
|
|
101
|
+
| | `sandbox` | `true` | check confined writes against the policy |
|
|
102
|
+
| | `sessionSandbox` | `true` | run confined writes in the session runtime |
|
|
103
|
+
| | `substrate` | `agent` | in-distro agent; `share` is rejected at startup |
|
|
104
|
+
| `directory-picker-wsl` | `includeHostHome` | `true` | also list the Windows home directory |
|
|
105
|
+
| `subprocess-wsl` | `distro` | `''` | distro used by the GUI terminal |
|
|
106
|
+
|
|
107
|
+
`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.
|
|
101
108
|
|
|
102
109
|
## Architecture
|
|
103
110
|
|
|
104
|
-
One
|
|
111
|
+
One harness process can serve Windows-folder and distro-folder sessions at the same time. Services attach at two levels:
|
|
105
112
|
|
|
106
113
|
```text
|
|
107
|
-
|
|
108
|
-
├─
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
├─
|
|
114
|
-
|
|
115
|
-
├─ wsl-shell-env the DSH_WSL_DISTRO / _SHELL / _HOME / _PORTS facts the model sees
|
|
116
|
-
└─ auto-preset binds the wsl preset when a session opens a distro folder
|
|
117
|
-
|
|
118
|
-
preset-wsl (the wsl agent preset; its settings are separate from the app's)
|
|
119
|
-
├─ wsl-shell ctx.shell — wsl.exe --exec <login shell>, confined by bubblewrap inside the distro
|
|
120
|
-
└─ wsl-fs ctx.fs — real distro files on the resident agent substrate (ext4)
|
|
114
|
+
app level (one per process)
|
|
115
|
+
├─ directory-picker-wsl, workspace-files, subprocess-wsl
|
|
116
|
+
├─ fs-routing: root filesystem routed by path
|
|
117
|
+
└─ auto-preset: binds the wsl preset when a session opens a distro folder
|
|
118
|
+
|
|
119
|
+
wsl preset (per session)
|
|
120
|
+
├─ wsl-shell: commands
|
|
121
|
+
└─ wsl-fs: file operations
|
|
121
122
|
```
|
|
122
123
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
**How file operations run.** Reads, writes and searches also happen inside the distro, on its own filesystem, with the distro's own tools. A search over a distro workspace runs the distro's `rg`, never the Windows binary over the 9p share. File writes are checked against the same policy the command sandbox enforces; with `wsl-fs` `sessionSandbox` on (the shipped preset), a confined write runs in the same session runtime as the commands — the same native/shell agent — so it sees the same `/tmp` and workspace mounts. Reads use the shared shell agent.
|
|
124
|
+
- `wsl-shell` runs commands through one resident agent inside a systemd user scope. The agent is the packaged static-musl `dsh-wsl-agent` for the distro's architecture (`x86_64` or `aarch64`), with the shell agent as fallback. Each command runs in the login shell under a `bubblewrap` mount profile. The scope outlives the command, so background servers survive and `ps` sees the session. With `sessionSandbox: false`, the agent stays per distro and each command gets its own profile.
|
|
125
|
+
- `wsl-fs` serves reads and writes through the same in-distro agent. Confined writes run in a long-lived agent whose mount profile matches the policy, so the kernel enforces the write boundary. There is no host-side file fallback: if the agent is unavailable, file operations fail.
|
|
126
|
+
- The GUI file tree and previews are the shipped `workspace-files` row, not a plugin replacement: its client half is the browser `file` provider. It reads WSL paths through the routed root filesystem, so it stays distro-side, at the cost of a few extra agent round trips.
|
|
127
|
+
- App-level services route by path. A distro UNC path goes to the distro; a Windows drive path stays on the host. See [docs/root-fs-routing.md](docs/root-fs-routing.md).
|
|
128
128
|
|
|
129
129
|
## Sandbox
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
| Mode | What a command inside the distro can do |
|
|
131
|
+
| Mode | What a command can do |
|
|
134
132
|
|---|---|
|
|
135
|
-
| `read-only` | read the whole distro
|
|
136
|
-
| `workspace-write` |
|
|
137
|
-
| `danger-full-access` | no sandbox
|
|
133
|
+
| `read-only` | read the whole distro; a fresh `/dev` is mounted, nothing else is writable |
|
|
134
|
+
| `workspace-write` | the above, plus the session workspace; `/tmp` is the distro's real `/tmp` with the session runtime |
|
|
135
|
+
| `danger-full-access` | no sandbox |
|
|
138
136
|
|
|
139
|
-
|
|
137
|
+
With `sessionSandbox: false`, each command gets a private `/tmp` instead of the distro's real one.
|
|
140
138
|
|
|
141
|
-
|
|
139
|
+
`bubblewrap` is required. If it is missing or broken, confined commands report `SANDBOX_UNAVAILABLE` and do not run. Setting `sandbox: false` on the owning row disables confinement; the model is told that the operation is unsandboxed.
|
|
142
140
|
|
|
143
|
-
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#sandbox)
|
|
141
|
+
Enforcement is reported as `partial`. A command inside the distro can still start a Windows executable through `/mnt/c`, and `bubblewrap` cannot control Windows processes. `maskWindowsDrive: true` hides `/mnt` from the confined view, which narrows the hole but does not close it. The complete fix is distro-level: set `[interop] enabled=false` in `/etc/wsl.conf`. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#sandbox) and [SECURITY.md](SECURITY.md).
|
|
144
142
|
|
|
145
143
|
## Troubleshooting
|
|
146
144
|
|
|
147
|
-
| Symptom |
|
|
148
|
-
|
|
149
|
-
| every command reports `SANDBOX_UNAVAILABLE` | `bubblewrap`
|
|
150
|
-
|
|
|
151
|
-
|
|
|
152
|
-
| `dsh plugin add` prints
|
|
153
|
-
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
| `glob` and `grep` are slow | searches inside the distro need `rg` installed in the distro; Windows-folder searches are unaffected | run `pnpm run bootstrap -- <distro> --install` (installs ripgrep); distro searches always run the distro's rg — they never cross the slow bridge |
|
|
157
|
-
| the terminal reports `unknown` activity | only while the in-distro agent is temporarily unavailable — distro terminals are watched from inside the distro (a `DSH_TERMINAL_ID` marker scanned in `/proc`: a shell alone is `idle`, a shell running anything is `busy`) | check the distro is running; idle terminals are closed automatically after the controller's idle timeout (2 h by default), and `terminalIdleReclaim: false` turns the auto-close off |
|
|
158
|
-
| a result names an `FS_*` code | the code says what refused it, and what clears it | see the error-code table in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#error-codes) |
|
|
145
|
+
| Symptom | Fix |
|
|
146
|
+
|---|---|
|
|
147
|
+
| 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` |
|
|
149
|
+
| `glob` and `grep` are slow in the distro | install `ripgrep` with `pnpm run bootstrap -- <distro> --install` |
|
|
150
|
+
| `dsh plugin add` prints nothing | the plugin is already installed; this is success |
|
|
151
|
+
| changes under `lib/` have no effect | run `pnpm run sync:windows`, then restart the app |
|
|
152
|
+
|
|
153
|
+
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).
|
|
159
154
|
|
|
160
155
|
## Development
|
|
161
156
|
|
|
162
157
|
```bash
|
|
163
|
-
pnpm
|
|
164
|
-
pnpm
|
|
165
|
-
pnpm
|
|
166
|
-
pnpm run test:coverage
|
|
167
|
-
pnpm run
|
|
168
|
-
pnpm run bootstrap -- <distro> # check the four distro tools; add --install to install what is missing
|
|
169
|
-
pnpm run probe:sandbox # measure inside the distro what bubblewrap does and does not confine
|
|
170
|
-
pnpm run probe # filesystem probe against a real distro (Windows + WSL only)
|
|
171
|
-
pnpm run probe:sandbox-shell # boot a real harness and drive the confined executor
|
|
172
|
-
pnpm run probe:terminal # open a PTY through the terminal provider
|
|
173
|
-
pnpm run probe:substrate # drive the agent filesystem over a real wsl.exe transport (from inside the distro)
|
|
174
|
-
pnpm run probe:watch # arm the in-distro watcher over a real directory (from inside the distro)
|
|
175
|
-
pnpm run probe:agent # compare the resident path against the fallback path (from inside the distro)
|
|
176
|
-
pnpm run probe:exec # the agent-backed execution: timeout, kill, cwd failure (from inside the distro)
|
|
177
|
-
pnpm run probe:missing-wsl # boot a profile whose wslPath cannot start (Windows + WSL only)
|
|
178
|
-
pnpm run probe:picker # list the picker's root level, refusals and its cap (Windows + WSL only)
|
|
179
|
-
pnpm run probe:mode # which POSIX-mode facts survive the share (needs a Windows node and an installed harness; opens no profile)
|
|
180
|
-
pnpm run probe:sandbox-off # prove sandbox: false unconfines both providers (Windows + WSL only)
|
|
158
|
+
pnpm install # development dependencies
|
|
159
|
+
pnpm run build # compile src/ into lib/ (lib/ is committed)
|
|
160
|
+
pnpm test # style, syntax, build consistency, types, unit tests
|
|
161
|
+
pnpm run test:coverage # unit tests with coverage thresholds
|
|
162
|
+
pnpm run sync:windows # update the Windows-side copy the app loads
|
|
181
163
|
```
|
|
182
164
|
|
|
183
|
-
The
|
|
184
|
-
|
|
185
|
-
Run `pnpm install` once to get the development dependencies. The tests use the pinned `@deepseek-ai/*` packages; the plugin itself ships none. CI runs the tests on Node 22 and 24, on Linux and Windows, and coverage on Node 24.
|
|
165
|
+
The toolchain follows the harness host: Node `^22.19.0 || >=24.0.0`. `src/` is TypeScript; `lib/` is generated and must stay in sync. The app loads a Windows-side copy of `lib/`, so run `sync:windows` and restart the app after a build.
|
|
186
166
|
|
|
187
|
-
|
|
167
|
+
Real-machine probes run on Windows + WSL only. See [CONTRIBUTING.md](CONTRIBUTING.md) and `test/probe/`.
|
|
188
168
|
|
|
189
169
|
## Documentation
|
|
190
170
|
|
|
191
|
-
| Document |
|
|
171
|
+
| Document | Content |
|
|
192
172
|
|---|---|
|
|
193
|
-
| [docs/CONFIGURATION.md](docs/CONFIGURATION.md) | every configuration key
|
|
194
|
-
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) |
|
|
195
|
-
| [docs/LIMITATIONS.md](docs/LIMITATIONS.md) | what the plugin does not do
|
|
196
|
-
| [docs/
|
|
197
|
-
| [
|
|
198
|
-
| [
|
|
199
|
-
| [
|
|
200
|
-
| [
|
|
201
|
-
| [docs/
|
|
173
|
+
| [docs/CONFIGURATION.md](docs/CONFIGURATION.md) | every configuration key and how to override it |
|
|
174
|
+
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | services, path routing, sandbox, error codes |
|
|
175
|
+
| [docs/LIMITATIONS.md](docs/LIMITATIONS.md) | what the plugin does not do |
|
|
176
|
+
| [docs/ROADMAP.md](docs/ROADMAP.md) | current plan and deferred work |
|
|
177
|
+
| [docs/VALUE.zh.md](docs/VALUE.zh.md) | value and trade-offs in Chinese |
|
|
178
|
+
| [CONTRIBUTING.md](CONTRIBUTING.md) | development loop, gates, probes |
|
|
179
|
+
| [SECURITY.md](SECURITY.md) | private vulnerability reporting |
|
|
180
|
+
| [SUPPORT.md](SUPPORT.md) | supported versions and support channels |
|
|
181
|
+
| [docs/RELEASING.md](docs/RELEASING.md) | release checklist |
|
|
202
182
|
|
|
203
183
|
## License
|
|
204
184
|
|
package/cordis.patch.yml
CHANGED
|
@@ -104,25 +104,24 @@
|
|
|
104
104
|
name: '@deepseek-ai/dsh-client-ui-directory-picker-browse'
|
|
105
105
|
|
|
106
106
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
107
|
-
# The distro-side
|
|
107
|
+
# The distro-side takeover of the root-plane filesystem.
|
|
108
108
|
#
|
|
109
109
|
# A patch layer is a flat list: an `id:` entry patches the shipped row, and a
|
|
110
110
|
# bare `insert:` list appends new rows as data. A disable written INSIDE an
|
|
111
111
|
# insert list is not a patch — it becomes a duplicate-id data row that shadows
|
|
112
112
|
# the real service row (the loader keys rows by id, last occurrence wins), and
|
|
113
|
-
# a nested `insert:` list is never interpreted at all.
|
|
113
|
+
# a nested `insert:` list is never interpreted at all. This takeover is
|
|
114
114
|
# therefore two top-level entries: disable the shipped row by id, then append
|
|
115
115
|
# the replacement with a bare insert. test/composition.test.mjs runs this file
|
|
116
116
|
# through the real patch algorithm to keep the shape honest.
|
|
117
117
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
118
118
|
|
|
119
|
-
# The GUI file
|
|
120
|
-
# the
|
|
121
|
-
#
|
|
122
|
-
#
|
|
123
|
-
#
|
|
124
|
-
|
|
125
|
-
disabled: true
|
|
119
|
+
# The GUI file service keeps the shipped `workspace-files` row: its client half
|
|
120
|
+
# is the browser `file` resource provider the Sidebar previews need, and a
|
|
121
|
+
# server-only replacement drops it (docs/LIMITATIONS.md). The shipped service
|
|
122
|
+
# reads WSL paths through the routed root filesystem below, so the GUI file tree
|
|
123
|
+
# and previews still run inside the distro, at the cost of a few extra agent
|
|
124
|
+
# round trips.
|
|
126
125
|
|
|
127
126
|
# The root-plane filesystem routes by coordinate: distro UNC identities are
|
|
128
127
|
# served by the resident agent (real watch events, ext4 reads), drive paths
|
|
@@ -133,14 +132,6 @@
|
|
|
133
132
|
disabled: true
|
|
134
133
|
|
|
135
134
|
- insert:
|
|
136
|
-
- id: workspace-files-wsl
|
|
137
|
-
name: 'dsh-plugin-wsl-env/workspace-files-wsl'
|
|
138
|
-
config:
|
|
139
|
-
maxBytes: 2097152
|
|
140
|
-
maxFileBytes: 33554432
|
|
141
|
-
maxLines: 5000
|
|
142
|
-
maxEntries: 2000
|
|
143
|
-
|
|
144
135
|
- id: fs-routing
|
|
145
136
|
name: 'dsh-plugin-wsl-env/fs-routing'
|
|
146
137
|
config:
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -26,19 +26,25 @@ root-plane services, and adds three smaller integrations.
|
|
|
26
26
|
extends the shipped `LocalFileSystem`. Only the parts that need a distro are
|
|
27
27
|
overridden.
|
|
28
28
|
|
|
29
|
-
## The
|
|
29
|
+
## The root-plane filesystem takeover
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
One of the shipped services is session-less — the root `ctx.fs` — so it cannot
|
|
32
|
+
be served from an agent preset. The layer replaces its shipped row outright with
|
|
33
|
+
a subclass that routes by coordinate:
|
|
34
34
|
|
|
35
35
|
| Service | Class | File | What it does |
|
|
36
36
|
|---|---|---|---|
|
|
37
|
-
| `ctx.workspaceFiles` | `WorkspaceFilesWsl` | [`lib/workspace-files-wsl.js`](../lib/workspace-files-wsl.js) | The GUI file tree and previews for a `\\wsl.localhost\<distro>` workspace, served from inside the distro over the resident agent instead of Windows-side 9P walks. Extends the shipped service: a distro UNC routes distro-side, a drive path calls `super`, and the watch stream delegates unchanged — so replacing the row is safe for Windows-folder sessions too. Replaces the shipped `workspace-files` row. |
|
|
38
37
|
| `ctx.fs` (root plane) | `WslRoutingFileSystem` | [`lib/fs-routing.js`](../lib/fs-routing.js) | The root filesystem, routed by coordinate: a distro UNC identity is served by the resident agent (real watch events, ext4 reads), a drive path stays host-native. Replaces the shipped `fs-sandbox` row, whose write fence has no root-plane consumers — the model's writes ride the preset filesystems. [`root-fs-routing.md`](root-fs-routing.md) is the design. |
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
The GUI file tree and previews keep the shipped `workspace-files` row. Its
|
|
40
|
+
client half is the browser `file` resource provider the Sidebar previews need,
|
|
41
|
+
so replacing the row with a server-only subclass would drop it; the shipped
|
|
42
|
+
service reads WSL paths through the routed root filesystem instead. The cost is
|
|
43
|
+
extra `ctx.fs` round trips per listing, `stat` or page read (see
|
|
44
|
+
[LIMITATIONS](LIMITATIONS.md)).
|
|
45
|
+
|
|
46
|
+
The takeover is composition-level for the mirror-image reason the terminal is
|
|
47
|
+
(see below): no session identity reaches the root filesystem. It is a top-level
|
|
42
48
|
`disabled:` entry targeting the shipped row plus a bare `insert:` list holding
|
|
43
49
|
the replacement — because a `disabled` written *inside* an insert list is not a
|
|
44
50
|
patch but a duplicate-id data row, and a nested `insert:` is never interpreted at
|
|
@@ -79,7 +85,7 @@ The environment is per session, not per process:
|
|
|
79
85
|
|---|---|---|
|
|
80
86
|
| `wsl-shell`, `wsl-fs` | inside `preset-wsl`, an agent preset with isolate realms | so one process can serve a Windows workspace and a distro workspace at the same time |
|
|
81
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 |
|
|
82
|
-
| `workspace-files
|
|
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 |
|
|
83
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 |
|
|
84
90
|
| `directory-picker-wsl`, `wsl-shell-env`, `auto-preset` | at the app level | they answer questions that are not scoped to a session |
|
|
85
91
|
|
|
@@ -282,7 +288,8 @@ failures are named as well. Every `FsError` code the plugin itself raises appear
|
|
|
282
288
|
below — a model sees the code, and a reader asking what refused them finds it here.
|
|
283
289
|
Codes the shipped host stack raises behind a drive path pass through
|
|
284
290
|
`lib/fs-routing.js` unchanged and are the peer's, not this table's; the GUI file
|
|
285
|
-
|
|
291
|
+
service is the shipped `workspace-files` row, so its wire refusals are the peer's
|
|
292
|
+
own dialect, described after the table.
|
|
286
293
|
|
|
287
294
|
| Code | Raised by | Meaning | What clears it |
|
|
288
295
|
|---|---|---|---|
|
|
@@ -301,14 +308,11 @@ tree's own wire refusals are a separate dialect, listed after the table.
|
|
|
301
308
|
| `FS_EDIT_NOT_FOUND` | `lib/fsio-text.js` (`applyLiteralEdit`) | `old_string` was empty, or matched nothing in the file | give an `old_string` the file actually contains |
|
|
302
309
|
| `FS_AMBIGUOUS_EDIT` | `lib/fsio-text.js` (`applyLiteralEdit`) | `old_string` matched more than once and `replace_all` was not set | make `old_string` unique, or set `replace_all` |
|
|
303
310
|
|
|
304
|
-
The GUI file
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
`
|
|
308
|
-
`
|
|
309
|
-
full-file cap and the byte-window cap), plus `gateway/bad-request` for a malformed
|
|
310
|
-
page request (a non-integer offset, or a limit above `maxLines`). A drive-path
|
|
311
|
-
workspace calls the shipped service and gets the peer's own answers instead.
|
|
311
|
+
The GUI file service keeps the shipped `workspace-files` row, so its wire
|
|
312
|
+
refusals are the peer's `workspace-file/*` dialect (plus `gateway/bad-request`
|
|
313
|
+
for a malformed page request), not this plugin's `FsError` codes. The plugin
|
|
314
|
+
supplies the routed `ctx.fs`; an agent outage reaches the service as
|
|
315
|
+
`FS_IO_ERROR` and the peer maps it.
|
|
312
316
|
|
|
313
317
|
Two failures come from `wsl.exe` itself rather than from a code of ours, and both used
|
|
314
318
|
to be invisible:
|
|
@@ -361,9 +365,8 @@ waits for the app to exit and then runs the whole real-machine suite once —
|
|
|
361
365
|
The unit tests come in two kinds: most import only Node builtins, while
|
|
362
366
|
[`test/composition.test.mjs`](../test/composition.test.mjs),
|
|
363
367
|
[`test/fs.boot.test.mjs`](../test/fs.boot.test.mjs),
|
|
364
|
-
[`test/fsio-agent.test.mjs`](../test/fsio-agent.test.mjs)
|
|
365
|
-
[`test/provider.test.mjs`](../test/provider.test.mjs)
|
|
366
|
-
[`test/workspace-files-wsl.test.mjs`](../test/workspace-files-wsl.test.mjs) import
|
|
368
|
+
[`test/fsio-agent.test.mjs`](../test/fsio-agent.test.mjs) and
|
|
369
|
+
[`test/provider.test.mjs`](../test/provider.test.mjs) import
|
|
367
370
|
pinned `@deepseek-ai/*` development dependencies (the composition and boot tests run
|
|
368
371
|
the loader's real patch algorithm). One `pnpm install` reproduces that tree before
|
|
369
372
|
the first run; the runtime package itself still ships zero dependencies. CI runs
|
|
@@ -444,10 +447,6 @@ src/terminal-activity.ts the distro-side activity probe for idle terminal
|
|
|
444
447
|
reclamation
|
|
445
448
|
src/terminal-route.ts which execution world a terminal launch belongs to (pure)
|
|
446
449
|
src/watcher.ts in-distro file watching for WslFileSystem.watch
|
|
447
|
-
src/workspace-files-route.ts pure builders and parsers for the routed
|
|
448
|
-
workspace-files service
|
|
449
|
-
src/workspace-files-wsl.ts WorkspaceFilesWsl (ctx.workspaceFiles), the GUI
|
|
450
|
-
file tree and previews
|
|
451
450
|
src/wsl.ts wsl.exe interop primitives (no DSH imports)
|
|
452
451
|
```
|
|
453
452
|
|