@orkestrel/process 0.0.5 → 0.0.7

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.
@@ -362,29 +362,23 @@ export declare function mergeEnvironment(isolated: boolean, base?: Readonly<Reco
362
362
  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;
363
363
 
364
364
  /**
365
- * One supervised child process with framed output, a bounded backlog, and bounded termination.
365
+ * Supervises one child while keeping every observation channel aligned at termination.
366
366
  *
367
367
  * @remarks
368
- * Standard output is framed through `readline` into a single-consumer stream: each line goes to
369
- * exactly one waiting iterator, so concurrent iterators split the output rather than each receiving
370
- * all of it. While no consumer has ever requested an iterator, stdout keeps draining so `exit` still
371
- * resolves, and retention stops at the `backlog` mark: a
372
- * consumer attaching later receives the retained head, a gap, then the live stream. After an iterator
373
- * has been requested, stdout pauses at the mark and resumes at half of it, so that consumer loses
374
- * nothing before termination and the child feels real backpressure. The mark is soft — the ordinary backlog can
375
- * overshoot it by the line that crossed it plus the rest of its delivered chunk. Termination never
376
- * reapplies backpressure, and retained lines are capped at twice `backlog`; later lines are dropped
377
- * and `truncated` reports the omission. Standard error is decoded and forwarded live as the `stderr`
378
- * event while a byte-bounded raw tail is retained as `evidence`. The typed `emitter` also carries a
379
- * child `error` cause on a spawn fault, a `protocol` {@link ProcessError} on a host-reported stdin
380
- * fault after the constructor input phase, and the terminal `exit`, alongside the `exit` promise.
381
- * A fault arising from the constructor-supplied `input` write or its closing `end` stays quiet
382
- * because the package initiated that sequence. `pid`,
383
- * `code`, and `signal` read the spawned child directly, so the native exit is readable before the
384
- * `exit` promise settles on stdio close. `stop` ends the whole tree and reports whether the native
385
- * exit was observed; `destroy` stops the child, destroys stdin so every pending `send` settles, and
386
- * destroys the emitter last. `destroy` resolving does not imply the child's stdio has closed, so
387
- * `exit` and `lines` can still be outstanding when a descendant holds an inherited pipe.
368
+ * The child's ending and the supervision's ending are distinct. `pid`, `code`, and `signal` read
369
+ * the host child directly, so they expose native exit as soon as the host records it. `settled`,
370
+ * `exit`, `evidence`, and `lines` reach one terminal moment after the read channels close or the
371
+ * bounded `drain` window cuts them off. That window is armed by the child's native exit and by a
372
+ * termination this package initiated, so every way a child can end reaches the moment. The terminal
373
+ * routine freezes `evidence` and ends `lines` before it destroys stdout, stderr, or the emitter, so
374
+ * the push channel never disappears while a pull channel can still change or wait indefinitely.
375
+ *
376
+ * Standard output is framed through `readline` into a single-consumer stream. Lines already framed
377
+ * and queued remain available through the terminal moment, while bytes arriving after a requested
378
+ * termination may be cut off. Standard error is forwarded through the live `stderr` event and
379
+ * retained as a byte-bounded raw tail until that same moment. `stop` ends the child tree and the
380
+ * observation surfaces. `destroy` performs that stop and then destroys the emitter after the
381
+ * frozen terminal state exists.
388
382
  *
389
383
  * @example
390
384
  * ```ts
@@ -415,13 +409,17 @@ export declare class Process implements ProcessInterface {
415
409
  get signal(): string | null;
416
410
  /** The typed lifecycle observation surface. */
417
411
  get emitter(): EmitterInterface<ProcessEventMap>;
418
- /** The captured stdout lines, in arrival order, ending when the child's stdout closes. */
412
+ /** The captured stdout lines, in arrival order, ending after queued lines at the terminal moment. */
419
413
  get lines(): AsyncIterable<string>;
420
- /** The decoded byte-bounded stderr tail. */
414
+ /** The decoded byte-bounded stderr tail, frozen at the terminal moment. */
421
415
  get evidence(): string;
422
416
  /** True when the `lines` stream omitted output after a retention bound was reached. */
423
417
  get truncated(): boolean;
424
- /** The terminal child state, observed once from the close event. */
418
+ /** True after the terminal moment arrived and `exit` settled. */
419
+ get settled(): boolean;
420
+ /** True after termination began, including after the terminal moment. */
421
+ get stopping(): boolean;
422
+ /** The terminal child state, observed once after stream close or the drain cutoff. */
425
423
  get exit(): Promise<ProcessExit>;
426
424
  /**
427
425
  * Write one line to the open standard-input channel.
@@ -442,19 +440,22 @@ export declare class Process implements ProcessInterface {
442
440
  */
443
441
  send(text: string): Promise<boolean>;
444
442
  /**
445
- * Terminate the child process tree and await its observed exit.
443
+ * Terminates the child process tree and reaches the terminal observation moment.
446
444
  *
447
445
  * @remarks
448
- * Never rejects, and every call shares one termination.
446
+ * Never rejects, and every call shares one termination. After the termination sequence returns,
447
+ * confirmed or not, it waits at most `drain` for the read channels to close, then cuts them off
448
+ * and settles `exit` when they remain open. A cutoff reached before the native exit reports the
449
+ * `code` and `signal` the host had recorded by then, which is `null` for a child still running.
449
450
  *
450
451
  * @returns True when the child's native exit was observed; false when the confirmation deadline elapsed without it
451
452
  */
452
453
  stop(): Promise<boolean>;
453
454
  /**
454
- * Stop the child, close its standard-input channel, and destroy the observation emitter.
455
+ * Stops the child and destroys the observation emitter after the terminal state freezes.
455
456
  *
456
457
  * @remarks
457
- * Always resolves, including when termination was never confirmed.
458
+ * Always resolves, including when termination was never confirmed. Every call shares one barrier.
458
459
  *
459
460
  * @returns The stable barrier shared by every call
460
461
  */
@@ -477,8 +478,8 @@ export declare class Process implements ProcessInterface {
477
478
  * members, which is what lets a caller drive `stopChild` over a child it spawned itself. Each helper
478
479
  * takes the slice of this contract it reads: `exitCode` and `signalCode` are the host's
479
480
  * authoritative liveness answer, `pid` addresses a POSIX process group, `kill` delivers one signal,
480
- * `once` reports the native exit, and `off` releases that listener when the wait ends at its
481
- * deadline instead.
481
+ * `once` reports the native exit to `waitForExit` or the stream close to `waitForClose`, and `off`
482
+ * releases the corresponding listener when either wait ends.
482
483
  *
483
484
  * A non-detached child stays in its caller's process group, so no group carries its pid. `kill` is
484
485
  * the route that reaches it: `killProcess` signals the negated `pid` first and falls back to `kill`
@@ -499,25 +500,25 @@ export declare interface ProcessChild {
499
500
  */
500
501
  kill(signal: NodeJS.Signals): boolean;
501
502
  /**
502
- * Register a one-shot listener for the native exit.
503
+ * Register a one-shot listener for the native exit or stream close.
503
504
  *
504
- * @param event - The `exit` event name
505
- * @param listener - The listener invoked after the process exits
505
+ * @param event - The `exit` or `close` event name
506
+ * @param listener - The listener invoked after the selected event
506
507
  * @returns Whatever the emitter returns, which the helpers ignore
507
508
  */
508
- once(event: 'exit', listener: () => void): unknown;
509
+ once(event: 'exit' | 'close', listener: () => void): unknown;
509
510
  /**
510
- * Release one previously registered exit listener.
511
+ * Release one previously registered exit or close listener.
511
512
  *
512
513
  * @remarks
513
- * `waitForExit` calls this when its deadline elapses before the exit, so a child that outlives
514
- * several bounded waits accumulates no listeners.
514
+ * `waitForExit` releases its `exit` listener, and `waitForClose` releases its `close` listener, so
515
+ * a child that outlives several bounded waits accumulates neither.
515
516
  *
516
- * @param event - The `exit` event name
517
+ * @param event - The `exit` or `close` event name
517
518
  * @param listener - The listener registered through `once`
518
519
  * @returns Whatever the emitter returns, which the helpers ignore
519
520
  */
520
- off(event: 'exit', listener: () => void): unknown;
521
+ off(event: 'exit' | 'close', listener: () => void): unknown;
521
522
  }
522
523
 
523
524
  /**
@@ -532,9 +533,9 @@ export declare interface ProcessChild {
532
533
  * the child registered. `launch` throws a {@link createDuplicateError} when the id is already live
533
534
  * and a {@link createProtocolError} after `destroy` has begun, including when the caller's own option
534
535
  * getter begins that teardown mid-construction — a spawn fault surfaces through the returned child's
535
- * `exit`, never from `launch`. `destroy` awaits every child's own teardown, which destroys each
536
- * child's observation emitter and leaves every subscription on it silently inert, then destroys the
537
- * registry emitter last.
536
+ * `exit`, never from `launch`. `destroy` awaits every child's own teardown, including the teardown
537
+ * of a child a lost race refused, which destroys each child's observation emitter and leaves every
538
+ * subscription on it silently inert, then destroys the registry emitter last.
538
539
  *
539
540
  * @example
540
541
  * ```ts
@@ -580,11 +581,11 @@ export declare class ProcessManager implements ProcessManagerInterface {
580
581
  * @remarks
581
582
  * {@link Process} reads every option before it spawns, so a caller's own option getter runs while
582
583
  * nothing has started and a throw from one strands no process. A getter that begins `destroy`
583
- * without throwing is the one remaining race, and it leaves a bounded residual: the child is
584
- * already spawned, so the launch is refused with a {@link createProtocolError} and that child is
585
- * torn down asynchronously, bounded by `grace` plus the confirmation window. The `protocol`
586
- * refusal throws synchronously before the `destroy` barrier settles. The barrier does not cover
587
- * that child's asynchronous teardown.
584
+ * without throwing is the one remaining race: the child is already spawned, so the launch is
585
+ * refused with a {@link createProtocolError} and that child is destroyed rather than adopted, its
586
+ * teardown bounded by `grace` plus the confirmation window. The `protocol` refusal throws
587
+ * synchronously, and the `destroy` barrier covers that teardown, so the refused child reaches its
588
+ * terminal moment before the barrier resolves.
588
589
  *
589
590
  * @param id - The registry key, unique among live children
590
591
  * @param options - The child construction options
@@ -759,14 +760,16 @@ export declare function snapshotCommand(command: ProcessCommand): ProcessCommand
759
760
  * Terminates one child process tree and reports whether its native exit was observed.
760
761
  *
761
762
  * @remarks
762
- * Windows ends the whole tree at once through {@link killTree}, falling back to a direct kill after
763
- * the utility fails, because the host has no cooperative termination to offer. A POSIX host signals
764
- * the process group `SIGTERM`, falls back to the direct child when no group owns its pid, waits
765
- * `grace`, then sends `SIGKILL` through the same route. No signal is initiated after the native exit
766
- * is observed; the window between initiating a signal and the host delivering it belongs to the
767
- * operating system. `confirm` bounds each awaited step rather than the call as a whole: on Windows
768
- * it bounds the `taskkill` call and then the final wait, and on a POSIX host `grace` bounds the
769
- * cooperative wait and `confirm` the final one. Never rejects.
763
+ * An already-exited child is returned on before any route to its pid runs, because the host has
764
+ * reaped that number and may have handed it to another process. Windows ends the whole tree at once
765
+ * through {@link killTree}, falling back to a direct kill after the utility fails, because the host
766
+ * has no cooperative termination to offer. A POSIX host signals the process group `SIGTERM`, falls
767
+ * back to the direct child when no group owns its pid, waits `grace`, then sends `SIGKILL` through
768
+ * the same route. No signal is initiated after the native exit is observed; the window between
769
+ * initiating a signal and the host delivering it belongs to the operating system. `confirm` bounds
770
+ * each awaited step rather than the call as a whole: on Windows it bounds the `taskkill` call and
771
+ * then the final wait, and on a POSIX host `grace` bounds the cooperative wait and `confirm` the
772
+ * final one. Never rejects.
770
773
  *
771
774
  * @param child - The child boundary
772
775
  * @param grace - The cooperative POSIX window in milliseconds between `SIGTERM` and `SIGKILL`
@@ -919,6 +922,20 @@ export declare function validateTimer(value: number | undefined, subject: string
919
922
  */
920
923
  export declare function validateWorkspace(workspace: string | undefined): void;
921
924
 
925
+ /**
926
+ * Waits for one child process's streams to close, bounded by a deadline.
927
+ *
928
+ * @param child - The child boundary
929
+ * @param timeout - The milliseconds to wait before giving up
930
+ * @returns True when the host close arrived inside the deadline; false otherwise
931
+ *
932
+ * @example
933
+ * ```ts
934
+ * const closed = await waitForClose(child, 5_000)
935
+ * ```
936
+ */
937
+ export declare function waitForClose(child: Pick<ProcessChild, 'once' | 'off'>, timeout: number): Promise<boolean>;
938
+
922
939
  /**
923
940
  * Waits for one child process's native exit, bounded by a deadline.
924
941
  *
@@ -362,29 +362,23 @@ export declare function mergeEnvironment(isolated: boolean, base?: Readonly<Reco
362
362
  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;
363
363
 
364
364
  /**
365
- * One supervised child process with framed output, a bounded backlog, and bounded termination.
365
+ * Supervises one child while keeping every observation channel aligned at termination.
366
366
  *
367
367
  * @remarks
368
- * Standard output is framed through `readline` into a single-consumer stream: each line goes to
369
- * exactly one waiting iterator, so concurrent iterators split the output rather than each receiving
370
- * all of it. While no consumer has ever requested an iterator, stdout keeps draining so `exit` still
371
- * resolves, and retention stops at the `backlog` mark: a
372
- * consumer attaching later receives the retained head, a gap, then the live stream. After an iterator
373
- * has been requested, stdout pauses at the mark and resumes at half of it, so that consumer loses
374
- * nothing before termination and the child feels real backpressure. The mark is soft — the ordinary backlog can
375
- * overshoot it by the line that crossed it plus the rest of its delivered chunk. Termination never
376
- * reapplies backpressure, and retained lines are capped at twice `backlog`; later lines are dropped
377
- * and `truncated` reports the omission. Standard error is decoded and forwarded live as the `stderr`
378
- * event while a byte-bounded raw tail is retained as `evidence`. The typed `emitter` also carries a
379
- * child `error` cause on a spawn fault, a `protocol` {@link ProcessError} on a host-reported stdin
380
- * fault after the constructor input phase, and the terminal `exit`, alongside the `exit` promise.
381
- * A fault arising from the constructor-supplied `input` write or its closing `end` stays quiet
382
- * because the package initiated that sequence. `pid`,
383
- * `code`, and `signal` read the spawned child directly, so the native exit is readable before the
384
- * `exit` promise settles on stdio close. `stop` ends the whole tree and reports whether the native
385
- * exit was observed; `destroy` stops the child, destroys stdin so every pending `send` settles, and
386
- * destroys the emitter last. `destroy` resolving does not imply the child's stdio has closed, so
387
- * `exit` and `lines` can still be outstanding when a descendant holds an inherited pipe.
368
+ * The child's ending and the supervision's ending are distinct. `pid`, `code`, and `signal` read
369
+ * the host child directly, so they expose native exit as soon as the host records it. `settled`,
370
+ * `exit`, `evidence`, and `lines` reach one terminal moment after the read channels close or the
371
+ * bounded `drain` window cuts them off. That window is armed by the child's native exit and by a
372
+ * termination this package initiated, so every way a child can end reaches the moment. The terminal
373
+ * routine freezes `evidence` and ends `lines` before it destroys stdout, stderr, or the emitter, so
374
+ * the push channel never disappears while a pull channel can still change or wait indefinitely.
375
+ *
376
+ * Standard output is framed through `readline` into a single-consumer stream. Lines already framed
377
+ * and queued remain available through the terminal moment, while bytes arriving after a requested
378
+ * termination may be cut off. Standard error is forwarded through the live `stderr` event and
379
+ * retained as a byte-bounded raw tail until that same moment. `stop` ends the child tree and the
380
+ * observation surfaces. `destroy` performs that stop and then destroys the emitter after the
381
+ * frozen terminal state exists.
388
382
  *
389
383
  * @example
390
384
  * ```ts
@@ -415,13 +409,17 @@ export declare class Process implements ProcessInterface {
415
409
  get signal(): string | null;
416
410
  /** The typed lifecycle observation surface. */
417
411
  get emitter(): EmitterInterface<ProcessEventMap>;
418
- /** The captured stdout lines, in arrival order, ending when the child's stdout closes. */
412
+ /** The captured stdout lines, in arrival order, ending after queued lines at the terminal moment. */
419
413
  get lines(): AsyncIterable<string>;
420
- /** The decoded byte-bounded stderr tail. */
414
+ /** The decoded byte-bounded stderr tail, frozen at the terminal moment. */
421
415
  get evidence(): string;
422
416
  /** True when the `lines` stream omitted output after a retention bound was reached. */
423
417
  get truncated(): boolean;
424
- /** The terminal child state, observed once from the close event. */
418
+ /** True after the terminal moment arrived and `exit` settled. */
419
+ get settled(): boolean;
420
+ /** True after termination began, including after the terminal moment. */
421
+ get stopping(): boolean;
422
+ /** The terminal child state, observed once after stream close or the drain cutoff. */
425
423
  get exit(): Promise<ProcessExit>;
426
424
  /**
427
425
  * Write one line to the open standard-input channel.
@@ -442,19 +440,22 @@ export declare class Process implements ProcessInterface {
442
440
  */
443
441
  send(text: string): Promise<boolean>;
444
442
  /**
445
- * Terminate the child process tree and await its observed exit.
443
+ * Terminates the child process tree and reaches the terminal observation moment.
446
444
  *
447
445
  * @remarks
448
- * Never rejects, and every call shares one termination.
446
+ * Never rejects, and every call shares one termination. After the termination sequence returns,
447
+ * confirmed or not, it waits at most `drain` for the read channels to close, then cuts them off
448
+ * and settles `exit` when they remain open. A cutoff reached before the native exit reports the
449
+ * `code` and `signal` the host had recorded by then, which is `null` for a child still running.
449
450
  *
450
451
  * @returns True when the child's native exit was observed; false when the confirmation deadline elapsed without it
451
452
  */
452
453
  stop(): Promise<boolean>;
453
454
  /**
454
- * Stop the child, close its standard-input channel, and destroy the observation emitter.
455
+ * Stops the child and destroys the observation emitter after the terminal state freezes.
455
456
  *
456
457
  * @remarks
457
- * Always resolves, including when termination was never confirmed.
458
+ * Always resolves, including when termination was never confirmed. Every call shares one barrier.
458
459
  *
459
460
  * @returns The stable barrier shared by every call
460
461
  */
@@ -477,8 +478,8 @@ export declare class Process implements ProcessInterface {
477
478
  * members, which is what lets a caller drive `stopChild` over a child it spawned itself. Each helper
478
479
  * takes the slice of this contract it reads: `exitCode` and `signalCode` are the host's
479
480
  * authoritative liveness answer, `pid` addresses a POSIX process group, `kill` delivers one signal,
480
- * `once` reports the native exit, and `off` releases that listener when the wait ends at its
481
- * deadline instead.
481
+ * `once` reports the native exit to `waitForExit` or the stream close to `waitForClose`, and `off`
482
+ * releases the corresponding listener when either wait ends.
482
483
  *
483
484
  * A non-detached child stays in its caller's process group, so no group carries its pid. `kill` is
484
485
  * the route that reaches it: `killProcess` signals the negated `pid` first and falls back to `kill`
@@ -499,25 +500,25 @@ export declare interface ProcessChild {
499
500
  */
500
501
  kill(signal: NodeJS.Signals): boolean;
501
502
  /**
502
- * Register a one-shot listener for the native exit.
503
+ * Register a one-shot listener for the native exit or stream close.
503
504
  *
504
- * @param event - The `exit` event name
505
- * @param listener - The listener invoked after the process exits
505
+ * @param event - The `exit` or `close` event name
506
+ * @param listener - The listener invoked after the selected event
506
507
  * @returns Whatever the emitter returns, which the helpers ignore
507
508
  */
508
- once(event: 'exit', listener: () => void): unknown;
509
+ once(event: 'exit' | 'close', listener: () => void): unknown;
509
510
  /**
510
- * Release one previously registered exit listener.
511
+ * Release one previously registered exit or close listener.
511
512
  *
512
513
  * @remarks
513
- * `waitForExit` calls this when its deadline elapses before the exit, so a child that outlives
514
- * several bounded waits accumulates no listeners.
514
+ * `waitForExit` releases its `exit` listener, and `waitForClose` releases its `close` listener, so
515
+ * a child that outlives several bounded waits accumulates neither.
515
516
  *
516
- * @param event - The `exit` event name
517
+ * @param event - The `exit` or `close` event name
517
518
  * @param listener - The listener registered through `once`
518
519
  * @returns Whatever the emitter returns, which the helpers ignore
519
520
  */
520
- off(event: 'exit', listener: () => void): unknown;
521
+ off(event: 'exit' | 'close', listener: () => void): unknown;
521
522
  }
522
523
 
523
524
  /**
@@ -532,9 +533,9 @@ export declare interface ProcessChild {
532
533
  * the child registered. `launch` throws a {@link createDuplicateError} when the id is already live
533
534
  * and a {@link createProtocolError} after `destroy` has begun, including when the caller's own option
534
535
  * getter begins that teardown mid-construction — a spawn fault surfaces through the returned child's
535
- * `exit`, never from `launch`. `destroy` awaits every child's own teardown, which destroys each
536
- * child's observation emitter and leaves every subscription on it silently inert, then destroys the
537
- * registry emitter last.
536
+ * `exit`, never from `launch`. `destroy` awaits every child's own teardown, including the teardown
537
+ * of a child a lost race refused, which destroys each child's observation emitter and leaves every
538
+ * subscription on it silently inert, then destroys the registry emitter last.
538
539
  *
539
540
  * @example
540
541
  * ```ts
@@ -580,11 +581,11 @@ export declare class ProcessManager implements ProcessManagerInterface {
580
581
  * @remarks
581
582
  * {@link Process} reads every option before it spawns, so a caller's own option getter runs while
582
583
  * nothing has started and a throw from one strands no process. A getter that begins `destroy`
583
- * without throwing is the one remaining race, and it leaves a bounded residual: the child is
584
- * already spawned, so the launch is refused with a {@link createProtocolError} and that child is
585
- * torn down asynchronously, bounded by `grace` plus the confirmation window. The `protocol`
586
- * refusal throws synchronously before the `destroy` barrier settles. The barrier does not cover
587
- * that child's asynchronous teardown.
584
+ * without throwing is the one remaining race: the child is already spawned, so the launch is
585
+ * refused with a {@link createProtocolError} and that child is destroyed rather than adopted, its
586
+ * teardown bounded by `grace` plus the confirmation window. The `protocol` refusal throws
587
+ * synchronously, and the `destroy` barrier covers that teardown, so the refused child reaches its
588
+ * terminal moment before the barrier resolves.
588
589
  *
589
590
  * @param id - The registry key, unique among live children
590
591
  * @param options - The child construction options
@@ -759,14 +760,16 @@ export declare function snapshotCommand(command: ProcessCommand): ProcessCommand
759
760
  * Terminates one child process tree and reports whether its native exit was observed.
760
761
  *
761
762
  * @remarks
762
- * Windows ends the whole tree at once through {@link killTree}, falling back to a direct kill after
763
- * the utility fails, because the host has no cooperative termination to offer. A POSIX host signals
764
- * the process group `SIGTERM`, falls back to the direct child when no group owns its pid, waits
765
- * `grace`, then sends `SIGKILL` through the same route. No signal is initiated after the native exit
766
- * is observed; the window between initiating a signal and the host delivering it belongs to the
767
- * operating system. `confirm` bounds each awaited step rather than the call as a whole: on Windows
768
- * it bounds the `taskkill` call and then the final wait, and on a POSIX host `grace` bounds the
769
- * cooperative wait and `confirm` the final one. Never rejects.
763
+ * An already-exited child is returned on before any route to its pid runs, because the host has
764
+ * reaped that number and may have handed it to another process. Windows ends the whole tree at once
765
+ * through {@link killTree}, falling back to a direct kill after the utility fails, because the host
766
+ * has no cooperative termination to offer. A POSIX host signals the process group `SIGTERM`, falls
767
+ * back to the direct child when no group owns its pid, waits `grace`, then sends `SIGKILL` through
768
+ * the same route. No signal is initiated after the native exit is observed; the window between
769
+ * initiating a signal and the host delivering it belongs to the operating system. `confirm` bounds
770
+ * each awaited step rather than the call as a whole: on Windows it bounds the `taskkill` call and
771
+ * then the final wait, and on a POSIX host `grace` bounds the cooperative wait and `confirm` the
772
+ * final one. Never rejects.
770
773
  *
771
774
  * @param child - The child boundary
772
775
  * @param grace - The cooperative POSIX window in milliseconds between `SIGTERM` and `SIGKILL`
@@ -919,6 +922,20 @@ export declare function validateTimer(value: number | undefined, subject: string
919
922
  */
920
923
  export declare function validateWorkspace(workspace: string | undefined): void;
921
924
 
925
+ /**
926
+ * Waits for one child process's streams to close, bounded by a deadline.
927
+ *
928
+ * @param child - The child boundary
929
+ * @param timeout - The milliseconds to wait before giving up
930
+ * @returns True when the host close arrived inside the deadline; false otherwise
931
+ *
932
+ * @example
933
+ * ```ts
934
+ * const closed = await waitForClose(child, 5_000)
935
+ * ```
936
+ */
937
+ export declare function waitForClose(child: Pick<ProcessChild, 'once' | 'off'>, timeout: number): Promise<boolean>;
938
+
922
939
  /**
923
940
  * Waits for one child process's native exit, bounded by a deadline.
924
941
  *