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.
Files changed (92) hide show
  1. package/CHANGELOG.md +345 -8
  2. package/CONTRIBUTING.md +221 -68
  3. package/README.md +50 -35
  4. package/SECURITY.md +18 -2
  5. package/SUPPORT.md +23 -7
  6. package/cordis.patch.yml +9 -0
  7. package/docs/ARCHITECTURE.md +200 -57
  8. package/docs/CONFIGURATION.md +180 -16
  9. package/docs/LIMITATIONS.md +91 -33
  10. package/docs/PARITY.md +66 -38
  11. package/docs/PEER-PARITY.md +95 -32
  12. package/docs/README.zh.md +49 -31
  13. package/docs/UPSTREAM-FSIO-EXPORT.md +159 -50
  14. package/docs/UPSTREAM-TERMINAL-TITLE.md +49 -14
  15. package/lib/agent-confined.d.ts +42 -0
  16. package/lib/agent-confined.js +25 -41
  17. package/lib/agent-errors.d.ts +31 -0
  18. package/lib/agent-errors.js +19 -16
  19. package/lib/agent-exec.d.ts +159 -0
  20. package/lib/agent-exec.js +151 -165
  21. package/lib/agent-protocol.d.ts +192 -0
  22. package/lib/agent-protocol.js +76 -100
  23. package/lib/agent-shared.d.ts +27 -0
  24. package/lib/agent-shared.js +12 -12
  25. package/lib/agent.d.ts +353 -0
  26. package/lib/agent.js +536 -552
  27. package/lib/auto-preset.d.ts +110 -0
  28. package/lib/auto-preset.js +157 -141
  29. package/lib/bwrap.d.ts +90 -0
  30. package/lib/bwrap.js +18 -18
  31. package/lib/file-reference-wsl.d.ts +160 -0
  32. package/lib/file-reference-wsl.js +192 -173
  33. package/lib/fs-decisions.d.ts +107 -0
  34. package/lib/fs-decisions.js +64 -60
  35. package/lib/fs-routing.d.ts +182 -0
  36. package/lib/fs-routing.js +173 -116
  37. package/lib/fs-substrate.d.ts +258 -0
  38. package/lib/fs-substrate.js +248 -248
  39. package/lib/fs.d.ts +10 -0
  40. package/lib/fs.js +5 -0
  41. package/lib/fsio-agent.d.ts +307 -0
  42. package/lib/fsio-agent.js +414 -410
  43. package/lib/fsio-text.d.ts +115 -0
  44. package/lib/fsio-text.js +104 -84
  45. package/lib/git-route.d.ts +100 -0
  46. package/lib/git-route.js +46 -47
  47. package/lib/index.d.ts +1003 -0
  48. package/lib/index.js +1106 -1003
  49. package/lib/listing.d.ts +114 -0
  50. package/lib/listing.js +68 -55
  51. package/lib/paths.d.ts +182 -0
  52. package/lib/paths.js +134 -109
  53. package/lib/picker.d.ts +266 -0
  54. package/lib/picker.js +371 -351
  55. package/lib/ports.d.ts +44 -0
  56. package/lib/ports.js +39 -29
  57. package/lib/preflight.d.ts +91 -0
  58. package/lib/preflight.js +94 -0
  59. package/lib/preset-choice.d.ts +33 -0
  60. package/lib/preset-choice.js +15 -7
  61. package/lib/probe-cache.d.ts +51 -0
  62. package/lib/probe-cache.js +43 -29
  63. package/lib/sandbox-core.d.ts +149 -0
  64. package/lib/sandbox-core.js +239 -87
  65. package/lib/sandbox.d.ts +48 -0
  66. package/lib/sandbox.js +4 -4
  67. package/lib/search-exec.d.ts +96 -0
  68. package/lib/search-exec.js +52 -79
  69. package/lib/search-route.d.ts +75 -0
  70. package/lib/search-route.js +30 -31
  71. package/lib/shell-env.d.ts +88 -0
  72. package/lib/shell-env.js +78 -74
  73. package/lib/shell.d.ts +10 -0
  74. package/lib/shell.js +5 -0
  75. package/lib/subprocess.d.ts +313 -0
  76. package/lib/subprocess.js +267 -262
  77. package/lib/terminal-activity.d.ts +98 -0
  78. package/lib/terminal-activity.js +29 -39
  79. package/lib/terminal-route.d.ts +27 -0
  80. package/lib/terminal-route.js +16 -8
  81. package/lib/watcher.d.ts +77 -0
  82. package/lib/watcher.js +120 -126
  83. package/lib/workspace-files-route.d.ts +132 -0
  84. package/lib/workspace-files-route.js +73 -81
  85. package/lib/workspace-files-wsl.d.ts +257 -0
  86. package/lib/workspace-files-wsl.js +311 -251
  87. package/lib/wsl.d.ts +266 -0
  88. package/lib/wsl.js +150 -155
  89. package/locale/preflight/en.json +6 -0
  90. package/locale/preflight/zh.json +6 -0
  91. package/package.json +78 -22
  92. 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`). This
6
- is the record of a gap this plugin worked around, could not work around, and
7
- now documents: the GUI terminal titles every tab from the composition's single
8
- shell profile, so a per-session shell choice is invisible in the title.
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
- `TerminalController.spawn` fixes a terminal's display title from the resolved
13
- shell profile before the provider is ever consulted:
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
- `1 entry did not activate: @deepseek-ai/dsh-client-ui-sidebar-terminal:
53
- pending (waiting for service: webTerminals)`. Verified live against
54
- `dsh-desktop 0.2.0-rc.2`.
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 `create()` uses it for `info.title` when
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
- - Add `shellCandidates` to the `terminal-controller` row in a user layer: a
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;
@@ -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 {object} request - the confinement request.
43
- * @param {string} request.distro - the distro to run in.
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
- const key = `${wslPath}\0${distro}\0${policy.mode}\0${policy.workspaceRoot}\0${maskWindowsDrive ? "masked" : "visible"}\0${realTmp ? "real" : "ephemeral"}`;
60
- let agent = confinedAgents.get(key);
61
- if (agent === undefined) {
62
- agent = new WslAgent({
63
- distro,
64
- wslPath,
65
- scriptPath: toLinuxPath(agentScriptPath),
66
- expectedDigest: agentScriptDigest,
67
- // The prefix is a whole command from the distro's point of view — program,
68
- // profile, `--` — because the transport inserts it between
69
- // `wsl.exe --exec` and the `sh <script>` pair (`lib/agent.js`'s
70
- // `spawnDefault`). Both confinement sites share that assembly in
71
- // `lib/bwrap.js`, whose module doc records what happened when they did not.
72
- argvPrefix: bwrapArgvPrefix(
73
- { mode: policy.mode, workspaceRoot: toLinuxPath(policy.workspaceRoot, { distro }) },
74
- { maskWindowsDrive, realTmp },
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
- confinedAgents.clear();
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
+ }
@@ -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
- * @param {string} message - why the agent is out, for the log.
12
- */
13
- constructor(message) {
14
- super(message);
15
- this.name = "AgentUnavailableError";
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
- * @param {string} linuxPath - the path that could not be entered.
27
- */
28
- constructor(linuxPath) {
29
- super(`could not enter working directory "${linuxPath}"`);
30
- this.name = "CwdError";
31
- this.linuxPath = linuxPath;
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;