@orkestrel/process 0.0.15 → 0.0.16

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.
@@ -9,7 +9,7 @@ let _src_core = require("../core/index.cjs");
9
9
  let node_readline = require("node:readline");
10
10
  let _orkestrel_emitter = require("@orkestrel/emitter");
11
11
  let node_string_decoder = require("node:string_decoder");
12
- //#region src/server/cloners.ts
12
+ // src/server/cloners.ts
13
13
  /**
14
14
  * Takes one owned frozen snapshot of a caller's command.
15
15
  *
@@ -44,8 +44,7 @@ function snapshotCommand(command) {
44
44
  ...isolated === void 0 ? {} : { isolated }
45
45
  });
46
46
  }
47
- //#endregion
48
- //#region src/server/helpers.ts
47
+ // src/server/helpers.ts
49
48
  /**
50
49
  * Trims a buffer to at most `limit` trailing bytes without splitting a UTF-8 sequence.
51
50
  *
@@ -66,13 +65,13 @@ function snapshotCommand(command) {
66
65
  function trimTail(bytes, limit) {
67
66
  const buffer = node_buffer.Buffer.isBuffer(bytes) ? bytes : node_buffer.Buffer.from(bytes);
68
67
  if (buffer.byteLength <= limit) return buffer;
69
- let start = buffer.byteLength - limit;
70
- while (start < buffer.byteLength) {
68
+ const offset = buffer.byteLength - limit;
69
+ if (!(offset < buffer.byteLength)) return node_buffer.Buffer.from(buffer.subarray(offset));
70
+ for (const start of Array.from({ length: Math.ceil(buffer.byteLength - offset) }, (_, index) => offset + index)) {
71
71
  const byte = buffer[start];
72
- if (byte === void 0 || (byte & 192) !== 128) break;
73
- start += 1;
72
+ if (byte === void 0 || (byte & 192) !== 128) return node_buffer.Buffer.from(buffer.subarray(start));
74
73
  }
75
- return node_buffer.Buffer.from(buffer.subarray(start));
74
+ return node_buffer.Buffer.from(buffer.subarray(buffer.byteLength));
76
75
  }
77
76
  /**
78
77
  * Trims a buffer to at most `limit` leading bytes without splitting a UTF-8 sequence.
@@ -100,18 +99,12 @@ function trimHead(bytes, limit) {
100
99
  const excluded = buffer[limit];
101
100
  if (excluded === void 0 || (excluded & 192) !== 128) return node_buffer.Buffer.from(buffer.subarray(0, limit));
102
101
  const floor = Math.max(0, limit - 3);
103
- let start = limit - 1;
104
- while (start >= floor) {
102
+ for (const start of Array.from({ length: Math.max(0, Math.floor(limit - floor)) }, (_, index) => limit - 1 - index)) {
105
103
  const lead = buffer[start];
106
104
  if (lead !== void 0 && (lead & 192) !== 128) {
107
- let span = 1;
108
- if ((lead & 224) === 192) span = 2;
109
- else if ((lead & 240) === 224) span = 3;
110
- else if ((lead & 248) === 240) span = 4;
111
- if (start + span > limit) return node_buffer.Buffer.from(buffer.subarray(0, start));
105
+ if (start + ((lead & 224) === 192 ? 2 : (lead & 240) === 224 ? 3 : (lead & 248) === 240 ? 4 : 1) > limit) return node_buffer.Buffer.from(buffer.subarray(0, start));
112
106
  break;
113
107
  }
114
- start -= 1;
115
108
  }
116
109
  return node_buffer.Buffer.from(buffer.subarray(0, limit));
117
110
  }
@@ -553,6 +546,27 @@ function isExited(child) {
553
546
  return child.exitCode !== null || child.signalCode !== null;
554
547
  }
555
548
  /**
549
+ * Reports whether a process or POSIX process group still exists.
550
+ *
551
+ * @param pid - A positive process id, or a negative POSIX process-group id
552
+ * @returns True when the host confirms existence or refuses permission; false for ESRCH; undefined for another host failure
553
+ * @throws A {@link ProcessError} coded `invalid` for an unsafe integer, zero, minus one, or a negative id on Windows
554
+ * @remarks Negative ids probe POSIX process groups. The `process.platform` branch refuses negative ids on Windows, where the host supports positive process ids only.
555
+ * @example
556
+ * ```ts
557
+ * inspectProcess(process.pid) // true
558
+ * ```
559
+ */
560
+ function inspectProcess(pid) {
561
+ if (!Number.isSafeInteger(pid) || pid === 0 || pid === -1 || process.platform === "win32" && pid < 0) throw (0, _src_core.createInvalidError)("process id", pid);
562
+ try {
563
+ return process.kill(pid, 0);
564
+ } catch (error) {
565
+ const code = (0, _orkestrel_contract.isError)(error) && "code" in error ? error.code : void 0;
566
+ return code === "ESRCH" ? false : code === "EPERM" ? true : void 0;
567
+ }
568
+ }
569
+ /**
556
570
  * Signals one owned child process, or its detached process group on a POSIX host.
557
571
  *
558
572
  * @remarks
@@ -745,262 +759,6 @@ function buildExecuteResult(input) {
745
759
  });
746
760
  }
747
761
  /**
748
- * Runs one command to completion, buffering its output, and settles with the outcome.
749
- *
750
- * @remarks
751
- * The executable is resolved through {@link buildSpawn}, so no run uses a shell. On a POSIX host the
752
- * child is detached, which is what lets its whole process group be terminated. Standard output and
753
- * error are each byte-bounded by `limit` (default {@link PROCESS_OUTPUT}) and `truncated` reports
754
- * whether either stream exceeded it. Each capture keeps one byte beyond `limit`, so the single
755
- * code-point-boundary trim in {@link buildExecuteResult} reads the first excluded byte and never
756
- * delivers a split multibyte sequence. A positive `timeout` and an aborting `signal` both terminate the
757
- * child through the same bounded stop, and only the earliest is recorded: a timeout reports
758
- * `expired` and an abort reports `aborted`, never both. After termination the outcome is awaited for
759
- * a bounded window, so a descendant holding the child's stdio cannot keep the run pending. That bound
760
- * covers a terminated run alone: a run with no `timeout` and no `signal` settles on stdio completion
761
- * rather than on process exit, so a descendant that inherited the child's stdio holds the run open
762
- * after the child itself has gone. Give such a run a `timeout`. The child's `environment` merges over
763
- * the parent unless the command is `isolated`, then `options.environment` on top, and `options.input`
764
- * overrides `command.input`. A host fault while writing that input terminates the run by design and
765
- * marks its result failed; a strict rejection carries the host fault as its cause. This differs
766
- * from `Process` constructor input, whose package-initiated input phase stays quiet. Unless `strict`
767
- * is `false`, a failed run rejects with a {@link ProcessError} carrying the {@link ExecuteResult};
768
- * {@link createExecuteError} constructs every rejection outside the input-fault door. An invalid
769
- * option or command string rejects before the child is spawned, because an async function cannot
770
- * throw synchronously.
771
- *
772
- * @param command - The executable, arguments, and optional environment and input
773
- * @param options - Working directory, timeout, grace, signal, capture limit, and failure delivery
774
- * @returns The settled run outcome
775
- * @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`
776
- *
777
- * @example
778
- * ```ts
779
- * const result = await execute({ file: 'git', arguments: ['status'] }, { workspace: process.cwd() })
780
- * ```
781
- */
782
- async function execute(command, options) {
783
- const snapshot = snapshotCommand(command);
784
- const optionEnvironment = options?.environment;
785
- const optionWorkspace = options?.workspace;
786
- const optionInput = options?.input;
787
- const optionTimeout = options?.timeout;
788
- const optionGrace = options?.grace;
789
- const optionSignal = options?.signal;
790
- const optionStrict = options?.strict;
791
- const optionLimit = options?.limit;
792
- validateCommand(snapshot);
793
- validateEnvironment(optionEnvironment);
794
- validateWorkspace(optionWorkspace);
795
- validateTimer(optionTimeout, "option 'timeout'");
796
- validateTimer(optionGrace, "option 'grace'");
797
- validateBytes(optionLimit, "option 'limit'", 0);
798
- const limit = optionLimit ?? _src_core.PROCESS_OUTPUT;
799
- const grace = optionGrace ?? _src_core.PROCESS_GRACE;
800
- const timeout = optionTimeout ?? 0;
801
- const strict = optionStrict ?? true;
802
- const text = optionInput ?? snapshot.input;
803
- const line = formatCommand(snapshot);
804
- const workspace = optionWorkspace ?? process.cwd();
805
- const environment = mergeEnvironment(snapshot.isolated === true, snapshot.environment, optionEnvironment);
806
- const plan = buildSpawn(snapshot, {
807
- workspace,
808
- environment
809
- });
810
- const settled = Promise.withResolvers();
811
- const terminate = new AbortController();
812
- const cleanup = new AbortController();
813
- const finish = new AbortController();
814
- const inputFailure = new AbortController();
815
- const outChunks = [];
816
- const errChunks = [];
817
- let spawned = false;
818
- let expired = false;
819
- let aborted = false;
820
- let outRetained = 0;
821
- let errRetained = 0;
822
- let cause;
823
- let timeoutTimer;
824
- let confirmTimer;
825
- const child = (0, node_child_process.spawn)(plan.file, [...plan.arguments], {
826
- cwd: workspace,
827
- detached: process.platform !== "win32",
828
- env: environment,
829
- stdio: [
830
- "pipe",
831
- "pipe",
832
- "pipe"
833
- ],
834
- windowsHide: true,
835
- windowsVerbatimArguments: plan.verbatim
836
- });
837
- finish.signal.addEventListener("abort", () => {
838
- clearTimeout(timeoutTimer);
839
- clearTimeout(confirmTimer);
840
- cleanup.abort();
841
- settled.resolve(buildExecuteResult({
842
- command: line,
843
- stdout: node_buffer.Buffer.concat(outChunks),
844
- stderr: node_buffer.Buffer.concat(errChunks),
845
- code: child.exitCode,
846
- signal: child.signalCode,
847
- expired,
848
- aborted,
849
- truncated: outRetained > limit || errRetained > limit,
850
- limit,
851
- ...cause === void 0 ? {} : { cause }
852
- }));
853
- }, { once: true });
854
- terminate.signal.addEventListener("abort", () => {
855
- if (finish.signal.aborted) return;
856
- clearTimeout(timeoutTimer);
857
- cleanup.abort();
858
- stopChild(child, grace, _src_core.PROCESS_CONFIRMATION).then(() => {
859
- if (finish.signal.aborted) return;
860
- confirmTimer = setTimeout(() => finish.abort(), _src_core.PROCESS_CONFIRMATION);
861
- });
862
- }, { once: true });
863
- inputFailure.signal.addEventListener("abort", () => {
864
- if (finish.signal.aborted) return;
865
- cause = inputFailure.signal.reason;
866
- terminate.abort();
867
- }, { once: true });
868
- child.stdin.on("error", (error) => inputFailure.abort(error));
869
- child.stdout.on("data", (chunk) => {
870
- const captured = captureChunk(chunk, limit + 1 - outRetained);
871
- if (captured === void 0) return;
872
- outRetained += captured.byteLength;
873
- outChunks.push(captured);
874
- });
875
- child.stderr.on("data", (chunk) => {
876
- const captured = captureChunk(chunk, limit + 1 - errRetained);
877
- if (captured === void 0) return;
878
- errRetained += captured.byteLength;
879
- errChunks.push(captured);
880
- });
881
- child.once("spawn", () => {
882
- spawned = true;
883
- });
884
- child.once("error", (error) => {
885
- if (spawned) return;
886
- cause = error;
887
- finish.abort();
888
- });
889
- child.once("close", () => finish.abort());
890
- if (text !== void 0) child.stdin.write(text, (error) => {
891
- if (error !== void 0 && error !== null) inputFailure.abort(error);
892
- });
893
- child.stdin.end();
894
- if (timeout > 0) timeoutTimer = setTimeout(() => {
895
- expired = true;
896
- terminate.abort();
897
- }, timeout);
898
- if (optionSignal !== void 0) {
899
- optionSignal.addEventListener("abort", () => {
900
- aborted = true;
901
- terminate.abort();
902
- }, {
903
- once: true,
904
- signal: cleanup.signal
905
- });
906
- if (optionSignal.aborted) {
907
- aborted = true;
908
- terminate.abort();
909
- }
910
- }
911
- const result = await settled.promise;
912
- if (result.failed && strict) {
913
- if (cause !== void 0 && cause === inputFailure.signal.reason) throw new _src_core.ProcessError(`Command '${result.command}' failed while writing standard input`, {
914
- code: "input",
915
- context: {
916
- command: result.command,
917
- code: result.code,
918
- signal: result.signal
919
- },
920
- cause: inputFailure.signal.reason,
921
- result
922
- });
923
- throw (0, _src_core.createExecuteError)(result, cause);
924
- }
925
- return result;
926
- }
927
- /**
928
- * Runs one command to completion synchronously, buffering its output, and returns the outcome.
929
- *
930
- * @remarks
931
- * The synchronous counterpart of {@link execute}, spawned through the same resolver and never through a
932
- * shell. The host offers no cooperative termination window and no in-flight cancellation, so this
933
- * contract carries neither. A positive `timeout` ends only the root process and can leave
934
- * descendants running; use {@link execute} or {@link Process} when timeout must terminate the tree. A
935
- * timeout and an output overflow both end the root with `SIGKILL`: an overflow reports `truncated`
936
- * and `failed` together and trims the partial output to `limit`, where {@link execute} keeps reading and
937
- * reports `truncated` without failing. The environment and input follow the same merge as
938
- * {@link execute}. Unless `strict` is `false`, a failed run throws a {@link createExecuteError}
939
- * carrying the {@link ExecuteResult}.
940
- *
941
- * @param command - The executable, arguments, and optional environment and input
942
- * @param options - Working directory, timeout, capture limit, and failure delivery
943
- * @returns The run outcome
944
- * @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`
945
- *
946
- * @example
947
- * ```ts
948
- * const result = executeSync({ file: 'git', arguments: ['--version'] }, { strict: false })
949
- * ```
950
- */
951
- function executeSync(command, options) {
952
- const snapshot = snapshotCommand(command);
953
- const optionEnvironment = options?.environment;
954
- const optionWorkspace = options?.workspace;
955
- const optionInput = options?.input;
956
- const optionTimeout = options?.timeout;
957
- const optionStrict = options?.strict;
958
- const optionLimit = options?.limit;
959
- validateCommand(snapshot);
960
- validateEnvironment(optionEnvironment);
961
- validateWorkspace(optionWorkspace);
962
- validateTimer(optionTimeout, "option 'timeout'");
963
- validateBytes(optionLimit, "option 'limit'", 0);
964
- const limit = optionLimit ?? _src_core.PROCESS_OUTPUT;
965
- const timeout = optionTimeout ?? 0;
966
- const strict = optionStrict ?? true;
967
- const text = optionInput ?? snapshot.input;
968
- const line = formatCommand(snapshot);
969
- const workspace = optionWorkspace ?? process.cwd();
970
- const environment = mergeEnvironment(snapshot.isolated === true, snapshot.environment, optionEnvironment);
971
- const plan = buildSpawn(snapshot, {
972
- workspace,
973
- environment
974
- });
975
- const outcome = (0, node_child_process.spawnSync)(plan.file, [...plan.arguments], {
976
- cwd: workspace,
977
- env: environment,
978
- encoding: "buffer",
979
- maxBuffer: limit,
980
- killSignal: "SIGKILL",
981
- windowsHide: true,
982
- windowsVerbatimArguments: plan.verbatim,
983
- ...text !== void 0 ? { input: node_buffer.Buffer.from(text) } : {},
984
- ...timeout > 0 ? { timeout } : {}
985
- });
986
- const error = outcome.error;
987
- const fault = error !== void 0 && "code" in error && (0, _orkestrel_contract.isString)(error.code) ? error.code : "";
988
- const result = buildExecuteResult({
989
- command: line,
990
- stdout: node_buffer.Buffer.isBuffer(outcome.stdout) ? outcome.stdout : node_buffer.Buffer.alloc(0),
991
- stderr: node_buffer.Buffer.isBuffer(outcome.stderr) ? outcome.stderr : node_buffer.Buffer.alloc(0),
992
- code: outcome.status,
993
- signal: outcome.signal,
994
- expired: fault === "ETIMEDOUT",
995
- aborted: false,
996
- truncated: fault === "ENOBUFS",
997
- limit,
998
- ...error === void 0 ? {} : { cause: error }
999
- });
1000
- if (result.failed && strict) throw (0, _src_core.createExecuteError)(result, error);
1001
- return result;
1002
- }
1003
- /**
1004
762
  * Spawns one command as a detached process and returns without waiting for it.
1005
763
  *
1006
764
  * @remarks
@@ -1041,8 +799,7 @@ function detach(command, options) {
1041
799
  child.once("error", () => void 0);
1042
800
  child.unref();
1043
801
  }
1044
- //#endregion
1045
- //#region src/server/processes/Supervisor.ts
802
+ // src/server/processes/Supervisor.ts
1046
803
  /**
1047
804
  * Supervises one child process and reports each lifecycle moment to the face composing it.
1048
805
  *
@@ -1055,7 +812,7 @@ function detach(command, options) {
1055
812
  * emitter and event map its consumers subscribe to.
1056
813
  *
1057
814
  * The child's ending and the supervision's ending are distinct. `pid`, `code`, and `signal` read the
1058
- * host child directly, so they expose native exit as soon as the host records it, and `ending`
815
+ * host child directly, so they expose native exit as soon as the host records it, and `ended`
1059
816
  * settles at that same moment. `settled`, `exit`, and `evidence` reach one terminal moment after the
1060
817
  * read channels close or the bounded `drain` window cuts them off. That window is armed by the
1061
818
  * child's native exit and by a termination this package initiated, so every way a child can end
@@ -1105,12 +862,12 @@ var Supervisor = class {
1105
862
  #delivery;
1106
863
  #decoder = new node_string_decoder.StringDecoder("utf8");
1107
864
  #exit = Promise.withResolvers();
1108
- #ending = Promise.withResolvers();
865
+ #ended = Promise.withResolvers();
1109
866
  #signal;
1110
867
  #abort;
1111
868
  #writes = /* @__PURE__ */ new Map();
1112
869
  #tail = node_buffer.Buffer.alloc(0);
1113
- #stopping = false;
870
+ #terminated = false;
1114
871
  #settled = false;
1115
872
  #input = 0;
1116
873
  #failure;
@@ -1221,8 +978,8 @@ var Supervisor = class {
1221
978
  return this.#settled;
1222
979
  }
1223
980
  /** Reports whether termination began, including after the terminal moment. */
1224
- get stopping() {
1225
- return this.#stopping;
981
+ get terminated() {
982
+ return this.#terminated;
1226
983
  }
1227
984
  /**
1228
985
  * Settles at the child's own ending, awaited without the terminal moment's drain window.
@@ -1232,8 +989,8 @@ var Supervisor = class {
1232
989
  * the host records them. It settles at the native exit, and at the terminal moment for a child
1233
990
  * whose spawn produced no native exit at all, so it can never outlive the supervision.
1234
991
  */
1235
- get ending() {
1236
- return this.#ending.promise;
992
+ get ended() {
993
+ return this.#ended.promise;
1237
994
  }
1238
995
  /** Settles with the terminal child state, observed once after stream close or the drain cutoff. */
1239
996
  get exit() {
@@ -1279,7 +1036,7 @@ var Supervisor = class {
1279
1036
  */
1280
1037
  deliver(bytes) {
1281
1038
  const stdin = this.#child.stdin;
1282
- if (this.#settled || this.#stopping || this.#failure !== void 0 || !stdin.writable) return Promise.resolve(false);
1039
+ if (this.#settled || this.#terminated || this.#failure !== void 0 || !stdin.writable) return Promise.resolve(false);
1283
1040
  const settled = Promise.withResolvers();
1284
1041
  this.#writes.set(settled, void 0);
1285
1042
  try {
@@ -1299,7 +1056,7 @@ var Supervisor = class {
1299
1056
  * Closes the standard-input channel while leaving the child running.
1300
1057
  *
1301
1058
  * @remarks
1302
- * Never rejects, and terminates nothing: no signal is sent, `stopping` stays false, no drain
1059
+ * Never rejects, and terminates nothing: no signal is sent, `terminated` stays false, no drain
1303
1060
  * window is armed, and the terminal moment does not arrive. The child observes end of input and
1304
1061
  * decides for itself what to do about it, which is what separates this from `stop`.
1305
1062
  *
@@ -1357,7 +1114,7 @@ var Supervisor = class {
1357
1114
  this.#settle(true);
1358
1115
  }
1359
1116
  #expire() {
1360
- this.#ending.resolve();
1117
+ this.#ended.resolve();
1361
1118
  if (this.#settled) return;
1362
1119
  this.#wait();
1363
1120
  }
@@ -1379,7 +1136,7 @@ var Supervisor = class {
1379
1136
  drained
1380
1137
  });
1381
1138
  this.#exit.resolve(exit);
1382
- this.#ending.resolve();
1139
+ this.#ended.resolve();
1383
1140
  this.#terminal(exit);
1384
1141
  this.#child.stdout.destroy();
1385
1142
  this.#child.stderr.destroy();
@@ -1403,7 +1160,7 @@ var Supervisor = class {
1403
1160
  }
1404
1161
  #endInput() {
1405
1162
  const stdin = this.#child.stdin;
1406
- if (this.#stopping || this.#settled || stdin.writableEnded || stdin.destroyed) return Promise.resolve();
1163
+ if (this.#terminated || this.#settled || stdin.writableEnded || stdin.destroyed) return Promise.resolve();
1407
1164
  const ended = Promise.withResolvers();
1408
1165
  stdin.end(() => ended.resolve());
1409
1166
  return ended.promise;
@@ -1430,7 +1187,7 @@ var Supervisor = class {
1430
1187
  this.#settleWrites();
1431
1188
  return;
1432
1189
  }
1433
- if (this.#failure !== void 0 || this.#stopping) return;
1190
+ if (this.#failure !== void 0 || this.#terminated) return;
1434
1191
  this.#failure = cause;
1435
1192
  this.#settleWrites();
1436
1193
  this.#fault(new _src_core.ProcessError("The standard-input channel failed", {
@@ -1439,7 +1196,7 @@ var Supervisor = class {
1439
1196
  }));
1440
1197
  }
1441
1198
  async #kill() {
1442
- this.#stopping = true;
1199
+ this.#terminated = true;
1443
1200
  this.#relieve?.();
1444
1201
  const confirmed = await stopChild(this.#child, this.#grace, _src_core.PROCESS_CONFIRMATION);
1445
1202
  this.#settleWrites();
@@ -1453,8 +1210,7 @@ var Supervisor = class {
1453
1210
  this.#teardown();
1454
1211
  }
1455
1212
  };
1456
- //#endregion
1457
- //#region src/server/processes/Process.ts
1213
+ // src/server/processes/Process.ts
1458
1214
  /**
1459
1215
  * Supervises one child, frames its standard output into lines under a bounded backlog, and keeps
1460
1216
  * every observation channel aligned at termination.
@@ -1590,8 +1346,8 @@ var Process = class {
1590
1346
  return this.#engine.settled;
1591
1347
  }
1592
1348
  /** Reports whether termination began, including after the terminal moment. */
1593
- get stopping() {
1594
- return this.#engine.stopping;
1349
+ get terminated() {
1350
+ return this.#engine.terminated;
1595
1351
  }
1596
1352
  /** Settles with the terminal child state, observed once after stream close or the drain cutoff. */
1597
1353
  get exit() {
@@ -1685,9 +1441,9 @@ var Process = class {
1685
1441
  return;
1686
1442
  }
1687
1443
  const bytes = node_buffer.Buffer.byteLength(line) + 1;
1688
- const stopping = this.#engine.stopping;
1689
- const limit = stopping ? this.#backlog * 2 : this.#backlog;
1690
- if ((!this.#requested || stopping) && this.#pending + bytes > limit) {
1444
+ const terminated = this.#engine.terminated;
1445
+ const limit = terminated ? this.#backlog * 2 : this.#backlog;
1446
+ if ((!this.#requested || terminated) && this.#pending + bytes > limit) {
1691
1447
  this.#truncated = true;
1692
1448
  return;
1693
1449
  }
@@ -1696,14 +1452,14 @@ var Process = class {
1696
1452
  this.#restrain();
1697
1453
  }
1698
1454
  #restrain() {
1699
- if (this.#paused || this.#engine.stopping || !this.#requested) return;
1455
+ if (this.#paused || this.#engine.terminated || !this.#requested) return;
1700
1456
  if (this.#pending < this.#backlog) return;
1701
1457
  this.#paused = true;
1702
1458
  this.#reader.pause();
1703
1459
  }
1704
1460
  #relieve() {
1705
1461
  if (!this.#paused || this.#ended) return;
1706
- if (!this.#engine.stopping && this.#pending > this.#backlog / 2) return;
1462
+ if (!this.#engine.terminated && this.#pending > this.#backlog / 2) return;
1707
1463
  this.#paused = false;
1708
1464
  this.#reader.resume();
1709
1465
  }
@@ -1743,8 +1499,7 @@ var Process = class {
1743
1499
  }
1744
1500
  }
1745
1501
  };
1746
- //#endregion
1747
- //#region src/server/processes/ProcessManager.ts
1502
+ // src/server/processes/ProcessManager.ts
1748
1503
  /**
1749
1504
  * Launches supervised children under caller-chosen ids, evicts each one as it settles, and destroys
1750
1505
  * every live child on teardown.
@@ -1781,7 +1536,7 @@ var ProcessManager = class {
1781
1536
  #ids = /* @__PURE__ */ new Set();
1782
1537
  #orphans = /* @__PURE__ */ new Set();
1783
1538
  #destroying = false;
1784
- #ending;
1539
+ #ended;
1785
1540
  /**
1786
1541
  * Constructs a process registry.
1787
1542
  *
@@ -1869,10 +1624,10 @@ var ProcessManager = class {
1869
1624
  * @returns The stable barrier shared by every call
1870
1625
  */
1871
1626
  destroy() {
1872
- if (this.#ending !== void 0) return this.#ending;
1627
+ if (this.#ended !== void 0) return this.#ended;
1873
1628
  this.#destroying = true;
1874
- this.#ending = this.#teardown();
1875
- return this.#ending;
1629
+ this.#ended = this.#teardown();
1630
+ return this.#ended;
1876
1631
  }
1877
1632
  #construct(id, options) {
1878
1633
  try {
@@ -1911,8 +1666,7 @@ var ProcessManager = class {
1911
1666
  this.#emitter.destroy();
1912
1667
  }
1913
1668
  };
1914
- //#endregion
1915
- //#region src/server/processes/Session.ts
1669
+ // src/server/processes/Session.ts
1916
1670
  /**
1917
1671
  * Supervises one child and publishes its standard output as raw bytes.
1918
1672
  *
@@ -1930,7 +1684,7 @@ var ProcessManager = class {
1930
1684
  *
1931
1685
  * The standard-input channel is open from the spawn, and `end` closes it without terminating
1932
1686
  * anything. That is the member a cooperative protocol reaches for: the child observes end of input
1933
- * and exits on its own, which `ending` reports. `stop` and `destroy` remain the terminations, and
1687
+ * and exits on its own, which `ended` reports. `stop` and `destroy` remain the terminations, and
1934
1688
  * `exit` remains the terminal moment — the native exit plus at most `drain` while a descendant holds
1935
1689
  * the inherited read ends.
1936
1690
  *
@@ -2012,12 +1766,12 @@ var Session = class {
2012
1766
  return this.#engine.settled;
2013
1767
  }
2014
1768
  /** Reports whether termination began, including after the terminal moment. `end` never turns it true. */
2015
- get stopping() {
2016
- return this.#engine.stopping;
1769
+ get terminated() {
1770
+ return this.#engine.terminated;
2017
1771
  }
2018
1772
  /** Settles at the child's own ending, awaited without the terminal moment's drain window. */
2019
- get ending() {
2020
- return this.#engine.ending;
1773
+ get ended() {
1774
+ return this.#engine.ended;
2021
1775
  }
2022
1776
  /** Settles with the terminal child state, observed once after stream close or the drain cutoff. */
2023
1777
  get exit() {
@@ -2042,7 +1796,7 @@ var Session = class {
2042
1796
  * Closes the standard-input channel and leaves the child running.
2043
1797
  *
2044
1798
  * @remarks
2045
- * Never rejects and terminates nothing: `stopping` stays false and the terminal moment does not
1799
+ * Never rejects and terminates nothing: `terminated` stays false and the terminal moment does not
2046
1800
  * arrive. Every call shares one barrier, which resolves after the host flushes the writes it had
2047
1801
  * already accepted. A later `write` resolves `false`.
2048
1802
  *
@@ -2090,8 +1844,7 @@ var Session = class {
2090
1844
  this.#engine.stdout.off("data", this.#output);
2091
1845
  }
2092
1846
  };
2093
- //#endregion
2094
- //#region src/server/factories.ts
1847
+ // src/server/factories.ts
2095
1848
  /**
2096
1849
  * Creates one supervised child process and returns it as a {@link ProcessInterface}, so a caller
2097
1850
  * holds the published contract rather than the `Process` class.
@@ -2160,7 +1913,284 @@ function createSession(options) {
2160
1913
  function createProcessManager(options) {
2161
1914
  return new ProcessManager(options);
2162
1915
  }
2163
- //#endregion
1916
+ // src/server/processes/Execution.ts
1917
+ /** Owns the child, capture, cancellation, and terminal state of one buffered execution. */
1918
+ var Execution = class {
1919
+ #child;
1920
+ #settled;
1921
+ #terminate;
1922
+ #cleanup;
1923
+ #finish;
1924
+ #inputFailure;
1925
+ #outChunks = [];
1926
+ #errChunks = [];
1927
+ #strict;
1928
+ #spawned = false;
1929
+ #expired = false;
1930
+ #aborted = false;
1931
+ #outRetained = 0;
1932
+ #errRetained = 0;
1933
+ #cause;
1934
+ #timeoutTimer;
1935
+ #confirmTimer;
1936
+ constructor(command, options) {
1937
+ const snapshot = snapshotCommand(command);
1938
+ const optionEnvironment = options?.environment;
1939
+ const optionWorkspace = options?.workspace;
1940
+ const optionInput = options?.input;
1941
+ const optionTimeout = options?.timeout;
1942
+ const optionGrace = options?.grace;
1943
+ const optionSignal = options?.signal;
1944
+ const optionStrict = options?.strict;
1945
+ const optionLimit = options?.limit;
1946
+ validateCommand(snapshot);
1947
+ validateEnvironment(optionEnvironment);
1948
+ validateWorkspace(optionWorkspace);
1949
+ validateTimer(optionTimeout, "option 'timeout'");
1950
+ validateTimer(optionGrace, "option 'grace'");
1951
+ validateBytes(optionLimit, "option 'limit'", 0);
1952
+ const limit = optionLimit ?? _src_core.PROCESS_OUTPUT;
1953
+ const grace = optionGrace ?? _src_core.PROCESS_GRACE;
1954
+ const timeout = optionTimeout ?? 0;
1955
+ this.#strict = optionStrict ?? true;
1956
+ const text = optionInput ?? snapshot.input;
1957
+ const line = formatCommand(snapshot);
1958
+ const workspace = optionWorkspace ?? process.cwd();
1959
+ const environment = mergeEnvironment(snapshot.isolated === true, snapshot.environment, optionEnvironment);
1960
+ const plan = buildSpawn(snapshot, {
1961
+ workspace,
1962
+ environment
1963
+ });
1964
+ this.#settled = Promise.withResolvers();
1965
+ this.#terminate = new AbortController();
1966
+ this.#cleanup = new AbortController();
1967
+ this.#finish = new AbortController();
1968
+ this.#inputFailure = new AbortController();
1969
+ this.#child = (0, node_child_process.spawn)(plan.file, [...plan.arguments], {
1970
+ cwd: workspace,
1971
+ detached: process.platform !== "win32",
1972
+ env: environment,
1973
+ stdio: [
1974
+ "pipe",
1975
+ "pipe",
1976
+ "pipe"
1977
+ ],
1978
+ windowsHide: true,
1979
+ windowsVerbatimArguments: plan.verbatim
1980
+ });
1981
+ this.#finish.signal.addEventListener("abort", () => {
1982
+ clearTimeout(this.#timeoutTimer);
1983
+ clearTimeout(this.#confirmTimer);
1984
+ this.#cleanup.abort();
1985
+ this.#settled.resolve(buildExecuteResult({
1986
+ command: line,
1987
+ stdout: node_buffer.Buffer.concat(this.#outChunks),
1988
+ stderr: node_buffer.Buffer.concat(this.#errChunks),
1989
+ code: this.#child.exitCode,
1990
+ signal: this.#child.signalCode,
1991
+ expired: this.#expired,
1992
+ aborted: this.#aborted,
1993
+ truncated: this.#outRetained > limit || this.#errRetained > limit,
1994
+ limit,
1995
+ ...this.#cause === void 0 ? {} : { cause: this.#cause }
1996
+ }));
1997
+ }, { once: true });
1998
+ this.#terminate.signal.addEventListener("abort", () => {
1999
+ if (this.#finish.signal.aborted) return;
2000
+ clearTimeout(this.#timeoutTimer);
2001
+ this.#cleanup.abort();
2002
+ stopChild(this.#child, grace, _src_core.PROCESS_CONFIRMATION).then(() => {
2003
+ if (this.#finish.signal.aborted) return;
2004
+ this.#confirmTimer = setTimeout(() => this.#finish.abort(), _src_core.PROCESS_CONFIRMATION);
2005
+ });
2006
+ }, { once: true });
2007
+ this.#inputFailure.signal.addEventListener("abort", () => {
2008
+ if (this.#finish.signal.aborted) return;
2009
+ this.#cause = this.#inputFailure.signal.reason;
2010
+ this.#terminate.abort();
2011
+ }, { once: true });
2012
+ this.#child.stdin.on("error", (error) => this.#inputFailure.abort(error));
2013
+ this.#child.stdout.on("data", (chunk) => {
2014
+ const captured = captureChunk(chunk, limit + 1 - this.#outRetained);
2015
+ if (captured === void 0) return;
2016
+ this.#outRetained += captured.byteLength;
2017
+ this.#outChunks.push(captured);
2018
+ });
2019
+ this.#child.stderr.on("data", (chunk) => {
2020
+ const captured = captureChunk(chunk, limit + 1 - this.#errRetained);
2021
+ if (captured === void 0) return;
2022
+ this.#errRetained += captured.byteLength;
2023
+ this.#errChunks.push(captured);
2024
+ });
2025
+ this.#child.once("spawn", () => {
2026
+ this.#spawned = true;
2027
+ });
2028
+ this.#child.once("error", (error) => {
2029
+ if (this.#spawned) return;
2030
+ this.#cause = error;
2031
+ this.#finish.abort();
2032
+ });
2033
+ this.#child.once("close", () => this.#finish.abort());
2034
+ if (text !== void 0) this.#child.stdin.write(text, (error) => {
2035
+ if (error !== void 0 && error !== null) this.#inputFailure.abort(error);
2036
+ });
2037
+ this.#child.stdin.end();
2038
+ if (timeout > 0) this.#timeoutTimer = setTimeout(() => {
2039
+ this.#expired = true;
2040
+ this.#terminate.abort();
2041
+ }, timeout);
2042
+ if (optionSignal !== void 0) {
2043
+ optionSignal.addEventListener("abort", () => {
2044
+ this.#aborted = true;
2045
+ this.#terminate.abort();
2046
+ }, {
2047
+ once: true,
2048
+ signal: this.#cleanup.signal
2049
+ });
2050
+ if (optionSignal.aborted) {
2051
+ this.#aborted = true;
2052
+ this.#terminate.abort();
2053
+ }
2054
+ }
2055
+ }
2056
+ get settled() {
2057
+ return this.#settled.promise;
2058
+ }
2059
+ read(result) {
2060
+ if (result.failed && this.#strict) {
2061
+ if (this.#cause !== void 0 && this.#cause === this.#inputFailure.signal.reason) throw new _src_core.ProcessError(`Command '${result.command}' failed while writing standard input`, {
2062
+ code: "input",
2063
+ context: {
2064
+ command: result.command,
2065
+ code: result.code,
2066
+ signal: result.signal
2067
+ },
2068
+ cause: this.#inputFailure.signal.reason,
2069
+ result
2070
+ });
2071
+ throw (0, _src_core.createExecuteError)(result, this.#cause);
2072
+ }
2073
+ return result;
2074
+ }
2075
+ };
2076
+ // src/server/execution/execute.ts
2077
+ /**
2078
+ * Runs one command to completion, buffering its output, and settles with the outcome.
2079
+ *
2080
+ * @remarks
2081
+ * The executable is resolved through {@link buildSpawn}, so no run uses a shell. On a POSIX host the
2082
+ * child is detached, which is what lets its whole process group be terminated. Standard output and
2083
+ * error are each byte-bounded by `limit` (default {@link PROCESS_OUTPUT}) and `truncated` reports
2084
+ * whether either stream exceeded it. Each capture keeps one byte beyond `limit`, so the single
2085
+ * code-point-boundary trim in {@link buildExecuteResult} reads the first excluded byte and never
2086
+ * delivers a split multibyte sequence. A positive `timeout` and an aborting `signal` both terminate the
2087
+ * child through the same bounded stop, and only the earliest is recorded: a timeout reports
2088
+ * `expired` and an abort reports `aborted`, never both. After termination the outcome is awaited for
2089
+ * a bounded window, so a descendant holding the child's stdio cannot keep the run pending. That bound
2090
+ * covers a terminated run alone: a run with no `timeout` and no `signal` settles on stdio completion
2091
+ * rather than on process exit, so a descendant that inherited the child's stdio holds the run open
2092
+ * after the child itself has gone. Give such a run a `timeout`. The child's `environment` merges over
2093
+ * the parent unless the command is `isolated`, then `options.environment` on top, and `options.input`
2094
+ * overrides `command.input`. A host fault while writing that input terminates the run by design and
2095
+ * marks its result failed; a strict rejection carries the host fault as its cause. This differs
2096
+ * from `Process` constructor input, whose package-initiated input phase stays quiet. Unless `strict`
2097
+ * is `false`, a failed run rejects with a {@link ProcessError} carrying the {@link ExecuteResult};
2098
+ * {@link createExecuteError} constructs every rejection outside the input-fault door. An invalid
2099
+ * option or command string rejects before the child is spawned, because an async function cannot
2100
+ * throw synchronously.
2101
+ *
2102
+ * @param command - The executable, arguments, and optional environment and input
2103
+ * @param options - Working directory, timeout, grace, signal, capture limit, and failure delivery
2104
+ * @returns The settled run outcome
2105
+ * @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`
2106
+ *
2107
+ * @example
2108
+ * ```ts
2109
+ * const result = await execute({ file: 'git', arguments: ['status'] }, { workspace: process.cwd() })
2110
+ * ```
2111
+ */
2112
+ async function execute(command, options) {
2113
+ const execution = new Execution(command, options);
2114
+ const result = await execution.settled;
2115
+ return execution.read(result);
2116
+ }
2117
+ // src/server/execution/executeSync.ts
2118
+ /**
2119
+ * Runs one command to completion synchronously, buffering its output, and returns the outcome.
2120
+ *
2121
+ * @remarks
2122
+ * The synchronous counterpart of {@link execute}, spawned through the same resolver and never through a
2123
+ * shell. The host offers no cooperative termination window and no in-flight cancellation, so this
2124
+ * contract carries neither. A positive `timeout` ends only the root process and can leave
2125
+ * descendants running; use {@link execute} or {@link Process} when timeout must terminate the tree. A
2126
+ * timeout and an output overflow both end the root with `SIGKILL`: an overflow reports `truncated`
2127
+ * and `failed` together and trims the partial output to `limit`, where {@link execute} keeps reading and
2128
+ * reports `truncated` without failing. The environment and input follow the same merge as
2129
+ * {@link execute}. Unless `strict` is `false`, a failed run throws a {@link createExecuteError}
2130
+ * carrying the {@link ExecuteResult}.
2131
+ *
2132
+ * @param command - The executable, arguments, and optional environment and input
2133
+ * @param options - Working directory, timeout, capture limit, and failure delivery
2134
+ * @returns The run outcome
2135
+ * @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`
2136
+ *
2137
+ * @example
2138
+ * ```ts
2139
+ * const result = executeSync({ file: 'git', arguments: ['--version'] }, { strict: false })
2140
+ * ```
2141
+ */
2142
+ function executeSync(command, options) {
2143
+ const snapshot = snapshotCommand(command);
2144
+ const optionEnvironment = options?.environment;
2145
+ const optionWorkspace = options?.workspace;
2146
+ const optionInput = options?.input;
2147
+ const optionTimeout = options?.timeout;
2148
+ const optionStrict = options?.strict;
2149
+ const optionLimit = options?.limit;
2150
+ validateCommand(snapshot);
2151
+ validateEnvironment(optionEnvironment);
2152
+ validateWorkspace(optionWorkspace);
2153
+ validateTimer(optionTimeout, "option 'timeout'");
2154
+ validateBytes(optionLimit, "option 'limit'", 0);
2155
+ const limit = optionLimit ?? _src_core.PROCESS_OUTPUT;
2156
+ const timeout = optionTimeout ?? 0;
2157
+ const strict = optionStrict ?? true;
2158
+ const text = optionInput ?? snapshot.input;
2159
+ const line = formatCommand(snapshot);
2160
+ const workspace = optionWorkspace ?? process.cwd();
2161
+ const environment = mergeEnvironment(snapshot.isolated === true, snapshot.environment, optionEnvironment);
2162
+ const plan = buildSpawn(snapshot, {
2163
+ workspace,
2164
+ environment
2165
+ });
2166
+ const outcome = (0, node_child_process.spawnSync)(plan.file, [...plan.arguments], {
2167
+ cwd: workspace,
2168
+ env: environment,
2169
+ encoding: "buffer",
2170
+ maxBuffer: limit,
2171
+ killSignal: "SIGKILL",
2172
+ windowsHide: true,
2173
+ windowsVerbatimArguments: plan.verbatim,
2174
+ ...text !== void 0 ? { input: node_buffer.Buffer.from(text) } : {},
2175
+ ...timeout > 0 ? { timeout } : {}
2176
+ });
2177
+ const error = outcome.error;
2178
+ const fault = error !== void 0 && "code" in error && (0, _orkestrel_contract.isString)(error.code) ? error.code : "";
2179
+ const result = buildExecuteResult({
2180
+ command: line,
2181
+ stdout: node_buffer.Buffer.isBuffer(outcome.stdout) ? outcome.stdout : node_buffer.Buffer.alloc(0),
2182
+ stderr: node_buffer.Buffer.isBuffer(outcome.stderr) ? outcome.stderr : node_buffer.Buffer.alloc(0),
2183
+ code: outcome.status,
2184
+ signal: outcome.signal,
2185
+ expired: fault === "ETIMEDOUT",
2186
+ aborted: false,
2187
+ truncated: fault === "ENOBUFS",
2188
+ limit,
2189
+ ...error === void 0 ? {} : { cause: error }
2190
+ });
2191
+ if (result.failed && strict) throw (0, _src_core.createExecuteError)(result, error);
2192
+ return result;
2193
+ }
2164
2194
  exports.Process = Process;
2165
2195
  exports.ProcessManager = ProcessManager;
2166
2196
  exports.Session = Session;
@@ -2177,6 +2207,7 @@ exports.detach = detach;
2177
2207
  exports.execute = execute;
2178
2208
  exports.executeSync = executeSync;
2179
2209
  exports.formatCommand = formatCommand;
2210
+ exports.inspectProcess = inspectProcess;
2180
2211
  exports.isExited = isExited;
2181
2212
  exports.isFile = isFile;
2182
2213
  exports.killProcess = killProcess;