crosscheck-mcp 0.1.11 → 0.1.13
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/dist/node-stdio.cjs +1076 -402
- package/dist/node-stdio.cjs.map +1 -1
- package/dist/node-stdio.d.cts +44 -25
- package/dist/node-stdio.d.ts +44 -25
- package/dist/node-stdio.js +1043 -369
- package/dist/node-stdio.js.map +1 -1
- package/dist/personas/auditor-finance.md +35 -0
- package/dist/personas/auditor-security.md +34 -0
- package/dist/personas/design.md +34 -0
- package/dist/personas/engineering.md +41 -0
- package/dist/personas/finance.md +33 -0
- package/dist/personas/research.md +34 -0
- package/package.json +1 -1
package/dist/node-stdio.js
CHANGED
|
@@ -918,155 +918,11 @@ var init_better_sqlite3 = __esm({
|
|
|
918
918
|
|
|
919
919
|
// src/entrypoints/node-stdio.ts
|
|
920
920
|
init_esm_shims();
|
|
921
|
-
import { existsSync as
|
|
922
|
-
import
|
|
921
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync9 } from "fs";
|
|
922
|
+
import path14 from "path";
|
|
923
923
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
924
924
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
925
925
|
|
|
926
|
-
// src/bridge/index.ts
|
|
927
|
-
init_esm_shims();
|
|
928
|
-
|
|
929
|
-
// src/bridge/python-bridge.ts
|
|
930
|
-
init_esm_shims();
|
|
931
|
-
import path2 from "path";
|
|
932
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
933
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
934
|
-
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
935
|
-
function defaultServerPath() {
|
|
936
|
-
let here;
|
|
937
|
-
try {
|
|
938
|
-
here = path2.dirname(fileURLToPath2(import.meta.url));
|
|
939
|
-
} catch {
|
|
940
|
-
here = __dirname;
|
|
941
|
-
}
|
|
942
|
-
const upToPkg = here.includes(`${path2.sep}src${path2.sep}bridge`) ? "../.." : "..";
|
|
943
|
-
return path2.resolve(here, upToPkg, "..", "python", "crosscheck_server.py");
|
|
944
|
-
}
|
|
945
|
-
async function spawnPythonBridge(opts = {}) {
|
|
946
|
-
const pythonPath = opts.pythonPath ?? "python3";
|
|
947
|
-
const serverPath = opts.serverPath ?? defaultServerPath();
|
|
948
|
-
const args = [...opts.extraArgs ?? [], serverPath];
|
|
949
|
-
const transport = new StdioClientTransport({
|
|
950
|
-
command: pythonPath,
|
|
951
|
-
args,
|
|
952
|
-
env: { ...process.env, ...opts.env ?? {} }
|
|
953
|
-
});
|
|
954
|
-
const client = new Client(
|
|
955
|
-
{ name: "crosscheck-agent-bridge", version: "0.1.0" },
|
|
956
|
-
{ capabilities: {} }
|
|
957
|
-
);
|
|
958
|
-
const initDeadline = opts.initTimeoutMs ?? 3e4;
|
|
959
|
-
await Promise.race([
|
|
960
|
-
client.connect(transport),
|
|
961
|
-
new Promise(
|
|
962
|
-
(_, rej) => setTimeout(
|
|
963
|
-
() => rej(new Error(`bridge: handshake timeout after ${initDeadline}ms`)),
|
|
964
|
-
initDeadline
|
|
965
|
-
)
|
|
966
|
-
)
|
|
967
|
-
]);
|
|
968
|
-
let toolNames = await fetchToolNames(client, initDeadline);
|
|
969
|
-
let closed = false;
|
|
970
|
-
return {
|
|
971
|
-
get toolNames() {
|
|
972
|
-
return toolNames;
|
|
973
|
-
},
|
|
974
|
-
get pid() {
|
|
975
|
-
const p = transport.pid;
|
|
976
|
-
return typeof p === "number" ? p : null;
|
|
977
|
-
},
|
|
978
|
-
async callTool(name, callArgs) {
|
|
979
|
-
const r = await client.callTool({ name, arguments: callArgs });
|
|
980
|
-
const content = r.content;
|
|
981
|
-
if (!Array.isArray(content)) {
|
|
982
|
-
throw new Error(
|
|
983
|
-
`bridge: tools/call(${name}) returned a malformed envelope: ${JSON.stringify(r).slice(0, 200)}`
|
|
984
|
-
);
|
|
985
|
-
}
|
|
986
|
-
const out = [];
|
|
987
|
-
for (const c of content) {
|
|
988
|
-
if (c && typeof c === "object") {
|
|
989
|
-
const co = c;
|
|
990
|
-
out.push({
|
|
991
|
-
type: String(co["type"] ?? "text"),
|
|
992
|
-
text: typeof co["text"] === "string" ? co["text"] : JSON.stringify(co["text"])
|
|
993
|
-
});
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
const isError = r.isError;
|
|
997
|
-
return isError !== void 0 ? { content: out, isError } : { content: out };
|
|
998
|
-
},
|
|
999
|
-
async refreshTools() {
|
|
1000
|
-
toolNames = await fetchToolNames(client, initDeadline);
|
|
1001
|
-
return toolNames;
|
|
1002
|
-
},
|
|
1003
|
-
async close() {
|
|
1004
|
-
if (closed) return;
|
|
1005
|
-
closed = true;
|
|
1006
|
-
try {
|
|
1007
|
-
await client.close();
|
|
1008
|
-
} catch {
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
};
|
|
1012
|
-
}
|
|
1013
|
-
async function fetchToolNames(client, timeoutMs) {
|
|
1014
|
-
const r = await Promise.race([
|
|
1015
|
-
client.listTools(),
|
|
1016
|
-
new Promise(
|
|
1017
|
-
(_, rej) => setTimeout(
|
|
1018
|
-
() => rej(new Error(`bridge: tools/list timeout after ${timeoutMs}ms`)),
|
|
1019
|
-
timeoutMs
|
|
1020
|
-
)
|
|
1021
|
-
)
|
|
1022
|
-
]);
|
|
1023
|
-
const tools = r.tools ?? [];
|
|
1024
|
-
return new Set(
|
|
1025
|
-
tools.map((t) => String(t?.name ?? "")).filter((n) => n.length > 0)
|
|
1026
|
-
);
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
// src/bridge/proxy-tools.ts
|
|
1030
|
-
init_esm_shims();
|
|
1031
|
-
function buildPythonProxies(bridge) {
|
|
1032
|
-
const proxies = /* @__PURE__ */ new Map();
|
|
1033
|
-
for (const name of bridge.toolNames) {
|
|
1034
|
-
proxies.set(name, makeProxy(bridge, name));
|
|
1035
|
-
}
|
|
1036
|
-
return proxies;
|
|
1037
|
-
}
|
|
1038
|
-
function makeProxy(bridge, name) {
|
|
1039
|
-
return {
|
|
1040
|
-
name,
|
|
1041
|
-
description: `(forwarded to Python crosscheck-agent) ${name}`,
|
|
1042
|
-
// We don't carry the per-tool input schema across the bridge in
|
|
1043
|
-
// route-all mode — the TS server is a transparent forwarder, so
|
|
1044
|
-
// arg validation happens on the Python side. inputSchema is left
|
|
1045
|
-
// permissive; the Python validator throws structured errors that
|
|
1046
|
-
// tunnel back through `callTool()`.
|
|
1047
|
-
inputSchema: {
|
|
1048
|
-
type: "object",
|
|
1049
|
-
additionalProperties: true,
|
|
1050
|
-
description: `Forwarded to Python \u2014 see Python tool '${name}' for full schema.`
|
|
1051
|
-
},
|
|
1052
|
-
handler: async (args) => {
|
|
1053
|
-
const r = await bridge.callTool(name, args);
|
|
1054
|
-
if (!r.content.length) {
|
|
1055
|
-
return {};
|
|
1056
|
-
}
|
|
1057
|
-
const first = r.content[0];
|
|
1058
|
-
if (first.type === "text" && first.text) {
|
|
1059
|
-
try {
|
|
1060
|
-
return JSON.parse(first.text);
|
|
1061
|
-
} catch {
|
|
1062
|
-
return { text: first.text };
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
return { content: r.content };
|
|
1066
|
-
}
|
|
1067
|
-
};
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
926
|
// src/core/events.ts
|
|
1071
927
|
init_esm_shims();
|
|
1072
928
|
import { appendFileSync, mkdirSync } from "fs";
|
|
@@ -1080,10 +936,10 @@ var StderrEmitter = class {
|
|
|
1080
936
|
}
|
|
1081
937
|
};
|
|
1082
938
|
var FileEmitter = class {
|
|
1083
|
-
constructor(
|
|
1084
|
-
this.path =
|
|
939
|
+
constructor(path15) {
|
|
940
|
+
this.path = path15;
|
|
1085
941
|
try {
|
|
1086
|
-
mkdirSync(dirname(
|
|
942
|
+
mkdirSync(dirname(path15), { recursive: true });
|
|
1087
943
|
} catch {
|
|
1088
944
|
}
|
|
1089
945
|
}
|
|
@@ -1116,13 +972,13 @@ var NullEmitter = class {
|
|
|
1116
972
|
function buildDefaultEmitter(env) {
|
|
1117
973
|
const mode = (env["CROSSCHECK_EVENTS"] ?? "stderr").toLowerCase();
|
|
1118
974
|
if (mode === "off") return new NullEmitter();
|
|
1119
|
-
const
|
|
975
|
+
const path15 = env["CROSSCHECK_EVENTS_PATH"];
|
|
1120
976
|
if (mode === "file") {
|
|
1121
|
-
return
|
|
977
|
+
return path15 ? new FileEmitter(path15) : new NullEmitter();
|
|
1122
978
|
}
|
|
1123
979
|
if (mode === "both") {
|
|
1124
980
|
const ems = [new StderrEmitter()];
|
|
1125
|
-
if (
|
|
981
|
+
if (path15) ems.push(new FileEmitter(path15));
|
|
1126
982
|
return new TeeEmitter(ems);
|
|
1127
983
|
}
|
|
1128
984
|
return new StderrEmitter();
|
|
@@ -1171,10 +1027,10 @@ function envelopeBytes(v) {
|
|
|
1171
1027
|
// src/core/pricing.ts
|
|
1172
1028
|
init_esm_shims();
|
|
1173
1029
|
import { readFileSync } from "fs";
|
|
1174
|
-
function loadPricing(
|
|
1030
|
+
function loadPricing(path15) {
|
|
1175
1031
|
let raw;
|
|
1176
1032
|
try {
|
|
1177
|
-
raw = readFileSync(
|
|
1033
|
+
raw = readFileSync(path15, "utf8");
|
|
1178
1034
|
} catch {
|
|
1179
1035
|
return {};
|
|
1180
1036
|
}
|
|
@@ -1304,7 +1160,7 @@ function isCreditsFailure(status, bodyText) {
|
|
|
1304
1160
|
}
|
|
1305
1161
|
return false;
|
|
1306
1162
|
}
|
|
1307
|
-
function httpFailureToProviderError(provider, status, bodyText) {
|
|
1163
|
+
function httpFailureToProviderError(provider, status, bodyText, retryAfterS) {
|
|
1308
1164
|
const detail = bodyText.slice(0, 512);
|
|
1309
1165
|
if (isCreditsFailure(status, bodyText)) {
|
|
1310
1166
|
return new ProviderError(
|
|
@@ -1324,14 +1180,14 @@ function httpFailureToProviderError(provider, status, bodyText) {
|
|
|
1324
1180
|
return new ProviderError(
|
|
1325
1181
|
"rate_limit",
|
|
1326
1182
|
`${provider}: rate limited (HTTP 429). Detail: ${detail}`,
|
|
1327
|
-
{ status }
|
|
1183
|
+
{ status, ...retryAfterS !== void 0 ? { retryAfterS } : {} }
|
|
1328
1184
|
);
|
|
1329
1185
|
}
|
|
1330
1186
|
if (status >= 500 && status <= 599) {
|
|
1331
1187
|
return new ProviderError(
|
|
1332
1188
|
"server",
|
|
1333
1189
|
`${provider}: upstream server error (HTTP ${status}). Detail: ${detail}`,
|
|
1334
|
-
{ status }
|
|
1190
|
+
{ status, ...retryAfterS !== void 0 ? { retryAfterS } : {} }
|
|
1335
1191
|
);
|
|
1336
1192
|
}
|
|
1337
1193
|
return new ProviderError(
|
|
@@ -1341,6 +1197,156 @@ function httpFailureToProviderError(provider, status, bodyText) {
|
|
|
1341
1197
|
);
|
|
1342
1198
|
}
|
|
1343
1199
|
|
|
1200
|
+
// src/providers/retry.ts
|
|
1201
|
+
init_esm_shims();
|
|
1202
|
+
var override = null;
|
|
1203
|
+
function setRetryConfig(cfg) {
|
|
1204
|
+
override = cfg;
|
|
1205
|
+
}
|
|
1206
|
+
function resolveRetryConfig() {
|
|
1207
|
+
const envInt = (k, d) => {
|
|
1208
|
+
const n = Number(process.env[k]);
|
|
1209
|
+
return Number.isFinite(n) && n >= 0 ? Math.trunc(n) : d;
|
|
1210
|
+
};
|
|
1211
|
+
const envSecMs = (k, dMs) => {
|
|
1212
|
+
const n = Number(process.env[k]);
|
|
1213
|
+
return Number.isFinite(n) && n >= 0 ? Math.round(n * 1e3) : dMs;
|
|
1214
|
+
};
|
|
1215
|
+
return {
|
|
1216
|
+
maxAttempts: Math.max(1, override?.maxAttempts ?? envInt("CROSSCHECK_RETRY_MAX_ATTEMPTS", 3)),
|
|
1217
|
+
backoffBaseMs: override?.backoffBaseMs ?? envSecMs("CROSSCHECK_RETRY_BACKOFF_BASE_S", 750),
|
|
1218
|
+
maxBackoffMs: override?.maxBackoffMs ?? 2e4
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
function parseRetryAfter(respLike) {
|
|
1222
|
+
try {
|
|
1223
|
+
const h = respLike?.headers;
|
|
1224
|
+
if (!h) return void 0;
|
|
1225
|
+
let raw = null;
|
|
1226
|
+
const getter = h.get;
|
|
1227
|
+
if (typeof getter === "function") {
|
|
1228
|
+
raw = getter.call(h, "retry-after");
|
|
1229
|
+
} else if (typeof h === "object") {
|
|
1230
|
+
const rec = h;
|
|
1231
|
+
raw = rec["retry-after"] ?? rec["Retry-After"] ?? null;
|
|
1232
|
+
}
|
|
1233
|
+
if (!raw) return void 0;
|
|
1234
|
+
const secs = Number(raw);
|
|
1235
|
+
if (Number.isFinite(secs) && secs >= 0) return secs;
|
|
1236
|
+
const dateMs = Date.parse(raw);
|
|
1237
|
+
if (Number.isFinite(dateMs)) {
|
|
1238
|
+
const delta = (dateMs - Date.now()) / 1e3;
|
|
1239
|
+
return delta > 0 ? delta : 0;
|
|
1240
|
+
}
|
|
1241
|
+
} catch {
|
|
1242
|
+
}
|
|
1243
|
+
return void 0;
|
|
1244
|
+
}
|
|
1245
|
+
function defaultSleep(ms, signal) {
|
|
1246
|
+
return new Promise((resolve2, reject) => {
|
|
1247
|
+
if (signal?.aborted) {
|
|
1248
|
+
reject(new ProviderError("network", "request aborted", { transient: false }));
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
const cleanup = () => {
|
|
1252
|
+
clearTimeout(t);
|
|
1253
|
+
signal?.removeEventListener?.("abort", onAbort);
|
|
1254
|
+
};
|
|
1255
|
+
const onAbort = () => {
|
|
1256
|
+
cleanup();
|
|
1257
|
+
reject(new ProviderError("network", "request aborted", { transient: false }));
|
|
1258
|
+
};
|
|
1259
|
+
const t = setTimeout(() => {
|
|
1260
|
+
cleanup();
|
|
1261
|
+
resolve2();
|
|
1262
|
+
}, ms);
|
|
1263
|
+
signal?.addEventListener?.("abort", onAbort);
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
async function sendWithRetry(attemptOnce, cfg, signal, deps) {
|
|
1267
|
+
const sleep = deps?.sleepImpl ?? ((ms) => defaultSleep(ms, signal));
|
|
1268
|
+
const rand = deps?.random ?? Math.random;
|
|
1269
|
+
let lastErr;
|
|
1270
|
+
for (let attempt = 1; attempt <= cfg.maxAttempts; attempt += 1) {
|
|
1271
|
+
try {
|
|
1272
|
+
const result = await attemptOnce();
|
|
1273
|
+
return { ...result, attempts: attempt };
|
|
1274
|
+
} catch (e) {
|
|
1275
|
+
lastErr = e;
|
|
1276
|
+
const pe = e instanceof ProviderError ? e : null;
|
|
1277
|
+
if (pe?.transient !== true || attempt >= cfg.maxAttempts) throw e;
|
|
1278
|
+
let backoffMs = pe.retryAfterS !== void 0 ? pe.retryAfterS * 1e3 : cfg.backoffBaseMs * 2 ** (attempt - 1);
|
|
1279
|
+
backoffMs += rand() * cfg.backoffBaseMs;
|
|
1280
|
+
backoffMs = Math.min(backoffMs, cfg.maxBackoffMs);
|
|
1281
|
+
await sleep(backoffMs);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
throw lastErr;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// src/providers/rate-limit.ts
|
|
1288
|
+
init_esm_shims();
|
|
1289
|
+
var DEFAULT_SPEC = { capacity: 5, refillPerSec: 5 };
|
|
1290
|
+
var MAX_WAIT_SLICE_MS = 250;
|
|
1291
|
+
var override2 = null;
|
|
1292
|
+
function setRateLimitConfig(cfg) {
|
|
1293
|
+
override2 = cfg;
|
|
1294
|
+
buckets.clear();
|
|
1295
|
+
}
|
|
1296
|
+
function disabled() {
|
|
1297
|
+
return process.env["CROSSCHECK_RATE_LIMIT"] === "off";
|
|
1298
|
+
}
|
|
1299
|
+
function specFor(provider) {
|
|
1300
|
+
const fromCfg = override2?.[provider] ?? override2?.["default"];
|
|
1301
|
+
const spec = fromCfg ?? DEFAULT_SPEC;
|
|
1302
|
+
const capacity = Number.isFinite(spec.capacity) && spec.capacity > 0 ? spec.capacity : DEFAULT_SPEC.capacity;
|
|
1303
|
+
const refillPerSec = Number.isFinite(spec.refillPerSec) && spec.refillPerSec > 0 ? spec.refillPerSec : capacity;
|
|
1304
|
+
return { capacity, refillPerSec };
|
|
1305
|
+
}
|
|
1306
|
+
var Bucket = class {
|
|
1307
|
+
constructor(spec, now) {
|
|
1308
|
+
this.spec = spec;
|
|
1309
|
+
this.tokens = spec.capacity;
|
|
1310
|
+
this.last = now();
|
|
1311
|
+
}
|
|
1312
|
+
spec;
|
|
1313
|
+
tokens;
|
|
1314
|
+
last;
|
|
1315
|
+
refill(now) {
|
|
1316
|
+
const elapsed = (now - this.last) / 1e3;
|
|
1317
|
+
this.tokens = Math.min(this.spec.capacity, this.tokens + elapsed * this.spec.refillPerSec);
|
|
1318
|
+
this.last = now;
|
|
1319
|
+
}
|
|
1320
|
+
async acquire(deps) {
|
|
1321
|
+
for (; ; ) {
|
|
1322
|
+
this.refill(deps.now());
|
|
1323
|
+
if (this.tokens >= 1) {
|
|
1324
|
+
this.tokens -= 1;
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
if (deps.signal?.aborted) {
|
|
1328
|
+
throw new ProviderError("network", "request aborted while rate-limited", { transient: false });
|
|
1329
|
+
}
|
|
1330
|
+
const needed = 1 - this.tokens;
|
|
1331
|
+
const waitMs = needed / Math.max(this.spec.refillPerSec, 1e-6) * 1e3;
|
|
1332
|
+
await deps.sleep(Math.min(waitMs, MAX_WAIT_SLICE_MS));
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
var buckets = /* @__PURE__ */ new Map();
|
|
1337
|
+
var realSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1338
|
+
async function acquireRateLimit(provider, deps) {
|
|
1339
|
+
if (disabled()) return;
|
|
1340
|
+
const now = deps?.now ?? Date.now;
|
|
1341
|
+
const sleep = deps?.sleep ?? realSleep;
|
|
1342
|
+
let bucket = buckets.get(provider);
|
|
1343
|
+
if (!bucket) {
|
|
1344
|
+
bucket = new Bucket(specFor(provider), now);
|
|
1345
|
+
buckets.set(provider, bucket);
|
|
1346
|
+
}
|
|
1347
|
+
await bucket.acquire({ now, sleep, ...deps?.signal ? { signal: deps.signal } : {} });
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1344
1350
|
// src/providers/anthropic.ts
|
|
1345
1351
|
var ANTHROPIC_API_URL = "https://api.anthropic.com/v1/messages";
|
|
1346
1352
|
var ANTHROPIC_VERSION_HEADER = "2023-06-01";
|
|
@@ -1467,29 +1473,33 @@ async function sendAnthropic(args) {
|
|
|
1467
1473
|
body: JSON.stringify(body)
|
|
1468
1474
|
};
|
|
1469
1475
|
if (args.signal) init.signal = args.signal;
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
respLike = await doFetch(url, init);
|
|
1473
|
-
} catch (e) {
|
|
1474
|
-
throw new ProviderError("network", `anthropic: fetch failed: ${e.message}`);
|
|
1475
|
-
}
|
|
1476
|
-
const status = respLike.status;
|
|
1477
|
-
if (status >= 200 && status < 300) {
|
|
1478
|
-
let parsed;
|
|
1476
|
+
const attemptOnce = async () => {
|
|
1477
|
+
let respLike;
|
|
1479
1478
|
try {
|
|
1480
|
-
|
|
1479
|
+
respLike = await doFetch(url, init);
|
|
1481
1480
|
} catch (e) {
|
|
1482
|
-
throw new ProviderError("
|
|
1481
|
+
throw new ProviderError("network", `anthropic: fetch failed: ${e.message}`);
|
|
1483
1482
|
}
|
|
1484
|
-
const
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1483
|
+
const status = respLike.status;
|
|
1484
|
+
if (status >= 200 && status < 300) {
|
|
1485
|
+
let parsed;
|
|
1486
|
+
try {
|
|
1487
|
+
parsed = await respLike.json();
|
|
1488
|
+
} catch (e) {
|
|
1489
|
+
throw new ProviderError("parse", `anthropic: response body not JSON: ${e.message}`);
|
|
1490
|
+
}
|
|
1491
|
+
const { text, usage } = parseAnthropicResponse({
|
|
1492
|
+
resp: parsed,
|
|
1493
|
+
model: args.model,
|
|
1494
|
+
purpose: args.purpose ?? "worker"
|
|
1495
|
+
});
|
|
1496
|
+
return { text, attempts: 1, usage: applyPricing(usage, args.pricing) };
|
|
1497
|
+
}
|
|
1498
|
+
const bodyText = await respLike.text().catch(() => "");
|
|
1499
|
+
throw httpFailureToProviderError("anthropic", status, bodyText, parseRetryAfter(respLike));
|
|
1500
|
+
};
|
|
1501
|
+
await acquireRateLimit("anthropic", args.signal ? { signal: args.signal } : void 0);
|
|
1502
|
+
return sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
|
|
1493
1503
|
}
|
|
1494
1504
|
|
|
1495
1505
|
// src/providers/gemini.ts
|
|
@@ -1641,29 +1651,33 @@ async function sendGemini(args) {
|
|
|
1641
1651
|
body: JSON.stringify(body)
|
|
1642
1652
|
};
|
|
1643
1653
|
if (args.signal) init.signal = args.signal;
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
respLike = await doFetch(url, init);
|
|
1647
|
-
} catch (e) {
|
|
1648
|
-
throw new ProviderError("network", `gemini: fetch failed: ${e.message}`);
|
|
1649
|
-
}
|
|
1650
|
-
const status = respLike.status;
|
|
1651
|
-
if (status >= 200 && status < 300) {
|
|
1652
|
-
let parsed;
|
|
1654
|
+
const attemptOnce = async () => {
|
|
1655
|
+
let respLike;
|
|
1653
1656
|
try {
|
|
1654
|
-
|
|
1657
|
+
respLike = await doFetch(url, init);
|
|
1655
1658
|
} catch (e) {
|
|
1656
|
-
throw new ProviderError("
|
|
1659
|
+
throw new ProviderError("network", `gemini: fetch failed: ${e.message}`);
|
|
1657
1660
|
}
|
|
1658
|
-
const
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1661
|
+
const status = respLike.status;
|
|
1662
|
+
if (status >= 200 && status < 300) {
|
|
1663
|
+
let parsed;
|
|
1664
|
+
try {
|
|
1665
|
+
parsed = await respLike.json();
|
|
1666
|
+
} catch (e) {
|
|
1667
|
+
throw new ProviderError("parse", `gemini: response body not JSON: ${e.message}`);
|
|
1668
|
+
}
|
|
1669
|
+
const { text, usage } = parseGeminiResponse({
|
|
1670
|
+
resp: parsed,
|
|
1671
|
+
model: args.model,
|
|
1672
|
+
purpose: args.purpose ?? "worker"
|
|
1673
|
+
});
|
|
1674
|
+
return { text, attempts: 1, usage: applyPricing2(usage, args.pricing) };
|
|
1675
|
+
}
|
|
1676
|
+
const bodyText = await respLike.text().catch(() => "");
|
|
1677
|
+
throw httpFailureToProviderError("gemini", status, bodyText, parseRetryAfter(respLike));
|
|
1678
|
+
};
|
|
1679
|
+
await acquireRateLimit("gemini", args.signal ? { signal: args.signal } : void 0);
|
|
1680
|
+
return sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
|
|
1667
1681
|
}
|
|
1668
1682
|
|
|
1669
1683
|
// src/providers/openai-compatible.ts
|
|
@@ -1803,30 +1817,34 @@ async function sendOpenAICompatible(args) {
|
|
|
1803
1817
|
body: JSON.stringify(body)
|
|
1804
1818
|
};
|
|
1805
1819
|
if (args.signal) init.signal = args.signal;
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
respLike = await doFetch(url, init);
|
|
1809
|
-
} catch (e) {
|
|
1810
|
-
throw new ProviderError("network", `${args.provider}: fetch failed: ${e.message}`);
|
|
1811
|
-
}
|
|
1812
|
-
const status = respLike.status;
|
|
1813
|
-
if (status >= 200 && status < 300) {
|
|
1814
|
-
let parsed;
|
|
1820
|
+
const attemptOnce = async () => {
|
|
1821
|
+
let respLike;
|
|
1815
1822
|
try {
|
|
1816
|
-
|
|
1823
|
+
respLike = await doFetch(url, init);
|
|
1817
1824
|
} catch (e) {
|
|
1818
|
-
throw new ProviderError("
|
|
1825
|
+
throw new ProviderError("network", `${args.provider}: fetch failed: ${e.message}`);
|
|
1819
1826
|
}
|
|
1820
|
-
const
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1827
|
+
const status = respLike.status;
|
|
1828
|
+
if (status >= 200 && status < 300) {
|
|
1829
|
+
let parsed;
|
|
1830
|
+
try {
|
|
1831
|
+
parsed = await respLike.json();
|
|
1832
|
+
} catch (e) {
|
|
1833
|
+
throw new ProviderError("parse", `${args.provider}: response body not JSON: ${e.message}`);
|
|
1834
|
+
}
|
|
1835
|
+
const { text, usage } = parseOpenAICompatibleResponse({
|
|
1836
|
+
resp: parsed,
|
|
1837
|
+
provider: args.provider,
|
|
1838
|
+
model: args.model,
|
|
1839
|
+
purpose: args.purpose ?? "worker"
|
|
1840
|
+
});
|
|
1841
|
+
return { text, attempts: 1, usage: applyPricing3(usage, args.pricing) };
|
|
1842
|
+
}
|
|
1843
|
+
const bodyText = await respLike.text().catch(() => "");
|
|
1844
|
+
throw httpFailureToProviderError(args.provider, status, bodyText, parseRetryAfter(respLike));
|
|
1845
|
+
};
|
|
1846
|
+
await acquireRateLimit(args.provider, args.signal ? { signal: args.signal } : void 0);
|
|
1847
|
+
return sendWithRetry(attemptOnce, resolveRetryConfig(), args.signal);
|
|
1830
1848
|
}
|
|
1831
1849
|
|
|
1832
1850
|
// src/providers/registry.ts
|
|
@@ -1926,6 +1944,50 @@ import {
|
|
|
1926
1944
|
ListToolsRequestSchema
|
|
1927
1945
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
1928
1946
|
|
|
1947
|
+
// src/bridge/index.ts
|
|
1948
|
+
init_esm_shims();
|
|
1949
|
+
|
|
1950
|
+
// src/bridge/proxy-tools.ts
|
|
1951
|
+
init_esm_shims();
|
|
1952
|
+
function buildPythonProxies(bridge) {
|
|
1953
|
+
const proxies = /* @__PURE__ */ new Map();
|
|
1954
|
+
for (const name of bridge.toolNames) {
|
|
1955
|
+
proxies.set(name, makeProxy(bridge, name));
|
|
1956
|
+
}
|
|
1957
|
+
return proxies;
|
|
1958
|
+
}
|
|
1959
|
+
function makeProxy(bridge, name) {
|
|
1960
|
+
return {
|
|
1961
|
+
name,
|
|
1962
|
+
description: `(forwarded to Python crosscheck-agent) ${name}`,
|
|
1963
|
+
// We don't carry the per-tool input schema across the bridge in
|
|
1964
|
+
// route-all mode — the TS server is a transparent forwarder, so
|
|
1965
|
+
// arg validation happens on the Python side. inputSchema is left
|
|
1966
|
+
// permissive; the Python validator throws structured errors that
|
|
1967
|
+
// tunnel back through `callTool()`.
|
|
1968
|
+
inputSchema: {
|
|
1969
|
+
type: "object",
|
|
1970
|
+
additionalProperties: true,
|
|
1971
|
+
description: `Forwarded to Python \u2014 see Python tool '${name}' for full schema.`
|
|
1972
|
+
},
|
|
1973
|
+
handler: async (args) => {
|
|
1974
|
+
const r = await bridge.callTool(name, args);
|
|
1975
|
+
if (!r.content.length) {
|
|
1976
|
+
return {};
|
|
1977
|
+
}
|
|
1978
|
+
const first = r.content[0];
|
|
1979
|
+
if (first.type === "text" && first.text) {
|
|
1980
|
+
try {
|
|
1981
|
+
return JSON.parse(first.text);
|
|
1982
|
+
} catch {
|
|
1983
|
+
return { text: first.text };
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
return { content: r.content };
|
|
1987
|
+
}
|
|
1988
|
+
};
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1929
1991
|
// src/instructions.ts
|
|
1930
1992
|
init_esm_shims();
|
|
1931
1993
|
var ROUTABLE_TOOLS = [
|
|
@@ -1991,7 +2053,7 @@ import { z } from "zod";
|
|
|
1991
2053
|
|
|
1992
2054
|
// src/tools/audit.ts
|
|
1993
2055
|
init_esm_shims();
|
|
1994
|
-
import { readdirSync, readFileSync as
|
|
2056
|
+
import { readdirSync, readFileSync as readFileSync3, statSync } from "fs";
|
|
1995
2057
|
import { join } from "path";
|
|
1996
2058
|
|
|
1997
2059
|
// src/core/structured.ts
|
|
@@ -2054,27 +2116,27 @@ function extractJson(text) {
|
|
|
2054
2116
|
|
|
2055
2117
|
// src/core/json-schema.ts
|
|
2056
2118
|
init_esm_shims();
|
|
2057
|
-
function validateSchema(value, schema,
|
|
2119
|
+
function validateSchema(value, schema, path15 = "") {
|
|
2058
2120
|
const errs = [];
|
|
2059
2121
|
if ("anyOf" in schema) {
|
|
2060
2122
|
const subs = schema["anyOf"].map(
|
|
2061
|
-
(s) => validateSchema(value, s,
|
|
2123
|
+
(s) => validateSchema(value, s, path15)
|
|
2062
2124
|
);
|
|
2063
2125
|
if (!subs.some((e) => e.length === 0)) {
|
|
2064
|
-
errs.push(`${
|
|
2126
|
+
errs.push(`${path15 || "<root>"}: did not match anyOf`);
|
|
2065
2127
|
}
|
|
2066
2128
|
return errs;
|
|
2067
2129
|
}
|
|
2068
2130
|
if ("oneOf" in schema) {
|
|
2069
|
-
const passed = schema["oneOf"].filter((s) => validateSchema(value, s,
|
|
2131
|
+
const passed = schema["oneOf"].filter((s) => validateSchema(value, s, path15).length === 0).length;
|
|
2070
2132
|
if (passed !== 1) {
|
|
2071
|
-
errs.push(`${
|
|
2133
|
+
errs.push(`${path15 || "<root>"}: matched ${passed} of oneOf, expected 1`);
|
|
2072
2134
|
}
|
|
2073
2135
|
return errs;
|
|
2074
2136
|
}
|
|
2075
2137
|
if ("const" in schema && !deepEqual(value, schema["const"])) {
|
|
2076
2138
|
errs.push(
|
|
2077
|
-
`${
|
|
2139
|
+
`${path15 || "<root>"}: expected const ${pyRepr(schema["const"])}, got ${pyRepr(value)}`
|
|
2078
2140
|
);
|
|
2079
2141
|
return errs;
|
|
2080
2142
|
}
|
|
@@ -2084,7 +2146,7 @@ function validateSchema(value, schema, path13 = "") {
|
|
|
2084
2146
|
const ok = types.some((tt) => matchesType(value, String(tt)));
|
|
2085
2147
|
if (!ok) {
|
|
2086
2148
|
errs.push(
|
|
2087
|
-
`${
|
|
2149
|
+
`${path15 || "<root>"}: expected type ${pyReprType(t)}, got ${pyTypeName(value)}`
|
|
2088
2150
|
);
|
|
2089
2151
|
return errs;
|
|
2090
2152
|
}
|
|
@@ -2093,29 +2155,29 @@ function validateSchema(value, schema, path13 = "") {
|
|
|
2093
2155
|
if ("enum" in schema) {
|
|
2094
2156
|
const en = schema["enum"];
|
|
2095
2157
|
if (!en.some((x) => deepEqual(x, value))) {
|
|
2096
|
-
errs.push(`${
|
|
2158
|
+
errs.push(`${path15 || "<root>"}: value ${pyRepr(value)} not in enum`);
|
|
2097
2159
|
}
|
|
2098
2160
|
}
|
|
2099
2161
|
if ("minLength" in schema && value.length < schema["minLength"]) {
|
|
2100
|
-
errs.push(`${
|
|
2162
|
+
errs.push(`${path15 || "<root>"}: shorter than minLength ${schema["minLength"]}`);
|
|
2101
2163
|
}
|
|
2102
2164
|
}
|
|
2103
2165
|
if (typeof value === "number" && !Number.isNaN(value)) {
|
|
2104
2166
|
if ("minimum" in schema && value < schema["minimum"]) {
|
|
2105
|
-
errs.push(`${
|
|
2167
|
+
errs.push(`${path15 || "<root>"}: ${value} < minimum ${schema["minimum"]}`);
|
|
2106
2168
|
}
|
|
2107
2169
|
if ("maximum" in schema && value > schema["maximum"]) {
|
|
2108
|
-
errs.push(`${
|
|
2170
|
+
errs.push(`${path15 || "<root>"}: ${value} > maximum ${schema["maximum"]}`);
|
|
2109
2171
|
}
|
|
2110
2172
|
}
|
|
2111
2173
|
if (Array.isArray(value)) {
|
|
2112
2174
|
if ("minItems" in schema && value.length < schema["minItems"]) {
|
|
2113
|
-
errs.push(`${
|
|
2175
|
+
errs.push(`${path15 || "<root>"}: fewer items than minItems ${schema["minItems"]}`);
|
|
2114
2176
|
}
|
|
2115
2177
|
const itemSchema = schema["items"];
|
|
2116
2178
|
if (isObj(itemSchema)) {
|
|
2117
2179
|
for (let i = 0; i < value.length; i++) {
|
|
2118
|
-
errs.push(...validateSchema(value[i], itemSchema, `${
|
|
2180
|
+
errs.push(...validateSchema(value[i], itemSchema, `${path15}[${i}]`));
|
|
2119
2181
|
}
|
|
2120
2182
|
}
|
|
2121
2183
|
}
|
|
@@ -2124,19 +2186,19 @@ function validateSchema(value, schema, path13 = "") {
|
|
|
2124
2186
|
const required = schema["required"] ?? [];
|
|
2125
2187
|
for (const r of required) {
|
|
2126
2188
|
if (!(r in value)) {
|
|
2127
|
-
errs.push(`${
|
|
2189
|
+
errs.push(`${path15 || "<root>"}: missing required key ${pyRepr(r)}`);
|
|
2128
2190
|
}
|
|
2129
2191
|
}
|
|
2130
2192
|
if (schema["additionalProperties"] === false) {
|
|
2131
2193
|
for (const k of Object.keys(value)) {
|
|
2132
2194
|
if (!(k in props)) {
|
|
2133
|
-
errs.push(`${
|
|
2195
|
+
errs.push(`${path15 || "<root>"}: unknown key ${pyRepr(k)}`);
|
|
2134
2196
|
}
|
|
2135
2197
|
}
|
|
2136
2198
|
}
|
|
2137
2199
|
for (const [k, v] of Object.entries(value)) {
|
|
2138
2200
|
const ps = props[k];
|
|
2139
|
-
if (ps) errs.push(...validateSchema(v, ps,
|
|
2201
|
+
if (ps) errs.push(...validateSchema(v, ps, path15 ? `${path15}.${k}` : k));
|
|
2140
2202
|
}
|
|
2141
2203
|
}
|
|
2142
2204
|
return errs;
|
|
@@ -2525,6 +2587,21 @@ async function recordSessionCall(storage, sessionId, answers, wallMs, cpuMs, now
|
|
|
2525
2587
|
}
|
|
2526
2588
|
}
|
|
2527
2589
|
|
|
2590
|
+
// src/core/budgets.ts
|
|
2591
|
+
init_esm_shims();
|
|
2592
|
+
var operatorBudgets = null;
|
|
2593
|
+
function setOperatorBudgets(cfg) {
|
|
2594
|
+
operatorBudgets = cfg;
|
|
2595
|
+
}
|
|
2596
|
+
function operatorCeiling(purpose, provider) {
|
|
2597
|
+
const direct = operatorBudgets?.token_budgets?.[purpose];
|
|
2598
|
+
if (typeof direct === "number" && direct > 0) return Math.trunc(direct);
|
|
2599
|
+
const byProv = operatorBudgets?.token_budgets_by_provider?.[provider.toLowerCase()];
|
|
2600
|
+
const pv = byProv?.[purpose];
|
|
2601
|
+
if (typeof pv === "number" && pv > 0) return Math.trunc(pv);
|
|
2602
|
+
return null;
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2528
2605
|
// src/core/structured.ts
|
|
2529
2606
|
async function requestStructured(provider, baseMessages, schema, opts) {
|
|
2530
2607
|
const maxRetries = opts.maxRetries ?? 1;
|
|
@@ -2599,10 +2676,13 @@ ${schemaText}`;
|
|
|
2599
2676
|
async function askOne(provider, messages, opts) {
|
|
2600
2677
|
const startedWall = performance.now();
|
|
2601
2678
|
const startedCpu = process.cpuUsage();
|
|
2679
|
+
let maxTokens = opts.maxTokens;
|
|
2680
|
+
const ceiling = operatorCeiling(opts.purpose, provider.name);
|
|
2681
|
+
if (ceiling !== null && ceiling < maxTokens) maxTokens = ceiling;
|
|
2602
2682
|
try {
|
|
2603
2683
|
const r = await provider.send({
|
|
2604
2684
|
messages,
|
|
2605
|
-
maxTokens
|
|
2685
|
+
maxTokens,
|
|
2606
2686
|
temperature: opts.temperature,
|
|
2607
2687
|
purpose: opts.purpose,
|
|
2608
2688
|
...opts.signal ? { signal: opts.signal } : {},
|
|
@@ -2643,6 +2723,191 @@ async function askOne(provider, messages, opts) {
|
|
|
2643
2723
|
}
|
|
2644
2724
|
}
|
|
2645
2725
|
|
|
2726
|
+
// src/core/personas.ts
|
|
2727
|
+
init_esm_shims();
|
|
2728
|
+
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
2729
|
+
import path2 from "path";
|
|
2730
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2731
|
+
var PERSONA_IDS = [
|
|
2732
|
+
"engineering",
|
|
2733
|
+
"research",
|
|
2734
|
+
"auditor-security",
|
|
2735
|
+
"auditor-finance",
|
|
2736
|
+
"finance",
|
|
2737
|
+
"design"
|
|
2738
|
+
];
|
|
2739
|
+
function isPersonaId(s) {
|
|
2740
|
+
return PERSONA_IDS.includes(s);
|
|
2741
|
+
}
|
|
2742
|
+
var personaConfig = null;
|
|
2743
|
+
function setPersonaConfig(cfg) {
|
|
2744
|
+
personaConfig = cfg;
|
|
2745
|
+
}
|
|
2746
|
+
var processFirstCallConsumed = false;
|
|
2747
|
+
var sessionsSeen = /* @__PURE__ */ new Set();
|
|
2748
|
+
function consumeFirstCall(sessionId) {
|
|
2749
|
+
if (sessionId) {
|
|
2750
|
+
if (sessionsSeen.has(sessionId)) return false;
|
|
2751
|
+
sessionsSeen.add(sessionId);
|
|
2752
|
+
return true;
|
|
2753
|
+
}
|
|
2754
|
+
if (processFirstCallConsumed) return false;
|
|
2755
|
+
processFirstCallConsumed = true;
|
|
2756
|
+
return true;
|
|
2757
|
+
}
|
|
2758
|
+
var TOOL_PRIORS = {
|
|
2759
|
+
// The audit tool is for auditing — lean to security audit by default (prior
|
|
2760
|
+
// clears the >=2 margin on its own), while finance keywords still flip it to
|
|
2761
|
+
// auditor-finance.
|
|
2762
|
+
audit: { "auditor-security": 3, "auditor-finance": 1 }
|
|
2763
|
+
};
|
|
2764
|
+
var TERMS = {
|
|
2765
|
+
"auditor-security": [
|
|
2766
|
+
[/\b(vuln(erabilit|)|cve|cwe|exploit|owasp|injection|sql\s*i|xss|csrf|ssrf|rce|deserializ|auth[zn]|secret|threat\s*model|attack\s*surface|privilege)\b/i, 3],
|
|
2767
|
+
[/\b(security|hardening|pentest|malicious|sandbox\s*escape)\b/i, 1]
|
|
2768
|
+
],
|
|
2769
|
+
"auditor-finance": [
|
|
2770
|
+
[/\b(gaap|ifrs|sox|materiality|reconcil|ledger|accrual|audit\s*trail|segregation\s*of\s*duties|internal\s*control|journal\s*entry)\b/i, 3],
|
|
2771
|
+
[/\b(audit|invoice|p&l|balance\s*sheet)\b/i, 1]
|
|
2772
|
+
],
|
|
2773
|
+
finance: [
|
|
2774
|
+
[/\b(valuation|dcf|npv|irr|cash\s*flow|unit\s*economics|burn\s*rate|runway|cap\s*table|forecast|p&l|ebitda|cohort|ltv|cac)\b/i, 3],
|
|
2775
|
+
[/\b(budget|revenue|margin|pricing|financial\s*model)\b/i, 1]
|
|
2776
|
+
],
|
|
2777
|
+
research: [
|
|
2778
|
+
[/\b(literature|hypothes(is|es)|methodology|empirical|citation|peer[-\s]?review|ablation|systematic\s*review|meta[-\s]?analysis)\b/i, 3],
|
|
2779
|
+
[/\b(research|study|survey|evidence|paper|findings)\b/i, 1]
|
|
2780
|
+
],
|
|
2781
|
+
design: [
|
|
2782
|
+
[/\b(ux|usability|wireframe|figma|interaction\s*design|user\s*journey|design\s*system|a11y|accessibilit|heuristic\s*evaluation|information\s*architecture)\b/i, 3],
|
|
2783
|
+
[/\b(design|layout|visual|prototype|onboarding\s*flow)\b/i, 1]
|
|
2784
|
+
]
|
|
2785
|
+
};
|
|
2786
|
+
function routePersona(prompt, toolName) {
|
|
2787
|
+
const text = String(prompt ?? "");
|
|
2788
|
+
const scores = { engineering: 1 };
|
|
2789
|
+
const priors = TOOL_PRIORS[toolName] ?? {};
|
|
2790
|
+
for (const id of PERSONA_IDS) {
|
|
2791
|
+
if (id === "engineering") continue;
|
|
2792
|
+
let s = priors[id] ?? 0;
|
|
2793
|
+
for (const [re, w] of TERMS[id]) {
|
|
2794
|
+
if (re.test(text)) s += w;
|
|
2795
|
+
}
|
|
2796
|
+
scores[id] = s;
|
|
2797
|
+
}
|
|
2798
|
+
let bestId = "engineering";
|
|
2799
|
+
let bestScore = -Infinity;
|
|
2800
|
+
for (const id of PERSONA_IDS) {
|
|
2801
|
+
if (id === "engineering") continue;
|
|
2802
|
+
if (scores[id] > bestScore) {
|
|
2803
|
+
bestScore = scores[id];
|
|
2804
|
+
bestId = id;
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
if (bestScore - (scores["engineering"] ?? 0) >= 2) return { id: bestId, scores };
|
|
2808
|
+
return { id: "engineering", scores };
|
|
2809
|
+
}
|
|
2810
|
+
function resolvePersona(opts) {
|
|
2811
|
+
const cfg = opts.config ?? personaConfig;
|
|
2812
|
+
if (cfg?.enabled === false) return { id: null, source: "disabled", bypassed: true };
|
|
2813
|
+
const explicit = opts.explicit?.trim().toLowerCase();
|
|
2814
|
+
if (explicit === "none" || explicit === "off") {
|
|
2815
|
+
return { id: null, source: "none", bypassed: true };
|
|
2816
|
+
}
|
|
2817
|
+
if (explicit && explicit !== "auto" && isPersonaId(explicit)) {
|
|
2818
|
+
return { id: explicit, source: "explicit", bypassed: false };
|
|
2819
|
+
}
|
|
2820
|
+
const envPin = (opts.envPin ?? process.env["CROSSCHECK_PERSONA"])?.trim().toLowerCase();
|
|
2821
|
+
if (envPin && isPersonaId(envPin)) {
|
|
2822
|
+
return { id: envPin, source: "env", bypassed: false };
|
|
2823
|
+
}
|
|
2824
|
+
const routed = routePersona(opts.prompt, opts.toolName);
|
|
2825
|
+
if (routed.id !== "engineering") {
|
|
2826
|
+
return { id: routed.id, source: "heuristic", scores: routed.scores, bypassed: false };
|
|
2827
|
+
}
|
|
2828
|
+
if (opts.firstCall) {
|
|
2829
|
+
return { id: "engineering", source: "default-first-call", scores: routed.scores, bypassed: false };
|
|
2830
|
+
}
|
|
2831
|
+
return { id: null, source: "heuristic", scores: routed.scores, bypassed: true };
|
|
2832
|
+
}
|
|
2833
|
+
var bodyCache = /* @__PURE__ */ new Map();
|
|
2834
|
+
function bundledPersonasDir() {
|
|
2835
|
+
let dir;
|
|
2836
|
+
try {
|
|
2837
|
+
dir = path2.dirname(fileURLToPath2(import.meta.url));
|
|
2838
|
+
} catch {
|
|
2839
|
+
dir = process.cwd();
|
|
2840
|
+
}
|
|
2841
|
+
for (let i = 0; i < 8; i += 1) {
|
|
2842
|
+
const cand = path2.join(dir, "personas");
|
|
2843
|
+
if (existsSync(path2.join(cand, "engineering.md"))) return cand;
|
|
2844
|
+
const parent = path2.dirname(dir);
|
|
2845
|
+
if (parent === dir) break;
|
|
2846
|
+
dir = parent;
|
|
2847
|
+
}
|
|
2848
|
+
return void 0;
|
|
2849
|
+
}
|
|
2850
|
+
function readIfExists(p) {
|
|
2851
|
+
try {
|
|
2852
|
+
return existsSync(p) ? readFileSync2(p, "utf8") : null;
|
|
2853
|
+
} catch {
|
|
2854
|
+
return null;
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
function loadPersonaBody(id, opts) {
|
|
2858
|
+
const cfg = opts?.config ?? personaConfig;
|
|
2859
|
+
const cacheKey = `${id}|${opts?.repoRoot ?? ""}|${cfg?.dir ?? ""}`;
|
|
2860
|
+
const cached = bodyCache.get(cacheKey);
|
|
2861
|
+
if (cached !== void 0) return cached;
|
|
2862
|
+
const candidates = [];
|
|
2863
|
+
if (cfg?.dir) candidates.push(path2.join(cfg.dir, `${id}.md`));
|
|
2864
|
+
if (opts?.repoRoot) candidates.push(path2.join(opts.repoRoot, ".crosscheck", "personas", `${id}.md`));
|
|
2865
|
+
if (id === "engineering" && opts?.repoRoot) candidates.push(path2.join(opts.repoRoot, "CLAUDE.md"));
|
|
2866
|
+
const bundled = bundledPersonasDir();
|
|
2867
|
+
if (bundled) candidates.push(path2.join(bundled, `${id}.md`));
|
|
2868
|
+
for (const c of candidates) {
|
|
2869
|
+
const body = readIfExists(c);
|
|
2870
|
+
if (body && body.trim()) {
|
|
2871
|
+
const trimmed = body.trim();
|
|
2872
|
+
bodyCache.set(cacheKey, trimmed);
|
|
2873
|
+
return trimmed;
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
return null;
|
|
2877
|
+
}
|
|
2878
|
+
function renderPersonaBlock(id, body) {
|
|
2879
|
+
return `<operating-context source="${id}">
|
|
2880
|
+
${body}
|
|
2881
|
+
</operating-context>`;
|
|
2882
|
+
}
|
|
2883
|
+
function buildPersonaInjection(opts) {
|
|
2884
|
+
try {
|
|
2885
|
+
const firstCall = consumeFirstCall(opts.sessionId);
|
|
2886
|
+
const res = resolvePersona({
|
|
2887
|
+
toolName: opts.toolName,
|
|
2888
|
+
prompt: opts.prompt,
|
|
2889
|
+
explicit: opts.explicit,
|
|
2890
|
+
firstCall,
|
|
2891
|
+
config: personaConfig
|
|
2892
|
+
});
|
|
2893
|
+
const meta = {
|
|
2894
|
+
used: res.id,
|
|
2895
|
+
source: res.source,
|
|
2896
|
+
injected_into: res.id ? ["moderator"] : [],
|
|
2897
|
+
bypassed: res.bypassed,
|
|
2898
|
+
...res.scores ? { scores: res.scores } : {}
|
|
2899
|
+
};
|
|
2900
|
+
if (!res.id) return { block: null, meta };
|
|
2901
|
+
const body = loadPersonaBody(res.id, { repoRoot: opts.repoRoot });
|
|
2902
|
+
if (!body) {
|
|
2903
|
+
return { block: null, meta: { ...meta, injected_into: [], bypassed: true } };
|
|
2904
|
+
}
|
|
2905
|
+
return { block: renderPersonaBlock(res.id, body), meta };
|
|
2906
|
+
} catch {
|
|
2907
|
+
return { block: null, meta: { used: null, source: "none", injected_into: [], bypassed: true } };
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2646
2911
|
// src/core/utils.ts
|
|
2647
2912
|
init_esm_shims();
|
|
2648
2913
|
function checkSessionBreakers(session, cfg) {
|
|
@@ -2860,6 +3125,13 @@ function redactObj(obj, cfg) {
|
|
|
2860
3125
|
}
|
|
2861
3126
|
return obj;
|
|
2862
3127
|
}
|
|
3128
|
+
var globalRedaction = null;
|
|
3129
|
+
function setRedactionConfig(cfg) {
|
|
3130
|
+
globalRedaction = cfg;
|
|
3131
|
+
}
|
|
3132
|
+
function getRedactionConfig() {
|
|
3133
|
+
return globalRedaction ?? void 0;
|
|
3134
|
+
}
|
|
2863
3135
|
|
|
2864
3136
|
// src/core/transcripts.ts
|
|
2865
3137
|
var FTS_INDEX_MAX_CHARS = 64 * 1024;
|
|
@@ -2917,7 +3189,7 @@ async function writeTranscript(opts) {
|
|
|
2917
3189
|
const now = opts.nowMs ? opts.nowMs() : Date.now();
|
|
2918
3190
|
const stampMs = Math.trunc(now);
|
|
2919
3191
|
const fileName = `${stampMs}-${opts.kind}.json`;
|
|
2920
|
-
const redacted = redactObj(opts.payload, opts.redact);
|
|
3192
|
+
const redacted = redactObj(opts.payload, opts.redact ?? getRedactionConfig());
|
|
2921
3193
|
let absPath;
|
|
2922
3194
|
try {
|
|
2923
3195
|
mkdirSync2(opts.transcriptsDir, { recursive: true });
|
|
@@ -3414,7 +3686,18 @@ async function runAudit(args, opts) {
|
|
|
3414
3686
|
for (const r2 of DEFAULT_AUDIT_RUBRICS) rubricItems.push({ ...r2 });
|
|
3415
3687
|
}
|
|
3416
3688
|
const rubricText = rubricItems.map((it) => `- ${it.id} (severity=${it.severity}): ${it.description}`).join("\n");
|
|
3417
|
-
const
|
|
3689
|
+
const persona = buildPersonaInjection({
|
|
3690
|
+
toolName: "audit",
|
|
3691
|
+
prompt: `${userConstraints ?? ""}
|
|
3692
|
+
${outputResolved}`,
|
|
3693
|
+
...typeof args["persona"] === "string" ? { explicit: args["persona"] } : {},
|
|
3694
|
+
...opts.repoRoot ? { repoRoot: opts.repoRoot } : {},
|
|
3695
|
+
sessionId: typeof args["session_id"] === "string" ? args["session_id"] : null
|
|
3696
|
+
});
|
|
3697
|
+
const auditorSys = "You are an independent auditor. Score the OUTPUT against each rubric item on a 0..1 likelihood that the rubric is satisfied. Set pass=true iff score >= 0.7. Be concise in `rationale` (1-2 sentences each).";
|
|
3698
|
+
const sysMsg = persona.block ? `${persona.block}
|
|
3699
|
+
|
|
3700
|
+
${auditorSys}` : auditorSys;
|
|
3418
3701
|
const userMsg = (userConstraints ? `USER CONSTRAINTS:
|
|
3419
3702
|
${userConstraints}
|
|
3420
3703
|
|
|
@@ -3511,6 +3794,7 @@ ${rubricText}`;
|
|
|
3511
3794
|
audit_process_failure: flags.audit_process_failure,
|
|
3512
3795
|
judges_stats: flags.judges_stats
|
|
3513
3796
|
};
|
|
3797
|
+
if (persona.meta.used !== null) coalesceResult["persona"] = persona.meta;
|
|
3514
3798
|
if (!allPass2 && opts.storage && typeof sessionId === "string" && sessionId) {
|
|
3515
3799
|
const marked = await markStaleOnAuditFailure(
|
|
3516
3800
|
opts.storage,
|
|
@@ -3591,6 +3875,7 @@ ${rubricText}`;
|
|
|
3591
3875
|
overall_score: overall,
|
|
3592
3876
|
passed: allPass
|
|
3593
3877
|
};
|
|
3878
|
+
if (persona.meta.used !== null) result["persona"] = persona.meta;
|
|
3594
3879
|
if (errs.length > 0) result["validation_errors"] = errs;
|
|
3595
3880
|
if (!allPass && opts.storage && typeof sessionId === "string" && sessionId) {
|
|
3596
3881
|
const marked = await markStaleOnAuditFailure(
|
|
@@ -3887,7 +4172,7 @@ function loadAuditTextFromSession(sessionId, transcriptsDir) {
|
|
|
3887
4172
|
const p = join(transcriptsDir, name);
|
|
3888
4173
|
let doc2;
|
|
3889
4174
|
try {
|
|
3890
|
-
doc2 = JSON.parse(
|
|
4175
|
+
doc2 = JSON.parse(readFileSync3(p, "utf8"));
|
|
3891
4176
|
} catch {
|
|
3892
4177
|
continue;
|
|
3893
4178
|
}
|
|
@@ -3907,7 +4192,7 @@ function loadAuditTextFromSession(sessionId, transcriptsDir) {
|
|
|
3907
4192
|
if (bestPath === null) return "";
|
|
3908
4193
|
let doc;
|
|
3909
4194
|
try {
|
|
3910
|
-
doc = JSON.parse(
|
|
4195
|
+
doc = JSON.parse(readFileSync3(bestPath, "utf8"));
|
|
3911
4196
|
} catch {
|
|
3912
4197
|
return "";
|
|
3913
4198
|
}
|
|
@@ -3940,7 +4225,7 @@ function boolArg(v, defaultVal) {
|
|
|
3940
4225
|
|
|
3941
4226
|
// src/tools/bench.ts
|
|
3942
4227
|
init_esm_shims();
|
|
3943
|
-
import { existsSync, readdirSync as readdirSync2, readFileSync as
|
|
4228
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
|
|
3944
4229
|
import path4 from "path";
|
|
3945
4230
|
|
|
3946
4231
|
// src/tools/confer.ts
|
|
@@ -4477,21 +4762,21 @@ function extractToolCall(text) {
|
|
|
4477
4762
|
return { call, parseError: null };
|
|
4478
4763
|
}
|
|
4479
4764
|
function wrapToolResult(name, content) {
|
|
4480
|
-
let
|
|
4481
|
-
if (typeof content === "string")
|
|
4765
|
+
let str2;
|
|
4766
|
+
if (typeof content === "string") str2 = content;
|
|
4482
4767
|
else {
|
|
4483
4768
|
try {
|
|
4484
|
-
|
|
4769
|
+
str2 = JSON.stringify(content);
|
|
4485
4770
|
} catch {
|
|
4486
|
-
|
|
4771
|
+
str2 = String(content);
|
|
4487
4772
|
}
|
|
4488
4773
|
}
|
|
4489
|
-
if (
|
|
4490
|
-
|
|
4774
|
+
if (str2.length > WORKER_TOOLS_MAX_RESULT_CHARS) {
|
|
4775
|
+
str2 = str2.slice(0, WORKER_TOOLS_MAX_RESULT_CHARS) + "\n... (truncated)";
|
|
4491
4776
|
}
|
|
4492
4777
|
return `<tool_result name="${name}">
|
|
4493
4778
|
<untrusted_input>
|
|
4494
|
-
${neutralizeInjection(
|
|
4779
|
+
${neutralizeInjection(str2)}
|
|
4495
4780
|
</untrusted_input>
|
|
4496
4781
|
</tool_result>`;
|
|
4497
4782
|
}
|
|
@@ -5056,8 +5341,18 @@ async function runConfer(args, opts) {
|
|
|
5056
5341
|
const untrusted = Boolean(args["untrusted_input"]);
|
|
5057
5342
|
const canary = untrusted ? mintCanary() : null;
|
|
5058
5343
|
const baseSys = "You are part of a panel of LLMs consulted by an engineer working inside Claude Code. Answer directly, cite assumptions, and keep it crisp.";
|
|
5059
|
-
const
|
|
5060
|
-
|
|
5344
|
+
const persona = buildPersonaInjection({
|
|
5345
|
+
toolName: "confer",
|
|
5346
|
+
prompt: question,
|
|
5347
|
+
...typeof args["persona"] === "string" ? { explicit: args["persona"] } : {},
|
|
5348
|
+
...opts.repoRoot ? { repoRoot: opts.repoRoot } : {},
|
|
5349
|
+
sessionId: typeof args["session_id"] === "string" ? args["session_id"] : null
|
|
5350
|
+
});
|
|
5351
|
+
const baseSysP = persona.block ? `${persona.block}
|
|
5352
|
+
|
|
5353
|
+
${baseSys}` : baseSys;
|
|
5354
|
+
const sysMsg = untrusted ? `${baseSysP}
|
|
5355
|
+
${UNTRUSTED_SYSTEM_NOTE}` : baseSysP;
|
|
5061
5356
|
const messages = [{ role: "system", content: sysMsg }];
|
|
5062
5357
|
if (context) {
|
|
5063
5358
|
const ctxBody = untrusted ? wrapUntrusted(context, canary) : context;
|
|
@@ -5171,6 +5466,7 @@ ${ctxBody}` });
|
|
|
5171
5466
|
question,
|
|
5172
5467
|
answers: sanitized
|
|
5173
5468
|
};
|
|
5469
|
+
if (persona.meta.used !== null) result["persona"] = persona.meta;
|
|
5174
5470
|
if (earlyStopped) {
|
|
5175
5471
|
result["early_stopped"] = true;
|
|
5176
5472
|
result["skipped_providers"] = skippedProviders;
|
|
@@ -5390,9 +5686,19 @@ async function runSandboxed(args) {
|
|
|
5390
5686
|
}
|
|
5391
5687
|
resolve2(result);
|
|
5392
5688
|
};
|
|
5689
|
+
const memKb = isUnix && args.memoryMb && args.memoryMb > 0 ? Math.trunc(args.memoryMb * 1024) : 0;
|
|
5690
|
+
const cpuS = isUnix && args.cpuSeconds && args.cpuSeconds > 0 ? Math.trunc(args.cpuSeconds) : 0;
|
|
5691
|
+
const useUlimit = memKb > 0 || cpuS > 0;
|
|
5692
|
+
const spawnBin = useUlimit ? "/bin/sh" : args.cmd[0];
|
|
5693
|
+
const spawnArgv = useUlimit ? [
|
|
5694
|
+
"-c",
|
|
5695
|
+
`${[memKb > 0 ? `ulimit -v ${memKb}` : "", cpuS > 0 ? `ulimit -t ${cpuS}` : ""].filter(Boolean).join("; ")} 2>/dev/null; exec "$@"`,
|
|
5696
|
+
"sh",
|
|
5697
|
+
...args.cmd
|
|
5698
|
+
] : args.cmd.slice(1);
|
|
5393
5699
|
let child;
|
|
5394
5700
|
try {
|
|
5395
|
-
child = spawn(
|
|
5701
|
+
child = spawn(spawnBin, spawnArgv, {
|
|
5396
5702
|
cwd: tempCwd ?? process.cwd(),
|
|
5397
5703
|
env,
|
|
5398
5704
|
// detached: own process-group on Unix (equivalent to setsid).
|
|
@@ -6081,7 +6387,7 @@ async function runBench(args, opts) {
|
|
|
6081
6387
|
return result;
|
|
6082
6388
|
}
|
|
6083
6389
|
function loadGoldens(dirPath, filter) {
|
|
6084
|
-
if (!
|
|
6390
|
+
if (!existsSync2(dirPath)) return [];
|
|
6085
6391
|
let entries;
|
|
6086
6392
|
try {
|
|
6087
6393
|
entries = readdirSync2(dirPath);
|
|
@@ -6099,7 +6405,7 @@ function loadGoldens(dirPath, filter) {
|
|
|
6099
6405
|
}
|
|
6100
6406
|
let doc;
|
|
6101
6407
|
try {
|
|
6102
|
-
doc = JSON.parse(
|
|
6408
|
+
doc = JSON.parse(readFileSync4(p, "utf8"));
|
|
6103
6409
|
} catch {
|
|
6104
6410
|
continue;
|
|
6105
6411
|
}
|
|
@@ -6180,9 +6486,9 @@ function isObj4(v) {
|
|
|
6180
6486
|
init_esm_shims();
|
|
6181
6487
|
import { createHash as createHash2 } from "crypto";
|
|
6182
6488
|
import {
|
|
6183
|
-
existsSync as
|
|
6489
|
+
existsSync as existsSync3,
|
|
6184
6490
|
mkdirSync as mkdirSync3,
|
|
6185
|
-
readFileSync as
|
|
6491
|
+
readFileSync as readFileSync5,
|
|
6186
6492
|
statSync as statSync3,
|
|
6187
6493
|
unlinkSync,
|
|
6188
6494
|
writeFileSync as writeFileSync2
|
|
@@ -6259,7 +6565,7 @@ async function runConfigPin(args, opts) {
|
|
|
6259
6565
|
};
|
|
6260
6566
|
}
|
|
6261
6567
|
const absPath = resolvePinPath(opts);
|
|
6262
|
-
const existed =
|
|
6568
|
+
const existed = existsSync3(absPath);
|
|
6263
6569
|
if (existed) {
|
|
6264
6570
|
try {
|
|
6265
6571
|
unlinkSync(absPath);
|
|
@@ -6297,7 +6603,7 @@ function computeDrift(opts) {
|
|
|
6297
6603
|
missing_files: missingFiles.sort(),
|
|
6298
6604
|
pin_file: relPath(opts, absPath),
|
|
6299
6605
|
pinned_at: Number(doc?.["pinned_at"] ?? 0) || 0,
|
|
6300
|
-
has_pin_file:
|
|
6606
|
+
has_pin_file: existsSync3(absPath)
|
|
6301
6607
|
};
|
|
6302
6608
|
}
|
|
6303
6609
|
function shouldBlock(opts, drift) {
|
|
@@ -6322,7 +6628,7 @@ function resolveTargets(opts) {
|
|
|
6322
6628
|
const out = [];
|
|
6323
6629
|
for (const p of list) {
|
|
6324
6630
|
const abs = path5.isAbsolute(p) ? p : path5.join(opts.repoRoot, p);
|
|
6325
|
-
if (
|
|
6631
|
+
if (existsSync3(abs)) {
|
|
6326
6632
|
try {
|
|
6327
6633
|
const st = statSync3(abs);
|
|
6328
6634
|
if (st.isFile()) out.push(abs);
|
|
@@ -6335,7 +6641,7 @@ function resolveTargets(opts) {
|
|
|
6335
6641
|
function hashFile(abs) {
|
|
6336
6642
|
let raw;
|
|
6337
6643
|
try {
|
|
6338
|
-
raw =
|
|
6644
|
+
raw = readFileSync5(abs);
|
|
6339
6645
|
} catch {
|
|
6340
6646
|
return "";
|
|
6341
6647
|
}
|
|
@@ -6355,9 +6661,9 @@ function resolvePinPath(opts) {
|
|
|
6355
6661
|
return path5.isAbsolute(raw) ? raw : path5.join(opts.repoRoot, raw);
|
|
6356
6662
|
}
|
|
6357
6663
|
function loadDoc(absPath) {
|
|
6358
|
-
if (!
|
|
6664
|
+
if (!existsSync3(absPath)) return null;
|
|
6359
6665
|
try {
|
|
6360
|
-
const txt =
|
|
6666
|
+
const txt = readFileSync5(absPath, "utf8");
|
|
6361
6667
|
const doc = JSON.parse(txt);
|
|
6362
6668
|
return doc && typeof doc === "object" && !Array.isArray(doc) ? doc : null;
|
|
6363
6669
|
} catch {
|
|
@@ -6419,7 +6725,7 @@ function pyRepr2(v) {
|
|
|
6419
6725
|
// src/tools/create.ts
|
|
6420
6726
|
init_esm_shims();
|
|
6421
6727
|
import { createHash as createHash5 } from "crypto";
|
|
6422
|
-
import { existsSync as
|
|
6728
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
6423
6729
|
import path7 from "path";
|
|
6424
6730
|
|
|
6425
6731
|
// src/tools/orchestrate.ts
|
|
@@ -6523,9 +6829,9 @@ function isDeadModelError(err) {
|
|
|
6523
6829
|
init_esm_shims();
|
|
6524
6830
|
import { createHash as createHash3 } from "crypto";
|
|
6525
6831
|
import {
|
|
6526
|
-
existsSync as
|
|
6832
|
+
existsSync as existsSync4,
|
|
6527
6833
|
mkdirSync as mkdirSync4,
|
|
6528
|
-
readFileSync as
|
|
6834
|
+
readFileSync as readFileSync6,
|
|
6529
6835
|
renameSync,
|
|
6530
6836
|
statSync as statSync4,
|
|
6531
6837
|
utimesSync,
|
|
@@ -6597,7 +6903,7 @@ function cachePath(cfg, repoRoot, key) {
|
|
|
6597
6903
|
function getNodeCache(cfg, repoRoot, key, nowMs) {
|
|
6598
6904
|
if (!cfg || cfg.enabled === false) return null;
|
|
6599
6905
|
const p = cachePath(cfg, repoRoot, key);
|
|
6600
|
-
if (!
|
|
6906
|
+
if (!existsSync4(p)) return null;
|
|
6601
6907
|
const ttlS = cfg.ttl_seconds ?? 604800;
|
|
6602
6908
|
try {
|
|
6603
6909
|
const st = statSync4(p);
|
|
@@ -6605,7 +6911,7 @@ function getNodeCache(cfg, repoRoot, key, nowMs) {
|
|
|
6605
6911
|
const now = nowMs ?? Date.now();
|
|
6606
6912
|
if ((now - st.mtimeMs) / 1e3 > ttlS) return null;
|
|
6607
6913
|
}
|
|
6608
|
-
const data = JSON.parse(
|
|
6914
|
+
const data = JSON.parse(readFileSync6(p, "utf8"));
|
|
6609
6915
|
try {
|
|
6610
6916
|
const t = (nowMs ?? Date.now()) / 1e3;
|
|
6611
6917
|
utimesSync(p, t, t);
|
|
@@ -6616,11 +6922,11 @@ function getNodeCache(cfg, repoRoot, key, nowMs) {
|
|
|
6616
6922
|
return null;
|
|
6617
6923
|
}
|
|
6618
6924
|
}
|
|
6619
|
-
function atomicWriteJson(
|
|
6620
|
-
mkdirSync4(dirname2(
|
|
6621
|
-
const tmp = `${
|
|
6925
|
+
function atomicWriteJson(path15, payload) {
|
|
6926
|
+
mkdirSync4(dirname2(path15), { recursive: true });
|
|
6927
|
+
const tmp = `${path15}.${process.pid}.${Date.now()}.tmp`;
|
|
6622
6928
|
writeFileSync3(tmp, JSON.stringify(payload), "utf8");
|
|
6623
|
-
renameSync(tmp,
|
|
6929
|
+
renameSync(tmp, path15);
|
|
6624
6930
|
}
|
|
6625
6931
|
function putNodeCache(cfg, repoRoot, key, value) {
|
|
6626
6932
|
if (!cfg || cfg.enabled === false) return;
|
|
@@ -6631,7 +6937,7 @@ function putNodeCache(cfg, repoRoot, key, value) {
|
|
|
6631
6937
|
const maxBytes = cfg.max_cache_bytes ?? 100 * 1024 * 1024;
|
|
6632
6938
|
if (maxEntries <= 0 && maxBytes <= 0) return;
|
|
6633
6939
|
const base = resolveCacheDir(cfg, repoRoot);
|
|
6634
|
-
if (!
|
|
6940
|
+
if (!existsSync4(base)) return;
|
|
6635
6941
|
const entries = [];
|
|
6636
6942
|
let totalBytes = 0;
|
|
6637
6943
|
for (const shard of readdirSync3(base)) {
|
|
@@ -7223,8 +7529,19 @@ NODE OUTPUTS:
|
|
|
7223
7529
|
${recombineLines.join("\n\n")}
|
|
7224
7530
|
|
|
7225
7531
|
Synthesize the node outputs into a single coherent deliverable. Preserve any [MISSING: ...] markers verbatim where the upstream node failed so the caller sees what is incomplete.`;
|
|
7532
|
+
const persona = buildPersonaInjection({
|
|
7533
|
+
toolName: "orchestrate",
|
|
7534
|
+
prompt: goal ?? recombinePrompt,
|
|
7535
|
+
...typeof args["persona"] === "string" ? { explicit: args["persona"] } : {},
|
|
7536
|
+
...opts.repoRoot ? { repoRoot: opts.repoRoot } : {},
|
|
7537
|
+
sessionId: typeof args["session_id"] === "string" ? args["session_id"] : null
|
|
7538
|
+
});
|
|
7539
|
+
const personaMeta = persona.meta;
|
|
7540
|
+
const recombineSys = "You are the orchestrator. Combine node outputs into the final result.";
|
|
7226
7541
|
const recMsgs = [
|
|
7227
|
-
{ role: "system", content:
|
|
7542
|
+
{ role: "system", content: persona.block ? `${persona.block}
|
|
7543
|
+
|
|
7544
|
+
${recombineSys}` : recombineSys },
|
|
7228
7545
|
{ role: "user", content: recombinePrompt }
|
|
7229
7546
|
];
|
|
7230
7547
|
let synthProvider = moderator;
|
|
@@ -7297,6 +7614,7 @@ Synthesize the node outputs into a single coherent deliverable. Preserve any [MI
|
|
|
7297
7614
|
fail_fast: failFast,
|
|
7298
7615
|
cheap_mode: cheapMode
|
|
7299
7616
|
};
|
|
7617
|
+
if (personaMeta.used !== null) result["persona"] = personaMeta;
|
|
7300
7618
|
if (synthModel) result["synth_model"] = synthModel;
|
|
7301
7619
|
if (synthErr) result["synth_error"] = synthErr;
|
|
7302
7620
|
if (plannerErrors.length > 0) result["planner_errors"] = plannerErrors;
|
|
@@ -7688,7 +8006,7 @@ function pyRound6(x) {
|
|
|
7688
8006
|
// src/tools/fetch.ts
|
|
7689
8007
|
init_esm_shims();
|
|
7690
8008
|
import { createHash as createHash4 } from "crypto";
|
|
7691
|
-
import { existsSync as
|
|
8009
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
7692
8010
|
import path6 from "path";
|
|
7693
8011
|
var DEFAULT_CONFIG = {
|
|
7694
8012
|
enabled: true,
|
|
@@ -7770,9 +8088,9 @@ async function runFetch(args, opts) {
|
|
|
7770
8088
|
const evDir = resolveEvidenceDir(cfg.evidence_dir, opts.repoRoot);
|
|
7771
8089
|
const urlHash16 = sha256Hex2(url).slice(0, 16);
|
|
7772
8090
|
const metaPath = path6.join(evDir, `by-url-${urlHash16}.json`);
|
|
7773
|
-
if (
|
|
8091
|
+
if (existsSync5(metaPath) && !force) {
|
|
7774
8092
|
try {
|
|
7775
|
-
const meta2 = JSON.parse(
|
|
8093
|
+
const meta2 = JSON.parse(readFileSync7(metaPath, "utf8"));
|
|
7776
8094
|
return {
|
|
7777
8095
|
...base,
|
|
7778
8096
|
accepted: true,
|
|
@@ -7812,15 +8130,41 @@ async function runFetch(args, opts) {
|
|
|
7812
8130
|
}
|
|
7813
8131
|
let data;
|
|
7814
8132
|
try {
|
|
7815
|
-
const
|
|
7816
|
-
if (
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
accepted: false,
|
|
7820
|
-
|
|
7821
|
-
|
|
8133
|
+
const reader = response.body?.getReader?.();
|
|
8134
|
+
if (!reader) {
|
|
8135
|
+
const buf = await response.arrayBuffer();
|
|
8136
|
+
if (buf.byteLength > cfg.max_bytes) {
|
|
8137
|
+
return { ...base, accepted: false, reason: `response exceeds max_bytes=${cfg.max_bytes}` };
|
|
8138
|
+
}
|
|
8139
|
+
data = new Uint8Array(buf);
|
|
8140
|
+
} else {
|
|
8141
|
+
const chunks = [];
|
|
8142
|
+
let total = 0;
|
|
8143
|
+
let exceeded = false;
|
|
8144
|
+
for (; ; ) {
|
|
8145
|
+
const { done, value } = await reader.read();
|
|
8146
|
+
if (done) break;
|
|
8147
|
+
if (value) {
|
|
8148
|
+
total += value.byteLength;
|
|
8149
|
+
if (total > cfg.max_bytes) {
|
|
8150
|
+
exceeded = true;
|
|
8151
|
+
await reader.cancel().catch(() => {
|
|
8152
|
+
});
|
|
8153
|
+
break;
|
|
8154
|
+
}
|
|
8155
|
+
chunks.push(value);
|
|
8156
|
+
}
|
|
8157
|
+
}
|
|
8158
|
+
if (exceeded) {
|
|
8159
|
+
return { ...base, accepted: false, reason: `response exceeds max_bytes=${cfg.max_bytes}` };
|
|
8160
|
+
}
|
|
8161
|
+
data = new Uint8Array(total);
|
|
8162
|
+
let offset = 0;
|
|
8163
|
+
for (const c of chunks) {
|
|
8164
|
+
data.set(c, offset);
|
|
8165
|
+
offset += c.byteLength;
|
|
8166
|
+
}
|
|
7822
8167
|
}
|
|
7823
|
-
data = new Uint8Array(buf);
|
|
7824
8168
|
} catch (e) {
|
|
7825
8169
|
return {
|
|
7826
8170
|
...base,
|
|
@@ -8199,7 +8543,7 @@ async function ingestDocuments(documents, sessionId, opts) {
|
|
|
8199
8543
|
if (typeof evidencePath === "string" && evidencePath) {
|
|
8200
8544
|
const abs = path7.isAbsolute(evidencePath) ? evidencePath : opts.repoRoot ? path7.join(opts.repoRoot, evidencePath) : evidencePath;
|
|
8201
8545
|
try {
|
|
8202
|
-
text =
|
|
8546
|
+
text = readFileSync8(abs, "utf8");
|
|
8203
8547
|
} catch {
|
|
8204
8548
|
text = "";
|
|
8205
8549
|
}
|
|
@@ -8213,7 +8557,7 @@ async function ingestDocuments(documents, sessionId, opts) {
|
|
|
8213
8557
|
}, text));
|
|
8214
8558
|
} else {
|
|
8215
8559
|
const abs = path7.isAbsolute(ref) ? ref : opts.repoRoot ? path7.join(opts.repoRoot, ref) : path7.resolve(ref);
|
|
8216
|
-
if (!
|
|
8560
|
+
if (!existsSync6(abs)) {
|
|
8217
8561
|
out.push({
|
|
8218
8562
|
source: ref,
|
|
8219
8563
|
type: "file",
|
|
@@ -8225,7 +8569,7 @@ async function ingestDocuments(documents, sessionId, opts) {
|
|
|
8225
8569
|
}
|
|
8226
8570
|
let text = "";
|
|
8227
8571
|
try {
|
|
8228
|
-
text =
|
|
8572
|
+
text = readFileSync8(abs, "utf8");
|
|
8229
8573
|
} catch (e) {
|
|
8230
8574
|
out.push({
|
|
8231
8575
|
source: ref,
|
|
@@ -9240,15 +9584,27 @@ ${prior}` });
|
|
|
9240
9584
|
let synthesis = null;
|
|
9241
9585
|
let synthesisStructured = null;
|
|
9242
9586
|
let synthesisErrors = [];
|
|
9587
|
+
let personaMeta = null;
|
|
9243
9588
|
if (moderator) {
|
|
9244
9589
|
const condensed = transcript.map(
|
|
9245
9590
|
(e) => `[${e.provider} \u2014 round ${e.round}]
|
|
9246
9591
|
${e.response ?? "(error)"}`
|
|
9247
9592
|
).join("\n\n");
|
|
9593
|
+
const persona = buildPersonaInjection({
|
|
9594
|
+
toolName: "debate",
|
|
9595
|
+
prompt: topic,
|
|
9596
|
+
...typeof args["persona"] === "string" ? { explicit: args["persona"] } : {},
|
|
9597
|
+
...opts.repoRoot ? { repoRoot: opts.repoRoot } : {},
|
|
9598
|
+
sessionId: typeof args["session_id"] === "string" ? args["session_id"] : null
|
|
9599
|
+
});
|
|
9600
|
+
personaMeta = persona.meta;
|
|
9601
|
+
const synthSys = "You are the moderator. Synthesise the debate into a single grounded recommendation.";
|
|
9248
9602
|
const synthMessages = [
|
|
9249
9603
|
{
|
|
9250
9604
|
role: "system",
|
|
9251
|
-
content:
|
|
9605
|
+
content: persona.block ? `${persona.block}
|
|
9606
|
+
|
|
9607
|
+
${synthSys}` : synthSys
|
|
9252
9608
|
},
|
|
9253
9609
|
{
|
|
9254
9610
|
role: "user",
|
|
@@ -9300,6 +9656,7 @@ ${condensed}`
|
|
|
9300
9656
|
transcript,
|
|
9301
9657
|
synthesis
|
|
9302
9658
|
};
|
|
9659
|
+
if (personaMeta && personaMeta.used !== null) result["persona"] = personaMeta;
|
|
9303
9660
|
if (synthesisStructured !== null) {
|
|
9304
9661
|
result["synthesis_structured"] = synthesisStructured;
|
|
9305
9662
|
}
|
|
@@ -9459,9 +9816,9 @@ async function runDelegate(args, opts) {
|
|
|
9459
9816
|
return await deferDelegate(args, opts.bridge);
|
|
9460
9817
|
}
|
|
9461
9818
|
return errorEnvelope10(
|
|
9462
|
-
"
|
|
9463
|
-
"delegate
|
|
9464
|
-
"
|
|
9819
|
+
"DELEGATE_STORAGE_UNAVAILABLE",
|
|
9820
|
+
"delegate needs the local database (for quota tracking), which isn't available",
|
|
9821
|
+
"The engine stores data at <data dir>/db.sqlite (default ~/.crosscheck/db.sqlite). This usually means the native better-sqlite3 module failed to load \u2014 run `npx -y crosscheck-cli@latest doctor`, or set CROSSCHECK_DB_PATH to a writable location, then reconnect."
|
|
9465
9822
|
);
|
|
9466
9823
|
}
|
|
9467
9824
|
const storage = opts.storage;
|
|
@@ -9614,7 +9971,7 @@ function pyListRepr2(xs) {
|
|
|
9614
9971
|
|
|
9615
9972
|
// src/tools/explain.ts
|
|
9616
9973
|
init_esm_shims();
|
|
9617
|
-
import { readdirSync as readdirSync4, readFileSync as
|
|
9974
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync9, statSync as statSync5, existsSync as existsSync7 } from "fs";
|
|
9618
9975
|
import path8 from "path";
|
|
9619
9976
|
async function runExplain(args, opts) {
|
|
9620
9977
|
const sessionId = args["session_id"];
|
|
@@ -9630,9 +9987,9 @@ async function runExplain(args, opts) {
|
|
|
9630
9987
|
return await deferExplain(args, opts.bridge);
|
|
9631
9988
|
}
|
|
9632
9989
|
return errorEnvelope11(
|
|
9633
|
-
"
|
|
9634
|
-
"explain
|
|
9635
|
-
"
|
|
9990
|
+
"EXPLAIN_STORAGE_UNAVAILABLE",
|
|
9991
|
+
"explain needs the local transcript/stats database, which isn't available",
|
|
9992
|
+
"The engine stores data at <data dir>/db.sqlite (default ~/.crosscheck/db.sqlite). This usually means the native better-sqlite3 module failed to load \u2014 run `npx -y crosscheck-cli@latest doctor`, or set CROSSCHECK_DB_PATH to a writable location, then reconnect."
|
|
9636
9993
|
);
|
|
9637
9994
|
}
|
|
9638
9995
|
const storage = opts.storage;
|
|
@@ -9716,7 +10073,7 @@ async function runExplain(args, opts) {
|
|
|
9716
10073
|
return result;
|
|
9717
10074
|
}
|
|
9718
10075
|
function loadTranscriptsForSession(dir, sessionId) {
|
|
9719
|
-
if (!
|
|
10076
|
+
if (!existsSync7(dir)) return [];
|
|
9720
10077
|
let names;
|
|
9721
10078
|
try {
|
|
9722
10079
|
names = readdirSync4(dir);
|
|
@@ -9735,7 +10092,7 @@ function loadTranscriptsForSession(dir, sessionId) {
|
|
|
9735
10092
|
}
|
|
9736
10093
|
let doc;
|
|
9737
10094
|
try {
|
|
9738
|
-
doc = JSON.parse(
|
|
10095
|
+
doc = JSON.parse(readFileSync9(p, "utf8"));
|
|
9739
10096
|
} catch {
|
|
9740
10097
|
continue;
|
|
9741
10098
|
}
|
|
@@ -10395,9 +10752,9 @@ async function runRecall(args, opts) {
|
|
|
10395
10752
|
return await deferRecall(args, opts.bridge);
|
|
10396
10753
|
}
|
|
10397
10754
|
return errorEnvelope12(
|
|
10398
|
-
"
|
|
10399
|
-
"recall
|
|
10400
|
-
"
|
|
10755
|
+
"RECALL_STORAGE_UNAVAILABLE",
|
|
10756
|
+
"recall needs the local database, which isn't available",
|
|
10757
|
+
"The engine stores data at <data dir>/db.sqlite (default ~/.crosscheck/db.sqlite). This usually means the native better-sqlite3 module failed to load \u2014 run `npx -y crosscheck-cli@latest doctor`, or set CROSSCHECK_DB_PATH to a writable location, then reconnect."
|
|
10401
10758
|
);
|
|
10402
10759
|
}
|
|
10403
10760
|
const filter = {};
|
|
@@ -10488,9 +10845,9 @@ async function runRecommendPanel(args, opts) {
|
|
|
10488
10845
|
return await deferRecommendPanel(args, opts.bridge);
|
|
10489
10846
|
}
|
|
10490
10847
|
return errorEnvelope13(
|
|
10491
|
-
"
|
|
10492
|
-
"recommend_panel
|
|
10493
|
-
"
|
|
10848
|
+
"RECOMMEND_PANEL_STORAGE_UNAVAILABLE",
|
|
10849
|
+
"recommend_panel needs the local stats database, which isn't available",
|
|
10850
|
+
"The engine stores stats at <data dir>/db.sqlite (default ~/.crosscheck/db.sqlite). This usually means the native better-sqlite3 module failed to load \u2014 run `npx -y crosscheck-cli@latest doctor`, or set CROSSCHECK_DB_PATH to a writable location, then reconnect."
|
|
10494
10851
|
);
|
|
10495
10852
|
}
|
|
10496
10853
|
const n = Math.max(1, clampInt2(args["n"], 1, 100, 2));
|
|
@@ -10591,16 +10948,16 @@ function toStringArray3(v) {
|
|
|
10591
10948
|
|
|
10592
10949
|
// src/tools/scoreboard.ts
|
|
10593
10950
|
init_esm_shims();
|
|
10594
|
-
import { readFileSync as
|
|
10951
|
+
import { readFileSync as readFileSync10, existsSync as existsSync8 } from "fs";
|
|
10595
10952
|
async function runScoreboard(args, opts) {
|
|
10596
10953
|
if (!opts.storage) {
|
|
10597
10954
|
if (opts.bridge && opts.bridge.toolNames.has("scoreboard")) {
|
|
10598
10955
|
return await deferScoreboard(args, opts.bridge);
|
|
10599
10956
|
}
|
|
10600
10957
|
return errorEnvelope14(
|
|
10601
|
-
"
|
|
10602
|
-
"scoreboard
|
|
10603
|
-
"
|
|
10958
|
+
"SCOREBOARD_STORAGE_UNAVAILABLE",
|
|
10959
|
+
"scoreboard needs the local stats database, which isn't available",
|
|
10960
|
+
"The engine stores stats at <data dir>/db.sqlite (default ~/.crosscheck/db.sqlite). This usually means the native better-sqlite3 module failed to load \u2014 run `npx -y crosscheck-cli@latest doctor`, or set CROSSCHECK_DB_PATH to a writable location, then reconnect."
|
|
10604
10961
|
);
|
|
10605
10962
|
}
|
|
10606
10963
|
const topK = Math.max(1, clampInt3(args["top_k"], 1, 1e4, 20));
|
|
@@ -10674,9 +11031,9 @@ async function runScoreboard(args, opts) {
|
|
|
10674
11031
|
}
|
|
10675
11032
|
usageTotals.cost_usd = Number(usageTotals.cost_usd.toFixed(8));
|
|
10676
11033
|
let recentEvents = [];
|
|
10677
|
-
if (recentLimit > 0 && opts.eventsPath &&
|
|
11034
|
+
if (recentLimit > 0 && opts.eventsPath && existsSync8(opts.eventsPath)) {
|
|
10678
11035
|
try {
|
|
10679
|
-
const lines =
|
|
11036
|
+
const lines = readFileSync10(opts.eventsPath, "utf8").split("\n").filter((l) => l.length > 0);
|
|
10680
11037
|
const tail = lines.slice(-recentLimit);
|
|
10681
11038
|
for (const ln of tail) {
|
|
10682
11039
|
try {
|
|
@@ -10775,9 +11132,9 @@ async function runSessionMemory(args, opts) {
|
|
|
10775
11132
|
return await deferSessionMemory(args, opts.bridge);
|
|
10776
11133
|
}
|
|
10777
11134
|
return errorEnvelope15(
|
|
10778
|
-
"
|
|
10779
|
-
"session_memory
|
|
10780
|
-
"
|
|
11135
|
+
"SESSION_MEMORY_STORAGE_UNAVAILABLE",
|
|
11136
|
+
"session_memory needs the local database, which isn't available",
|
|
11137
|
+
"The engine stores data at <data dir>/db.sqlite (default ~/.crosscheck/db.sqlite). This usually means the native better-sqlite3 module failed to load \u2014 run `npx -y crosscheck-cli@latest doctor`, or set CROSSCHECK_DB_PATH to a writable location, then reconnect."
|
|
10781
11138
|
);
|
|
10782
11139
|
}
|
|
10783
11140
|
const storage = opts.storage;
|
|
@@ -10925,6 +11282,122 @@ function pyRepr6(v) {
|
|
|
10925
11282
|
// src/tools/solve.ts
|
|
10926
11283
|
init_esm_shims();
|
|
10927
11284
|
import { performance as performance12 } from "perf_hooks";
|
|
11285
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
11286
|
+
import path9 from "path";
|
|
11287
|
+
|
|
11288
|
+
// src/core/unified-diff.ts
|
|
11289
|
+
init_esm_shims();
|
|
11290
|
+
function diffLines(a, b) {
|
|
11291
|
+
const n = a.length;
|
|
11292
|
+
const m = b.length;
|
|
11293
|
+
const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
|
|
11294
|
+
for (let i2 = n - 1; i2 >= 0; i2 -= 1) {
|
|
11295
|
+
for (let j2 = m - 1; j2 >= 0; j2 -= 1) {
|
|
11296
|
+
dp[i2][j2] = a[i2] === b[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
|
|
11297
|
+
}
|
|
11298
|
+
}
|
|
11299
|
+
const ops = [];
|
|
11300
|
+
let i = 0;
|
|
11301
|
+
let j = 0;
|
|
11302
|
+
while (i < n && j < m) {
|
|
11303
|
+
if (a[i] === b[j]) {
|
|
11304
|
+
ops.push({ type: "eq", line: a[i] });
|
|
11305
|
+
i += 1;
|
|
11306
|
+
j += 1;
|
|
11307
|
+
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
11308
|
+
ops.push({ type: "del", line: a[i] });
|
|
11309
|
+
i += 1;
|
|
11310
|
+
} else {
|
|
11311
|
+
ops.push({ type: "ins", line: b[j] });
|
|
11312
|
+
j += 1;
|
|
11313
|
+
}
|
|
11314
|
+
}
|
|
11315
|
+
while (i < n) {
|
|
11316
|
+
ops.push({ type: "del", line: a[i] });
|
|
11317
|
+
i += 1;
|
|
11318
|
+
}
|
|
11319
|
+
while (j < m) {
|
|
11320
|
+
ops.push({ type: "ins", line: b[j] });
|
|
11321
|
+
j += 1;
|
|
11322
|
+
}
|
|
11323
|
+
return ops;
|
|
11324
|
+
}
|
|
11325
|
+
function unifiedDiff(oldText, newText, opts) {
|
|
11326
|
+
if (oldText === newText) return "";
|
|
11327
|
+
const context = Math.max(0, opts?.context ?? 3);
|
|
11328
|
+
const a = oldText.split("\n");
|
|
11329
|
+
const b = newText.split("\n");
|
|
11330
|
+
const ops = diffLines(a, b);
|
|
11331
|
+
const indexed = [];
|
|
11332
|
+
let ai = 0;
|
|
11333
|
+
let bi = 0;
|
|
11334
|
+
for (const op of ops) {
|
|
11335
|
+
if (op.type === "eq") {
|
|
11336
|
+
indexed.push({ ...op, aIdx: ai, bIdx: bi });
|
|
11337
|
+
ai += 1;
|
|
11338
|
+
bi += 1;
|
|
11339
|
+
} else if (op.type === "del") {
|
|
11340
|
+
indexed.push({ ...op, aIdx: ai, bIdx: bi });
|
|
11341
|
+
ai += 1;
|
|
11342
|
+
} else {
|
|
11343
|
+
indexed.push({ ...op, aIdx: ai, bIdx: bi });
|
|
11344
|
+
bi += 1;
|
|
11345
|
+
}
|
|
11346
|
+
}
|
|
11347
|
+
const changeIdx = indexed.map((o, k) => o.type === "eq" ? -1 : k).filter((k) => k >= 0);
|
|
11348
|
+
if (changeIdx.length === 0) return "";
|
|
11349
|
+
const groups = [];
|
|
11350
|
+
let gStart = changeIdx[0];
|
|
11351
|
+
let gEnd = changeIdx[0];
|
|
11352
|
+
for (let k = 1; k < changeIdx.length; k += 1) {
|
|
11353
|
+
const idx = changeIdx[k];
|
|
11354
|
+
if (idx - gEnd <= 2 * context + 1) {
|
|
11355
|
+
gEnd = idx;
|
|
11356
|
+
} else {
|
|
11357
|
+
groups.push([gStart, gEnd]);
|
|
11358
|
+
gStart = idx;
|
|
11359
|
+
gEnd = idx;
|
|
11360
|
+
}
|
|
11361
|
+
}
|
|
11362
|
+
groups.push([gStart, gEnd]);
|
|
11363
|
+
const lines = [];
|
|
11364
|
+
lines.push(`--- ${opts?.fromFile ?? "a"}`);
|
|
11365
|
+
lines.push(`+++ ${opts?.toFile ?? "b"}`);
|
|
11366
|
+
for (const [start, end] of groups) {
|
|
11367
|
+
const from = Math.max(0, start - context);
|
|
11368
|
+
const to = Math.min(indexed.length - 1, end + context);
|
|
11369
|
+
let aCount = 0;
|
|
11370
|
+
let bCount = 0;
|
|
11371
|
+
let aStart = -1;
|
|
11372
|
+
let bStart = -1;
|
|
11373
|
+
const body = [];
|
|
11374
|
+
for (let k = from; k <= to; k += 1) {
|
|
11375
|
+
const o = indexed[k];
|
|
11376
|
+
if (o.type === "eq") {
|
|
11377
|
+
if (aStart < 0) aStart = o.aIdx;
|
|
11378
|
+
if (bStart < 0) bStart = o.bIdx;
|
|
11379
|
+
aCount += 1;
|
|
11380
|
+
bCount += 1;
|
|
11381
|
+
body.push(` ${o.line}`);
|
|
11382
|
+
} else if (o.type === "del") {
|
|
11383
|
+
if (aStart < 0) aStart = o.aIdx;
|
|
11384
|
+
aCount += 1;
|
|
11385
|
+
body.push(`-${o.line}`);
|
|
11386
|
+
} else {
|
|
11387
|
+
if (bStart < 0) bStart = o.bIdx;
|
|
11388
|
+
bCount += 1;
|
|
11389
|
+
body.push(`+${o.line}`);
|
|
11390
|
+
}
|
|
11391
|
+
}
|
|
11392
|
+
const aHdr = aCount === 0 ? 0 : aStart + 1;
|
|
11393
|
+
const bHdr = bCount === 0 ? 0 : bStart + 1;
|
|
11394
|
+
lines.push(`@@ -${aHdr},${aCount} +${bHdr},${bCount} @@`);
|
|
11395
|
+
lines.push(...body);
|
|
11396
|
+
}
|
|
11397
|
+
return lines.join("\n") + "\n";
|
|
11398
|
+
}
|
|
11399
|
+
|
|
11400
|
+
// src/tools/solve.ts
|
|
10928
11401
|
async function runSolve(args, opts) {
|
|
10929
11402
|
const problem = typeof args["problem"] === "string" ? args["problem"] : String(args["problem"] ?? "");
|
|
10930
11403
|
if (!problem) {
|
|
@@ -11066,7 +11539,23 @@ Re-emit the FULL solution. No commentary.`
|
|
|
11066
11539
|
const targetPath = args["target_path"];
|
|
11067
11540
|
if (typeof targetPath === "string" && targetPath) {
|
|
11068
11541
|
result["target_path"] = targetPath;
|
|
11069
|
-
|
|
11542
|
+
if (solved && typeof finalProposal === "string") {
|
|
11543
|
+
const abs = path9.isAbsolute(targetPath) ? targetPath : path9.join(opts.repoRoot ?? process.cwd(), targetPath);
|
|
11544
|
+
let oldText = "";
|
|
11545
|
+
try {
|
|
11546
|
+
oldText = readFileSync11(abs, "utf8");
|
|
11547
|
+
} catch {
|
|
11548
|
+
oldText = "";
|
|
11549
|
+
}
|
|
11550
|
+
const newText = finalProposal.endsWith("\n") ? finalProposal : `${finalProposal}
|
|
11551
|
+
`;
|
|
11552
|
+
result["patch"] = unifiedDiff(oldText, newText, {
|
|
11553
|
+
fromFile: `a/${targetPath}`,
|
|
11554
|
+
toFile: `b/${targetPath}`
|
|
11555
|
+
}) || null;
|
|
11556
|
+
} else {
|
|
11557
|
+
result["patch"] = null;
|
|
11558
|
+
}
|
|
11070
11559
|
}
|
|
11071
11560
|
if (unknownNames.length > 0) result["skipped_unknown_providers"] = unknownNames;
|
|
11072
11561
|
if (blocked.length > 0) result["blocked_by_allowlist"] = blocked;
|
|
@@ -11152,6 +11641,8 @@ async function verifyProposalShell(verifier, proposal) {
|
|
|
11152
11641
|
}
|
|
11153
11642
|
const argv = cmd.map((x) => String(x));
|
|
11154
11643
|
const timeoutS = numberArg2(verifier["timeout_s"], 10);
|
|
11644
|
+
const memoryMb = numberArg2(verifier["memory_mb"], 0);
|
|
11645
|
+
const cpuSeconds = numberArg2(verifier["cpu_seconds"], 0);
|
|
11155
11646
|
const expectExit = pyIntCoerce2(verifier["expect_exit_code"], 0);
|
|
11156
11647
|
const expectContains = typeof verifier["expect_stdout_contains"] === "string" ? verifier["expect_stdout_contains"] : null;
|
|
11157
11648
|
const expectRegex = typeof verifier["expect_stdout_regex"] === "string" ? verifier["expect_stdout_regex"] : null;
|
|
@@ -11161,7 +11652,9 @@ async function verifyProposalShell(verifier, proposal) {
|
|
|
11161
11652
|
input: proposal,
|
|
11162
11653
|
// proposal piped to stdin
|
|
11163
11654
|
stdoutTailBytes: 2048,
|
|
11164
|
-
stderrTailBytes: 2048
|
|
11655
|
+
stderrTailBytes: 2048,
|
|
11656
|
+
...memoryMb > 0 ? { memoryMb } : {},
|
|
11657
|
+
...cpuSeconds > 0 ? { cpuSeconds } : {}
|
|
11165
11658
|
});
|
|
11166
11659
|
if (r.status === "timeout") {
|
|
11167
11660
|
return {
|
|
@@ -11331,24 +11824,24 @@ function pyRepr7(v) {
|
|
|
11331
11824
|
// src/tools/update-crosscheck.ts
|
|
11332
11825
|
init_esm_shims();
|
|
11333
11826
|
import { spawnSync } from "child_process";
|
|
11334
|
-
import { existsSync as
|
|
11335
|
-
import
|
|
11827
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
11828
|
+
import path11 from "path";
|
|
11336
11829
|
|
|
11337
11830
|
// src/core/update-notify.ts
|
|
11338
11831
|
init_esm_shims();
|
|
11339
|
-
import { existsSync as
|
|
11832
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
|
|
11340
11833
|
import os from "os";
|
|
11341
|
-
import
|
|
11834
|
+
import path10 from "path";
|
|
11342
11835
|
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
11343
11836
|
var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
|
|
11344
11837
|
var DEFAULT_PACKAGE = "crosscheck-cli";
|
|
11345
11838
|
var FETCH_TIMEOUT_MS = 3e3;
|
|
11346
11839
|
function engineVersion() {
|
|
11347
|
-
return true ? "0.1.
|
|
11840
|
+
return true ? "0.1.13" : "0.0.0-dev";
|
|
11348
11841
|
}
|
|
11349
11842
|
function defaultUpdateCachePath() {
|
|
11350
|
-
const base = process.env["CROSSCHECK_DATA_DIR"] ||
|
|
11351
|
-
return
|
|
11843
|
+
const base = process.env["CROSSCHECK_DATA_DIR"] || path10.join(os.homedir() || os.tmpdir(), ".crosscheck");
|
|
11844
|
+
return path10.join(base, "update_check.json");
|
|
11352
11845
|
}
|
|
11353
11846
|
function isNewer(latest, current) {
|
|
11354
11847
|
const a = parseTriple(latest);
|
|
@@ -11378,8 +11871,8 @@ then reconnect the crosscheck MCP server (run \`/mcp\` in Claude Code, or restar
|
|
|
11378
11871
|
}
|
|
11379
11872
|
function readCache(cachePath2) {
|
|
11380
11873
|
try {
|
|
11381
|
-
if (!
|
|
11382
|
-
const data = JSON.parse(
|
|
11874
|
+
if (!existsSync9(cachePath2)) return null;
|
|
11875
|
+
const data = JSON.parse(readFileSync12(cachePath2, "utf8"));
|
|
11383
11876
|
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
11384
11877
|
return data;
|
|
11385
11878
|
}
|
|
@@ -11390,7 +11883,7 @@ function readCache(cachePath2) {
|
|
|
11390
11883
|
}
|
|
11391
11884
|
function writeCache(cachePath2, record2) {
|
|
11392
11885
|
try {
|
|
11393
|
-
mkdirSync7(
|
|
11886
|
+
mkdirSync7(path10.dirname(cachePath2), { recursive: true });
|
|
11394
11887
|
writeFileSync6(cachePath2, JSON.stringify(record2, null, 2), "utf8");
|
|
11395
11888
|
} catch {
|
|
11396
11889
|
}
|
|
@@ -11444,7 +11937,7 @@ async function runUpdateCrosscheck(args, opts) {
|
|
|
11444
11937
|
const gitRun = opts.gitRun ?? defaultGitRun(opts.repoRoot);
|
|
11445
11938
|
const fetchFn = opts.httpFetch ?? globalThis.fetch;
|
|
11446
11939
|
const now = opts.nowEpochSeconds ? opts.nowEpochSeconds() : Math.floor(Date.now() / 1e3);
|
|
11447
|
-
const cachePath2 = opts.cachePath ??
|
|
11940
|
+
const cachePath2 = opts.cachePath ?? path11.join(opts.repoRoot, ".crosscheck", "update_check.json");
|
|
11448
11941
|
const local = readLocalSha(gitRun);
|
|
11449
11942
|
if (!local) {
|
|
11450
11943
|
return {
|
|
@@ -11616,7 +12109,7 @@ function gitRelationship(gitRun, local, remote) {
|
|
|
11616
12109
|
}
|
|
11617
12110
|
function writeUpdateCache(p, payload) {
|
|
11618
12111
|
try {
|
|
11619
|
-
mkdirSync8(
|
|
12112
|
+
mkdirSync8(path11.dirname(p), { recursive: true });
|
|
11620
12113
|
writeFileSync7(p, JSON.stringify(payload, null, 2), "utf8");
|
|
11621
12114
|
} catch {
|
|
11622
12115
|
}
|
|
@@ -11964,7 +12457,7 @@ function createTool(o, toolName, cheapDefault) {
|
|
|
11964
12457
|
function orchestrateTool(providers, allowlist, bridge, moderator, storage, pricing, breakers, transcriptsDir, repoRoot, nodeCache) {
|
|
11965
12458
|
return {
|
|
11966
12459
|
name: "orchestrate",
|
|
11967
|
-
description: "Plan and execute a DAG of LLM subtasks. Either supply a `goal` (planner will draft the DAG) OR a hand-authored `dag`. Workers run topologically; recombine produces a single `final`.
|
|
12460
|
+
description: "Plan and execute a DAG of LLM subtasks. Either supply a `goal` (planner will draft the DAG) OR a hand-authored `dag`. Workers run topologically; recombine produces a single `final`. Covers the sequential flow, cheap_mode (per-node tier picking), and reactive (mid-flight DAG updates) \u2014 all native.",
|
|
11968
12461
|
inputSchema: {
|
|
11969
12462
|
type: "object",
|
|
11970
12463
|
additionalProperties: true,
|
|
@@ -11998,7 +12491,7 @@ function orchestrateTool(providers, allowlist, bridge, moderator, storage, prici
|
|
|
11998
12491
|
function solveTool(providers, allowlist, bridge, storage, breakers, transcriptsDir, repoRoot) {
|
|
11999
12492
|
return {
|
|
12000
12493
|
name: "solve",
|
|
12001
|
-
description: "Iterative LLM solver. Generates a proposal, verifies it against the supplied verifier, and retries with feedback up to max_attempts.
|
|
12494
|
+
description: "Iterative LLM solver. Generates a proposal, verifies it against the supplied verifier, and retries with feedback up to max_attempts. Verifier kinds: regex_response and shell (run in a native sandbox with wall-clock + optional memory/CPU limits). With target_path, returns a unified-diff patch.",
|
|
12002
12495
|
inputSchema: {
|
|
12003
12496
|
type: "object",
|
|
12004
12497
|
additionalProperties: true,
|
|
@@ -12196,7 +12689,7 @@ function explainTool(storage, bridge, transcriptsDir) {
|
|
|
12196
12689
|
function scoreboardTool(storage, bridge, eventsPath) {
|
|
12197
12690
|
return {
|
|
12198
12691
|
name: "scoreboard",
|
|
12199
|
-
description: "Aggregate provider ballot stats + delegation counts + table totals. Supports top_k (rank limit) and recent_limit (tail of events.jsonl when configured).
|
|
12692
|
+
description: "Aggregate provider ballot stats + delegation counts + table totals. Supports top_k (rank limit) and recent_limit (tail of events.jsonl when configured). Reads the engine's local stats database (auto-created at <data dir>/db.sqlite).",
|
|
12200
12693
|
inputSchema: {
|
|
12201
12694
|
type: "object",
|
|
12202
12695
|
additionalProperties: true,
|
|
@@ -12215,7 +12708,7 @@ function scoreboardTool(storage, bridge, eventsPath) {
|
|
|
12215
12708
|
function sessionMemoryTool(storage, bridge) {
|
|
12216
12709
|
return {
|
|
12217
12710
|
name: "session_memory",
|
|
12218
|
-
description: "CRUD over the per-session working memory ledger. Actions: list, add, mark_stale, clear.
|
|
12711
|
+
description: "CRUD over the per-session working memory ledger. Actions: list, add, mark_stale, clear. Uses the engine's local database (auto-created at <data dir>/db.sqlite).",
|
|
12219
12712
|
inputSchema: {
|
|
12220
12713
|
type: "object",
|
|
12221
12714
|
additionalProperties: true,
|
|
@@ -12632,8 +13125,8 @@ function pingTool() {
|
|
|
12632
13125
|
|
|
12633
13126
|
// src/core/wrappers.ts
|
|
12634
13127
|
init_esm_shims();
|
|
12635
|
-
import { existsSync as
|
|
12636
|
-
import
|
|
13128
|
+
import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
|
|
13129
|
+
import path12 from "path";
|
|
12637
13130
|
function configPinGate(toolName, opts) {
|
|
12638
13131
|
if (toolName === "config_pin") return null;
|
|
12639
13132
|
if (!opts || !opts.repoRoot) return null;
|
|
@@ -12665,8 +13158,8 @@ function configPinGate(toolName, opts) {
|
|
|
12665
13158
|
var cachedUpdateNotice = null;
|
|
12666
13159
|
function readUpdateCache(cachePath2) {
|
|
12667
13160
|
try {
|
|
12668
|
-
if (!
|
|
12669
|
-
const data = JSON.parse(
|
|
13161
|
+
if (!existsSync11(cachePath2)) return null;
|
|
13162
|
+
const data = JSON.parse(readFileSync13(cachePath2, "utf8"));
|
|
12670
13163
|
if (data && typeof data === "object" && !Array.isArray(data)) return data;
|
|
12671
13164
|
return null;
|
|
12672
13165
|
} catch {
|
|
@@ -12678,7 +13171,7 @@ function attachUpdateNotice(result, toolName, opts) {
|
|
|
12678
13171
|
if (toolName === "update_crosscheck") return;
|
|
12679
13172
|
if ("update_notice" in result) return;
|
|
12680
13173
|
if (cachedUpdateNotice === null) {
|
|
12681
|
-
const cachePath2 = opts?.cachePath ?? (opts?.repoRoot ?
|
|
13174
|
+
const cachePath2 = opts?.cachePath ?? (opts?.repoRoot ? path12.join(opts.repoRoot, ".crosscheck", "update_check.json") : defaultUpdateCachePath());
|
|
12682
13175
|
const cached = readUpdateCache(cachePath2);
|
|
12683
13176
|
if (cached && cached.update_available && cached.notice && typeof cached.notice === "object") {
|
|
12684
13177
|
cachedUpdateNotice = cached.notice;
|
|
@@ -12876,7 +13369,7 @@ async function flush() {
|
|
|
12876
13369
|
|
|
12877
13370
|
// src/server.ts
|
|
12878
13371
|
var SERVER_NAME = "crosscheck-agent";
|
|
12879
|
-
var SERVER_VERSION = true ? "0.1.
|
|
13372
|
+
var SERVER_VERSION = true ? "0.1.13" : "0.0.0-dev";
|
|
12880
13373
|
function extractRunSummaryText(out) {
|
|
12881
13374
|
if (out === null || typeof out !== "object" || Array.isArray(out)) return void 0;
|
|
12882
13375
|
const rs = out["run_summary"];
|
|
@@ -13029,6 +13522,10 @@ function buildToolRegistry(opts) {
|
|
|
13029
13522
|
if (opts.nodeCache) registerOpts.nodeCache = opts.nodeCache;
|
|
13030
13523
|
if (opts.configPinning) registerOpts.configPinning = opts.configPinning;
|
|
13031
13524
|
if (opts.rejectConfigDriftEnv) registerOpts.rejectConfigDriftEnv = opts.rejectConfigDriftEnv;
|
|
13525
|
+
if (opts.fetchConfig) registerOpts.fetchConfig = opts.fetchConfig;
|
|
13526
|
+
if (opts.moderatorDefault) registerOpts.moderatorDefault = opts.moderatorDefault;
|
|
13527
|
+
if (opts.benchGoldensDir) registerOpts.benchGoldensDir = opts.benchGoldensDir;
|
|
13528
|
+
if (opts.eventsPath) registerOpts.eventsPath = opts.eventsPath;
|
|
13032
13529
|
const tools = registerCoreTools(registerOpts);
|
|
13033
13530
|
if (!opts.bridge) return tools;
|
|
13034
13531
|
const proxies = buildPythonProxies(opts.bridge);
|
|
@@ -13038,29 +13535,207 @@ function buildToolRegistry(opts) {
|
|
|
13038
13535
|
return tools;
|
|
13039
13536
|
}
|
|
13040
13537
|
|
|
13538
|
+
// src/core/config-file.ts
|
|
13539
|
+
init_esm_shims();
|
|
13540
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14 } from "fs";
|
|
13541
|
+
import path13 from "path";
|
|
13542
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
13543
|
+
function asObj(v) {
|
|
13544
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
|
|
13545
|
+
}
|
|
13546
|
+
function num(v) {
|
|
13547
|
+
const n = Number(v);
|
|
13548
|
+
return Number.isFinite(n) ? n : void 0;
|
|
13549
|
+
}
|
|
13550
|
+
function str(v) {
|
|
13551
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
13552
|
+
}
|
|
13553
|
+
function bool(v) {
|
|
13554
|
+
return typeof v === "boolean" ? v : void 0;
|
|
13555
|
+
}
|
|
13556
|
+
function strArr(v) {
|
|
13557
|
+
return Array.isArray(v) ? v.filter((x) => typeof x === "string") : void 0;
|
|
13558
|
+
}
|
|
13559
|
+
function pickNums(src, keys) {
|
|
13560
|
+
const out = {};
|
|
13561
|
+
for (const k of keys) {
|
|
13562
|
+
const n = num(src[k]);
|
|
13563
|
+
if (n !== void 0) out[k] = n;
|
|
13564
|
+
}
|
|
13565
|
+
return out;
|
|
13566
|
+
}
|
|
13567
|
+
function findConfigFile(opts) {
|
|
13568
|
+
const direct = opts.explicitPath ?? process.env["CROSSCHECK_CONFIG_PATH"];
|
|
13569
|
+
if (direct) return existsSync12(direct) ? direct : void 0;
|
|
13570
|
+
const candidates = [];
|
|
13571
|
+
let dir = opts.startDir;
|
|
13572
|
+
for (let i = 0; dir && i < 8; i += 1) {
|
|
13573
|
+
candidates.push(path13.join(dir, "crosscheck.config.json"));
|
|
13574
|
+
const parent = path13.dirname(dir);
|
|
13575
|
+
if (parent === dir) break;
|
|
13576
|
+
dir = parent;
|
|
13577
|
+
}
|
|
13578
|
+
if (opts.repoRoot) candidates.push(path13.join(opts.repoRoot, "crosscheck.config.json"));
|
|
13579
|
+
if (opts.dataDir) candidates.push(path13.join(opts.dataDir, "crosscheck.config.json"));
|
|
13580
|
+
return candidates.find((p) => existsSync12(p));
|
|
13581
|
+
}
|
|
13582
|
+
function mapConfig(raw, sourcePath) {
|
|
13583
|
+
const result = { sourcePath };
|
|
13584
|
+
const cb = asObj(raw["circuit_breakers"]);
|
|
13585
|
+
if (cb) {
|
|
13586
|
+
const picked = pickNums(cb, [
|
|
13587
|
+
"max_session_cost_usd",
|
|
13588
|
+
"max_session_tokens",
|
|
13589
|
+
"max_session_wall_seconds",
|
|
13590
|
+
"max_dag_nodes",
|
|
13591
|
+
"max_dag_depth"
|
|
13592
|
+
]);
|
|
13593
|
+
if (Object.keys(picked).length > 0) result.circuitBreakers = picked;
|
|
13594
|
+
}
|
|
13595
|
+
const nc = asObj(raw["node_cache"]);
|
|
13596
|
+
if (nc) {
|
|
13597
|
+
const cfg = {
|
|
13598
|
+
...pickNums(nc, ["ttl_seconds", "max_entries", "max_cache_bytes"])
|
|
13599
|
+
};
|
|
13600
|
+
if (bool(nc["enabled"]) !== void 0) cfg.enabled = bool(nc["enabled"]);
|
|
13601
|
+
if (str(nc["dir"]) !== void 0) cfg.dir = str(nc["dir"]);
|
|
13602
|
+
result.nodeCache = cfg;
|
|
13603
|
+
}
|
|
13604
|
+
const f = asObj(raw["fetch"]);
|
|
13605
|
+
if (f) {
|
|
13606
|
+
const cfg = {
|
|
13607
|
+
...pickNums(f, [
|
|
13608
|
+
"max_bytes",
|
|
13609
|
+
"max_bytes_per_session",
|
|
13610
|
+
"max_unique_hosts_per_session",
|
|
13611
|
+
"timeout_s"
|
|
13612
|
+
])
|
|
13613
|
+
};
|
|
13614
|
+
if (bool(f["enabled"]) !== void 0) cfg.enabled = bool(f["enabled"]);
|
|
13615
|
+
if (strArr(f["url_allowlist"]) !== void 0) cfg.url_allowlist = strArr(f["url_allowlist"]);
|
|
13616
|
+
if (str(f["evidence_dir"]) !== void 0) cfg.evidence_dir = str(f["evidence_dir"]);
|
|
13617
|
+
result.fetchConfig = cfg;
|
|
13618
|
+
}
|
|
13619
|
+
if ("provider_allowlist" in raw) {
|
|
13620
|
+
const pa = raw["provider_allowlist"];
|
|
13621
|
+
if (pa === null) result.providerAllowlist = null;
|
|
13622
|
+
else if (strArr(pa) !== void 0) result.providerAllowlist = strArr(pa);
|
|
13623
|
+
}
|
|
13624
|
+
const moderator = str(raw["moderator"]);
|
|
13625
|
+
if (moderator) result.moderatorDefault = moderator.toLowerCase();
|
|
13626
|
+
const bench = asObj(raw["bench"]);
|
|
13627
|
+
const goldens = bench ? str(bench["goldens_dir"]) : void 0;
|
|
13628
|
+
if (goldens) result.benchGoldensDir = goldens;
|
|
13629
|
+
const td = str(raw["transcript_dir"]);
|
|
13630
|
+
if (td) result.transcriptsDir = td;
|
|
13631
|
+
const ev = str(raw["events_log"]);
|
|
13632
|
+
if (ev) result.eventsPath = ev;
|
|
13633
|
+
const db = str(raw["session_db"]);
|
|
13634
|
+
if (db) result.dbPath = db;
|
|
13635
|
+
const lt = bool(raw["log_transcripts"]);
|
|
13636
|
+
if (lt !== void 0) result.logTranscripts = lt;
|
|
13637
|
+
const retries = asObj(raw["retries"]);
|
|
13638
|
+
if (retries) {
|
|
13639
|
+
const maxAttempts = num(retries["max_attempts"]);
|
|
13640
|
+
const baseS = num(retries["backoff_base_s"]);
|
|
13641
|
+
const cfg = {};
|
|
13642
|
+
if (maxAttempts !== void 0) cfg.maxAttempts = Math.max(1, Math.trunc(maxAttempts));
|
|
13643
|
+
if (baseS !== void 0) cfg.backoffBaseMs = Math.round(baseS * 1e3);
|
|
13644
|
+
if (Object.keys(cfg).length > 0) setRetryConfig(cfg);
|
|
13645
|
+
}
|
|
13646
|
+
const rl = asObj(raw["rate_limits"]);
|
|
13647
|
+
if (rl) {
|
|
13648
|
+
const mapped = {};
|
|
13649
|
+
for (const [provider, spec] of Object.entries(rl)) {
|
|
13650
|
+
const s = asObj(spec);
|
|
13651
|
+
const capacity = s ? num(s["capacity"]) : void 0;
|
|
13652
|
+
const refill = s ? num(s["refill_per_sec"]) : void 0;
|
|
13653
|
+
if (capacity !== void 0 && refill !== void 0) {
|
|
13654
|
+
mapped[provider] = { capacity, refillPerSec: refill };
|
|
13655
|
+
}
|
|
13656
|
+
}
|
|
13657
|
+
if (Object.keys(mapped).length > 0) setRateLimitConfig(mapped);
|
|
13658
|
+
}
|
|
13659
|
+
const tb = asObj(raw["token_budgets"]);
|
|
13660
|
+
if (tb) {
|
|
13661
|
+
const budgets = {};
|
|
13662
|
+
for (const [purpose, v] of Object.entries(tb)) {
|
|
13663
|
+
const n = num(v);
|
|
13664
|
+
if (n !== void 0 && n > 0) budgets[purpose] = Math.trunc(n);
|
|
13665
|
+
}
|
|
13666
|
+
if (Object.keys(budgets).length > 0) setOperatorBudgets({ token_budgets: budgets });
|
|
13667
|
+
}
|
|
13668
|
+
const red = asObj(raw["redaction"]);
|
|
13669
|
+
if (red) {
|
|
13670
|
+
const cfg = {};
|
|
13671
|
+
if (bool(red["enabled"]) !== void 0) cfg.enabled = bool(red["enabled"]);
|
|
13672
|
+
const extras = strArr(red["patterns_extra"]);
|
|
13673
|
+
if (extras && extras.length > 0) cfg.patterns_extra = extras;
|
|
13674
|
+
if (bool(red["hmac_tokens"]) === true) {
|
|
13675
|
+
cfg.hmac_tokens = true;
|
|
13676
|
+
cfg.hmac_secret = randomBytes2(32);
|
|
13677
|
+
}
|
|
13678
|
+
if (Object.keys(cfg).length > 0) setRedactionConfig(cfg);
|
|
13679
|
+
}
|
|
13680
|
+
const personas = asObj(raw["personas"]);
|
|
13681
|
+
if (personas) {
|
|
13682
|
+
const cfg = {};
|
|
13683
|
+
if (bool(personas["enabled"]) !== void 0) cfg.enabled = bool(personas["enabled"]);
|
|
13684
|
+
if (str(personas["dir"]) !== void 0) cfg.dir = str(personas["dir"]);
|
|
13685
|
+
if (Object.keys(cfg).length > 0) setPersonaConfig(cfg);
|
|
13686
|
+
}
|
|
13687
|
+
return result;
|
|
13688
|
+
}
|
|
13689
|
+
function loadConfigFile(opts) {
|
|
13690
|
+
const file = findConfigFile(opts);
|
|
13691
|
+
if (!file) return null;
|
|
13692
|
+
let raw;
|
|
13693
|
+
try {
|
|
13694
|
+
raw = JSON.parse(readFileSync14(file, "utf8"));
|
|
13695
|
+
} catch (e) {
|
|
13696
|
+
try {
|
|
13697
|
+
process.stderr.write(
|
|
13698
|
+
`crosscheck-agent: failed to parse ${file} (${e.message}); using defaults
|
|
13699
|
+
`
|
|
13700
|
+
);
|
|
13701
|
+
} catch {
|
|
13702
|
+
}
|
|
13703
|
+
return null;
|
|
13704
|
+
}
|
|
13705
|
+
const obj = asObj(raw);
|
|
13706
|
+
if (!obj) return null;
|
|
13707
|
+
return mapConfig(obj, file);
|
|
13708
|
+
}
|
|
13709
|
+
|
|
13041
13710
|
// src/entrypoints/node-stdio.ts
|
|
13042
13711
|
var SHUTDOWN_TIMEOUT_MS = 2e3;
|
|
13043
13712
|
async function main() {
|
|
13713
|
+
const cfgDataDir = process.env["CROSSCHECK_DATA_DIR"] || path14.join(process.env["HOME"] ?? process.env["USERPROFILE"] ?? "", ".crosscheck");
|
|
13714
|
+
const configFile = loadConfigFile({
|
|
13715
|
+
startDir: process.cwd(),
|
|
13716
|
+
repoRoot: findGitRoot(__dirname),
|
|
13717
|
+
dataDir: cfgDataDir
|
|
13718
|
+
});
|
|
13719
|
+
if (configFile) {
|
|
13720
|
+
if (configFile.eventsPath && !process.env["CROSSCHECK_EVENTS_PATH"]) {
|
|
13721
|
+
process.env["CROSSCHECK_EVENTS_PATH"] = configFile.eventsPath;
|
|
13722
|
+
}
|
|
13723
|
+
if (configFile.dbPath && !process.env["CROSSCHECK_DB_PATH"]) {
|
|
13724
|
+
process.env["CROSSCHECK_DB_PATH"] = configFile.dbPath;
|
|
13725
|
+
}
|
|
13726
|
+
process.stderr.write(`crosscheck-agent: loaded config from ${configFile.sourcePath}
|
|
13727
|
+
`);
|
|
13728
|
+
}
|
|
13044
13729
|
setEventEmitter(buildDefaultEmitter(process.env));
|
|
13045
|
-
let bridge;
|
|
13046
13730
|
if (process.env["CROSSCHECK_BRIDGE_PYTHON"] === "1") {
|
|
13047
|
-
bridge = await spawnPythonBridge({
|
|
13048
|
-
...process.env["CROSSCHECK_PYTHON_PATH"] ? { pythonPath: process.env["CROSSCHECK_PYTHON_PATH"] } : {},
|
|
13049
|
-
...process.env["CROSSCHECK_PYTHON_SERVER"] ? { serverPath: process.env["CROSSCHECK_PYTHON_SERVER"] } : {}
|
|
13050
|
-
});
|
|
13051
|
-
process.stderr.write(
|
|
13052
|
-
`crosscheck-agent: bridge online (pid=${bridge.pid ?? "?"}); proxying ${bridge.toolNames.size} Python tool(s)
|
|
13053
|
-
`
|
|
13054
|
-
);
|
|
13055
|
-
} else {
|
|
13056
13731
|
process.stderr.write(
|
|
13057
|
-
"crosscheck-agent:
|
|
13732
|
+
"crosscheck-agent: CROSSCHECK_BRIDGE_PYTHON is deprecated and ignored \u2014 the engine is native-only.\n"
|
|
13058
13733
|
);
|
|
13059
13734
|
}
|
|
13060
|
-
installShutdownHandlers(
|
|
13735
|
+
installShutdownHandlers();
|
|
13061
13736
|
const bundledPricing = (() => {
|
|
13062
13737
|
try {
|
|
13063
|
-
return
|
|
13738
|
+
return path14.join(path14.dirname(fileURLToPath3(import.meta.url)), "pricing.json");
|
|
13064
13739
|
} catch {
|
|
13065
13740
|
return void 0;
|
|
13066
13741
|
}
|
|
@@ -13069,7 +13744,7 @@ async function main() {
|
|
|
13069
13744
|
process.env["CROSSCHECK_PRICING_PATH"],
|
|
13070
13745
|
resolveRepoFile("config/pricing.json"),
|
|
13071
13746
|
bundledPricing
|
|
13072
|
-
].find((p) => p &&
|
|
13747
|
+
].find((p) => p && existsSync13(p));
|
|
13073
13748
|
const pricing = pricingPath ? loadPricing(pricingPath) : {};
|
|
13074
13749
|
const providers = buildProviders({ env: process.env, pricing });
|
|
13075
13750
|
if (Object.keys(providers).length > 0) {
|
|
@@ -13080,10 +13755,10 @@ async function main() {
|
|
|
13080
13755
|
}
|
|
13081
13756
|
let storage;
|
|
13082
13757
|
if (process.env["CROSSCHECK_DB_DISABLED"] !== "1") {
|
|
13083
|
-
const dbPath = process.env["CROSSCHECK_DB_PATH"] ?? resolveRepoFile(".crosscheck/db.sqlite");
|
|
13758
|
+
const dbPath = process.env["CROSSCHECK_DB_PATH"] ?? resolveRepoFile(".crosscheck/db.sqlite") ?? path14.join(cfgDataDir, "db.sqlite");
|
|
13084
13759
|
if (dbPath) {
|
|
13085
13760
|
try {
|
|
13086
|
-
mkdirSync9(
|
|
13761
|
+
mkdirSync9(path14.dirname(dbPath), { recursive: true });
|
|
13087
13762
|
const { openBetterSqliteStorage: openBetterSqliteStorage2 } = await Promise.resolve().then(() => (init_better_sqlite3(), better_sqlite3_exports));
|
|
13088
13763
|
storage = openBetterSqliteStorage2({ path: dbPath });
|
|
13089
13764
|
await storage.migrate();
|
|
@@ -13093,40 +13768,39 @@ async function main() {
|
|
|
13093
13768
|
);
|
|
13094
13769
|
} catch (e) {
|
|
13095
13770
|
process.stderr.write(
|
|
13096
|
-
`crosscheck-agent: storage init failed (${e.message}); storage tools will
|
|
13771
|
+
`crosscheck-agent: storage init failed (${e.message}); storage-backed tools (recall, scoreboard, recommend_panel, session_memory, explain) will be unavailable
|
|
13097
13772
|
`
|
|
13098
13773
|
);
|
|
13099
13774
|
storage = void 0;
|
|
13100
13775
|
}
|
|
13101
13776
|
}
|
|
13102
13777
|
}
|
|
13103
|
-
const transcriptsDir = process.env["CROSSCHECK_TRANSCRIPTS_DIR"] ?? resolveRepoFile(".crosscheck/transcripts");
|
|
13778
|
+
const transcriptsDir = configFile?.logTranscripts === false ? void 0 : process.env["CROSSCHECK_TRANSCRIPTS_DIR"] ?? configFile?.transcriptsDir ?? resolveRepoFile(".crosscheck/transcripts");
|
|
13104
13779
|
const repoRoot = process.env["CROSSCHECK_REPO_ROOT"] ?? findGitRoot(__dirname);
|
|
13105
13780
|
const transport = new StdioServerTransport();
|
|
13106
13781
|
const serverOpts = { providers };
|
|
13107
|
-
if (bridge) serverOpts.bridge = bridge;
|
|
13108
13782
|
if (storage) serverOpts.storage = storage;
|
|
13109
13783
|
if (transcriptsDir) serverOpts.transcriptsDir = transcriptsDir;
|
|
13110
13784
|
if (repoRoot) serverOpts.repoRoot = repoRoot;
|
|
13111
13785
|
if (pricing && Object.keys(pricing).length > 0) {
|
|
13112
13786
|
serverOpts.pricing = pricing;
|
|
13113
13787
|
}
|
|
13788
|
+
if (configFile?.circuitBreakers) serverOpts.circuitBreakers = configFile.circuitBreakers;
|
|
13789
|
+
if (configFile?.nodeCache) serverOpts.nodeCache = configFile.nodeCache;
|
|
13790
|
+
if (configFile?.fetchConfig) serverOpts.fetchConfig = configFile.fetchConfig;
|
|
13791
|
+
if (configFile?.providerAllowlist !== void 0) {
|
|
13792
|
+
serverOpts.providerAllowlist = configFile.providerAllowlist;
|
|
13793
|
+
}
|
|
13794
|
+
if (configFile?.moderatorDefault) serverOpts.moderatorDefault = configFile.moderatorDefault;
|
|
13795
|
+
if (configFile?.benchGoldensDir) serverOpts.benchGoldensDir = configFile.benchGoldensDir;
|
|
13796
|
+
if (configFile?.eventsPath) serverOpts.eventsPath = configFile.eventsPath;
|
|
13114
13797
|
await connectAndServe(transport, serverOpts);
|
|
13115
13798
|
}
|
|
13116
|
-
function installShutdownHandlers(
|
|
13799
|
+
function installShutdownHandlers() {
|
|
13117
13800
|
let shuttingDown = false;
|
|
13118
13801
|
const shutdown = async (reason) => {
|
|
13119
13802
|
if (shuttingDown) return;
|
|
13120
13803
|
shuttingDown = true;
|
|
13121
|
-
if (bridge) {
|
|
13122
|
-
await Promise.race([
|
|
13123
|
-
bridge.close(),
|
|
13124
|
-
new Promise(
|
|
13125
|
-
(resolve2) => setTimeout(resolve2, SHUTDOWN_TIMEOUT_MS)
|
|
13126
|
-
)
|
|
13127
|
-
]).catch(() => {
|
|
13128
|
-
});
|
|
13129
|
-
}
|
|
13130
13804
|
await Promise.race([
|
|
13131
13805
|
flush(),
|
|
13132
13806
|
new Promise((resolve2) => setTimeout(resolve2, SHUTDOWN_TIMEOUT_MS))
|
|
@@ -13152,9 +13826,9 @@ function installShutdownHandlers(bridge) {
|
|
|
13152
13826
|
function resolveRepoFile(rel) {
|
|
13153
13827
|
let dir = __dirname;
|
|
13154
13828
|
for (let i = 0; i < 8; i++) {
|
|
13155
|
-
const candidate =
|
|
13156
|
-
if (
|
|
13157
|
-
const parent =
|
|
13829
|
+
const candidate = path14.join(dir, rel);
|
|
13830
|
+
if (existsSync13(candidate)) return candidate;
|
|
13831
|
+
const parent = path14.dirname(dir);
|
|
13158
13832
|
if (parent === dir) break;
|
|
13159
13833
|
dir = parent;
|
|
13160
13834
|
}
|
|
@@ -13163,8 +13837,8 @@ function resolveRepoFile(rel) {
|
|
|
13163
13837
|
function findGitRoot(startDir) {
|
|
13164
13838
|
let dir = startDir;
|
|
13165
13839
|
for (let i = 0; i < 12; i++) {
|
|
13166
|
-
if (
|
|
13167
|
-
const parent =
|
|
13840
|
+
if (existsSync13(path14.join(dir, ".git"))) return dir;
|
|
13841
|
+
const parent = path14.dirname(dir);
|
|
13168
13842
|
if (parent === dir) break;
|
|
13169
13843
|
dir = parent;
|
|
13170
13844
|
}
|