@orkestrel/process 0.0.2 → 0.0.4

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.
@@ -1,6 +1,14 @@
1
1
  import { Buffer as Buffer_2 } from 'node:buffer';
2
+ import { DetachOptions } from '@orkestrel/process';
2
3
  import { EmitterInterface } from '@orkestrel/emitter';
4
+ import { ExecutableOptions } from '@orkestrel/process';
5
+ import { ExecuteInput } from '@orkestrel/process';
6
+ import { ExecuteOptions } from '@orkestrel/process';
7
+ import { ExecuteResult } from '@orkestrel/process';
8
+ import { ExecuteResult as ExecuteResult_2 } from '@orkestrel/process';
9
+ import { ExecuteSyncOptions } from '@orkestrel/process';
3
10
  import { ProcessCommand } from '@orkestrel/process';
11
+ import { ProcessCommand as ProcessCommand_2 } from '@orkestrel/process';
4
12
  import { ProcessEventMap } from '@orkestrel/process';
5
13
  import { ProcessExit } from '@orkestrel/process';
6
14
  import { ProcessInterface } from '@orkestrel/process';
@@ -8,45 +16,106 @@ import { ProcessManagerEventMap } from '@orkestrel/process';
8
16
  import { ProcessManagerInterface } from '@orkestrel/process';
9
17
  import { ProcessManagerOptions } from '@orkestrel/process';
10
18
  import { ProcessOptions } from '@orkestrel/process';
11
- import { RunOptions } from '@orkestrel/process';
12
- import { RunResult } from '@orkestrel/process';
19
+ import { SpawnInput } from '@orkestrel/process';
13
20
 
14
21
  /**
15
- * Builds one settled {@link RunResult} from a completed one-shot run's captured bytes and exit.
22
+ * Builds the executable candidates an explicit platform would search.
16
23
  *
17
24
  * @remarks
18
- * `failed` is derived: a run failed when it timed out, was ended by a signal, or exited with a
19
- * code other than `0` — a `null` code from a spawn fault is therefore a failure. Both output
20
- * buffers are byte-bounded by `limit` before decoding.
21
- *
22
- * @param command - The diagnostic command line
23
- * @param stdout - The captured standard output bytes
24
- * @param stderr - The captured standard error bytes
25
- * @param code - The exit code, or `null` when a signal or spawn fault ended the run
26
- * @param signal - The terminating signal, or `null`
27
- * @param timedOut - If `true`, the run's own timeout elapsed; if `false`, it did not
28
- * @param limit - The maximum captured bytes for each stream
25
+ * Windows alone needs this list: the host searches the working directory before `PATH` and applies
26
+ * `PATHEXT`, and Node reproduces neither for a direct spawn. Within each searched directory the
27
+ * literal name is tried first and each `PATHEXT` candidate after it, whether or not the name already
28
+ * carries an extension — so `report.txt` resolves to a `report.txt` file where one exists and to
29
+ * `report.txt.cmd` where none does. The lookup reads the child's effective environment, so an
30
+ * overridden `PATH` selects the executable the child would have found. A POSIX host resolves the
31
+ * file itself, so the candidate list there is empty. An appended extension is spelled the way
32
+ * `PATHEXT` spells it rather than the way the directory entry does, which the case-insensitive host
33
+ * treats as the same file. A non-Windows platform returns an empty list because `execvp` performs
34
+ * its own lookup.
35
+ *
36
+ * @param file - The command executable name or path
37
+ * @param workspace - The directory searched first
38
+ * @param environment - The child's effective environment
39
+ * @param platform - The platform whose lookup rules apply
40
+ * @returns The ordered absolute candidate paths
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * buildExecutableCandidates('git', 'C:\\work', { PATH: 'C:\\bin' }, 'win32')
45
+ * ```
46
+ */
47
+ export declare function buildExecutableCandidates(file: string, workspace: string, environment: Readonly<Record<string, string | undefined>>, platform: NodeJS.Platform): readonly string[];
48
+
49
+ /**
50
+ * Builds one settled {@link ExecuteResult} from a completed run's captured bytes and terminal facts.
51
+ *
52
+ * @remarks
53
+ * `failed` is derived: a run failed when it timed out, was aborted, ended on a host fault, was ended
54
+ * by a signal, or exited with a code other than `0` — a `null` code from a spawn fault is therefore
55
+ * a failure. Each byte field is bounded by `limit` on a code-point boundary here, which is the one
56
+ * place the captured bytes are decoded.
57
+ *
58
+ * @param input - The captured bytes, terminal state, and capture limit
29
59
  * @returns The frozen run outcome
30
60
  *
31
61
  * @example
32
62
  * ```ts
33
- * buildRunResult('git status', Buffer.from('ok'), Buffer.alloc(0), 0, null, false, 1024)
63
+ * buildExecuteResult({
64
+ * command: 'git status',
65
+ * stdout: Buffer.from('ok'),
66
+ * stderr: Buffer.alloc(0),
67
+ * code: 0,
68
+ * signal: null,
69
+ * expired: false,
70
+ * aborted: false,
71
+ * truncated: false,
72
+ * limit: 1_024,
73
+ * }).failed // false
34
74
  * ```
35
75
  */
36
- export declare function buildRunResult(command: string, stdout: Buffer_2, stderr: Buffer_2, code: number | null, signal: string | null, timedOut: boolean, limit: number): RunResult;
76
+ export declare function buildExecuteResult(input: ExecuteInput): ExecuteResult;
37
77
 
38
78
  /**
39
- * Renders one command into its diagnostic command line.
79
+ * Builds a spawn form from a resolved file and an explicit platform.
80
+ *
81
+ * @remarks
82
+ * A resolved `.cmd` or `.bat` script cannot be spawned directly **on Windows**, so it runs there
83
+ * through an explicitly quoted `cmd.exe /d /s /c` command line with the argument vector passed
84
+ * verbatim. No path uses a shell, so a metacharacter in an argument is never interpreted. `cmd.exe`
85
+ * expands `%NAME%` before it parses quotes, so no quoting can carry a percent sign through to a
86
+ * Windows batch target: an argument carrying one is refused there rather than silently rewritten.
87
+ * The whole batch path is Windows-only. A POSIX host has no `cmd.exe` and no restriction on spawning
88
+ * a file directly, so a target named `worker.cmd` spawns directly there and receives a percent sign
89
+ * as literal text.
90
+ *
91
+ * @param command - The executable and its argument vector
92
+ * @param file - The resolved executable file
93
+ * @param environment - The child's effective environment
94
+ * @param platform - The platform whose batch rules apply
95
+ * @returns The file, argument vector, and verbatim flag to spawn with
96
+ * @throws A {@link ProcessError} coded `invalid` when `platform` is `win32`, the resolved target is a batch script, and an argument carries a percent sign
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * buildPlatformSpawn({ file: 'node', arguments: ['--version'] }, 'node', {}, 'linux').verbatim // false
101
+ * ```
102
+ */
103
+ export declare function buildPlatformSpawn(command: ProcessCommand, file: string, environment: Readonly<Record<string, string | undefined>>, platform: NodeJS.Platform): SpawnInput;
104
+
105
+ /**
106
+ * Builds the resolved spawn form of one command for the current host.
40
107
  *
41
108
  * @param command - The executable and its argument vector
42
- * @returns The space-joined command line, for a {@link RunResult} and error messages
109
+ * @param options - The directory searched first and the child's effective environment
110
+ * @returns The file, argument vector, and verbatim flag to spawn with
111
+ * @throws A {@link ProcessError} coded `invalid` when the host is Windows, the resolved target is a batch script, and an argument carries a percent sign
43
112
  *
44
113
  * @example
45
114
  * ```ts
46
- * commandLine({ file: 'git', arguments: ['status'] }) // 'git status'
115
+ * buildSpawn({ file: 'node', arguments: ['--version'] }).verbatim // false
47
116
  * ```
48
117
  */
49
- export declare function commandLine(command: ProcessCommand): string;
118
+ export declare function buildSpawn(command: ProcessCommand, options?: ExecutableOptions): SpawnInput;
50
119
 
51
120
  /**
52
121
  * Creates one supervised child process.
@@ -82,16 +151,144 @@ export declare function createProcess(options: ProcessOptions): ProcessInterface
82
151
  */
83
152
  export declare function createProcessManager(options?: ProcessManagerOptions): ProcessManagerInterface;
84
153
 
154
+ /**
155
+ * Spawns one command as a detached process and returns without waiting for it.
156
+ *
157
+ * @remarks
158
+ * The child owns no stdio and is unreferenced, so it outlives this process and nothing here observes
159
+ * its outcome. The error listener is attached before the child is unreferenced, so a post-spawn host
160
+ * fault is swallowed rather than crashing the caller; an invalid command is refused before anything
161
+ * is spawned.
162
+ *
163
+ * @param command - The executable, arguments, and optional environment
164
+ * @param options - The working directory the detached child starts in
165
+ * @returns Nothing
166
+ * @throws A {@link ProcessError} coded `invalid` when the working directory or a command string is malformed
167
+ *
168
+ * @example
169
+ * ```ts
170
+ * detach({ file: 'node', arguments: ['daemon.js'] }, { workspace: process.cwd() })
171
+ * ```
172
+ */
173
+ export declare function detach(command: ProcessCommand_2, options?: DetachOptions): void;
174
+
175
+ /**
176
+ * Runs one command to completion, buffering its output, and settles with the outcome.
177
+ *
178
+ * @remarks
179
+ * The executable is resolved through {@link buildSpawn}, so no run uses a shell. On a POSIX host the
180
+ * child is detached, which is what lets its whole process group be terminated. Standard output and
181
+ * error are each byte-bounded by `limit` (default {@link PROCESS_OUTPUT}) and `truncated` reports
182
+ * whether either stream exceeded it. A positive `timeout` and an aborting `signal` both terminate the
183
+ * child through the same bounded stop, and only the earliest is recorded: a timeout reports
184
+ * `expired` and an abort reports `aborted`, never both. After termination the outcome is awaited for
185
+ * a bounded window, so a descendant holding the child's stdio cannot keep the run pending. That bound
186
+ * covers a terminated run alone: a run with no `timeout` and no `signal` settles on stdio completion
187
+ * rather than on process exit, so a descendant that inherited the child's stdio holds the run open
188
+ * after the child itself has gone. Give such a run a `timeout`. The child's `environment` merges over
189
+ * the parent unless the command is `isolated`, then `options.environment` on top, and `options.input`
190
+ * overrides `command.input`. Unless `strict` is `false`, a failed run rejects with a
191
+ * {@link createExecuteError} carrying the {@link ExecuteResult}. An invalid option or command string rejects
192
+ * before the child is spawned, because an async function cannot throw synchronously.
193
+ *
194
+ * @param command - The executable, arguments, and optional environment and input
195
+ * @param options - Working directory, timeout, grace, signal, capture limit, and failure delivery
196
+ * @returns The settled run outcome
197
+ * @throws A {@link ProcessError} coded `invalid` for a malformed option, command string, or batch-bound argument, or one carrying the {@link ExecuteResult} when the run failed and `strict` is not `false`
198
+ *
199
+ * @example
200
+ * ```ts
201
+ * const result = await execute({ file: 'git', arguments: ['status'] }, { workspace: process.cwd() })
202
+ * ```
203
+ */
204
+ export declare function execute(command: ProcessCommand_2, options?: ExecuteOptions): Promise<ExecuteResult_2>;
205
+
206
+ /**
207
+ * Runs one command to completion synchronously, buffering its output, and returns the outcome.
208
+ *
209
+ * @remarks
210
+ * The synchronous counterpart of {@link execute}, spawned through the same resolver and never through a
211
+ * shell. The host offers no cooperative termination window and no in-flight cancellation, so this
212
+ * contract carries neither. A positive `timeout` ends only the root process and can leave
213
+ * descendants running; use {@link execute} or {@link Process} when timeout must terminate the tree. A
214
+ * timeout and an output overflow both end the root with `SIGKILL`: an overflow reports `truncated`
215
+ * and `failed` together and trims the partial output to `limit`, where {@link execute} keeps reading and
216
+ * reports `truncated` without failing. The environment and input follow the same merge as
217
+ * {@link execute}. Unless `strict` is `false`, a failed run throws a {@link createExecuteError}
218
+ * carrying the {@link ExecuteResult}.
219
+ *
220
+ * @param command - The executable, arguments, and optional environment and input
221
+ * @param options - Working directory, timeout, capture limit, and failure delivery
222
+ * @returns The run outcome
223
+ * @throws A {@link ProcessError} coded `invalid` for a malformed option, command string, or batch-bound argument, or one carrying the {@link ExecuteResult} when the run failed and `strict` is not `false`
224
+ *
225
+ * @example
226
+ * ```ts
227
+ * const result = executeSync({ file: 'git', arguments: ['--version'] }, { strict: false })
228
+ * ```
229
+ */
230
+ export declare function executeSync(command: ProcessCommand_2, options?: ExecuteSyncOptions): ExecuteResult_2;
231
+
232
+ /**
233
+ * Renders one command into its diagnostic command line.
234
+ *
235
+ * @param command - The executable and its argument vector
236
+ * @returns The space-joined command line, for an {@link ExecuteResult} and error messages
237
+ *
238
+ * @example
239
+ * ```ts
240
+ * formatCommand({ file: 'git', arguments: ['status'] }) // 'git status'
241
+ * ```
242
+ */
243
+ export declare function formatCommand(command: ProcessCommand): string;
244
+
245
+ /**
246
+ * Checks whether a child process has reached its native exit.
247
+ *
248
+ * @remarks
249
+ * The host sets exactly one of these fields when the process ends, so together they are the
250
+ * authoritative liveness answer. Reading them before each signal is what keeps a signal from being
251
+ * initiated against a pid the host may have reused.
252
+ *
253
+ * @param child - The child boundary
254
+ * @returns True when the process has exited; false while it is live
255
+ *
256
+ * @example
257
+ * ```ts
258
+ * isExited({ exitCode: 0, signalCode: null }) // true
259
+ * ```
260
+ */
261
+ export declare function isExited(child: Pick<ProcessChild, 'exitCode' | 'signalCode'>): boolean;
262
+
263
+ /**
264
+ * Checks whether a path names a regular file.
265
+ *
266
+ * @remarks
267
+ * Existence alone does not make a lookup hit: a directory named `git` on `PATH` is not the `git`
268
+ * command. An unreadable or malformed path reports `false` rather than throwing.
269
+ *
270
+ * @param target - The absolute or relative path to inspect
271
+ * @returns True when the path resolves to a regular file; false otherwise
272
+ *
273
+ * @example
274
+ * ```ts
275
+ * isFile(process.execPath) // true
276
+ * ```
277
+ */
278
+ export declare function isFile(target: string): boolean;
279
+
85
280
  /**
86
281
  * Signals one owned child process, or its detached process group on a POSIX host.
87
282
  *
88
283
  * @remarks
89
284
  * On a POSIX host a child spawned `detached` leads its own process group, so signalling the
90
- * negated pid reaches the whole tree. On Windows, or when no pid is available, the signal goes to
91
- * the child directly. A throw during signalling is swallowed: the process can exit and win the
92
- * signal race, and the child's close event remains the authoritative terminal state.
285
+ * negated pid reaches the whole tree. When no group owns the pid and the host reports `ESRCH`, the
286
+ * signal falls back to the child directly. On Windows, or when no pid is available, the signal also
287
+ * goes to the child directly. Every other throw during signalling is swallowed: the process can
288
+ * exit between the caller's liveness check and this call, and the child's native exit remains the
289
+ * authoritative terminal state.
93
290
  *
94
- * @param child - The child process signal boundary
291
+ * @param child - The child boundary
95
292
  * @param signal - The signal to deliver
96
293
  * @returns Nothing
97
294
  *
@@ -100,40 +297,87 @@ export declare function createProcessManager(options?: ProcessManagerOptions): P
100
297
  * killProcess(child, 'SIGTERM')
101
298
  * ```
102
299
  */
103
- export declare function killProcess(child: {
104
- readonly pid?: number | undefined;
105
- kill(signal: NodeJS.Signals): boolean;
106
- }, signal: NodeJS.Signals): void;
300
+ export declare function killProcess(child: Pick<ProcessChild, 'pid' | 'kill'>, signal: NodeJS.Signals): void;
301
+
302
+ /**
303
+ * Kills one Windows process tree through `taskkill`.
304
+ *
305
+ * @remarks
306
+ * Windows has no process group a signal can reach, so the whole tree is ended by the host utility,
307
+ * addressed by its absolute `System32` path so a `PATH` override cannot substitute another program.
308
+ * The call is bounded: it is killed itself when `timeout` elapses, which is what keeps a termination
309
+ * from waiting on the host indefinitely. A tree is discoverable only while its root lives, so a
310
+ * descendant that outlives the root is beyond this mechanism.
311
+ *
312
+ * @param pid - The root process id of the tree to end
313
+ * @param timeout - The milliseconds the utility is given before it is killed
314
+ * @returns True when the utility reported success; false when it failed, was unavailable, or was cut off
315
+ *
316
+ * @example
317
+ * ```ts
318
+ * await killTree(child.pid ?? 0, 5_000)
319
+ * ```
320
+ */
321
+ export declare function killTree(pid: number, timeout: number): Promise<boolean>;
322
+
323
+ /**
324
+ * Merges environment overrides into the environment one child receives on the current host.
325
+ *
326
+ * @param isolated - If `true`, the parent environment is excluded; if `false`, the overrides layer over it
327
+ * @param base - The command's own environment overrides
328
+ * @param override - Per-invocation overrides applied last
329
+ * @returns The environment for a spawn, carrying no unset key
330
+ *
331
+ * @example
332
+ * ```ts
333
+ * mergeEnvironment(true, { TOKEN: 'a' }) // { TOKEN: 'a' }
334
+ * ```
335
+ */
336
+ export declare function mergeEnvironment(isolated: boolean, base?: Readonly<Record<string, string | undefined>>, override?: Readonly<Record<string, string | undefined>>): NodeJS.ProcessEnv;
107
337
 
108
338
  /**
109
- * Merges environment overrides over the current process environment.
339
+ * Merges environment layers under an explicit platform's key rules.
110
340
  *
111
341
  * @remarks
112
- * Later maps override earlier ones and an `undefined` value unsets a key. The base is the parent
113
- * process environment, so a child inherits it unless a key is overridden or unset.
342
+ * Later maps override earlier ones and an `undefined` value unsets a key. For `win32` the keys fold
343
+ * case-insensitively and the last writer wins, so `PATH` followed by `Path` yields one variable
344
+ * rather than two the host would resolve unpredictably.
114
345
  *
346
+ * @param platform - The platform whose key rules apply
347
+ * @param parent - The parent environment layer
348
+ * @param isolated - If `true`, the parent environment is excluded; if `false`, the overrides layer over it
115
349
  * @param base - The command's own environment overrides
116
350
  * @param override - Per-invocation overrides applied last
117
- * @returns The merged environment for a spawn
351
+ * @returns The environment for a spawn, carrying no unset key
118
352
  *
119
353
  * @example
120
354
  * ```ts
121
- * mergeEnvironment({ TOKEN: 'a' }, { TOKEN: undefined }) // TOKEN unset
355
+ * mergePlatformEnvironment('linux', {}, false, { TOKEN: 'a' }, { TOKEN: undefined }) // TOKEN unset
122
356
  * ```
123
357
  */
124
- export declare function mergeEnvironment(base?: Readonly<Record<string, string | undefined>>, override?: Readonly<Record<string, string | undefined>>): NodeJS.ProcessEnv;
358
+ export declare function mergePlatformEnvironment(platform: NodeJS.Platform, parent: Readonly<Record<string, string | undefined>>, isolated: boolean, base?: Readonly<Record<string, string | undefined>>, override?: Readonly<Record<string, string | undefined>>): NodeJS.ProcessEnv;
125
359
 
126
360
  /**
127
- * One supervised child process with eagerly framed output and bounded termination.
361
+ * One supervised child process with framed output, a bounded backlog, and bounded termination.
128
362
  *
129
363
  * @remarks
130
- * Standard output is drained eagerly through `readline`, so `exit` resolves and a late consumer of
131
- * `lines` still receives every framed line, including a final line written without a trailing
132
- * newline. Standard error is decoded and forwarded live as the `stderr` event while a byte-bounded
133
- * raw tail is retained as `evidence`. The typed `emitter` also carries the child `error` cause on a
134
- * spawn fault and the terminal `exit`, alongside the `exit` promise. `stop` terminates the child
135
- * through `SIGTERM`, then `SIGKILL` after
136
- * `grace`, and is idempotent; `destroy` stops the child and destroys the emitter last.
364
+ * Standard output is framed through `readline` into a single-consumer stream: each line goes to
365
+ * exactly one waiting iterator, so concurrent iterators split the output rather than each receiving
366
+ * all of it. While no consumer has ever requested an iterator, stdout keeps draining so `exit` still
367
+ * resolves, and retention stops at the `backlog` mark: a
368
+ * consumer attaching later receives the retained head, a gap, then the live stream. After an iterator
369
+ * has been requested, stdout pauses at the mark and resumes at half of it, so that consumer loses
370
+ * nothing before termination and the child feels real backpressure. The mark is soft — the ordinary backlog can
371
+ * overshoot it by the line that crossed it plus the rest of its delivered chunk. Termination never
372
+ * reapplies backpressure, and retained lines are capped at twice `backlog`; later lines are dropped
373
+ * and `truncated` reports the omission. Standard error is decoded and forwarded live as the `stderr`
374
+ * event while a byte-bounded raw tail is retained as `evidence`. The typed `emitter` also carries the
375
+ * child `error` cause on a spawn fault and the terminal `exit`, alongside the `exit` promise. `pid`,
376
+ * `code`, and `signal` read the spawned child directly, so the native exit is readable before the
377
+ * `exit` promise settles on stdio close. `stop` ends the whole tree and reports whether the native
378
+ * exit was observed; `destroy` stops the child, destroys stdin so every pending `send` settles, and
379
+ * destroys the emitter last. `destroy` resolving does not imply the child's stdio has closed, so
380
+ * `exit` and `lines` can still be outstanding when a descendant holds an inherited pipe.
137
381
  *
138
382
  * @example
139
383
  * ```ts
@@ -142,7 +386,6 @@ export declare function mergeEnvironment(base?: Readonly<Record<string, string |
142
386
  * const child = new Process({
143
387
  * command: { file: 'node', arguments: ['--version'] },
144
388
  * workspace: process.cwd(),
145
- * grace: 5000,
146
389
  * })
147
390
  * const exit = await child.exit
148
391
  * await child.destroy()
@@ -151,48 +394,134 @@ export declare function mergeEnvironment(base?: Readonly<Record<string, string |
151
394
  export declare class Process implements ProcessInterface {
152
395
  #private;
153
396
  /**
154
- * Spawn one child process and begin eager stream capture.
397
+ * Spawn one child process and begin stream capture.
155
398
  *
156
- * @param options - Command, workspace, termination, evidence, stdin, and observation settings
399
+ * @param options - Command, workspace, termination, capture, stdin, and observation settings
400
+ * @throws A {@link ProcessError} coded `invalid` when an option or command string is malformed
157
401
  */
158
402
  constructor(options: ProcessOptions);
403
+ /** The host process id, fixed when construction returns, or `undefined` when the spawn produced none. */
404
+ get pid(): number | undefined;
405
+ /** The exit code the host recorded, or `null` while the child has not exited and when a signal ended it. */
406
+ get code(): number | null;
407
+ /** The terminating signal name the host recorded, or `null` while the child has not exited and when it exited on its own. */
408
+ get signal(): string | null;
159
409
  /** The typed lifecycle observation surface. */
160
410
  get emitter(): EmitterInterface<ProcessEventMap>;
161
- /** The eagerly captured stdout lines, in arrival order, ending when the child's stdout closes. */
411
+ /** The captured stdout lines, in arrival order, ending when the child's stdout closes. */
162
412
  get lines(): AsyncIterable<string>;
163
413
  /** The decoded byte-bounded stderr tail. */
164
414
  get evidence(): string;
415
+ /** True when the `lines` stream omitted output after a retention bound was reached. */
416
+ get truncated(): boolean;
165
417
  /** The terminal child state, observed once from the close event. */
166
418
  get exit(): Promise<ProcessExit>;
167
419
  /**
168
420
  * Write one line to the open standard-input channel.
169
421
  *
422
+ * @remarks
423
+ * Never rejects. The promise settles when the host reports the line handled, so a line written to
424
+ * a child that is not reading stays pending until the child drains it or the channel closes. Every
425
+ * terminal teardown path destroys stdin, so a pending write always settles by teardown; a caller
426
+ * that needs its own deadline arms a timer and calls `stop`.
427
+ *
170
428
  * @param text - The line text without its trailing newline
171
- * @returns True when the channel accepted the line without backpressure; false otherwise
429
+ * @returns True when the line reached the host without error; false when the channel was closed, destroyed, ended, or the write failed
172
430
  */
173
- send(text: string): boolean;
431
+ send(text: string): Promise<boolean>;
174
432
  /**
175
433
  * Terminate the child process tree and await its observed exit.
176
434
  *
177
- * @returns A promise that resolves after bounded termination
435
+ * @remarks
436
+ * Never rejects, and every call shares one termination.
437
+ *
438
+ * @returns True when the child's native exit was observed; false when the confirmation deadline elapsed without it
178
439
  */
179
- stop(): Promise<void>;
440
+ stop(): Promise<boolean>;
180
441
  /**
181
- * Stop the child and destroy the observation emitter.
442
+ * Stop the child, close its standard-input channel, and destroy the observation emitter.
443
+ *
444
+ * @remarks
445
+ * Always resolves, including when termination was never confirmed.
182
446
  *
183
447
  * @returns The stable barrier shared by every call
184
448
  */
185
449
  destroy(): Promise<void>;
186
450
  }
187
451
 
452
+ /**
453
+ * The Node-side contracts of `@orkestrel/process/server`.
454
+ *
455
+ * @remarks
456
+ * The host-independent contracts live in `@orkestrel/process`. This module declares only what a
457
+ * Node child process boundary requires, which the published contracts cannot express without
458
+ * naming `node:child_process` types.
459
+ */
460
+ /**
461
+ * The child boundary the termination helpers drive.
462
+ *
463
+ * @remarks
464
+ * A `ChildProcess` satisfies this structurally, and so does any object carrying the same
465
+ * members, which is what lets a caller drive `stopChild` over a child it spawned itself. Each helper
466
+ * takes the slice of this contract it reads: `exitCode` and `signalCode` are the host's
467
+ * authoritative liveness answer, `pid` addresses a POSIX process group, `kill` delivers one signal,
468
+ * `once` reports the native exit, and `off` releases that listener when the wait ends at its
469
+ * deadline instead.
470
+ *
471
+ * A non-detached child stays in its caller's process group, so no group carries its pid. `kill` is
472
+ * the route that reaches it: `killProcess` signals the negated `pid` first and falls back to `kill`
473
+ * when the host reports that no group owns that pid.
474
+ */
475
+ export declare interface ProcessChild {
476
+ /** The process id the host assigned, or `undefined` when the spawn never produced one. */
477
+ readonly pid?: number | undefined;
478
+ /** The exit code, or `null` while the process is live or a signal ended it. */
479
+ readonly exitCode: number | null;
480
+ /** The terminating signal name, or `null` while the process is live or it exited on its own. */
481
+ readonly signalCode: string | null;
482
+ /**
483
+ * Deliver one signal to the process.
484
+ *
485
+ * @param signal - The signal to deliver
486
+ * @returns True when the host accepted the signal; false otherwise
487
+ */
488
+ kill(signal: NodeJS.Signals): boolean;
489
+ /**
490
+ * Register a one-shot listener for the native exit.
491
+ *
492
+ * @param event - The `exit` event name
493
+ * @param listener - The listener invoked after the process exits
494
+ * @returns Whatever the emitter returns, which the helpers ignore
495
+ */
496
+ once(event: 'exit', listener: () => void): unknown;
497
+ /**
498
+ * Release one previously registered exit listener.
499
+ *
500
+ * @remarks
501
+ * `waitForExit` calls this when its deadline elapses before the exit, so a child that outlives
502
+ * several bounded waits accumulates no listeners.
503
+ *
504
+ * @param event - The `exit` event name
505
+ * @param listener - The listener registered through `once`
506
+ * @returns Whatever the emitter returns, which the helpers ignore
507
+ */
508
+ off(event: 'exit', listener: () => void): unknown;
509
+ }
510
+
188
511
  /**
189
512
  * A keyed registry of live supervised child processes.
190
513
  *
191
514
  * @remarks
192
515
  * A child launched under an id joins the registry and emits `launch`; when it settles it removes
193
- * itself and emits `exit`, so `count` and `processes` reflect only live children. `launch` throws
194
- * a {@link createDuplicateError} when the id is already live — a spawn fault surfaces through the
195
- * returned child's `exit`, never from `launch`. `destroy` stops every child, then destroys the
516
+ * itself and emits `exit`, so `count` and `processes` reflect only live children. The id is reserved
517
+ * before the child is constructed and released when construction throws, so a refused launch never
518
+ * strands its key. Eviction follows the child's own `exit` promise, which no listener can forge, so
519
+ * it lands one microtask after the child's public `exit` event: a listener on that event still sees
520
+ * the child registered. `launch` throws a {@link createDuplicateError} when the id is already live
521
+ * and a {@link createProtocolError} after `destroy` has begun, including when the caller's own option
522
+ * getter begins that teardown mid-construction — a spawn fault surfaces through the returned child's
523
+ * `exit`, never from `launch`. `destroy` awaits every child's own teardown, which destroys each
524
+ * child's observation emitter and leaves every subscription on it silently inert, then destroys the
196
525
  * registry emitter last.
197
526
  *
198
527
  * @example
@@ -203,7 +532,6 @@ export declare class Process implements ProcessInterface {
203
532
  * const child = manager.launch('build', {
204
533
  * command: { file: 'node', arguments: ['build.js'] },
205
534
  * workspace: process.cwd(),
206
- * grace: 5000,
207
535
  * })
208
536
  * await child.exit
209
537
  * await manager.destroy()
@@ -237,107 +565,208 @@ export declare class ProcessManager implements ProcessManagerInterface {
237
565
  /**
238
566
  * Spawn and register one child under `id`.
239
567
  *
568
+ * @remarks
569
+ * {@link Process} reads every option before it spawns, so a caller's own option getter runs while
570
+ * nothing has started and a throw from one strands no process. A getter that begins `destroy`
571
+ * without throwing is the one remaining race, and it leaves a bounded residual: the child is
572
+ * already spawned, so the launch is refused with a {@link createProtocolError} and that child is
573
+ * torn down asynchronously, bounded by `grace` plus the confirmation window. The `protocol`
574
+ * refusal throws synchronously before the `destroy` barrier settles. The barrier does not cover
575
+ * that child's asynchronous teardown.
576
+ *
240
577
  * @param id - The registry key, unique among live children
241
578
  * @param options - The child construction options
242
579
  * @returns The launched child
243
- * @throws A {@link ProcessError} coded `duplicate` when `id` is already live
580
+ * @throws A {@link ProcessError} coded `duplicate` when `id` is already live, `protocol` when the registry is being destroyed, or `invalid` when an option or command string is malformed
244
581
  */
245
582
  launch(id: string, options: ProcessOptions): ProcessInterface;
246
583
  /**
247
584
  * Terminate the named children and await their exit.
248
585
  *
249
586
  * @param ids - The registry keys to stop
250
- * @returns True when every named child stopped, false when any id was not live
587
+ * @returns True when every named child was live and its exit was confirmed; false otherwise
251
588
  */
252
589
  stop(ids: readonly string[]): Promise<boolean>;
253
590
  /**
254
591
  * Terminate one child and await its exit.
255
592
  *
256
593
  * @param id - The registry key to stop
257
- * @returns True when the child was live and stopped, false when the id was not live
594
+ * @returns True when the child was live and its exit was confirmed; false when the id was not live or the confirmation deadline elapsed
258
595
  */
259
596
  stop(id: string): Promise<boolean>;
260
597
  /**
261
598
  * Terminate every live child and await their exit.
262
599
  *
263
- * @returns A promise that resolves after all children stop
600
+ * @returns A promise that resolves after every child stops
264
601
  */
265
602
  stop(): Promise<void>;
266
603
  /**
267
604
  * Stop every child, then destroy the registry emitter last.
268
605
  *
606
+ * @remarks
607
+ * Always resolves, and refuses a later `launch` with a {@link createProtocolError}. Each child is
608
+ * destroyed rather than merely stopped, so its own observation emitter is destroyed too and every
609
+ * subscription on it goes silently inert.
610
+ *
269
611
  * @returns The stable barrier shared by every call
270
612
  */
271
613
  destroy(): Promise<void>;
272
614
  }
273
615
 
274
616
  /**
275
- * Checks whether a command file must be spawned through a shell on the current host.
617
+ * Quotes one command-line token for a `cmd.exe` command line.
276
618
  *
277
619
  * @remarks
278
- * Node v22+ refuses to spawn a Windows `.cmd` or `.bat` batch file directly and returns `EINVAL`,
279
- * and a bare command name such as `git` resolves through `PATHEXT` to a batch shim rather than an
280
- * executable. Both cases run through the shell. A POSIX host, and a Windows path carrying a real
281
- * executable extension such as `.exe`, spawn directly. When the result is `true`, treat the
282
- * command file and arguments as trusted: shell invocation exposes shell metacharacters.
620
+ * A token carrying whitespace or a shell metacharacter is wrapped in double quotes so `cmd.exe`
621
+ * passes it as one literal argument; an embedded double quote is doubled, which is how `cmd.exe`
622
+ * reads a literal quote inside a quoted token. The quoted set includes `%`, although quoting cannot
623
+ * stop `cmd.exe` expansion; {@link buildSpawn} therefore refuses `%` in an argument to a Windows
624
+ * batch target.
283
625
  *
284
- * @param file - The command executable name or path
285
- * @returns True when the file must run through a shell; false when it spawns directly
626
+ * @param value - The token to quote
627
+ * @returns The token, quoted only when `cmd.exe` would otherwise split or interpret it
286
628
  *
287
629
  * @example
288
630
  * ```ts
289
- * requiresShell('git') // true on Windows, false on POSIX
631
+ * quoteArgument('a&b') // '"a&b"'
290
632
  * ```
291
633
  */
292
- export declare function requiresShell(file: string): boolean;
634
+ export declare function quoteArgument(value: string): string;
293
635
 
294
636
  /**
295
- * Runs one command to completion, buffering its output, and settles with the outcome.
637
+ * Reads one environment variable under an explicit platform's key rules.
296
638
  *
297
639
  * @remarks
298
- * The child is spawned through the cross-platform command resolver ({@link requiresShell}) and, on
299
- * a POSIX host, detached so its whole process group can be terminated. Standard output and error
300
- * are each byte-bounded by `limit` (default {@link PROCESS_OUTPUT}). A positive `timeout` arms a
301
- * timer that terminates the child through `SIGTERM`, then `SIGKILL` after `grace`
302
- * (default {@link PROCESS_GRACE}); aborting `signal` terminates the same way. The child's
303
- * `environment` merges over the parent, then `options.environment` on top, and `options.input`
304
- * overrides `command.input`. Unless `reject` is `false`, a failed run rejects with a
305
- * {@link createRunError} carrying the {@link RunResult}.
640
+ * Windows environment keys are case-insensitive, so a merged record can hold `Path` where the
641
+ * caller asks for `PATH`. The exact key is tried first and a folded scan runs only for `win32`.
306
642
  *
307
- * @param command - The executable, arguments, and optional environment and input
308
- * @param options - Working directory, timeout, grace, signal, capture limit, and reject behavior
309
- * @returns The settled run outcome
310
- * @throws A {@link ProcessError} carrying the {@link RunResult} when the run failed and `reject` is not `false`
643
+ * @param environment - The environment record to read
644
+ * @param name - The variable name
645
+ * @param platform - The platform whose key rules apply
646
+ * @returns The value, or `undefined` when the environment declares no such variable
311
647
  *
312
648
  * @example
313
649
  * ```ts
314
- * const result = await run({ file: 'git', arguments: ['status'] }, { workspace: process.cwd() })
650
+ * readPlatformVariable({ Path: 'C:\\Windows' }, 'PATH', 'win32') // 'C:\\Windows'
315
651
  * ```
316
652
  */
317
- export declare function run(command: ProcessCommand, options?: RunOptions): Promise<RunResult>;
653
+ export declare function readPlatformVariable(environment: Readonly<Record<string, string | undefined>>, name: string, platform: NodeJS.Platform): string | undefined;
318
654
 
319
655
  /**
320
- * Runs one command to completion synchronously, buffering its output, and returns the outcome.
656
+ * Reads one environment variable the way the current host resolves it.
657
+ *
658
+ * @param environment - The environment record to read
659
+ * @param name - The variable name
660
+ * @returns The value, or `undefined` when the environment declares no such variable
661
+ *
662
+ * @example
663
+ * ```ts
664
+ * readVariable({ PATH: '/usr/bin' }, 'PATH') // '/usr/bin'
665
+ * ```
666
+ */
667
+ export declare function readVariable(environment: Readonly<Record<string, string | undefined>>, name: string): string | undefined;
668
+
669
+ /**
670
+ * Resolves a command file to the executable path the host would launch.
671
+ *
672
+ * @param file - The command executable name or path
673
+ * @param options - The directory searched first and the child's effective environment
674
+ * @returns The resolved absolute path, or `undefined` when no candidate is a regular file
675
+ *
676
+ * @example
677
+ * ```ts
678
+ * resolveExecutable('git', {}) ?? 'git' // an absolute path on Windows, 'git' elsewhere
679
+ * ```
680
+ */
681
+ export declare function resolveExecutable(file: string, options?: ExecutableOptions): string | undefined;
682
+
683
+ /**
684
+ * Accumulates byte totals while retaining a bounded stream head.
685
+ *
686
+ * @example
687
+ * ```ts
688
+ * const retention = new Retention()
689
+ * retention.retain(Buffer.from('hello'), 3)?.toString('utf8') // 'hel'
690
+ * retention.retained // 3
691
+ * ```
692
+ */
693
+ export declare class Retention implements RetentionInterface {
694
+ #private;
695
+ /** The bytes delivered by the stream. */
696
+ get delivered(): number;
697
+ /** The bytes retained from the stream head. */
698
+ get retained(): number;
699
+ /**
700
+ * Retains a delivered chunk within a byte limit.
701
+ *
702
+ * @param chunk - The delivered chunk, ignored when it is not a buffer
703
+ * @param limit - The maximum retained byte count
704
+ * @returns The retained slice, or `undefined` when the chunk contributes no retained bytes
705
+ */
706
+ retain(chunk: unknown, limit: number): Buffer_2 | undefined;
707
+ }
708
+
709
+ /** Exposes byte totals and bounded stream-head retention. */
710
+ export declare interface RetentionInterface {
711
+ /** The bytes delivered by the stream. */
712
+ readonly delivered: number;
713
+ /** The bytes retained from the stream head. */
714
+ readonly retained: number;
715
+ /**
716
+ * Retains a delivered chunk within a byte limit.
717
+ *
718
+ * @param chunk - The delivered chunk, ignored when it is not a buffer
719
+ * @param limit - The maximum retained byte count
720
+ * @returns The retained slice, or `undefined` when the chunk contributes no retained bytes
721
+ */
722
+ retain(chunk: unknown, limit: number): Buffer_2 | undefined;
723
+ }
724
+
725
+ /**
726
+ * Takes one owned frozen snapshot of a caller's command.
321
727
  *
322
728
  * @remarks
323
- * The synchronous counterpart of {@link run}. The child is spawned through the same cross-platform
324
- * command resolver ({@link requiresShell}). Standard output and error are each byte-bounded by
325
- * `limit` (default {@link PROCESS_OUTPUT}). A positive `timeout` is enforced by the host, which
326
- * ends the child with `SIGKILL` when it elapses. The environment and input follow the same merge
327
- * as {@link run}. Unless `reject` is `false`, a failed run throws a {@link createRunError} carrying
328
- * the {@link RunResult}.
729
+ * Every public entry point snapshots before it validates, so the object validated is the object
730
+ * spawned. Each property is read exactly once, because reading one runs the caller's own getter: a
731
+ * command whose `file` changes between reads would otherwise validate one executable and spawn
732
+ * another. The argument vector and the environment record are copied and frozen, so a caller
733
+ * mutating either after the call cannot reach the spawn. An absent optional stays absent rather than
734
+ * becoming an explicit `undefined`.
329
735
  *
330
- * @param command - The executable, arguments, and optional environment and input
331
- * @param options - Working directory, timeout, signal, capture limit, and reject behavior
332
- * @returns The run outcome
333
- * @throws A {@link ProcessError} carrying the {@link RunResult} when the run failed and `reject` is not `false`
736
+ * @param command - The caller's command, whose properties may be getters
737
+ * @returns A frozen command carrying the values read at this instant
334
738
  *
335
739
  * @example
336
740
  * ```ts
337
- * const result = runSync({ file: 'git', arguments: ['--version'] }, { reject: false })
741
+ * snapshotCommand({ file: 'git', arguments: ['status'] }) // { file: 'git', arguments: ['status'] }
338
742
  * ```
339
743
  */
340
- export declare function runSync(command: ProcessCommand, options?: RunOptions): RunResult;
744
+ export declare function snapshotCommand(command: ProcessCommand): ProcessCommand;
745
+
746
+ /**
747
+ * Terminates one child process tree and reports whether its native exit was observed.
748
+ *
749
+ * @remarks
750
+ * Windows ends the whole tree at once through {@link killTree}, falling back to a direct kill after
751
+ * the utility fails, because the host has no cooperative termination to offer. A POSIX host signals
752
+ * the process group `SIGTERM`, falls back to the direct child when no group owns its pid, waits
753
+ * `grace`, then sends `SIGKILL` through the same route. No signal is initiated after the native exit
754
+ * is observed; the window between initiating a signal and the host delivering it belongs to the
755
+ * operating system. `confirm` bounds each awaited step rather than the call as a whole: on Windows
756
+ * it bounds the `taskkill` call and then the final wait, and on a POSIX host `grace` bounds the
757
+ * cooperative wait and `confirm` the final one. Never rejects.
758
+ *
759
+ * @param child - The child boundary
760
+ * @param grace - The cooperative POSIX window in milliseconds between `SIGTERM` and `SIGKILL`
761
+ * @param confirm - The milliseconds the native exit is awaited after the final kill
762
+ * @returns True when the native exit was observed; false when `confirm` elapsed without it
763
+ *
764
+ * @example
765
+ * ```ts
766
+ * const confirmed = await stopChild(child, 5_000, 5_000)
767
+ * ```
768
+ */
769
+ export declare function stopChild(child: ProcessChild, grace: number, confirm: number): Promise<boolean>;
341
770
 
342
771
  /**
343
772
  * Trims a buffer to at most `limit` leading bytes without splitting a UTF-8 sequence.
@@ -347,7 +776,7 @@ export declare function runSync(command: ProcessCommand, options?: RunOptions):
347
776
  * a multibyte sequence, the end retreats to the sequence's start so the retained bytes always end
348
777
  * on a code-point boundary and decode without a replacement character.
349
778
  *
350
- * @param buffer - The accumulated bytes
779
+ * @param bytes - The accumulated bytes
351
780
  * @param limit - The maximum retained byte count
352
781
  * @returns The leading bytes, at most `limit` and never ending mid-sequence
353
782
  *
@@ -356,7 +785,7 @@ export declare function runSync(command: ProcessCommand, options?: RunOptions):
356
785
  * trimHead(Buffer.from('hello'), 3) // <Buffer 68 65 6c>
357
786
  * ```
358
787
  */
359
- export declare function trimHead(buffer: Buffer_2, limit: number): Buffer_2;
788
+ export declare function trimHead(bytes: Uint8Array, limit: number): Buffer_2;
360
789
 
361
790
  /**
362
791
  * Trims a buffer to at most `limit` trailing bytes without splitting a UTF-8 sequence.
@@ -366,7 +795,7 @@ export declare function trimHead(buffer: Buffer_2, limit: number): Buffer_2;
366
795
  * multibyte sequence, the start advances past the leading continuation bytes so the retained
367
796
  * bytes always begin on a code-point boundary.
368
797
  *
369
- * @param buffer - The accumulated bytes
798
+ * @param bytes - The accumulated bytes
370
799
  * @param limit - The maximum retained byte count
371
800
  * @returns The trailing bytes, at most `limit` and never starting mid-sequence
372
801
  *
@@ -375,6 +804,121 @@ export declare function trimHead(buffer: Buffer_2, limit: number): Buffer_2;
375
804
  * trimTail(Buffer.from('hello'), 3) // <Buffer 6c 6c 6f>
376
805
  * ```
377
806
  */
378
- export declare function trimTail(buffer: Buffer_2, limit: number): Buffer_2;
807
+ export declare function trimTail(bytes: Uint8Array, limit: number): Buffer_2;
808
+
809
+ /**
810
+ * Validates one byte-valued option.
811
+ *
812
+ * @param value - The value to inspect, or `undefined` when the option was omitted
813
+ * @param subject - The rejected input named as the caller wrote it, such as `option 'limit'`
814
+ * @param minimum - The smallest accepted value
815
+ * @returns Nothing; a successful return means the value bounds a real byte count
816
+ * @throws A {@link ProcessError} coded `invalid` when the value is not a safe integer at or above `minimum`
817
+ *
818
+ * @example
819
+ * ```ts
820
+ * validateBytes(1_024, "option 'limit'", 0) // returns
821
+ * ```
822
+ */
823
+ export declare function validateBytes(value: number | undefined, subject: string, minimum: number): void;
824
+
825
+ /**
826
+ * Validates every spawn-bound string of one command.
827
+ *
828
+ * @param command - The executable, arguments, and optional environment overrides
829
+ * @returns Nothing; a successful return means the command reaches the host intact
830
+ * @throws A {@link ProcessError} coded `invalid` when the file is empty or any string carries a NUL
831
+ *
832
+ * @example
833
+ * ```ts
834
+ * validateCommand({ file: 'git', arguments: ['status'] }) // returns
835
+ * ```
836
+ */
837
+ export declare function validateCommand(command: ProcessCommand): void;
838
+
839
+ /**
840
+ * Validates every spawn-bound string of one environment override map.
841
+ *
842
+ * @remarks
843
+ * The host refuses a NUL in a variable name or value with a raw `ERR_INVALID_ARG_VALUE`, so the same
844
+ * refusal runs here for a command's own overrides and for a per-invocation override map alike.
845
+ *
846
+ * @param environment - The override map, or `undefined` when none was supplied
847
+ * @returns Nothing; a successful return means every name and value reaches the host intact
848
+ * @throws A {@link ProcessError} coded `invalid` when a name is empty or a name or value carries a NUL
849
+ *
850
+ * @example
851
+ * ```ts
852
+ * validateEnvironment({ TOKEN: 'a' }) // returns
853
+ * ```
854
+ */
855
+ export declare function validateEnvironment(environment: Readonly<Record<string, string | undefined>> | undefined): void;
856
+
857
+ /**
858
+ * Validates one spawn-bound string.
859
+ *
860
+ * @remarks
861
+ * A NUL character terminates a string for the host, so it is refused everywhere rather than
862
+ * reaching the spawn as a raw `ERR_INVALID_ARG_VALUE`.
863
+ *
864
+ * @param value - The value to inspect
865
+ * @param subject - The rejected input named as the caller wrote it, such as `command file`
866
+ * @param required - If `true`, an empty string is refused; if `false`, it is accepted
867
+ * @returns Nothing; a successful return means the string is spawn-safe
868
+ * @throws A {@link ProcessError} coded `invalid` when the value is not a string, carries a NUL, or is required and empty
869
+ *
870
+ * @example
871
+ * ```ts
872
+ * validateText('status', 'command argument', false) // returns
873
+ * ```
874
+ */
875
+ export declare function validateText(value: unknown, subject: string, required: boolean): void;
876
+
877
+ /**
878
+ * Validates one timer-valued option in milliseconds.
879
+ *
880
+ * @remarks
881
+ * The host truncates a fractional delay and converts anything above {@link PROCESS_TIMER} to one
882
+ * millisecond, so a requested 25-day timeout would otherwise fire immediately.
883
+ *
884
+ * @param value - The value to inspect, or `undefined` when the option was omitted
885
+ * @param subject - The rejected input named as the caller wrote it, such as `option 'grace'`
886
+ * @returns Nothing; a successful return means the value schedules as written
887
+ * @throws A {@link ProcessError} coded `invalid` when the value is not a non-negative integer at or below `PROCESS_TIMER`
888
+ *
889
+ * @example
890
+ * ```ts
891
+ * validateTimer(5_000, "option 'grace'") // returns
892
+ * ```
893
+ */
894
+ export declare function validateTimer(value: number | undefined, subject: string): void;
895
+
896
+ /**
897
+ * Validates the working directory one child starts in.
898
+ *
899
+ * @param workspace - The directory, or `undefined` when the option was omitted
900
+ * @returns Nothing; a successful return means the directory reaches the host intact
901
+ * @throws A {@link ProcessError} coded `invalid` when the directory is empty or carries a NUL
902
+ *
903
+ * @example
904
+ * ```ts
905
+ * validateWorkspace(process.cwd()) // returns
906
+ * ```
907
+ */
908
+ export declare function validateWorkspace(workspace: string | undefined): void;
909
+
910
+ /**
911
+ * Waits for one child process's native exit, bounded by a deadline.
912
+ *
913
+ * @param child - The child boundary
914
+ * @param timeout - The milliseconds to wait before giving up
915
+ * @returns A promise that resolves on the native exit or when the deadline elapses, whichever comes first
916
+ *
917
+ * @example
918
+ * ```ts
919
+ * await waitForExit(child, 5_000)
920
+ * ```
921
+ */
922
+ export declare function waitForExit(child: Pick<ProcessChild, 'exitCode' | 'signalCode' | 'once' | 'off'>, timeout: number): Promise<void>;
379
923
 
380
924
  export { }