@qlzt/dsh-subprocess-local 0.0.0-stage → 0.2.1-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/LICENSE +21 -0
- package/README.i18n.yaml +70 -0
- package/README.md +167 -2
- package/README.zh.md +168 -0
- package/lib/index.js +1487 -0
- package/lib/output.js +239 -0
- package/lib/runner-launch-B2zsQ1Dz.js +1529 -0
- package/lib/runner.js +436 -0
- package/lib/types/bin.d.ts +7 -0
- package/lib/types/control-spawn.d.ts +19 -0
- package/lib/types/index.d.ts +52 -0
- package/lib/types/linux-execve.d.ts +9 -0
- package/lib/types/linux-scope.d.ts +91 -0
- package/lib/types/managed-owner.d.ts +36 -0
- package/lib/types/output.d.ts +137 -0
- package/lib/types/process-inspector.d.ts +138 -0
- package/lib/types/runner-launch.d.ts +68 -0
- package/lib/types/runner-protocol.d.ts +111 -0
- package/lib/types/shell-activity.d.ts +37 -0
- package/lib/types/spawn-runner.d.ts +36 -0
- package/lib/types/spawn.d.ts +86 -0
- package/lib/types/terminal.d.ts +80 -0
- package/lib/types/windows-inspector.d.ts +79 -0
- package/lib/types/windows-job.d.ts +30 -0
- package/package.json +60 -4
- package/scripts/ensure-spawn-helper.mjs +16 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { CollectedOutput } from '@qlzt/dsh-subprocess';
|
|
2
|
+
/**
|
|
3
|
+
* Receives one spill failure so the owner can log it through its own logger.
|
|
4
|
+
* Called at most once per collector, after the spill has been discarded and
|
|
5
|
+
* the in-memory tail has kept collecting. A reporter that throws is contained
|
|
6
|
+
* and its failure written to stderr.
|
|
7
|
+
* @param error - the `node:fs` failure from opening or appending the spill file.
|
|
8
|
+
* @param label - the stream label of the collector that failed.
|
|
9
|
+
*/
|
|
10
|
+
export type SpillFailureReporter = (error: unknown, label: string) => void;
|
|
11
|
+
/** Spill storage for one collected stream; `undefined` means tail-only collection. */
|
|
12
|
+
export interface SpillOptions {
|
|
13
|
+
/** Whole-stream byte cap beyond which an incomplete spill is discarded. */
|
|
14
|
+
maxBytes: number;
|
|
15
|
+
/** Private directory receiving the spill file. */
|
|
16
|
+
dir: string;
|
|
17
|
+
/** Owner-side report of a spill open or write failure. */
|
|
18
|
+
onFailure: SpillFailureReporter;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Build the reporter an owner passes as {@link SpillOptions.onFailure}: one
|
|
22
|
+
* error-level log line naming the owner and stream, with the failure appended
|
|
23
|
+
* so its `code`, `syscall`, and `path` reach the log.
|
|
24
|
+
* @param logger - the owner's plugin logger.
|
|
25
|
+
* @param owner - the component named in the line.
|
|
26
|
+
* @returns the reporter.
|
|
27
|
+
*/
|
|
28
|
+
export declare function logSpillFailure(logger: {
|
|
29
|
+
error(message: string, ...detail: unknown[]): void;
|
|
30
|
+
}, owner: string): SpillFailureReporter;
|
|
31
|
+
/** Inputs a managed native process needs before its output streams are bound. */
|
|
32
|
+
export interface ManagedProcessBinding {
|
|
33
|
+
/** Directory receiving spill files. */
|
|
34
|
+
spillDir: string;
|
|
35
|
+
/** Receives a spill open or write failure. */
|
|
36
|
+
onSpillFailure: SpillFailureReporter;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Prepare fallible output storage before starting a managed native process.
|
|
40
|
+
* This is the explicit resolve step for spill inputs: the spill directory
|
|
41
|
+
* defaults to the private per-process directory, and the failure reporter
|
|
42
|
+
* defaults to a stderr line when the caller has no logger.
|
|
43
|
+
* @param internals - optional caller-owned spill directory and failure reporter.
|
|
44
|
+
* @returns binding inputs whose spill directory is ready for use.
|
|
45
|
+
*/
|
|
46
|
+
export declare function prepareManagedProcessBinding(internals?: {
|
|
47
|
+
spillDir?: string;
|
|
48
|
+
onSpillFailure?: SpillFailureReporter;
|
|
49
|
+
}): ManagedProcessBinding;
|
|
50
|
+
/**
|
|
51
|
+
* Collects one stream with a bounded in-memory tail. With spill options, on
|
|
52
|
+
* first overflow a spill file is created and every chunk (including those
|
|
53
|
+
* already collected) is appended there while the full stream remains within
|
|
54
|
+
* the cap; without them, only the in-memory tail is ever retained (the
|
|
55
|
+
* diagnostic-tail shape — a language server's stderr).
|
|
56
|
+
*
|
|
57
|
+
* Spilling is best-effort: a spill open or write failure discards the spill,
|
|
58
|
+
* reports once through {@link SpillOptions.onFailure}, and never interrupts
|
|
59
|
+
* in-memory collection, because `push()` runs inside the stream's `'data'`
|
|
60
|
+
* listener where a thrown error would become an uncaught exception.
|
|
61
|
+
*
|
|
62
|
+
* Tail-keep rationale (pi/OpenCode): errors and final results cluster at the
|
|
63
|
+
* end of command output; the spill file covers the head.
|
|
64
|
+
*/
|
|
65
|
+
export declare class OutputCollector {
|
|
66
|
+
private readonly maxBytes;
|
|
67
|
+
private readonly label;
|
|
68
|
+
private readonly spill;
|
|
69
|
+
private chunks;
|
|
70
|
+
private bytes;
|
|
71
|
+
private dropped;
|
|
72
|
+
private spillFd;
|
|
73
|
+
private spillFile;
|
|
74
|
+
private spillDisabled;
|
|
75
|
+
/** Total bytes ever pushed (not just retained). */
|
|
76
|
+
private total;
|
|
77
|
+
/**
|
|
78
|
+
* @param maxBytes - in-memory tail cap in bytes.
|
|
79
|
+
* @param label - stream label used in spill file names and failure reports.
|
|
80
|
+
* @param spill - spill storage; omit for tail-only collection.
|
|
81
|
+
*/
|
|
82
|
+
constructor(maxBytes: number, label: string, spill: SpillOptions | undefined);
|
|
83
|
+
/**
|
|
84
|
+
* Ingest one stream chunk, counting it toward the whole-stream total. On
|
|
85
|
+
* first overflow of the in-memory cap a spill file is opened (when spilling
|
|
86
|
+
* is enabled) and every chunk (already-collected ones included) is appended
|
|
87
|
+
* there from then on; the in-memory tail then drops whole chunks from its
|
|
88
|
+
* head (or the head of a single over-cap chunk) until it fits the cap again.
|
|
89
|
+
* @param chunk - the raw bytes from one stream 'data' event.
|
|
90
|
+
*/
|
|
91
|
+
push(chunk: Buffer): void;
|
|
92
|
+
/**
|
|
93
|
+
* Open the spill file lazily and append `chunk` (and any prior chunks once).
|
|
94
|
+
* Runs inside the stream's `'data'` listener, so every filesystem failure is
|
|
95
|
+
* contained here: the spill is discarded, reported once, and collection
|
|
96
|
+
* continues with the in-memory tail alone.
|
|
97
|
+
*/
|
|
98
|
+
private spillAll;
|
|
99
|
+
/** Stop spilling and remove the file once it can no longer hold the complete stream. */
|
|
100
|
+
private discardSpill;
|
|
101
|
+
/**
|
|
102
|
+
* Incremental read in whole-stream byte coordinates: returns everything
|
|
103
|
+
* pushed since `fromByte`. When `fromByte` has already slid out of the
|
|
104
|
+
* in-memory tail window, the read is `lossy` — it returns the whole
|
|
105
|
+
* retained tail and the gap is only recoverable from the spill file.
|
|
106
|
+
* @param fromByte - whole-stream offset to resume from (a prior read's `nextOffset`; 0 for the first read).
|
|
107
|
+
* @returns the delta text, the offset for the next read, the `lossy` flag, and the spill path when one was created.
|
|
108
|
+
*/
|
|
109
|
+
readFrom(fromByte: number): {
|
|
110
|
+
text: string;
|
|
111
|
+
nextOffset: number;
|
|
112
|
+
lossy: boolean;
|
|
113
|
+
spillPath?: string;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Copy the retained raw tail with its position in the complete observed stream.
|
|
117
|
+
* @returns independent tail bytes and the total byte count before truncation.
|
|
118
|
+
*/
|
|
119
|
+
snapshot(): {
|
|
120
|
+
bytes: Buffer;
|
|
121
|
+
totalBytes: number;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Close the spill file once the stream has ended. A failed close (delayed
|
|
125
|
+
* writeback fault) stops advertising the spill path — the file may be
|
|
126
|
+
* missing its tail — while every in-memory read keeps working. Idempotent;
|
|
127
|
+
* the spawn path seals both collectors at settlement so reads after exit
|
|
128
|
+
* never point at a still-open file.
|
|
129
|
+
*/
|
|
130
|
+
seal(): void;
|
|
131
|
+
/**
|
|
132
|
+
* Seal the spill file and return the final output.
|
|
133
|
+
* @returns the final collected output: tail text, truncation flag, and the spill path when intact.
|
|
134
|
+
*/
|
|
135
|
+
finalize(): CollectedOutput;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=output.d.ts.map
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/** Platform process-table inspection for terminal readiness, signals, and teardown. */
|
|
2
|
+
import type { SubprocessTerminalSignal } from '@qlzt/dsh-subprocess';
|
|
3
|
+
/** PID plus start identity, preventing teardown escalation after PID reuse. */
|
|
4
|
+
export interface ProcessIdentity {
|
|
5
|
+
pid: number;
|
|
6
|
+
started: string;
|
|
7
|
+
}
|
|
8
|
+
interface FileStatus {
|
|
9
|
+
readonly rdev: number;
|
|
10
|
+
isCharacterDevice(): boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* One observation of the platform process table, shared by every question a
|
|
14
|
+
* single readiness poll or teardown pass asks.
|
|
15
|
+
*
|
|
16
|
+
* The table is read at most once, on the first question that needs it — a
|
|
17
|
+
* `/bin/ps` fork on macOS, a `/proc` walk on Linux, a Toolhelp32 enumeration on
|
|
18
|
+
* Windows. Later questions never re-read it, which is what keeps a poll's cost
|
|
19
|
+
* independent of how many descendants the running command spawned. Windows
|
|
20
|
+
* liveness needs no table at all: wait state is a per-handle question there, so
|
|
21
|
+
* a snapshot asked only for liveness never enumerates.
|
|
22
|
+
*
|
|
23
|
+
* A snapshot answers what the process table showed, which is what batch
|
|
24
|
+
* filtering wants and what signalling must not use: {@link ProcessInspector.isAlive}
|
|
25
|
+
* is the fence a signal takes, because it reads current state instead.
|
|
26
|
+
*/
|
|
27
|
+
export interface ProcessSnapshot {
|
|
28
|
+
/** Whether the process-table scan omitted no unreadable rows; absent means unverified. */
|
|
29
|
+
readonly complete?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Return the root and its transitive descendants as observed, children first.
|
|
32
|
+
* @param rootPid - tree root to descend from.
|
|
33
|
+
* @returns Observed root and descendants, children before parents.
|
|
34
|
+
*/
|
|
35
|
+
tree(rootPid: number): ProcessIdentity[];
|
|
36
|
+
/**
|
|
37
|
+
* Return observed members of one POSIX process session.
|
|
38
|
+
* @param sessionId - POSIX session identifier.
|
|
39
|
+
* @returns Observed session members, empty where the platform's table omits session ids.
|
|
40
|
+
*/
|
|
41
|
+
session(sessionId: number): ProcessIdentity[];
|
|
42
|
+
/**
|
|
43
|
+
* Return whether the exact identity was a non-quiescent process.
|
|
44
|
+
* @param identity - PID plus start identity to match.
|
|
45
|
+
* @returns Whether that exact identity — not merely that PID — was running.
|
|
46
|
+
*/
|
|
47
|
+
alive(identity: ProcessIdentity): boolean;
|
|
48
|
+
}
|
|
49
|
+
/** Injectable OS process operations used by one local PTY session. */
|
|
50
|
+
export interface ProcessInspector {
|
|
51
|
+
foregroundPgid(shellPid: number): number | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* Report whether the foreground group waits on the terminal shell's stdin.
|
|
54
|
+
*
|
|
55
|
+
* @param pgid Foreground process-group identifier.
|
|
56
|
+
* @param shellPid Persistent terminal shell process identifier.
|
|
57
|
+
* @returns Whether a group member is blocked reading the shell's terminal input.
|
|
58
|
+
*/
|
|
59
|
+
isStdinWaiting(pgid: number, shellPid: number): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Read the process table once and answer tree, session, and liveness from it.
|
|
62
|
+
* @returns A process-table observation whose reads are shared.
|
|
63
|
+
* @throws when the platform process table cannot be enumerated.
|
|
64
|
+
*/
|
|
65
|
+
snapshot(): ProcessSnapshot;
|
|
66
|
+
/**
|
|
67
|
+
* Return whether the exact identity is a non-quiescent process right now.
|
|
68
|
+
*
|
|
69
|
+
* Reads the narrowest per-identity source the platform offers rather than a
|
|
70
|
+
* whole table, so a signalling round can re-check every target without
|
|
71
|
+
* paying for a scan. Callers filtering many members at once want
|
|
72
|
+
* {@link ProcessSnapshot.alive} instead.
|
|
73
|
+
*
|
|
74
|
+
* @param identity - PID plus start identity to match.
|
|
75
|
+
* @returns Whether that exact identity — not merely that PID — is running.
|
|
76
|
+
*/
|
|
77
|
+
isAlive(identity: ProcessIdentity): boolean;
|
|
78
|
+
signalGroup(pgid: number, signal: SubprocessTerminalSignal): void;
|
|
79
|
+
/**
|
|
80
|
+
* Signal one exact process identity, fenced against PID reuse.
|
|
81
|
+
*
|
|
82
|
+
* The fence reads current state immediately before the signal. An observation
|
|
83
|
+
* taken earlier in the same round cannot stand in for it: the observation
|
|
84
|
+
* preserves the original PID-to-start-time pairing, so a recycled PID would
|
|
85
|
+
* still match and take a signal meant for the process that exited.
|
|
86
|
+
*
|
|
87
|
+
* @param identity - PID plus start identity to signal.
|
|
88
|
+
* @param signal - termination signal to deliver.
|
|
89
|
+
*/
|
|
90
|
+
signalProcess(identity: ProcessIdentity, signal: 'SIGTERM' | 'SIGKILL'): void;
|
|
91
|
+
}
|
|
92
|
+
/** Testable boundary around filesystem, process-table, and signal syscalls. */
|
|
93
|
+
export interface ProcessInspectorInternals {
|
|
94
|
+
readFile(path: string): string;
|
|
95
|
+
readDir(path: string): string[];
|
|
96
|
+
readLink(path: string): string;
|
|
97
|
+
stat(path: string): FileStatus;
|
|
98
|
+
open(path: string): number;
|
|
99
|
+
read(fd: number, buffer: Buffer, length: number, position: number): number;
|
|
100
|
+
close(fd: number): void;
|
|
101
|
+
exec(file: string, args: string[]): string;
|
|
102
|
+
kill(pid: number, signal: NodeJS.Signals): void;
|
|
103
|
+
}
|
|
104
|
+
interface ProcStat {
|
|
105
|
+
pid: number;
|
|
106
|
+
parentPid: number;
|
|
107
|
+
pgrp: number;
|
|
108
|
+
session: number;
|
|
109
|
+
state: string;
|
|
110
|
+
ttyDevice: number;
|
|
111
|
+
tpgid: number;
|
|
112
|
+
started: string;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Parse fields used from Linux `/proc/<pid>/stat`, including parenthesized comm text.
|
|
116
|
+
* @param text - complete stat line.
|
|
117
|
+
* @returns Parsed identity/group fields, or undefined for malformed input.
|
|
118
|
+
*/
|
|
119
|
+
export declare function parseProcStat(text: string): ProcStat | undefined;
|
|
120
|
+
/**
|
|
121
|
+
* Report whether a Linux process group has an executing member. `false`
|
|
122
|
+
* means the group contains only zombie/dead entries; `undefined` means the
|
|
123
|
+
* process table could not prove either outcome.
|
|
124
|
+
* @param processGroupId - POSIX process-group id to inspect.
|
|
125
|
+
* @param internals - injectable process-table operations.
|
|
126
|
+
* @returns Live-member presence, or `undefined` when unavailable/absent.
|
|
127
|
+
*/
|
|
128
|
+
export declare function linuxProcessGroupHasLiveMembers(processGroupId: number, internals?: ProcessInspectorInternals): boolean | undefined;
|
|
129
|
+
/**
|
|
130
|
+
* Create the supported platform inspector or fail at plugin load.
|
|
131
|
+
* @param platform - target Node platform.
|
|
132
|
+
* @param arch - target CPU architecture for Linux syscall numbers.
|
|
133
|
+
* @param internals - filesystem/process boundary, injectable for deterministic tests.
|
|
134
|
+
* @returns Platform process inspector.
|
|
135
|
+
*/
|
|
136
|
+
export declare function createProcessInspector(platform?: NodeJS.Platform, arch?: NodeJS.Architecture, internals?: ProcessInspectorInternals): ProcessInspector;
|
|
137
|
+
export {};
|
|
138
|
+
//# sourceMappingURL=process-inspector.d.ts.map
|
|
@@ -0,0 +1,68 @@
|
|
|
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 '@qlzt/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; optional control uses fd 7.
|
|
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'> & {
|
|
66
|
+
stdio?: SubprocessSpawnSpec['stdio'];
|
|
67
|
+
}): Record<string, string>;
|
|
68
|
+
//# sourceMappingURL=runner-launch.d.ts.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/** Closed private transports shared by the native subprocess runner. */
|
|
2
|
+
/** Target state restored by the Linux bootstrap after systemd establishes the scope. */
|
|
3
|
+
export interface LinuxLaunchRequest {
|
|
4
|
+
cwd: string;
|
|
5
|
+
env: Record<string, string>;
|
|
6
|
+
control?: 'pipe';
|
|
7
|
+
}
|
|
8
|
+
/** Bounded Node-shaped error fields allowed across a private runner boundary. */
|
|
9
|
+
export interface SerializedRunnerError {
|
|
10
|
+
name: string;
|
|
11
|
+
message: string;
|
|
12
|
+
code?: string;
|
|
13
|
+
syscall?: string;
|
|
14
|
+
path?: string;
|
|
15
|
+
}
|
|
16
|
+
/** A Linux pre-exec failure published beside its consumed request. */
|
|
17
|
+
export type LinuxStartupError = {
|
|
18
|
+
type: 'error';
|
|
19
|
+
error: SerializedRunnerError;
|
|
20
|
+
};
|
|
21
|
+
/** The only parent-to-runner start message on Windows. */
|
|
22
|
+
export interface WindowsStartRequest {
|
|
23
|
+
type: 'start';
|
|
24
|
+
cwd: string;
|
|
25
|
+
env: Record<string, string>;
|
|
26
|
+
control?: 'pipe';
|
|
27
|
+
}
|
|
28
|
+
/** The only parent-to-runner control message on Windows. */
|
|
29
|
+
export interface WindowsTerminateRequest {
|
|
30
|
+
type: 'terminate';
|
|
31
|
+
}
|
|
32
|
+
/** Exactly one direct-result branch is sent by a connected Windows runner. */
|
|
33
|
+
export type WindowsRunnerResult = {
|
|
34
|
+
type: 'target-exit';
|
|
35
|
+
exitCode: number;
|
|
36
|
+
} | {
|
|
37
|
+
type: 'error';
|
|
38
|
+
error: SerializedRunnerError;
|
|
39
|
+
};
|
|
40
|
+
/** Private paths owned by one Linux ordinary or PTY spawn. */
|
|
41
|
+
export interface LinuxLaunchFiles {
|
|
42
|
+
directory: string;
|
|
43
|
+
requestPath: string;
|
|
44
|
+
startupErrorPath: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Create a private 0700 directory and one complete 0600 launch request.
|
|
48
|
+
* @param request - target cwd and complete environment for the bootstrap.
|
|
49
|
+
* @returns private paths owned by this launch.
|
|
50
|
+
*/
|
|
51
|
+
export declare function createLinuxLaunchFiles(request: LinuxLaunchRequest): LinuxLaunchFiles;
|
|
52
|
+
/**
|
|
53
|
+
* Derive the only permitted startup-error path from an absolute request locator.
|
|
54
|
+
* @param requestPath - absolute path to the private launch-request file.
|
|
55
|
+
* @returns validated sibling paths for this launch.
|
|
56
|
+
*/
|
|
57
|
+
export declare function linuxLaunchFilesFromLocator(requestPath: string): LinuxLaunchFiles;
|
|
58
|
+
/**
|
|
59
|
+
* Strictly read and remove a one-shot Linux launch request.
|
|
60
|
+
* @param requestPath - private launch-request path to consume.
|
|
61
|
+
* @returns validated target cwd and environment.
|
|
62
|
+
*/
|
|
63
|
+
export declare function consumeLinuxLaunchRequest(requestPath: string): LinuxLaunchRequest;
|
|
64
|
+
/**
|
|
65
|
+
* Publish one strict 0600 Linux pre-exec error.
|
|
66
|
+
* @param files - private paths for this launch.
|
|
67
|
+
* @param error - bounded spawn or runner failure to publish.
|
|
68
|
+
*/
|
|
69
|
+
export declare function writeLinuxStartupError(files: LinuxLaunchFiles, error: LinuxStartupError): void;
|
|
70
|
+
/**
|
|
71
|
+
* Read the Linux pre-exec error, if the bootstrap published one.
|
|
72
|
+
* @param path - expected startup-error path.
|
|
73
|
+
* @returns the validated failure, or undefined when none was published.
|
|
74
|
+
*/
|
|
75
|
+
export declare function readLinuxStartupError(path: string): LinuxStartupError | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* Strictly parse the single Windows start message.
|
|
78
|
+
* @param value - untrusted IPC payload.
|
|
79
|
+
* @returns validated target start request.
|
|
80
|
+
*/
|
|
81
|
+
export declare function parseWindowsStartRequest(value: unknown): WindowsStartRequest;
|
|
82
|
+
/**
|
|
83
|
+
* Return true only for the exact, payload-free Windows terminate control.
|
|
84
|
+
* @param value - untrusted IPC payload.
|
|
85
|
+
* @returns whether the payload is the exact terminate request.
|
|
86
|
+
*/
|
|
87
|
+
export declare function isWindowsTerminateRequest(value: unknown): value is WindowsTerminateRequest;
|
|
88
|
+
/**
|
|
89
|
+
* Strictly parse one of the two Windows direct-result branches.
|
|
90
|
+
* @param value - untrusted IPC payload.
|
|
91
|
+
* @returns validated direct-result message.
|
|
92
|
+
*/
|
|
93
|
+
export declare function parseWindowsRunnerResult(value: unknown): WindowsRunnerResult;
|
|
94
|
+
/**
|
|
95
|
+
* Convert an unknown failure into the bounded cross-process error record.
|
|
96
|
+
* @param error - failure caught at the process boundary.
|
|
97
|
+
* @returns bounded serializable error fields.
|
|
98
|
+
*/
|
|
99
|
+
export declare function serializeRunnerError(error: unknown): SerializedRunnerError;
|
|
100
|
+
/**
|
|
101
|
+
* Rebuild a Node-shaped Error from a strict runner record.
|
|
102
|
+
* @param serialized - validated bounded error fields.
|
|
103
|
+
* @returns reconstructed Error with supported Node fields.
|
|
104
|
+
*/
|
|
105
|
+
export declare function deserializeRunnerError(serialized: SerializedRunnerError): Error;
|
|
106
|
+
/**
|
|
107
|
+
* Best-effort removal of only the private paths created for this Linux spawn.
|
|
108
|
+
* @param files - exact private paths owned by this launch.
|
|
109
|
+
*/
|
|
110
|
+
export declare function cleanupLinuxLaunchFiles(files: LinuxLaunchFiles): void;
|
|
111
|
+
//# sourceMappingURL=runner-protocol.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { SubprocessTerminalActivity, SubprocessTerminalSpawnSpec } from '@qlzt/dsh-subprocess';
|
|
2
|
+
/** Opt-in startup integration and revision tracking for one ordinary interactive shell. */
|
|
3
|
+
export declare class ShellActivity {
|
|
4
|
+
private readonly directory;
|
|
5
|
+
readonly argv: readonly string[];
|
|
6
|
+
readonly env: Record<string, string>;
|
|
7
|
+
private revision;
|
|
8
|
+
private observed;
|
|
9
|
+
private invalidated;
|
|
10
|
+
private state;
|
|
11
|
+
/**
|
|
12
|
+
* @param directory - private startup and status file directory.
|
|
13
|
+
* @param argv - shell launch preserving supported user startup files.
|
|
14
|
+
* @param env - environment with any temporary startup redirect.
|
|
15
|
+
*/
|
|
16
|
+
constructor(directory: string, argv: readonly string[], env: Record<string, string>);
|
|
17
|
+
/** Invalidate prompt evidence before delivering input or a foreground signal. */
|
|
18
|
+
invalidate(): void;
|
|
19
|
+
/**
|
|
20
|
+
* Read the latest top-level shell transition, fenced against input since that transition.
|
|
21
|
+
* @param pid - original shell process id.
|
|
22
|
+
* @returns lifecycle evidence; process ownership must be checked separately.
|
|
23
|
+
*/
|
|
24
|
+
inspect(pid: number): SubprocessTerminalActivity;
|
|
25
|
+
/** Remove private startup and status files after process quiescence. */
|
|
26
|
+
dispose(): void;
|
|
27
|
+
private read;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Prepare optional Bash or Zsh integration for a plain, non-login interactive launch.
|
|
31
|
+
* @param spec - terminal request; custom arguments and wrapped executables remain unmodified.
|
|
32
|
+
* @param env - scrubbed target environment.
|
|
33
|
+
* @param platform - execution platform.
|
|
34
|
+
* @returns private integration, or undefined for unsupported launches.
|
|
35
|
+
*/
|
|
36
|
+
export declare function prepareShellActivity(spec: SubprocessTerminalSpawnSpec, env: Record<string, string>, platform: NodeJS.Platform): ShellActivity | undefined;
|
|
37
|
+
//# sourceMappingURL=shell-activity.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** One-shot Linux exec bootstrap and Windows Job-owning subprocess runner. */
|
|
2
|
+
import { closeHandleChecked, isJobEmpty, pollProcessExit, spawnCurrentTokenJobProcess, terminateJob } from '@qlzt/dsh-win32-process';
|
|
3
|
+
import type { CurrentTokenProcessBindings } from '@qlzt/dsh-win32-process';
|
|
4
|
+
import { resolveWindowsExecutable } from './runner-launch.ts';
|
|
5
|
+
type RunnerHost = Pick<NodeJS.Process, 'env' | 'exitCode' | 'connected' | 'cwd' | 'chdir' | 'on' | 'off' | 'once' | 'disconnect'> & {
|
|
6
|
+
send?: NodeJS.Process['send'];
|
|
7
|
+
};
|
|
8
|
+
/** Injectable operations used by the protocol-owner tests. */
|
|
9
|
+
export interface SpawnRunnerInternals {
|
|
10
|
+
execve(file: string, argv: string[], env: Record<string, string>, control?: 'pipe'): never;
|
|
11
|
+
loadWin32ProcessBindings(): CurrentTokenProcessBindings;
|
|
12
|
+
spawnCurrentTokenJobProcess: typeof spawnCurrentTokenJobProcess;
|
|
13
|
+
closeFileDescriptor(fileDescriptor: number): void;
|
|
14
|
+
resolveWindowsExecutable: typeof resolveWindowsExecutable;
|
|
15
|
+
pollProcessExit: typeof pollProcessExit;
|
|
16
|
+
isJobEmpty: typeof isJobEmpty;
|
|
17
|
+
terminateJob: typeof terminateJob;
|
|
18
|
+
closeHandleChecked: typeof closeHandleChecked;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Execute the selected Linux bootstrap or Windows Job runner.
|
|
22
|
+
* @param selection - Windows sentinel or Linux launch-request locator.
|
|
23
|
+
* @param argv - private runner arguments beginning with the target delimiter.
|
|
24
|
+
* @param host - process transport and lifecycle host.
|
|
25
|
+
* @param internals - native and filesystem operations used by the runner.
|
|
26
|
+
*/
|
|
27
|
+
export declare function runSpawnRunner(selection: string, argv: readonly string[], host?: RunnerHost, internals?: SpawnRunnerInternals): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Best-effort reporting for failures before the selected runner established its owner.
|
|
30
|
+
* @param selection - Windows sentinel, Linux launch-request locator, or no selection.
|
|
31
|
+
* @param error - failure raised before normal runner settlement.
|
|
32
|
+
* @param host - process transport and lifecycle host.
|
|
33
|
+
*/
|
|
34
|
+
export declare function reportSpawnRunnerFailure(selection: string | undefined, error: unknown, host?: RunnerHost): Promise<void>;
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=spawn-runner.d.ts.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process plumbing for the local subprocess service: ordinary process launch
|
|
3
|
+
* with per-stream stdio dispositions, tail-keep collection with spill
|
|
4
|
+
* files, provider-owned range signalling, and common termination scheduling.
|
|
5
|
+
* POSIX owners stage TERM before KILL; Windows owners terminate immediately.
|
|
6
|
+
* This layer reacts to an abort signal; callers own deadlines, teardown
|
|
7
|
+
* ladders, and cause classification.
|
|
8
|
+
* @module dsh-subprocess-local/spawn
|
|
9
|
+
*/
|
|
10
|
+
import { type ChildProcess, type SpawnOptions } from 'node:child_process';
|
|
11
|
+
import type { SubprocessHandle, SubprocessSpawnSpec } from '@qlzt/dsh-subprocess';
|
|
12
|
+
import type { ManagedProcessLaunch } from './managed-owner.ts';
|
|
13
|
+
import { type SpillFailureReporter } from './output.ts';
|
|
14
|
+
type SpawnProcess = (program: string, args: readonly string[], options: SpawnOptions) => ChildProcess;
|
|
15
|
+
/**
|
|
16
|
+
* Build a child environment: explicit caller entries override the scrubbed
|
|
17
|
+
* parent base using the target platform's environment-key semantics. A string
|
|
18
|
+
* deliberately restores or overrides an entry; an explicit `undefined`
|
|
19
|
+
* tombstone removes an ordinary ambient entry.
|
|
20
|
+
* @param extra - explicit caller entries and tombstones, merged after the scrub.
|
|
21
|
+
* @returns the environment to hand to `spawn` for the child process.
|
|
22
|
+
*/
|
|
23
|
+
export declare function childEnv(extra?: Readonly<NodeJS.ProcessEnv>): NodeJS.ProcessEnv;
|
|
24
|
+
/** Injectable process, spill, and platform operations. */
|
|
25
|
+
export interface SpawnInternals {
|
|
26
|
+
/** Process spawner (defaults to `node:child_process` `spawn`). */
|
|
27
|
+
spawn?: SpawnProcess;
|
|
28
|
+
/** Directory for spill files (defaults to the OS temp dir). */
|
|
29
|
+
spillDir?: string;
|
|
30
|
+
/** Receives spill open/write failures; the runtime supplies its plugin logger, bare callers get a stderr line. */
|
|
31
|
+
onSpillFailure?: SpillFailureReporter;
|
|
32
|
+
/** Windows tree-termination runner (defaults to `taskkill /PID <pid> /T /F`). */
|
|
33
|
+
taskkill?: (pid: number) => void;
|
|
34
|
+
/** Host platform override for signalling decisions. */
|
|
35
|
+
platform?: NodeJS.Platform;
|
|
36
|
+
/** Linux process-group member probe (defaults to `/proc` inspection). */
|
|
37
|
+
linuxProcessGroupHasLiveMembers?: (processGroupId: number) => boolean | undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Local-only synchronous final termination used by the owning service during
|
|
41
|
+
* host exit and as the last fallback after failed normal disposal. It is
|
|
42
|
+
* intentionally absent from the public subprocess seam.
|
|
43
|
+
*/
|
|
44
|
+
export interface LocalSubprocessHandle extends SubprocessHandle {
|
|
45
|
+
/** Force-terminate the current tree synchronously without starting timers or waits. */
|
|
46
|
+
terminateForHostExit(): void;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Send `sig` to a detached POSIX process group. Never throws: delivery races
|
|
50
|
+
* process exit and may run in a timer callback, so failures are contained and
|
|
51
|
+
* a missing pid is a no-op.
|
|
52
|
+
* @param pid - the group leader's pid, when the spawn published one.
|
|
53
|
+
* @param sig - the signal to deliver to the whole group.
|
|
54
|
+
*/
|
|
55
|
+
export declare function killGroup(pid: number | undefined, sig: NodeJS.Signals): void;
|
|
56
|
+
/**
|
|
57
|
+
* Terminate one Windows process tree with `taskkill /T /F`. Contained like
|
|
58
|
+
* POSIX group signalling — delivery races tree exit, so an absent tree, a
|
|
59
|
+
* nonzero status, or a missing taskkill binary must not break idempotent
|
|
60
|
+
* teardown.
|
|
61
|
+
* @param pid - root process id, when the spawn published one.
|
|
62
|
+
*/
|
|
63
|
+
export declare function taskkillProcessTree(pid: number | undefined): void;
|
|
64
|
+
/**
|
|
65
|
+
* Validate the synchronous portion of one ordinary spawn request.
|
|
66
|
+
* @param spec - exact target request.
|
|
67
|
+
* @throws when grace, cancellation, or argv is invalid before launch.
|
|
68
|
+
*/
|
|
69
|
+
export declare function validateSubprocessSpec(spec: SubprocessSpawnSpec): void;
|
|
70
|
+
/**
|
|
71
|
+
* Bind platform launch facts to the existing stdio, outcome, abort, and termination lifecycle.
|
|
72
|
+
* @param spec - fully resolved argv, cwd, stdio, grace, cancellation, environment.
|
|
73
|
+
* @param launch - platform streams, direct outcome, and managed-range owner.
|
|
74
|
+
* @param internals - spill-directory override and spill failure reporter.
|
|
75
|
+
* @returns live subprocess handle.
|
|
76
|
+
*/
|
|
77
|
+
export declare function bindManagedProcess(spec: SubprocessSpawnSpec, launch: ManagedProcessLaunch, internals?: Pick<SpawnInternals, 'spillDir' | 'onSpillFailure'>): LocalSubprocessHandle;
|
|
78
|
+
/**
|
|
79
|
+
* Spawn one detached PGID/taskkill fallback and bind the common lifecycle.
|
|
80
|
+
* @param spec - fully resolved argv, cwd, stdio, grace, cancellation, environment.
|
|
81
|
+
* @param internals - test-only spill-directory, platform, and taskkill overrides.
|
|
82
|
+
* @returns live subprocess handle.
|
|
83
|
+
*/
|
|
84
|
+
export declare function spawnSubprocess(spec: SubprocessSpawnSpec, internals?: SpawnInternals): LocalSubprocessHandle;
|
|
85
|
+
export {};
|
|
86
|
+
//# sourceMappingURL=spawn.d.ts.map
|