@deepseek-ai/dsh-subprocess-local 0.1.2-alpha.5 → 0.1.3-alpha.2
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.i18n.yaml +2 -2
- package/README.md +27 -18
- package/README.zh.md +28 -19
- package/lib/index.js +673 -961
- package/lib/runner-launch-COYGu0Dl.js +1623 -0
- package/lib/runner.js +425 -0
- package/lib/types/bin.d.ts +7 -0
- package/lib/types/index.d.ts +17 -11
- package/lib/types/linux-execve.d.ts +9 -0
- package/lib/types/linux-scope.d.ts +81 -0
- package/lib/types/managed-owner.d.ts +30 -0
- package/lib/types/runner-launch.d.ts +66 -0
- package/lib/types/runner-protocol.d.ts +109 -0
- package/lib/types/spawn-runner.d.ts +36 -0
- package/lib/types/spawn.d.ts +41 -15
- package/lib/types/terminal.d.ts +11 -2
- package/lib/types/windows-job.d.ts +30 -0
- package/package.json +15 -8
package/lib/runner.js
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { S as loadLinuxExecve, a as resolveWindowsExecutable, b as serializeRunnerError, f as consumeLinuxLaunchRequest, g as linuxLaunchFilesFromLocator, h as isWindowsTerminateRequest, i as parseRunnerTargetArgv, r as consumeRunnerSelection, t as SUBPROCESS_RUNNER_ENV, v as parseWindowsStartRequest, x as writeLinuxStartupError } from "./runner-launch-COYGu0Dl.js";
|
|
2
|
+
import { closeSync } from "node:fs";
|
|
3
|
+
import { Win32Error, closeHandleChecked, isJobEmpty, loadWin32ProcessBindings, pollProcessExit, spawnCurrentTokenJobProcess, terminateJob } from "@deepseek-ai/dsh-win32-process";
|
|
4
|
+
//#region lib/types/spawn-runner.js
|
|
5
|
+
/** One-shot Linux exec bootstrap and Windows Job-owning subprocess runner. */
|
|
6
|
+
const defaultInternals = {
|
|
7
|
+
/* v8 ignore next -- source/built/packaged subprocess smoke executes this only in a replaceable child process. */
|
|
8
|
+
execve: (file, argv, env) => loadLinuxExecve()(file, argv, env),
|
|
9
|
+
loadWin32ProcessBindings,
|
|
10
|
+
spawnCurrentTokenJobProcess,
|
|
11
|
+
closeFileDescriptor: closeSync,
|
|
12
|
+
resolveWindowsExecutable,
|
|
13
|
+
pollProcessExit,
|
|
14
|
+
isJobEmpty,
|
|
15
|
+
terminateJob,
|
|
16
|
+
closeHandleChecked
|
|
17
|
+
};
|
|
18
|
+
const NODE_SPAWN_DETAIL_CODES = new Set(["EACCES", "ENOENT"]);
|
|
19
|
+
const WINDOWS_SPAWN_ERROR_CODES = new Map([
|
|
20
|
+
[2, "ENOENT"],
|
|
21
|
+
[3, "ENOENT"],
|
|
22
|
+
[267, "ENOENT"],
|
|
23
|
+
[5, "EPERM"],
|
|
24
|
+
[193, "EFTYPE"],
|
|
25
|
+
[740, "EACCES"]
|
|
26
|
+
]);
|
|
27
|
+
function nodeSpawnError(syscall, code, path) {
|
|
28
|
+
return {
|
|
29
|
+
name: "Error",
|
|
30
|
+
message: `${syscall} ${code}`,
|
|
31
|
+
code,
|
|
32
|
+
syscall,
|
|
33
|
+
...path === void 0 ? {} : { path }
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function asSpawnError(error, program) {
|
|
37
|
+
const serialized = serializeRunnerError(error);
|
|
38
|
+
if (!(error instanceof Win32Error)) return serialized.code === void 0 ? serialized : nodeSpawnError(`spawn ${program}`, serialized.code, program);
|
|
39
|
+
const code = WINDOWS_SPAWN_ERROR_CODES.get(error.win32Code) ?? "UNKNOWN";
|
|
40
|
+
if (NODE_SPAWN_DETAIL_CODES.has(code)) return nodeSpawnError(`spawn ${program}`, code, program);
|
|
41
|
+
return nodeSpawnError("spawn", code);
|
|
42
|
+
}
|
|
43
|
+
function windowsPathNotFoundError(program) {
|
|
44
|
+
return nodeSpawnError(`spawn ${program}`, "ENOENT", program);
|
|
45
|
+
}
|
|
46
|
+
function windowsStartCancelledError() {
|
|
47
|
+
return {
|
|
48
|
+
name: "Error",
|
|
49
|
+
message: "subprocess target start was cancelled"
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function linuxPathNotFoundError(program) {
|
|
53
|
+
return Object.assign(/* @__PURE__ */ new Error(`spawn ${program} ENOENT`), {
|
|
54
|
+
code: "ENOENT",
|
|
55
|
+
errno: -2,
|
|
56
|
+
syscall: `spawn ${program}`,
|
|
57
|
+
path: program,
|
|
58
|
+
spawnargs: []
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function execLinuxFile(file, argv, env, internals) {
|
|
62
|
+
try {
|
|
63
|
+
return internals.execve(file, argv, env);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error.code !== "ENOEXEC") throw error;
|
|
66
|
+
return internals.execve("/bin/sh", [
|
|
67
|
+
"/bin/sh",
|
|
68
|
+
file,
|
|
69
|
+
...argv.slice(1)
|
|
70
|
+
], env);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function execLinuxTarget(request, argv, internals) {
|
|
74
|
+
const program = argv[0];
|
|
75
|
+
if (program.includes("/")) return execLinuxFile(program, argv, request.env, internals);
|
|
76
|
+
const path = request.env.PATH ?? "/usr/bin:/bin";
|
|
77
|
+
let permissionFailure;
|
|
78
|
+
for (const directory of path.split(":")) {
|
|
79
|
+
const root = directory.startsWith("/") ? directory : `${request.cwd}${request.cwd.endsWith("/") ? "" : "/"}${directory}`;
|
|
80
|
+
const candidate = `${root}${root.endsWith("/") ? "" : "/"}${program}`;
|
|
81
|
+
try {
|
|
82
|
+
return execLinuxFile(candidate, argv, request.env, internals);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const code = error.code;
|
|
85
|
+
if (code === "EACCES") {
|
|
86
|
+
permissionFailure ??= error;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (code === "ENOENT" || code === "ENOTDIR") continue;
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
throw permissionFailure ?? linuxPathNotFoundError(program);
|
|
94
|
+
}
|
|
95
|
+
function runLinux(locator, argv, host, internals) {
|
|
96
|
+
const files = linuxLaunchFilesFromLocator(locator);
|
|
97
|
+
let request;
|
|
98
|
+
try {
|
|
99
|
+
request = consumeLinuxLaunchRequest(files.requestPath);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
writeLinuxStartupError(files, {
|
|
102
|
+
type: "error",
|
|
103
|
+
error: serializeRunnerError(error)
|
|
104
|
+
});
|
|
105
|
+
host.exitCode = 127;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
host.chdir(request.cwd);
|
|
110
|
+
execLinuxTarget({
|
|
111
|
+
...request,
|
|
112
|
+
cwd: host.cwd()
|
|
113
|
+
}, argv, internals);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
writeLinuxStartupError(files, {
|
|
116
|
+
type: "error",
|
|
117
|
+
error: asSpawnError(error, argv[0])
|
|
118
|
+
});
|
|
119
|
+
host.exitCode = 127;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function sendMessage(host, result) {
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
if (!host.connected || host.send === void 0) {
|
|
125
|
+
reject(/* @__PURE__ */ new Error("subprocess runner IPC is not connected"));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
host.send(result, (error) => {
|
|
130
|
+
if (error === null) resolve();
|
|
131
|
+
else reject(error);
|
|
132
|
+
});
|
|
133
|
+
} catch (error) {
|
|
134
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
var WindowsJobRunner = class {
|
|
139
|
+
argv;
|
|
140
|
+
host;
|
|
141
|
+
internals;
|
|
142
|
+
api;
|
|
143
|
+
processHandle;
|
|
144
|
+
jobHandle;
|
|
145
|
+
pollTimer;
|
|
146
|
+
startSeen = false;
|
|
147
|
+
terminateRequested = false;
|
|
148
|
+
resultStarted = false;
|
|
149
|
+
resultDelivered = false;
|
|
150
|
+
finished = false;
|
|
151
|
+
completion = Promise.withResolvers();
|
|
152
|
+
constructor(argv, host, internals) {
|
|
153
|
+
this.argv = argv;
|
|
154
|
+
this.host = host;
|
|
155
|
+
this.internals = internals;
|
|
156
|
+
}
|
|
157
|
+
run() {
|
|
158
|
+
if (!this.host.connected || this.host.send === void 0) {
|
|
159
|
+
this.finish(127);
|
|
160
|
+
return this.completion.promise;
|
|
161
|
+
}
|
|
162
|
+
this.host.on("message", this.onMessage);
|
|
163
|
+
this.host.once("disconnect", this.onDisconnect);
|
|
164
|
+
return this.completion.promise;
|
|
165
|
+
}
|
|
166
|
+
onMessage = (value) => {
|
|
167
|
+
if (this.finished) return;
|
|
168
|
+
if (isWindowsTerminateRequest(value)) {
|
|
169
|
+
this.requestTermination();
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (this.startSeen) {
|
|
173
|
+
this.runnerFailure(/* @__PURE__ */ new Error("subprocess runner received more than one Windows start request"));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
let request;
|
|
177
|
+
try {
|
|
178
|
+
request = parseWindowsStartRequest(value);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
this.runnerFailure(error);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
this.startSeen = true;
|
|
184
|
+
this.start(request);
|
|
185
|
+
};
|
|
186
|
+
onDisconnect = () => {
|
|
187
|
+
if (this.finished) return;
|
|
188
|
+
this.releaseOwnedJob();
|
|
189
|
+
this.finish(127, false);
|
|
190
|
+
};
|
|
191
|
+
async start(request) {
|
|
192
|
+
if (this.terminateRequested) {
|
|
193
|
+
await this.publishTerminalResult({
|
|
194
|
+
type: "error",
|
|
195
|
+
error: windowsStartCancelledError()
|
|
196
|
+
}, 0);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
await new Promise((resolveImmediate) => {
|
|
200
|
+
setImmediate(resolveImmediate);
|
|
201
|
+
});
|
|
202
|
+
if (this.finished) return;
|
|
203
|
+
if (this.terminateRequested) {
|
|
204
|
+
await this.publishTerminalResult({
|
|
205
|
+
type: "error",
|
|
206
|
+
error: windowsStartCancelledError()
|
|
207
|
+
}, 0);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
const [command, ...args] = this.argv;
|
|
212
|
+
const applicationName = this.internals.resolveWindowsExecutable(command, request.cwd, request.env, void 0, { ...this.host.env });
|
|
213
|
+
if (applicationName === void 0) {
|
|
214
|
+
await this.publishTerminalResult({
|
|
215
|
+
type: "error",
|
|
216
|
+
error: windowsPathNotFoundError(command)
|
|
217
|
+
}, 0);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
this.api = this.internals.loadWin32ProcessBindings();
|
|
221
|
+
const spawned = this.internals.spawnCurrentTokenJobProcess(this.api, {
|
|
222
|
+
command,
|
|
223
|
+
applicationName,
|
|
224
|
+
args,
|
|
225
|
+
cwd: request.cwd,
|
|
226
|
+
env: request.env,
|
|
227
|
+
stdio: {
|
|
228
|
+
stdin: 4,
|
|
229
|
+
stdout: 5,
|
|
230
|
+
stderr: 6
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
this.processHandle = spawned.process;
|
|
234
|
+
this.jobHandle = spawned.job;
|
|
235
|
+
for (const fileDescriptor of [
|
|
236
|
+
4,
|
|
237
|
+
5,
|
|
238
|
+
6
|
|
239
|
+
]) this.internals.closeFileDescriptor(fileDescriptor);
|
|
240
|
+
this.pollTimer = setInterval(() => {
|
|
241
|
+
this.poll();
|
|
242
|
+
}, 10);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
if (this.jobHandle === void 0 && error instanceof Win32Error && error.api === "CreateProcessW") {
|
|
245
|
+
await this.publishTerminalResult({
|
|
246
|
+
type: "error",
|
|
247
|
+
error: asSpawnError(error, this.argv[0])
|
|
248
|
+
}, 0);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
await this.runnerFailure(error);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
requestTermination() {
|
|
255
|
+
if (this.terminateRequested) return;
|
|
256
|
+
this.terminateRequested = true;
|
|
257
|
+
try {
|
|
258
|
+
this.terminateOwnedJob();
|
|
259
|
+
} catch (error) {
|
|
260
|
+
this.runnerFailure(error);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
terminateOwnedJob() {
|
|
264
|
+
const job = this.jobHandle;
|
|
265
|
+
if (job === void 0) return;
|
|
266
|
+
/* v8 ignore next -- a Job handle is assigned only after the bindings are loaded;
|
|
267
|
+
* the guard above is the only reachable empty-owner state. */
|
|
268
|
+
if (this.api === void 0) return;
|
|
269
|
+
this.internals.terminateJob(this.api, job, 1);
|
|
270
|
+
}
|
|
271
|
+
poll() {
|
|
272
|
+
if (this.finished) return;
|
|
273
|
+
/* v8 ignore next -- poll is installed only after start() stores the bindings; retained as a defensive invariant guard. */
|
|
274
|
+
if (this.api === void 0) return;
|
|
275
|
+
try {
|
|
276
|
+
if (this.processHandle !== void 0) {
|
|
277
|
+
const exitCode = this.internals.pollProcessExit(this.api, this.processHandle);
|
|
278
|
+
if (exitCode !== void 0) {
|
|
279
|
+
this.internals.closeHandleChecked(this.api, this.processHandle, "ordinary direct process");
|
|
280
|
+
this.processHandle = void 0;
|
|
281
|
+
this.publishTerminalResult({
|
|
282
|
+
type: "target-exit",
|
|
283
|
+
exitCode
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (this.jobHandle !== void 0 && this.internals.isJobEmpty(this.api, this.jobHandle)) {
|
|
288
|
+
this.internals.closeHandleChecked(this.api, this.jobHandle, "ordinary process Job");
|
|
289
|
+
this.jobHandle = void 0;
|
|
290
|
+
if (this.resultDelivered) this.finish(0);
|
|
291
|
+
}
|
|
292
|
+
} catch (error) {
|
|
293
|
+
this.runnerFailure(error);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async publishTerminalResult(result, exitCode) {
|
|
297
|
+
/* v8 ignore next -- each state transition has a single result call site; the guard contains only re-entrant internal defects. */
|
|
298
|
+
if (this.finished || this.resultStarted) return;
|
|
299
|
+
this.resultStarted = true;
|
|
300
|
+
try {
|
|
301
|
+
await sendMessage(this.host, result);
|
|
302
|
+
this.resultDelivered = true;
|
|
303
|
+
} catch {
|
|
304
|
+
this.releaseOwnedJob();
|
|
305
|
+
this.finish(127, false);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (exitCode !== void 0) {
|
|
309
|
+
this.finish(exitCode);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (this.jobHandle === void 0) this.finish(0);
|
|
313
|
+
}
|
|
314
|
+
async runnerFailure(error) {
|
|
315
|
+
/* v8 ignore next -- callers stop/detach on finish; this guard contains only an already-queued internal callback. */
|
|
316
|
+
if (this.finished) return;
|
|
317
|
+
if (!this.resultStarted) {
|
|
318
|
+
this.resultStarted = true;
|
|
319
|
+
try {
|
|
320
|
+
await sendMessage(this.host, {
|
|
321
|
+
type: "error",
|
|
322
|
+
error: serializeRunnerError(error)
|
|
323
|
+
});
|
|
324
|
+
this.resultDelivered = true;
|
|
325
|
+
} catch {}
|
|
326
|
+
}
|
|
327
|
+
this.releaseOwnedJob();
|
|
328
|
+
this.finish(127);
|
|
329
|
+
}
|
|
330
|
+
releaseOwnedJob() {
|
|
331
|
+
if (this.pollTimer !== void 0) clearInterval(this.pollTimer);
|
|
332
|
+
this.pollTimer = void 0;
|
|
333
|
+
const api = this.api;
|
|
334
|
+
if (api === void 0) return;
|
|
335
|
+
if (this.jobHandle !== void 0) {
|
|
336
|
+
try {
|
|
337
|
+
this.internals.terminateJob(api, this.jobHandle, 1);
|
|
338
|
+
} catch {}
|
|
339
|
+
try {
|
|
340
|
+
this.internals.closeHandleChecked(api, this.jobHandle, "ordinary process Job cleanup");
|
|
341
|
+
} catch {}
|
|
342
|
+
this.jobHandle = void 0;
|
|
343
|
+
}
|
|
344
|
+
if (this.processHandle !== void 0) {
|
|
345
|
+
try {
|
|
346
|
+
this.internals.closeHandleChecked(api, this.processHandle, "ordinary direct process cleanup");
|
|
347
|
+
} catch {}
|
|
348
|
+
this.processHandle = void 0;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
finish(exitCode, disconnect = true) {
|
|
352
|
+
if (this.finished) return;
|
|
353
|
+
this.finished = true;
|
|
354
|
+
if (this.pollTimer !== void 0) clearInterval(this.pollTimer);
|
|
355
|
+
this.pollTimer = void 0;
|
|
356
|
+
this.host.off("message", this.onMessage);
|
|
357
|
+
this.host.off("disconnect", this.onDisconnect);
|
|
358
|
+
this.host.exitCode = exitCode;
|
|
359
|
+
if (disconnect && this.host.connected) this.host.disconnect();
|
|
360
|
+
this.completion.resolve();
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
/**
|
|
364
|
+
* Execute the selected Linux bootstrap or Windows Job runner.
|
|
365
|
+
* @param selection - Windows sentinel or Linux launch-request locator.
|
|
366
|
+
* @param argv - private runner arguments beginning with the target delimiter.
|
|
367
|
+
* @param host - process transport and lifecycle host.
|
|
368
|
+
* @param internals - native and filesystem operations used by the runner.
|
|
369
|
+
*/
|
|
370
|
+
async function runSpawnRunner(selection, argv, host = process, internals = defaultInternals) {
|
|
371
|
+
Reflect.deleteProperty(host.env, SUBPROCESS_RUNNER_ENV);
|
|
372
|
+
const targetArgv = parseRunnerTargetArgv(argv);
|
|
373
|
+
if (selection === "windows") {
|
|
374
|
+
await new WindowsJobRunner(targetArgv, host, internals).run();
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
runLinux(selection, targetArgv, host, internals);
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Best-effort reporting for failures before the selected runner established its owner.
|
|
381
|
+
* @param selection - Windows sentinel, Linux launch-request locator, or no selection.
|
|
382
|
+
* @param error - failure raised before normal runner settlement.
|
|
383
|
+
* @param host - process transport and lifecycle host.
|
|
384
|
+
*/
|
|
385
|
+
async function reportSpawnRunnerFailure(selection, error, host = process) {
|
|
386
|
+
if (selection === "windows") {
|
|
387
|
+
try {
|
|
388
|
+
await sendMessage(host, {
|
|
389
|
+
type: "error",
|
|
390
|
+
error: serializeRunnerError(error)
|
|
391
|
+
});
|
|
392
|
+
} catch {}
|
|
393
|
+
host.exitCode = 127;
|
|
394
|
+
if (host.connected) host.disconnect();
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (selection !== void 0) try {
|
|
398
|
+
writeLinuxStartupError(linuxLaunchFilesFromLocator(selection), {
|
|
399
|
+
type: "error",
|
|
400
|
+
error: serializeRunnerError(error)
|
|
401
|
+
});
|
|
402
|
+
} catch {}
|
|
403
|
+
host.exitCode = 127;
|
|
404
|
+
}
|
|
405
|
+
//#endregion
|
|
406
|
+
//#region lib/types/bin.js
|
|
407
|
+
/** Thin executable/importable entry for the provider-private runner core. */
|
|
408
|
+
/**
|
|
409
|
+
* Run a selector already removed by a packaging bootstrap.
|
|
410
|
+
* @param selection - private runner selector or Linux launch-request locator.
|
|
411
|
+
*/
|
|
412
|
+
async function runSelectedSubprocessRunner(selection) {
|
|
413
|
+
try {
|
|
414
|
+
await runSpawnRunner(selection, process.argv.slice(2));
|
|
415
|
+
} catch (error) {
|
|
416
|
+
await reportSpawnRunnerFailure(selection, error);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (import.meta.main) {
|
|
420
|
+
const selection = consumeRunnerSelection();
|
|
421
|
+
if (selection === void 0) process.exitCode = 127;
|
|
422
|
+
else runSelectedSubprocessRunner(selection);
|
|
423
|
+
}
|
|
424
|
+
//#endregion
|
|
425
|
+
export { runSelectedSubprocessRunner };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Thin executable/importable entry for the provider-private runner core. */
|
|
2
|
+
/**
|
|
3
|
+
* Run a selector already removed by a packaging bootstrap.
|
|
4
|
+
* @param selection - private runner selector or Linux launch-request locator.
|
|
5
|
+
*/
|
|
6
|
+
export declare function runSelectedSubprocessRunner(selection: string): Promise<void>;
|
|
7
|
+
//# sourceMappingURL=bin.d.ts.map
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Local Service Provider for the subprocess capability seam. Each spawn
|
|
3
|
-
*
|
|
4
|
-
* terminates and joins live
|
|
5
|
-
* any
|
|
6
|
-
* limit arrives on the spec, so
|
|
7
|
-
* caller's config (the bash executor's, the LSP host's, …).
|
|
2
|
+
* Local Service Provider for the subprocess capability seam. Each spawn owns a
|
|
3
|
+
* platform-selected managed range with the spec's per-stream stdio dispositions.
|
|
4
|
+
* Normal disposal terminates and joins live ranges; Node's synchronous exit
|
|
5
|
+
* phase force-stops any ranges the service still owns. It has no config: every
|
|
6
|
+
* disposition and limit arrives on the spec, so deployment-varying choices
|
|
7
|
+
* stay with the caller's config (the bash executor's, the LSP host's, …).
|
|
8
8
|
* @module @deepseek-ai/dsh-subprocess-local
|
|
9
9
|
*/
|
|
10
10
|
import { Context } from '@deepseek-ai/cordis';
|
|
@@ -13,19 +13,23 @@ import type { SubprocessHandle, SubprocessSpawnSpec, SubprocessTerminalHandle, S
|
|
|
13
13
|
import type { SpawnInternals } from './spawn.ts';
|
|
14
14
|
import type { ProcessInspector } from './process-inspector.ts';
|
|
15
15
|
/**
|
|
16
|
-
* Local subprocess service:
|
|
16
|
+
* Local subprocess service: platform-selected managed ranges, Node-shaped stdio
|
|
17
17
|
* dispositions (raw pipes, inherit, bounded tail-keep collection with spill
|
|
18
|
-
* files), credential-scrubbed environment, and
|
|
19
|
-
*
|
|
20
|
-
* JavaScript-observable host exit.
|
|
18
|
+
* files), credential-scrubbed environment, and provider-owned range signalling.
|
|
19
|
+
* POSIX paths stage TERM before KILL; Windows paths terminate immediately.
|
|
20
|
+
* JavaScript-observable host exit also performs synchronous final termination.
|
|
21
21
|
*/
|
|
22
22
|
export declare class LocalSubprocessRuntime extends SubprocessRuntime {
|
|
23
23
|
/** Live handles retained for normal disposal and synchronous host-exit finalization. */
|
|
24
24
|
private live;
|
|
25
25
|
/** Live terminals retained through normal quiescence or host-exit finalization. */
|
|
26
26
|
private terminals;
|
|
27
|
-
/** Test hook: spill and platform
|
|
27
|
+
/** Test hook: process, spill, and platform operations forwarded to spawnSubprocess. */
|
|
28
28
|
internals: SpawnInternals;
|
|
29
|
+
/** Provider-lifetime latch suppressing repeated weaker-containment warnings. */
|
|
30
|
+
private fallbackWarningIssued;
|
|
31
|
+
/** Positive-only cache for the expensive Linux bootstrap and scope probe. */
|
|
32
|
+
private linuxDeepProbePassed;
|
|
29
33
|
/** Test hook for platform process inspection; production resolves lazily on terminal spawn. */
|
|
30
34
|
terminalInspector: ProcessInspector | undefined;
|
|
31
35
|
constructor(ctx: Context);
|
|
@@ -34,6 +38,8 @@ export declare class LocalSubprocessRuntime extends SubprocessRuntime {
|
|
|
34
38
|
resolveExecutable(command: string, env?: Readonly<Record<string, string>>, signal?: AbortSignal): Promise<string>;
|
|
35
39
|
private executableCandidates;
|
|
36
40
|
spawn(spec: SubprocessSpawnSpec): SubprocessHandle;
|
|
41
|
+
private selectContainmentMode;
|
|
42
|
+
private warnFallback;
|
|
37
43
|
spawnTerminal(spec: SubprocessTerminalSpawnSpec): Promise<SubprocessTerminalHandle>;
|
|
38
44
|
}
|
|
39
45
|
export default LocalSubprocessRuntime;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Lazy libc execve and descriptor bindings used by the one-shot Linux bootstrap. */
|
|
2
|
+
/** Replace the current process image while preserving the supplied argv and environment. */
|
|
3
|
+
export type LinuxExecve = (file: string, argv: string[], env: Record<string, string>) => never;
|
|
4
|
+
/**
|
|
5
|
+
* Load libc's execve and fcntl symbols on first use and retain the native bindings.
|
|
6
|
+
* @returns a process-replacing execve operation that throws Node-style errors on failure.
|
|
7
|
+
*/
|
|
8
|
+
export declare function loadLinuxExecve(): LinuxExecve;
|
|
9
|
+
//# sourceMappingURL=linux-execve.d.ts.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** Linux user-systemd scope launch and managed-range ownership. */
|
|
2
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
3
|
+
import type { SubprocessOutcome, SubprocessSpawnSpec, SubprocessTerminalSpawnSpec } from '@deepseek-ai/dsh-subprocess';
|
|
4
|
+
import { loadLinuxExecve } from './linux-execve.ts';
|
|
5
|
+
import type { BoundProcessOwner, ManagedProcessLaunch } from './managed-owner.ts';
|
|
6
|
+
import type { RunnerInvocation } from './runner-launch.ts';
|
|
7
|
+
/** Test seams for systemd command execution. */
|
|
8
|
+
export interface LinuxScopeInternals {
|
|
9
|
+
spawn?: typeof spawn;
|
|
10
|
+
spawnSync?: typeof spawnSync;
|
|
11
|
+
systemctlQuery?: (command: string, args: readonly string[]) => Promise<SystemctlResult>;
|
|
12
|
+
systemdRun?: string;
|
|
13
|
+
systemctl?: string;
|
|
14
|
+
runnerInvocation?: RunnerInvocation;
|
|
15
|
+
resolveRunnerInvocation?: () => RunnerInvocation;
|
|
16
|
+
runnerAvailable?: (invocation: RunnerInvocation) => boolean;
|
|
17
|
+
loadLinuxExecve?: typeof loadLinuxExecve;
|
|
18
|
+
sleep?: (delayMs: number, signal?: AbortSignal) => Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
interface SystemctlResult {
|
|
21
|
+
status: number | null;
|
|
22
|
+
stdout: string;
|
|
23
|
+
stderr: string;
|
|
24
|
+
error?: Error;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Confirm this exact runner entry and libc execve binding without a probe mode.
|
|
28
|
+
* @param internals - optional runner and libc-binding seams used by tests.
|
|
29
|
+
* @returns whether the bootstrap can enter the final target.
|
|
30
|
+
*/
|
|
31
|
+
export declare function probeLinuxBootstrap(internals?: LinuxScopeInternals): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Confirm current literal-argv transient-scope support before selecting native launch.
|
|
34
|
+
* @param internals - optional systemd command seams used by tests.
|
|
35
|
+
* @returns whether the current user manager supports the required scope invocation.
|
|
36
|
+
*/
|
|
37
|
+
export declare function probeLinuxScope(internals?: LinuxScopeInternals): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Confirm that the current user manager remains reachable after a positive deep probe.
|
|
40
|
+
* @param internals - optional systemctl seam used by tests.
|
|
41
|
+
* @returns whether one lightweight manager query succeeds.
|
|
42
|
+
*/
|
|
43
|
+
export declare function probeLinuxManager(internals?: LinuxScopeInternals): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Re-check every Linux native prerequisite for one eligible spawn.
|
|
46
|
+
* @param internals - optional native capability seams used by tests.
|
|
47
|
+
* @returns whether the Linux native containment path is currently available.
|
|
48
|
+
*/
|
|
49
|
+
export declare function probeLinuxNative(internals?: LinuxScopeInternals): boolean;
|
|
50
|
+
interface DirectRange {
|
|
51
|
+
running(): boolean;
|
|
52
|
+
signal(signal: 'SIGTERM' | 'SIGKILL'): void;
|
|
53
|
+
}
|
|
54
|
+
/** Linux PTY invocation and owner for the exact one-shot scope/bootstrap. */
|
|
55
|
+
export interface LinuxTerminalScopeLaunch {
|
|
56
|
+
command: string;
|
|
57
|
+
args: string[];
|
|
58
|
+
cwd: string;
|
|
59
|
+
env: NodeJS.ProcessEnv;
|
|
60
|
+
bindOwner: (direct: DirectRange) => BoundProcessOwner;
|
|
61
|
+
resolveOutcome: (outcome: SubprocessOutcome) => SubprocessOutcome;
|
|
62
|
+
cleanup: () => void;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Prepare one Linux PTY scope using the same launch request and bootstrap core.
|
|
66
|
+
* @param spec - terminal target request.
|
|
67
|
+
* @param targetEnv - validated complete target environment.
|
|
68
|
+
* @param internals - optional runner and systemd seams used by tests.
|
|
69
|
+
* @returns invocation facts and ownership callbacks for node-pty.
|
|
70
|
+
*/
|
|
71
|
+
export declare function prepareLinuxTerminalScope(spec: SubprocessTerminalSpawnSpec, targetEnv: Record<string, string>, internals?: LinuxScopeInternals): LinuxTerminalScopeLaunch;
|
|
72
|
+
/**
|
|
73
|
+
* Launch one ordinary target inside a transient user scope.
|
|
74
|
+
* @param spec - ordinary target request.
|
|
75
|
+
* @param targetEnv - validated complete target environment.
|
|
76
|
+
* @param internals - optional runner and systemd seams used by tests.
|
|
77
|
+
* @returns direct streams, result, and managed-scope owner.
|
|
78
|
+
*/
|
|
79
|
+
export declare function launchLinuxScope(spec: SubprocessSpawnSpec, targetEnv: Record<string, string>, internals?: LinuxScopeInternals): ManagedProcessLaunch;
|
|
80
|
+
export {};
|
|
81
|
+
//# sourceMappingURL=linux-scope.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Minimal managed-range ownership bound to one ordinary subprocess handle. */
|
|
2
|
+
import type { Readable, Writable } from 'node:stream';
|
|
3
|
+
import type { SubprocessOutcome } from '@deepseek-ai/dsh-subprocess';
|
|
4
|
+
/** Platform owner used by termination and whole-range settlement. */
|
|
5
|
+
export interface BoundProcessOwner {
|
|
6
|
+
/** Signal the managed range; `cancellationReason` is used only before Windows target commit. */
|
|
7
|
+
signal(signal: 'SIGTERM' | 'SIGKILL', cancellationReason?: unknown): void;
|
|
8
|
+
/** Wait for the same managed range to become empty; reject when it cannot be observed. */
|
|
9
|
+
waitForExit(): Promise<void>;
|
|
10
|
+
/** Synchronously force final termination during JavaScript-observable host exit. */
|
|
11
|
+
terminateForHostExit(): void;
|
|
12
|
+
/** Release provider-private protocol artifacts after outcome and range settlement. */
|
|
13
|
+
cleanup?(): void;
|
|
14
|
+
}
|
|
15
|
+
/** Platform launch facts consumed by the common stdio and result lifecycle. */
|
|
16
|
+
export interface ManagedProcessLaunch {
|
|
17
|
+
stdin: Writable | null;
|
|
18
|
+
stdout: Readable | null;
|
|
19
|
+
stderr: Readable | null;
|
|
20
|
+
direct: Promise<SubprocessOutcome>;
|
|
21
|
+
owner: BoundProcessOwner;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Apply an optional abort bound to one shared wait promise.
|
|
25
|
+
* @param pending - managed-range wait shared by all callers.
|
|
26
|
+
* @param signal - optional caller cancellation signal.
|
|
27
|
+
* @returns whether the managed-range wait completed before cancellation.
|
|
28
|
+
*/
|
|
29
|
+
export declare function waitWithAbort(pending: Promise<void>, signal?: AbortSignal): Promise<boolean>;
|
|
30
|
+
//# sourceMappingURL=managed-owner.d.ts.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** Parent-side invocation and bootstrap state for the private native runner. */
|
|
2
|
+
import type { StdioOptions } from 'node:child_process';
|
|
3
|
+
import type { SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess';
|
|
4
|
+
/** The one private environment variable consumed before target state is restored. */
|
|
5
|
+
export declare const SUBPROCESS_RUNNER_ENV: "DSH_SUBPROCESS_RUNNER";
|
|
6
|
+
/** Sentinel used by the packaged bootstrap for the Windows IPC runner. */
|
|
7
|
+
export declare const WINDOWS_RUNNER_SELECTION: "windows";
|
|
8
|
+
/** Non-empty command tuple used to launch the private runner entry. */
|
|
9
|
+
export type RunnerInvocation = [string, ...string[]];
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the source, built, or packaged entry that calls the same runner core.
|
|
12
|
+
* @returns executable and arguments for the active runtime form.
|
|
13
|
+
*/
|
|
14
|
+
export declare function spawnRunnerInvocation(): RunnerInvocation;
|
|
15
|
+
/**
|
|
16
|
+
* Check the concrete runner executable and entry paths without executing a probe mode.
|
|
17
|
+
* @param invocation - resolved executable and runner-entry arguments.
|
|
18
|
+
* @returns whether every concrete executable or entry path is accessible.
|
|
19
|
+
*/
|
|
20
|
+
export declare function runnerInvocationAvailable(invocation?: RunnerInvocation): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Build the bootstrap-safe environment; target overrides arrive through request/IPC.
|
|
23
|
+
* @param selection - private runner selector or Linux launch-request locator.
|
|
24
|
+
* @param invocation - resolved runner invocation whose source form needs the workspace paths map.
|
|
25
|
+
* @returns environment for the runner before target state is restored.
|
|
26
|
+
*/
|
|
27
|
+
export declare function runnerEnvironment(selection: string, invocation?: RunnerInvocation): NodeJS.ProcessEnv;
|
|
28
|
+
/**
|
|
29
|
+
* Read and delete the private selector before importing or restoring target state.
|
|
30
|
+
* @param env - mutable environment containing the private selector.
|
|
31
|
+
* @returns the consumed selector, or undefined when no runner was requested.
|
|
32
|
+
*/
|
|
33
|
+
export declare function consumeRunnerSelection(env?: NodeJS.ProcessEnv): string | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Require the private argv delimiter and at least one target argv entry.
|
|
36
|
+
* @param argv - private runner arguments.
|
|
37
|
+
* @returns copied target argv after the private delimiter.
|
|
38
|
+
*/
|
|
39
|
+
export declare function parseRunnerTargetArgv(argv: readonly string[]): string[];
|
|
40
|
+
/**
|
|
41
|
+
* Build direct Linux target stdio, or isolated Windows runner stdio with IPC
|
|
42
|
+
* on fd 3 and target carriers on fd 4 through fd 6.
|
|
43
|
+
* @param spec - ordinary subprocess request whose stdio modes are preserved.
|
|
44
|
+
* @param ipc - whether to isolate the runner and add its private Node IPC descriptor.
|
|
45
|
+
* @param stdinCarrier - runner fd 4 carrier; Windows ignore passes an opened null-device fd.
|
|
46
|
+
* @returns child-process stdio options for the runner.
|
|
47
|
+
*/
|
|
48
|
+
export declare function runnerStdio(spec: SubprocessSpawnSpec, ipc: boolean, stdinCarrier?: 'pipe' | number): StdioOptions;
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the executable path with libuv/Node Windows spawn search order while
|
|
51
|
+
* preserving the caller's original command-line argv entry separately.
|
|
52
|
+
* @param command - original target argv[0].
|
|
53
|
+
* @param cwd - final target working directory used for relative search roots.
|
|
54
|
+
* @param env - final target environment containing the child PATH.
|
|
55
|
+
* @param exists - injectable non-directory candidate probe used by tests.
|
|
56
|
+
* @param currentEnv - runner environment supplying PATH fallback and cwd-search policy.
|
|
57
|
+
* @returns a resolved application name suitable for `CreateProcessW`, or undefined when no candidate exists.
|
|
58
|
+
*/
|
|
59
|
+
export declare function resolveWindowsExecutable(command: string, cwd: string, env: Readonly<Record<string, string>>, exists?: (candidate: string) => boolean, currentEnv?: Readonly<Record<string, string | undefined>>): string | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Materialize and synchronously validate the final target environment.
|
|
62
|
+
* @param spec - final target argv, cwd, and environment overrides.
|
|
63
|
+
* @returns complete target environment after Node-equivalent validation.
|
|
64
|
+
*/
|
|
65
|
+
export declare function targetEnvironment(spec: Pick<SubprocessSpawnSpec, 'argv' | 'cwd' | 'env'>): Record<string, string>;
|
|
66
|
+
//# sourceMappingURL=runner-launch.d.ts.map
|