@byok-sdk/client 0.13.0 → 0.14.0

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 (39) hide show
  1. package/README.md +80 -0
  2. package/dist/adapters/claude/process-client.d.ts +24 -0
  3. package/dist/adapters/codex/process-runner.d.ts +46 -1
  4. package/dist/adapters/index.js +359 -28
  5. package/dist/adapters/index.js.map +1 -1
  6. package/dist/adapters/pi/events.d.ts +1 -1
  7. package/dist/adapters/pi/rpc-client.d.ts +47 -1
  8. package/dist/adapters/pi/subagents-policy-extension.js +1 -1
  9. package/dist/adapters/pi/subagents-policy-extension.js.map +1 -1
  10. package/dist/adapters/pi/team-interaction-extension.d.ts +24 -0
  11. package/dist/adapters/pi/team-interaction-extension.js +81 -0
  12. package/dist/adapters/pi/team-interaction-extension.js.map +1 -0
  13. package/dist/adapters/process-tree.d.ts +74 -4
  14. package/dist/adapters/provider-credential-environment.d.ts +1 -1
  15. package/dist/adapters/win32-job-object.d.ts +101 -0
  16. package/dist/bin/byok-agent-memory-mcp.js.map +1 -1
  17. package/dist/bin/byok-agent-message-mcp.js.map +1 -1
  18. package/dist/bin/byok-agent-team-mcp.js.map +1 -1
  19. package/dist/bin/byok-agent.js +9305 -7988
  20. package/dist/bin/byok-agent.js.map +1 -1
  21. package/dist/bin/byok-approval-mcp.js.map +1 -1
  22. package/dist/bin/commands/team-pi-relay.d.ts +24 -0
  23. package/dist/bin/commands/team-relay.d.ts +11 -0
  24. package/dist/bin/team-codex-relay.d.ts +32 -0
  25. package/dist/bin/team-notification-relay.d.ts +40 -0
  26. package/dist/bin/team-pi-session.d.ts +59 -0
  27. package/dist/daemon/agent-egress-policy.d.ts +8 -0
  28. package/dist/daemon/connection-manager.d.ts +6 -100
  29. package/dist/daemon/control-protocol.d.ts +2 -0
  30. package/dist/daemon/create-daemon.d.ts +32 -0
  31. package/dist/daemon/event-spill.d.ts +90 -0
  32. package/dist/daemon/journal/journal.d.ts +15 -3
  33. package/dist/daemon/journal/sqlite-journal.d.ts +7 -2
  34. package/dist/daemon/long-poll-transport.d.ts +2 -50
  35. package/dist/daemon/task-runner.d.ts +13 -0
  36. package/dist/daemon/team-workspace.d.ts +9 -0
  37. package/dist/index.js +897 -241
  38. package/dist/index.js.map +1 -1
  39. package/package.json +8 -5
@@ -1,4 +1,4 @@
1
- import { execFile, spawn } from 'child_process';
1
+ import { execFile, spawn, spawnSync } from 'child_process';
2
2
  import { promises, existsSync, readFileSync, realpathSync } from 'fs';
3
3
  import os from 'os';
4
4
  import path5, { isAbsolute } from 'path';
@@ -6,6 +6,131 @@ import { promisify } from 'util';
6
6
  import { fileURLToPath } from 'url';
7
7
  import 'readline';
8
8
 
9
+ var __defProp = Object.defineProperty;
10
+ var __getOwnPropNames = Object.getOwnPropertyNames;
11
+ var __esm = (fn, res) => function __init() {
12
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
13
+ };
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+
19
+ // src/adapters/win32-job-object.ts
20
+ var win32_job_object_exports = {};
21
+ __export(win32_job_object_exports, {
22
+ Win32JobObjectFailure: () => Win32JobObjectFailure,
23
+ assignOwnedProcessToJob: () => assignOwnedProcessToJob,
24
+ isWin32JobObjectFailure: () => isWin32JobObjectFailure,
25
+ loadBindings: () => loadBindings
26
+ });
27
+ function isWin32JobObjectFailure(value) {
28
+ return value instanceof Error && value.name === "Win32JobObjectFailure";
29
+ }
30
+ function isNullHandle(value) {
31
+ return value === null || value === void 0 || value === 0n || value === 0;
32
+ }
33
+ async function loadBindings() {
34
+ if (cachedBindings !== void 0) return cachedBindings;
35
+ let koffi;
36
+ try {
37
+ const imported = await import('koffi');
38
+ koffi = imported.default ?? imported;
39
+ } catch (cause) {
40
+ throw new Win32JobObjectFailure({
41
+ step: "loadKoffi",
42
+ reason: "win32 runtime process ownership requires the koffi optional dependency, which could not be loaded"
43
+ }, { cause });
44
+ }
45
+ const kernel32 = koffi.load("kernel32.dll");
46
+ const PVOID = koffi.pointer("void");
47
+ const bind = (name, result, args) => kernel32.func("__stdcall", name, result, args);
48
+ cachedBindings = {
49
+ createJobObjectW: bind("CreateJobObjectW", PVOID, [PVOID, "str16"]),
50
+ setInformationJobObject: bind("SetInformationJobObject", "int", [PVOID, "int", PVOID, "uint32"]),
51
+ openProcess: bind("OpenProcess", PVOID, ["uint32", "int", "uint32"]),
52
+ assignProcessToJobObject: bind("AssignProcessToJobObject", "int", [PVOID, PVOID]),
53
+ closeHandle: bind("CloseHandle", "int", [PVOID]),
54
+ getLastError: bind("GetLastError", "uint32", [])
55
+ };
56
+ return cachedBindings;
57
+ }
58
+ function killOnCloseLimitInformation() {
59
+ const information = Buffer.alloc(JOBOBJECT_EXTENDED_LIMIT_SIZE);
60
+ information.writeUInt32LE(JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, JOBOBJECT_EXTENDED_LIMIT_FLAGS_OFFSET);
61
+ return information;
62
+ }
63
+ function daemonJob(bindings) {
64
+ const existing = jobsByBindings.get(bindings);
65
+ if (existing !== void 0) return existing;
66
+ const job = bindings.createJobObjectW(null, null);
67
+ if (isNullHandle(job)) {
68
+ throw new Win32JobObjectFailure({
69
+ step: "CreateJobObjectW",
70
+ reason: "win32 runtime process ownership could not create the daemon kill-on-close job object",
71
+ win32Code: bindings.getLastError()
72
+ });
73
+ }
74
+ const information = killOnCloseLimitInformation();
75
+ if (bindings.setInformationJobObject(job, JobObjectExtendedLimitInformation, information, information.length) === 0) {
76
+ const win32Code = bindings.getLastError();
77
+ bindings.closeHandle(job);
78
+ throw new Win32JobObjectFailure({
79
+ step: "SetInformationJobObject",
80
+ reason: "win32 runtime process ownership could not make the daemon job object kill-on-close",
81
+ win32Code
82
+ });
83
+ }
84
+ jobsByBindings.set(bindings, job);
85
+ return job;
86
+ }
87
+ async function assignOwnedProcessToJob(pid, options) {
88
+ const bindings = options?.bindings ?? await loadBindings();
89
+ const job = daemonJob(bindings);
90
+ const processHandle = bindings.openProcess(OWNED_PROCESS_ACCESS, 0, pid);
91
+ if (isNullHandle(processHandle)) {
92
+ throw new Win32JobObjectFailure({
93
+ step: "OpenProcess",
94
+ reason: `win32 runtime process ownership could not open process ${pid} for job assignment`,
95
+ win32Code: bindings.getLastError()
96
+ });
97
+ }
98
+ try {
99
+ if (bindings.assignProcessToJobObject(job, processHandle) === 0) {
100
+ throw new Win32JobObjectFailure({
101
+ step: "AssignProcessToJobObject",
102
+ reason: `win32 runtime process ownership could not assign process ${pid} to the daemon kill-on-close job object`,
103
+ win32Code: bindings.getLastError()
104
+ });
105
+ }
106
+ } finally {
107
+ bindings.closeHandle(processHandle);
108
+ }
109
+ }
110
+ var JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, JobObjectExtendedLimitInformation, JOBOBJECT_EXTENDED_LIMIT_SIZE, JOBOBJECT_EXTENDED_LIMIT_FLAGS_OFFSET, PROCESS_SET_QUOTA, PROCESS_TERMINATE, OWNED_PROCESS_ACCESS, Win32JobObjectFailure, cachedBindings, jobsByBindings;
111
+ var init_win32_job_object = __esm({
112
+ "src/adapters/win32-job-object.ts"() {
113
+ JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 8192;
114
+ JobObjectExtendedLimitInformation = 9;
115
+ JOBOBJECT_EXTENDED_LIMIT_SIZE = 144;
116
+ JOBOBJECT_EXTENDED_LIMIT_FLAGS_OFFSET = 16;
117
+ PROCESS_SET_QUOTA = 256;
118
+ PROCESS_TERMINATE = 1;
119
+ OWNED_PROCESS_ACCESS = PROCESS_SET_QUOTA | PROCESS_TERMINATE;
120
+ Win32JobObjectFailure = class extends Error {
121
+ step;
122
+ win32Code;
123
+ constructor(input, options) {
124
+ super(input.reason, options);
125
+ this.name = "Win32JobObjectFailure";
126
+ this.step = input.step;
127
+ this.win32Code = input.win32Code;
128
+ }
129
+ };
130
+ jobsByBindings = /* @__PURE__ */ new WeakMap();
131
+ }
132
+ });
133
+
9
134
  // src/runtime-failure.ts
10
135
  var RUNTIME_EXECUTION_FAILURE_BRAND = /* @__PURE__ */ Symbol.for("@byok-sdk/client/RuntimeExecutionFailure/v1");
11
136
  var RUNTIME_DISPOSAL_FAILURE_BRAND = /* @__PURE__ */ Symbol.for("@byok-sdk/client/RuntimeDisposalFailure/v1");
@@ -436,6 +561,55 @@ function withOwnedProcessTree(options) {
436
561
  ...process.platform === "win32" ? { windowsHide: true } : { detached: true }
437
562
  };
438
563
  }
564
+ var hostExitTargets = /* @__PURE__ */ new Map();
565
+ var hostExitHandlerInstalled = false;
566
+ function terminateOwnedTreesForHostExit() {
567
+ for (const target of hostExitTargets.values()) {
568
+ try {
569
+ if (target.platform === "win32") {
570
+ target.spawnSyncFn("taskkill", ["/PID", String(target.pid), "/T", "/F"], {
571
+ stdio: "ignore",
572
+ windowsHide: true
573
+ });
574
+ } else {
575
+ target.kill(-target.pid, "SIGKILL");
576
+ }
577
+ } catch {
578
+ }
579
+ }
580
+ hostExitTargets.clear();
581
+ }
582
+ function ensureHostExitHandler() {
583
+ if (hostExitHandlerInstalled) return;
584
+ hostExitHandlerInstalled = true;
585
+ process.prependListener("exit", terminateOwnedTreesForHostExit);
586
+ }
587
+ var defaultJobObject = {
588
+ async assign(pid) {
589
+ const { assignOwnedProcessToJob: assignOwnedProcessToJob2 } = await Promise.resolve().then(() => (init_win32_job_object(), win32_job_object_exports));
590
+ await assignOwnedProcessToJob2(pid);
591
+ }
592
+ };
593
+ async function adoptOwnedProcessTree(options) {
594
+ const pid = positivePid(options.child, options.label);
595
+ if (pid === void 0) return;
596
+ const platform = options.platform ?? process.platform;
597
+ ensureHostExitHandler();
598
+ hostExitTargets.set(options.child, {
599
+ pid,
600
+ label: options.label,
601
+ platform,
602
+ kill: options.killFn ?? defaultKill,
603
+ spawnSyncFn: options.spawnSyncFn ?? spawnSync
604
+ });
605
+ const forget = () => {
606
+ hostExitTargets.delete(options.child);
607
+ };
608
+ options.child.once("close", forget);
609
+ options.child.once("exit", forget);
610
+ if (platform !== "win32") return;
611
+ await (options.jobObject ?? defaultJobObject).assign(pid);
612
+ }
439
613
  function positivePid(child, label) {
440
614
  const pid = child.pid;
441
615
  if (pid === void 0) return void 0;
@@ -647,21 +821,8 @@ async function disposeOwnedProcessTree(options) {
647
821
  var STDERR_RING_CAPACITY = 20;
648
822
  var DIALOG_UI_METHODS = /* @__PURE__ */ new Set(["select", "confirm", "input", "editor"]);
649
823
  var PiRpcClient = class {
650
- child;
651
- buffer = "";
652
- nextId = 1;
653
- pending = /* @__PURE__ */ new Map();
654
- eventQueue = new AsyncQueue();
655
- closed = false;
656
- exitError;
657
- closedPromise;
658
- resolveClosed;
659
- disposalAttempt;
660
- /** Bounded tail of recent stderr lines — pi discarded this entirely before (nothing ever read `child.stderr`), which is exactly why finding #1 (`Error: Unknown option: --session-id`, exit 1) had to be root-caused by hand instead of reading it off a thrown error. See `buildExitError`. */
661
- stderrRing = [];
662
- /** Count of pi RPC message types `PiSession` (pi-adapter.ts) has told us have no `AgentEvent` mapping and aren't routine bookkeeping — see `recordUnmappedFrame`. */
663
- unmappedFrameCounts = /* @__PURE__ */ new Map();
664
824
  constructor(options) {
825
+ this.options = options;
665
826
  const spawnFn = options.spawnFn ?? spawn;
666
827
  this.child = spawnFn(options.command, options.args, withOwnedProcessTree({
667
828
  cwd: options.cwd,
@@ -671,6 +832,9 @@ var PiRpcClient = class {
671
832
  this.closedPromise = new Promise((resolve) => {
672
833
  this.resolveClosed = resolve;
673
834
  });
835
+ this.adopted = this.adoptOwnedTree(options);
836
+ this.adopted.catch(() => {
837
+ });
674
838
  this.child.stdout.setEncoding("utf8");
675
839
  this.child.stdout.on("data", (chunk) => this.onData(chunk));
676
840
  this.child.stderr.setEncoding("utf8");
@@ -682,10 +846,37 @@ var PiRpcClient = class {
682
846
  this.onClosed(err);
683
847
  });
684
848
  }
685
- /** Send a command, resolved with its correlated `response` message. */
686
- send(command) {
849
+ options;
850
+ child;
851
+ buffer = "";
852
+ nextId = 1;
853
+ pending = /* @__PURE__ */ new Map();
854
+ eventQueue = new AsyncQueue();
855
+ closed = false;
856
+ exitError;
857
+ closedPromise;
858
+ resolveClosed;
859
+ disposalAttempt;
860
+ /** Resolves once this tree is backstopped (see `adoptOwnedProcessTree`); rejects with the adoption failure, having already terminated the tree. */
861
+ adopted;
862
+ /** Set before the fail-closed termination starts, so it — not the exit code of the kill we ourselves requested — becomes this client's exit error. */
863
+ adoptionFailure;
864
+ /** Bounded tail of recent stderr lines — pi discarded this entirely before (nothing ever read `child.stderr`), which is exactly why finding #1 (`Error: Unknown option: --session-id`, exit 1) had to be root-caused by hand instead of reading it off a thrown error. See `buildExitError`. */
865
+ stderrRing = [];
866
+ /** Count of pi RPC message types `PiSession` (pi-adapter.ts) has told us have no `AgentEvent` mapping and aren't routine bookkeeping — see `recordUnmappedFrame`. */
867
+ unmappedFrameCounts = /* @__PURE__ */ new Map();
868
+ /**
869
+ * Send a command, resolved with its correlated `response` message.
870
+ *
871
+ * Adoption is awaited FIRST — this is `PiAdapter.start()`'s first awaited
872
+ * operation, so an unbackstopped tree never receives the initial prompt and
873
+ * never yields a session. The wait costs one settled-promise turn once the
874
+ * tree is adopted; every later command sees it already resolved.
875
+ */
876
+ async send(command) {
877
+ await this.adopted;
687
878
  if (this.closed) {
688
- return Promise.reject(this.exitError ?? new Error("pi process is closed"));
879
+ throw this.exitError ?? new Error("pi process is closed");
689
880
  }
690
881
  const id = command.id ?? `req-${this.nextId++}`;
691
882
  const full = { ...command, id };
@@ -758,6 +949,32 @@ var PiRpcClient = class {
758
949
  label: "pi"
759
950
  };
760
951
  }
952
+ /**
953
+ * Backstop this tree, or tear it down. Adoption failure is a start-time
954
+ * precondition, not a degraded mode: the child is terminated through the one
955
+ * disposal authority and the failure is re-thrown, which is what makes the
956
+ * first `send()` — and therefore `PiAdapter.start()` — fail before any
957
+ * session is published. Both cleanup attempts are best-effort because the
958
+ * adoption failure, not a terminator's own complaint, is the reason to report.
959
+ */
960
+ async adoptOwnedTree(options) {
961
+ try {
962
+ await adoptOwnedProcessTree({
963
+ child: this.child,
964
+ label: "pi",
965
+ platform: options.platform,
966
+ jobObject: options.jobObject
967
+ });
968
+ } catch (cause) {
969
+ const failure = cause instanceof Error ? cause : new Error(String(cause));
970
+ this.adoptionFailure = failure;
971
+ await requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
972
+ });
973
+ await disposeOwnedProcessTree(this.processTreeOptions()).catch(() => {
974
+ });
975
+ throw failure;
976
+ }
977
+ }
761
978
  onData(chunk) {
762
979
  this.buffer += chunk;
763
980
  let newlineIndex = this.buffer.indexOf("\n");
@@ -776,6 +993,7 @@ var PiRpcClient = class {
776
993
  } catch {
777
994
  return;
778
995
  }
996
+ this.options.onFrame?.(msg);
779
997
  if (msg.type === "response" && typeof msg.id === "string" && this.pending.has(msg.id)) {
780
998
  const waiter = this.pending.get(msg.id);
781
999
  this.pending.delete(msg.id);
@@ -783,11 +1001,20 @@ var PiRpcClient = class {
783
1001
  return;
784
1002
  }
785
1003
  if (msg.type === "extension_ui_request" && typeof msg.id === "string" && typeof msg.method === "string") {
786
- this.respondToExtensionUiRequest(msg.id, msg.method);
1004
+ if (this.options.extensionUi?.mode === "hold") this.options.extensionUi.onRequest(msg);
1005
+ else this.respondToExtensionUiRequest(msg.id, msg.method);
787
1006
  return;
788
1007
  }
789
1008
  this.eventQueue.push(msg);
790
1009
  }
1010
+ /** Write an explicit host-owned response; completion is a transport receipt only. */
1011
+ async respondExtensionUi(response) {
1012
+ if (this.options.extensionUi?.mode !== "hold") throw new Error("Pi interactions are not host-owned");
1013
+ await this.adopted;
1014
+ if (this.closed) throw new Error("Pi process is closed");
1015
+ await new Promise((resolve, reject) => this.child.stdin.write(`${JSON.stringify({ type: "extension_ui_response", ...response })}
1016
+ `, (error) => error ? reject(error) : resolve()));
1017
+ }
791
1018
  /**
792
1019
  * Answer pi's extension-UI blocking protocol headlessly (rpc.md's
793
1020
  * "Extension UI Protocol"). Fail-closed policy, stated explicitly because
@@ -841,9 +1068,10 @@ var PiRpcClient = class {
841
1068
  onClosed(err) {
842
1069
  if (this.closed) return;
843
1070
  this.closed = true;
844
- this.exitError = err;
1071
+ const terminal = this.adoptionFailure ?? err;
1072
+ this.exitError = terminal;
845
1073
  this.resolveClosed();
846
- for (const [, waiter] of this.pending) waiter.reject(err);
1074
+ for (const [, waiter] of this.pending) waiter.reject(terminal);
847
1075
  this.pending.clear();
848
1076
  this.eventQueue.end();
849
1077
  }
@@ -877,6 +1105,8 @@ var PROVIDER_CREDENTIAL_ENV_DENY_NAMES = [
877
1105
  "TOGETHER_API_KEY",
878
1106
  "BASETEN_API_KEY",
879
1107
  "KIMI_API_KEY",
1108
+ "HF_TOKEN",
1109
+ "MOONSHOT_API_KEY",
880
1110
  "MINIMAX_API_KEY",
881
1111
  "MINIMAX_CN_API_KEY",
882
1112
  "QWEN_TOKEN_PLAN_API_KEY",
@@ -1753,6 +1983,10 @@ var ClaudeProcessClient = class {
1753
1983
  closedPromise;
1754
1984
  resolveClosed;
1755
1985
  disposalAttempt;
1986
+ /** Resolves once this tree is backstopped (see `adoptOwnedProcessTree`); rejects with the adoption failure, having already terminated the tree. */
1987
+ adopted;
1988
+ /** Set before the fail-closed termination starts, so it — not the exit code of the kill we ourselves requested — becomes this client's exit error. */
1989
+ adoptionFailure;
1756
1990
  stderrRing = [];
1757
1991
  unmappedFrameCounts = /* @__PURE__ */ new Map();
1758
1992
  sessionId;
@@ -1767,6 +2001,9 @@ var ClaudeProcessClient = class {
1767
2001
  this.closedPromise = new Promise((resolve) => {
1768
2002
  this.resolveClosed = resolve;
1769
2003
  });
2004
+ this.adopted = this.adoptOwnedTree(options);
2005
+ this.adopted.catch(() => {
2006
+ });
1770
2007
  this.child.stdout.setEncoding("utf8");
1771
2008
  this.child.stdout.on("data", (chunk) => this.onData(chunk));
1772
2009
  this.child.stderr.setEncoding("utf8");
@@ -1810,9 +2047,10 @@ var ClaudeProcessClient = class {
1810
2047
  * same id; if the process already closed before init ever arrived,
1811
2048
  * every call rejects with that same exit error.
1812
2049
  */
1813
- waitForInit() {
1814
- if (this.sessionId !== void 0) return Promise.resolve(this.sessionId);
1815
- if (this.closed) return Promise.reject(this.exitError ?? new Error("claude process is closed"));
2050
+ async waitForInit() {
2051
+ await this.adopted;
2052
+ if (this.sessionId !== void 0) return this.sessionId;
2053
+ if (this.closed) throw this.exitError ?? new Error("claude process is closed");
1816
2054
  return new Promise((resolve, reject) => {
1817
2055
  this.initWaiter = { resolve, reject };
1818
2056
  });
@@ -1876,6 +2114,32 @@ var ClaudeProcessClient = class {
1876
2114
  label: "claude"
1877
2115
  };
1878
2116
  }
2117
+ /**
2118
+ * Backstop this tree, or tear it down. Adoption failure is a start-time
2119
+ * precondition, not a degraded mode: the child is terminated through the one
2120
+ * disposal authority and the failure is re-thrown, which is what makes
2121
+ * `waitForInit()` — and therefore `ClaudeAdapter.start()` — fail before any
2122
+ * session is published. Both cleanup attempts are best-effort because the
2123
+ * adoption failure, not a terminator's own complaint, is the reason to report.
2124
+ */
2125
+ async adoptOwnedTree(options) {
2126
+ try {
2127
+ await adoptOwnedProcessTree({
2128
+ child: this.child,
2129
+ label: "claude",
2130
+ platform: options.platform,
2131
+ jobObject: options.jobObject
2132
+ });
2133
+ } catch (cause) {
2134
+ const failure = cause instanceof Error ? cause : new Error(String(cause));
2135
+ this.adoptionFailure = failure;
2136
+ await requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
2137
+ });
2138
+ await disposeOwnedProcessTree(this.processTreeOptions()).catch(() => {
2139
+ });
2140
+ throw failure;
2141
+ }
2142
+ }
1879
2143
  onData(chunk) {
1880
2144
  this.buffer += chunk;
1881
2145
  let newlineIndex = this.buffer.indexOf("\n");
@@ -1924,9 +2188,10 @@ var ClaudeProcessClient = class {
1924
2188
  onClosed(err) {
1925
2189
  if (this.closed) return;
1926
2190
  this.closed = true;
1927
- this.exitError = err;
2191
+ const terminal = this.adoptionFailure ?? err;
2192
+ this.exitError = terminal;
1928
2193
  this.resolveClosed();
1929
- this.initWaiter?.reject(err);
2194
+ this.initWaiter?.reject(terminal);
1930
2195
  this.initWaiter = void 0;
1931
2196
  this.eventQueue.end();
1932
2197
  }
@@ -2662,6 +2927,19 @@ var CodexProcessRunner = class {
2662
2927
  closedPromise;
2663
2928
  resolveClosed;
2664
2929
  disposalAttempt;
2930
+ /** Resolves once this tree is backstopped (see `adoptOwnedProcessTree`); rejects with the adoption failure, having already terminated the tree. */
2931
+ adopted;
2932
+ /** Set before the fail-closed termination starts; `buildExitError` reports it instead of the exit status of the kill we ourselves requested. */
2933
+ adoptionFailure;
2934
+ adoption = "pending";
2935
+ /**
2936
+ * Lines parsed before adoption settled. Unlike pi and claude, this runner has
2937
+ * no first awaited operation of its own to gate on — its caller reads the
2938
+ * FIRST event as the authoritative thread id. Holding events until the tree
2939
+ * is backstopped is what keeps that caller from publishing a session for a
2940
+ * tree the job object never took.
2941
+ */
2942
+ deferredEvents = [];
2665
2943
  constructor(options) {
2666
2944
  this.onEvent = options.onEvent;
2667
2945
  const spawnFn = options.spawnFn ?? spawn;
@@ -2673,6 +2951,9 @@ var CodexProcessRunner = class {
2673
2951
  this.closedPromise = new Promise((resolve) => {
2674
2952
  this.resolveClosed = resolve;
2675
2953
  });
2954
+ this.adopted = this.adoptOwnedTree(options);
2955
+ this.adopted.catch(() => {
2956
+ });
2676
2957
  this.child.stdout.setEncoding("utf8");
2677
2958
  this.child.stdout.on("data", (chunk) => this.onData(chunk));
2678
2959
  this.child.stderr.setEncoding("utf8");
@@ -2738,8 +3019,58 @@ var CodexProcessRunner = class {
2738
3019
  label: "codex"
2739
3020
  };
2740
3021
  }
2741
- /** Builds a descriptive error folding in the exit code/signal and the stderr tail — mirrors `PiRpcClient.buildExitError`'s reasoning: a post-mortem on a failed start/resume should never need separately re-running codex by hand with a raw JSONL logger to learn why. */
3022
+ /**
3023
+ * Backstop this tree, or tear it down. Adoption failure is a start-time
3024
+ * precondition, not a degraded mode: the child is terminated through the one
3025
+ * disposal authority, every parsed line is dropped instead of delivered, and
3026
+ * the resulting close makes the caller's own `waitClosed()` race reject with
3027
+ * the adoption failure (`buildExitError`) before a thread id is published.
3028
+ * Both cleanup attempts are best-effort because the adoption failure, not a
3029
+ * terminator's own complaint, is the reason to report.
3030
+ */
3031
+ async adoptOwnedTree(options) {
3032
+ try {
3033
+ await adoptOwnedProcessTree({
3034
+ child: this.child,
3035
+ label: "codex",
3036
+ platform: options.platform,
3037
+ jobObject: options.jobObject
3038
+ });
3039
+ } catch (cause) {
3040
+ const failure = cause instanceof Error ? cause : new Error(String(cause));
3041
+ this.adoptionFailure = failure;
3042
+ this.adoption = "failed";
3043
+ this.deferredEvents.length = 0;
3044
+ await requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
3045
+ });
3046
+ await disposeOwnedProcessTree(this.processTreeOptions()).catch(() => {
3047
+ });
3048
+ throw failure;
3049
+ }
3050
+ this.adoption = "adopted";
3051
+ for (const evt of this.deferredEvents.splice(0)) this.onEvent(evt);
3052
+ }
3053
+ /** Arrival-order delivery, held back until the tree is backstopped (see `deferredEvents`). */
3054
+ deliver(evt) {
3055
+ if (this.adoption === "failed") return;
3056
+ if (this.adoption === "pending") {
3057
+ this.deferredEvents.push(evt);
3058
+ return;
3059
+ }
3060
+ this.onEvent(evt);
3061
+ }
3062
+ /**
3063
+ * Builds a descriptive error folding in the exit code/signal and the stderr
3064
+ * tail — mirrors `PiRpcClient.buildExitError`'s reasoning: a post-mortem on a
3065
+ * failed start/resume should never need separately re-running codex by hand
3066
+ * with a raw JSONL logger to learn why.
3067
+ *
3068
+ * A tree this runner could not backstop is the one exception: that process
3069
+ * exited because THIS runner killed it, so `exit code=null, signal=SIGKILL`
3070
+ * plus an empty stderr tail would bury the only reason anyone can act on.
3071
+ */
2742
3072
  buildExitError(context) {
3073
+ if (this.adoptionFailure !== void 0) return this.adoptionFailure;
2743
3074
  const parts = [`${context} (exit code=${this.exitCode}, signal=${this.exitSignal})`];
2744
3075
  if (this.stderrRing.length > 0) {
2745
3076
  parts.push(`stderr: ${this.stderrRing.join(" | ")}`);
@@ -2767,7 +3098,7 @@ var CodexProcessRunner = class {
2767
3098
  if (!parsed || typeof parsed !== "object" || typeof parsed.type !== "string") {
2768
3099
  return;
2769
3100
  }
2770
- this.onEvent(parsed);
3101
+ this.deliver(parsed);
2771
3102
  }
2772
3103
  onStderr(chunk) {
2773
3104
  for (const rawLine of chunk.split("\n")) {