dsh-plugin-wsl-env 0.7.5 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +278 -0
- package/CONTRIBUTING.md +129 -39
- package/README.md +20 -20
- package/docs/ARCHITECTURE.md +6 -3
- package/docs/LIMITATIONS.md +13 -0
- package/docs/README.zh.md +20 -20
- package/lib/agent-confined.d.ts +42 -0
- package/lib/agent-confined.js +25 -41
- package/lib/agent-errors.d.ts +31 -0
- package/lib/agent-errors.js +19 -16
- package/lib/agent-exec.d.ts +159 -0
- package/lib/agent-exec.js +151 -165
- package/lib/agent-protocol.d.ts +192 -0
- package/lib/agent-protocol.js +76 -100
- package/lib/agent-shared.d.ts +27 -0
- package/lib/agent-shared.js +12 -12
- package/lib/agent.d.ts +353 -0
- package/lib/agent.js +536 -552
- package/lib/auto-preset.d.ts +110 -0
- package/lib/auto-preset.js +157 -141
- package/lib/bwrap.d.ts +90 -0
- package/lib/bwrap.js +18 -18
- package/lib/file-reference-wsl.d.ts +160 -0
- package/lib/file-reference-wsl.js +192 -173
- package/lib/fs-decisions.d.ts +107 -0
- package/lib/fs-decisions.js +64 -60
- package/lib/fs-routing.d.ts +182 -0
- package/lib/fs-routing.js +173 -116
- package/lib/fs-substrate.d.ts +258 -0
- package/lib/fs-substrate.js +248 -248
- package/lib/fs.d.ts +10 -0
- package/lib/fs.js +5 -0
- package/lib/fsio-agent.d.ts +307 -0
- package/lib/fsio-agent.js +414 -410
- package/lib/fsio-text.d.ts +115 -0
- package/lib/fsio-text.js +104 -84
- package/lib/git-route.d.ts +100 -0
- package/lib/git-route.js +46 -47
- package/lib/index.d.ts +1003 -0
- package/lib/index.js +1101 -1003
- package/lib/listing.d.ts +114 -0
- package/lib/listing.js +68 -55
- package/lib/paths.d.ts +182 -0
- package/lib/paths.js +134 -109
- package/lib/picker.d.ts +266 -0
- package/lib/picker.js +371 -351
- package/lib/ports.d.ts +44 -0
- package/lib/ports.js +39 -29
- package/lib/preset-choice.d.ts +33 -0
- package/lib/preset-choice.js +15 -7
- package/lib/probe-cache.d.ts +40 -0
- package/lib/probe-cache.js +35 -29
- package/lib/sandbox-core.d.ts +79 -0
- package/lib/sandbox-core.js +71 -88
- package/lib/sandbox.d.ts +48 -0
- package/lib/sandbox.js +4 -4
- package/lib/search-exec.d.ts +96 -0
- package/lib/search-exec.js +52 -79
- package/lib/search-route.d.ts +75 -0
- package/lib/search-route.js +30 -31
- package/lib/shell-env.d.ts +88 -0
- package/lib/shell-env.js +78 -74
- package/lib/shell.d.ts +10 -0
- package/lib/shell.js +5 -0
- package/lib/subprocess.d.ts +313 -0
- package/lib/subprocess.js +267 -262
- package/lib/terminal-activity.d.ts +98 -0
- package/lib/terminal-activity.js +29 -39
- package/lib/terminal-route.d.ts +27 -0
- package/lib/terminal-route.js +16 -8
- package/lib/watcher.d.ts +77 -0
- package/lib/watcher.js +120 -126
- package/lib/workspace-files-route.d.ts +132 -0
- package/lib/workspace-files-route.js +73 -81
- package/lib/workspace-files-wsl.d.ts +257 -0
- package/lib/workspace-files-wsl.js +311 -251
- package/lib/wsl.d.ts +266 -0
- package/lib/wsl.js +144 -155
- package/package.json +68 -20
package/lib/sandbox.js
CHANGED
|
@@ -30,21 +30,21 @@
|
|
|
30
30
|
* module is the shipped binding that hands it the peer's `SandboxUnavailableError`,
|
|
31
31
|
* so `instanceof` consumers keep matching the class their composition knows.
|
|
32
32
|
*
|
|
33
|
+
* This is a TypeScript source built to `lib/sandbox.js`; edit THIS file and run
|
|
34
|
+
* `pnpm run build` — the artifact under `lib/` is generated, and `pnpm test`
|
|
35
|
+
* fails when it drifts.
|
|
36
|
+
*
|
|
33
37
|
* @module dsh-plugin-wsl/sandbox
|
|
34
38
|
*/
|
|
35
|
-
|
|
36
39
|
import { SandboxUnavailableError } from "@deepseek-ai/dsh-sandbox";
|
|
37
40
|
import { createSandboxCore } from "./sandbox-core.js";
|
|
38
|
-
|
|
39
41
|
// The profile and the argv prefix are also what `lib/agent-confined.js` builds
|
|
40
42
|
// for its residents, so both live in the peer-free `lib/bwrap.js` and are
|
|
41
43
|
// re-exported here, where the sandbox's callers have always found them.
|
|
42
44
|
export { bwrapProfileArgs } from "./bwrap.js";
|
|
43
|
-
|
|
44
45
|
/**
|
|
45
46
|
* The confinement over WSL: the probe-then-confine class bound to the peer's
|
|
46
47
|
* failure type. See the core module for the probe policy and the profile.
|
|
47
48
|
*/
|
|
48
49
|
export const WslSandbox = createSandboxCore({ SandboxUnavailableError });
|
|
49
|
-
|
|
50
50
|
export default WslSandbox;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The agent-backed execution handle for the file-search spawn.
|
|
3
|
+
*
|
|
4
|
+
* `dsh-tool-fs-search` consumes a deliberately small slice of the subprocess
|
|
5
|
+
* handle: it awaits `handle.done` for `{exitCode, signal}`, then reads
|
|
6
|
+
* `handle.collected.stdout/stderr.readFrom(0)` — nothing else. This module
|
|
7
|
+
* builds exactly that surface around one resident-agent `exec`, so a distro
|
|
8
|
+
* search pays one round trip on the warm agent (~10 ms) instead of a one-shot
|
|
9
|
+
* `wsl.exe` spawn (~400 ms of process startup for a few ms of rg).
|
|
10
|
+
*
|
|
11
|
+
* The agent being out is not an error condition here — it is the fallback
|
|
12
|
+
* cue. `spawnFallback` (the shipped one-shot handle the caller builds) is
|
|
13
|
+
* consulted lazily inside `done`; from that point the facade proxies the
|
|
14
|
+
* delegate's `collected` and `done` verbatim. Neither transport ever touches
|
|
15
|
+
* the 9p share.
|
|
16
|
+
*
|
|
17
|
+
* Cancellation shares one channel: the caller's `signal` and `kill()` abort
|
|
18
|
+
* the agent exec, which settles `done` as killed (`{exitCode: null, signal:
|
|
19
|
+
* "SIGTERM"}`) — the same shape the one-shot path reports for a killed search.
|
|
20
|
+
* A genuine agent failure (anything but unavailability) propagates through
|
|
21
|
+
* `done` as a rejection, which the tool classifies as a search failure.
|
|
22
|
+
*
|
|
23
|
+
* Pure shaping over injected collaborators, so the handle semantics are
|
|
24
|
+
* unit-testable without a distro.
|
|
25
|
+
*
|
|
26
|
+
* This is a TypeScript source built to `lib/search-exec.js`; edit THIS file and
|
|
27
|
+
* run `pnpm run build` — the artifact under `lib/` is generated, and `pnpm test`
|
|
28
|
+
* fails when it drifts.
|
|
29
|
+
*
|
|
30
|
+
* @module dsh-plugin-wsl/search-exec
|
|
31
|
+
*/
|
|
32
|
+
import type { AgentExecRunner } from "./agent-exec.js";
|
|
33
|
+
/**
|
|
34
|
+
* The output-stream reader the search tool consumes: `handle.collected.stdout`
|
|
35
|
+
* / `stderr` expose `readFrom(offset)` with the seam's standard shape.
|
|
36
|
+
*/
|
|
37
|
+
export interface SearchCollectedReader {
|
|
38
|
+
/** Read the stream from a byte offset. */
|
|
39
|
+
readFrom: (from: number) => {
|
|
40
|
+
text: string;
|
|
41
|
+
lossy: boolean;
|
|
42
|
+
nextOffset: number;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The slice of the subprocess handle the file-search tool consumes
|
|
47
|
+
* (`dsh-tool-fs-search` `runRipgrep`): settle on `done`, then read the
|
|
48
|
+
* collected streams. Nothing else of the handle is contract.
|
|
49
|
+
*/
|
|
50
|
+
export interface SearchExecutionHandle {
|
|
51
|
+
/** Settles with the exit code and signal; rejects on a genuine failure. */
|
|
52
|
+
done: Promise<{
|
|
53
|
+
exitCode: number | null;
|
|
54
|
+
signal: string | null;
|
|
55
|
+
}>;
|
|
56
|
+
/** The collected stream readers; a delegated handle's own on fallback. */
|
|
57
|
+
collected: {
|
|
58
|
+
stdout?: SearchCollectedReader | undefined;
|
|
59
|
+
stderr?: SearchCollectedReader | undefined;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Optional cooperative kill; the tool relies on its `signal` for
|
|
63
|
+
* cancellation instead.
|
|
64
|
+
*/
|
|
65
|
+
kill?: () => boolean | void;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Run one search command through the agent and present it as a search handle.
|
|
69
|
+
*
|
|
70
|
+
* @param object - the handle's collaborators.
|
|
71
|
+
* @returns the search handle (`done`, `collected`, `kill`).
|
|
72
|
+
*/
|
|
73
|
+
export declare function searchExecutionHandle({ agent, cwd, argv, maxOutputBytes, signal, spawnFallback }: {
|
|
74
|
+
/**
|
|
75
|
+
* The {@link WslAgent}-like runner: `exec` accepting
|
|
76
|
+
* `{cwd, argv, maxOutputBytes, signal}` and resolving
|
|
77
|
+
* `{exitCode, stdout, stderr}`; throws `AgentUnavailableError` when out.
|
|
78
|
+
*/
|
|
79
|
+
agent: AgentExecRunner;
|
|
80
|
+
/** The Linux working directory to search from. */
|
|
81
|
+
cwd: string;
|
|
82
|
+
/** The distro-side argv (`rg` and its arguments). */
|
|
83
|
+
argv: string[];
|
|
84
|
+
/**
|
|
85
|
+
* Per-stream capture ceiling the agent enforces; the tool's own raw-output
|
|
86
|
+
* budget rides here.
|
|
87
|
+
*/
|
|
88
|
+
maxOutputBytes?: number;
|
|
89
|
+
/** The caller's cancellation. */
|
|
90
|
+
signal?: AbortSignal;
|
|
91
|
+
/**
|
|
92
|
+
* Builds the one-shot handle the facade delegates to when the agent is out;
|
|
93
|
+
* called at most once, lazily.
|
|
94
|
+
*/
|
|
95
|
+
spawnFallback: () => SearchExecutionHandle;
|
|
96
|
+
}): SearchExecutionHandle;
|
package/lib/search-exec.js
CHANGED
|
@@ -23,92 +23,65 @@
|
|
|
23
23
|
* Pure shaping over injected collaborators, so the handle semantics are
|
|
24
24
|
* unit-testable without a distro.
|
|
25
25
|
*
|
|
26
|
+
* This is a TypeScript source built to `lib/search-exec.js`; edit THIS file and
|
|
27
|
+
* run `pnpm run build` — the artifact under `lib/` is generated, and `pnpm test`
|
|
28
|
+
* fails when it drifts.
|
|
29
|
+
*
|
|
26
30
|
* @module dsh-plugin-wsl/search-exec
|
|
27
31
|
*/
|
|
28
|
-
|
|
29
32
|
import { AgentUnavailableError } from "./agent-errors.js";
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* The output-stream reader the search tool consumes: `handle.collected.stdout`
|
|
33
|
-
* / `stderr` expose `readFrom(offset)` with the seam's standard shape.
|
|
34
|
-
*
|
|
35
|
-
* @typedef {Object} SearchCollectedReader
|
|
36
|
-
* @property {(from: number) => {text: string, lossy: boolean, nextOffset: number}} readFrom
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* The slice of the subprocess handle the file-search tool consumes
|
|
41
|
-
* (`dsh-tool-fs-search` `runRipgrep`): settle on `done`, then read the
|
|
42
|
-
* collected streams. Nothing else of the handle is contract.
|
|
43
|
-
*
|
|
44
|
-
* @typedef {Object} SearchExecutionHandle
|
|
45
|
-
* @property {Promise<{exitCode: number|null, signal: string|null}>} done
|
|
46
|
-
* @property {{stdout: SearchCollectedReader, stderr: SearchCollectedReader}} collected
|
|
47
|
-
* @property {() => boolean|void} [kill] - optional cooperative kill; the tool
|
|
48
|
-
* relies on its `signal` for cancellation instead.
|
|
49
|
-
*/
|
|
50
|
-
|
|
51
33
|
/**
|
|
52
34
|
* Run one search command through the agent and present it as a search handle.
|
|
53
35
|
*
|
|
54
36
|
* @param object - the handle's collaborators.
|
|
55
|
-
* @
|
|
56
|
-
* accepting `{cwd, argv, maxOutputBytes, signal}` and resolving
|
|
57
|
-
* `{exitCode, stdout, stderr}`; throws `AgentUnavailableError` when out.
|
|
58
|
-
* @param {string} object.cwd - the Linux working directory to search from.
|
|
59
|
-
* @param {string[]} object.argv - the distro-side argv (`rg` and its arguments).
|
|
60
|
-
* @param {number} [object.maxOutputBytes] - per-stream capture ceiling the
|
|
61
|
-
* agent enforces; the tool's own raw-output budget rides here.
|
|
62
|
-
* @param {AbortSignal} [object.signal] - the caller's cancellation.
|
|
63
|
-
* @param {() => object} object.spawnFallback - builds the one-shot handle the
|
|
64
|
-
* facade delegates to when the agent is out; called at most once, lazily.
|
|
65
|
-
* @returns {object} the search handle (`done`, `collected`, `kill`).
|
|
37
|
+
* @returns the search handle (`done`, `collected`, `kill`).
|
|
66
38
|
*/
|
|
67
39
|
export function searchExecutionHandle({ agent, cwd, argv, maxOutputBytes = 0, signal, spawnFallback }) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
40
|
+
const state = { delegate: undefined, stdout: undefined, stderr: undefined };
|
|
41
|
+
const controller = new AbortController();
|
|
42
|
+
if (signal?.aborted)
|
|
43
|
+
controller.abort(signal.reason);
|
|
44
|
+
else
|
|
45
|
+
signal?.addEventListener("abort", () => controller.abort(signal.reason), { once: true });
|
|
46
|
+
const reader = (slot) => ({
|
|
47
|
+
readFrom(from) {
|
|
48
|
+
const buffer = state[slot] ?? Buffer.alloc(0);
|
|
49
|
+
const start = Math.min(from, buffer.length);
|
|
50
|
+
return { text: buffer.subarray(start).toString("utf8"), lossy: false, nextOffset: buffer.length };
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
const handle = {
|
|
54
|
+
get collected() {
|
|
55
|
+
if (state.delegate !== undefined)
|
|
56
|
+
return state.delegate.collected;
|
|
57
|
+
return { stdout: reader("stdout"), stderr: reader("stderr") };
|
|
58
|
+
},
|
|
59
|
+
done: (async () => {
|
|
60
|
+
let result;
|
|
61
|
+
try {
|
|
62
|
+
result = await agent.exec({ cwd, argv, maxOutputBytes, signal: controller.signal });
|
|
63
|
+
state.stdout = result.stdout;
|
|
64
|
+
state.stderr = result.stderr;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (!(error instanceof AgentUnavailableError)) {
|
|
68
|
+
if (controller.signal.aborted) {
|
|
69
|
+
// Our kill() or the caller's cancellation: the killed outcome the
|
|
70
|
+
// one-shot path reports, not a failure.
|
|
71
|
+
return { exitCode: null, signal: "SIGTERM" };
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
const delegate = spawnFallback();
|
|
76
|
+
state.delegate = delegate;
|
|
77
|
+
return delegate.done;
|
|
78
|
+
}
|
|
79
|
+
return { exitCode: result.exitCode, signal: null };
|
|
80
|
+
})(),
|
|
81
|
+
kill() {
|
|
82
|
+
controller.abort(new Error("killed"));
|
|
83
|
+
state.delegate?.kill?.();
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
return handle;
|
|
114
87
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which execution world a file-search spawn belongs to, and the `wsl.exe`
|
|
3
|
+
* argv it becomes there.
|
|
4
|
+
*
|
|
5
|
+
* `dsh-tool-fs-search` spawns the packaged ripgrep binary through
|
|
6
|
+
* `ctx.subprocess` with the session workspace as the working directory. Left
|
|
7
|
+
* alone, that spawn runs the Windows binary over the distro's 9p share — the
|
|
8
|
+
* one model-facing I/O path that still crossed the share, and in the slow
|
|
9
|
+
* direction: measured at ~17 ms per file of metadata round trips where the
|
|
10
|
+
* distro's own ext4 answers in microseconds. The interception point is the
|
|
11
|
+
* subprocess provider's `spawn`, and the decision lives here — pure,
|
|
12
|
+
* peer-free, and unit-testable, mirroring `terminal-route.js`.
|
|
13
|
+
*
|
|
14
|
+
* The rewrite is deliberately narrow. A spawn is intercepted only when the
|
|
15
|
+
* working directory NAMES the distro outright — a `\\wsl.localhost\<distro>`
|
|
16
|
+
* UNC — because that is the form a distro session's search carries and the
|
|
17
|
+
* only one that resolves without an async distro query: `spawn` hands back
|
|
18
|
+
* its handle synchronously, so the override must stay synchronous. A Windows
|
|
19
|
+
* drive directory keeps the host binary, which is native there and touches
|
|
20
|
+
* no share; anything else passes through untouched, exactly as before.
|
|
21
|
+
*
|
|
22
|
+
* The rewritten argv forwards every rg argument verbatim, so the tool reads
|
|
23
|
+
* the output format and exit codes of the distro's own rg. One addition is
|
|
24
|
+
* made: when the tool named no search path — its pattern rides `--regexp=`,
|
|
25
|
+
* and a bare `--` separator is exactly how it spells an explicit path — the
|
|
26
|
+
* rewrite appends `-- .`. Reason: `wsl.exe --exec` hands the distro-side rg a
|
|
27
|
+
* relay FIFO for stdin, and rg's readable-stdin heuristic then searches stdin
|
|
28
|
+
* (instantly empty) instead of walking the working directory; the packaged
|
|
29
|
+
* binary under the old path saw a non-readable NUL stdin and walked it. An
|
|
30
|
+
* explicit path makes the search deterministic on either transport.
|
|
31
|
+
*
|
|
32
|
+
* The distro-side binary must therefore exist — `scripts/bootstrap.sh` reports
|
|
33
|
+
* it — and a distro without one surfaces rg's own "command not found" instead
|
|
34
|
+
* of a silent fall-back to the share.
|
|
35
|
+
*
|
|
36
|
+
* This is a TypeScript source built to `lib/search-route.js`; edit THIS file
|
|
37
|
+
* and run `pnpm run build` — the artifact under `lib/` is generated, and
|
|
38
|
+
* `pnpm test` fails when it drifts.
|
|
39
|
+
*
|
|
40
|
+
* @module dsh-plugin-wsl/search-route
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Whether a spawn's program is the ripgrep binary.
|
|
44
|
+
*
|
|
45
|
+
* A bare `argv[0]` is judged by its own name, so the packaged binary's
|
|
46
|
+
* absolute path and any other spelling of `rg` match alike.
|
|
47
|
+
*
|
|
48
|
+
* @param program - the spawn's `argv[0]`.
|
|
49
|
+
* @returns true when the spawn is a ripgrep launch.
|
|
50
|
+
*/
|
|
51
|
+
export declare function isSearchProgram(program: unknown): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* The distro-side rewrite of one search spawn, when it belongs in the distro.
|
|
54
|
+
*
|
|
55
|
+
* Two argv forms come back for one decision. `rgArgv` is the distro command
|
|
56
|
+
* itself (`rg` and its arguments) — what the resident agent execs. `wslArgv`
|
|
57
|
+
* is the same command wrapped for a one-shot `wsl.exe --exec` launch — the
|
|
58
|
+
* fallback transport for when the agent is out. The working directory becomes
|
|
59
|
+
* the `--cd` the distro starts in; `wsl.exe` itself must start in a Windows
|
|
60
|
+
* directory, so the caller supplies its own host cwd when applying `wslArgv`.
|
|
61
|
+
*
|
|
62
|
+
* @param object - the spawn's inputs.
|
|
63
|
+
* @returns the rewrite, or undefined when the spawn is not a distro search and
|
|
64
|
+
* must run exactly as the shipped implementation would run it.
|
|
65
|
+
*/
|
|
66
|
+
export declare function searchSpawnRewrite({ argv, cwd, wslPath }: {
|
|
67
|
+
argv: readonly string[];
|
|
68
|
+
cwd: string;
|
|
69
|
+
wslPath: string;
|
|
70
|
+
}): {
|
|
71
|
+
distro: string;
|
|
72
|
+
linuxCwd: string;
|
|
73
|
+
rgArgv: string[];
|
|
74
|
+
wslArgv: string[];
|
|
75
|
+
} | undefined;
|
package/lib/search-route.js
CHANGED
|
@@ -33,29 +33,30 @@
|
|
|
33
33
|
* it — and a distro without one surfaces rg's own "command not found" instead
|
|
34
34
|
* of a silent fall-back to the share.
|
|
35
35
|
*
|
|
36
|
+
* This is a TypeScript source built to `lib/search-route.js`; edit THIS file
|
|
37
|
+
* and run `pnpm run build` — the artifact under `lib/` is generated, and
|
|
38
|
+
* `pnpm test` fails when it drifts.
|
|
39
|
+
*
|
|
36
40
|
* @module dsh-plugin-wsl/search-route
|
|
37
41
|
*/
|
|
38
|
-
|
|
39
42
|
import { uncToPosix } from "./paths.js";
|
|
40
|
-
|
|
41
43
|
/** The one program this rewrite passes through to the distro. */
|
|
42
44
|
const SEARCH_PROGRAM = "rg";
|
|
43
|
-
|
|
44
45
|
/**
|
|
45
46
|
* Whether a spawn's program is the ripgrep binary.
|
|
46
47
|
*
|
|
47
48
|
* A bare `argv[0]` is judged by its own name, so the packaged binary's
|
|
48
49
|
* absolute path and any other spelling of `rg` match alike.
|
|
49
50
|
*
|
|
50
|
-
* @param
|
|
51
|
+
* @param program - the spawn's `argv[0]`.
|
|
51
52
|
* @returns true when the spawn is a ripgrep launch.
|
|
52
53
|
*/
|
|
53
54
|
export function isSearchProgram(program) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
if (typeof program !== "string" || program.length === 0)
|
|
56
|
+
return false;
|
|
57
|
+
const base = program.slice(Math.max(program.lastIndexOf("/"), program.lastIndexOf("\\")) + 1);
|
|
58
|
+
return base.toLowerCase().replace(/\.exe$/u, "") === SEARCH_PROGRAM;
|
|
57
59
|
}
|
|
58
|
-
|
|
59
60
|
/**
|
|
60
61
|
* The distro-side rewrite of one search spawn, when it belongs in the distro.
|
|
61
62
|
*
|
|
@@ -67,29 +68,27 @@ export function isSearchProgram(program) {
|
|
|
67
68
|
* directory, so the caller supplies its own host cwd when applying `wslArgv`.
|
|
68
69
|
*
|
|
69
70
|
* @param object - the spawn's inputs.
|
|
70
|
-
* @
|
|
71
|
-
*
|
|
72
|
-
* @param {string} object.wslPath - path to `wsl.exe`.
|
|
73
|
-
* @returns {{distro: string, linuxCwd: string, rgArgv: string[], wslArgv: string[]}|undefined} the
|
|
74
|
-
* rewrite, or undefined when the spawn is not a distro search and must run
|
|
75
|
-
* exactly as the shipped implementation would run it.
|
|
71
|
+
* @returns the rewrite, or undefined when the spawn is not a distro search and
|
|
72
|
+
* must run exactly as the shipped implementation would run it.
|
|
76
73
|
*/
|
|
77
74
|
export function searchSpawnRewrite({ argv, cwd, wslPath }) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
75
|
+
if (!Array.isArray(argv) || !isSearchProgram(argv[0]))
|
|
76
|
+
return undefined;
|
|
77
|
+
const named = uncToPosix(cwd);
|
|
78
|
+
if (named === undefined)
|
|
79
|
+
return undefined;
|
|
80
|
+
const linuxCwd = named.linuxPath.length > 0 ? named.linuxPath : "/";
|
|
81
|
+
// No bare `--` means the tool named no search path; without one, distro-side
|
|
82
|
+
// rg reads the relay fifo on stdin (rg's readable-stdin heuristic) instead of
|
|
83
|
+
// walking the directory. `.` is the working directory — the same directory
|
|
84
|
+
// the packaged binary walked when the host spawned it directly.
|
|
85
|
+
const forwarded = argv.slice(1);
|
|
86
|
+
const tool = forwarded.includes("--") ? forwarded : [...forwarded, "--", "."];
|
|
87
|
+
const rgArgv = [SEARCH_PROGRAM, ...tool];
|
|
88
|
+
return {
|
|
89
|
+
distro: named.distro,
|
|
90
|
+
linuxCwd,
|
|
91
|
+
rgArgv,
|
|
92
|
+
wslArgv: [wslPath, "-d", named.distro, "--cd", linuxCwd, "--exec", ...rgArgv],
|
|
93
|
+
};
|
|
95
94
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contribute the WSL environment's facts to the managed `DSH_*` namespace.
|
|
3
|
+
*
|
|
4
|
+
* `dsh-shell-env` owns `ctx.shellEnv`, the registry that rebuilds the trusted
|
|
5
|
+
* `DSH_*` variables injected into every model shell call. That is DSH's sanctioned
|
|
6
|
+
* channel for telling a model what its shell environment actually is — the shell
|
|
7
|
+
* tool's own description points the model at it ("Managed `$DSH_*` variables
|
|
8
|
+
* expose current harness environment facts").
|
|
9
|
+
*
|
|
10
|
+
* It has one built-in key that looks like it should carry this, and does not:
|
|
11
|
+
*
|
|
12
|
+
* collect() { const values = { [DSH_HOME_ENV]: this.dshHome, [DSH_SHELL_KEY]: "1" } }
|
|
13
|
+
*
|
|
14
|
+
* `DSH_SHELL` is a marker FLAG (`=1`) meaning "a DSH-managed shell call", not the
|
|
15
|
+
* shell path, and it is in `RESERVED_BASH_ENV_KEYS`, so a contributor may not own
|
|
16
|
+
* it. The supported way to convey the real shell is a `DSH_`-prefixed key of our
|
|
17
|
+
* own, declared with a description and resolved synchronously.
|
|
18
|
+
*
|
|
19
|
+
* Scoping uses the same test as `auto-preset`: the agent's recorded workspace
|
|
20
|
+
* directory. A session whose workspace is inside a distro is exactly a session
|
|
21
|
+
* whose shell calls run in that distro — so a host session sees none of these.
|
|
22
|
+
*
|
|
23
|
+
* This is a TypeScript source built to `lib/shell-env.js`; edit THIS file and
|
|
24
|
+
* run `pnpm run build` — the artifact under `lib/` is generated, and `pnpm test`
|
|
25
|
+
* fails when it drifts.
|
|
26
|
+
*
|
|
27
|
+
* @module dsh-plugin-wsl/shell-env
|
|
28
|
+
*/
|
|
29
|
+
import z from "@deepseek-ai/schemastery";
|
|
30
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
31
|
+
export declare const name = "wsl-shell-env";
|
|
32
|
+
export declare const inject: string[];
|
|
33
|
+
export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
34
|
+
/** Distro these facts describe; empty means this machine's default distro. */
|
|
35
|
+
distro: z<string, string, "defined">;
|
|
36
|
+
/** How often the listening-port snapshot refreshes, in milliseconds. */
|
|
37
|
+
portsRefreshMs: z<number, number, "defined">;
|
|
38
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
39
|
+
/** Distro these facts describe; empty means this machine's default distro. */
|
|
40
|
+
distro: z<string, string, "defined">;
|
|
41
|
+
/** How often the listening-port snapshot refreshes, in milliseconds. */
|
|
42
|
+
portsRefreshMs: z<number, number, "defined">;
|
|
43
|
+
}>>, "plain">;
|
|
44
|
+
/** The config schema's validated output. */
|
|
45
|
+
export interface ShellEnvConfig {
|
|
46
|
+
/** Distro these facts describe; empty means this machine's default distro. */
|
|
47
|
+
distro?: string;
|
|
48
|
+
/** How often the listening-port snapshot refreshes, in milliseconds. */
|
|
49
|
+
portsRefreshMs?: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The execution slice the shell-env registry hands a contributor's `resolve`;
|
|
53
|
+
* the harness's execution object is structural here.
|
|
54
|
+
*/
|
|
55
|
+
export interface ShellEnvExecution {
|
|
56
|
+
/** The executing session's agent stub. */
|
|
57
|
+
agent?: {
|
|
58
|
+
session?: {
|
|
59
|
+
header?: {
|
|
60
|
+
cwd?: string | undefined;
|
|
61
|
+
} | undefined;
|
|
62
|
+
} | undefined;
|
|
63
|
+
} | undefined;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The cordis context extended with the `shellEnv` registry slice this plugin
|
|
67
|
+
* contributes through. The registry belongs to the harness and its
|
|
68
|
+
* declaration is not visible to this plugin, so it is spelled structurally on
|
|
69
|
+
* top of the framework's {@link Context}.
|
|
70
|
+
*/
|
|
71
|
+
export type ShellEnvContext = Context & {
|
|
72
|
+
shellEnv: {
|
|
73
|
+
register: (contributor: {
|
|
74
|
+
name: string;
|
|
75
|
+
variables: Record<string, {
|
|
76
|
+
description: string;
|
|
77
|
+
}>;
|
|
78
|
+
resolve: (execution: ShellEnvExecution | undefined) => Record<string, string>;
|
|
79
|
+
}) => () => void;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Contribute the WSL environment's facts to the shell-env registry.
|
|
84
|
+
*
|
|
85
|
+
* @param ctx - the cordis context.
|
|
86
|
+
* @param config - the schema-validated plugin config.
|
|
87
|
+
*/
|
|
88
|
+
export declare function apply(ctx: ShellEnvContext, config?: ShellEnvConfig): Promise<void>;
|