dsh-wsl-tool 1.10.12 → 1.11.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/PUBLISHING.md +32 -13
- package/README.md +123 -15
- package/README.zh-CN.md +105 -12
- package/SUPPORT.md +2 -2
- package/index.js +15 -2
- package/lib/bootstrap.js +266 -0
- package/lib/client.js +29 -3
- package/lib/config.js +17 -1
- package/lib/doctor.js +296 -0
- package/lib/result.js +3 -0
- package/lib/runner.js +24 -8
- package/lib/tools/wsl-bootstrap.js +137 -0
- package/lib/tools/wsl-doctor.js +128 -0
- package/lib/tools/wsl.js +42 -18
- package/package.json +2 -2
- package/screenshots.json +1 -1
package/PUBLISHING.md
CHANGED
|
@@ -78,7 +78,7 @@ both that the entry stays relative and that it resolves.
|
|
|
78
78
|
It must print `function` and the resolved defaults. If it prints `undefined`,
|
|
79
79
|
the plugin declares no `Config`, the platform has no schema to project, the
|
|
80
80
|
entry's config status stays `absent` and the sidebar panel renders without a
|
|
81
|
-
single switch while
|
|
81
|
+
single switch while every tool keeps working. The fragile step is the
|
|
82
82
|
interop hop: `@deepseek-ai/schemastery` exports its builder as the **default**
|
|
83
83
|
export, so `const { Schema } = await import(...)` silently yields `undefined`
|
|
84
84
|
(see `lib/schema.js`, and the regression test that pins the picking).
|
|
@@ -187,24 +187,43 @@ consequences worth remembering:
|
|
|
187
187
|
hand-written keys are `url`, `name`, `category`, `tarball` and `description`;
|
|
188
188
|
everything else (including `screenshots`) is generated and must not be written by
|
|
189
189
|
hand. Only the description needs a PR against `awesome-dsh-plugin`.
|
|
190
|
-
- **Replacing a file in place
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
190
|
+
- **Replacing a file in place does NOT refresh the preview.** Two caches are keyed on
|
|
191
|
+
the exact URL string and neither revalidates the bytes: dsh-market loads images
|
|
192
|
+
through the `images.weserv.nl` proxy (`?url=<the raw URL>&h=<height>&we=1` — see its
|
|
193
|
+
`client/client.js`), and the DSH desktop webview keeps its own HTTP disk cache under
|
|
194
|
+
`%APPDATA%\@deepseek-ai\dsh-desktop\Cache\Cache_Data`. A redesign committed over the
|
|
195
|
+
same path therefore keeps serving the previous image for as long as those copies
|
|
196
|
+
live. The 2026-10-05 preview did exactly that: the mode-picker screenshot (with
|
|
197
|
+
`WSL Dev` ticked) was replaced in place at `assets/screenshot-1.png`, and the listing
|
|
198
|
+
went on showing it. **Ship a preview under a new filename** — add the file, point
|
|
199
|
+
`screenshots.json` and the README embed at it, and retire the old path.
|
|
200
|
+
Retiring it costs one day of transition: the catalog's `screenshots` array is
|
|
201
|
+
generated from this repository and published about once a day, so a client still
|
|
202
|
+
holding the previous catalog requests the old URL after the file is gone and finds
|
|
203
|
+
nothing. Keep the old file until the next catalog publish when that URL is worth
|
|
204
|
+
keeping alive; delete it in the same change when the point is to retire the URL
|
|
205
|
+
(as here), accepting the empty slot until the rebuild.
|
|
206
|
+
|
|
207
|
+
The preview is rendered from `assets/market-preview.html` (the editable source of
|
|
208
|
+
truth) with headless Edge, under a **new filename each redesign** — the name is what
|
|
209
|
+
defeats the caches, so the render target changes with the design:
|
|
196
210
|
|
|
197
211
|
```powershell
|
|
212
|
+
$out = Join-Path $PWD 'assets\market-preview-<new>.png'
|
|
213
|
+
$url = 'file:///' + ($PWD.Path -replace '\\','/') + '/assets/market-preview.html'
|
|
198
214
|
& 'C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe' --headless=new `
|
|
199
215
|
--disable-gpu --hide-scrollbars --force-device-scale-factor=2 `
|
|
200
|
-
--window-size=1280,800 --virtual-time-budget=
|
|
201
|
-
--user-data-dir="$env:TEMP\edge-shot"
|
|
202
|
-
--screenshot="assets\screenshot-1.png" "file:///$PWD/assets/market-preview.html"
|
|
216
|
+
--window-size=1280,800 --virtual-time-budget=4000 `
|
|
217
|
+
--user-data-dir="$env:TEMP\edge-shot" --screenshot="$out" $url
|
|
203
218
|
```
|
|
204
219
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
220
|
+
Two things the renderer demands: `--screenshot` must be an **absolute** path (Edge
|
|
221
|
+
resolves it against its own working directory, and a relative one fails with
|
|
222
|
+
"Failed to write file … 系统找不到指定的路径"), and `--headless=new` needs a moment
|
|
223
|
+
before the file appears. It renders 2560×1600 (1280×800 at 2×) in a few seconds with
|
|
224
|
+
no browser window; `--user-data-dir` keeps it away from the user's real Edge profile.
|
|
225
|
+
Edit the HTML, re-run, commit the PNG, and point `screenshots.json` plus the README
|
|
226
|
+
embed at the new name: no build step, no design tool.
|
|
208
227
|
|
|
209
228
|
## Things that bite
|
|
210
229
|
|
package/README.md
CHANGED
|
@@ -15,17 +15,20 @@ project kept on a Windows drive keeps **Windows** filesystem semantics — no PO
|
|
|
15
15
|
permissions, case-insensitive names, no file-change notifications — at a fraction
|
|
16
16
|
of the speed (see [Notes](#notes)).
|
|
17
17
|
|
|
18
|
-

|
|
19
19
|
|
|
20
20
|
## Tools
|
|
21
21
|
|
|
22
|
-
The plugin registers
|
|
22
|
+
The plugin registers five tools (the last two can be switched off independently,
|
|
23
|
+
and `wsl-bootstrap` is OFF by default):
|
|
23
24
|
|
|
24
25
|
| Tool | Purpose |
|
|
25
26
|
|---|---|
|
|
26
27
|
| `wsl` | Run a Linux command and return `stdout`/`stderr` with exit-code, timeout and truncation markers. |
|
|
27
28
|
| `wsl-path` | Convert between Windows and WSL paths via `wslpath`. |
|
|
28
29
|
| `wsl-env` | Summarize the WSL environment (distros, kernel, cpu, mem, disk). |
|
|
30
|
+
| `wsl-doctor` | Compare what a project needs with what the distribution has — including commands that are really Windows binaries. Read-only. |
|
|
31
|
+
| `wsl-bootstrap` | Install the missing toolchain inside the distribution, from a fixed recipe list, plan first. |
|
|
29
32
|
|
|
30
33
|
### `wsl`
|
|
31
34
|
|
|
@@ -90,6 +93,96 @@ unknown distro is an error rather than a partial answer. Note that `wsl --versio
|
|
|
90
93
|
is **localized**, so its labels are passed through as the launcher printed them
|
|
91
94
|
rather than parsed by name; the Direct3D/MSRDC/DXCore versions are omitted.
|
|
92
95
|
|
|
96
|
+
### `wsl-doctor`
|
|
97
|
+
|
|
98
|
+
Answers the question a project's own README cannot: **will the tooling this
|
|
99
|
+
checkout expects actually work inside this distribution?** It reads the manifests
|
|
100
|
+
in the directory (`package.json` and lockfiles, `.nvmrc`, `tsconfig.json`,
|
|
101
|
+
`Cargo.toml`, `go.mod`, `pyproject.toml`, `requirements.txt`, `Dockerfile`,
|
|
102
|
+
`docker-compose.yml`, `Makefile`, `CMakeLists.txt`) and reports, per need, whether
|
|
103
|
+
it resolves on the Linux side:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
distro: Ubuntu-22.04 (system default)
|
|
107
|
+
workspace: /mnt/d/proj (Windows drive mount /mnt/d — builds, installs and git are much slower here; …)
|
|
108
|
+
project: package.json, pnpm-lock.yaml
|
|
109
|
+
|
|
110
|
+
needs (from the project): node, npm, pnpm
|
|
111
|
+
MISSING node — not on PATH in this distribution (from package.json)
|
|
112
|
+
WINDOWS npm — resolves to /mnt/c/Program Files/nodejs/npm, a Windows binary reached through interop; …
|
|
113
|
+
MISSING pnpm — not on PATH in this distribution (from pnpm-lock.yaml)
|
|
114
|
+
|
|
115
|
+
usable on the Linux side: python3 3.10.12, gcc 11.4.0, make 4.3, git 2.34.1, curl, rsync, tar, sudo
|
|
116
|
+
Windows binaries on the Linux PATH (interop): npm -> /mnt/c/Program Files/nodejs/npm, npx -> …
|
|
117
|
+
privileges: uid=1000 (xiny) · sudo: requires a password (unusable from a tool call) · root: switched off
|
|
118
|
+
|
|
119
|
+
next: node, npm are not usable — wsl-bootstrap({ recipes: ["node", "pnpm"] }) installs them …
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
That `WINDOWS` row is the reason the tool exists. WSL appends the Windows `PATH`
|
|
123
|
+
to the Linux one, so `npm` resolves on a machine whose distribution has no `node`
|
|
124
|
+
at all — measured here as `/mnt/c/Program Files/nodejs/npm` — and the errors a
|
|
125
|
+
model gets from running it against a Linux directory name neither cause. Overall
|
|
126
|
+
it is read-only, cheap, and worth running before blaming a failed command on the
|
|
127
|
+
command. Takes an optional `workspace` (a Linux directory; default: the configured
|
|
128
|
+
directory, else the session workspace) and `distro`.
|
|
129
|
+
|
|
130
|
+
### `wsl-bootstrap`
|
|
131
|
+
|
|
132
|
+
Installs the missing toolchain **inside the distribution**, from a fixed recipe
|
|
133
|
+
list and nothing else: `node` (Node.js LTS into `/usr/local`, checksum-verified,
|
|
134
|
+
with corepack), `pnpm`, `python` (python3 + pip + venv), `build`
|
|
135
|
+
(build-essential), `tools` (jq, rsync, curl, git, ca-certificates). Dependencies
|
|
136
|
+
are added when they are needed: `pnpm` brings `node`, and `node` brings `tools`
|
|
137
|
+
when `curl` is missing.
|
|
138
|
+
|
|
139
|
+
**It is off by default**, and its `dryRun` defaults to `true`, so the first call
|
|
140
|
+
prints the plan and changes nothing:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
dry run — nothing has been installed (distro Ubuntu-22.04)
|
|
144
|
+
|
|
145
|
+
already present:
|
|
146
|
+
python — python3, pip and venv (apt)
|
|
147
|
+
|
|
148
|
+
to install (python, tools):
|
|
149
|
+
[tools] apt packages: ca-certificates curl git jq rsync — runs as root inside the distribution
|
|
150
|
+
export DEBIAN_FRONTEND=noninteractive; apt-get update && apt-get install -y ca-certificates curl git jq rsync
|
|
151
|
+
|
|
152
|
+
apt would report:
|
|
153
|
+
Inst libjq1 (1.6-2.1ubuntu3.2 Ubuntu:22.04/jammy-updates [amd64])
|
|
154
|
+
…
|
|
155
|
+
|
|
156
|
+
run it for real with `dryRun: false`.
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The apt recipes collapse into **one** step (the `apt-get update` is the slow half,
|
|
160
|
+
and running it once per recipe would look like a hang), and an `apt-get -s`
|
|
161
|
+
simulation is included so the plan shows what apt itself would do. Recipes already
|
|
162
|
+
satisfied are skipped, so a second run is cheap. The steps run through
|
|
163
|
+
`wsl -u root` — see the next section for why — and a failing step stops the run
|
|
164
|
+
with the tail of its output.
|
|
165
|
+
|
|
166
|
+
## Running privileged commands (`asRoot`)
|
|
167
|
+
|
|
168
|
+
`sudo` cannot work from a tool call: it needs a password, and stdin is `/dev/null`
|
|
169
|
+
unless you send one. WSL has its own answer — `wsl.exe -u root` needs no password
|
|
170
|
+
(measured: it answers `uid=0` on a machine where `sudo -n` fails) — so the `wsl`
|
|
171
|
+
tool exposes it as `asRoot: true`, gated by a switch in the panel
|
|
172
|
+
(「管理员模式(root)」, off by default):
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
wsl({ command: 'apt-get install -y jq', description: 'install jq', asRoot: true })
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The authorisation is the switch, not the parameter: with it off, `asRoot: true` is
|
|
179
|
+
**refused with that instruction** rather than quietly downgraded to your user
|
|
180
|
+
account — a caller that asked for root and got a permission error would look for
|
|
181
|
+
the cause in the wrong place. Every privileged result is marked
|
|
182
|
+
(`[ran as root: wsl -u root]`) so a transcript says what happened, and the
|
|
183
|
+
destructive-command guard applies independently: `rm -rf` still needs
|
|
184
|
+
`allowDangerous: true`, as root or not.
|
|
185
|
+
|
|
93
186
|
## Install
|
|
94
187
|
|
|
95
188
|
The published npm package is **`dsh-wsl-tool`**, not `dsh-wsl`: the registry
|
|
@@ -110,8 +203,10 @@ specifier you install under.
|
|
|
110
203
|
checkout (which names the dependency after this package).
|
|
111
204
|
|
|
112
205
|
2. Nothing else. The package's `cordis.patch.yml` inserts the `tool-wsl` row
|
|
113
|
-
itself when the bundle is loaded, process-wide, so the
|
|
114
|
-
available to every agent preset without further wiring.
|
|
206
|
+
itself when the bundle is loaded, process-wide, so the tools are
|
|
207
|
+
available to every agent preset without further wiring. **There is no mode to
|
|
208
|
+
pick and nothing to add to a preset** — the left-sidebar WSL panel arrives the
|
|
209
|
+
same way.
|
|
115
210
|
|
|
116
211
|
Do **not** also list `tool-wsl` in a preset: DSH registers tools by name, and
|
|
117
212
|
the second registration fails with
|
|
@@ -122,7 +217,7 @@ specifier you install under.
|
|
|
122
217
|
The settings panel is the one optional piece: its form needs
|
|
123
218
|
`@deepseek-ai/schemastery`, which most profiles already have (any plugin that
|
|
124
219
|
depends on it brings it in — add it to the profile's dependencies if yours does
|
|
125
|
-
not). Without it, the
|
|
220
|
+
not). Without it, the tools and the panel's 「WSL 终端启动路径」 field work
|
|
126
221
|
as usual and only the switches are absent — the panel says so instead of waiting
|
|
127
222
|
forever.
|
|
128
223
|
|
|
@@ -170,6 +265,9 @@ explanation.
|
|
|
170
265
|
| Linux 默认工作目录 | a fixed Linux directory (for example `/mnt/d/project`) used when `workdir` is omitted. Filling it in wins over the switch above; clearing it goes back to following the session |
|
|
171
266
|
| WSL 终端启动路径 | the optional sidebar terminal's startup directory — `--cd <dir>` on the `terminal-controller` row of your **profile patch** (see [Optional: a WSL terminal in the sidebar](#optional-a-wsl-terminal-in-the-sidebar)). Empty clears it, and the panel reads the current value from the same file |
|
|
172
267
|
| 危险命令守卫 | whether a destructive command needs an explicit `allowDangerous` |
|
|
268
|
+
| 管理员模式(root) | whether `wsl` accepts `asRoot: true` (`wsl -u root`). WSL's root needs no password, so this switch is the authorisation itself; **off by default** |
|
|
269
|
+
| wsl-doctor 项目体检 | registers `wsl-doctor` |
|
|
270
|
+
| wsl-bootstrap 安装工具链 | registers `wsl-bootstrap`, which may install into the distribution. **Off by default**, and its `dryRun` still has to be turned off per call |
|
|
173
271
|
|
|
174
272
|
The panel edits the plugin's own configuration, so the same values can be written
|
|
175
273
|
by hand (`- id: tool-wsl` with `config:` in a profile patch) or by environment
|
|
@@ -189,7 +287,7 @@ features — set them in the patch or with `DSH_WSL_DISTRO` / `DSH_WSL_TIMEOUT_M
|
|
|
189
287
|
and the panel shows what is currently in effect.
|
|
190
288
|
|
|
191
289
|
The settings surface needs `@deepseek-ai/schemastery`, which the plugin declares as
|
|
192
|
-
an optional peer dependency: without it the
|
|
290
|
+
an optional peer dependency: without it the tools still run on their defaults
|
|
193
291
|
and only the panel is missing.
|
|
194
292
|
|
|
195
293
|
At the bottom of the panel there is one feedback entry, next to it a short
|
|
@@ -256,10 +354,18 @@ start too.
|
|
|
256
354
|
| `stdin` | no | string | text written to the command stdin (UTF-8) before it runs |
|
|
257
355
|
| `runInBackground` | no | boolean | run as a job and return its id immediately; read with `job_output`, stop with `job_kill` |
|
|
258
356
|
| `allowDangerous` | no | boolean | set `true` to run destructive commands |
|
|
357
|
+
| `asRoot` | no | boolean | run this call as root (`wsl -u root`); refused unless the panel's 管理员模式 switch is on |
|
|
259
358
|
| `translatePaths` | no | boolean | default `true`; set `false` to pass `command` through verbatim |
|
|
260
359
|
|
|
261
360
|
## Configuration
|
|
262
361
|
|
|
362
|
+
The panel's switches and value fields (`tools.wsl`, `tools.path`, `tools.env`,
|
|
363
|
+
`tools.doctor`, `tools.bootstrap`, `backgroundJobs`, `translatePaths`,
|
|
364
|
+
`startInSessionWorkspace`, `workdir`, `dangerGuard`, `allowRoot`, `distro`,
|
|
365
|
+
`timeoutMs`) live in the profile patch and take effect as described on each row.
|
|
366
|
+
Two of them have no environment variable on purpose: `dangerGuard` (its OFF
|
|
367
|
+
position is the footgun) and `allowRoot` (WSL's root needs no password, so the
|
|
368
|
+
switch IS the authorisation). The environment layer:
|
|
263
369
|
| Environment variable | Default | Effect |
|
|
264
370
|
|---|---|---|
|
|
265
371
|
| `DSH_WSL_DISTRO` | (system default) | Pin the distribution for every call. |
|
|
@@ -269,7 +375,7 @@ start too.
|
|
|
269
375
|
| `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. |
|
|
270
376
|
|
|
271
377
|
An unparsable or out-of-range value falls back to the default: one bad variable
|
|
272
|
-
must not take
|
|
378
|
+
must not take every tool down. Values are read once per mount, so a change
|
|
273
379
|
takes effect on restart.
|
|
274
380
|
|
|
275
381
|
## Notes
|
|
@@ -372,9 +478,11 @@ is split by concern:
|
|
|
372
478
|
| `lib/result.js` | Launcher-noise filters, truncation facts, marker rendering. |
|
|
373
479
|
| `lib/diagnostics.js` | The `wsl-env` capability probe, its parser and its lines. |
|
|
374
480
|
| `lib/runner.js` | The single spawn path plus launcher-error classification. |
|
|
375
|
-
| `lib/tools/*.js` | The
|
|
481
|
+
| `lib/tools/*.js` | The tool definitions (schema, execute, presentCall). |
|
|
482
|
+
| `lib/doctor.js` | The project probe for `wsl-doctor`: one shell script, its parser, the report composer. |
|
|
483
|
+
| `lib/bootstrap.js` | The recipes, plan builder and outcome rendering for `wsl-bootstrap`. |
|
|
376
484
|
|
|
377
|
-
- `apply()` resolves the configuration and registers
|
|
485
|
+
- `apply()` resolves the configuration and registers the enabled tools, each with a
|
|
378
486
|
JSON-schema parameter definition, an output schema, a `render` hook and an
|
|
379
487
|
async `execute`.
|
|
380
488
|
- Every call goes through one spawn path (`runner.runWsl`): `wsl.exe -d <distro>
|
|
@@ -404,8 +512,8 @@ is split by concern:
|
|
|
404
512
|
segments, judges each `rm` invocation on its own flags, and matches the
|
|
405
513
|
device/power tools at command position before dispatch.
|
|
406
514
|
|
|
407
|
-
The plugin publishes no services of its own
|
|
408
|
-
|
|
515
|
+
The plugin publishes no services of its own and is inserted process-wide by its own
|
|
516
|
+
bundle patch, so it needs no realm — and no preset entry either.
|
|
409
517
|
|
|
410
518
|
## Development
|
|
411
519
|
|
|
@@ -416,19 +524,19 @@ DSH host plane. Iterate by pointing a profile dependency at the checkout:
|
|
|
416
524
|
{ "dependencies": { "dsh-wsl-tool": "file:/path/to/dsh-wsl" } }
|
|
417
525
|
```
|
|
418
526
|
|
|
419
|
-
then restart DSH and
|
|
420
|
-
|
|
527
|
+
then restart DSH and call the tools from **any** session: the bundle patch registers
|
|
528
|
+
them process-wide, so no preset declares the row.
|
|
421
529
|
|
|
422
530
|
### Tests
|
|
423
531
|
|
|
424
532
|
```sh
|
|
425
|
-
npm test #
|
|
533
|
+
npm test # 591 host checks against real WSL (shim for ctx.subprocess) + 133 client checks
|
|
426
534
|
npm run test:real # the same checks against the REAL provider, plus the seam-fact suite
|
|
427
535
|
```
|
|
428
536
|
|
|
429
537
|
`npm test` substitutes only `ctx.subprocess`, with a shim that reproduces the
|
|
430
538
|
seam's bounded tail windows, spill files and termination ladder, and drives the
|
|
431
|
-
|
|
539
|
+
the tools against the real WSL installation. It covers path translation,
|
|
432
540
|
workdir quoting, the destructive guard, distro selection, exit-code/timeout/
|
|
433
541
|
truncation markers, `wsl-path`, `wsl-env`, argument validation, configuration
|
|
434
542
|
parsing, launcher-error classification, the returned shape against each declared
|
package/README.zh-CN.md
CHANGED
|
@@ -11,17 +11,19 @@ Linux 原生 DSH:真实的 Linux 内核与 bash、退出码与信号、超时
|
|
|
11
11
|
(见[沙箱边界](#沙箱边界));项目放在 Windows 盘上时仍是 **Windows** 的文件系统语义——没有 POSIX
|
|
12
12
|
权限位、文件名大小写不敏感、收不到文件变更通知——速度也低一个数量级(见[注意事项](#注意事项))。
|
|
13
13
|
|
|
14
|
-

|
|
15
15
|
|
|
16
16
|
## 工具
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
插件注册五个工具(后两个可单独关掉,其中 `wsl-bootstrap` **默认关**):
|
|
19
19
|
|
|
20
20
|
| 工具 | 用途 |
|
|
21
21
|
|---|---|
|
|
22
22
|
| `wsl` | 执行 Linux 命令,返回带退出码、超时与截断标记的 `stdout`/`stderr` |
|
|
23
23
|
| `wsl-path` | 通过 `wslpath` 在 Windows 与 WSL 路径间互转 |
|
|
24
24
|
| `wsl-env` | 汇总 WSL 环境(发行版、内核、CPU、内存、磁盘) |
|
|
25
|
+
| `wsl-doctor` | 对照项目清单与发行版实际能力,指出缺什么——包括那些其实是 Windows 二进制的命令。只读 |
|
|
26
|
+
| `wsl-bootstrap` | 按固定配方在发行版里补装缺失的工具链,先出计划再动手 |
|
|
25
27
|
|
|
26
28
|
### `wsl`
|
|
27
29
|
|
|
@@ -78,6 +80,82 @@ launcher: WSL 版本: 2.6.3.0 · 内核版本: 6.6.87.2-1 · WSLg 版本: 1.0.71
|
|
|
78
80
|
发行版不存在则直接报错,而不是给出一份残缺答案。注意 `wsl --version` 输出是**本地化**的,
|
|
79
81
|
因此其标签按启动器原样透传、不按名称解析;Direct3D/MSRDC/DXCore 版本作为噪声被省略。
|
|
80
82
|
|
|
83
|
+
### `wsl-doctor`
|
|
84
|
+
|
|
85
|
+
回答一个项目自己的 README 答不了的问题:**这份代码指望的工具链,在这套发行版里真的能用吗?**
|
|
86
|
+
它读取目录里的清单(`package.json` 与各种 lockfile、`.nvmrc`、`tsconfig.json`、`Cargo.toml`、
|
|
87
|
+
`go.mod`、`pyproject.toml`、`requirements.txt`、`Dockerfile`、`docker-compose.yml`、`Makefile`、
|
|
88
|
+
`CMakeLists.txt`),然后逐项给出它在 Linux 侧的位置:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
distro: Ubuntu-22.04 (system default)
|
|
92
|
+
workspace: /mnt/d/proj (Windows drive mount /mnt/d — builds, installs and git are much slower here; …)
|
|
93
|
+
project: package.json, pnpm-lock.yaml
|
|
94
|
+
|
|
95
|
+
needs (from the project): node, npm, pnpm
|
|
96
|
+
MISSING node — not on PATH in this distribution (from package.json)
|
|
97
|
+
WINDOWS npm — resolves to /mnt/c/Program Files/nodejs/npm, a Windows binary reached through interop; …
|
|
98
|
+
MISSING pnpm — not on PATH in this distribution (from pnpm-lock.yaml)
|
|
99
|
+
|
|
100
|
+
usable on the Linux side: python3 3.10.12, gcc 11.4.0, make 4.3, git 2.34.1, curl, rsync, tar, sudo
|
|
101
|
+
Windows binaries on the Linux PATH (interop): npm -> /mnt/c/Program Files/nodejs/npm, npx -> …
|
|
102
|
+
privileges: uid=1000 (xiny) · sudo: requires a password (unusable from a tool call) · root: switched off
|
|
103
|
+
|
|
104
|
+
next: node, npm are not usable — wsl-bootstrap({ recipes: ["node", "pnpm"] }) installs them …
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
那行 `WINDOWS` 就是它存在的理由。WSL 会把 Windows 的 `PATH` 接到 Linux 的后面,于是
|
|
108
|
+
`npm` 在一套**根本没有 `node`** 的发行版里"找得到"(本机实测就是
|
|
109
|
+
`/mnt/c/Program Files/nodejs/npm`),模型拿它去跑 Linux 目录时报的错两头都不沾。整体只读、
|
|
110
|
+
开销很小,值得在把失败归咎于命令本身之前先跑一次。可选参数 `workspace`(Linux 目录;默认取
|
|
111
|
+
配置的目录,否则取会话工作区)与 `distro`。
|
|
112
|
+
|
|
113
|
+
### `wsl-bootstrap`
|
|
114
|
+
|
|
115
|
+
**在发行版内部**补装缺失的工具链,只认固定配方:`node`(Node.js LTS 装进 `/usr/local`,
|
|
116
|
+
校验 sha256,并启用 corepack)、`pnpm`、`python`(python3 + pip + venv)、`build`
|
|
117
|
+
(build-essential)、`tools`(jq、rsync、curl、git、ca-certificates)。依赖按需带上:
|
|
118
|
+
`pnpm` 会带上 `node`,`curl` 缺失时 `node` 会带上 `tools`。
|
|
119
|
+
|
|
120
|
+
**它默认关**,且 `dryRun` 默认为 `true`,所以第一次调用只打印计划、什么都不改:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
dry run — nothing has been installed (distro Ubuntu-22.04)
|
|
124
|
+
|
|
125
|
+
already present:
|
|
126
|
+
python — python3, pip and venv (apt)
|
|
127
|
+
|
|
128
|
+
to install (python, tools):
|
|
129
|
+
[tools] apt packages: ca-certificates curl git jq rsync — runs as root inside the distribution
|
|
130
|
+
export DEBIAN_FRONTEND=noninteractive; apt-get update && apt-get install -y ca-certificates curl git jq rsync
|
|
131
|
+
|
|
132
|
+
apt would report:
|
|
133
|
+
Inst libjq1 (1.6-2.1ubuntu3.2 Ubuntu:22.04/jammy-updates [amd64])
|
|
134
|
+
…
|
|
135
|
+
|
|
136
|
+
run it for real with `dryRun: false`.
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
所有 apt 配方会**合并成一步**(`apt-get update` 是慢的那一半,每个配方各跑一次看起来就像卡死),
|
|
140
|
+
并附带一条 `apt-get -s` 只读模拟,让计划直接显示 apt 自己会做什么。已经满足的配方会被跳过,
|
|
141
|
+
所以第二次跑很便宜。步骤通过 `wsl -u root` 执行——为什么见下一节——某一步失败即停下并附上
|
|
142
|
+
它的输出末尾。
|
|
143
|
+
|
|
144
|
+
## 以 root 执行(`asRoot`)
|
|
145
|
+
|
|
146
|
+
`sudo` 在工具调用里根本用不了:它要密码,而除非你显式喂 stdin,stdin 就是 `/dev/null`。
|
|
147
|
+
WSL 自带答案——`wsl.exe -u root` **免密**(本机实测:`sudo -n` 失败时它照样给出 `uid=0`)——
|
|
148
|
+
于是 `wsl` 工具把它暴露成 `asRoot: true`,由面板里一项开关把关(「管理员模式(root)」,默认关):
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
wsl({ command: 'apt-get install -y jq', description: 'install jq', asRoot: true })
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
授权来自开关而不是参数:开关关着时,`asRoot: true` 会被**明确拒绝并给出开关位置**,而不是
|
|
155
|
+
悄悄降级成你的普通用户——一个要了 root 却拿到权限错误的人,会去错误的地方找原因。每次以 root
|
|
156
|
+
执行的结果都会标注(`[ran as root: wsl -u root]`),转录里说得清发生过什么;危险命令守卫则
|
|
157
|
+
独立生效:`rm -rf` 无论是不是 root 都仍需 `allowDangerous: true`。
|
|
158
|
+
|
|
81
159
|
## 安装
|
|
82
160
|
|
|
83
161
|
发布到 npm 的包名是 **`dsh-wsl-tool`**,不是 `dsh-wsl`:registry 判定 `dsh-wsl` 与既有包
|
|
@@ -96,7 +174,8 @@ launcher: WSL 版本: 2.6.3.0 · 内核版本: 6.6.87.2-1 · WSLg 版本: 1.0.71
|
|
|
96
174
|
(依赖名会取本包自身的名字)。
|
|
97
175
|
|
|
98
176
|
2. 不必再做别的。本包的 `cordis.patch.yml` 会在组合包加载时**自己插入** `tool-wsl` 行
|
|
99
|
-
|
|
177
|
+
(进程级),因此这些工具对所有 agent preset 都可用,无需额外接线。**不需要选择任何模式,
|
|
178
|
+
也不要在 preset 里加任何东西**——左侧栏的 WSL 面板同样是这样出现的。
|
|
100
179
|
|
|
101
180
|
**不要**再在 preset 里列一遍 `tool-wsl`:DSH 按名字注册工具,第二次注册会直接失败
|
|
102
181
|
—— `tool "wsl" is already registered in this scope`。这一行只由组合包提供。
|
|
@@ -104,7 +183,7 @@ launcher: WSL 版本: 2.6.3.0 · 内核版本: 6.6.87.2-1 · WSLg 版本: 1.0.71
|
|
|
104
183
|
3. 重启 DSH。
|
|
105
184
|
|
|
106
185
|
设置面板是唯一可选的一块:它的表单需要 `@deepseek-ai/schemastery`,多数 profile 已经有了
|
|
107
|
-
(只要装过任何依赖它的插件;没有的话把它加进 profile
|
|
186
|
+
(只要装过任何依赖它的插件;没有的话把它加进 profile 的依赖即可)。没有它时,工具与
|
|
108
187
|
面板里的「WSL 终端启动路径」照常可用,只是开关不显示 —— 面板会直接说明这一点,不会一直转圈等待。
|
|
109
188
|
|
|
110
189
|
## 兼容性
|
|
@@ -144,6 +223,9 @@ DSH_SUBPROCESS_LOCAL=/path/to/dsh/node_modules npm run test:real
|
|
|
144
223
|
| Linux 默认工作目录 | 未传 `workdir` 时使用的固定 Linux 目录(如 `/mnt/d/project`)。填了就优先于上面的开关;清空则回到跟随会话 |
|
|
145
224
|
| WSL 终端启动路径 | 可选的侧边栏终端从哪个目录启动 —— 写进你 **profile patch** 里 `terminal-controller` 那一行的 `--cd <目录>`(见[可选:在侧边栏开一个 WSL 终端](#可选在侧边栏开一个-wsl-终端))。留空则删掉该参数;当前值也是从这个文件读的 |
|
|
146
225
|
| 危险命令守卫 | 危险命令是否必须显式 `allowDangerous` |
|
|
226
|
+
| 管理员模式(root) | `wsl` 是否接受 `asRoot: true`(`wsl -u root`)。WSL 的 root 免密,所以这项开关就是授权本身;**默认关** |
|
|
227
|
+
| wsl-doctor 项目体检 | 注册 `wsl-doctor` |
|
|
228
|
+
| wsl-bootstrap 安装工具链 | 注册 `wsl-bootstrap`,它可能往发行版里装东西。**默认关**,且每次调用还得显式关掉 `dryRun` |
|
|
147
229
|
|
|
148
230
|
面板改的是插件自己的配置,所以同样的值也可以手写进 profile patch(`- id: tool-wsl` 加 `config:`)
|
|
149
231
|
或用环境变量设。优先级由 `lib/config.js` 定:**插件配置 > 环境变量 > 内置默认值**;开关停在默认值时
|
|
@@ -156,7 +238,7 @@ patch 层 —— 每次写入前先备份该文件,只重写终端那一行的
|
|
|
156
238
|
**改动在下次启动 DSH 后生效**:宿主每次挂载只读一次该配置,面板里也写着这句。发行版与超时属于"值"
|
|
157
239
|
而不是"功能":在 patch 里或用 `DSH_WSL_DISTRO` / `DSH_WSL_TIMEOUT_MS` 设置,面板只显示当前生效值。
|
|
158
240
|
|
|
159
|
-
这个设置界面需要 `@deepseek-ai/schemastery`(插件把它声明为可选 peer
|
|
241
|
+
这个设置界面需要 `@deepseek-ai/schemastery`(插件把它声明为可选 peer 依赖):没有它工具照常按
|
|
160
242
|
默认值工作,只是没有面板。
|
|
161
243
|
|
|
162
244
|
面板**最底部**是一个反馈入口,旁边跟着一段简短的提交指南,以及一个「复制插件信息」按钮(详见
|
|
@@ -211,10 +293,17 @@ patch 层 —— 每次写入前先备份该文件,只重写终端那一行的
|
|
|
211
293
|
| `stdin` | 否 | string | 在命令运行前写入其 stdin 的文本(UTF-8) |
|
|
212
294
|
| `runInBackground` | 否 | boolean | 作为后台任务运行并立即返回任务 id;用 `job_output` 读、`job_kill` 停 |
|
|
213
295
|
| `allowDangerous` | 否 | boolean | 置 `true` 才允许执行危险命令 |
|
|
296
|
+
| `asRoot` | 否 | boolean | 本次调用以 root 执行(`wsl -u root`);面板「管理员模式」关着时会被拒绝 |
|
|
214
297
|
| `translatePaths` | 否 | boolean | 默认 `true`;置 `false` 时 `command` 原样传入,不做路径改写 |
|
|
215
298
|
|
|
216
299
|
## 配置
|
|
217
300
|
|
|
301
|
+
面板里的开关与取值字段(`tools.wsl`、`tools.path`、`tools.env`、`tools.doctor`、
|
|
302
|
+
`tools.bootstrap`、`backgroundJobs`、`translatePaths`、`startInSessionWorkspace`、`workdir`、
|
|
303
|
+
`dangerGuard`、`allowRoot`、`distro`、`timeoutMs`)都写在 profile patch 里,各自行上有说明。
|
|
304
|
+
其中两项**故意没有环境变量**:`dangerGuard`(它的关闭位置本身就是风险)与 `allowRoot`
|
|
305
|
+
(WSL 的 root 免密,所以那项开关就是授权本身)。环境变量这一层:
|
|
306
|
+
|
|
218
307
|
| 环境变量 | 默认值 | 作用 |
|
|
219
308
|
|---|---|---|
|
|
220
309
|
| `DSH_WSL_DISTRO` | (系统默认) | 为所有调用固定发行版 |
|
|
@@ -223,7 +312,7 @@ patch 层 —— 每次写入前先备份该文件,只重写终端那一行的
|
|
|
223
312
|
| `DSH_WSL_MAX_OUTPUT_BYTES` | `65536` | 每条流的内存窗口(1 KiB – 8 MiB);设到 64 MiB 以上时也会抬高落盘上限 |
|
|
224
313
|
| `DSH_WSL_WORKDIR` | `home` | 不传 `workdir` 时的起点:`home`(Linux 的 `~`)、`session`(会话工作目录,Windows 检出对应 `/mnt/<盘>/...`),或任意显式路径 |
|
|
225
314
|
|
|
226
|
-
|
|
315
|
+
无法解析或越界的值会回退到默认值——一个写错的环境变量不该让所有工具一起挂掉。
|
|
227
316
|
配置在挂载时读取一次,改动需重启 DSH 生效。
|
|
228
317
|
|
|
229
318
|
## 注意事项
|
|
@@ -337,9 +426,11 @@ argv 包一层过 `ctx.sandbox`)和文件系统服务(`@deepseek-ai/dsh-fs-s
|
|
|
337
426
|
| `lib/result.js` | 启动器噪声过滤、截断事实、标记渲染 |
|
|
338
427
|
| `lib/diagnostics.js` | `wsl-env` 的能力探针、解析器与输出行 |
|
|
339
428
|
| `lib/runner.js` | 唯一的 spawn 路径与启动器错误分类 |
|
|
340
|
-
| `lib/tools/*.js` |
|
|
429
|
+
| `lib/tools/*.js` | 工具定义(schema / execute / presentCall) |
|
|
430
|
+
| `lib/doctor.js` | `wsl-doctor` 的项目探针:一段 shell、它的解析器与报告组装 |
|
|
431
|
+
| `lib/bootstrap.js` | `wsl-bootstrap` 的配方、计划生成与结果渲染 |
|
|
341
432
|
|
|
342
|
-
- `apply()`
|
|
433
|
+
- `apply()` 解析配置并注册被启用的工具,每个都有 JSON-schema 参数定义、输出 schema、
|
|
343
434
|
`render` 钩子与异步 `execute`。
|
|
344
435
|
- 所有调用都走同一条 spawn 路径(`runner.runWsl`):通过宿主 `subprocess` 执行
|
|
345
436
|
`wsl.exe -d <distro> -e bash -lc "<exports; cd workdir && command>"`,stdout/stderr
|
|
@@ -359,7 +450,8 @@ argv 包一层过 `ctx.sandbox`)和文件系统服务(`@deepseek-ai/dsh-fs-s
|
|
|
359
450
|
- 危险命令防护把命令按 `;`/`&`/`|`/换行切成段,每次 `rm` 调用按自身标志单独判定,
|
|
360
451
|
设备/电源类工具在命令位置匹配后才拦截。
|
|
361
452
|
|
|
362
|
-
|
|
453
|
+
插件不发布任何自身服务,且由它自己的组合包 patch **进程级**插入,因此既不需要 realm,
|
|
454
|
+
也不需要任何 preset 条目。
|
|
363
455
|
|
|
364
456
|
## 开发
|
|
365
457
|
|
|
@@ -370,17 +462,18 @@ argv 包一层过 `ctx.sandbox`)和文件系统服务(`@deepseek-ai/dsh-fs-s
|
|
|
370
462
|
{ "dependencies": { "dsh-wsl-tool": "file:/path/to/dsh-wsl" } }
|
|
371
463
|
```
|
|
372
464
|
|
|
373
|
-
然后重启 DSH
|
|
465
|
+
然后重启 DSH,在**任意**会话里调用工具即可:组合包 patch 是进程级注册的,没有哪个 preset
|
|
466
|
+
需要声明这一行。
|
|
374
467
|
|
|
375
468
|
### 测试
|
|
376
469
|
|
|
377
470
|
```sh
|
|
378
|
-
npm test #
|
|
471
|
+
npm test # 591 项宿主检查(跑在真实 WSL 上,仅用 shim 顶替 ctx.subprocess)+ 133 项客户端检查
|
|
379
472
|
npm run test:real # 同一套检查改跑真实 provider,外加 seam 事实套件
|
|
380
473
|
```
|
|
381
474
|
|
|
382
475
|
`npm test` 只替换 `ctx.subprocess`,用一个复刻了 seam 行为(有界尾窗、落盘文件、
|
|
383
|
-
终止阶梯)的 shim
|
|
476
|
+
终止阶梯)的 shim 驱动这些工具跑在真实 WSL 上,覆盖路径改写、workdir 引号处理、
|
|
384
477
|
危险命令防护、发行版选择、退出码/超时/截断标记、`wsl-path`、`wsl-env`、参数校验、
|
|
385
478
|
配置解析、启动器错误分类、返回结构与各自 `output.schema` 的一致性,以及模型可见
|
|
386
479
|
目录的 token 预算。
|
package/SUPPORT.md
CHANGED
|
@@ -32,5 +32,5 @@ The panel ends with **one** feedback entry: a submission guide beside it (how to
|
|
|
32
32
|
|
|
33
33
|
## 修复的节奏 / How fixes happen
|
|
34
34
|
|
|
35
|
-
报告 → 复现(必要时我会请你补 `wsl-env` 输出)→ 改代码 → 测试(这个插件有
|
|
36
|
-
Report → reproduce (I may ask for a full `wsl-env`) → fix → tests (the plugin ships with
|
|
35
|
+
报告 → 复现(必要时我会请你补 `wsl-env` 输出)→ 改代码 → 测试(这个插件有 591 项宿主检查 + 133 项客户端检查)→ 发版(npm + 市场资产同一次发布,逐字节一致)→ 你升级后回帖确认 → 关闭。
|
|
36
|
+
Report → reproduce (I may ask for a full `wsl-env`) → fix → tests (the plugin ships with 591 host checks and 133 client checks) → release (npm and the market asset go out byte-identical in one run) → you confirm after upgrading → close.
|
package/index.js
CHANGED
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
//
|
|
11
11
|
// Each call runs in a fresh shell, so no state persists between calls. This
|
|
12
12
|
// plugin publishes nothing and only consumes the host-plane `subprocess` and
|
|
13
|
-
// `tools` registries, so it
|
|
13
|
+
// `tools` registries, so it needs no realm. Its own `cordis.patch.yml` inserts
|
|
14
|
+
// the `tool-wsl` row process-wide, which is why the tools reach every agent
|
|
15
|
+
// preset with no preset entry of its own (and why adding one there would fail).
|
|
14
16
|
//
|
|
15
17
|
// The implementation lives in `lib/`: `config` (defaults, environment overrides
|
|
16
18
|
// and the plugin's own switches), `paths` (path translation and shell quoting),
|
|
@@ -33,6 +35,8 @@ import { readTerminalCwd, reviewTerminalWrite, writeTerminalCwd } from './lib/te
|
|
|
33
35
|
import { createWslTool } from './lib/tools/wsl.js'
|
|
34
36
|
import { createWslPathTool } from './lib/tools/wsl-path.js'
|
|
35
37
|
import { createWslEnvTool } from './lib/tools/wsl-env.js'
|
|
38
|
+
import { createWslDoctorTool } from './lib/tools/wsl-doctor.js'
|
|
39
|
+
import { createWslBootstrapTool } from './lib/tools/wsl-bootstrap.js'
|
|
36
40
|
|
|
37
41
|
export const name = 'tool-wsl'
|
|
38
42
|
export const inject = ['tools', 'subprocess']
|
|
@@ -89,12 +93,15 @@ export const Config = Schema?.object({
|
|
|
89
93
|
wsl: Schema.boolean().default(true).description('注册 `wsl` 工具:在 WSL 发行版中执行 Linux 命令。').volatile(),
|
|
90
94
|
path: Schema.boolean().default(true).description('注册 `wsl-path` 工具:在 Windows 路径与 `/mnt/...` 之间互转。').volatile(),
|
|
91
95
|
env: Schema.boolean().default(true).description('注册 `wsl-env` 工具:汇总发行版、内核、systemd、cgroup、GPU 直通、docker 与挂载盘。').volatile(),
|
|
92
|
-
|
|
96
|
+
doctor: Schema.boolean().default(true).description('注册 `wsl-doctor` 工具:对照项目清单与发行版实际能力,指出缺什么,以及哪些命令其实是 Windows 的。').volatile(),
|
|
97
|
+
bootstrap: Schema.boolean().default(false).description('注册 `wsl-bootstrap` 工具:在发行版里补装缺失的工具链。默认关;打开后它以 root 执行固定配方,且调用方仍需显式 `dryRun: false`。').volatile(),
|
|
98
|
+
}).description('要注册哪些工具;这几项在重启 DSH 后生效。'),
|
|
93
99
|
backgroundJobs: Schema.boolean().default(true).description('允许长任务以 `runInBackground` 后台执行,结果由内置 job 工具读取。').volatile(),
|
|
94
100
|
translatePaths: Schema.boolean().default(true).description('命令中的 Windows 路径默认转为 `/mnt/...`。').volatile(),
|
|
95
101
|
startInSessionWorkspace: Schema.boolean().default(true).description('未传 `workdir` 时从会话目录启动;默认开。关闭后使用下方固定目录,该目录留空时为家目录 `~`。').volatile(),
|
|
96
102
|
workdir: Schema.string().default('').description('未传 `workdir` 时使用的固定 Linux 目录(如 `/mnt/d/project`),优先于「跟随会话工作区」;留空表示不指定。').volatile(),
|
|
97
103
|
dangerGuard: Schema.boolean().default(true).description('危险命令(删除、分区、关机等)需显式 `allowDangerous` 才放行;关闭后模型可直接执行。').volatile(),
|
|
104
|
+
allowRoot: Schema.boolean().default(false).description('允许 `wsl` 以 root 执行(`asRoot: true`)。WSL 的 root 免密,所以这项开关就是授权本身;默认关。').volatile(),
|
|
98
105
|
distro: Schema.string().default('').description('固定使用的发行版;留空表示使用系统默认(也可用 `DSH_WSL_DISTRO`)。').volatile(),
|
|
99
106
|
timeoutMs: Schema.number().default(0).description('默认命令超时(毫秒);0 表示使用内置默认(也可用 `DSH_WSL_TIMEOUT_MS`)。').volatile(),
|
|
100
107
|
}).description('dsh-wsl 的功能开关与默认值。')
|
|
@@ -583,6 +590,7 @@ async function selfInfo(config, runner) {
|
|
|
583
590
|
translatePaths: config.translatePaths,
|
|
584
591
|
startInSessionWorkspace: config.startInSessionWorkspace,
|
|
585
592
|
dangerGuard: config.dangerGuard,
|
|
593
|
+
allowRoot: config.allowRoot,
|
|
586
594
|
distro: config.distro,
|
|
587
595
|
commandTimeoutMs: config.commandTimeoutMs,
|
|
588
596
|
maxOutputBytes: config.maxOutputBytes,
|
|
@@ -601,6 +609,11 @@ export function apply(ctx, settings = {}) {
|
|
|
601
609
|
if (config.tools.wsl) ctx.tools.register(createWslTool({ ctx, config, runner }))
|
|
602
610
|
if (config.tools.path) ctx.tools.register(createWslPathTool({ config, runner }))
|
|
603
611
|
if (config.tools.env) ctx.tools.register(createWslEnvTool({ config, runner }))
|
|
612
|
+
if (config.tools.doctor) ctx.tools.register(createWslDoctorTool({ config, runner }))
|
|
613
|
+
// The installer is the one tool that is NOT registered by default: turning on
|
|
614
|
+
// its switch is the user's decision to let this plugin add software to their
|
|
615
|
+
// distribution.
|
|
616
|
+
if (config.tools.bootstrap) ctx.tools.register(createWslBootstrapTool({ config, runner }))
|
|
604
617
|
|
|
605
618
|
// ---------------------------------------------------------------- self info
|
|
606
619
|
//
|