dsh-plugin-wsl-env 0.13.0 → 0.13.2

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 CHANGED
@@ -16,6 +16,114 @@ were part of the record.
16
16
 
17
17
  ## [Unreleased]
18
18
 
19
+ ## [0.13.2] - 2026-10-26
20
+
21
+ This release tracks DeepSeek Harness `0.2.1-rc.3`: the peer declarations become
22
+ floor ranges so a newer harness installs the plugin unchanged, and the
23
+ lockfile's transitive `undici` moves past a security advisory that Dependabot
24
+ could not clear on its own.
25
+
26
+ ### Fixed
27
+
28
+ - **`undici` in the lockfile moves to the security-fixed `8.12.3` via a pnpm
29
+ override.** `undici` rides in only as an exact transitive pin of
30
+ `@deepseek-ai/dsh-http-proxy` — a package upstream controls — so Dependabot's
31
+ security job could resolve no patched version (it reports "the latest
32
+ possible version … is 8.11.2" and fails). `pnpm-workspace.yaml` now carries
33
+ `overrides: { undici: '>=8.12.3' }` (the pnpm 11 settings home; the
34
+ `package.json` `pnpm` field is no longer read), the lockfile swaps the one
35
+ package, and the override comment records that it is retired when upstream
36
+ moves the pin. Nothing under `src/` reads `undici`; the check chain is
37
+ unaffected.
38
+
39
+ ### Changed
40
+
41
+ - **The peer declarations become floor ranges, so a newer harness installs
42
+ the plugin unchanged.** The `@deepseek-ai/dsh-*` entries in
43
+ `peerDependencies` move from the exact pin to `>=0.2.1-rc.3`, and
44
+ `@deepseek-ai/cordis` / `@deepseek-ai/schemastery` widen from `~` to `^`
45
+ (`^4.0.5`, `^3.18.5`). An app that ships a newer harness release now
46
+ satisfies the peers without waiting for a plugin release — same-line rc
47
+ bumps and every stable release install cleanly; only a brand-new
48
+ prerelease line (`0.2.2-rc.1` against `>=0.2.1-rc.3`) still warns, because
49
+ semver admits a prerelease only against a comparator on its own tuple and
50
+ no range can promise an unpublished line. That residue is what the weekly
51
+ peer-canary is for: it still resolves the `next` dist-tag into a scratch
52
+ tree and flags when the line moves past a floor — the signal to re-run the
53
+ `docs/PEER-PARITY.md` comparison and raise the floor in the commit that
54
+ matches the harness release. The devDependencies keep their exact pins, so
55
+ the gates stay reproducible and the parity audit keeps naming one line.
56
+ `scripts/doctor.mjs` learned the new shapes (`peerSatisfied` now reads `>=`
57
+ and `^`, and its `compareVersions` orders prerelease identifiers by semver
58
+ §11 — `rc.10` above `rc.9`), `scripts/peer-canary.mjs` parses and preserves
59
+ the shapes, and `AGENTS.md`, `CONTRIBUTING.md`, `SUPPORT.md` and
60
+ `docs/PEER-PARITY.md` state the new policy.
61
+
62
+ - **The dev pin line moves to DeepSeek Harness `0.2.1-rc.3`.** Every
63
+ `@deepseek-ai/dsh-*` pin in `devDependencies` goes from `0.2.1-rc.2` to
64
+ `0.2.1-rc.3` — the release the `next` dist-tag resolves to — and
65
+ `pnpm-lock.yaml` refreshes with it. `@deepseek-ai/cordis` and
66
+ `@deepseek-ai/schemastery` stay at `4.0.5` / `3.18.5`: upstream's `vendor/`
67
+ did not change on this line.
68
+
69
+ No module under `src/` changed, and that is the finding. The three peers
70
+ this plugin subclasses or replicates ship byte-identical `lib/index.js`
71
+ files on rc.3 — `dsh-fs-local` (the fsio dialect, still 909 lines), `dsh-fs`
72
+ (the `FsVersion` brand) and `dsh-bash-local` (the spill machinery) — so
73
+ every line reference in `docs/PEER-PARITY.md` still names the same code;
74
+ only the tarballs' digests changed, and the parity doc now carries the
75
+ `0.2.1-rc.3` ones with the re-check dated 2026-10-26. That byte-identity is
76
+ also the evidence behind the new peer floor: the same code answers above
77
+ rc.3. Upstream's release notes for the line are UI- and LLM-side
78
+ (navigation entry customization, analytics, the official `/models`
79
+ listing): the one composition-visible change is a new `entry-customization`
80
+ UI row in the app's own `cordis.patch.yml`, which rides with the app's
81
+ bundle rather than this plugin's layer. `preset-wsl` needed no
82
+ regeneration — `packages/bundle/web-app/presets/standard.patch.yml` is
83
+ byte-identical on rc.3 — and the provenance comment in `cordis.patch.yml`
84
+ now names the rc.3 tree.
85
+
86
+ ## [0.13.1] - 2026-10-19
87
+
88
+ A patch release restoring `wsl-shell-env`: the component mounts again, and the
89
+ managed `DSH_*` facts it contributes now reach commands on the default
90
+ resident-agent path.
91
+
92
+ ### Changed
93
+
94
+ - **Upstream proposals filed.** The session-notice seam
95
+ ([#10263](https://github.com/deepseek-ai/deepseek-harness/discussions/10263)),
96
+ the `spawnSpec` seam (#10261), the fsio export (#10262), the UNC-workspace bug
97
+ (#10260) and the WSL-workspaces RFC (#10264) now point at their discussions;
98
+ the local drafts carry the links.
99
+
100
+ ### Fixed
101
+
102
+ - **`wsl-shell-env` no longer fails to activate.** The row sweeps the
103
+ live-agent list at mount to warm facts for sessions that opened before it,
104
+ but declared only `shellEnv` in `inject` and read `ctx.agents` as a property.
105
+ Cordis 4 refuses that read — `cannot get property "agents" without inject` —
106
+ so `apply()` rejected and the plugin manager listed the component as 异常
107
+ (`包含的组件 → 异常`), with the whole contribution missing from shell calls.
108
+ The sweep now reads the service through `ctx.get("agents")`, the sanctioned
109
+ access for a service this row does not inject: activation no longer depends
110
+ on an agent loop, and the sweep still runs when one is composed.
111
+ `wsl-preflight` carried the same property read inside a `try`/`catch`, where
112
+ the throw silently disabled its mount sweep; it gets the same fix.
113
+ `test/shell-env.test.mjs` and the real-cordis check in
114
+ `test/preflight.test.mjs` mount both rows against the guard.
115
+
116
+ - **The managed `DSH_*` namespace now reaches commands on the default
117
+ resident-agent path.** `ShellExecSpec.dshEnv` was forwarded only by the
118
+ one-shot `wsl.exe` fallback; `executeViaAgent` dropped it before
119
+ `agent.exec` — after the mount fix above, the row registered its facts but
120
+ no model shell call could see `DSH_HOME`, `DSH_SESSION_ID` or any
121
+ `DSH_WSL_*` value (`env | grep '^DSH_'` was empty). The handle now carries
122
+ the merged map (`env` first, `dshEnv` last — the same order the one-shot
123
+ path uses) into the agent's `SETENV` frames. `test/agent-exec.test.mjs` and
124
+ `test/provider.test.mjs` pin the frame, and both checks fail against the
125
+ previous build.
126
+
19
127
  ## [0.13.0] - 2026-10-19
20
128
 
21
129
  This release ships the P0/P1 hardening batch: session-scoped `auto`
package/CONTRIBUTING.md CHANGED
@@ -23,13 +23,24 @@ Developing and running the gates do have dependencies. The tests import pinned
23
23
  algorithm, and the boot test starts a row through the real Loader — so a fresh
24
24
  checkout needs one `pnpm install` before the gates pass.
25
25
 
26
- Those pins are deliberate, and they move with the harness rather than on their
27
- own: the `@deepseek-ai/dsh-*` peers sit at `0.2.1-rc.2`, `@deepseek-ai/cordis`
28
- at `~4.0.5` and `@deepseek-ai/schemastery` at `~3.18.5`. The plugin subclasses
29
- peer classes and mirrors peer protocol, so raising one pin alone is how the
30
- seams drift: bump them in the commit that matches a harness release, re-run the
31
- comparison `docs/PEER-PARITY.md` describes, and let the gate chain and the
32
- probes confirm the result.
26
+ Those pins are deliberate, and they split by role. The `devDependencies` stay
27
+ exact so the gates and the `docs/PEER-PARITY.md` audit name one reproducible
28
+ line. The `peerDependencies` are floors — `>=` for the `@deepseek-ai/dsh-*`
29
+ packages, caret for `@deepseek-ai/cordis` and `@deepseek-ai/schemastery` — so
30
+ an app that ships a newer harness keeps installing the plugin without waiting
31
+ for a plugin release. A floor is still raised on purpose, not by drift: it
32
+ moves in the commit that matches a harness release, with the parity comparison
33
+ and the gate chain re-run, and the weekly peer-canary
34
+ (`.github/workflows/peer-canary.yml`) resolves the `next` dist-tag into a
35
+ scratch tree and flags when the line has moved past the floor. One semver
36
+ limit is worth knowing: a floor range cannot name future prerelease lines —
37
+ `0.2.2-rc.1` does not satisfy `>=0.2.1-rc.3`, because the spec admits a
38
+ prerelease only against a comparator on its own `[major, minor, patch]` tuple
39
+ — so a brand-new rc line warns until the floor rises; same-line rcs and every
40
+ stable release never do.
41
+
42
+ The plugin subclasses peer classes and mirrors peer protocol, so the range
43
+ says nothing about the parity claims: those are re-verified per floor raise.
33
44
 
34
45
  pnpm runs the scripts in this repository, pinned through the `packageManager`
35
46
  field (corepack resolves it). Treat `pnpm run <script>` as the reference
package/SUPPORT.md CHANGED
@@ -6,7 +6,7 @@
6
6
  |---|---|
7
7
  | Operating system | Windows 10 and Windows 11 |
8
8
  | Linux side | WSL2 with at least one distro, and systemd as PID 1 with a lingering user manager (`loginctl enable-linger`) for the shipped session runtime. Set `sessionSandbox: false` on the preset's `wsl-shell` to keep the per-command sandbox instead |
9
- | DSH packages | the `0.2.1-rc.2`-era packages listed in `peerDependencies` |
9
+ | DSH packages | the `0.2.1-rc.3`-or-newer packages the `peerDependencies` floors declare; an app on a newer harness installs unchanged |
10
10
 
11
11
  The DSH row is checked weekly: `.github/workflows/peer-canary.yml` resolves the
12
12
  `next` dist-tag of every pinned peer, installs that line into a scratch copy and
package/cordis.patch.yml CHANGED
@@ -185,7 +185,8 @@
185
185
  # Generated from the shipped standard preset so the embedded plan-mode
186
186
  # section and every nested group survive byte-for-byte: this copy is
187
187
  # `packages/bundle/web-app/presets/standard.patch.yml` of the
188
- # `dsh-v0.2.1-rc.2` source tree, changed in exactly three ways —
188
+ # `dsh-v0.2.1-rc.3` source tree (unchanged since `0.2.1-rc.2`), changed
189
+ # in exactly three ways —
189
190
  # 1. every shipped row mounts inside `wsl-env`, so the preset's tools see
190
191
  # the distro's shell and fs (the realm is declared on the group);
191
192
  # 2. `tool-bash` is enabled and `tool-pwsh` disabled outright, where the
@@ -7,18 +7,20 @@ same refusal dialect on both substrates. The **read path** holds end to end;
7
7
  the **edit path** carries two declared divergences, listed at the end. This
8
8
  file records the verification against the real peers.
9
9
 
10
- - Peers: `@deepseek-ai/dsh-fs-local@0.2.1-rc.2` (the exact version
11
- `package.json` pins), plus `@deepseek-ai/dsh-fs@0.2.1-rc.2` for the
10
+ - Peers: `@deepseek-ai/dsh-fs-local@0.2.1-rc.3` (the exact version the
11
+ devDependencies pin, and the floor the peer range `>=0.2.1-rc.3` was
12
+ verified against), plus `@deepseek-ai/dsh-fs@0.2.1-rc.3` for the
12
13
  `FsVersion` brand the local package imports. Divergence 2 is measured
13
- against a third peer, `@deepseek-ai/dsh-bash-local@0.2.1-rc.2` — the shell
14
+ against a third peer, `@deepseek-ai/dsh-bash-local@0.2.1-rc.3` — the shell
14
15
  executor carries the collect-budget/spill machinery; `spill` and
15
16
  `maxSpillBytes` do not appear in `dsh-fs-local` at all.
16
- - The `0.2.1-rc.2` move leaves every line reference below intact: all three
17
- peers' shipped `lib/index.js` are **byte-identical** to the `0.2.0-rc.2`
17
+ - The `0.2.1-rc.3` move leaves every line reference below intact: all three
18
+ peers' shipped `lib/index.js` are **byte-identical** to the `0.2.1-rc.2`
18
19
  ones (`cmp` clean; `dsh-fs-local`'s is still 909 lines), so only the
19
- tarballs' digests changed. `dsh-fs@0.2.1-rc.2` did drop its unpublished
20
- `./invariant` export subpath; this plugin imports neither it nor
21
- `dsh-invariants`.
20
+ tarballs' digests changed — upstream shipped the manifests' version fields
21
+ and nothing else in these packages. `dsh-fs@0.2.1-rc.3` still has no
22
+ `./invariant` export subpath (dropped back at `0.2.1-rc.2`); this plugin
23
+ imports neither it nor `dsh-invariants`.
22
24
  - What "not imported" means: the peer's **fsio module** is not reachable from
23
25
  the package's exports, so `lib/fsio-text.js` reimplements it and imports
24
26
  nothing from it (the export is proposed in `docs/UPSTREAM-FSIO-EXPORT.md`).
@@ -33,24 +35,24 @@ file records the verification against the real peers.
33
35
 
34
36
  ```sh
35
37
  mkdir -p /tmp/peer-audit && cd /tmp/peer-audit
36
- npm pack @deepseek-ai/dsh-fs-local@0.2.1-rc.2 @deepseek-ai/dsh-fs@0.2.1-rc.2 --pack-destination .
37
- openssl dgst -sha512 -binary deepseek-ai-dsh-fs-local-0.2.1-rc.2.tgz | base64 -w0
38
- tar xzf deepseek-ai-dsh-fs-local-0.2.1-rc.2.tgz # -> package/lib/index.js
38
+ npm pack @deepseek-ai/dsh-fs-local@0.2.1-rc.3 @deepseek-ai/dsh-fs@0.2.1-rc.3 --pack-destination .
39
+ openssl dgst -sha512 -binary deepseek-ai-dsh-fs-local-0.2.1-rc.3.tgz | base64 -w0
40
+ tar xzf deepseek-ai-dsh-fs-local-0.2.1-rc.3.tgz # -> package/lib/index.js
39
41
  wc -l package/lib/index.js # expect 909
40
42
  ```
41
43
 
42
44
  The tarballs this audit compared, by content — the same values
43
- `npm view <pkg>@0.2.1-rc.2 dist.integrity` reports:
45
+ `npm view <pkg>@0.2.1-rc.3 dist.integrity` reports:
44
46
 
45
47
  ```
46
- @deepseek-ai/dsh-fs-local@0.2.1-rc.2 sha512-IZPJ12/EpV6fu3CCWtW4w4GthXAKLFjplm4Feoep2HAum7J1aGzHZwiw7ipsPCZiA5/TyK3lDUUAGZ/pcXgRcw==
47
- @deepseek-ai/dsh-fs@0.2.1-rc.2 sha512-Z6d2ZKsAiJc0FaiES8wC66vuUQMGohl9tCc2p3bAsPFYkJ0+L2zTViQ9Y5BArtRe9Br8qhNjNgLlN0jb/uwv1g==
48
+ @deepseek-ai/dsh-fs-local@0.2.1-rc.3 sha512-bB2CwCqvYAdAKl28+5XXMRRq1NBtRmYX3wNOpJ2JlGBm7uj2Aix9Vl9rFig4AQcXXd5Bk738b5KNsOIkbXy+XQ==
49
+ @deepseek-ai/dsh-fs@0.2.1-rc.3 sha512-tENLWe471WlAXMc13J4cns8xU6lFyu1smwpkSKR85vhGr6FyWZhTlpVohZYm66LbwKTmSWiDhd9Urk7VxN9B9w==
48
50
  ```
49
51
 
50
52
  Each row of the table below names the peer's `lib/index.js` line and the
51
53
  literal both sides must produce. Verified 2026-10-03; the read/edit split
52
54
  re-verified 2026-10-06; the whole table re-checked against `0.2.1-rc.2`,
53
- 2026-10-17.
55
+ 2026-10-17 and against `0.2.1-rc.3`, 2026-10-26.
54
56
 
55
57
  ## Verdict
56
58
 
@@ -93,7 +95,7 @@ declared extensions, listed at the end.
93
95
  2. **Per-stream truncation flags.** The EXEC frame carries an output budget;
94
96
  the RES line reports which streams the agent cut, surfaced as honest
95
97
  `truncated` flags. The collect-budget/spill machinery of the shell peer,
96
- `@deepseek-ai/dsh-bash-local@0.2.1-rc.2` (`lib/index.js:30`, `:110`), is the
98
+ `@deepseek-ai/dsh-bash-local@0.2.1-rc.3` (`lib/index.js:30`, `:110`), is the
97
99
  spill-file path we deliberately do not run.
98
100
  3. **The transport around the ops** (ACK, KILL, the confined resident) is
99
101
  protocol this package owns; the peer has no agent.
@@ -120,9 +122,12 @@ declared extensions, listed at the end.
120
122
 
121
123
  ## Maintenance
122
124
 
123
- When `package.json` bumps the peer pins, re-run the comparison above — pack the
124
- new tarballs, check the sha512, and walk the table's peer line references
125
- against the replicas. The fastest canary: stat a file, list its directory, and
125
+ When a peer floor or dev pin moves in `package.json`, re-run the comparison
126
+ above — pack the new tarballs, check the sha512, and walk the table's peer line
127
+ references against the replicas. The weekly peer-canary resolves the `next`
128
+ dist-tag into a scratch tree and runs the gate chain against it, so the signal
129
+ to raise the floor arrives before a user reports a drift. The fastest canary:
130
+ stat a file, list its directory, and
126
131
  compare the versions (`test/fsio-agent.test.mjs` asserts the intra-distro half
127
132
  of this); the strings above are the other half. The split dialect has its own
128
133
  regression: `test/fs-substrate.test.mjs` asserts the missing-file refusal, and
@@ -339,9 +339,9 @@ command.
339
339
  - [x] The preflight checks bwrap, systemd PID 1, `systemd-run` and Linger.
340
340
  - [x] The warning is visibly attached to the session through the
341
341
  runtime-context entry (not only logs).
342
- - [ ] A GUI notice/banner renders it: upstream seam missing; the filing payload
343
- is ready at [proposal-session-notice.md](upstream/proposal-session-notice.md)
344
- and has not been submitted (a maintainer action).
342
+ - [ ] A GUI notice/banner renders it: filed upstream as
343
+ [#10263](https://github.com/deepseek-ai/deepseek-harness/discussions/10263);
344
+ until a seam lands, the advisory stays in the log and runtime context.
345
345
  - [x] The warning never installs anything and never changes fail-closed
346
346
  behavior.
347
347
  - [x] A session with a usable environment produces no warning.
@@ -1,7 +1,8 @@
1
1
  # Upstream proposal: export the fsio text helpers from `dsh-fs-local`
2
2
 
3
- Status: draft, not yet filed upstream — no discussion link yet (the filing
4
- convention is [UPSTREAM-TERMINAL-TITLE.md](UPSTREAM-TERMINAL-TITLE.md)).
3
+ Status: filed upstream as
4
+ [deepseek-ai/deepseek-harness#10262](https://github.com/deepseek-ai/deepseek-harness/discussions/10262)
5
+ (Ideas); the filing convention is [UPSTREAM-TERMINAL-TITLE.md](UPSTREAM-TERMINAL-TITLE.md).
5
6
  Targeting `deepseek-harness` @ `packages/fs/fs-local`, the `0.2.1-rc.x` line
6
7
  this plugin pins exactly (`@deepseek-ai/dsh-fs-local@0.2.1-rc.2`, in both the
7
8
  dev and peer dependency blocks of `package.json`). This is the Shape-A
@@ -129,10 +129,10 @@ export interface AgentExecutionHandle {
129
129
  * @returns the execution handle (`status`, `exitCode`, `signal`, `observed`,
130
130
  * `done`, `readOutput`, `kill`, `result`).
131
131
  */
132
- export declare function agentExecutionHandle({ agent, cwd, argv, timeoutMs, maxOutputBytes, signal, onStarted }: {
132
+ export declare function agentExecutionHandle({ agent, cwd, argv, timeoutMs, maxOutputBytes, env, signal, onStarted }: {
133
133
  /**
134
134
  * The {@link WslAgent}-like runner: `exec` accepting
135
- * `{cwd, argv, timeoutMs, signal}` and resolving `{exitCode, stdout, stderr}`;
135
+ * `{cwd, argv, timeoutMs, env, signal}` and resolving `{exitCode, stdout, stderr}`;
136
136
  * throws `AgentUnavailableError` when out.
137
137
  */
138
138
  agent: AgentExecRunner;
@@ -150,6 +150,13 @@ export declare function agentExecutionHandle({ agent, cwd, argv, timeoutMs, maxO
150
150
  * stream as `truncated: true`.
151
151
  */
152
152
  maxOutputBytes?: number;
153
+ /**
154
+ * Environment for this command: the caller's entries with the managed
155
+ * `DSH_*` snapshot merged last. The agent applies it as `SETENV` frames
156
+ * immediately before the command's `EXEC` (see `WslAgent.exec`); dropping it
157
+ * here is what kept every managed fact out of the resident-agent path.
158
+ */
159
+ env?: Record<string, string>;
153
160
  /** The caller's cancellation. */
154
161
  signal?: AbortSignal;
155
162
  /**
package/lib/agent-exec.js CHANGED
@@ -54,7 +54,7 @@ function memoryReader(buffer) {
54
54
  * @returns the execution handle (`status`, `exitCode`, `signal`, `observed`,
55
55
  * `done`, `readOutput`, `kill`, `result`).
56
56
  */
57
- export function agentExecutionHandle({ agent, cwd, argv, timeoutMs = 0, maxOutputBytes = 0, signal, onStarted }) {
57
+ export function agentExecutionHandle({ agent, cwd, argv, timeoutMs = 0, maxOutputBytes = 0, env, signal, onStarted }) {
58
58
  // Our own controller is the kill channel; the caller's signal races it.
59
59
  const controller = new AbortController();
60
60
  let timedOut = false;
@@ -72,7 +72,7 @@ export function agentExecutionHandle({ agent, cwd, argv, timeoutMs = 0, maxOutpu
72
72
  controller.abort(new Error(BASH_TIMEOUT_REASON));
73
73
  }, timeoutMs).unref?.();
74
74
  }
75
- const settle = agent.exec({ cwd, argv, timeoutMs, maxOutputBytes, signal: controller.signal }).then((result) => ({ ok: true, result }), (error) => {
75
+ const settle = agent.exec({ cwd, argv, timeoutMs, maxOutputBytes, env, signal: controller.signal }).then((result) => ({ ok: true, result }), (error) => {
76
76
  // A workdir the distro cannot enter is the command's own failure, not
77
77
  // the agent's: synthesize the relay-shaped stderr the one-shot path
78
78
  // produces, so the executor's `workdirFailure` check reports it the
package/lib/index.js CHANGED
@@ -831,6 +831,13 @@ export class WslShellExecutor extends LocalBashExecutor {
831
831
  const argv = runtime !== undefined
832
832
  ? this.distroArgv(resolved, shell)
833
833
  : confined !== undefined ? confined.argv : this.distroArgv(resolved, shell);
834
+ // The managed `DSH_*` snapshot merges last, exactly as the one-shot path's
835
+ // spawn env does (`ShellExecSpec`: `dshEnv` wins over `env`). The agent
836
+ // applies these as SETENV frames immediately before the command's EXEC, so
837
+ // the resident path now carries `DSH_HOME`, `DSH_SESSION_ID` and the
838
+ // `DSH_WSL_*` facts too — without this the whole registry was invisible on
839
+ // the default path while the one-shot fallback forwarded it.
840
+ const env = { ...resolved.env, ...resolved.dshEnv };
834
841
  const execution = agentExecutionHandle({
835
842
  agent,
836
843
  // The one-shot path converts the spec's workdir into the distro's
@@ -839,6 +846,7 @@ export class WslShellExecutor extends LocalBashExecutor {
839
846
  // reach `chdir` (that is the bug this line fixes).
840
847
  cwd: toLinuxPath(resolved.workdir, { distro }),
841
848
  argv,
849
+ env: Object.keys(env).length > 0 ? env : undefined,
842
850
  timeoutMs: resolved.timeoutMs,
843
851
  // The agent enforces the collect budget distro-side (the EXEC frame
844
852
  // carries it); `maxSpillBytes` stays one-shot-only — the agent path has
package/lib/preflight.js CHANGED
@@ -176,8 +176,14 @@ export async function apply(ctx, config = {}) {
176
176
  // Mount order is not a guarantee: a session created during startup can finish
177
177
  // before this service mounts and miss the event entirely (dsh-headless does
178
178
  // exactly that, and auto-preset sweeps for the same reason).
179
+ //
180
+ // `agents` is optional (this row injects nothing), so read it with `ctx.get`:
181
+ // a property read of an undeclared service throws in cordis 4, and the catch
182
+ // below would turn that into a silent no-op — exactly the sweep this block
183
+ // exists to perform.
179
184
  try {
180
- for (const agent of ctx.agents?.list() ?? [])
185
+ const agents = ctx.get("agents");
186
+ for (const agent of agents?.list() ?? [])
181
187
  void check(agent);
182
188
  }
183
189
  catch (error) {
package/lib/shell-env.js CHANGED
@@ -157,7 +157,14 @@ export async function apply(ctx, config = {}) {
157
157
  ensurePorts(parsed.distro);
158
158
  };
159
159
  const off = lifecycle.on?.("agent/created", (payload) => consider(payload?.agent));
160
- for (const agent of lifecycle.agents?.list?.() ?? [])
160
+ // The live-agent sweep is a convenience, not a dependency: `inject` names
161
+ // only `shellEnv`, so reading `ctx.agents` as a property makes cordis 4
162
+ // throw "cannot get property \"agents\" without inject" and marks the whole
163
+ // component failed. `ctx.get` is the sanctioned read for a service this
164
+ // plugin did not inject, and it answers undefined when no agent loop is
165
+ // composed — the row still activates and resolves facts per shell call.
166
+ const live = ctx.get("agents");
167
+ for (const agent of live?.list?.() ?? [])
161
168
  consider(agent);
162
169
  const dispose = ctx.shellEnv.register({
163
170
  name: "dsh-plugin-wsl-env",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-plugin-wsl-env",
3
- "version": "0.13.0",
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",
3
+ "version": "0.13.2",
4
+ "description": "让 DeepSeek Harness 以 WSL 子系统作为会话的执行环境:shell、文件读写与命令执行均在子系统内完成,目录选择器与 GUI 终端同样定位到子系统路径,并通过子系统内的 bubblewrap 对命令进行沙箱隔离",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "dsh",
@@ -125,9 +125,9 @@
125
125
  "lint:types": "tsc --noEmit",
126
126
  "build": "node scripts/build.mjs",
127
127
  "lint:build": "node test/build-freshness.mjs",
128
- "test:coverage": "node --test --experimental-test-coverage --test-coverage-lines=85 --test-coverage-branches=84 --test-coverage-functions=70 --test-coverage-exclude='scripts/**' 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/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 test/distro-route.test.mjs test/fs-routing.test.mjs test/doctor.test.mjs test/profile-preset.test.mjs test/packaging.test.mjs test/peer-canary.test.mjs",
128
+ "test:coverage": "node --test --experimental-test-coverage --test-coverage-lines=85 --test-coverage-branches=84 --test-coverage-functions=70 --test-coverage-exclude='scripts/**' 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/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/shell-env.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 test/distro-route.test.mjs test/fs-routing.test.mjs test/doctor.test.mjs test/profile-preset.test.mjs test/packaging.test.mjs test/peer-canary.test.mjs",
129
129
  "test:syntax": "node test/syntax.mjs",
130
- "test:unit": "node --test test/fs.boot.test.mjs test/fsio-text.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/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 test/distro-route.test.mjs test/fs-routing.test.mjs test/doctor.test.mjs test/profile-preset.test.mjs test/packaging.test.mjs test/peer-canary.test.mjs",
130
+ "test:unit": "node --test test/fs.boot.test.mjs test/fsio-text.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/shell-env.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/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 test/distro-route.test.mjs test/fs-routing.test.mjs test/doctor.test.mjs test/profile-preset.test.mjs test/packaging.test.mjs test/peer-canary.test.mjs",
131
131
  "lint:style": "node test/style.mjs",
132
132
  "lint:links": "node scripts/check-links.mjs",
133
133
  "prepublishOnly": "pnpm test",
@@ -179,32 +179,32 @@
179
179
  "@deepseek-ai/cordis": "4.0.5",
180
180
  "@deepseek-ai/cordis-plugin-include": "*",
181
181
  "@deepseek-ai/cordis-plugin-loader": "*",
182
- "@deepseek-ai/dsh-bash-local": "0.2.1-rc.2",
183
- "@deepseek-ai/dsh-fs": "0.2.1-rc.2",
184
- "@deepseek-ai/dsh-fs-local": "0.2.1-rc.2",
185
- "@deepseek-ai/dsh-fs-sandbox": "0.2.1-rc.2",
186
- "@deepseek-ai/dsh-host-directory-picker": "0.2.1-rc.2",
187
- "@deepseek-ai/dsh-sandbox": "0.2.1-rc.2",
188
- "@deepseek-ai/dsh-sandbox-policy": "0.2.1-rc.2",
189
- "@deepseek-ai/dsh-subprocess": "0.2.1-rc.2",
190
- "@deepseek-ai/dsh-subprocess-local": "0.2.1-rc.2",
191
- "@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.2",
182
+ "@deepseek-ai/dsh-bash-local": "0.2.1-rc.3",
183
+ "@deepseek-ai/dsh-fs": "0.2.1-rc.3",
184
+ "@deepseek-ai/dsh-fs-local": "0.2.1-rc.3",
185
+ "@deepseek-ai/dsh-fs-sandbox": "0.2.1-rc.3",
186
+ "@deepseek-ai/dsh-host-directory-picker": "0.2.1-rc.3",
187
+ "@deepseek-ai/dsh-sandbox": "0.2.1-rc.3",
188
+ "@deepseek-ai/dsh-sandbox-policy": "0.2.1-rc.3",
189
+ "@deepseek-ai/dsh-subprocess": "0.2.1-rc.3",
190
+ "@deepseek-ai/dsh-subprocess-local": "0.2.1-rc.3",
191
+ "@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.3",
192
192
  "@types/js-yaml": "^4.0.9",
193
193
  "@types/node": "~22.20.5",
194
194
  "js-yaml": "4.3.2",
195
195
  "typescript": "~6.0.3"
196
196
  },
197
197
  "peerDependencies": {
198
- "@deepseek-ai/cordis": "~4.0.5",
199
- "@deepseek-ai/dsh-bash-local": "0.2.1-rc.2",
200
- "@deepseek-ai/dsh-fs": "0.2.1-rc.2",
201
- "@deepseek-ai/dsh-fs-local": "0.2.1-rc.2",
202
- "@deepseek-ai/dsh-host-directory-picker": "0.2.1-rc.2",
203
- "@deepseek-ai/dsh-sandbox": "0.2.1-rc.2",
204
- "@deepseek-ai/dsh-subprocess": "0.2.1-rc.2",
205
- "@deepseek-ai/dsh-subprocess-local": "0.2.1-rc.2",
206
- "@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.2",
207
- "@deepseek-ai/schemastery": "~3.18.5"
198
+ "@deepseek-ai/cordis": "^4.0.5",
199
+ "@deepseek-ai/dsh-bash-local": ">=0.2.1-rc.3",
200
+ "@deepseek-ai/dsh-fs": ">=0.2.1-rc.3",
201
+ "@deepseek-ai/dsh-fs-local": ">=0.2.1-rc.3",
202
+ "@deepseek-ai/dsh-host-directory-picker": ">=0.2.1-rc.3",
203
+ "@deepseek-ai/dsh-sandbox": ">=0.2.1-rc.3",
204
+ "@deepseek-ai/dsh-subprocess": ">=0.2.1-rc.3",
205
+ "@deepseek-ai/dsh-subprocess-local": ">=0.2.1-rc.3",
206
+ "@deepseek-ai/dsh-typert-protocol": ">=0.2.1-rc.3",
207
+ "@deepseek-ai/schemastery": "^3.18.5"
208
208
  },
209
209
  "peerDependenciesMeta": {
210
210
  "@deepseek-ai/cordis": {
@@ -145,19 +145,24 @@ function parseArgs(argv) {
145
145
  }
146
146
 
147
147
  /**
148
- * Whether an installed version satisfies one peer range. The plugin's pins are
149
- * exact or tilde ranges, so those two shapes are all this understands; anything
150
- * else is reported as unchecked rather than guessed at.
148
+ * Whether an installed version satisfies one peer range. The manifest's peer
149
+ * ranges are floor ranges (`>=`) and caret ranges (`^`), and the
150
+ * devDependencies carry exact pins — those three shapes are all this
151
+ * understands; anything else is reported as unchecked rather than guessed at.
152
+ * The floor is the plugin's own compatibility claim, so it compares plainly
153
+ * against the floor version; the semver gate that excludes other lines'
154
+ * prereleases is an installer concern, not this check's.
151
155
  * @param {string} installed - the installed version.
152
156
  * @param {string} range - the declared peer range.
153
157
  * @returns {boolean | undefined} true/false, or undefined for an unsupported shape.
154
158
  */
155
159
  export function peerSatisfied(installed, range) {
156
- if (range.startsWith("~")) {
160
+ if (range.startsWith(">=")) return compareVersions(installed, range.slice(2)) >= 0;
161
+ if (range.startsWith("^")) {
157
162
  const [major, minor, patch] = range.slice(1).split(".");
158
163
  return compareVersions(installed, `${major}.${minor}.${patch ?? "0"}`) >= 0
159
164
  && installed.split(".")[0] === major
160
- && installed.split(".")[1] === minor;
165
+ && (major !== "0" || installed.split(".")[1] === minor);
161
166
  }
162
167
  if (/^[0-9]/.test(range)) return installed === range;
163
168
  return undefined;
@@ -188,7 +193,35 @@ export function compareVersions(left, right) {
188
193
  if (a.pre === b.pre) return 0;
189
194
  if (a.pre === "") return 1;
190
195
  if (b.pre === "") return -1;
191
- return a.pre.localeCompare(b.pre);
196
+ return comparePrerelease(a.pre, b.pre);
197
+ }
198
+
199
+ /**
200
+ * Compare two prerelease strings by semver §11: dot-separated identifiers,
201
+ * numeric ones numerically and below alphanumeric ones, the rest lexically,
202
+ * a longer list above its own prefix. Plain `localeCompare` would order
203
+ * `rc.10` below `rc.9`.
204
+ * @param {string} left - the first prerelease.
205
+ * @param {string} right - the second prerelease.
206
+ * @returns {number} negative, zero or positive like a comparator.
207
+ */
208
+ function comparePrerelease(left, right) {
209
+ const a = left.split(".");
210
+ const b = right.split(".");
211
+ for (let i = 0; i < Math.max(a.length, b.length); i += 1) {
212
+ const x = a[i];
213
+ const y = b[i];
214
+ if (x === undefined) return -1;
215
+ if (y === undefined) return 1;
216
+ if (x === y) continue;
217
+ const numericX = /^\d+$/.test(x);
218
+ const numericY = /^\d+$/.test(y);
219
+ if (numericX && numericY) return Number(x) - Number(y);
220
+ if (numericX) return -1;
221
+ if (numericY) return 1;
222
+ return x < y ? -1 : 1;
223
+ }
224
+ return 0;
192
225
  }
193
226
 
194
227
  /**