crosscheck-mcp 0.2.29 → 0.2.31

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.
@@ -1269,6 +1269,11 @@ function reasoningSharesOutputBudget(provider) {
1269
1269
 
1270
1270
  // src/providers/types.ts
1271
1271
  init_esm_shims();
1272
+ var IMAGE_MEDIA_TYPES = ["image/png", "image/jpeg", "image/webp", "image/gif"];
1273
+ function imagesOf(m) {
1274
+ if (m.role !== "user" || !Array.isArray(m.images)) return [];
1275
+ return m.images.filter((i) => Boolean(i) && typeof i.data === "string" && i.data.length > 0 && IMAGE_MEDIA_TYPES.includes(i.mediaType));
1276
+ }
1272
1277
  var ProviderError = class extends Error {
1273
1278
  name = "ProviderError";
1274
1279
  kind;
@@ -1668,7 +1673,12 @@ function buildAnthropicRequest(opts) {
1668
1673
  }
1669
1674
  continue;
1670
1675
  }
1671
- convo.push({ role: m.role, content: typeof m.content === "string" ? m.content : "" });
1676
+ const text = typeof m.content === "string" ? m.content : "";
1677
+ const images = imagesOf(m);
1678
+ convo.push(images.length ? { role: m.role, content: [
1679
+ ...images.map((i) => ({ type: "image", source: { type: "base64", media_type: i.mediaType, data: i.data } })),
1680
+ { type: "text", text }
1681
+ ] } : { role: m.role, content: text });
1672
1682
  }
1673
1683
  const body = {
1674
1684
  model: opts.model,
@@ -1909,7 +1919,10 @@ function buildGeminiRequest(opts) {
1909
1919
  const role = m.role === "user" ? "user" : "model";
1910
1920
  contents.push({
1911
1921
  role,
1912
- parts: [{ text: typeof m.content === "string" ? m.content : "" }]
1922
+ parts: [
1923
+ ...imagesOf(m).map((i) => ({ inline_data: { mime_type: i.mediaType, data: i.data } })),
1924
+ { text: typeof m.content === "string" ? m.content : "" }
1925
+ ]
1913
1926
  });
1914
1927
  }
1915
1928
  const body = {
@@ -2078,10 +2091,11 @@ function supportsNativeJsonSchema(provider, model) {
2078
2091
  return true;
2079
2092
  }
2080
2093
  function buildOpenAICompatibleRequest(opts) {
2081
- const msgs = opts.messages.map((m) => ({
2082
- role: m.role,
2083
- content: typeof m.content === "string" ? m.content : ""
2084
- }));
2094
+ const msgs = opts.messages.map((m) => {
2095
+ const text = typeof m.content === "string" ? m.content : "";
2096
+ const images = imagesOf(m);
2097
+ return images.length ? { role: m.role, content: [{ type: "text", text }, ...images.map((i) => ({ type: "image_url", image_url: { url: `data:${i.mediaType};base64,${i.data}` } }))] } : { role: m.role, content: text };
2098
+ });
2085
2099
  const body = {
2086
2100
  model: opts.model,
2087
2101
  messages: msgs
@@ -2457,6 +2471,7 @@ Routing rules:
2457
2471
  2. If the next token does NOT match a tool name (or there is no next token), default to \`mcp__crosscheck__${DEFAULT_TOOL}\` and pass the entire message tail (everything after \`xc\`/\`XC\`) as \`question\`.
2458
2472
  3. Preserve the user's wording verbatim in the argument \u2014 do not paraphrase, summarize, or "clean up" the prompt before forwarding.
2459
2473
  4. If the user message is exactly \`xc\` or \`XC\` with no tail, ask them what they want crosscheck to do; do not invent a question.
2474
+ 5. If the user names a moderator ("let Gemini moderate", "moderator gpt", "moderated by openai:gpt-6-sol"), ALSO pass it as \`moderator\` \u2014 a provider or \`provider:model\` \u2014 on tools that accept one (debate, plan, coordinate, orchestrate, create). Any provider can moderate. Still forward the wording verbatim.
2460
2475
 
2461
2476
  Routable tool names: ${ROUTABLE_TOOLS.join(", ")}.
2462
2477
 
@@ -2494,7 +2509,7 @@ import { z } from "zod";
2494
2509
  // src/server-meta.ts
2495
2510
  init_esm_shims();
2496
2511
  var SERVER_NAME = "crosscheck-agent";
2497
- var SERVER_VERSION = true ? "0.2.29" : "0.0.0-dev";
2512
+ var SERVER_VERSION = true ? "0.2.31" : "0.0.0-dev";
2498
2513
 
2499
2514
  // src/tools/audit.ts
2500
2515
  init_esm_shims();
@@ -4128,6 +4143,231 @@ function numberOrNull(v) {
4128
4143
  // src/tools/audit.ts
4129
4144
  import { performance as performance2 } from "perf_hooks";
4130
4145
 
4146
+ // src/core/moderator.ts
4147
+ init_esm_shims();
4148
+
4149
+ // src/core/super-mode.ts
4150
+ init_esm_shims();
4151
+ import { AsyncLocalStorage } from "async_hooks";
4152
+
4153
+ // src/core/reasoning-upgrade.ts
4154
+ init_esm_shims();
4155
+ var UPGRADE_MODEL = "claude-fable-5";
4156
+ var UPGRADE_LABEL = "Fable 5";
4157
+ function isUpgradeRequested(args) {
4158
+ if (args["upgrade"] === true) return true;
4159
+ const reasoning = args["reasoning"];
4160
+ if (typeof reasoning === "string" && reasoning.trim().toLowerCase() === "max") {
4161
+ return true;
4162
+ }
4163
+ const model = args["model"];
4164
+ if (typeof model === "string") {
4165
+ const m = model.trim().toLowerCase();
4166
+ if (m === "fable5" || m === "fable-5" || m === "fable" || m === UPGRADE_MODEL) {
4167
+ return true;
4168
+ }
4169
+ }
4170
+ return false;
4171
+ }
4172
+ var HEAVY_TERMS = [
4173
+ /\bprov(e|ing|en)\b|\bproof\b|\btheorem\b|\bderiv(e|ation)\b/i,
4174
+ /\barchitect(ure|ing)?\b|\bdesign\s+(a|the|an)\b|\bhigh[-\s]?level\s+design\b/i,
4175
+ /\btrade[-\s]?offs?\b|\bpros?\s+and\s+cons?\b|\bweigh\b/i,
4176
+ /\boptimi[sz]e\b|\bcomplexity\b|\bO\(|\balgorithm(ic)?\b/i,
4177
+ /\brace\s+condition\b|\bconcurren(t|cy)\b|\bdeadlock\b|\bdistributed\b|\bconsensus\b/i,
4178
+ /\broot[-\s]?cause\b|\bdebug(ging)?\b|\bwhy\s+(does|is|would|might)\b/i,
4179
+ /\bsecurity\b|\bthreat\s+model\b|\bexploit\b|\bvulnerabilit(y|ies)\b/i,
4180
+ /\bmigrat(e|ion)\b|\brefactor\b|\bre[-\s]?architect\b/i,
4181
+ /\binvariant\b|\bcorrectness\b|\bedge\s+cases?\b|\bformal(ly)?\b/i,
4182
+ /\bstrateg(y|ic|ies)\b|\bplan\s+(the|a|an|out)\b|\broadmap\b/i,
4183
+ /\breason\s+(through|about)\b|\bstep[-\s]?by[-\s]?step\b|\bthink\s+deeply\b/i,
4184
+ /\bnp[-\s]?(hard|complete)\b|\bgraph\b|\bstate\s+machine\b|\bconstraint\b/i
4185
+ ];
4186
+ var DIFFICULTY_TERMS = /\b(hard|tricky|subtle|complex|complicated|non[-\s]?trivial|thorny|gnarly|difficult|deep)\b/i;
4187
+ function assessReasoning(text) {
4188
+ const s = typeof text === "string" ? text : "";
4189
+ const signals = [];
4190
+ let score = 0;
4191
+ let termHits = 0;
4192
+ for (const re of HEAVY_TERMS) {
4193
+ if (re.test(s)) termHits += 1;
4194
+ }
4195
+ if (termHits > 0) {
4196
+ const add = Math.min(termHits, 4);
4197
+ score += add;
4198
+ signals.push(`${termHits} reasoning cue${termHits === 1 ? "" : "s"}`);
4199
+ }
4200
+ if (DIFFICULTY_TERMS.test(s)) {
4201
+ score += 1;
4202
+ signals.push("explicit difficulty marker");
4203
+ }
4204
+ const words = s.split(/\s+/).filter(Boolean).length;
4205
+ if (words >= 120) {
4206
+ score += 2;
4207
+ signals.push("long, detailed prompt");
4208
+ } else if (words >= 50) {
4209
+ score += 1;
4210
+ signals.push("substantial prompt");
4211
+ }
4212
+ const constraints = (s.match(/\b(must|should|need to|ensure|require[ds]?|constraint)\b/gi) ?? []).length + (s.match(/(^|\n)\s*(?:[-*]|\d+[.)])\s+/g) ?? []).length;
4213
+ if (constraints >= 3) {
4214
+ score += 1;
4215
+ signals.push(`${constraints} constraints/steps`);
4216
+ }
4217
+ const heavy = score >= 3;
4218
+ const reason = heavy ? `Task looks reasoning-heavy (${signals.join(", ") || "score " + score}).` : `Task looks routine (score ${score}).`;
4219
+ return { heavy, score, reason, signals };
4220
+ }
4221
+ function buildSuggestedUpgrade(toolName, assessment) {
4222
+ return {
4223
+ model: UPGRADE_MODEL,
4224
+ label: UPGRADE_LABEL,
4225
+ reason: assessment.reason,
4226
+ how: `re-run \`${toolName}\` with reasoning:"max" (or upgrade:true) to route the lead reasoning seat to ${UPGRADE_LABEL}, and \u2014 when an OpenAI key is configured \u2014 add an OpenAI second-opinion pass that ${UPGRADE_LABEL} weighs before finalizing (the result names which model ran). Other panel seats stay on their normal models.`
4227
+ };
4228
+ }
4229
+
4230
+ // src/core/super-mode.ts
4231
+ var SUPER_MODELS = {
4232
+ // Fable 5.1 verified present on the account; 5 as the fallback for accounts
4233
+ // that haven't been granted 5.1 yet.
4234
+ anthropic: ["claude-fable-5-1", UPGRADE_MODEL],
4235
+ // Astra is live (verified against the account's model list 2026-09-23).
4236
+ // The fallback is named explicitly rather than reusing CO_REASON_MODEL:
4237
+ // that constant means "OpenAI's top reasoning model" for the max-reasoning
4238
+ // co-reasoner, which is a different question from "what should super fall
4239
+ // back to". Sharing the symbol silently coupled two unrelated decisions.
4240
+ openai: ["gpt-6-astra", "gpt-6-sol"],
4241
+ // Explicitly left as-is per product decision. Note grok-4-latest is not in
4242
+ // xAI's published model list, but it resolves in practice — real panel calls
4243
+ // on it succeeded as recently as 2026-08-30 — so it is an unlisted alias
4244
+ // rather than a dead id.
4245
+ xai: [DEFAULT_MODELS["xai"]],
4246
+ // 3.8 Flash is now BOTH the everyday default and the super pick for Gemini,
4247
+ // so this can no longer borrow DEFAULT_MODELS as its fallback — that would
4248
+ // repeat the same model and retry one that just failed. Named explicitly.
4249
+ gemini: ["gemini-3.8-flash", "gemini-3.1-pro-preview"],
4250
+ kimi: [DEFAULT_MODELS["kimi"]],
4251
+ qwen: [DEFAULT_MODELS["qwen"]]
4252
+ };
4253
+ var SUPER_ENV_VARS = {
4254
+ anthropic: "ANTHROPIC_SUPER_MODEL",
4255
+ openai: "OPENAI_SUPER_MODEL",
4256
+ xai: "XAI_SUPER_MODEL",
4257
+ gemini: "GEMINI_SUPER_MODEL",
4258
+ kimi: "KIMI_SUPER_MODEL",
4259
+ qwen: "QWEN_SUPER_MODEL",
4260
+ mistral: "MISTRAL_SUPER_MODEL",
4261
+ groq: "GROQ_SUPER_MODEL",
4262
+ deepseek: "DEEPSEEK_SUPER_MODEL"
4263
+ };
4264
+ function parseChain(v) {
4265
+ if (!v) return [];
4266
+ const out = [];
4267
+ for (const raw of v.split(",")) {
4268
+ const m = raw.trim();
4269
+ if (m && !out.includes(m)) out.push(m);
4270
+ }
4271
+ return out;
4272
+ }
4273
+ var superEnvStore = new AsyncLocalStorage();
4274
+ var currentEnv = () => superEnvStore.getStore() ?? process.env;
4275
+ function superChainFor(provider, env = currentEnv()) {
4276
+ const varName = SUPER_ENV_VARS[provider];
4277
+ const user = varName ? parseChain(env[varName]) : [];
4278
+ if (user.length > 0) {
4279
+ const fallbacks = varName ? parseChain(env[`${varName}_FALLBACKS`]) : [];
4280
+ return [...user, ...fallbacks.filter((m) => !user.includes(m))];
4281
+ }
4282
+ return SUPER_MODELS[provider] ?? [];
4283
+ }
4284
+ function superPrimary(provider, env = currentEnv()) {
4285
+ return superChainFor(provider, env)[0];
4286
+ }
4287
+ var SUPER_PROVIDER_NAMES = Object.keys(SUPER_MODELS);
4288
+ function isSuperRequested(args) {
4289
+ return args["super"] === true;
4290
+ }
4291
+ function retargetForSuper(p, env = currentEnv()) {
4292
+ const chain = superChainFor(p.name, env);
4293
+ if (chain.length === 0) return p;
4294
+ const [primary, ...fallbacks] = chain;
4295
+ return retargetProvider(p, primary, fallbacks);
4296
+ }
4297
+ var SUPER_BUDGET_MULTIPLIER = 3;
4298
+ var SUPER_BUDGET_CEILING = 16e3;
4299
+ function superBudget(base) {
4300
+ return Math.min(base * SUPER_BUDGET_MULTIPLIER, SUPER_BUDGET_CEILING);
4301
+ }
4302
+
4303
+ // src/core/moderator.ts
4304
+ var DEFAULT_MODERATOR = "anthropic";
4305
+ var PROVIDER_ALIASES = {
4306
+ grok: "xai",
4307
+ claude: "anthropic",
4308
+ google: "gemini",
4309
+ chatgpt: "openai",
4310
+ gpt: "openai",
4311
+ moonshot: "kimi"
4312
+ };
4313
+ function parseModeratorSpec(raw) {
4314
+ if (typeof raw !== "string") return null;
4315
+ const s = raw.trim();
4316
+ if (!s) return null;
4317
+ const i = s.indexOf(":");
4318
+ const providerPart = (i < 0 ? s : s.slice(0, i)).trim().toLowerCase();
4319
+ const modelPart = i < 0 ? "" : s.slice(i + 1).trim();
4320
+ if (!providerPart) return null;
4321
+ return {
4322
+ provider: PROVIDER_ALIASES[providerPart] ?? providerPart,
4323
+ model: modelPart || null
4324
+ };
4325
+ }
4326
+ function formatModeratorSpec(spec) {
4327
+ return spec.model ? `${spec.provider}:${spec.model}` : spec.provider;
4328
+ }
4329
+ function pickModeratorSpec(...candidates) {
4330
+ for (const c of candidates) {
4331
+ const s = parseModeratorSpec(c);
4332
+ if (s) return s;
4333
+ }
4334
+ return { provider: DEFAULT_MODERATOR, model: null };
4335
+ }
4336
+ function resolveModerator(providers, ...candidates) {
4337
+ const spec = pickModeratorSpec(...candidates);
4338
+ const base = providers[spec.provider];
4339
+ if (!base) return { provider: void 0, spec, pinned: false };
4340
+ if (!spec.model) return { provider: base, spec, pinned: false };
4341
+ return { provider: retargetProvider(base, spec.model), spec, pinned: true };
4342
+ }
4343
+ function synthesisSeat(o) {
4344
+ const { moderator, pinned, superRequested, upgradeRequested } = o;
4345
+ const wantsMore = superRequested || upgradeRequested;
4346
+ if (!wantsMore) return { provider: moderator, upgraded: false, label: null, skipReason: null };
4347
+ if (pinned) {
4348
+ return {
4349
+ provider: moderator,
4350
+ upgraded: false,
4351
+ label: null,
4352
+ skipReason: `the moderator is pinned to ${moderator.name}:${moderator.model}; a pinned model is used as-is.`
4353
+ };
4354
+ }
4355
+ if (moderator.name === "anthropic" && !superRequested) {
4356
+ return { provider: retargetProvider(moderator, UPGRADE_MODEL), upgraded: true, label: UPGRADE_LABEL, skipReason: null };
4357
+ }
4358
+ if (superChainFor(moderator.name).length === 0) {
4359
+ return {
4360
+ provider: moderator,
4361
+ upgraded: false,
4362
+ label: null,
4363
+ skipReason: `no flagship model is known for ${moderator.name}; the moderator ran at ${moderator.model}.`
4364
+ };
4365
+ }
4366
+ const seat = retargetForSuper(moderator);
4367
+ const label = moderator.name === "anthropic" ? UPGRADE_LABEL : seat.model;
4368
+ return { provider: seat, upgraded: true, label, skipReason: null };
4369
+ }
4370
+
4131
4371
  // src/core/co-reason.ts
4132
4372
  init_esm_shims();
4133
4373
  var CO_REASON_MODEL = "gpt-6-sol";
@@ -4306,83 +4546,6 @@ function selectForDifficulty(args) {
4306
4546
  };
4307
4547
  }
4308
4548
 
4309
- // src/core/reasoning-upgrade.ts
4310
- init_esm_shims();
4311
- var UPGRADE_MODEL = "claude-fable-5";
4312
- var UPGRADE_LABEL = "Fable 5";
4313
- function isUpgradeRequested(args) {
4314
- if (args["upgrade"] === true) return true;
4315
- const reasoning = args["reasoning"];
4316
- if (typeof reasoning === "string" && reasoning.trim().toLowerCase() === "max") {
4317
- return true;
4318
- }
4319
- const model = args["model"];
4320
- if (typeof model === "string") {
4321
- const m = model.trim().toLowerCase();
4322
- if (m === "fable5" || m === "fable-5" || m === "fable" || m === UPGRADE_MODEL) {
4323
- return true;
4324
- }
4325
- }
4326
- return false;
4327
- }
4328
- var HEAVY_TERMS = [
4329
- /\bprov(e|ing|en)\b|\bproof\b|\btheorem\b|\bderiv(e|ation)\b/i,
4330
- /\barchitect(ure|ing)?\b|\bdesign\s+(a|the|an)\b|\bhigh[-\s]?level\s+design\b/i,
4331
- /\btrade[-\s]?offs?\b|\bpros?\s+and\s+cons?\b|\bweigh\b/i,
4332
- /\boptimi[sz]e\b|\bcomplexity\b|\bO\(|\balgorithm(ic)?\b/i,
4333
- /\brace\s+condition\b|\bconcurren(t|cy)\b|\bdeadlock\b|\bdistributed\b|\bconsensus\b/i,
4334
- /\broot[-\s]?cause\b|\bdebug(ging)?\b|\bwhy\s+(does|is|would|might)\b/i,
4335
- /\bsecurity\b|\bthreat\s+model\b|\bexploit\b|\bvulnerabilit(y|ies)\b/i,
4336
- /\bmigrat(e|ion)\b|\brefactor\b|\bre[-\s]?architect\b/i,
4337
- /\binvariant\b|\bcorrectness\b|\bedge\s+cases?\b|\bformal(ly)?\b/i,
4338
- /\bstrateg(y|ic|ies)\b|\bplan\s+(the|a|an|out)\b|\broadmap\b/i,
4339
- /\breason\s+(through|about)\b|\bstep[-\s]?by[-\s]?step\b|\bthink\s+deeply\b/i,
4340
- /\bnp[-\s]?(hard|complete)\b|\bgraph\b|\bstate\s+machine\b|\bconstraint\b/i
4341
- ];
4342
- var DIFFICULTY_TERMS = /\b(hard|tricky|subtle|complex|complicated|non[-\s]?trivial|thorny|gnarly|difficult|deep)\b/i;
4343
- function assessReasoning(text) {
4344
- const s = typeof text === "string" ? text : "";
4345
- const signals = [];
4346
- let score = 0;
4347
- let termHits = 0;
4348
- for (const re of HEAVY_TERMS) {
4349
- if (re.test(s)) termHits += 1;
4350
- }
4351
- if (termHits > 0) {
4352
- const add = Math.min(termHits, 4);
4353
- score += add;
4354
- signals.push(`${termHits} reasoning cue${termHits === 1 ? "" : "s"}`);
4355
- }
4356
- if (DIFFICULTY_TERMS.test(s)) {
4357
- score += 1;
4358
- signals.push("explicit difficulty marker");
4359
- }
4360
- const words = s.split(/\s+/).filter(Boolean).length;
4361
- if (words >= 120) {
4362
- score += 2;
4363
- signals.push("long, detailed prompt");
4364
- } else if (words >= 50) {
4365
- score += 1;
4366
- signals.push("substantial prompt");
4367
- }
4368
- const constraints = (s.match(/\b(must|should|need to|ensure|require[ds]?|constraint)\b/gi) ?? []).length + (s.match(/(^|\n)\s*(?:[-*]|\d+[.)])\s+/g) ?? []).length;
4369
- if (constraints >= 3) {
4370
- score += 1;
4371
- signals.push(`${constraints} constraints/steps`);
4372
- }
4373
- const heavy = score >= 3;
4374
- const reason = heavy ? `Task looks reasoning-heavy (${signals.join(", ") || "score " + score}).` : `Task looks routine (score ${score}).`;
4375
- return { heavy, score, reason, signals };
4376
- }
4377
- function buildSuggestedUpgrade(toolName, assessment) {
4378
- return {
4379
- model: UPGRADE_MODEL,
4380
- label: UPGRADE_LABEL,
4381
- reason: assessment.reason,
4382
- how: `re-run \`${toolName}\` with reasoning:"max" (or upgrade:true) to route the lead reasoning seat to ${UPGRADE_LABEL}, and \u2014 when an OpenAI key is configured \u2014 add an OpenAI second-opinion pass that ${UPGRADE_LABEL} weighs before finalizing (the result names which model ran). Other panel seats stay on their normal models.`
4383
- };
4384
- }
4385
-
4386
4549
  // src/tools/audit.ts
4387
4550
  var AUDIT_OBVIOUS_FAILURE_HIGH = 0.3;
4388
4551
  var AUDIT_OBVIOUS_FAILURE_MED = 0.2;
@@ -4680,9 +4843,14 @@ ${rubricText}`;
4680
4843
  );
4681
4844
  }
4682
4845
  const upgradeRequested = isUpgradeRequested(args);
4683
- const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
4684
- const auditorForCall = upgraded ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : auditor;
4685
- const coProvider = pickCoReasoner(opts.providers, upgradeRequested);
4846
+ const seat = synthesisSeat({ moderator: auditor, pinned: false, superRequested: false, upgradeRequested });
4847
+ const upgraded = seat.upgraded;
4848
+ const auditorForCall = seat.provider;
4849
+ const coProvider = pickCoReasoner(
4850
+ opts.providers,
4851
+ upgradeRequested,
4852
+ auditorForCall.name === "openai" ? auditorForCall.model : void 0
4853
+ );
4686
4854
  const coReason = await coReasonThenInject({
4687
4855
  provider: coProvider,
4688
4856
  messages: msgs,
@@ -4765,15 +4933,16 @@ ${rubricText}`;
4765
4933
  if (upgraded) {
4766
4934
  result["reasoning_upgrade"] = {
4767
4935
  applied: true,
4768
- model: UPGRADE_MODEL,
4769
- label: UPGRADE_LABEL,
4936
+ provider: auditorForCall.name,
4937
+ model: auditorForCall.model,
4938
+ label: seat.label ?? auditorForCall.model,
4770
4939
  seat: "auditor",
4771
4940
  ...coReasoned ? { co_reasoner: { model: coProvider.model, label: coReasonLabel(coProvider.model) } } : {}
4772
4941
  };
4773
4942
  } else if (upgradeRequested) {
4774
4943
  result["reasoning_upgrade"] = {
4775
4944
  applied: false,
4776
- reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
4945
+ reason: seat.skipReason ?? "the auditor couldn't be upgraded."
4777
4946
  };
4778
4947
  } else {
4779
4948
  const assessment = assessReasoning(
@@ -4856,7 +5025,7 @@ function pickAuditor(providers, exclude, explicit, moderatorName, allowlist, che
4856
5025
  }
4857
5026
  }
4858
5027
  }
4859
- const mod = providers[moderatorName.toLowerCase()];
5028
+ const mod = resolveModerator(providers, moderatorName).provider;
4860
5029
  if (mod !== void 0 && !exclude.has(mod.name) && (allowlist === null || allowlist.includes(mod.name))) {
4861
5030
  return { auditor: mod, reason: null };
4862
5031
  }
@@ -5181,9 +5350,7 @@ function pickPanelJudge(providers, moderatorName, pricing) {
5181
5350
  return entry.model && entry.model !== base.model ? retargetProvider(base, entry.model) : base;
5182
5351
  }
5183
5352
  }
5184
- const mod = providers[moderatorName.toLowerCase()];
5185
- if (mod !== void 0) return mod;
5186
- return null;
5353
+ return resolveModerator(providers, moderatorName).provider ?? null;
5187
5354
  }
5188
5355
  var AGREEMENT_CHECK_SCHEMA = {
5189
5356
  type: "object",
@@ -5501,78 +5668,6 @@ async function pickAutoPanel(storage, purpose, n, providers, allowlist) {
5501
5668
  return { providers: ranked.slice(0, Math.max(1, n)), coldStart: true };
5502
5669
  }
5503
5670
 
5504
- // src/core/super-mode.ts
5505
- init_esm_shims();
5506
- var SUPER_MODELS = {
5507
- // Fable 5.1 verified present on the account; 5 as the fallback for accounts
5508
- // that haven't been granted 5.1 yet.
5509
- anthropic: ["claude-fable-5-1", UPGRADE_MODEL],
5510
- // Astra is live (verified against the account's model list 2026-09-23).
5511
- // The fallback is named explicitly rather than reusing CO_REASON_MODEL:
5512
- // that constant means "OpenAI's top reasoning model" for the max-reasoning
5513
- // co-reasoner, which is a different question from "what should super fall
5514
- // back to". Sharing the symbol silently coupled two unrelated decisions.
5515
- openai: ["gpt-6-astra", "gpt-6-sol"],
5516
- // Explicitly left as-is per product decision. Note grok-4-latest is not in
5517
- // xAI's published model list, but it resolves in practice — real panel calls
5518
- // on it succeeded as recently as 2026-08-30 — so it is an unlisted alias
5519
- // rather than a dead id.
5520
- xai: [DEFAULT_MODELS["xai"]],
5521
- // 3.8 Flash is now BOTH the everyday default and the super pick for Gemini,
5522
- // so this can no longer borrow DEFAULT_MODELS as its fallback — that would
5523
- // repeat the same model and retry one that just failed. Named explicitly.
5524
- gemini: ["gemini-3.8-flash", "gemini-3.1-pro-preview"],
5525
- kimi: [DEFAULT_MODELS["kimi"]],
5526
- qwen: [DEFAULT_MODELS["qwen"]]
5527
- };
5528
- var SUPER_ENV_VARS = {
5529
- anthropic: "ANTHROPIC_SUPER_MODEL",
5530
- openai: "OPENAI_SUPER_MODEL",
5531
- xai: "XAI_SUPER_MODEL",
5532
- gemini: "GEMINI_SUPER_MODEL",
5533
- kimi: "KIMI_SUPER_MODEL",
5534
- qwen: "QWEN_SUPER_MODEL",
5535
- mistral: "MISTRAL_SUPER_MODEL",
5536
- groq: "GROQ_SUPER_MODEL",
5537
- deepseek: "DEEPSEEK_SUPER_MODEL"
5538
- };
5539
- function parseChain(v) {
5540
- if (!v) return [];
5541
- const out = [];
5542
- for (const raw of v.split(",")) {
5543
- const m = raw.trim();
5544
- if (m && !out.includes(m)) out.push(m);
5545
- }
5546
- return out;
5547
- }
5548
- function superChainFor(provider, env = process.env) {
5549
- const varName = SUPER_ENV_VARS[provider];
5550
- const user = varName ? parseChain(env[varName]) : [];
5551
- if (user.length > 0) {
5552
- const fallbacks = varName ? parseChain(env[`${varName}_FALLBACKS`]) : [];
5553
- return [...user, ...fallbacks.filter((m) => !user.includes(m))];
5554
- }
5555
- return SUPER_MODELS[provider] ?? [];
5556
- }
5557
- function superPrimary(provider, env = process.env) {
5558
- return superChainFor(provider, env)[0];
5559
- }
5560
- var SUPER_PROVIDER_NAMES = Object.keys(SUPER_MODELS);
5561
- function isSuperRequested(args) {
5562
- return args["super"] === true;
5563
- }
5564
- function retargetForSuper(p, env = process.env) {
5565
- const chain = superChainFor(p.name, env);
5566
- if (chain.length === 0) return p;
5567
- const [primary, ...fallbacks] = chain;
5568
- return retargetProvider(p, primary, fallbacks);
5569
- }
5570
- var SUPER_BUDGET_MULTIPLIER = 3;
5571
- var SUPER_BUDGET_CEILING = 16e3;
5572
- function superBudget(base) {
5573
- return Math.min(base * SUPER_BUDGET_MULTIPLIER, SUPER_BUDGET_CEILING);
5574
- }
5575
-
5576
5671
  // src/core/session-memory.ts
5577
5672
  init_esm_shims();
5578
5673
  var SESSION_MEMORY_DEFAULT_LIMIT = 50;
@@ -7649,9 +7744,9 @@ import { performance as performance7 } from "perf_hooks";
7649
7744
 
7650
7745
  // src/core/progress.ts
7651
7746
  init_esm_shims();
7652
- import { AsyncLocalStorage } from "async_hooks";
7747
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
7653
7748
  import { performance as performance6 } from "perf_hooks";
7654
- var progressStorage = new AsyncLocalStorage();
7749
+ var progressStorage = new AsyncLocalStorage2();
7655
7750
  async function withProgress(token, sendNotification, fn) {
7656
7751
  if (token === void 0 || token === null) {
7657
7752
  return fn();
@@ -7965,8 +8060,9 @@ async function runOrchestrate(args, opts) {
7965
8060
  blocked
7966
8061
  };
7967
8062
  }
7968
- const moderatorName = typeof args["moderator"] === "string" && args["moderator"] ? args["moderator"] : opts.moderator ?? "anthropic";
7969
- const moderator = opts.providers[moderatorName.toLowerCase()] ?? selected[0];
8063
+ const resolvedModerator = resolveModerator(opts.providers, args["moderator"], opts.moderator);
8064
+ const moderator = resolvedModerator.provider ?? selected[0];
8065
+ const moderatorChosen = resolvedModerator.provider !== void 0 && (resolvedModerator.pinned || parseModeratorSpec(args["moderator"]) !== null);
7970
8066
  const failFast = boolArg2(args["fail_fast"], false);
7971
8067
  const planOnly = boolArg2(args["plan_only"], false);
7972
8068
  const cheapMode = boolArg2(args["cheap_mode"], opts.ctx?.cheap_mode ?? false);
@@ -8450,11 +8546,17 @@ ${recombineSys}` : recombineSys },
8450
8546
  let synthProvider = moderator;
8451
8547
  let synthModel = null;
8452
8548
  const upgradeRequested = isUpgradeRequested(args);
8453
- const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
8549
+ const seat = synthesisSeat({
8550
+ moderator,
8551
+ pinned: resolvedModerator.provider !== void 0 && resolvedModerator.pinned,
8552
+ superRequested: false,
8553
+ upgradeRequested
8554
+ });
8555
+ const upgraded = seat.upgraded;
8454
8556
  if (upgraded) {
8455
- synthProvider = retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL);
8456
- synthModel = `anthropic/${UPGRADE_MODEL}`;
8457
- } else if (cheapMode && opts.pricing) {
8557
+ synthProvider = seat.provider;
8558
+ synthModel = `${seat.provider.name}/${seat.provider.model}`;
8559
+ } else if (cheapMode && opts.pricing && !moderatorChosen) {
8458
8560
  const pick = selectForDifficulty({
8459
8561
  pricing: opts.pricing,
8460
8562
  tier: "med",
@@ -8480,7 +8582,11 @@ ${recombineSys}` : recombineSys },
8480
8582
  provider: synthProvider.name
8481
8583
  } }
8482
8584
  );
8483
- const coProvider = pickCoReasoner(opts.providers, upgradeRequested);
8585
+ const coProvider = pickCoReasoner(
8586
+ opts.providers,
8587
+ upgradeRequested,
8588
+ synthProvider.name === "openai" ? synthProvider.model : void 0
8589
+ );
8484
8590
  const coReason = await coReasonThenInject({
8485
8591
  provider: coProvider,
8486
8592
  messages: recMsgs,
@@ -8530,7 +8636,8 @@ ${recombineSys}` : recombineSys },
8530
8636
  missing,
8531
8637
  partial,
8532
8638
  fail_fast: failFast,
8533
- cheap_mode: cheapMode
8639
+ cheap_mode: cheapMode,
8640
+ moderator: { provider: moderator.name, model: moderator.model }
8534
8641
  };
8535
8642
  if (personaMeta.used !== null) result["persona"] = personaMeta;
8536
8643
  if (synthModel) result["synth_model"] = synthModel;
@@ -8538,15 +8645,16 @@ ${recombineSys}` : recombineSys },
8538
8645
  if (upgraded) {
8539
8646
  result["reasoning_upgrade"] = {
8540
8647
  applied: true,
8541
- model: UPGRADE_MODEL,
8542
- label: UPGRADE_LABEL,
8648
+ provider: seat.provider.name,
8649
+ model: seat.provider.model,
8650
+ label: seat.label ?? seat.provider.model,
8543
8651
  seat: "recombine/synthesis",
8544
8652
  ...coReasoned ? { co_reasoner: { model: coProvider.model, label: coReasonLabel(coProvider.model) } } : {}
8545
8653
  };
8546
8654
  } else if (upgradeRequested) {
8547
8655
  result["reasoning_upgrade"] = {
8548
8656
  applied: false,
8549
- reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
8657
+ reason: seat.skipReason ?? "the moderator couldn't be upgraded."
8550
8658
  };
8551
8659
  } else if (goal) {
8552
8660
  const assessment = assessReasoning(goal);
@@ -9731,13 +9839,24 @@ async function runCoordinate(args, opts) {
9731
9839
  };
9732
9840
  }
9733
9841
  const proposerName = typeof args["proposer"] === "string" && args["proposer"] ? args["proposer"] : selected[0].name;
9734
- const synthName = typeof args["synthesizer"] === "string" && args["synthesizer"] ? args["synthesizer"] : typeof args["moderator"] === "string" && args["moderator"] ? args["moderator"] : opts.moderator ?? "anthropic";
9842
+ const resolvedSynth = resolveModerator(
9843
+ opts.providers,
9844
+ args["synthesizer"],
9845
+ args["moderator"],
9846
+ opts.moderator
9847
+ );
9735
9848
  const proposer = opts.providers[proposerName.toLowerCase()] ?? selected[0];
9736
- const synth = opts.providers[synthName.toLowerCase()] ?? proposer;
9849
+ const synth = resolvedSynth.provider ?? proposer;
9737
9850
  const proposerForCall = superRequested ? retargetForSuper(proposer) : proposer;
9738
9851
  const upgradeRequested = isUpgradeRequested(args) || superRequested;
9739
- const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
9740
- const synthForCall = superRequested && opts.providers["anthropic"] ? retargetForSuper(opts.providers["anthropic"]) : upgraded ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : synth;
9852
+ const seat = synthesisSeat({
9853
+ moderator: synth,
9854
+ pinned: resolvedSynth.provider !== void 0 && resolvedSynth.pinned,
9855
+ superRequested,
9856
+ upgradeRequested
9857
+ });
9858
+ const upgraded = seat.upgraded;
9859
+ const synthForCall = seat.provider;
9741
9860
  let critics = [];
9742
9861
  if (Array.isArray(args["critics"])) {
9743
9862
  for (const n of args["critics"]) {
@@ -9888,7 +10007,11 @@ CRITIQUES:
9888
10007
  ${critiqueBlock}`
9889
10008
  }
9890
10009
  ];
9891
- const coProvider = pickCoReasoner(opts.providers, upgradeRequested);
10010
+ const coProvider = pickCoReasoner(
10011
+ opts.providers,
10012
+ upgradeRequested,
10013
+ synthForCall.name === "openai" ? synthForCall.model : void 0
10014
+ );
9892
10015
  const coReason = await coReasonThenInject({
9893
10016
  provider: coProvider,
9894
10017
  messages: synthMessages,
@@ -9933,7 +10056,8 @@ ${critiqueBlock}`
9933
10056
  critique_answers: scannedCritiques,
9934
10057
  critique_structured: critiqueStructured,
9935
10058
  synthesis_answer: synthAns,
9936
- synthesis_structured: synthObj
10059
+ synthesis_structured: synthObj,
10060
+ moderator: { provider: synthForCall.name, model: synthForCall.model }
9937
10061
  };
9938
10062
  if (synthErrs.length > 0) result["synthesis_errors"] = synthErrs;
9939
10063
  if (canaryLeaks.length > 0) result["canary_leaks"] = canaryLeaks;
@@ -9951,15 +10075,16 @@ ${critiqueBlock}`
9951
10075
  result["reasoning_upgrade"] = {
9952
10076
  applied: true,
9953
10077
  // The model that actually ran the seat, not the one we'd have picked.
9954
- model: superRequested ? superPrimary("anthropic") ?? UPGRADE_MODEL : UPGRADE_MODEL,
9955
- label: UPGRADE_LABEL,
10078
+ provider: synthForCall.name,
10079
+ model: synthForCall.model,
10080
+ label: seat.label ?? synthForCall.model,
9956
10081
  seat: "synthesizer",
9957
10082
  ...coReasonAns ? { co_reasoner: { model: coProvider.model, label: coReasonLabel(coProvider.model) } } : {}
9958
10083
  };
9959
10084
  } else if (upgradeRequested) {
9960
10085
  result["reasoning_upgrade"] = {
9961
10086
  applied: false,
9962
- reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
10087
+ reason: seat.skipReason ?? "the synthesizer couldn't be upgraded."
9963
10088
  };
9964
10089
  } else {
9965
10090
  const assessment = assessReasoning(topic);
@@ -10570,10 +10695,12 @@ async function runDebate(args, opts) {
10570
10695
  const earlyStopThreshold = Number.isFinite(earlyStopThresholdRaw) ? earlyStopThresholdRaw : 0.7;
10571
10696
  let earlyStoppedRound = null;
10572
10697
  let agreementBlock = null;
10573
- const moderatorName = typeof args["moderator"] === "string" && args["moderator"] ? args["moderator"] : opts.moderator ?? "anthropic";
10574
- const moderator = opts.providers[moderatorName.toLowerCase()] ?? selected[0];
10698
+ const resolvedModerator = resolveModerator(opts.providers, args["moderator"], opts.moderator);
10699
+ const moderatorName = resolvedModerator.spec.provider;
10700
+ const moderator = resolvedModerator.provider ?? selected[0];
10575
10701
  const upgradeRequested = isUpgradeRequested(args) || superRequested;
10576
- const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
10702
+ const seat = moderator ? synthesisSeat({ moderator, pinned: resolvedModerator.pinned, superRequested, upgradeRequested }) : null;
10703
+ const upgraded = seat?.upgraded ?? false;
10577
10704
  for (let rnd = 1; rnd <= maxRounds; rnd++) {
10578
10705
  const roundMessages = [
10579
10706
  {
@@ -10658,8 +10785,10 @@ ${prior.body}` });
10658
10785
  let personaMeta = null;
10659
10786
  let coReasoned = false;
10660
10787
  let coReasonerModel = null;
10788
+ let moderatorUsed = null;
10661
10789
  if (moderator) {
10662
- const synthProvider = superRequested && opts.providers["anthropic"] ? retargetForSuper(opts.providers["anthropic"]) : upgraded ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : moderator;
10790
+ const synthProvider = seat?.provider ?? moderator;
10791
+ moderatorUsed = { provider: synthProvider.name, model: synthProvider.model };
10663
10792
  const condensed = buildModeratorTranscript(transcript);
10664
10793
  const persona = buildPersonaInjection({
10665
10794
  toolName: "debate",
@@ -10685,7 +10814,7 @@ TRANSCRIPT:
10685
10814
  ${condensed}`
10686
10815
  }
10687
10816
  ];
10688
- const openAiSeat = selected.find((p) => p.name === "openai")?.model;
10817
+ const openAiSeat = synthProvider.name === "openai" ? synthProvider.model : selected.find((p) => p.name === "openai")?.model;
10689
10818
  const coProvider = pickCoReasoner(opts.providers, upgradeRequested, openAiSeat);
10690
10819
  const coReason = await coReasonThenInject({
10691
10820
  provider: coProvider,
@@ -10745,20 +10874,22 @@ ${condensed}`
10745
10874
  transcript,
10746
10875
  synthesis
10747
10876
  };
10877
+ if (moderatorUsed) result["moderator"] = moderatorUsed;
10748
10878
  if (personaMeta && personaMeta.used !== null) result["persona"] = personaMeta;
10749
10879
  if (compactionMeta) result["round_compaction"] = compactionMeta;
10750
10880
  if (upgraded) {
10751
10881
  result["reasoning_upgrade"] = {
10752
10882
  applied: true,
10753
- model: superRequested ? superPrimary("anthropic") ?? UPGRADE_MODEL : UPGRADE_MODEL,
10754
- label: UPGRADE_LABEL,
10883
+ provider: seat.provider.name,
10884
+ model: seat.provider.model,
10885
+ label: seat.label ?? seat.provider.model,
10755
10886
  seat: "moderator synthesis",
10756
10887
  ...coReasoned && coReasonerModel ? { co_reasoner: { model: coReasonerModel, label: coReasonLabel(coReasonerModel) } } : {}
10757
10888
  };
10758
10889
  } else if (upgradeRequested) {
10759
10890
  result["reasoning_upgrade"] = {
10760
10891
  applied: false,
10761
- reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
10892
+ reason: seat?.skipReason ?? "no moderator was available for the upgrade."
10762
10893
  };
10763
10894
  } else {
10764
10895
  const assessment = assessReasoning(topic);
@@ -11390,12 +11521,25 @@ function runListProviders(args, opts) {
11390
11521
  }
11391
11522
  const result = {
11392
11523
  providers,
11393
- moderator_default: opts.moderatorDefault ?? "anthropic",
11524
+ moderator_default: opts.moderatorDefault ?? DEFAULT_MODERATOR,
11525
+ // Who will actually moderate right now: the configured spec if that
11526
+ // provider has a key, else the first available (what the tools fall to).
11527
+ moderator: describeModerator(opts.providers ?? {}, opts.moderatorDefault),
11394
11528
  usage_hint: USAGE_HINT
11395
11529
  };
11396
11530
  if (superRequested) result["super_mode"] = true;
11397
11531
  return result;
11398
11532
  }
11533
+ function describeModerator(providers, spec) {
11534
+ const r = resolveModerator(providers, spec);
11535
+ const p = r.provider ?? Object.values(providers)[0];
11536
+ return {
11537
+ spec: spec ?? DEFAULT_MODERATOR,
11538
+ provider: p?.name ?? null,
11539
+ model: p?.model ?? null,
11540
+ available: r.provider !== void 0
11541
+ };
11542
+ }
11399
11543
 
11400
11544
  // src/tools/models.ts
11401
11545
  init_esm_shims();
@@ -12956,7 +13100,7 @@ var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
12956
13100
  var DEFAULT_PACKAGE = "crosscheck-cli";
12957
13101
  var FETCH_TIMEOUT_MS = 3e3;
12958
13102
  function engineVersion() {
12959
- return true ? "0.2.29" : "0.0.0-dev";
13103
+ return true ? "0.2.31" : "0.0.0-dev";
12960
13104
  }
12961
13105
  function defaultUpdateCachePath() {
12962
13106
  const base = process.env["CROSSCHECK_DATA_DIR"] || path10.join(os.homedir() || os.tmpdir(), ".crosscheck");
@@ -13403,7 +13547,7 @@ var HELP_CATALOG = [
13403
13547
  primaryArg: "topic",
13404
13548
  summary: "Bounded multi-round debate between models, ending in a synthesized recommendation.",
13405
13549
  example: "xc debate should we adopt a monorepo?",
13406
- detail: `The panel argues across a few rounds \u2014 each model sees the others' prior turns, concedes or pushes back \u2014 then a moderator synthesizes a single grounded recommendation. Supports reasoning:"max" to bump the moderator synthesis seat to Fable 5. Great for contentious or trade-off-heavy calls.`
13550
+ detail: `The panel argues across a few rounds \u2014 each model sees the others' prior turns, concedes or pushes back \u2014 then a moderator synthesizes a single grounded recommendation. Any model can moderate \u2014 pass moderator:"openai" or an exact model (moderator:"openai:gpt-6-sol"), or set a default with \`crosscheck moderator set\`. reasoning:"max" bumps the moderator's synthesis to its provider's flagship (Fable 5 for Anthropic). Great for contentious or trade-off-heavy calls.`
13407
13551
  },
13408
13552
  {
13409
13553
  name: "coordinate",
@@ -13646,6 +13790,10 @@ function renderHelpCommand(entry) {
13646
13790
  }
13647
13791
 
13648
13792
  // src/tools/index.ts
13793
+ var MODERATOR_PARAM = {
13794
+ type: "string",
13795
+ description: 'Who moderates (synthesises) this run: a provider ("openai") or an exact model ("openai:gpt-6-sol"). Any configured provider works. Defaults to the configured moderator (CROSSCHECK_MODERATOR / config `moderator`).'
13796
+ };
13649
13797
  function defineTool(opts) {
13650
13798
  return {
13651
13799
  name: opts.name,
@@ -13880,7 +14028,7 @@ function createTool(o, toolName, cheapDefault) {
13880
14028
  instruction: { type: "string" },
13881
14029
  documents: { type: "array", items: { type: "string" } },
13882
14030
  providers: { type: "array", items: { type: "string" } },
13883
- moderator: { type: "string" },
14031
+ moderator: MODERATOR_PARAM,
13884
14032
  constraints: { type: "string" },
13885
14033
  target_path: { type: "string" },
13886
14034
  session_id: { type: "string" },
@@ -13921,7 +14069,7 @@ function orchestrateTool(providers, allowlist, moderator, storage, pricing, brea
13921
14069
  dag: { type: "object", additionalProperties: true },
13922
14070
  context: { type: "string" },
13923
14071
  providers: { type: "array", items: { type: "string" } },
13924
- moderator: { type: "string" },
14072
+ moderator: MODERATOR_PARAM,
13925
14073
  fail_fast: { type: "boolean" },
13926
14074
  plan_only: { type: "boolean" },
13927
14075
  cheap_mode: { type: "boolean" },
@@ -14300,7 +14448,7 @@ function planTool(providers, allowlist, storage, breakers, transcriptsDir, repoR
14300
14448
  constraints: { type: "string" },
14301
14449
  context: { type: "string" },
14302
14450
  providers: { type: "array", items: { type: "string" } },
14303
- moderator: { type: "string" },
14451
+ moderator: MODERATOR_PARAM,
14304
14452
  session_id: { type: "string" },
14305
14453
  structured: { type: "boolean" },
14306
14454
  mode: { type: "string", enum: ["fast", "thorough"] },
@@ -14362,7 +14510,7 @@ function coordinateTool(providers, allowlist, innerCallers, storage, breakers, t
14362
14510
  providers: { type: "array", items: { type: "string" } },
14363
14511
  proposer: { type: "string" },
14364
14512
  synthesizer: { type: "string" },
14365
- moderator: { type: "string" },
14513
+ moderator: MODERATOR_PARAM,
14366
14514
  critics: { type: "array", items: { type: "string" } },
14367
14515
  session_id: { type: "string" },
14368
14516
  untrusted_input: { type: "boolean" },
@@ -14395,7 +14543,7 @@ function debateTool(providers, allowlist, innerCallers, storage, pricing, breake
14395
14543
  context: { type: "string" },
14396
14544
  max_rounds: { type: "integer", minimum: 1 },
14397
14545
  providers: { type: "array", items: { type: "string" } },
14398
- moderator: { type: "string" },
14546
+ moderator: MODERATOR_PARAM,
14399
14547
  session_id: { type: "string" },
14400
14548
  reasoning: { type: "string", enum: ["default", "max"], description: '"max" bumps the moderator-synthesis seat to Fable 5 (premium thinking model); the debaters stay on their normal models. Omit for the Opus 4.8 default.' },
14401
14549
  structured: { type: "boolean" },
@@ -15398,8 +15546,8 @@ function mapConfig(raw, sourcePath) {
15398
15546
  if (pa === null) result.providerAllowlist = null;
15399
15547
  else if (strArr(pa) !== void 0) result.providerAllowlist = strArr(pa);
15400
15548
  }
15401
- const moderator = str(raw["moderator"]);
15402
- if (moderator) result.moderatorDefault = moderator.toLowerCase();
15549
+ const moderator = parseModeratorSpec(raw["moderator"]);
15550
+ if (moderator) result.moderatorDefault = formatModeratorSpec(moderator);
15403
15551
  const bench = asObj(raw["bench"]);
15404
15552
  const goldens = bench ? str(bench["goldens_dir"]) : void 0;
15405
15553
  if (goldens) result.benchGoldensDir = goldens;
@@ -15603,7 +15751,9 @@ async function main() {
15603
15751
  if (configFile?.providerAllowlist !== void 0) {
15604
15752
  serverOpts.providerAllowlist = configFile.providerAllowlist;
15605
15753
  }
15606
- if (configFile?.moderatorDefault) serverOpts.moderatorDefault = configFile.moderatorDefault;
15754
+ const envModerator = parseModeratorSpec(process.env["CROSSCHECK_MODERATOR"]);
15755
+ const moderatorDefault = envModerator ? formatModeratorSpec(envModerator) : configFile?.moderatorDefault;
15756
+ if (moderatorDefault) serverOpts.moderatorDefault = moderatorDefault;
15607
15757
  if (configFile?.benchGoldensDir) serverOpts.benchGoldensDir = configFile.benchGoldensDir;
15608
15758
  if (configFile?.eventsPath) serverOpts.eventsPath = configFile.eventsPath;
15609
15759
  await connectAndServe(transport, serverOpts);