crosscheck-mcp 0.2.27 → 0.2.29

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.
@@ -15,20 +15,24 @@ var pricing_default = {
15
15
  notes: "Prices reflect public list pricing per provider. cached_per_1k applies to prompt tokens served from prompt-cache (Anthropic) or input-cache (OpenAI) when supported. Update when providers change rates. Missing models fall back to cost=0 with estimated=true. 2026-06-07: removed decommissioned gemini-2.0-flash / gemini-2.0-flash-lite (Google returns 404) and replaced the low-tier lite model with gemini-2.5-flash-lite. 2026-06-08: bumped the default Anthropic model to claude-opus-4-8. Kept gemini-2.5-pro as the Gemini default (gemini-3.1-pro-preview measured ~2.2x the cost at $2/$12 per M \u2014 output-token dominated by ~1.5k thinking tokens \u2014 so not worth it for a 1-of-4 panel voice). 2026-06-09: corrected rates verified against live provider pricing pages \u2014 gemini-2.5-pro output $5->$10/M; gemini-2.5-flash $0.15/$0.60 -> $0.30/$2.50; gpt-5 $5/$15 -> $1.25/$10 (cached $0.125); grok-4-latest input $5->$3/M (legacy grok-4 tier). Anthropic opus/sonnet/haiku rates confirmed unchanged. Older/unused models (gpt-4o, o1, o3, grok-3) not all re-verified. 2026-07-02: added claude-fable-5 as the optional reasoning-upgrade model (used only for the lead reasoning seat when the operator bumps a heavy task). 2026-07-04: set claude-fable-5 to its real pay-as-you-go API price effective 2026-07-07 \u2014 $10/M input ($0.01/1k), $50/M output ($0.05/1k), cached at 10% of input ($0.001/1k). Dropped the estimated flag. Note: Fable 5 is cheaper than opus-4-8 on both input and output. 2026-07-09: Google retired gemini-2.5-pro AND gemini-3-pro-preview (both now 404 'no longer available'). Moved the Gemini default to gemini-3.1-pro-preview ($2/M in, $12/M out, cache $0.20/M \u2014 the newest working pro; note it's a preview and may need another bump) and added gemini-3.5-flash ($1.50/M in, $9/M out, cache $0.15/M \u2014 GA) to the low/cheap tier. Removed the dead gemini-2.5-pro rate. IMPORTANT: gemini-3.x are heavy thinking models \u2014 thoughtsTokenCount (billed as output) is now folded into completion_tokens in the adapter, so cost reflects reasoning tokens. 2026-07-13: bumped the default OpenAI model to gpt-5.5 ($5/M in, $30/M out, cached $0.50/M \u2014 verified live). Note gpt-5.5 is markedly pricier than gpt-5 ($1.25/$10): ~4x input, 3x output. Kept gpt-5 in the table for cost lookups on pinned installs. 2026-07-16: added gpt-5.6 at the Sol (flagship) tier \u2014 $5/M in, $30/M out, cached $0.50/M (cache-write $6.25/M not modeled). gpt-5.6 is the second premium voice for the max-reasoning co-reasoner (runs alongside claude-fable-5 when an OpenAI key is set). Terra/Luna tiers not added (unused). 2026-07-17: added Moonshot AI (Kimi) provider with kimi-k3, base URL https://api.moonshot.ai/v1. Confirmed real rates \u2014 $3.00/M input (cache miss, $0.003/1k), $15.00/M output ($0.015/1k), $0.30/M cache-hit input ($0.0003/1k); 1M-token context. Kimi K3 is reasoning-class (temperature must be 1; uses max_completion_tokens). 2026-07-24: added claude-opus-5 ($5/M in, $25/M out, cached $0.50/M -- 10% of input, matching this table's existing cache-discount ratio) and made it the default anthropic model (was claude-opus-4-8). Fable 5 remains the reasoning-upgrade/super-mode target, unchanged. 2026-08-04: added Alibaba Cloud Qwen provider (OpenAI-compatible, DashScope international endpoint https://dashscope-intl.aliyuncs.com/compatible-mode/v1) with default model qwen3.8-max -- confirmed business pricing $2.00/M input ($0.002/1k), $6.00/M output ($0.006/1k), cache estimated at 10% of input ($0.0002/1k, not independently confirmed). Also added qwen3.7-max as a fallback tier at $1.25/M in ($0.00125/1k) / $3.75/M out ($0.00375/1k). Note: the model string initially configured (qwen-v3.8 / qwen-v3.7) does not exist on DashScope and 404s -- live-probed the real key against qwen3.8-max, qwen3.7-max, qwen3-max, qwen-max, and qwen-plus, all of which work; corrected the default and pricing keys to qwen3.8-max/qwen3.7-max and fixed QWEN_MODEL in both .env files to match. Qwen's Max tier is a standard instruct model, not reasoning-class -- normal temperature/max_tokens handling. 2026-09-06: added gpt-6-astra at published list rates ($10/$50 per Mtok, $1/Mtok cache read; the $12.50/Mtok cache-WRITE rate has no field in this schema and is not modelled). gemini-3.8-flash now carries Google's published paid-tier rates ($0.75/$3.75/$0.075 per Mtok). THESE DOUBLE ON 2027-01-01 ($1.50/$7.50/$0.15) and this schema has no effective-date support, so they must be updated by hand on that date or every Gemini cost will read half of actual. claude-fable-5-1 now carries published rates ($10/$50/$0.25 per Mtok). Note its cache-read rate is 4x cheaper than claude-fable-5's, so the two must not be assumed to track each other. No inferred rows remain. All three were previously absent, which silently recorded their cost as $0."
16
16
  },
17
17
  anthropic: {
18
+ _source: {
19
+ url: "https://platform.claude.com/docs/en/about-claude/pricing",
20
+ verified_at: "2026-09-27"
21
+ },
18
22
  "claude-opus-4-5": {
19
- prompt_per_1k: 0.015,
20
- completion_per_1k: 0.075,
21
- cached_per_1k: 15e-4
23
+ prompt_per_1k: 5e-3,
24
+ completion_per_1k: 0.025,
25
+ cached_per_1k: 5e-4
22
26
  },
23
27
  "claude-opus-4-7": {
24
- prompt_per_1k: 0.015,
25
- completion_per_1k: 0.075,
26
- cached_per_1k: 15e-4
28
+ prompt_per_1k: 5e-3,
29
+ completion_per_1k: 0.025,
30
+ cached_per_1k: 5e-4
27
31
  },
28
32
  "claude-opus-4-8": {
29
- prompt_per_1k: 0.015,
30
- completion_per_1k: 0.075,
31
- cached_per_1k: 15e-4
33
+ prompt_per_1k: 5e-3,
34
+ completion_per_1k: 0.025,
35
+ cached_per_1k: 5e-4
32
36
  },
33
37
  "claude-opus-5-5": {
34
38
  prompt_per_1k: 4e-3,
@@ -77,6 +81,10 @@ var pricing_default = {
77
81
  }
78
82
  },
79
83
  openai: {
84
+ _source: {
85
+ url: "https://developers.openai.com/api/docs/pricing",
86
+ verified_at: "2026-09-27"
87
+ },
80
88
  "gpt-6-astra": {
81
89
  prompt_per_1k: 0.01,
82
90
  completion_per_1k: 0.05,
@@ -161,6 +169,36 @@ var pricing_default = {
161
169
  prompt_per_1k: 11e-4,
162
170
  completion_per_1k: 44e-4,
163
171
  cached_per_1k: 55e-5
172
+ },
173
+ "gpt-5-pro": {
174
+ prompt_per_1k: 0.015,
175
+ completion_per_1k: 0.12,
176
+ cached_per_1k: 0
177
+ },
178
+ "gpt-5.2-pro": {
179
+ prompt_per_1k: 0.021,
180
+ completion_per_1k: 0.168,
181
+ cached_per_1k: 0
182
+ },
183
+ "gpt-5.4-pro": {
184
+ prompt_per_1k: 0.03,
185
+ completion_per_1k: 0.18,
186
+ cached_per_1k: 0
187
+ },
188
+ "gpt-5.5-pro": {
189
+ prompt_per_1k: 0.03,
190
+ completion_per_1k: 0.18,
191
+ cached_per_1k: 0
192
+ },
193
+ "o1-pro": {
194
+ prompt_per_1k: 0.15,
195
+ completion_per_1k: 0.6,
196
+ cached_per_1k: 0
197
+ },
198
+ "o3-pro": {
199
+ prompt_per_1k: 0.02,
200
+ completion_per_1k: 0.08,
201
+ cached_per_1k: 0
164
202
  }
165
203
  },
166
204
  xai: {
@@ -395,20 +433,30 @@ function modelPricing(pricing, provider, model) {
395
433
  const block = pricing[provider];
396
434
  if (!block || typeof block !== "object" || Array.isArray(block)) return null;
397
435
  const table = block;
436
+ if (model.startsWith("_")) return null;
398
437
  let entry = table[model];
399
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
400
- const best = Object.keys(table).filter((k) => model.startsWith(k)).sort((a, b) => b.length - a.length)[0];
401
- entry = best !== void 0 ? table[best] : void 0;
438
+ let match = "exact";
439
+ if (!isRates(entry)) {
440
+ const best = Object.keys(table).filter((k) => !k.startsWith("_")).filter((k) => model.startsWith(k) && DATE_STAMP.test(model.slice(k.length))).sort((a, b) => b.length - a.length)[0];
441
+ if (best !== void 0) {
442
+ entry = table[best];
443
+ match = "stamp";
444
+ }
402
445
  }
403
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) return null;
446
+ if (!isRates(entry)) return null;
404
447
  const e = entry;
405
448
  return {
406
449
  prompt_per_1k: numberOrZero(e["prompt_per_1k"]),
407
450
  completion_per_1k: numberOrZero(e["completion_per_1k"]),
408
451
  cached_per_1k: numberOrZero(e["cached_per_1k"]),
409
- ...e["estimated"] === true ? { estimated: true } : {}
452
+ ...e["estimated"] === true ? { estimated: true } : {},
453
+ match
410
454
  };
411
455
  }
456
+ var DATE_STAMP = /^-\d{8}$/;
457
+ function isRates(v) {
458
+ return Boolean(v) && typeof v === "object" && !Array.isArray(v);
459
+ }
412
460
  function numberOrZero(v) {
413
461
  if (typeof v === "number" && Number.isFinite(v)) return v;
414
462
  if (typeof v === "string") {
@@ -488,8 +536,20 @@ var PROVIDER_CAPS = {
488
536
  gemini: {
489
537
  family: "gemini",
490
538
  system_role: "separate",
491
- supports_temperature: true,
492
- reasoning_prefixes: ["gemini-3.1-pro", "gemini-3.5-flash", "gemini-2.5-pro"]
539
+ // No sampling, on any Gemini model. Ignored since 3.6 Flash and rejected
540
+ // by upcoming models (Google, 2026-10). "model" means "omit for reasoning
541
+ // models", and every Gemini model is reasoning-class (below) — so this
542
+ // resolves to never, through the same rule the other providers use. The
543
+ // adapter also never sends it; this keeps the record honest for anything
544
+ // else that reads it.
545
+ supports_temperature: "model",
546
+ // EVERY Gemini model is reasoning-class. The old list named three
547
+ // families and missed gemini-3.8-flash — the DEFAULT — so the default
548
+ // model got the small non-reasoning budgets (768 tokens for an audit)
549
+ // while spending up to ~480 tokens of each call thinking. Google's docs
550
+ // say thinking cannot be switched off on current models, so a list of
551
+ // which ones think is a list that can only ever be incomplete.
552
+ reasoning_prefixes: ["gemini-"]
493
553
  },
494
554
  // Qwen's "Max" tier (qwen-v3.8, qwen-v3.7) is a standard instruct/chat
495
555
  // model, not a visible-reasoning model like QwQ — normal temperature and
@@ -612,6 +672,73 @@ function httpFailureToProviderError(provider, status, bodyText, retryAfterS, mod
612
672
  );
613
673
  }
614
674
 
675
+ // src/providers/network-errors.ts
676
+ function codeOf(e) {
677
+ if (!e || typeof e !== "object") return null;
678
+ const c = e.code;
679
+ return typeof c === "string" && c.length > 0 ? c : null;
680
+ }
681
+ function rootCause(e, depth = 0) {
682
+ if (depth > 5 || !e || typeof e !== "object") return e;
683
+ const o = e;
684
+ if (Array.isArray(o.errors) && o.errors.length > 0) {
685
+ const coded = o.errors.find((x) => codeOf(x) !== null);
686
+ return rootCause(coded ?? o.errors[0], depth + 1);
687
+ }
688
+ if (o.cause !== void 0 && o.cause !== null) return rootCause(o.cause, depth + 1);
689
+ return e;
690
+ }
691
+ var BY_CODE = {
692
+ ENOTFOUND: { cause: "dns", detail: "the provider's hostname did not resolve \u2014 DNS, or a typo'd base URL", transient: false, kind: "network" },
693
+ EAI_AGAIN: { cause: "dns", detail: "DNS lookup failed temporarily \u2014 resolver or network is flapping", transient: true, kind: "network" },
694
+ ECONNREFUSED: { cause: "refused", detail: "the connection was refused \u2014 nothing is listening, or a proxy blocked it", transient: false, kind: "network" },
695
+ ECONNRESET: { cause: "reset", detail: "the peer closed the connection mid-request \u2014 flaky path, or an upstream error behind a proxy", transient: true, kind: "network" },
696
+ EPIPE: { cause: "reset", detail: "the connection closed while we were still writing", transient: true, kind: "network" },
697
+ ETIMEDOUT: { cause: "connect_timeout", detail: "the connection timed out before it was established", transient: true, kind: "timeout" },
698
+ EHOSTUNREACH: { cause: "unreachable", detail: "no route to the provider \u2014 VPN, or offline", transient: true, kind: "network" },
699
+ ENETUNREACH: { cause: "unreachable", detail: "the network is unreachable \u2014 offline, or a routing change", transient: true, kind: "network" },
700
+ ENETDOWN: { cause: "unreachable", detail: "the local network is down", transient: true, kind: "network" },
701
+ EPROTO: { cause: "tls", detail: "the TLS handshake failed \u2014 an intercepting proxy, or a protocol mismatch", transient: false, kind: "network" },
702
+ ERR_TLS_CERT_ALTNAME_INVALID: { cause: "tls", detail: "the certificate did not match the hostname \u2014 traffic is being intercepted", transient: false, kind: "network" },
703
+ CERT_HAS_EXPIRED: { cause: "tls", detail: "the server certificate has expired, or this machine's clock is wrong", transient: false, kind: "network" },
704
+ UNABLE_TO_VERIFY_LEAF_SIGNATURE: { cause: "tls", detail: "the certificate chain could not be verified \u2014 a corporate proxy's root is probably not trusted here", transient: false, kind: "network" },
705
+ UND_ERR_CONNECT_TIMEOUT: { cause: "connect_timeout", detail: "could not open a connection within the timeout \u2014 firewall, or the host is down", transient: true, kind: "timeout" },
706
+ UND_ERR_HEADERS_TIMEOUT: { cause: "read_timeout", detail: "connected, but the provider sent no response headers in time \u2014 it is overloaded or thinking too long", transient: true, kind: "timeout" },
707
+ UND_ERR_BODY_TIMEOUT: { cause: "read_timeout", detail: "the response body stalled mid-stream", transient: true, kind: "timeout" },
708
+ UND_ERR_SOCKET: { cause: "reset", detail: "the socket closed unexpectedly", transient: true, kind: "network" },
709
+ UND_ERR_ABORTED: { cause: "aborted", detail: "the request was cancelled", transient: false, kind: "network" },
710
+ ABORT_ERR: { cause: "aborted", detail: "the request was cancelled", transient: false, kind: "network" }
711
+ };
712
+ function diagnoseNetworkError(e) {
713
+ const root = rootCause(e);
714
+ const code = codeOf(root) ?? codeOf(e);
715
+ if (code && BY_CODE[code]) return { ...BY_CODE[code], code };
716
+ const name = root && typeof root === "object" ? root.name : void 0;
717
+ if (name === "AbortError" || name === "TimeoutError") {
718
+ return {
719
+ cause: name === "TimeoutError" ? "read_timeout" : "aborted",
720
+ code: code ?? String(name),
721
+ detail: name === "TimeoutError" ? "the request exceeded its timeout" : "the request was cancelled",
722
+ transient: name === "TimeoutError",
723
+ kind: name === "TimeoutError" ? "timeout" : "network"
724
+ };
725
+ }
726
+ return {
727
+ cause: "unknown",
728
+ code,
729
+ detail: code ? `the connection failed with ${code}` : "the connection failed and the underlying error carried no code",
730
+ transient: true,
731
+ kind: "network"
732
+ };
733
+ }
734
+ function looksLikeNetworkFailure(d) {
735
+ return d.code !== null || d.cause !== "unknown";
736
+ }
737
+ function networkErrorMessage(provider, d) {
738
+ const code = d.code ? ` [${d.code}]` : "";
739
+ return `${provider}: ${d.detail}${code}`;
740
+ }
741
+
615
742
  // src/providers/retry.ts
616
743
  var override = null;
617
744
  function resolveRetryConfig() {
@@ -764,6 +891,52 @@ function rejectsDisabledThinking(model) {
764
891
  const m = model.toLowerCase();
765
892
  return ADAPTIVE_THINKING_PREFIXES.some((p) => m.startsWith(p));
766
893
  }
894
+ var NO_FORCED_TOOL_PREFIXES = [
895
+ "claude-fable-5-1",
896
+ "claude-mythos-5-1",
897
+ "claude-opus-5-5",
898
+ "claude-sonnet-5-5"
899
+ ];
900
+ function rejectsForcedToolUse(model) {
901
+ const m = model.toLowerCase();
902
+ return NO_FORCED_TOOL_PREFIXES.some((p) => m.startsWith(p));
903
+ }
904
+ var UNSUPPORTED_SCHEMA_KEYS = /* @__PURE__ */ new Set([
905
+ "minimum",
906
+ "maximum",
907
+ "exclusiveMinimum",
908
+ "exclusiveMaximum",
909
+ "multipleOf",
910
+ "minLength",
911
+ "maxLength",
912
+ "pattern",
913
+ "minItems",
914
+ "maxItems",
915
+ "uniqueItems",
916
+ "minProperties",
917
+ "maxProperties",
918
+ "$schema"
919
+ ]);
920
+ function toStructuredOutputSchema(schema) {
921
+ const walk = (node) => {
922
+ if (Array.isArray(node)) return node.map(walk);
923
+ if (!node || typeof node !== "object") return node;
924
+ const out = {};
925
+ for (const [k, v] of Object.entries(node)) {
926
+ if (UNSUPPORTED_SCHEMA_KEYS.has(k)) continue;
927
+ if (k === "properties" && v && typeof v === "object") {
928
+ out[k] = Object.fromEntries(Object.entries(v).map(([pk, pv]) => [pk, walk(pv)]));
929
+ } else if (["items", "anyOf", "allOf", "oneOf", "not", "$defs", "definitions"].includes(k)) {
930
+ out[k] = k === "$defs" || k === "definitions" ? Object.fromEntries(Object.entries(v ?? {}).map(([dk, dv]) => [dk, walk(dv)])) : walk(v);
931
+ } else {
932
+ out[k] = v;
933
+ }
934
+ }
935
+ if (out["type"] === "object" || "properties" in out) out["additionalProperties"] = false;
936
+ return out;
937
+ };
938
+ return walk(schema);
939
+ }
767
940
  var ANTHROPIC_STRUCTURED_TOOL_NAME = "structured_output";
768
941
  function applyPromptCaching(body, env = process.env) {
769
942
  const flag = (env["CROSSCHECK_ANTHROPIC_PROMPT_CACHE"] ?? "").trim();
@@ -815,7 +988,12 @@ function buildAnthropicRequest(opts) {
815
988
  body.thinking = { type: "disabled" };
816
989
  }
817
990
  }
818
- if (opts.jsonSchema) {
991
+ if (opts.jsonSchema && rejectsForcedToolUse(opts.model)) {
992
+ body.output_config = {
993
+ ...body.output_config ?? {},
994
+ format: { type: "json_schema", schema: toStructuredOutputSchema(opts.jsonSchema) }
995
+ };
996
+ } else if (opts.jsonSchema) {
819
997
  body.tools = [{
820
998
  name: ANTHROPIC_STRUCTURED_TOOL_NAME,
821
999
  description: "Emit a single JSON object matching the requested schema.",
@@ -934,7 +1112,11 @@ async function sendAnthropic(args) {
934
1112
  try {
935
1113
  respLike = await doFetch(url, init);
936
1114
  } catch (e) {
937
- throw new ProviderError("network", `anthropic: fetch failed: ${e.message}`);
1115
+ const d = diagnoseNetworkError(e);
1116
+ if (!looksLikeNetworkFailure(d)) throw e;
1117
+ throw new ProviderError(d.kind, networkErrorMessage("anthropic", d), {
1118
+ transient: d.transient
1119
+ });
938
1120
  }
939
1121
  const status = respLike.status;
940
1122
  if (status >= 200 && status < 300) {
@@ -1007,6 +1189,11 @@ function jsonSchemaToGeminiSchema(schema) {
1007
1189
  }
1008
1190
  return out;
1009
1191
  }
1192
+ var GEMINI_THINKING_HEADROOM_TOKENS = 16384;
1193
+ function thinkingHeadroom() {
1194
+ const raw = Number(process.env["CROSSCHECK_GEMINI_THINKING_HEADROOM_TOKENS"]);
1195
+ return Number.isFinite(raw) && raw >= 0 ? Math.trunc(raw) : GEMINI_THINKING_HEADROOM_TOKENS;
1196
+ }
1010
1197
  function buildGeminiRequest(opts) {
1011
1198
  const contents = [];
1012
1199
  let systemText = null;
@@ -1025,8 +1212,9 @@ function buildGeminiRequest(opts) {
1025
1212
  const body = {
1026
1213
  contents,
1027
1214
  generationConfig: {
1028
- maxOutputTokens: opts.maxTokens,
1029
- temperature: opts.temperature
1215
+ // Answer budget + thinking headroom. No temperature / topP / topK /
1216
+ // thinkingBudget, on any model — see the header note.
1217
+ maxOutputTokens: opts.maxTokens + thinkingHeadroom()
1030
1218
  }
1031
1219
  };
1032
1220
  if (systemText !== null && systemText !== "") {
@@ -1036,8 +1224,8 @@ function buildGeminiRequest(opts) {
1036
1224
  body.generationConfig.responseMimeType = "application/json";
1037
1225
  body.generationConfig.responseSchema = jsonSchemaToGeminiSchema(opts.jsonSchema);
1038
1226
  }
1039
- const url = `${GEMINI_API_URL_BASE}/${encodeURIComponent(opts.model)}:generateContent?key=${encodeURIComponent(opts.apiKey)}`;
1040
- return { url, headers: {}, body };
1227
+ const url = `${GEMINI_API_URL_BASE}/${encodeURIComponent(opts.model)}:generateContent`;
1228
+ return { url, headers: { "x-goog-api-key": opts.apiKey }, body };
1041
1229
  }
1042
1230
  function parseGeminiResponse(opts) {
1043
1231
  const r = opts.resp ?? {};
@@ -1067,16 +1255,19 @@ function parseGeminiResponse(opts) {
1067
1255
  }
1068
1256
  try {
1069
1257
  const first = cands[0];
1070
- const content = first["content"];
1071
- const parts = content["parts"];
1258
+ const finishReason = typeof first["finishReason"] === "string" ? first["finishReason"] : void 0;
1259
+ const content = first["content"] ?? {};
1260
+ const parts = Array.isArray(content["parts"]) ? content["parts"] : [];
1072
1261
  let text = "";
1073
1262
  for (const p of parts) {
1074
1263
  if (p && typeof p === "object") {
1075
- const t = p["text"];
1264
+ const part = p;
1265
+ if (part["thought"] === true) continue;
1266
+ const t = part["text"];
1076
1267
  if (typeof t === "string") text += t;
1077
1268
  }
1078
1269
  }
1079
- return { text, usage };
1270
+ return { text, usage, ...finishReason ? { finishReason } : {} };
1080
1271
  } catch (e) {
1081
1272
  throw new ProviderError(
1082
1273
  "parse",
@@ -1105,7 +1296,8 @@ async function sendGemini(args) {
1105
1296
  apiKey: args.apiKey,
1106
1297
  messages: args.messages,
1107
1298
  maxTokens: args.maxTokens,
1108
- temperature: args.temperature,
1299
+ // args.temperature is deliberately NOT forwarded: Gemini ignores it and
1300
+ // upcoming models reject it. See the header note.
1109
1301
  ...args.jsonSchema ? { jsonSchema: args.jsonSchema } : {}
1110
1302
  });
1111
1303
  const doFetch = args.fetchImpl ?? globalThis.fetch;
@@ -1120,7 +1312,11 @@ async function sendGemini(args) {
1120
1312
  try {
1121
1313
  respLike = await doFetch(url, init);
1122
1314
  } catch (e) {
1123
- throw new ProviderError("network", `gemini: fetch failed: ${e.message}`);
1315
+ const d = diagnoseNetworkError(e);
1316
+ if (!looksLikeNetworkFailure(d)) throw e;
1317
+ throw new ProviderError(d.kind, networkErrorMessage("gemini", d), {
1318
+ transient: d.transient
1319
+ });
1124
1320
  }
1125
1321
  const status = respLike.status;
1126
1322
  if (status >= 200 && status < 300) {
@@ -1130,11 +1326,18 @@ async function sendGemini(args) {
1130
1326
  } catch (e) {
1131
1327
  throw new ProviderError("parse", `gemini: response body not JSON: ${e.message}`);
1132
1328
  }
1133
- const { text, usage } = parseGeminiResponse({
1329
+ const { text, usage, finishReason } = parseGeminiResponse({
1134
1330
  resp: parsed,
1135
1331
  model: args.model,
1136
1332
  purpose: args.purpose ?? "worker"
1137
1333
  });
1334
+ if (text.trim() === "" && finishReason === "MAX_TOKENS") {
1335
+ throw new ProviderError(
1336
+ "parse",
1337
+ `gemini: the answer was cut off before any visible text \u2014 thinking used the whole output budget (${usage.completion_tokens} tokens). Raise CROSSCHECK_GEMINI_THINKING_HEADROOM_TOKENS or the purpose's budget.`,
1338
+ { transient: false }
1339
+ );
1340
+ }
1138
1341
  return { text, attempts: 1, usage: applyPricing2(usage, args.pricing) };
1139
1342
  }
1140
1343
  const bodyText = await respLike.text().catch(() => "");
@@ -1191,7 +1394,15 @@ function buildOpenAICompatibleRequest(opts) {
1191
1394
  json_schema: {
1192
1395
  name: "structured_output",
1193
1396
  schema: opts.jsonSchema,
1194
- strict: true
1397
+ // NOT strict. Strict mode requires `additionalProperties: false` on
1398
+ // every object and every property listed in `required` — the
1399
+ // engine's schemas don't meet that, so every structured OpenAI call
1400
+ // (planner, judges, synthesis) was a 400, unnoticed until the canary
1401
+ // grew a structured check (2026-10-11). Non-strict still shapes the
1402
+ // reply to the schema, and structured.ts validates it against the
1403
+ // full schema regardless, so strictness added a failure mode and no
1404
+ // safety.
1405
+ strict: false
1195
1406
  }
1196
1407
  };
1197
1408
  }
@@ -1301,7 +1512,11 @@ async function sendOpenAICompatible(args) {
1301
1512
  try {
1302
1513
  respLike = await doFetch(url, init);
1303
1514
  } catch (e) {
1304
- throw new ProviderError("network", `${args.provider}: fetch failed: ${e.message}`);
1515
+ const d = diagnoseNetworkError(e);
1516
+ if (!looksLikeNetworkFailure(d)) throw e;
1517
+ throw new ProviderError(d.kind, networkErrorMessage(args.provider, d), {
1518
+ transient: d.transient
1519
+ });
1305
1520
  }
1306
1521
  const status = respLike.status;
1307
1522
  if (status >= 200 && status < 300) {
@@ -1475,7 +1690,7 @@ import { z } from "zod";
1475
1690
 
1476
1691
  // src/server-meta.ts
1477
1692
  var SERVER_NAME = "crosscheck-agent";
1478
- var SERVER_VERSION = true ? "0.2.27" : "0.0.0-dev";
1693
+ var SERVER_VERSION = true ? "0.2.29" : "0.0.0-dev";
1479
1694
 
1480
1695
  // src/tools/audit.ts
1481
1696
  import { readdirSync, readFileSync as readFileSync3, statSync } from "fs";
@@ -1698,6 +1913,31 @@ function pyReprType(t) {
1698
1913
  return String(t);
1699
1914
  }
1700
1915
 
1916
+ // src/core/session.ts
1917
+ import { randomUUID } from "crypto";
1918
+ var processSessionId = null;
1919
+ var startedAt = null;
1920
+ function engineSessionId() {
1921
+ if (processSessionId === null) {
1922
+ processSessionId = randomUUID();
1923
+ startedAt = Date.now();
1924
+ }
1925
+ return processSessionId;
1926
+ }
1927
+ function resolveSessionId(callerSupplied) {
1928
+ const trimmed = typeof callerSupplied === "string" ? callerSupplied.trim() : "";
1929
+ return trimmed.length > 0 ? trimmed : engineSessionId();
1930
+ }
1931
+ function localSessionRecordingEnabled(env = globalThis.process?.env ?? {}) {
1932
+ const v = (env["CROSSCHECK_RECORD_SESSIONS"] ?? "").trim().toLowerCase();
1933
+ return !["0", "false", "off", "no"].includes(v);
1934
+ }
1935
+ function recordingSessionId(callerSupplied) {
1936
+ if (localSessionRecordingEnabled()) return resolveSessionId(callerSupplied);
1937
+ const trimmed = typeof callerSupplied === "string" ? callerSupplied.trim() : "";
1938
+ return trimmed.length > 0 ? trimmed : null;
1939
+ }
1940
+
1701
1941
  // src/core/usage.ts
1702
1942
  function emptyUsage(provider, model, purpose = "worker") {
1703
1943
  return {
@@ -1803,11 +2043,11 @@ function withCommas(n) {
1803
2043
  function renderRunSummary(opts) {
1804
2044
  const endedAt = opts.endedAtS ?? Math.trunc(Date.now() / 1e3);
1805
2045
  let scope = "call";
1806
- let startedAt = null;
2046
+ let startedAt2 = null;
1807
2047
  let rows = [];
1808
2048
  if (opts.sessionScope && opts.sessionScope.rows.length > 0) {
1809
2049
  scope = "session";
1810
- startedAt = opts.sessionScope.startedAt;
2050
+ startedAt2 = opts.sessionScope.startedAt;
1811
2051
  rows = opts.sessionScope.rows.map((r) => ({ ...r }));
1812
2052
  } else {
1813
2053
  const byPurpose = /* @__PURE__ */ new Map();
@@ -1838,6 +2078,7 @@ function renderRunSummary(opts) {
1838
2078
  row.cpu_ms += a.cpu_ms ?? 0;
1839
2079
  if (a.cache_hit) row.cache_hits += 1;
1840
2080
  if (a.error) row.errors += 1;
2081
+ if (a.usage?.estimated) row.estimated = true;
1841
2082
  }
1842
2083
  rows = Array.from(byPurpose.values());
1843
2084
  }
@@ -1850,16 +2091,18 @@ function renderRunSummary(opts) {
1850
2091
  wall_ms: rows.reduce((s, r) => s + r.wall_ms, 0),
1851
2092
  cpu_ms: rows.reduce((s, r) => s + r.cpu_ms, 0),
1852
2093
  cache_hits: rows.reduce((s, r) => s + r.cache_hits, 0),
1853
- errors: rows.reduce((s, r) => s + r.errors, 0)
2094
+ errors: rows.reduce((s, r) => s + r.errors, 0),
2095
+ ...rows.some((r) => r.estimated) ? { estimated: true } : {}
1854
2096
  };
1855
2097
  const titleLeft = scope === "session" ? `session: ${opts.sessionId ?? ""}` : `call: ${opts.toolName}`;
1856
- const header = `${titleLeft} (${totals.calls} calls, ${withCommas(totals.total_tokens)} tokens, $${totals.cost_usd.toFixed(4)}, ${(totals.wall_ms / 1e3).toFixed(1)}s wall, ${(totals.cpu_ms / 1e3).toFixed(3)}s cpu)`;
2098
+ const costText = totals.estimated ? `$${totals.cost_usd.toFixed(4)}+ (some calls unpriced)` : `$${totals.cost_usd.toFixed(4)}`;
2099
+ const header = `${titleLeft} (${totals.calls} calls, ${withCommas(totals.total_tokens)} tokens, ${costText}, ${(totals.wall_ms / 1e3).toFixed(1)}s wall, ${(totals.cpu_ms / 1e3).toFixed(3)}s cpu)`;
1857
2100
  const bodyLines = [];
1858
2101
  for (let i = 0; i < rows.length; i++) {
1859
2102
  const r = rows[i];
1860
2103
  const glyph = i === rows.length - 1 ? "`-" : "|-";
1861
2104
  bodyLines.push(
1862
- `${glyph} purpose: ${r.purpose} (calls=${r.calls} tokens=${withCommas(r.total_tokens)} cost=$${r.cost_usd.toFixed(4)} wall=${(r.wall_ms / 1e3).toFixed(1)}s cpu=${(r.cpu_ms / 1e3).toFixed(3)}s)`
2105
+ `${glyph} purpose: ${r.purpose} (calls=${r.calls} tokens=${withCommas(r.total_tokens)} cost=$${r.cost_usd.toFixed(4)}${r.estimated ? "+" : ""} wall=${(r.wall_ms / 1e3).toFixed(1)}s cpu=${(r.cpu_ms / 1e3).toFixed(3)}s)`
1863
2106
  );
1864
2107
  }
1865
2108
  const callByModel = rollupSpendByModel(
@@ -1907,7 +2150,7 @@ function renderRunSummary(opts) {
1907
2150
  tool: opts.toolName,
1908
2151
  scope,
1909
2152
  currency: "USD",
1910
- started_at: startedAt,
2153
+ started_at: startedAt2,
1911
2154
  ended_at: endedAt,
1912
2155
  rows,
1913
2156
  totals,
@@ -2032,14 +2275,15 @@ async function attachUsageBlock(result, answers, opts) {
2032
2275
  delete result["_suppress_run_summary"];
2033
2276
  }
2034
2277
  async function appendUsageLog(storage, sessionId, toolName, answers, nowS) {
2035
- if (!sessionId || answers.length === 0) return;
2278
+ const sid = recordingSessionId(sessionId);
2279
+ if (!sid || answers.length === 0) return;
2036
2280
  const now = nowS ?? Math.trunc(Date.now() / 1e3);
2037
2281
  const rows = [];
2038
2282
  for (const a of answers) {
2039
2283
  const u = a.usage;
2040
2284
  if (!u || typeof u.provider !== "string" || u.provider === "") continue;
2041
2285
  rows.push({
2042
- session_id: sessionId,
2286
+ session_id: sid,
2043
2287
  ts: now,
2044
2288
  tool: toolName,
2045
2289
  purpose: u.purpose ?? "worker",
@@ -2062,13 +2306,14 @@ async function appendUsageLog(storage, sessionId, toolName, answers, nowS) {
2062
2306
  }
2063
2307
  }
2064
2308
  async function recordSessionCall(storage, sessionId, answers, wallMs, cpuMs, nowS) {
2065
- if (!sessionId) return;
2309
+ const sid = recordingSessionId(sessionId);
2310
+ if (!sid) return;
2066
2311
  const now = nowS ?? Math.trunc(Date.now() / 1e3);
2067
2312
  try {
2068
- let existing = await storage.getSession(sessionId);
2313
+ let existing = await storage.getSession(sid);
2069
2314
  if (!existing) {
2070
2315
  await storage.upsertSession({
2071
- session_id: sessionId,
2316
+ session_id: sid,
2072
2317
  started_at: now,
2073
2318
  last_at: now,
2074
2319
  calls: 0,
@@ -2081,7 +2326,7 @@ async function recordSessionCall(storage, sessionId, answers, wallMs, cpuMs, now
2081
2326
  total_cost_usd: 0,
2082
2327
  total_cpu_ms: 0
2083
2328
  });
2084
- existing = await storage.getSession(sessionId);
2329
+ existing = await storage.getSession(sid);
2085
2330
  }
2086
2331
  if (!existing) return;
2087
2332
  let promptDelta = 0;
@@ -2101,7 +2346,7 @@ async function recordSessionCall(storage, sessionId, answers, wallMs, cpuMs, now
2101
2346
  }
2102
2347
  if (a.cache_hit) cacheHitDelta += 1;
2103
2348
  }
2104
- await storage.accumulateSessionTotals(sessionId, {
2349
+ await storage.accumulateSessionTotals(sid, {
2105
2350
  calls: answers.length,
2106
2351
  wall_ms: wallMs,
2107
2352
  cache_hits: cacheHitDelta,
@@ -5033,10 +5278,6 @@ If you need supporting evidence, you may emit ONE \`<tool_call>...</tool_call>\`
5033
5278
  }
5034
5279
 
5035
5280
  // src/tools/confer.ts
5036
- var DEFERRED_OPTS = [
5037
- // empty — every confer opt runs natively except worker_tools, which
5038
- // is gated below (non-empty list → defer).
5039
- ];
5040
5281
  async function runConfer(args, opts) {
5041
5282
  const callStartedWall = performance3.now();
5042
5283
  const callStartedCpu = process.cpuUsage();
@@ -5052,16 +5293,11 @@ async function runConfer(args, opts) {
5052
5293
  rejectedWorkerTools.push(t);
5053
5294
  }
5054
5295
  }
5055
- const workerToolsNeedsBridge = acceptedWorkerTools.length > 0 && !opts.innerCallers;
5056
- const needsBridge = DEFERRED_OPTS.some((k) => Boolean(args[k])) || workerToolsNeedsBridge;
5057
- if (needsBridge) {
5058
- if (opts.bridge && opts.bridge.toolNames.has("confer")) {
5059
- return await deferConfer(args, opts.bridge);
5060
- }
5296
+ if (acceptedWorkerTools.length > 0 && !opts.innerCallers) {
5061
5297
  return errorEnvelope2(
5062
5298
  "CONFER_OPT_NOT_NATIVE",
5063
5299
  "worker_tools requested but innerCallers are not wired",
5064
- "Native confer covers untrusted_input + early_stop + extract_claims + inject_session_memory + auto_panel. worker_tools needs innerCallers wired by the host (or CROSSCHECK_BRIDGE_PYTHON=1 as a backstop)."
5300
+ "Native confer covers untrusted_input + early_stop + extract_claims + inject_session_memory + auto_panel. worker_tools needs innerCallers wired by the host."
5065
5301
  );
5066
5302
  }
5067
5303
  const question = typeof args["question"] === "string" ? args["question"] : String(args["question"] ?? "");
@@ -5412,21 +5648,6 @@ function unknownProviderError(unknownNames, available) {
5412
5648
  available_now: Object.keys(available).sort()
5413
5649
  };
5414
5650
  }
5415
- async function deferConfer(args, bridge) {
5416
- const r = await bridge.callTool("confer", args);
5417
- const text = r.content[0]?.text;
5418
- if (typeof text === "string") {
5419
- try {
5420
- return JSON.parse(text);
5421
- } catch {
5422
- }
5423
- }
5424
- return errorEnvelope2(
5425
- "CONFER_BRIDGE_BAD_ENVELOPE",
5426
- "bridge returned an unparseable envelope for confer",
5427
- "Check that the Python child is healthy."
5428
- );
5429
- }
5430
5651
  function errorEnvelope2(code, message, hint) {
5431
5652
  return {
5432
5653
  tool: "confer",
@@ -5798,7 +6019,7 @@ function errorEnvelope3(code, message, hint) {
5798
6019
  transient: false
5799
6020
  };
5800
6021
  }
5801
- async function runVerify(args, bridge, fetchConfig) {
6022
+ async function runVerify(args, fetchConfig) {
5802
6023
  const checks = args["checks"];
5803
6024
  if (!Array.isArray(checks) || checks.length === 0) {
5804
6025
  return {
@@ -5810,7 +6031,6 @@ async function runVerify(args, bridge, fetchConfig) {
5810
6031
  )
5811
6032
  };
5812
6033
  }
5813
- void bridge;
5814
6034
  const allowShell = Boolean(args["allow_shell"] ?? false);
5815
6035
  const wallStart = performance4.now();
5816
6036
  const cpuStart = process.cpuUsage();
@@ -6112,7 +6332,6 @@ async function runBench(args, opts) {
6112
6332
  const innerOpts = {
6113
6333
  providers: opts.providers,
6114
6334
  allowlist: opts.allowlist ?? null,
6115
- ...opts.bridge ? { bridge: opts.bridge } : {},
6116
6335
  ...opts.moderator ? { moderator: opts.moderator } : {}
6117
6336
  };
6118
6337
  let inner;
@@ -6806,7 +7025,6 @@ var EST_TOKENS = {
6806
7025
  med: [1500, 800],
6807
7026
  high: [2500, 1500]
6808
7027
  };
6809
- var DEFERRED_OPTS2 = [];
6810
7028
  var REACTIVE_SIGNAL_RE = /<signals>\s*(\{[\s\S]*?\})\s*<\/signals>/g;
6811
7029
  function parseReactiveSignals(output) {
6812
7030
  if (typeof output !== "string" || !output.includes("<signals>")) return [];
@@ -6846,17 +7064,6 @@ async function runOrchestrate(args, opts) {
6846
7064
  );
6847
7065
  if (breakerEnv) return breakerEnv;
6848
7066
  }
6849
- const needsBridge = DEFERRED_OPTS2.some((k) => Boolean(args[k]));
6850
- if (needsBridge) {
6851
- if (opts.bridge && opts.bridge.toolNames.has("orchestrate")) {
6852
- return await deferOrchestrate(args, opts.bridge);
6853
- }
6854
- return errorEnvelope5(
6855
- "ORCHESTRATE_OPT_NOT_NATIVE",
6856
- "this orchestrate opt-in requires the Python bridge in v1",
6857
- "v1 native covers the plain DAG flow + cheap_mode. reactive (mid-flight DAG updates) still defers. Set CROSSCHECK_BRIDGE_PYTHON=1 to enable."
6858
- );
6859
- }
6860
7067
  const goal = typeof args["goal"] === "string" ? args["goal"] : null;
6861
7068
  const dagIn = isObj5(args["dag"]) ? args["dag"] : null;
6862
7069
  if (goal === null === (dagIn === null)) {
@@ -7798,21 +8005,6 @@ function resolveProviders3(names, available, allowlist) {
7798
8005
  }
7799
8006
  return out;
7800
8007
  }
7801
- async function deferOrchestrate(args, bridge) {
7802
- const r = await bridge.callTool("orchestrate", args);
7803
- const text = r.content[0]?.text;
7804
- if (typeof text === "string") {
7805
- try {
7806
- return JSON.parse(text);
7807
- } catch {
7808
- }
7809
- }
7810
- return errorEnvelope5(
7811
- "ORCHESTRATE_BRIDGE_BAD_ENVELOPE",
7812
- "bridge returned an unparseable envelope for orchestrate",
7813
- "Check that the Python child is healthy."
7814
- );
7815
- }
7816
8008
  function errorEnvelope5(code, message, hint, kind = "client") {
7817
8009
  return {
7818
8010
  tool: "orchestrate",
@@ -8130,6 +8322,16 @@ function buildCallContext(args) {
8130
8322
  var CREATE_DOC_MAX_BYTES = 32 * 1024;
8131
8323
  var CREATE_AUDIT_THRESHOLD = 0.7;
8132
8324
  async function runCreate(args, opts) {
8325
+ const subEnvelopes = [];
8326
+ const tracked = (fn) => async (...a) => {
8327
+ const out = await fn(...a);
8328
+ subEnvelopes.push(out);
8329
+ return out;
8330
+ };
8331
+ const confer = tracked(runConfer);
8332
+ const orchestrate = tracked(runOrchestrate);
8333
+ const review = tracked(runReview);
8334
+ const audit = tracked(runAudit);
8133
8335
  const instruction = typeof args["instruction"] === "string" ? args["instruction"] : String(args["instruction"] ?? "");
8134
8336
  if (!instruction) {
8135
8337
  return errorEnvelope6(
@@ -8166,7 +8368,7 @@ ${constraints}
8166
8368
 
8167
8369
  ` : "") + `DOCUMENTS (${okCount} of ${descriptors.length} ingested):
8168
8370
  ${documentsPayload}`;
8169
- const scope = await runConfer({
8371
+ const scope = await confer({
8170
8372
  question: "We are about to orchestrate a full plan->build->review->audit lifecycle for this instruction. List 3-6 concrete sub-tasks (each with a one-line description and a difficulty: low|med|high), any critical assumptions, and any missing-information risks. Be concise and decisive \u2014 your output will be used to build the orchestration DAG.\n\nINSTRUCTION:\n" + instruction,
8171
8373
  context: contextBlock,
8172
8374
  ...providers !== void 0 ? { providers } : {},
@@ -8175,7 +8377,6 @@ ${documentsPayload}`;
8175
8377
  }, {
8176
8378
  providers: opts.providers,
8177
8379
  allowlist: opts.allowlist ?? null,
8178
- ...opts.bridge ? { bridge: opts.bridge } : {},
8179
8380
  ctx
8180
8381
  });
8181
8382
  const scopeAnswer = Array.isArray(scope["answers"]) ? scope["answers"].filter((a) => typeof a["response"] === "string" && a["response"]).map((a) => `[${a["provider"]}]
@@ -8194,11 +8395,10 @@ ${scopeAnswer || "(none)"}
8194
8395
  };
8195
8396
  if (providers !== void 0) orchestrateArgs["providers"] = providers;
8196
8397
  if (planOnly) orchestrateArgs["plan_only"] = true;
8197
- let orchestration = await runOrchestrate(orchestrateArgs, {
8398
+ let orchestration = await orchestrate(orchestrateArgs, {
8198
8399
  providers: opts.providers,
8199
8400
  allowlist: opts.allowlist ?? null,
8200
8401
  moderator,
8201
- ...opts.bridge ? { bridge: opts.bridge } : {},
8202
8402
  ctx
8203
8403
  });
8204
8404
  let attempts = 1;
@@ -8224,7 +8424,7 @@ ${scopeAnswer || "(none)"}
8224
8424
  }
8225
8425
  let reviewEnvelope = null;
8226
8426
  if (!skipReview && typeof orchestration["final"] === "string" && orchestration["final"]) {
8227
- reviewEnvelope = await runReview({
8427
+ reviewEnvelope = await review({
8228
8428
  snippet: orchestration["final"],
8229
8429
  intent: `final deliverable for: ${instruction}`,
8230
8430
  ...providers !== void 0 ? { providers } : {},
@@ -8233,7 +8433,6 @@ ${scopeAnswer || "(none)"}
8233
8433
  }, {
8234
8434
  providers: opts.providers,
8235
8435
  allowlist: opts.allowlist ?? null,
8236
- ...opts.bridge ? { bridge: opts.bridge } : {},
8237
8436
  ctx
8238
8437
  });
8239
8438
  }
@@ -8245,7 +8444,7 @@ ${scopeAnswer || "(none)"}
8245
8444
  const panelExhausted = producingPanel.length > 0 && producingPanel.every(
8246
8445
  (p) => p in opts.providers || Object.keys(opts.providers).includes(p.toLowerCase())
8247
8446
  ) && producingPanel.length >= Object.keys(opts.providers).length;
8248
- auditEnvelope = await runAudit({
8447
+ auditEnvelope = await audit({
8249
8448
  output_to_audit: orchestration["final"],
8250
8449
  producing_panelists: producingPanel,
8251
8450
  ...auditRubric !== void 0 ? { rubric: auditRubric } : {},
@@ -8256,7 +8455,6 @@ ${scopeAnswer || "(none)"}
8256
8455
  }, {
8257
8456
  providers: opts.providers,
8258
8457
  allowlist: opts.allowlist ?? null,
8259
- ...opts.bridge ? { bridge: opts.bridge } : {},
8260
8458
  ctx
8261
8459
  });
8262
8460
  let overallF = toNumberOrNull(auditEnvelope["overall_score"]);
@@ -8275,13 +8473,12 @@ ${retryConstraints}
8275
8473
  DOCUMENTS:
8276
8474
  ${documentsPayload}`
8277
8475
  };
8278
- const retryOrchestration = await runOrchestrate(
8476
+ const retryOrchestration = await orchestrate(
8279
8477
  retryOrchestrateArgs,
8280
8478
  {
8281
8479
  providers: opts.providers,
8282
8480
  allowlist: opts.allowlist ?? null,
8283
8481
  moderator,
8284
- ...opts.bridge ? { bridge: opts.bridge } : {},
8285
8482
  ctx
8286
8483
  }
8287
8484
  );
@@ -8290,7 +8487,7 @@ ${documentsPayload}`
8290
8487
  if (typeof retryOrchestration["final"] === "string" && retryOrchestration["final"]) {
8291
8488
  const retryPanel = computeProducingPanel(retryOrchestration);
8292
8489
  const retryPanelExhausted = retryPanel.length > 0 && retryPanel.length >= Object.keys(opts.providers).length;
8293
- auditEnvelope = await runAudit({
8490
+ auditEnvelope = await audit({
8294
8491
  output_to_audit: retryOrchestration["final"],
8295
8492
  producing_panelists: retryPanel,
8296
8493
  ...auditRubric !== void 0 ? { rubric: auditRubric } : {},
@@ -8301,7 +8498,6 @@ ${documentsPayload}`
8301
8498
  }, {
8302
8499
  providers: opts.providers,
8303
8500
  allowlist: opts.allowlist ?? null,
8304
- ...opts.bridge ? { bridge: opts.bridge } : {},
8305
8501
  ctx
8306
8502
  });
8307
8503
  overallF = toNumberOrNull(auditEnvelope["overall_score"]);
@@ -8366,9 +8562,28 @@ ${documentsPayload}`
8366
8562
  ["auditor", "items", "overall_score", "passed", "rubric"]
8367
8563
  ),
8368
8564
  artifacts,
8369
- warnings
8565
+ warnings,
8566
+ ...combinedUsage(subEnvelopes)
8370
8567
  };
8371
8568
  }
8569
+ function combinedUsage(envelopes) {
8570
+ const usages = [];
8571
+ const byCallTiming = [];
8572
+ let wall = 0, cpu = 0;
8573
+ for (const env of envelopes) {
8574
+ const e = env ?? {};
8575
+ const calls = e["usage"]?.["by_call"] ?? [];
8576
+ const t = e["timing"] ?? {};
8577
+ const timing = Array.isArray(t["by_call"]) ? t["by_call"] : [];
8578
+ if (!Array.isArray(calls) || calls.length === 0) continue;
8579
+ usages.push(...calls);
8580
+ if (timing.length === calls.length) byCallTiming.push(...timing);
8581
+ else for (const c of calls) byCallTiming.push({ provider: c.provider, model: c.model, purpose: c.purpose ?? null, wall_ms: 0, cpu_ms: 0, cache_hit: false });
8582
+ wall += Number(t["wall_ms"] ?? 0) || 0;
8583
+ cpu += Number(t["cpu_ms"] ?? 0) || 0;
8584
+ }
8585
+ return { usage: aggregateUsage(usages), timing: { wall_ms: wall, cpu_ms: cpu, by_call: byCallTiming } };
8586
+ }
8372
8587
  async function ingestDocuments(documents, sessionId, opts) {
8373
8588
  if (documents.length === 0) return [];
8374
8589
  const out = [];
@@ -8539,11 +8754,6 @@ function errorEnvelope6(toolName, code, message, hint) {
8539
8754
 
8540
8755
  // src/tools/coordinate.ts
8541
8756
  import { performance as performance8 } from "perf_hooks";
8542
- var DEFERRED_OPTS3 = [
8543
- // empty — every coordinate opt runs natively when its deps
8544
- // (storage / innerCallers) are wired. Without them the
8545
- // worker_tools gate still triggers a bridge defer.
8546
- ];
8547
8757
  var COORDINATE_DEFAULT_TEMP = 0.4;
8548
8758
  var ROLE_TURN_SCHEMA = {
8549
8759
  type: "object",
@@ -8598,23 +8808,18 @@ async function runCoordinate(args, opts) {
8598
8808
  rejectedWorkerTools.push(t);
8599
8809
  }
8600
8810
  }
8601
- const workerToolsNeedsBridge = acceptedWorkerTools.length > 0 && !opts.innerCallers;
8602
- const needsBridge = DEFERRED_OPTS3.some((k) => Boolean(args[k])) || workerToolsNeedsBridge;
8603
- if (needsBridge) {
8811
+ if (acceptedWorkerTools.length > 0 && !opts.innerCallers) {
8604
8812
  if (superRequested) {
8605
8813
  return errorEnvelope7(
8606
8814
  "COORDINATE_SUPER_NOT_NATIVE",
8607
- "super mode requested but worker_tools needs the Python bridge",
8608
- "worker_tools without innerCallers wired defers to the Python bridge, which doesn't support super mode. Drop worker_tools, or wire innerCallers, to use super."
8815
+ "super mode requested with worker_tools, but innerCallers are not wired",
8816
+ "worker_tools needs innerCallers wired by the host. Drop worker_tools, or wire innerCallers, to use super."
8609
8817
  );
8610
8818
  }
8611
- if (opts.bridge && opts.bridge.toolNames.has("coordinate")) {
8612
- return await deferCoordinate(args, opts.bridge);
8613
- }
8614
8819
  return errorEnvelope7(
8615
8820
  "COORDINATE_OPT_NOT_NATIVE",
8616
8821
  "worker_tools requested but innerCallers are not wired",
8617
- "Native coordinate covers the three-role flow + untrusted_input + inject_session_memory. worker_tools needs innerCallers wired by the host (or CROSSCHECK_BRIDGE_PYTHON=1 as a backstop)."
8822
+ "Native coordinate covers the three-role flow + untrusted_input + inject_session_memory. worker_tools needs innerCallers wired by the host."
8618
8823
  );
8619
8824
  }
8620
8825
  const untrusted = Boolean(args["untrusted_input"]);
@@ -9014,21 +9219,6 @@ function unknownProviderError3(unknownNames, available) {
9014
9219
  available_now: Object.keys(available).sort()
9015
9220
  };
9016
9221
  }
9017
- async function deferCoordinate(args, bridge) {
9018
- const r = await bridge.callTool("coordinate", args);
9019
- const text = r.content[0]?.text;
9020
- if (typeof text === "string") {
9021
- try {
9022
- return JSON.parse(text);
9023
- } catch {
9024
- }
9025
- }
9026
- return errorEnvelope7(
9027
- "COORDINATE_BRIDGE_BAD_ENVELOPE",
9028
- "bridge returned an unparseable envelope for coordinate",
9029
- "Check that the Python child is healthy."
9030
- );
9031
- }
9032
9222
  function errorEnvelope7(code, message, hint) {
9033
9223
  return {
9034
9224
  tool: "coordinate",
@@ -9356,10 +9546,6 @@ function buildModeratorTranscript(transcript) {
9356
9546
 
9357
9547
  // src/tools/debate.ts
9358
9548
  import { performance as performance10 } from "perf_hooks";
9359
- var DEFERRED_OPTS4 = [
9360
- // empty — every debate opt runs natively when its deps
9361
- // (storage / innerCallers) are wired.
9362
- ];
9363
9549
  async function runDebate(args, opts) {
9364
9550
  const callStartedWall = performance10.now();
9365
9551
  const callStartedCpu = process.cpuUsage();
@@ -9377,23 +9563,18 @@ async function runDebate(args, opts) {
9377
9563
  rejectedWorkerTools.push(t);
9378
9564
  }
9379
9565
  }
9380
- const workerToolsNeedsBridge = acceptedWorkerTools.length > 0 && !opts.innerCallers;
9381
- const needsBridge = DEFERRED_OPTS4.some((k) => Boolean(args[k])) || workerToolsNeedsBridge;
9382
- if (needsBridge) {
9566
+ if (acceptedWorkerTools.length > 0 && !opts.innerCallers) {
9383
9567
  if (superRequested) {
9384
9568
  return errorEnvelope9(
9385
9569
  "DEBATE_SUPER_NOT_NATIVE",
9386
- "super mode requested but worker_tools needs the Python bridge",
9387
- "worker_tools without innerCallers wired defers to the Python bridge, which doesn't support super mode. Drop worker_tools, or wire innerCallers, to use super."
9570
+ "super mode requested with worker_tools, but innerCallers are not wired",
9571
+ "worker_tools needs innerCallers wired by the host. Drop worker_tools, or wire innerCallers, to use super."
9388
9572
  );
9389
9573
  }
9390
- if (opts.bridge && opts.bridge.toolNames.has("debate")) {
9391
- return await deferDebate(args, opts.bridge);
9392
- }
9393
9574
  return errorEnvelope9(
9394
9575
  "DEBATE_OPT_NOT_NATIVE",
9395
9576
  "worker_tools requested but innerCallers are not wired",
9396
- "Native debate covers N-round + plain/structured moderator synthesis + inject_session_memory + auto_panel + extract_claims + early_stop. worker_tools needs innerCallers wired by the host (or CROSSCHECK_BRIDGE_PYTHON=1 as a backstop)."
9577
+ "Native debate covers N-round + plain/structured moderator synthesis + inject_session_memory + auto_panel + extract_claims + early_stop. worker_tools needs innerCallers wired by the host."
9397
9578
  );
9398
9579
  }
9399
9580
  const topic = typeof args["topic"] === "string" ? args["topic"] : String(args["topic"] ?? "");
@@ -9830,21 +10011,6 @@ function unknownProviderError5(unknownNames, available) {
9830
10011
  available_now: Object.keys(available).sort()
9831
10012
  };
9832
10013
  }
9833
- async function deferDebate(args, bridge) {
9834
- const r = await bridge.callTool("debate", args);
9835
- const text = r.content[0]?.text;
9836
- if (typeof text === "string") {
9837
- try {
9838
- return JSON.parse(text);
9839
- } catch {
9840
- }
9841
- }
9842
- return errorEnvelope9(
9843
- "DEBATE_BRIDGE_BAD_ENVELOPE",
9844
- "bridge returned an unparseable envelope for debate",
9845
- "Check that the Python child is healthy."
9846
- );
9847
- }
9848
10014
  function errorEnvelope9(code, message, hint) {
9849
10015
  return {
9850
10016
  tool: "debate",
@@ -9864,9 +10030,6 @@ var DEFAULT_DELEGATION_LIMITS = {
9864
10030
  };
9865
10031
  async function runDelegate(args, opts) {
9866
10032
  if (!opts.storage) {
9867
- if (opts.bridge && opts.bridge.toolNames.has("delegate")) {
9868
- return await deferDelegate(args, opts.bridge);
9869
- }
9870
10033
  return errorEnvelope10(
9871
10034
  "DELEGATE_STORAGE_UNAVAILABLE",
9872
10035
  "delegate needs the local database (for quota tracking), which isn't available",
@@ -9938,7 +10101,6 @@ async function runDelegate(args, opts) {
9938
10101
  const innerOpts = {
9939
10102
  providers: opts.providers,
9940
10103
  allowlist: opts.allowlist ?? null,
9941
- ...opts.bridge ? { bridge: opts.bridge } : {},
9942
10104
  ...opts.moderator ? { moderator: opts.moderator } : {}
9943
10105
  };
9944
10106
  let result;
@@ -9977,21 +10139,6 @@ async function record(storage, sessionId, requester, toolCall, via, accepted, no
9977
10139
  created_at: nowEpochSeconds
9978
10140
  });
9979
10141
  }
9980
- async function deferDelegate(args, bridge) {
9981
- const r = await bridge.callTool("delegate", args);
9982
- const text = r.content[0]?.text;
9983
- if (typeof text === "string") {
9984
- try {
9985
- return JSON.parse(text);
9986
- } catch {
9987
- }
9988
- }
9989
- return errorEnvelope10(
9990
- "DELEGATE_BRIDGE_BAD_ENVELOPE",
9991
- "bridge returned an unparseable envelope for delegate",
9992
- "Check that the Python child is healthy."
9993
- );
9994
- }
9995
10142
  function errorEnvelope10(code, message, hint) {
9996
10143
  return {
9997
10144
  tool: "delegate",
@@ -10034,9 +10181,6 @@ async function runExplain(args, opts) {
10034
10181
  );
10035
10182
  }
10036
10183
  if (!opts.storage) {
10037
- if (opts.bridge && opts.bridge.toolNames.has("explain")) {
10038
- return await deferExplain(args, opts.bridge);
10039
- }
10040
10184
  return errorEnvelope11(
10041
10185
  "EXPLAIN_STORAGE_UNAVAILABLE",
10042
10186
  "explain needs the local transcript/stats database, which isn't available",
@@ -10318,21 +10462,6 @@ function pyRepr5(v) {
10318
10462
  }
10319
10463
  return String(v);
10320
10464
  }
10321
- async function deferExplain(args, bridge) {
10322
- const r = await bridge.callTool("explain", args);
10323
- const text = r.content[0]?.text;
10324
- if (typeof text === "string") {
10325
- try {
10326
- return JSON.parse(text);
10327
- } catch {
10328
- }
10329
- }
10330
- return errorEnvelope11(
10331
- "EXPLAIN_BRIDGE_BAD_ENVELOPE",
10332
- "bridge returned an unparseable envelope for explain",
10333
- "Check that the Python child is healthy."
10334
- );
10335
- }
10336
10465
  function errorEnvelope11(code, message, hint, kind = "client", extra = {}) {
10337
10466
  return {
10338
10467
  tool: "explain",
@@ -10885,9 +11014,6 @@ async function runRecall(args, opts) {
10885
11014
  if (k < 1) k = 1;
10886
11015
  if (k > 50) k = 50;
10887
11016
  if (!opts.storage) {
10888
- if (opts.bridge && opts.bridge.toolNames.has("recall")) {
10889
- return await deferRecall(args, opts.bridge);
10890
- }
10891
11017
  return errorEnvelope12(
10892
11018
  "RECALL_STORAGE_UNAVAILABLE",
10893
11019
  "recall needs the local database, which isn't available",
@@ -10940,21 +11066,6 @@ async function runRecall(args, opts) {
10940
11066
  applied_filters: applied
10941
11067
  };
10942
11068
  }
10943
- async function deferRecall(args, bridge) {
10944
- const r = await bridge.callTool("recall", args);
10945
- const text = r.content[0]?.text;
10946
- if (typeof text === "string") {
10947
- try {
10948
- return JSON.parse(text);
10949
- } catch {
10950
- }
10951
- }
10952
- return errorEnvelope12(
10953
- "RECALL_BRIDGE_BAD_ENVELOPE",
10954
- "bridge returned an unparseable envelope for recall",
10955
- "Check that the Python child is healthy."
10956
- );
10957
- }
10958
11069
  function errorEnvelope12(code, message, hint) {
10959
11070
  return {
10960
11071
  tool: "recall",
@@ -10977,9 +11088,6 @@ async function runRecommendPanel(args, opts) {
10977
11088
  );
10978
11089
  }
10979
11090
  if (!opts.storage) {
10980
- if (opts.bridge && opts.bridge.toolNames.has("recommend_panel")) {
10981
- return await deferRecommendPanel(args, opts.bridge);
10982
- }
10983
11091
  return errorEnvelope13(
10984
11092
  "RECOMMEND_PANEL_STORAGE_UNAVAILABLE",
10985
11093
  "recommend_panel needs the local stats database, which isn't available",
@@ -11042,21 +11150,6 @@ async function runRecommendPanel(args, opts) {
11042
11150
  meta: result.meta
11043
11151
  };
11044
11152
  }
11045
- async function deferRecommendPanel(args, bridge) {
11046
- const r = await bridge.callTool("recommend_panel", args);
11047
- const text = r.content[0]?.text;
11048
- if (typeof text === "string") {
11049
- try {
11050
- return JSON.parse(text);
11051
- } catch {
11052
- }
11053
- }
11054
- return errorEnvelope13(
11055
- "RECOMMEND_PANEL_BRIDGE_BAD_ENVELOPE",
11056
- "bridge returned an unparseable envelope for recommend_panel",
11057
- "Check that the Python child is healthy."
11058
- );
11059
- }
11060
11153
  function errorEnvelope13(code, message, hint) {
11061
11154
  return {
11062
11155
  tool: "recommend_panel",
@@ -11086,9 +11179,6 @@ function toStringArray3(v) {
11086
11179
  import { readFileSync as readFileSync10, existsSync as existsSync8 } from "fs";
11087
11180
  async function runScoreboard(args, opts) {
11088
11181
  if (!opts.storage) {
11089
- if (opts.bridge && opts.bridge.toolNames.has("scoreboard")) {
11090
- return await deferScoreboard(args, opts.bridge);
11091
- }
11092
11182
  return errorEnvelope14(
11093
11183
  "SCOREBOARD_STORAGE_UNAVAILABLE",
11094
11184
  "scoreboard needs the local stats database, which isn't available",
@@ -11211,21 +11301,6 @@ function clampInt3(raw, min, max, fallback) {
11211
11301
  if (!Number.isFinite(n)) return fallback;
11212
11302
  return Math.min(max, Math.max(min, Math.trunc(n)));
11213
11303
  }
11214
- async function deferScoreboard(args, bridge) {
11215
- const r = await bridge.callTool("scoreboard", args);
11216
- const text = r.content[0]?.text;
11217
- if (typeof text === "string") {
11218
- try {
11219
- return JSON.parse(text);
11220
- } catch {
11221
- }
11222
- }
11223
- return errorEnvelope14(
11224
- "SCOREBOARD_BRIDGE_BAD_ENVELOPE",
11225
- "bridge returned an unparseable envelope for scoreboard",
11226
- "Check that the Python child is healthy."
11227
- );
11228
- }
11229
11304
  function errorEnvelope14(code, message, hint) {
11230
11305
  return {
11231
11306
  tool: "scoreboard",
@@ -11262,9 +11337,6 @@ async function runSessionMemory(args, opts) {
11262
11337
  );
11263
11338
  }
11264
11339
  if (!opts.storage) {
11265
- if (opts.bridge && opts.bridge.toolNames.has("session_memory")) {
11266
- return await deferSessionMemory(args, opts.bridge);
11267
- }
11268
11340
  return errorEnvelope15(
11269
11341
  "SESSION_MEMORY_STORAGE_UNAVAILABLE",
11270
11342
  "session_memory needs the local database, which isn't available",
@@ -11369,21 +11441,6 @@ function clampInt4(raw, min, max, fallback) {
11369
11441
  if (!Number.isFinite(n)) return fallback;
11370
11442
  return Math.min(max, Math.max(min, Math.trunc(n)));
11371
11443
  }
11372
- async function deferSessionMemory(args, bridge) {
11373
- const r = await bridge.callTool("session_memory", args);
11374
- const text = r.content[0]?.text;
11375
- if (typeof text === "string") {
11376
- try {
11377
- return JSON.parse(text);
11378
- } catch {
11379
- }
11380
- }
11381
- return errorEnvelope15(
11382
- "SESSION_MEMORY_BRIDGE_BAD_ENVELOPE",
11383
- "bridge returned an unparseable envelope for session_memory",
11384
- "Check that the Python child is healthy."
11385
- );
11386
- }
11387
11444
  function errorEnvelope15(code, message, hint) {
11388
11445
  return {
11389
11446
  tool: "session_memory",
@@ -12003,7 +12060,7 @@ var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
12003
12060
  var DEFAULT_PACKAGE = "crosscheck-cli";
12004
12061
  var FETCH_TIMEOUT_MS = 3e3;
12005
12062
  function engineVersion() {
12006
- return true ? "0.2.27" : "0.0.0-dev";
12063
+ return true ? "0.2.29" : "0.0.0-dev";
12007
12064
  }
12008
12065
  function defaultUpdateCachePath() {
12009
12066
  const base = process.env["CROSSCHECK_DATA_DIR"] || path9.join(os.homedir() || os.tmpdir(), ".crosscheck");
@@ -12775,12 +12832,11 @@ function helpTool() {
12775
12832
  }
12776
12833
  });
12777
12834
  }
12778
- function registerCoreTools(opts = {}) {
12779
- const o = opts && typeof opts === "object" && "toolNames" in opts ? { bridge: opts } : opts;
12835
+ function registerCoreTools(o = {}) {
12780
12836
  const tools = /* @__PURE__ */ new Map();
12781
12837
  const list = [
12782
12838
  pingTool(),
12783
- verifyTool(o.bridge, o.fetchConfig),
12839
+ verifyTool(o.fetchConfig),
12784
12840
  pickTool(
12785
12841
  o.providers ?? {},
12786
12842
  o.providerAllowlist ?? null,
@@ -12792,7 +12848,6 @@ function registerCoreTools(opts = {}) {
12792
12848
  auditTool(
12793
12849
  o.providers ?? {},
12794
12850
  o.providerAllowlist ?? null,
12795
- o.bridge,
12796
12851
  o.transcriptsDir,
12797
12852
  o.pricing,
12798
12853
  o.storage,
@@ -12802,7 +12857,6 @@ function registerCoreTools(opts = {}) {
12802
12857
  conferTool(
12803
12858
  o.providers ?? {},
12804
12859
  o.providerAllowlist ?? null,
12805
- o.bridge,
12806
12860
  o.moderatorDefault ?? "anthropic",
12807
12861
  o.pricing,
12808
12862
  o.storage,
@@ -12815,7 +12869,6 @@ function registerCoreTools(opts = {}) {
12815
12869
  debateTool(
12816
12870
  o.providers ?? {},
12817
12871
  o.providerAllowlist ?? null,
12818
- o.bridge,
12819
12872
  buildInnerCallers(o),
12820
12873
  o.storage,
12821
12874
  o.pricing,
@@ -12827,7 +12880,6 @@ function registerCoreTools(opts = {}) {
12827
12880
  coordinateTool(
12828
12881
  o.providers ?? {},
12829
12882
  o.providerAllowlist ?? null,
12830
- o.bridge,
12831
12883
  buildInnerCallers(o),
12832
12884
  o.storage,
12833
12885
  o.circuitBreakers,
@@ -12838,7 +12890,6 @@ function registerCoreTools(opts = {}) {
12838
12890
  triangulateTool(
12839
12891
  o.providers ?? {},
12840
12892
  o.providerAllowlist ?? null,
12841
- o.bridge,
12842
12893
  o.storage,
12843
12894
  o.circuitBreakers,
12844
12895
  o.transcriptsDir,
@@ -12847,7 +12898,6 @@ function registerCoreTools(opts = {}) {
12847
12898
  planTool(
12848
12899
  o.providers ?? {},
12849
12900
  o.providerAllowlist ?? null,
12850
- o.bridge,
12851
12901
  o.storage,
12852
12902
  o.circuitBreakers,
12853
12903
  o.transcriptsDir,
@@ -12856,7 +12906,6 @@ function registerCoreTools(opts = {}) {
12856
12906
  critiqueTool(
12857
12907
  o.providers ?? {},
12858
12908
  o.providerAllowlist ?? null,
12859
- o.bridge,
12860
12909
  o.storage,
12861
12910
  o.circuitBreakers,
12862
12911
  o.transcriptsDir,
@@ -12865,7 +12914,6 @@ function registerCoreTools(opts = {}) {
12865
12914
  reviewTool(
12866
12915
  o.providers ?? {},
12867
12916
  o.providerAllowlist ?? null,
12868
- o.bridge,
12869
12917
  o.storage,
12870
12918
  o.circuitBreakers,
12871
12919
  o.transcriptsDir,
@@ -12873,25 +12921,23 @@ function registerCoreTools(opts = {}) {
12873
12921
  ),
12874
12922
  listProvidersTool(o.providers ?? {}, o.activeProviders ?? null, o.moderatorDefault ?? "anthropic"),
12875
12923
  modelsTool(o.providers ?? {}, o.activeProviders ?? null),
12876
- recallTool(o.storage, o.bridge),
12877
- sessionMemoryTool(o.storage, o.bridge),
12878
- scoreboardTool(o.storage, o.bridge, o.eventsPath),
12879
- explainTool(o.storage, o.bridge, o.transcriptsDir),
12924
+ recallTool(o.storage),
12925
+ sessionMemoryTool(o.storage),
12926
+ scoreboardTool(o.storage, o.eventsPath),
12927
+ explainTool(o.storage, o.transcriptsDir),
12880
12928
  delegateTool(
12881
12929
  o.providers ?? {},
12882
12930
  o.providerAllowlist ?? null,
12883
12931
  o.storage,
12884
- o.bridge,
12885
12932
  o.moderatorDefault ?? "anthropic"
12886
12933
  ),
12887
12934
  fetchTool(o.storage, o.fetchConfig, o.repoRoot),
12888
- recommendPanelTool(o.providers ?? {}, o.storage, o.bridge),
12935
+ recommendPanelTool(o.providers ?? {}, o.storage),
12889
12936
  updateCrosscheckTool(o.repoRoot ?? null),
12890
12937
  benchTool(
12891
12938
  o.providers ?? {},
12892
12939
  o.providerAllowlist ?? null,
12893
12940
  o.storage,
12894
- o.bridge,
12895
12941
  o.moderatorDefault ?? "anthropic",
12896
12942
  o.benchGoldensDir,
12897
12943
  o.circuitBreakers,
@@ -12902,7 +12948,6 @@ function registerCoreTools(opts = {}) {
12902
12948
  solveTool(
12903
12949
  o.providers ?? {},
12904
12950
  o.providerAllowlist ?? null,
12905
- o.bridge,
12906
12951
  o.storage,
12907
12952
  o.circuitBreakers,
12908
12953
  o.transcriptsDir,
@@ -12911,7 +12956,6 @@ function registerCoreTools(opts = {}) {
12911
12956
  orchestrateTool(
12912
12957
  o.providers ?? {},
12913
12958
  o.providerAllowlist ?? null,
12914
- o.bridge,
12915
12959
  o.moderatorDefault ?? "anthropic",
12916
12960
  o.storage,
12917
12961
  o.pricing,
@@ -12958,7 +13002,6 @@ function createTool(o, toolName, cheapDefault) {
12958
13002
  providers: o.providers ?? {},
12959
13003
  allowlist: o.providerAllowlist ?? null,
12960
13004
  ...o.storage ? { storage: o.storage } : {},
12961
- ...o.bridge ? { bridge: o.bridge } : {},
12962
13005
  ...o.moderatorDefault ? { moderator: o.moderatorDefault } : {},
12963
13006
  ...o.fetchConfig ? { fetchConfig: o.fetchConfig } : {},
12964
13007
  ...o.repoRoot ? { repoRoot: o.repoRoot } : {},
@@ -12967,7 +13010,7 @@ function createTool(o, toolName, cheapDefault) {
12967
13010
  })
12968
13011
  };
12969
13012
  }
12970
- function orchestrateTool(providers, allowlist, bridge, moderator, storage, pricing, breakers, transcriptsDir, repoRoot, nodeCache) {
13013
+ function orchestrateTool(providers, allowlist, moderator, storage, pricing, breakers, transcriptsDir, repoRoot, nodeCache) {
12971
13014
  return {
12972
13015
  name: "orchestrate",
12973
13016
  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.",
@@ -12992,7 +13035,6 @@ function orchestrateTool(providers, allowlist, bridge, moderator, storage, prici
12992
13035
  providers,
12993
13036
  allowlist,
12994
13037
  moderator,
12995
- ...bridge ? { bridge } : {},
12996
13038
  ...storage ? { storage } : {},
12997
13039
  ...pricing ? { pricing } : {},
12998
13040
  ...breakers ? { breakers } : {},
@@ -13002,7 +13044,7 @@ function orchestrateTool(providers, allowlist, bridge, moderator, storage, prici
13002
13044
  })
13003
13045
  };
13004
13046
  }
13005
- function solveTool(providers, allowlist, bridge, storage, breakers, transcriptsDir, repoRoot) {
13047
+ function solveTool(providers, allowlist, storage, breakers, transcriptsDir, repoRoot) {
13006
13048
  return {
13007
13049
  name: "solve",
13008
13050
  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.",
@@ -13024,7 +13066,6 @@ function solveTool(providers, allowlist, bridge, storage, breakers, transcriptsD
13024
13066
  handler: (args) => runSolve(args, {
13025
13067
  providers,
13026
13068
  allowlist,
13027
- ...bridge ? { bridge } : {},
13028
13069
  ...storage ? { storage } : {},
13029
13070
  ...breakers ? { breakers } : {},
13030
13071
  ...transcriptsDir ? { transcriptsDir } : {},
@@ -13063,7 +13104,7 @@ function configPinTool(repoRoot, config, rejectDriftEnv) {
13063
13104
  }
13064
13105
  };
13065
13106
  }
13066
- function benchTool(providers, allowlist, storage, bridge, moderator, defaultGoldensDir, breakers, transcriptsDir, repoRoot) {
13107
+ function benchTool(providers, allowlist, storage, moderator, defaultGoldensDir, breakers, transcriptsDir, repoRoot) {
13067
13108
  return {
13068
13109
  name: "bench",
13069
13110
  description: "Run benchmark goldens against each provider's confer/review output. Each golden: {name, tool_call: confer|review, args, verifiers[]}. Returns per-provider pass/fail/error + a ranking sorted by score. Records ballots into provider_stats when storage is wired (used by triangulate's win-rate weights).",
@@ -13082,7 +13123,6 @@ function benchTool(providers, allowlist, storage, bridge, moderator, defaultGold
13082
13123
  allowlist,
13083
13124
  moderator,
13084
13125
  ...storage ? { storage } : {},
13085
- ...bridge ? { bridge } : {},
13086
13126
  ...defaultGoldensDir ? { defaultGoldensDir } : {},
13087
13127
  ...breakers ? { breakers } : {},
13088
13128
  ...transcriptsDir ? { transcriptsDir } : {},
@@ -13109,7 +13149,7 @@ function updateCrosscheckTool(repoRoot) {
13109
13149
  }
13110
13150
  };
13111
13151
  }
13112
- function recommendPanelTool(providers, storage, bridge) {
13152
+ function recommendPanelTool(providers, storage) {
13113
13153
  return {
13114
13154
  name: "recommend_panel",
13115
13155
  description: "Recommend a minimal effective panel for a given purpose, based on historical usage_log + provider_stats. Cold-start falls back to the configured panel ordered by win-rate.",
@@ -13127,8 +13167,7 @@ function recommendPanelTool(providers, storage, bridge) {
13127
13167
  },
13128
13168
  handler: (args) => runRecommendPanel(args, {
13129
13169
  providers,
13130
- ...storage ? { storage } : {},
13131
- ...bridge ? { bridge } : {}
13170
+ ...storage ? { storage } : {}
13132
13171
  })
13133
13172
  };
13134
13173
  }
@@ -13153,7 +13192,7 @@ function fetchTool(storage, fetchConfig, repoRoot) {
13153
13192
  })
13154
13193
  };
13155
13194
  }
13156
- function delegateTool(providers, allowlist, storage, bridge, moderator) {
13195
+ function delegateTool(providers, allowlist, storage, moderator) {
13157
13196
  return {
13158
13197
  name: "delegate",
13159
13198
  description: "Run a delegable tool (confer | review) restricted to a single named provider, with explicit quota check. Records every attempt (accepted or refused) to the delegations table; quota in the response reflects counts AFTER the current call.",
@@ -13173,12 +13212,11 @@ function delegateTool(providers, allowlist, storage, bridge, moderator) {
13173
13212
  providers,
13174
13213
  allowlist,
13175
13214
  moderator,
13176
- ...storage ? { storage } : {},
13177
- ...bridge ? { bridge } : {}
13215
+ ...storage ? { storage } : {}
13178
13216
  })
13179
13217
  };
13180
13218
  }
13181
- function explainTool(storage, bridge, transcriptsDir) {
13219
+ function explainTool(storage, transcriptsDir) {
13182
13220
  return {
13183
13221
  name: "explain",
13184
13222
  description: "Replay a session as a navigable tree with cost/latency annotations. Reads usage_log + optionally walks the transcripts directory for per-tool summaries.",
@@ -13196,12 +13234,11 @@ function explainTool(storage, bridge, transcriptsDir) {
13196
13234
  },
13197
13235
  handler: (args) => runExplain(args, {
13198
13236
  ...storage ? { storage } : {},
13199
- ...bridge ? { bridge } : {},
13200
13237
  ...transcriptsDir ? { transcriptsDir } : {}
13201
13238
  })
13202
13239
  };
13203
13240
  }
13204
- function scoreboardTool(storage, bridge, eventsPath) {
13241
+ function scoreboardTool(storage, eventsPath) {
13205
13242
  return {
13206
13243
  name: "scoreboard",
13207
13244
  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).",
@@ -13215,12 +13252,11 @@ function scoreboardTool(storage, bridge, eventsPath) {
13215
13252
  },
13216
13253
  handler: (args) => runScoreboard(args, {
13217
13254
  ...storage ? { storage } : {},
13218
- ...bridge ? { bridge } : {},
13219
13255
  ...eventsPath ? { eventsPath } : {}
13220
13256
  })
13221
13257
  };
13222
13258
  }
13223
- function sessionMemoryTool(storage, bridge) {
13259
+ function sessionMemoryTool(storage) {
13224
13260
  return {
13225
13261
  name: "session_memory",
13226
13262
  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).",
@@ -13243,15 +13279,14 @@ function sessionMemoryTool(storage, bridge) {
13243
13279
  required: ["action", "session_id"]
13244
13280
  },
13245
13281
  handler: (args) => runSessionMemory(args, {
13246
- ...storage ? { storage } : {},
13247
- ...bridge ? { bridge } : {}
13282
+ ...storage ? { storage } : {}
13248
13283
  })
13249
13284
  };
13250
13285
  }
13251
- function recallTool(storage, bridge) {
13286
+ function recallTool(storage) {
13252
13287
  return {
13253
13288
  name: "recall",
13254
- description: "Full-text search across persisted transcripts via SQLite FTS5. Returns rows ordered by relevance with a windowed snippet. Requires a wired Storage adapter; falls back to the Python bridge when not available.",
13289
+ description: "Full-text search across persisted transcripts via SQLite FTS5. Returns rows ordered by relevance with a windowed snippet. Requires a wired Storage adapter.",
13255
13290
  inputSchema: {
13256
13291
  type: "object",
13257
13292
  additionalProperties: true,
@@ -13265,8 +13300,7 @@ function recallTool(storage, bridge) {
13265
13300
  required: ["query"]
13266
13301
  },
13267
13302
  handler: (args) => runRecall(args, {
13268
- ...storage ? { storage } : {},
13269
- ...bridge ? { bridge } : {}
13303
+ ...storage ? { storage } : {}
13270
13304
  })
13271
13305
  };
13272
13306
  }
@@ -13302,7 +13336,7 @@ function modelsTool(providers, activeProviders) {
13302
13336
  handler: async (args) => runModels(args, { providers, activeProviders })
13303
13337
  };
13304
13338
  }
13305
- function reviewTool(providers, allowlist, bridge, storage, breakers, transcriptsDir, repoRoot) {
13339
+ function reviewTool(providers, allowlist, storage, breakers, transcriptsDir, repoRoot) {
13306
13340
  return {
13307
13341
  name: "review",
13308
13342
  description: "Have an LLM panel peer-review a code or proposal snippet. Returns the confer envelope (one answer per provider).",
@@ -13321,7 +13355,6 @@ function reviewTool(providers, allowlist, bridge, storage, breakers, transcripts
13321
13355
  handler: (args) => runReview(args, {
13322
13356
  providers,
13323
13357
  allowlist,
13324
- ...bridge ? { bridge } : {},
13325
13358
  ...storage ? { storage } : {},
13326
13359
  ...breakers ? { breakers } : {},
13327
13360
  ...transcriptsDir ? { transcriptsDir } : {},
@@ -13329,7 +13362,7 @@ function reviewTool(providers, allowlist, bridge, storage, breakers, transcripts
13329
13362
  })
13330
13363
  };
13331
13364
  }
13332
- function critiqueTool(providers, allowlist, bridge, storage, breakers, transcriptsDir, repoRoot) {
13365
+ function critiqueTool(providers, allowlist, storage, breakers, transcriptsDir, repoRoot) {
13333
13366
  return {
13334
13367
  name: "critique",
13335
13368
  description: "Have each LLM panelist list the top weaknesses of a proposed answer or approach (severity-rated). Returns per-provider weakness lists + a merged list ordered by severity.",
@@ -13349,7 +13382,6 @@ function critiqueTool(providers, allowlist, bridge, storage, breakers, transcrip
13349
13382
  handler: (args) => runCritique(args, {
13350
13383
  providers,
13351
13384
  allowlist,
13352
- ...bridge ? { bridge } : {},
13353
13385
  ...storage ? { storage } : {},
13354
13386
  ...breakers ? { breakers } : {},
13355
13387
  ...transcriptsDir ? { transcriptsDir } : {},
@@ -13357,7 +13389,7 @@ function critiqueTool(providers, allowlist, bridge, storage, breakers, transcrip
13357
13389
  })
13358
13390
  };
13359
13391
  }
13360
- function planTool(providers, allowlist, bridge, storage, breakers, transcriptsDir, repoRoot) {
13392
+ function planTool(providers, allowlist, storage, breakers, transcriptsDir, repoRoot) {
13361
13393
  return {
13362
13394
  name: "plan",
13363
13395
  description: 'Have an LLM panel debate a step-by-step plan for the stated goal under the given constraints. Returns the debate envelope with a moderator-synthesised plan. Defaults to `mode: "fast"` (2 rounds + early_stop) \u2014 set `mode: "thorough"` for 5 rounds without early_stop. Explicit `max_rounds` / `early_stop` / `early_stop_threshold` always override the mode preset. Use `structured: true` for schema-validated synthesis.',
@@ -13384,7 +13416,6 @@ function planTool(providers, allowlist, bridge, storage, breakers, transcriptsDi
13384
13416
  handler: (args) => runPlan(args, {
13385
13417
  providers,
13386
13418
  allowlist,
13387
- ...bridge ? { bridge } : {},
13388
13419
  ...storage ? { storage } : {},
13389
13420
  ...breakers ? { breakers } : {},
13390
13421
  ...transcriptsDir ? { transcriptsDir } : {},
@@ -13392,7 +13423,7 @@ function planTool(providers, allowlist, bridge, storage, breakers, transcriptsDi
13392
13423
  })
13393
13424
  };
13394
13425
  }
13395
- function triangulateTool(providers, allowlist, bridge, storage, breakers, transcriptsDir, repoRoot) {
13426
+ function triangulateTool(providers, allowlist, storage, breakers, transcriptsDir, repoRoot) {
13396
13427
  return {
13397
13428
  name: "triangulate",
13398
13429
  description: "Run a coordinate flow and reshape the output as a consensus + minority report with per-provider weights. v1 uses 1.0 weights (matches a fresh provider_stats DB); future versions thread real win-rate weights when the DB layer ports.",
@@ -13413,7 +13444,6 @@ function triangulateTool(providers, allowlist, bridge, storage, breakers, transc
13413
13444
  handler: (args) => runTriangulate(args, {
13414
13445
  providers,
13415
13446
  allowlist,
13416
- ...bridge ? { bridge } : {},
13417
13447
  ...storage ? { storage } : {},
13418
13448
  ...breakers ? { breakers } : {},
13419
13449
  ...transcriptsDir ? { transcriptsDir } : {},
@@ -13421,7 +13451,7 @@ function triangulateTool(providers, allowlist, bridge, storage, breakers, transc
13421
13451
  })
13422
13452
  };
13423
13453
  }
13424
- function coordinateTool(providers, allowlist, bridge, innerCallers, storage, breakers, transcriptsDir, repoRoot, workerToolsCfg) {
13454
+ function coordinateTool(providers, allowlist, innerCallers, storage, breakers, transcriptsDir, repoRoot, workerToolsCfg) {
13425
13455
  return {
13426
13456
  name: "coordinate",
13427
13457
  description: "Run a three-role coordination flow (proposer \u2192 critics \u2192 synthesizer) with structured output at every step. Native opts: untrusted_input (canary + neutralize), worker_tools (bounded ReAct on proposer + critic roles; synth never sees inner tools), inject_session_memory (storage-backed working-memory block prepended to the topic).",
@@ -13446,7 +13476,6 @@ function coordinateTool(providers, allowlist, bridge, innerCallers, storage, bre
13446
13476
  handler: (args) => runCoordinate(args, {
13447
13477
  providers,
13448
13478
  allowlist,
13449
- ...bridge ? { bridge } : {},
13450
13479
  innerCallers,
13451
13480
  ...storage ? { storage } : {},
13452
13481
  ...breakers ? { breakers } : {},
@@ -13456,7 +13485,7 @@ function coordinateTool(providers, allowlist, bridge, innerCallers, storage, bre
13456
13485
  })
13457
13486
  };
13458
13487
  }
13459
- function debateTool(providers, allowlist, bridge, innerCallers, storage, pricing, breakers, transcriptsDir, repoRoot, workerToolsCfg) {
13488
+ function debateTool(providers, allowlist, innerCallers, storage, pricing, breakers, transcriptsDir, repoRoot, workerToolsCfg) {
13460
13489
  return {
13461
13490
  name: "debate",
13462
13491
  description: "Run a multi-round debate between LLM providers and synthesise the result via a moderator. Native opts: inject_session_memory, auto_panel, worker_tools, structured synthesis (schema-validated), extract_claims (final-round distillation), early_stop (post-round agreement check).",
@@ -13486,7 +13515,6 @@ function debateTool(providers, allowlist, bridge, innerCallers, storage, pricing
13486
13515
  handler: (args) => runDebate(args, {
13487
13516
  providers,
13488
13517
  allowlist,
13489
- ...bridge ? { bridge } : {},
13490
13518
  innerCallers,
13491
13519
  ...storage ? { storage } : {},
13492
13520
  ...pricing ? { pricing } : {},
@@ -13506,10 +13534,10 @@ function buildInnerCallers(o) {
13506
13534
  ...o.repoRoot ? { repoRoot: o.repoRoot } : {}
13507
13535
  });
13508
13536
  }
13509
- out.verify = (args) => runVerify(args, o.bridge, o.fetchConfig);
13537
+ out.verify = (args) => runVerify(args, o.fetchConfig);
13510
13538
  return out;
13511
13539
  }
13512
- function conferTool(providers, allowlist, bridge, moderator, pricing, storage, innerCallers, breakers, transcriptsDir, repoRoot, workerToolsCfg) {
13540
+ function conferTool(providers, allowlist, moderator, pricing, storage, innerCallers, breakers, transcriptsDir, repoRoot, workerToolsCfg) {
13513
13541
  return {
13514
13542
  name: "confer",
13515
13543
  description: "Ask a panel of LLM providers the same question; return one answer per provider. Native opts: untrusted_input (canary + neutralization), early_stop (cheap-tier agreement check after first 2 panelists when N>=3), extract_claims (cheap-tier extractor with per-provider support maps), inject_session_memory (storage-backed working-memory block prepended to the first user message), auto_panel (router-driven top-N panel pick), worker_tools (bounded ReAct loop per panelist when innerCallers are wired by the host).",
@@ -13536,7 +13564,6 @@ function conferTool(providers, allowlist, bridge, moderator, pricing, storage, i
13536
13564
  handler: (args) => runConfer(args, {
13537
13565
  providers,
13538
13566
  allowlist,
13539
- ...bridge ? { bridge } : {},
13540
13567
  moderator,
13541
13568
  ...pricing ? { pricing } : {},
13542
13569
  ...storage ? { storage } : {},
@@ -13548,7 +13575,7 @@ function conferTool(providers, allowlist, bridge, moderator, pricing, storage, i
13548
13575
  })
13549
13576
  };
13550
13577
  }
13551
- function auditTool(providers, allowlist, bridge, transcriptsDir, pricing, storage, breakers, repoRoot) {
13578
+ function auditTool(providers, allowlist, transcriptsDir, pricing, storage, breakers, repoRoot) {
13552
13579
  return {
13553
13580
  name: "audit",
13554
13581
  description: "Score a piece of output against an audit rubric. Single-judge by default; coalesce-mode (multi-judge consensus) and panel-exhausted self-audit run natively. cheap_mode picks the cheapest 'med'-tier model as auditor when a pricing.json is wired. session_id-only inputs load the latest transcript automatically when a transcripts directory is wired.",
@@ -13574,7 +13601,6 @@ function auditTool(providers, allowlist, bridge, transcriptsDir, pricing, storag
13574
13601
  handler: (args) => runAudit(args, {
13575
13602
  providers,
13576
13603
  allowlist,
13577
- ...bridge ? { bridge } : {},
13578
13604
  ...transcriptsDir ? { transcriptsDir } : {},
13579
13605
  ...pricing ? { pricing } : {},
13580
13606
  ...storage ? { storage } : {},
@@ -13638,7 +13664,7 @@ function pickTool(providers, allowlist, storage, breakers, transcriptsDir, repoR
13638
13664
  })
13639
13665
  };
13640
13666
  }
13641
- function verifyTool(bridge, fetchConfig) {
13667
+ function verifyTool(fetchConfig) {
13642
13668
  return {
13643
13669
  name: "verify",
13644
13670
  description: "Run a list of deterministic property checks against caller-supplied data. No LLM calls; everything is local. Returns per-check {passed, reason} plus all_passed / summary / timing fields. Supports the text kinds (contains, not_contains, regex_match, contains_any, contains_all, min_length), shell (sandboxed subprocess with timeout + Unix pgid isolation), and url_head (HEAD request gated by fetch.url_allowlist). All kinds run natively.",
@@ -13656,7 +13682,7 @@ function verifyTool(bridge, fetchConfig) {
13656
13682
  },
13657
13683
  required: ["checks"]
13658
13684
  },
13659
- handler: (args) => runVerify(args, bridge, fetchConfig)
13685
+ handler: (args) => runVerify(args, fetchConfig)
13660
13686
  };
13661
13687
  }
13662
13688
  function pingTool() {
@@ -13706,7 +13732,7 @@ function createCrosscheck(opts) {
13706
13732
  // Direct runConfer call with a streaming hook — bypasses the tool
13707
13733
  // registry (whose handler signature can't carry a callback). Uses the
13708
13734
  // same opts the registry wires for confer in a browser build: providers
13709
- // + pricing + default moderator; storage/bridge/innerCallers aren't
13735
+ // + pricing + default moderator; storage/innerCallers aren't
13710
13736
  // available here, matching the non-streaming path.
13711
13737
  conferStream: (args, onAnswer) => runConfer(args, { providers, pricing: pricingDoc, moderator: "anthropic", onAnswer }),
13712
13738
  debateStream: (args, { onTurn, onSynthesis } = {}) => runDebate(args, {