@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/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execFile, spawn
|
|
1
|
+
import { execFile, spawn } from 'child_process';
|
|
2
2
|
import { createHash, randomUUID, sign, createPrivateKey, generateKeyPairSync, randomBytes, timingSafeEqual, createHmac } from 'crypto';
|
|
3
3
|
import { promises, mkdirSync, existsSync, renameSync, writeFileSync, chmodSync, statSync, readdirSync, linkSync, fstatSync, lstatSync, unlinkSync, constants, readFileSync, realpathSync } from 'fs';
|
|
4
4
|
import path17, { join, isAbsolute } from 'path';
|
|
@@ -834,12 +834,12 @@ function resolvePiBin() {
|
|
|
834
834
|
}
|
|
835
835
|
} catch (cause) {
|
|
836
836
|
throw new Error(
|
|
837
|
-
`Required ${PI_PACKAGE_NAME} could not be resolved; install @byok-sdk/client dependencies or set BYOK_PI_BIN to a Node 22.
|
|
837
|
+
`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`,
|
|
838
838
|
{ cause }
|
|
839
839
|
);
|
|
840
840
|
}
|
|
841
841
|
throw new Error(
|
|
842
|
-
`Required ${PI_PACKAGE_NAME} does not expose the pi CLI; reinstall the pinned dependency or set BYOK_PI_BIN to a Node 22.
|
|
842
|
+
`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`
|
|
843
843
|
);
|
|
844
844
|
}
|
|
845
845
|
|
|
@@ -878,6 +878,24 @@ function mapPermissionPolicyToPiArgs(policy) {
|
|
|
878
878
|
}
|
|
879
879
|
|
|
880
880
|
// src/adapters/pi/events.ts
|
|
881
|
+
function requireToolCallId(msg) {
|
|
882
|
+
if (typeof msg.toolCallId === "string" && msg.toolCallId.trim().length > 0) return msg.toolCallId;
|
|
883
|
+
throw new RuntimeExecutionFailure({
|
|
884
|
+
phase: "run",
|
|
885
|
+
category: "authority",
|
|
886
|
+
retry: "non-retryable",
|
|
887
|
+
reason: `pi ${msg.type} frame had no authoritative tool call id`
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
function requireToolResultOutcome(msg) {
|
|
891
|
+
if (typeof msg.isError === "boolean") return msg.isError;
|
|
892
|
+
throw new RuntimeExecutionFailure({
|
|
893
|
+
phase: "run",
|
|
894
|
+
category: "authority",
|
|
895
|
+
retry: "non-retryable",
|
|
896
|
+
reason: "pi tool_execution_end frame had no authoritative isError outcome"
|
|
897
|
+
});
|
|
898
|
+
}
|
|
881
899
|
function mapPiMessageToAgentEvent(msg) {
|
|
882
900
|
switch (msg.type) {
|
|
883
901
|
case "message_update": {
|
|
@@ -888,16 +906,22 @@ function mapPiMessageToAgentEvent(msg) {
|
|
|
888
906
|
return void 0;
|
|
889
907
|
}
|
|
890
908
|
case "tool_execution_start": {
|
|
909
|
+
const toolCallId = requireToolCallId(msg);
|
|
891
910
|
if (typeof msg.toolName !== "string") return void 0;
|
|
892
|
-
return { type: "tool_use", tool: msg.toolName, input: msg.args };
|
|
911
|
+
return { type: "tool_use", tool: msg.toolName, input: msg.args, toolCallId };
|
|
893
912
|
}
|
|
894
913
|
case "tool_execution_end": {
|
|
914
|
+
const toolCallId = requireToolCallId(msg);
|
|
915
|
+
const isError = requireToolResultOutcome(msg);
|
|
895
916
|
if (typeof msg.toolName !== "string") return void 0;
|
|
896
|
-
|
|
917
|
+
const event = {
|
|
897
918
|
type: "tool_result",
|
|
898
919
|
tool: msg.toolName,
|
|
899
|
-
output: { result: msg.result
|
|
920
|
+
output: { result: msg.result },
|
|
921
|
+
toolCallId,
|
|
922
|
+
isError
|
|
900
923
|
};
|
|
924
|
+
return event;
|
|
901
925
|
}
|
|
902
926
|
case "agent_settled":
|
|
903
927
|
return { type: "turn_end" };
|
|
@@ -1044,11 +1068,54 @@ var AsyncQueue = class {
|
|
|
1044
1068
|
};
|
|
1045
1069
|
}
|
|
1046
1070
|
};
|
|
1071
|
+
|
|
1072
|
+
// src/adapters/taskkill-pid-set.ts
|
|
1073
|
+
var INTEGER_PATTERN = /\d+/g;
|
|
1074
|
+
function isCandidatePid(value) {
|
|
1075
|
+
return Number.isSafeInteger(value) && value > 0;
|
|
1076
|
+
}
|
|
1077
|
+
function walkTaskkillPidSet(text, rootPid, excludedPids = []) {
|
|
1078
|
+
const excluded = new Set(excludedPids);
|
|
1079
|
+
const accepted = /* @__PURE__ */ new Set();
|
|
1080
|
+
if (isCandidatePid(rootPid)) accepted.add(rootPid);
|
|
1081
|
+
const lines = text.split(/\r?\n/).map((line) => {
|
|
1082
|
+
const pids = [];
|
|
1083
|
+
for (const match of line.matchAll(INTEGER_PATTERN)) {
|
|
1084
|
+
const pid = Number(match[0]);
|
|
1085
|
+
if (isCandidatePid(pid) && !excluded.has(pid)) pids.push(pid);
|
|
1086
|
+
}
|
|
1087
|
+
return pids;
|
|
1088
|
+
});
|
|
1089
|
+
let changed = true;
|
|
1090
|
+
while (changed) {
|
|
1091
|
+
changed = false;
|
|
1092
|
+
for (const pids of lines) {
|
|
1093
|
+
if (!pids.some((pid) => accepted.has(pid))) continue;
|
|
1094
|
+
for (const pid of pids) {
|
|
1095
|
+
if (accepted.has(pid)) continue;
|
|
1096
|
+
accepted.add(pid);
|
|
1097
|
+
changed = true;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
return accepted;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// src/adapters/process-tree.ts
|
|
1047
1105
|
var DEFAULT_TERM_GRACE_MS = 750;
|
|
1048
1106
|
var DEFAULT_KILL_GRACE_MS = 2e3;
|
|
1049
1107
|
var POLL_MS = 20;
|
|
1050
|
-
var
|
|
1051
|
-
|
|
1108
|
+
var terminationState = /* @__PURE__ */ new WeakMap();
|
|
1109
|
+
function stateFor(child) {
|
|
1110
|
+
const existing = terminationState.get(child);
|
|
1111
|
+
if (existing) return existing;
|
|
1112
|
+
const created = { requested: false, acceptedPids: /* @__PURE__ */ new Set() };
|
|
1113
|
+
terminationState.set(child, created);
|
|
1114
|
+
return created;
|
|
1115
|
+
}
|
|
1116
|
+
function defaultKill(pid, signal) {
|
|
1117
|
+
process.kill(pid, signal);
|
|
1118
|
+
}
|
|
1052
1119
|
function withOwnedProcessTree(options) {
|
|
1053
1120
|
return {
|
|
1054
1121
|
...options,
|
|
@@ -1066,9 +1133,9 @@ function positivePid(child, label) {
|
|
|
1066
1133
|
}
|
|
1067
1134
|
return pid;
|
|
1068
1135
|
}
|
|
1069
|
-
function groupExists(pid, label) {
|
|
1136
|
+
function groupExists(pid, label, kill) {
|
|
1070
1137
|
try {
|
|
1071
|
-
|
|
1138
|
+
kill(-pid, 0);
|
|
1072
1139
|
return true;
|
|
1073
1140
|
} catch (cause) {
|
|
1074
1141
|
const code = cause.code;
|
|
@@ -1080,9 +1147,23 @@ function groupExists(pid, label) {
|
|
|
1080
1147
|
}, { cause });
|
|
1081
1148
|
}
|
|
1082
1149
|
}
|
|
1083
|
-
function
|
|
1150
|
+
function processExists(pid, label, kill) {
|
|
1084
1151
|
try {
|
|
1085
|
-
|
|
1152
|
+
kill(pid, 0);
|
|
1153
|
+
return true;
|
|
1154
|
+
} catch (cause) {
|
|
1155
|
+
const code = cause.code;
|
|
1156
|
+
if (code === "ESRCH") return false;
|
|
1157
|
+
if (code === "EPERM") return true;
|
|
1158
|
+
throw new RuntimeDisposalFailure({
|
|
1159
|
+
stage: "quiescence",
|
|
1160
|
+
reason: `${label} runtime process ${pid} state could not be verified`
|
|
1161
|
+
}, { cause });
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
function signalGroup(pid, signal, label, kill) {
|
|
1165
|
+
try {
|
|
1166
|
+
kill(-pid, signal);
|
|
1086
1167
|
} catch (cause) {
|
|
1087
1168
|
const code = cause.code;
|
|
1088
1169
|
if (code === "ESRCH" || code === "EPERM") return;
|
|
@@ -1092,6 +1173,39 @@ function signalGroup(pid, signal, label) {
|
|
|
1092
1173
|
}, { cause });
|
|
1093
1174
|
}
|
|
1094
1175
|
}
|
|
1176
|
+
async function runTaskkill(pid, options) {
|
|
1177
|
+
const spawnFn = options.spawnFn ?? spawn;
|
|
1178
|
+
return new Promise((resolve, reject) => {
|
|
1179
|
+
const signalFailure = (cause) => {
|
|
1180
|
+
reject(new RuntimeDisposalFailure({
|
|
1181
|
+
stage: "signal",
|
|
1182
|
+
reason: `${options.label} runtime process tree termination could not be requested`
|
|
1183
|
+
}, { cause }));
|
|
1184
|
+
};
|
|
1185
|
+
let taskkill;
|
|
1186
|
+
try {
|
|
1187
|
+
taskkill = spawnFn("taskkill", ["/PID", String(pid), "/T", "/F"], {
|
|
1188
|
+
windowsHide: true,
|
|
1189
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1190
|
+
});
|
|
1191
|
+
} catch (cause) {
|
|
1192
|
+
signalFailure(cause);
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
const chunks = [];
|
|
1196
|
+
taskkill.stdout?.on("data", (chunk) => chunks.push(chunk));
|
|
1197
|
+
taskkill.stderr?.on("data", (chunk) => chunks.push(chunk));
|
|
1198
|
+
taskkill.once("error", signalFailure);
|
|
1199
|
+
taskkill.once("close", () => resolve(Buffer.concat(chunks).toString("latin1")));
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
function liveAcceptedPids(accepted, label, kill) {
|
|
1203
|
+
const live = [];
|
|
1204
|
+
for (const pid of accepted) {
|
|
1205
|
+
if (processExists(pid, label, kill)) live.push(pid);
|
|
1206
|
+
}
|
|
1207
|
+
return live;
|
|
1208
|
+
}
|
|
1095
1209
|
async function waitUntil(predicate, timeoutMs) {
|
|
1096
1210
|
const deadline = Date.now() + timeoutMs;
|
|
1097
1211
|
while (predicate()) {
|
|
@@ -1129,29 +1243,27 @@ async function waitWithDeadline(promise, timeoutMs) {
|
|
|
1129
1243
|
);
|
|
1130
1244
|
});
|
|
1131
1245
|
}
|
|
1132
|
-
function requestOwnedProcessTreeTermination(options) {
|
|
1133
|
-
|
|
1246
|
+
async function requestOwnedProcessTreeTermination(options) {
|
|
1247
|
+
const platform = options.platform ?? process.platform;
|
|
1248
|
+
if (platform !== "win32" && options.isClosed()) return;
|
|
1134
1249
|
const pid = positivePid(options.child, options.label);
|
|
1135
1250
|
if (pid === void 0) return;
|
|
1136
|
-
if (
|
|
1137
|
-
const
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
reason: `${options.label} runtime process tree could not be terminated`
|
|
1142
|
-
}, { cause: result.error });
|
|
1143
|
-
}
|
|
1144
|
-
terminationRequested.add(options.child);
|
|
1145
|
-
if (result.status !== 0) terminationRequestFailed.add(options.child);
|
|
1251
|
+
if (platform === "win32") {
|
|
1252
|
+
const output = await runTaskkill(pid, options);
|
|
1253
|
+
const state = stateFor(options.child);
|
|
1254
|
+
for (const walked of walkTaskkillPidSet(output, pid, [process.pid])) state.acceptedPids.add(walked);
|
|
1255
|
+
state.requested = true;
|
|
1146
1256
|
return;
|
|
1147
1257
|
}
|
|
1148
|
-
signalGroup(pid, "SIGTERM", options.label);
|
|
1149
|
-
|
|
1258
|
+
signalGroup(pid, "SIGTERM", options.label, options.killFn ?? defaultKill);
|
|
1259
|
+
stateFor(options.child).requested = true;
|
|
1150
1260
|
}
|
|
1151
1261
|
async function disposeOwnedProcessTree(options) {
|
|
1152
1262
|
const pid = positivePid(options.child, options.label);
|
|
1153
1263
|
const termGraceMs = options.termGraceMs ?? DEFAULT_TERM_GRACE_MS;
|
|
1154
1264
|
const killGraceMs = options.killGraceMs ?? DEFAULT_KILL_GRACE_MS;
|
|
1265
|
+
const platform = options.platform ?? process.platform;
|
|
1266
|
+
const kill = options.killFn ?? defaultKill;
|
|
1155
1267
|
if (pid === void 0) {
|
|
1156
1268
|
if (await waitWithDeadline(options.waitClosed(), killGraceMs)) return;
|
|
1157
1269
|
throw new RuntimeDisposalFailure({
|
|
@@ -1159,27 +1271,50 @@ async function disposeOwnedProcessTree(options) {
|
|
|
1159
1271
|
reason: `${options.label} runtime process did not settle after spawn failure`
|
|
1160
1272
|
});
|
|
1161
1273
|
}
|
|
1162
|
-
if (
|
|
1163
|
-
if (!
|
|
1164
|
-
|
|
1165
|
-
|
|
1274
|
+
if (platform === "win32") {
|
|
1275
|
+
if (!terminationState.get(options.child)?.requested) {
|
|
1276
|
+
await requestOwnedProcessTreeTermination(options);
|
|
1277
|
+
}
|
|
1278
|
+
const accepted = terminationState.get(options.child)?.acceptedPids ?? /* @__PURE__ */ new Set();
|
|
1279
|
+
const deadline = Date.now() + killGraceMs;
|
|
1280
|
+
const resweepAt = Date.now() + Math.floor(killGraceMs / 2);
|
|
1281
|
+
let reswept = false;
|
|
1282
|
+
let live = liveAcceptedPids(accepted, options.label, kill);
|
|
1283
|
+
while (live.length > 0) {
|
|
1284
|
+
if (Date.now() >= deadline) {
|
|
1285
|
+
throw new RuntimeDisposalFailure({
|
|
1286
|
+
stage: "quiescence",
|
|
1287
|
+
reason: `${options.label} runtime process tree did not quiesce: ${live.length} of ${accepted.size} walked process ids were still alive at the disposal deadline`
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
if (!reswept && Date.now() >= resweepAt) {
|
|
1291
|
+
reswept = true;
|
|
1292
|
+
for (const livePid of live) {
|
|
1293
|
+
for (const walked of walkTaskkillPidSet(await runTaskkill(livePid, options), livePid, [process.pid])) {
|
|
1294
|
+
accepted.add(walked);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
await new Promise((resolve) => {
|
|
1299
|
+
setTimeout(resolve, POLL_MS);
|
|
1300
|
+
});
|
|
1301
|
+
live = liveAcceptedPids(accepted, options.label, kill);
|
|
1302
|
+
}
|
|
1303
|
+
if (!await waitWithDeadline(options.waitClosed(), killGraceMs)) {
|
|
1166
1304
|
throw new RuntimeDisposalFailure({
|
|
1167
|
-
stage: "
|
|
1168
|
-
reason: `${options.label} runtime
|
|
1305
|
+
stage: "quiescence",
|
|
1306
|
+
reason: `${options.label} runtime root did not emit close after its process tree quiesced`
|
|
1169
1307
|
});
|
|
1170
1308
|
}
|
|
1171
|
-
|
|
1172
|
-
stage: "quiescence",
|
|
1173
|
-
reason: `${options.label} runtime process tree did not close before the disposal deadline`
|
|
1174
|
-
});
|
|
1309
|
+
return;
|
|
1175
1310
|
}
|
|
1176
|
-
if (groupExists(pid, options.label) && !
|
|
1177
|
-
signalGroup(pid, "SIGTERM", options.label);
|
|
1178
|
-
|
|
1311
|
+
if (groupExists(pid, options.label, kill) && !terminationState.get(options.child)?.requested) {
|
|
1312
|
+
signalGroup(pid, "SIGTERM", options.label, kill);
|
|
1313
|
+
stateFor(options.child).requested = true;
|
|
1179
1314
|
}
|
|
1180
|
-
if (!await waitUntil(() => groupExists(pid, options.label), termGraceMs)) {
|
|
1181
|
-
signalGroup(pid, "SIGKILL", options.label);
|
|
1182
|
-
if (!await waitUntil(() => groupExists(pid, options.label), killGraceMs)) {
|
|
1315
|
+
if (!await waitUntil(() => groupExists(pid, options.label, kill), termGraceMs)) {
|
|
1316
|
+
signalGroup(pid, "SIGKILL", options.label, kill);
|
|
1317
|
+
if (!await waitUntil(() => groupExists(pid, options.label, kill), killGraceMs)) {
|
|
1183
1318
|
throw new RuntimeDisposalFailure({
|
|
1184
1319
|
stage: "quiescence",
|
|
1185
1320
|
reason: `${options.label} runtime process group remained live after SIGKILL`
|
|
@@ -1277,9 +1412,16 @@ var PiRpcClient = class {
|
|
|
1277
1412
|
);
|
|
1278
1413
|
}
|
|
1279
1414
|
}
|
|
1280
|
-
/**
|
|
1415
|
+
/**
|
|
1416
|
+
* Immediate process-tree termination request. `dispose()` is the settlement
|
|
1417
|
+
* receipt, so this stays fire-and-forget: an interrupt must not block on a
|
|
1418
|
+
* terminator. A request that could not be spawned is left unrecorded, so
|
|
1419
|
+
* `dispose()` re-issues it and raises the typed `stage:'signal'` failure —
|
|
1420
|
+
* swallowing it here loses nothing.
|
|
1421
|
+
*/
|
|
1281
1422
|
kill() {
|
|
1282
|
-
requestOwnedProcessTreeTermination(this.processTreeOptions())
|
|
1423
|
+
void requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
|
|
1424
|
+
});
|
|
1283
1425
|
}
|
|
1284
1426
|
waitClosed() {
|
|
1285
1427
|
return this.closedPromise;
|
|
@@ -1791,6 +1933,17 @@ function subtractDenied(tools, denyTools) {
|
|
|
1791
1933
|
function createToolUseCorrelation() {
|
|
1792
1934
|
return { toolNameByUseId: /* @__PURE__ */ new Map() };
|
|
1793
1935
|
}
|
|
1936
|
+
function missingToolCallIdFailure(frame) {
|
|
1937
|
+
return new RuntimeExecutionFailure({
|
|
1938
|
+
phase: "run",
|
|
1939
|
+
category: "authority",
|
|
1940
|
+
retry: "non-retryable",
|
|
1941
|
+
reason: `claude ${frame} frame had no authoritative tool call id`
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1944
|
+
function isAuthoritativeToolCallId(value) {
|
|
1945
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
1946
|
+
}
|
|
1794
1947
|
var ROUTINE_CLAUDE_SYSTEM_SUBTYPES = /* @__PURE__ */ new Set([
|
|
1795
1948
|
"init",
|
|
1796
1949
|
"hook_started",
|
|
@@ -1834,9 +1987,12 @@ function mapAssistant(msg, correlation) {
|
|
|
1834
1987
|
}
|
|
1835
1988
|
break;
|
|
1836
1989
|
case "tool_use":
|
|
1837
|
-
if (
|
|
1990
|
+
if (!isAuthoritativeToolCallId(block.id)) {
|
|
1991
|
+
return { events: [], terminalFailure: missingToolCallIdFailure("tool_use") };
|
|
1992
|
+
}
|
|
1993
|
+
if (typeof block.name === "string") {
|
|
1838
1994
|
correlation.toolNameByUseId.set(block.id, block.name);
|
|
1839
|
-
events.push({ type: "tool_use", tool: block.name, input: block.input });
|
|
1995
|
+
events.push({ type: "tool_use", tool: block.name, input: block.input, toolCallId: block.id });
|
|
1840
1996
|
}
|
|
1841
1997
|
break;
|
|
1842
1998
|
// Deliberately NOT mapped to `progress` — mirrors pi's own choice to
|
|
@@ -1872,11 +2028,20 @@ function mapUser(msg, correlation, options) {
|
|
|
1872
2028
|
unmappedLabel = unmappedLabel ?? `user-block:${String(block.type)}`;
|
|
1873
2029
|
continue;
|
|
1874
2030
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
2031
|
+
if (!isAuthoritativeToolCallId(block.tool_use_id)) {
|
|
2032
|
+
return { events: [], terminalFailure: missingToolCallIdFailure("tool_result") };
|
|
2033
|
+
}
|
|
2034
|
+
const tool = correlation.toolNameByUseId.get(block.tool_use_id) ?? "unknown";
|
|
2035
|
+
const isError = typeof block.is_error === "boolean" ? block.is_error : void 0;
|
|
2036
|
+
const event = {
|
|
2037
|
+
type: "tool_result",
|
|
2038
|
+
tool,
|
|
2039
|
+
output: { content: block.content },
|
|
2040
|
+
toolCallId: block.tool_use_id
|
|
2041
|
+
};
|
|
2042
|
+
if (isError !== void 0) event.isError = isError;
|
|
2043
|
+
events.push(event);
|
|
2044
|
+
if (isError === false && FILE_WRITING_TOOLS.has(tool)) {
|
|
1880
2045
|
const artifact = tryBuildArtifactEvent(msg, options.workspaceDir);
|
|
1881
2046
|
if (artifact) events.push(artifact);
|
|
1882
2047
|
}
|
|
@@ -2082,9 +2247,16 @@ var ClaudeProcessClient = class {
|
|
|
2082
2247
|
);
|
|
2083
2248
|
}
|
|
2084
2249
|
}
|
|
2085
|
-
/**
|
|
2250
|
+
/**
|
|
2251
|
+
* Immediate process-tree termination request. `dispose()` is the settlement
|
|
2252
|
+
* receipt, so this stays fire-and-forget: an interrupt must not block on a
|
|
2253
|
+
* terminator. A request that could not be spawned is left unrecorded, so
|
|
2254
|
+
* `dispose()` re-issues it and raises the typed `stage:'signal'` failure —
|
|
2255
|
+
* swallowing it here loses nothing.
|
|
2256
|
+
*/
|
|
2086
2257
|
kill() {
|
|
2087
|
-
requestOwnedProcessTreeTermination(this.processTreeOptions())
|
|
2258
|
+
void requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
|
|
2259
|
+
});
|
|
2088
2260
|
}
|
|
2089
2261
|
waitClosed() {
|
|
2090
2262
|
return this.closedPromise;
|
|
@@ -2492,8 +2664,8 @@ var ClaudeSession = class {
|
|
|
2492
2664
|
for (; ; ) {
|
|
2493
2665
|
const buffered = pending.shift();
|
|
2494
2666
|
if (buffered) return { value: buffered, done: false };
|
|
2667
|
+
if (terminalFailure) throw terminalFailure;
|
|
2495
2668
|
if (turnSettled) {
|
|
2496
|
-
if (terminalFailure) throw terminalFailure;
|
|
2497
2669
|
return { value: void 0, done: true };
|
|
2498
2670
|
}
|
|
2499
2671
|
let raw;
|
|
@@ -2695,6 +2867,15 @@ function extractCodexUsageEvent(rawUsage) {
|
|
|
2695
2867
|
function toNonNegativeInt2(value) {
|
|
2696
2868
|
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
2697
2869
|
}
|
|
2870
|
+
function requireToolCallId2(item) {
|
|
2871
|
+
if (typeof item.id === "string" && item.id.trim().length > 0) return item.id;
|
|
2872
|
+
throw new RuntimeExecutionFailure({
|
|
2873
|
+
phase: "run",
|
|
2874
|
+
category: "authority",
|
|
2875
|
+
retry: "non-retryable",
|
|
2876
|
+
reason: "codex tool item had no authoritative tool call id"
|
|
2877
|
+
});
|
|
2878
|
+
}
|
|
2698
2879
|
function mapItem(rawItem, phase, workspaceDir) {
|
|
2699
2880
|
if (!rawItem || typeof rawItem !== "object") return [];
|
|
2700
2881
|
const item = rawItem;
|
|
@@ -2705,9 +2886,10 @@ function mapItem(rawItem, phase, workspaceDir) {
|
|
|
2705
2886
|
return typeof item.text === "string" ? [{ type: "progress", text: item.text }] : [];
|
|
2706
2887
|
}
|
|
2707
2888
|
case "command_execution": {
|
|
2889
|
+
const toolCallId = requireToolCallId2(item);
|
|
2708
2890
|
const command = typeof item.command === "string" ? item.command : void 0;
|
|
2709
2891
|
if (phase === "started") {
|
|
2710
|
-
return command !== void 0 ? [{ type: "tool_use", tool: "command_execution", input: { command } }] : [];
|
|
2892
|
+
return command !== void 0 ? [{ type: "tool_use", tool: "command_execution", input: { command }, toolCallId }] : [];
|
|
2711
2893
|
}
|
|
2712
2894
|
return [
|
|
2713
2895
|
{
|
|
@@ -2718,17 +2900,19 @@ function mapItem(rawItem, phase, workspaceDir) {
|
|
|
2718
2900
|
aggregatedOutput: item.aggregated_output,
|
|
2719
2901
|
exitCode: item.exit_code,
|
|
2720
2902
|
status: item.status
|
|
2721
|
-
}
|
|
2903
|
+
},
|
|
2904
|
+
toolCallId
|
|
2722
2905
|
}
|
|
2723
2906
|
];
|
|
2724
2907
|
}
|
|
2725
2908
|
case "file_change": {
|
|
2909
|
+
const toolCallId = requireToolCallId2(item);
|
|
2726
2910
|
const changes = Array.isArray(item.changes) ? item.changes : [];
|
|
2727
2911
|
if (phase === "started") {
|
|
2728
|
-
return [{ type: "tool_use", tool: "file_change", input: { changes } }];
|
|
2912
|
+
return [{ type: "tool_use", tool: "file_change", input: { changes }, toolCallId }];
|
|
2729
2913
|
}
|
|
2730
2914
|
return [
|
|
2731
|
-
{ type: "tool_result", tool: "file_change", output: { changes, status: item.status } },
|
|
2915
|
+
{ type: "tool_result", tool: "file_change", output: { changes, status: item.status }, toolCallId },
|
|
2732
2916
|
...extractArtifactEvents(changes, workspaceDir)
|
|
2733
2917
|
];
|
|
2734
2918
|
}
|
|
@@ -2849,9 +3033,15 @@ var CodexProcessRunner = class {
|
|
|
2849
3033
|
* cleanly resumable afterward via `codex exec resume` (no corruption from
|
|
2850
3034
|
* killing mid-turn). `taskkill /T /F` on Windows, mirroring
|
|
2851
3035
|
* `../pi/rpc-client.ts`'s own cross-platform convention.
|
|
3036
|
+
*
|
|
3037
|
+
* Fire-and-forget by design: an interrupt must not block on a terminator,
|
|
3038
|
+
* and `dispose()` is the settlement receipt. A request that could not be
|
|
3039
|
+
* spawned is left unrecorded, so `dispose()` re-issues it and raises the
|
|
3040
|
+
* typed `stage:'signal'` failure — swallowing it here loses nothing.
|
|
2852
3041
|
*/
|
|
2853
3042
|
kill() {
|
|
2854
|
-
requestOwnedProcessTreeTermination(this.processTreeOptions())
|
|
3043
|
+
void requestOwnedProcessTreeTermination(this.processTreeOptions()).catch(() => {
|
|
3044
|
+
});
|
|
2855
3045
|
}
|
|
2856
3046
|
dispose() {
|
|
2857
3047
|
if (!this.disposalAttempt) {
|
|
@@ -3145,7 +3335,15 @@ async function runCodexTurn(params) {
|
|
|
3145
3335
|
}
|
|
3146
3336
|
return;
|
|
3147
3337
|
}
|
|
3148
|
-
|
|
3338
|
+
let mapped;
|
|
3339
|
+
try {
|
|
3340
|
+
mapped = mapCodexEventToAgentEvents(evt, params.workspaceDir);
|
|
3341
|
+
} catch (cause) {
|
|
3342
|
+
if (!isRuntimeExecutionFailure(cause)) throw cause;
|
|
3343
|
+
params.terminal.failure = cause;
|
|
3344
|
+
params.queue.end();
|
|
3345
|
+
return;
|
|
3346
|
+
}
|
|
3149
3347
|
for (const agentEvent of mapped) {
|
|
3150
3348
|
if (agentEvent.type === "turn_end") turnEnded = true;
|
|
3151
3349
|
params.queue.push(agentEvent);
|
|
@@ -4631,14 +4829,17 @@ function parseLooseEventsPollResponse(raw) {
|
|
|
4631
4829
|
if (typeof raw !== "object" || raw === null) {
|
|
4632
4830
|
throw new Error("events poll response is not an object");
|
|
4633
4831
|
}
|
|
4634
|
-
const { events, cursor } = raw;
|
|
4832
|
+
const { events, cursor, capabilities } = raw;
|
|
4635
4833
|
if (!Array.isArray(events)) {
|
|
4636
4834
|
throw new Error("events poll response.events is not an array");
|
|
4637
4835
|
}
|
|
4638
4836
|
if (typeof cursor !== "number" || !Number.isInteger(cursor)) {
|
|
4639
4837
|
throw new Error("events poll response.cursor is not an integer");
|
|
4640
4838
|
}
|
|
4641
|
-
|
|
4839
|
+
if (capabilities !== void 0 && (!Array.isArray(capabilities) || capabilities.some((flag) => typeof flag !== "string"))) {
|
|
4840
|
+
throw new Error("events poll response.capabilities is not an array of strings");
|
|
4841
|
+
}
|
|
4842
|
+
return { events, cursor, capabilities: capabilities ?? [] };
|
|
4642
4843
|
}
|
|
4643
4844
|
function extractSkippableSeq(raw) {
|
|
4644
4845
|
if (typeof raw !== "object" || raw === null) return void 0;
|
|
@@ -4718,12 +4919,14 @@ var LongPollClient = class {
|
|
|
4718
4919
|
if (cursor !== void 0) url.searchParams.set("cursor", String(cursor));
|
|
4719
4920
|
const res = await authedFetch(url, { method: "GET" }, this.opts.auth);
|
|
4720
4921
|
if (!res.ok) {
|
|
4922
|
+
this.opts.onServerCapabilities?.([]);
|
|
4721
4923
|
this.opts.onOperationalOutcome?.("failure");
|
|
4722
4924
|
const baseMs = this.opts.retryDelayMs ?? 2e3;
|
|
4723
4925
|
await sleep(this.opts.retryDelayForAttempt?.(retryAttempt++, baseMs) ?? baseMs);
|
|
4724
4926
|
continue;
|
|
4725
4927
|
}
|
|
4726
4928
|
const parsed = parseLooseEventsPollResponse(await res.json());
|
|
4929
|
+
this.opts.onServerCapabilities?.(parsed.capabilities);
|
|
4727
4930
|
let hadValidationFailureThisBatch = false;
|
|
4728
4931
|
for (const raw of parsed.events) {
|
|
4729
4932
|
let envelope;
|
|
@@ -4767,6 +4970,7 @@ var LongPollClient = class {
|
|
|
4767
4970
|
this.opts.onOperationalOutcome?.("success");
|
|
4768
4971
|
}
|
|
4769
4972
|
} catch (err) {
|
|
4973
|
+
this.opts.onServerCapabilities?.([]);
|
|
4770
4974
|
if (err instanceof DeviceRevokedError) {
|
|
4771
4975
|
this.running = false;
|
|
4772
4976
|
this.opts.onRevoked?.();
|
|
@@ -5002,6 +5206,9 @@ var ConnectionManager = class {
|
|
|
5002
5206
|
// re-attempted.
|
|
5003
5207
|
getCursor: () => this.dedupWatermark(),
|
|
5004
5208
|
onEnvelope: (envelope) => this.deliver(envelope),
|
|
5209
|
+
onServerCapabilities: (capabilities) => {
|
|
5210
|
+
if (this.mode === "long-poll") this.serverCapabilities = capabilities;
|
|
5211
|
+
},
|
|
5005
5212
|
onRevoked: () => this.enterRevoked(),
|
|
5006
5213
|
// M4 Phase 4 (version-negotiation drill fix): a batch entry
|
|
5007
5214
|
// LongPollClient couldn't parse into a known Envelope at all (an
|
|
@@ -5133,25 +5340,24 @@ var ConnectionManager = class {
|
|
|
5133
5340
|
*/
|
|
5134
5341
|
cancelPendingDrainRetry;
|
|
5135
5342
|
/**
|
|
5136
|
-
* The capabilities the
|
|
5137
|
-
* `
|
|
5138
|
-
*
|
|
5139
|
-
*
|
|
5140
|
-
* successful handshake.
|
|
5343
|
+
* The capabilities the CURRENT transport's server advertised — untyped
|
|
5344
|
+
* `string[]` for forward compatibility. WS populates it from `conn.ack`;
|
|
5345
|
+
* long-poll populates it from each successful events response. Empty until
|
|
5346
|
+
* the active transport supplies an advertisement.
|
|
5141
5347
|
*
|
|
5142
5348
|
* Finding R2 (cross-model re-review — was P1): strictly PER-CONNECTION,
|
|
5143
5349
|
* not per-daemon-lifetime. Cleared to `[]` the instant the acked WS
|
|
5144
5350
|
* connection ends for ANY reason — an ordinary disconnect (`onWsOutcome`'s
|
|
5145
5351
|
* `acked` branch), `stop()`, or a transport switch to long-poll
|
|
5146
|
-
* (`enterLongPoll`) — and only
|
|
5352
|
+
* (`enterLongPoll`) — and only repopulated by a fresh advertisement from
|
|
5353
|
+
* the transport that is still current.
|
|
5147
5354
|
* The previous version of this doc comment claimed long-poll mode simply
|
|
5148
5355
|
* "stays at whatever the last real WS `conn.ack` said" — that was the bug:
|
|
5149
5356
|
* a daemon that once learned e.g. `approval_resolved` from an earlier WS
|
|
5150
5357
|
* session kept believing it applied to whatever it's connected to NOW,
|
|
5151
5358
|
* even after a disconnect/degrade where nothing has actually confirmed
|
|
5152
5359
|
* that's still true (a reconnect could land on a DIFFERENT server behind a
|
|
5153
|
-
* load balancer
|
|
5154
|
-
* handshake at all). Concretely, `TaskRunner.sendApprovalResolved` gates
|
|
5360
|
+
* load balancer). Concretely, `TaskRunner.sendApprovalResolved` gates
|
|
5155
5361
|
* `task.approval_resolved` on this list — sending it to a server that
|
|
5156
5362
|
* doesn't actually understand it over the long-poll path would get a
|
|
5157
5363
|
* batch-level 400 from `MessagesSendRequestSchema` (protocol §8.2), which
|
|
@@ -5271,13 +5477,10 @@ var ConnectionManager = class {
|
|
|
5271
5477
|
return this.mode === "long-poll";
|
|
5272
5478
|
}
|
|
5273
5479
|
/**
|
|
5274
|
-
* The capabilities the
|
|
5275
|
-
* `conn.ack`
|
|
5276
|
-
*
|
|
5277
|
-
*
|
|
5278
|
-
* first handshake completes, AND (finding R2) once again empty after any
|
|
5279
|
-
* disconnect/degrade — see `serverCapabilities`'s own doc comment for why
|
|
5280
|
-
* this is strictly per-connection rather than "sticky" across one.
|
|
5480
|
+
* The capabilities the CURRENT transport's server advertised: from
|
|
5481
|
+
* `conn.ack` on WS, or the latest successful `GET /byok/events` response
|
|
5482
|
+
* on long-poll. Empty before either transport has supplied its current
|
|
5483
|
+
* advertisement, and cleared across disconnect/switch boundaries.
|
|
5281
5484
|
*/
|
|
5282
5485
|
getServerCapabilities() {
|
|
5283
5486
|
return this.serverCapabilities;
|
|
@@ -9647,9 +9850,9 @@ var TaskRunner = class {
|
|
|
9647
9850
|
/**
|
|
9648
9851
|
* Whether the CURRENTLY connected server advertised `result-document` —
|
|
9649
9852
|
* read fresh on every call, never captured, because the answer changes
|
|
9650
|
-
* across a reconnect (`ConnectionManager
|
|
9651
|
-
*
|
|
9652
|
-
*
|
|
9853
|
+
* across a reconnect or transport switch (`ConnectionManager` clears the
|
|
9854
|
+
* old advertisement at the boundary, then repopulates it from a fresh WS
|
|
9855
|
+
* ack or successful poll response). An absent `getServerCapabilities` seam is
|
|
9653
9856
|
* "no capabilities", the fail-closed reading.
|
|
9654
9857
|
*/
|
|
9655
9858
|
hasResultDocumentCapability() {
|