@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/dist/bin/byok-agent.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { execFile, spawn
|
|
2
|
+
import { execFile, spawn } from 'child_process';
|
|
3
3
|
import { randomUUID, createHash, randomBytes, timingSafeEqual, createHmac, createPrivateKey, generateKeyPairSync, sign } from 'crypto';
|
|
4
4
|
import { readFileSync, promises, linkSync, fstatSync, lstatSync, unlinkSync, constants, readSync, openSync, writeFileSync, fchmodSync, fsyncSync, closeSync, opendirSync, existsSync, realpathSync, mkdirSync, renameSync, chmodSync, statSync, readdirSync } from 'fs';
|
|
5
5
|
import path20, { isAbsolute, join } from 'path';
|
|
@@ -887,12 +887,12 @@ function resolvePiBin() {
|
|
|
887
887
|
}
|
|
888
888
|
} catch (cause) {
|
|
889
889
|
throw new Error(
|
|
890
|
-
`Required ${PI_PACKAGE_NAME} could not be resolved; install @byok-sdk/client dependencies or set BYOK_PI_BIN to a Node 22.
|
|
890
|
+
`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`,
|
|
891
891
|
{ cause }
|
|
892
892
|
);
|
|
893
893
|
}
|
|
894
894
|
throw new Error(
|
|
895
|
-
`Required ${PI_PACKAGE_NAME} does not expose the pi CLI; reinstall the pinned dependency or set BYOK_PI_BIN to a Node 22.
|
|
895
|
+
`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`
|
|
896
896
|
);
|
|
897
897
|
}
|
|
898
898
|
|
|
@@ -931,6 +931,24 @@ function mapPermissionPolicyToPiArgs(policy) {
|
|
|
931
931
|
}
|
|
932
932
|
|
|
933
933
|
// src/adapters/pi/events.ts
|
|
934
|
+
function requireToolCallId(msg) {
|
|
935
|
+
if (typeof msg.toolCallId === "string" && msg.toolCallId.trim().length > 0) return msg.toolCallId;
|
|
936
|
+
throw new RuntimeExecutionFailure({
|
|
937
|
+
phase: "run",
|
|
938
|
+
category: "authority",
|
|
939
|
+
retry: "non-retryable",
|
|
940
|
+
reason: `pi ${msg.type} frame had no authoritative tool call id`
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
function requireToolResultOutcome(msg) {
|
|
944
|
+
if (typeof msg.isError === "boolean") return msg.isError;
|
|
945
|
+
throw new RuntimeExecutionFailure({
|
|
946
|
+
phase: "run",
|
|
947
|
+
category: "authority",
|
|
948
|
+
retry: "non-retryable",
|
|
949
|
+
reason: "pi tool_execution_end frame had no authoritative isError outcome"
|
|
950
|
+
});
|
|
951
|
+
}
|
|
934
952
|
function mapPiMessageToAgentEvent(msg) {
|
|
935
953
|
switch (msg.type) {
|
|
936
954
|
case "message_update": {
|
|
@@ -941,16 +959,22 @@ function mapPiMessageToAgentEvent(msg) {
|
|
|
941
959
|
return void 0;
|
|
942
960
|
}
|
|
943
961
|
case "tool_execution_start": {
|
|
962
|
+
const toolCallId = requireToolCallId(msg);
|
|
944
963
|
if (typeof msg.toolName !== "string") return void 0;
|
|
945
|
-
return { type: "tool_use", tool: msg.toolName, input: msg.args };
|
|
964
|
+
return { type: "tool_use", tool: msg.toolName, input: msg.args, toolCallId };
|
|
946
965
|
}
|
|
947
966
|
case "tool_execution_end": {
|
|
967
|
+
const toolCallId = requireToolCallId(msg);
|
|
968
|
+
const isError = requireToolResultOutcome(msg);
|
|
948
969
|
if (typeof msg.toolName !== "string") return void 0;
|
|
949
|
-
|
|
970
|
+
const event = {
|
|
950
971
|
type: "tool_result",
|
|
951
972
|
tool: msg.toolName,
|
|
952
|
-
output: { result: msg.result
|
|
973
|
+
output: { result: msg.result },
|
|
974
|
+
toolCallId,
|
|
975
|
+
isError
|
|
953
976
|
};
|
|
977
|
+
return event;
|
|
954
978
|
}
|
|
955
979
|
case "agent_settled":
|
|
956
980
|
return { type: "turn_end" };
|
|
@@ -1097,11 +1121,54 @@ var AsyncQueue = class {
|
|
|
1097
1121
|
};
|
|
1098
1122
|
}
|
|
1099
1123
|
};
|
|
1124
|
+
|
|
1125
|
+
// src/adapters/taskkill-pid-set.ts
|
|
1126
|
+
var INTEGER_PATTERN = /\d+/g;
|
|
1127
|
+
function isCandidatePid(value) {
|
|
1128
|
+
return Number.isSafeInteger(value) && value > 0;
|
|
1129
|
+
}
|
|
1130
|
+
function walkTaskkillPidSet(text, rootPid, excludedPids = []) {
|
|
1131
|
+
const excluded = new Set(excludedPids);
|
|
1132
|
+
const accepted = /* @__PURE__ */ new Set();
|
|
1133
|
+
if (isCandidatePid(rootPid)) accepted.add(rootPid);
|
|
1134
|
+
const lines = text.split(/\r?\n/).map((line) => {
|
|
1135
|
+
const pids = [];
|
|
1136
|
+
for (const match of line.matchAll(INTEGER_PATTERN)) {
|
|
1137
|
+
const pid = Number(match[0]);
|
|
1138
|
+
if (isCandidatePid(pid) && !excluded.has(pid)) pids.push(pid);
|
|
1139
|
+
}
|
|
1140
|
+
return pids;
|
|
1141
|
+
});
|
|
1142
|
+
let changed = true;
|
|
1143
|
+
while (changed) {
|
|
1144
|
+
changed = false;
|
|
1145
|
+
for (const pids of lines) {
|
|
1146
|
+
if (!pids.some((pid) => accepted.has(pid))) continue;
|
|
1147
|
+
for (const pid of pids) {
|
|
1148
|
+
if (accepted.has(pid)) continue;
|
|
1149
|
+
accepted.add(pid);
|
|
1150
|
+
changed = true;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
return accepted;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// src/adapters/process-tree.ts
|
|
1100
1158
|
var DEFAULT_TERM_GRACE_MS = 750;
|
|
1101
1159
|
var DEFAULT_KILL_GRACE_MS = 2e3;
|
|
1102
1160
|
var POLL_MS = 20;
|
|
1103
|
-
var
|
|
1104
|
-
|
|
1161
|
+
var terminationState = /* @__PURE__ */ new WeakMap();
|
|
1162
|
+
function stateFor(child) {
|
|
1163
|
+
const existing = terminationState.get(child);
|
|
1164
|
+
if (existing) return existing;
|
|
1165
|
+
const created = { requested: false, acceptedPids: /* @__PURE__ */ new Set() };
|
|
1166
|
+
terminationState.set(child, created);
|
|
1167
|
+
return created;
|
|
1168
|
+
}
|
|
1169
|
+
function defaultKill(pid, signal) {
|
|
1170
|
+
process.kill(pid, signal);
|
|
1171
|
+
}
|
|
1105
1172
|
function withOwnedProcessTree(options) {
|
|
1106
1173
|
return {
|
|
1107
1174
|
...options,
|
|
@@ -1119,9 +1186,9 @@ function positivePid(child, label) {
|
|
|
1119
1186
|
}
|
|
1120
1187
|
return pid;
|
|
1121
1188
|
}
|
|
1122
|
-
function groupExists(pid, label) {
|
|
1189
|
+
function groupExists(pid, label, kill) {
|
|
1123
1190
|
try {
|
|
1124
|
-
|
|
1191
|
+
kill(-pid, 0);
|
|
1125
1192
|
return true;
|
|
1126
1193
|
} catch (cause) {
|
|
1127
1194
|
const code = cause.code;
|
|
@@ -1133,9 +1200,23 @@ function groupExists(pid, label) {
|
|
|
1133
1200
|
}, { cause });
|
|
1134
1201
|
}
|
|
1135
1202
|
}
|
|
1136
|
-
function
|
|
1203
|
+
function processExists(pid, label, kill) {
|
|
1137
1204
|
try {
|
|
1138
|
-
|
|
1205
|
+
kill(pid, 0);
|
|
1206
|
+
return true;
|
|
1207
|
+
} catch (cause) {
|
|
1208
|
+
const code = cause.code;
|
|
1209
|
+
if (code === "ESRCH") return false;
|
|
1210
|
+
if (code === "EPERM") return true;
|
|
1211
|
+
throw new RuntimeDisposalFailure({
|
|
1212
|
+
stage: "quiescence",
|
|
1213
|
+
reason: `${label} runtime process ${pid} state could not be verified`
|
|
1214
|
+
}, { cause });
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
function signalGroup(pid, signal, label, kill) {
|
|
1218
|
+
try {
|
|
1219
|
+
kill(-pid, signal);
|
|
1139
1220
|
} catch (cause) {
|
|
1140
1221
|
const code = cause.code;
|
|
1141
1222
|
if (code === "ESRCH" || code === "EPERM") return;
|
|
@@ -1145,6 +1226,39 @@ function signalGroup(pid, signal, label) {
|
|
|
1145
1226
|
}, { cause });
|
|
1146
1227
|
}
|
|
1147
1228
|
}
|
|
1229
|
+
async function runTaskkill(pid, options) {
|
|
1230
|
+
const spawnFn = options.spawnFn ?? spawn;
|
|
1231
|
+
return new Promise((resolve, reject) => {
|
|
1232
|
+
const signalFailure = (cause) => {
|
|
1233
|
+
reject(new RuntimeDisposalFailure({
|
|
1234
|
+
stage: "signal",
|
|
1235
|
+
reason: `${options.label} runtime process tree termination could not be requested`
|
|
1236
|
+
}, { cause }));
|
|
1237
|
+
};
|
|
1238
|
+
let taskkill;
|
|
1239
|
+
try {
|
|
1240
|
+
taskkill = spawnFn("taskkill", ["/PID", String(pid), "/T", "/F"], {
|
|
1241
|
+
windowsHide: true,
|
|
1242
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1243
|
+
});
|
|
1244
|
+
} catch (cause) {
|
|
1245
|
+
signalFailure(cause);
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
const chunks = [];
|
|
1249
|
+
taskkill.stdout?.on("data", (chunk) => chunks.push(chunk));
|
|
1250
|
+
taskkill.stderr?.on("data", (chunk) => chunks.push(chunk));
|
|
1251
|
+
taskkill.once("error", signalFailure);
|
|
1252
|
+
taskkill.once("close", () => resolve(Buffer.concat(chunks).toString("latin1")));
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
function liveAcceptedPids(accepted, label, kill) {
|
|
1256
|
+
const live = [];
|
|
1257
|
+
for (const pid of accepted) {
|
|
1258
|
+
if (processExists(pid, label, kill)) live.push(pid);
|
|
1259
|
+
}
|
|
1260
|
+
return live;
|
|
1261
|
+
}
|
|
1148
1262
|
async function waitUntil(predicate, timeoutMs) {
|
|
1149
1263
|
const deadline = Date.now() + timeoutMs;
|
|
1150
1264
|
while (predicate()) {
|
|
@@ -1182,29 +1296,27 @@ async function waitWithDeadline(promise, timeoutMs) {
|
|
|
1182
1296
|
);
|
|
1183
1297
|
});
|
|
1184
1298
|
}
|
|
1185
|
-
function requestOwnedProcessTreeTermination(options) {
|
|
1186
|
-
|
|
1299
|
+
async function requestOwnedProcessTreeTermination(options) {
|
|
1300
|
+
const platform = options.platform ?? process.platform;
|
|
1301
|
+
if (platform !== "win32" && options.isClosed()) return;
|
|
1187
1302
|
const pid = positivePid(options.child, options.label);
|
|
1188
1303
|
if (pid === void 0) return;
|
|
1189
|
-
if (
|
|
1190
|
-
const
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
reason: `${options.label} runtime process tree could not be terminated`
|
|
1195
|
-
}, { cause: result.error });
|
|
1196
|
-
}
|
|
1197
|
-
terminationRequested.add(options.child);
|
|
1198
|
-
if (result.status !== 0) terminationRequestFailed.add(options.child);
|
|
1304
|
+
if (platform === "win32") {
|
|
1305
|
+
const output = await runTaskkill(pid, options);
|
|
1306
|
+
const state = stateFor(options.child);
|
|
1307
|
+
for (const walked of walkTaskkillPidSet(output, pid, [process.pid])) state.acceptedPids.add(walked);
|
|
1308
|
+
state.requested = true;
|
|
1199
1309
|
return;
|
|
1200
1310
|
}
|
|
1201
|
-
signalGroup(pid, "SIGTERM", options.label);
|
|
1202
|
-
|
|
1311
|
+
signalGroup(pid, "SIGTERM", options.label, options.killFn ?? defaultKill);
|
|
1312
|
+
stateFor(options.child).requested = true;
|
|
1203
1313
|
}
|
|
1204
1314
|
async function disposeOwnedProcessTree(options) {
|
|
1205
1315
|
const pid = positivePid(options.child, options.label);
|
|
1206
1316
|
const termGraceMs = options.termGraceMs ?? DEFAULT_TERM_GRACE_MS;
|
|
1207
1317
|
const killGraceMs = options.killGraceMs ?? DEFAULT_KILL_GRACE_MS;
|
|
1318
|
+
const platform = options.platform ?? process.platform;
|
|
1319
|
+
const kill = options.killFn ?? defaultKill;
|
|
1208
1320
|
if (pid === void 0) {
|
|
1209
1321
|
if (await waitWithDeadline(options.waitClosed(), killGraceMs)) return;
|
|
1210
1322
|
throw new RuntimeDisposalFailure({
|
|
@@ -1212,27 +1324,50 @@ async function disposeOwnedProcessTree(options) {
|
|
|
1212
1324
|
reason: `${options.label} runtime process did not settle after spawn failure`
|
|
1213
1325
|
});
|
|
1214
1326
|
}
|
|
1215
|
-
if (
|
|
1216
|
-
if (!
|
|
1217
|
-
|
|
1218
|
-
|
|
1327
|
+
if (platform === "win32") {
|
|
1328
|
+
if (!terminationState.get(options.child)?.requested) {
|
|
1329
|
+
await requestOwnedProcessTreeTermination(options);
|
|
1330
|
+
}
|
|
1331
|
+
const accepted = terminationState.get(options.child)?.acceptedPids ?? /* @__PURE__ */ new Set();
|
|
1332
|
+
const deadline = Date.now() + killGraceMs;
|
|
1333
|
+
const resweepAt = Date.now() + Math.floor(killGraceMs / 2);
|
|
1334
|
+
let reswept = false;
|
|
1335
|
+
let live = liveAcceptedPids(accepted, options.label, kill);
|
|
1336
|
+
while (live.length > 0) {
|
|
1337
|
+
if (Date.now() >= deadline) {
|
|
1338
|
+
throw new RuntimeDisposalFailure({
|
|
1339
|
+
stage: "quiescence",
|
|
1340
|
+
reason: `${options.label} runtime process tree did not quiesce: ${live.length} of ${accepted.size} walked process ids were still alive at the disposal deadline`
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
if (!reswept && Date.now() >= resweepAt) {
|
|
1344
|
+
reswept = true;
|
|
1345
|
+
for (const livePid of live) {
|
|
1346
|
+
for (const walked of walkTaskkillPidSet(await runTaskkill(livePid, options), livePid, [process.pid])) {
|
|
1347
|
+
accepted.add(walked);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
await new Promise((resolve) => {
|
|
1352
|
+
setTimeout(resolve, POLL_MS);
|
|
1353
|
+
});
|
|
1354
|
+
live = liveAcceptedPids(accepted, options.label, kill);
|
|
1355
|
+
}
|
|
1356
|
+
if (!await waitWithDeadline(options.waitClosed(), killGraceMs)) {
|
|
1219
1357
|
throw new RuntimeDisposalFailure({
|
|
1220
|
-
stage: "
|
|
1221
|
-
reason: `${options.label} runtime
|
|
1358
|
+
stage: "quiescence",
|
|
1359
|
+
reason: `${options.label} runtime root did not emit close after its process tree quiesced`
|
|
1222
1360
|
});
|
|
1223
1361
|
}
|
|
1224
|
-
|
|
1225
|
-
stage: "quiescence",
|
|
1226
|
-
reason: `${options.label} runtime process tree did not close before the disposal deadline`
|
|
1227
|
-
});
|
|
1362
|
+
return;
|
|
1228
1363
|
}
|
|
1229
|
-
if (groupExists(pid, options.label) && !
|
|
1230
|
-
signalGroup(pid, "SIGTERM", options.label);
|
|
1231
|
-
|
|
1364
|
+
if (groupExists(pid, options.label, kill) && !terminationState.get(options.child)?.requested) {
|
|
1365
|
+
signalGroup(pid, "SIGTERM", options.label, kill);
|
|
1366
|
+
stateFor(options.child).requested = true;
|
|
1232
1367
|
}
|
|
1233
|
-
if (!await waitUntil(() => groupExists(pid, options.label), termGraceMs)) {
|
|
1234
|
-
signalGroup(pid, "SIGKILL", options.label);
|
|
1235
|
-
if (!await waitUntil(() => groupExists(pid, options.label), killGraceMs)) {
|
|
1368
|
+
if (!await waitUntil(() => groupExists(pid, options.label, kill), termGraceMs)) {
|
|
1369
|
+
signalGroup(pid, "SIGKILL", options.label, kill);
|
|
1370
|
+
if (!await waitUntil(() => groupExists(pid, options.label, kill), killGraceMs)) {
|
|
1236
1371
|
throw new RuntimeDisposalFailure({
|
|
1237
1372
|
stage: "quiescence",
|
|
1238
1373
|
reason: `${options.label} runtime process group remained live after SIGKILL`
|
|
@@ -1330,9 +1465,16 @@ var PiRpcClient = class {
|
|
|
1330
1465
|
);
|
|
1331
1466
|
}
|
|
1332
1467
|
}
|
|
1333
|
-
/**
|
|
1468
|
+
/**
|
|
1469
|
+
* Immediate process-tree termination request. `dispose()` is the settlement
|
|
1470
|
+
* receipt, so this stays fire-and-forget: an interrupt must not block on a
|
|
1471
|
+
* terminator. A request that could not be spawned is left unrecorded, so
|
|
1472
|
+
* `dispose()` re-issues it and raises the typed `stage:'signal'` failure —
|
|
1473
|
+
* swallowing it here loses nothing.
|
|
1474
|
+
*/
|
|
1334
1475
|
kill() {
|
|
1335
|
-
requestOwnedProcessTreeTermination(this.processTreeOptions())
|
|
1476
|
+
void requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
|
|
1477
|
+
});
|
|
1336
1478
|
}
|
|
1337
1479
|
waitClosed() {
|
|
1338
1480
|
return this.closedPromise;
|
|
@@ -1844,6 +1986,17 @@ function subtractDenied(tools, denyTools) {
|
|
|
1844
1986
|
function createToolUseCorrelation() {
|
|
1845
1987
|
return { toolNameByUseId: /* @__PURE__ */ new Map() };
|
|
1846
1988
|
}
|
|
1989
|
+
function missingToolCallIdFailure(frame) {
|
|
1990
|
+
return new RuntimeExecutionFailure({
|
|
1991
|
+
phase: "run",
|
|
1992
|
+
category: "authority",
|
|
1993
|
+
retry: "non-retryable",
|
|
1994
|
+
reason: `claude ${frame} frame had no authoritative tool call id`
|
|
1995
|
+
});
|
|
1996
|
+
}
|
|
1997
|
+
function isAuthoritativeToolCallId(value) {
|
|
1998
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
1999
|
+
}
|
|
1847
2000
|
var ROUTINE_CLAUDE_SYSTEM_SUBTYPES = /* @__PURE__ */ new Set([
|
|
1848
2001
|
"init",
|
|
1849
2002
|
"hook_started",
|
|
@@ -1887,9 +2040,12 @@ function mapAssistant(msg, correlation) {
|
|
|
1887
2040
|
}
|
|
1888
2041
|
break;
|
|
1889
2042
|
case "tool_use":
|
|
1890
|
-
if (
|
|
2043
|
+
if (!isAuthoritativeToolCallId(block.id)) {
|
|
2044
|
+
return { events: [], terminalFailure: missingToolCallIdFailure("tool_use") };
|
|
2045
|
+
}
|
|
2046
|
+
if (typeof block.name === "string") {
|
|
1891
2047
|
correlation.toolNameByUseId.set(block.id, block.name);
|
|
1892
|
-
events.push({ type: "tool_use", tool: block.name, input: block.input });
|
|
2048
|
+
events.push({ type: "tool_use", tool: block.name, input: block.input, toolCallId: block.id });
|
|
1893
2049
|
}
|
|
1894
2050
|
break;
|
|
1895
2051
|
// Deliberately NOT mapped to `progress` — mirrors pi's own choice to
|
|
@@ -1925,11 +2081,20 @@ function mapUser(msg, correlation, options) {
|
|
|
1925
2081
|
unmappedLabel = unmappedLabel ?? `user-block:${String(block.type)}`;
|
|
1926
2082
|
continue;
|
|
1927
2083
|
}
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
2084
|
+
if (!isAuthoritativeToolCallId(block.tool_use_id)) {
|
|
2085
|
+
return { events: [], terminalFailure: missingToolCallIdFailure("tool_result") };
|
|
2086
|
+
}
|
|
2087
|
+
const tool = correlation.toolNameByUseId.get(block.tool_use_id) ?? "unknown";
|
|
2088
|
+
const isError = typeof block.is_error === "boolean" ? block.is_error : void 0;
|
|
2089
|
+
const event = {
|
|
2090
|
+
type: "tool_result",
|
|
2091
|
+
tool,
|
|
2092
|
+
output: { content: block.content },
|
|
2093
|
+
toolCallId: block.tool_use_id
|
|
2094
|
+
};
|
|
2095
|
+
if (isError !== void 0) event.isError = isError;
|
|
2096
|
+
events.push(event);
|
|
2097
|
+
if (isError === false && FILE_WRITING_TOOLS.has(tool)) {
|
|
1933
2098
|
const artifact = tryBuildArtifactEvent(msg, options.workspaceDir);
|
|
1934
2099
|
if (artifact) events.push(artifact);
|
|
1935
2100
|
}
|
|
@@ -2135,9 +2300,16 @@ var ClaudeProcessClient = class {
|
|
|
2135
2300
|
);
|
|
2136
2301
|
}
|
|
2137
2302
|
}
|
|
2138
|
-
/**
|
|
2303
|
+
/**
|
|
2304
|
+
* Immediate process-tree termination request. `dispose()` is the settlement
|
|
2305
|
+
* receipt, so this stays fire-and-forget: an interrupt must not block on a
|
|
2306
|
+
* terminator. A request that could not be spawned is left unrecorded, so
|
|
2307
|
+
* `dispose()` re-issues it and raises the typed `stage:'signal'` failure —
|
|
2308
|
+
* swallowing it here loses nothing.
|
|
2309
|
+
*/
|
|
2139
2310
|
kill() {
|
|
2140
|
-
requestOwnedProcessTreeTermination(this.processTreeOptions())
|
|
2311
|
+
void requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
|
|
2312
|
+
});
|
|
2141
2313
|
}
|
|
2142
2314
|
waitClosed() {
|
|
2143
2315
|
return this.closedPromise;
|
|
@@ -2545,8 +2717,8 @@ var ClaudeSession = class {
|
|
|
2545
2717
|
for (; ; ) {
|
|
2546
2718
|
const buffered = pending.shift();
|
|
2547
2719
|
if (buffered) return { value: buffered, done: false };
|
|
2720
|
+
if (terminalFailure) throw terminalFailure;
|
|
2548
2721
|
if (turnSettled) {
|
|
2549
|
-
if (terminalFailure) throw terminalFailure;
|
|
2550
2722
|
return { value: void 0, done: true };
|
|
2551
2723
|
}
|
|
2552
2724
|
let raw;
|
|
@@ -2748,6 +2920,15 @@ function extractCodexUsageEvent(rawUsage) {
|
|
|
2748
2920
|
function toNonNegativeInt2(value) {
|
|
2749
2921
|
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
2750
2922
|
}
|
|
2923
|
+
function requireToolCallId2(item) {
|
|
2924
|
+
if (typeof item.id === "string" && item.id.trim().length > 0) return item.id;
|
|
2925
|
+
throw new RuntimeExecutionFailure({
|
|
2926
|
+
phase: "run",
|
|
2927
|
+
category: "authority",
|
|
2928
|
+
retry: "non-retryable",
|
|
2929
|
+
reason: "codex tool item had no authoritative tool call id"
|
|
2930
|
+
});
|
|
2931
|
+
}
|
|
2751
2932
|
function mapItem(rawItem, phase, workspaceDir) {
|
|
2752
2933
|
if (!rawItem || typeof rawItem !== "object") return [];
|
|
2753
2934
|
const item = rawItem;
|
|
@@ -2758,9 +2939,10 @@ function mapItem(rawItem, phase, workspaceDir) {
|
|
|
2758
2939
|
return typeof item.text === "string" ? [{ type: "progress", text: item.text }] : [];
|
|
2759
2940
|
}
|
|
2760
2941
|
case "command_execution": {
|
|
2942
|
+
const toolCallId = requireToolCallId2(item);
|
|
2761
2943
|
const command = typeof item.command === "string" ? item.command : void 0;
|
|
2762
2944
|
if (phase === "started") {
|
|
2763
|
-
return command !== void 0 ? [{ type: "tool_use", tool: "command_execution", input: { command } }] : [];
|
|
2945
|
+
return command !== void 0 ? [{ type: "tool_use", tool: "command_execution", input: { command }, toolCallId }] : [];
|
|
2764
2946
|
}
|
|
2765
2947
|
return [
|
|
2766
2948
|
{
|
|
@@ -2771,17 +2953,19 @@ function mapItem(rawItem, phase, workspaceDir) {
|
|
|
2771
2953
|
aggregatedOutput: item.aggregated_output,
|
|
2772
2954
|
exitCode: item.exit_code,
|
|
2773
2955
|
status: item.status
|
|
2774
|
-
}
|
|
2956
|
+
},
|
|
2957
|
+
toolCallId
|
|
2775
2958
|
}
|
|
2776
2959
|
];
|
|
2777
2960
|
}
|
|
2778
2961
|
case "file_change": {
|
|
2962
|
+
const toolCallId = requireToolCallId2(item);
|
|
2779
2963
|
const changes = Array.isArray(item.changes) ? item.changes : [];
|
|
2780
2964
|
if (phase === "started") {
|
|
2781
|
-
return [{ type: "tool_use", tool: "file_change", input: { changes } }];
|
|
2965
|
+
return [{ type: "tool_use", tool: "file_change", input: { changes }, toolCallId }];
|
|
2782
2966
|
}
|
|
2783
2967
|
return [
|
|
2784
|
-
{ type: "tool_result", tool: "file_change", output: { changes, status: item.status } },
|
|
2968
|
+
{ type: "tool_result", tool: "file_change", output: { changes, status: item.status }, toolCallId },
|
|
2785
2969
|
...extractArtifactEvents(changes, workspaceDir)
|
|
2786
2970
|
];
|
|
2787
2971
|
}
|
|
@@ -2902,9 +3086,15 @@ var CodexProcessRunner = class {
|
|
|
2902
3086
|
* cleanly resumable afterward via `codex exec resume` (no corruption from
|
|
2903
3087
|
* killing mid-turn). `taskkill /T /F` on Windows, mirroring
|
|
2904
3088
|
* `../pi/rpc-client.ts`'s own cross-platform convention.
|
|
3089
|
+
*
|
|
3090
|
+
* Fire-and-forget by design: an interrupt must not block on a terminator,
|
|
3091
|
+
* and `dispose()` is the settlement receipt. A request that could not be
|
|
3092
|
+
* spawned is left unrecorded, so `dispose()` re-issues it and raises the
|
|
3093
|
+
* typed `stage:'signal'` failure — swallowing it here loses nothing.
|
|
2905
3094
|
*/
|
|
2906
3095
|
kill() {
|
|
2907
|
-
requestOwnedProcessTreeTermination(this.processTreeOptions())
|
|
3096
|
+
void requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
|
|
3097
|
+
});
|
|
2908
3098
|
}
|
|
2909
3099
|
dispose() {
|
|
2910
3100
|
if (!this.disposalAttempt) {
|
|
@@ -3198,7 +3388,15 @@ async function runCodexTurn(params) {
|
|
|
3198
3388
|
}
|
|
3199
3389
|
return;
|
|
3200
3390
|
}
|
|
3201
|
-
|
|
3391
|
+
let mapped;
|
|
3392
|
+
try {
|
|
3393
|
+
mapped = mapCodexEventToAgentEvents(evt, params.workspaceDir);
|
|
3394
|
+
} catch (cause) {
|
|
3395
|
+
if (!isRuntimeExecutionFailure(cause)) throw cause;
|
|
3396
|
+
params.terminal.failure = cause;
|
|
3397
|
+
params.queue.end();
|
|
3398
|
+
return;
|
|
3399
|
+
}
|
|
3202
3400
|
for (const agentEvent of mapped) {
|
|
3203
3401
|
if (agentEvent.type === "turn_end") turnEnded = true;
|
|
3204
3402
|
params.queue.push(agentEvent);
|
|
@@ -4684,14 +4882,17 @@ function parseLooseEventsPollResponse(raw) {
|
|
|
4684
4882
|
if (typeof raw !== "object" || raw === null) {
|
|
4685
4883
|
throw new Error("events poll response is not an object");
|
|
4686
4884
|
}
|
|
4687
|
-
const { events, cursor } = raw;
|
|
4885
|
+
const { events, cursor, capabilities } = raw;
|
|
4688
4886
|
if (!Array.isArray(events)) {
|
|
4689
4887
|
throw new Error("events poll response.events is not an array");
|
|
4690
4888
|
}
|
|
4691
4889
|
if (typeof cursor !== "number" || !Number.isInteger(cursor)) {
|
|
4692
4890
|
throw new Error("events poll response.cursor is not an integer");
|
|
4693
4891
|
}
|
|
4694
|
-
|
|
4892
|
+
if (capabilities !== void 0 && (!Array.isArray(capabilities) || capabilities.some((flag) => typeof flag !== "string"))) {
|
|
4893
|
+
throw new Error("events poll response.capabilities is not an array of strings");
|
|
4894
|
+
}
|
|
4895
|
+
return { events, cursor, capabilities: capabilities ?? [] };
|
|
4695
4896
|
}
|
|
4696
4897
|
function extractSkippableSeq(raw) {
|
|
4697
4898
|
if (typeof raw !== "object" || raw === null) return void 0;
|
|
@@ -4771,12 +4972,14 @@ var LongPollClient = class {
|
|
|
4771
4972
|
if (cursor !== void 0) url.searchParams.set("cursor", String(cursor));
|
|
4772
4973
|
const res = await authedFetch(url, { method: "GET" }, this.opts.auth);
|
|
4773
4974
|
if (!res.ok) {
|
|
4975
|
+
this.opts.onServerCapabilities?.([]);
|
|
4774
4976
|
this.opts.onOperationalOutcome?.("failure");
|
|
4775
4977
|
const baseMs = this.opts.retryDelayMs ?? 2e3;
|
|
4776
4978
|
await sleep(this.opts.retryDelayForAttempt?.(retryAttempt++, baseMs) ?? baseMs);
|
|
4777
4979
|
continue;
|
|
4778
4980
|
}
|
|
4779
4981
|
const parsed = parseLooseEventsPollResponse(await res.json());
|
|
4982
|
+
this.opts.onServerCapabilities?.(parsed.capabilities);
|
|
4780
4983
|
let hadValidationFailureThisBatch = false;
|
|
4781
4984
|
for (const raw of parsed.events) {
|
|
4782
4985
|
let envelope;
|
|
@@ -4820,6 +5023,7 @@ var LongPollClient = class {
|
|
|
4820
5023
|
this.opts.onOperationalOutcome?.("success");
|
|
4821
5024
|
}
|
|
4822
5025
|
} catch (err) {
|
|
5026
|
+
this.opts.onServerCapabilities?.([]);
|
|
4823
5027
|
if (err instanceof DeviceRevokedError) {
|
|
4824
5028
|
this.running = false;
|
|
4825
5029
|
this.opts.onRevoked?.();
|
|
@@ -5055,6 +5259,9 @@ var ConnectionManager = class {
|
|
|
5055
5259
|
// re-attempted.
|
|
5056
5260
|
getCursor: () => this.dedupWatermark(),
|
|
5057
5261
|
onEnvelope: (envelope) => this.deliver(envelope),
|
|
5262
|
+
onServerCapabilities: (capabilities) => {
|
|
5263
|
+
if (this.mode === "long-poll") this.serverCapabilities = capabilities;
|
|
5264
|
+
},
|
|
5058
5265
|
onRevoked: () => this.enterRevoked(),
|
|
5059
5266
|
// M4 Phase 4 (version-negotiation drill fix): a batch entry
|
|
5060
5267
|
// LongPollClient couldn't parse into a known Envelope at all (an
|
|
@@ -5186,25 +5393,24 @@ var ConnectionManager = class {
|
|
|
5186
5393
|
*/
|
|
5187
5394
|
cancelPendingDrainRetry;
|
|
5188
5395
|
/**
|
|
5189
|
-
* The capabilities the
|
|
5190
|
-
* `
|
|
5191
|
-
*
|
|
5192
|
-
*
|
|
5193
|
-
* successful handshake.
|
|
5396
|
+
* The capabilities the CURRENT transport's server advertised — untyped
|
|
5397
|
+
* `string[]` for forward compatibility. WS populates it from `conn.ack`;
|
|
5398
|
+
* long-poll populates it from each successful events response. Empty until
|
|
5399
|
+
* the active transport supplies an advertisement.
|
|
5194
5400
|
*
|
|
5195
5401
|
* Finding R2 (cross-model re-review — was P1): strictly PER-CONNECTION,
|
|
5196
5402
|
* not per-daemon-lifetime. Cleared to `[]` the instant the acked WS
|
|
5197
5403
|
* connection ends for ANY reason — an ordinary disconnect (`onWsOutcome`'s
|
|
5198
5404
|
* `acked` branch), `stop()`, or a transport switch to long-poll
|
|
5199
|
-
* (`enterLongPoll`) — and only
|
|
5405
|
+
* (`enterLongPoll`) — and only repopulated by a fresh advertisement from
|
|
5406
|
+
* the transport that is still current.
|
|
5200
5407
|
* The previous version of this doc comment claimed long-poll mode simply
|
|
5201
5408
|
* "stays at whatever the last real WS `conn.ack` said" — that was the bug:
|
|
5202
5409
|
* a daemon that once learned e.g. `approval_resolved` from an earlier WS
|
|
5203
5410
|
* session kept believing it applied to whatever it's connected to NOW,
|
|
5204
5411
|
* even after a disconnect/degrade where nothing has actually confirmed
|
|
5205
5412
|
* that's still true (a reconnect could land on a DIFFERENT server behind a
|
|
5206
|
-
* load balancer
|
|
5207
|
-
* handshake at all). Concretely, `TaskRunner.sendApprovalResolved` gates
|
|
5413
|
+
* load balancer). Concretely, `TaskRunner.sendApprovalResolved` gates
|
|
5208
5414
|
* `task.approval_resolved` on this list — sending it to a server that
|
|
5209
5415
|
* doesn't actually understand it over the long-poll path would get a
|
|
5210
5416
|
* batch-level 400 from `MessagesSendRequestSchema` (protocol §8.2), which
|
|
@@ -5324,13 +5530,10 @@ var ConnectionManager = class {
|
|
|
5324
5530
|
return this.mode === "long-poll";
|
|
5325
5531
|
}
|
|
5326
5532
|
/**
|
|
5327
|
-
* The capabilities the
|
|
5328
|
-
* `conn.ack`
|
|
5329
|
-
*
|
|
5330
|
-
*
|
|
5331
|
-
* first handshake completes, AND (finding R2) once again empty after any
|
|
5332
|
-
* disconnect/degrade — see `serverCapabilities`'s own doc comment for why
|
|
5333
|
-
* this is strictly per-connection rather than "sticky" across one.
|
|
5533
|
+
* The capabilities the CURRENT transport's server advertised: from
|
|
5534
|
+
* `conn.ack` on WS, or the latest successful `GET /byok/events` response
|
|
5535
|
+
* on long-poll. Empty before either transport has supplied its current
|
|
5536
|
+
* advertisement, and cleared across disconnect/switch boundaries.
|
|
5334
5537
|
*/
|
|
5335
5538
|
getServerCapabilities() {
|
|
5336
5539
|
return this.serverCapabilities;
|
|
@@ -9734,9 +9937,9 @@ var TaskRunner = class {
|
|
|
9734
9937
|
/**
|
|
9735
9938
|
* Whether the CURRENTLY connected server advertised `result-document` —
|
|
9736
9939
|
* read fresh on every call, never captured, because the answer changes
|
|
9737
|
-
* across a reconnect (`ConnectionManager
|
|
9738
|
-
*
|
|
9739
|
-
*
|
|
9940
|
+
* across a reconnect or transport switch (`ConnectionManager` clears the
|
|
9941
|
+
* old advertisement at the boundary, then repopulates it from a fresh WS
|
|
9942
|
+
* ack or successful poll response). An absent `getServerCapabilities` seam is
|
|
9740
9943
|
* "no capabilities", the fail-closed reading.
|
|
9741
9944
|
*/
|
|
9742
9945
|
hasResultDocumentCapability() {
|