@byok-sdk/client 0.2.0 → 0.4.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 +58 -5
- package/dist/adapters/claude/claude-adapter.d.ts +6 -19
- package/dist/adapters/claude/events.d.ts +3 -0
- package/dist/adapters/claude/process-client.d.ts +9 -1
- package/dist/adapters/codex/codex-adapter.d.ts +4 -15
- package/dist/adapters/codex/process-runner.d.ts +4 -1
- package/dist/adapters/index.d.ts +4 -2
- package/dist/adapters/index.js +1081 -258
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/pi/pi-adapter.d.ts +24 -15
- package/dist/adapters/pi/rpc-client.d.ts +9 -1
- package/dist/adapters/process-tree.d.ts +19 -0
- package/dist/adapters/provider-credential-environment.d.ts +18 -0
- package/dist/bin/audit-log.d.ts +12 -0
- package/dist/bin/byok-agent.js +2686 -912
- package/dist/bin/byok-agent.js.map +1 -1
- package/dist/bin/byok-approval-mcp.js +2 -2
- 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/assertion-client.d.ts +68 -0
- package/dist/daemon/capabilities-client.d.ts +48 -0
- package/dist/daemon/connection-manager.d.ts +4 -2
- package/dist/daemon/control-protocol.d.ts +81 -4
- package/dist/daemon/control-server.d.ts +18 -1
- package/dist/daemon/create-daemon.d.ts +171 -3
- package/dist/daemon/daemon-owner.d.ts +37 -0
- package/dist/daemon/device-assertion-signer.d.ts +41 -0
- package/dist/daemon/device-keys.d.ts +15 -13
- package/dist/daemon/environment.d.ts +9 -9
- package/dist/daemon/git-workspace.d.ts +21 -0
- package/dist/daemon/observer.d.ts +81 -3
- package/dist/daemon/presence-publisher.d.ts +98 -0
- package/dist/daemon/runtime-capabilities.d.ts +1 -1
- package/dist/daemon/skill-pack-installer.d.ts +116 -0
- package/dist/daemon/task-runner.d.ts +156 -37
- package/dist/daemon/ws-transport.d.ts +3 -1
- package/dist/index.d.ts +25 -4
- package/dist/index.js +2972 -597
- package/dist/index.js.map +1 -1
- package/dist/runtime-failure.d.ts +64 -0
- package/dist/types.d.ts +114 -58
- package/package.json +4 -4
package/dist/adapters/index.js
CHANGED
|
@@ -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);
|
|
@@ -267,6 +338,155 @@ var AsyncQueue = class {
|
|
|
267
338
|
};
|
|
268
339
|
}
|
|
269
340
|
};
|
|
341
|
+
var DEFAULT_TERM_GRACE_MS = 750;
|
|
342
|
+
var DEFAULT_KILL_GRACE_MS = 2e3;
|
|
343
|
+
var POLL_MS = 20;
|
|
344
|
+
var terminationRequested = /* @__PURE__ */ new WeakSet();
|
|
345
|
+
var terminationRequestFailed = /* @__PURE__ */ new WeakSet();
|
|
346
|
+
function withOwnedProcessTree(options) {
|
|
347
|
+
return {
|
|
348
|
+
...options,
|
|
349
|
+
...process.platform === "win32" ? { windowsHide: true } : { detached: true }
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
function positivePid(child, label) {
|
|
353
|
+
const pid = child.pid;
|
|
354
|
+
if (pid === void 0) return void 0;
|
|
355
|
+
if (!Number.isSafeInteger(pid) || pid <= 0 || pid === process.pid) {
|
|
356
|
+
throw new RuntimeDisposalFailure({
|
|
357
|
+
stage: "signal",
|
|
358
|
+
reason: `${label} runtime process has an unsafe owned pid`
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
return pid;
|
|
362
|
+
}
|
|
363
|
+
function groupExists(pid, label) {
|
|
364
|
+
try {
|
|
365
|
+
process.kill(-pid, 0);
|
|
366
|
+
return true;
|
|
367
|
+
} catch (cause) {
|
|
368
|
+
const code = cause.code;
|
|
369
|
+
if (code === "ESRCH") return false;
|
|
370
|
+
if (code === "EPERM") return true;
|
|
371
|
+
throw new RuntimeDisposalFailure({
|
|
372
|
+
stage: "quiescence",
|
|
373
|
+
reason: `${label} runtime process-group state could not be verified`
|
|
374
|
+
}, { cause });
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function signalGroup(pid, signal, label) {
|
|
378
|
+
try {
|
|
379
|
+
process.kill(-pid, signal);
|
|
380
|
+
} catch (cause) {
|
|
381
|
+
const code = cause.code;
|
|
382
|
+
if (code === "ESRCH" || code === "EPERM") return;
|
|
383
|
+
throw new RuntimeDisposalFailure({
|
|
384
|
+
stage: "signal",
|
|
385
|
+
reason: `${label} runtime process group ${pid} could not receive ${signal} (${code ?? "unknown"})`
|
|
386
|
+
}, { cause });
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
async function waitUntil(predicate, timeoutMs) {
|
|
390
|
+
const deadline = Date.now() + timeoutMs;
|
|
391
|
+
while (predicate()) {
|
|
392
|
+
if (Date.now() >= deadline) return false;
|
|
393
|
+
await new Promise((resolve) => {
|
|
394
|
+
setTimeout(resolve, POLL_MS);
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
async function waitWithDeadline(promise, timeoutMs) {
|
|
400
|
+
return new Promise((resolve) => {
|
|
401
|
+
let settled = false;
|
|
402
|
+
const timer = setTimeout(() => {
|
|
403
|
+
if (!settled) {
|
|
404
|
+
settled = true;
|
|
405
|
+
resolve(false);
|
|
406
|
+
}
|
|
407
|
+
}, timeoutMs);
|
|
408
|
+
void promise.then(
|
|
409
|
+
() => {
|
|
410
|
+
if (!settled) {
|
|
411
|
+
settled = true;
|
|
412
|
+
clearTimeout(timer);
|
|
413
|
+
resolve(true);
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
() => {
|
|
417
|
+
if (!settled) {
|
|
418
|
+
settled = true;
|
|
419
|
+
clearTimeout(timer);
|
|
420
|
+
resolve(false);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
);
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
function requestOwnedProcessTreeTermination(options) {
|
|
427
|
+
if (options.isClosed()) return;
|
|
428
|
+
const pid = positivePid(options.child, options.label);
|
|
429
|
+
if (pid === void 0) return;
|
|
430
|
+
if (process.platform === "win32") {
|
|
431
|
+
const result = spawnSync("taskkill", ["/PID", String(pid), "/T", "/F"], { windowsHide: true });
|
|
432
|
+
if (result.error) {
|
|
433
|
+
throw new RuntimeDisposalFailure({
|
|
434
|
+
stage: "signal",
|
|
435
|
+
reason: `${options.label} runtime process tree could not be terminated`
|
|
436
|
+
}, { cause: result.error });
|
|
437
|
+
}
|
|
438
|
+
terminationRequested.add(options.child);
|
|
439
|
+
if (result.status !== 0) terminationRequestFailed.add(options.child);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
signalGroup(pid, "SIGTERM", options.label);
|
|
443
|
+
terminationRequested.add(options.child);
|
|
444
|
+
}
|
|
445
|
+
async function disposeOwnedProcessTree(options) {
|
|
446
|
+
const pid = positivePid(options.child, options.label);
|
|
447
|
+
const termGraceMs = options.termGraceMs ?? DEFAULT_TERM_GRACE_MS;
|
|
448
|
+
const killGraceMs = options.killGraceMs ?? DEFAULT_KILL_GRACE_MS;
|
|
449
|
+
if (pid === void 0) {
|
|
450
|
+
if (await waitWithDeadline(options.waitClosed(), killGraceMs)) return;
|
|
451
|
+
throw new RuntimeDisposalFailure({
|
|
452
|
+
stage: "quiescence",
|
|
453
|
+
reason: `${options.label} runtime process did not settle after spawn failure`
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
if (process.platform === "win32") {
|
|
457
|
+
if (!options.isClosed() && !terminationRequested.has(options.child)) requestOwnedProcessTreeTermination(options);
|
|
458
|
+
if (await waitWithDeadline(options.waitClosed(), killGraceMs)) return;
|
|
459
|
+
if (terminationRequestFailed.has(options.child)) {
|
|
460
|
+
throw new RuntimeDisposalFailure({
|
|
461
|
+
stage: "signal",
|
|
462
|
+
reason: `${options.label} runtime process tree could not be terminated`
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
throw new RuntimeDisposalFailure({
|
|
466
|
+
stage: "quiescence",
|
|
467
|
+
reason: `${options.label} runtime process tree did not close before the disposal deadline`
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
if (groupExists(pid, options.label) && !terminationRequested.has(options.child)) {
|
|
471
|
+
signalGroup(pid, "SIGTERM", options.label);
|
|
472
|
+
terminationRequested.add(options.child);
|
|
473
|
+
}
|
|
474
|
+
if (!await waitUntil(() => groupExists(pid, options.label), termGraceMs)) {
|
|
475
|
+
signalGroup(pid, "SIGKILL", options.label);
|
|
476
|
+
if (!await waitUntil(() => groupExists(pid, options.label), killGraceMs)) {
|
|
477
|
+
throw new RuntimeDisposalFailure({
|
|
478
|
+
stage: "quiescence",
|
|
479
|
+
reason: `${options.label} runtime process group remained live after SIGKILL`
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (!await waitWithDeadline(options.waitClosed(), killGraceMs)) {
|
|
484
|
+
throw new RuntimeDisposalFailure({
|
|
485
|
+
stage: "quiescence",
|
|
486
|
+
reason: `${options.label} runtime root did not emit close after its process group exited`
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
}
|
|
270
490
|
|
|
271
491
|
// src/adapters/pi/rpc-client.ts
|
|
272
492
|
var STDERR_RING_CAPACITY = 20;
|
|
@@ -279,16 +499,22 @@ var PiRpcClient = class {
|
|
|
279
499
|
eventQueue = new AsyncQueue();
|
|
280
500
|
closed = false;
|
|
281
501
|
exitError;
|
|
502
|
+
closedPromise;
|
|
503
|
+
resolveClosed;
|
|
504
|
+
disposalAttempt;
|
|
282
505
|
/** 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
506
|
stderrRing = [];
|
|
284
507
|
/** 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
508
|
unmappedFrameCounts = /* @__PURE__ */ new Map();
|
|
286
509
|
constructor(options) {
|
|
287
510
|
const spawnFn = options.spawnFn ?? spawn;
|
|
288
|
-
this.child = spawnFn(options.command, options.args, {
|
|
511
|
+
this.child = spawnFn(options.command, options.args, withOwnedProcessTree({
|
|
289
512
|
cwd: options.cwd,
|
|
290
513
|
env: options.env,
|
|
291
514
|
stdio: ["pipe", "pipe", "pipe"]
|
|
515
|
+
}));
|
|
516
|
+
this.closedPromise = new Promise((resolve) => {
|
|
517
|
+
this.resolveClosed = resolve;
|
|
292
518
|
});
|
|
293
519
|
this.child.stdout.setEncoding("utf8");
|
|
294
520
|
this.child.stdout.on("data", (chunk) => this.onData(chunk));
|
|
@@ -323,6 +549,10 @@ var PiRpcClient = class {
|
|
|
323
549
|
get events() {
|
|
324
550
|
return this.eventQueue;
|
|
325
551
|
}
|
|
552
|
+
/** Local transport diagnostic retained when the process closes; consumers must classify it explicitly. */
|
|
553
|
+
get terminalError() {
|
|
554
|
+
return this.exitError;
|
|
555
|
+
}
|
|
326
556
|
/**
|
|
327
557
|
* Record a pi RPC message `type` that `PiSession` (pi-adapter.ts) decided
|
|
328
558
|
* has no `AgentEvent` mapping and isn't routine bookkeeping (see
|
|
@@ -341,15 +571,30 @@ var PiRpcClient = class {
|
|
|
341
571
|
);
|
|
342
572
|
}
|
|
343
573
|
}
|
|
344
|
-
/**
|
|
574
|
+
/** Immediate process-tree termination request. `dispose()` is the settlement receipt. */
|
|
345
575
|
kill() {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
576
|
+
requestOwnedProcessTreeTermination(this.processTreeOptions());
|
|
577
|
+
}
|
|
578
|
+
waitClosed() {
|
|
579
|
+
return this.closedPromise;
|
|
580
|
+
}
|
|
581
|
+
dispose() {
|
|
582
|
+
if (!this.disposalAttempt) {
|
|
583
|
+
const attempt = disposeOwnedProcessTree(this.processTreeOptions());
|
|
584
|
+
this.disposalAttempt = attempt.catch((error) => {
|
|
585
|
+
this.disposalAttempt = void 0;
|
|
586
|
+
throw error;
|
|
587
|
+
});
|
|
352
588
|
}
|
|
589
|
+
return this.disposalAttempt;
|
|
590
|
+
}
|
|
591
|
+
processTreeOptions() {
|
|
592
|
+
return {
|
|
593
|
+
child: this.child,
|
|
594
|
+
waitClosed: () => this.closedPromise,
|
|
595
|
+
isClosed: () => this.closed,
|
|
596
|
+
label: "pi"
|
|
597
|
+
};
|
|
353
598
|
}
|
|
354
599
|
onData(chunk) {
|
|
355
600
|
this.buffer += chunk;
|
|
@@ -435,19 +680,15 @@ var PiRpcClient = class {
|
|
|
435
680
|
if (this.closed) return;
|
|
436
681
|
this.closed = true;
|
|
437
682
|
this.exitError = err;
|
|
683
|
+
this.resolveClosed();
|
|
438
684
|
for (const [, waiter] of this.pending) waiter.reject(err);
|
|
439
685
|
this.pending.clear();
|
|
440
686
|
this.eventQueue.end();
|
|
441
687
|
}
|
|
442
688
|
};
|
|
443
689
|
|
|
444
|
-
// src/adapters/
|
|
445
|
-
var
|
|
446
|
-
var DETECT_TIMEOUT_MS = 5e3;
|
|
447
|
-
function errorMessage(err) {
|
|
448
|
-
return err instanceof Error ? err.message : String(err);
|
|
449
|
-
}
|
|
450
|
-
var KNOWN_PROVIDER_ENV_VARS = [
|
|
690
|
+
// src/adapters/provider-credential-environment.ts
|
|
691
|
+
var PROVIDER_CREDENTIAL_ENV_NAMES = [
|
|
451
692
|
"ANTHROPIC_API_KEY",
|
|
452
693
|
"ANTHROPIC_OAUTH_TOKEN",
|
|
453
694
|
"OPENAI_API_KEY",
|
|
@@ -458,123 +699,288 @@ var KNOWN_PROVIDER_ENV_VARS = [
|
|
|
458
699
|
"MISTRAL_API_KEY",
|
|
459
700
|
"OPENROUTER_API_KEY",
|
|
460
701
|
"XAI_API_KEY",
|
|
461
|
-
// Confirmed against the installed pi's own docs/providers.md ("ZAI |
|
|
462
|
-
// `ZAI_API_KEY` | `zai`") and exercised live against real GLM traffic
|
|
463
|
-
// during this task's acceptance run — omitting it made `authPresent`
|
|
464
|
-
// silently false for a perfectly valid, working z.ai/GLM setup.
|
|
465
702
|
"ZAI_API_KEY"
|
|
466
703
|
];
|
|
704
|
+
var PROVIDER_CREDENTIAL_ENV_DENY_NAMES = [
|
|
705
|
+
...PROVIDER_CREDENTIAL_ENV_NAMES,
|
|
706
|
+
"ANT_LING_API_KEY",
|
|
707
|
+
"NVIDIA_API_KEY",
|
|
708
|
+
"CEREBRAS_API_KEY",
|
|
709
|
+
"CLOUDFLARE_API_KEY",
|
|
710
|
+
"AI_GATEWAY_API_KEY",
|
|
711
|
+
"ZAI_CODING_CN_API_KEY",
|
|
712
|
+
"OPENCODE_API_KEY",
|
|
713
|
+
"RADIUS_API_KEY",
|
|
714
|
+
"FIREWORKS_API_KEY",
|
|
715
|
+
"TOGETHER_API_KEY",
|
|
716
|
+
"BASETEN_API_KEY",
|
|
717
|
+
"KIMI_API_KEY",
|
|
718
|
+
"MINIMAX_API_KEY",
|
|
719
|
+
"MINIMAX_CN_API_KEY",
|
|
720
|
+
"QWEN_TOKEN_PLAN_API_KEY",
|
|
721
|
+
"QWEN_TOKEN_PLAN_CN_API_KEY",
|
|
722
|
+
"XIAOMI_API_KEY",
|
|
723
|
+
"XIAOMI_TOKEN_PLAN_CN_API_KEY",
|
|
724
|
+
"XIAOMI_TOKEN_PLAN_AMS_API_KEY",
|
|
725
|
+
"XIAOMI_TOKEN_PLAN_SGP_API_KEY",
|
|
726
|
+
"AWS_ACCESS_KEY_ID",
|
|
727
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
728
|
+
"AWS_SESSION_TOKEN",
|
|
729
|
+
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
730
|
+
// Reserved by the keys-owned Pi projection. It must never be inherited
|
|
731
|
+
// from the daemon; the launcher deletes any ambient copy and injects only
|
|
732
|
+
// the exact credential it just resolved from OS custody.
|
|
733
|
+
"PI_PROVIDER_API_KEY"
|
|
734
|
+
];
|
|
735
|
+
function withoutProviderCredentials(env) {
|
|
736
|
+
const sanitized = { ...env };
|
|
737
|
+
for (const name of PROVIDER_CREDENTIAL_ENV_DENY_NAMES) {
|
|
738
|
+
delete sanitized[name];
|
|
739
|
+
}
|
|
740
|
+
return sanitized;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// src/adapters/pi/pi-adapter.ts
|
|
744
|
+
var execFileAsync = promisify(execFile);
|
|
745
|
+
var DETECT_TIMEOUT_MS = 5e3;
|
|
746
|
+
function errorMessage(err) {
|
|
747
|
+
return err instanceof Error ? err.message : String(err);
|
|
748
|
+
}
|
|
467
749
|
var PiAdapter = class {
|
|
468
750
|
constructor(options = {}) {
|
|
469
751
|
this.options = options;
|
|
470
752
|
}
|
|
471
753
|
options;
|
|
472
|
-
|
|
754
|
+
descriptor = freezeRuntimeAdapterDescriptor({
|
|
755
|
+
id: "pi",
|
|
756
|
+
supportsDispatchSelection: true,
|
|
757
|
+
capabilities: {
|
|
758
|
+
steer: true,
|
|
759
|
+
resume: true,
|
|
760
|
+
approvalInteractive: false,
|
|
761
|
+
permissionModes: ["auto", "readonly"]
|
|
762
|
+
},
|
|
763
|
+
environmentRequirements: { credentialNames: PROVIDER_CREDENTIAL_ENV_NAMES }
|
|
764
|
+
});
|
|
473
765
|
async detect() {
|
|
474
766
|
try {
|
|
475
767
|
const bin = this.resolveBin();
|
|
476
768
|
const { stdout, stderr } = await execFileAsync(bin.command, ["--version"], { timeout: DETECT_TIMEOUT_MS });
|
|
477
769
|
const version = stdout.trim() || stderr.trim();
|
|
478
|
-
const authPresent =
|
|
770
|
+
const authPresent = PROVIDER_CREDENTIAL_ENV_NAMES.some((name) => process.env[name] !== void 0);
|
|
479
771
|
return { present: true, version, authPresent };
|
|
480
772
|
} catch {
|
|
481
773
|
return { present: false };
|
|
482
774
|
}
|
|
483
775
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
}
|
|
487
|
-
/**
|
|
488
|
-
* M5: pi authenticates to its ~30 supported providers via env-var API
|
|
489
|
-
* keys — `detect()`'s own `authPresent` probe above checks this identical
|
|
490
|
-
* list — so these MUST keep flowing into pi's spawned process or pi auth
|
|
491
|
-
* breaks entirely. `KNOWN_PROVIDER_ENV_VARS` above is the single source
|
|
492
|
-
* of truth, reused here rather than duplicated. No `baseNames`: nothing
|
|
493
|
-
* in this adapter or `rpc-client.ts` reads a pi-specific config-discovery
|
|
494
|
-
* variable beyond the platform baseline (`daemon/environment.ts`).
|
|
495
|
-
*/
|
|
496
|
-
environmentRequirements() {
|
|
497
|
-
return { credentialNames: KNOWN_PROVIDER_ENV_VARS };
|
|
498
|
-
}
|
|
499
|
-
async start(task, ctx) {
|
|
500
|
-
if (typeof task.instruction !== "string") {
|
|
501
|
-
throw new PolicyUnsupportedError("pi adapter only supports string instructions in M0 (no blob-ref fetch yet)");
|
|
502
|
-
}
|
|
503
|
-
const mapping = mapPermissionPolicyToPiArgs(ctx.policy);
|
|
776
|
+
async prepare(input) {
|
|
777
|
+
const mapping = mapPermissionPolicyToPiArgs(input.policy);
|
|
504
778
|
if (!mapping.ok) {
|
|
505
|
-
|
|
779
|
+
return { kind: "reject", reason: mapping.reason ?? "policy rejected by pi adapter", retryable: false };
|
|
506
780
|
}
|
|
507
781
|
const bin = this.resolveBin();
|
|
508
|
-
const
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
rpc.kill();
|
|
520
|
-
throw new Error(typeof response.error === "string" ? response.error : "pi rejected the initial prompt");
|
|
521
|
-
}
|
|
522
|
-
let sessionRef;
|
|
523
|
-
if (resumeSessionId) {
|
|
524
|
-
sessionRef = resumeSessionId;
|
|
525
|
-
} else {
|
|
526
|
-
try {
|
|
527
|
-
sessionRef = await resolveFreshSessionId(rpc);
|
|
528
|
-
} catch (err) {
|
|
529
|
-
rpc.kill();
|
|
530
|
-
throw err;
|
|
782
|
+
const selection = input.offer.dispatchSelection;
|
|
783
|
+
const pinnedSelection = selection === void 0 ? void 0 : Object.freeze({ ...selection });
|
|
784
|
+
let command = bin.command;
|
|
785
|
+
let launcherArgs;
|
|
786
|
+
if (pinnedSelection !== void 0) {
|
|
787
|
+
if (pinnedSelection.lane !== "byok" || pinnedSelection.runtimeId !== "pi") {
|
|
788
|
+
return { kind: "reject", reason: `pi adapter cannot execute ${pinnedSelection.lane} selection for runtime ${pinnedSelection.runtimeId}`, retryable: false };
|
|
789
|
+
}
|
|
790
|
+
const launcher = this.options.byokLauncher;
|
|
791
|
+
if (launcher === void 0) {
|
|
792
|
+
return { kind: "reject", reason: "pi BYOK selection requires a configured credential-custody launcher", retryable: false };
|
|
531
793
|
}
|
|
794
|
+
command = launcher.command;
|
|
795
|
+
launcherArgs = [
|
|
796
|
+
...launcher.args ?? [],
|
|
797
|
+
"--pi-bin",
|
|
798
|
+
bin.command,
|
|
799
|
+
"--profile-db",
|
|
800
|
+
launcher.profileDbPath,
|
|
801
|
+
"--session-dir",
|
|
802
|
+
launcher.sessionDir,
|
|
803
|
+
...launcher.secretServicePrefix ? ["--secret-service-prefix", launcher.secretServicePrefix] : [],
|
|
804
|
+
"--provider",
|
|
805
|
+
pinnedSelection.providerId,
|
|
806
|
+
"--model",
|
|
807
|
+
pinnedSelection.modelId
|
|
808
|
+
];
|
|
532
809
|
}
|
|
533
|
-
return
|
|
810
|
+
return {
|
|
811
|
+
kind: "prepared",
|
|
812
|
+
operation: {
|
|
813
|
+
start: async (startInput) => {
|
|
814
|
+
const manifestSelection = startInput.manifest.dispatchSelection;
|
|
815
|
+
if (!sameDispatchSelection(manifestSelection, pinnedSelection)) {
|
|
816
|
+
throw new RuntimeExecutionFailure({
|
|
817
|
+
phase: "start",
|
|
818
|
+
category: "authority",
|
|
819
|
+
retry: "non-retryable",
|
|
820
|
+
reason: "prepared pi operation received a manifest with different runtime selection"
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
if (typeof startInput.instruction !== "string") {
|
|
824
|
+
throw new RuntimeExecutionFailure({
|
|
825
|
+
phase: "start",
|
|
826
|
+
category: "authority",
|
|
827
|
+
retry: "non-retryable",
|
|
828
|
+
reason: "prepared pi operation requires a resolved string instruction"
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
const resumeSessionId = startInput.manifest.sessionRef;
|
|
832
|
+
const piArgs = ["--mode", "rpc", ...resumeSessionId ? ["--session", resumeSessionId] : [], ...mapping.args];
|
|
833
|
+
const args = launcherArgs === void 0 ? piArgs : [...launcherArgs, "--", ...piArgs];
|
|
834
|
+
let rpc;
|
|
835
|
+
try {
|
|
836
|
+
rpc = new PiRpcClient({
|
|
837
|
+
command,
|
|
838
|
+
args,
|
|
839
|
+
cwd: startInput.manifest.workspace.workspaceDir,
|
|
840
|
+
env: manifestSelection === void 0 ? startInput.env : withoutProviderCredentials(startInput.env),
|
|
841
|
+
spawnFn: this.options.spawnFn
|
|
842
|
+
});
|
|
843
|
+
} catch (cause) {
|
|
844
|
+
throw new RuntimeExecutionFailure({
|
|
845
|
+
phase: "start",
|
|
846
|
+
category: "infrastructure",
|
|
847
|
+
retry: "retryable",
|
|
848
|
+
reason: "pi runtime process could not be spawned"
|
|
849
|
+
}, { cause });
|
|
850
|
+
}
|
|
851
|
+
let response;
|
|
852
|
+
try {
|
|
853
|
+
response = await rpc.send({ type: "prompt", message: startInput.instruction });
|
|
854
|
+
} catch (cause) {
|
|
855
|
+
rpc.kill();
|
|
856
|
+
throw new RuntimeExecutionFailure({
|
|
857
|
+
phase: "start",
|
|
858
|
+
category: "infrastructure",
|
|
859
|
+
retry: "retryable",
|
|
860
|
+
reason: `pi initial prompt transport failed: ${errorMessage(cause)}`
|
|
861
|
+
}, { cause });
|
|
862
|
+
}
|
|
863
|
+
if (response.success === false) {
|
|
864
|
+
rpc.kill();
|
|
865
|
+
throw new RuntimeExecutionFailure({
|
|
866
|
+
phase: "start",
|
|
867
|
+
category: "semantic",
|
|
868
|
+
retry: "non-retryable",
|
|
869
|
+
reason: typeof response.error === "string" ? response.error : "pi rejected the initial prompt"
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
let sessionRef;
|
|
873
|
+
try {
|
|
874
|
+
sessionRef = await resolveAuthoritativeSessionId(rpc);
|
|
875
|
+
} catch (err) {
|
|
876
|
+
rpc.kill();
|
|
877
|
+
throw err;
|
|
878
|
+
}
|
|
879
|
+
if (resumeSessionId !== void 0 && sessionRef !== resumeSessionId) {
|
|
880
|
+
rpc.kill();
|
|
881
|
+
throw new RuntimeExecutionFailure({
|
|
882
|
+
phase: "start",
|
|
883
|
+
category: "authority",
|
|
884
|
+
retry: "non-retryable",
|
|
885
|
+
reason: "pi resumed a different authoritative session than requested"
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
return new PiSession(sessionRef, rpc, manifestSelection);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
};
|
|
534
892
|
}
|
|
535
893
|
resolveBin() {
|
|
536
894
|
return (this.options.resolveBin ?? resolvePiBin)();
|
|
537
895
|
}
|
|
538
896
|
};
|
|
539
|
-
|
|
897
|
+
function sameDispatchSelection(left, right) {
|
|
898
|
+
if (left === void 0 || right === void 0) return left === right;
|
|
899
|
+
return left.lane === right.lane && left.runtimeId === right.runtimeId && left.providerId === right.providerId && left.modelId === right.modelId;
|
|
900
|
+
}
|
|
901
|
+
async function resolveAuthoritativeSessionId(rpc) {
|
|
540
902
|
let state;
|
|
541
903
|
try {
|
|
542
904
|
state = await rpc.send({ type: "get_state" });
|
|
543
905
|
} catch (err) {
|
|
544
|
-
|
|
906
|
+
if (isRuntimeExecutionFailure(err)) throw err;
|
|
907
|
+
throw new RuntimeExecutionFailure({
|
|
908
|
+
phase: "start",
|
|
909
|
+
category: "infrastructure",
|
|
910
|
+
retry: "retryable",
|
|
911
|
+
reason: `pi transport ended before yielding an authoritative session id: ${errorMessage(err)}`
|
|
912
|
+
}, {
|
|
545
913
|
cause: err
|
|
546
914
|
});
|
|
547
915
|
}
|
|
548
916
|
if (state.success === false) {
|
|
549
917
|
const reason = typeof state.error === "string" ? state.error : "get_state reported failure";
|
|
550
|
-
throw new
|
|
918
|
+
throw new RuntimeExecutionFailure({
|
|
919
|
+
phase: "start",
|
|
920
|
+
category: "authority",
|
|
921
|
+
retry: "non-retryable",
|
|
922
|
+
reason: `pi did not yield an authoritative session id: ${reason}`
|
|
923
|
+
});
|
|
551
924
|
}
|
|
552
925
|
const data = state.data;
|
|
553
926
|
if (typeof data?.sessionId === "string" && data.sessionId.length > 0) {
|
|
554
927
|
return data.sessionId;
|
|
555
928
|
}
|
|
556
|
-
throw new
|
|
557
|
-
|
|
558
|
-
|
|
929
|
+
throw new RuntimeExecutionFailure({
|
|
930
|
+
phase: "start",
|
|
931
|
+
category: "authority",
|
|
932
|
+
retry: "non-retryable",
|
|
933
|
+
reason: "pi get_state reported no authoritative session id"
|
|
934
|
+
});
|
|
559
935
|
}
|
|
560
936
|
var PiSession = class {
|
|
561
|
-
constructor(sessionRef, rpc) {
|
|
937
|
+
constructor(sessionRef, rpc, selection) {
|
|
562
938
|
this.sessionRef = sessionRef;
|
|
563
939
|
this.rpc = rpc;
|
|
940
|
+
this.selection = selection;
|
|
564
941
|
}
|
|
565
942
|
sessionRef;
|
|
566
943
|
rpc;
|
|
944
|
+
selection;
|
|
567
945
|
get events() {
|
|
568
946
|
const rpc = this.rpc;
|
|
569
947
|
return {
|
|
570
948
|
[Symbol.asyncIterator]() {
|
|
571
949
|
const inner = rpc.events[Symbol.asyncIterator]();
|
|
950
|
+
let terminalFailure;
|
|
572
951
|
return {
|
|
573
952
|
async next() {
|
|
574
953
|
for (; ; ) {
|
|
575
|
-
|
|
576
|
-
|
|
954
|
+
if (terminalFailure) throw terminalFailure;
|
|
955
|
+
let result;
|
|
956
|
+
try {
|
|
957
|
+
result = await inner.next();
|
|
958
|
+
} catch (cause) {
|
|
959
|
+
throw new RuntimeExecutionFailure({
|
|
960
|
+
phase: "run",
|
|
961
|
+
category: "infrastructure",
|
|
962
|
+
retry: "retryable",
|
|
963
|
+
reason: "pi runtime event transport failed"
|
|
964
|
+
}, { cause });
|
|
965
|
+
}
|
|
966
|
+
const { value, done } = result;
|
|
967
|
+
if (done) {
|
|
968
|
+
throw new RuntimeExecutionFailure({
|
|
969
|
+
phase: "run",
|
|
970
|
+
category: "infrastructure",
|
|
971
|
+
retry: "retryable",
|
|
972
|
+
reason: "pi runtime process ended before agent_settled"
|
|
973
|
+
}, { cause: rpc.terminalError });
|
|
974
|
+
}
|
|
577
975
|
const mapped = mapPiMessageToAgentEvent(value);
|
|
976
|
+
if (value.type === "auto_retry_end" && value.success === false) {
|
|
977
|
+
terminalFailure = new RuntimeExecutionFailure({
|
|
978
|
+
phase: "run",
|
|
979
|
+
category: "semantic",
|
|
980
|
+
retry: "non-retryable",
|
|
981
|
+
reason: "pi exhausted its native retry policy"
|
|
982
|
+
});
|
|
983
|
+
}
|
|
578
984
|
if (mapped) return { value: mapped, done: false };
|
|
579
985
|
if (!ROUTINE_PI_EVENT_TYPES.has(value.type)) {
|
|
580
986
|
rpc.recordUnmappedFrame(value.type);
|
|
@@ -592,13 +998,19 @@ var PiSession = class {
|
|
|
592
998
|
if (typeof task.instruction !== "string") {
|
|
593
999
|
throw new PolicyUnsupportedError("pi adapter only supports string instructions in M0 (no blob-ref fetch yet)");
|
|
594
1000
|
}
|
|
1001
|
+
const requestedSelection = task.dispatchSelection;
|
|
1002
|
+
if (requestedSelection !== void 0 && (this.selection?.lane !== "byok" || requestedSelection.lane !== "byok" || requestedSelection.runtimeId !== "pi" || requestedSelection.providerId !== this.selection.providerId || requestedSelection.modelId !== this.selection.modelId)) {
|
|
1003
|
+
throw new PolicyUnsupportedError(
|
|
1004
|
+
"pi persistent session cannot change its authoritative BYOK provider/model selection"
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
595
1007
|
await this.rpc.send({ type: "prompt", message: task.instruction, streamingBehavior: "followUp" });
|
|
596
1008
|
}
|
|
597
1009
|
async interrupt() {
|
|
598
1010
|
await this.rpc.send({ type: "abort" });
|
|
599
1011
|
}
|
|
600
1012
|
async close() {
|
|
601
|
-
this.rpc.
|
|
1013
|
+
await this.rpc.dispose();
|
|
602
1014
|
}
|
|
603
1015
|
async resolveApproval() {
|
|
604
1016
|
throw new Error("pi adapter does not support approval resume: pi never emits needs_approval in M0/M1");
|
|
@@ -810,7 +1222,15 @@ function mapResult(msg) {
|
|
|
810
1222
|
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`
|
|
811
1223
|
);
|
|
812
1224
|
const events = usageEvent ? [usageEvent, { type: "error", message }] : [{ type: "error", message }];
|
|
813
|
-
return {
|
|
1225
|
+
return {
|
|
1226
|
+
events,
|
|
1227
|
+
terminalFailure: new RuntimeExecutionFailure({
|
|
1228
|
+
phase: "run",
|
|
1229
|
+
category: msg.is_error === true ? "semantic" : "authority",
|
|
1230
|
+
retry: "non-retryable",
|
|
1231
|
+
reason: msg.is_error === true ? "claude reported terminal task failure" : "claude emitted a malformed terminal result frame"
|
|
1232
|
+
})
|
|
1233
|
+
};
|
|
814
1234
|
}
|
|
815
1235
|
var RESULT_DIAGNOSTIC_MAX_CHARS = 2e3;
|
|
816
1236
|
function truncateResultDiagnostic(text) {
|
|
@@ -862,16 +1282,22 @@ var ClaudeProcessClient = class {
|
|
|
862
1282
|
eventQueue = new AsyncQueue();
|
|
863
1283
|
closed = false;
|
|
864
1284
|
exitError;
|
|
1285
|
+
closedPromise;
|
|
1286
|
+
resolveClosed;
|
|
1287
|
+
disposalAttempt;
|
|
865
1288
|
stderrRing = [];
|
|
866
1289
|
unmappedFrameCounts = /* @__PURE__ */ new Map();
|
|
867
1290
|
sessionId;
|
|
868
1291
|
initWaiter;
|
|
869
1292
|
constructor(options) {
|
|
870
1293
|
const spawnFn = options.spawnFn ?? spawn;
|
|
871
|
-
this.child = spawnFn(options.command, options.args, {
|
|
1294
|
+
this.child = spawnFn(options.command, options.args, withOwnedProcessTree({
|
|
872
1295
|
cwd: options.cwd,
|
|
873
1296
|
env: options.env,
|
|
874
1297
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1298
|
+
}));
|
|
1299
|
+
this.closedPromise = new Promise((resolve) => {
|
|
1300
|
+
this.resolveClosed = resolve;
|
|
875
1301
|
});
|
|
876
1302
|
this.child.stdout.setEncoding("utf8");
|
|
877
1303
|
this.child.stdout.on("data", (chunk) => this.onData(chunk));
|
|
@@ -927,6 +1353,10 @@ var ClaudeProcessClient = class {
|
|
|
927
1353
|
get events() {
|
|
928
1354
|
return this.eventQueue;
|
|
929
1355
|
}
|
|
1356
|
+
/** Local transport diagnostic retained when the process closes; consumers classify it at the session boundary. */
|
|
1357
|
+
get terminalError() {
|
|
1358
|
+
return this.exitError;
|
|
1359
|
+
}
|
|
930
1360
|
/**
|
|
931
1361
|
* Record a claude stream-json frame/subtype/content-block label that
|
|
932
1362
|
* `ClaudeSession`'s event iterator (`../claude-adapter.ts`) decided has
|
|
@@ -946,15 +1376,30 @@ var ClaudeProcessClient = class {
|
|
|
946
1376
|
);
|
|
947
1377
|
}
|
|
948
1378
|
}
|
|
949
|
-
/**
|
|
1379
|
+
/** Immediate process-tree termination request. `dispose()` is the settlement receipt. */
|
|
950
1380
|
kill() {
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1381
|
+
requestOwnedProcessTreeTermination(this.processTreeOptions());
|
|
1382
|
+
}
|
|
1383
|
+
waitClosed() {
|
|
1384
|
+
return this.closedPromise;
|
|
1385
|
+
}
|
|
1386
|
+
dispose() {
|
|
1387
|
+
if (!this.disposalAttempt) {
|
|
1388
|
+
const attempt = disposeOwnedProcessTree(this.processTreeOptions());
|
|
1389
|
+
this.disposalAttempt = attempt.catch((error) => {
|
|
1390
|
+
this.disposalAttempt = void 0;
|
|
1391
|
+
throw error;
|
|
1392
|
+
});
|
|
957
1393
|
}
|
|
1394
|
+
return this.disposalAttempt;
|
|
1395
|
+
}
|
|
1396
|
+
processTreeOptions() {
|
|
1397
|
+
return {
|
|
1398
|
+
child: this.child,
|
|
1399
|
+
waitClosed: () => this.closedPromise,
|
|
1400
|
+
isClosed: () => this.closed,
|
|
1401
|
+
label: "claude"
|
|
1402
|
+
};
|
|
958
1403
|
}
|
|
959
1404
|
onData(chunk) {
|
|
960
1405
|
this.buffer += chunk;
|
|
@@ -1005,6 +1450,7 @@ var ClaudeProcessClient = class {
|
|
|
1005
1450
|
if (this.closed) return;
|
|
1006
1451
|
this.closed = true;
|
|
1007
1452
|
this.exitError = err;
|
|
1453
|
+
this.resolveClosed();
|
|
1008
1454
|
this.initWaiter?.reject(err);
|
|
1009
1455
|
this.initWaiter = void 0;
|
|
1010
1456
|
this.eventQueue.end();
|
|
@@ -1016,17 +1462,37 @@ var APPROVAL_TOOL_NAME = "approval_prompt";
|
|
|
1016
1462
|
var APPROVAL_MCP_SERVER_NAME = "byokapproval";
|
|
1017
1463
|
var execFileAsync2 = promisify(execFile);
|
|
1018
1464
|
var DETECT_TIMEOUT_MS2 = 5e3;
|
|
1019
|
-
|
|
1465
|
+
function errorMessage2(err) {
|
|
1466
|
+
return err instanceof Error ? err.message : String(err);
|
|
1467
|
+
}
|
|
1468
|
+
async function cleanupMcpConfigDir(dir) {
|
|
1020
1469
|
if (!dir) return;
|
|
1021
|
-
|
|
1022
|
-
|
|
1470
|
+
try {
|
|
1471
|
+
await promises.rm(dir, { recursive: true, force: true });
|
|
1472
|
+
} catch (cause) {
|
|
1473
|
+
throw new RuntimeDisposalFailure({
|
|
1474
|
+
stage: "cleanup",
|
|
1475
|
+
reason: "claude task-scoped MCP configuration could not be removed"
|
|
1476
|
+
}, { cause });
|
|
1477
|
+
}
|
|
1023
1478
|
}
|
|
1024
1479
|
var ClaudeAdapter = class {
|
|
1025
1480
|
constructor(options = {}) {
|
|
1026
1481
|
this.options = options;
|
|
1027
1482
|
}
|
|
1028
1483
|
options;
|
|
1029
|
-
|
|
1484
|
+
descriptor = freezeRuntimeAdapterDescriptor({
|
|
1485
|
+
id: "claude",
|
|
1486
|
+
supportsDispatchSelection: true,
|
|
1487
|
+
capabilities: {
|
|
1488
|
+
steer: false,
|
|
1489
|
+
resume: true,
|
|
1490
|
+
approvalInteractive: true,
|
|
1491
|
+
mcpToolsets: true,
|
|
1492
|
+
permissionModes: ["auto", "readonly", "plan", "confirm"]
|
|
1493
|
+
},
|
|
1494
|
+
environmentRequirements: { credentialNames: [] }
|
|
1495
|
+
});
|
|
1030
1496
|
async detect() {
|
|
1031
1497
|
const bin = this.resolveBin();
|
|
1032
1498
|
try {
|
|
@@ -1038,74 +1504,137 @@ var ClaudeAdapter = class {
|
|
|
1038
1504
|
return { present: false };
|
|
1039
1505
|
}
|
|
1040
1506
|
}
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
* passthrough for claude is a separate, still-pending product decision.
|
|
1050
|
-
* A product that genuinely needs it can opt in locally per-device via
|
|
1051
|
-
* `DaemonConfig.runtimeEnvironment.claude.allow` (`create-daemon.ts`).
|
|
1052
|
-
* `baseNames` is empty too: nothing in this adapter reads a
|
|
1053
|
-
* claude-specific config-discovery variable (e.g. `CLAUDE_CONFIG_DIR`)
|
|
1054
|
-
* today — if a future version of this adapter starts reading one, it
|
|
1055
|
-
* belongs here, not left to rely on the platform baseline alone.
|
|
1056
|
-
*/
|
|
1057
|
-
environmentRequirements() {
|
|
1058
|
-
return { credentialNames: [] };
|
|
1059
|
-
}
|
|
1060
|
-
async start(task, ctx) {
|
|
1061
|
-
if (typeof task.instruction !== "string") {
|
|
1062
|
-
throw new PolicyUnsupportedError("claude adapter only supports string instructions in M2 (no blob-ref fetch yet)");
|
|
1507
|
+
async prepare(input) {
|
|
1508
|
+
const mapping = mapPermissionPolicyToClaudeArgs(input.policy);
|
|
1509
|
+
if (!mapping.ok) return { kind: "reject", reason: mapping.reason ?? "policy rejected by claude adapter", retryable: false };
|
|
1510
|
+
let modelId;
|
|
1511
|
+
try {
|
|
1512
|
+
modelId = subscriptionModel(input.offer.dispatchSelection, "claude");
|
|
1513
|
+
} catch (error) {
|
|
1514
|
+
return { kind: "reject", reason: error instanceof Error ? error.message : String(error), retryable: false };
|
|
1063
1515
|
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1516
|
+
if (mapping.needsApprovalMcp && Object.prototype.hasOwnProperty.call(input.mcpServers ?? {}, APPROVAL_MCP_SERVER_NAME)) {
|
|
1517
|
+
return { kind: "reject", reason: `MCP server name "${APPROVAL_MCP_SERVER_NAME}" is reserved by the claude approval channel`, retryable: false };
|
|
1518
|
+
}
|
|
1519
|
+
let bin;
|
|
1520
|
+
try {
|
|
1521
|
+
bin = this.resolveBin();
|
|
1522
|
+
} catch (error) {
|
|
1523
|
+
return { kind: "reject", reason: error instanceof Error ? error.message : String(error), retryable: true };
|
|
1067
1524
|
}
|
|
1068
|
-
let
|
|
1525
|
+
let approvalMcpBin;
|
|
1069
1526
|
if (mapping.needsApprovalMcp) {
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
);
|
|
1527
|
+
try {
|
|
1528
|
+
approvalMcpBin = (this.options.resolveApprovalMcpBin ?? resolveApprovalMcpBin)();
|
|
1529
|
+
} catch (error) {
|
|
1530
|
+
return { kind: "reject", reason: error instanceof Error ? error.message : String(error), retryable: true };
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
return {
|
|
1534
|
+
kind: "prepared",
|
|
1535
|
+
operation: {
|
|
1536
|
+
start: (startInput) => this.startPrepared(startInput, mapping, modelId, bin, approvalMcpBin)
|
|
1074
1537
|
}
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
async startPrepared(startInput, initialMapping, modelId, bin, approvalMcpBin) {
|
|
1541
|
+
if (!initialMapping.ok) throw new RuntimeExecutionFailure({
|
|
1542
|
+
phase: "start",
|
|
1543
|
+
category: "authority",
|
|
1544
|
+
retry: "non-retryable",
|
|
1545
|
+
reason: "prepared claude permission mapping was invalid"
|
|
1546
|
+
});
|
|
1547
|
+
if (typeof startInput.instruction !== "string") {
|
|
1548
|
+
throw new RuntimeExecutionFailure({
|
|
1549
|
+
phase: "start",
|
|
1550
|
+
category: "authority",
|
|
1551
|
+
retry: "non-retryable",
|
|
1552
|
+
reason: "prepared claude operation requires a resolved string instruction"
|
|
1078
1553
|
});
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1554
|
+
}
|
|
1555
|
+
const mapping = { ...initialMapping, args: [...initialMapping.args] };
|
|
1556
|
+
let mcpConfigDir;
|
|
1557
|
+
const taskMcpServers = startInput.mcpServers ?? {};
|
|
1558
|
+
const needsMcpConfig = mapping.needsApprovalMcp || Object.keys(taskMcpServers).length > 0;
|
|
1559
|
+
if (mapping.needsApprovalMcp) {
|
|
1560
|
+
if (!startInput.approvalChannel) {
|
|
1561
|
+
throw new RuntimeExecutionFailure({
|
|
1562
|
+
phase: "start",
|
|
1563
|
+
category: "authority",
|
|
1564
|
+
retry: "non-retryable",
|
|
1565
|
+
reason: 'claude adapter requires policy.mode "confirm" to be started with an approval channel'
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
if (!approvalMcpBin) throw new RuntimeExecutionFailure({
|
|
1569
|
+
phase: "start",
|
|
1570
|
+
category: "authority",
|
|
1571
|
+
retry: "non-retryable",
|
|
1572
|
+
reason: "prepared claude approval MCP binary was not resolved"
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
if (needsMcpConfig) {
|
|
1576
|
+
mcpConfigDir = await promises.mkdtemp(path3.join(os.tmpdir(), "byok-mcp-"));
|
|
1577
|
+
await promises.chmod(mcpConfigDir, 448).catch(() => {
|
|
1578
|
+
});
|
|
1579
|
+
const mcpConfigPath = path3.join(mcpConfigDir, "mcp-config.json");
|
|
1580
|
+
const mcpServers = { ...taskMcpServers };
|
|
1581
|
+
if (mapping.needsApprovalMcp) {
|
|
1582
|
+
const approvalChannel = startInput.approvalChannel;
|
|
1583
|
+
if (!approvalChannel) throw new RuntimeExecutionFailure({
|
|
1584
|
+
phase: "start",
|
|
1585
|
+
category: "authority",
|
|
1586
|
+
retry: "non-retryable",
|
|
1587
|
+
reason: "prepared claude approval channel was not available"
|
|
1588
|
+
});
|
|
1589
|
+
const preparedApprovalMcpBin = approvalMcpBin;
|
|
1590
|
+
if (!preparedApprovalMcpBin) throw new RuntimeExecutionFailure({
|
|
1591
|
+
phase: "start",
|
|
1592
|
+
category: "authority",
|
|
1593
|
+
retry: "non-retryable",
|
|
1594
|
+
reason: "prepared claude approval MCP binary was not resolved"
|
|
1595
|
+
});
|
|
1596
|
+
mcpServers[APPROVAL_MCP_SERVER_NAME] = {
|
|
1597
|
+
command: preparedApprovalMcpBin.command,
|
|
1598
|
+
args: preparedApprovalMcpBin.args,
|
|
1599
|
+
env: {
|
|
1600
|
+
BYOK_STORE_DIR: approvalChannel.storeDir,
|
|
1601
|
+
BYOK_PRODUCT_ID: approvalChannel.productId,
|
|
1602
|
+
BYOK_TASK_ID: approvalChannel.taskId,
|
|
1603
|
+
BYOK_APPROVAL_TIMEOUT_MS: String(approvalChannel.timeoutMs)
|
|
1091
1604
|
}
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
await promises.writeFile(mcpConfigPath, JSON.stringify(
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
await promises.writeFile(mcpConfigPath, JSON.stringify({ mcpServers }), { mode: 384 });
|
|
1095
1608
|
mapping.args = [
|
|
1096
1609
|
...mapping.args,
|
|
1097
|
-
"--permission-prompt-tool",
|
|
1098
|
-
`mcp__${APPROVAL_MCP_SERVER_NAME}__${APPROVAL_TOOL_NAME}`,
|
|
1610
|
+
...mapping.needsApprovalMcp ? ["--permission-prompt-tool", `mcp__${APPROVAL_MCP_SERVER_NAME}__${APPROVAL_TOOL_NAME}`] : [],
|
|
1099
1611
|
"--mcp-config",
|
|
1100
1612
|
mcpConfigPath,
|
|
1101
|
-
//
|
|
1102
|
-
//
|
|
1103
|
-
// the only MCP server this invocation should ever see.
|
|
1613
|
+
// The generated file is the complete task-scoped MCP authority.
|
|
1614
|
+
// Never merge ambient user/project MCP configuration into it.
|
|
1104
1615
|
"--strict-mcp-config"
|
|
1105
1616
|
];
|
|
1106
1617
|
}
|
|
1107
|
-
const
|
|
1108
|
-
|
|
1618
|
+
const resumeSessionId = startInput.manifest.sessionRef;
|
|
1619
|
+
let manifestModelId;
|
|
1620
|
+
try {
|
|
1621
|
+
manifestModelId = subscriptionModel(startInput.manifest.dispatchSelection, "claude");
|
|
1622
|
+
} catch (cause) {
|
|
1623
|
+
throw new RuntimeExecutionFailure({
|
|
1624
|
+
phase: "start",
|
|
1625
|
+
category: "authority",
|
|
1626
|
+
retry: "non-retryable",
|
|
1627
|
+
reason: "prepared claude operation received an invalid runtime selection manifest"
|
|
1628
|
+
}, { cause });
|
|
1629
|
+
}
|
|
1630
|
+
if (manifestModelId !== modelId) {
|
|
1631
|
+
throw new RuntimeExecutionFailure({
|
|
1632
|
+
phase: "start",
|
|
1633
|
+
category: "authority",
|
|
1634
|
+
retry: "non-retryable",
|
|
1635
|
+
reason: "prepared claude operation received a manifest with different runtime selection"
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1109
1638
|
const args = [
|
|
1110
1639
|
"-p",
|
|
1111
1640
|
"--input-format",
|
|
@@ -1117,33 +1646,72 @@ var ClaudeAdapter = class {
|
|
|
1117
1646
|
// "Error: When using --print, --output-format=stream-json requires
|
|
1118
1647
|
// --verbose", before spawning any model call.
|
|
1119
1648
|
"--verbose",
|
|
1649
|
+
...manifestModelId ? ["--model", manifestModelId] : [],
|
|
1120
1650
|
...resumeSessionId ? ["--resume", resumeSessionId] : [],
|
|
1121
1651
|
...mapping.args
|
|
1122
1652
|
];
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1653
|
+
let client;
|
|
1654
|
+
try {
|
|
1655
|
+
client = new ClaudeProcessClient({
|
|
1656
|
+
command: bin.command,
|
|
1657
|
+
args,
|
|
1658
|
+
cwd: startInput.manifest.workspace.workspaceDir,
|
|
1659
|
+
env: withoutProviderCredentials(startInput.env),
|
|
1660
|
+
spawnFn: this.options.spawnFn
|
|
1661
|
+
});
|
|
1662
|
+
} catch (cause) {
|
|
1663
|
+
await cleanupMcpConfigDir(mcpConfigDir);
|
|
1664
|
+
throw new RuntimeExecutionFailure({
|
|
1665
|
+
phase: "start",
|
|
1666
|
+
category: "infrastructure",
|
|
1667
|
+
retry: "retryable",
|
|
1668
|
+
reason: "claude runtime process could not be spawned"
|
|
1669
|
+
}, { cause });
|
|
1670
|
+
}
|
|
1671
|
+
try {
|
|
1672
|
+
client.writeUserMessage(startInput.instruction);
|
|
1673
|
+
} catch (cause) {
|
|
1674
|
+
client.kill();
|
|
1675
|
+
await cleanupMcpConfigDir(mcpConfigDir);
|
|
1676
|
+
throw new RuntimeExecutionFailure({
|
|
1677
|
+
phase: "start",
|
|
1678
|
+
category: "infrastructure",
|
|
1679
|
+
retry: "retryable",
|
|
1680
|
+
reason: "claude initial instruction transport failed"
|
|
1681
|
+
}, { cause });
|
|
1682
|
+
}
|
|
1131
1683
|
let sessionRef;
|
|
1132
1684
|
try {
|
|
1133
1685
|
sessionRef = await client.waitForInit();
|
|
1134
1686
|
} catch (err) {
|
|
1135
1687
|
client.kill();
|
|
1136
|
-
await
|
|
1137
|
-
throw err;
|
|
1688
|
+
await cleanupMcpConfigDir(mcpConfigDir);
|
|
1689
|
+
if (isRuntimeExecutionFailure(err)) throw err;
|
|
1690
|
+
throw new RuntimeExecutionFailure({
|
|
1691
|
+
phase: "start",
|
|
1692
|
+
category: "infrastructure",
|
|
1693
|
+
retry: "retryable",
|
|
1694
|
+
reason: `claude exited before yielding an authoritative session id: ${errorMessage2(err)}`
|
|
1695
|
+
}, { cause: err });
|
|
1138
1696
|
}
|
|
1139
1697
|
if (resumeSessionId !== void 0 && sessionRef !== resumeSessionId) {
|
|
1140
1698
|
client.kill();
|
|
1141
|
-
await
|
|
1142
|
-
throw new
|
|
1143
|
-
|
|
1144
|
-
|
|
1699
|
+
await cleanupMcpConfigDir(mcpConfigDir);
|
|
1700
|
+
throw new RuntimeExecutionFailure({
|
|
1701
|
+
phase: "start",
|
|
1702
|
+
category: "authority",
|
|
1703
|
+
retry: "non-retryable",
|
|
1704
|
+
reason: `claude --resume echoed a different session id than requested (requested ${resumeSessionId}, got ${sessionRef})`
|
|
1705
|
+
});
|
|
1145
1706
|
}
|
|
1146
|
-
return new ClaudeSession(
|
|
1707
|
+
return new ClaudeSession(
|
|
1708
|
+
sessionRef,
|
|
1709
|
+
client,
|
|
1710
|
+
startInput.manifest.workspace.workspaceDir,
|
|
1711
|
+
startInput.approvalChannel,
|
|
1712
|
+
mcpConfigDir,
|
|
1713
|
+
manifestModelId
|
|
1714
|
+
);
|
|
1147
1715
|
}
|
|
1148
1716
|
/**
|
|
1149
1717
|
* `claude auth status --json` is claude's OWN non-secret login-state
|
|
@@ -1177,20 +1745,32 @@ var ClaudeAdapter = class {
|
|
|
1177
1745
|
return (this.options.resolveBin ?? resolveClaudeBin)();
|
|
1178
1746
|
}
|
|
1179
1747
|
};
|
|
1748
|
+
function subscriptionModel(selection, runtimeId) {
|
|
1749
|
+
if (selection === void 0) return void 0;
|
|
1750
|
+
if (selection.lane !== "subscription" || selection.runtimeId !== runtimeId) {
|
|
1751
|
+
throw new PolicyUnsupportedError(
|
|
1752
|
+
`claude adapter cannot execute ${selection.lane} selection for runtime ${selection.runtimeId}`
|
|
1753
|
+
);
|
|
1754
|
+
}
|
|
1755
|
+
return selection.modelId;
|
|
1756
|
+
}
|
|
1180
1757
|
var ClaudeSession = class {
|
|
1181
|
-
constructor(sessionRef, client, workspaceDir, approvalChannel,
|
|
1758
|
+
constructor(sessionRef, client, workspaceDir, approvalChannel, mcpConfigDir, modelId) {
|
|
1182
1759
|
this.sessionRef = sessionRef;
|
|
1183
1760
|
this.client = client;
|
|
1184
1761
|
this.workspaceDir = workspaceDir;
|
|
1185
1762
|
this.approvalChannel = approvalChannel;
|
|
1186
|
-
this.
|
|
1763
|
+
this.mcpConfigDir = mcpConfigDir;
|
|
1764
|
+
this.modelId = modelId;
|
|
1187
1765
|
}
|
|
1188
1766
|
sessionRef;
|
|
1189
1767
|
client;
|
|
1190
1768
|
workspaceDir;
|
|
1191
1769
|
approvalChannel;
|
|
1192
|
-
|
|
1770
|
+
mcpConfigDir;
|
|
1771
|
+
modelId;
|
|
1193
1772
|
correlation = createToolUseCorrelation();
|
|
1773
|
+
closeAttempt;
|
|
1194
1774
|
get events() {
|
|
1195
1775
|
const client = this.client;
|
|
1196
1776
|
const correlation = this.correlation;
|
|
@@ -1199,17 +1779,40 @@ var ClaudeSession = class {
|
|
|
1199
1779
|
[Symbol.asyncIterator]() {
|
|
1200
1780
|
const inner = client.events[Symbol.asyncIterator]();
|
|
1201
1781
|
let pending = [];
|
|
1782
|
+
let terminalFailure;
|
|
1202
1783
|
let turnSettled = false;
|
|
1203
1784
|
return {
|
|
1204
1785
|
async next() {
|
|
1205
1786
|
for (; ; ) {
|
|
1206
1787
|
const buffered = pending.shift();
|
|
1207
1788
|
if (buffered) return { value: buffered, done: false };
|
|
1208
|
-
if (turnSettled)
|
|
1209
|
-
|
|
1210
|
-
|
|
1789
|
+
if (turnSettled) {
|
|
1790
|
+
if (terminalFailure) throw terminalFailure;
|
|
1791
|
+
return { value: void 0, done: true };
|
|
1792
|
+
}
|
|
1793
|
+
let raw;
|
|
1794
|
+
try {
|
|
1795
|
+
raw = await inner.next();
|
|
1796
|
+
} catch (cause) {
|
|
1797
|
+
throw new RuntimeExecutionFailure({
|
|
1798
|
+
phase: "run",
|
|
1799
|
+
category: "infrastructure",
|
|
1800
|
+
retry: "retryable",
|
|
1801
|
+
reason: "claude runtime event transport failed"
|
|
1802
|
+
}, { cause });
|
|
1803
|
+
}
|
|
1804
|
+
const { value, done } = raw;
|
|
1805
|
+
if (done) {
|
|
1806
|
+
throw new RuntimeExecutionFailure({
|
|
1807
|
+
phase: "run",
|
|
1808
|
+
category: "infrastructure",
|
|
1809
|
+
retry: "retryable",
|
|
1810
|
+
reason: "claude runtime process ended before a terminal result frame"
|
|
1811
|
+
}, { cause: client.terminalError });
|
|
1812
|
+
}
|
|
1211
1813
|
if (value.type === "result") turnSettled = true;
|
|
1212
1814
|
const mapped = mapClaudeMessageToAgentEvents(value, correlation, { workspaceDir });
|
|
1815
|
+
terminalFailure = mapped.terminalFailure ?? terminalFailure;
|
|
1213
1816
|
if (mapped.unmappedLabel) {
|
|
1214
1817
|
client.recordUnmappedFrame(mapped.unmappedLabel);
|
|
1215
1818
|
}
|
|
@@ -1240,6 +1843,12 @@ var ClaudeSession = class {
|
|
|
1240
1843
|
if (typeof task.instruction !== "string") {
|
|
1241
1844
|
throw new PolicyUnsupportedError("claude adapter only supports string instructions in M2 (no blob-ref fetch yet)");
|
|
1242
1845
|
}
|
|
1846
|
+
const requestedModel = subscriptionModel(task.dispatchSelection, "claude");
|
|
1847
|
+
if (requestedModel !== void 0 && requestedModel !== this.modelId) {
|
|
1848
|
+
throw new PolicyUnsupportedError(
|
|
1849
|
+
`claude persistent session cannot change model from ${this.modelId ?? "(legacy default)"} to ${requestedModel}`
|
|
1850
|
+
);
|
|
1851
|
+
}
|
|
1243
1852
|
this.client.writeUserMessage(task.instruction);
|
|
1244
1853
|
}
|
|
1245
1854
|
/**
|
|
@@ -1258,8 +1867,17 @@ var ClaudeSession = class {
|
|
|
1258
1867
|
this.client.kill();
|
|
1259
1868
|
}
|
|
1260
1869
|
async close() {
|
|
1261
|
-
this.
|
|
1262
|
-
|
|
1870
|
+
if (!this.closeAttempt) {
|
|
1871
|
+
const attempt = (async () => {
|
|
1872
|
+
await this.client.dispose();
|
|
1873
|
+
await cleanupMcpConfigDir(this.mcpConfigDir);
|
|
1874
|
+
})();
|
|
1875
|
+
this.closeAttempt = attempt.catch((error) => {
|
|
1876
|
+
this.closeAttempt = void 0;
|
|
1877
|
+
throw error;
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1880
|
+
await this.closeAttempt;
|
|
1263
1881
|
}
|
|
1264
1882
|
/**
|
|
1265
1883
|
* M4 Phase 3: routes into the out-of-band approval channel `start()`
|
|
@@ -1476,14 +2094,15 @@ var CodexProcessRunner = class {
|
|
|
1476
2094
|
exitSignal = null;
|
|
1477
2095
|
closedPromise;
|
|
1478
2096
|
resolveClosed;
|
|
2097
|
+
disposalAttempt;
|
|
1479
2098
|
constructor(options) {
|
|
1480
2099
|
this.onEvent = options.onEvent;
|
|
1481
2100
|
const spawnFn = options.spawnFn ?? spawn;
|
|
1482
|
-
this.child = spawnFn(options.command, options.args, {
|
|
2101
|
+
this.child = spawnFn(options.command, options.args, withOwnedProcessTree({
|
|
1483
2102
|
cwd: options.cwd,
|
|
1484
2103
|
env: options.env,
|
|
1485
2104
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1486
|
-
});
|
|
2105
|
+
}));
|
|
1487
2106
|
this.closedPromise = new Promise((resolve) => {
|
|
1488
2107
|
this.resolveClosed = resolve;
|
|
1489
2108
|
});
|
|
@@ -1513,7 +2132,7 @@ var CodexProcessRunner = class {
|
|
|
1513
2132
|
return this.closed;
|
|
1514
2133
|
}
|
|
1515
2134
|
/**
|
|
1516
|
-
*
|
|
2135
|
+
* Immediate tree termination request. SIGTERM on POSIX: SIGINT was empirically confirmed
|
|
1517
2136
|
* to be silently ignored by `codex exec` (a real, direct test — a 60s
|
|
1518
2137
|
* shell `sleep` ran to full, unaffected completion despite SIGINT sent at
|
|
1519
2138
|
* t=4s) — a genuine, evidence-based correction to this task's own initial
|
|
@@ -1526,13 +2145,25 @@ var CodexProcessRunner = class {
|
|
|
1526
2145
|
* `../pi/rpc-client.ts`'s own cross-platform convention.
|
|
1527
2146
|
*/
|
|
1528
2147
|
kill() {
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
this.
|
|
2148
|
+
requestOwnedProcessTreeTermination(this.processTreeOptions());
|
|
2149
|
+
}
|
|
2150
|
+
dispose() {
|
|
2151
|
+
if (!this.disposalAttempt) {
|
|
2152
|
+
const attempt = disposeOwnedProcessTree(this.processTreeOptions());
|
|
2153
|
+
this.disposalAttempt = attempt.catch((error) => {
|
|
2154
|
+
this.disposalAttempt = void 0;
|
|
2155
|
+
throw error;
|
|
2156
|
+
});
|
|
1535
2157
|
}
|
|
2158
|
+
return this.disposalAttempt;
|
|
2159
|
+
}
|
|
2160
|
+
processTreeOptions() {
|
|
2161
|
+
return {
|
|
2162
|
+
child: this.child,
|
|
2163
|
+
waitClosed: () => this.closedPromise,
|
|
2164
|
+
isClosed: () => this.closed,
|
|
2165
|
+
label: "codex"
|
|
2166
|
+
};
|
|
1536
2167
|
}
|
|
1537
2168
|
/** 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. */
|
|
1538
2169
|
buildExitError(context) {
|
|
@@ -1583,7 +2214,17 @@ var CodexAdapter = class {
|
|
|
1583
2214
|
this.options = options;
|
|
1584
2215
|
}
|
|
1585
2216
|
options;
|
|
1586
|
-
|
|
2217
|
+
descriptor = freezeRuntimeAdapterDescriptor({
|
|
2218
|
+
id: "codex",
|
|
2219
|
+
supportsDispatchSelection: true,
|
|
2220
|
+
capabilities: {
|
|
2221
|
+
steer: false,
|
|
2222
|
+
resume: true,
|
|
2223
|
+
approvalInteractive: false,
|
|
2224
|
+
permissionModes: ["auto", "readonly"]
|
|
2225
|
+
},
|
|
2226
|
+
environmentRequirements: { credentialNames: [] }
|
|
2227
|
+
});
|
|
1587
2228
|
async detect() {
|
|
1588
2229
|
const bin = this.resolveBin();
|
|
1589
2230
|
try {
|
|
@@ -1632,57 +2273,100 @@ ${result.stderr}`);
|
|
|
1632
2273
|
${withStreams.stderr ?? ""}`);
|
|
1633
2274
|
}
|
|
1634
2275
|
}
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
* API-key passthrough remains a separate, pending product decision. No
|
|
1644
|
-
* `baseNames` either: nothing in this adapter reads a codex-specific
|
|
1645
|
-
* config-discovery variable (e.g. `CODEX_HOME`) today.
|
|
1646
|
-
*/
|
|
1647
|
-
environmentRequirements() {
|
|
1648
|
-
return { credentialNames: [] };
|
|
1649
|
-
}
|
|
1650
|
-
async start(task, ctx) {
|
|
1651
|
-
if (typeof task.instruction !== "string") {
|
|
1652
|
-
throw new PolicyUnsupportedError("codex adapter only supports string instructions in M2 (no blob-ref fetch yet)");
|
|
2276
|
+
async prepare(input) {
|
|
2277
|
+
const mapping = mapPermissionPolicyToCodexArgs(input.policy);
|
|
2278
|
+
if (!mapping.ok) return { kind: "reject", reason: mapping.reason ?? "policy rejected by codex adapter", retryable: false };
|
|
2279
|
+
let modelId;
|
|
2280
|
+
try {
|
|
2281
|
+
modelId = subscriptionModel2(input.offer.dispatchSelection);
|
|
2282
|
+
} catch (error) {
|
|
2283
|
+
return { kind: "reject", reason: error instanceof Error ? error.message : String(error), retryable: false };
|
|
1653
2284
|
}
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
2285
|
+
let command;
|
|
2286
|
+
try {
|
|
2287
|
+
command = this.resolveBin().command;
|
|
2288
|
+
} catch (error) {
|
|
2289
|
+
return { kind: "reject", reason: error instanceof Error ? error.message : String(error), retryable: true };
|
|
2290
|
+
}
|
|
2291
|
+
return {
|
|
2292
|
+
kind: "prepared",
|
|
2293
|
+
operation: {
|
|
2294
|
+
start: (startInput) => this.startPrepared(startInput, mapping.args, modelId, command)
|
|
2295
|
+
}
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
async startPrepared(startInput, policyArgs, modelId, command) {
|
|
2299
|
+
if (typeof startInput.instruction !== "string") {
|
|
2300
|
+
throw new RuntimeExecutionFailure({
|
|
2301
|
+
phase: "start",
|
|
2302
|
+
category: "authority",
|
|
2303
|
+
retry: "non-retryable",
|
|
2304
|
+
reason: "prepared codex operation requires a resolved string instruction"
|
|
2305
|
+
});
|
|
1657
2306
|
}
|
|
1658
|
-
const bin = this.resolveBin();
|
|
1659
2307
|
const queue = new AsyncQueue();
|
|
2308
|
+
const terminal = {};
|
|
1660
2309
|
const recordUnmapped = makeUnmappedFrameRecorder(/* @__PURE__ */ new Map());
|
|
1661
|
-
|
|
2310
|
+
let workspaceDir;
|
|
2311
|
+
try {
|
|
2312
|
+
workspaceDir = await resolveRealWorkspaceDir(startInput.manifest.workspace.workspaceDir);
|
|
2313
|
+
} catch (cause) {
|
|
2314
|
+
throw new RuntimeExecutionFailure({
|
|
2315
|
+
phase: "start",
|
|
2316
|
+
category: "infrastructure",
|
|
2317
|
+
retry: "retryable",
|
|
2318
|
+
reason: "codex runtime workspace could not be resolved"
|
|
2319
|
+
}, { cause });
|
|
2320
|
+
}
|
|
2321
|
+
const runtimeEnv = withoutProviderCredentials(startInput.env);
|
|
2322
|
+
let manifestModelId;
|
|
2323
|
+
try {
|
|
2324
|
+
manifestModelId = subscriptionModel2(startInput.manifest.dispatchSelection);
|
|
2325
|
+
} catch (cause) {
|
|
2326
|
+
throw new RuntimeExecutionFailure({
|
|
2327
|
+
phase: "start",
|
|
2328
|
+
category: "authority",
|
|
2329
|
+
retry: "non-retryable",
|
|
2330
|
+
reason: "prepared codex operation received an invalid runtime selection manifest"
|
|
2331
|
+
}, { cause });
|
|
2332
|
+
}
|
|
2333
|
+
if (manifestModelId !== modelId) {
|
|
2334
|
+
throw new RuntimeExecutionFailure({
|
|
2335
|
+
phase: "start",
|
|
2336
|
+
category: "authority",
|
|
2337
|
+
retry: "non-retryable",
|
|
2338
|
+
reason: "prepared codex operation received a manifest with different runtime selection"
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
1662
2341
|
const { sessionRef, runner } = await runCodexTurn({
|
|
1663
|
-
command
|
|
1664
|
-
resumeRef:
|
|
1665
|
-
instruction:
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
2342
|
+
command,
|
|
2343
|
+
resumeRef: startInput.manifest.sessionRef,
|
|
2344
|
+
instruction: startInput.instruction,
|
|
2345
|
+
modelId: manifestModelId,
|
|
2346
|
+
policyArgs: [...policyArgs],
|
|
2347
|
+
cwd: startInput.manifest.workspace.workspaceDir,
|
|
2348
|
+
env: runtimeEnv,
|
|
1669
2349
|
spawnFn: this.options.spawnFn,
|
|
1670
2350
|
workspaceDir,
|
|
1671
2351
|
queue,
|
|
1672
2352
|
recordUnmapped,
|
|
1673
|
-
expectedSessionRef:
|
|
1674
|
-
preparedGit:
|
|
2353
|
+
expectedSessionRef: startInput.manifest.sessionRef,
|
|
2354
|
+
preparedGit: startInput.manifest.workspace.workspaceId !== void 0,
|
|
2355
|
+
failurePhase: "start",
|
|
2356
|
+
terminal
|
|
1675
2357
|
});
|
|
1676
2358
|
return new CodexSession({
|
|
1677
2359
|
sessionRef,
|
|
1678
|
-
command
|
|
2360
|
+
command,
|
|
1679
2361
|
workspaceDir,
|
|
1680
|
-
env:
|
|
2362
|
+
env: startInput.env,
|
|
1681
2363
|
spawnFn: this.options.spawnFn,
|
|
1682
2364
|
queue,
|
|
1683
2365
|
recordUnmapped,
|
|
1684
2366
|
initialRunner: runner,
|
|
1685
|
-
preparedGit:
|
|
2367
|
+
preparedGit: startInput.manifest.workspace.workspaceId !== void 0,
|
|
2368
|
+
modelId: manifestModelId,
|
|
2369
|
+
terminal
|
|
1686
2370
|
});
|
|
1687
2371
|
}
|
|
1688
2372
|
resolveBin() {
|
|
@@ -1703,12 +2387,25 @@ function makeUnmappedFrameRecorder(counts) {
|
|
|
1703
2387
|
}
|
|
1704
2388
|
};
|
|
1705
2389
|
}
|
|
1706
|
-
function buildArgv(resumeRef, policyArgs, instruction, preparedGit = false) {
|
|
2390
|
+
function buildArgv(resumeRef, policyArgs, instruction, modelId, preparedGit = false) {
|
|
1707
2391
|
const base = resumeRef !== void 0 ? ["exec", "resume", resumeRef] : ["exec"];
|
|
1708
|
-
return [
|
|
2392
|
+
return [
|
|
2393
|
+
...base,
|
|
2394
|
+
"--json",
|
|
2395
|
+
...modelId ? ["--model", modelId] : [],
|
|
2396
|
+
...preparedGit ? [] : ["--skip-git-repo-check"],
|
|
2397
|
+
...policyArgs,
|
|
2398
|
+
instruction
|
|
2399
|
+
];
|
|
1709
2400
|
}
|
|
1710
2401
|
async function runCodexTurn(params) {
|
|
1711
|
-
const argv = buildArgv(
|
|
2402
|
+
const argv = buildArgv(
|
|
2403
|
+
params.resumeRef,
|
|
2404
|
+
params.policyArgs,
|
|
2405
|
+
params.instruction,
|
|
2406
|
+
params.modelId,
|
|
2407
|
+
params.preparedGit
|
|
2408
|
+
);
|
|
1712
2409
|
let firstLineSettled = false;
|
|
1713
2410
|
let resolveFirstLine;
|
|
1714
2411
|
let rejectFirstLine;
|
|
@@ -1717,37 +2414,69 @@ async function runCodexTurn(params) {
|
|
|
1717
2414
|
rejectFirstLine = reject;
|
|
1718
2415
|
});
|
|
1719
2416
|
let turnEnded = false;
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
if (
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
2417
|
+
let runner;
|
|
2418
|
+
try {
|
|
2419
|
+
runner = new CodexProcessRunner({
|
|
2420
|
+
command: params.command,
|
|
2421
|
+
args: argv,
|
|
2422
|
+
cwd: params.cwd,
|
|
2423
|
+
env: params.env,
|
|
2424
|
+
spawnFn: params.spawnFn,
|
|
2425
|
+
onEvent: (evt) => {
|
|
2426
|
+
if (!firstLineSettled) {
|
|
2427
|
+
firstLineSettled = true;
|
|
2428
|
+
if (evt.type === "thread.started" && typeof evt.thread_id === "string" && evt.thread_id.length > 0) {
|
|
2429
|
+
resolveFirstLine(evt.thread_id);
|
|
2430
|
+
} else {
|
|
2431
|
+
rejectFirstLine(
|
|
2432
|
+
new RuntimeExecutionFailure({
|
|
2433
|
+
phase: params.failurePhase,
|
|
2434
|
+
category: "authority",
|
|
2435
|
+
retry: "non-retryable",
|
|
2436
|
+
reason: `codex did not yield thread.started as its first event (got ${JSON.stringify(evt).slice(0, 200)})`
|
|
2437
|
+
})
|
|
2438
|
+
);
|
|
2439
|
+
}
|
|
2440
|
+
return;
|
|
2441
|
+
}
|
|
2442
|
+
const mapped = mapCodexEventToAgentEvents(evt, params.workspaceDir);
|
|
2443
|
+
for (const agentEvent of mapped) {
|
|
2444
|
+
if (agentEvent.type === "turn_end") turnEnded = true;
|
|
2445
|
+
params.queue.push(agentEvent);
|
|
2446
|
+
}
|
|
2447
|
+
if (evt.type === "turn.failed") {
|
|
2448
|
+
params.terminal.failure = new RuntimeExecutionFailure({
|
|
2449
|
+
phase: "run",
|
|
2450
|
+
category: "semantic",
|
|
2451
|
+
retry: "non-retryable",
|
|
2452
|
+
reason: "codex reported terminal task failure"
|
|
2453
|
+
});
|
|
2454
|
+
params.queue.end();
|
|
2455
|
+
}
|
|
2456
|
+
if (mapped.length === 0 && !isRoutineCodexEvent(evt)) {
|
|
2457
|
+
params.recordUnmapped(unmappedFrameKey(evt));
|
|
1735
2458
|
}
|
|
1736
|
-
return;
|
|
1737
|
-
}
|
|
1738
|
-
const mapped = mapCodexEventToAgentEvents(evt, params.workspaceDir);
|
|
1739
|
-
for (const agentEvent of mapped) {
|
|
1740
|
-
if (agentEvent.type === "turn_end") turnEnded = true;
|
|
1741
|
-
params.queue.push(agentEvent);
|
|
1742
|
-
}
|
|
1743
|
-
if (mapped.length === 0 && !isRoutineCodexEvent(evt)) {
|
|
1744
|
-
params.recordUnmapped(unmappedFrameKey(evt));
|
|
1745
2459
|
}
|
|
1746
|
-
}
|
|
1747
|
-
})
|
|
2460
|
+
});
|
|
2461
|
+
} catch (cause) {
|
|
2462
|
+
throw new RuntimeExecutionFailure({
|
|
2463
|
+
phase: params.failurePhase,
|
|
2464
|
+
category: "infrastructure",
|
|
2465
|
+
retry: "retryable",
|
|
2466
|
+
reason: "codex runtime process could not be spawned"
|
|
2467
|
+
}, { cause });
|
|
2468
|
+
}
|
|
2469
|
+
params.onRunnerCreated?.(runner);
|
|
1748
2470
|
void runner.waitClosed().then(() => {
|
|
1749
|
-
if (turnEnded) return;
|
|
1750
|
-
|
|
2471
|
+
if (turnEnded || params.terminal.failure) return;
|
|
2472
|
+
const cause = runner.buildExitError("codex exited without completing the turn");
|
|
2473
|
+
params.terminal.failure = new RuntimeExecutionFailure({
|
|
2474
|
+
phase: "run",
|
|
2475
|
+
category: "infrastructure",
|
|
2476
|
+
retry: "retryable",
|
|
2477
|
+
reason: cause.message
|
|
2478
|
+
}, { cause });
|
|
2479
|
+
params.queue.push({ type: "error", message: cause.message });
|
|
1751
2480
|
params.queue.end();
|
|
1752
2481
|
});
|
|
1753
2482
|
let sessionRef;
|
|
@@ -1771,7 +2500,13 @@ async function runCodexTurn(params) {
|
|
|
1771
2500
|
void runner.waitClosed().then(() => {
|
|
1772
2501
|
if (!settled) {
|
|
1773
2502
|
settled = true;
|
|
1774
|
-
|
|
2503
|
+
const cause = runner.buildExitError("codex exited before yielding an authoritative thread id");
|
|
2504
|
+
reject(new RuntimeExecutionFailure({
|
|
2505
|
+
phase: params.failurePhase,
|
|
2506
|
+
category: "infrastructure",
|
|
2507
|
+
retry: "retryable",
|
|
2508
|
+
reason: cause.message
|
|
2509
|
+
}, { cause }));
|
|
1775
2510
|
}
|
|
1776
2511
|
});
|
|
1777
2512
|
});
|
|
@@ -1781,9 +2516,12 @@ async function runCodexTurn(params) {
|
|
|
1781
2516
|
}
|
|
1782
2517
|
if (params.expectedSessionRef !== void 0 && sessionRef !== params.expectedSessionRef) {
|
|
1783
2518
|
runner.kill();
|
|
1784
|
-
throw new
|
|
1785
|
-
|
|
1786
|
-
|
|
2519
|
+
throw new RuntimeExecutionFailure({
|
|
2520
|
+
phase: params.failurePhase,
|
|
2521
|
+
category: "authority",
|
|
2522
|
+
retry: "non-retryable",
|
|
2523
|
+
reason: `codex exec resume echoed a different thread id than requested (requested ${params.expectedSessionRef}, got ${sessionRef})`
|
|
2524
|
+
});
|
|
1787
2525
|
}
|
|
1788
2526
|
return { sessionRef, runner };
|
|
1789
2527
|
}
|
|
@@ -1809,8 +2547,13 @@ var CodexSession = class {
|
|
|
1809
2547
|
queue;
|
|
1810
2548
|
recordUnmapped;
|
|
1811
2549
|
preparedGit;
|
|
2550
|
+
modelId;
|
|
2551
|
+
terminal;
|
|
1812
2552
|
currentRunner;
|
|
2553
|
+
ownedRunners = /* @__PURE__ */ new Set();
|
|
2554
|
+
followUpAttempts = /* @__PURE__ */ new Set();
|
|
1813
2555
|
closed = false;
|
|
2556
|
+
closeAttempt;
|
|
1814
2557
|
constructor(options) {
|
|
1815
2558
|
this.sessionRef = options.sessionRef;
|
|
1816
2559
|
this.command = options.command;
|
|
@@ -1820,11 +2563,37 @@ var CodexSession = class {
|
|
|
1820
2563
|
this.queue = options.queue;
|
|
1821
2564
|
this.recordUnmapped = options.recordUnmapped;
|
|
1822
2565
|
this.preparedGit = options.preparedGit;
|
|
2566
|
+
this.modelId = options.modelId;
|
|
2567
|
+
this.terminal = options.terminal;
|
|
1823
2568
|
this.currentRunner = options.initialRunner;
|
|
2569
|
+
this.ownedRunners.add(options.initialRunner);
|
|
1824
2570
|
void this.forgetRunnerOnceClosed(options.initialRunner);
|
|
1825
2571
|
}
|
|
1826
2572
|
get events() {
|
|
1827
|
-
|
|
2573
|
+
const queue = this.queue;
|
|
2574
|
+
const session = this;
|
|
2575
|
+
return {
|
|
2576
|
+
[Symbol.asyncIterator]() {
|
|
2577
|
+
const inner = queue[Symbol.asyncIterator]();
|
|
2578
|
+
return {
|
|
2579
|
+
async next() {
|
|
2580
|
+
let result;
|
|
2581
|
+
try {
|
|
2582
|
+
result = await inner.next();
|
|
2583
|
+
} catch (cause) {
|
|
2584
|
+
throw new RuntimeExecutionFailure({
|
|
2585
|
+
phase: "run",
|
|
2586
|
+
category: "infrastructure",
|
|
2587
|
+
retry: "retryable",
|
|
2588
|
+
reason: "codex runtime event transport failed"
|
|
2589
|
+
}, { cause });
|
|
2590
|
+
}
|
|
2591
|
+
if (result.done && session.terminal.failure) throw session.terminal.failure;
|
|
2592
|
+
return result;
|
|
2593
|
+
}
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
};
|
|
1828
2597
|
}
|
|
1829
2598
|
async forgetRunnerOnceClosed(runner) {
|
|
1830
2599
|
await runner.waitClosed();
|
|
@@ -1869,7 +2638,14 @@ var CodexSession = class {
|
|
|
1869
2638
|
* stale id even after codex had moved on) — it just can now only ever be
|
|
1870
2639
|
* the SAME id this call asked to resume, never a silently-different one.
|
|
1871
2640
|
*/
|
|
1872
|
-
|
|
2641
|
+
followUp(task) {
|
|
2642
|
+
const attempt = this.runFollowUp(task);
|
|
2643
|
+
this.followUpAttempts.add(attempt);
|
|
2644
|
+
void attempt.finally(() => this.followUpAttempts.delete(attempt)).catch(() => {
|
|
2645
|
+
});
|
|
2646
|
+
return attempt;
|
|
2647
|
+
}
|
|
2648
|
+
async runFollowUp(task) {
|
|
1873
2649
|
if (typeof task.instruction !== "string") {
|
|
1874
2650
|
throw new PolicyUnsupportedError("codex adapter only supports string instructions in M2 (no blob-ref fetch yet)");
|
|
1875
2651
|
}
|
|
@@ -1880,41 +2656,79 @@ var CodexSession = class {
|
|
|
1880
2656
|
if (!mapping.ok) {
|
|
1881
2657
|
throw new PolicyUnsupportedError(mapping.reason ?? "policy rejected by codex adapter");
|
|
1882
2658
|
}
|
|
2659
|
+
const requestedModel = subscriptionModel2(task.dispatchSelection);
|
|
2660
|
+
if (requestedModel !== void 0 && requestedModel !== this.modelId) {
|
|
2661
|
+
throw new PolicyUnsupportedError(
|
|
2662
|
+
`codex persistent session cannot change model from ${this.modelId ?? "(legacy default)"} to ${requestedModel}`
|
|
2663
|
+
);
|
|
2664
|
+
}
|
|
2665
|
+
const modelId = this.modelId;
|
|
1883
2666
|
const resumeRef = this.sessionRef;
|
|
1884
2667
|
let sessionRef;
|
|
1885
2668
|
let runner;
|
|
2669
|
+
const terminal = {};
|
|
1886
2670
|
try {
|
|
1887
2671
|
({ sessionRef, runner } = await runCodexTurn({
|
|
1888
2672
|
command: this.command,
|
|
1889
2673
|
resumeRef,
|
|
1890
2674
|
instruction: task.instruction,
|
|
2675
|
+
modelId,
|
|
1891
2676
|
policyArgs: mapping.args,
|
|
1892
2677
|
cwd: this.workspaceDir,
|
|
1893
|
-
env: this.env,
|
|
2678
|
+
env: withoutProviderCredentials(this.env),
|
|
1894
2679
|
spawnFn: this.spawnFn,
|
|
1895
2680
|
workspaceDir: this.workspaceDir,
|
|
1896
2681
|
queue: this.queue,
|
|
1897
2682
|
recordUnmapped: this.recordUnmapped,
|
|
1898
2683
|
expectedSessionRef: resumeRef,
|
|
1899
|
-
preparedGit: this.preparedGit
|
|
2684
|
+
preparedGit: this.preparedGit,
|
|
2685
|
+
failurePhase: "run",
|
|
2686
|
+
terminal,
|
|
2687
|
+
onRunnerCreated: (created) => {
|
|
2688
|
+
this.ownedRunners.add(created);
|
|
2689
|
+
void this.forgetRunnerOnceClosed(created);
|
|
2690
|
+
}
|
|
1900
2691
|
}));
|
|
1901
2692
|
} catch (err) {
|
|
1902
2693
|
this.queue.end();
|
|
2694
|
+
this.terminal = {
|
|
2695
|
+
failure: isRuntimeExecutionFailure(err) ? err : new RuntimeExecutionFailure({
|
|
2696
|
+
phase: "run",
|
|
2697
|
+
category: "authority",
|
|
2698
|
+
retry: "non-retryable",
|
|
2699
|
+
reason: "codex follow-up violated the runtime adapter contract"
|
|
2700
|
+
}, { cause: err })
|
|
2701
|
+
};
|
|
1903
2702
|
throw err;
|
|
1904
2703
|
}
|
|
2704
|
+
if (this.closed) {
|
|
2705
|
+
await runner.dispose();
|
|
2706
|
+
throw new Error("codex session closed while follow-up was starting");
|
|
2707
|
+
}
|
|
2708
|
+
this.terminal = terminal;
|
|
1905
2709
|
this.sessionRef = sessionRef;
|
|
1906
2710
|
this.currentRunner = runner;
|
|
1907
|
-
void this.forgetRunnerOnceClosed(runner);
|
|
1908
2711
|
}
|
|
1909
2712
|
/** 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. */
|
|
1910
2713
|
async interrupt() {
|
|
1911
2714
|
this.currentRunner?.kill();
|
|
1912
2715
|
}
|
|
1913
2716
|
async close() {
|
|
1914
|
-
if (this.
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
2717
|
+
if (!this.closeAttempt) {
|
|
2718
|
+
this.closed = true;
|
|
2719
|
+
this.queue.end();
|
|
2720
|
+
const attempt = (async () => {
|
|
2721
|
+
const runners = [...this.ownedRunners];
|
|
2722
|
+
await Promise.all(runners.map((runner) => runner.dispose()));
|
|
2723
|
+
for (const runner of runners) this.ownedRunners.delete(runner);
|
|
2724
|
+
await Promise.allSettled([...this.followUpAttempts]);
|
|
2725
|
+
})();
|
|
2726
|
+
this.closeAttempt = attempt.catch((error) => {
|
|
2727
|
+
this.closeAttempt = void 0;
|
|
2728
|
+
throw error;
|
|
2729
|
+
});
|
|
2730
|
+
}
|
|
2731
|
+
await this.closeAttempt;
|
|
1918
2732
|
}
|
|
1919
2733
|
/**
|
|
1920
2734
|
* `codex exec` has no in-band channel to inject text into an already-
|
|
@@ -1950,7 +2764,16 @@ var CodexSession = class {
|
|
|
1950
2764
|
);
|
|
1951
2765
|
}
|
|
1952
2766
|
};
|
|
2767
|
+
function subscriptionModel2(selection) {
|
|
2768
|
+
if (selection === void 0) return void 0;
|
|
2769
|
+
if (selection.lane !== "subscription" || selection.runtimeId !== "codex") {
|
|
2770
|
+
throw new PolicyUnsupportedError(
|
|
2771
|
+
`codex adapter cannot execute ${selection.lane} selection for runtime ${selection.runtimeId}`
|
|
2772
|
+
);
|
|
2773
|
+
}
|
|
2774
|
+
return selection.modelId;
|
|
2775
|
+
}
|
|
1953
2776
|
|
|
1954
|
-
export { ClaudeAdapter, CodexAdapter, PI_PACKAGE_NAME, PiAdapter };
|
|
2777
|
+
export { ClaudeAdapter, CodexAdapter, PI_PACKAGE_NAME, PiAdapter, RuntimeDisposalFailure, RuntimeExecutionFailure };
|
|
1955
2778
|
//# sourceMappingURL=index.js.map
|
|
1956
2779
|
//# sourceMappingURL=index.js.map
|