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/cordis.patch.yml
CHANGED
|
@@ -162,6 +162,15 @@
|
|
|
162
162
|
- id: auto-preset
|
|
163
163
|
name: 'dsh-plugin-wsl-env/auto-preset'
|
|
164
164
|
|
|
165
|
+
# Session-start sandbox preflight: when a distro session opens, run the
|
|
166
|
+
# providers' own bwrap usability probe (shared cache, so the first confined
|
|
167
|
+
# command pays nothing) and surface a missing or broken bubblewrap as a
|
|
168
|
+
# warning with the full remedy — instead of the first command's fail-closed
|
|
169
|
+
# error being the discovery moment. Advisory only: it never installs and
|
|
170
|
+
# never changes the providers' fail-closed semantics.
|
|
171
|
+
- id: wsl-preflight
|
|
172
|
+
name: 'dsh-plugin-wsl-env/preflight'
|
|
173
|
+
|
|
165
174
|
- insert:
|
|
166
175
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
167
176
|
# preset-wsl — the WSL environment, scoped to agents that select this preset.
|
|
@@ -178,9 +187,20 @@
|
|
|
178
187
|
# Because a preset's plugins mount under a REGISTRY-owned scope (not this row's
|
|
179
188
|
# ctx), the realm has to be declared here, inside config.plugins.
|
|
180
189
|
#
|
|
181
|
-
# Generated from the shipped standard preset
|
|
182
|
-
#
|
|
183
|
-
#
|
|
190
|
+
# Generated from the shipped standard preset so the embedded plan-mode
|
|
191
|
+
# section and every nested group survive byte-for-byte: this copy is
|
|
192
|
+
# `packages/bundle/web-app/presets/standard.patch.yml` of the
|
|
193
|
+
# `dsh-v0.2.1-rc.2` source tree, changed in exactly three ways —
|
|
194
|
+
# 1. every shipped row mounts inside `wsl-env`, so the preset's tools see
|
|
195
|
+
# the distro's shell and fs (the realm is declared on the group);
|
|
196
|
+
# 2. `tool-bash` is enabled and `tool-pwsh` disabled outright, where the
|
|
197
|
+
# shipped copy derives the choice from `process.platform`;
|
|
198
|
+
# 3. the identity: row `preset-wsl`, preset `wsl`, `order: 2`.
|
|
199
|
+
# Regenerate it when an app update changes the preset: take
|
|
200
|
+
# `packages/bundle/web-app/presets/standard.patch.yml` from the harness tree
|
|
201
|
+
# the peer pins track and run `node scripts/build-preset-wsl.mjs <that
|
|
202
|
+
# file>`, then replace this row to the end of the file. The script applies
|
|
203
|
+
# changes 1 and 2 itself, so only change 3 is yours.
|
|
184
204
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
185
205
|
- id: preset-wsl
|
|
186
206
|
name: '@deepseek-ai/dsh-agent-preset'
|
|
@@ -200,12 +220,15 @@
|
|
|
200
220
|
# asked of the distro itself. Pin a directory here to override it —
|
|
201
221
|
# a value that is right on one machine is wrong on every other.
|
|
202
222
|
#
|
|
203
|
-
#
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
#
|
|
207
|
-
#
|
|
208
|
-
#
|
|
223
|
+
# Commands run in one persistent systemd user scope per session
|
|
224
|
+
# (ADR 0001): a background dev server survives the command that
|
|
225
|
+
# started it, `/tmp` is the distro's real `/tmp`, and `ps` sees the
|
|
226
|
+
# session. This needs systemd as PID 1 (`[boot] systemd=true` in
|
|
227
|
+
# /etc/wsl.conf) and a lingering user manager (`loginctl
|
|
228
|
+
# enable-linger <user>`); the preflight refuses with those remedies
|
|
229
|
+
# when one is missing. Set `sessionSandbox: false` for the old
|
|
230
|
+
# per-command shape. Bubblewrap is still required for the mount
|
|
231
|
+
# profile; without it the providers fail closed.
|
|
209
232
|
- id: wsl-shell
|
|
210
233
|
name: 'dsh-plugin-wsl-env'
|
|
211
234
|
config:
|
|
@@ -213,6 +236,13 @@
|
|
|
213
236
|
loginShell: true
|
|
214
237
|
timeoutMs: 120000
|
|
215
238
|
maxTimeoutMs: 600000
|
|
239
|
+
sessionSandbox: true
|
|
240
|
+
# auto: the native session-runtime agent when a verified
|
|
241
|
+
# source is available (packaged binary, or the
|
|
242
|
+
# DSH_WSL_AGENT_NATIVE development override); otherwise the
|
|
243
|
+
# shipped shell agent. native fails the first command instead
|
|
244
|
+
# of falling back; shell forces the shell agent.
|
|
245
|
+
agentBackend: auto
|
|
216
246
|
- id: wsl-fs
|
|
217
247
|
name: 'dsh-plugin-wsl-env/fs'
|
|
218
248
|
config:
|
|
@@ -225,15 +255,19 @@
|
|
|
225
255
|
# share: the opt-out — the Windows-side host fs stack over
|
|
226
256
|
# the 9p share.
|
|
227
257
|
substrate: agent
|
|
258
|
+
# Confined writes ride the session runtime (ADR 0001), so they
|
|
259
|
+
# share the command's real /tmp and workspace mounts.
|
|
260
|
+
sessionSandbox: true
|
|
228
261
|
- id: persona
|
|
229
262
|
name: '@deepseek-ai/dsh-persona'
|
|
230
263
|
config:
|
|
231
|
-
suffix: Your working directory is {{cwd}}.
|
|
232
264
|
prefix: You are a coding agent powered by the {{model}} model.
|
|
233
265
|
- id: agent-instructions
|
|
234
266
|
name: '@deepseek-ai/dsh-agent-instructions'
|
|
235
267
|
config:
|
|
236
268
|
maxBytes: 65536
|
|
269
|
+
- id: time-context
|
|
270
|
+
name: '@deepseek-ai/dsh-time-context'
|
|
237
271
|
- id: tool-bash
|
|
238
272
|
name: '@deepseek-ai/dsh-tool-bash'
|
|
239
273
|
disabled: false
|
|
@@ -248,6 +282,8 @@
|
|
|
248
282
|
sampleOverCapGlobResults: false
|
|
249
283
|
- id: tool-jobs
|
|
250
284
|
name: '@deepseek-ai/dsh-tool-jobs'
|
|
285
|
+
- id: tool-schedule
|
|
286
|
+
name: '@deepseek-ai/dsh-tool-schedule'
|
|
251
287
|
- id: skill-filesystem
|
|
252
288
|
name: '@deepseek-ai/dsh-skill-filesystem'
|
|
253
289
|
- id: tool-skill
|
|
@@ -266,7 +302,7 @@
|
|
|
266
302
|
name: '@deepseek-ai/dsh-plan-mode'
|
|
267
303
|
config:
|
|
268
304
|
section: |
|
|
269
|
-
You are in plan mode. Stay in plan mode until
|
|
305
|
+
You are in plan mode. Stay in plan mode until the user approves your plan through exit_plan_mode or switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
|
|
270
306
|
|
|
271
307
|
Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
|
|
272
308
|
|
|
@@ -310,41 +346,17 @@
|
|
|
310
346
|
provider: spawn
|
|
311
347
|
toolName: subagent
|
|
312
348
|
modelSelectionSettings: true
|
|
313
|
-
backgroundMode: continuable
|
|
314
349
|
- id: tool-subagent-fork
|
|
315
350
|
name: '@deepseek-ai/dsh-tool-subagent'
|
|
316
351
|
config:
|
|
317
352
|
provider: fork
|
|
318
353
|
toolName: subagent_fork
|
|
319
|
-
backgroundMode: continuable
|
|
320
|
-
- id: tool-subagent-codex
|
|
321
|
-
name: '@deepseek-ai/dsh-tool-subagent'
|
|
322
|
-
disabled: true
|
|
323
|
-
config:
|
|
324
|
-
provider: codex
|
|
325
|
-
toolName: subagent_codex
|
|
326
|
-
backgroundMode: one-shot
|
|
327
|
-
maxDepth: provider-managed
|
|
328
|
-
- id: tool-subagent-claude-code
|
|
329
|
-
name: '@deepseek-ai/dsh-tool-subagent'
|
|
330
|
-
disabled: true
|
|
331
|
-
config:
|
|
332
|
-
provider: claude-code
|
|
333
|
-
toolName: subagent_claude_code
|
|
334
|
-
backgroundMode: one-shot
|
|
335
|
-
maxDepth: provider-managed
|
|
336
354
|
- id: workflow-ptc
|
|
337
355
|
name: '@deepseek-ai/dsh-workflow-ptc'
|
|
338
356
|
config:
|
|
339
357
|
provider: spawn
|
|
340
358
|
- id: tool-workflow
|
|
341
359
|
name: '@deepseek-ai/dsh-tool-workflow'
|
|
342
|
-
- id: tool-ralph
|
|
343
|
-
name: '@deepseek-ai/dsh-tool-ralph'
|
|
344
|
-
disabled: true
|
|
345
|
-
config:
|
|
346
|
-
subagentProvider: spawn
|
|
347
|
-
maxRounds: 64
|
|
348
360
|
- id: tool-ask-user
|
|
349
361
|
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
350
362
|
- id: tool-todo
|
|
@@ -361,3 +373,6 @@
|
|
|
361
373
|
- id: tool-plugin-manager
|
|
362
374
|
name: '@deepseek-ai/dsh-plugin-manager/tools'
|
|
363
375
|
disabled: true
|
|
376
|
+
- id: tool-plugin-market
|
|
377
|
+
name: '@deepseek-ai/dsh-plugin-market/tools'
|
|
378
|
+
disabled: true
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -11,28 +11,56 @@ DSH exposes its capabilities as services. The model-facing tools (`bash`, `read`
|
|
|
11
11
|
`write`, `edit`, `glob`, `grep`) consume those services. They never touch a
|
|
12
12
|
filesystem or a shell directly.
|
|
13
13
|
|
|
14
|
-
This plugin supplies WSL-backed implementations of three of them,
|
|
15
|
-
smaller integrations.
|
|
14
|
+
This plugin supplies WSL-backed implementations of three of them, takes over two
|
|
15
|
+
root-plane services, and adds three smaller integrations.
|
|
16
16
|
|
|
17
17
|
## The three service providers
|
|
18
18
|
|
|
19
19
|
| Service | Class | File | What it does |
|
|
20
20
|
|---|---|---|---|
|
|
21
|
-
| `ctx.shell` | `WslShellExecutor` | [`lib/index.js`](../lib/index.js) | Runs each command
|
|
21
|
+
| `ctx.shell` | `WslShellExecutor` | [`lib/index.js`](../lib/index.js) | Runs each command in the distro's login shell over the resident in-distro agent (the default), wrapped in a distro-side `bwrap` sandbox; while the agent is out, or with `agent: false`, the same command falls back to one `wsl.exe -d <distro> --cd <linux dir> --exec <login shell> -lc <cmd>` process. |
|
|
22
22
|
| `ctx.fs` | `WslFileSystem` | [`lib/index.js`](../lib/index.js) | Serves the file tools from real distro files on the resident in-distro agent, on ext4 — the former Windows-side share substrate is retired (see below). |
|
|
23
|
-
| `ctx.subprocess` | `WslSubprocessRuntime` | [`lib/subprocess.js`](../lib/subprocess.js) | Opens the GUI terminal inside the distro, in the session workspace, and rewrites the file-search
|
|
23
|
+
| `ctx.subprocess` | `WslSubprocessRuntime` | [`lib/subprocess.js`](../lib/subprocess.js) | Opens the GUI terminal inside the distro, in the session workspace, and rewrites the file-search and git-snapshot spawns into the distro (see below). |
|
|
24
24
|
|
|
25
25
|
`WslShellExecutor` extends the shipped `LocalBashExecutor`, and `WslFileSystem`
|
|
26
26
|
extends the shipped `LocalFileSystem`. Only the parts that need a distro are
|
|
27
27
|
overridden.
|
|
28
28
|
|
|
29
|
+
## The two root-plane takeovers
|
|
30
|
+
|
|
31
|
+
Two of the shipped services are session-less — the GUI file tree and the root
|
|
32
|
+
`ctx.fs` — so they cannot be served from an agent preset. The layer replaces
|
|
33
|
+
their shipped rows outright, with subclasses that route by coordinate:
|
|
34
|
+
|
|
35
|
+
| Service | Class | File | What it does |
|
|
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
|
+
| `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
|
+
|
|
40
|
+
Both routes are composition-level for the mirror-image reason the terminal is
|
|
41
|
+
(see below): no session identity reaches them. Each takeover is a top-level
|
|
42
|
+
`disabled:` entry targeting the shipped row plus a bare `insert:` list holding
|
|
43
|
+
the replacement — because a `disabled` written *inside* an insert list is not a
|
|
44
|
+
patch but a duplicate-id data row, and a nested `insert:` is never interpreted at
|
|
45
|
+
all. [`cordis.patch.yml`](../cordis.patch.yml) carries the full row list, and
|
|
46
|
+
`test/composition.test.mjs` runs the file through the real patch algorithm to
|
|
47
|
+
keep the shape honest.
|
|
48
|
+
|
|
49
|
+
One further module ships built but deliberately unwired:
|
|
50
|
+
[`lib/file-reference-wsl.js`](../lib/file-reference-wsl.js), the distro-side
|
|
51
|
+
traversal strategy for `@` completion. It is built and tested, but **not** wired
|
|
52
|
+
into `cordis.patch.yml` — the seam it consumes is an upstream proposal
|
|
53
|
+
([`upstream/rfc-wsl-workspaces.md`](upstream/rfc-wsl-workspaces.md)), and
|
|
54
|
+
reaching the funnels without it would mean subclassing past module-internal
|
|
55
|
+
functions.
|
|
56
|
+
|
|
29
57
|
## The three smaller integrations
|
|
30
58
|
|
|
31
59
|
| Subpath | Class or name | File | What it does |
|
|
32
60
|
|---|---|---|---|
|
|
33
61
|
| `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). |
|
|
34
62
|
| `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. |
|
|
35
|
-
| `dsh-plugin-wsl-env/shell-env` | `wsl-shell-env` | [`lib/shell-env.js`](../lib/shell-env.js) | Contributes `DSH_WSL_DISTRO`, `DSH_WSL_SHELL`, and `
|
|
63
|
+
| `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. |
|
|
36
64
|
|
|
37
65
|
Three modules support them and import no DSH package.
|
|
38
66
|
[`lib/paths.js`](../lib/paths.js) translates paths.
|
|
@@ -50,7 +78,9 @@ The environment is per session, not per process:
|
|
|
50
78
|
| Row | Mounted | Why there |
|
|
51
79
|
|---|---|---|
|
|
52
80
|
| `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 |
|
|
53
|
-
| `subprocess-wsl` | at the app level, called the composition | because no session identity reaches the terminal controller, see below |
|
|
81
|
+
| `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-wsl` | at the app level; the shipped `workspace-files` row is disabled | the GUI file tree is session-less, so it cannot be served from a preset |
|
|
83
|
+
| `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 |
|
|
54
84
|
| `directory-picker-wsl`, `wsl-shell-env`, `auto-preset` | at the app level | they answer questions that are not scoped to a session |
|
|
55
85
|
|
|
56
86
|
The preset mount requires the isolate realms. Without them the registry rejects the
|
|
@@ -71,8 +101,15 @@ does not use it.
|
|
|
71
101
|
|
|
72
102
|
So the layer disables the shipped `subprocess` row and inserts
|
|
73
103
|
`dsh-plugin-wsl-env/subprocess` in its place at the app level. The subclass
|
|
74
|
-
overrides `spawnTerminal`
|
|
75
|
-
|
|
104
|
+
overrides two methods: `spawnTerminal` rewrites a `wsl.exe` launch into
|
|
105
|
+
`wsl.exe -d <distro> --cd <linux dir>`, and `spawn()` rewrites two kinds of
|
|
106
|
+
launches into the distro — the file-search tool's packaged ripgrep (agent-first,
|
|
107
|
+
with the one-shot `wsl.exe --exec` handle as the fallback when the agent is out)
|
|
108
|
+
and git snapshot commands. Both spawn decisions are pure coordinate functions
|
|
109
|
+
(`lib/search-route.js`, `lib/git-route.js`); every other `spawn()` — including
|
|
110
|
+
the one-shot fallback's own `wsl.exe -lc <command>` (the default agent path
|
|
111
|
+
carries the command over the resident instead of spawning) — reaches the shipped
|
|
112
|
+
implementation untouched, as do the pwsh executor and the LSP host.
|
|
76
113
|
|
|
77
114
|
The terminal's execution world follows the session (`hostSessions`, default on):
|
|
78
115
|
a WSL-folder session runs the distro shell, a Windows-folder session runs
|
|
@@ -124,9 +161,11 @@ profile carrying the value is refused at boot, and no file tool crosses the
|
|
|
124
161
|
refuses confined mutations with the bootstrap command, the same closed
|
|
125
162
|
failure the command path has.
|
|
126
163
|
|
|
127
|
-
Writes fence with the same host-side policy
|
|
128
|
-
|
|
129
|
-
|
|
164
|
+
Writes fence with the same host-side policy the command path's sandbox
|
|
165
|
+
describes — `WslFileSystem.checkedTarget`, over the same writable list; the
|
|
166
|
+
command path itself is confined by the distro-side profile and never calls it —
|
|
167
|
+
and the substrate does not change what `sandboxMode` reports. The wire protocol
|
|
168
|
+
the substrate speaks is documented in
|
|
130
169
|
[`agent/wsl-agent.sh`](../agent/wsl-agent.sh); the orchestration layers are
|
|
131
170
|
[`lib/fsio-agent.js`](../lib/fsio-agent.js) (fsio's mechanics over the FS
|
|
132
171
|
frames), [`lib/fs-substrate.js`](../lib/fs-substrate.js) (the provider-shaped
|
|
@@ -148,24 +187,39 @@ sandbox.
|
|
|
148
187
|
|
|
149
188
|
### What the plugin does instead
|
|
150
189
|
|
|
151
|
-
The plugin builds a `bubblewrap` profile on the Windows side and
|
|
152
|
-
|
|
190
|
+
The plugin builds a `bubblewrap` profile on the Windows side and hands the
|
|
191
|
+
resulting argv to the distro — over the resident agent by default, or to
|
|
192
|
+
`wsl.exe --exec` on the one-shot fallback. The confinement is created inside the
|
|
193
|
+
distro, and the argv is the same either way:
|
|
153
194
|
|
|
154
195
|
```text
|
|
155
196
|
wsl.exe -d <distro> --cd <linux dir> --exec bwrap \
|
|
156
197
|
--ro-bind / / --dev /dev --unshare-pid --proc /proc --die-with-parent \
|
|
157
|
-
[--tmpfs /tmp --bind <workspace> <workspace>] -- <shell> -lc <cmd>
|
|
198
|
+
[--tmpfs /tmp --bind <workspace> <workspace>] [--tmpfs /mnt] -- <shell> -lc <cmd>
|
|
158
199
|
```
|
|
159
200
|
|
|
201
|
+
By default the profile wraps the session's resident agent ONCE, under
|
|
202
|
+
`systemd-run --user --scope`, and the command argv is the shell line itself:
|
|
203
|
+
`bwrap … --die-with-parent --bind /tmp /tmp --bind <workspace> <workspace> -- sh agent.sh`.
|
|
204
|
+
There is no `--unshare-pid` and no per-command tmpfs, so background processes
|
|
205
|
+
survive, `/tmp` is the distro's real `/tmp`, and `ps` sees the session. The
|
|
206
|
+
per-command argv above is what `sessionSandbox: false` (and the one-shot
|
|
207
|
+
fallback) still runs.
|
|
208
|
+
|
|
160
209
|
These are the same arguments DSH's Linux runner uses (`dsh-sandbox-local`), so the
|
|
161
210
|
behaviour and the error messages match a Linux host. The function
|
|
162
211
|
`bwrapProfileArgs` in [`lib/bwrap.js`](../lib/bwrap.js) builds them — the
|
|
163
|
-
peer-free builder every confinement site composes from.
|
|
212
|
+
peer-free builder every confinement site composes from. The trailing
|
|
213
|
+
`[--tmpfs /mnt]` is the `maskWindowsDrive` option: when it is on, the Windows
|
|
214
|
+
drive is shadowed by an empty tmpfs in every confined profile, so its files
|
|
215
|
+
cannot be read or exfiltrated and its executables cannot be launched through
|
|
216
|
+
interop. The option is off by default; [CONFIGURATION.md](CONFIGURATION.md) lists
|
|
217
|
+
the row keys, and `lib/sandbox-core.js` states its honest ceiling.
|
|
164
218
|
|
|
165
219
|
| Mode | What a command inside the distro can do |
|
|
166
220
|
|---|---|
|
|
167
221
|
| `read-only` | read the whole distro; a fresh `/dev` is mounted writable, so `/dev/null` and `/dev/shm` work, and nothing else does |
|
|
168
|
-
| `workspace-write` | the above, plus the session workspace is writable
|
|
222
|
+
| `workspace-write` | the above, plus the session workspace is writable; `/tmp` is the distro's real `/tmp` in the session runtime, and a fresh tmpfs in the per-command profile (`sessionSandbox: false`) |
|
|
169
223
|
| `danger-full-access` | no sandbox; used for an approved wider-permission request |
|
|
170
224
|
|
|
171
225
|
`WslFileSystem` checks writes against the same policy and the same writable list,
|
|
@@ -185,6 +239,18 @@ selector read that value.
|
|
|
185
239
|
distro can still run a Windows program through WSL interop, for example
|
|
186
240
|
`/mnt/c/.../*.exe`. Bubblewrap does not govern that process.
|
|
187
241
|
|
|
242
|
+
`maskWindowsDrive` is the one mitigation the profile can offer, and it is a
|
|
243
|
+
narrowing rather than a closure: it takes the drive's data and its executables
|
|
244
|
+
off the table, but `binfmt` interop dispatches on file *content*, so a command
|
|
245
|
+
that can write the workspace can still write a PE there and have it launched as
|
|
246
|
+
a Windows process. Either way the provider reports `partial` — the complete
|
|
247
|
+
closure is distro-level (`[interop] enabled=false` in `wsl.conf`). The mask is
|
|
248
|
+
reported on the result's sandbox fact as `windowsDrive: "masked"` (or
|
|
249
|
+
`"visible"`), but that is this plugin's own extra field: the pinned harness's
|
|
250
|
+
sandbox type and its model-facing notes carry `mode`, `denied`, `enforcement` and
|
|
251
|
+
`runnerFailed` only, so the field reaches the result and the tests, not the
|
|
252
|
+
model.
|
|
253
|
+
|
|
188
254
|
`pnpm run probe:sandbox` demonstrates the boundary on your machine and records it as
|
|
189
255
|
`INFO`.
|
|
190
256
|
|
|
@@ -212,18 +278,37 @@ and `sandboxMode` returns `undefined`.
|
|
|
212
278
|
## Error codes
|
|
213
279
|
|
|
214
280
|
The plugin raises `FsError`s with a code the tool layer maps, and `wsl.exe`'s own
|
|
215
|
-
failures are named as well.
|
|
216
|
-
finds it here.
|
|
281
|
+
failures are named as well. Every `FsError` code the plugin itself raises appears
|
|
282
|
+
below — a model sees the code, and a reader asking what refused them finds it here.
|
|
283
|
+
Codes the shipped host stack raises behind a drive path pass through
|
|
284
|
+
`lib/fs-routing.js` unchanged and are the peer's, not this table's; the GUI file
|
|
285
|
+
tree's own wire refusals are a separate dialect, listed after the table.
|
|
217
286
|
|
|
218
287
|
| Code | Raised by | Meaning | What clears it |
|
|
219
288
|
|---|---|---|---|
|
|
220
289
|
| `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` |
|
|
221
|
-
| `FS_SANDBOX_DENIED` | `WslFileSystem.checkedTarget` | the file-effect policy refused the write: `read-only`, or a target outside the writable roots | a wider permission for that one call, or a session workspace that contains the target |
|
|
290
|
+
| `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 |
|
|
222
291
|
| `FS_OUTSIDE_DISTRO` | `WslFileSystem.worldPath` | `restrictToDistro` is on and the path names **another distro**'s share — a configuration fence, not a sandbox decision | open a session in that distro, or set `restrictToDistro: false`. A wider permission does **not** lift it |
|
|
223
|
-
| `FS_NOT_OBSERVED` | `
|
|
224
|
-
| `FS_STALE_VERSION` | `
|
|
225
|
-
| `FS_IO_ERROR` | `WslFileSystem.watch` | the
|
|
226
|
-
| `FS_NOT_FOUND` | the host filesystem stack | the path does not exist — the ordinary answer
|
|
292
|
+
| `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 |
|
|
293
|
+
| `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 |
|
|
294
|
+
| `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 pinned 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`) |
|
|
295
|
+
| `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 | — |
|
|
296
|
+
| `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 |
|
|
297
|
+
| `FS_NOT_DIRECTORY` | `lib/fsio-agent.js` (`listChildren`) | the target is not a directory, so there are no children to list | list a directory |
|
|
298
|
+
| `FS_NOT_TEXT` | `lib/fsio-agent.js`, `lib/fsio-text.js` | the content is not UTF-8 text: a NUL sample (binary) or a decode failure | not a policy refusal — read text, or use a command for binary content |
|
|
299
|
+
| `FS_TOO_LARGE` | `lib/fsio-agent.js` | the content exceeds the byte cap the caller's operation carries | read a byte range (`WslFileSystem.readByteRange`) instead of the whole file, or raise the cap |
|
|
300
|
+
| `FS_ABORTED` | `lib/fsio-agent.js`, `lib/fsio-text.js` | the caller's signal was aborted before or during the operation | not a refusal — the caller cancelled; a fresh call clears it |
|
|
301
|
+
| `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
|
+
| `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
|
+
|
|
304
|
+
The GUI file tree speaks a second dialect. `WorkspaceFilesWsl` refuses through the
|
|
305
|
+
typert wire's `RemoteError`s, not `FsError`s, so its codes are not in the table
|
|
306
|
+
above: `workspace-file/not-found`, `workspace-file/outside-workspace`,
|
|
307
|
+
`workspace-file/not-directory`, `workspace-file/not-regular-file`,
|
|
308
|
+
`workspace-file/not-text`, and `workspace-file/too-large` (one code for both the
|
|
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.
|
|
227
312
|
|
|
228
313
|
Two failures come from `wsl.exe` itself rather than from a code of ours, and both used
|
|
229
314
|
to be invisible:
|
|
@@ -258,15 +343,35 @@ step with the checkout, and `pnpm run sync:windows` runs that script.
|
|
|
258
343
|
|---|---|---|
|
|
259
344
|
| Style and packaging checks | [`test/style.mjs`](../test/style.mjs) | no |
|
|
260
345
|
| Syntax pass | [`test/syntax.mjs`](../test/syntax.mjs) | no |
|
|
346
|
+
| Build consistency | [`test/build-freshness.mjs`](../test/build-freshness.mjs) | no; it re-runs the real build and byte-compares the committed `lib/`, so it also catches an orphan there |
|
|
347
|
+
| Type check | `tsc --noEmit` over `test/**` and `types/*.d.ts` (`src/` is checked by the build itself) | no |
|
|
261
348
|
| Unit tests | `test/*.test.mjs` | no |
|
|
262
349
|
| Sandbox probe | [`test/probe/sandbox.sh`](../test/probe/sandbox.sh) | no; it applies the profile arguments directly |
|
|
263
350
|
| 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 |
|
|
264
|
-
|
|
|
351
|
+
| 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 |
|
|
352
|
+
| Host probe | [`test/probe/mode.sh`](../test/probe/mode.sh) | Windows Node, no harness |
|
|
265
353
|
| Probe plugins | the `*-probe.mjs` and `*-probe.yml` files in `test/probe/` | loaded by the scripts above |
|
|
266
354
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
355
|
+
`pnpm test` runs the first five rows in order: `lint:style`, `test:syntax`,
|
|
356
|
+
`lint:build`, `lint:types`, `test:unit`.
|
|
357
|
+
[`test/probe/run-all-when-closed.sh`](../test/probe/run-all-when-closed.sh)
|
|
358
|
+
waits for the app to exit and then runs the whole real-machine suite once —
|
|
359
|
+
`--include-fs` prepends `run.sh`.
|
|
360
|
+
|
|
361
|
+
The unit tests come in two kinds: most import only Node builtins, while
|
|
362
|
+
[`test/composition.test.mjs`](../test/composition.test.mjs),
|
|
363
|
+
[`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) and
|
|
366
|
+
[`test/workspace-files-wsl.test.mjs`](../test/workspace-files-wsl.test.mjs) import
|
|
367
|
+
pinned `@deepseek-ai/*` development dependencies (the composition and boot tests run
|
|
368
|
+
the loader's real patch algorithm). One `pnpm install` reproduces that tree before
|
|
369
|
+
the first run; the runtime package itself still ships zero dependencies. CI runs
|
|
370
|
+
`pnpm test` on Linux and Windows, on Node 22 and 24, and `pnpm run test:coverage` on
|
|
371
|
+
Node 24 only — the threshold flags need Node 22.8. Node 20 is absent on purpose:
|
|
372
|
+
`engines` requires `^22.19.0 || >=24.0.0`, the harness host's own floor. The harness
|
|
373
|
+
probes are a manual step, because a hosted runner has no WSL distro and no profile to
|
|
374
|
+
mount.
|
|
270
375
|
|
|
271
376
|
The probe scripts share [`test/probe/env.sh`](../test/probe/env.sh), which derives
|
|
272
377
|
the Windows user, the distro name and the UNC spelling of the checkout from the
|
|
@@ -276,33 +381,74 @@ placeholders that the scripts substitute into generated copies.
|
|
|
276
381
|
|
|
277
382
|
## File layout
|
|
278
383
|
|
|
384
|
+
`lib/` is generated output: every module's source is `src/<name>.ts`, and
|
|
385
|
+
`pnpm run build` emits `lib/<name>.js` plus its `lib/<name>.d.ts`. Never edit
|
|
386
|
+
`lib/` by hand — `lint:build` fails when the committed bytes drift from a real
|
|
387
|
+
build, and it also fails on an orphan in either direction.
|
|
388
|
+
|
|
389
|
+
```text
|
|
390
|
+
src/<name>.ts the sources — edit THESE; each builds to lib/<name>.{js,d.ts}
|
|
391
|
+
agent/wsl-agent.sh the resident in-distro agent (POSIX sh, coreutils only)
|
|
392
|
+
cordis.patch.yml the bundle configuration layer (dsh.bundle), commented
|
|
393
|
+
examples/ one machine-local profile layer, for comparison
|
|
394
|
+
locale/ the Plugins-page display metadata (title/description)
|
|
395
|
+
scripts/ the build (build.mjs), the release helpers
|
|
396
|
+
(check-release-tag.mjs, changelog-section.mjs), and the
|
|
397
|
+
distro-side bootstrap installer (bootstrap.sh)
|
|
398
|
+
test/ unit tests, the style/build/type gates, and the probes
|
|
399
|
+
docs/ this file, the configuration and limitation references,
|
|
400
|
+
the release checklist, and the archived designs
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
The modules, in alphabetical order:
|
|
404
|
+
|
|
279
405
|
```text
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
406
|
+
src/agent-confined.ts the confined residents' factory (one per policy)
|
|
407
|
+
src/agent-errors.ts the error a resident outage rejects with
|
|
408
|
+
src/agent-exec.ts the agent-backed shell execution handle
|
|
409
|
+
src/agent-protocol.ts the wire protocol codecs (pure)
|
|
410
|
+
src/agent-shared.ts the per-distro resident shared by every provider
|
|
411
|
+
src/agent.ts the resident in-distro agent's host side
|
|
412
|
+
src/auto-preset.ts per-session environment selection
|
|
413
|
+
src/bwrap.ts the bwrap command line every confinement site
|
|
414
|
+
assembles (no imports, so a bare checkout can
|
|
415
|
+
assert it)
|
|
416
|
+
src/file-reference-wsl.ts the distro-side `@`-completion traversal — built,
|
|
417
|
+
tested, not yet wired (see above)
|
|
418
|
+
src/fs.ts the ctx.fs subpath entry point
|
|
419
|
+
src/fs-decisions.ts the filesystem provider's policy decisions (pure,
|
|
420
|
+
unit-testable in a bare checkout)
|
|
421
|
+
src/fs-routing.ts WslRoutingFileSystem, the root-plane ctx.fs
|
|
422
|
+
src/fs-substrate.ts the agent substrate's provider-shaped operations
|
|
423
|
+
src/fsio-agent.ts fsio's mechanics over the agent's FS frames
|
|
424
|
+
src/fsio-text.ts the peer's pure text mechanics, replicated pending
|
|
425
|
+
the upstream fsio export (UPSTREAM-FSIO-EXPORT.md)
|
|
426
|
+
src/git-route.ts which git spawns belong inside the distro (pure)
|
|
427
|
+
src/index.ts WslShellExecutor (ctx.shell) and WslFileSystem (ctx.fs)
|
|
428
|
+
src/listing.ts pure directory listing and breadcrumb helpers
|
|
429
|
+
src/paths.ts pure conversion between the three path formats
|
|
430
|
+
src/picker.ts WslDirectoryPicker (ctx.directoryPicker)
|
|
431
|
+
src/ports.ts the listening-port snapshot behind DSH_WSL_PORTS
|
|
432
|
+
src/preset-choice.ts whether a new session adopts the WSL preset (pure)
|
|
433
|
+
src/probe-cache.ts which probe verdicts may be remembered (pure)
|
|
434
|
+
src/sandbox.ts the distro-side bwrap confinement shared by both
|
|
435
|
+
providers, bound to the peer's failure type
|
|
436
|
+
src/sandbox-core.ts the probe-and-confinement core, free of DSH peers
|
|
437
|
+
src/search-exec.ts the agent-backed execution handle for a search
|
|
438
|
+
src/search-route.ts which execution world a search spawn belongs to (pure)
|
|
439
|
+
src/shell.ts the ctx.shell subpath entry point
|
|
440
|
+
src/shell-env.ts registers the DSH_WSL_* environment variables
|
|
441
|
+
src/subprocess.ts WslSubprocessRuntime (ctx.subprocess), the GUI
|
|
442
|
+
terminal window
|
|
443
|
+
src/terminal-activity.ts the distro-side activity probe for idle terminal
|
|
444
|
+
reclamation
|
|
445
|
+
src/terminal-route.ts which execution world a terminal launch belongs to (pure)
|
|
446
|
+
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
|
+
src/wsl.ts wsl.exe interop primitives (no DSH imports)
|
|
306
452
|
```
|
|
307
453
|
|
|
308
454
|
`package.json` publishes a subset of this tree: the `files` list is the contract, and
|
|
@@ -318,6 +464,8 @@ docs/ this file, the configuration and limitation references, the
|
|
|
318
464
|
- [`archive/engineering-record.zh.md`](archive/engineering-record.zh.md) is the
|
|
319
465
|
full engineering record, in Chinese: measured contracts, debugging rounds, and
|
|
320
466
|
the conclusions as they stood then.
|
|
321
|
-
- [`archive/README.md`](archive/README.md)
|
|
322
|
-
|
|
467
|
+
- [`archive/README.md`](archive/README.md) and
|
|
468
|
+
[`archive/README.en.md`](archive/README.en.md) are two English indexes of the
|
|
469
|
+
same three archived files, the second so a reader who does not read Chinese can
|
|
470
|
+
still tell what is there; the records themselves are Chinese.
|
|
323
471
|
- [`README.md`](../README.md) is the only statement of current state.
|