dsh-wsl-workspace 0.2.2 → 0.3.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/lib/shell.js CHANGED
@@ -1,382 +1,383 @@
1
- import { c as windowsToMntPath, i as joinUnc, n as isValidWslUsername, r as isWindowsPathShaped, s as parseWslUnc } from "./paths-DBaSmi7x.js";
2
- import { a as getWorkspaceUsername, n as defaultDistroSync } from "./wsl-GjkUifnx.js";
3
- import z from "@deepseek-ai/schemastery";
4
- import { ShellExecutor } from "@deepseek-ai/dsh-shell";
5
- import { MAX_TIMER_DELAY_MS, clampTimeout, deadline, timeoutOf } from "@deepseek-ai/dsh-timeout";
6
- //#region \0@oxc-project+runtime@0.135.0/helpers/esm/usingCtx.js
7
- function _usingCtx() {
8
- var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
9
- var n = Error();
10
- return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
11
- }, e = {}, n = [];
12
- function using(r, e) {
13
- if (null != e) {
14
- if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
15
- if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
16
- if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
17
- if ("function" != typeof o) throw new TypeError("Object is not disposable.");
18
- t && (o = function o() {
19
- try {
20
- t.call(e);
21
- } catch (r) {
22
- return Promise.reject(r);
23
- }
24
- }), n.push({
25
- v: e,
26
- d: o,
27
- a: r
28
- });
29
- } else r && n.push({
30
- d: e,
31
- a: r
32
- });
33
- return e;
34
- }
35
- return {
36
- e,
37
- u: using.bind(null, !1),
38
- a: using.bind(null, !0),
39
- d: function d() {
40
- var o, t = this.e, s = 0;
41
- function next() {
42
- for (; o = n.pop();) try {
43
- if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
44
- if (o.d) {
45
- var r = o.d.call(o.v);
46
- if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
47
- } else s |= 1;
48
- } catch (r) {
49
- return err(r);
50
- }
51
- if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
52
- if (t !== e) throw t;
53
- }
54
- function err(n) {
55
- return t = t !== e ? new r(n, t) : n, next();
56
- }
57
- return next();
58
- }
59
- };
60
- }
61
- //#endregion
62
- //#region src/shell.ts
63
- /**
64
- * Model-friendly environment overrides (same set `dsh-bash-local` hardcodes):
65
- * disable colors, pagers, and interactive terminal features that would garble
66
- * tool output. These values cross into the Linux process through WSLENV.
67
- */
68
- const ENV_OVERRIDES = {
69
- NO_COLOR: "1",
70
- TERM: "dumb",
71
- PAGER: "cat",
72
- GIT_PAGER: "cat"
73
- };
74
- /** Default SIGTERM→SIGKILL grace period (matches `dsh-bash-local`). */
75
- const DEFAULT_GRACE_MS = 3e3;
76
- /** Default per-stream spill cap (matches `dsh-bash-local`). */
77
- const DEFAULT_MAX_SPILL_BYTES = 64 * 1024 * 1024;
78
- /** Project a settled collect-mode reader into the final CollectedOutput shape. */
79
- function finalOutput(reader) {
80
- const read = reader.readFrom(0);
81
- return {
82
- text: read.text,
83
- truncated: read.lossy,
84
- ...read.spillPath !== void 0 ? { spillPath: read.spillPath } : {}
85
- };
86
- }
87
- function assertPositiveFinite(name, value) {
88
- if (!Number.isFinite(value) || value <= 0) throw new Error(`wsl-shell: ${name} must be a positive finite number`);
89
- }
90
- /**
91
- * Reject a resolved configuration this executor could not run with, so a
92
- * stored value is refused where it is written instead of failing at the next
93
- * command.
94
- * @param config - the schema-validated configuration.
95
- * @throws Error naming the field that cannot be used.
96
- */
97
- function assertServiceableWslConfig(config) {
98
- const resolved = config;
99
- assertPositiveFinite("timeoutMs", resolved.timeoutMs);
100
- assertPositiveFinite("maxTimeoutMs", resolved.maxTimeoutMs);
101
- assertPositiveFinite("maxOutputBytes", resolved.maxOutputBytes);
102
- assertPositiveFinite("maxSpillBytes", resolved.maxSpillBytes);
103
- assertPositiveFinite("graceMs", resolved.graceMs);
104
- if (resolved.graceMs > MAX_TIMER_DELAY_MS) throw new Error(`wsl-shell: graceMs must be no greater than ${MAX_TIMER_DELAY_MS}`);
105
- if (resolved.distro !== void 0 && resolved.distro.trim() === "") throw new Error("wsl-shell: distro must be a non-empty distribution name");
106
- if (resolved.username !== void 0 && resolved.username !== "" && !isValidWslUsername(resolved.username)) throw new Error("wsl-shell: username must match the Linux username pattern [A-Za-z_][A-Za-z0-9_.-]*");
107
- }
108
- /**
109
- * WSL bash executor over the LOCAL subprocess service: `wsl.exe` is a Windows
110
- * executable, so the Windows-side spawn, bounded output, spill files, and
111
- * process-group termination are the local subprocess seam's mechanics; this
112
- * executor supplies the Linux-world argv, cwd translation, and WSLENV.
113
- */
114
- var WslShellExecutor = class WslShellExecutor extends ShellExecutor {
115
- static inject = ["subprocess"];
116
- static Config = z.object({
117
- cwd: z.string(),
118
- distro: z.string(),
119
- username: z.string(),
120
- wslPath: z.string().default("wsl.exe"),
121
- loginShell: z.boolean().default(true),
122
- timeoutMs: z.number().default(12e4),
123
- maxTimeoutMs: z.number().default(6e5),
124
- maxOutputBytes: z.number().default(64e3),
125
- maxSpillBytes: z.number().default(DEFAULT_MAX_SPILL_BYTES),
126
- graceMs: z.number().default(DEFAULT_GRACE_MS)
127
- });
128
- resolved;
129
- /** Validated config (schemastery applied the defaults before construction). */
130
- get config() {
131
- return this.resolved;
132
- }
133
- constructor(ctx, config) {
134
- super(ctx);
135
- const entry = config;
136
- assertServiceableWslConfig(entry);
137
- this.resolved = entry;
138
- }
139
- /**
140
- * Resolve a request into a fully-specified spec: fill `workdir` from
141
- * `config.cwd`, and `timeoutMs` from `config.timeoutMs`, capped at
142
- * `config.maxTimeoutMs`. The tool layer calls this before
143
- * {@link run}/{@link start}, so those methods receive explicit values.
144
- */
145
- resolve(request) {
146
- const timeoutMs = clampTimeout(request.timeoutMs, this.config.timeoutMs, this.config.maxTimeoutMs, "wsl-shell: request.timeoutMs");
147
- const stdoutMaxBytes = request.stdoutMaxBytes ?? this.config.maxOutputBytes;
148
- assertPositiveFinite("request.stdoutMaxBytes", stdoutMaxBytes);
149
- return {
150
- command: request.command,
151
- workdir: request.workdir ?? this.config.cwd ?? process.cwd(),
152
- timeoutMs,
153
- stdoutMaxBytes,
154
- ...request.signal ? { signal: request.signal } : {},
155
- ...request.stdin !== void 0 ? { stdin: request.stdin } : {},
156
- ...request.env !== void 0 ? { env: request.env } : {},
157
- ...request.dshEnv !== void 0 ? { dshEnv: request.dshEnv } : {},
158
- sandboxPolicy: request.sandboxPolicy
159
- };
160
- }
161
- /**
162
- * Translate a resolved spec into the Linux execution plan. Fails loud on a
163
- * workdir that names neither the WSL world (UNC or Linux path) nor a
164
- * Windows drive path (reached through `/mnt/<drive>`).
165
- * @param spec - the resolved execution spec.
166
- * @returns the translated plan, including the complete argv.
167
- */
168
- plan(spec) {
169
- const workdir = spec.workdir;
170
- let distro;
171
- let linuxCwd;
172
- let windowsCwd;
173
- let username;
174
- const unc = parseWslUnc(workdir);
175
- if (unc !== null) {
176
- distro = unc.distro;
177
- linuxCwd = unc.linuxPath;
178
- windowsCwd = process.env.SystemRoot ?? process.cwd();
179
- username = this.resolveUser(spec, joinUnc(unc.distro, unc.linuxPath));
180
- } else if (workdir.startsWith("/")) {
181
- distro = this.resolveDistro(spec);
182
- linuxCwd = workdir;
183
- windowsCwd = process.cwd();
184
- username = this.resolveUser(spec, void 0);
185
- } else {
186
- const mnt = windowsToMntPath(workdir);
187
- if (mnt === null) throw new Error(`wsl-shell: workdir "${workdir}" is not in the WSL execution world`);
188
- distro = this.resolveDistro(spec);
189
- linuxCwd = mnt;
190
- windowsCwd = workdir;
191
- username = this.resolveUser(spec, void 0);
192
- }
193
- const env = this.withWslEnv(spec);
194
- const argv = [
195
- this.config.wslPath,
196
- "-d",
197
- distro,
198
- ...username !== void 0 && username !== "" ? ["-u", username] : [],
199
- "--cd",
200
- linuxCwd,
201
- "-e",
202
- "bash",
203
- this.config.loginShell ? "-lc" : "-c",
204
- spec.command
205
- ];
206
- return {
207
- distro,
208
- linuxCwd,
209
- windowsCwd,
210
- env,
211
- argv
212
- };
213
- }
214
- /**
215
- * Resolve the distribution for a workdir that carries none. The chain:
216
- * the calling session's distribution (`DSH_WSL_DISTRO`, contributed by the
217
- * host half from the session's UNC workspace cwd — the common case for a
218
- * model passing a Linux `workdir`), then the configured `distro`, then the
219
- * host's default distribution (cached registry read) as a last resort for
220
- * plugin-driven calls with no session. Fails loud when every source is
221
- * absent rather than guessing a distro the path does not belong to.
222
- * @param spec - the resolved execution spec (its dshEnv carries the session fact).
223
- * @returns the distribution name.
224
- */
225
- resolveDistro(spec) {
226
- const fromEnv = spec.dshEnv?.DSH_WSL_DISTRO;
227
- if (fromEnv !== void 0 && fromEnv !== "") return fromEnv;
228
- const configured = this.config.distro;
229
- if (configured !== void 0 && configured !== "") return configured;
230
- const fallback = defaultDistroSync();
231
- if (fallback !== void 0) return fallback;
232
- throw new Error("wsl-shell: Linux workdir carries no distribution; no session DSH_WSL_DISTRO, distro config, or default distribution is available");
233
- }
234
- /**
235
- * Resolve the Linux user bash runs as. The chain: the calling session's
236
- * workspace user (`DSH_WSL_USER`, contributed by the host half), then the
237
- * workspace's stored username when the workdir is a UNC path, then the
238
- * configured `username`. Absent everywhere, the distribution's default
239
- * user runs. Invalid values are skipped (they were validated on write;
240
- * the guard is defense in depth).
241
- * @param spec - the resolved execution spec (its dshEnv carries the session fact).
242
- * @param uncKey - canonical UNC key of the workdir when it is a UNC path.
243
- * @returns the username, or undefined for the distro default user.
244
- */
245
- resolveUser(spec, uncKey) {
246
- const candidates = [
247
- spec.dshEnv?.DSH_WSL_USER,
248
- uncKey === void 0 ? void 0 : getWorkspaceUsername(uncKey),
249
- this.config.username
250
- ];
251
- for (const candidate of candidates) if (candidate !== void 0 && candidate !== "" && isValidWslUsername(candidate)) return candidate;
252
- }
253
- /**
254
- * Merge the caller env layers and inject `WSLENV` so the Windows-side
255
- * values reach the Linux process. Windows-path-shaped values get the `/p`
256
- * translation flag (they become `/mnt/<drive>/…` inside WSL); the ambient
257
- * `WSLENV` value is preserved and extended.
258
- * @param spec - the resolved execution spec.
259
- * @returns the explicit environment map for the spawn.
260
- */
261
- withWslEnv(spec) {
262
- const env = {
263
- ...ENV_OVERRIDES,
264
- ...spec.env,
265
- ...spec.dshEnv
266
- };
267
- const flags = [];
268
- for (const [key, value] of Object.entries(env)) {
269
- if (key.toUpperCase() === "WSLENV") continue;
270
- flags.push(isWindowsPathShaped(value) ? `${key}/p` : key);
271
- }
272
- env.WSLENV = [process.env.WSLENV, flags.join(":")].filter((part) => part !== void 0 && part !== "").join(":");
273
- return env;
274
- }
275
- /** Map a plan onto a fully-specified subprocess spawn. */
276
- spawnSpec(plan, spec, stdoutMaxBytes, signal) {
277
- const collect = (maxBytes) => ({
278
- maxBytes,
279
- spill: { maxBytes: this.config.maxSpillBytes }
280
- });
281
- return {
282
- argv: plan.argv,
283
- cwd: plan.windowsCwd,
284
- stdio: {
285
- stdin: spec.stdin !== void 0 ? { data: spec.stdin } : "ignore",
286
- stdout: collect(stdoutMaxBytes),
287
- stderr: collect(this.config.maxOutputBytes)
288
- },
289
- graceMs: this.config.graceMs,
290
- signal,
291
- env: plan.env
292
- };
293
- }
294
- /** The collect-mode readers this executor requested (present by construction). */
295
- static collected(handle) {
296
- const { stdout, stderr } = handle.collected;
297
- /* v8 ignore start -- collect dispositions expose both readers by the seam contract; defensive. */
298
- if (stdout === void 0 || stderr === void 0) throw new Error("wsl-shell: subprocess implementation dropped a requested collect stream");
299
- /* v8 ignore stop */
300
- return {
301
- stdout,
302
- stderr
303
- };
304
- }
305
- /** Run one command in the foreground. */
306
- async run(spec) {
307
- try {
308
- var _usingCtx$1 = _usingCtx();
309
- const plan = this.plan(spec);
310
- const d = _usingCtx$1.u(deadline(spec.signal, spec.timeoutMs, "WSL_BASH_TIMEOUT"));
311
- const handle = this.ctx.subprocess.spawn(this.spawnSpec(plan, spec, spec.stdoutMaxBytes, d.signal));
312
- const outcome = await handle.done;
313
- const collected = WslShellExecutor.collected(handle);
314
- const timedOut = timeoutOf(d.signal, "WSL_BASH_TIMEOUT") !== void 0;
315
- const aborted = d.signal.aborted && !timedOut;
316
- return {
317
- ...outcome,
318
- timedOut,
319
- aborted,
320
- timeoutMs: spec.timeoutMs,
321
- stdout: finalOutput(collected.stdout),
322
- stderr: finalOutput(collected.stderr)
323
- };
324
- } catch (_) {
325
- _usingCtx$1.e = _;
326
- } finally {
327
- _usingCtx$1.d();
328
- }
329
- }
330
- /** Start one command in the background and return its live handle. */
331
- start(spec) {
332
- const plan = this.plan(spec);
333
- const running = this.ctx.subprocess.spawn(this.spawnSpec(plan, spec, this.config.maxOutputBytes, spec.signal));
334
- const collected = WslShellExecutor.collected(running);
335
- let spawnFailureNote;
336
- const consumeSpawnFailure = () => {
337
- const note = spawnFailureNote ?? "";
338
- spawnFailureNote = void 0;
339
- return note;
340
- };
341
- let stdoutOffset = 0;
342
- let stderrOffset = 0;
343
- const proc = {
344
- status: "running",
345
- exitCode: null,
346
- signal: null,
347
- done: running.done.then((outcome) => {
348
- if (proc.status === "running") proc.status = spec.signal?.aborted === true || outcome.signal !== null ? "killed" : "completed";
349
- proc.exitCode = outcome.exitCode;
350
- proc.signal = outcome.signal;
351
- }, (error) => {
352
- proc.status = "killed";
353
- spawnFailureNote = `spawn failed: ${String(error)}`;
354
- }),
355
- readOutput: () => {
356
- const out = collected.stdout.readFrom(stdoutOffset);
357
- const err = collected.stderr.readFrom(stderrOffset);
358
- stdoutOffset = out.nextOffset;
359
- stderrOffset = err.nextOffset;
360
- const errText = err.text.length > 0 ? err.text : consumeSpawnFailure();
361
- const separator = out.text.length > 0 && !out.text.endsWith("\n") ? "\n" : "";
362
- return {
363
- delta: out.text + (errText.length > 0 ? `${separator}[stderr]\n${errText}` : ""),
364
- lossy: out.lossy || err.lossy,
365
- ...out.spillPath !== void 0 ? { stdoutSpillPath: out.spillPath } : {},
366
- ...err.spillPath !== void 0 ? { stderrSpillPath: err.spillPath } : {}
367
- };
368
- },
369
- kill: () => {
370
- if (proc.status !== "running") return false;
371
- proc.status = "killed";
372
- running.terminate();
373
- return true;
374
- }
375
- };
376
- return proc;
377
- }
378
- };
379
- //#endregion
380
- export { WslShellExecutor, WslShellExecutor as default, assertServiceableWslConfig };
381
-
1
+ import { c as joinUnc, d as parseWslUnc, f as windowsToMntPath, n as defaultDistroSync, o as isValidWslUsername, s as isWindowsPathShaped } from "./wsl-C5_mxGPM.js";
2
+ import { r as getWorkspaceUsername } from "./wsl-credentials-BI4v5TNZ.js";
3
+ import z from "@deepseek-ai/schemastery";
4
+ import { ShellExecutor } from "@deepseek-ai/dsh-shell";
5
+ import { MAX_TIMER_DELAY_MS, clampTimeout, deadline, timeoutOf } from "@deepseek-ai/dsh-timeout";
6
+ //#region \0@oxc-project+runtime@0.135.0/helpers/esm/usingCtx.js
7
+ function _usingCtx() {
8
+ var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
9
+ var n = Error();
10
+ return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
11
+ }, e = {}, n = [];
12
+ function using(r, e) {
13
+ if (null != e) {
14
+ if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
15
+ if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
16
+ if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
17
+ if ("function" != typeof o) throw new TypeError("Object is not disposable.");
18
+ t && (o = function o() {
19
+ try {
20
+ t.call(e);
21
+ } catch (r) {
22
+ return Promise.reject(r);
23
+ }
24
+ }), n.push({
25
+ v: e,
26
+ d: o,
27
+ a: r
28
+ });
29
+ } else r && n.push({
30
+ d: e,
31
+ a: r
32
+ });
33
+ return e;
34
+ }
35
+ return {
36
+ e,
37
+ u: using.bind(null, !1),
38
+ a: using.bind(null, !0),
39
+ d: function d() {
40
+ var o, t = this.e, s = 0;
41
+ function next() {
42
+ for (; o = n.pop();) try {
43
+ if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
44
+ if (o.d) {
45
+ var r = o.d.call(o.v);
46
+ if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
47
+ } else s |= 1;
48
+ } catch (r) {
49
+ return err(r);
50
+ }
51
+ if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
52
+ if (t !== e) throw t;
53
+ }
54
+ function err(n) {
55
+ return t = t !== e ? new r(n, t) : n, next();
56
+ }
57
+ return next();
58
+ }
59
+ };
60
+ }
61
+ //#endregion
62
+ //#region src/shell.ts
63
+ /**
64
+ * Model-friendly environment overrides (same set `dsh-bash-local` hardcodes):
65
+ * disable colors, pagers, and interactive terminal features that would garble
66
+ * tool output. These values cross into the Linux process through WSLENV.
67
+ */
68
+ const ENV_OVERRIDES = {
69
+ NO_COLOR: "1",
70
+ TERM: "dumb",
71
+ PAGER: "cat",
72
+ GIT_PAGER: "cat"
73
+ };
74
+ /** Default SIGTERM→SIGKILL grace period (matches `dsh-bash-local`). */
75
+ const DEFAULT_GRACE_MS = 3e3;
76
+ /** Default per-stream spill cap (matches `dsh-bash-local`). */
77
+ const DEFAULT_MAX_SPILL_BYTES = 64 * 1024 * 1024;
78
+ /** Project a settled collect-mode reader into the final CollectedOutput shape. */
79
+ function finalOutput(reader) {
80
+ const read = reader.readFrom(0);
81
+ return {
82
+ text: read.text,
83
+ truncated: read.lossy,
84
+ ...read.spillPath !== void 0 ? { spillPath: read.spillPath } : {}
85
+ };
86
+ }
87
+ function assertPositiveFinite(name, value) {
88
+ if (!Number.isFinite(value) || value <= 0) throw new Error(`wsl-shell: ${name} must be a positive finite number`);
89
+ }
90
+ /**
91
+ * Reject a resolved configuration this executor could not run with, so a
92
+ * stored value is refused where it is written instead of failing at the next
93
+ * command.
94
+ * @param config - the schema-validated configuration.
95
+ * @throws Error naming the field that cannot be used.
96
+ */
97
+ function assertServiceableWslConfig(config) {
98
+ const resolved = config;
99
+ assertPositiveFinite("timeoutMs", resolved.timeoutMs);
100
+ assertPositiveFinite("maxTimeoutMs", resolved.maxTimeoutMs);
101
+ assertPositiveFinite("maxOutputBytes", resolved.maxOutputBytes);
102
+ assertPositiveFinite("maxSpillBytes", resolved.maxSpillBytes);
103
+ assertPositiveFinite("graceMs", resolved.graceMs);
104
+ if (resolved.graceMs > MAX_TIMER_DELAY_MS) throw new Error(`wsl-shell: graceMs must be no greater than ${MAX_TIMER_DELAY_MS}`);
105
+ if (resolved.distro !== void 0 && resolved.distro.trim() === "") throw new Error("wsl-shell: distro must be a non-empty distribution name");
106
+ if (resolved.username !== void 0 && resolved.username !== "" && !isValidWslUsername(resolved.username)) throw new Error("wsl-shell: username must match the Linux username pattern [A-Za-z_][A-Za-z0-9_.-]*");
107
+ }
108
+ /**
109
+ * WSL bash executor over the LOCAL subprocess service: `wsl.exe` is a Windows
110
+ * executable, so the Windows-side spawn, bounded output, spill files, and
111
+ * process-group termination are the local subprocess seam's mechanics; this
112
+ * executor supplies the Linux-world argv, cwd translation, and WSLENV.
113
+ */
114
+ var WslShellExecutor = class WslShellExecutor extends ShellExecutor {
115
+ static inject = ["subprocess"];
116
+ static Config = z.object({
117
+ cwd: z.string(),
118
+ distro: z.string(),
119
+ username: z.string(),
120
+ wslPath: z.string().default("wsl.exe"),
121
+ loginShell: z.boolean().default(true),
122
+ timeoutMs: z.number().default(12e4),
123
+ maxTimeoutMs: z.number().default(6e5),
124
+ maxOutputBytes: z.number().default(64e3),
125
+ maxSpillBytes: z.number().default(DEFAULT_MAX_SPILL_BYTES),
126
+ graceMs: z.number().default(DEFAULT_GRACE_MS)
127
+ });
128
+ resolved;
129
+ /** Validated config (schemastery applied the defaults before construction). */
130
+ get config() {
131
+ return this.resolved;
132
+ }
133
+ constructor(ctx, config) {
134
+ super(ctx);
135
+ const entry = config;
136
+ assertServiceableWslConfig(entry);
137
+ this.resolved = entry;
138
+ }
139
+ /**
140
+ * Resolve a request into a fully-specified spec: fill `workdir` from
141
+ * `config.cwd`, and `timeoutMs` from `config.timeoutMs`, capped at
142
+ * `config.maxTimeoutMs`. The tool layer calls this before
143
+ * {@link run}/{@link start}, so those methods receive explicit values.
144
+ */
145
+ resolve(request) {
146
+ const timeoutMs = clampTimeout(request.timeoutMs, this.config.timeoutMs, this.config.maxTimeoutMs, "wsl-shell: request.timeoutMs");
147
+ const stdoutMaxBytes = request.stdoutMaxBytes ?? this.config.maxOutputBytes;
148
+ assertPositiveFinite("request.stdoutMaxBytes", stdoutMaxBytes);
149
+ return {
150
+ command: request.command,
151
+ workdir: request.workdir ?? this.config.cwd ?? process.cwd(),
152
+ timeoutMs,
153
+ stdoutMaxBytes,
154
+ ...request.signal ? { signal: request.signal } : {},
155
+ ...request.stdin !== void 0 ? { stdin: request.stdin } : {},
156
+ ...request.env !== void 0 ? { env: request.env } : {},
157
+ ...request.dshEnv !== void 0 ? { dshEnv: request.dshEnv } : {},
158
+ sandboxPolicy: request.sandboxPolicy
159
+ };
160
+ }
161
+ /**
162
+ * Translate a resolved spec into the Linux execution plan. Fails loud on a
163
+ * workdir that names neither the WSL world (UNC or Linux path) nor a
164
+ * Windows drive path (reached through `/mnt/<drive>`).
165
+ * @param spec - the resolved execution spec.
166
+ * @returns the translated plan, including the complete argv.
167
+ */
168
+ plan(spec) {
169
+ const workdir = spec.workdir;
170
+ let distro;
171
+ let linuxCwd;
172
+ let windowsCwd;
173
+ let username;
174
+ const unc = parseWslUnc(workdir);
175
+ if (unc !== null) {
176
+ distro = unc.distro;
177
+ linuxCwd = unc.linuxPath;
178
+ windowsCwd = process.env.SystemRoot ?? process.cwd();
179
+ username = this.resolveUser(spec, joinUnc(unc.distro, unc.linuxPath));
180
+ } else if (workdir.startsWith("/")) {
181
+ distro = this.resolveDistro(spec);
182
+ linuxCwd = workdir;
183
+ windowsCwd = process.cwd();
184
+ username = this.resolveUser(spec, void 0);
185
+ } else {
186
+ const mnt = windowsToMntPath(workdir);
187
+ if (mnt === null) throw new Error(`wsl-shell: workdir "${workdir}" is not in the WSL execution world`);
188
+ distro = this.resolveDistro(spec);
189
+ linuxCwd = mnt;
190
+ windowsCwd = workdir;
191
+ username = this.resolveUser(spec, void 0);
192
+ }
193
+ const env = this.withWslEnv(spec);
194
+ const command = this.config.loginShell ? `cd '${linuxCwd.replace(/'/g, `'\\''`)}' && ${spec.command}` : spec.command;
195
+ const argv = [
196
+ this.config.wslPath,
197
+ "-d",
198
+ distro,
199
+ ...username !== void 0 && username !== "" ? ["-u", username] : [],
200
+ "--cd",
201
+ linuxCwd,
202
+ "-e",
203
+ "bash",
204
+ this.config.loginShell ? "-lc" : "-c",
205
+ command
206
+ ];
207
+ return {
208
+ distro,
209
+ linuxCwd,
210
+ windowsCwd,
211
+ env,
212
+ argv
213
+ };
214
+ }
215
+ /**
216
+ * Resolve the distribution for a workdir that carries none. The chain:
217
+ * the calling session's distribution (`DSH_WSL_DISTRO`, contributed by the
218
+ * host half from the session's UNC workspace cwd — the common case for a
219
+ * model passing a Linux `workdir`), then the configured `distro`, then the
220
+ * host's default distribution (cached registry read) as a last resort for
221
+ * plugin-driven calls with no session. Fails loud when every source is
222
+ * absent rather than guessing a distro the path does not belong to.
223
+ * @param spec - the resolved execution spec (its dshEnv carries the session fact).
224
+ * @returns the distribution name.
225
+ */
226
+ resolveDistro(spec) {
227
+ const fromEnv = spec.dshEnv?.DSH_WSL_DISTRO;
228
+ if (fromEnv !== void 0 && fromEnv !== "") return fromEnv;
229
+ const configured = this.config.distro;
230
+ if (configured !== void 0 && configured !== "") return configured;
231
+ const fallback = defaultDistroSync();
232
+ if (fallback !== void 0) return fallback;
233
+ throw new Error("wsl-shell: Linux workdir carries no distribution; no session DSH_WSL_DISTRO, distro config, or default distribution is available");
234
+ }
235
+ /**
236
+ * Resolve the Linux user bash runs as. The chain: the calling session's
237
+ * workspace user (`DSH_WSL_USER`, contributed by the host half), then the
238
+ * workspace's stored username when the workdir is a UNC path, then the
239
+ * configured `username`. Absent everywhere, the distribution's default
240
+ * user runs. Invalid values are skipped (they were validated on write;
241
+ * the guard is defense in depth).
242
+ * @param spec - the resolved execution spec (its dshEnv carries the session fact).
243
+ * @param uncKey - canonical UNC key of the workdir when it is a UNC path.
244
+ * @returns the username, or undefined for the distro default user.
245
+ */
246
+ resolveUser(spec, uncKey) {
247
+ const candidates = [
248
+ spec.dshEnv?.DSH_WSL_USER,
249
+ uncKey === void 0 ? void 0 : getWorkspaceUsername(uncKey),
250
+ this.config.username
251
+ ];
252
+ for (const candidate of candidates) if (candidate !== void 0 && candidate !== "" && isValidWslUsername(candidate)) return candidate;
253
+ }
254
+ /**
255
+ * Merge the caller env layers and inject `WSLENV` so the Windows-side
256
+ * values reach the Linux process. Windows-path-shaped values get the `/p`
257
+ * translation flag (they become `/mnt/<drive>/…` inside WSL); the ambient
258
+ * `WSLENV` value is preserved and extended.
259
+ * @param spec - the resolved execution spec.
260
+ * @returns the explicit environment map for the spawn.
261
+ */
262
+ withWslEnv(spec) {
263
+ const env = {
264
+ ...ENV_OVERRIDES,
265
+ ...spec.env,
266
+ ...spec.dshEnv
267
+ };
268
+ const flags = [];
269
+ for (const [key, value] of Object.entries(env)) {
270
+ if (key.toUpperCase() === "WSLENV") continue;
271
+ flags.push(isWindowsPathShaped(value) ? `${key}/p` : key);
272
+ }
273
+ env.WSLENV = [process.env.WSLENV, flags.join(":")].filter((part) => part !== void 0 && part !== "").join(":");
274
+ return env;
275
+ }
276
+ /** Map a plan onto a fully-specified subprocess spawn. */
277
+ spawnSpec(plan, spec, stdoutMaxBytes, signal) {
278
+ const collect = (maxBytes) => ({
279
+ maxBytes,
280
+ spill: { maxBytes: this.config.maxSpillBytes }
281
+ });
282
+ return {
283
+ argv: plan.argv,
284
+ cwd: plan.windowsCwd,
285
+ stdio: {
286
+ stdin: spec.stdin !== void 0 ? { data: spec.stdin } : "ignore",
287
+ stdout: collect(stdoutMaxBytes),
288
+ stderr: collect(this.config.maxOutputBytes)
289
+ },
290
+ graceMs: this.config.graceMs,
291
+ signal,
292
+ env: plan.env
293
+ };
294
+ }
295
+ /** The collect-mode readers this executor requested (present by construction). */
296
+ static collected(handle) {
297
+ const { stdout, stderr } = handle.collected;
298
+ /* v8 ignore start -- collect dispositions expose both readers by the seam contract; defensive. */
299
+ if (stdout === void 0 || stderr === void 0) throw new Error("wsl-shell: subprocess implementation dropped a requested collect stream");
300
+ /* v8 ignore stop */
301
+ return {
302
+ stdout,
303
+ stderr
304
+ };
305
+ }
306
+ /** Run one command in the foreground. */
307
+ async run(spec) {
308
+ try {
309
+ var _usingCtx$1 = _usingCtx();
310
+ const plan = this.plan(spec);
311
+ const d = _usingCtx$1.u(deadline(spec.signal, spec.timeoutMs, "WSL_BASH_TIMEOUT"));
312
+ const handle = this.ctx.subprocess.spawn(this.spawnSpec(plan, spec, spec.stdoutMaxBytes, d.signal));
313
+ const outcome = await handle.done;
314
+ const collected = WslShellExecutor.collected(handle);
315
+ const timedOut = timeoutOf(d.signal, "WSL_BASH_TIMEOUT") !== void 0;
316
+ const aborted = d.signal.aborted && !timedOut;
317
+ return {
318
+ ...outcome,
319
+ timedOut,
320
+ aborted,
321
+ timeoutMs: spec.timeoutMs,
322
+ stdout: finalOutput(collected.stdout),
323
+ stderr: finalOutput(collected.stderr)
324
+ };
325
+ } catch (_) {
326
+ _usingCtx$1.e = _;
327
+ } finally {
328
+ _usingCtx$1.d();
329
+ }
330
+ }
331
+ /** Start one command in the background and return its live handle. */
332
+ start(spec) {
333
+ const plan = this.plan(spec);
334
+ const running = this.ctx.subprocess.spawn(this.spawnSpec(plan, spec, this.config.maxOutputBytes, spec.signal));
335
+ const collected = WslShellExecutor.collected(running);
336
+ let spawnFailureNote;
337
+ const consumeSpawnFailure = () => {
338
+ const note = spawnFailureNote ?? "";
339
+ spawnFailureNote = void 0;
340
+ return note;
341
+ };
342
+ let stdoutOffset = 0;
343
+ let stderrOffset = 0;
344
+ const proc = {
345
+ status: "running",
346
+ exitCode: null,
347
+ signal: null,
348
+ done: running.done.then((outcome) => {
349
+ if (proc.status === "running") proc.status = spec.signal?.aborted === true || outcome.signal !== null ? "killed" : "completed";
350
+ proc.exitCode = outcome.exitCode;
351
+ proc.signal = outcome.signal;
352
+ }, (error) => {
353
+ proc.status = "killed";
354
+ spawnFailureNote = `spawn failed: ${String(error)}`;
355
+ }),
356
+ readOutput: () => {
357
+ const out = collected.stdout.readFrom(stdoutOffset);
358
+ const err = collected.stderr.readFrom(stderrOffset);
359
+ stdoutOffset = out.nextOffset;
360
+ stderrOffset = err.nextOffset;
361
+ const errText = err.text.length > 0 ? err.text : consumeSpawnFailure();
362
+ const separator = out.text.length > 0 && !out.text.endsWith("\n") ? "\n" : "";
363
+ return {
364
+ delta: out.text + (errText.length > 0 ? `${separator}[stderr]\n${errText}` : ""),
365
+ lossy: out.lossy || err.lossy,
366
+ ...out.spillPath !== void 0 ? { stdoutSpillPath: out.spillPath } : {},
367
+ ...err.spillPath !== void 0 ? { stderrSpillPath: err.spillPath } : {}
368
+ };
369
+ },
370
+ kill: () => {
371
+ if (proc.status !== "running") return false;
372
+ proc.status = "killed";
373
+ running.terminate();
374
+ return true;
375
+ }
376
+ };
377
+ return proc;
378
+ }
379
+ };
380
+ //#endregion
381
+ export { WslShellExecutor, WslShellExecutor as default, assertServiceableWslConfig };
382
+
382
383
  //# sourceMappingURL=shell.js.map