dsh-plugin-wsl-env 0.14.0 → 0.16.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 +167 -14
- package/CONTRIBUTING.md +25 -33
- package/README.md +10 -10
- package/SECURITY.md +6 -4
- package/SUPPORT.md +8 -8
- 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 +4 -5
- package/cordis.patch.yml +8 -1
- package/docs/ARCHITECTURE.md +42 -35
- package/docs/COMPATIBILITY.md +121 -0
- package/docs/CONFIGURATION.md +254 -272
- package/docs/LIMITATIONS.md +78 -35
- package/docs/MIGRATION.md +16 -13
- package/docs/PARITY.md +4 -3
- package/docs/PERFORMANCE.md +11 -9
- package/docs/QUICKSTART.md +60 -17
- package/docs/README.zh.md +7 -7
- package/docs/RELEASING.md +257 -0
- package/docs/REQUIREMENTS.zh.md +77 -0
- package/docs/ROADMAP-1.0.md +105 -61
- package/docs/ROADMAP.md +15 -9
- package/docs/UPSTREAM-FILE-REFERENCE-SEAM.md +3 -3
- package/docs/UPSTREAM-FSIO-EXPORT.md +7 -7
- package/docs/UPSTREAM-OPEN-IN-APP.md +57 -0
- package/docs/UPSTREAM-SESSION-NOTICE.md +4 -4
- package/docs/VALUE.zh.md +144 -0
- package/docs/adr/0002-native-agent.md +348 -0
- package/docs/root-fs-routing.md +186 -0
- package/docs/screenshots/picker.png +0 -0
- package/docs/screenshots/preview.png +0 -0
- package/docs/screenshots/sandbox.png +0 -0
- package/docs/screenshots/terminal.png +0 -0
- package/lib/agent-confined.d.ts +29 -23
- package/lib/agent-confined.js +58 -25
- package/lib/agent-exec.d.ts +2 -3
- package/lib/agent-shared.js +1 -0
- package/lib/agent.d.ts +19 -8
- package/lib/agent.js +73 -13
- package/lib/fs-routing.d.ts +2 -1
- package/lib/fs-routing.js +13 -1
- package/lib/fs-substrate.d.ts +11 -9
- package/lib/fs-substrate.js +6 -9
- package/lib/fsio-agent.d.ts +2 -3
- package/lib/fsio-agent.js +2 -3
- package/lib/index.d.ts +77 -13
- package/lib/index.js +63 -13
- package/lib/listing.d.ts +19 -12
- package/lib/listing.js +34 -19
- package/lib/picker.js +4 -4
- package/lib/session-runtime.d.ts +5 -3
- package/lib/session-runtime.js +5 -4
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +13 -4
- package/scripts/diagnose.sh +1 -1
- package/scripts/doctor.mjs +83 -9
package/scripts/doctor.mjs
CHANGED
|
@@ -36,6 +36,7 @@ import { fileURLToPath } from "node:url";
|
|
|
36
36
|
import { parseDistroList } from "../lib/wsl.js";
|
|
37
37
|
import { AGENT_NAME, PROTOCOL_VERSION } from "../lib/agent-protocol.js";
|
|
38
38
|
import { sessionScopeSweepScript } from "../lib/session-scope.js";
|
|
39
|
+
import { parseListeningPorts, procNetScript } from "../lib/ports.js";
|
|
39
40
|
|
|
40
41
|
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
41
42
|
const WSL = "wsl.exe";
|
|
@@ -63,10 +64,10 @@ const STALE_SCOPE_REPORT = [
|
|
|
63
64
|
' if [ -n "$cg" ] && [ -r "/sys/fs/cgroup$cg/cgroup.procs" ]; then',
|
|
64
65
|
' while read -r pid; do',
|
|
65
66
|
' [ -n "$pid" ] || continue',
|
|
66
|
-
" if tr '\\0' ' ' < \"/proc/$pid/cmdline\" 2>/dev/null | grep -q -e 'wsl-agent.sh' -e 'dsh-wsl-agent'; then alive=1;
|
|
67
|
+
" if tr '\\0' ' ' < \"/proc/$pid/cmdline\" 2>/dev/null | grep -q -e 'wsl-agent.sh' -e 'dsh-wsl-agent'; then alive=1; pids=\"$pids $pid\"; fi",
|
|
67
68
|
' done < "/sys/fs/cgroup$cg/cgroup.procs"',
|
|
68
69
|
" fi",
|
|
69
|
-
' echo "$unit $([ "$alive" -eq 1 ] && echo active || echo stale)"',
|
|
70
|
+
' echo "$unit $([ "$alive" -eq 1 ] && echo active || echo stale) pids:${pids:-none}"',
|
|
70
71
|
"done",
|
|
71
72
|
].join("\n");
|
|
72
73
|
/** The agent-cache inventory: one `state digest` line per cached binary. */
|
|
@@ -254,7 +255,7 @@ function installRemedy(distro, family) {
|
|
|
254
255
|
* @returns {number} the exit status.
|
|
255
256
|
*/
|
|
256
257
|
function rejectReadOnly() {
|
|
257
|
-
console.error("doctor: --read-only refuses --fix/--install; run `pnpm run doctor --
|
|
258
|
+
console.error("doctor: --read-only refuses --fix/--install; run `pnpm run doctor --fix` instead");
|
|
258
259
|
return 2;
|
|
259
260
|
}
|
|
260
261
|
|
|
@@ -333,7 +334,7 @@ async function main(options) {
|
|
|
333
334
|
const bwrapPath = (await inDistro(distro, "command -v bwrap")).stdout.trim();
|
|
334
335
|
const install = installRemedy(distro, family);
|
|
335
336
|
if (bwrapPath.length === 0) {
|
|
336
|
-
check("sandbox.bwrap", "fail", "bubblewrap", "missing; every confined command fails closed without it", `${install}, or run "pnpm run bootstrap
|
|
337
|
+
check("sandbox.bwrap", "fail", "bubblewrap", "missing; every confined command fails closed without it", `${install}, or run "pnpm run bootstrap ${distro} --install"`);
|
|
337
338
|
} else {
|
|
338
339
|
check("sandbox.bwrap", "ok", "bubblewrap", bwrapPath);
|
|
339
340
|
const userns = await inDistro(distro, "bwrap --ro-bind / / -- true");
|
|
@@ -385,6 +386,14 @@ async function main(options) {
|
|
|
385
386
|
check("agent.cache", bad > 0 ? "warn" : "ok", "Agent cache", `${entries.length} cached binary(ies)${bad > 0 ? `, ${bad} not executable` : ""}`, bad > 0 ? `rm -rf ~/.cache/dsh-wsl/agent inside "${distro}"; the next command repopulates it` : undefined);
|
|
386
387
|
}
|
|
387
388
|
|
|
389
|
+
const scopes = (await inDistro(distro, STALE_SCOPE_REPORT)).stdout.trim();
|
|
390
|
+
const scopeLines = scopes === "none" || scopes.length === 0 ? [] : scopes.split(/\r?\n/);
|
|
391
|
+
const stale = scopeLines.filter((line) => /\sstale(\s|$)/.test(line)).length;
|
|
392
|
+
const livePids = scopeLines.flatMap((line) => {
|
|
393
|
+
const m = /\spids:([0-9 ]+)$/.exec(line);
|
|
394
|
+
if (m === null) return [];
|
|
395
|
+
return (m[1] ?? "").trim().split(/\s+/).filter((p) => p.length > 0);
|
|
396
|
+
});
|
|
388
397
|
// ---- resident handshake --------------------------------------------------
|
|
389
398
|
const agentScript = join(ROOT, "agent", "wsl-agent.sh");
|
|
390
399
|
if (!agentScript.startsWith("/")) {
|
|
@@ -394,8 +403,11 @@ async function main(options) {
|
|
|
394
403
|
} else {
|
|
395
404
|
const handshake = await probeResident(distro, agentScript);
|
|
396
405
|
if (handshake.ok) {
|
|
406
|
+
const live = livePids.length > 0
|
|
407
|
+
? `; a live resident (pid${livePids.length > 1 ? "s" : ""} ${livePids.join(", ")}) is already running in this distro — this check starts a fresh one, which cannot read its last error`
|
|
408
|
+
: "";
|
|
397
409
|
check("agent.handshake", handshake.digestMatches ? "ok" : "fail", "Agent handshake",
|
|
398
|
-
`pid ${handshake.pid}, protocol ${PROTOCOL_VERSION}, digest ${handshake.digestMatches ? "matches" : "MISMATCH — the distro reads a different script than this package"}`,
|
|
410
|
+
`pid ${handshake.pid}, protocol ${PROTOCOL_VERSION}, digest ${handshake.digestMatches ? "matches" : "MISMATCH — the distro reads a different script than this package"}${live}`,
|
|
399
411
|
handshake.digestMatches ? undefined : "resync the runtime copy (pnpm run sync:windows), then restart the app");
|
|
400
412
|
} else {
|
|
401
413
|
check("agent.handshake", "fail", "Agent handshake", handshake.detail, "restart the DSH app; if it persists, delete ~/.cache/dsh-wsl/agent inside the distro and rerun the doctor");
|
|
@@ -403,13 +415,75 @@ async function main(options) {
|
|
|
403
415
|
}
|
|
404
416
|
|
|
405
417
|
// ---- stale session scopes ------------------------------------------------
|
|
406
|
-
const scopes = (await inDistro(distro, STALE_SCOPE_REPORT)).stdout.trim();
|
|
407
|
-
const scopeLines = scopes === "none" || scopes.length === 0 ? [] : scopes.split(/\r?\n/);
|
|
408
|
-
const stale = scopeLines.filter((line) => line.endsWith(" stale")).length;
|
|
409
418
|
if (scopeLines.length === 0) check("runtime.scopes", "info", "Session scopes", "none (no user manager, or none left behind)");
|
|
410
419
|
else if (stale === 0) check("runtime.scopes", "ok", "Session scopes", `${scopeLines.length} live, none stale`);
|
|
411
420
|
else check("runtime.scopes", "warn", "Session scopes", `${stale} stale of ${scopeLines.length}`, "--fix stops them (the same sweep the session preflight runs); they are safe to stop when no agent process is inside");
|
|
412
421
|
|
|
422
|
+
// ---- sessions across running distros (WSL-309) ----------------------------
|
|
423
|
+
// The single-distro checks above answer "is this distro healthy?"; this
|
|
424
|
+
// survey answers "what is running right now, everywhere?" — the concurrent
|
|
425
|
+
// sessions coordinate routing makes possible, made visible. Idle timers are
|
|
426
|
+
// the host process's private state and stay out: a scope's presence and its
|
|
427
|
+
// resident pids are the facts the distro side can attest.
|
|
428
|
+
// The distro lists both surveys share: `run` settles an object
|
|
429
|
+
// ({ ok, stdout, code }); `-q` prints plain names, and the running list's
|
|
430
|
+
// header is dropped by the intersection with them. The default distro
|
|
431
|
+
// carries a localized marker ("ubuntu (默认值)" / "ubuntu (default)") —
|
|
432
|
+
// strip a trailing parenthesized note before the intersection.
|
|
433
|
+
const distroNames = ((await run([WSL, "--list", "-q"])).stdout ?? "").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
434
|
+
const running = ((await run([WSL, "--list", "--running"])).stdout ?? "").split(/\r?\n/).map((line) => line.trim().replace(/\s+\([^)]*\)$/, "")).filter((line) => distroNames.includes(line));
|
|
435
|
+
if (!options.json) {
|
|
436
|
+
console.log("");
|
|
437
|
+
console.log("sessions across running distros");
|
|
438
|
+
if (running.length === 0) {
|
|
439
|
+
console.log(" (none)");
|
|
440
|
+
} else {
|
|
441
|
+
for (const d of running) {
|
|
442
|
+
const report = (await inDistro(d, STALE_SCOPE_REPORT)).stdout.trim();
|
|
443
|
+
const lines = report === "none" || report.length === 0 ? [] : report.split(/\r?\n/);
|
|
444
|
+
if (report === "none") {
|
|
445
|
+
console.log(` ${d} no user manager — scopes unavailable`);
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
if (lines.length === 0) {
|
|
449
|
+
console.log(` ${d} no session scopes`);
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
for (const line of lines) {
|
|
453
|
+
const m = /^(\S+) (active|stale) pids:(.*)$/.exec(line);
|
|
454
|
+
if (m === null) { console.log(` ${d} ${line}`); continue; }
|
|
455
|
+
const scope = m[1] ?? line;
|
|
456
|
+
const state = m[2] ?? "";
|
|
457
|
+
const pidNote = m[3] === "none" ? "" : ` resident pids:${(m[3] ?? "").trim()}`;
|
|
458
|
+
console.log(` ${d} ${scope} ${state}${pidNote}`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ---- listening ports across running distros (WSL-308) ----------------------
|
|
465
|
+
// The port poller serves the model's session (DSH_WSL_PORTS); this survey
|
|
466
|
+
// serves the human, with the two actions the facts imply: the localhost URL
|
|
467
|
+
// WSL2's forwarding already makes reachable, and the netsh line that would
|
|
468
|
+
// extend reach beyond the host (printed, never applied).
|
|
469
|
+
if (!options.json) {
|
|
470
|
+
const portScript = procNetScript();
|
|
471
|
+
let anyPorts = false;
|
|
472
|
+
for (const d of running) {
|
|
473
|
+
const answer = await inDistro(d, portScript);
|
|
474
|
+
const ports = parseListeningPorts(answer.stdout);
|
|
475
|
+
if (ports.length === 0) continue;
|
|
476
|
+
anyPorts = true;
|
|
477
|
+
const ip = ((await inDistro(d, "hostname -I")).stdout ?? "").trim().split(/\s+/)[0] ?? "";
|
|
478
|
+
console.log("");
|
|
479
|
+
console.log(`listening ports in ${d}`);
|
|
480
|
+
for (const port of ports) {
|
|
481
|
+
console.log(` :${port} http://localhost:${port} (LAN: netsh interface portproxy add v4tov4 listenport=${port} listenaddress=0.0.0.0 connectport=${port} connectaddress=${ip})`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (!anyPorts && running.length > 0) console.log("\nno listening ports in the running distros");
|
|
485
|
+
}
|
|
486
|
+
|
|
413
487
|
// ---- peers ---------------------------------------------------------------
|
|
414
488
|
const manifest = /** @type {{ peerDependencies?: Record<string, string> }} */ (JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")));
|
|
415
489
|
const peers = Object.entries(manifest.peerDependencies ?? {});
|
|
@@ -442,7 +516,7 @@ async function main(options) {
|
|
|
442
516
|
if (/^\s*-\s*id:\s*(wsl-shell|wsl-fs)\s*$/m.test(text)) trapped.push(entry.name);
|
|
443
517
|
}
|
|
444
518
|
if (trapped.length > 0) {
|
|
445
|
-
check("config.preset-trap", "warn", "Profile patches", `nested rows patched by id in: ${trapped.join(", ")}`, "wsl-shell and wsl-fs live inside preset-wsl; an id patch matches nothing and is
|
|
519
|
+
check("config.preset-trap", "warn", "Profile patches", `nested rows patched by id in: ${trapped.join(", ")}`, "wsl-shell and wsl-fs live inside preset-wsl; an id patch matches nothing and is ignored with a warning. Run \"pnpm run preset --check <patch>\" to get the complete block, or see docs/CONFIGURATION.md#configure-wsl-shell-and-wsl-fs");
|
|
446
520
|
} else {
|
|
447
521
|
check("config.profiles", "ok", "Profile patches", `${seen} profile patch(es) scanned`);
|
|
448
522
|
}
|