@byok-sdk/client 0.13.0 → 0.15.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 (69) hide show
  1. package/README.md +95 -0
  2. package/dist/adapters/claude/process-client.d.ts +24 -0
  3. package/dist/adapters/codex/codex-adapter.d.ts +2 -0
  4. package/dist/adapters/codex/process-runner.d.ts +62 -18
  5. package/dist/adapters/detect-outcome.d.ts +18 -0
  6. package/dist/adapters/index.d.ts +1 -1
  7. package/dist/adapters/index.js +546 -76
  8. package/dist/adapters/index.js.map +1 -1
  9. package/dist/adapters/pi/events.d.ts +1 -1
  10. package/dist/adapters/pi/rpc-client.d.ts +47 -1
  11. package/dist/adapters/pi/subagents-policy-extension.js +1 -1
  12. package/dist/adapters/pi/subagents-policy-extension.js.map +1 -1
  13. package/dist/adapters/pi/team-interaction-extension.d.ts +24 -0
  14. package/dist/adapters/pi/team-interaction-extension.js +81 -0
  15. package/dist/adapters/pi/team-interaction-extension.js.map +1 -0
  16. package/dist/adapters/process-tree.d.ts +74 -4
  17. package/dist/adapters/provider-credential-environment.d.ts +1 -1
  18. package/dist/adapters/win32-job-object.d.ts +101 -0
  19. package/dist/bin/byok-agent-memory-mcp.js +29 -0
  20. package/dist/bin/byok-agent-memory-mcp.js.map +1 -1
  21. package/dist/bin/byok-agent-message-mcp.js +29 -0
  22. package/dist/bin/byok-agent-message-mcp.js.map +1 -1
  23. package/dist/bin/byok-agent-team-mcp.js +29 -0
  24. package/dist/bin/byok-agent-team-mcp.js.map +1 -1
  25. package/dist/bin/byok-agent.js +20274 -18057
  26. package/dist/bin/byok-agent.js.map +1 -1
  27. package/dist/bin/byok-approval-mcp.js +29 -0
  28. package/dist/bin/byok-approval-mcp.js.map +1 -1
  29. package/dist/bin/byok-mcp-env.d.ts +2 -0
  30. package/dist/bin/byok-mcp-env.js +36 -0
  31. package/dist/bin/byok-mcp-env.js.map +1 -0
  32. package/dist/bin/commands/doctor.d.ts +3 -0
  33. package/dist/bin/commands/team-pi-relay.d.ts +24 -0
  34. package/dist/bin/commands/team-relay.d.ts +11 -0
  35. package/dist/bin/mcp-env-launcher.d.ts +5 -0
  36. package/dist/bin/runtime-probe.d.ts +6 -7
  37. package/dist/bin/team-codex-relay.d.ts +32 -0
  38. package/dist/bin/team-notification-relay.d.ts +40 -0
  39. package/dist/bin/team-pi-session.d.ts +59 -0
  40. package/dist/daemon/admission-wait.d.ts +2 -0
  41. package/dist/daemon/agent-egress-policy.d.ts +8 -0
  42. package/dist/daemon/agent-egress-spool.d.ts +1 -0
  43. package/dist/daemon/agent-message-outbox.d.ts +15 -3
  44. package/dist/daemon/artifact-read.d.ts +7 -0
  45. package/dist/daemon/connection-manager.d.ts +23 -176
  46. package/dist/daemon/control-protocol.d.ts +2 -0
  47. package/dist/daemon/create-daemon.d.ts +43 -1
  48. package/dist/daemon/event-spill.d.ts +90 -0
  49. package/dist/daemon/journal/journal.d.ts +15 -3
  50. package/dist/daemon/journal/sqlite-journal.d.ts +7 -2
  51. package/dist/daemon/long-poll-transport.d.ts +2 -50
  52. package/dist/daemon/runtime-start.d.ts +3 -0
  53. package/dist/daemon/store.d.ts +2 -0
  54. package/dist/daemon/task-runner.d.ts +36 -0
  55. package/dist/daemon/team-workspace.d.ts +9 -0
  56. package/dist/daemon/terminal-commit-queue.d.ts +21 -0
  57. package/dist/daemon/terminal-identity.d.ts +6 -0
  58. package/dist/diagnostics/device-doctor.d.ts +46 -0
  59. package/dist/diagnostics/diagnostics.d.ts +3 -80
  60. package/dist/diagnostics/types.d.ts +82 -0
  61. package/dist/index.d.ts +3 -1
  62. package/dist/index.js +2824 -649
  63. package/dist/index.js.map +1 -1
  64. package/dist/runtime-detection.d.ts +3 -0
  65. package/dist/runtime-failure.d.ts +6 -0
  66. package/dist/sdk-reserved-helper-host.d.ts +1 -1
  67. package/dist/types.d.ts +20 -10
  68. package/dist/util/durable-jsonl.d.ts +12 -0
  69. package/package.json +8 -5
@@ -1,10 +1,136 @@
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';
5
5
  import { promisify } from 'util';
6
6
  import { fileURLToPath } from 'url';
7
7
  import 'readline';
8
+ import { randomBytes } from 'crypto';
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __esm = (fn, res) => function __init() {
13
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
14
+ };
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+
20
+ // src/adapters/win32-job-object.ts
21
+ var win32_job_object_exports = {};
22
+ __export(win32_job_object_exports, {
23
+ Win32JobObjectFailure: () => Win32JobObjectFailure,
24
+ assignOwnedProcessToJob: () => assignOwnedProcessToJob,
25
+ isWin32JobObjectFailure: () => isWin32JobObjectFailure,
26
+ loadBindings: () => loadBindings
27
+ });
28
+ function isWin32JobObjectFailure(value) {
29
+ return value instanceof Error && value.name === "Win32JobObjectFailure";
30
+ }
31
+ function isNullHandle(value) {
32
+ return value === null || value === void 0 || value === 0n || value === 0;
33
+ }
34
+ async function loadBindings() {
35
+ if (cachedBindings !== void 0) return cachedBindings;
36
+ let koffi;
37
+ try {
38
+ const imported = await import('koffi');
39
+ koffi = imported.default ?? imported;
40
+ } catch (cause) {
41
+ throw new Win32JobObjectFailure({
42
+ step: "loadKoffi",
43
+ reason: "win32 runtime process ownership requires the koffi optional dependency, which could not be loaded"
44
+ }, { cause });
45
+ }
46
+ const kernel32 = koffi.load("kernel32.dll");
47
+ const PVOID = koffi.pointer("void");
48
+ const bind = (name, result, args) => kernel32.func("__stdcall", name, result, args);
49
+ cachedBindings = {
50
+ createJobObjectW: bind("CreateJobObjectW", PVOID, [PVOID, "str16"]),
51
+ setInformationJobObject: bind("SetInformationJobObject", "int", [PVOID, "int", PVOID, "uint32"]),
52
+ openProcess: bind("OpenProcess", PVOID, ["uint32", "int", "uint32"]),
53
+ assignProcessToJobObject: bind("AssignProcessToJobObject", "int", [PVOID, PVOID]),
54
+ closeHandle: bind("CloseHandle", "int", [PVOID]),
55
+ getLastError: bind("GetLastError", "uint32", [])
56
+ };
57
+ return cachedBindings;
58
+ }
59
+ function killOnCloseLimitInformation() {
60
+ const information = Buffer.alloc(JOBOBJECT_EXTENDED_LIMIT_SIZE);
61
+ information.writeUInt32LE(JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, JOBOBJECT_EXTENDED_LIMIT_FLAGS_OFFSET);
62
+ return information;
63
+ }
64
+ function daemonJob(bindings) {
65
+ const existing = jobsByBindings.get(bindings);
66
+ if (existing !== void 0) return existing;
67
+ const job = bindings.createJobObjectW(null, null);
68
+ if (isNullHandle(job)) {
69
+ throw new Win32JobObjectFailure({
70
+ step: "CreateJobObjectW",
71
+ reason: "win32 runtime process ownership could not create the daemon kill-on-close job object",
72
+ win32Code: bindings.getLastError()
73
+ });
74
+ }
75
+ const information = killOnCloseLimitInformation();
76
+ if (bindings.setInformationJobObject(job, JobObjectExtendedLimitInformation, information, information.length) === 0) {
77
+ const win32Code = bindings.getLastError();
78
+ bindings.closeHandle(job);
79
+ throw new Win32JobObjectFailure({
80
+ step: "SetInformationJobObject",
81
+ reason: "win32 runtime process ownership could not make the daemon job object kill-on-close",
82
+ win32Code
83
+ });
84
+ }
85
+ jobsByBindings.set(bindings, job);
86
+ return job;
87
+ }
88
+ async function assignOwnedProcessToJob(pid, options) {
89
+ const bindings = options?.bindings ?? await loadBindings();
90
+ const job = daemonJob(bindings);
91
+ const processHandle = bindings.openProcess(OWNED_PROCESS_ACCESS, 0, pid);
92
+ if (isNullHandle(processHandle)) {
93
+ throw new Win32JobObjectFailure({
94
+ step: "OpenProcess",
95
+ reason: `win32 runtime process ownership could not open process ${pid} for job assignment`,
96
+ win32Code: bindings.getLastError()
97
+ });
98
+ }
99
+ try {
100
+ if (bindings.assignProcessToJobObject(job, processHandle) === 0) {
101
+ throw new Win32JobObjectFailure({
102
+ step: "AssignProcessToJobObject",
103
+ reason: `win32 runtime process ownership could not assign process ${pid} to the daemon kill-on-close job object`,
104
+ win32Code: bindings.getLastError()
105
+ });
106
+ }
107
+ } finally {
108
+ bindings.closeHandle(processHandle);
109
+ }
110
+ }
111
+ 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;
112
+ var init_win32_job_object = __esm({
113
+ "src/adapters/win32-job-object.ts"() {
114
+ JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 8192;
115
+ JobObjectExtendedLimitInformation = 9;
116
+ JOBOBJECT_EXTENDED_LIMIT_SIZE = 144;
117
+ JOBOBJECT_EXTENDED_LIMIT_FLAGS_OFFSET = 16;
118
+ PROCESS_SET_QUOTA = 256;
119
+ PROCESS_TERMINATE = 1;
120
+ OWNED_PROCESS_ACCESS = PROCESS_SET_QUOTA | PROCESS_TERMINATE;
121
+ Win32JobObjectFailure = class extends Error {
122
+ step;
123
+ win32Code;
124
+ constructor(input, options) {
125
+ super(input.reason, options);
126
+ this.name = "Win32JobObjectFailure";
127
+ this.step = input.step;
128
+ this.win32Code = input.win32Code;
129
+ }
130
+ };
131
+ jobsByBindings = /* @__PURE__ */ new WeakMap();
132
+ }
133
+ });
8
134
 
9
135
  // src/runtime-failure.ts
10
136
  var RUNTIME_EXECUTION_FAILURE_BRAND = /* @__PURE__ */ Symbol.for("@byok-sdk/client/RuntimeExecutionFailure/v1");
@@ -56,6 +182,48 @@ function isRuntimeExecutionFailure(value) {
56
182
  const candidate = value;
57
183
  return candidate[RUNTIME_EXECUTION_FAILURE_BRAND] === true && isRuntimeFailurePhase(candidate.phase) && isRuntimeFailureCategory(candidate.category) && isRuntimeRetryDisposition(candidate.retry) && typeof candidate.message === "string" && candidate.message.length > 0;
58
184
  }
185
+ var RuntimeStartupDisposalFailure = class extends Error {
186
+ retryDisposal;
187
+ constructor(retryDisposal, options) {
188
+ super("runtime startup failed and process ownership remains quarantined", options);
189
+ this.name = "RuntimeStartupDisposalFailure";
190
+ this.retryDisposal = retryDisposal;
191
+ Object.defineProperty(this, /* @__PURE__ */ Symbol.for("@byok-sdk/client/RuntimeStartupDisposalFailure/v1"), { value: true });
192
+ Object.freeze(this);
193
+ }
194
+ };
195
+ function classifyDetectError(error) {
196
+ const code = error !== null && typeof error === "object" ? error.code : void 0;
197
+ if (code === "ENOENT") return { kind: "not-found" };
198
+ if (code === "EACCES" || code === "EPERM" || code === "ENOEXEC") return { kind: "not-executable" };
199
+ return { kind: "probe-failed" };
200
+ }
201
+ async function probeRuntimeVersion(command, timeoutMs) {
202
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) throw new TypeError("invalid runtime probe timeout");
203
+ return new Promise((resolve) => {
204
+ let timer;
205
+ let timedOut = false;
206
+ try {
207
+ const child = execFile(command, ["--version"], { encoding: "utf8" }, (error, stdout, stderr) => {
208
+ if (timer) clearTimeout(timer);
209
+ if (error?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") resolve({ kind: "probe-failed" });
210
+ else if (timedOut) resolve({ kind: "timeout" });
211
+ else if (error) resolve(classifyDetectError(error));
212
+ else resolve({ kind: "available", stdout, stderr });
213
+ });
214
+ timer = setTimeout(() => {
215
+ timedOut = true;
216
+ child.stdout?.destroy();
217
+ child.stderr?.destroy();
218
+ child.kill("SIGKILL");
219
+ }, timeoutMs);
220
+ timer.unref?.();
221
+ } catch (error) {
222
+ if (timer) clearTimeout(timer);
223
+ resolve(classifyDetectError(error));
224
+ }
225
+ });
226
+ }
59
227
 
60
228
  // src/types.ts
61
229
  function frozenStrings(values) {
@@ -436,6 +604,55 @@ function withOwnedProcessTree(options) {
436
604
  ...process.platform === "win32" ? { windowsHide: true } : { detached: true }
437
605
  };
438
606
  }
607
+ var hostExitTargets = /* @__PURE__ */ new Map();
608
+ var hostExitHandlerInstalled = false;
609
+ function terminateOwnedTreesForHostExit() {
610
+ for (const target of hostExitTargets.values()) {
611
+ try {
612
+ if (target.platform === "win32") {
613
+ target.spawnSyncFn("taskkill", ["/PID", String(target.pid), "/T", "/F"], {
614
+ stdio: "ignore",
615
+ windowsHide: true
616
+ });
617
+ } else {
618
+ target.kill(-target.pid, "SIGKILL");
619
+ }
620
+ } catch {
621
+ }
622
+ }
623
+ hostExitTargets.clear();
624
+ }
625
+ function ensureHostExitHandler() {
626
+ if (hostExitHandlerInstalled) return;
627
+ hostExitHandlerInstalled = true;
628
+ process.prependListener("exit", terminateOwnedTreesForHostExit);
629
+ }
630
+ var defaultJobObject = {
631
+ async assign(pid) {
632
+ const { assignOwnedProcessToJob: assignOwnedProcessToJob2 } = await Promise.resolve().then(() => (init_win32_job_object(), win32_job_object_exports));
633
+ await assignOwnedProcessToJob2(pid);
634
+ }
635
+ };
636
+ async function adoptOwnedProcessTree(options) {
637
+ const pid = positivePid(options.child, options.label);
638
+ if (pid === void 0) return;
639
+ const platform = options.platform ?? process.platform;
640
+ ensureHostExitHandler();
641
+ hostExitTargets.set(options.child, {
642
+ pid,
643
+ label: options.label,
644
+ platform,
645
+ kill: options.killFn ?? defaultKill,
646
+ spawnSyncFn: options.spawnSyncFn ?? spawnSync
647
+ });
648
+ const forget = () => {
649
+ hostExitTargets.delete(options.child);
650
+ };
651
+ options.child.once("close", forget);
652
+ options.child.once("exit", forget);
653
+ if (platform !== "win32") return;
654
+ await (options.jobObject ?? defaultJobObject).assign(pid);
655
+ }
439
656
  function positivePid(child, label) {
440
657
  const pid = child.pid;
441
658
  if (pid === void 0) return void 0;
@@ -647,21 +864,8 @@ async function disposeOwnedProcessTree(options) {
647
864
  var STDERR_RING_CAPACITY = 20;
648
865
  var DIALOG_UI_METHODS = /* @__PURE__ */ new Set(["select", "confirm", "input", "editor"]);
649
866
  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
867
  constructor(options) {
868
+ this.options = options;
665
869
  const spawnFn = options.spawnFn ?? spawn;
666
870
  this.child = spawnFn(options.command, options.args, withOwnedProcessTree({
667
871
  cwd: options.cwd,
@@ -671,6 +875,9 @@ var PiRpcClient = class {
671
875
  this.closedPromise = new Promise((resolve) => {
672
876
  this.resolveClosed = resolve;
673
877
  });
878
+ this.adopted = this.adoptOwnedTree(options);
879
+ this.adopted.catch(() => {
880
+ });
674
881
  this.child.stdout.setEncoding("utf8");
675
882
  this.child.stdout.on("data", (chunk) => this.onData(chunk));
676
883
  this.child.stderr.setEncoding("utf8");
@@ -682,10 +889,37 @@ var PiRpcClient = class {
682
889
  this.onClosed(err);
683
890
  });
684
891
  }
685
- /** Send a command, resolved with its correlated `response` message. */
686
- send(command) {
892
+ options;
893
+ child;
894
+ buffer = "";
895
+ nextId = 1;
896
+ pending = /* @__PURE__ */ new Map();
897
+ eventQueue = new AsyncQueue();
898
+ closed = false;
899
+ exitError;
900
+ closedPromise;
901
+ resolveClosed;
902
+ disposalAttempt;
903
+ /** Resolves once this tree is backstopped (see `adoptOwnedProcessTree`); rejects with the adoption failure, having already terminated the tree. */
904
+ adopted;
905
+ /** 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. */
906
+ adoptionFailure;
907
+ /** 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`. */
908
+ stderrRing = [];
909
+ /** Count of pi RPC message types `PiSession` (pi-adapter.ts) has told us have no `AgentEvent` mapping and aren't routine bookkeeping — see `recordUnmappedFrame`. */
910
+ unmappedFrameCounts = /* @__PURE__ */ new Map();
911
+ /**
912
+ * Send a command, resolved with its correlated `response` message.
913
+ *
914
+ * Adoption is awaited FIRST — this is `PiAdapter.start()`'s first awaited
915
+ * operation, so an unbackstopped tree never receives the initial prompt and
916
+ * never yields a session. The wait costs one settled-promise turn once the
917
+ * tree is adopted; every later command sees it already resolved.
918
+ */
919
+ async send(command) {
920
+ await this.adopted;
687
921
  if (this.closed) {
688
- return Promise.reject(this.exitError ?? new Error("pi process is closed"));
922
+ throw this.exitError ?? new Error("pi process is closed");
689
923
  }
690
924
  const id = command.id ?? `req-${this.nextId++}`;
691
925
  const full = { ...command, id };
@@ -758,6 +992,32 @@ var PiRpcClient = class {
758
992
  label: "pi"
759
993
  };
760
994
  }
995
+ /**
996
+ * Backstop this tree, or tear it down. Adoption failure is a start-time
997
+ * precondition, not a degraded mode: the child is terminated through the one
998
+ * disposal authority and the failure is re-thrown, which is what makes the
999
+ * first `send()` — and therefore `PiAdapter.start()` — fail before any
1000
+ * session is published. Both cleanup attempts are best-effort because the
1001
+ * adoption failure, not a terminator's own complaint, is the reason to report.
1002
+ */
1003
+ async adoptOwnedTree(options) {
1004
+ try {
1005
+ await adoptOwnedProcessTree({
1006
+ child: this.child,
1007
+ label: "pi",
1008
+ platform: options.platform,
1009
+ jobObject: options.jobObject
1010
+ });
1011
+ } catch (cause) {
1012
+ const failure = cause instanceof Error ? cause : new Error(String(cause));
1013
+ this.adoptionFailure = failure;
1014
+ await requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
1015
+ });
1016
+ await disposeOwnedProcessTree(this.processTreeOptions()).catch(() => {
1017
+ });
1018
+ throw failure;
1019
+ }
1020
+ }
761
1021
  onData(chunk) {
762
1022
  this.buffer += chunk;
763
1023
  let newlineIndex = this.buffer.indexOf("\n");
@@ -776,6 +1036,7 @@ var PiRpcClient = class {
776
1036
  } catch {
777
1037
  return;
778
1038
  }
1039
+ this.options.onFrame?.(msg);
779
1040
  if (msg.type === "response" && typeof msg.id === "string" && this.pending.has(msg.id)) {
780
1041
  const waiter = this.pending.get(msg.id);
781
1042
  this.pending.delete(msg.id);
@@ -783,11 +1044,20 @@ var PiRpcClient = class {
783
1044
  return;
784
1045
  }
785
1046
  if (msg.type === "extension_ui_request" && typeof msg.id === "string" && typeof msg.method === "string") {
786
- this.respondToExtensionUiRequest(msg.id, msg.method);
1047
+ if (this.options.extensionUi?.mode === "hold") this.options.extensionUi.onRequest(msg);
1048
+ else this.respondToExtensionUiRequest(msg.id, msg.method);
787
1049
  return;
788
1050
  }
789
1051
  this.eventQueue.push(msg);
790
1052
  }
1053
+ /** Write an explicit host-owned response; completion is a transport receipt only. */
1054
+ async respondExtensionUi(response) {
1055
+ if (this.options.extensionUi?.mode !== "hold") throw new Error("Pi interactions are not host-owned");
1056
+ await this.adopted;
1057
+ if (this.closed) throw new Error("Pi process is closed");
1058
+ await new Promise((resolve, reject) => this.child.stdin.write(`${JSON.stringify({ type: "extension_ui_response", ...response })}
1059
+ `, (error) => error ? reject(error) : resolve()));
1060
+ }
791
1061
  /**
792
1062
  * Answer pi's extension-UI blocking protocol headlessly (rpc.md's
793
1063
  * "Extension UI Protocol"). Fail-closed policy, stated explicitly because
@@ -841,9 +1111,10 @@ var PiRpcClient = class {
841
1111
  onClosed(err) {
842
1112
  if (this.closed) return;
843
1113
  this.closed = true;
844
- this.exitError = err;
1114
+ const terminal = this.adoptionFailure ?? err;
1115
+ this.exitError = terminal;
845
1116
  this.resolveClosed();
846
- for (const [, waiter] of this.pending) waiter.reject(err);
1117
+ for (const [, waiter] of this.pending) waiter.reject(terminal);
847
1118
  this.pending.clear();
848
1119
  this.eventQueue.end();
849
1120
  }
@@ -877,6 +1148,8 @@ var PROVIDER_CREDENTIAL_ENV_DENY_NAMES = [
877
1148
  "TOGETHER_API_KEY",
878
1149
  "BASETEN_API_KEY",
879
1150
  "KIMI_API_KEY",
1151
+ "HF_TOKEN",
1152
+ "MOONSHOT_API_KEY",
880
1153
  "MINIMAX_API_KEY",
881
1154
  "MINIMAX_CN_API_KEY",
882
1155
  "QWEN_TOKEN_PLAN_API_KEY",
@@ -1001,12 +1274,13 @@ var PiAdapter = class {
1001
1274
  async detect() {
1002
1275
  try {
1003
1276
  const bin = this.resolveBin();
1004
- const { stdout, stderr } = await execFileAsync(bin.command, ["--version"], { timeout: DETECT_TIMEOUT_MS });
1005
- const version = stdout.trim() || stderr.trim();
1277
+ const probe = await probeRuntimeVersion(bin.command, DETECT_TIMEOUT_MS);
1278
+ if (probe.kind !== "available") return probe;
1279
+ const version = probe.stdout.trim() || probe.stderr.trim();
1006
1280
  const authPresent = PROVIDER_CREDENTIAL_ENV_NAMES.some((name) => process.env[name] !== void 0);
1007
- return { present: true, version, authPresent };
1008
- } catch {
1009
- return { present: false };
1281
+ return { kind: "available", version, authPresent };
1282
+ } catch (error) {
1283
+ return classifyDetectError(error);
1010
1284
  }
1011
1285
  }
1012
1286
  async prepare(input) {
@@ -1427,7 +1701,8 @@ var DIST_SCRIPT_BY_KIND = Object.freeze({
1427
1701
  "agent-message-mcp": "byok-agent-message-mcp.js",
1428
1702
  "agent-memory-mcp": "byok-agent-memory-mcp.js",
1429
1703
  "approval-mcp": "byok-approval-mcp.js",
1430
- "agent-team-mcp": "byok-agent-team-mcp.js"
1704
+ "agent-team-mcp": "byok-agent-team-mcp.js",
1705
+ "mcp-env": "byok-mcp-env.js"
1431
1706
  });
1432
1707
  function assertExecutable(executable) {
1433
1708
  if (!path5.isAbsolute(executable) || /[\u0000\r\n]/u.test(executable)) {
@@ -1445,8 +1720,11 @@ function resolveSdkReservedHelperBin(kind, host) {
1445
1720
  source: "self-executable"
1446
1721
  });
1447
1722
  }
1723
+ const entryDir = path5.dirname(fileURLToPath(import.meta.url));
1724
+ const entryKind = path5.basename(entryDir);
1725
+ const distDir = entryKind === "adapters" || entryKind === "bin" ? path5.dirname(entryDir) : entryDir;
1448
1726
  const script = path5.join(
1449
- path5.dirname(fileURLToPath(import.meta.url)),
1727
+ distDir,
1450
1728
  "bin",
1451
1729
  DIST_SCRIPT_BY_KIND[kind]
1452
1730
  );
@@ -1753,6 +2031,10 @@ var ClaudeProcessClient = class {
1753
2031
  closedPromise;
1754
2032
  resolveClosed;
1755
2033
  disposalAttempt;
2034
+ /** Resolves once this tree is backstopped (see `adoptOwnedProcessTree`); rejects with the adoption failure, having already terminated the tree. */
2035
+ adopted;
2036
+ /** 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. */
2037
+ adoptionFailure;
1756
2038
  stderrRing = [];
1757
2039
  unmappedFrameCounts = /* @__PURE__ */ new Map();
1758
2040
  sessionId;
@@ -1767,6 +2049,9 @@ var ClaudeProcessClient = class {
1767
2049
  this.closedPromise = new Promise((resolve) => {
1768
2050
  this.resolveClosed = resolve;
1769
2051
  });
2052
+ this.adopted = this.adoptOwnedTree(options);
2053
+ this.adopted.catch(() => {
2054
+ });
1770
2055
  this.child.stdout.setEncoding("utf8");
1771
2056
  this.child.stdout.on("data", (chunk) => this.onData(chunk));
1772
2057
  this.child.stderr.setEncoding("utf8");
@@ -1810,9 +2095,10 @@ var ClaudeProcessClient = class {
1810
2095
  * same id; if the process already closed before init ever arrived,
1811
2096
  * every call rejects with that same exit error.
1812
2097
  */
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"));
2098
+ async waitForInit() {
2099
+ await this.adopted;
2100
+ if (this.sessionId !== void 0) return this.sessionId;
2101
+ if (this.closed) throw this.exitError ?? new Error("claude process is closed");
1816
2102
  return new Promise((resolve, reject) => {
1817
2103
  this.initWaiter = { resolve, reject };
1818
2104
  });
@@ -1876,6 +2162,32 @@ var ClaudeProcessClient = class {
1876
2162
  label: "claude"
1877
2163
  };
1878
2164
  }
2165
+ /**
2166
+ * Backstop this tree, or tear it down. Adoption failure is a start-time
2167
+ * precondition, not a degraded mode: the child is terminated through the one
2168
+ * disposal authority and the failure is re-thrown, which is what makes
2169
+ * `waitForInit()` — and therefore `ClaudeAdapter.start()` — fail before any
2170
+ * session is published. Both cleanup attempts are best-effort because the
2171
+ * adoption failure, not a terminator's own complaint, is the reason to report.
2172
+ */
2173
+ async adoptOwnedTree(options) {
2174
+ try {
2175
+ await adoptOwnedProcessTree({
2176
+ child: this.child,
2177
+ label: "claude",
2178
+ platform: options.platform,
2179
+ jobObject: options.jobObject
2180
+ });
2181
+ } catch (cause) {
2182
+ const failure = cause instanceof Error ? cause : new Error(String(cause));
2183
+ this.adoptionFailure = failure;
2184
+ await requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
2185
+ });
2186
+ await disposeOwnedProcessTree(this.processTreeOptions()).catch(() => {
2187
+ });
2188
+ throw failure;
2189
+ }
2190
+ }
1879
2191
  onData(chunk) {
1880
2192
  this.buffer += chunk;
1881
2193
  let newlineIndex = this.buffer.indexOf("\n");
@@ -1924,9 +2236,10 @@ var ClaudeProcessClient = class {
1924
2236
  onClosed(err) {
1925
2237
  if (this.closed) return;
1926
2238
  this.closed = true;
1927
- this.exitError = err;
2239
+ const terminal = this.adoptionFailure ?? err;
2240
+ this.exitError = terminal;
1928
2241
  this.resolveClosed();
1929
- this.initWaiter?.reject(err);
2242
+ this.initWaiter?.reject(terminal);
1930
2243
  this.initWaiter = void 0;
1931
2244
  this.eventQueue.end();
1932
2245
  }
@@ -2025,14 +2338,15 @@ var ClaudeAdapter = class {
2025
2338
  environmentRequirements: { credentialNames: [] }
2026
2339
  });
2027
2340
  async detect() {
2028
- const bin = this.resolveBin();
2029
2341
  try {
2030
- const { stdout } = await execFileAsync2(bin.command, ["--version"], { timeout: DETECT_TIMEOUT_MS2 });
2031
- const version = stdout.trim();
2342
+ const bin = this.resolveBin();
2343
+ const probe = await probeRuntimeVersion(bin.command, DETECT_TIMEOUT_MS2);
2344
+ if (probe.kind !== "available") return probe;
2345
+ const version = probe.stdout.trim();
2032
2346
  const authPresent = await this.probeAuthPresent(bin.command);
2033
- return { present: true, version, authPresent };
2034
- } catch {
2035
- return { present: false };
2347
+ return { kind: "available", version, authPresent };
2348
+ } catch (error) {
2349
+ return classifyDetectError(error);
2036
2350
  }
2037
2351
  }
2038
2352
  async prepare(input) {
@@ -2651,10 +2965,18 @@ function unmappedFrameKey(evt) {
2651
2965
  return evt.type;
2652
2966
  }
2653
2967
  var STDERR_RING_CAPACITY3 = 20;
2968
+ var CODEX_MAX_FRAME_BYTES = 1024 * 1024;
2969
+ var CODEX_MAX_DEFERRED_BYTES = 4 * 1024 * 1024;
2970
+ var CODEX_MAX_STDERR_BYTES = 64 * 1024;
2654
2971
  var CodexProcessRunner = class {
2655
2972
  child;
2656
2973
  onEvent;
2657
- buffer = "";
2974
+ frameChunks = [];
2975
+ frameBytes = 0;
2976
+ deferredBytes = 0;
2977
+ stderrBytes = 0;
2978
+ transportFailure;
2979
+ onFailure;
2658
2980
  stderrRing = [];
2659
2981
  closed = false;
2660
2982
  exitCode = null;
@@ -2662,18 +2984,34 @@ var CodexProcessRunner = class {
2662
2984
  closedPromise;
2663
2985
  resolveClosed;
2664
2986
  disposalAttempt;
2987
+ /** Resolves once this tree is backstopped (see `adoptOwnedProcessTree`); rejects with the adoption failure, having already terminated the tree. */
2988
+ adopted;
2989
+ /** Set before the fail-closed termination starts; `buildExitError` reports it instead of the exit status of the kill we ourselves requested. */
2990
+ adoptionFailure;
2991
+ adoption = "pending";
2992
+ /**
2993
+ * Lines parsed before adoption settled. Unlike pi and claude, this runner has
2994
+ * no first awaited operation of its own to gate on — its caller reads the
2995
+ * FIRST event as the authoritative thread id. Holding events until the tree
2996
+ * is backstopped is what keeps that caller from publishing a session for a
2997
+ * tree the job object never took.
2998
+ */
2999
+ deferredEvents = [];
2665
3000
  constructor(options) {
2666
3001
  this.onEvent = options.onEvent;
3002
+ this.onFailure = options.onFailure;
2667
3003
  const spawnFn = options.spawnFn ?? spawn;
2668
3004
  this.child = spawnFn(options.command, options.args, withOwnedProcessTree({
2669
3005
  cwd: options.cwd,
2670
3006
  env: options.env,
2671
- stdio: ["ignore", "pipe", "pipe"]
3007
+ stdio: [options.instruction === void 0 ? "ignore" : "pipe", "pipe", "pipe"]
2672
3008
  }));
2673
3009
  this.closedPromise = new Promise((resolve) => {
2674
3010
  this.resolveClosed = resolve;
2675
3011
  });
2676
- this.child.stdout.setEncoding("utf8");
3012
+ this.adopted = this.adoptOwnedTree(options);
3013
+ this.adopted.catch(() => {
3014
+ });
2677
3015
  this.child.stdout.on("data", (chunk) => this.onData(chunk));
2678
3016
  this.child.stderr.setEncoding("utf8");
2679
3017
  this.child.stderr.on("data", (chunk) => this.onStderr(chunk));
@@ -2682,6 +3020,12 @@ var CodexProcessRunner = class {
2682
3020
  this.exitSignal = signal;
2683
3021
  this.finishClosing();
2684
3022
  });
3023
+ if (options.instruction !== void 0) {
3024
+ this.child.stdin.on("error", () => {
3025
+ this.failTransport("codex prompt stdin failed before delivery");
3026
+ });
3027
+ this.child.stdin.end(options.instruction, "utf8");
3028
+ }
2685
3029
  this.child.on("error", () => {
2686
3030
  this.finishClosing();
2687
3031
  });
@@ -2738,26 +3082,111 @@ var CodexProcessRunner = class {
2738
3082
  label: "codex"
2739
3083
  };
2740
3084
  }
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. */
3085
+ /**
3086
+ * Backstop this tree, or tear it down. Adoption failure is a start-time
3087
+ * precondition, not a degraded mode: the child is terminated through the one
3088
+ * disposal authority, every parsed line is dropped instead of delivered, and
3089
+ * the resulting close makes the caller's own `waitClosed()` race reject with
3090
+ * the adoption failure (`buildExitError`) before a thread id is published.
3091
+ * Both cleanup attempts are best-effort because the adoption failure, not a
3092
+ * terminator's own complaint, is the reason to report.
3093
+ */
3094
+ async adoptOwnedTree(options) {
3095
+ try {
3096
+ await adoptOwnedProcessTree({
3097
+ child: this.child,
3098
+ label: "codex",
3099
+ platform: options.platform,
3100
+ jobObject: options.jobObject
3101
+ });
3102
+ } catch (cause) {
3103
+ const failure = cause instanceof Error ? cause : new Error(String(cause));
3104
+ this.adoptionFailure = failure;
3105
+ this.adoption = "failed";
3106
+ this.deferredEvents.length = 0;
3107
+ await requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
3108
+ });
3109
+ await disposeOwnedProcessTree(this.processTreeOptions()).catch(() => {
3110
+ });
3111
+ throw failure;
3112
+ }
3113
+ this.adoption = "adopted";
3114
+ this.deferredBytes = 0;
3115
+ for (const evt of this.deferredEvents.splice(0)) this.onEvent(evt);
3116
+ }
3117
+ /** Arrival-order delivery, held back until the tree is backstopped (see `deferredEvents`). */
3118
+ deliver(evt, bytes) {
3119
+ if (this.transportFailure) return;
3120
+ if (this.adoption === "failed") return;
3121
+ if (this.adoption === "pending") {
3122
+ if (this.deferredBytes + bytes > CODEX_MAX_DEFERRED_BYTES) {
3123
+ this.failTransport("codex pre-adoption event buffer exceeded its byte budget");
3124
+ return;
3125
+ }
3126
+ this.deferredBytes += bytes;
3127
+ this.deferredEvents.push(evt);
3128
+ return;
3129
+ }
3130
+ this.onEvent(evt);
3131
+ }
3132
+ /**
3133
+ * Builds a descriptive error folding in the exit code/signal and the stderr
3134
+ * tail — mirrors `PiRpcClient.buildExitError`'s reasoning: a post-mortem on a
3135
+ * failed start/resume should never need separately re-running codex by hand
3136
+ * with a raw JSONL logger to learn why.
3137
+ *
3138
+ * A tree this runner could not backstop is the one exception: that process
3139
+ * exited because THIS runner killed it, so `exit code=null, signal=SIGKILL`
3140
+ * plus an empty stderr tail would bury the only reason anyone can act on.
3141
+ */
2742
3142
  buildExitError(context) {
3143
+ if (this.transportFailure) return this.transportFailure;
3144
+ if (this.adoptionFailure !== void 0) return this.adoptionFailure;
2743
3145
  const parts = [`${context} (exit code=${this.exitCode}, signal=${this.exitSignal})`];
2744
3146
  if (this.stderrRing.length > 0) {
2745
3147
  parts.push(`stderr: ${this.stderrRing.join(" | ")}`);
2746
3148
  }
2747
3149
  return new Error(parts.join("; "));
2748
3150
  }
2749
- onData(chunk) {
2750
- this.buffer += chunk;
2751
- let newlineIndex = this.buffer.indexOf("\n");
2752
- while (newlineIndex !== -1) {
2753
- let line = this.buffer.slice(0, newlineIndex);
2754
- this.buffer = this.buffer.slice(newlineIndex + 1);
2755
- if (line.endsWith("\r")) line = line.slice(0, -1);
2756
- if (line.length > 0) this.parseLine(line);
2757
- newlineIndex = this.buffer.indexOf("\n");
2758
- }
3151
+ failTransport(reason) {
3152
+ if (this.transportFailure) return;
3153
+ this.transportFailure = new RuntimeExecutionFailure({
3154
+ phase: "run",
3155
+ category: "infrastructure",
3156
+ retry: "non-retryable",
3157
+ reason
3158
+ });
3159
+ this.frameChunks.length = 0;
3160
+ this.frameBytes = 0;
3161
+ this.deferredEvents.length = 0;
3162
+ this.deferredBytes = 0;
3163
+ this.onFailure?.(this.transportFailure);
3164
+ this.kill();
2759
3165
  }
2760
- parseLine(line) {
3166
+ onData(chunk) {
3167
+ if (this.transportFailure) return;
3168
+ let offset = 0;
3169
+ while (offset < chunk.length) {
3170
+ const newline = chunk.indexOf(10, offset);
3171
+ const end = newline === -1 ? chunk.length : newline;
3172
+ const bytes = end - offset;
3173
+ if (this.frameBytes + bytes > CODEX_MAX_FRAME_BYTES) {
3174
+ this.failTransport("codex raw JSONL frame exceeded its byte budget");
3175
+ return;
3176
+ }
3177
+ if (bytes > 0) this.frameChunks.push(Buffer.from(chunk.subarray(offset, end)));
3178
+ this.frameBytes += bytes;
3179
+ if (newline === -1) return;
3180
+ const frame = Buffer.concat(this.frameChunks, this.frameBytes);
3181
+ this.frameChunks.length = 0;
3182
+ this.frameBytes = 0;
3183
+ const line = frame.toString("utf8").replace(/\r$/, "");
3184
+ if (line.length > 0) this.parseLine(line, frame.length);
3185
+ if (this.transportFailure) return;
3186
+ offset = newline + 1;
3187
+ }
3188
+ }
3189
+ parseLine(line, bytes) {
2761
3190
  let parsed;
2762
3191
  try {
2763
3192
  parsed = JSON.parse(line);
@@ -2767,14 +3196,18 @@ var CodexProcessRunner = class {
2767
3196
  if (!parsed || typeof parsed !== "object" || typeof parsed.type !== "string") {
2768
3197
  return;
2769
3198
  }
2770
- this.onEvent(parsed);
3199
+ this.deliver(parsed, bytes);
2771
3200
  }
2772
3201
  onStderr(chunk) {
2773
- for (const rawLine of chunk.split("\n")) {
3202
+ const bounded = Buffer.from(chunk).subarray(-CODEX_MAX_STDERR_BYTES).toString("utf8");
3203
+ for (const rawLine of bounded.split("\n")) {
2774
3204
  const line = rawLine.trim();
2775
3205
  if (line.length === 0) continue;
2776
3206
  this.stderrRing.push(line);
2777
- if (this.stderrRing.length > STDERR_RING_CAPACITY3) this.stderrRing.shift();
3207
+ this.stderrBytes += Buffer.byteLength(line);
3208
+ while (this.stderrRing.length > STDERR_RING_CAPACITY3 || this.stderrBytes > CODEX_MAX_STDERR_BYTES) {
3209
+ this.stderrBytes -= Buffer.byteLength(this.stderrRing.shift());
3210
+ }
2778
3211
  }
2779
3212
  }
2780
3213
  };
@@ -2807,14 +3240,15 @@ var CodexAdapter = class {
2807
3240
  environmentRequirements: { credentialNames: [] }
2808
3241
  });
2809
3242
  async detect() {
2810
- const bin = this.resolveBin();
2811
3243
  try {
2812
- const versionResult = await execFileAsync3(bin.command, ["--version"], { timeout: DETECT_TIMEOUT_MS3 });
2813
- const version = versionResult.stdout.trim() || versionResult.stderr.trim();
3244
+ const bin = this.resolveBin();
3245
+ const probe = await probeRuntimeVersion(bin.command, DETECT_TIMEOUT_MS3);
3246
+ if (probe.kind !== "available") return probe;
3247
+ const version = probe.stdout.trim() || probe.stderr.trim();
2814
3248
  const authPresent = await this.probeAuthPresent(bin);
2815
- return { present: true, version, authPresent };
2816
- } catch {
2817
- return { present: false };
3249
+ return { kind: "available", version, authPresent };
3250
+ } catch (error) {
3251
+ return classifyDetectError(error);
2818
3252
  }
2819
3253
  }
2820
3254
  /**
@@ -2957,7 +3391,7 @@ ${withStreams.stderr ?? ""}`);
2957
3391
  reason: "prepared codex operation received a manifest with different runtime selection"
2958
3392
  });
2959
3393
  }
2960
- const mcpConfigArgs = codexMcpConfigArgs(startInput.mcpServers, preparedMcpGrants);
3394
+ const mcpConfigArgs = codexMcpConfigArgs(startInput.mcpServers, runtimeEnv, this.options.sdkHelperHost, preparedMcpGrants);
2961
3395
  const { sessionRef, runner } = await runCodexTurn({
2962
3396
  command,
2963
3397
  resumeRef: startInput.manifest.sessionRef,
@@ -2973,6 +3407,7 @@ ${withStreams.stderr ?? ""}`);
2973
3407
  expectedSessionRef: startInput.manifest.sessionRef,
2974
3408
  preparedGit: startInput.manifest.workspace.workspaceId !== void 0,
2975
3409
  failurePhase: "start",
3410
+ signal: startInput.signal,
2976
3411
  terminal
2977
3412
  });
2978
3413
  return new CodexSession({
@@ -2980,6 +3415,7 @@ ${withStreams.stderr ?? ""}`);
2980
3415
  command,
2981
3416
  workspaceDir,
2982
3417
  env: startInput.env,
3418
+ mcpEnvironment: Object.fromEntries(Object.entries(runtimeEnv).filter(([key]) => key.startsWith("BYOK_MCP_PAYLOAD_"))),
2983
3419
  spawnFn: this.options.spawnFn,
2984
3420
  queue,
2985
3421
  recordUnmapped,
@@ -3032,16 +3468,18 @@ function codexMcpToolApprovalArgs(name, tools) {
3032
3468
  }
3033
3469
  return args;
3034
3470
  }
3035
- function codexMcpConfigArgs(servers, grants = []) {
3471
+ function codexMcpConfigArgs(servers, env, helperHost, grants = []) {
3036
3472
  if (servers === void 0 || Object.keys(servers).length === 0) return [];
3037
3473
  const grantedTools = new Map(grants.map((grant) => [grant.server, grant.tools]));
3038
3474
  const args = ["--ignore-user-config"];
3039
3475
  for (const [name, server] of Object.entries(servers).sort(([left], [right]) => left.localeCompare(right))) {
3040
- args.push("-c", `mcp_servers.${name}.command=${JSON.stringify(server.command)}`);
3041
- if (server.args !== void 0) args.push("-c", `mcp_servers.${name}.args=${JSON.stringify([...server.args])}`);
3042
- for (const [key, value] of Object.entries(server.env ?? {}).sort(([left], [right]) => left.localeCompare(right))) {
3043
- args.push("-c", `mcp_servers.${name}.env.${key}=${JSON.stringify(value)}`);
3044
- }
3476
+ const key = `BYOK_MCP_PAYLOAD_${randomBytes(16).toString("hex").toUpperCase()}`;
3477
+ env[key] = JSON.stringify(server);
3478
+ const helper = resolveSdkReservedHelperBin("mcp-env", helperHost);
3479
+ args.push("-c", `mcp_servers.${name}.command=${JSON.stringify(helper.command)}`);
3480
+ args.push("-c", `mcp_servers.${name}.args=${JSON.stringify([...helper.args])}`);
3481
+ args.push("-c", `mcp_servers.${name}.env.BYOK_MCP_ENV_KEY=${JSON.stringify(key)}`);
3482
+ args.push("-c", `mcp_servers.${name}.env_vars=${JSON.stringify([key])}`);
3045
3483
  const granted = grantedTools.get(name);
3046
3484
  if (granted !== void 0) args.push(...codexMcpToolApprovalArgs(name, granted));
3047
3485
  }
@@ -3069,7 +3507,7 @@ function buildArgv(resumeRef, policyArgs, instruction, modelId, preparedGit = fa
3069
3507
  ...modelId ? ["--model", modelId] : [],
3070
3508
  ...preparedGit ? [] : ["--skip-git-repo-check"],
3071
3509
  ...policyArgs,
3072
- instruction
3510
+ "-"
3073
3511
  ];
3074
3512
  }
3075
3513
  async function runCodexTurn(params) {
@@ -3093,9 +3531,18 @@ async function runCodexTurn(params) {
3093
3531
  runner = new CodexProcessRunner({
3094
3532
  command: params.command,
3095
3533
  args: argv,
3534
+ instruction: params.instruction,
3096
3535
  cwd: params.cwd,
3097
3536
  env: params.env,
3098
3537
  spawnFn: params.spawnFn,
3538
+ onFailure: (failure) => {
3539
+ if (!firstLineSettled) {
3540
+ firstLineSettled = true;
3541
+ rejectFirstLine(failure);
3542
+ }
3543
+ params.terminal.failure = failure;
3544
+ params.queue.end();
3545
+ },
3099
3546
  onEvent: (evt) => {
3100
3547
  if (!firstLineSettled) {
3101
3548
  firstLineSettled = true;
@@ -3162,6 +3609,16 @@ async function runCodexTurn(params) {
3162
3609
  params.queue.end();
3163
3610
  });
3164
3611
  let sessionRef;
3612
+ const abortStartup = () => rejectFirstLine(new RuntimeExecutionFailure({
3613
+ phase: params.failurePhase,
3614
+ category: "infrastructure",
3615
+ retry: "non-retryable",
3616
+ reason: "codex startup cancelled or exceeded its deadline"
3617
+ }));
3618
+ const startupTimer = setTimeout(abortStartup, 3e4);
3619
+ startupTimer.unref?.();
3620
+ params.signal?.addEventListener("abort", abortStartup, { once: true });
3621
+ if (params.signal?.aborted) abortStartup();
3165
3622
  try {
3166
3623
  sessionRef = await new Promise((resolve, reject) => {
3167
3624
  let settled = false;
@@ -3193,20 +3650,31 @@ async function runCodexTurn(params) {
3193
3650
  });
3194
3651
  });
3195
3652
  } catch (err) {
3196
- runner.kill();
3653
+ await disposeFailedStartup(runner, err);
3197
3654
  throw err;
3655
+ } finally {
3656
+ clearTimeout(startupTimer);
3657
+ params.signal?.removeEventListener("abort", abortStartup);
3198
3658
  }
3199
3659
  if (params.expectedSessionRef !== void 0 && sessionRef !== params.expectedSessionRef) {
3200
- runner.kill();
3201
- throw new RuntimeExecutionFailure({
3660
+ const failure = new RuntimeExecutionFailure({
3202
3661
  phase: params.failurePhase,
3203
3662
  category: "authority",
3204
3663
  retry: "non-retryable",
3205
3664
  reason: `codex exec resume echoed a different thread id than requested (requested ${params.expectedSessionRef}, got ${sessionRef})`
3206
3665
  });
3666
+ await disposeFailedStartup(runner, failure);
3667
+ throw failure;
3207
3668
  }
3208
3669
  return { sessionRef, runner };
3209
3670
  }
3671
+ async function disposeFailedStartup(runner, cause) {
3672
+ try {
3673
+ await runner.dispose();
3674
+ } catch (disposalFailure) {
3675
+ throw new RuntimeStartupDisposalFailure(() => runner.dispose(), { cause: new AggregateError([cause, disposalFailure]) });
3676
+ }
3677
+ }
3210
3678
  var CodexSession = class {
3211
3679
  /**
3212
3680
  * NOT `readonly` (cross-model review finding): `followUp()` below
@@ -3242,6 +3710,7 @@ var CodexSession = class {
3242
3710
  * recomputed from anything a later turn supplies, so a follow-up can never
3243
3711
  * widen this session's MCP authority.
3244
3712
  */
3713
+ mcpEnvironment;
3245
3714
  mcpConfigArgs;
3246
3715
  terminal;
3247
3716
  currentRunner;
@@ -3261,6 +3730,7 @@ var CodexSession = class {
3261
3730
  this.modelId = options.modelId;
3262
3731
  this.terminal = options.terminal;
3263
3732
  this.mcpConfigArgs = options.mcpConfigArgs;
3733
+ this.mcpEnvironment = Object.freeze({ ...options.mcpEnvironment });
3264
3734
  this.currentRunner = options.initialRunner;
3265
3735
  this.ownedRunners.add(options.initialRunner);
3266
3736
  void this.forgetRunnerOnceClosed(options.initialRunner);
@@ -3373,7 +3843,7 @@ var CodexSession = class {
3373
3843
  // frozen MCP config replayed verbatim — see `mcpConfigArgs`.
3374
3844
  policyArgs: [...mapping.args, ...this.mcpConfigArgs],
3375
3845
  cwd: this.workspaceDir,
3376
- env: withoutProviderCredentials(this.env),
3846
+ env: { ...withoutProviderCredentials(this.env), ...this.mcpEnvironment },
3377
3847
  spawnFn: this.spawnFn,
3378
3848
  workspaceDir: this.workspaceDir,
3379
3849
  queue: this.queue,
@@ -3472,6 +3942,6 @@ function subscriptionModel2(selection) {
3472
3942
  return selection.modelId;
3473
3943
  }
3474
3944
 
3475
- export { ClaudeAdapter, CodexAdapter, PI_PACKAGE_NAME, PiAdapter, RuntimeDisposalFailure, RuntimeExecutionFailure };
3945
+ export { ClaudeAdapter, CodexAdapter, PI_PACKAGE_NAME, PiAdapter, RuntimeDisposalFailure, RuntimeExecutionFailure, RuntimeStartupDisposalFailure };
3476
3946
  //# sourceMappingURL=index.js.map
3477
3947
  //# sourceMappingURL=index.js.map