@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.
- package/README.md +95 -0
- package/dist/adapters/claude/process-client.d.ts +24 -0
- package/dist/adapters/codex/codex-adapter.d.ts +2 -0
- package/dist/adapters/codex/process-runner.d.ts +62 -18
- package/dist/adapters/detect-outcome.d.ts +18 -0
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +546 -76
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/pi/events.d.ts +1 -1
- package/dist/adapters/pi/rpc-client.d.ts +47 -1
- package/dist/adapters/pi/subagents-policy-extension.js +1 -1
- package/dist/adapters/pi/subagents-policy-extension.js.map +1 -1
- package/dist/adapters/pi/team-interaction-extension.d.ts +24 -0
- package/dist/adapters/pi/team-interaction-extension.js +81 -0
- package/dist/adapters/pi/team-interaction-extension.js.map +1 -0
- package/dist/adapters/process-tree.d.ts +74 -4
- package/dist/adapters/provider-credential-environment.d.ts +1 -1
- package/dist/adapters/win32-job-object.d.ts +101 -0
- package/dist/bin/byok-agent-memory-mcp.js +29 -0
- package/dist/bin/byok-agent-memory-mcp.js.map +1 -1
- package/dist/bin/byok-agent-message-mcp.js +29 -0
- package/dist/bin/byok-agent-message-mcp.js.map +1 -1
- package/dist/bin/byok-agent-team-mcp.js +29 -0
- package/dist/bin/byok-agent-team-mcp.js.map +1 -1
- package/dist/bin/byok-agent.js +20274 -18057
- package/dist/bin/byok-agent.js.map +1 -1
- package/dist/bin/byok-approval-mcp.js +29 -0
- package/dist/bin/byok-approval-mcp.js.map +1 -1
- package/dist/bin/byok-mcp-env.d.ts +2 -0
- package/dist/bin/byok-mcp-env.js +36 -0
- package/dist/bin/byok-mcp-env.js.map +1 -0
- package/dist/bin/commands/doctor.d.ts +3 -0
- package/dist/bin/commands/team-pi-relay.d.ts +24 -0
- package/dist/bin/commands/team-relay.d.ts +11 -0
- package/dist/bin/mcp-env-launcher.d.ts +5 -0
- package/dist/bin/runtime-probe.d.ts +6 -7
- package/dist/bin/team-codex-relay.d.ts +32 -0
- package/dist/bin/team-notification-relay.d.ts +40 -0
- package/dist/bin/team-pi-session.d.ts +59 -0
- package/dist/daemon/admission-wait.d.ts +2 -0
- package/dist/daemon/agent-egress-policy.d.ts +8 -0
- package/dist/daemon/agent-egress-spool.d.ts +1 -0
- package/dist/daemon/agent-message-outbox.d.ts +15 -3
- package/dist/daemon/artifact-read.d.ts +7 -0
- package/dist/daemon/connection-manager.d.ts +23 -176
- package/dist/daemon/control-protocol.d.ts +2 -0
- package/dist/daemon/create-daemon.d.ts +43 -1
- package/dist/daemon/event-spill.d.ts +90 -0
- package/dist/daemon/journal/journal.d.ts +15 -3
- package/dist/daemon/journal/sqlite-journal.d.ts +7 -2
- package/dist/daemon/long-poll-transport.d.ts +2 -50
- package/dist/daemon/runtime-start.d.ts +3 -0
- package/dist/daemon/store.d.ts +2 -0
- package/dist/daemon/task-runner.d.ts +36 -0
- package/dist/daemon/team-workspace.d.ts +9 -0
- package/dist/daemon/terminal-commit-queue.d.ts +21 -0
- package/dist/daemon/terminal-identity.d.ts +6 -0
- package/dist/diagnostics/device-doctor.d.ts +46 -0
- package/dist/diagnostics/diagnostics.d.ts +3 -80
- package/dist/diagnostics/types.d.ts +82 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2824 -649
- package/dist/index.js.map +1 -1
- package/dist/runtime-detection.d.ts +3 -0
- package/dist/runtime-failure.d.ts +6 -0
- package/dist/sdk-reserved-helper-host.d.ts +1 -1
- package/dist/types.d.ts +20 -10
- package/dist/util/durable-jsonl.d.ts +12 -0
- package/package.json +8 -5
package/dist/adapters/index.js
CHANGED
|
@@ -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
|
-
|
|
686
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
1114
|
+
const terminal = this.adoptionFailure ?? err;
|
|
1115
|
+
this.exitError = terminal;
|
|
845
1116
|
this.resolveClosed();
|
|
846
|
-
for (const [, waiter] of this.pending) waiter.reject(
|
|
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
|
|
1005
|
-
|
|
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 {
|
|
1008
|
-
} catch {
|
|
1009
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
1815
|
-
if (this.
|
|
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.
|
|
2239
|
+
const terminal = this.adoptionFailure ?? err;
|
|
2240
|
+
this.exitError = terminal;
|
|
1928
2241
|
this.resolveClosed();
|
|
1929
|
-
this.initWaiter?.reject(
|
|
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
|
|
2031
|
-
const
|
|
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 {
|
|
2034
|
-
} catch {
|
|
2035
|
-
return
|
|
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
|
-
|
|
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.
|
|
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
|
-
/**
|
|
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
|
-
|
|
2750
|
-
this.
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
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
|
-
|
|
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.
|
|
3199
|
+
this.deliver(parsed, bytes);
|
|
2771
3200
|
}
|
|
2772
3201
|
onStderr(chunk) {
|
|
2773
|
-
|
|
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
|
-
|
|
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
|
|
2813
|
-
const
|
|
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 {
|
|
2816
|
-
} catch {
|
|
2817
|
-
return
|
|
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
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|