stitchkit 0.108.1 → 0.108.3

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/entrypoints/agent-runtime/coding-tools.js +3 -2
  3. package/dist/entrypoints/agent-runtime/sandbox.js +5 -3
  4. package/dist/entrypoints/cli.js +1 -1
  5. package/dist/entrypoints/contract.js +1 -1
  6. package/dist/entrypoints/index.js +1 -1
  7. package/dist/entrypoints/node.js +1 -1
  8. package/dist/entrypoints/process/owner-loss.d.ts +3 -0
  9. package/dist/entrypoints/process/owner-loss.d.ts.map +1 -0
  10. package/dist/entrypoints/process/owner-loss.js +6 -0
  11. package/dist/entrypoints/process.js +4 -2
  12. package/dist/entrypoints/server.js +1 -1
  13. package/dist/entrypoints/tools.js +1 -1
  14. package/dist/{index-epg514ys.js → index-dasy32c8.js} +76 -328
  15. package/dist/index-dj1aycxw.js +639 -0
  16. package/dist/{index-4qh90tyw.js → index-fgq4w9bf.js} +23 -1
  17. package/dist/{index-e35py42g.js → index-tycmtxky.js} +6 -4
  18. package/dist/process/bun-child.d.ts +10 -0
  19. package/dist/process/bun-child.d.ts.map +1 -1
  20. package/dist/process/command-owner.d.ts.map +1 -1
  21. package/dist/process/contract.d.ts +5 -0
  22. package/dist/process/contract.d.ts.map +1 -1
  23. package/dist/process/launch.d.ts +1 -1
  24. package/dist/process/launch.d.ts.map +1 -1
  25. package/dist/process/owned-child.d.ts +3 -0
  26. package/dist/process/owned-child.d.ts.map +1 -1
  27. package/dist/process/owner-loss-control.d.ts +15 -0
  28. package/dist/process/owner-loss-control.d.ts.map +1 -0
  29. package/dist/process/owner-loss-guard.d.ts +12 -0
  30. package/dist/process/owner-loss-guard.d.ts.map +1 -0
  31. package/dist/process/owner-loss-protocol.d.ts +10 -0
  32. package/dist/process/owner-loss-protocol.d.ts.map +1 -0
  33. package/dist/process/owner-loss-socket.d.ts +4 -0
  34. package/dist/process/owner-loss-socket.d.ts.map +1 -0
  35. package/dist/tools/cli/args-fields.d.ts.map +1 -1
  36. package/llms/cli.2.txt +107 -0
  37. package/llms/cli.txt +8 -107
  38. package/llms/process-owner-loss.txt +18 -0
  39. package/llms/process.txt +43 -10
  40. package/llms/stitchkit.txt +2 -1
  41. package/llms-full.txt +61 -11
  42. package/llms.txt +4 -3
  43. package/package.json +5 -1
  44. /package/dist/{index-q4hjtyye.js → index-qd8x73e8.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -15,6 +15,35 @@ additive**; the first breaking change landed in 0.10.0. Grep the file for
15
15
 
16
16
  ## [Unreleased]
17
17
 
18
+ ## [0.108.3] - 2026-10-09
19
+
20
+ ### Added
21
+
22
+ - `stitchkit/process/owner-loss` — `bootstrapNativeCommandOwnerLoss()` lets a single-file Bun
23
+ application keep `stitchkit/process` behind a dynamic import while carrying the same package-owned
24
+ owner-loss guard in its bundle. Call it before application argv routing and skip that routing when
25
+ it returns `true`; consumers do not inspect or copy the private guard protocol. → ADR 0255
26
+
27
+ ### Fixed
28
+
29
+ - `stitchkit/cli` — numeric, bigint and boolean `z.literal()` fields now receive their primitive
30
+ values from argv, including homogeneous literal sets, unions, wrappers and array elements. String
31
+ literals retain their lexical form (`007` stays `"007"`), and mixed-type literal sets remain raw
32
+ rather than guessing a type from spelling.
33
+
34
+ ## [0.108.2] - 2026-10-09
35
+
36
+ ### Added
37
+
38
+ - `stitchkit/process` — `runNativeCommand` takes `ownerLoss: 'terminate'`: on Linux and Darwin under
39
+ Bun or Node, a package-owned per-command guard binds the command's process group to a private
40
+ inherited kernel channel. If the JavaScript caller is killed or crashes, channel EOF makes the
41
+ guard KILL its own group without polling, `finally` or PID reclamation. The option requires
42
+ `group: 'own'`; unsupported platforms refuse with `COMMAND_UNAVAILABLE`. It is opt-in because each
43
+ guarded call uses one extra short-lived process and descriptor. Output, stdin, target exit status,
44
+ deadlines and cancellation retain the existing contract; `onLeaderStarted` reports the guard that
45
+ leads the owned group. → ADR 0255
46
+
18
47
  ## [0.108.1] - 2026-10-07
19
48
 
20
49
  ### Fixed
@@ -6,10 +6,11 @@ import {
6
6
  AgentCodingToolAuthorizationSchema2,
7
7
  AgentCodingToolPathAuthorizationSchema2,
8
8
  createAgentCodingTools2
9
- } from "../../index-e35py42g.js";
9
+ } from "../../index-tycmtxky.js";
10
10
  import"../../index-2kbysk9s.js";
11
- import"../../index-epg514ys.js";
11
+ import"../../index-dj1aycxw.js";
12
12
  import"../../index-8ks9b85e.js";
13
+ import"../../index-dasy32c8.js";
13
14
  import"../../index-zrtrjkbf.js";
14
15
  import"../../index-636mggad.js";
15
16
  export {
@@ -8,16 +8,18 @@ import"../../index-q6crhe1n.js";
8
8
  import"../../index-fynr93w4.js";
9
9
  import {
10
10
  createAgentCodingTools2
11
- } from "../../index-e35py42g.js";
11
+ } from "../../index-tycmtxky.js";
12
12
  import"../../index-2kbysk9s.js";
13
13
  import {
14
14
  COMMAND_CLEANUP_TIMEOUT_MS,
15
- NativeCommandError2,
15
+ NativeCommandError2
16
+ } from "../../index-dj1aycxw.js";
17
+ import {
16
18
  spawnOwnedCommand,
17
19
  nativeCommandOwner,
18
20
  terminateOwnedGroup,
19
21
  startNativeCommand
20
- } from "../../index-epg514ys.js";
22
+ } from "../../index-dasy32c8.js";
21
23
  import"../../index-zrtrjkbf.js";
22
24
  import {
23
25
  MAX_TIMER_MS
@@ -15,7 +15,7 @@ import {
15
15
  renderCliView2,
16
16
  pollUntilDone2,
17
17
  createCli2
18
- } from "../index-4qh90tyw.js";
18
+ } from "../index-fgq4w9bf.js";
19
19
  import"../index-fynr93w4.js";
20
20
  import"../index-ct75c3at.js";
21
21
  import"../index-q6crhe1n.js";
@@ -4,7 +4,7 @@ import {
4
4
  paginatedSchema2,
5
5
  encodeCursor2,
6
6
  decodeCursor2
7
- } from "../index-q4hjtyye.js";
7
+ } from "../index-qd8x73e8.js";
8
8
  import {
9
9
  DEFAULT_CONTRACT_STREAM_FRAME_BYTES2,
10
10
  ContractStreamFrameSchema2
@@ -44,7 +44,7 @@ import {
44
44
  paginatedSchema2,
45
45
  encodeCursor2,
46
46
  decodeCursor2
47
- } from "../index-q4hjtyye.js";
47
+ } from "../index-qd8x73e8.js";
48
48
  import {
49
49
  ManagedFilePathSchema2,
50
50
  ManagedFileRefSchema2
@@ -11,7 +11,7 @@ import {
11
11
  import"../index-ykdcnq1f.js";
12
12
  import"../index-4s0fep5q.js";
13
13
  import"../index-fsw1ybf3.js";
14
- import"../index-q4hjtyye.js";
14
+ import"../index-qd8x73e8.js";
15
15
  import"../index-xqhede6x.js";
16
16
  import {
17
17
  createHandler2,
@@ -0,0 +1,3 @@
1
+ /** Narrow startup bootstrap for a bundled application that lazy-imports `stitchkit/process`. */
2
+ export { bootstrapNativeCommandOwnerLoss } from '../../process/owner-loss-guard.js';
3
+ //# sourceMappingURL=owner-loss.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-loss.d.ts","sourceRoot":"","sources":["../../../src/entrypoints/process/owner-loss.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAEhG,OAAO,EAAE,+BAA+B,EAAE,MAAM,gCAAgC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import {
2
+ bootstrapNativeCommandOwnerLoss2
3
+ } from "../../index-dj1aycxw.js";
4
+ export {
5
+ bootstrapNativeCommandOwnerLoss2 as bootstrapNativeCommandOwnerLoss
6
+ };
@@ -5,9 +5,11 @@ import {
5
5
  probeProcessOwner2
6
6
  } from "../index-bh2ht9y5.js";
7
7
  import {
8
- NativeCommandError2,
8
+ NativeCommandError2
9
+ } from "../index-dj1aycxw.js";
10
+ import {
9
11
  startNativeCommand
10
- } from "../index-epg514ys.js";
12
+ } from "../index-dasy32c8.js";
11
13
  // src/process/command.ts
12
14
  function runNativeCommand(input) {
13
15
  return startNativeCommand(input).result;
@@ -26,7 +26,7 @@ import {
26
26
  import"../index-ykdcnq1f.js";
27
27
  import"../index-4s0fep5q.js";
28
28
  import"../index-fsw1ybf3.js";
29
- import"../index-q4hjtyye.js";
29
+ import"../index-qd8x73e8.js";
30
30
  import"../index-xqhede6x.js";
31
31
  import {
32
32
  contextContributionDelta,
@@ -71,7 +71,7 @@ import {
71
71
  WaitTimeoutError,
72
72
  runWaitOperation,
73
73
  createCli2
74
- } from "../index-4qh90tyw.js";
74
+ } from "../index-fgq4w9bf.js";
75
75
  import {
76
76
  ManagedFilePathSchema2,
77
77
  ManagedFileRefSchema2
@@ -1,3 +1,17 @@
1
+ import {
2
+ NativeCommandOptionsSchema,
3
+ NativeCommandError2,
4
+ abortOwnerLossLaunch,
5
+ attachOwnerLossControl,
6
+ transferOwnerLossControl,
7
+ awaitCommandStart,
8
+ ownerLossAvailable,
9
+ ownerLossGuardCommand,
10
+ hostIsBun,
11
+ spawnBunChild,
12
+ spawnBunInheritedChild,
13
+ ownerLossGuardEntry
14
+ } from "./index-dj1aycxw.js";
1
15
  import {
2
16
  raceAbort
3
17
  } from "./index-b0kddvzw.js";
@@ -9,147 +23,6 @@ import {
9
23
  assertPositiveSafeInteger
10
24
  } from "./index-0s4xebbg.js";
11
25
 
12
- // src/process/contract.ts
13
- import { z } from "zod";
14
- var NativeCommandSettlementSchema = z.discriminatedUnion("kind", [
15
- z.object({
16
- kind: z.literal("exit"),
17
- exitCode: z.number().int().nullable(),
18
- signal: z.string().nullable()
19
- }),
20
- z.object({
21
- kind: z.literal("stopped"),
22
- cause: z.unknown(),
23
- exitCode: z.number().int().nullable(),
24
- signal: z.string().nullable()
25
- }),
26
- z.object({ kind: z.literal("error"), cause: z.unknown() })
27
- ]);
28
- var COMMAND_CLEANUP_TIMEOUT_MS = 2000;
29
- var MAX_COMMAND_STOP_GRACE_MS = 3600000;
30
- var NativeCommandStopPolicySchema = z.strictObject({
31
- target: z.enum(["group", "leader"]),
32
- signal: z.enum(["SIGTERM", "SIGINT", "SIGHUP", "SIGQUIT", "SIGUSR1", "SIGUSR2"]).default("SIGTERM"),
33
- graceMs: z.number().int().nonnegative().max(MAX_COMMAND_STOP_GRACE_MS),
34
- killOn: z.custom((v) => v instanceof AbortSignal).optional()
35
- });
36
- var REMOVED_NATIVE_COMMAND_OPTIONS = {
37
- killGraceMs: "stop: { target: 'group', graceMs } (removed in 0.105.0)"
38
- };
39
- function unknownOptionMessage(keys) {
40
- return keys.map((key) => {
41
- const name = String(key);
42
- const replacement = Object.hasOwn(REMOVED_NATIVE_COMMAND_OPTIONS, name) ? REMOVED_NATIVE_COMMAND_OPTIONS[name] : undefined;
43
- return replacement === undefined ? `Unknown runNativeCommand option ${name}` : `Unknown runNativeCommand option ${name}; use ${replacement}`;
44
- }).join("; ");
45
- }
46
- var DEFAULT_GROUP_STOP = {
47
- target: "group",
48
- signal: "SIGTERM",
49
- graceMs: 100
50
- };
51
- var DEFAULT_LEADER_STOP = {
52
- target: "leader",
53
- signal: "SIGTERM",
54
- graceMs: 100
55
- };
56
- var NativeCommandOptionsSchema = z.strictObject({
57
- executable: z.string().min(1),
58
- args: z.array(z.string()).default([]),
59
- cwd: z.string().optional(),
60
- env: z.record(z.string(), z.string()).optional(),
61
- envPolicy: z.enum(["declared-only", "ambient"]).default("declared-only"),
62
- signal: z.custom((v) => v instanceof AbortSignal).optional(),
63
- timeoutMs: z.number().int().positive().max(MAX_TIMER_MS).optional(),
64
- maxOutputBytes: z.number().int().positive().optional(),
65
- capture: z.boolean().default(false),
66
- stdin: z.custom((v) => v instanceof Uint8Array).optional(),
67
- maxStdinBytes: z.number().int().positive().default(1024 * 1024),
68
- onOutput: z.custom((v) => typeof v === "function").optional(),
69
- onLeaderSettled: z.custom((v) => typeof v === "function").optional(),
70
- onLeaderStarted: z.custom((v) => typeof v === "function").optional(),
71
- drainTimeoutMs: z.number().int().positive().max(MAX_TIMER_MS).optional(),
72
- group: z.enum(["own", "caller"]).default("own"),
73
- stdio: z.enum(["pipe", "inherit"]).default("pipe"),
74
- stop: NativeCommandStopPolicySchema.optional(),
75
- descendants: z.enum(["terminate-after-leader", "leave"]).optional(),
76
- cleanupTimeoutMs: z.number().int().positive().max(30000).default(COMMAND_CLEANUP_TIMEOUT_MS)
77
- }, {
78
- error: (issue) => issue.code === "unrecognized_keys" ? unknownOptionMessage(issue.keys) : undefined
79
- }).check((ctx) => {
80
- const input = ctx.value;
81
- if (!input.signal && input.timeoutMs === undefined)
82
- ctx.issues.push({
83
- code: "custom",
84
- input: ctx.value,
85
- message: "Declare a caller signal or a finite timeout"
86
- });
87
- if (input.capture && input.maxOutputBytes === undefined)
88
- ctx.issues.push({
89
- code: "custom",
90
- input: ctx.value,
91
- message: "Capture requires a finite combined output budget"
92
- });
93
- if (input.stdin && input.stdin.byteLength > input.maxStdinBytes)
94
- ctx.issues.push({
95
- code: "custom",
96
- input: ctx.value,
97
- message: "stdin exceeds maxStdinBytes"
98
- });
99
- if (input.group === "caller" && input.stop?.target === "group")
100
- ctx.issues.push({
101
- code: "custom",
102
- input: ctx.value,
103
- message: "group: 'caller' stops only the leader; use stop.target 'leader'"
104
- });
105
- if (input.group === "caller" && input.descendants !== undefined)
106
- ctx.issues.push({
107
- code: "custom",
108
- input: ctx.value,
109
- message: "group: 'caller' has no group of its own, so descendants does not apply"
110
- });
111
- if (input.stdio === "inherit") {
112
- const piped = [
113
- input.capture && "capture",
114
- input.onOutput && "onOutput",
115
- input.stdin && "stdin",
116
- input.maxOutputBytes !== undefined && "maxOutputBytes",
117
- input.drainTimeoutMs !== undefined && "drainTimeoutMs"
118
- ].filter((name) => typeof name === "string");
119
- for (const name of piped)
120
- ctx.issues.push({
121
- code: "custom",
122
- input: ctx.value,
123
- message: `stdio: 'inherit' leaves no pipe for ${name}`
124
- });
125
- }
126
- }).transform((input) => ({
127
- ...input,
128
- stop: input.stop ?? (input.group === "caller" ? DEFAULT_LEADER_STOP : DEFAULT_GROUP_STOP),
129
- descendants: input.descendants ?? "terminate-after-leader"
130
- }));
131
- var NativeCommandResultSchema = z.object({
132
- exitCode: z.number().int().nullable(),
133
- signal: z.string().nullable(),
134
- descendantsStopped: z.boolean(),
135
- stdout: z.instanceof(Uint8Array),
136
- stderr: z.instanceof(Uint8Array)
137
- });
138
- var NativeCommandLimitReasonSchema = z.enum(["deadline", "output-budget"]);
139
-
140
- class NativeCommandError2 extends Error {
141
- code;
142
- reason;
143
- constructor(code, message, options) {
144
- super(message, options);
145
- this.code = code;
146
- if (options?.reason !== undefined && code !== "COMMAND_LIMIT")
147
- throw new TypeError("Only COMMAND_LIMIT accepts a limit reason");
148
- this.reason = options?.reason === undefined ? undefined : NativeCommandLimitReasonSchema.parse(options.reason);
149
- this.name = "NativeCommandError";
150
- }
151
- }
152
-
153
26
  // src/process/group.ts
154
27
  function createCommandDeadline(timeoutMs, reason) {
155
28
  assertPositiveSafeInteger("Command deadline", timeoutMs, RangeError);
@@ -395,176 +268,11 @@ function createCommandLifetime(input) {
395
268
  }
396
269
 
397
270
  // src/process/launch.ts
398
- import { Readable as Readable3 } from "node:stream";
271
+ import { Readable as Readable2 } from "node:stream";
399
272
 
400
273
  // src/process/owned-child.ts
401
274
  import { spawn } from "node:child_process";
402
-
403
- // src/process/bun-child.ts
404
- import { EventEmitter } from "node:events";
405
- import { Readable, Writable } from "node:stream";
406
- function hostIsBun() {
407
- return typeof Bun !== "undefined";
408
- }
409
- function readableOf(source) {
410
- const reader = source.getReader();
411
- let reading = false;
412
- return new Readable({
413
- read() {
414
- if (reading)
415
- return;
416
- reading = true;
417
- reader.read().then(({ done, value }) => {
418
- reading = false;
419
- if (done)
420
- this.push(null);
421
- else
422
- this.push(value);
423
- }, (error) => {
424
- reading = false;
425
- this.destroy(error instanceof Error ? error : new Error(String(error)));
426
- });
427
- },
428
- destroy(error, done) {
429
- reader.cancel().then(() => done(error), () => done(error));
430
- }
431
- });
432
- }
433
- var REPORTED_SPAWN_CODES = new Set(["ENOENT", "EACCES", "EAGAIN", "EMFILE", "ENFILE"]);
434
- function reportedSpawnFailure(cause) {
435
- return typeof cause === "object" && cause !== null && "code" in cause && typeof cause.code === "string" && REPORTED_SPAWN_CODES.has(cause.code);
436
- }
437
- var waitClosed = (stream) => stream.closed ? Promise.resolve() : new Promise((resolve) => stream.once("close", () => resolve()));
438
- function defineBunChildren() {
439
-
440
- class BunProcess extends EventEmitter {
441
- pipes;
442
- pid;
443
- exitCode = null;
444
- signalCode = null;
445
- #subprocess;
446
- constructor(pipes) {
447
- super();
448
- this.pipes = pipes;
449
- }
450
- watch(subprocess) {
451
- this.pid = subprocess.pid;
452
- this.#subprocess = subprocess;
453
- subprocess.exited.then(() => {
454
- this.exitCode = subprocess.exitCode;
455
- this.signalCode = subprocess.signalCode;
456
- this.emit("exit", this.exitCode, this.signalCode);
457
- for (const pipe of this.pipes)
458
- if (pipe.readableFlowing === null)
459
- pipe.resume();
460
- this.closeAfterPipes();
461
- });
462
- }
463
- fail(cause) {
464
- for (const pipe of this.pipes)
465
- pipe.resume();
466
- setImmediate(() => {
467
- this.emit("error", cause);
468
- this.closeAfterPipes();
469
- });
470
- }
471
- kill(signal) {
472
- const subprocess = this.#subprocess;
473
- if (!subprocess || this.exitCode !== null || this.signalCode !== null)
474
- return false;
475
- try {
476
- subprocess.kill(signal);
477
- return true;
478
- } catch {
479
- return false;
480
- }
481
- }
482
- closeAfterPipes() {
483
- Promise.all(this.pipes.map(waitClosed)).then(() => this.emit("close", this.exitCode, this.signalCode));
484
- }
485
- }
486
-
487
- class BunChild extends BunProcess {
488
- stdin;
489
- stdout;
490
- stderr;
491
- constructor(stdin, stdout, stderr) {
492
- super([stdout, stderr]);
493
- this.stdin = stdin;
494
- this.stdout = stdout;
495
- this.stderr = stderr;
496
- }
497
- }
498
- return { BunProcess, BunChild };
499
- }
500
- var bunChildren;
501
- function children() {
502
- bunChildren ??= defineBunChildren();
503
- return bunChildren;
504
- }
505
- function sinkWritable(sink) {
506
- let ended = false;
507
- const end = () => {
508
- if (ended)
509
- return Promise.resolve(0);
510
- ended = true;
511
- return Promise.resolve().then(() => sink.end());
512
- };
513
- return new Writable({
514
- write(chunk, _encoding, done) {
515
- Promise.resolve().then(() => sink.write(chunk)).then(() => done(), done);
516
- },
517
- final(done) {
518
- end().then(() => done(), done);
519
- },
520
- destroy(error, done) {
521
- end().then(() => done(error), () => done(error));
522
- }
523
- });
524
- }
525
- function spawnBunChild(input) {
526
- const { BunChild } = children();
527
- try {
528
- const subprocess = Bun.spawn([input.executable, ...input.args], {
529
- cwd: input.cwd,
530
- env: input.env,
531
- detached: input.group,
532
- stdin: "pipe",
533
- stdout: "pipe",
534
- stderr: "pipe"
535
- });
536
- const child = new BunChild(sinkWritable(subprocess.stdin), readableOf(subprocess.stdout), readableOf(subprocess.stderr));
537
- child.watch(subprocess);
538
- return child;
539
- } catch (cause) {
540
- if (!reportedSpawnFailure(cause))
541
- throw cause;
542
- const child = new BunChild(new Writable({ write: (_chunk, _encoding, done) => done() }), Readable.from([]), Readable.from([]));
543
- child.fail(cause);
544
- return child;
545
- }
546
- }
547
- function spawnBunInheritedChild(input) {
548
- const { BunProcess } = children();
549
- const child = new BunProcess([]);
550
- try {
551
- child.watch(Bun.spawn([input.executable, ...input.args], {
552
- cwd: input.cwd,
553
- env: input.env,
554
- detached: input.group,
555
- stdin: "inherit",
556
- stdout: "inherit",
557
- stderr: "inherit"
558
- }));
559
- } catch (cause) {
560
- if (!reportedSpawnFailure(cause))
561
- throw cause;
562
- child.fail(cause);
563
- }
564
- return child;
565
- }
566
-
567
- // src/process/owned-child.ts
275
+ import { Duplex } from "node:stream";
568
276
  var groups = new WeakSet;
569
277
  function spawnOwnedCommand(input) {
570
278
  const child = hostIsBun() ? spawnBunChild(input) : nodeChild(input);
@@ -573,12 +281,27 @@ function spawnOwnedCommand(input) {
573
281
  return child;
574
282
  }
575
283
  function nodeChild(input) {
576
- return spawn(input.executable, [...input.args], {
284
+ if (input.ownerLoss === undefined)
285
+ return spawn(input.executable, [...input.args], {
286
+ cwd: input.cwd,
287
+ env: input.env,
288
+ detached: input.group,
289
+ stdio: ["pipe", "pipe", "pipe"]
290
+ });
291
+ const [executable, ...args] = ownerLossGuardCommand(input.ownerLoss, input.executable, input.args);
292
+ const child = spawn(executable, args, {
577
293
  cwd: input.cwd,
578
294
  env: input.env,
579
295
  detached: input.group,
580
- stdio: ["pipe", "pipe", "pipe"]
296
+ stdio: ["pipe", "pipe", "pipe", "pipe"]
581
297
  });
298
+ const control = child.stdio[3];
299
+ if (!(control instanceof Duplex)) {
300
+ abortOwnerLossLaunch(child.pid);
301
+ throw new Error("Owner-loss guard supplied no control pipe");
302
+ }
303
+ attachOwnerLossControl(child, control);
304
+ return child;
582
305
  }
583
306
  var INHERITED_OUTPUT = {
584
307
  destroyed: true,
@@ -614,12 +337,31 @@ class InheritedChild {
614
337
  }
615
338
  }
616
339
  function spawnInheritedCommand(input) {
617
- const child = new InheritedChild(hostIsBun() ? spawnBunInheritedChild(input) : spawn(input.executable, [...input.args], {
618
- cwd: input.cwd,
619
- env: input.env,
620
- detached: input.group,
621
- stdio: "inherit"
622
- }));
340
+ const observable = hostIsBun() ? spawnBunInheritedChild(input) : (() => {
341
+ if (input.ownerLoss === undefined)
342
+ return spawn(input.executable, [...input.args], {
343
+ cwd: input.cwd,
344
+ env: input.env,
345
+ detached: input.group,
346
+ stdio: "inherit"
347
+ });
348
+ const [executable, ...args] = ownerLossGuardCommand(input.ownerLoss, input.executable, input.args);
349
+ const spawned = spawn(executable, args, {
350
+ cwd: input.cwd,
351
+ env: input.env,
352
+ detached: input.group,
353
+ stdio: ["inherit", "inherit", "inherit", "pipe"]
354
+ });
355
+ const control = spawned.stdio[3];
356
+ if (!(control instanceof Duplex)) {
357
+ abortOwnerLossLaunch(spawned.pid);
358
+ throw new Error("Owner-loss guard supplied no control pipe");
359
+ }
360
+ attachOwnerLossControl(spawned, control);
361
+ return spawned;
362
+ })();
363
+ const child = new InheritedChild(observable);
364
+ transferOwnerLossControl(observable, child);
623
365
  if (input.group)
624
366
  groups.add(child);
625
367
  return child;
@@ -629,7 +371,7 @@ function ownsCommandGroup(child) {
629
371
  }
630
372
 
631
373
  // src/process/transport.ts
632
- import { Readable as Readable2, Writable as Writable2 } from "node:stream";
374
+ import { Readable, Writable } from "node:stream";
633
375
  function createCommandTransport(child) {
634
376
  const leader = Promise.withResolvers();
635
377
  const closed = Promise.withResolvers();
@@ -686,17 +428,17 @@ function createCommandTransport(child) {
686
428
  stderr = child.stderr;
687
429
  destructors.set("stderr", () => stderr?.destroy());
688
430
  const input = "stdin" in child ? child.stdin : undefined;
689
- if (input instanceof Writable2) {
431
+ if (input instanceof Writable) {
690
432
  stdin = input;
691
433
  pendingHandles.add("stdin");
692
434
  destructors.set("stdin", () => input.destroy());
693
435
  pipe("stdin", input);
694
436
  }
695
- if (stdout instanceof Readable2)
437
+ if (stdout instanceof Readable)
696
438
  pipe("stdout", stdout);
697
- if (stderr instanceof Readable2)
439
+ if (stderr instanceof Readable)
698
440
  pipe("stderr", stderr);
699
- if (stdout instanceof Readable2 && stderr instanceof Readable2)
441
+ if (stdout instanceof Readable && stderr instanceof Readable)
700
442
  release("launcher");
701
443
  return { stdout, stderr };
702
444
  },
@@ -714,7 +456,7 @@ function createCommandTransport(child) {
714
456
  child.stdout.destroy();
715
457
  else if (name === "stderr")
716
458
  child.stderr.destroy();
717
- else if ("stdin" in child && child.stdin instanceof Writable2)
459
+ else if ("stdin" in child && child.stdin instanceof Writable)
718
460
  child.stdin.destroy();
719
461
  } catch (cause) {
720
462
  failures.push(cause);
@@ -747,7 +489,7 @@ function nativeCommandOwner(child) {
747
489
  return owners.get(child);
748
490
  }
749
491
  function outputStream(source, closed, maxBufferedBytes) {
750
- if (source instanceof Readable3)
492
+ if (source instanceof Readable2)
751
493
  return {
752
494
  bytes: source,
753
495
  destroy() {
@@ -830,7 +572,8 @@ function launchNativeCommand({
830
572
  args: options.args,
831
573
  cwd: options.cwd,
832
574
  env: options.envPolicy === "ambient" ? { ...process.env, ...options.env } : options.env ?? {},
833
- group: options.group === "own" && (driver?.group ?? true)
575
+ group: options.group === "own" && (driver?.group ?? true),
576
+ ownerLoss: options.ownerLoss === "terminate" ? { entry: ownerLossGuardEntry(), signalTarget: options.stop.target } : undefined
834
577
  };
835
578
  let inherited;
836
579
  if (!launch) {
@@ -859,10 +602,12 @@ function launchNativeCommand({
859
602
  ...transport,
860
603
  stdout: stdout.bytes,
861
604
  stderr: stderr.bytes,
862
- start() {
605
+ async start() {
863
606
  if (native)
864
607
  onSpawn?.(native);
608
+ const pid = await awaitCommandStart(child);
865
609
  stdin?.end(options.stdin);
610
+ return pid;
866
611
  }
867
612
  };
868
613
  }
@@ -1021,6 +766,10 @@ function startNativeCommand(input, onSpawn, driver) {
1021
766
  killOn?.throwIfAborted();
1022
767
  if (process.platform === "win32" && driver?.group !== false)
1023
768
  throw new NativeCommandError2("COMMAND_UNAVAILABLE", "POSIX process groups are required");
769
+ if (options.ownerLoss === "terminate" && !ownerLossAvailable(process.platform))
770
+ throw new NativeCommandError2("COMMAND_UNAVAILABLE", "ownerLoss: 'terminate' is available on Linux and Darwin");
771
+ if (options.ownerLoss === "terminate" && (driver?.launch !== undefined || driver?.group === false))
772
+ throw new NativeCommandError2("COMMAND_UNAVAILABLE", "ownerLoss: 'terminate' requires the package-owned process-group launcher");
1024
773
  const { cleanupTimeoutMs, timeoutMs } = nativeCommandBudgets(options, driver);
1025
774
  const controller = new AbortController;
1026
775
  const abort = () => controller.abort(options.signal?.reason);
@@ -1126,8 +875,7 @@ function startNativeCommand(input, onSpawn, driver) {
1126
875
  };
1127
876
  const run = (async () => {
1128
877
  try {
1129
- launched.start();
1130
- const pid = acquired?.child.pid;
878
+ const pid = await raceAbort(launched.start(), controller.signal);
1131
879
  if (pid !== undefined)
1132
880
  options.onLeaderStarted?.({ pid });
1133
881
  const drains = Promise.all([
@@ -1166,4 +914,4 @@ function startNativeCommand(input, onSpawn, driver) {
1166
914
  return owner;
1167
915
  }
1168
916
 
1169
- export { COMMAND_CLEANUP_TIMEOUT_MS, NativeCommandError2, CommandStopped, spawnOwnedCommand, nativeCommandOwner, terminateOwnedGroup, startNativeCommand };
917
+ export { CommandStopped, spawnOwnedCommand, nativeCommandOwner, terminateOwnedGroup, startNativeCommand };