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.
Files changed (79) hide show
  1. package/CHANGELOG.md +278 -0
  2. package/CONTRIBUTING.md +129 -39
  3. package/README.md +20 -20
  4. package/docs/ARCHITECTURE.md +6 -3
  5. package/docs/LIMITATIONS.md +13 -0
  6. package/docs/README.zh.md +20 -20
  7. package/lib/agent-confined.d.ts +42 -0
  8. package/lib/agent-confined.js +25 -41
  9. package/lib/agent-errors.d.ts +31 -0
  10. package/lib/agent-errors.js +19 -16
  11. package/lib/agent-exec.d.ts +159 -0
  12. package/lib/agent-exec.js +151 -165
  13. package/lib/agent-protocol.d.ts +192 -0
  14. package/lib/agent-protocol.js +76 -100
  15. package/lib/agent-shared.d.ts +27 -0
  16. package/lib/agent-shared.js +12 -12
  17. package/lib/agent.d.ts +353 -0
  18. package/lib/agent.js +536 -552
  19. package/lib/auto-preset.d.ts +110 -0
  20. package/lib/auto-preset.js +157 -141
  21. package/lib/bwrap.d.ts +90 -0
  22. package/lib/bwrap.js +18 -18
  23. package/lib/file-reference-wsl.d.ts +160 -0
  24. package/lib/file-reference-wsl.js +192 -173
  25. package/lib/fs-decisions.d.ts +107 -0
  26. package/lib/fs-decisions.js +64 -60
  27. package/lib/fs-routing.d.ts +182 -0
  28. package/lib/fs-routing.js +173 -116
  29. package/lib/fs-substrate.d.ts +258 -0
  30. package/lib/fs-substrate.js +248 -248
  31. package/lib/fs.d.ts +10 -0
  32. package/lib/fs.js +5 -0
  33. package/lib/fsio-agent.d.ts +307 -0
  34. package/lib/fsio-agent.js +414 -410
  35. package/lib/fsio-text.d.ts +115 -0
  36. package/lib/fsio-text.js +104 -84
  37. package/lib/git-route.d.ts +100 -0
  38. package/lib/git-route.js +46 -47
  39. package/lib/index.d.ts +1003 -0
  40. package/lib/index.js +1101 -1003
  41. package/lib/listing.d.ts +114 -0
  42. package/lib/listing.js +68 -55
  43. package/lib/paths.d.ts +182 -0
  44. package/lib/paths.js +134 -109
  45. package/lib/picker.d.ts +266 -0
  46. package/lib/picker.js +371 -351
  47. package/lib/ports.d.ts +44 -0
  48. package/lib/ports.js +39 -29
  49. package/lib/preset-choice.d.ts +33 -0
  50. package/lib/preset-choice.js +15 -7
  51. package/lib/probe-cache.d.ts +40 -0
  52. package/lib/probe-cache.js +35 -29
  53. package/lib/sandbox-core.d.ts +79 -0
  54. package/lib/sandbox-core.js +71 -88
  55. package/lib/sandbox.d.ts +48 -0
  56. package/lib/sandbox.js +4 -4
  57. package/lib/search-exec.d.ts +96 -0
  58. package/lib/search-exec.js +52 -79
  59. package/lib/search-route.d.ts +75 -0
  60. package/lib/search-route.js +30 -31
  61. package/lib/shell-env.d.ts +88 -0
  62. package/lib/shell-env.js +78 -74
  63. package/lib/shell.d.ts +10 -0
  64. package/lib/shell.js +5 -0
  65. package/lib/subprocess.d.ts +313 -0
  66. package/lib/subprocess.js +267 -262
  67. package/lib/terminal-activity.d.ts +98 -0
  68. package/lib/terminal-activity.js +29 -39
  69. package/lib/terminal-route.d.ts +27 -0
  70. package/lib/terminal-route.js +16 -8
  71. package/lib/watcher.d.ts +77 -0
  72. package/lib/watcher.js +120 -126
  73. package/lib/workspace-files-route.d.ts +132 -0
  74. package/lib/workspace-files-route.js +73 -81
  75. package/lib/workspace-files-wsl.d.ts +257 -0
  76. package/lib/workspace-files-wsl.js +311 -251
  77. package/lib/wsl.d.ts +266 -0
  78. package/lib/wsl.js +144 -155
  79. package/package.json +68 -20
package/lib/shell-env.js CHANGED
@@ -20,90 +20,94 @@
20
20
  * directory. A session whose workspace is inside a distro is exactly a session
21
21
  * whose shell calls run in that distro — so a host session sees none of these.
22
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
+ *
23
27
  * @module dsh-plugin-wsl/shell-env
24
28
  */
25
-
26
29
  import z from "@deepseek-ai/schemastery";
27
30
  import { isAnotherDistrosUnc, isWslUnc } from "./paths.js";
28
31
  import { defaultDistro, defaultShell, linuxHomePath } from "./wsl.js";
29
32
  import { sharedAgent } from "./agent-shared.js";
30
33
  import { listeningPorts } from "./ports.js";
31
-
32
34
  export const name = "wsl-shell-env";
33
-
34
35
  export const inject = ["shellEnv"];
35
-
36
36
  export const Config = z.object({
37
- /** Distro these facts describe; empty means this machine's default distro. */
38
- distro: z.string().default(""),
39
- /** How often the listening-port snapshot refreshes, in milliseconds. */
40
- portsRefreshMs: z.number().default(10 * 1000),
37
+ /** Distro these facts describe; empty means this machine's default distro. */
38
+ distro: z.string().default(""),
39
+ /** How often the listening-port snapshot refreshes, in milliseconds. */
40
+ portsRefreshMs: z.number().default(10 * 1000),
41
41
  });
42
-
42
+ /**
43
+ * Contribute the WSL environment's facts to the shell-env registry.
44
+ *
45
+ * @param ctx - the cordis context.
46
+ * @param config - the schema-validated plugin config.
47
+ */
43
48
  export async function apply(ctx, config = {}) {
44
- // `resolve` is called synchronously by the registry (`Object.entries` of its
45
- // result), so every fact has to be resolved up front, not per call.
46
- const configured = config.distro ?? "";
47
- const distro = configured.length > 0 ? configured : await defaultDistro();
48
- // undefined when both probes failed (a cold distro): the fact is then
49
- // simply absent, which beats presenting the "bash" placeholder as the
50
- // distro's login shell for this process's lifetime.
51
- const shell = await defaultShell(distro);
52
- // The consumer is a shell running INSIDE the distro, so the fact is the POSIX
53
- // path, not the host-side world path.
54
- const home = await linuxHomePath(distro);
55
-
56
- const variables = {
57
- DSH_WSL_DISTRO: {
58
- description: `Name of the WSL distro this workspace lives in (${distro}).`,
59
- },
60
- DSH_WSL_SHELL: {
61
- description: `Absolute path of the login shell that model shell calls run in inside ${distro}. Commands are executed by this shell, not by bash.`,
62
- },
63
- DSH_WSL_HOME: {
64
- description: `Home directory of the user model shell calls run as inside ${distro}.`,
65
- },
66
- DSH_WSL_PORTS: {
67
- description: `Ports with a listener inside ${distro} (comma-separated, ascending), as of the last snapshot — refreshed every ${Math.round(config.portsRefreshMs / 1000)}s, so a dev server started moments ago may not be listed yet. Empty means none were listening at the last scan.`,
68
- },
69
- };
70
-
71
- // The port snapshot refreshes on a timer and is served synchronously: the
72
- // registry's resolve is sync by contract, so live reads are impossible —
73
- // the timer bounds staleness instead (see lib/ports.js).
74
- let ports = [];
75
- const refreshPorts = async () => {
76
- ports = await listeningPorts(sharedAgent(distro), 8000);
77
- };
78
- void refreshPorts();
79
- const portsTimer = setInterval(() => void refreshPorts(), config.portsRefreshMs);
80
- portsTimer.unref?.();
81
-
82
- const dispose = ctx.shellEnv.register({
83
- name: "dsh-plugin-wsl-env",
84
- variables,
85
- resolve: (execution) => {
86
- const cwd = execution?.agent?.session?.header?.cwd;
87
- if (!isWslUnc(cwd)) return {};
88
- // The facts describe ONE distro. A workspace under another distro's UNC
89
- // is not that distro — the shell refuses its commands outright (the
90
- // cross-distro guard) — so the honest resolution is no facts at all,
91
- // not another distro's home and shell presented as this session's.
92
- if (isAnotherDistrosUnc(cwd, distro)) return {};
93
- const facts = {
94
- DSH_WSL_DISTRO: distro,
95
- DSH_WSL_HOME: home,
96
- DSH_WSL_PORTS: ports.join(","),
97
- };
98
- if (shell !== undefined) facts.DSH_WSL_SHELL = shell;
99
- return facts;
100
- },
101
- });
102
-
103
- // Disposing matters: a config hot-reload re-runs apply(), and the registry
104
- // rejects a second contributor claiming the same keys.
105
- ctx.effect(() => {
106
- clearInterval(portsTimer);
107
- dispose();
108
- });
49
+ // `resolve` is called synchronously by the registry (`Object.entries` of its
50
+ // result), so every fact has to be resolved up front, not per call.
51
+ const configured = config.distro ?? "";
52
+ const distro = configured.length > 0 ? configured : await defaultDistro();
53
+ // undefined when both probes failed (a cold distro): the fact is then
54
+ // simply absent, which beats presenting the "bash" placeholder as the
55
+ // distro's login shell for this process's lifetime.
56
+ const shell = await defaultShell(distro);
57
+ // The consumer is a shell running INSIDE the distro, so the fact is the POSIX
58
+ // path, not the host-side world path.
59
+ const home = await linuxHomePath(distro);
60
+ const variables = {
61
+ DSH_WSL_DISTRO: {
62
+ description: `Name of the WSL distro this workspace lives in (${distro}).`,
63
+ },
64
+ DSH_WSL_SHELL: {
65
+ description: `Absolute path of the login shell that model shell calls run in inside ${distro}. Commands are executed by this shell, not by bash.`,
66
+ },
67
+ DSH_WSL_HOME: {
68
+ description: `Home directory of the user model shell calls run as inside ${distro}.`,
69
+ },
70
+ DSH_WSL_PORTS: {
71
+ description: `Ports with a listener inside ${distro} (comma-separated, ascending), as of the last snapshot — refreshed every ${Math.round(config.portsRefreshMs / 1000)}s, so a dev server started moments ago may not be listed yet. Empty means none were listening at the last scan.`,
72
+ },
73
+ };
74
+ // The port snapshot refreshes on a timer and is served synchronously: the
75
+ // registry's resolve is sync by contract, so live reads are impossible —
76
+ // the timer bounds staleness instead (see lib/ports.js).
77
+ let ports = [];
78
+ const refreshPorts = async () => {
79
+ ports = await listeningPorts(sharedAgent(distro), 8000);
80
+ };
81
+ void refreshPorts();
82
+ const portsTimer = setInterval(() => void refreshPorts(), config.portsRefreshMs);
83
+ portsTimer.unref?.();
84
+ const dispose = ctx.shellEnv.register({
85
+ name: "dsh-plugin-wsl-env",
86
+ variables,
87
+ resolve: (execution) => {
88
+ const cwd = execution?.agent?.session?.header?.cwd;
89
+ if (cwd === undefined || !isWslUnc(cwd))
90
+ return {};
91
+ // The facts describe ONE distro. A workspace under another distro's UNC
92
+ // is not that distro — the shell refuses its commands outright (the
93
+ // cross-distro guard) — so the honest resolution is no facts at all,
94
+ // not another distro's home and shell presented as this session's.
95
+ if (isAnotherDistrosUnc(cwd, distro))
96
+ return {};
97
+ const facts = {
98
+ DSH_WSL_DISTRO: distro,
99
+ DSH_WSL_HOME: home,
100
+ DSH_WSL_PORTS: ports.join(","),
101
+ };
102
+ if (shell !== undefined)
103
+ facts.DSH_WSL_SHELL = shell;
104
+ return facts;
105
+ },
106
+ });
107
+ // Disposing matters: a config hot-reload re-runs apply(), and the registry
108
+ // rejects a second contributor claiming the same keys.
109
+ ctx.effect(() => () => {
110
+ clearInterval(portsTimer);
111
+ dispose();
112
+ });
109
113
  }
package/lib/shell.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `ctx.shell` entrypoint — load as `dsh-plugin-wsl` in a profile patch.
3
+ *
4
+ * This is a TypeScript source built to `lib/shell.js`; edit THIS file and run
5
+ * `pnpm run build` — the artifact under `lib/` is generated, and `pnpm test`
6
+ * fails when it drifts.
7
+ *
8
+ * @module dsh-plugin-wsl/shell
9
+ */
10
+ export { WslShellExecutor as default, WslShellExecutor } from "./index.js";
package/lib/shell.js CHANGED
@@ -1,5 +1,10 @@
1
1
  /**
2
2
  * `ctx.shell` entrypoint — load as `dsh-plugin-wsl` in a profile patch.
3
+ *
4
+ * This is a TypeScript source built to `lib/shell.js`; edit THIS file and run
5
+ * `pnpm run build` — the artifact under `lib/` is generated, and `pnpm test`
6
+ * fails when it drifts.
7
+ *
3
8
  * @module dsh-plugin-wsl/shell
4
9
  */
5
10
  export { WslShellExecutor as default, WslShellExecutor } from "./index.js";
@@ -0,0 +1,313 @@
1
+ /**
2
+ * `ctx.subprocess` entrypoint — the WSL execution world behind the GUI's
3
+ * terminal window.
4
+ *
5
+ * ## Why this seam, and not `ctx.terminals`
6
+ *
7
+ * `ctx.terminals` (`@deepseek-ai/dsh-terminal` + `dsh-terminal-bash`) is the
8
+ * *model-facing* persistent-terminal service. The right-hand terminal window is
9
+ * a different subsystem: its host half is
10
+ * `@deepseek-ai/dsh-api-terminal-controller`, mounted once by the Web bundle,
11
+ * and it resolves the execution environment of the Session it serves with
12
+ *
13
+ * execution(agent) {
14
+ * const subprocess = agent.ctx.get("subprocess");
15
+ * const sandboxPolicy = agent.ctx.get("sandboxPolicy");
16
+ * ...
17
+ * }
18
+ *
19
+ * ## Why a preset-scoped provider cannot serve it
20
+ *
21
+ * The obvious design — mount this provider in the `wsl` preset's isolate realm
22
+ * next to `shell` and `fs` — does not work, and the reason is worth recording
23
+ * because it is invisible from the profile YAML:
24
+ *
25
+ * - A preset's isolate realms are created by `dsh-agent-preset-registry`
26
+ * under the registry's **own** context:
27
+ * `createScope(this.owner, key)` → `mountPreset(scope.ctx…)`.
28
+ * - An Agent's context is a **parallel branch** created by the agent loop:
29
+ * `this.scope = createScope(loopCtx, this); this.ctx = this.scope.ctx`,
30
+ * where `loopCtx` is the root-mounted `ctx.agents` service context.
31
+ * `createScope` only wraps a Cordis plugin and tags a scope key; it neither
32
+ * touches `Context.isolate` nor passes through a loader entry, so the
33
+ * agent's isolate map still resolves every service to the root realm.
34
+ *
35
+ * So `agent.ctx.get("subprocess")` always finds the **composition-level**
36
+ * provider, whatever the agent's preset isolates. The shipped registry provides
37
+ * `agentPresets.serviceFor(agent, name)` for exactly this gap, and the terminal
38
+ * controller does not use it. (`shell` and `fs` are unaffected: the model-facing
39
+ * tools live *inside* the preset tree, so their own contexts do resolve the
40
+ * realm.)
41
+ *
42
+ * ## What this provider therefore changes
43
+ *
44
+ * It replaces the shipped `subprocess` row at the composition level and
45
+ * overrides two methods. `spawnTerminal` rewrites a `wsl.exe` launch into a
46
+ * launch that actually names the distro and the Session workspace's Linux
47
+ * directory. `spawn` rewrites one further launch — the file-search tool's
48
+ * packaged ripgrep binary, which a distro workspace's UNC working directory
49
+ * names outright — into the distro's own rg: agent-first, one round trip on
50
+ * the warm resident, with the one-shot `wsl.exe --exec` handle as the
51
+ * documented fallback when the agent is out (`lib/search-route.js` holds the
52
+ * decision, `lib/search-exec.js` the handle). Every other request —
53
+ * including every other `spawn()`, which the command path uses for
54
+ * `wsl.exe -lc <command>` — reaches the shipped implementation untouched.
55
+ *
56
+ * The interpreter, the PTY, output handling and process-tree teardown stay the
57
+ * shipped implementation's, so the blast radius of replacing a root service
58
+ * stays at the two things that were wrong: the directory and distro a terminal
59
+ * starts in, and the filesystem a search walks.
60
+ *
61
+ * ## What the operator must pair it with
62
+ *
63
+ * `terminalEnvironment()` and `resolveExecutable()` are also resolved from the
64
+ * root realm, and neither receives a Session or a directory, so this provider
65
+ * deliberately leaves them alone: rewriting them would change executable lookup
66
+ * for every other root consumer (the pwsh executor, the LSP host, the file
67
+ * search) to make the *shell menu* of one window prettier. The profile instead
68
+ * configures the terminal controller with the single shell profile that matters:
69
+ * `shell: { path: wsl.exe, name: WSL }` and `shellCandidates: []`.
70
+ *
71
+ * Consequence worth stating plainly: the terminal is a property of the
72
+ * composition, not of the Session. A Session whose workspace is a Windows folder
73
+ * gets the same terminal, with that folder mounted at `/mnt/<drive>/…`.
74
+ *
75
+ * This is a TypeScript source built to `lib/subprocess.js`; edit THIS file and
76
+ * run `pnpm run build` — the artifact under `lib/` is generated, and `pnpm test`
77
+ * fails when it drifts.
78
+ *
79
+ * @module dsh-plugin-wsl/subprocess
80
+ */
81
+ import z from "@deepseek-ai/schemastery";
82
+ import { LocalSubprocessRuntime } from "@deepseek-ai/dsh-subprocess-local";
83
+ import type { Context } from "@deepseek-ai/cordis";
84
+ import type { SubprocessHandle, SubprocessSpawnSpec, SubprocessTerminalHandle, SubprocessTerminalSpawnSpec } from "@deepseek-ai/dsh-subprocess";
85
+ import type { SubprocessTerminalActivity } from "./terminal-activity.js";
86
+ /**
87
+ * The static Config schema's validated output. Every defaulted field is
88
+ * present after validation; `hostCwd` is the volatile accessor the schema
89
+ * builds.
90
+ */
91
+ export interface WslSubprocessConfig {
92
+ /** Distro name; empty means WSL's own default distro. */
93
+ distro: string;
94
+ /** Path to `wsl.exe`. */
95
+ wslPath: string;
96
+ /** A shell to pin inside the distro, as an absolute Linux path. */
97
+ shell: string;
98
+ /** Login-shell semantics for a pinned shell. */
99
+ loginShell: boolean;
100
+ /** Fallback directory when a launch names none. */
101
+ cwd: string;
102
+ /** Windows directory the `wsl.exe` process itself starts in. */
103
+ hostCwd: {
104
+ get: () => string | undefined;
105
+ };
106
+ /** Extra env names to forward into the distro through WSLENV. */
107
+ forwardEnv: string[];
108
+ /** Whether a session on a Windows folder gets a host shell. */
109
+ hostSessions: boolean;
110
+ /** Whether the controller may reclaim an idle distro terminal. */
111
+ terminalIdleReclaim: boolean;
112
+ }
113
+ /**
114
+ * Subprocess provider that serves a WSL distro as the composition's terminal
115
+ * execution world.
116
+ *
117
+ * Mount it at the profile root in place of the shipped local provider (the
118
+ * profile patch disables the `subprocess` row and inserts this one). It is a
119
+ * subclass rather than a wrapper, so every method it does not override is
120
+ * literally the shipped implementation.
121
+ */
122
+ export declare class WslSubprocessRuntime extends LocalSubprocessRuntime {
123
+ static Config: z<Schemastery.ObjectS<NoInfer<{
124
+ /** Distro name; empty means WSL's own default distro. */
125
+ distro: z<string, string, "defined">;
126
+ /** Path to `wsl.exe`; overridable for a non-standard install. */
127
+ wslPath: z<string, string, "defined">;
128
+ /**
129
+ * A shell to pin inside the distro, as an absolute Linux path. Empty — the
130
+ * default — leaves the choice to `wsl.exe`, which starts the distro user's
131
+ * login shell exactly as an interactive `wsl.exe` does.
132
+ */
133
+ shell: z<string, string, "defined">;
134
+ /**
135
+ * Login-shell semantics for a pinned shell. Ignored when no shell is pinned,
136
+ * because then the login shell is the shell.
137
+ */
138
+ loginShell: z<boolean, boolean, "defined">;
139
+ /** Fallback directory when a launch names none; accepts any coordinate system. */
140
+ cwd: z<string, string, "defined">;
141
+ /** Windows directory the `wsl.exe` process itself starts in. */
142
+ hostCwd: z<string, string, "volatile">;
143
+ /** Extra env names to forward into the distro through WSLENV. */
144
+ forwardEnv: z<string[], string[], "defined">;
145
+ /**
146
+ * Give a session opened on a WINDOWS folder a host shell instead of the
147
+ * distro's shell started at `/mnt/<drive>/…`. WSL-folder sessions always
148
+ * get the distro shell — that is this provider's purpose. `false` restores
149
+ * the composition-owned terminal for every session.
150
+ */
151
+ hostSessions: z<boolean, boolean, "defined">;
152
+ /**
153
+ * Let the controller reclaim an idle distro terminal. The terminal is
154
+ * marked at launch (`DSH_TERMINAL_ID` through the existing `WSLENV`
155
+ * forwarding) and its handle's `inspectActivity` is replaced with a
156
+ * distro-side observation: the resident agent scans `/proc` for the
157
+ * marker — a shell with live children is busy, a shell without children
158
+ * is idle. The shipped observation can only answer `unknown` for a distro
159
+ * terminal, and `unknown` never accumulates idle, which is why these
160
+ * terminals had to be closed by hand. When the agent is out the
161
+ * observation answers `unknown` (no reclaim) — a probe that cannot answer
162
+ * must not authorize a close. `false` restores that posture.
163
+ */
164
+ terminalIdleReclaim: z<boolean, boolean, "defined">;
165
+ }>>, Schemastery.ObjectT<NoInfer<{
166
+ /** Distro name; empty means WSL's own default distro. */
167
+ distro: z<string, string, "defined">;
168
+ /** Path to `wsl.exe`; overridable for a non-standard install. */
169
+ wslPath: z<string, string, "defined">;
170
+ /**
171
+ * A shell to pin inside the distro, as an absolute Linux path. Empty — the
172
+ * default — leaves the choice to `wsl.exe`, which starts the distro user's
173
+ * login shell exactly as an interactive `wsl.exe` does.
174
+ */
175
+ shell: z<string, string, "defined">;
176
+ /**
177
+ * Login-shell semantics for a pinned shell. Ignored when no shell is pinned,
178
+ * because then the login shell is the shell.
179
+ */
180
+ loginShell: z<boolean, boolean, "defined">;
181
+ /** Fallback directory when a launch names none; accepts any coordinate system. */
182
+ cwd: z<string, string, "defined">;
183
+ /** Windows directory the `wsl.exe` process itself starts in. */
184
+ hostCwd: z<string, string, "volatile">;
185
+ /** Extra env names to forward into the distro through WSLENV. */
186
+ forwardEnv: z<string[], string[], "defined">;
187
+ /**
188
+ * Give a session opened on a WINDOWS folder a host shell instead of the
189
+ * distro's shell started at `/mnt/<drive>/…`. WSL-folder sessions always
190
+ * get the distro shell — that is this provider's purpose. `false` restores
191
+ * the composition-owned terminal for every session.
192
+ */
193
+ hostSessions: z<boolean, boolean, "defined">;
194
+ /**
195
+ * Let the controller reclaim an idle distro terminal. The terminal is
196
+ * marked at launch (`DSH_TERMINAL_ID` through the existing `WSLENV`
197
+ * forwarding) and its handle's `inspectActivity` is replaced with a
198
+ * distro-side observation: the resident agent scans `/proc` for the
199
+ * marker — a shell with live children is busy, a shell without children
200
+ * is idle. The shipped observation can only answer `unknown` for a distro
201
+ * terminal, and `unknown` never accumulates idle, which is why these
202
+ * terminals had to be closed by hand. When the agent is out the
203
+ * observation answers `unknown` (no reclaim) — a probe that cannot answer
204
+ * must not authorize a close. `false` restores that posture.
205
+ */
206
+ terminalIdleReclaim: z<boolean, boolean, "defined">;
207
+ }>>, "plain">;
208
+ /** The schema-validated config. */
209
+ config: WslSubprocessConfig;
210
+ /** Cached default distro, resolved once from `wsl.exe -l -q`. */
211
+ resolvedDistro: string;
212
+ /**
213
+ * @param ctx - the cordis context.
214
+ * @param config - the schema-validated config.
215
+ */
216
+ constructor(ctx: Context, config: WslSubprocessConfig);
217
+ /**
218
+ * The distro a launch uses when the request's directory does not name one,
219
+ * resolving WSL's default on first use.
220
+ *
221
+ * @returns the distro name.
222
+ */
223
+ distro(): Promise<string>;
224
+ /** Windows directory the `wsl.exe` process is started from. */
225
+ hostCwd(): string;
226
+ /**
227
+ * The distro and Linux directory one terminal request names.
228
+ *
229
+ * The Session workspace the terminal controller passes in is the coordinate
230
+ * the harness already speaks, and it is also the strongest evidence of the
231
+ * intended distro: a UNC path names one outright. Anything else — a Windows
232
+ * drive path or a directory-less request — may legitimately be served by the
233
+ * configured distro.
234
+ *
235
+ * @param cwd - the request's working directory, in any coordinate system.
236
+ * @returns the distro name and its absolute Linux directory.
237
+ */
238
+ terminalTarget(cwd: string | undefined): Promise<{
239
+ distro: string;
240
+ linuxCwd: string;
241
+ }>;
242
+ /**
243
+ * Open one interactive shell inside the distro over the host's PTY.
244
+ *
245
+ * Only a `wsl.exe` launch is rewritten. Everything else — a shipped host
246
+ * shell, the PTY backend's `bash -i`, any other consumer's terminal — is the
247
+ * shipped implementation's, which is what keeps replacing a root provider
248
+ * safe.
249
+ *
250
+ * The request's remaining arguments are intentionally not forwarded: the
251
+ * controller's shell profile carries only the program, and a second set of
252
+ * `wsl.exe` arguments would fight the distro and `--cd` this method derives
253
+ * from the Session.
254
+ *
255
+ * @param spec - the terminal request from the PTY consumer.
256
+ * @returns the live terminal handle.
257
+ */
258
+ spawnTerminal(spec: SubprocessTerminalSpawnSpec): Promise<SubprocessTerminalHandle>;
259
+ /**
260
+ * The replacement `inspectActivity` for one distro terminal: ask the
261
+ * resident agent whether the marked shell is at its prompt.
262
+ *
263
+ * The revision increments only when the reported state changes, so the
264
+ * controller's retention accumulates idle across consecutive `idle`
265
+ * observations and restarts the clock on every transition. Any probe
266
+ * failure — the agent out, the distro stopped, a script error — answers
267
+ * `unknown`, which the retention reads as "not idle": the probe that cannot
268
+ * answer never authorizes a close. The first unexpected (non-availability)
269
+ * failure is logged once, so a broken probe is diagnosable without
270
+ * spamming the log every poll.
271
+ *
272
+ * @param distro - the distro the terminal lives in.
273
+ * @param terminalId - the launch's `DSH_TERMINAL_ID` mark.
274
+ * @returns the seam's activity observation.
275
+ */
276
+ distroActivity(distro: string, terminalId: string): () => Promise<SubprocessTerminalActivity>;
277
+ /**
278
+ * Open one host-side process, rewriting two kinds of launches into the
279
+ * distro: the file-search tool's ripgrep (agent-first, one-shot fallback)
280
+ * and git snapshot commands (one-shot only — snapshot operations are not
281
+ * latency-sensitive, and their stdin relay and collected streams are the
282
+ * shipped handle's). Both decisions are pure coordinate functions
283
+ * (`search-route.js`, `git-route.js`); a spawn neither names (any other
284
+ * program, any drive or relative directory) reaches the shipped
285
+ * implementation verbatim.
286
+ *
287
+ * @param spec - the spawn request.
288
+ * @returns the live search handle.
289
+ */
290
+ spawn(spec: SubprocessSpawnSpec): SubprocessHandle;
291
+ /**
292
+ * The environment one `wsl.exe` launch forwards: the safe list plus every
293
+ * managed `DSH_*` name, admitted through `WSLENV` and never `PATH` — see the
294
+ * WSLENV note in the recipes.
295
+ *
296
+ * @param spec - the launch request whose env is forwarded.
297
+ * @returns the environment the host launch carries.
298
+ */
299
+ forwardedEnv(spec: {
300
+ env?: Record<string, string | undefined> | undefined;
301
+ }): Record<string, string> | Record<string, string | undefined> | undefined;
302
+ /**
303
+ * Whether a terminal request is a `wsl.exe` launch.
304
+ *
305
+ * A bare `argv[0]` is judged by its own name, so the shipped program name and
306
+ * an operator-pinned absolute path both match.
307
+ *
308
+ * @param argv - the request's program and arguments.
309
+ * @returns true when this provider owns the launch.
310
+ */
311
+ launchesDistro(argv?: readonly string[]): boolean;
312
+ }
313
+ export default WslSubprocessRuntime;