@byok-sdk/client 0.3.0 → 0.4.1
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 +14 -1
- package/dist/adapters/claude/claude-adapter.d.ts +4 -20
- package/dist/adapters/claude/events.d.ts +3 -0
- package/dist/adapters/claude/process-client.d.ts +15 -1
- package/dist/adapters/codex/codex-adapter.d.ts +4 -16
- package/dist/adapters/codex/process-runner.d.ts +9 -1
- package/dist/adapters/index.d.ts +3 -1
- package/dist/adapters/index.js +1057 -261
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/pi/pi-adapter.d.ts +3 -16
- package/dist/adapters/pi/resolve-bin.d.ts +1 -1
- package/dist/adapters/pi/rpc-client.d.ts +15 -1
- package/dist/adapters/process-tree.d.ts +60 -0
- package/dist/adapters/taskkill-pid-set.d.ts +34 -0
- package/dist/bin/audit-log.d.ts +12 -0
- package/dist/bin/byok-agent.js +1452 -507
- package/dist/bin/byok-agent.js.map +1 -1
- package/dist/bin/byok-approval-mcp.js.map +1 -1
- package/dist/bin/commands/workspaces.d.ts +11 -0
- package/dist/bin/format.d.ts +13 -0
- package/dist/bin/runtime-probe.d.ts +1 -1
- package/dist/bin/tasks-view.d.ts +13 -0
- package/dist/daemon/approvals.d.ts +2 -2
- package/dist/daemon/connection-manager.d.ts +15 -17
- package/dist/daemon/control-server.d.ts +18 -1
- package/dist/daemon/create-daemon.d.ts +2 -2
- package/dist/daemon/daemon-owner.d.ts +4 -2
- package/dist/daemon/environment.d.ts +9 -9
- package/dist/daemon/git-workspace.d.ts +21 -0
- package/dist/daemon/long-poll-transport.d.ts +6 -0
- package/dist/daemon/observer.d.ts +13 -0
- package/dist/daemon/presence-publisher.d.ts +29 -0
- package/dist/daemon/runtime-capabilities.d.ts +1 -1
- package/dist/daemon/task-runner.d.ts +34 -40
- package/dist/daemon/ws-transport.d.ts +3 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1413 -455
- package/dist/index.js.map +1 -1
- package/dist/runtime-failure.d.ts +64 -0
- package/dist/types.d.ts +100 -73
- package/package.json +14 -14
package/dist/adapters/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execFile, spawn
|
|
1
|
+
import { execFile, spawn } from 'child_process';
|
|
2
2
|
import { promisify } from 'util';
|
|
3
3
|
import { promises, existsSync, readFileSync, realpathSync } from 'fs';
|
|
4
4
|
import path3 from 'path';
|
|
@@ -6,9 +6,80 @@ import { fileURLToPath } from 'url';
|
|
|
6
6
|
import os from 'os';
|
|
7
7
|
import 'readline';
|
|
8
8
|
|
|
9
|
-
// src/
|
|
9
|
+
// src/runtime-failure.ts
|
|
10
|
+
var RUNTIME_EXECUTION_FAILURE_BRAND = /* @__PURE__ */ Symbol.for("@byok-sdk/client/RuntimeExecutionFailure/v1");
|
|
11
|
+
var RUNTIME_DISPOSAL_FAILURE_BRAND = /* @__PURE__ */ Symbol.for("@byok-sdk/client/RuntimeDisposalFailure/v1");
|
|
12
|
+
var RuntimeDisposalFailure = class extends Error {
|
|
13
|
+
stage;
|
|
14
|
+
constructor(input, options) {
|
|
15
|
+
if (!isRuntimeDisposalStage(input.stage) || typeof input.reason !== "string" || input.reason.length === 0) {
|
|
16
|
+
throw new TypeError("invalid RuntimeDisposalFailure input");
|
|
17
|
+
}
|
|
18
|
+
super(input.reason, options);
|
|
19
|
+
this.name = "RuntimeDisposalFailure";
|
|
20
|
+
this.stage = input.stage;
|
|
21
|
+
Object.defineProperty(this, RUNTIME_DISPOSAL_FAILURE_BRAND, { value: true });
|
|
22
|
+
Object.freeze(this);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
function isRuntimeDisposalStage(value) {
|
|
26
|
+
return value === "signal" || value === "quiescence" || value === "cleanup";
|
|
27
|
+
}
|
|
28
|
+
var RuntimeExecutionFailure = class extends Error {
|
|
29
|
+
phase;
|
|
30
|
+
category;
|
|
31
|
+
retry;
|
|
32
|
+
constructor(input, options) {
|
|
33
|
+
if (!isRuntimeFailurePhase(input.phase) || !isRuntimeFailureCategory(input.category) || !isRuntimeRetryDisposition(input.retry) || typeof input.reason !== "string" || input.reason.length === 0) {
|
|
34
|
+
throw new TypeError("invalid RuntimeExecutionFailure input");
|
|
35
|
+
}
|
|
36
|
+
super(input.reason, options);
|
|
37
|
+
this.name = "RuntimeExecutionFailure";
|
|
38
|
+
this.phase = input.phase;
|
|
39
|
+
this.category = input.category;
|
|
40
|
+
this.retry = input.retry;
|
|
41
|
+
Object.defineProperty(this, RUNTIME_EXECUTION_FAILURE_BRAND, { value: true });
|
|
42
|
+
Object.freeze(this);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
function isRuntimeFailurePhase(value) {
|
|
46
|
+
return value === "start" || value === "run";
|
|
47
|
+
}
|
|
48
|
+
function isRuntimeFailureCategory(value) {
|
|
49
|
+
return value === "semantic" || value === "infrastructure" || value === "authority";
|
|
50
|
+
}
|
|
51
|
+
function isRuntimeRetryDisposition(value) {
|
|
52
|
+
return value === "retryable" || value === "non-retryable";
|
|
53
|
+
}
|
|
54
|
+
function isRuntimeExecutionFailure(value) {
|
|
55
|
+
if (typeof value !== "object" || value === null) return false;
|
|
56
|
+
const candidate = value;
|
|
57
|
+
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
|
+
}
|
|
10
59
|
|
|
11
60
|
// src/types.ts
|
|
61
|
+
function frozenStrings(values) {
|
|
62
|
+
return values === void 0 ? void 0 : Object.freeze([...values]);
|
|
63
|
+
}
|
|
64
|
+
function freezeRuntimeAdapterDescriptor(descriptor) {
|
|
65
|
+
const baseNames = frozenStrings(descriptor.environmentRequirements.baseNames);
|
|
66
|
+
const credentialNames = frozenStrings(descriptor.environmentRequirements.credentialNames);
|
|
67
|
+
return Object.freeze({
|
|
68
|
+
id: descriptor.id,
|
|
69
|
+
supportsDispatchSelection: descriptor.supportsDispatchSelection === true,
|
|
70
|
+
capabilities: Object.freeze({
|
|
71
|
+
steer: descriptor.capabilities.steer === true,
|
|
72
|
+
resume: descriptor.capabilities.resume === true,
|
|
73
|
+
approvalInteractive: descriptor.capabilities.approvalInteractive === true,
|
|
74
|
+
...descriptor.capabilities.mcpToolsets === void 0 ? {} : { mcpToolsets: descriptor.capabilities.mcpToolsets === true },
|
|
75
|
+
permissionModes: Object.freeze([...descriptor.capabilities.permissionModes])
|
|
76
|
+
}),
|
|
77
|
+
environmentRequirements: Object.freeze({
|
|
78
|
+
...baseNames === void 0 ? {} : { baseNames },
|
|
79
|
+
...credentialNames === void 0 ? {} : { credentialNames }
|
|
80
|
+
})
|
|
81
|
+
});
|
|
82
|
+
}
|
|
12
83
|
var PolicyUnsupportedError = class extends Error {
|
|
13
84
|
constructor(message) {
|
|
14
85
|
super(message);
|
|
@@ -16,7 +87,7 @@ var PolicyUnsupportedError = class extends Error {
|
|
|
16
87
|
}
|
|
17
88
|
};
|
|
18
89
|
var SteerUnsupportedError = class extends Error {
|
|
19
|
-
/** The `RuntimeAdapter.id` that cannot steer (e.g. `claude`, `codex`). */
|
|
90
|
+
/** The `RuntimeAdapter.descriptor.id` that cannot steer (e.g. `claude`, `codex`). */
|
|
20
91
|
runtimeId;
|
|
21
92
|
constructor(runtimeId, message) {
|
|
22
93
|
super(message);
|
|
@@ -57,12 +128,12 @@ function resolvePiBin() {
|
|
|
57
128
|
}
|
|
58
129
|
} catch (cause) {
|
|
59
130
|
throw new Error(
|
|
60
|
-
`Required ${PI_PACKAGE_NAME} could not be resolved; install @byok-sdk/client dependencies or set BYOK_PI_BIN to a Node 22.
|
|
131
|
+
`Required ${PI_PACKAGE_NAME} could not be resolved; install @byok-sdk/client dependencies or set BYOK_PI_BIN to a Node 22.22+ pi sidecar`,
|
|
61
132
|
{ cause }
|
|
62
133
|
);
|
|
63
134
|
}
|
|
64
135
|
throw new Error(
|
|
65
|
-
`Required ${PI_PACKAGE_NAME} does not expose the pi CLI; reinstall the pinned dependency or set BYOK_PI_BIN to a Node 22.
|
|
136
|
+
`Required ${PI_PACKAGE_NAME} does not expose the pi CLI; reinstall the pinned dependency or set BYOK_PI_BIN to a Node 22.22+ pi sidecar`
|
|
66
137
|
);
|
|
67
138
|
}
|
|
68
139
|
|
|
@@ -268,6 +339,266 @@ var AsyncQueue = class {
|
|
|
268
339
|
}
|
|
269
340
|
};
|
|
270
341
|
|
|
342
|
+
// src/adapters/taskkill-pid-set.ts
|
|
343
|
+
var INTEGER_PATTERN = /\d+/g;
|
|
344
|
+
function isCandidatePid(value) {
|
|
345
|
+
return Number.isSafeInteger(value) && value > 0;
|
|
346
|
+
}
|
|
347
|
+
function walkTaskkillPidSet(text, rootPid, excludedPids = []) {
|
|
348
|
+
const excluded = new Set(excludedPids);
|
|
349
|
+
const accepted = /* @__PURE__ */ new Set();
|
|
350
|
+
if (isCandidatePid(rootPid)) accepted.add(rootPid);
|
|
351
|
+
const lines = text.split(/\r?\n/).map((line) => {
|
|
352
|
+
const pids = [];
|
|
353
|
+
for (const match of line.matchAll(INTEGER_PATTERN)) {
|
|
354
|
+
const pid = Number(match[0]);
|
|
355
|
+
if (isCandidatePid(pid) && !excluded.has(pid)) pids.push(pid);
|
|
356
|
+
}
|
|
357
|
+
return pids;
|
|
358
|
+
});
|
|
359
|
+
let changed = true;
|
|
360
|
+
while (changed) {
|
|
361
|
+
changed = false;
|
|
362
|
+
for (const pids of lines) {
|
|
363
|
+
if (!pids.some((pid) => accepted.has(pid))) continue;
|
|
364
|
+
for (const pid of pids) {
|
|
365
|
+
if (accepted.has(pid)) continue;
|
|
366
|
+
accepted.add(pid);
|
|
367
|
+
changed = true;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return accepted;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/adapters/process-tree.ts
|
|
375
|
+
var DEFAULT_TERM_GRACE_MS = 750;
|
|
376
|
+
var DEFAULT_KILL_GRACE_MS = 2e3;
|
|
377
|
+
var POLL_MS = 20;
|
|
378
|
+
var terminationState = /* @__PURE__ */ new WeakMap();
|
|
379
|
+
function stateFor(child) {
|
|
380
|
+
const existing = terminationState.get(child);
|
|
381
|
+
if (existing) return existing;
|
|
382
|
+
const created = { requested: false, acceptedPids: /* @__PURE__ */ new Set() };
|
|
383
|
+
terminationState.set(child, created);
|
|
384
|
+
return created;
|
|
385
|
+
}
|
|
386
|
+
function defaultKill(pid, signal) {
|
|
387
|
+
process.kill(pid, signal);
|
|
388
|
+
}
|
|
389
|
+
function withOwnedProcessTree(options) {
|
|
390
|
+
return {
|
|
391
|
+
...options,
|
|
392
|
+
...process.platform === "win32" ? { windowsHide: true } : { detached: true }
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function positivePid(child, label) {
|
|
396
|
+
const pid = child.pid;
|
|
397
|
+
if (pid === void 0) return void 0;
|
|
398
|
+
if (!Number.isSafeInteger(pid) || pid <= 0 || pid === process.pid) {
|
|
399
|
+
throw new RuntimeDisposalFailure({
|
|
400
|
+
stage: "signal",
|
|
401
|
+
reason: `${label} runtime process has an unsafe owned pid`
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
return pid;
|
|
405
|
+
}
|
|
406
|
+
function groupExists(pid, label, kill) {
|
|
407
|
+
try {
|
|
408
|
+
kill(-pid, 0);
|
|
409
|
+
return true;
|
|
410
|
+
} catch (cause) {
|
|
411
|
+
const code = cause.code;
|
|
412
|
+
if (code === "ESRCH") return false;
|
|
413
|
+
if (code === "EPERM") return true;
|
|
414
|
+
throw new RuntimeDisposalFailure({
|
|
415
|
+
stage: "quiescence",
|
|
416
|
+
reason: `${label} runtime process-group state could not be verified`
|
|
417
|
+
}, { cause });
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function processExists(pid, label, kill) {
|
|
421
|
+
try {
|
|
422
|
+
kill(pid, 0);
|
|
423
|
+
return true;
|
|
424
|
+
} catch (cause) {
|
|
425
|
+
const code = cause.code;
|
|
426
|
+
if (code === "ESRCH") return false;
|
|
427
|
+
if (code === "EPERM") return true;
|
|
428
|
+
throw new RuntimeDisposalFailure({
|
|
429
|
+
stage: "quiescence",
|
|
430
|
+
reason: `${label} runtime process ${pid} state could not be verified`
|
|
431
|
+
}, { cause });
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
function signalGroup(pid, signal, label, kill) {
|
|
435
|
+
try {
|
|
436
|
+
kill(-pid, signal);
|
|
437
|
+
} catch (cause) {
|
|
438
|
+
const code = cause.code;
|
|
439
|
+
if (code === "ESRCH" || code === "EPERM") return;
|
|
440
|
+
throw new RuntimeDisposalFailure({
|
|
441
|
+
stage: "signal",
|
|
442
|
+
reason: `${label} runtime process group ${pid} could not receive ${signal} (${code ?? "unknown"})`
|
|
443
|
+
}, { cause });
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
async function runTaskkill(pid, options) {
|
|
447
|
+
const spawnFn = options.spawnFn ?? spawn;
|
|
448
|
+
return new Promise((resolve, reject) => {
|
|
449
|
+
const signalFailure = (cause) => {
|
|
450
|
+
reject(new RuntimeDisposalFailure({
|
|
451
|
+
stage: "signal",
|
|
452
|
+
reason: `${options.label} runtime process tree termination could not be requested`
|
|
453
|
+
}, { cause }));
|
|
454
|
+
};
|
|
455
|
+
let taskkill;
|
|
456
|
+
try {
|
|
457
|
+
taskkill = spawnFn("taskkill", ["/PID", String(pid), "/T", "/F"], {
|
|
458
|
+
windowsHide: true,
|
|
459
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
460
|
+
});
|
|
461
|
+
} catch (cause) {
|
|
462
|
+
signalFailure(cause);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
const chunks = [];
|
|
466
|
+
taskkill.stdout?.on("data", (chunk) => chunks.push(chunk));
|
|
467
|
+
taskkill.stderr?.on("data", (chunk) => chunks.push(chunk));
|
|
468
|
+
taskkill.once("error", signalFailure);
|
|
469
|
+
taskkill.once("close", () => resolve(Buffer.concat(chunks).toString("latin1")));
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
function liveAcceptedPids(accepted, label, kill) {
|
|
473
|
+
const live = [];
|
|
474
|
+
for (const pid of accepted) {
|
|
475
|
+
if (processExists(pid, label, kill)) live.push(pid);
|
|
476
|
+
}
|
|
477
|
+
return live;
|
|
478
|
+
}
|
|
479
|
+
async function waitUntil(predicate, timeoutMs) {
|
|
480
|
+
const deadline = Date.now() + timeoutMs;
|
|
481
|
+
while (predicate()) {
|
|
482
|
+
if (Date.now() >= deadline) return false;
|
|
483
|
+
await new Promise((resolve) => {
|
|
484
|
+
setTimeout(resolve, POLL_MS);
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
return true;
|
|
488
|
+
}
|
|
489
|
+
async function waitWithDeadline(promise, timeoutMs) {
|
|
490
|
+
return new Promise((resolve) => {
|
|
491
|
+
let settled = false;
|
|
492
|
+
const timer = setTimeout(() => {
|
|
493
|
+
if (!settled) {
|
|
494
|
+
settled = true;
|
|
495
|
+
resolve(false);
|
|
496
|
+
}
|
|
497
|
+
}, timeoutMs);
|
|
498
|
+
void promise.then(
|
|
499
|
+
() => {
|
|
500
|
+
if (!settled) {
|
|
501
|
+
settled = true;
|
|
502
|
+
clearTimeout(timer);
|
|
503
|
+
resolve(true);
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
() => {
|
|
507
|
+
if (!settled) {
|
|
508
|
+
settled = true;
|
|
509
|
+
clearTimeout(timer);
|
|
510
|
+
resolve(false);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
);
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
async function requestOwnedProcessTreeTermination(options) {
|
|
517
|
+
const platform = options.platform ?? process.platform;
|
|
518
|
+
if (platform !== "win32" && options.isClosed()) return;
|
|
519
|
+
const pid = positivePid(options.child, options.label);
|
|
520
|
+
if (pid === void 0) return;
|
|
521
|
+
if (platform === "win32") {
|
|
522
|
+
const output = await runTaskkill(pid, options);
|
|
523
|
+
const state = stateFor(options.child);
|
|
524
|
+
for (const walked of walkTaskkillPidSet(output, pid, [process.pid])) state.acceptedPids.add(walked);
|
|
525
|
+
state.requested = true;
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
signalGroup(pid, "SIGTERM", options.label, options.killFn ?? defaultKill);
|
|
529
|
+
stateFor(options.child).requested = true;
|
|
530
|
+
}
|
|
531
|
+
async function disposeOwnedProcessTree(options) {
|
|
532
|
+
const pid = positivePid(options.child, options.label);
|
|
533
|
+
const termGraceMs = options.termGraceMs ?? DEFAULT_TERM_GRACE_MS;
|
|
534
|
+
const killGraceMs = options.killGraceMs ?? DEFAULT_KILL_GRACE_MS;
|
|
535
|
+
const platform = options.platform ?? process.platform;
|
|
536
|
+
const kill = options.killFn ?? defaultKill;
|
|
537
|
+
if (pid === void 0) {
|
|
538
|
+
if (await waitWithDeadline(options.waitClosed(), killGraceMs)) return;
|
|
539
|
+
throw new RuntimeDisposalFailure({
|
|
540
|
+
stage: "quiescence",
|
|
541
|
+
reason: `${options.label} runtime process did not settle after spawn failure`
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
if (platform === "win32") {
|
|
545
|
+
if (!terminationState.get(options.child)?.requested) {
|
|
546
|
+
await requestOwnedProcessTreeTermination(options);
|
|
547
|
+
}
|
|
548
|
+
const accepted = terminationState.get(options.child)?.acceptedPids ?? /* @__PURE__ */ new Set();
|
|
549
|
+
const deadline = Date.now() + killGraceMs;
|
|
550
|
+
const resweepAt = Date.now() + Math.floor(killGraceMs / 2);
|
|
551
|
+
let reswept = false;
|
|
552
|
+
let live = liveAcceptedPids(accepted, options.label, kill);
|
|
553
|
+
while (live.length > 0) {
|
|
554
|
+
if (Date.now() >= deadline) {
|
|
555
|
+
throw new RuntimeDisposalFailure({
|
|
556
|
+
stage: "quiescence",
|
|
557
|
+
reason: `${options.label} runtime process tree did not quiesce: ${live.length} of ${accepted.size} walked process ids were still alive at the disposal deadline`
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
if (!reswept && Date.now() >= resweepAt) {
|
|
561
|
+
reswept = true;
|
|
562
|
+
for (const livePid of live) {
|
|
563
|
+
for (const walked of walkTaskkillPidSet(await runTaskkill(livePid, options), livePid, [process.pid])) {
|
|
564
|
+
accepted.add(walked);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
await new Promise((resolve) => {
|
|
569
|
+
setTimeout(resolve, POLL_MS);
|
|
570
|
+
});
|
|
571
|
+
live = liveAcceptedPids(accepted, options.label, kill);
|
|
572
|
+
}
|
|
573
|
+
if (!await waitWithDeadline(options.waitClosed(), killGraceMs)) {
|
|
574
|
+
throw new RuntimeDisposalFailure({
|
|
575
|
+
stage: "quiescence",
|
|
576
|
+
reason: `${options.label} runtime root did not emit close after its process tree quiesced`
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
if (groupExists(pid, options.label, kill) && !terminationState.get(options.child)?.requested) {
|
|
582
|
+
signalGroup(pid, "SIGTERM", options.label, kill);
|
|
583
|
+
stateFor(options.child).requested = true;
|
|
584
|
+
}
|
|
585
|
+
if (!await waitUntil(() => groupExists(pid, options.label, kill), termGraceMs)) {
|
|
586
|
+
signalGroup(pid, "SIGKILL", options.label, kill);
|
|
587
|
+
if (!await waitUntil(() => groupExists(pid, options.label, kill), killGraceMs)) {
|
|
588
|
+
throw new RuntimeDisposalFailure({
|
|
589
|
+
stage: "quiescence",
|
|
590
|
+
reason: `${options.label} runtime process group remained live after SIGKILL`
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
if (!await waitWithDeadline(options.waitClosed(), killGraceMs)) {
|
|
595
|
+
throw new RuntimeDisposalFailure({
|
|
596
|
+
stage: "quiescence",
|
|
597
|
+
reason: `${options.label} runtime root did not emit close after its process group exited`
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
271
602
|
// src/adapters/pi/rpc-client.ts
|
|
272
603
|
var STDERR_RING_CAPACITY = 20;
|
|
273
604
|
var DIALOG_UI_METHODS = /* @__PURE__ */ new Set(["select", "confirm", "input", "editor"]);
|
|
@@ -279,16 +610,22 @@ var PiRpcClient = class {
|
|
|
279
610
|
eventQueue = new AsyncQueue();
|
|
280
611
|
closed = false;
|
|
281
612
|
exitError;
|
|
613
|
+
closedPromise;
|
|
614
|
+
resolveClosed;
|
|
615
|
+
disposalAttempt;
|
|
282
616
|
/** 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`. */
|
|
283
617
|
stderrRing = [];
|
|
284
618
|
/** Count of pi RPC message types `PiSession` (pi-adapter.ts) has told us have no `AgentEvent` mapping and aren't routine bookkeeping — see `recordUnmappedFrame`. */
|
|
285
619
|
unmappedFrameCounts = /* @__PURE__ */ new Map();
|
|
286
620
|
constructor(options) {
|
|
287
621
|
const spawnFn = options.spawnFn ?? spawn;
|
|
288
|
-
this.child = spawnFn(options.command, options.args, {
|
|
622
|
+
this.child = spawnFn(options.command, options.args, withOwnedProcessTree({
|
|
289
623
|
cwd: options.cwd,
|
|
290
624
|
env: options.env,
|
|
291
625
|
stdio: ["pipe", "pipe", "pipe"]
|
|
626
|
+
}));
|
|
627
|
+
this.closedPromise = new Promise((resolve) => {
|
|
628
|
+
this.resolveClosed = resolve;
|
|
292
629
|
});
|
|
293
630
|
this.child.stdout.setEncoding("utf8");
|
|
294
631
|
this.child.stdout.on("data", (chunk) => this.onData(chunk));
|
|
@@ -323,6 +660,10 @@ var PiRpcClient = class {
|
|
|
323
660
|
get events() {
|
|
324
661
|
return this.eventQueue;
|
|
325
662
|
}
|
|
663
|
+
/** Local transport diagnostic retained when the process closes; consumers must classify it explicitly. */
|
|
664
|
+
get terminalError() {
|
|
665
|
+
return this.exitError;
|
|
666
|
+
}
|
|
326
667
|
/**
|
|
327
668
|
* Record a pi RPC message `type` that `PiSession` (pi-adapter.ts) decided
|
|
328
669
|
* has no `AgentEvent` mapping and isn't routine bookkeeping (see
|
|
@@ -341,15 +682,37 @@ var PiRpcClient = class {
|
|
|
341
682
|
);
|
|
342
683
|
}
|
|
343
684
|
}
|
|
344
|
-
/**
|
|
685
|
+
/**
|
|
686
|
+
* Immediate process-tree termination request. `dispose()` is the settlement
|
|
687
|
+
* receipt, so this stays fire-and-forget: an interrupt must not block on a
|
|
688
|
+
* terminator. A request that could not be spawned is left unrecorded, so
|
|
689
|
+
* `dispose()` re-issues it and raises the typed `stage:'signal'` failure —
|
|
690
|
+
* swallowing it here loses nothing.
|
|
691
|
+
*/
|
|
345
692
|
kill() {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
693
|
+
void requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
waitClosed() {
|
|
697
|
+
return this.closedPromise;
|
|
698
|
+
}
|
|
699
|
+
dispose() {
|
|
700
|
+
if (!this.disposalAttempt) {
|
|
701
|
+
const attempt = disposeOwnedProcessTree(this.processTreeOptions());
|
|
702
|
+
this.disposalAttempt = attempt.catch((error) => {
|
|
703
|
+
this.disposalAttempt = void 0;
|
|
704
|
+
throw error;
|
|
705
|
+
});
|
|
352
706
|
}
|
|
707
|
+
return this.disposalAttempt;
|
|
708
|
+
}
|
|
709
|
+
processTreeOptions() {
|
|
710
|
+
return {
|
|
711
|
+
child: this.child,
|
|
712
|
+
waitClosed: () => this.closedPromise,
|
|
713
|
+
isClosed: () => this.closed,
|
|
714
|
+
label: "pi"
|
|
715
|
+
};
|
|
353
716
|
}
|
|
354
717
|
onData(chunk) {
|
|
355
718
|
this.buffer += chunk;
|
|
@@ -435,6 +798,7 @@ var PiRpcClient = class {
|
|
|
435
798
|
if (this.closed) return;
|
|
436
799
|
this.closed = true;
|
|
437
800
|
this.exitError = err;
|
|
801
|
+
this.resolveClosed();
|
|
438
802
|
for (const [, waiter] of this.pending) waiter.reject(err);
|
|
439
803
|
this.pending.clear();
|
|
440
804
|
this.eventQueue.end();
|
|
@@ -505,8 +869,17 @@ var PiAdapter = class {
|
|
|
505
869
|
this.options = options;
|
|
506
870
|
}
|
|
507
871
|
options;
|
|
508
|
-
|
|
509
|
-
|
|
872
|
+
descriptor = freezeRuntimeAdapterDescriptor({
|
|
873
|
+
id: "pi",
|
|
874
|
+
supportsDispatchSelection: true,
|
|
875
|
+
capabilities: {
|
|
876
|
+
steer: true,
|
|
877
|
+
resume: true,
|
|
878
|
+
approvalInteractive: false,
|
|
879
|
+
permissionModes: ["auto", "readonly"]
|
|
880
|
+
},
|
|
881
|
+
environmentRequirements: { credentialNames: PROVIDER_CREDENTIAL_ENV_NAMES }
|
|
882
|
+
});
|
|
510
883
|
async detect() {
|
|
511
884
|
try {
|
|
512
885
|
const bin = this.resolveBin();
|
|
@@ -518,49 +891,26 @@ var PiAdapter = class {
|
|
|
518
891
|
return { present: false };
|
|
519
892
|
}
|
|
520
893
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
}
|
|
524
|
-
/**
|
|
525
|
-
* M5: pi authenticates to its ~30 supported providers via env-var API
|
|
526
|
-
* keys — `detect()`'s own `authPresent` probe above checks this identical
|
|
527
|
-
* list — so these MUST keep flowing into pi's spawned process or pi auth
|
|
528
|
-
* breaks entirely. `KNOWN_PROVIDER_ENV_VARS` above is the single source
|
|
529
|
-
* of truth, reused here rather than duplicated. No `baseNames`: nothing
|
|
530
|
-
* in this adapter or `rpc-client.ts` reads a pi-specific config-discovery
|
|
531
|
-
* variable beyond the platform baseline (`daemon/environment.ts`).
|
|
532
|
-
*/
|
|
533
|
-
environmentRequirements() {
|
|
534
|
-
return { credentialNames: PROVIDER_CREDENTIAL_ENV_NAMES };
|
|
535
|
-
}
|
|
536
|
-
async start(task, ctx) {
|
|
537
|
-
if (typeof task.instruction !== "string") {
|
|
538
|
-
throw new PolicyUnsupportedError("pi adapter only supports string instructions in M0 (no blob-ref fetch yet)");
|
|
539
|
-
}
|
|
540
|
-
const mapping = mapPermissionPolicyToPiArgs(ctx.policy);
|
|
894
|
+
async prepare(input) {
|
|
895
|
+
const mapping = mapPermissionPolicyToPiArgs(input.policy);
|
|
541
896
|
if (!mapping.ok) {
|
|
542
|
-
|
|
897
|
+
return { kind: "reject", reason: mapping.reason ?? "policy rejected by pi adapter", retryable: false };
|
|
543
898
|
}
|
|
544
899
|
const bin = this.resolveBin();
|
|
545
|
-
const
|
|
546
|
-
const
|
|
547
|
-
const selection = task.dispatchSelection;
|
|
900
|
+
const selection = input.offer.dispatchSelection;
|
|
901
|
+
const pinnedSelection = selection === void 0 ? void 0 : Object.freeze({ ...selection });
|
|
548
902
|
let command = bin.command;
|
|
549
|
-
let
|
|
550
|
-
if (
|
|
551
|
-
if (
|
|
552
|
-
|
|
553
|
-
`pi adapter cannot execute ${selection.lane} selection for runtime ${selection.runtimeId}`
|
|
554
|
-
);
|
|
903
|
+
let launcherArgs;
|
|
904
|
+
if (pinnedSelection !== void 0) {
|
|
905
|
+
if (pinnedSelection.lane !== "byok" || pinnedSelection.runtimeId !== "pi") {
|
|
906
|
+
return { kind: "reject", reason: `pi adapter cannot execute ${pinnedSelection.lane} selection for runtime ${pinnedSelection.runtimeId}`, retryable: false };
|
|
555
907
|
}
|
|
556
908
|
const launcher = this.options.byokLauncher;
|
|
557
909
|
if (launcher === void 0) {
|
|
558
|
-
|
|
559
|
-
"pi BYOK selection requires a configured credential-custody launcher"
|
|
560
|
-
);
|
|
910
|
+
return { kind: "reject", reason: "pi BYOK selection requires a configured credential-custody launcher", retryable: false };
|
|
561
911
|
}
|
|
562
912
|
command = launcher.command;
|
|
563
|
-
|
|
913
|
+
launcherArgs = [
|
|
564
914
|
...launcher.args ?? [],
|
|
565
915
|
"--pi-bin",
|
|
566
916
|
bin.command,
|
|
@@ -570,62 +920,136 @@ var PiAdapter = class {
|
|
|
570
920
|
launcher.sessionDir,
|
|
571
921
|
...launcher.secretServicePrefix ? ["--secret-service-prefix", launcher.secretServicePrefix] : [],
|
|
572
922
|
"--provider",
|
|
573
|
-
|
|
923
|
+
pinnedSelection.providerId,
|
|
574
924
|
"--model",
|
|
575
|
-
|
|
576
|
-
"--",
|
|
577
|
-
...piArgs
|
|
925
|
+
pinnedSelection.modelId
|
|
578
926
|
];
|
|
579
927
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
928
|
+
return {
|
|
929
|
+
kind: "prepared",
|
|
930
|
+
operation: {
|
|
931
|
+
start: async (startInput) => {
|
|
932
|
+
const manifestSelection = startInput.manifest.dispatchSelection;
|
|
933
|
+
if (!sameDispatchSelection(manifestSelection, pinnedSelection)) {
|
|
934
|
+
throw new RuntimeExecutionFailure({
|
|
935
|
+
phase: "start",
|
|
936
|
+
category: "authority",
|
|
937
|
+
retry: "non-retryable",
|
|
938
|
+
reason: "prepared pi operation received a manifest with different runtime selection"
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
if (typeof startInput.instruction !== "string") {
|
|
942
|
+
throw new RuntimeExecutionFailure({
|
|
943
|
+
phase: "start",
|
|
944
|
+
category: "authority",
|
|
945
|
+
retry: "non-retryable",
|
|
946
|
+
reason: "prepared pi operation requires a resolved string instruction"
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
const resumeSessionId = startInput.manifest.sessionRef;
|
|
950
|
+
const piArgs = ["--mode", "rpc", ...resumeSessionId ? ["--session", resumeSessionId] : [], ...mapping.args];
|
|
951
|
+
const args = launcherArgs === void 0 ? piArgs : [...launcherArgs, "--", ...piArgs];
|
|
952
|
+
let rpc;
|
|
953
|
+
try {
|
|
954
|
+
rpc = new PiRpcClient({
|
|
955
|
+
command,
|
|
956
|
+
args,
|
|
957
|
+
cwd: startInput.manifest.workspace.workspaceDir,
|
|
958
|
+
env: manifestSelection === void 0 ? startInput.env : withoutProviderCredentials(startInput.env),
|
|
959
|
+
spawnFn: this.options.spawnFn
|
|
960
|
+
});
|
|
961
|
+
} catch (cause) {
|
|
962
|
+
throw new RuntimeExecutionFailure({
|
|
963
|
+
phase: "start",
|
|
964
|
+
category: "infrastructure",
|
|
965
|
+
retry: "retryable",
|
|
966
|
+
reason: "pi runtime process could not be spawned"
|
|
967
|
+
}, { cause });
|
|
968
|
+
}
|
|
969
|
+
let response;
|
|
970
|
+
try {
|
|
971
|
+
response = await rpc.send({ type: "prompt", message: startInput.instruction });
|
|
972
|
+
} catch (cause) {
|
|
973
|
+
rpc.kill();
|
|
974
|
+
throw new RuntimeExecutionFailure({
|
|
975
|
+
phase: "start",
|
|
976
|
+
category: "infrastructure",
|
|
977
|
+
retry: "retryable",
|
|
978
|
+
reason: `pi initial prompt transport failed: ${errorMessage(cause)}`
|
|
979
|
+
}, { cause });
|
|
980
|
+
}
|
|
981
|
+
if (response.success === false) {
|
|
982
|
+
rpc.kill();
|
|
983
|
+
throw new RuntimeExecutionFailure({
|
|
984
|
+
phase: "start",
|
|
985
|
+
category: "semantic",
|
|
986
|
+
retry: "non-retryable",
|
|
987
|
+
reason: typeof response.error === "string" ? response.error : "pi rejected the initial prompt"
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
let sessionRef;
|
|
991
|
+
try {
|
|
992
|
+
sessionRef = await resolveAuthoritativeSessionId(rpc);
|
|
993
|
+
} catch (err) {
|
|
994
|
+
rpc.kill();
|
|
995
|
+
throw err;
|
|
996
|
+
}
|
|
997
|
+
if (resumeSessionId !== void 0 && sessionRef !== resumeSessionId) {
|
|
998
|
+
rpc.kill();
|
|
999
|
+
throw new RuntimeExecutionFailure({
|
|
1000
|
+
phase: "start",
|
|
1001
|
+
category: "authority",
|
|
1002
|
+
retry: "non-retryable",
|
|
1003
|
+
reason: "pi resumed a different authoritative session than requested"
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
return new PiSession(sessionRef, rpc, manifestSelection);
|
|
1007
|
+
}
|
|
601
1008
|
}
|
|
602
|
-
}
|
|
603
|
-
return new PiSession(sessionRef, rpc, selection);
|
|
1009
|
+
};
|
|
604
1010
|
}
|
|
605
1011
|
resolveBin() {
|
|
606
1012
|
return (this.options.resolveBin ?? resolvePiBin)();
|
|
607
1013
|
}
|
|
608
1014
|
};
|
|
609
|
-
|
|
1015
|
+
function sameDispatchSelection(left, right) {
|
|
1016
|
+
if (left === void 0 || right === void 0) return left === right;
|
|
1017
|
+
return left.lane === right.lane && left.runtimeId === right.runtimeId && left.providerId === right.providerId && left.modelId === right.modelId;
|
|
1018
|
+
}
|
|
1019
|
+
async function resolveAuthoritativeSessionId(rpc) {
|
|
610
1020
|
let state;
|
|
611
1021
|
try {
|
|
612
1022
|
state = await rpc.send({ type: "get_state" });
|
|
613
1023
|
} catch (err) {
|
|
614
|
-
|
|
1024
|
+
if (isRuntimeExecutionFailure(err)) throw err;
|
|
1025
|
+
throw new RuntimeExecutionFailure({
|
|
1026
|
+
phase: "start",
|
|
1027
|
+
category: "infrastructure",
|
|
1028
|
+
retry: "retryable",
|
|
1029
|
+
reason: `pi transport ended before yielding an authoritative session id: ${errorMessage(err)}`
|
|
1030
|
+
}, {
|
|
615
1031
|
cause: err
|
|
616
1032
|
});
|
|
617
1033
|
}
|
|
618
1034
|
if (state.success === false) {
|
|
619
1035
|
const reason = typeof state.error === "string" ? state.error : "get_state reported failure";
|
|
620
|
-
throw new
|
|
1036
|
+
throw new RuntimeExecutionFailure({
|
|
1037
|
+
phase: "start",
|
|
1038
|
+
category: "authority",
|
|
1039
|
+
retry: "non-retryable",
|
|
1040
|
+
reason: `pi did not yield an authoritative session id: ${reason}`
|
|
1041
|
+
});
|
|
621
1042
|
}
|
|
622
1043
|
const data = state.data;
|
|
623
1044
|
if (typeof data?.sessionId === "string" && data.sessionId.length > 0) {
|
|
624
1045
|
return data.sessionId;
|
|
625
1046
|
}
|
|
626
|
-
throw new
|
|
627
|
-
|
|
628
|
-
|
|
1047
|
+
throw new RuntimeExecutionFailure({
|
|
1048
|
+
phase: "start",
|
|
1049
|
+
category: "authority",
|
|
1050
|
+
retry: "non-retryable",
|
|
1051
|
+
reason: "pi get_state reported no authoritative session id"
|
|
1052
|
+
});
|
|
629
1053
|
}
|
|
630
1054
|
var PiSession = class {
|
|
631
1055
|
constructor(sessionRef, rpc, selection) {
|
|
@@ -641,12 +1065,40 @@ var PiSession = class {
|
|
|
641
1065
|
return {
|
|
642
1066
|
[Symbol.asyncIterator]() {
|
|
643
1067
|
const inner = rpc.events[Symbol.asyncIterator]();
|
|
1068
|
+
let terminalFailure;
|
|
644
1069
|
return {
|
|
645
1070
|
async next() {
|
|
646
1071
|
for (; ; ) {
|
|
647
|
-
|
|
648
|
-
|
|
1072
|
+
if (terminalFailure) throw terminalFailure;
|
|
1073
|
+
let result;
|
|
1074
|
+
try {
|
|
1075
|
+
result = await inner.next();
|
|
1076
|
+
} catch (cause) {
|
|
1077
|
+
throw new RuntimeExecutionFailure({
|
|
1078
|
+
phase: "run",
|
|
1079
|
+
category: "infrastructure",
|
|
1080
|
+
retry: "retryable",
|
|
1081
|
+
reason: "pi runtime event transport failed"
|
|
1082
|
+
}, { cause });
|
|
1083
|
+
}
|
|
1084
|
+
const { value, done } = result;
|
|
1085
|
+
if (done) {
|
|
1086
|
+
throw new RuntimeExecutionFailure({
|
|
1087
|
+
phase: "run",
|
|
1088
|
+
category: "infrastructure",
|
|
1089
|
+
retry: "retryable",
|
|
1090
|
+
reason: "pi runtime process ended before agent_settled"
|
|
1091
|
+
}, { cause: rpc.terminalError });
|
|
1092
|
+
}
|
|
649
1093
|
const mapped = mapPiMessageToAgentEvent(value);
|
|
1094
|
+
if (value.type === "auto_retry_end" && value.success === false) {
|
|
1095
|
+
terminalFailure = new RuntimeExecutionFailure({
|
|
1096
|
+
phase: "run",
|
|
1097
|
+
category: "semantic",
|
|
1098
|
+
retry: "non-retryable",
|
|
1099
|
+
reason: "pi exhausted its native retry policy"
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
650
1102
|
if (mapped) return { value: mapped, done: false };
|
|
651
1103
|
if (!ROUTINE_PI_EVENT_TYPES.has(value.type)) {
|
|
652
1104
|
rpc.recordUnmappedFrame(value.type);
|
|
@@ -676,7 +1128,7 @@ var PiSession = class {
|
|
|
676
1128
|
await this.rpc.send({ type: "abort" });
|
|
677
1129
|
}
|
|
678
1130
|
async close() {
|
|
679
|
-
this.rpc.
|
|
1131
|
+
await this.rpc.dispose();
|
|
680
1132
|
}
|
|
681
1133
|
async resolveApproval() {
|
|
682
1134
|
throw new Error("pi adapter does not support approval resume: pi never emits needs_approval in M0/M1");
|
|
@@ -888,7 +1340,15 @@ function mapResult(msg) {
|
|
|
888
1340
|
diagnostic ? `claude result frame had a missing/invalid is_error flag (got ${JSON.stringify(msg.is_error)}) \u2014 treating as failure, fail-closed; diagnostic content on the frame: ${truncateResultDiagnostic(diagnostic)}` : `claude result frame had a missing/invalid is_error flag (got ${JSON.stringify(msg.is_error)}) \u2014 treating as failure, fail-closed`
|
|
889
1341
|
);
|
|
890
1342
|
const events = usageEvent ? [usageEvent, { type: "error", message }] : [{ type: "error", message }];
|
|
891
|
-
return {
|
|
1343
|
+
return {
|
|
1344
|
+
events,
|
|
1345
|
+
terminalFailure: new RuntimeExecutionFailure({
|
|
1346
|
+
phase: "run",
|
|
1347
|
+
category: msg.is_error === true ? "semantic" : "authority",
|
|
1348
|
+
retry: "non-retryable",
|
|
1349
|
+
reason: msg.is_error === true ? "claude reported terminal task failure" : "claude emitted a malformed terminal result frame"
|
|
1350
|
+
})
|
|
1351
|
+
};
|
|
892
1352
|
}
|
|
893
1353
|
var RESULT_DIAGNOSTIC_MAX_CHARS = 2e3;
|
|
894
1354
|
function truncateResultDiagnostic(text) {
|
|
@@ -940,16 +1400,22 @@ var ClaudeProcessClient = class {
|
|
|
940
1400
|
eventQueue = new AsyncQueue();
|
|
941
1401
|
closed = false;
|
|
942
1402
|
exitError;
|
|
1403
|
+
closedPromise;
|
|
1404
|
+
resolveClosed;
|
|
1405
|
+
disposalAttempt;
|
|
943
1406
|
stderrRing = [];
|
|
944
1407
|
unmappedFrameCounts = /* @__PURE__ */ new Map();
|
|
945
1408
|
sessionId;
|
|
946
1409
|
initWaiter;
|
|
947
1410
|
constructor(options) {
|
|
948
1411
|
const spawnFn = options.spawnFn ?? spawn;
|
|
949
|
-
this.child = spawnFn(options.command, options.args, {
|
|
1412
|
+
this.child = spawnFn(options.command, options.args, withOwnedProcessTree({
|
|
950
1413
|
cwd: options.cwd,
|
|
951
1414
|
env: options.env,
|
|
952
1415
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1416
|
+
}));
|
|
1417
|
+
this.closedPromise = new Promise((resolve) => {
|
|
1418
|
+
this.resolveClosed = resolve;
|
|
953
1419
|
});
|
|
954
1420
|
this.child.stdout.setEncoding("utf8");
|
|
955
1421
|
this.child.stdout.on("data", (chunk) => this.onData(chunk));
|
|
@@ -1005,6 +1471,10 @@ var ClaudeProcessClient = class {
|
|
|
1005
1471
|
get events() {
|
|
1006
1472
|
return this.eventQueue;
|
|
1007
1473
|
}
|
|
1474
|
+
/** Local transport diagnostic retained when the process closes; consumers classify it at the session boundary. */
|
|
1475
|
+
get terminalError() {
|
|
1476
|
+
return this.exitError;
|
|
1477
|
+
}
|
|
1008
1478
|
/**
|
|
1009
1479
|
* Record a claude stream-json frame/subtype/content-block label that
|
|
1010
1480
|
* `ClaudeSession`'s event iterator (`../claude-adapter.ts`) decided has
|
|
@@ -1024,15 +1494,37 @@ var ClaudeProcessClient = class {
|
|
|
1024
1494
|
);
|
|
1025
1495
|
}
|
|
1026
1496
|
}
|
|
1027
|
-
/**
|
|
1497
|
+
/**
|
|
1498
|
+
* Immediate process-tree termination request. `dispose()` is the settlement
|
|
1499
|
+
* receipt, so this stays fire-and-forget: an interrupt must not block on a
|
|
1500
|
+
* terminator. A request that could not be spawned is left unrecorded, so
|
|
1501
|
+
* `dispose()` re-issues it and raises the typed `stage:'signal'` failure —
|
|
1502
|
+
* swallowing it here loses nothing.
|
|
1503
|
+
*/
|
|
1028
1504
|
kill() {
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1505
|
+
void requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
waitClosed() {
|
|
1509
|
+
return this.closedPromise;
|
|
1510
|
+
}
|
|
1511
|
+
dispose() {
|
|
1512
|
+
if (!this.disposalAttempt) {
|
|
1513
|
+
const attempt = disposeOwnedProcessTree(this.processTreeOptions());
|
|
1514
|
+
this.disposalAttempt = attempt.catch((error) => {
|
|
1515
|
+
this.disposalAttempt = void 0;
|
|
1516
|
+
throw error;
|
|
1517
|
+
});
|
|
1035
1518
|
}
|
|
1519
|
+
return this.disposalAttempt;
|
|
1520
|
+
}
|
|
1521
|
+
processTreeOptions() {
|
|
1522
|
+
return {
|
|
1523
|
+
child: this.child,
|
|
1524
|
+
waitClosed: () => this.closedPromise,
|
|
1525
|
+
isClosed: () => this.closed,
|
|
1526
|
+
label: "claude"
|
|
1527
|
+
};
|
|
1036
1528
|
}
|
|
1037
1529
|
onData(chunk) {
|
|
1038
1530
|
this.buffer += chunk;
|
|
@@ -1083,6 +1575,7 @@ var ClaudeProcessClient = class {
|
|
|
1083
1575
|
if (this.closed) return;
|
|
1084
1576
|
this.closed = true;
|
|
1085
1577
|
this.exitError = err;
|
|
1578
|
+
this.resolveClosed();
|
|
1086
1579
|
this.initWaiter?.reject(err);
|
|
1087
1580
|
this.initWaiter = void 0;
|
|
1088
1581
|
this.eventQueue.end();
|
|
@@ -1094,18 +1587,37 @@ var APPROVAL_TOOL_NAME = "approval_prompt";
|
|
|
1094
1587
|
var APPROVAL_MCP_SERVER_NAME = "byokapproval";
|
|
1095
1588
|
var execFileAsync2 = promisify(execFile);
|
|
1096
1589
|
var DETECT_TIMEOUT_MS2 = 5e3;
|
|
1590
|
+
function errorMessage2(err) {
|
|
1591
|
+
return err instanceof Error ? err.message : String(err);
|
|
1592
|
+
}
|
|
1097
1593
|
async function cleanupMcpConfigDir(dir) {
|
|
1098
1594
|
if (!dir) return;
|
|
1099
|
-
|
|
1100
|
-
|
|
1595
|
+
try {
|
|
1596
|
+
await promises.rm(dir, { recursive: true, force: true });
|
|
1597
|
+
} catch (cause) {
|
|
1598
|
+
throw new RuntimeDisposalFailure({
|
|
1599
|
+
stage: "cleanup",
|
|
1600
|
+
reason: "claude task-scoped MCP configuration could not be removed"
|
|
1601
|
+
}, { cause });
|
|
1602
|
+
}
|
|
1101
1603
|
}
|
|
1102
1604
|
var ClaudeAdapter = class {
|
|
1103
1605
|
constructor(options = {}) {
|
|
1104
1606
|
this.options = options;
|
|
1105
1607
|
}
|
|
1106
1608
|
options;
|
|
1107
|
-
|
|
1108
|
-
|
|
1609
|
+
descriptor = freezeRuntimeAdapterDescriptor({
|
|
1610
|
+
id: "claude",
|
|
1611
|
+
supportsDispatchSelection: true,
|
|
1612
|
+
capabilities: {
|
|
1613
|
+
steer: false,
|
|
1614
|
+
resume: true,
|
|
1615
|
+
approvalInteractive: true,
|
|
1616
|
+
mcpToolsets: true,
|
|
1617
|
+
permissionModes: ["auto", "readonly", "plan", "confirm"]
|
|
1618
|
+
},
|
|
1619
|
+
environmentRequirements: { credentialNames: [] }
|
|
1620
|
+
});
|
|
1109
1621
|
async detect() {
|
|
1110
1622
|
const bin = this.resolveBin();
|
|
1111
1623
|
try {
|
|
@@ -1117,54 +1629,73 @@ var ClaudeAdapter = class {
|
|
|
1117
1629
|
return { present: false };
|
|
1118
1630
|
}
|
|
1119
1631
|
}
|
|
1120
|
-
|
|
1632
|
+
async prepare(input) {
|
|
1633
|
+
const mapping = mapPermissionPolicyToClaudeArgs(input.policy);
|
|
1634
|
+
if (!mapping.ok) return { kind: "reject", reason: mapping.reason ?? "policy rejected by claude adapter", retryable: false };
|
|
1635
|
+
let modelId;
|
|
1636
|
+
try {
|
|
1637
|
+
modelId = subscriptionModel(input.offer.dispatchSelection, "claude");
|
|
1638
|
+
} catch (error) {
|
|
1639
|
+
return { kind: "reject", reason: error instanceof Error ? error.message : String(error), retryable: false };
|
|
1640
|
+
}
|
|
1641
|
+
if (mapping.needsApprovalMcp && Object.prototype.hasOwnProperty.call(input.mcpServers ?? {}, APPROVAL_MCP_SERVER_NAME)) {
|
|
1642
|
+
return { kind: "reject", reason: `MCP server name "${APPROVAL_MCP_SERVER_NAME}" is reserved by the claude approval channel`, retryable: false };
|
|
1643
|
+
}
|
|
1644
|
+
let bin;
|
|
1645
|
+
try {
|
|
1646
|
+
bin = this.resolveBin();
|
|
1647
|
+
} catch (error) {
|
|
1648
|
+
return { kind: "reject", reason: error instanceof Error ? error.message : String(error), retryable: true };
|
|
1649
|
+
}
|
|
1650
|
+
let approvalMcpBin;
|
|
1651
|
+
if (mapping.needsApprovalMcp) {
|
|
1652
|
+
try {
|
|
1653
|
+
approvalMcpBin = (this.options.resolveApprovalMcpBin ?? resolveApprovalMcpBin)();
|
|
1654
|
+
} catch (error) {
|
|
1655
|
+
return { kind: "reject", reason: error instanceof Error ? error.message : String(error), retryable: true };
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1121
1658
|
return {
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
permissionModes: ["auto", "readonly", "plan", "confirm"]
|
|
1659
|
+
kind: "prepared",
|
|
1660
|
+
operation: {
|
|
1661
|
+
start: (startInput) => this.startPrepared(startInput, mapping, modelId, bin, approvalMcpBin)
|
|
1662
|
+
}
|
|
1127
1663
|
};
|
|
1128
1664
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
return { credentialNames: [] };
|
|
1144
|
-
}
|
|
1145
|
-
async start(task, ctx) {
|
|
1146
|
-
if (typeof task.instruction !== "string") {
|
|
1147
|
-
throw new PolicyUnsupportedError("claude adapter only supports string instructions in M2 (no blob-ref fetch yet)");
|
|
1148
|
-
}
|
|
1149
|
-
const mapping = mapPermissionPolicyToClaudeArgs(ctx.policy);
|
|
1150
|
-
if (!mapping.ok) {
|
|
1151
|
-
throw new PolicyUnsupportedError(mapping.reason ?? "policy rejected by claude adapter");
|
|
1665
|
+
async startPrepared(startInput, initialMapping, modelId, bin, approvalMcpBin) {
|
|
1666
|
+
if (!initialMapping.ok) throw new RuntimeExecutionFailure({
|
|
1667
|
+
phase: "start",
|
|
1668
|
+
category: "authority",
|
|
1669
|
+
retry: "non-retryable",
|
|
1670
|
+
reason: "prepared claude permission mapping was invalid"
|
|
1671
|
+
});
|
|
1672
|
+
if (typeof startInput.instruction !== "string") {
|
|
1673
|
+
throw new RuntimeExecutionFailure({
|
|
1674
|
+
phase: "start",
|
|
1675
|
+
category: "authority",
|
|
1676
|
+
retry: "non-retryable",
|
|
1677
|
+
reason: "prepared claude operation requires a resolved string instruction"
|
|
1678
|
+
});
|
|
1152
1679
|
}
|
|
1153
|
-
const
|
|
1680
|
+
const mapping = { ...initialMapping, args: [...initialMapping.args] };
|
|
1154
1681
|
let mcpConfigDir;
|
|
1155
|
-
const taskMcpServers =
|
|
1682
|
+
const taskMcpServers = startInput.mcpServers ?? {};
|
|
1156
1683
|
const needsMcpConfig = mapping.needsApprovalMcp || Object.keys(taskMcpServers).length > 0;
|
|
1157
1684
|
if (mapping.needsApprovalMcp) {
|
|
1158
|
-
if (!
|
|
1159
|
-
throw new
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
`MCP server name "${APPROVAL_MCP_SERVER_NAME}" is reserved by the claude approval channel`
|
|
1166
|
-
);
|
|
1685
|
+
if (!startInput.approvalChannel) {
|
|
1686
|
+
throw new RuntimeExecutionFailure({
|
|
1687
|
+
phase: "start",
|
|
1688
|
+
category: "authority",
|
|
1689
|
+
retry: "non-retryable",
|
|
1690
|
+
reason: 'claude adapter requires policy.mode "confirm" to be started with an approval channel'
|
|
1691
|
+
});
|
|
1167
1692
|
}
|
|
1693
|
+
if (!approvalMcpBin) throw new RuntimeExecutionFailure({
|
|
1694
|
+
phase: "start",
|
|
1695
|
+
category: "authority",
|
|
1696
|
+
retry: "non-retryable",
|
|
1697
|
+
reason: "prepared claude approval MCP binary was not resolved"
|
|
1698
|
+
});
|
|
1168
1699
|
}
|
|
1169
1700
|
if (needsMcpConfig) {
|
|
1170
1701
|
mcpConfigDir = await promises.mkdtemp(path3.join(os.tmpdir(), "byok-mcp-"));
|
|
@@ -1173,12 +1704,23 @@ var ClaudeAdapter = class {
|
|
|
1173
1704
|
const mcpConfigPath = path3.join(mcpConfigDir, "mcp-config.json");
|
|
1174
1705
|
const mcpServers = { ...taskMcpServers };
|
|
1175
1706
|
if (mapping.needsApprovalMcp) {
|
|
1176
|
-
const approvalChannel =
|
|
1177
|
-
if (!approvalChannel) throw new
|
|
1178
|
-
|
|
1707
|
+
const approvalChannel = startInput.approvalChannel;
|
|
1708
|
+
if (!approvalChannel) throw new RuntimeExecutionFailure({
|
|
1709
|
+
phase: "start",
|
|
1710
|
+
category: "authority",
|
|
1711
|
+
retry: "non-retryable",
|
|
1712
|
+
reason: "prepared claude approval channel was not available"
|
|
1713
|
+
});
|
|
1714
|
+
const preparedApprovalMcpBin = approvalMcpBin;
|
|
1715
|
+
if (!preparedApprovalMcpBin) throw new RuntimeExecutionFailure({
|
|
1716
|
+
phase: "start",
|
|
1717
|
+
category: "authority",
|
|
1718
|
+
retry: "non-retryable",
|
|
1719
|
+
reason: "prepared claude approval MCP binary was not resolved"
|
|
1720
|
+
});
|
|
1179
1721
|
mcpServers[APPROVAL_MCP_SERVER_NAME] = {
|
|
1180
|
-
command:
|
|
1181
|
-
args:
|
|
1722
|
+
command: preparedApprovalMcpBin.command,
|
|
1723
|
+
args: preparedApprovalMcpBin.args,
|
|
1182
1724
|
env: {
|
|
1183
1725
|
BYOK_STORE_DIR: approvalChannel.storeDir,
|
|
1184
1726
|
BYOK_PRODUCT_ID: approvalChannel.productId,
|
|
@@ -1198,8 +1740,26 @@ var ClaudeAdapter = class {
|
|
|
1198
1740
|
"--strict-mcp-config"
|
|
1199
1741
|
];
|
|
1200
1742
|
}
|
|
1201
|
-
const
|
|
1202
|
-
|
|
1743
|
+
const resumeSessionId = startInput.manifest.sessionRef;
|
|
1744
|
+
let manifestModelId;
|
|
1745
|
+
try {
|
|
1746
|
+
manifestModelId = subscriptionModel(startInput.manifest.dispatchSelection, "claude");
|
|
1747
|
+
} catch (cause) {
|
|
1748
|
+
throw new RuntimeExecutionFailure({
|
|
1749
|
+
phase: "start",
|
|
1750
|
+
category: "authority",
|
|
1751
|
+
retry: "non-retryable",
|
|
1752
|
+
reason: "prepared claude operation received an invalid runtime selection manifest"
|
|
1753
|
+
}, { cause });
|
|
1754
|
+
}
|
|
1755
|
+
if (manifestModelId !== modelId) {
|
|
1756
|
+
throw new RuntimeExecutionFailure({
|
|
1757
|
+
phase: "start",
|
|
1758
|
+
category: "authority",
|
|
1759
|
+
retry: "non-retryable",
|
|
1760
|
+
reason: "prepared claude operation received a manifest with different runtime selection"
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1203
1763
|
const args = [
|
|
1204
1764
|
"-p",
|
|
1205
1765
|
"--input-format",
|
|
@@ -1211,40 +1771,71 @@ var ClaudeAdapter = class {
|
|
|
1211
1771
|
// "Error: When using --print, --output-format=stream-json requires
|
|
1212
1772
|
// --verbose", before spawning any model call.
|
|
1213
1773
|
"--verbose",
|
|
1214
|
-
...
|
|
1774
|
+
...manifestModelId ? ["--model", manifestModelId] : [],
|
|
1215
1775
|
...resumeSessionId ? ["--resume", resumeSessionId] : [],
|
|
1216
1776
|
...mapping.args
|
|
1217
1777
|
];
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1778
|
+
let client;
|
|
1779
|
+
try {
|
|
1780
|
+
client = new ClaudeProcessClient({
|
|
1781
|
+
command: bin.command,
|
|
1782
|
+
args,
|
|
1783
|
+
cwd: startInput.manifest.workspace.workspaceDir,
|
|
1784
|
+
env: withoutProviderCredentials(startInput.env),
|
|
1785
|
+
spawnFn: this.options.spawnFn
|
|
1786
|
+
});
|
|
1787
|
+
} catch (cause) {
|
|
1788
|
+
await cleanupMcpConfigDir(mcpConfigDir);
|
|
1789
|
+
throw new RuntimeExecutionFailure({
|
|
1790
|
+
phase: "start",
|
|
1791
|
+
category: "infrastructure",
|
|
1792
|
+
retry: "retryable",
|
|
1793
|
+
reason: "claude runtime process could not be spawned"
|
|
1794
|
+
}, { cause });
|
|
1795
|
+
}
|
|
1796
|
+
try {
|
|
1797
|
+
client.writeUserMessage(startInput.instruction);
|
|
1798
|
+
} catch (cause) {
|
|
1799
|
+
client.kill();
|
|
1800
|
+
await cleanupMcpConfigDir(mcpConfigDir);
|
|
1801
|
+
throw new RuntimeExecutionFailure({
|
|
1802
|
+
phase: "start",
|
|
1803
|
+
category: "infrastructure",
|
|
1804
|
+
retry: "retryable",
|
|
1805
|
+
reason: "claude initial instruction transport failed"
|
|
1806
|
+
}, { cause });
|
|
1807
|
+
}
|
|
1226
1808
|
let sessionRef;
|
|
1227
1809
|
try {
|
|
1228
1810
|
sessionRef = await client.waitForInit();
|
|
1229
1811
|
} catch (err) {
|
|
1230
1812
|
client.kill();
|
|
1231
1813
|
await cleanupMcpConfigDir(mcpConfigDir);
|
|
1232
|
-
throw err;
|
|
1814
|
+
if (isRuntimeExecutionFailure(err)) throw err;
|
|
1815
|
+
throw new RuntimeExecutionFailure({
|
|
1816
|
+
phase: "start",
|
|
1817
|
+
category: "infrastructure",
|
|
1818
|
+
retry: "retryable",
|
|
1819
|
+
reason: `claude exited before yielding an authoritative session id: ${errorMessage2(err)}`
|
|
1820
|
+
}, { cause: err });
|
|
1233
1821
|
}
|
|
1234
1822
|
if (resumeSessionId !== void 0 && sessionRef !== resumeSessionId) {
|
|
1235
1823
|
client.kill();
|
|
1236
1824
|
await cleanupMcpConfigDir(mcpConfigDir);
|
|
1237
|
-
throw new
|
|
1238
|
-
|
|
1239
|
-
|
|
1825
|
+
throw new RuntimeExecutionFailure({
|
|
1826
|
+
phase: "start",
|
|
1827
|
+
category: "authority",
|
|
1828
|
+
retry: "non-retryable",
|
|
1829
|
+
reason: `claude --resume echoed a different session id than requested (requested ${resumeSessionId}, got ${sessionRef})`
|
|
1830
|
+
});
|
|
1240
1831
|
}
|
|
1241
1832
|
return new ClaudeSession(
|
|
1242
1833
|
sessionRef,
|
|
1243
1834
|
client,
|
|
1244
|
-
|
|
1245
|
-
|
|
1835
|
+
startInput.manifest.workspace.workspaceDir,
|
|
1836
|
+
startInput.approvalChannel,
|
|
1246
1837
|
mcpConfigDir,
|
|
1247
|
-
|
|
1838
|
+
manifestModelId
|
|
1248
1839
|
);
|
|
1249
1840
|
}
|
|
1250
1841
|
/**
|
|
@@ -1279,8 +1870,7 @@ var ClaudeAdapter = class {
|
|
|
1279
1870
|
return (this.options.resolveBin ?? resolveClaudeBin)();
|
|
1280
1871
|
}
|
|
1281
1872
|
};
|
|
1282
|
-
function subscriptionModel(
|
|
1283
|
-
const selection = task.dispatchSelection;
|
|
1873
|
+
function subscriptionModel(selection, runtimeId) {
|
|
1284
1874
|
if (selection === void 0) return void 0;
|
|
1285
1875
|
if (selection.lane !== "subscription" || selection.runtimeId !== runtimeId) {
|
|
1286
1876
|
throw new PolicyUnsupportedError(
|
|
@@ -1305,6 +1895,7 @@ var ClaudeSession = class {
|
|
|
1305
1895
|
mcpConfigDir;
|
|
1306
1896
|
modelId;
|
|
1307
1897
|
correlation = createToolUseCorrelation();
|
|
1898
|
+
closeAttempt;
|
|
1308
1899
|
get events() {
|
|
1309
1900
|
const client = this.client;
|
|
1310
1901
|
const correlation = this.correlation;
|
|
@@ -1313,17 +1904,40 @@ var ClaudeSession = class {
|
|
|
1313
1904
|
[Symbol.asyncIterator]() {
|
|
1314
1905
|
const inner = client.events[Symbol.asyncIterator]();
|
|
1315
1906
|
let pending = [];
|
|
1907
|
+
let terminalFailure;
|
|
1316
1908
|
let turnSettled = false;
|
|
1317
1909
|
return {
|
|
1318
1910
|
async next() {
|
|
1319
1911
|
for (; ; ) {
|
|
1320
1912
|
const buffered = pending.shift();
|
|
1321
1913
|
if (buffered) return { value: buffered, done: false };
|
|
1322
|
-
if (turnSettled)
|
|
1323
|
-
|
|
1324
|
-
|
|
1914
|
+
if (turnSettled) {
|
|
1915
|
+
if (terminalFailure) throw terminalFailure;
|
|
1916
|
+
return { value: void 0, done: true };
|
|
1917
|
+
}
|
|
1918
|
+
let raw;
|
|
1919
|
+
try {
|
|
1920
|
+
raw = await inner.next();
|
|
1921
|
+
} catch (cause) {
|
|
1922
|
+
throw new RuntimeExecutionFailure({
|
|
1923
|
+
phase: "run",
|
|
1924
|
+
category: "infrastructure",
|
|
1925
|
+
retry: "retryable",
|
|
1926
|
+
reason: "claude runtime event transport failed"
|
|
1927
|
+
}, { cause });
|
|
1928
|
+
}
|
|
1929
|
+
const { value, done } = raw;
|
|
1930
|
+
if (done) {
|
|
1931
|
+
throw new RuntimeExecutionFailure({
|
|
1932
|
+
phase: "run",
|
|
1933
|
+
category: "infrastructure",
|
|
1934
|
+
retry: "retryable",
|
|
1935
|
+
reason: "claude runtime process ended before a terminal result frame"
|
|
1936
|
+
}, { cause: client.terminalError });
|
|
1937
|
+
}
|
|
1325
1938
|
if (value.type === "result") turnSettled = true;
|
|
1326
1939
|
const mapped = mapClaudeMessageToAgentEvents(value, correlation, { workspaceDir });
|
|
1940
|
+
terminalFailure = mapped.terminalFailure ?? terminalFailure;
|
|
1327
1941
|
if (mapped.unmappedLabel) {
|
|
1328
1942
|
client.recordUnmappedFrame(mapped.unmappedLabel);
|
|
1329
1943
|
}
|
|
@@ -1354,7 +1968,7 @@ var ClaudeSession = class {
|
|
|
1354
1968
|
if (typeof task.instruction !== "string") {
|
|
1355
1969
|
throw new PolicyUnsupportedError("claude adapter only supports string instructions in M2 (no blob-ref fetch yet)");
|
|
1356
1970
|
}
|
|
1357
|
-
const requestedModel = subscriptionModel(task, "claude");
|
|
1971
|
+
const requestedModel = subscriptionModel(task.dispatchSelection, "claude");
|
|
1358
1972
|
if (requestedModel !== void 0 && requestedModel !== this.modelId) {
|
|
1359
1973
|
throw new PolicyUnsupportedError(
|
|
1360
1974
|
`claude persistent session cannot change model from ${this.modelId ?? "(legacy default)"} to ${requestedModel}`
|
|
@@ -1378,8 +1992,17 @@ var ClaudeSession = class {
|
|
|
1378
1992
|
this.client.kill();
|
|
1379
1993
|
}
|
|
1380
1994
|
async close() {
|
|
1381
|
-
this.
|
|
1382
|
-
|
|
1995
|
+
if (!this.closeAttempt) {
|
|
1996
|
+
const attempt = (async () => {
|
|
1997
|
+
await this.client.dispose();
|
|
1998
|
+
await cleanupMcpConfigDir(this.mcpConfigDir);
|
|
1999
|
+
})();
|
|
2000
|
+
this.closeAttempt = attempt.catch((error) => {
|
|
2001
|
+
this.closeAttempt = void 0;
|
|
2002
|
+
throw error;
|
|
2003
|
+
});
|
|
2004
|
+
}
|
|
2005
|
+
await this.closeAttempt;
|
|
1383
2006
|
}
|
|
1384
2007
|
/**
|
|
1385
2008
|
* M4 Phase 3: routes into the out-of-band approval channel `start()`
|
|
@@ -1596,14 +2219,15 @@ var CodexProcessRunner = class {
|
|
|
1596
2219
|
exitSignal = null;
|
|
1597
2220
|
closedPromise;
|
|
1598
2221
|
resolveClosed;
|
|
2222
|
+
disposalAttempt;
|
|
1599
2223
|
constructor(options) {
|
|
1600
2224
|
this.onEvent = options.onEvent;
|
|
1601
2225
|
const spawnFn = options.spawnFn ?? spawn;
|
|
1602
|
-
this.child = spawnFn(options.command, options.args, {
|
|
2226
|
+
this.child = spawnFn(options.command, options.args, withOwnedProcessTree({
|
|
1603
2227
|
cwd: options.cwd,
|
|
1604
2228
|
env: options.env,
|
|
1605
2229
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1606
|
-
});
|
|
2230
|
+
}));
|
|
1607
2231
|
this.closedPromise = new Promise((resolve) => {
|
|
1608
2232
|
this.resolveClosed = resolve;
|
|
1609
2233
|
});
|
|
@@ -1633,7 +2257,7 @@ var CodexProcessRunner = class {
|
|
|
1633
2257
|
return this.closed;
|
|
1634
2258
|
}
|
|
1635
2259
|
/**
|
|
1636
|
-
*
|
|
2260
|
+
* Immediate tree termination request. SIGTERM on POSIX: SIGINT was empirically confirmed
|
|
1637
2261
|
* to be silently ignored by `codex exec` (a real, direct test — a 60s
|
|
1638
2262
|
* shell `sleep` ran to full, unaffected completion despite SIGINT sent at
|
|
1639
2263
|
* t=4s) — a genuine, evidence-based correction to this task's own initial
|
|
@@ -1644,15 +2268,33 @@ var CodexProcessRunner = class {
|
|
|
1644
2268
|
* cleanly resumable afterward via `codex exec resume` (no corruption from
|
|
1645
2269
|
* killing mid-turn). `taskkill /T /F` on Windows, mirroring
|
|
1646
2270
|
* `../pi/rpc-client.ts`'s own cross-platform convention.
|
|
2271
|
+
*
|
|
2272
|
+
* Fire-and-forget by design: an interrupt must not block on a terminator,
|
|
2273
|
+
* and `dispose()` is the settlement receipt. A request that could not be
|
|
2274
|
+
* spawned is left unrecorded, so `dispose()` re-issues it and raises the
|
|
2275
|
+
* typed `stage:'signal'` failure — swallowing it here loses nothing.
|
|
1647
2276
|
*/
|
|
1648
2277
|
kill() {
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
this.
|
|
2278
|
+
void requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
|
|
2279
|
+
});
|
|
2280
|
+
}
|
|
2281
|
+
dispose() {
|
|
2282
|
+
if (!this.disposalAttempt) {
|
|
2283
|
+
const attempt = disposeOwnedProcessTree(this.processTreeOptions());
|
|
2284
|
+
this.disposalAttempt = attempt.catch((error) => {
|
|
2285
|
+
this.disposalAttempt = void 0;
|
|
2286
|
+
throw error;
|
|
2287
|
+
});
|
|
1655
2288
|
}
|
|
2289
|
+
return this.disposalAttempt;
|
|
2290
|
+
}
|
|
2291
|
+
processTreeOptions() {
|
|
2292
|
+
return {
|
|
2293
|
+
child: this.child,
|
|
2294
|
+
waitClosed: () => this.closedPromise,
|
|
2295
|
+
isClosed: () => this.closed,
|
|
2296
|
+
label: "codex"
|
|
2297
|
+
};
|
|
1656
2298
|
}
|
|
1657
2299
|
/** 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. */
|
|
1658
2300
|
buildExitError(context) {
|
|
@@ -1703,8 +2345,17 @@ var CodexAdapter = class {
|
|
|
1703
2345
|
this.options = options;
|
|
1704
2346
|
}
|
|
1705
2347
|
options;
|
|
1706
|
-
|
|
1707
|
-
|
|
2348
|
+
descriptor = freezeRuntimeAdapterDescriptor({
|
|
2349
|
+
id: "codex",
|
|
2350
|
+
supportsDispatchSelection: true,
|
|
2351
|
+
capabilities: {
|
|
2352
|
+
steer: false,
|
|
2353
|
+
resume: true,
|
|
2354
|
+
approvalInteractive: false,
|
|
2355
|
+
permissionModes: ["auto", "readonly"]
|
|
2356
|
+
},
|
|
2357
|
+
environmentRequirements: { credentialNames: [] }
|
|
2358
|
+
});
|
|
1708
2359
|
async detect() {
|
|
1709
2360
|
const bin = this.resolveBin();
|
|
1710
2361
|
try {
|
|
@@ -1753,61 +2404,100 @@ ${result.stderr}`);
|
|
|
1753
2404
|
${withStreams.stderr ?? ""}`);
|
|
1754
2405
|
}
|
|
1755
2406
|
}
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
* API-key passthrough remains a separate, pending product decision. No
|
|
1765
|
-
* `baseNames` either: nothing in this adapter reads a codex-specific
|
|
1766
|
-
* config-discovery variable (e.g. `CODEX_HOME`) today.
|
|
1767
|
-
*/
|
|
1768
|
-
environmentRequirements() {
|
|
1769
|
-
return { credentialNames: [] };
|
|
1770
|
-
}
|
|
1771
|
-
async start(task, ctx) {
|
|
1772
|
-
if (typeof task.instruction !== "string") {
|
|
1773
|
-
throw new PolicyUnsupportedError("codex adapter only supports string instructions in M2 (no blob-ref fetch yet)");
|
|
2407
|
+
async prepare(input) {
|
|
2408
|
+
const mapping = mapPermissionPolicyToCodexArgs(input.policy);
|
|
2409
|
+
if (!mapping.ok) return { kind: "reject", reason: mapping.reason ?? "policy rejected by codex adapter", retryable: false };
|
|
2410
|
+
let modelId;
|
|
2411
|
+
try {
|
|
2412
|
+
modelId = subscriptionModel2(input.offer.dispatchSelection);
|
|
2413
|
+
} catch (error) {
|
|
2414
|
+
return { kind: "reject", reason: error instanceof Error ? error.message : String(error), retryable: false };
|
|
1774
2415
|
}
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
2416
|
+
let command;
|
|
2417
|
+
try {
|
|
2418
|
+
command = this.resolveBin().command;
|
|
2419
|
+
} catch (error) {
|
|
2420
|
+
return { kind: "reject", reason: error instanceof Error ? error.message : String(error), retryable: true };
|
|
2421
|
+
}
|
|
2422
|
+
return {
|
|
2423
|
+
kind: "prepared",
|
|
2424
|
+
operation: {
|
|
2425
|
+
start: (startInput) => this.startPrepared(startInput, mapping.args, modelId, command)
|
|
2426
|
+
}
|
|
2427
|
+
};
|
|
2428
|
+
}
|
|
2429
|
+
async startPrepared(startInput, policyArgs, modelId, command) {
|
|
2430
|
+
if (typeof startInput.instruction !== "string") {
|
|
2431
|
+
throw new RuntimeExecutionFailure({
|
|
2432
|
+
phase: "start",
|
|
2433
|
+
category: "authority",
|
|
2434
|
+
retry: "non-retryable",
|
|
2435
|
+
reason: "prepared codex operation requires a resolved string instruction"
|
|
2436
|
+
});
|
|
1778
2437
|
}
|
|
1779
|
-
const modelId = subscriptionModel2(task);
|
|
1780
|
-
const bin = this.resolveBin();
|
|
1781
2438
|
const queue = new AsyncQueue();
|
|
2439
|
+
const terminal = {};
|
|
1782
2440
|
const recordUnmapped = makeUnmappedFrameRecorder(/* @__PURE__ */ new Map());
|
|
1783
|
-
|
|
1784
|
-
|
|
2441
|
+
let workspaceDir;
|
|
2442
|
+
try {
|
|
2443
|
+
workspaceDir = await resolveRealWorkspaceDir(startInput.manifest.workspace.workspaceDir);
|
|
2444
|
+
} catch (cause) {
|
|
2445
|
+
throw new RuntimeExecutionFailure({
|
|
2446
|
+
phase: "start",
|
|
2447
|
+
category: "infrastructure",
|
|
2448
|
+
retry: "retryable",
|
|
2449
|
+
reason: "codex runtime workspace could not be resolved"
|
|
2450
|
+
}, { cause });
|
|
2451
|
+
}
|
|
2452
|
+
const runtimeEnv = withoutProviderCredentials(startInput.env);
|
|
2453
|
+
let manifestModelId;
|
|
2454
|
+
try {
|
|
2455
|
+
manifestModelId = subscriptionModel2(startInput.manifest.dispatchSelection);
|
|
2456
|
+
} catch (cause) {
|
|
2457
|
+
throw new RuntimeExecutionFailure({
|
|
2458
|
+
phase: "start",
|
|
2459
|
+
category: "authority",
|
|
2460
|
+
retry: "non-retryable",
|
|
2461
|
+
reason: "prepared codex operation received an invalid runtime selection manifest"
|
|
2462
|
+
}, { cause });
|
|
2463
|
+
}
|
|
2464
|
+
if (manifestModelId !== modelId) {
|
|
2465
|
+
throw new RuntimeExecutionFailure({
|
|
2466
|
+
phase: "start",
|
|
2467
|
+
category: "authority",
|
|
2468
|
+
retry: "non-retryable",
|
|
2469
|
+
reason: "prepared codex operation received a manifest with different runtime selection"
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
1785
2472
|
const { sessionRef, runner } = await runCodexTurn({
|
|
1786
|
-
command
|
|
1787
|
-
resumeRef:
|
|
1788
|
-
instruction:
|
|
1789
|
-
modelId,
|
|
1790
|
-
policyArgs:
|
|
1791
|
-
cwd:
|
|
2473
|
+
command,
|
|
2474
|
+
resumeRef: startInput.manifest.sessionRef,
|
|
2475
|
+
instruction: startInput.instruction,
|
|
2476
|
+
modelId: manifestModelId,
|
|
2477
|
+
policyArgs: [...policyArgs],
|
|
2478
|
+
cwd: startInput.manifest.workspace.workspaceDir,
|
|
1792
2479
|
env: runtimeEnv,
|
|
1793
2480
|
spawnFn: this.options.spawnFn,
|
|
1794
2481
|
workspaceDir,
|
|
1795
2482
|
queue,
|
|
1796
2483
|
recordUnmapped,
|
|
1797
|
-
expectedSessionRef:
|
|
1798
|
-
preparedGit:
|
|
2484
|
+
expectedSessionRef: startInput.manifest.sessionRef,
|
|
2485
|
+
preparedGit: startInput.manifest.workspace.workspaceId !== void 0,
|
|
2486
|
+
failurePhase: "start",
|
|
2487
|
+
terminal
|
|
1799
2488
|
});
|
|
1800
2489
|
return new CodexSession({
|
|
1801
2490
|
sessionRef,
|
|
1802
|
-
command
|
|
2491
|
+
command,
|
|
1803
2492
|
workspaceDir,
|
|
1804
|
-
env:
|
|
2493
|
+
env: startInput.env,
|
|
1805
2494
|
spawnFn: this.options.spawnFn,
|
|
1806
2495
|
queue,
|
|
1807
2496
|
recordUnmapped,
|
|
1808
2497
|
initialRunner: runner,
|
|
1809
|
-
preparedGit:
|
|
1810
|
-
modelId
|
|
2498
|
+
preparedGit: startInput.manifest.workspace.workspaceId !== void 0,
|
|
2499
|
+
modelId: manifestModelId,
|
|
2500
|
+
terminal
|
|
1811
2501
|
});
|
|
1812
2502
|
}
|
|
1813
2503
|
resolveBin() {
|
|
@@ -1855,37 +2545,69 @@ async function runCodexTurn(params) {
|
|
|
1855
2545
|
rejectFirstLine = reject;
|
|
1856
2546
|
});
|
|
1857
2547
|
let turnEnded = false;
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
if (
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
2548
|
+
let runner;
|
|
2549
|
+
try {
|
|
2550
|
+
runner = new CodexProcessRunner({
|
|
2551
|
+
command: params.command,
|
|
2552
|
+
args: argv,
|
|
2553
|
+
cwd: params.cwd,
|
|
2554
|
+
env: params.env,
|
|
2555
|
+
spawnFn: params.spawnFn,
|
|
2556
|
+
onEvent: (evt) => {
|
|
2557
|
+
if (!firstLineSettled) {
|
|
2558
|
+
firstLineSettled = true;
|
|
2559
|
+
if (evt.type === "thread.started" && typeof evt.thread_id === "string" && evt.thread_id.length > 0) {
|
|
2560
|
+
resolveFirstLine(evt.thread_id);
|
|
2561
|
+
} else {
|
|
2562
|
+
rejectFirstLine(
|
|
2563
|
+
new RuntimeExecutionFailure({
|
|
2564
|
+
phase: params.failurePhase,
|
|
2565
|
+
category: "authority",
|
|
2566
|
+
retry: "non-retryable",
|
|
2567
|
+
reason: `codex did not yield thread.started as its first event (got ${JSON.stringify(evt).slice(0, 200)})`
|
|
2568
|
+
})
|
|
2569
|
+
);
|
|
2570
|
+
}
|
|
2571
|
+
return;
|
|
2572
|
+
}
|
|
2573
|
+
const mapped = mapCodexEventToAgentEvents(evt, params.workspaceDir);
|
|
2574
|
+
for (const agentEvent of mapped) {
|
|
2575
|
+
if (agentEvent.type === "turn_end") turnEnded = true;
|
|
2576
|
+
params.queue.push(agentEvent);
|
|
2577
|
+
}
|
|
2578
|
+
if (evt.type === "turn.failed") {
|
|
2579
|
+
params.terminal.failure = new RuntimeExecutionFailure({
|
|
2580
|
+
phase: "run",
|
|
2581
|
+
category: "semantic",
|
|
2582
|
+
retry: "non-retryable",
|
|
2583
|
+
reason: "codex reported terminal task failure"
|
|
2584
|
+
});
|
|
2585
|
+
params.queue.end();
|
|
2586
|
+
}
|
|
2587
|
+
if (mapped.length === 0 && !isRoutineCodexEvent(evt)) {
|
|
2588
|
+
params.recordUnmapped(unmappedFrameKey(evt));
|
|
1873
2589
|
}
|
|
1874
|
-
return;
|
|
1875
|
-
}
|
|
1876
|
-
const mapped = mapCodexEventToAgentEvents(evt, params.workspaceDir);
|
|
1877
|
-
for (const agentEvent of mapped) {
|
|
1878
|
-
if (agentEvent.type === "turn_end") turnEnded = true;
|
|
1879
|
-
params.queue.push(agentEvent);
|
|
1880
|
-
}
|
|
1881
|
-
if (mapped.length === 0 && !isRoutineCodexEvent(evt)) {
|
|
1882
|
-
params.recordUnmapped(unmappedFrameKey(evt));
|
|
1883
2590
|
}
|
|
1884
|
-
}
|
|
1885
|
-
})
|
|
2591
|
+
});
|
|
2592
|
+
} catch (cause) {
|
|
2593
|
+
throw new RuntimeExecutionFailure({
|
|
2594
|
+
phase: params.failurePhase,
|
|
2595
|
+
category: "infrastructure",
|
|
2596
|
+
retry: "retryable",
|
|
2597
|
+
reason: "codex runtime process could not be spawned"
|
|
2598
|
+
}, { cause });
|
|
2599
|
+
}
|
|
2600
|
+
params.onRunnerCreated?.(runner);
|
|
1886
2601
|
void runner.waitClosed().then(() => {
|
|
1887
|
-
if (turnEnded) return;
|
|
1888
|
-
|
|
2602
|
+
if (turnEnded || params.terminal.failure) return;
|
|
2603
|
+
const cause = runner.buildExitError("codex exited without completing the turn");
|
|
2604
|
+
params.terminal.failure = new RuntimeExecutionFailure({
|
|
2605
|
+
phase: "run",
|
|
2606
|
+
category: "infrastructure",
|
|
2607
|
+
retry: "retryable",
|
|
2608
|
+
reason: cause.message
|
|
2609
|
+
}, { cause });
|
|
2610
|
+
params.queue.push({ type: "error", message: cause.message });
|
|
1889
2611
|
params.queue.end();
|
|
1890
2612
|
});
|
|
1891
2613
|
let sessionRef;
|
|
@@ -1909,7 +2631,13 @@ async function runCodexTurn(params) {
|
|
|
1909
2631
|
void runner.waitClosed().then(() => {
|
|
1910
2632
|
if (!settled) {
|
|
1911
2633
|
settled = true;
|
|
1912
|
-
|
|
2634
|
+
const cause = runner.buildExitError("codex exited before yielding an authoritative thread id");
|
|
2635
|
+
reject(new RuntimeExecutionFailure({
|
|
2636
|
+
phase: params.failurePhase,
|
|
2637
|
+
category: "infrastructure",
|
|
2638
|
+
retry: "retryable",
|
|
2639
|
+
reason: cause.message
|
|
2640
|
+
}, { cause }));
|
|
1913
2641
|
}
|
|
1914
2642
|
});
|
|
1915
2643
|
});
|
|
@@ -1919,9 +2647,12 @@ async function runCodexTurn(params) {
|
|
|
1919
2647
|
}
|
|
1920
2648
|
if (params.expectedSessionRef !== void 0 && sessionRef !== params.expectedSessionRef) {
|
|
1921
2649
|
runner.kill();
|
|
1922
|
-
throw new
|
|
1923
|
-
|
|
1924
|
-
|
|
2650
|
+
throw new RuntimeExecutionFailure({
|
|
2651
|
+
phase: params.failurePhase,
|
|
2652
|
+
category: "authority",
|
|
2653
|
+
retry: "non-retryable",
|
|
2654
|
+
reason: `codex exec resume echoed a different thread id than requested (requested ${params.expectedSessionRef}, got ${sessionRef})`
|
|
2655
|
+
});
|
|
1925
2656
|
}
|
|
1926
2657
|
return { sessionRef, runner };
|
|
1927
2658
|
}
|
|
@@ -1948,8 +2679,12 @@ var CodexSession = class {
|
|
|
1948
2679
|
recordUnmapped;
|
|
1949
2680
|
preparedGit;
|
|
1950
2681
|
modelId;
|
|
2682
|
+
terminal;
|
|
1951
2683
|
currentRunner;
|
|
2684
|
+
ownedRunners = /* @__PURE__ */ new Set();
|
|
2685
|
+
followUpAttempts = /* @__PURE__ */ new Set();
|
|
1952
2686
|
closed = false;
|
|
2687
|
+
closeAttempt;
|
|
1953
2688
|
constructor(options) {
|
|
1954
2689
|
this.sessionRef = options.sessionRef;
|
|
1955
2690
|
this.command = options.command;
|
|
@@ -1960,11 +2695,36 @@ var CodexSession = class {
|
|
|
1960
2695
|
this.recordUnmapped = options.recordUnmapped;
|
|
1961
2696
|
this.preparedGit = options.preparedGit;
|
|
1962
2697
|
this.modelId = options.modelId;
|
|
2698
|
+
this.terminal = options.terminal;
|
|
1963
2699
|
this.currentRunner = options.initialRunner;
|
|
2700
|
+
this.ownedRunners.add(options.initialRunner);
|
|
1964
2701
|
void this.forgetRunnerOnceClosed(options.initialRunner);
|
|
1965
2702
|
}
|
|
1966
2703
|
get events() {
|
|
1967
|
-
|
|
2704
|
+
const queue = this.queue;
|
|
2705
|
+
const session = this;
|
|
2706
|
+
return {
|
|
2707
|
+
[Symbol.asyncIterator]() {
|
|
2708
|
+
const inner = queue[Symbol.asyncIterator]();
|
|
2709
|
+
return {
|
|
2710
|
+
async next() {
|
|
2711
|
+
let result;
|
|
2712
|
+
try {
|
|
2713
|
+
result = await inner.next();
|
|
2714
|
+
} catch (cause) {
|
|
2715
|
+
throw new RuntimeExecutionFailure({
|
|
2716
|
+
phase: "run",
|
|
2717
|
+
category: "infrastructure",
|
|
2718
|
+
retry: "retryable",
|
|
2719
|
+
reason: "codex runtime event transport failed"
|
|
2720
|
+
}, { cause });
|
|
2721
|
+
}
|
|
2722
|
+
if (result.done && session.terminal.failure) throw session.terminal.failure;
|
|
2723
|
+
return result;
|
|
2724
|
+
}
|
|
2725
|
+
};
|
|
2726
|
+
}
|
|
2727
|
+
};
|
|
1968
2728
|
}
|
|
1969
2729
|
async forgetRunnerOnceClosed(runner) {
|
|
1970
2730
|
await runner.waitClosed();
|
|
@@ -2009,7 +2769,14 @@ var CodexSession = class {
|
|
|
2009
2769
|
* stale id even after codex had moved on) — it just can now only ever be
|
|
2010
2770
|
* the SAME id this call asked to resume, never a silently-different one.
|
|
2011
2771
|
*/
|
|
2012
|
-
|
|
2772
|
+
followUp(task) {
|
|
2773
|
+
const attempt = this.runFollowUp(task);
|
|
2774
|
+
this.followUpAttempts.add(attempt);
|
|
2775
|
+
void attempt.finally(() => this.followUpAttempts.delete(attempt)).catch(() => {
|
|
2776
|
+
});
|
|
2777
|
+
return attempt;
|
|
2778
|
+
}
|
|
2779
|
+
async runFollowUp(task) {
|
|
2013
2780
|
if (typeof task.instruction !== "string") {
|
|
2014
2781
|
throw new PolicyUnsupportedError("codex adapter only supports string instructions in M2 (no blob-ref fetch yet)");
|
|
2015
2782
|
}
|
|
@@ -2020,7 +2787,7 @@ var CodexSession = class {
|
|
|
2020
2787
|
if (!mapping.ok) {
|
|
2021
2788
|
throw new PolicyUnsupportedError(mapping.reason ?? "policy rejected by codex adapter");
|
|
2022
2789
|
}
|
|
2023
|
-
const requestedModel = subscriptionModel2(task);
|
|
2790
|
+
const requestedModel = subscriptionModel2(task.dispatchSelection);
|
|
2024
2791
|
if (requestedModel !== void 0 && requestedModel !== this.modelId) {
|
|
2025
2792
|
throw new PolicyUnsupportedError(
|
|
2026
2793
|
`codex persistent session cannot change model from ${this.modelId ?? "(legacy default)"} to ${requestedModel}`
|
|
@@ -2030,6 +2797,7 @@ var CodexSession = class {
|
|
|
2030
2797
|
const resumeRef = this.sessionRef;
|
|
2031
2798
|
let sessionRef;
|
|
2032
2799
|
let runner;
|
|
2800
|
+
const terminal = {};
|
|
2033
2801
|
try {
|
|
2034
2802
|
({ sessionRef, runner } = await runCodexTurn({
|
|
2035
2803
|
command: this.command,
|
|
@@ -2044,25 +2812,54 @@ var CodexSession = class {
|
|
|
2044
2812
|
queue: this.queue,
|
|
2045
2813
|
recordUnmapped: this.recordUnmapped,
|
|
2046
2814
|
expectedSessionRef: resumeRef,
|
|
2047
|
-
preparedGit: this.preparedGit
|
|
2815
|
+
preparedGit: this.preparedGit,
|
|
2816
|
+
failurePhase: "run",
|
|
2817
|
+
terminal,
|
|
2818
|
+
onRunnerCreated: (created) => {
|
|
2819
|
+
this.ownedRunners.add(created);
|
|
2820
|
+
void this.forgetRunnerOnceClosed(created);
|
|
2821
|
+
}
|
|
2048
2822
|
}));
|
|
2049
2823
|
} catch (err) {
|
|
2050
2824
|
this.queue.end();
|
|
2825
|
+
this.terminal = {
|
|
2826
|
+
failure: isRuntimeExecutionFailure(err) ? err : new RuntimeExecutionFailure({
|
|
2827
|
+
phase: "run",
|
|
2828
|
+
category: "authority",
|
|
2829
|
+
retry: "non-retryable",
|
|
2830
|
+
reason: "codex follow-up violated the runtime adapter contract"
|
|
2831
|
+
}, { cause: err })
|
|
2832
|
+
};
|
|
2051
2833
|
throw err;
|
|
2052
2834
|
}
|
|
2835
|
+
if (this.closed) {
|
|
2836
|
+
await runner.dispose();
|
|
2837
|
+
throw new Error("codex session closed while follow-up was starting");
|
|
2838
|
+
}
|
|
2839
|
+
this.terminal = terminal;
|
|
2053
2840
|
this.sessionRef = sessionRef;
|
|
2054
2841
|
this.currentRunner = runner;
|
|
2055
|
-
void this.forgetRunnerOnceClosed(runner);
|
|
2056
2842
|
}
|
|
2057
2843
|
/** Best-effort abort of the current turn. SIGTERM's the currently-running child, if any — see `process-runner.ts`'s `kill()` doc comment for why SIGTERM (not SIGINT) and why this is safe: the underlying codex thread survives and stays resumable, confirmed empirically. A no-op when no turn is currently in flight. */
|
|
2058
2844
|
async interrupt() {
|
|
2059
2845
|
this.currentRunner?.kill();
|
|
2060
2846
|
}
|
|
2061
2847
|
async close() {
|
|
2062
|
-
if (this.
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2848
|
+
if (!this.closeAttempt) {
|
|
2849
|
+
this.closed = true;
|
|
2850
|
+
this.queue.end();
|
|
2851
|
+
const attempt = (async () => {
|
|
2852
|
+
const runners = [...this.ownedRunners];
|
|
2853
|
+
await Promise.all(runners.map((runner) => runner.dispose()));
|
|
2854
|
+
for (const runner of runners) this.ownedRunners.delete(runner);
|
|
2855
|
+
await Promise.allSettled([...this.followUpAttempts]);
|
|
2856
|
+
})();
|
|
2857
|
+
this.closeAttempt = attempt.catch((error) => {
|
|
2858
|
+
this.closeAttempt = void 0;
|
|
2859
|
+
throw error;
|
|
2860
|
+
});
|
|
2861
|
+
}
|
|
2862
|
+
await this.closeAttempt;
|
|
2066
2863
|
}
|
|
2067
2864
|
/**
|
|
2068
2865
|
* `codex exec` has no in-band channel to inject text into an already-
|
|
@@ -2098,8 +2895,7 @@ var CodexSession = class {
|
|
|
2098
2895
|
);
|
|
2099
2896
|
}
|
|
2100
2897
|
};
|
|
2101
|
-
function subscriptionModel2(
|
|
2102
|
-
const selection = task.dispatchSelection;
|
|
2898
|
+
function subscriptionModel2(selection) {
|
|
2103
2899
|
if (selection === void 0) return void 0;
|
|
2104
2900
|
if (selection.lane !== "subscription" || selection.runtimeId !== "codex") {
|
|
2105
2901
|
throw new PolicyUnsupportedError(
|
|
@@ -2109,6 +2905,6 @@ function subscriptionModel2(task) {
|
|
|
2109
2905
|
return selection.modelId;
|
|
2110
2906
|
}
|
|
2111
2907
|
|
|
2112
|
-
export { ClaudeAdapter, CodexAdapter, PI_PACKAGE_NAME, PiAdapter };
|
|
2908
|
+
export { ClaudeAdapter, CodexAdapter, PI_PACKAGE_NAME, PiAdapter, RuntimeDisposalFailure, RuntimeExecutionFailure };
|
|
2113
2909
|
//# sourceMappingURL=index.js.map
|
|
2114
2910
|
//# sourceMappingURL=index.js.map
|