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/README.de.md +49 -0
- package/README.es.md +49 -0
- package/README.fr.md +49 -0
- package/README.ja.md +49 -0
- package/README.ko.md +49 -0
- package/README.md +47 -47
- package/README.pt.md +49 -0
- package/README.ru.md +49 -0
- package/README.zh.md +46 -46
- package/lib/client.js +76 -5
- package/lib/client.js.map +1 -1
- package/lib/fs.js +188 -174
- package/lib/fs.js.map +1 -1
- package/lib/index.js +573 -541
- package/lib/index.js.map +1 -1
- package/lib/{paths-DBaSmi7x.js → paths-BDE1NVOv.js} +17 -2
- package/lib/paths-BDE1NVOv.js.map +1 -0
- package/lib/shell.js +382 -381
- package/lib/shell.js.map +1 -1
- package/lib/wsl-C5_mxGPM.js +228 -0
- package/lib/wsl-C5_mxGPM.js.map +1 -0
- package/lib/wsl-DdNPGaPo.js +1019 -0
- package/lib/wsl-DdNPGaPo.js.map +1 -0
- package/lib/wsl-credentials-BI4v5TNZ.js +109 -0
- package/lib/wsl-credentials-BI4v5TNZ.js.map +1 -0
- package/package.json +71 -61
- package/src/client/AddWslWorkspace.tsx +349 -346
- package/src/client/api.ts +124 -104
- package/src/client/index.ts +233 -194
- package/src/fs.ts +247 -228
- package/src/host/variants.ts +164 -199
- package/src/index.ts +93 -27
- package/src/shared/paths.ts +175 -159
- package/src/shared/wsl-credentials.ts +135 -85
- package/src/shell.ts +451 -441
- package/image-1.png +0 -0
- package/image.png +0 -0
- package/lib/paths-DBaSmi7x.js.map +0 -1
- package/lib/wsl-GjkUifnx.js +0 -179
- package/lib/wsl-GjkUifnx.js.map +0 -1
package/lib/shell.js
CHANGED
|
@@ -1,382 +1,383 @@
|
|
|
1
|
-
import { c as
|
|
2
|
-
import {
|
|
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
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
"
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
"
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
* the
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
* @
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
*
|
|
237
|
-
* workspace
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
* @param
|
|
243
|
-
* @
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
* @
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
...
|
|
265
|
-
...spec.
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const
|
|
311
|
-
const
|
|
312
|
-
const
|
|
313
|
-
const
|
|
314
|
-
const
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
const
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
let
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
proc.
|
|
350
|
-
proc.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
...
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
proc.status
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
|
|
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
|