dsh-plugin-wsl-env 0.10.0 → 0.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/CHANGELOG.md +39 -0
- package/README.md +2 -1
- package/docs/CONFIGURATION.md +1 -1
- package/docs/LIMITATIONS.md +6 -0
- package/docs/README.zh.md +2 -1
- package/docs/ROADMAP.md +79 -0
- package/lib/agent-shared.js +33 -5
- package/lib/agent.d.ts +103 -0
- package/lib/agent.js +144 -8
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,45 @@ were part of the record.
|
|
|
16
16
|
|
|
17
17
|
## [Unreleased]
|
|
18
18
|
|
|
19
|
+
## [0.11.0] - 2026-10-18
|
|
20
|
+
|
|
21
|
+
The shared read agent is the native binary (A7): unconfined reads now use the
|
|
22
|
+
packaged static-musl agent with the shell as the fallback, cutting 16
|
|
23
|
+
concurrent reads from 342-614 ms to 2.4-8.2 ms. The A6 FS measurement and
|
|
24
|
+
metrics ship with it.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **The shared read agent is native (A7).** `WslAgent` gained an async
|
|
29
|
+
`prepare()` hook; `sharedAgent` uses it to select the packaged native binary
|
|
30
|
+
through `agentBackend` (`auto`/`native`), with the shell as the fallback
|
|
31
|
+
(`shell`, a failed preparation, or a native HELLO/runtime failure; the reads
|
|
32
|
+
never fail closed). The A6 probe measures 16 concurrent shared reads at
|
|
33
|
+
2.4-8.2 ms, down from 342-614 ms on the shell. The native shared agent also
|
|
34
|
+
serves the picker, the subprocess activity probes and the non-session
|
|
35
|
+
command path, so those inherit EXEC concurrency.
|
|
36
|
+
|
|
37
|
+
- **`WslAgent.metrics()`.** Per-kind queue wait (sent -> ACK), execution time
|
|
38
|
+
(ACK -> settle), in-flight peak, rejected and cancelled counters; the
|
|
39
|
+
47-check lifecycle suite covers the counters and the reset.
|
|
40
|
+
|
|
41
|
+
- **`test/probe/fs-queue.mjs`.** A live measurement of both FS agents. The A6
|
|
42
|
+
run showed the shared shell reads were the batch bottleneck (16 concurrent
|
|
43
|
+
`stat`/`read`/`list` at 342-614 ms, queue up to 583 ms) while native FS ops
|
|
44
|
+
execute in 0.2-0.3 ms; the A7 run measures the native shared reads at
|
|
45
|
+
2.4-8.2 ms. v6's lock removal only takes 16 native writes from 7.7 ms to
|
|
46
|
+
~5 ms, so it stays deferred.
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- **The plan after the A6 measurement: A7 (native shared reads) landed, v6
|
|
51
|
+
deferred.** [`docs/ROADMAP.md`](docs/ROADMAP.md) records the done, next and
|
|
52
|
+
deferred steps; ADR 0002, LIMITATIONS and the READMEs describe the native
|
|
53
|
+
shared reads and the v6 deferral.
|
|
54
|
+
|
|
55
|
+
- `test/probe/fs-queue.mjs` and the README/LIMITATIONS/ROADMAP describe the
|
|
56
|
+
native shared reads; `test/agent-shared.test.mjs` covers the shell selection.
|
|
57
|
+
|
|
19
58
|
## [0.10.0] - 2026-10-18
|
|
20
59
|
|
|
21
60
|
The native in-distro agent is the default session-runtime backend: `agentBackend: auto`
|
package/README.md
CHANGED
|
@@ -124,7 +124,7 @@ preset-wsl (the wsl agent preset; its settings are separate from the app's)
|
|
|
124
124
|
|
|
125
125
|
**How a command runs.** A session gets one resident agent inside a systemd user scope. By default that agent is the native `dsh-wsl-agent` — the packaged static-musl binary for the distro's architecture, selected by `agentBackend: auto`; the runtime's shell agent is the fallback when the binary is missing or fails its copy/digest/exec check, and `agentBackend: shell` forces it while `native` refuses instead of falling back. The agent executes each command in your login shell under a bubblewrap mount profile. Because the scope outlives the command, a background dev server survives, `/tmp` is the distro's real `/tmp`, and `ps` sees the whole session. With `sessionSandbox: false`, the agent stays per distro, each command gets its own bwrap profile (`--unshare-pid`, a fresh `/tmp`), background processes do not survive, and the native backend is not used. If the agent is unavailable (or you set `agent: false`), the one-shot `wsl.exe` path runs the command instead — same result, slightly slower, and per-command isolation.
|
|
126
126
|
|
|
127
|
-
**How file operations run.** Reads, writes and searches also happen inside the distro, on its own filesystem, with the distro's own tools. A search over a distro workspace runs the distro's `rg`, never the Windows binary over the 9p share. File writes are checked against the same policy the command sandbox enforces; with `wsl-fs` `sessionSandbox` on (the shipped preset), a confined write runs in the same session runtime as the commands — the same native/shell agent — so it sees the same `/tmp` and workspace mounts. Reads use the shared shell
|
|
127
|
+
**How file operations run.** Reads, writes and searches also happen inside the distro, on its own filesystem, with the distro's own tools. A search over a distro workspace runs the distro's `rg`, never the Windows binary over the 9p share. File writes are checked against the same policy the command sandbox enforces; with `wsl-fs` `sessionSandbox` on (the shipped preset), a confined write runs in the same session runtime as the commands — the same native/shell agent — so it sees the same `/tmp` and workspace mounts. Reads use the shared native read agent (A7; the packaged binary, shell fallback): 16 concurrent reads measure ~2.4 ms, down from 342-614 ms on the shell.
|
|
128
128
|
|
|
129
129
|
## Sandbox
|
|
130
130
|
|
|
@@ -192,6 +192,7 @@ Run `pnpm install` once to get the development dependencies. The tests use the p
|
|
|
192
192
|
|---|---|
|
|
193
193
|
| [docs/CONFIGURATION.md](docs/CONFIGURATION.md) | every configuration key, and how to override it |
|
|
194
194
|
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | the plugin's parts and how they attach, path coordinates, the sandbox, testing, file layout |
|
|
195
|
+
| [docs/ROADMAP.md](docs/ROADMAP.md) | the plan: done, next (A7 native shared reads, A8 aarch64), deferred (v6) |
|
|
195
196
|
| [docs/LIMITATIONS.md](docs/LIMITATIONS.md) | what the plugin does not do, and why |
|
|
196
197
|
| [docs/VALUE.zh.md](docs/VALUE.zh.md) | the plugin's value in Chinese: what it is worth, what it costs, and when not to use it |
|
|
197
198
|
| [CONTRIBUTING.md](CONTRIBUTING.md) | the development loop, gates, conventions, verification |
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -27,7 +27,7 @@ has its own table at the end of this page.
|
|
|
27
27
|
| | `loginShell` | `true` *(shipped)* | use `<shell> -lc`, which sources your profile, instead of a bare `-c`. Applies only to the POSIX family; a non-POSIX shell (fish, csh) still gets a bare `-c` |
|
|
28
28
|
| | `agent` | `true` | run commands over the resident in-distro agent (one long-lived process; in-distro timeout); the one-shot `wsl.exe` path is fallen back to only when the agent is out of service. Anything else — a cwd refusal, a sandbox refusal — is the command's own verdict and is raised, not retried on the other path |
|
|
29
29
|
| | `sessionSandbox` | `true` *(shipped)* | run this session's commands in one persistent systemd user scope (ADR 0001): a background dev server survives the command that started it, `/tmp` is the distro's real `/tmp` under `workspace-write`, and `ps` sees the session. The executor holds the runtime for the session and stops the scope from the preset's disposer, which reaps the cgroup. Requires `agent: true`, systemd as PID 1, a reachable user manager and `Linger` (`loginctl enable-linger <user>`); the preflight refuses with the remedy when systemd, the user manager, `systemd-run` or `Linger` is missing (a success is cached; a failure is re-probed, so following the remedy needs no app restart). On in the shipped preset; set `false` for the per-command shape |
|
|
30
|
-
| | `agentBackend` | `auto` | the session runtime's in-distro agent. `auto` uses the native agent when a verified source is available — the packaged per-arch static-musl binary at `agent/native/linux-<arch>/dsh-wsl-agent` (x86_64 and aarch64; runs on glibc and musl distros), or the `DSH_WSL_AGENT_NATIVE` development override — and otherwise the shipped shell agent; `native` fails the first command when it cannot prepare instead of silently falling back; `shell` forces the shell agent. Confined writes share the runtime, so they follow the same selection. The native backend is the default (static musl x86_64/aarch64); the shell agent is the fallback, and `native` refuses instead of falling back |
|
|
30
|
+
| | `agentBackend` | `auto` | the session runtime's in-distro agent. `auto` uses the native agent when a verified source is available — the packaged per-arch static-musl binary at `agent/native/linux-<arch>/dsh-wsl-agent` (x86_64 and aarch64; runs on glibc and musl distros), or the `DSH_WSL_AGENT_NATIVE` development override — and otherwise the shipped shell agent; `native` fails the first command when it cannot prepare instead of silently falling back; `shell` forces the shell agent. Confined writes share the runtime, so they follow the same selection. The native backend is the default (static musl x86_64/aarch64); the shell agent is the fallback, and `native` refuses instead of falling back. This key covers the session runtime (commands and confined writes) and, since A7, the shared read agent (unconfined reads), which also follows this selection with the shell fallback |
|
|
31
31
|
| | `sandbox` | `true` | confine commands with `bubblewrap`; `false` disables the sandbox. Needs a usable `bwrap` inside the distro and fails closed without one (`SANDBOX_UNAVAILABLE`, never an unconfined run) — see [Turning the sandbox off](#turning-the-sandbox-off) |
|
|
32
32
|
| | `maskWindowsDrive` | `false` | shadow `/mnt` with an empty tmpfs in every confined profile: the Windows drive's files cannot be read or exfiltrated and its executables cannot be launched through interop — the hole that lets a "sandboxed" command reach your full Windows token. A narrowing, not a closure (a command can still write an executable into the workspace and run it — binfmt dispatches on content, not location), so enforcement stays `partial` and the result's sandbox fact reports `windowsDrive: "masked"`. The complete closure is distro-level: `[interop] enabled=false` in `wsl.conf` |
|
|
33
33
|
| | `cwd` | `''` | default working directory; empty means the distro user's home |
|
package/docs/LIMITATIONS.md
CHANGED
|
@@ -119,6 +119,12 @@ crosses the 9p share. What to know about the one substrate there is:
|
|
|
119
119
|
call. The session runtime path — the shipped default — is cleaned by its
|
|
120
120
|
scope. A systemd-scoped one-shot (with explicit environment pass-through) is
|
|
121
121
|
not shipped; prefer `agent: true` and the session runtime for long commands.
|
|
122
|
+
- **Batch file reads share one agent per distro.** Since A7 the shared read
|
|
123
|
+
agent is the native binary, so 16 concurrent `stat`/`read`/`list` calls
|
|
124
|
+
complete in 2.4-4.1 ms (A6 measured 342-614 ms on the shell). The first call
|
|
125
|
+
pays the native preparation/cache cost (~70 ms once); `agentBackend: shell`
|
|
126
|
+
forces the shell reads. v6's FS concurrency would only shave the native
|
|
127
|
+
runtime's 7.7 ms/16 writes and is deferred.
|
|
122
128
|
- **The native backend is the default; aarch64 is build-tested only.** The
|
|
123
129
|
packaged x86_64 musl binary runs the 18-check suite in CI. The same job
|
|
124
130
|
builds the aarch64 binary, but the runner cannot execute it, so its
|
package/docs/README.zh.md
CHANGED
|
@@ -125,7 +125,7 @@ preset-wsl(wsl agent preset;这个环境里的服务相互独立)
|
|
|
125
125
|
|
|
126
126
|
**命令的执行路径。** 一个会话有一个常驻 agent,运行在 systemd user scope 内。默认它是 native `dsh-wsl-agent`——按 distro 架构选择的包内静态 musl 二进制,由 `agentBackend: auto` 选中;二进制缺失或 copy/digest/exec 校验失败时回退到 runtime 的 shell agent,`agentBackend: shell` 强制 shell,`native` 则拒绝回退。agent 在你的登录 shell 中、bubblewrap mount profile 内执行每条命令。scope 比命令活得久,所以后台 dev server 存活、`/tmp` 是子系统真实 `/tmp`、`ps` 能看到整个会话。设 `sessionSandbox: false` 时,agent 仍是每子系统一个、每条命令一套 bwrap profile(`--unshare-pid`、临时 `/tmp`),后台进程不存活,且不使用 native 后端。agent 不可用时(或 `agent: false`),改走一次性 `wsl.exe`——结果一致、略慢、仍是每命令隔离。
|
|
127
127
|
|
|
128
|
-
**文件操作的执行路径。** 读、写、搜索同样发生在子系统内部:使用子系统自己的磁盘与工具。子系统工作区的搜索运行子系统内的 `rg`,不使用 Windows 侧副本走 9p 共享。文件写入与命令沙箱使用同一份策略检查;`wsl-fs` 的 `sessionSandbox` 打开时(默认),受限写入跑在与命令相同的会话 runtime 里(同一个 native/shell agent),看到相同的 `/tmp` 与 workspace mount。读操作走共享的 shell
|
|
128
|
+
**文件操作的执行路径。** 读、写、搜索同样发生在子系统内部:使用子系统自己的磁盘与工具。子系统工作区的搜索运行子系统内的 `rg`,不使用 Windows 侧副本走 9p 共享。文件写入与命令沙箱使用同一份策略检查;`wsl-fs` 的 `sessionSandbox` 打开时(默认),受限写入跑在与命令相同的会话 runtime 里(同一个 native/shell agent),看到相同的 `/tmp` 与 workspace mount。读操作走共享的 native 读 agent(A7;包内二进制,shell 回退):16 个并发读实测约 2.4 ms,此前 shell 为 342–614 ms。
|
|
129
129
|
|
|
130
130
|
## 沙箱
|
|
131
131
|
|
|
@@ -193,6 +193,7 @@ pnpm run probe:sandbox-off # 验证 sandbox: false 确实关掉两侧沙箱
|
|
|
193
193
|
|---|---|
|
|
194
194
|
| [docs/CONFIGURATION.md](CONFIGURATION.md) | 每个配置键,以及如何覆盖 |
|
|
195
195
|
| [docs/ARCHITECTURE.md](ARCHITECTURE.md) | provider、挂载方式、路径坐标、沙箱、测试分层、文件布局 |
|
|
196
|
+
| [docs/ROADMAP.md](ROADMAP.md) | 路线图:已完成、下一步(A7 native 共享读、A8 aarch64)、已推迟(v6) |
|
|
196
197
|
| [docs/LIMITATIONS.md](LIMITATIONS.md) | 插件不做的事情,以及原因 |
|
|
197
198
|
| [docs/VALUE.zh.md](VALUE.zh.md) | 本插件的价值、代价,以及什么情况下不该用它 |
|
|
198
199
|
| [CONTRIBUTING.md](../CONTRIBUTING.md) | 开发循环、质量门、约定、已验证内容 |
|
package/docs/ROADMAP.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
The native in-distro agent is the default session-runtime backend since 0.10.0
|
|
4
|
+
([ADR 0002](adr/0002-native-agent.md)). This plan was updated after the A6 FS
|
|
5
|
+
measurement; the numbers are in the ADR's
|
|
6
|
+
[FS queue measurement](adr/0002-native-agent.md#fs-queue-measurement-a6-2026-10-18)
|
|
7
|
+
section.
|
|
8
|
+
|
|
9
|
+
## Done
|
|
10
|
+
|
|
11
|
+
| Step | Outcome |
|
|
12
|
+
|---|---|
|
|
13
|
+
| A1 | `wsl-shell.agentBackend` (`auto`/`native`/`shell`): the native session-runtime agent, the shell fallback, the strict `native` mode |
|
|
14
|
+
| A2 | packaged static-musl `agent/native/linux-x86_64` and `linux-aarch64` binaries, selected by the distro's `uname -m`; the dev override |
|
|
15
|
+
| A3 | the native HELLO carries `/proc/self/exe`'s sha256; the host binds it with `expectedDigest` |
|
|
16
|
+
| A4 | the native-session probe (scope, EXEC, FS, KILL, digest, teardown, 8/8) in the real-machine suite; the warm-cache and stdin-EPIPE fixes |
|
|
17
|
+
| A5 | the native agent is the documented default; 0.10.0 published (npm + GitHub Release) |
|
|
18
|
+
| A6 | `WslAgent.metrics()` and `test/probe/fs-queue.mjs`; the shared shell read agent is the measured batch bottleneck |
|
|
19
|
+
|
|
20
|
+
## Next
|
|
21
|
+
|
|
22
|
+
### A7 — native shared read agent (v5) — done
|
|
23
|
+
|
|
24
|
+
The shared unconfined read agent was the only remaining measured bottleneck
|
|
25
|
+
(16 concurrent reads 342-614 ms, queue up to 583 ms, versus native FS execution
|
|
26
|
+
0.2-0.3 ms). A7 swaps its shell for the native binary (v5) while keeping the
|
|
27
|
+
shell as the fallback:
|
|
28
|
+
|
|
29
|
+
- **selection:** the same `agentBackend` semantics — `auto` prepares the
|
|
30
|
+
packaged binary for the distro's architecture, `native` refuses instead of
|
|
31
|
+
falling back, `shell` forces the shell; the dev override still wins;
|
|
32
|
+
- **preparation:** `ensureNativeBackend` once per distro (cache/digest/exec
|
|
33
|
+
probe); the `WslAgent` with `expectedDigest` and `signalable: false`; on a
|
|
34
|
+
preparation or HELLO failure the shell agent serves the reads — fail-open to
|
|
35
|
+
the shell, never fail-closed;
|
|
36
|
+
- **surface:** the same six v5 FS ops and `dsh-fs` side channel; the
|
|
37
|
+
`fs-substrate`/`DistroFs` parse is unchanged; the A2 differential covers the
|
|
38
|
+
records;
|
|
39
|
+
- **result:** `WslAgent` gained a `prepare()` hook and the shared agent now
|
|
40
|
+
selects the native entry through it. `test/probe/fs-queue.mjs` measures 16
|
|
41
|
+
concurrent shared reads at **2.4-4.1 ms** (from 342-614 ms); the first start
|
|
42
|
+
pays the ensure/cache cost (~70 ms once). The shell still serves the reads
|
|
43
|
+
under `agentBackend: shell` and after a native failure.
|
|
44
|
+
|
|
45
|
+
### A8 — aarch64 functional validation
|
|
46
|
+
|
|
47
|
+
The aarch64 binary is built by CI but never executed there. Run the 18-check
|
|
48
|
+
spike suite and the native-session probe on real aarch64 WSL or qemu; record
|
|
49
|
+
the result in the ADR.
|
|
50
|
+
|
|
51
|
+
### A9 — maintenance
|
|
52
|
+
|
|
53
|
+
- Release A7 as a minor (0.11.0) through the tag workflow — done;
|
|
54
|
+
- `pnpm run sync:windows` after each release, then restart the app;
|
|
55
|
+
- keep the probes and the documentation current.
|
|
56
|
+
|
|
57
|
+
## Deferred
|
|
58
|
+
|
|
59
|
+
- **v6 (FS concurrency / ERR frames / per-request env).** The A6 measurement
|
|
60
|
+
shows the lock-removal gain is marginal (~7.7 -> ~5 ms per 16 writes, ~12.2
|
|
61
|
+
-> ~8 ms per 16 reads). Revisit only if the native shared v5 still queues
|
|
62
|
+
measurably or the ERR/env framing is needed for another reason.
|
|
63
|
+
- **Event stream.** No current consumer: the `fs.watch` contract is coarse and
|
|
64
|
+
the inotify watcher satisfies it; terminal activity and ports poll.
|
|
65
|
+
- **Shell retirement.** Not planned. The shell agent remains the fallback and
|
|
66
|
+
the shared read path until A7 lands, and the protocol/script stays supported.
|
|
67
|
+
|
|
68
|
+
## Not planned
|
|
69
|
+
|
|
70
|
+
- Streaming command output (the harness has no consumer for it).
|
|
71
|
+
- A per-subagent agent: subagents share the session runtime by workspace and
|
|
72
|
+
policy; separate runtimes only if a measurable need appears.
|
|
73
|
+
|
|
74
|
+
## Decision rules
|
|
75
|
+
|
|
76
|
+
A step moves out of Deferred only with a measurement or a concrete consumer,
|
|
77
|
+
not because the protocol makes it possible. A7 itself is conditional on the
|
|
78
|
+
batch-read demand (the GUI file tree, parallel subagents) being real; the
|
|
79
|
+
single-op latency is the ~22 ms host round trip, not the FS backend.
|
package/lib/agent-shared.js
CHANGED
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
* @module dsh-plugin-wsl/agent-shared
|
|
15
15
|
*/
|
|
16
16
|
import { createHash } from "node:crypto";
|
|
17
|
+
import { spawn } from "node:child_process";
|
|
17
18
|
import { readFileSync } from "node:fs";
|
|
18
19
|
import { fileURLToPath } from "node:url";
|
|
19
20
|
import { toLinuxPath } from "./paths.js";
|
|
20
|
-
import { WslAgent } from "./agent.js";
|
|
21
|
+
import { pinnedWindowsEnv, WslAgent } from "./agent.js";
|
|
22
|
+
import { configuredAgentBackend, ensureNativeBackend, NATIVE_AGENT_ENV } from "./native-agent.js";
|
|
21
23
|
const AGENT_SCRIPT_PATH = fileURLToPath(new URL("../agent/wsl-agent.sh", import.meta.url));
|
|
22
24
|
// The script the distro reads must be byte-identical to the copy this
|
|
23
25
|
// package shipped: the deployed layout reads it from the /mnt/c mirror, and
|
|
@@ -25,6 +27,9 @@ const AGENT_SCRIPT_PATH = fileURLToPath(new URL("../agent/wsl-agent.sh", import.
|
|
|
25
27
|
// Both resident factories (plain and confined) pin it.
|
|
26
28
|
const AGENT_SCRIPT_DIGEST = createHash("sha256").update(readFileSync(AGENT_SCRIPT_PATH)).digest("hex");
|
|
27
29
|
const sharedAgents = new Map();
|
|
30
|
+
// Distros where the native shared agent failed at runtime; the reads fail open
|
|
31
|
+
// to the shell instead of retrying the same broken binary on every call.
|
|
32
|
+
const nativeFailed = new Set();
|
|
28
33
|
/** The agent script as a host-side path, for factories that need both spellings. */
|
|
29
34
|
export const agentScriptPath = AGENT_SCRIPT_PATH;
|
|
30
35
|
/** The sha256 of the shipped agent script; every resident verifies its HELLO against it. */
|
|
@@ -37,9 +42,32 @@ export const agentScriptDigest = AGENT_SCRIPT_DIGEST;
|
|
|
37
42
|
*/
|
|
38
43
|
export function sharedAgent(distro) {
|
|
39
44
|
let agent = sharedAgents.get(distro);
|
|
40
|
-
if (agent
|
|
41
|
-
agent
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
if (agent !== undefined && !agent.unavailable)
|
|
46
|
+
return agent;
|
|
47
|
+
if (agent !== undefined)
|
|
48
|
+
nativeFailed.add(distro);
|
|
49
|
+
agent = new WslAgent({
|
|
50
|
+
distro,
|
|
51
|
+
scriptPath: toLinuxPath(AGENT_SCRIPT_PATH),
|
|
52
|
+
expectedDigest: AGENT_SCRIPT_DIGEST,
|
|
53
|
+
prepare: async () => {
|
|
54
|
+
if (configuredAgentBackend() === "shell" || nativeFailed.has(distro))
|
|
55
|
+
return undefined;
|
|
56
|
+
const prepared = await ensureNativeBackend({ wslPath: "wsl.exe", distro, source: process.env[NATIVE_AGENT_ENV] ?? "" });
|
|
57
|
+
if (prepared === undefined)
|
|
58
|
+
return undefined;
|
|
59
|
+
return {
|
|
60
|
+
scriptPath: prepared.path,
|
|
61
|
+
expectedDigest: prepared.digest,
|
|
62
|
+
signalable: false,
|
|
63
|
+
spawnTransport: ({ wslPath, distro: target, argvPrefix }) => spawn(wslPath, ["-d", target, "--exec", ...argvPrefix, prepared.path], {
|
|
64
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
65
|
+
windowsHide: true,
|
|
66
|
+
env: pinnedWindowsEnv(process.env),
|
|
67
|
+
}),
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
sharedAgents.set(distro, agent);
|
|
44
72
|
return agent;
|
|
45
73
|
}
|
package/lib/agent.d.ts
CHANGED
|
@@ -59,6 +59,14 @@ export interface ExecResult {
|
|
|
59
59
|
export interface PendingRequest {
|
|
60
60
|
/** The request id, as the agent echoes it. */
|
|
61
61
|
id: string;
|
|
62
|
+
/** Which metric bucket this request belongs to. */
|
|
63
|
+
kind: "fs" | "exec";
|
|
64
|
+
/** When the frame was written, for the queue measurement. */
|
|
65
|
+
sentAt: number;
|
|
66
|
+
/** When the agent ACKed it, for the execution measurement. */
|
|
67
|
+
ackedAt?: number;
|
|
68
|
+
/** Whether the metrics recorded this request's settle. */
|
|
69
|
+
measured: boolean;
|
|
62
70
|
/** The frame lines (kept so a rebuild can re-send). */
|
|
63
71
|
lines: string[];
|
|
64
72
|
/** Settles a RES. */
|
|
@@ -80,6 +88,51 @@ export interface PendingRequest {
|
|
|
80
88
|
/** The caller was already settled; a late answer only cleans up. */
|
|
81
89
|
settled: boolean;
|
|
82
90
|
}
|
|
91
|
+
/** Per-kind request metrics. */
|
|
92
|
+
export interface RequestMetrics {
|
|
93
|
+
/** Requests that settled (successful, failed or cancelled). */
|
|
94
|
+
requests: number;
|
|
95
|
+
/** Total queue wait (sent -> ACK), ms. */
|
|
96
|
+
queueMs: number;
|
|
97
|
+
/** Largest single queue wait, ms. */
|
|
98
|
+
queueMaxMs: number;
|
|
99
|
+
/** Total execution time (ACK -> settle), ms. */
|
|
100
|
+
execMs: number;
|
|
101
|
+
/** Largest single execution time, ms. */
|
|
102
|
+
execMaxMs: number;
|
|
103
|
+
/** Settled with an error (not cancellation). */
|
|
104
|
+
failed: number;
|
|
105
|
+
/** Cancelled by the caller. */
|
|
106
|
+
cancelled: number;
|
|
107
|
+
/** Rejected before tracking: queue full, already aborted, or send failed. */
|
|
108
|
+
rejected: number;
|
|
109
|
+
}
|
|
110
|
+
/** The agent's cumulative request metrics. */
|
|
111
|
+
export interface AgentMetrics {
|
|
112
|
+
/** FS frames. */
|
|
113
|
+
fs: RequestMetrics;
|
|
114
|
+
/** EXEC frames (commands, ping). */
|
|
115
|
+
exec: RequestMetrics;
|
|
116
|
+
/** Peak number of pending requests. */
|
|
117
|
+
inFlightMax: number;
|
|
118
|
+
}
|
|
119
|
+
/** The entry an async preparation may select before the first spawn. */
|
|
120
|
+
export interface WslAgentPrepared {
|
|
121
|
+
/** The agent entrypoint (the shell script or the native cache path). */
|
|
122
|
+
scriptPath: string;
|
|
123
|
+
/** The HELLO digest to verify. */
|
|
124
|
+
expectedDigest?: string;
|
|
125
|
+
/** Whether USR1 can reach the agent (false for the native KILL-frame path). */
|
|
126
|
+
signalable?: boolean;
|
|
127
|
+
/** The process factory for this entry. */
|
|
128
|
+
spawnTransport?: (options: {
|
|
129
|
+
wslPath: string;
|
|
130
|
+
distro: string;
|
|
131
|
+
scriptPath: string;
|
|
132
|
+
argvPrefix: string[];
|
|
133
|
+
leaseMs: number;
|
|
134
|
+
}) => ChildProcess;
|
|
135
|
+
}
|
|
83
136
|
/**
|
|
84
137
|
* The Windows environment the transport starts from, pinned.
|
|
85
138
|
*
|
|
@@ -180,6 +233,10 @@ export declare class WslAgent {
|
|
|
180
233
|
idleTimer: NodeJS.Timeout | null;
|
|
181
234
|
/** The last failure, for `unavailableReason` and log messages. */
|
|
182
235
|
lastError: string;
|
|
236
|
+
/** Cumulative request metrics; see {@link metrics}. */
|
|
237
|
+
requestMetrics: AgentMetrics;
|
|
238
|
+
/** The optional async preparation hook. */
|
|
239
|
+
prepare: (() => Promise<WslAgentPrepared | undefined>) | undefined;
|
|
183
240
|
/**
|
|
184
241
|
* @param config - the agent configuration.
|
|
185
242
|
*/
|
|
@@ -248,6 +305,8 @@ export declare class WslAgent {
|
|
|
248
305
|
* refused instead of growing the request map without bound.
|
|
249
306
|
*/
|
|
250
307
|
maxPending?: number;
|
|
308
|
+
/** Optional async preparation selecting the entry before the first spawn. */
|
|
309
|
+
prepare?: () => Promise<WslAgentPrepared | undefined>;
|
|
251
310
|
});
|
|
252
311
|
/**
|
|
253
312
|
* Whether the agent is permanently out — the executor's fallback cue.
|
|
@@ -264,6 +323,18 @@ export declare class WslAgent {
|
|
|
264
323
|
* @returns resolved once the state is `ready`.
|
|
265
324
|
*/
|
|
266
325
|
start(): Promise<void>;
|
|
326
|
+
/**
|
|
327
|
+
* Apply an async preparation entry to this agent before the first spawn.
|
|
328
|
+
*
|
|
329
|
+
* @param entry - the selected entry.
|
|
330
|
+
*/
|
|
331
|
+
private applyPrepared;
|
|
332
|
+
/**
|
|
333
|
+
* Spawn the process and run the handshake.
|
|
334
|
+
*
|
|
335
|
+
* @returns resolved once the state is `ready`.
|
|
336
|
+
*/
|
|
337
|
+
private beginStart;
|
|
267
338
|
/**
|
|
268
339
|
* One stdout line from the agent. The first must be a matching HELLO.
|
|
269
340
|
*
|
|
@@ -384,6 +455,38 @@ export declare class WslAgent {
|
|
|
384
455
|
*
|
|
385
456
|
* @param signal - the signal to deliver (`SIGUSR1` is the cancel trap).
|
|
386
457
|
*/
|
|
458
|
+
/**
|
|
459
|
+
* The cumulative request metrics: queue wait (sent -> ACK) and execution
|
|
460
|
+
* time (ACK -> settle) per request kind, plus the peak in-flight count.
|
|
461
|
+
*
|
|
462
|
+
* @returns a snapshot the caller can keep.
|
|
463
|
+
*/
|
|
464
|
+
metrics(): AgentMetrics;
|
|
465
|
+
/** Reset the cumulative metrics (diagnostics/probes). */
|
|
466
|
+
resetMetrics(): void;
|
|
467
|
+
/**
|
|
468
|
+
* The metric bucket for a request kind.
|
|
469
|
+
* @param kind - fs or exec.
|
|
470
|
+
* @returns the bucket.
|
|
471
|
+
*/
|
|
472
|
+
private kindMetrics;
|
|
473
|
+
/**
|
|
474
|
+
* Record a request that never entered the pending map.
|
|
475
|
+
* @param kind - the bucket.
|
|
476
|
+
*/
|
|
477
|
+
private noteRejected;
|
|
478
|
+
/**
|
|
479
|
+
* Record the dequeue ACK: the request's queue wait.
|
|
480
|
+
* @param pending - the request.
|
|
481
|
+
*/
|
|
482
|
+
private recordAck;
|
|
483
|
+
/**
|
|
484
|
+
* Record a settle: the requests count, the execution time (or the queue wait
|
|
485
|
+
* for a request that never ACKed), and the outcome.
|
|
486
|
+
* @param pending - the request.
|
|
487
|
+
* @param error - the failure, when any.
|
|
488
|
+
*/
|
|
489
|
+
private recordSettle;
|
|
387
490
|
private signalAgent;
|
|
388
491
|
/**
|
|
389
492
|
* Shut the agent down: SHUTDOWN first, then kill after a short grace. The
|
package/lib/agent.js
CHANGED
|
@@ -40,6 +40,20 @@
|
|
|
40
40
|
import { spawn } from "node:child_process";
|
|
41
41
|
import { AgentUnavailableError, CwdError } from "./agent-errors.js";
|
|
42
42
|
import { AGENT_NAME, MAX_FRAME_BYTES, PROTOCOL_VERSION, encodeExecFrame, encodeFsFrame, encodeKill, encodeSetEnv, parseAgentLine } from "./agent-protocol.js";
|
|
43
|
+
/**
|
|
44
|
+
* A zeroed per-kind metrics record.
|
|
45
|
+
* @returns the record.
|
|
46
|
+
*/
|
|
47
|
+
function emptyRequestMetrics() {
|
|
48
|
+
return { requests: 0, queueMs: 0, queueMaxMs: 0, execMs: 0, execMaxMs: 0, failed: 0, cancelled: 0, rejected: 0 };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A zeroed metrics snapshot.
|
|
52
|
+
* @returns the snapshot.
|
|
53
|
+
*/
|
|
54
|
+
function emptyAgentMetrics() {
|
|
55
|
+
return { fs: emptyRequestMetrics(), exec: emptyRequestMetrics(), inFlightMax: 0 };
|
|
56
|
+
}
|
|
43
57
|
/** How long a rebuilt agent has to re-answer the handshake before giving up. */
|
|
44
58
|
const HANDSHAKE_TIMEOUT_MS = 15000;
|
|
45
59
|
/** Extra time the host watchdog waits past the request's own in-distro timeout. */
|
|
@@ -160,6 +174,8 @@ export class WslAgent {
|
|
|
160
174
|
this.rebuildUsed = false;
|
|
161
175
|
this.idleTimer = null;
|
|
162
176
|
this.lastError = "";
|
|
177
|
+
this.requestMetrics = emptyAgentMetrics();
|
|
178
|
+
this.prepare = config.prepare;
|
|
163
179
|
}
|
|
164
180
|
/**
|
|
165
181
|
* Whether the agent is permanently out — the executor's fallback cue.
|
|
@@ -187,9 +203,42 @@ export class WslAgent {
|
|
|
187
203
|
return Promise.resolve();
|
|
188
204
|
if (this.startPromise)
|
|
189
205
|
return this.startPromise;
|
|
206
|
+
const prepared = this.prepare?.();
|
|
207
|
+
if (prepared === undefined) {
|
|
208
|
+
const started = this.beginStart();
|
|
209
|
+
this.startPromise = started;
|
|
210
|
+
return started;
|
|
211
|
+
}
|
|
212
|
+
const started = prepared.then((entry) => {
|
|
213
|
+
if (entry !== undefined)
|
|
214
|
+
this.applyPrepared(entry);
|
|
215
|
+
return this.beginStart();
|
|
216
|
+
});
|
|
217
|
+
this.startPromise = started;
|
|
218
|
+
return started;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Apply an async preparation entry to this agent before the first spawn.
|
|
222
|
+
*
|
|
223
|
+
* @param entry - the selected entry.
|
|
224
|
+
*/
|
|
225
|
+
applyPrepared(entry) {
|
|
226
|
+
this.scriptPath = entry.scriptPath;
|
|
227
|
+
this.expectedDigest = entry.expectedDigest;
|
|
228
|
+
if (entry.signalable !== undefined)
|
|
229
|
+
this.signalable = entry.signalable;
|
|
230
|
+
if (entry.spawnTransport !== undefined)
|
|
231
|
+
this.configuredSpawn = entry.spawnTransport;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Spawn the process and run the handshake.
|
|
235
|
+
*
|
|
236
|
+
* @returns resolved once the state is `ready`.
|
|
237
|
+
*/
|
|
238
|
+
beginStart() {
|
|
190
239
|
this.state = "starting";
|
|
191
240
|
this.agentPid = undefined;
|
|
192
|
-
|
|
241
|
+
return new Promise((resolve, reject) => {
|
|
193
242
|
// stdio is all-pipe (the spawn options above), so every standard stream
|
|
194
243
|
// exists; the broad ChildProcess type models them as nullable.
|
|
195
244
|
const child = (this.process = (this.configuredSpawn ?? spawnDefault)({
|
|
@@ -286,7 +335,6 @@ export class WslAgent {
|
|
|
286
335
|
this.lastError = String(chunk).trim().slice(0, 2000) || this.lastError;
|
|
287
336
|
});
|
|
288
337
|
});
|
|
289
|
-
return this.startPromise;
|
|
290
338
|
}
|
|
291
339
|
/**
|
|
292
340
|
* One stdout line from the agent. The first must be a matching HELLO.
|
|
@@ -333,6 +381,7 @@ export class WslAgent {
|
|
|
333
381
|
const pending = this.pending.get(message.id);
|
|
334
382
|
if (pending) {
|
|
335
383
|
pending.started = true;
|
|
384
|
+
this.recordAck(pending);
|
|
336
385
|
if (pending.cancelled)
|
|
337
386
|
this.signalAgent("SIGUSR1");
|
|
338
387
|
pending.armWatchdog?.();
|
|
@@ -487,16 +536,26 @@ export class WslAgent {
|
|
|
487
536
|
*
|
|
488
537
|
* @param request - the request to register.
|
|
489
538
|
*/
|
|
490
|
-
track({ id, lines, resolve, reject, timeoutMs, signal }) {
|
|
539
|
+
track({ id, kind = "exec", lines, resolve, reject, timeoutMs, signal }) {
|
|
491
540
|
if (this.pending.size >= this.maxPending) {
|
|
541
|
+
this.noteRejected(kind);
|
|
492
542
|
reject(new Error(`the resident agent has ${this.pending.size} requests in flight (max ${this.maxPending}); retry when the queue drains`));
|
|
493
543
|
return;
|
|
494
544
|
}
|
|
495
545
|
const pending = {
|
|
496
546
|
id,
|
|
547
|
+
kind,
|
|
548
|
+
sentAt: performance.now(),
|
|
549
|
+
measured: false,
|
|
497
550
|
lines,
|
|
498
|
-
resolve
|
|
499
|
-
|
|
551
|
+
resolve: (result) => {
|
|
552
|
+
this.recordSettle(pending, undefined);
|
|
553
|
+
resolve(result);
|
|
554
|
+
},
|
|
555
|
+
reject: (error) => {
|
|
556
|
+
this.recordSettle(pending, error);
|
|
557
|
+
reject(error);
|
|
558
|
+
},
|
|
500
559
|
watchdog: null,
|
|
501
560
|
signal: signal ?? null,
|
|
502
561
|
onAbort: null,
|
|
@@ -525,9 +584,11 @@ export class WslAgent {
|
|
|
525
584
|
: null,
|
|
526
585
|
};
|
|
527
586
|
this.pending.set(id, pending);
|
|
587
|
+
this.requestMetrics.inFlightMax = Math.max(this.requestMetrics.inFlightMax, this.pending.size);
|
|
528
588
|
if (signal) {
|
|
529
589
|
if (signal.aborted) {
|
|
530
590
|
this.pending.delete(id);
|
|
591
|
+
this.noteRejected(kind);
|
|
531
592
|
reject(signal.reason instanceof Error ? signal.reason : new Error("aborted"));
|
|
532
593
|
return;
|
|
533
594
|
}
|
|
@@ -557,6 +618,7 @@ export class WslAgent {
|
|
|
557
618
|
if (!this.writeLines(lines)) {
|
|
558
619
|
this.clearPending(pending);
|
|
559
620
|
this.pending.delete(pending.id);
|
|
621
|
+
this.noteRejected(kind);
|
|
560
622
|
reject(new AgentUnavailableError("the agent exited before the request could be sent; it never ran"));
|
|
561
623
|
return;
|
|
562
624
|
}
|
|
@@ -580,7 +642,7 @@ export class WslAgent {
|
|
|
580
642
|
...encodeExecFrame({ id, cwd, argv, timeoutMs, maxOutputBytes }),
|
|
581
643
|
];
|
|
582
644
|
return new Promise((resolve, reject) => {
|
|
583
|
-
this.track({ id, lines, resolve, reject, timeoutMs, signal });
|
|
645
|
+
this.track({ id, kind: "exec", lines, resolve, reject, timeoutMs, signal });
|
|
584
646
|
});
|
|
585
647
|
}
|
|
586
648
|
/**
|
|
@@ -602,7 +664,7 @@ export class WslAgent {
|
|
|
602
664
|
const id = `r${this.nextRequestId++}`;
|
|
603
665
|
const lines = encodeFsFrame({ id, op, args, timeoutMs });
|
|
604
666
|
return new Promise((resolve, reject) => {
|
|
605
|
-
this.track({ id, lines, resolve, reject, timeoutMs, signal });
|
|
667
|
+
this.track({ id, kind: "fs", lines, resolve, reject, timeoutMs, signal });
|
|
606
668
|
});
|
|
607
669
|
}
|
|
608
670
|
/**
|
|
@@ -620,7 +682,7 @@ export class WslAgent {
|
|
|
620
682
|
return new Promise((resolve, reject) => {
|
|
621
683
|
const id = `r${this.nextRequestId++}`;
|
|
622
684
|
const lines = encodeExecFrame({ id, cwd: "/", argv: ["true"], timeoutMs: 0 });
|
|
623
|
-
this.track({ id, lines, resolve: () => resolve(), reject, timeoutMs: HANDSHAKE_TIMEOUT_MS, signal });
|
|
685
|
+
this.track({ id, kind: "exec", lines, resolve: () => resolve(), reject, timeoutMs: HANDSHAKE_TIMEOUT_MS, signal });
|
|
624
686
|
});
|
|
625
687
|
}
|
|
626
688
|
/**
|
|
@@ -637,6 +699,80 @@ export class WslAgent {
|
|
|
637
699
|
*
|
|
638
700
|
* @param signal - the signal to deliver (`SIGUSR1` is the cancel trap).
|
|
639
701
|
*/
|
|
702
|
+
/**
|
|
703
|
+
* The cumulative request metrics: queue wait (sent -> ACK) and execution
|
|
704
|
+
* time (ACK -> settle) per request kind, plus the peak in-flight count.
|
|
705
|
+
*
|
|
706
|
+
* @returns a snapshot the caller can keep.
|
|
707
|
+
*/
|
|
708
|
+
metrics() {
|
|
709
|
+
return {
|
|
710
|
+
fs: { ...this.requestMetrics.fs },
|
|
711
|
+
exec: { ...this.requestMetrics.exec },
|
|
712
|
+
inFlightMax: this.requestMetrics.inFlightMax,
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
/** Reset the cumulative metrics (diagnostics/probes). */
|
|
716
|
+
resetMetrics() {
|
|
717
|
+
this.requestMetrics = emptyAgentMetrics();
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* The metric bucket for a request kind.
|
|
721
|
+
* @param kind - fs or exec.
|
|
722
|
+
* @returns the bucket.
|
|
723
|
+
*/
|
|
724
|
+
kindMetrics(kind) {
|
|
725
|
+
return kind === "fs" ? this.requestMetrics.fs : this.requestMetrics.exec;
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Record a request that never entered the pending map.
|
|
729
|
+
* @param kind - the bucket.
|
|
730
|
+
*/
|
|
731
|
+
noteRejected(kind) {
|
|
732
|
+
this.kindMetrics(kind).rejected += 1;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Record the dequeue ACK: the request's queue wait.
|
|
736
|
+
* @param pending - the request.
|
|
737
|
+
*/
|
|
738
|
+
recordAck(pending) {
|
|
739
|
+
if (pending.ackedAt !== undefined)
|
|
740
|
+
return;
|
|
741
|
+
const now = performance.now();
|
|
742
|
+
pending.ackedAt = now;
|
|
743
|
+
const stats = this.kindMetrics(pending.kind);
|
|
744
|
+
const wait = now - pending.sentAt;
|
|
745
|
+
stats.queueMs += wait;
|
|
746
|
+
stats.queueMaxMs = Math.max(stats.queueMaxMs, wait);
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Record a settle: the requests count, the execution time (or the queue wait
|
|
750
|
+
* for a request that never ACKed), and the outcome.
|
|
751
|
+
* @param pending - the request.
|
|
752
|
+
* @param error - the failure, when any.
|
|
753
|
+
*/
|
|
754
|
+
recordSettle(pending, error) {
|
|
755
|
+
if (pending.measured)
|
|
756
|
+
return;
|
|
757
|
+
pending.measured = true;
|
|
758
|
+
const now = performance.now();
|
|
759
|
+
const stats = this.kindMetrics(pending.kind);
|
|
760
|
+
stats.requests += 1;
|
|
761
|
+
if (pending.ackedAt !== undefined) {
|
|
762
|
+
const exec = now - pending.ackedAt;
|
|
763
|
+
stats.execMs += exec;
|
|
764
|
+
stats.execMaxMs = Math.max(stats.execMaxMs, exec);
|
|
765
|
+
}
|
|
766
|
+
else {
|
|
767
|
+
const wait = now - pending.sentAt;
|
|
768
|
+
stats.queueMs += wait;
|
|
769
|
+
stats.queueMaxMs = Math.max(stats.queueMaxMs, wait);
|
|
770
|
+
}
|
|
771
|
+
if (pending.cancelled)
|
|
772
|
+
stats.cancelled += 1;
|
|
773
|
+
else if (error !== undefined)
|
|
774
|
+
stats.failed += 1;
|
|
775
|
+
}
|
|
640
776
|
signalAgent(signal) {
|
|
641
777
|
const pid = this.agentPid;
|
|
642
778
|
if (pid === undefined || !this.signalable)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-wsl-env",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "\u8ba9 DeepSeek Harness \u4ee5 WSL \u5b50\u7cfb\u7edf\u4f5c\u4e3a\u4f1a\u8bdd\u7684\u6267\u884c\u73af\u5883\uff1ashell\u3001\u6587\u4ef6\u8bfb\u5199\u4e0e\u547d\u4ee4\u6267\u884c\u5747\u5728\u5b50\u7cfb\u7edf\u5185\u5b8c\u6210\uff0c\u76ee\u5f55\u9009\u62e9\u5668\u4e0e GUI \u7ec8\u7aef\u540c\u6837\u5b9a\u4f4d\u5230\u5b50\u7cfb\u7edf\u8def\u5f84\uff0c\u5e76\u901a\u8fc7\u5b50\u7cfb\u7edf\u5185\u7684 bubblewrap \u5bf9\u547d\u4ee4\u8fdb\u884c\u6c99\u7bb1\u9694\u79bb",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -106,6 +106,7 @@
|
|
|
106
106
|
"docs/UPSTREAM-FSIO-EXPORT.md",
|
|
107
107
|
"docs/UPSTREAM-TERMINAL-TITLE.md",
|
|
108
108
|
"docs/ARCHITECTURE.md",
|
|
109
|
+
"docs/ROADMAP.md",
|
|
109
110
|
"CHANGELOG.md",
|
|
110
111
|
"CONTRIBUTING.md",
|
|
111
112
|
"SECURITY.md",
|
|
@@ -119,9 +120,9 @@
|
|
|
119
120
|
"lint:types": "tsc --noEmit",
|
|
120
121
|
"build": "node scripts/build.mjs",
|
|
121
122
|
"lint:build": "node test/build-freshness.mjs",
|
|
122
|
-
"test:coverage": "node --test --experimental-test-coverage --test-coverage-lines=85 --test-coverage-branches=84 --test-coverage-functions=70 test/agent-exec.test.mjs test/agent-fs-script.test.mjs test/agent-protocol.test.mjs test/agent.test.mjs test/fs-substrate.test.mjs test/fs.boot.test.mjs test/workspace-files-wsl.test.mjs test/composition.test.mjs test/provider.test.mjs test/file-reference-wsl.test.mjs test/fsio-agent.test.mjs test/fsio-text.test.mjs test/git-route.test.mjs test/listing.test.mjs test/paths.test.mjs test/ports.test.mjs test/preset-choice.test.mjs test/probe-cache.test.mjs test/preflight.test.mjs test/sandbox.test.mjs test/search-exec.test.mjs test/search-route.test.mjs test/shell.test.mjs test/terminal-activity.test.mjs test/terminal.test.mjs test/watcher.test.mjs test/wsl.test.mjs test/fs-decisions.test.mjs test/session-scope.test.mjs test/session-runtime.test.mjs test/native-agent.test.mjs test/peers.test.mjs",
|
|
123
|
+
"test:coverage": "node --test --experimental-test-coverage --test-coverage-lines=85 --test-coverage-branches=84 --test-coverage-functions=70 test/agent-exec.test.mjs test/agent-fs-script.test.mjs test/agent-protocol.test.mjs test/agent.test.mjs test/fs-substrate.test.mjs test/fs.boot.test.mjs test/workspace-files-wsl.test.mjs test/composition.test.mjs test/provider.test.mjs test/file-reference-wsl.test.mjs test/fsio-agent.test.mjs test/fsio-text.test.mjs test/git-route.test.mjs test/listing.test.mjs test/paths.test.mjs test/ports.test.mjs test/preset-choice.test.mjs test/probe-cache.test.mjs test/preflight.test.mjs test/sandbox.test.mjs test/search-exec.test.mjs test/search-route.test.mjs test/shell.test.mjs test/terminal-activity.test.mjs test/terminal.test.mjs test/watcher.test.mjs test/wsl.test.mjs test/fs-decisions.test.mjs test/session-scope.test.mjs test/session-runtime.test.mjs test/native-agent.test.mjs test/peers.test.mjs test/agent-shared.test.mjs",
|
|
123
124
|
"test:syntax": "node test/syntax.mjs",
|
|
124
|
-
"test:unit": "node --test test/fs.boot.test.mjs test/fsio-text.test.mjs test/workspace-files-wsl.test.mjs test/composition.test.mjs test/provider.test.mjs test/file-reference-wsl.test.mjs test/paths.test.mjs test/listing.test.mjs test/shell.test.mjs test/terminal.test.mjs test/probe-cache.test.mjs test/preflight.test.mjs test/preset-choice.test.mjs test/sandbox.test.mjs test/agent-protocol.test.mjs test/agent.test.mjs test/agent-fs-script.test.mjs test/fsio-agent.test.mjs test/fs-substrate.test.mjs test/agent-exec.test.mjs test/ports.test.mjs test/watcher.test.mjs test/wsl.test.mjs test/fs-decisions.test.mjs test/git-route.test.mjs test/fsio-text.test.mjs test/search-route.test.mjs test/search-exec.test.mjs test/terminal-activity.test.mjs test/session-scope.test.mjs test/session-runtime.test.mjs test/native-agent.test.mjs test/peers.test.mjs",
|
|
125
|
+
"test:unit": "node --test test/fs.boot.test.mjs test/fsio-text.test.mjs test/workspace-files-wsl.test.mjs test/composition.test.mjs test/provider.test.mjs test/file-reference-wsl.test.mjs test/paths.test.mjs test/listing.test.mjs test/shell.test.mjs test/terminal.test.mjs test/probe-cache.test.mjs test/preflight.test.mjs test/preset-choice.test.mjs test/sandbox.test.mjs test/agent-protocol.test.mjs test/agent.test.mjs test/agent-fs-script.test.mjs test/fsio-agent.test.mjs test/fs-substrate.test.mjs test/agent-exec.test.mjs test/ports.test.mjs test/watcher.test.mjs test/wsl.test.mjs test/fs-decisions.test.mjs test/git-route.test.mjs test/fsio-text.test.mjs test/search-route.test.mjs test/search-exec.test.mjs test/terminal-activity.test.mjs test/session-scope.test.mjs test/session-runtime.test.mjs test/native-agent.test.mjs test/peers.test.mjs test/agent-shared.test.mjs",
|
|
125
126
|
"lint:style": "node test/style.mjs",
|
|
126
127
|
"prepublishOnly": "pnpm test",
|
|
127
128
|
"probe": "bash test/probe/run.sh",
|