dsh-wsl-tool 1.8.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/LICENSE +21 -0
- package/README.md +318 -0
- package/README.zh-CN.md +304 -0
- package/assets/screenshot-1.png +0 -0
- package/cordis.patch.yml +15 -0
- package/index.js +39 -0
- package/lib/config.js +130 -0
- package/lib/diagnostics.js +150 -0
- package/lib/guard.js +107 -0
- package/lib/paths.js +85 -0
- package/lib/result.js +104 -0
- package/lib/runner.js +273 -0
- package/lib/tools/wsl-env.js +113 -0
- package/lib/tools/wsl-path.js +77 -0
- package/lib/tools/wsl.js +297 -0
- package/package.json +50 -0
- package/screenshots.json +3 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 XINY11451
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# dsh-wsl
|
|
2
|
+
|
|
3
|
+
[English](README.md) | [简体中文](README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
A model-facing **WSL** tool plugin for DeepSeek Harness (DSH). It lets an agent run Linux commands through `wsl.exe` directly — no hand-written `.sh` scripts or `pwsh` wrappers.
|
|
6
|
+
|
|
7
|
+
## Tools
|
|
8
|
+
|
|
9
|
+
The plugin registers three tools:
|
|
10
|
+
|
|
11
|
+
| Tool | Purpose |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `wsl` | Run a Linux command and return `stdout`/`stderr` with exit-code, timeout and truncation markers. |
|
|
14
|
+
| `wsl-path` | Convert between Windows and WSL paths via `wslpath`. |
|
|
15
|
+
| `wsl-env` | Summarize the WSL environment (distros, kernel, cpu, mem, disk). |
|
|
16
|
+
|
|
17
|
+
### `wsl`
|
|
18
|
+
|
|
19
|
+
Runs:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
wsl.exe [-d <distro>] -e bash -lc "cd <workdir> && <command>"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
and returns `stdout`/`stderr` with `[exit code: N]` / `[killed by signal: ...]` /
|
|
26
|
+
`[timed out after Nms; the command was killed]` / `[output truncated: ...]` markers.
|
|
27
|
+
|
|
28
|
+
`<distro>` is the caller's `distro` argument, else `DSH_WSL_DISTRO`, else omitted
|
|
29
|
+
entirely so `wsl.exe` uses the **system default distribution** — that is what makes
|
|
30
|
+
the package portable to a machine that has no `Ubuntu-22.04`.
|
|
31
|
+
|
|
32
|
+
A script longer than the Windows command-line limit (32767 characters) is fed to
|
|
33
|
+
`wsl.exe [-d <distro>] -e bash -ls` on **stdin** instead, so long commands work
|
|
34
|
+
without any size ceiling.
|
|
35
|
+
|
|
36
|
+
Pass `stdin` to write text into the command (the default is `/dev/null`, so an
|
|
37
|
+
interactive command gets EOF), and `runInBackground: true` for work that outlives
|
|
38
|
+
the call: the command becomes a job in the host's registry, which the model then
|
|
39
|
+
reads with the **`job_output`** tool and stops with **`job_kill`** — no extra tool
|
|
40
|
+
is involved. Background jobs need `@deepseek-ai/dsh-tool-jobs` in the preset's
|
|
41
|
+
composition; without it the call fails with that instruction instead of silently
|
|
42
|
+
running in the foreground.
|
|
43
|
+
|
|
44
|
+
### `wsl-path`
|
|
45
|
+
|
|
46
|
+
Converts a path in either direction: `C:\Users\me\a.txt` -> `/mnt/c/Users/me/a.txt`
|
|
47
|
+
or `/home/me/a.txt` -> `\\wsl.localhost\Ubuntu-22.04\home\me\a.txt`. Direction is
|
|
48
|
+
auto-detected from the path, or forced with `direction: 'win' | 'linux'`.
|
|
49
|
+
|
|
50
|
+
### `wsl-env`
|
|
51
|
+
|
|
52
|
+
Returns the distribution list, the probed distro, kernel and architecture, CPU
|
|
53
|
+
count, memory and disk usage, and what the machine can actually **do**:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
distro: Ubuntu-22.04 (system default)
|
|
57
|
+
Linux 6.6.87.2-microsoft-standard-WSL2 x86_64
|
|
58
|
+
nproc: 24
|
|
59
|
+
Ubuntu 22.04.5 LTS · WSL2 · cgroup v2
|
|
60
|
+
systemd: yes · docker: not installed
|
|
61
|
+
GPU: /dev/dxg present (GPU passthrough enabled) · nvidia-smi: GPU 0: NVIDIA GeForce RTX 5070 Laptop GPU
|
|
62
|
+
drives: /mnt/c /mnt/d
|
|
63
|
+
/etc/wsl.conf: [boot];systemd=true;[user];default=xiny; · .wslconfig: not set
|
|
64
|
+
launcher: WSL 版本: 2.6.3.0 · 内核版本: 6.6.87.2-1 · WSLg 版本: 1.0.71 · Windows: 10.0.26200.9457
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
so an agent can decide what is available before running commands: WSL1 vs WSL2,
|
|
68
|
+
whether systemd manages services, the cgroup version (matters for containers),
|
|
69
|
+
GPU passthrough, docker (installed / cli-only / daemon version), which drives are
|
|
70
|
+
mounted, and how `/etc/wsl.conf` and the Windows-side `.wslconfig` are
|
|
71
|
+
configured. Takes an optional `distro`.
|
|
72
|
+
|
|
73
|
+
Every fact is optional and degrades honestly: a probe that cannot run leaves its
|
|
74
|
+
line out, an empty value is stated (`docker: not installed`, `.wslconfig: not
|
|
75
|
+
set`), a failed probe is reported in the summary instead of being dropped, and an
|
|
76
|
+
unknown distro is an error rather than a partial answer. Note that `wsl --version`
|
|
77
|
+
is **localized**, so its labels are passed through as the launcher printed them
|
|
78
|
+
rather than parsed by name; the Direct3D/MSRDC/DXCore versions are omitted.
|
|
79
|
+
|
|
80
|
+
## Install
|
|
81
|
+
|
|
82
|
+
The published npm package is **`dsh-wsl-tool`**, not `dsh-wsl`: the registry
|
|
83
|
+
refuses `dsh-wsl` as too similar to the existing package `is-wsl`, and no token
|
|
84
|
+
or setting overrides that. The repository, the plugin and the market listing keep
|
|
85
|
+
the `dsh-wsl` name. The bundle patch points at its own entry by relative path, so
|
|
86
|
+
the folder under `node_modules` is free to differ — what has to match is the
|
|
87
|
+
specifier you install under.
|
|
88
|
+
|
|
89
|
+
1. Add the package to your DSH profile (`profiles/<profile>/package.json`):
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{ "dependencies": { "dsh-wsl-tool": "file:<path-to-this-repo>" } }
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
or run `dsh plugin add --profile <profile> dsh-wsl-tool` to install from npm,
|
|
96
|
+
or `dsh plugin add --profile <profile> file:<path-to-this-repo>` from a
|
|
97
|
+
checkout (which names the dependency after this package).
|
|
98
|
+
|
|
99
|
+
2. Add a `tool-wsl` row to an agent preset's `agent.cordis.yml`:
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
- id: tool-wsl
|
|
103
|
+
name: 'dsh-wsl-tool'
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`name` is the installed package name — use your own dependency key if you
|
|
107
|
+
installed under a different one.
|
|
108
|
+
|
|
109
|
+
3. Restart DSH.
|
|
110
|
+
|
|
111
|
+
## `wsl` parameters
|
|
112
|
+
|
|
113
|
+
| Param | Required | Type | Notes |
|
|
114
|
+
|---|---|---|---|
|
|
115
|
+
| `command` | yes | string | Linux command to execute |
|
|
116
|
+
| `description` | yes | string | short UI label |
|
|
117
|
+
| `workdir` | no | string | Linux path (`/home/me`, `~/src`) or Windows path, default `~` |
|
|
118
|
+
| `timeoutMs` | no | number | timeout in milliseconds, default 600000 (10 min); the process is killed and the result marked as timed out |
|
|
119
|
+
| `distro` | no | string | WSL distribution; defaults to the system default distribution |
|
|
120
|
+
| `env` | no | object | extra environment variables to export (keys must be valid shell names) |
|
|
121
|
+
| `stdin` | no | string | text written to the command stdin (UTF-8) before it runs |
|
|
122
|
+
| `runInBackground` | no | boolean | run as a job and return its id immediately; read with `job_output`, stop with `job_kill` |
|
|
123
|
+
| `allowDangerous` | no | boolean | set `true` to run destructive commands |
|
|
124
|
+
| `translatePaths` | no | boolean | default `true`; set `false` to pass `command` through verbatim |
|
|
125
|
+
|
|
126
|
+
## Configuration
|
|
127
|
+
|
|
128
|
+
| Environment variable | Default | Effect |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| `DSH_WSL_DISTRO` | (system default) | Pin the distribution for every call. |
|
|
131
|
+
| `DSH_WSL_TIMEOUT_MS` | `600000` | Default deadline for a model-issued command; `timeoutMs` overrides it per call. |
|
|
132
|
+
| `DSH_WSL_MAX_TIMEOUT_MS` | `86400000` | Ceiling for a per-call `timeoutMs`, mirroring the platform shell tools' `maxTimeoutMs`. The default deadline obeys it too. |
|
|
133
|
+
| `DSH_WSL_WORKDIR` | `home` | Where a call starts without a `workdir`: `home` (the Linux `~`), `session` (the session working directory, i.e. `/mnt/<drive>/...` for a Windows checkout), or any explicit path. |
|
|
134
|
+
| `DSH_WSL_MAX_OUTPUT_BYTES` | `65536` | Per-stream in-memory window (1 KiB – 8 MiB). Also raises the spill ceiling when set above 64 MiB. |
|
|
135
|
+
|
|
136
|
+
An unparsable or out-of-range value falls back to the default: one bad variable
|
|
137
|
+
must not take all three tools down. Values are read once per mount, so a change
|
|
138
|
+
takes effect on restart.
|
|
139
|
+
|
|
140
|
+
## Notes
|
|
141
|
+
|
|
142
|
+
- Each call runs in a fresh shell — no cwd/variables/functions persist between calls.
|
|
143
|
+
- **stdin is `/dev/null` unless you pass `stdin`.** An interactive command (`read`,
|
|
144
|
+
`cat`, a `sudo` password prompt without `-S`) otherwise gets EOF immediately and
|
|
145
|
+
cannot wait for input; nothing in this plugin can prompt. A password passed via
|
|
146
|
+
`stdin` is recorded in the session transcript.
|
|
147
|
+
- The default `workdir` is `~`; set `DSH_WSL_WORKDIR=session` to start in the
|
|
148
|
+
session's working directory instead (see Configuration).
|
|
149
|
+
- The distro is the caller's `distro`, else `DSH_WSL_DISTRO`, else the system
|
|
150
|
+
default; there is no hardcoded fallback name.
|
|
151
|
+
- An unknown distro is reported as a clear error (`distribution "X" is not registered`) instead of a raw `-1` exit code, and any other launcher failure (`Wsl/Service/WSL_E_*`) is surfaced with its code rather than passed off as the command's own exit status.
|
|
152
|
+
- **A background job outlives the call.** `runInBackground: true` returns a job id (`wsl-N`) immediately and registers the work with the host's job registry, so `job_output` reads it (with the same markers as a foreground call) and `job_kill` stops it. The 10-minute default deadline does **not** apply in the background; an explicit `timeoutMs` still does. A non-zero exit is reported as `completed` with the exit code in the detail, exactly like the foreground rendering.
|
|
153
|
+
- Windows paths in `command` and `workdir` are translated to `/mnt/...` automatically:
|
|
154
|
+
- `C:\Users\me\a.txt` -> `/mnt/c/Users/me/a.txt`, and paths containing spaces work in either slash style and with several paths on one line: `C:\Program Files\Git`, `C:/Program Files/Git`, `C:\Program Files (x86)\Steam` and `cp C:\a.txt D:\b.txt` (both paths are translated) all behave;
|
|
155
|
+
- `\\wsl.localhost\<distro>\home\x` and `\\wsl$\<distro>\home\x` -> `/home/x`;
|
|
156
|
+
- text that only *looks* like a drive path is left alone: a single lowercase letter followed by `/` (`a:/b`), a drive letter inside another expression (`sed "s/C:\x/y/"`), and a drive-like segment inside a URL;
|
|
157
|
+
- set `translatePaths: false` when the path belongs to a **Windows** program launched through interop — WSL does not translate `/mnt/c/...` back, so `notepad.exe C:\file.txt` needs its original spelling. This affects `command` only; `workdir` is always translated.
|
|
158
|
+
- A `~` in `workdir` or in a `wsl-path` argument is expanded by the shell (`~/my dir` works). Any other path is single-quoted, so `$VAR` inside a path is **not** expanded.
|
|
159
|
+
- Output is capped at 64 KiB per stream. The tail is kept and the marker names the spill file holding the **complete** stream, so nothing is unrecoverable:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
[stdout truncated: at most the last 65536 of 1288895 bytes were kept; full stream: C:\...\stdout.log]
|
|
163
|
+
```
|
|
164
|
+
- **Host shell facts are forwarded into the distro**, because WSL does not pass Windows environment variables across on its own: `DSH_SESSION_ID`, `DSH_SHELL` and `DSH_HOME` (translated to its `/mnt/...` view) are exported ahead of the command, so a script sees the same session facts the platform's own shell tools inject. They are resolved **per call** from the host's `shellEnv` registry — the same source the other shell tools read — because they are session-scoped, not host constants; reading the host's own `process.env` finds nothing and silently forwards nothing. **`DSH_WEB_URL` is deliberately not forwarded** — it is a `127.0.0.1` URL for the Windows-side server, and in the default NAT networking mode WSL cannot reach Windows loopback (measured: HTTP 000 on both `127.0.0.1` and the host IP, which the server does not bind either). An explicit `env` entry always overrides a forwarded one.
|
|
165
|
+
- `timeoutMs` defaults to 10 minutes so a wedged `wsl.exe` cannot hang the call forever; pass a larger value for genuinely long work, up to the `DSH_WSL_MAX_TIMEOUT_MS` ceiling (24 h by default) — a slip of the keyboard cannot mean "never time out". The value reported back is always the deadline actually armed. The timeout also kills the Linux-side process (the provider uses `taskkill /T /F` on Windows).
|
|
166
|
+
- `wsl-env` also states where the session's own files live, and whether that is a Windows drive mount:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
workspace: /mnt/d/DSHworkarea (Windows drive mount /mnt/d — builds, installs and git are much slower here; prefer a path under /home when it matters)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
It is worth believing. Measured on the author's machine: a 128 MB sequential write ran at ~2.1 GB/s on ext4 against ~247 MB/s on `/mnt/d`, and creating 400 small files took under 10 ms against 0.72 s.
|
|
173
|
+
- Destructive commands are refused unless the call passes `allowDangerous: true`:
|
|
174
|
+
- **any recursive delete** — `rm -r`, `rm -rf`, `rm -r -f`, `rm -R --force`, `rm --recursive` — because with stdin on `/dev/null` nothing prompts, so `rm -r tree` deletes silently. Each `rm` invocation is judged on its own command segment, so `rm a -f; rm b -r` cannot combine into a pass;
|
|
175
|
+
- `dd` onto a block device, `mkfs`, partitioning/wiping tools (`fdisk`, `parted`, `wipefs`, `mkswap`, …), power control (`shutdown`, `reboot`, `systemctl reboot`, …), redirection onto a block device, and fork bombs;
|
|
176
|
+
- the guard tolerates the ways a command word can be spelled (`sudo rm -r -f`, `bash -c "rm -rf /"`, `find . -exec rm -rf {} +`, `rm$IFS-rf`, `\rm -rf`, `$(which rm) -rf`) but matches device/power tools at **command position**, so inspecting them is fine: `man fdisk`, `grep -rn reboot /var/log/syslog` and `echo "the mkfs tool formats disks"` all run.
|
|
177
|
+
- Repeated launcher noise is stripped from stderr: the localhost-proxy warning and procps' `screen size is bogus` line.
|
|
178
|
+
- Uses `wsl.exe -e` (`--exec`) so quoting and `$VAR` expansion behave like a normal shell; the default `--` pass-through mangles single quotes and variables.
|
|
179
|
+
|
|
180
|
+
## Sandboxing
|
|
181
|
+
|
|
182
|
+
DSH's file sandbox is enforced at two points: the shell executors
|
|
183
|
+
(`@deepseek-ai/dsh-bash-sandbox`, `-pwsh-sandbox`, which wrap the exact argv
|
|
184
|
+
through `ctx.sandbox`) and the filesystem service (`@deepseek-ai/dsh-fs-sandbox`,
|
|
185
|
+
a policy fence on the two mutations). **`wsl` is in neither.** It spawns
|
|
186
|
+
`wsl.exe` through the host `subprocess` service, below that layer, so a
|
|
187
|
+
`workspace-write` policy does not confine it: it can write anywhere the Linux
|
|
188
|
+
side can, and anywhere under `/mnt/<drive>` that Windows permits.
|
|
189
|
+
|
|
190
|
+
That is not a gap this plugin can close by "joining" the sandbox. On Windows the
|
|
191
|
+
sandbox resolves to an ACL / restricted-token runner, while the file work happens
|
|
192
|
+
inside the Linux kernel: a write to `/home/...` touches the distro's own
|
|
193
|
+
filesystem image, which no Windows token constrains, and a write to `/mnt/c/...`
|
|
194
|
+
travels through the filesystem bridge, where ACL enforcement is not something to
|
|
195
|
+
rely on. Wrapping `wsl.exe` would confine the launcher, not the writes — and a
|
|
196
|
+
false sense of isolation is worse than a documented absence. (The platform's own
|
|
197
|
+
`dsh-fs-sandbox` is candid about its own limits too: "containment, not a security
|
|
198
|
+
boundary".)
|
|
199
|
+
|
|
200
|
+
What `wsl` does have is the destructive-command guard described above: a
|
|
201
|
+
deterministic refusal list, not a kernel boundary. Treat this tool as able to
|
|
202
|
+
touch anything your WSL installation can, and grant it accordingly.
|
|
203
|
+
|
|
204
|
+
## Install from the plugin list
|
|
205
|
+
|
|
206
|
+
The package declares a `dsh.bundle` manifest (see `package.json`), so once the
|
|
207
|
+
repository is listed it can be installed by name, e.g.
|
|
208
|
+
`dsh plugin add dsh-wsl-tool`, and storefronts will offer it for one-click
|
|
209
|
+
install. Installing from a local path (`file:`) as shown above keeps working
|
|
210
|
+
either way.
|
|
211
|
+
|
|
212
|
+
## How it works
|
|
213
|
+
|
|
214
|
+
The plugin is a cordis module that injects the host-plane `tools` and
|
|
215
|
+
`subprocess` registries. `index.js` is only the entry point; the implementation
|
|
216
|
+
is split by concern:
|
|
217
|
+
|
|
218
|
+
| Module | Responsibility |
|
|
219
|
+
|---|---|
|
|
220
|
+
| `lib/config.js` | Defaults and environment overrides, resolved once per mount. |
|
|
221
|
+
| `lib/paths.js` | Shell quoting and Windows -> WSL path translation. |
|
|
222
|
+
| `lib/guard.js` | The destructive-command rules. |
|
|
223
|
+
| `lib/result.js` | Launcher-noise filters, truncation facts, marker rendering. |
|
|
224
|
+
| `lib/diagnostics.js` | The `wsl-env` capability probe, its parser and its lines. |
|
|
225
|
+
| `lib/runner.js` | The single spawn path plus launcher-error classification. |
|
|
226
|
+
| `lib/tools/*.js` | The three tool definitions (schema, execute, presentCall). |
|
|
227
|
+
|
|
228
|
+
- `apply()` resolves the configuration and registers three tools, each with a
|
|
229
|
+
JSON-schema parameter definition, an output schema, a `render` hook and an
|
|
230
|
+
async `execute`.
|
|
231
|
+
- Every call goes through one spawn path (`runner.runWsl`): `wsl.exe -d <distro>
|
|
232
|
+
-e bash -lc "<exports; cd workdir && command>"` through the host `subprocess`
|
|
233
|
+
service, with stdout/stderr capped at the configured window (spilling to disk
|
|
234
|
+
up to 64 MiB), a 3 s grace period after abort, and a deadline that defaults to
|
|
235
|
+
10 minutes. The plugin's own probes get a 30 s ceiling so a wedged WSL service
|
|
236
|
+
cannot hang a tool call forever.
|
|
237
|
+
- `env` entries are exported at the front of the command so they reach the
|
|
238
|
+
Linux side reliably; Windows drive paths are rewritten to `/mnt/...` before
|
|
239
|
+
the command is built.
|
|
240
|
+
- Output is returned as `{ exitCode, signal, timedOut, timeoutMs, truncated,
|
|
241
|
+
stdout, stderr, stdoutTotalBytes, stdoutDroppedBytes, stderrTotalBytes,
|
|
242
|
+
stderrDroppedBytes, stdoutSpillPath, stderrSpillPath, jobId }`; the `render` hook
|
|
243
|
+
formats it into text with the markers listed above. The truncation marker
|
|
244
|
+
quotes the window size rather than a count derived from the decoded text,
|
|
245
|
+
which can be off by a byte or two when the window starts inside a multi-byte
|
|
246
|
+
character.
|
|
247
|
+
- `jobId` is set only by a background start; every other path returns `null`, so
|
|
248
|
+
the declared shape holds for both.
|
|
249
|
+
- Launching and settling are separate steps (`runner.launch` / `settle`) because
|
|
250
|
+
a background job has to hand the registry a synchronous `cancel`/`done` pair
|
|
251
|
+
while a foreground call simply awaits the same settle. A cancelled job maps the
|
|
252
|
+
provider's early-termination rejection onto `killed`, since `JobHooks.done`
|
|
253
|
+
must never reject.
|
|
254
|
+
- A destructive-command guard splits the command into `;`/`&`/`|`/newline
|
|
255
|
+
segments, judges each `rm` invocation on its own flags, and matches the
|
|
256
|
+
device/power tools at command position before dispatch.
|
|
257
|
+
|
|
258
|
+
The plugin publishes no services of its own, so it sits loose in an agent
|
|
259
|
+
preset without a realm.
|
|
260
|
+
|
|
261
|
+
## Development
|
|
262
|
+
|
|
263
|
+
The plugin is plain ESM with no build step or runtime dependencies outside the
|
|
264
|
+
DSH host plane. Iterate by pointing a profile dependency at the checkout:
|
|
265
|
+
|
|
266
|
+
```json
|
|
267
|
+
{ "dependencies": { "dsh-wsl-tool": "file:/path/to/dsh-wsl" } }
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
then restart DSH and exercise the tools from a session that uses a preset
|
|
271
|
+
containing the `tool-wsl` row.
|
|
272
|
+
|
|
273
|
+
### Tests
|
|
274
|
+
|
|
275
|
+
```sh
|
|
276
|
+
npm test # 260+ checks against real WSL, with a shim standing in for ctx.subprocess
|
|
277
|
+
npm run test:real # the same checks against the REAL provider, plus the seam-fact suite
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
`npm test` substitutes only `ctx.subprocess`, with a shim that reproduces the
|
|
281
|
+
seam's bounded tail windows, spill files and termination ladder, and drives the
|
|
282
|
+
three tools against the real WSL installation. It covers path translation,
|
|
283
|
+
workdir quoting, the destructive guard, distro selection, exit-code/timeout/
|
|
284
|
+
truncation markers, `wsl-path`, `wsl-env`, argument validation, configuration
|
|
285
|
+
parsing, launcher-error classification, the returned shape against each declared
|
|
286
|
+
`output.schema`, and a token budget for the model-facing catalog.
|
|
287
|
+
|
|
288
|
+
`npm run test:real` runs that same suite against `LocalSubprocessRuntime` — the
|
|
289
|
+
shim must not drift from the real seam — and then `test/real-seam.mjs`, which
|
|
290
|
+
checks the facts a shim cannot vouch for (that `readFrom(0).nextOffset` is the
|
|
291
|
+
whole-stream total, that the spill file holds the complete stream, that a
|
|
292
|
+
timeout really kills the Linux side of `wsl.exe`) and validates every published
|
|
293
|
+
schema with DSH's own `assertSupportedJsonSchema`. It needs a DSH installation:
|
|
294
|
+
|
|
295
|
+
```sh
|
|
296
|
+
DSH_SUBPROCESS_LOCAL=/path/to/dsh/node_modules npm run test:real
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Syncing a profile
|
|
300
|
+
|
|
301
|
+
A `file:` dependency is a **copy**, so editing this checkout does not change what
|
|
302
|
+
DSH loads. After any change:
|
|
303
|
+
|
|
304
|
+
```sh
|
|
305
|
+
npm run sync # copies into ~/.dsh/profiles/web/node_modules/dsh-wsl
|
|
306
|
+
npm run sync -- /path/to/profiles/<profile>/node_modules/<your-key>
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
The target must be the folder your profile's dependency key created (the default
|
|
310
|
+
above is this checkout's own key); the plugin loads its entry by relative path, so
|
|
311
|
+
the folder name itself never matters.
|
|
312
|
+
|
|
313
|
+
then restart DSH — the plugin is imported once at load.
|
|
314
|
+
|
|
315
|
+
## Listing
|
|
316
|
+
|
|
317
|
+
The repository carries the `dsh-plugin` topic and is listed under the `wsl`
|
|
318
|
+
category of the awesome-dsh-plugin community list.
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# dsh-wsl
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 简体中文
|
|
4
|
+
|
|
5
|
+
面向模型(model-facing)的 **WSL** 工具插件,用于 DeepSeek Harness(DSH)。它让智能体直接通过 `wsl.exe` 执行 Linux 命令——无需手写 `.sh` 脚本或 `pwsh` 包装。
|
|
6
|
+
|
|
7
|
+
## 工具
|
|
8
|
+
|
|
9
|
+
插件注册三个工具:
|
|
10
|
+
|
|
11
|
+
| 工具 | 用途 |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `wsl` | 执行 Linux 命令,返回带退出码、超时与截断标记的 `stdout`/`stderr` |
|
|
14
|
+
| `wsl-path` | 通过 `wslpath` 在 Windows 与 WSL 路径间互转 |
|
|
15
|
+
| `wsl-env` | 汇总 WSL 环境(发行版、内核、CPU、内存、磁盘) |
|
|
16
|
+
|
|
17
|
+
### `wsl`
|
|
18
|
+
|
|
19
|
+
执行形式:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
wsl.exe [-d <distro>] -e bash -lc "cd <workdir> && <command>"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
返回 `stdout`/`stderr`,并附加 `[exit code: N]` / `[killed by signal: ...]` /
|
|
26
|
+
`[timed out after Nms; the command was killed]` / `[output truncated: ...]` 标记。
|
|
27
|
+
|
|
28
|
+
`<distro>` 取调用参数 `distro`,其次 `DSH_WSL_DISTRO`,都没有时**完全不传 `-d`**,
|
|
29
|
+
由 `wsl.exe` 使用**系统默认发行版**——这正是本插件能装到没有 `Ubuntu-22.04` 的机器上的原因。
|
|
30
|
+
|
|
31
|
+
超过 Windows 命令行上限(32767 字符)的脚本会改为通过 **stdin** 交给
|
|
32
|
+
`wsl.exe [-d <distro>] -e bash -ls` 执行,因此长命令没有体积上限。
|
|
33
|
+
|
|
34
|
+
传 `stdin` 可以把文本喂给命令(默认是 `/dev/null`,交互式命令会立刻 EOF);传
|
|
35
|
+
`runInBackground: true` 则把命令交给宿主的任务注册表,模型随后用**已有的**
|
|
36
|
+
**`job_output`** 工具读它、用 **`job_kill`** 停它——不需要任何新工具。后台任务需要
|
|
37
|
+
preset 组合里有 `@deepseek-ai/dsh-tool-jobs`;没有时会直接报错说明,而不是悄悄退化成
|
|
38
|
+
前台执行。
|
|
39
|
+
|
|
40
|
+
### `wsl-path`
|
|
41
|
+
|
|
42
|
+
双向转换路径:`C:\Users\me\a.txt` → `/mnt/c/Users/me/a.txt`,或
|
|
43
|
+
`/home/me/a.txt` → `\\wsl.localhost\Ubuntu-22.04\home\me\a.txt`。方向自动识别,
|
|
44
|
+
也可用 `direction: 'win' | 'linux'` 强制指定。
|
|
45
|
+
|
|
46
|
+
### `wsl-env`
|
|
47
|
+
|
|
48
|
+
返回发行版列表、被探测的发行版、内核与架构、CPU 数、内存与磁盘占用,以及这台机器
|
|
49
|
+
**实际能做什么**:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
distro: Ubuntu-22.04 (system default)
|
|
53
|
+
Linux 6.6.87.2-microsoft-standard-WSL2 x86_64
|
|
54
|
+
nproc: 24
|
|
55
|
+
Ubuntu 22.04.5 LTS · WSL2 · cgroup v2
|
|
56
|
+
systemd: yes · docker: not installed
|
|
57
|
+
GPU: /dev/dxg present (GPU passthrough enabled) · nvidia-smi: GPU 0: NVIDIA GeForce RTX 5070 Laptop GPU
|
|
58
|
+
drives: /mnt/c /mnt/d
|
|
59
|
+
/etc/wsl.conf: [boot];systemd=true;[user];default=xiny; · .wslconfig: not set
|
|
60
|
+
launcher: WSL 版本: 2.6.3.0 · 内核版本: 6.6.87.2-1 · WSLg 版本: 1.0.71 · Windows: 10.0.26200.9457
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
让智能体在动手前就知道有什么可用:WSL1 还是 WSL2、服务是否由 systemd 托管、cgroup 版本
|
|
64
|
+
(容器相关)、GPU 直通、docker(未装/只有 CLI/守护进程版本)、挂载了哪些盘,以及
|
|
65
|
+
`/etc/wsl.conf` 与 Windows 侧 `.wslconfig` 的配置。可传可选参数 `distro`。
|
|
66
|
+
|
|
67
|
+
每一项都是可选的,且如实降级:探针没跑成的行会被省略,读到了但为空的值会写明
|
|
68
|
+
(`docker: not installed`、`.wslconfig: not set`),探测失败会写进摘要而不是被丢掉,
|
|
69
|
+
发行版不存在则直接报错,而不是给出一份残缺答案。注意 `wsl --version` 输出是**本地化**的,
|
|
70
|
+
因此其标签按启动器原样透传、不按名称解析;Direct3D/MSRDC/DXCore 版本作为噪声被省略。
|
|
71
|
+
|
|
72
|
+
## 安装
|
|
73
|
+
|
|
74
|
+
发布到 npm 的包名是 **`dsh-wsl-tool`**,不是 `dsh-wsl`:registry 判定 `dsh-wsl` 与既有包
|
|
75
|
+
`is-wsl` 过于相似而拒绝,换 token 或改设置都无法绕过。仓库名、插件名与市场条目仍沿用
|
|
76
|
+
`dsh-wsl`。组合包的 patch 用相对路径指向自身入口,因此 `node_modules` 下的文件夹名可以
|
|
77
|
+
不同——必须一致的是你安装时使用的那个名字。
|
|
78
|
+
|
|
79
|
+
1. 将本包加入 DSH 的 profile(`profiles/<profile>/package.json`):
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{ "dependencies": { "dsh-wsl-tool": "file:<path-to-this-repo>" } }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
或运行 `dsh plugin add --profile <profile> dsh-wsl-tool` 从 npm 安装,或用
|
|
86
|
+
`dsh plugin add --profile <profile> file:<path-to-this-repo>` 从本地检出安装
|
|
87
|
+
(依赖名会取本包自身的名字)。
|
|
88
|
+
|
|
89
|
+
2. 在某个 agent preset 的 `agent.cordis.yml` 中加入 `tool-wsl` 行:
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
- id: tool-wsl
|
|
93
|
+
name: 'dsh-wsl-tool'
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`name` 即安装后的包名;若你用了别的依赖名,就填那个名字。
|
|
97
|
+
|
|
98
|
+
3. 重启 DSH。
|
|
99
|
+
|
|
100
|
+
## `wsl` 参数
|
|
101
|
+
|
|
102
|
+
| 参数 | 必填 | 类型 | 说明 |
|
|
103
|
+
|---|---|---|---|
|
|
104
|
+
| `command` | 是 | string | 要执行的 Linux 命令 |
|
|
105
|
+
| `description` | 是 | string | 简短的界面说明文字 |
|
|
106
|
+
| `workdir` | 否 | string | Linux 路径(`/home/me`、`~/src`)或 Windows 路径,默认 `~` |
|
|
107
|
+
| `timeoutMs` | 否 | number | 超时毫秒数,默认 600000(10 分钟);到点后杀进程并把结果标记为超时 |
|
|
108
|
+
| `distro` | 否 | string | WSL 发行版;默认使用系统默认发行版 |
|
|
109
|
+
| `env` | 否 | object | 要导出的额外环境变量(键必须是合法 shell 变量名) |
|
|
110
|
+
| `stdin` | 否 | string | 在命令运行前写入其 stdin 的文本(UTF-8) |
|
|
111
|
+
| `runInBackground` | 否 | boolean | 作为后台任务运行并立即返回任务 id;用 `job_output` 读、`job_kill` 停 |
|
|
112
|
+
| `allowDangerous` | 否 | boolean | 置 `true` 才允许执行危险命令 |
|
|
113
|
+
| `translatePaths` | 否 | boolean | 默认 `true`;置 `false` 时 `command` 原样传入,不做路径改写 |
|
|
114
|
+
|
|
115
|
+
## 配置
|
|
116
|
+
|
|
117
|
+
| 环境变量 | 默认值 | 作用 |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| `DSH_WSL_DISTRO` | (系统默认) | 为所有调用固定发行版 |
|
|
120
|
+
| `DSH_WSL_TIMEOUT_MS` | `600000` | 模型命令的默认超时;单次调用可用 `timeoutMs` 覆盖 |
|
|
121
|
+
| `DSH_WSL_MAX_TIMEOUT_MS` | `86400000` | 单次 `timeoutMs` 的上限,对齐平台 shell 工具的 `maxTimeoutMs`;默认超时也受它约束 |
|
|
122
|
+
| `DSH_WSL_MAX_OUTPUT_BYTES` | `65536` | 每条流的内存窗口(1 KiB – 8 MiB);设到 64 MiB 以上时也会抬高落盘上限 |
|
|
123
|
+
| `DSH_WSL_WORKDIR` | `home` | 不传 `workdir` 时的起点:`home`(Linux 的 `~`)、`session`(会话工作目录,Windows 检出对应 `/mnt/<盘>/...`),或任意显式路径 |
|
|
124
|
+
|
|
125
|
+
无法解析或越界的值会回退到默认值——一个写错的环境变量不该让三个工具一起挂掉。
|
|
126
|
+
配置在挂载时读取一次,改动需重启 DSH 生效。
|
|
127
|
+
|
|
128
|
+
## 注意事项
|
|
129
|
+
|
|
130
|
+
- 每次调用都在全新的 shell 中执行——cwd / 变量 / 函数不会在调用间保留。
|
|
131
|
+
- **stdin 默认是 `/dev/null`,除非传 `stdin`**:交互式命令(`read`、`cat`、不带 `-S`
|
|
132
|
+
的 `sudo` 密码提示)否则会立刻收到 EOF,无法等待输入;本插件不会、也无法弹出任何提示。
|
|
133
|
+
通过 `stdin` 传的密码会被记进会话记录。
|
|
134
|
+
- `workdir` 默认 `~`;设 `DSH_WSL_WORKDIR=session` 可改为从会话工作目录开始(见"配置")。
|
|
135
|
+
- 发行版取调用参数 → `DSH_WSL_DISTRO` → 系统默认,代码里不再硬编码兜底名称。
|
|
136
|
+
- 发行版不存在时会给出明确报错(`distribution "X" is not registered`),而不是
|
|
137
|
+
一个原始 `-1` 退出码;其他启动器错误(`Wsl/Service/WSL_E_*`)也会带错误码上报,
|
|
138
|
+
不会被当成命令自身的退出状态。
|
|
139
|
+
- **后台任务可以活得比这次调用久**:`runInBackground: true` 立即返回任务 id(`wsl-N`)
|
|
140
|
+
并把工作登记进宿主任务注册表,`job_output` 读它(标记与前台一致)、`job_kill` 停它。
|
|
141
|
+
后台模式下 10 分钟默认超时**不适用**,但显式 `timeoutMs` 仍然生效;非零退出与前台一样
|
|
142
|
+
报成 `completed` 并把退出码写进 detail。
|
|
143
|
+
- `command` 与 `workdir` 中的 Windows 路径会自动转换为 `/mnt/...`:
|
|
144
|
+
- `C:\Users\me\a.txt` → `/mnt/c/Users/me/a.txt`;含空格、括号的路径与一行多个路径都支持
|
|
145
|
+
(`C:\Program Files\Git`、`C:/Program Files/Git`、`C:\Program Files (x86)\Steam`、
|
|
146
|
+
`cp C:\a.txt D:\b.txt` 两个路径都会转换);
|
|
147
|
+
- `\\wsl.localhost\<发行版>\home\x` 与 `\\wsl$\<发行版>\home\x` → `/home/x`;
|
|
148
|
+
- 只是"看起来像"盘符的文本不会被动:单个小写字母后跟 `/`(如 `a:/b`)、其他表达式里的
|
|
149
|
+
盘符(如 `sed "s/C:\x/y/"`)、URL 里的疑似盘符段;
|
|
150
|
+
- 当路径要交给**Windows 程序**(经 interop 调用)时请传 `translatePaths: false`:
|
|
151
|
+
WSL 不会把 `/mnt/c/...` 反向翻译,`notepad.exe C:\file.txt` 需要原始写法。
|
|
152
|
+
该开关只影响 `command`;`workdir` 始终会被转换。
|
|
153
|
+
- `workdir` 与 `wsl-path` 参数中的 `~` 会被 shell 展开(`~/my dir` 可用)。其余路径
|
|
154
|
+
一律单引号包裹,因此路径里的 `$VAR` **不会**展开。
|
|
155
|
+
- 每条流输出上限 64 KiB:保留尾部,标记中会给出保存**完整**输出的落盘文件路径,
|
|
156
|
+
信息不会不可恢复:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
[stdout truncated: at most the last 65536 of 1288895 bytes were kept; full stream: C:\...\stdout.log]
|
|
160
|
+
```
|
|
161
|
+
- `timeoutMs` 默认 10 分钟,避免卡死的 `wsl.exe` 永久挂住调用;需要长时间运行的命令可以传更大的值,
|
|
162
|
+
但上限是 `DSH_WSL_MAX_TIMEOUT_MS`(默认 24 小时)——手滑不会变成"永不超时"。结果里回报的始终是
|
|
163
|
+
**实际生效**的那个期限。到点会连 Linux 侧进程一起杀掉(Windows 上由 provider 使用 `taskkill /T /F`)。
|
|
164
|
+
- **宿主 shell 的环境事实会转发进发行版**(WSL 默认不跨边界传 Windows 环境变量):`DSH_SESSION_ID`、
|
|
165
|
+
`DSH_SHELL`、以及翻译成 `/mnt/...` 形式的 `DSH_HOME` 会在命令前 `export`,脚本因此能看到与平台自带
|
|
166
|
+
shell 工具一致的会话事实。这些值**按次**从宿主的 `shellEnv` 注册表解析(与其他 shell 工具同一个来源),
|
|
167
|
+
因为它们属于会话而非宿主常量——直接读宿主 `process.env` 会什么都读不到、静默地什么都不转发。
|
|
168
|
+
**`DSH_WEB_URL` 故意不转发**——它是 Windows 侧服务的 `127.0.0.1` 地址,
|
|
169
|
+
而默认 NAT 模式下 WSL 访问不到 Windows 回环(实测 `127.0.0.1` 与主机 IP 均返回 HTTP 000,服务本身
|
|
170
|
+
也只绑回环),转进去只会给一个打不开的 URL。显式传入的 `env` 条目总是覆盖转发值。
|
|
171
|
+
- `wsl-env` 还会报出**会话文件所在的位置**以及它是否落在 Windows 盘挂载上:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
workspace: /mnt/d/DSHworkarea (Windows drive mount /mnt/d — builds, installs and git are much slower here; prefer a path under /home when it matters)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
这个提示值得当真。作者机器实测:128 MB 顺序写在 ext4 上约 **2.1 GB/s**,在 `/mnt/d` 上约 **247 MB/s**;
|
|
178
|
+
创建 400 个小文件 ext4 **不到 10 ms**,`/mnt/d` 要 **0.72 s**。
|
|
179
|
+
- 危险命令默认被拒绝,除非调用时传 `allowDangerous: true`:
|
|
180
|
+
- **任何递归删除**——`rm -r`、`rm -rf`、`rm -r -f`、`rm -R --force`、`rm --recursive`——
|
|
181
|
+
因为 stdin 指向 `/dev/null` 时不会产生任何提示,`rm -r tree` 会静默删除整棵树。
|
|
182
|
+
每次 `rm` 调用按其所在命令段单独判定,所以 `rm a -f; rm b -r` 不能靠拼接标志蒙过去;
|
|
183
|
+
- 往块设备 `dd`、`mkfs`、分区/擦除类工具(`fdisk`、`parted`、`wipefs`、`mkswap` …)、
|
|
184
|
+
电源控制(`shutdown`、`reboot`、`systemctl reboot` …)、重定向到块设备、fork 炸弹;
|
|
185
|
+
- 防护容忍命令词的各种写法(`sudo rm -r -f`、`bash -c "rm -rf /"`、
|
|
186
|
+
`find . -exec rm -rf {} +`、`rm$IFS-rf`、`\rm -rf`、`$(which rm) -rf`),但设备/电源类
|
|
187
|
+
工具只在**命令位置**匹配,因此查看它们是允许的:`man fdisk`、
|
|
188
|
+
`grep -rn reboot /var/log/syslog`、`echo "the mkfs tool formats disks"` 都能正常执行。
|
|
189
|
+
- stderr 中重复出现的启动器噪声会被过滤:localhost 代理警告与 procps 的
|
|
190
|
+
`screen size is bogus` 行。
|
|
191
|
+
- 使用 `wsl.exe -e`(`--exec`),引号与 `$VAR` 展开行为与普通 shell 一致;
|
|
192
|
+
默认的 `--` 透传会破坏单引号和变量。
|
|
193
|
+
|
|
194
|
+
## 沙箱边界
|
|
195
|
+
|
|
196
|
+
DSH 的文件沙箱在**两个点**实施:shell 执行器(`@deepseek-ai/dsh-bash-sandbox`、`-pwsh-sandbox`,把
|
|
197
|
+
argv 包一层过 `ctx.sandbox`)和文件系统服务(`@deepseek-ai/dsh-fs-sandbox`,对两个写操作加策略栅栏)。
|
|
198
|
+
**`wsl` 两者都不经过**——它通过宿主 `subprocess` 服务直接拉起 `wsl.exe`,位于那一层之下。因此
|
|
199
|
+
`workspace-write` 策略**约束不到它**:它能写 Linux 侧能写的任何位置,也能写 `/mnt/<盘>` 下 Windows
|
|
200
|
+
允许的任何位置。
|
|
201
|
+
|
|
202
|
+
这不是靠"接入沙箱"能补上的缺口。Windows 上的沙箱最终落到 **ACL / 受限令牌**,而文件操作发生在
|
|
203
|
+
**Linux 内核里**:写 `/home/...` 动的是发行版自己的文件系统镜像,任何 Windows 令牌都够不着;写
|
|
204
|
+
`/mnt/c/...` 要经文件系统桥,ACL 是否生效不可依赖。把 `wsl.exe` 包起来只能约束"启动器",约束不了写入
|
|
205
|
+
——而给出**虚假的隔离感,比明说不隔离更危险**。(平台自己的 `dsh-fs-sandbox` 也坦白它的边界:
|
|
206
|
+
"containment, not a security boundary"。)
|
|
207
|
+
|
|
208
|
+
`wsl` 实际拥有的保护是上面那套危险命令守卫:一份确定性的拒绝清单,**不是内核边界**。请把它当作
|
|
209
|
+
"能碰到你的 WSL 安装能碰的一切"来授予权限。
|
|
210
|
+
|
|
211
|
+
## 从插件列表安装
|
|
212
|
+
|
|
213
|
+
本包声明了 `dsh.bundle` manifest(见 `package.json`),因此仓库被列表收录后可按
|
|
214
|
+
名称安装,例如 `dsh plugin add dsh-wsl-tool`,市场(storefront)也会提供一键安装。
|
|
215
|
+
上文 `file:` 的本地安装方式仍然有效。
|
|
216
|
+
|
|
217
|
+
## 工作原理
|
|
218
|
+
|
|
219
|
+
插件是一个 cordis 模块,注入宿主平面的 `tools` 与 `subprocess` 注册表。`index.js`
|
|
220
|
+
只是入口,实现按职责拆分:
|
|
221
|
+
|
|
222
|
+
| 模块 | 职责 |
|
|
223
|
+
|---|---|
|
|
224
|
+
| `lib/config.js` | 默认值与环境变量覆盖,挂载时解析一次 |
|
|
225
|
+
| `lib/paths.js` | shell 引号处理与 Windows → WSL 路径转换 |
|
|
226
|
+
| `lib/guard.js` | 危险命令规则 |
|
|
227
|
+
| `lib/result.js` | 启动器噪声过滤、截断事实、标记渲染 |
|
|
228
|
+
| `lib/diagnostics.js` | `wsl-env` 的能力探针、解析器与输出行 |
|
|
229
|
+
| `lib/runner.js` | 唯一的 spawn 路径与启动器错误分类 |
|
|
230
|
+
| `lib/tools/*.js` | 三个工具定义(schema / execute / presentCall) |
|
|
231
|
+
|
|
232
|
+
- `apply()` 解析配置并注册三个工具,每个都有 JSON-schema 参数定义、输出 schema、
|
|
233
|
+
`render` 钩子与异步 `execute`。
|
|
234
|
+
- 所有调用都走同一条 spawn 路径(`runner.runWsl`):通过宿主 `subprocess` 执行
|
|
235
|
+
`wsl.exe -d <distro> -e bash -lc "<exports; cd workdir && command>"`,stdout/stderr
|
|
236
|
+
上限由配置决定(超出最多落盘 64 MiB),abort 后有 3 秒宽限期,超时默认 10 分钟。
|
|
237
|
+
插件自身的探测调用另有 30 秒硬上限,避免 WSL 服务卡死时永久挂住工具调用。
|
|
238
|
+
- `env` 条目在命令前部以 `export` 形式注入,确保可靠到达 Linux 侧;Windows 盘符
|
|
239
|
+
路径在构造命令前先改写为 `/mnt/...`。
|
|
240
|
+
- 输出为 `{ exitCode, signal, timedOut, timeoutMs, truncated, stdout, stderr,
|
|
241
|
+
stdoutTotalBytes, stdoutDroppedBytes, stderrTotalBytes, stderrDroppedBytes,
|
|
242
|
+
stdoutSpillPath, stderrSpillPath, jobId }`;`render` 钩子将其格式化为文本并附加上述
|
|
243
|
+
标记。截断标记引用窗口大小本身而不是由解码文本推算的数字——窗口起点落在多字节字符
|
|
244
|
+
中间时后者会差一两个字节。
|
|
245
|
+
- `jobId` 只有后台启动才会赋值,其余路径一律 `null`,因此两种情况下声明形状都成立。
|
|
246
|
+
- 启动与结算被拆成两步(`runner.launch` / `settle`):后台任务要同步交给注册表一对
|
|
247
|
+
`cancel`/`done`,而前台调用只是 await 同一个 settle。被取消的任务会把 provider 的
|
|
248
|
+
"target 启动前即被终止"拒绝映射成 `killed`——`JobHooks.done` 不允许 reject。
|
|
249
|
+
- 危险命令防护把命令按 `;`/`&`/`|`/换行切成段,每次 `rm` 调用按自身标志单独判定,
|
|
250
|
+
设备/电源类工具在命令位置匹配后才拦截。
|
|
251
|
+
|
|
252
|
+
插件不发布任何自身服务,因此它可以无 realm 地挂在 agent preset 中。
|
|
253
|
+
|
|
254
|
+
## 开发
|
|
255
|
+
|
|
256
|
+
插件是纯 ESM,无构建步骤,除 DSH 宿主平面外无运行时依赖。迭代方式:把 profile
|
|
257
|
+
依赖指向本仓库:
|
|
258
|
+
|
|
259
|
+
```json
|
|
260
|
+
{ "dependencies": { "dsh-wsl-tool": "file:/path/to/dsh-wsl" } }
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
然后重启 DSH,在包含 `tool-wsl` 行的 preset 会话中调用工具验证。
|
|
264
|
+
|
|
265
|
+
### 测试
|
|
266
|
+
|
|
267
|
+
```sh
|
|
268
|
+
npm test # 260+ 项检查,跑在真实 WSL 上,仅用 shim 顶替 ctx.subprocess
|
|
269
|
+
npm run test:real # 同一套检查改跑真实 provider,外加 seam 事实套件
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
`npm test` 只替换 `ctx.subprocess`,用一个复刻了 seam 行为(有界尾窗、落盘文件、
|
|
273
|
+
终止阶梯)的 shim 驱动三个工具跑在真实 WSL 上,覆盖路径改写、workdir 引号处理、
|
|
274
|
+
危险命令防护、发行版选择、退出码/超时/截断标记、`wsl-path`、`wsl-env`、参数校验、
|
|
275
|
+
配置解析、启动器错误分类、返回结构与各自 `output.schema` 的一致性,以及模型可见
|
|
276
|
+
目录的 token 预算。
|
|
277
|
+
|
|
278
|
+
`npm run test:real` 把同一套检查改跑在 `LocalSubprocessRuntime` 上——shim 不允许与
|
|
279
|
+
真实 seam 漂移——然后跑 `test/real-seam.mjs`,校验 shim 无法担保的事实
|
|
280
|
+
(`readFrom(0).nextOffset` 是否为整条流字节总数、落盘文件是否完整、超时是否真的杀掉
|
|
281
|
+
`wsl.exe` 的 Linux 侧进程),并用 DSH 自己的 `assertSupportedJsonSchema` 校验全部
|
|
282
|
+
schema。它需要一份 DSH 安装:
|
|
283
|
+
|
|
284
|
+
```sh
|
|
285
|
+
DSH_SUBPROCESS_LOCAL=/path/to/dsh/node_modules npm run test:real
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### 同步到 profile
|
|
289
|
+
|
|
290
|
+
`file:` 依赖是**副本**,改本仓库不会改变 DSH 实际加载的内容。任何改动之后:
|
|
291
|
+
|
|
292
|
+
```sh
|
|
293
|
+
npm run sync # 复制到 ~/.dsh/profiles/web/node_modules/dsh-wsl
|
|
294
|
+
npm run sync -- /path/to/profiles/<profile>/node_modules/<你的依赖名>
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
目标目录必须是 profile 依赖名对应的文件夹(上面的默认值就是本检出的依赖名);
|
|
298
|
+
插件按相对路径加载自身入口,因此文件夹叫什么并不影响加载。
|
|
299
|
+
|
|
300
|
+
然后重启 DSH——插件在加载时只导入一次。
|
|
301
|
+
|
|
302
|
+
## 收录
|
|
303
|
+
|
|
304
|
+
本仓库带有 `dsh-plugin` topic,并已提交至 awesome-dsh-plugin 社区列表的 `wsl` 分类。
|
|
Binary file
|