@orkestrel/process 0.0.6 → 0.0.8

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.
@@ -6,8 +6,8 @@ import { platform } from "node:process";
6
6
  import { boundsOf, holds, isNonEmptyString, isNonNegativeInteger, isString } from "@orkestrel/contract";
7
7
  import { PROCESS_BACKLOG, PROCESS_CONFIRMATION, PROCESS_DRAIN, PROCESS_EVIDENCE, PROCESS_GRACE, PROCESS_OUTPUT, PROCESS_PATHEXT, PROCESS_TIMER, ProcessError, createDuplicateError, createExecuteError, createInvalidError, createProtocolError } from "../core/index.js";
8
8
  import { createInterface } from "node:readline";
9
- import { StringDecoder } from "node:string_decoder";
10
9
  import { Emitter } from "@orkestrel/emitter";
10
+ import { StringDecoder } from "node:string_decoder";
11
11
  //#region src/server/helpers.ts
12
12
  /**
13
13
  * Trims a buffer to at most `limit` trailing bytes without splitting a UTF-8 sequence.
@@ -692,87 +692,92 @@ function buildExecuteResult(input) {
692
692
  });
693
693
  }
694
694
  //#endregion
695
- //#region src/server/Process.ts
695
+ //#region src/server/Supervisor.ts
696
696
  /**
697
- * Supervises one child while keeping every observation channel aligned at termination.
697
+ * Supervises one child process and reports each lifecycle moment to the face composing it.
698
698
  *
699
699
  * @remarks
700
- * The child's ending and the supervision's ending are distinct. `pid`, `code`, and `signal` read
701
- * the host child directly, so they expose native exit as soon as the host records it. `settled`,
702
- * `exit`, `evidence`, and `lines` reach one terminal moment after the read channels close or the
703
- * bounded `drain` window cuts them off. That window is armed by the child's native exit and by a
704
- * termination this package initiated, so every way a child can end reaches the moment. The terminal
705
- * routine freezes `evidence` and ends `lines` before it destroys stdout, stderr, or the emitter, so
706
- * the push channel never disappears while a pull channel can still change or wait indefinitely.
707
- *
708
- * Standard output is framed through `readline` into a single-consumer stream. Lines already framed
709
- * and queued remain available through the terminal moment, while bytes arriving after a requested
710
- * termination may be cut off. Standard error is forwarded through the live `stderr` event and
711
- * retained as a byte-bounded raw tail until that same moment. `stop` ends the child tree and the
712
- * observation surfaces. `destroy` performs that stop and then destroys the emitter after the
713
- * frozen terminal state exists.
714
- *
715
- * @example
716
- * ```ts
717
- * import { Process } from '@orkestrel/process/server'
718
- *
719
- * const child = new Process({
720
- * command: { file: 'node', arguments: ['--version'] },
721
- * workspace: process.cwd(),
722
- * })
723
- * const exit = await child.exit
724
- * await child.destroy()
725
- * ```
700
+ * This is the engine every supervised-child face shares. It owns the eager spawn, the byte-bounded
701
+ * stderr tail and its decoded chunks, the standard-input channel and its bounded confirmation, the
702
+ * termination sequence, and the progression from the child's native exit through the bounded `drain`
703
+ * window to one terminal moment. It owns no output framing and no observation surface: a face
704
+ * attaches its own consumer to `stdout`, frames bytes however its contract promises, and owns the
705
+ * emitter and event map its consumers subscribe to.
706
+ *
707
+ * The child's ending and the supervision's ending are distinct. `pid`, `code`, and `signal` read the
708
+ * host child directly, so they expose native exit as soon as the host records it, and `ending`
709
+ * settles at that same moment. `settled`, `exit`, and `evidence` reach one terminal moment after the
710
+ * read channels close or the bounded `drain` window cuts them off. That window is armed by the
711
+ * child's native exit and by a termination this package initiated, so every way a child can end
712
+ * reaches the moment. The terminal routine hands the face its `close` and `terminal` moments before
713
+ * it destroys stdout and stderr, so a face's own pull surfaces are final while its read ends still
714
+ * exist.
715
+ *
716
+ * The face is a set of callbacks rather than an emitter, because each face publishes a different
717
+ * event map over the same moments. `chunk` and `fault` carry the live stderr text and the child or
718
+ * channel faults, `relieve` reports that a termination began so the face can release whatever
719
+ * backpressure it holds, `close` ends the face's read pipeline at the terminal moment, `terminal`
720
+ * carries the frozen {@link ProcessExit}, and `teardown` releases the face's own surface after the
721
+ * termination completes. `relieve` is optional because a face that never pauses the child's output
722
+ * holds no backpressure to release, and a face with nothing to do there would otherwise declare an
723
+ * empty method to say so.
726
724
  */
727
- var Process = class {
728
- #emitter;
725
+ var Supervisor = class {
726
+ #chunk;
727
+ #fault;
728
+ #relieve;
729
+ #close;
730
+ #terminal;
731
+ #teardown;
729
732
  #child;
730
- #reader;
731
733
  #grace;
732
734
  #drain;
733
735
  #evidence;
734
- #backlog;
735
736
  #delivery;
736
737
  #decoder = new StringDecoder("utf8");
737
738
  #exit = Promise.withResolvers();
738
- #lines;
739
- #queue = [];
740
- #waiters = [];
739
+ #ending = Promise.withResolvers();
741
740
  #signal;
742
741
  #abort;
743
742
  #writes = /* @__PURE__ */ new Map();
744
743
  #tail = Buffer.alloc(0);
745
- #head = 0;
746
- #pending = 0;
747
- #requested = false;
748
- #paused = false;
749
- #truncated = false;
750
744
  #stopping = false;
751
745
  #settled = false;
752
- #ended = false;
753
746
  #input = 0;
754
747
  #failure;
755
748
  #waiting;
749
+ #closure;
756
750
  #termination;
757
- #ending;
751
+ #destruction;
758
752
  /**
759
- * Spawn one child process and begin stream capture.
753
+ * Spawn one child process and begin standard-error capture.
760
754
  *
761
- * @param options - Command, workspace, termination, capture, stdin, and observation settings
755
+ * @remarks
756
+ * The face's callbacks are captured before anything is read or spawned, so the first moment the
757
+ * child can produce already has somewhere to go. `options` supplies the command, workspace,
758
+ * termination, capture, and standard-input settings; a face's own settings, such as an output
759
+ * retention bound, are read and validated by that face before it constructs this engine, so an
760
+ * invalid one refuses construction while nothing has been spawned.
761
+ *
762
+ * @param options - Command, workspace, termination, capture, and stdin settings
763
+ * @param face - The composing face's callbacks for each lifecycle moment
762
764
  * @throws A {@link ProcessError} coded `invalid` when an option or command string is malformed
763
765
  */
764
- constructor(options) {
766
+ constructor(options, face) {
767
+ this.#chunk = face.chunk;
768
+ this.#fault = face.fault;
769
+ this.#relieve = face.relieve;
770
+ this.#close = face.close;
771
+ this.#terminal = face.terminal;
772
+ this.#teardown = face.teardown;
765
773
  const source = options.command;
766
774
  const workspace = options.workspace;
767
775
  const grace = options.grace;
768
776
  const drain = options.drain;
769
777
  const evidence = options.evidence;
770
- const backlog = options.backlog;
771
778
  const delivery = options.delivery;
772
779
  const writable = options.writable;
773
780
  const signal = options.signal;
774
- const on = options.on;
775
- const error = options.error;
776
781
  const command = snapshotCommand(source);
777
782
  const input = command.input;
778
783
  validateCommand(command);
@@ -781,18 +786,11 @@ var Process = class {
781
786
  validateTimer(drain, "option 'drain'");
782
787
  validateTimer(delivery, "option 'delivery'");
783
788
  validateBytes(evidence, "option 'evidence'", 0);
784
- validateBytes(backlog, "option 'backlog'", 1);
785
- this.#emitter = new Emitter({
786
- ...on === void 0 ? {} : { on },
787
- ...error === void 0 ? {} : { error }
788
- });
789
789
  this.#grace = grace ?? PROCESS_GRACE;
790
790
  this.#drain = drain ?? PROCESS_DRAIN;
791
791
  this.#evidence = evidence ?? PROCESS_EVIDENCE;
792
- this.#backlog = backlog ?? PROCESS_BACKLOG;
793
792
  this.#delivery = delivery ?? 0;
794
793
  this.#signal = signal;
795
- this.#lines = Object.freeze({ [Symbol.asyncIterator]: this.#iterate.bind(this) });
796
794
  const childEnvironment = mergeEnvironment(command.isolated === true, command.environment);
797
795
  const plan = buildSpawn(command, {
798
796
  workspace,
@@ -810,16 +808,10 @@ var Process = class {
810
808
  windowsHide: true,
811
809
  windowsVerbatimArguments: plan.verbatim
812
810
  });
813
- this.#reader = createInterface({
814
- input: this.#child.stdout,
815
- crlfDelay: Infinity
816
- });
817
- this.#reader.on("line", this.#push.bind(this));
818
- this.#reader.once("close", this.#finish.bind(this));
819
- this.#child.once("error", (cause) => this.#emitter.emit("error", cause));
811
+ this.#child.once("error", this.#fault);
820
812
  this.#child.once("exit", this.#expire.bind(this));
821
- this.#child.once("close", this.#close.bind(this));
822
- this.#child.stdin.on("error", (cause) => this.#failInputStream(cause));
813
+ this.#child.once("close", this.#complete.bind(this));
814
+ this.#child.stdin.on("error", this.#failInputStream.bind(this));
823
815
  this.#child.stderr.on("data", this.#retain.bind(this));
824
816
  if (input !== void 0) {
825
817
  this.#input += 1;
@@ -835,6 +827,10 @@ var Process = class {
835
827
  if (signal.aborted) this.stop();
836
828
  }
837
829
  }
830
+ /** The child's standard-output stream, for the composing face to attach its own consumer to. */
831
+ get stdout() {
832
+ return this.#child.stdout;
833
+ }
838
834
  /** The host process id, fixed when construction returns, or `undefined` when the spawn produced none. */
839
835
  get pid() {
840
836
  return this.#child.pid;
@@ -847,22 +843,10 @@ var Process = class {
847
843
  get signal() {
848
844
  return this.#child.signalCode;
849
845
  }
850
- /** The typed lifecycle observation surface. */
851
- get emitter() {
852
- return this.#emitter;
853
- }
854
- /** The captured stdout lines, in arrival order, ending after queued lines at the terminal moment. */
855
- get lines() {
856
- return this.#lines;
857
- }
858
846
  /** The decoded byte-bounded stderr tail, frozen at the terminal moment. */
859
847
  get evidence() {
860
848
  return this.#tail.toString("utf8");
861
849
  }
862
- /** True when the `lines` stream omitted output after a retention bound was reached. */
863
- get truncated() {
864
- return this.#truncated;
865
- }
866
850
  /** True after the terminal moment arrived and `exit` settled. */
867
851
  get settled() {
868
852
  return this.#settled;
@@ -871,34 +855,43 @@ var Process = class {
871
855
  get stopping() {
872
856
  return this.#stopping;
873
857
  }
858
+ /**
859
+ * The child's own ending, awaited without the terminal moment's drain window.
860
+ *
861
+ * @remarks
862
+ * Never rejects, and resolves no value: `code` and `signal` carry the terminal facts as soon as
863
+ * the host records them. It settles at the native exit, and at the terminal moment for a child
864
+ * whose spawn produced no native exit at all, so it can never outlive the supervision.
865
+ */
866
+ get ending() {
867
+ return this.#ending.promise;
868
+ }
874
869
  /** The terminal child state, observed once after stream close or the drain cutoff. */
875
870
  get exit() {
876
871
  return this.#exit.promise;
877
872
  }
878
873
  /**
879
- * Write one line to the open standard-input channel.
874
+ * Writes raw bytes to the open standard-input channel.
880
875
  *
881
876
  * @remarks
882
- * Never rejects. `true` means the host accepted the bytes without reporting a fault; it does not
883
- * prove that the child read them. An ordinary write settles when the kernel accepts it. Only a
884
- * full pipe can hold the write unconfirmed. The `delivery` option can bound that wait, and every
885
- * terminal teardown path settles pending writes. On Windows 11 with Node v24.18.1, measured on
886
- * 2026-08-21, a child that closes its own file descriptor 0 can leave the parent pipe writable:
887
- * the write can settle `true` without a callback error or a stream error while the child remains
888
- * alive. After `stop` or `destroy` begins, a later call settles `false`. Version 0.0.4 could settle
889
- * that call `true` before teardown destroyed the pipe; returning `false` avoids claiming delivery
890
- * for bytes the package is about to discard.
877
+ * Never rejects, and adds no framing: a face composes whatever terminator its contract promises
878
+ * before it calls this. `true` means the host accepted the bytes without reporting a fault; it
879
+ * does not prove that the child read them. An ordinary write settles when the kernel accepts it.
880
+ * Only a full pipe can hold the write unconfirmed. The `delivery` option can bound that wait, and
881
+ * every terminal teardown path settles pending writes. After `stop` or `destroy` begins, a later
882
+ * call settles `false`, because teardown cannot confirm delivery for bytes it is about to
883
+ * discard.
891
884
  *
892
- * @param text - The line text without its trailing newline
885
+ * @param bytes - The payload to write, already framed by the caller
893
886
  * @returns True when the host accepted the bytes without reporting a fault; false when the channel was closed, destroyed, ended, failed, or remained unconfirmed through `delivery`
894
887
  */
895
- send(text) {
888
+ deliver(bytes) {
896
889
  const stdin = this.#child.stdin;
897
890
  if (this.#settled || this.#stopping || this.#failure !== void 0 || !stdin.writable) return Promise.resolve(false);
898
891
  const settled = Promise.withResolvers();
899
892
  this.#writes.set(settled, void 0);
900
893
  try {
901
- stdin.write(`${text}\n`, this.#confirmWrite.bind(this, settled));
894
+ stdin.write(bytes, this.#confirmWrite.bind(this, settled));
902
895
  } catch {
903
896
  this.#settleWrite(settled, false);
904
897
  return settled.promise;
@@ -911,6 +904,28 @@ var Process = class {
911
904
  return settled.promise;
912
905
  }
913
906
  /**
907
+ * Closes the standard-input channel while leaving the child running.
908
+ *
909
+ * @remarks
910
+ * Never rejects, and terminates nothing: no signal is sent, `stopping` stays false, no drain
911
+ * window is armed, and the terminal moment does not arrive. The child observes end of input and
912
+ * decides for itself what to do about it, which is what separates this from `stop`.
913
+ *
914
+ * Every call shares one barrier, and it resolves after the host flushes the writes it had already
915
+ * accepted, or reports that it cannot. A `deliver` after this call resolves `false`, because the
916
+ * host stops reporting the channel writable the moment it is ended. That same flag keeps the
917
+ * ended channel quiet for its remaining life, so a later host fault on it settles pending writes
918
+ * and reaches no face. After `stop` or `destroy` has begun, or after the terminal moment, the
919
+ * call resolves and changes nothing, because the channel is already being discarded.
920
+ *
921
+ * @returns The stable barrier shared by every call
922
+ */
923
+ end() {
924
+ if (this.#closure !== void 0) return this.#closure;
925
+ this.#closure = this.#endInput();
926
+ return this.#closure;
927
+ }
928
+ /**
914
929
  * Terminates the child process tree and reaches the terminal observation moment.
915
930
  *
916
931
  * @remarks
@@ -927,7 +942,7 @@ var Process = class {
927
942
  return this.#termination;
928
943
  }
929
944
  /**
930
- * Stops the child and destroys the observation emitter after the terminal state freezes.
945
+ * Stops the child and releases the composing face after the terminal state freezes.
931
946
  *
932
947
  * @remarks
933
948
  * Always resolves, including when termination was never confirmed. Every call shares one barrier.
@@ -935,95 +950,22 @@ var Process = class {
935
950
  * @returns The stable barrier shared by every call
936
951
  */
937
952
  destroy() {
938
- if (this.#ending !== void 0) return this.#ending;
939
- this.#ending = this.#end();
940
- return this.#ending;
941
- }
942
- #iterate() {
943
- this.#requested = true;
944
- return Object.freeze({ next: this.#next.bind(this) });
945
- }
946
- #next() {
947
- const line = this.#take();
948
- if (line !== void 0) return Promise.resolve(Object.freeze({
949
- done: false,
950
- value: line
951
- }));
952
- if (this.#ended) return Promise.resolve(Object.freeze({
953
- done: true,
954
- value: void 0
955
- }));
956
- const waiter = Promise.withResolvers();
957
- this.#waiters.push(waiter);
958
- return waiter.promise;
959
- }
960
- #take() {
961
- if (this.#head >= this.#queue.length) return void 0;
962
- const line = this.#queue[this.#head];
963
- this.#queue[this.#head] = "";
964
- this.#head += 1;
965
- if (this.#head >= this.#queue.length) {
966
- this.#queue.length = 0;
967
- this.#head = 0;
968
- }
969
- if (line === void 0) return void 0;
970
- this.#pending -= Buffer.byteLength(line) + 1;
971
- this.#relieve();
972
- return line;
973
- }
974
- #push(line) {
975
- if (this.#ended) return;
976
- const waiter = this.#waiters.shift();
977
- if (waiter !== void 0) {
978
- waiter.resolve(Object.freeze({
979
- done: false,
980
- value: line
981
- }));
982
- return;
983
- }
984
- const bytes = Buffer.byteLength(line) + 1;
985
- const limit = this.#stopping ? this.#backlog * 2 : this.#backlog;
986
- if ((!this.#requested || this.#stopping) && this.#pending + bytes > limit) {
987
- this.#truncated = true;
988
- return;
989
- }
990
- this.#queue.push(line);
991
- this.#pending += bytes;
992
- this.#restrain();
993
- }
994
- #restrain() {
995
- if (this.#paused || this.#stopping || !this.#requested) return;
996
- if (this.#pending < this.#backlog) return;
997
- this.#paused = true;
998
- this.#reader.pause();
999
- }
1000
- #relieve() {
1001
- if (!this.#paused || this.#ended) return;
1002
- if (!this.#stopping && this.#pending > this.#backlog / 2) return;
1003
- this.#paused = false;
1004
- this.#reader.resume();
1005
- }
1006
- #finish() {
1007
- if (this.#ended) return;
1008
- this.#ended = true;
1009
- this.#paused = false;
1010
- for (const waiter of this.#waiters) waiter.resolve(Object.freeze({
1011
- done: true,
1012
- value: void 0
1013
- }));
1014
- this.#waiters.length = 0;
953
+ if (this.#destruction !== void 0) return this.#destruction;
954
+ this.#destruction = this.#conclude();
955
+ return this.#destruction;
1015
956
  }
1016
957
  #retain(chunk) {
1017
958
  if (!Buffer.isBuffer(chunk) || this.#settled) return;
1018
959
  this.#tail = trimTail(Buffer.concat([this.#tail, chunk]), this.#evidence);
1019
960
  const text = this.#decoder.write(chunk);
1020
- if (text.length > 0) this.#emitter.emit("stderr", text);
961
+ if (text.length > 0) this.#chunk(text);
1021
962
  }
1022
- #close() {
963
+ #complete() {
1023
964
  if (this.#settled) return;
1024
965
  this.#settle(true);
1025
966
  }
1026
967
  #expire() {
968
+ this.#ending.resolve();
1027
969
  if (this.#settled) return;
1028
970
  this.#wait();
1029
971
  }
@@ -1035,17 +977,18 @@ var Process = class {
1035
977
  }
1036
978
  #settle(drained) {
1037
979
  const suffix = this.#decoder.end();
1038
- if (suffix.length > 0) this.#emitter.emit("stderr", suffix);
980
+ if (suffix.length > 0) this.#chunk(suffix);
1039
981
  this.#settled = true;
1040
982
  this.#removeAbortListener();
1041
- this.#reader.close();
983
+ this.#close();
1042
984
  const exit = Object.freeze({
1043
985
  code: this.code,
1044
986
  signal: this.signal,
1045
987
  drained
1046
988
  });
1047
989
  this.#exit.resolve(exit);
1048
- this.#emitter.emit("exit", exit);
990
+ this.#ending.resolve();
991
+ this.#terminal(exit);
1049
992
  this.#child.stdout.destroy();
1050
993
  this.#child.stderr.destroy();
1051
994
  }
@@ -1066,6 +1009,13 @@ var Process = class {
1066
1009
  #completeInput() {
1067
1010
  this.#input -= 1;
1068
1011
  }
1012
+ #endInput() {
1013
+ const stdin = this.#child.stdin;
1014
+ if (this.#stopping || this.#settled || stdin.writableEnded || stdin.destroyed) return Promise.resolve();
1015
+ const ended = Promise.withResolvers();
1016
+ stdin.end(() => ended.resolve());
1017
+ return ended.promise;
1018
+ }
1069
1019
  #settleWrite(settled, accepted) {
1070
1020
  if (!this.#writes.has(settled)) return;
1071
1021
  const timer = this.#writes.get(settled);
@@ -1077,34 +1027,28 @@ var Process = class {
1077
1027
  for (const settled of this.#writes.keys()) this.#settleWrite(settled, false);
1078
1028
  }
1079
1029
  #failInputStream(cause) {
1080
- if (this.#child.stdin.writableEnded || this.#input > 0) {
1030
+ if (this.#input > 0) {
1081
1031
  this.#settleWrites();
1082
1032
  return;
1083
1033
  }
1084
1034
  this.#failInputCallback(cause);
1085
1035
  }
1086
1036
  #failInputCallback(cause) {
1037
+ if (this.#child.stdin.writableEnded) {
1038
+ this.#settleWrites();
1039
+ return;
1040
+ }
1087
1041
  if (this.#failure !== void 0 || this.#stopping) return;
1088
1042
  this.#failure = cause;
1089
1043
  this.#settleWrites();
1090
- this.#emitter.emit("error", new ProcessError("The standard-input channel failed", {
1044
+ this.#fault(new ProcessError("The standard-input channel failed", {
1091
1045
  code: "protocol",
1092
1046
  cause
1093
1047
  }));
1094
1048
  }
1095
- #capBacklog() {
1096
- const limit = this.#backlog * 2;
1097
- while (this.#pending > limit && this.#queue.length > this.#head) {
1098
- const line = this.#queue.pop();
1099
- if (line === void 0) break;
1100
- this.#pending -= Buffer.byteLength(line) + 1;
1101
- this.#truncated = true;
1102
- }
1103
- }
1104
1049
  async #kill() {
1105
1050
  this.#stopping = true;
1106
- this.#capBacklog();
1107
- this.#relieve();
1051
+ this.#relieve?.();
1108
1052
  const confirmed = await stopChild(this.#child, this.#grace, PROCESS_CONFIRMATION);
1109
1053
  this.#settleWrites();
1110
1054
  this.#child.stdin.destroy();
@@ -1112,54 +1056,343 @@ var Process = class {
1112
1056
  if (!this.#settled) this.#settle(false);
1113
1057
  return confirmed;
1114
1058
  }
1115
- async #end() {
1059
+ async #conclude() {
1116
1060
  await this.stop();
1117
- this.#emitter.destroy();
1061
+ this.#teardown();
1118
1062
  }
1119
1063
  };
1120
1064
  //#endregion
1121
- //#region src/server/ProcessManager.ts
1065
+ //#region src/server/Process.ts
1122
1066
  /**
1123
- * A keyed registry of live supervised child processes.
1067
+ * Supervises one child while keeping every observation channel aligned at termination.
1124
1068
  *
1125
1069
  * @remarks
1126
- * A child launched under an id joins the registry and emits `launch`; when it settles it removes
1127
- * itself and emits `exit`, so `count` and `processes` reflect only live children. The id is reserved
1128
- * before the child is constructed and released when construction throws, so a refused launch never
1129
- * strands its key. Eviction follows the child's own `exit` promise, which no listener can forge, so
1130
- * it lands one microtask after the child's public `exit` event: a listener on that event still sees
1131
- * the child registered. `launch` throws a {@link createDuplicateError} when the id is already live
1132
- * and a {@link createProtocolError} after `destroy` has begun, including when the caller's own option
1133
- * getter begins that teardown mid-construction — a spawn fault surfaces through the returned child's
1134
- * `exit`, never from `launch`. `destroy` awaits every child's own teardown, including the teardown
1135
- * of a child a lost race refused, which destroys each child's observation emitter and leaves every
1136
- * subscription on it silently inert, then destroys the registry emitter last.
1070
+ * The child's ending and the supervision's ending are distinct. `pid`, `code`, and `signal` read
1071
+ * the host child directly, so they expose native exit as soon as the host records it. `settled`,
1072
+ * `exit`, `evidence`, and `lines` reach one terminal moment after the read channels close or the
1073
+ * bounded `drain` window cuts them off. That window is armed by the child's native exit and by a
1074
+ * termination this package initiated, so every way a child can end reaches the moment. The terminal
1075
+ * routine freezes `evidence` and ends `lines` before it destroys stdout, stderr, or the emitter, so
1076
+ * the push channel never disappears while a pull channel can still change or wait indefinitely.
1077
+ *
1078
+ * Standard output is framed through `readline` into a single-consumer stream. Lines already framed
1079
+ * and queued remain available through the terminal moment, while bytes arriving after a requested
1080
+ * termination may be cut off. Standard error is forwarded through the live `stderr` event and
1081
+ * retained as a byte-bounded raw tail until that same moment. `stop` ends the child tree and the
1082
+ * observation surfaces. `destroy` performs that stop and then destroys the emitter after the
1083
+ * frozen terminal state exists.
1137
1084
  *
1138
1085
  * @example
1139
1086
  * ```ts
1140
- * import { ProcessManager } from '@orkestrel/process/server'
1087
+ * import { Process } from '@orkestrel/process/server'
1141
1088
  *
1142
- * const manager = new ProcessManager()
1143
- * const child = manager.launch('build', {
1144
- * command: { file: 'node', arguments: ['build.js'] },
1089
+ * const child = new Process({
1090
+ * command: { file: 'node', arguments: ['--version'] },
1145
1091
  * workspace: process.cwd(),
1146
1092
  * })
1147
- * await child.exit
1148
- * await manager.destroy()
1093
+ * const exit = await child.exit
1094
+ * await child.destroy()
1149
1095
  * ```
1150
1096
  */
1151
- var ProcessManager = class {
1097
+ var Process = class {
1152
1098
  #emitter;
1153
- #children = /* @__PURE__ */ new Map();
1154
- #ids = /* @__PURE__ */ new Set();
1155
- #orphans = /* @__PURE__ */ new Set();
1156
- #destroying = false;
1157
- #ending;
1099
+ #engine;
1100
+ #reader;
1101
+ #backlog;
1102
+ #lines;
1103
+ #queue = [];
1104
+ #waiters = [];
1105
+ #head = 0;
1106
+ #pending = 0;
1107
+ #requested = false;
1108
+ #paused = false;
1109
+ #truncated = false;
1110
+ #ended = false;
1158
1111
  /**
1159
- * Construct a process registry.
1112
+ * Spawn one child process and begin stream capture.
1160
1113
  *
1161
- * @param options - Initial fleet-level observation hooks and listener-error handling
1162
- */
1114
+ * @param options - Command, workspace, termination, capture, stdin, and observation settings
1115
+ * @throws A {@link ProcessError} coded `invalid` when an option or command string is malformed
1116
+ */
1117
+ constructor(options) {
1118
+ const source = options.command;
1119
+ const workspace = options.workspace;
1120
+ const grace = options.grace;
1121
+ const drain = options.drain;
1122
+ const evidence = options.evidence;
1123
+ const backlog = options.backlog;
1124
+ const delivery = options.delivery;
1125
+ const writable = options.writable;
1126
+ const signal = options.signal;
1127
+ const on = options.on;
1128
+ const error = options.error;
1129
+ const command = snapshotCommand(source);
1130
+ validateCommand(command);
1131
+ validateWorkspace(workspace);
1132
+ validateTimer(grace, "option 'grace'");
1133
+ validateTimer(drain, "option 'drain'");
1134
+ validateTimer(delivery, "option 'delivery'");
1135
+ validateBytes(evidence, "option 'evidence'", 0);
1136
+ validateBytes(backlog, "option 'backlog'", 1);
1137
+ this.#emitter = new Emitter({
1138
+ ...on === void 0 ? {} : { on },
1139
+ ...error === void 0 ? {} : { error }
1140
+ });
1141
+ this.#backlog = backlog ?? PROCESS_BACKLOG;
1142
+ this.#lines = Object.freeze({ [Symbol.asyncIterator]: this.#iterate.bind(this) });
1143
+ this.#engine = new Supervisor({
1144
+ command,
1145
+ workspace,
1146
+ ...grace === void 0 ? {} : { grace },
1147
+ ...drain === void 0 ? {} : { drain },
1148
+ ...evidence === void 0 ? {} : { evidence },
1149
+ ...delivery === void 0 ? {} : { delivery },
1150
+ ...writable === void 0 ? {} : { writable },
1151
+ ...signal === void 0 ? {} : { signal }
1152
+ }, {
1153
+ chunk: this.#reportStderr.bind(this),
1154
+ fault: this.#reportFault.bind(this),
1155
+ relieve: this.#releaseBackpressure.bind(this),
1156
+ close: this.#closeReader.bind(this),
1157
+ terminal: this.#reportExit.bind(this),
1158
+ teardown: this.#emitter.destroy.bind(this.#emitter)
1159
+ });
1160
+ this.#reader = createInterface({
1161
+ input: this.#engine.stdout,
1162
+ crlfDelay: Infinity
1163
+ });
1164
+ this.#reader.on("line", this.#push.bind(this));
1165
+ this.#reader.once("close", this.#finish.bind(this));
1166
+ }
1167
+ /** The host process id, fixed when construction returns, or `undefined` when the spawn produced none. */
1168
+ get pid() {
1169
+ return this.#engine.pid;
1170
+ }
1171
+ /** The exit code the host recorded, or `null` while the child has not exited and when a signal ended it. */
1172
+ get code() {
1173
+ return this.#engine.code;
1174
+ }
1175
+ /** The terminating signal name the host recorded, or `null` while the child has not exited and when it exited on its own. */
1176
+ get signal() {
1177
+ return this.#engine.signal;
1178
+ }
1179
+ /** The typed lifecycle observation surface. */
1180
+ get emitter() {
1181
+ return this.#emitter;
1182
+ }
1183
+ /** The captured stdout lines, in arrival order, ending after queued lines at the terminal moment. */
1184
+ get lines() {
1185
+ return this.#lines;
1186
+ }
1187
+ /** The decoded byte-bounded stderr tail, frozen at the terminal moment. */
1188
+ get evidence() {
1189
+ return this.#engine.evidence;
1190
+ }
1191
+ /** True when the `lines` stream omitted output after a retention bound was reached. */
1192
+ get truncated() {
1193
+ return this.#truncated;
1194
+ }
1195
+ /** True after the terminal moment arrived and `exit` settled. */
1196
+ get settled() {
1197
+ return this.#engine.settled;
1198
+ }
1199
+ /** True after termination began, including after the terminal moment. */
1200
+ get stopping() {
1201
+ return this.#engine.stopping;
1202
+ }
1203
+ /** The terminal child state, observed once after stream close or the drain cutoff. */
1204
+ get exit() {
1205
+ return this.#engine.exit;
1206
+ }
1207
+ /**
1208
+ * Write one line to the open standard-input channel.
1209
+ *
1210
+ * @remarks
1211
+ * Never rejects. `true` means the host accepted the bytes without reporting a fault; it does not
1212
+ * prove that the child read them. An ordinary write settles when the kernel accepts it. Only a
1213
+ * full pipe can hold the write unconfirmed. The `delivery` option can bound that wait, and every
1214
+ * terminal teardown path settles pending writes. On Windows 11 with Node v24.18.1, measured on
1215
+ * 2026-08-21, a child that closes its own file descriptor 0 can leave the parent pipe writable:
1216
+ * the write can settle `true` without a callback error or a stream error while the child remains
1217
+ * alive. After `stop` or `destroy` begins, a later call settles `false`. Version 0.0.4 could settle
1218
+ * that call `true` before teardown destroyed the pipe; returning `false` avoids claiming delivery
1219
+ * for bytes the package is about to discard.
1220
+ *
1221
+ * @param text - The line text without its trailing newline
1222
+ * @returns True when the host accepted the bytes without reporting a fault; false when the channel was closed, destroyed, ended, failed, or remained unconfirmed through `delivery`
1223
+ */
1224
+ send(text) {
1225
+ return this.#engine.deliver(Buffer.from(`${text}\n`, "utf8"));
1226
+ }
1227
+ /**
1228
+ * Terminates the child process tree and reaches the terminal observation moment.
1229
+ *
1230
+ * @remarks
1231
+ * Never rejects, and every call shares one termination. After the termination sequence returns,
1232
+ * confirmed or not, it waits at most `drain` for the read channels to close, then cuts them off
1233
+ * and settles `exit` when they remain open. A cutoff reached before the native exit reports the
1234
+ * `code` and `signal` the host had recorded by then, which is `null` for a child still running.
1235
+ *
1236
+ * @returns True when the child's native exit was observed; false when the confirmation deadline elapsed without it
1237
+ */
1238
+ stop() {
1239
+ return this.#engine.stop();
1240
+ }
1241
+ /**
1242
+ * Stops the child and destroys the observation emitter after the terminal state freezes.
1243
+ *
1244
+ * @remarks
1245
+ * Always resolves, including when termination was never confirmed. Every call shares one barrier.
1246
+ *
1247
+ * @returns The stable barrier shared by every call
1248
+ */
1249
+ destroy() {
1250
+ return this.#engine.destroy();
1251
+ }
1252
+ #iterate() {
1253
+ this.#requested = true;
1254
+ return Object.freeze({ next: this.#next.bind(this) });
1255
+ }
1256
+ #next() {
1257
+ const line = this.#take();
1258
+ if (line !== void 0) return Promise.resolve(Object.freeze({
1259
+ done: false,
1260
+ value: line
1261
+ }));
1262
+ if (this.#ended) return Promise.resolve(Object.freeze({
1263
+ done: true,
1264
+ value: void 0
1265
+ }));
1266
+ const waiter = Promise.withResolvers();
1267
+ this.#waiters.push(waiter);
1268
+ return waiter.promise;
1269
+ }
1270
+ #take() {
1271
+ if (this.#head >= this.#queue.length) return void 0;
1272
+ const line = this.#queue[this.#head];
1273
+ this.#queue[this.#head] = "";
1274
+ this.#head += 1;
1275
+ if (this.#head >= this.#queue.length) {
1276
+ this.#queue.length = 0;
1277
+ this.#head = 0;
1278
+ }
1279
+ if (line === void 0) return void 0;
1280
+ this.#pending -= Buffer.byteLength(line) + 1;
1281
+ this.#relieve();
1282
+ return line;
1283
+ }
1284
+ #push(line) {
1285
+ if (this.#ended) return;
1286
+ const waiter = this.#waiters.shift();
1287
+ if (waiter !== void 0) {
1288
+ waiter.resolve(Object.freeze({
1289
+ done: false,
1290
+ value: line
1291
+ }));
1292
+ return;
1293
+ }
1294
+ const bytes = Buffer.byteLength(line) + 1;
1295
+ const stopping = this.#engine.stopping;
1296
+ const limit = stopping ? this.#backlog * 2 : this.#backlog;
1297
+ if ((!this.#requested || stopping) && this.#pending + bytes > limit) {
1298
+ this.#truncated = true;
1299
+ return;
1300
+ }
1301
+ this.#queue.push(line);
1302
+ this.#pending += bytes;
1303
+ this.#restrain();
1304
+ }
1305
+ #restrain() {
1306
+ if (this.#paused || this.#engine.stopping || !this.#requested) return;
1307
+ if (this.#pending < this.#backlog) return;
1308
+ this.#paused = true;
1309
+ this.#reader.pause();
1310
+ }
1311
+ #relieve() {
1312
+ if (!this.#paused || this.#ended) return;
1313
+ if (!this.#engine.stopping && this.#pending > this.#backlog / 2) return;
1314
+ this.#paused = false;
1315
+ this.#reader.resume();
1316
+ }
1317
+ #finish() {
1318
+ if (this.#ended) return;
1319
+ this.#ended = true;
1320
+ this.#paused = false;
1321
+ for (const waiter of this.#waiters) waiter.resolve(Object.freeze({
1322
+ done: true,
1323
+ value: void 0
1324
+ }));
1325
+ this.#waiters.length = 0;
1326
+ }
1327
+ #reportStderr(chunk) {
1328
+ this.#emitter.emit("stderr", chunk);
1329
+ }
1330
+ #reportFault(cause) {
1331
+ this.#emitter.emit("error", cause);
1332
+ }
1333
+ #reportExit(exit) {
1334
+ this.#emitter.emit("exit", exit);
1335
+ }
1336
+ #closeReader() {
1337
+ this.#reader.close();
1338
+ }
1339
+ #releaseBackpressure() {
1340
+ this.#capBacklog();
1341
+ this.#relieve();
1342
+ }
1343
+ #capBacklog() {
1344
+ const limit = this.#backlog * 2;
1345
+ while (this.#pending > limit && this.#queue.length > this.#head) {
1346
+ const line = this.#queue.pop();
1347
+ if (line === void 0) break;
1348
+ this.#pending -= Buffer.byteLength(line) + 1;
1349
+ this.#truncated = true;
1350
+ }
1351
+ }
1352
+ };
1353
+ //#endregion
1354
+ //#region src/server/ProcessManager.ts
1355
+ /**
1356
+ * A keyed registry of live supervised child processes.
1357
+ *
1358
+ * @remarks
1359
+ * A child launched under an id joins the registry and emits `launch`; when it settles it removes
1360
+ * itself and emits `exit`, so `count` and `processes` reflect only live children. The id is reserved
1361
+ * before the child is constructed and released when construction throws, so a refused launch never
1362
+ * strands its key. Eviction follows the child's own `exit` promise, which no listener can forge, so
1363
+ * it lands one microtask after the child's public `exit` event: a listener on that event still sees
1364
+ * the child registered. `launch` throws a {@link createDuplicateError} when the id is already live
1365
+ * and a {@link createProtocolError} after `destroy` has begun, including when the caller's own option
1366
+ * getter begins that teardown mid-construction — a spawn fault surfaces through the returned child's
1367
+ * `exit`, never from `launch`. `destroy` awaits every child's own teardown, including the teardown
1368
+ * of a child a lost race refused, which destroys each child's observation emitter and leaves every
1369
+ * subscription on it silently inert, then destroys the registry emitter last.
1370
+ *
1371
+ * @example
1372
+ * ```ts
1373
+ * import { ProcessManager } from '@orkestrel/process/server'
1374
+ *
1375
+ * const manager = new ProcessManager()
1376
+ * const child = manager.launch('build', {
1377
+ * command: { file: 'node', arguments: ['build.js'] },
1378
+ * workspace: process.cwd(),
1379
+ * })
1380
+ * await child.exit
1381
+ * await manager.destroy()
1382
+ * ```
1383
+ */
1384
+ var ProcessManager = class {
1385
+ #emitter;
1386
+ #children = /* @__PURE__ */ new Map();
1387
+ #ids = /* @__PURE__ */ new Set();
1388
+ #orphans = /* @__PURE__ */ new Set();
1389
+ #destroying = false;
1390
+ #ending;
1391
+ /**
1392
+ * Construct a process registry.
1393
+ *
1394
+ * @param options - Initial fleet-level observation hooks and listener-error handling
1395
+ */
1163
1396
  constructor(options) {
1164
1397
  const on = options?.on;
1165
1398
  const error = options?.error;
@@ -1285,6 +1518,185 @@ var ProcessManager = class {
1285
1518
  }
1286
1519
  };
1287
1520
  //#endregion
1521
+ //#region src/server/Session.ts
1522
+ /**
1523
+ * Supervises one child and publishes its standard output as raw bytes.
1524
+ *
1525
+ * @remarks
1526
+ * This is the transport face of the supervision engine `Process` also composes. It frames nothing,
1527
+ * retains nothing, and decodes nothing on the output side: every chunk the host delivers becomes one
1528
+ * `stdout` event carrying that chunk's bytes, in arrival order, so a consumer speaking a
1529
+ * length-prefixed or delimiter-framed protocol reads exactly what the child wrote and frames it
1530
+ * itself. Standard error is decoded and forwarded as `stderr`, with the same byte-bounded `evidence`
1531
+ * tail a `Process` retains, because a diagnostic stream is read rather than parsed.
1532
+ *
1533
+ * Each emitted chunk is this session's own copy, and a plain `Uint8Array` rather than the host buffer
1534
+ * the read produced, so a consumer keeps it, mutates it, concatenates it, and reads its whole backing
1535
+ * buffer without reaching memory the host still manages and without depending on a host type.
1536
+ *
1537
+ * The standard-input channel is open from the spawn, and `end` closes it without terminating
1538
+ * anything. That is the member a cooperative protocol reaches for: the child observes end of input
1539
+ * and exits on its own, which `ending` reports. `stop` and `destroy` remain the terminations, and
1540
+ * `exit` remains the terminal moment — the native exit plus at most `drain` while a descendant holds
1541
+ * the inherited read ends.
1542
+ *
1543
+ * @example
1544
+ * ```ts
1545
+ * import { Session } from '@orkestrel/process/server'
1546
+ *
1547
+ * const session = new Session({
1548
+ * command: { file: 'node', arguments: ['-e', 'process.stdin.pipe(process.stdout)'] },
1549
+ * workspace: process.cwd(),
1550
+ * })
1551
+ * ```
1552
+ */
1553
+ var Session = class {
1554
+ #emitter;
1555
+ #engine;
1556
+ #output;
1557
+ /**
1558
+ * Spawn one child process and begin byte capture.
1559
+ *
1560
+ * @param options - Command, workspace, termination, evidence, stdin, and observation settings
1561
+ * @throws A {@link ProcessError} coded `invalid` when an option or command string is malformed
1562
+ */
1563
+ constructor(options) {
1564
+ const command = options.command;
1565
+ const workspace = options.workspace;
1566
+ const grace = options.grace;
1567
+ const drain = options.drain;
1568
+ const evidence = options.evidence;
1569
+ const delivery = options.delivery;
1570
+ const signal = options.signal;
1571
+ const on = options.on;
1572
+ const error = options.error;
1573
+ this.#emitter = new Emitter({
1574
+ ...on === void 0 ? {} : { on },
1575
+ ...error === void 0 ? {} : { error }
1576
+ });
1577
+ this.#output = this.#publish.bind(this);
1578
+ this.#engine = new Supervisor({
1579
+ command,
1580
+ workspace,
1581
+ writable: true,
1582
+ ...grace === void 0 ? {} : { grace },
1583
+ ...drain === void 0 ? {} : { drain },
1584
+ ...evidence === void 0 ? {} : { evidence },
1585
+ ...delivery === void 0 ? {} : { delivery },
1586
+ ...signal === void 0 ? {} : { signal }
1587
+ }, {
1588
+ chunk: this.#reportStderr.bind(this),
1589
+ fault: this.#reportFault.bind(this),
1590
+ close: this.#closeOutput.bind(this),
1591
+ terminal: this.#reportExit.bind(this),
1592
+ teardown: this.#emitter.destroy.bind(this.#emitter)
1593
+ });
1594
+ this.#engine.stdout.on("data", this.#output);
1595
+ }
1596
+ /** The host process id, fixed when construction returns, or `undefined` when the spawn produced none. */
1597
+ get pid() {
1598
+ return this.#engine.pid;
1599
+ }
1600
+ /** The exit code the host recorded, or `null` while the child has not exited and when a signal ended it. */
1601
+ get code() {
1602
+ return this.#engine.code;
1603
+ }
1604
+ /** The terminating signal name the host recorded, or `null` while the child has not exited and when it exited on its own. */
1605
+ get signal() {
1606
+ return this.#engine.signal;
1607
+ }
1608
+ /** The typed lifecycle observation surface. */
1609
+ get emitter() {
1610
+ return this.#emitter;
1611
+ }
1612
+ /** The decoded byte-bounded stderr tail, frozen at the terminal moment. */
1613
+ get evidence() {
1614
+ return this.#engine.evidence;
1615
+ }
1616
+ /** True after the terminal moment arrived and `exit` settled. */
1617
+ get settled() {
1618
+ return this.#engine.settled;
1619
+ }
1620
+ /** True after termination began, including after the terminal moment. `end` never turns it true. */
1621
+ get stopping() {
1622
+ return this.#engine.stopping;
1623
+ }
1624
+ /** The child's own ending, awaited without the terminal moment's drain window. */
1625
+ get ending() {
1626
+ return this.#engine.ending;
1627
+ }
1628
+ /** The terminal child state, observed once after stream close or the drain cutoff. */
1629
+ get exit() {
1630
+ return this.#engine.exit;
1631
+ }
1632
+ /**
1633
+ * Writes raw bytes to the open standard-input channel.
1634
+ *
1635
+ * @remarks
1636
+ * Never rejects, and appends no terminator: the child receives exactly these bytes. `true` means
1637
+ * the host accepted them without reporting a fault; it does not prove that the child read them.
1638
+ * The channel can queue the payload, so treat the array as owned by the channel until the
1639
+ * returned promise settles.
1640
+ *
1641
+ * @param bytes - The payload to write, already framed by the caller
1642
+ * @returns True when the host accepted the bytes without reporting a fault; false when the channel was closed, destroyed, ended, failed, or remained unconfirmed through `delivery`
1643
+ */
1644
+ write(bytes) {
1645
+ return this.#engine.deliver(bytes);
1646
+ }
1647
+ /**
1648
+ * Closes the standard-input channel and leaves the child running.
1649
+ *
1650
+ * @remarks
1651
+ * Never rejects and terminates nothing: `stopping` stays false and the terminal moment does not
1652
+ * arrive. Every call shares one barrier, which resolves after the host flushes the writes it had
1653
+ * already accepted. A later `write` resolves `false`.
1654
+ *
1655
+ * @returns The stable barrier shared by every call
1656
+ */
1657
+ end() {
1658
+ return this.#engine.end();
1659
+ }
1660
+ /**
1661
+ * Terminates the child process tree and reaches the terminal observation moment.
1662
+ *
1663
+ * @remarks
1664
+ * Never rejects, and every call shares one termination bounded by `grace` and `drain`.
1665
+ *
1666
+ * @returns True when the child's native exit was observed; false when the confirmation deadline elapsed without it
1667
+ */
1668
+ stop() {
1669
+ return this.#engine.stop();
1670
+ }
1671
+ /**
1672
+ * Stops the child and destroys the observation emitter after the terminal state freezes.
1673
+ *
1674
+ * @remarks
1675
+ * Always resolves, including when termination was never confirmed. Every call shares one barrier.
1676
+ *
1677
+ * @returns The stable barrier shared by every call
1678
+ */
1679
+ destroy() {
1680
+ return this.#engine.destroy();
1681
+ }
1682
+ #publish(chunk) {
1683
+ if (!Buffer.isBuffer(chunk) || this.#engine.settled) return;
1684
+ this.#emitter.emit("stdout", new Uint8Array(chunk));
1685
+ }
1686
+ #reportStderr(chunk) {
1687
+ this.#emitter.emit("stderr", chunk);
1688
+ }
1689
+ #reportFault(cause) {
1690
+ this.#emitter.emit("error", cause);
1691
+ }
1692
+ #reportExit(exit) {
1693
+ this.#emitter.emit("exit", exit);
1694
+ }
1695
+ #closeOutput() {
1696
+ this.#engine.stdout.off("data", this.#output);
1697
+ }
1698
+ };
1699
+ //#endregion
1288
1700
  //#region src/server/factories.ts
1289
1701
  /**
1290
1702
  * Creates one supervised child process.
@@ -1307,6 +1719,26 @@ function createProcess(options) {
1307
1719
  return new Process(options);
1308
1720
  }
1309
1721
  /**
1722
+ * Creates one raw byte session over a supervised child process.
1723
+ *
1724
+ * @param options - Command, workspace, termination, evidence, stdin, and observation settings
1725
+ * @returns The launched {@link SessionInterface}
1726
+ *
1727
+ * @example
1728
+ * ```ts
1729
+ * import { createSession } from '@orkestrel/process/server'
1730
+ *
1731
+ * const session = createSession({
1732
+ * command: { file: 'node', arguments: ['server.js', '--stdio'] },
1733
+ * workspace: process.cwd(),
1734
+ * grace: 5000,
1735
+ * })
1736
+ * ```
1737
+ */
1738
+ function createSession(options) {
1739
+ return new Session(options);
1740
+ }
1741
+ /**
1310
1742
  * Creates one keyed registry of supervised child processes.
1311
1743
  *
1312
1744
  * @param options - Initial fleet-level observation hooks and listener-error handling
@@ -1660,6 +2092,6 @@ function detach(command, options) {
1660
2092
  child.unref();
1661
2093
  }
1662
2094
  //#endregion
1663
- export { Process, ProcessManager, Retention, buildExecutableCandidates, buildExecuteResult, buildPlatformSpawn, buildSpawn, createProcess, createProcessManager, detach, execute, executeSync, formatCommand, isExited, isFile, killProcess, killTree, mergeEnvironment, mergePlatformEnvironment, quoteArgument, readPlatformVariable, readVariable, resolveExecutable, snapshotCommand, stopChild, trimHead, trimTail, validateBytes, validateCommand, validateEnvironment, validateText, validateTimer, validateWorkspace, waitForClose, waitForExit };
2095
+ export { Process, ProcessManager, Retention, Session, buildExecutableCandidates, buildExecuteResult, buildPlatformSpawn, buildSpawn, createProcess, createProcessManager, createSession, detach, execute, executeSync, formatCommand, isExited, isFile, killProcess, killTree, mergeEnvironment, mergePlatformEnvironment, quoteArgument, readPlatformVariable, readVariable, resolveExecutable, snapshotCommand, stopChild, trimHead, trimTail, validateBytes, validateCommand, validateEnvironment, validateText, validateTimer, validateWorkspace, waitForClose, waitForExit };
1664
2096
 
1665
2097
  //# sourceMappingURL=index.js.map