dsh-plugin-wsl-env 0.7.5 → 0.9.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 +345 -8
- package/CONTRIBUTING.md +221 -68
- package/README.md +50 -35
- package/SECURITY.md +18 -2
- package/SUPPORT.md +23 -7
- package/cordis.patch.yml +9 -0
- package/docs/ARCHITECTURE.md +200 -57
- package/docs/CONFIGURATION.md +180 -16
- package/docs/LIMITATIONS.md +91 -33
- package/docs/PARITY.md +66 -38
- package/docs/PEER-PARITY.md +95 -32
- package/docs/README.zh.md +49 -31
- package/docs/UPSTREAM-FSIO-EXPORT.md +159 -50
- package/docs/UPSTREAM-TERMINAL-TITLE.md +49 -14
- 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 +1106 -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/preflight.d.ts +91 -0
- package/lib/preflight.js +94 -0
- package/lib/preset-choice.d.ts +33 -0
- package/lib/preset-choice.js +15 -7
- package/lib/probe-cache.d.ts +51 -0
- package/lib/probe-cache.js +43 -29
- package/lib/sandbox-core.d.ts +149 -0
- package/lib/sandbox-core.js +239 -87
- 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 +150 -155
- package/locale/preflight/en.json +6 -0
- package/locale/preflight/zh.json +6 -0
- package/package.json +78 -22
- package/scripts/diagnose.sh +92 -0
|
@@ -2,17 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
Status: proposed upstream as
|
|
4
4
|
[deepseek-ai/deepseek-harness#8769](https://github.com/deepseek-ai/deepseek-harness/discussions/8769)
|
|
5
|
-
(targeting `packages/api/terminal-controller`, version line `0.2.0-rc.x`).
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
(targeting `packages/api/terminal-controller`, version line `0.2.0-rc.x`). The
|
|
6
|
+
plugin worked around the execution half of this gap — a Windows-folder
|
|
7
|
+
session's terminal runs a host shell (`subprocess-wsl.hostSessions`) — and
|
|
8
|
+
could not work around the title half. This is the record: the GUI titles every
|
|
9
|
+
tab from the composition's single shell profile, so a per-session shell choice
|
|
10
|
+
is invisible in the title.
|
|
9
11
|
|
|
10
12
|
## The gap
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
The display title is fixed from the resolved shell profile and never revisited.
|
|
15
|
+
The private `TerminalController.spawn` builds `info`, with `title: shell.name`,
|
|
16
|
+
only after `await subprocess.spawnTerminal(...)` has returned, and it does not
|
|
17
|
+
read back what that call started:
|
|
14
18
|
|
|
15
19
|
```ts
|
|
20
|
+
const handle = await subprocess.spawnTerminal({
|
|
21
|
+
argv: [shell.path, ...shell.args], cwd: environment.cwd, cols: request.cols, ...
|
|
22
|
+
})
|
|
16
23
|
const info: WebTerminalInfo = {
|
|
17
24
|
id: request.id, shell, title: shell.name, cwd: environment.cwd, ...
|
|
18
25
|
}
|
|
@@ -48,10 +55,15 @@ Three escape hatches were evaluated against `0.2.0-rc.2` and each is closed:
|
|
|
48
55
|
inserts `terminal-controller`, and the client model that the
|
|
49
56
|
`dsh-client-ui-sidebar-terminal` entry waits for mounts with
|
|
50
57
|
`inject = ['remote', 'remote.terminal']` — the `terminal` namespace the host
|
|
51
|
-
controller registers in its constructor. Disabling the row fails web boot
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
controller registers in its constructor. Disabling the row fails web boot
|
|
59
|
+
with this two-line error:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
web boot: 1 entry did not activate
|
|
63
|
+
@deepseek-ai/dsh-client-ui-sidebar-terminal: pending (waiting for service: webTerminals)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Verified live against `dsh-desktop 0.2.0-rc.2` (Electron 44.0.0).
|
|
55
67
|
3. **Per-session config.** The `shell` profile is composition state; `!!js`
|
|
56
68
|
expressions evaluate once at config resolution and reach no Session
|
|
57
69
|
identity.
|
|
@@ -62,7 +74,7 @@ Any one of these closes the gap; the first is the smallest and keeps the
|
|
|
62
74
|
profile name as the fallback:
|
|
63
75
|
|
|
64
76
|
- `SubprocessTerminalSpawnSpec` gains an optional `displayTitle` (or the handle
|
|
65
|
-
exposes the launched `argv`), and `
|
|
77
|
+
exposes the launched `argv`), and `spawn()` uses it for `info.title` when
|
|
66
78
|
present — falling back to `shell.name`. A provider that rewrites a launch
|
|
67
79
|
states the program it started; every other launch keeps the profile name.
|
|
68
80
|
- Alternatively, the controller derives the title from the effective program
|
|
@@ -71,11 +83,34 @@ profile name as the fallback:
|
|
|
71
83
|
|
|
72
84
|
With either, this plugin deletes the limitation paragraph in
|
|
73
85
|
[LIMITATIONS.md](LIMITATIONS.md) and the `WSL`-over-PowerShell title, for any
|
|
74
|
-
provider that rewrites launches — not just WSL.
|
|
86
|
+
provider that rewrites launches — not just WSL. The same limitation is stated
|
|
87
|
+
as current fact in four places. Three go stale the day the proposal lands; the
|
|
88
|
+
fourth is already out of step and needs the corrections above whenever it is
|
|
89
|
+
next touched:
|
|
90
|
+
|
|
91
|
+
- the GUI-terminal comment block in [`cordis.patch.yml`](../cordis.patch.yml);
|
|
92
|
+
- the closing paragraph of "Why the terminal provider is app-level" in
|
|
93
|
+
[ARCHITECTURE.md](ARCHITECTURE.md);
|
|
94
|
+
- R4's `残差` (residual) column in [REQUIREMENTS.zh.md](REQUIREMENTS.zh.md);
|
|
95
|
+
- the `WSL` tab-title bullet under "Terminals" in
|
|
96
|
+
[LIMITATIONS.md](LIMITATIONS.md) — the paragraph named just above. It says
|
|
97
|
+
the controller titles a tab *before* the provider rewrites the launch, the
|
|
98
|
+
reverse of the ordering corrected at the top of this file, and it carries the
|
|
99
|
+
one-line `shellCandidates` workaround that drops the row's `shell` block.
|
|
75
100
|
|
|
76
101
|
## Workarounds until then
|
|
77
102
|
|
|
78
103
|
- Double-click a tab's title to rename it (a shipped client feature; the
|
|
79
104
|
controller's `rename` Remote API accepts 1–120 characters).
|
|
80
|
-
-
|
|
81
|
-
manually selected shell is titled after itself.
|
|
105
|
+
- Re-declare the `terminal-controller` row in a user layer with a non-empty
|
|
106
|
+
`shellCandidates`: a manually selected shell is titled after itself. A patch
|
|
107
|
+
config replaces the whole config rather than deep-merging it
|
|
108
|
+
(`packages/boot/app-boot/src/config-schema/document.ts`), so the override has
|
|
109
|
+
to carry this plugin's `shell` block verbatim — `path: wsl.exe`, `name: WSL`,
|
|
110
|
+
`args: []` — or the terminal stops being a distro terminal. Expect the menu
|
|
111
|
+
to grow the host shells discovery finds on the root provider, which receives
|
|
112
|
+
no directory: executables this terminal never starts (the second bullet of
|
|
113
|
+
the GUI-terminal comment in [`cordis.patch.yml`](../cordis.patch.yml)).
|
|
114
|
+
[LIMITATIONS.md](LIMITATIONS.md) still carries the older one-line form of
|
|
115
|
+
this tip, which drops the `shell` block; the version above is the one that
|
|
116
|
+
works.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { WslAgent } from "./agent.js";
|
|
2
|
+
/** One confinement request. */
|
|
3
|
+
export interface ConfinedAgentRequest {
|
|
4
|
+
/** The distro to run in. */
|
|
5
|
+
distro: string;
|
|
6
|
+
/** The `wsl.exe` path. */
|
|
7
|
+
wslPath?: string;
|
|
8
|
+
/**
|
|
9
|
+
* The RESOLVED file-effect policy: the profile binds exactly what the mode
|
|
10
|
+
* grants.
|
|
11
|
+
*/
|
|
12
|
+
policy: {
|
|
13
|
+
mode: string;
|
|
14
|
+
workspaceRoot: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Shadow `/mnt` with an empty tmpfs in the resident's profile; part of the
|
|
18
|
+
* cache key, since it changes the mount table the resident lives under.
|
|
19
|
+
*/
|
|
20
|
+
maskWindowsDrive?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Default TRUE: workspace-write binds the distro's real `/tmp` read-write
|
|
23
|
+
* into the resident's profile, so a write the fence approved lands where
|
|
24
|
+
* every reader reads. `false` restores the ephemeral tmpfs — for tests and
|
|
25
|
+
* probes, not production: a `/tmp` write behind an ephemeral mount reports
|
|
26
|
+
* success into a directory no reader can see, which is the bug this default
|
|
27
|
+
* exists to prevent.
|
|
28
|
+
*/
|
|
29
|
+
realTmp?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The confined agent for one distro and one resolved policy, created on first
|
|
33
|
+
* use and shared for the process lifetime. A success is remembered; a failed
|
|
34
|
+
* start is not (the agent's own rebuild rules take over from there).
|
|
35
|
+
*
|
|
36
|
+
* @param request - the confinement request.
|
|
37
|
+
* @returns the confined resident for this key.
|
|
38
|
+
*/
|
|
39
|
+
export declare function confinedAgent({ distro, wslPath, policy, maskWindowsDrive, realTmp }: ConfinedAgentRequest): WslAgent;
|
|
40
|
+
/** Test hook: forget every confined resident (the plain agent has no factory state). */
|
|
41
|
+
export declare function resetConfinedAgents(): void;
|
|
42
|
+
export default confinedAgent;
|
package/lib/agent-confined.js
CHANGED
|
@@ -25,63 +25,47 @@
|
|
|
25
25
|
* a third copy, so nothing compared production's command with a working one and
|
|
26
26
|
* every confined mutation failed the resident's handshake.
|
|
27
27
|
*
|
|
28
|
+
* This is a TypeScript source built to `lib/agent-confined.js`; edit THIS file
|
|
29
|
+
* and run `pnpm run build` — the artifact under `lib/` is generated, and
|
|
30
|
+
* `pnpm test` fails when it drifts.
|
|
31
|
+
*
|
|
28
32
|
* @module dsh-plugin-wsl/agent-confined
|
|
29
33
|
*/
|
|
30
34
|
import { bwrapArgvPrefix } from "./bwrap.js";
|
|
31
35
|
import { toLinuxPath } from "./paths.js";
|
|
32
36
|
import { agentScriptDigest, agentScriptPath } from "./agent-shared.js";
|
|
33
37
|
import { WslAgent } from "./agent.js";
|
|
34
|
-
|
|
35
38
|
const confinedAgents = new Map();
|
|
36
|
-
|
|
37
39
|
/**
|
|
38
40
|
* The confined agent for one distro and one resolved policy, created on first
|
|
39
41
|
* use and shared for the process lifetime. A success is remembered; a failed
|
|
40
42
|
* start is not (the agent's own rebuild rules take over from there).
|
|
41
43
|
*
|
|
42
|
-
* @param
|
|
43
|
-
* @
|
|
44
|
-
* @param {string} [request.wslPath] - the `wsl.exe` path.
|
|
45
|
-
* @param {{mode: string, workspaceRoot: string}} request.policy - the RESOLVED
|
|
46
|
-
* file-effect policy: the profile binds exactly what the mode grants.
|
|
47
|
-
* @param {boolean} [request.maskWindowsDrive] - shadow `/mnt` with an empty
|
|
48
|
-
* tmpfs in the resident's profile; part of the cache key, since it changes
|
|
49
|
-
* the mount table the resident lives under.
|
|
50
|
-
* @param {boolean} [request.realTmp] - default TRUE: workspace-write binds the
|
|
51
|
-
* distro's real `/tmp` read-write into the resident's profile, so a write
|
|
52
|
-
* the fence approved lands where every reader reads. `false` restores the
|
|
53
|
-
* ephemeral tmpfs — for tests and probes, not production: a `/tmp` write
|
|
54
|
-
* behind an ephemeral mount reports success into a directory no reader can
|
|
55
|
-
* see, which is the bug this default exists to prevent.
|
|
56
|
-
* @returns {WslAgent} the confined resident for this key.
|
|
44
|
+
* @param request - the confinement request.
|
|
45
|
+
* @returns the confined resident for this key.
|
|
57
46
|
*/
|
|
58
47
|
export function confinedAgent({ distro, wslPath = "wsl.exe", policy, maskWindowsDrive = false, realTmp = true }) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
confinedAgents.set(key, agent);
|
|
78
|
-
}
|
|
79
|
-
return agent;
|
|
48
|
+
const key = `${wslPath}\0${distro}\0${policy.mode}\0${policy.workspaceRoot}\0${maskWindowsDrive ? "masked" : "visible"}\0${realTmp ? "real" : "ephemeral"}`;
|
|
49
|
+
let agent = confinedAgents.get(key);
|
|
50
|
+
if (agent === undefined) {
|
|
51
|
+
agent = new WslAgent({
|
|
52
|
+
distro,
|
|
53
|
+
wslPath,
|
|
54
|
+
scriptPath: toLinuxPath(agentScriptPath),
|
|
55
|
+
expectedDigest: agentScriptDigest,
|
|
56
|
+
// The prefix is a whole command from the distro's point of view — program,
|
|
57
|
+
// profile, `--` — because the transport inserts it between
|
|
58
|
+
// `wsl.exe --exec` and the `sh <script>` pair (`lib/agent.js`'s
|
|
59
|
+
// `spawnDefault`). Both confinement sites share that assembly in
|
|
60
|
+
// `lib/bwrap.js`, whose module doc records what happened when they did not.
|
|
61
|
+
argvPrefix: bwrapArgvPrefix({ mode: policy.mode, workspaceRoot: toLinuxPath(policy.workspaceRoot, { distro }) }, { maskWindowsDrive, realTmp }),
|
|
62
|
+
});
|
|
63
|
+
confinedAgents.set(key, agent);
|
|
64
|
+
}
|
|
65
|
+
return agent;
|
|
80
66
|
}
|
|
81
|
-
|
|
82
67
|
/** Test hook: forget every confined resident (the plain agent has no factory state). */
|
|
83
68
|
export function resetConfinedAgents() {
|
|
84
|
-
|
|
69
|
+
confinedAgents.clear();
|
|
85
70
|
}
|
|
86
|
-
|
|
87
71
|
export default confinedAgent;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The error a {@link WslAgent} rejects with when the resident process is out
|
|
3
|
+
* for this host process — the executor's cue to fall back to the one-shot
|
|
4
|
+
* `wsl.exe` path. Deliberately its own class: callers must not confuse
|
|
5
|
+
* "the agent cannot serve" with a failure of the command itself.
|
|
6
|
+
*
|
|
7
|
+
* This is a TypeScript source built to `lib/agent-errors.js`; edit THIS file
|
|
8
|
+
* and run `pnpm run build` — the artifact under `lib/` is generated, and
|
|
9
|
+
* `pnpm test` fails when it drifts.
|
|
10
|
+
*
|
|
11
|
+
* @module dsh-plugin-wsl/agent-errors
|
|
12
|
+
*/
|
|
13
|
+
export declare class AgentUnavailableError extends Error {
|
|
14
|
+
/**
|
|
15
|
+
* @param message - why the agent is out, for the log.
|
|
16
|
+
*/
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The request's workdir could not be entered inside the distro. Carries the
|
|
21
|
+
* Linux path so a caller can synthesize the same diagnostic the one-shot
|
|
22
|
+
* `wsl.exe` path produces from the relay's `chdir(...) failed` text.
|
|
23
|
+
*/
|
|
24
|
+
export declare class CwdError extends Error {
|
|
25
|
+
/** The path that could not be entered, in the distro's own spelling. */
|
|
26
|
+
linuxPath: string;
|
|
27
|
+
/**
|
|
28
|
+
* @param linuxPath - the path that could not be entered.
|
|
29
|
+
*/
|
|
30
|
+
constructor(linuxPath: string);
|
|
31
|
+
}
|
package/lib/agent-errors.js
CHANGED
|
@@ -4,30 +4,33 @@
|
|
|
4
4
|
* `wsl.exe` path. Deliberately its own class: callers must not confuse
|
|
5
5
|
* "the agent cannot serve" with a failure of the command itself.
|
|
6
6
|
*
|
|
7
|
+
* This is a TypeScript source built to `lib/agent-errors.js`; edit THIS file
|
|
8
|
+
* and run `pnpm run build` — the artifact under `lib/` is generated, and
|
|
9
|
+
* `pnpm test` fails when it drifts.
|
|
10
|
+
*
|
|
7
11
|
* @module dsh-plugin-wsl/agent-errors
|
|
8
12
|
*/
|
|
9
13
|
export class AgentUnavailableError extends Error {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
/**
|
|
15
|
+
* @param message - why the agent is out, for the log.
|
|
16
|
+
*/
|
|
17
|
+
constructor(message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "AgentUnavailableError";
|
|
20
|
+
}
|
|
17
21
|
}
|
|
18
|
-
|
|
19
22
|
/**
|
|
20
23
|
* The request's workdir could not be entered inside the distro. Carries the
|
|
21
24
|
* Linux path so a caller can synthesize the same diagnostic the one-shot
|
|
22
25
|
* `wsl.exe` path produces from the relay's `chdir(...) failed` text.
|
|
23
26
|
*/
|
|
24
27
|
export class CwdError extends Error {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
/**
|
|
29
|
+
* @param linuxPath - the path that could not be entered.
|
|
30
|
+
*/
|
|
31
|
+
constructor(linuxPath) {
|
|
32
|
+
super(`could not enter working directory "${linuxPath}"`);
|
|
33
|
+
this.name = "CwdError";
|
|
34
|
+
this.linuxPath = linuxPath;
|
|
35
|
+
}
|
|
33
36
|
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An agent-backed implementation of the shell execution handle.
|
|
3
|
+
*
|
|
4
|
+
* `LocalBashExecutor.executeArgv` builds its handle around a subprocess spawn:
|
|
5
|
+
* live output readers with byte offsets, spill files beyond the collect
|
|
6
|
+
* budget, a deadline that kills the Windows process tree. This module builds
|
|
7
|
+
* the SAME handle shape around one agent `exec` — the shape `dsh-tool-bash`
|
|
8
|
+
* consumes, so the tool layer cannot tell which path ran. The deliberate
|
|
9
|
+
* deviations, documented here rather than hidden:
|
|
10
|
+
*
|
|
11
|
+
* - **No spill files; the capture cap is the agent's.** The EXEC frame
|
|
12
|
+
* carries the caller's per-stream budget and the AGENT cuts stdout and
|
|
13
|
+
* stderr at it, so a chatty command costs bounded distro memory AND bounded
|
|
14
|
+
* host memory; the RES line's flags say which streams were cut and the
|
|
15
|
+
* handle reports them as `truncated: true`. What the budget spares is the
|
|
16
|
+
* upstream spill machinery: the capture is still whole-in-memory, just
|
|
17
|
+
* whole-at-a-bounded-size.
|
|
18
|
+
* - **Killed commands report `SIGTERM`**, not a raw exit status: the
|
|
19
|
+
* in-distro timeout and `kill()` both mean "we stopped it", and upstream
|
|
20
|
+
* reports its own kills the same way.
|
|
21
|
+
*
|
|
22
|
+
* Pure shaping over an injected runner, so the handle semantics are
|
|
23
|
+
* unit-testable without a distro.
|
|
24
|
+
*
|
|
25
|
+
* This is a TypeScript source built to `lib/agent-exec.js`; edit THIS file and
|
|
26
|
+
* run `pnpm run build` — the artifact under `lib/` is generated, and `pnpm test`
|
|
27
|
+
* fails when it drifts.
|
|
28
|
+
*
|
|
29
|
+
* @module dsh-plugin-wsl/agent-exec
|
|
30
|
+
*/
|
|
31
|
+
/** The request one agent `exec` carries. */
|
|
32
|
+
export interface AgentExecRequest {
|
|
33
|
+
/** Linux workdir. */
|
|
34
|
+
cwd: string;
|
|
35
|
+
/** The distro-side argv. */
|
|
36
|
+
argv: string[];
|
|
37
|
+
/** Whole-command budget. */
|
|
38
|
+
timeoutMs?: number;
|
|
39
|
+
/** Extra environment for this request only. */
|
|
40
|
+
env?: Record<string, string>;
|
|
41
|
+
/** Per-stream capture ceiling. */
|
|
42
|
+
maxOutputBytes?: number;
|
|
43
|
+
/** The caller's cancellation. */
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
}
|
|
46
|
+
/** What an agent `exec` resolves with. */
|
|
47
|
+
export interface AgentExecResult {
|
|
48
|
+
/** The command's exit code. */
|
|
49
|
+
exitCode: number;
|
|
50
|
+
/** The captured stdout. */
|
|
51
|
+
stdout: Buffer;
|
|
52
|
+
/** The captured stderr. */
|
|
53
|
+
stderr: Buffer;
|
|
54
|
+
/** The agent's per-stream capture-cap flags. */
|
|
55
|
+
truncated?: {
|
|
56
|
+
stdout: boolean;
|
|
57
|
+
stderr: boolean;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** The {@link WslAgent}-like runner this module drives. */
|
|
61
|
+
export interface AgentExecRunner {
|
|
62
|
+
/** Run one command. */
|
|
63
|
+
exec: (request: AgentExecRequest) => Promise<AgentExecResult>;
|
|
64
|
+
}
|
|
65
|
+
/** How the exec promise settled: the result, or the failure to propagate. */
|
|
66
|
+
export type SettledExec = {
|
|
67
|
+
ok: true;
|
|
68
|
+
result: AgentExecResult;
|
|
69
|
+
error?: undefined;
|
|
70
|
+
} | {
|
|
71
|
+
ok: false;
|
|
72
|
+
result?: undefined;
|
|
73
|
+
error: unknown;
|
|
74
|
+
};
|
|
75
|
+
/** The memory-reader slice of one output stream. */
|
|
76
|
+
export interface OutputReader {
|
|
77
|
+
/** Read the stream from a byte offset. */
|
|
78
|
+
readFrom: (fromByte: number) => {
|
|
79
|
+
text: string;
|
|
80
|
+
lossy: boolean;
|
|
81
|
+
nextOffset: number;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** One settled foreground command, the shape `dsh-tool-bash` consumes. */
|
|
85
|
+
export interface AgentExecutionHandle {
|
|
86
|
+
/** The lifecycle state. */
|
|
87
|
+
status: "running" | "completed" | "killed";
|
|
88
|
+
/** The exit code; null until settled or when killed. */
|
|
89
|
+
exitCode: number | null;
|
|
90
|
+
/** The terminating signal; null on a clean exit. */
|
|
91
|
+
signal: string | null;
|
|
92
|
+
/** The live output readers. */
|
|
93
|
+
observed: {
|
|
94
|
+
stdout: OutputReader;
|
|
95
|
+
stderr: OutputReader;
|
|
96
|
+
};
|
|
97
|
+
/** Settles when the command settles. */
|
|
98
|
+
done: Promise<void>;
|
|
99
|
+
/** The incremental delta reader. */
|
|
100
|
+
readOutput: () => {
|
|
101
|
+
delta: string;
|
|
102
|
+
lossy: boolean;
|
|
103
|
+
};
|
|
104
|
+
/** Stop the command; true on the first call only. */
|
|
105
|
+
kill: () => boolean;
|
|
106
|
+
/** The canonical bash result. */
|
|
107
|
+
result: () => Promise<{
|
|
108
|
+
exitCode: number | null;
|
|
109
|
+
signal: string | null;
|
|
110
|
+
timedOut: boolean;
|
|
111
|
+
aborted: boolean;
|
|
112
|
+
timeoutMs: number;
|
|
113
|
+
stdout: {
|
|
114
|
+
text: string;
|
|
115
|
+
lossy: boolean;
|
|
116
|
+
truncated: boolean;
|
|
117
|
+
};
|
|
118
|
+
stderr: {
|
|
119
|
+
text: string;
|
|
120
|
+
lossy: boolean;
|
|
121
|
+
truncated: boolean;
|
|
122
|
+
};
|
|
123
|
+
}>;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Run one command through the agent and present it as an execution handle.
|
|
127
|
+
*
|
|
128
|
+
* @param request - the execution request.
|
|
129
|
+
* @returns the execution handle (`status`, `exitCode`, `signal`, `observed`,
|
|
130
|
+
* `done`, `readOutput`, `kill`, `result`).
|
|
131
|
+
*/
|
|
132
|
+
export declare function agentExecutionHandle({ agent, cwd, argv, timeoutMs, maxOutputBytes, signal, onStarted }: {
|
|
133
|
+
/**
|
|
134
|
+
* The {@link WslAgent}-like runner: `exec` accepting
|
|
135
|
+
* `{cwd, argv, timeoutMs, signal}` and resolving `{exitCode, stdout, stderr}`;
|
|
136
|
+
* throws `AgentUnavailableError` when out.
|
|
137
|
+
*/
|
|
138
|
+
agent: AgentExecRunner;
|
|
139
|
+
/** Linux workdir. */
|
|
140
|
+
cwd: string;
|
|
141
|
+
/** The distro-side argv, bwrap-wrapped when confined. */
|
|
142
|
+
argv: string[];
|
|
143
|
+
/**
|
|
144
|
+
* Whole-command budget; the agent TERMs at the ceiling second and KILLs
|
|
145
|
+
* after its grace.
|
|
146
|
+
*/
|
|
147
|
+
timeoutMs?: number;
|
|
148
|
+
/**
|
|
149
|
+
* Per-stream capture ceiling the agent enforces; the handle reports a cut
|
|
150
|
+
* stream as `truncated: true`.
|
|
151
|
+
*/
|
|
152
|
+
maxOutputBytes?: number;
|
|
153
|
+
/** The caller's cancellation. */
|
|
154
|
+
signal?: AbortSignal;
|
|
155
|
+
/**
|
|
156
|
+
* Provider-facts hook, called synchronously before the handle can settle.
|
|
157
|
+
*/
|
|
158
|
+
onStarted?: (proc: object) => void;
|
|
159
|
+
}): AgentExecutionHandle;
|