@byok-sdk/client 0.4.0 → 0.4.2
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 +1 -1
- package/dist/adapters/claude/process-client.d.ts +7 -1
- package/dist/adapters/codex/process-runner.d.ts +5 -0
- package/dist/adapters/index.js +258 -60
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/pi/resolve-bin.d.ts +1 -1
- package/dist/adapters/pi/rpc-client.d.ts +7 -1
- package/dist/adapters/process-tree.d.ts +45 -4
- package/dist/adapters/taskkill-pid-set.d.ts +34 -0
- package/dist/bin/byok-agent.js +283 -80
- package/dist/bin/byok-agent.js.map +1 -1
- package/dist/daemon/connection-manager.d.ts +11 -15
- package/dist/daemon/long-poll-transport.d.ts +6 -0
- package/dist/daemon/task-runner.d.ts +7 -6
- package/dist/index.js +283 -80
- package/dist/index.js.map +1 -1
- package/package.json +14 -14
package/README.md
CHANGED
|
@@ -97,7 +97,13 @@ export declare class ClaudeProcessClient {
|
|
|
97
97
|
* post-mortem without separate log scraping.
|
|
98
98
|
*/
|
|
99
99
|
recordUnmappedFrame(label: string): void;
|
|
100
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* Immediate process-tree termination request. `dispose()` is the settlement
|
|
102
|
+
* receipt, so this stays fire-and-forget: an interrupt must not block on a
|
|
103
|
+
* terminator. A request that could not be spawned is left unrecorded, so
|
|
104
|
+
* `dispose()` re-issues it and raises the typed `stage:'signal'` failure —
|
|
105
|
+
* swallowing it here loses nothing.
|
|
106
|
+
*/
|
|
101
107
|
kill(): void;
|
|
102
108
|
waitClosed(): Promise<void>;
|
|
103
109
|
dispose(): Promise<void>;
|
|
@@ -77,6 +77,11 @@ export declare class CodexProcessRunner {
|
|
|
77
77
|
* cleanly resumable afterward via `codex exec resume` (no corruption from
|
|
78
78
|
* killing mid-turn). `taskkill /T /F` on Windows, mirroring
|
|
79
79
|
* `../pi/rpc-client.ts`'s own cross-platform convention.
|
|
80
|
+
*
|
|
81
|
+
* Fire-and-forget by design: an interrupt must not block on a terminator,
|
|
82
|
+
* and `dispose()` is the settlement receipt. A request that could not be
|
|
83
|
+
* spawned is left unrecorded, so `dispose()` re-issues it and raises the
|
|
84
|
+
* typed `stage:'signal'` failure — swallowing it here loses nothing.
|
|
80
85
|
*/
|
|
81
86
|
kill(): void;
|
|
82
87
|
dispose(): Promise<void>;
|
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';
|
|
@@ -128,12 +128,12 @@ function resolvePiBin() {
|
|
|
128
128
|
}
|
|
129
129
|
} catch (cause) {
|
|
130
130
|
throw new Error(
|
|
131
|
-
`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`,
|
|
132
132
|
{ cause }
|
|
133
133
|
);
|
|
134
134
|
}
|
|
135
135
|
throw new Error(
|
|
136
|
-
`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`
|
|
137
137
|
);
|
|
138
138
|
}
|
|
139
139
|
|
|
@@ -172,6 +172,24 @@ function mapPermissionPolicyToPiArgs(policy) {
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
// src/adapters/pi/events.ts
|
|
175
|
+
function requireToolCallId(msg) {
|
|
176
|
+
if (typeof msg.toolCallId === "string" && msg.toolCallId.trim().length > 0) return msg.toolCallId;
|
|
177
|
+
throw new RuntimeExecutionFailure({
|
|
178
|
+
phase: "run",
|
|
179
|
+
category: "authority",
|
|
180
|
+
retry: "non-retryable",
|
|
181
|
+
reason: `pi ${msg.type} frame had no authoritative tool call id`
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
function requireToolResultOutcome(msg) {
|
|
185
|
+
if (typeof msg.isError === "boolean") return msg.isError;
|
|
186
|
+
throw new RuntimeExecutionFailure({
|
|
187
|
+
phase: "run",
|
|
188
|
+
category: "authority",
|
|
189
|
+
retry: "non-retryable",
|
|
190
|
+
reason: "pi tool_execution_end frame had no authoritative isError outcome"
|
|
191
|
+
});
|
|
192
|
+
}
|
|
175
193
|
function mapPiMessageToAgentEvent(msg) {
|
|
176
194
|
switch (msg.type) {
|
|
177
195
|
case "message_update": {
|
|
@@ -182,16 +200,22 @@ function mapPiMessageToAgentEvent(msg) {
|
|
|
182
200
|
return void 0;
|
|
183
201
|
}
|
|
184
202
|
case "tool_execution_start": {
|
|
203
|
+
const toolCallId = requireToolCallId(msg);
|
|
185
204
|
if (typeof msg.toolName !== "string") return void 0;
|
|
186
|
-
return { type: "tool_use", tool: msg.toolName, input: msg.args };
|
|
205
|
+
return { type: "tool_use", tool: msg.toolName, input: msg.args, toolCallId };
|
|
187
206
|
}
|
|
188
207
|
case "tool_execution_end": {
|
|
208
|
+
const toolCallId = requireToolCallId(msg);
|
|
209
|
+
const isError = requireToolResultOutcome(msg);
|
|
189
210
|
if (typeof msg.toolName !== "string") return void 0;
|
|
190
|
-
|
|
211
|
+
const event = {
|
|
191
212
|
type: "tool_result",
|
|
192
213
|
tool: msg.toolName,
|
|
193
|
-
output: { result: msg.result
|
|
214
|
+
output: { result: msg.result },
|
|
215
|
+
toolCallId,
|
|
216
|
+
isError
|
|
194
217
|
};
|
|
218
|
+
return event;
|
|
195
219
|
}
|
|
196
220
|
case "agent_settled":
|
|
197
221
|
return { type: "turn_end" };
|
|
@@ -338,11 +362,54 @@ var AsyncQueue = class {
|
|
|
338
362
|
};
|
|
339
363
|
}
|
|
340
364
|
};
|
|
365
|
+
|
|
366
|
+
// src/adapters/taskkill-pid-set.ts
|
|
367
|
+
var INTEGER_PATTERN = /\d+/g;
|
|
368
|
+
function isCandidatePid(value) {
|
|
369
|
+
return Number.isSafeInteger(value) && value > 0;
|
|
370
|
+
}
|
|
371
|
+
function walkTaskkillPidSet(text, rootPid, excludedPids = []) {
|
|
372
|
+
const excluded = new Set(excludedPids);
|
|
373
|
+
const accepted = /* @__PURE__ */ new Set();
|
|
374
|
+
if (isCandidatePid(rootPid)) accepted.add(rootPid);
|
|
375
|
+
const lines = text.split(/\r?\n/).map((line) => {
|
|
376
|
+
const pids = [];
|
|
377
|
+
for (const match of line.matchAll(INTEGER_PATTERN)) {
|
|
378
|
+
const pid = Number(match[0]);
|
|
379
|
+
if (isCandidatePid(pid) && !excluded.has(pid)) pids.push(pid);
|
|
380
|
+
}
|
|
381
|
+
return pids;
|
|
382
|
+
});
|
|
383
|
+
let changed = true;
|
|
384
|
+
while (changed) {
|
|
385
|
+
changed = false;
|
|
386
|
+
for (const pids of lines) {
|
|
387
|
+
if (!pids.some((pid) => accepted.has(pid))) continue;
|
|
388
|
+
for (const pid of pids) {
|
|
389
|
+
if (accepted.has(pid)) continue;
|
|
390
|
+
accepted.add(pid);
|
|
391
|
+
changed = true;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return accepted;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// src/adapters/process-tree.ts
|
|
341
399
|
var DEFAULT_TERM_GRACE_MS = 750;
|
|
342
400
|
var DEFAULT_KILL_GRACE_MS = 2e3;
|
|
343
401
|
var POLL_MS = 20;
|
|
344
|
-
var
|
|
345
|
-
|
|
402
|
+
var terminationState = /* @__PURE__ */ new WeakMap();
|
|
403
|
+
function stateFor(child) {
|
|
404
|
+
const existing = terminationState.get(child);
|
|
405
|
+
if (existing) return existing;
|
|
406
|
+
const created = { requested: false, acceptedPids: /* @__PURE__ */ new Set() };
|
|
407
|
+
terminationState.set(child, created);
|
|
408
|
+
return created;
|
|
409
|
+
}
|
|
410
|
+
function defaultKill(pid, signal) {
|
|
411
|
+
process.kill(pid, signal);
|
|
412
|
+
}
|
|
346
413
|
function withOwnedProcessTree(options) {
|
|
347
414
|
return {
|
|
348
415
|
...options,
|
|
@@ -360,9 +427,9 @@ function positivePid(child, label) {
|
|
|
360
427
|
}
|
|
361
428
|
return pid;
|
|
362
429
|
}
|
|
363
|
-
function groupExists(pid, label) {
|
|
430
|
+
function groupExists(pid, label, kill) {
|
|
364
431
|
try {
|
|
365
|
-
|
|
432
|
+
kill(-pid, 0);
|
|
366
433
|
return true;
|
|
367
434
|
} catch (cause) {
|
|
368
435
|
const code = cause.code;
|
|
@@ -374,9 +441,23 @@ function groupExists(pid, label) {
|
|
|
374
441
|
}, { cause });
|
|
375
442
|
}
|
|
376
443
|
}
|
|
377
|
-
function
|
|
444
|
+
function processExists(pid, label, kill) {
|
|
378
445
|
try {
|
|
379
|
-
|
|
446
|
+
kill(pid, 0);
|
|
447
|
+
return true;
|
|
448
|
+
} catch (cause) {
|
|
449
|
+
const code = cause.code;
|
|
450
|
+
if (code === "ESRCH") return false;
|
|
451
|
+
if (code === "EPERM") return true;
|
|
452
|
+
throw new RuntimeDisposalFailure({
|
|
453
|
+
stage: "quiescence",
|
|
454
|
+
reason: `${label} runtime process ${pid} state could not be verified`
|
|
455
|
+
}, { cause });
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
function signalGroup(pid, signal, label, kill) {
|
|
459
|
+
try {
|
|
460
|
+
kill(-pid, signal);
|
|
380
461
|
} catch (cause) {
|
|
381
462
|
const code = cause.code;
|
|
382
463
|
if (code === "ESRCH" || code === "EPERM") return;
|
|
@@ -386,6 +467,39 @@ function signalGroup(pid, signal, label) {
|
|
|
386
467
|
}, { cause });
|
|
387
468
|
}
|
|
388
469
|
}
|
|
470
|
+
async function runTaskkill(pid, options) {
|
|
471
|
+
const spawnFn = options.spawnFn ?? spawn;
|
|
472
|
+
return new Promise((resolve, reject) => {
|
|
473
|
+
const signalFailure = (cause) => {
|
|
474
|
+
reject(new RuntimeDisposalFailure({
|
|
475
|
+
stage: "signal",
|
|
476
|
+
reason: `${options.label} runtime process tree termination could not be requested`
|
|
477
|
+
}, { cause }));
|
|
478
|
+
};
|
|
479
|
+
let taskkill;
|
|
480
|
+
try {
|
|
481
|
+
taskkill = spawnFn("taskkill", ["/PID", String(pid), "/T", "/F"], {
|
|
482
|
+
windowsHide: true,
|
|
483
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
484
|
+
});
|
|
485
|
+
} catch (cause) {
|
|
486
|
+
signalFailure(cause);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const chunks = [];
|
|
490
|
+
taskkill.stdout?.on("data", (chunk) => chunks.push(chunk));
|
|
491
|
+
taskkill.stderr?.on("data", (chunk) => chunks.push(chunk));
|
|
492
|
+
taskkill.once("error", signalFailure);
|
|
493
|
+
taskkill.once("close", () => resolve(Buffer.concat(chunks).toString("latin1")));
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
function liveAcceptedPids(accepted, label, kill) {
|
|
497
|
+
const live = [];
|
|
498
|
+
for (const pid of accepted) {
|
|
499
|
+
if (processExists(pid, label, kill)) live.push(pid);
|
|
500
|
+
}
|
|
501
|
+
return live;
|
|
502
|
+
}
|
|
389
503
|
async function waitUntil(predicate, timeoutMs) {
|
|
390
504
|
const deadline = Date.now() + timeoutMs;
|
|
391
505
|
while (predicate()) {
|
|
@@ -423,29 +537,27 @@ async function waitWithDeadline(promise, timeoutMs) {
|
|
|
423
537
|
);
|
|
424
538
|
});
|
|
425
539
|
}
|
|
426
|
-
function requestOwnedProcessTreeTermination(options) {
|
|
427
|
-
|
|
540
|
+
async function requestOwnedProcessTreeTermination(options) {
|
|
541
|
+
const platform = options.platform ?? process.platform;
|
|
542
|
+
if (platform !== "win32" && options.isClosed()) return;
|
|
428
543
|
const pid = positivePid(options.child, options.label);
|
|
429
544
|
if (pid === void 0) return;
|
|
430
|
-
if (
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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);
|
|
545
|
+
if (platform === "win32") {
|
|
546
|
+
const output = await runTaskkill(pid, options);
|
|
547
|
+
const state = stateFor(options.child);
|
|
548
|
+
for (const walked of walkTaskkillPidSet(output, pid, [process.pid])) state.acceptedPids.add(walked);
|
|
549
|
+
state.requested = true;
|
|
440
550
|
return;
|
|
441
551
|
}
|
|
442
|
-
signalGroup(pid, "SIGTERM", options.label);
|
|
443
|
-
|
|
552
|
+
signalGroup(pid, "SIGTERM", options.label, options.killFn ?? defaultKill);
|
|
553
|
+
stateFor(options.child).requested = true;
|
|
444
554
|
}
|
|
445
555
|
async function disposeOwnedProcessTree(options) {
|
|
446
556
|
const pid = positivePid(options.child, options.label);
|
|
447
557
|
const termGraceMs = options.termGraceMs ?? DEFAULT_TERM_GRACE_MS;
|
|
448
558
|
const killGraceMs = options.killGraceMs ?? DEFAULT_KILL_GRACE_MS;
|
|
559
|
+
const platform = options.platform ?? process.platform;
|
|
560
|
+
const kill = options.killFn ?? defaultKill;
|
|
449
561
|
if (pid === void 0) {
|
|
450
562
|
if (await waitWithDeadline(options.waitClosed(), killGraceMs)) return;
|
|
451
563
|
throw new RuntimeDisposalFailure({
|
|
@@ -453,27 +565,50 @@ async function disposeOwnedProcessTree(options) {
|
|
|
453
565
|
reason: `${options.label} runtime process did not settle after spawn failure`
|
|
454
566
|
});
|
|
455
567
|
}
|
|
456
|
-
if (
|
|
457
|
-
if (!
|
|
458
|
-
|
|
459
|
-
|
|
568
|
+
if (platform === "win32") {
|
|
569
|
+
if (!terminationState.get(options.child)?.requested) {
|
|
570
|
+
await requestOwnedProcessTreeTermination(options);
|
|
571
|
+
}
|
|
572
|
+
const accepted = terminationState.get(options.child)?.acceptedPids ?? /* @__PURE__ */ new Set();
|
|
573
|
+
const deadline = Date.now() + killGraceMs;
|
|
574
|
+
const resweepAt = Date.now() + Math.floor(killGraceMs / 2);
|
|
575
|
+
let reswept = false;
|
|
576
|
+
let live = liveAcceptedPids(accepted, options.label, kill);
|
|
577
|
+
while (live.length > 0) {
|
|
578
|
+
if (Date.now() >= deadline) {
|
|
579
|
+
throw new RuntimeDisposalFailure({
|
|
580
|
+
stage: "quiescence",
|
|
581
|
+
reason: `${options.label} runtime process tree did not quiesce: ${live.length} of ${accepted.size} walked process ids were still alive at the disposal deadline`
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
if (!reswept && Date.now() >= resweepAt) {
|
|
585
|
+
reswept = true;
|
|
586
|
+
for (const livePid of live) {
|
|
587
|
+
for (const walked of walkTaskkillPidSet(await runTaskkill(livePid, options), livePid, [process.pid])) {
|
|
588
|
+
accepted.add(walked);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
await new Promise((resolve) => {
|
|
593
|
+
setTimeout(resolve, POLL_MS);
|
|
594
|
+
});
|
|
595
|
+
live = liveAcceptedPids(accepted, options.label, kill);
|
|
596
|
+
}
|
|
597
|
+
if (!await waitWithDeadline(options.waitClosed(), killGraceMs)) {
|
|
460
598
|
throw new RuntimeDisposalFailure({
|
|
461
|
-
stage: "
|
|
462
|
-
reason: `${options.label} runtime
|
|
599
|
+
stage: "quiescence",
|
|
600
|
+
reason: `${options.label} runtime root did not emit close after its process tree quiesced`
|
|
463
601
|
});
|
|
464
602
|
}
|
|
465
|
-
|
|
466
|
-
stage: "quiescence",
|
|
467
|
-
reason: `${options.label} runtime process tree did not close before the disposal deadline`
|
|
468
|
-
});
|
|
603
|
+
return;
|
|
469
604
|
}
|
|
470
|
-
if (groupExists(pid, options.label) && !
|
|
471
|
-
signalGroup(pid, "SIGTERM", options.label);
|
|
472
|
-
|
|
605
|
+
if (groupExists(pid, options.label, kill) && !terminationState.get(options.child)?.requested) {
|
|
606
|
+
signalGroup(pid, "SIGTERM", options.label, kill);
|
|
607
|
+
stateFor(options.child).requested = true;
|
|
473
608
|
}
|
|
474
|
-
if (!await waitUntil(() => groupExists(pid, options.label), termGraceMs)) {
|
|
475
|
-
signalGroup(pid, "SIGKILL", options.label);
|
|
476
|
-
if (!await waitUntil(() => groupExists(pid, options.label), killGraceMs)) {
|
|
609
|
+
if (!await waitUntil(() => groupExists(pid, options.label, kill), termGraceMs)) {
|
|
610
|
+
signalGroup(pid, "SIGKILL", options.label, kill);
|
|
611
|
+
if (!await waitUntil(() => groupExists(pid, options.label, kill), killGraceMs)) {
|
|
477
612
|
throw new RuntimeDisposalFailure({
|
|
478
613
|
stage: "quiescence",
|
|
479
614
|
reason: `${options.label} runtime process group remained live after SIGKILL`
|
|
@@ -571,9 +706,16 @@ var PiRpcClient = class {
|
|
|
571
706
|
);
|
|
572
707
|
}
|
|
573
708
|
}
|
|
574
|
-
/**
|
|
709
|
+
/**
|
|
710
|
+
* Immediate process-tree termination request. `dispose()` is the settlement
|
|
711
|
+
* receipt, so this stays fire-and-forget: an interrupt must not block on a
|
|
712
|
+
* terminator. A request that could not be spawned is left unrecorded, so
|
|
713
|
+
* `dispose()` re-issues it and raises the typed `stage:'signal'` failure —
|
|
714
|
+
* swallowing it here loses nothing.
|
|
715
|
+
*/
|
|
575
716
|
kill() {
|
|
576
|
-
requestOwnedProcessTreeTermination(this.processTreeOptions())
|
|
717
|
+
void requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
|
|
718
|
+
});
|
|
577
719
|
}
|
|
578
720
|
waitClosed() {
|
|
579
721
|
return this.closedPromise;
|
|
@@ -1085,6 +1227,17 @@ function subtractDenied(tools, denyTools) {
|
|
|
1085
1227
|
function createToolUseCorrelation() {
|
|
1086
1228
|
return { toolNameByUseId: /* @__PURE__ */ new Map() };
|
|
1087
1229
|
}
|
|
1230
|
+
function missingToolCallIdFailure(frame) {
|
|
1231
|
+
return new RuntimeExecutionFailure({
|
|
1232
|
+
phase: "run",
|
|
1233
|
+
category: "authority",
|
|
1234
|
+
retry: "non-retryable",
|
|
1235
|
+
reason: `claude ${frame} frame had no authoritative tool call id`
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
function isAuthoritativeToolCallId(value) {
|
|
1239
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
1240
|
+
}
|
|
1088
1241
|
var ROUTINE_CLAUDE_SYSTEM_SUBTYPES = /* @__PURE__ */ new Set([
|
|
1089
1242
|
"init",
|
|
1090
1243
|
"hook_started",
|
|
@@ -1128,9 +1281,12 @@ function mapAssistant(msg, correlation) {
|
|
|
1128
1281
|
}
|
|
1129
1282
|
break;
|
|
1130
1283
|
case "tool_use":
|
|
1131
|
-
if (
|
|
1284
|
+
if (!isAuthoritativeToolCallId(block.id)) {
|
|
1285
|
+
return { events: [], terminalFailure: missingToolCallIdFailure("tool_use") };
|
|
1286
|
+
}
|
|
1287
|
+
if (typeof block.name === "string") {
|
|
1132
1288
|
correlation.toolNameByUseId.set(block.id, block.name);
|
|
1133
|
-
events.push({ type: "tool_use", tool: block.name, input: block.input });
|
|
1289
|
+
events.push({ type: "tool_use", tool: block.name, input: block.input, toolCallId: block.id });
|
|
1134
1290
|
}
|
|
1135
1291
|
break;
|
|
1136
1292
|
// Deliberately NOT mapped to `progress` — mirrors pi's own choice to
|
|
@@ -1166,11 +1322,20 @@ function mapUser(msg, correlation, options) {
|
|
|
1166
1322
|
unmappedLabel = unmappedLabel ?? `user-block:${String(block.type)}`;
|
|
1167
1323
|
continue;
|
|
1168
1324
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1325
|
+
if (!isAuthoritativeToolCallId(block.tool_use_id)) {
|
|
1326
|
+
return { events: [], terminalFailure: missingToolCallIdFailure("tool_result") };
|
|
1327
|
+
}
|
|
1328
|
+
const tool = correlation.toolNameByUseId.get(block.tool_use_id) ?? "unknown";
|
|
1329
|
+
const isError = typeof block.is_error === "boolean" ? block.is_error : void 0;
|
|
1330
|
+
const event = {
|
|
1331
|
+
type: "tool_result",
|
|
1332
|
+
tool,
|
|
1333
|
+
output: { content: block.content },
|
|
1334
|
+
toolCallId: block.tool_use_id
|
|
1335
|
+
};
|
|
1336
|
+
if (isError !== void 0) event.isError = isError;
|
|
1337
|
+
events.push(event);
|
|
1338
|
+
if (isError === false && FILE_WRITING_TOOLS.has(tool)) {
|
|
1174
1339
|
const artifact = tryBuildArtifactEvent(msg, options.workspaceDir);
|
|
1175
1340
|
if (artifact) events.push(artifact);
|
|
1176
1341
|
}
|
|
@@ -1376,9 +1541,16 @@ var ClaudeProcessClient = class {
|
|
|
1376
1541
|
);
|
|
1377
1542
|
}
|
|
1378
1543
|
}
|
|
1379
|
-
/**
|
|
1544
|
+
/**
|
|
1545
|
+
* Immediate process-tree termination request. `dispose()` is the settlement
|
|
1546
|
+
* receipt, so this stays fire-and-forget: an interrupt must not block on a
|
|
1547
|
+
* terminator. A request that could not be spawned is left unrecorded, so
|
|
1548
|
+
* `dispose()` re-issues it and raises the typed `stage:'signal'` failure —
|
|
1549
|
+
* swallowing it here loses nothing.
|
|
1550
|
+
*/
|
|
1380
1551
|
kill() {
|
|
1381
|
-
requestOwnedProcessTreeTermination(this.processTreeOptions())
|
|
1552
|
+
void requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
|
|
1553
|
+
});
|
|
1382
1554
|
}
|
|
1383
1555
|
waitClosed() {
|
|
1384
1556
|
return this.closedPromise;
|
|
@@ -1786,8 +1958,8 @@ var ClaudeSession = class {
|
|
|
1786
1958
|
for (; ; ) {
|
|
1787
1959
|
const buffered = pending.shift();
|
|
1788
1960
|
if (buffered) return { value: buffered, done: false };
|
|
1961
|
+
if (terminalFailure) throw terminalFailure;
|
|
1789
1962
|
if (turnSettled) {
|
|
1790
|
-
if (terminalFailure) throw terminalFailure;
|
|
1791
1963
|
return { value: void 0, done: true };
|
|
1792
1964
|
}
|
|
1793
1965
|
let raw;
|
|
@@ -1989,6 +2161,15 @@ function extractCodexUsageEvent(rawUsage) {
|
|
|
1989
2161
|
function toNonNegativeInt2(value) {
|
|
1990
2162
|
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
1991
2163
|
}
|
|
2164
|
+
function requireToolCallId2(item) {
|
|
2165
|
+
if (typeof item.id === "string" && item.id.trim().length > 0) return item.id;
|
|
2166
|
+
throw new RuntimeExecutionFailure({
|
|
2167
|
+
phase: "run",
|
|
2168
|
+
category: "authority",
|
|
2169
|
+
retry: "non-retryable",
|
|
2170
|
+
reason: "codex tool item had no authoritative tool call id"
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
1992
2173
|
function mapItem(rawItem, phase, workspaceDir) {
|
|
1993
2174
|
if (!rawItem || typeof rawItem !== "object") return [];
|
|
1994
2175
|
const item = rawItem;
|
|
@@ -1999,9 +2180,10 @@ function mapItem(rawItem, phase, workspaceDir) {
|
|
|
1999
2180
|
return typeof item.text === "string" ? [{ type: "progress", text: item.text }] : [];
|
|
2000
2181
|
}
|
|
2001
2182
|
case "command_execution": {
|
|
2183
|
+
const toolCallId = requireToolCallId2(item);
|
|
2002
2184
|
const command = typeof item.command === "string" ? item.command : void 0;
|
|
2003
2185
|
if (phase === "started") {
|
|
2004
|
-
return command !== void 0 ? [{ type: "tool_use", tool: "command_execution", input: { command } }] : [];
|
|
2186
|
+
return command !== void 0 ? [{ type: "tool_use", tool: "command_execution", input: { command }, toolCallId }] : [];
|
|
2005
2187
|
}
|
|
2006
2188
|
return [
|
|
2007
2189
|
{
|
|
@@ -2012,17 +2194,19 @@ function mapItem(rawItem, phase, workspaceDir) {
|
|
|
2012
2194
|
aggregatedOutput: item.aggregated_output,
|
|
2013
2195
|
exitCode: item.exit_code,
|
|
2014
2196
|
status: item.status
|
|
2015
|
-
}
|
|
2197
|
+
},
|
|
2198
|
+
toolCallId
|
|
2016
2199
|
}
|
|
2017
2200
|
];
|
|
2018
2201
|
}
|
|
2019
2202
|
case "file_change": {
|
|
2203
|
+
const toolCallId = requireToolCallId2(item);
|
|
2020
2204
|
const changes = Array.isArray(item.changes) ? item.changes : [];
|
|
2021
2205
|
if (phase === "started") {
|
|
2022
|
-
return [{ type: "tool_use", tool: "file_change", input: { changes } }];
|
|
2206
|
+
return [{ type: "tool_use", tool: "file_change", input: { changes }, toolCallId }];
|
|
2023
2207
|
}
|
|
2024
2208
|
return [
|
|
2025
|
-
{ type: "tool_result", tool: "file_change", output: { changes, status: item.status } },
|
|
2209
|
+
{ type: "tool_result", tool: "file_change", output: { changes, status: item.status }, toolCallId },
|
|
2026
2210
|
...extractArtifactEvents(changes, workspaceDir)
|
|
2027
2211
|
];
|
|
2028
2212
|
}
|
|
@@ -2143,9 +2327,15 @@ var CodexProcessRunner = class {
|
|
|
2143
2327
|
* cleanly resumable afterward via `codex exec resume` (no corruption from
|
|
2144
2328
|
* killing mid-turn). `taskkill /T /F` on Windows, mirroring
|
|
2145
2329
|
* `../pi/rpc-client.ts`'s own cross-platform convention.
|
|
2330
|
+
*
|
|
2331
|
+
* Fire-and-forget by design: an interrupt must not block on a terminator,
|
|
2332
|
+
* and `dispose()` is the settlement receipt. A request that could not be
|
|
2333
|
+
* spawned is left unrecorded, so `dispose()` re-issues it and raises the
|
|
2334
|
+
* typed `stage:'signal'` failure — swallowing it here loses nothing.
|
|
2146
2335
|
*/
|
|
2147
2336
|
kill() {
|
|
2148
|
-
requestOwnedProcessTreeTermination(this.processTreeOptions())
|
|
2337
|
+
void requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
|
|
2338
|
+
});
|
|
2149
2339
|
}
|
|
2150
2340
|
dispose() {
|
|
2151
2341
|
if (!this.disposalAttempt) {
|
|
@@ -2439,7 +2629,15 @@ async function runCodexTurn(params) {
|
|
|
2439
2629
|
}
|
|
2440
2630
|
return;
|
|
2441
2631
|
}
|
|
2442
|
-
|
|
2632
|
+
let mapped;
|
|
2633
|
+
try {
|
|
2634
|
+
mapped = mapCodexEventToAgentEvents(evt, params.workspaceDir);
|
|
2635
|
+
} catch (cause) {
|
|
2636
|
+
if (!isRuntimeExecutionFailure(cause)) throw cause;
|
|
2637
|
+
params.terminal.failure = cause;
|
|
2638
|
+
params.queue.end();
|
|
2639
|
+
return;
|
|
2640
|
+
}
|
|
2443
2641
|
for (const agentEvent of mapped) {
|
|
2444
2642
|
if (agentEvent.type === "turn_end") turnEnded = true;
|
|
2445
2643
|
params.queue.push(agentEvent);
|