crosscheck-mcp 0.2.30 → 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.
@@ -2492,6 +2492,7 @@ Routing rules:
2492
2492
  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\`.
2493
2493
  3. Preserve the user's wording verbatim in the argument \u2014 do not paraphrase, summarize, or "clean up" the prompt before forwarding.
2494
2494
  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.
2495
+ 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.
2495
2496
 
2496
2497
  Routable tool names: ${ROUTABLE_TOOLS.join(", ")}.
2497
2498
 
@@ -2529,7 +2530,7 @@ var import_zod = require("zod");
2529
2530
  // src/server-meta.ts
2530
2531
  init_cjs_shims();
2531
2532
  var SERVER_NAME = "crosscheck-agent";
2532
- var SERVER_VERSION = true ? "0.2.30" : "0.0.0-dev";
2533
+ var SERVER_VERSION = true ? "0.2.31" : "0.0.0-dev";
2533
2534
 
2534
2535
  // src/tools/audit.ts
2535
2536
  init_cjs_shims();
@@ -4163,6 +4164,231 @@ function numberOrNull(v) {
4163
4164
  // src/tools/audit.ts
4164
4165
  var import_node_perf_hooks = require("perf_hooks");
4165
4166
 
4167
+ // src/core/moderator.ts
4168
+ init_cjs_shims();
4169
+
4170
+ // src/core/super-mode.ts
4171
+ init_cjs_shims();
4172
+ var import_node_async_hooks = require("async_hooks");
4173
+
4174
+ // src/core/reasoning-upgrade.ts
4175
+ init_cjs_shims();
4176
+ var UPGRADE_MODEL = "claude-fable-5";
4177
+ var UPGRADE_LABEL = "Fable 5";
4178
+ function isUpgradeRequested(args) {
4179
+ if (args["upgrade"] === true) return true;
4180
+ const reasoning = args["reasoning"];
4181
+ if (typeof reasoning === "string" && reasoning.trim().toLowerCase() === "max") {
4182
+ return true;
4183
+ }
4184
+ const model = args["model"];
4185
+ if (typeof model === "string") {
4186
+ const m = model.trim().toLowerCase();
4187
+ if (m === "fable5" || m === "fable-5" || m === "fable" || m === UPGRADE_MODEL) {
4188
+ return true;
4189
+ }
4190
+ }
4191
+ return false;
4192
+ }
4193
+ var HEAVY_TERMS = [
4194
+ /\bprov(e|ing|en)\b|\bproof\b|\btheorem\b|\bderiv(e|ation)\b/i,
4195
+ /\barchitect(ure|ing)?\b|\bdesign\s+(a|the|an)\b|\bhigh[-\s]?level\s+design\b/i,
4196
+ /\btrade[-\s]?offs?\b|\bpros?\s+and\s+cons?\b|\bweigh\b/i,
4197
+ /\boptimi[sz]e\b|\bcomplexity\b|\bO\(|\balgorithm(ic)?\b/i,
4198
+ /\brace\s+condition\b|\bconcurren(t|cy)\b|\bdeadlock\b|\bdistributed\b|\bconsensus\b/i,
4199
+ /\broot[-\s]?cause\b|\bdebug(ging)?\b|\bwhy\s+(does|is|would|might)\b/i,
4200
+ /\bsecurity\b|\bthreat\s+model\b|\bexploit\b|\bvulnerabilit(y|ies)\b/i,
4201
+ /\bmigrat(e|ion)\b|\brefactor\b|\bre[-\s]?architect\b/i,
4202
+ /\binvariant\b|\bcorrectness\b|\bedge\s+cases?\b|\bformal(ly)?\b/i,
4203
+ /\bstrateg(y|ic|ies)\b|\bplan\s+(the|a|an|out)\b|\broadmap\b/i,
4204
+ /\breason\s+(through|about)\b|\bstep[-\s]?by[-\s]?step\b|\bthink\s+deeply\b/i,
4205
+ /\bnp[-\s]?(hard|complete)\b|\bgraph\b|\bstate\s+machine\b|\bconstraint\b/i
4206
+ ];
4207
+ var DIFFICULTY_TERMS = /\b(hard|tricky|subtle|complex|complicated|non[-\s]?trivial|thorny|gnarly|difficult|deep)\b/i;
4208
+ function assessReasoning(text) {
4209
+ const s = typeof text === "string" ? text : "";
4210
+ const signals = [];
4211
+ let score = 0;
4212
+ let termHits = 0;
4213
+ for (const re of HEAVY_TERMS) {
4214
+ if (re.test(s)) termHits += 1;
4215
+ }
4216
+ if (termHits > 0) {
4217
+ const add = Math.min(termHits, 4);
4218
+ score += add;
4219
+ signals.push(`${termHits} reasoning cue${termHits === 1 ? "" : "s"}`);
4220
+ }
4221
+ if (DIFFICULTY_TERMS.test(s)) {
4222
+ score += 1;
4223
+ signals.push("explicit difficulty marker");
4224
+ }
4225
+ const words = s.split(/\s+/).filter(Boolean).length;
4226
+ if (words >= 120) {
4227
+ score += 2;
4228
+ signals.push("long, detailed prompt");
4229
+ } else if (words >= 50) {
4230
+ score += 1;
4231
+ signals.push("substantial prompt");
4232
+ }
4233
+ const constraints = (s.match(/\b(must|should|need to|ensure|require[ds]?|constraint)\b/gi) ?? []).length + (s.match(/(^|\n)\s*(?:[-*]|\d+[.)])\s+/g) ?? []).length;
4234
+ if (constraints >= 3) {
4235
+ score += 1;
4236
+ signals.push(`${constraints} constraints/steps`);
4237
+ }
4238
+ const heavy = score >= 3;
4239
+ const reason = heavy ? `Task looks reasoning-heavy (${signals.join(", ") || "score " + score}).` : `Task looks routine (score ${score}).`;
4240
+ return { heavy, score, reason, signals };
4241
+ }
4242
+ function buildSuggestedUpgrade(toolName, assessment) {
4243
+ return {
4244
+ model: UPGRADE_MODEL,
4245
+ label: UPGRADE_LABEL,
4246
+ reason: assessment.reason,
4247
+ 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.`
4248
+ };
4249
+ }
4250
+
4251
+ // src/core/super-mode.ts
4252
+ var SUPER_MODELS = {
4253
+ // Fable 5.1 verified present on the account; 5 as the fallback for accounts
4254
+ // that haven't been granted 5.1 yet.
4255
+ anthropic: ["claude-fable-5-1", UPGRADE_MODEL],
4256
+ // Astra is live (verified against the account's model list 2026-09-23).
4257
+ // The fallback is named explicitly rather than reusing CO_REASON_MODEL:
4258
+ // that constant means "OpenAI's top reasoning model" for the max-reasoning
4259
+ // co-reasoner, which is a different question from "what should super fall
4260
+ // back to". Sharing the symbol silently coupled two unrelated decisions.
4261
+ openai: ["gpt-6-astra", "gpt-6-sol"],
4262
+ // Explicitly left as-is per product decision. Note grok-4-latest is not in
4263
+ // xAI's published model list, but it resolves in practice — real panel calls
4264
+ // on it succeeded as recently as 2026-08-30 — so it is an unlisted alias
4265
+ // rather than a dead id.
4266
+ xai: [DEFAULT_MODELS["xai"]],
4267
+ // 3.8 Flash is now BOTH the everyday default and the super pick for Gemini,
4268
+ // so this can no longer borrow DEFAULT_MODELS as its fallback — that would
4269
+ // repeat the same model and retry one that just failed. Named explicitly.
4270
+ gemini: ["gemini-3.8-flash", "gemini-3.1-pro-preview"],
4271
+ kimi: [DEFAULT_MODELS["kimi"]],
4272
+ qwen: [DEFAULT_MODELS["qwen"]]
4273
+ };
4274
+ var SUPER_ENV_VARS = {
4275
+ anthropic: "ANTHROPIC_SUPER_MODEL",
4276
+ openai: "OPENAI_SUPER_MODEL",
4277
+ xai: "XAI_SUPER_MODEL",
4278
+ gemini: "GEMINI_SUPER_MODEL",
4279
+ kimi: "KIMI_SUPER_MODEL",
4280
+ qwen: "QWEN_SUPER_MODEL",
4281
+ mistral: "MISTRAL_SUPER_MODEL",
4282
+ groq: "GROQ_SUPER_MODEL",
4283
+ deepseek: "DEEPSEEK_SUPER_MODEL"
4284
+ };
4285
+ function parseChain(v) {
4286
+ if (!v) return [];
4287
+ const out = [];
4288
+ for (const raw of v.split(",")) {
4289
+ const m = raw.trim();
4290
+ if (m && !out.includes(m)) out.push(m);
4291
+ }
4292
+ return out;
4293
+ }
4294
+ var superEnvStore = new import_node_async_hooks.AsyncLocalStorage();
4295
+ var currentEnv = () => superEnvStore.getStore() ?? process.env;
4296
+ function superChainFor(provider, env = currentEnv()) {
4297
+ const varName = SUPER_ENV_VARS[provider];
4298
+ const user = varName ? parseChain(env[varName]) : [];
4299
+ if (user.length > 0) {
4300
+ const fallbacks = varName ? parseChain(env[`${varName}_FALLBACKS`]) : [];
4301
+ return [...user, ...fallbacks.filter((m) => !user.includes(m))];
4302
+ }
4303
+ return SUPER_MODELS[provider] ?? [];
4304
+ }
4305
+ function superPrimary(provider, env = currentEnv()) {
4306
+ return superChainFor(provider, env)[0];
4307
+ }
4308
+ var SUPER_PROVIDER_NAMES = Object.keys(SUPER_MODELS);
4309
+ function isSuperRequested(args) {
4310
+ return args["super"] === true;
4311
+ }
4312
+ function retargetForSuper(p, env = currentEnv()) {
4313
+ const chain = superChainFor(p.name, env);
4314
+ if (chain.length === 0) return p;
4315
+ const [primary, ...fallbacks] = chain;
4316
+ return retargetProvider(p, primary, fallbacks);
4317
+ }
4318
+ var SUPER_BUDGET_MULTIPLIER = 3;
4319
+ var SUPER_BUDGET_CEILING = 16e3;
4320
+ function superBudget(base) {
4321
+ return Math.min(base * SUPER_BUDGET_MULTIPLIER, SUPER_BUDGET_CEILING);
4322
+ }
4323
+
4324
+ // src/core/moderator.ts
4325
+ var DEFAULT_MODERATOR = "anthropic";
4326
+ var PROVIDER_ALIASES = {
4327
+ grok: "xai",
4328
+ claude: "anthropic",
4329
+ google: "gemini",
4330
+ chatgpt: "openai",
4331
+ gpt: "openai",
4332
+ moonshot: "kimi"
4333
+ };
4334
+ function parseModeratorSpec(raw) {
4335
+ if (typeof raw !== "string") return null;
4336
+ const s = raw.trim();
4337
+ if (!s) return null;
4338
+ const i = s.indexOf(":");
4339
+ const providerPart = (i < 0 ? s : s.slice(0, i)).trim().toLowerCase();
4340
+ const modelPart = i < 0 ? "" : s.slice(i + 1).trim();
4341
+ if (!providerPart) return null;
4342
+ return {
4343
+ provider: PROVIDER_ALIASES[providerPart] ?? providerPart,
4344
+ model: modelPart || null
4345
+ };
4346
+ }
4347
+ function formatModeratorSpec(spec) {
4348
+ return spec.model ? `${spec.provider}:${spec.model}` : spec.provider;
4349
+ }
4350
+ function pickModeratorSpec(...candidates) {
4351
+ for (const c of candidates) {
4352
+ const s = parseModeratorSpec(c);
4353
+ if (s) return s;
4354
+ }
4355
+ return { provider: DEFAULT_MODERATOR, model: null };
4356
+ }
4357
+ function resolveModerator(providers, ...candidates) {
4358
+ const spec = pickModeratorSpec(...candidates);
4359
+ const base = providers[spec.provider];
4360
+ if (!base) return { provider: void 0, spec, pinned: false };
4361
+ if (!spec.model) return { provider: base, spec, pinned: false };
4362
+ return { provider: retargetProvider(base, spec.model), spec, pinned: true };
4363
+ }
4364
+ function synthesisSeat(o) {
4365
+ const { moderator, pinned, superRequested, upgradeRequested } = o;
4366
+ const wantsMore = superRequested || upgradeRequested;
4367
+ if (!wantsMore) return { provider: moderator, upgraded: false, label: null, skipReason: null };
4368
+ if (pinned) {
4369
+ return {
4370
+ provider: moderator,
4371
+ upgraded: false,
4372
+ label: null,
4373
+ skipReason: `the moderator is pinned to ${moderator.name}:${moderator.model}; a pinned model is used as-is.`
4374
+ };
4375
+ }
4376
+ if (moderator.name === "anthropic" && !superRequested) {
4377
+ return { provider: retargetProvider(moderator, UPGRADE_MODEL), upgraded: true, label: UPGRADE_LABEL, skipReason: null };
4378
+ }
4379
+ if (superChainFor(moderator.name).length === 0) {
4380
+ return {
4381
+ provider: moderator,
4382
+ upgraded: false,
4383
+ label: null,
4384
+ skipReason: `no flagship model is known for ${moderator.name}; the moderator ran at ${moderator.model}.`
4385
+ };
4386
+ }
4387
+ const seat = retargetForSuper(moderator);
4388
+ const label = moderator.name === "anthropic" ? UPGRADE_LABEL : seat.model;
4389
+ return { provider: seat, upgraded: true, label, skipReason: null };
4390
+ }
4391
+
4166
4392
  // src/core/co-reason.ts
4167
4393
  init_cjs_shims();
4168
4394
  var CO_REASON_MODEL = "gpt-6-sol";
@@ -4341,83 +4567,6 @@ function selectForDifficulty(args) {
4341
4567
  };
4342
4568
  }
4343
4569
 
4344
- // src/core/reasoning-upgrade.ts
4345
- init_cjs_shims();
4346
- var UPGRADE_MODEL = "claude-fable-5";
4347
- var UPGRADE_LABEL = "Fable 5";
4348
- function isUpgradeRequested(args) {
4349
- if (args["upgrade"] === true) return true;
4350
- const reasoning = args["reasoning"];
4351
- if (typeof reasoning === "string" && reasoning.trim().toLowerCase() === "max") {
4352
- return true;
4353
- }
4354
- const model = args["model"];
4355
- if (typeof model === "string") {
4356
- const m = model.trim().toLowerCase();
4357
- if (m === "fable5" || m === "fable-5" || m === "fable" || m === UPGRADE_MODEL) {
4358
- return true;
4359
- }
4360
- }
4361
- return false;
4362
- }
4363
- var HEAVY_TERMS = [
4364
- /\bprov(e|ing|en)\b|\bproof\b|\btheorem\b|\bderiv(e|ation)\b/i,
4365
- /\barchitect(ure|ing)?\b|\bdesign\s+(a|the|an)\b|\bhigh[-\s]?level\s+design\b/i,
4366
- /\btrade[-\s]?offs?\b|\bpros?\s+and\s+cons?\b|\bweigh\b/i,
4367
- /\boptimi[sz]e\b|\bcomplexity\b|\bO\(|\balgorithm(ic)?\b/i,
4368
- /\brace\s+condition\b|\bconcurren(t|cy)\b|\bdeadlock\b|\bdistributed\b|\bconsensus\b/i,
4369
- /\broot[-\s]?cause\b|\bdebug(ging)?\b|\bwhy\s+(does|is|would|might)\b/i,
4370
- /\bsecurity\b|\bthreat\s+model\b|\bexploit\b|\bvulnerabilit(y|ies)\b/i,
4371
- /\bmigrat(e|ion)\b|\brefactor\b|\bre[-\s]?architect\b/i,
4372
- /\binvariant\b|\bcorrectness\b|\bedge\s+cases?\b|\bformal(ly)?\b/i,
4373
- /\bstrateg(y|ic|ies)\b|\bplan\s+(the|a|an|out)\b|\broadmap\b/i,
4374
- /\breason\s+(through|about)\b|\bstep[-\s]?by[-\s]?step\b|\bthink\s+deeply\b/i,
4375
- /\bnp[-\s]?(hard|complete)\b|\bgraph\b|\bstate\s+machine\b|\bconstraint\b/i
4376
- ];
4377
- var DIFFICULTY_TERMS = /\b(hard|tricky|subtle|complex|complicated|non[-\s]?trivial|thorny|gnarly|difficult|deep)\b/i;
4378
- function assessReasoning(text) {
4379
- const s = typeof text === "string" ? text : "";
4380
- const signals = [];
4381
- let score = 0;
4382
- let termHits = 0;
4383
- for (const re of HEAVY_TERMS) {
4384
- if (re.test(s)) termHits += 1;
4385
- }
4386
- if (termHits > 0) {
4387
- const add = Math.min(termHits, 4);
4388
- score += add;
4389
- signals.push(`${termHits} reasoning cue${termHits === 1 ? "" : "s"}`);
4390
- }
4391
- if (DIFFICULTY_TERMS.test(s)) {
4392
- score += 1;
4393
- signals.push("explicit difficulty marker");
4394
- }
4395
- const words = s.split(/\s+/).filter(Boolean).length;
4396
- if (words >= 120) {
4397
- score += 2;
4398
- signals.push("long, detailed prompt");
4399
- } else if (words >= 50) {
4400
- score += 1;
4401
- signals.push("substantial prompt");
4402
- }
4403
- const constraints = (s.match(/\b(must|should|need to|ensure|require[ds]?|constraint)\b/gi) ?? []).length + (s.match(/(^|\n)\s*(?:[-*]|\d+[.)])\s+/g) ?? []).length;
4404
- if (constraints >= 3) {
4405
- score += 1;
4406
- signals.push(`${constraints} constraints/steps`);
4407
- }
4408
- const heavy = score >= 3;
4409
- const reason = heavy ? `Task looks reasoning-heavy (${signals.join(", ") || "score " + score}).` : `Task looks routine (score ${score}).`;
4410
- return { heavy, score, reason, signals };
4411
- }
4412
- function buildSuggestedUpgrade(toolName, assessment) {
4413
- return {
4414
- model: UPGRADE_MODEL,
4415
- label: UPGRADE_LABEL,
4416
- reason: assessment.reason,
4417
- 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.`
4418
- };
4419
- }
4420
-
4421
4570
  // src/tools/audit.ts
4422
4571
  var AUDIT_OBVIOUS_FAILURE_HIGH = 0.3;
4423
4572
  var AUDIT_OBVIOUS_FAILURE_MED = 0.2;
@@ -4715,9 +4864,14 @@ ${rubricText}`;
4715
4864
  );
4716
4865
  }
4717
4866
  const upgradeRequested = isUpgradeRequested(args);
4718
- const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
4719
- const auditorForCall = upgraded ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : auditor;
4720
- const coProvider = pickCoReasoner(opts.providers, upgradeRequested);
4867
+ const seat = synthesisSeat({ moderator: auditor, pinned: false, superRequested: false, upgradeRequested });
4868
+ const upgraded = seat.upgraded;
4869
+ const auditorForCall = seat.provider;
4870
+ const coProvider = pickCoReasoner(
4871
+ opts.providers,
4872
+ upgradeRequested,
4873
+ auditorForCall.name === "openai" ? auditorForCall.model : void 0
4874
+ );
4721
4875
  const coReason = await coReasonThenInject({
4722
4876
  provider: coProvider,
4723
4877
  messages: msgs,
@@ -4800,15 +4954,16 @@ ${rubricText}`;
4800
4954
  if (upgraded) {
4801
4955
  result["reasoning_upgrade"] = {
4802
4956
  applied: true,
4803
- model: UPGRADE_MODEL,
4804
- label: UPGRADE_LABEL,
4957
+ provider: auditorForCall.name,
4958
+ model: auditorForCall.model,
4959
+ label: seat.label ?? auditorForCall.model,
4805
4960
  seat: "auditor",
4806
4961
  ...coReasoned ? { co_reasoner: { model: coProvider.model, label: coReasonLabel(coProvider.model) } } : {}
4807
4962
  };
4808
4963
  } else if (upgradeRequested) {
4809
4964
  result["reasoning_upgrade"] = {
4810
4965
  applied: false,
4811
- reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
4966
+ reason: seat.skipReason ?? "the auditor couldn't be upgraded."
4812
4967
  };
4813
4968
  } else {
4814
4969
  const assessment = assessReasoning(
@@ -4891,7 +5046,7 @@ function pickAuditor(providers, exclude, explicit, moderatorName, allowlist, che
4891
5046
  }
4892
5047
  }
4893
5048
  }
4894
- const mod = providers[moderatorName.toLowerCase()];
5049
+ const mod = resolveModerator(providers, moderatorName).provider;
4895
5050
  if (mod !== void 0 && !exclude.has(mod.name) && (allowlist === null || allowlist.includes(mod.name))) {
4896
5051
  return { auditor: mod, reason: null };
4897
5052
  }
@@ -5216,9 +5371,7 @@ function pickPanelJudge(providers, moderatorName, pricing) {
5216
5371
  return entry.model && entry.model !== base.model ? retargetProvider(base, entry.model) : base;
5217
5372
  }
5218
5373
  }
5219
- const mod = providers[moderatorName.toLowerCase()];
5220
- if (mod !== void 0) return mod;
5221
- return null;
5374
+ return resolveModerator(providers, moderatorName).provider ?? null;
5222
5375
  }
5223
5376
  var AGREEMENT_CHECK_SCHEMA = {
5224
5377
  type: "object",
@@ -5536,81 +5689,6 @@ async function pickAutoPanel(storage, purpose, n, providers, allowlist) {
5536
5689
  return { providers: ranked.slice(0, Math.max(1, n)), coldStart: true };
5537
5690
  }
5538
5691
 
5539
- // src/core/super-mode.ts
5540
- init_cjs_shims();
5541
- var import_node_async_hooks = require("async_hooks");
5542
- var SUPER_MODELS = {
5543
- // Fable 5.1 verified present on the account; 5 as the fallback for accounts
5544
- // that haven't been granted 5.1 yet.
5545
- anthropic: ["claude-fable-5-1", UPGRADE_MODEL],
5546
- // Astra is live (verified against the account's model list 2026-09-23).
5547
- // The fallback is named explicitly rather than reusing CO_REASON_MODEL:
5548
- // that constant means "OpenAI's top reasoning model" for the max-reasoning
5549
- // co-reasoner, which is a different question from "what should super fall
5550
- // back to". Sharing the symbol silently coupled two unrelated decisions.
5551
- openai: ["gpt-6-astra", "gpt-6-sol"],
5552
- // Explicitly left as-is per product decision. Note grok-4-latest is not in
5553
- // xAI's published model list, but it resolves in practice — real panel calls
5554
- // on it succeeded as recently as 2026-08-30 — so it is an unlisted alias
5555
- // rather than a dead id.
5556
- xai: [DEFAULT_MODELS["xai"]],
5557
- // 3.8 Flash is now BOTH the everyday default and the super pick for Gemini,
5558
- // so this can no longer borrow DEFAULT_MODELS as its fallback — that would
5559
- // repeat the same model and retry one that just failed. Named explicitly.
5560
- gemini: ["gemini-3.8-flash", "gemini-3.1-pro-preview"],
5561
- kimi: [DEFAULT_MODELS["kimi"]],
5562
- qwen: [DEFAULT_MODELS["qwen"]]
5563
- };
5564
- var SUPER_ENV_VARS = {
5565
- anthropic: "ANTHROPIC_SUPER_MODEL",
5566
- openai: "OPENAI_SUPER_MODEL",
5567
- xai: "XAI_SUPER_MODEL",
5568
- gemini: "GEMINI_SUPER_MODEL",
5569
- kimi: "KIMI_SUPER_MODEL",
5570
- qwen: "QWEN_SUPER_MODEL",
5571
- mistral: "MISTRAL_SUPER_MODEL",
5572
- groq: "GROQ_SUPER_MODEL",
5573
- deepseek: "DEEPSEEK_SUPER_MODEL"
5574
- };
5575
- function parseChain(v) {
5576
- if (!v) return [];
5577
- const out = [];
5578
- for (const raw of v.split(",")) {
5579
- const m = raw.trim();
5580
- if (m && !out.includes(m)) out.push(m);
5581
- }
5582
- return out;
5583
- }
5584
- var superEnvStore = new import_node_async_hooks.AsyncLocalStorage();
5585
- var currentEnv = () => superEnvStore.getStore() ?? process.env;
5586
- function superChainFor(provider, env = currentEnv()) {
5587
- const varName = SUPER_ENV_VARS[provider];
5588
- const user = varName ? parseChain(env[varName]) : [];
5589
- if (user.length > 0) {
5590
- const fallbacks = varName ? parseChain(env[`${varName}_FALLBACKS`]) : [];
5591
- return [...user, ...fallbacks.filter((m) => !user.includes(m))];
5592
- }
5593
- return SUPER_MODELS[provider] ?? [];
5594
- }
5595
- function superPrimary(provider, env = currentEnv()) {
5596
- return superChainFor(provider, env)[0];
5597
- }
5598
- var SUPER_PROVIDER_NAMES = Object.keys(SUPER_MODELS);
5599
- function isSuperRequested(args) {
5600
- return args["super"] === true;
5601
- }
5602
- function retargetForSuper(p, env = currentEnv()) {
5603
- const chain = superChainFor(p.name, env);
5604
- if (chain.length === 0) return p;
5605
- const [primary, ...fallbacks] = chain;
5606
- return retargetProvider(p, primary, fallbacks);
5607
- }
5608
- var SUPER_BUDGET_MULTIPLIER = 3;
5609
- var SUPER_BUDGET_CEILING = 16e3;
5610
- function superBudget(base) {
5611
- return Math.min(base * SUPER_BUDGET_MULTIPLIER, SUPER_BUDGET_CEILING);
5612
- }
5613
-
5614
5692
  // src/core/session-memory.ts
5615
5693
  init_cjs_shims();
5616
5694
  var SESSION_MEMORY_DEFAULT_LIMIT = 50;
@@ -7986,8 +8064,9 @@ async function runOrchestrate(args, opts) {
7986
8064
  blocked
7987
8065
  };
7988
8066
  }
7989
- const moderatorName = typeof args["moderator"] === "string" && args["moderator"] ? args["moderator"] : opts.moderator ?? "anthropic";
7990
- const moderator = opts.providers[moderatorName.toLowerCase()] ?? selected[0];
8067
+ const resolvedModerator = resolveModerator(opts.providers, args["moderator"], opts.moderator);
8068
+ const moderator = resolvedModerator.provider ?? selected[0];
8069
+ const moderatorChosen = resolvedModerator.provider !== void 0 && (resolvedModerator.pinned || parseModeratorSpec(args["moderator"]) !== null);
7991
8070
  const failFast = boolArg2(args["fail_fast"], false);
7992
8071
  const planOnly = boolArg2(args["plan_only"], false);
7993
8072
  const cheapMode = boolArg2(args["cheap_mode"], opts.ctx?.cheap_mode ?? false);
@@ -8471,11 +8550,17 @@ ${recombineSys}` : recombineSys },
8471
8550
  let synthProvider = moderator;
8472
8551
  let synthModel = null;
8473
8552
  const upgradeRequested = isUpgradeRequested(args);
8474
- const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
8553
+ const seat = synthesisSeat({
8554
+ moderator,
8555
+ pinned: resolvedModerator.provider !== void 0 && resolvedModerator.pinned,
8556
+ superRequested: false,
8557
+ upgradeRequested
8558
+ });
8559
+ const upgraded = seat.upgraded;
8475
8560
  if (upgraded) {
8476
- synthProvider = retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL);
8477
- synthModel = `anthropic/${UPGRADE_MODEL}`;
8478
- } else if (cheapMode && opts.pricing) {
8561
+ synthProvider = seat.provider;
8562
+ synthModel = `${seat.provider.name}/${seat.provider.model}`;
8563
+ } else if (cheapMode && opts.pricing && !moderatorChosen) {
8479
8564
  const pick = selectForDifficulty({
8480
8565
  pricing: opts.pricing,
8481
8566
  tier: "med",
@@ -8501,7 +8586,11 @@ ${recombineSys}` : recombineSys },
8501
8586
  provider: synthProvider.name
8502
8587
  } }
8503
8588
  );
8504
- const coProvider = pickCoReasoner(opts.providers, upgradeRequested);
8589
+ const coProvider = pickCoReasoner(
8590
+ opts.providers,
8591
+ upgradeRequested,
8592
+ synthProvider.name === "openai" ? synthProvider.model : void 0
8593
+ );
8505
8594
  const coReason = await coReasonThenInject({
8506
8595
  provider: coProvider,
8507
8596
  messages: recMsgs,
@@ -8551,7 +8640,8 @@ ${recombineSys}` : recombineSys },
8551
8640
  missing,
8552
8641
  partial,
8553
8642
  fail_fast: failFast,
8554
- cheap_mode: cheapMode
8643
+ cheap_mode: cheapMode,
8644
+ moderator: { provider: moderator.name, model: moderator.model }
8555
8645
  };
8556
8646
  if (personaMeta.used !== null) result["persona"] = personaMeta;
8557
8647
  if (synthModel) result["synth_model"] = synthModel;
@@ -8559,15 +8649,16 @@ ${recombineSys}` : recombineSys },
8559
8649
  if (upgraded) {
8560
8650
  result["reasoning_upgrade"] = {
8561
8651
  applied: true,
8562
- model: UPGRADE_MODEL,
8563
- label: UPGRADE_LABEL,
8652
+ provider: seat.provider.name,
8653
+ model: seat.provider.model,
8654
+ label: seat.label ?? seat.provider.model,
8564
8655
  seat: "recombine/synthesis",
8565
8656
  ...coReasoned ? { co_reasoner: { model: coProvider.model, label: coReasonLabel(coProvider.model) } } : {}
8566
8657
  };
8567
8658
  } else if (upgradeRequested) {
8568
8659
  result["reasoning_upgrade"] = {
8569
8660
  applied: false,
8570
- reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
8661
+ reason: seat.skipReason ?? "the moderator couldn't be upgraded."
8571
8662
  };
8572
8663
  } else if (goal) {
8573
8664
  const assessment = assessReasoning(goal);
@@ -9752,13 +9843,24 @@ async function runCoordinate(args, opts) {
9752
9843
  };
9753
9844
  }
9754
9845
  const proposerName = typeof args["proposer"] === "string" && args["proposer"] ? args["proposer"] : selected[0].name;
9755
- const synthName = typeof args["synthesizer"] === "string" && args["synthesizer"] ? args["synthesizer"] : typeof args["moderator"] === "string" && args["moderator"] ? args["moderator"] : opts.moderator ?? "anthropic";
9846
+ const resolvedSynth = resolveModerator(
9847
+ opts.providers,
9848
+ args["synthesizer"],
9849
+ args["moderator"],
9850
+ opts.moderator
9851
+ );
9756
9852
  const proposer = opts.providers[proposerName.toLowerCase()] ?? selected[0];
9757
- const synth = opts.providers[synthName.toLowerCase()] ?? proposer;
9853
+ const synth = resolvedSynth.provider ?? proposer;
9758
9854
  const proposerForCall = superRequested ? retargetForSuper(proposer) : proposer;
9759
9855
  const upgradeRequested = isUpgradeRequested(args) || superRequested;
9760
- const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
9761
- const synthForCall = superRequested && opts.providers["anthropic"] ? retargetForSuper(opts.providers["anthropic"]) : upgraded ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : synth;
9856
+ const seat = synthesisSeat({
9857
+ moderator: synth,
9858
+ pinned: resolvedSynth.provider !== void 0 && resolvedSynth.pinned,
9859
+ superRequested,
9860
+ upgradeRequested
9861
+ });
9862
+ const upgraded = seat.upgraded;
9863
+ const synthForCall = seat.provider;
9762
9864
  let critics = [];
9763
9865
  if (Array.isArray(args["critics"])) {
9764
9866
  for (const n of args["critics"]) {
@@ -9909,7 +10011,11 @@ CRITIQUES:
9909
10011
  ${critiqueBlock}`
9910
10012
  }
9911
10013
  ];
9912
- const coProvider = pickCoReasoner(opts.providers, upgradeRequested);
10014
+ const coProvider = pickCoReasoner(
10015
+ opts.providers,
10016
+ upgradeRequested,
10017
+ synthForCall.name === "openai" ? synthForCall.model : void 0
10018
+ );
9913
10019
  const coReason = await coReasonThenInject({
9914
10020
  provider: coProvider,
9915
10021
  messages: synthMessages,
@@ -9954,7 +10060,8 @@ ${critiqueBlock}`
9954
10060
  critique_answers: scannedCritiques,
9955
10061
  critique_structured: critiqueStructured,
9956
10062
  synthesis_answer: synthAns,
9957
- synthesis_structured: synthObj
10063
+ synthesis_structured: synthObj,
10064
+ moderator: { provider: synthForCall.name, model: synthForCall.model }
9958
10065
  };
9959
10066
  if (synthErrs.length > 0) result["synthesis_errors"] = synthErrs;
9960
10067
  if (canaryLeaks.length > 0) result["canary_leaks"] = canaryLeaks;
@@ -9972,15 +10079,16 @@ ${critiqueBlock}`
9972
10079
  result["reasoning_upgrade"] = {
9973
10080
  applied: true,
9974
10081
  // The model that actually ran the seat, not the one we'd have picked.
9975
- model: superRequested ? superPrimary("anthropic") ?? UPGRADE_MODEL : UPGRADE_MODEL,
9976
- label: UPGRADE_LABEL,
10082
+ provider: synthForCall.name,
10083
+ model: synthForCall.model,
10084
+ label: seat.label ?? synthForCall.model,
9977
10085
  seat: "synthesizer",
9978
10086
  ...coReasonAns ? { co_reasoner: { model: coProvider.model, label: coReasonLabel(coProvider.model) } } : {}
9979
10087
  };
9980
10088
  } else if (upgradeRequested) {
9981
10089
  result["reasoning_upgrade"] = {
9982
10090
  applied: false,
9983
- reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
10091
+ reason: seat.skipReason ?? "the synthesizer couldn't be upgraded."
9984
10092
  };
9985
10093
  } else {
9986
10094
  const assessment = assessReasoning(topic);
@@ -10591,10 +10699,12 @@ async function runDebate(args, opts) {
10591
10699
  const earlyStopThreshold = Number.isFinite(earlyStopThresholdRaw) ? earlyStopThresholdRaw : 0.7;
10592
10700
  let earlyStoppedRound = null;
10593
10701
  let agreementBlock = null;
10594
- const moderatorName = typeof args["moderator"] === "string" && args["moderator"] ? args["moderator"] : opts.moderator ?? "anthropic";
10595
- const moderator = opts.providers[moderatorName.toLowerCase()] ?? selected[0];
10702
+ const resolvedModerator = resolveModerator(opts.providers, args["moderator"], opts.moderator);
10703
+ const moderatorName = resolvedModerator.spec.provider;
10704
+ const moderator = resolvedModerator.provider ?? selected[0];
10596
10705
  const upgradeRequested = isUpgradeRequested(args) || superRequested;
10597
- const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
10706
+ const seat = moderator ? synthesisSeat({ moderator, pinned: resolvedModerator.pinned, superRequested, upgradeRequested }) : null;
10707
+ const upgraded = seat?.upgraded ?? false;
10598
10708
  for (let rnd = 1; rnd <= maxRounds; rnd++) {
10599
10709
  const roundMessages = [
10600
10710
  {
@@ -10679,8 +10789,10 @@ ${prior.body}` });
10679
10789
  let personaMeta = null;
10680
10790
  let coReasoned = false;
10681
10791
  let coReasonerModel = null;
10792
+ let moderatorUsed = null;
10682
10793
  if (moderator) {
10683
- const synthProvider = superRequested && opts.providers["anthropic"] ? retargetForSuper(opts.providers["anthropic"]) : upgraded ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : moderator;
10794
+ const synthProvider = seat?.provider ?? moderator;
10795
+ moderatorUsed = { provider: synthProvider.name, model: synthProvider.model };
10684
10796
  const condensed = buildModeratorTranscript(transcript);
10685
10797
  const persona = buildPersonaInjection({
10686
10798
  toolName: "debate",
@@ -10706,7 +10818,7 @@ TRANSCRIPT:
10706
10818
  ${condensed}`
10707
10819
  }
10708
10820
  ];
10709
- const openAiSeat = selected.find((p) => p.name === "openai")?.model;
10821
+ const openAiSeat = synthProvider.name === "openai" ? synthProvider.model : selected.find((p) => p.name === "openai")?.model;
10710
10822
  const coProvider = pickCoReasoner(opts.providers, upgradeRequested, openAiSeat);
10711
10823
  const coReason = await coReasonThenInject({
10712
10824
  provider: coProvider,
@@ -10766,20 +10878,22 @@ ${condensed}`
10766
10878
  transcript,
10767
10879
  synthesis
10768
10880
  };
10881
+ if (moderatorUsed) result["moderator"] = moderatorUsed;
10769
10882
  if (personaMeta && personaMeta.used !== null) result["persona"] = personaMeta;
10770
10883
  if (compactionMeta) result["round_compaction"] = compactionMeta;
10771
10884
  if (upgraded) {
10772
10885
  result["reasoning_upgrade"] = {
10773
10886
  applied: true,
10774
- model: superRequested ? superPrimary("anthropic") ?? UPGRADE_MODEL : UPGRADE_MODEL,
10775
- label: UPGRADE_LABEL,
10887
+ provider: seat.provider.name,
10888
+ model: seat.provider.model,
10889
+ label: seat.label ?? seat.provider.model,
10776
10890
  seat: "moderator synthesis",
10777
10891
  ...coReasoned && coReasonerModel ? { co_reasoner: { model: coReasonerModel, label: coReasonLabel(coReasonerModel) } } : {}
10778
10892
  };
10779
10893
  } else if (upgradeRequested) {
10780
10894
  result["reasoning_upgrade"] = {
10781
10895
  applied: false,
10782
- reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
10896
+ reason: seat?.skipReason ?? "no moderator was available for the upgrade."
10783
10897
  };
10784
10898
  } else {
10785
10899
  const assessment = assessReasoning(topic);
@@ -11411,12 +11525,25 @@ function runListProviders(args, opts) {
11411
11525
  }
11412
11526
  const result = {
11413
11527
  providers,
11414
- moderator_default: opts.moderatorDefault ?? "anthropic",
11528
+ moderator_default: opts.moderatorDefault ?? DEFAULT_MODERATOR,
11529
+ // Who will actually moderate right now: the configured spec if that
11530
+ // provider has a key, else the first available (what the tools fall to).
11531
+ moderator: describeModerator(opts.providers ?? {}, opts.moderatorDefault),
11415
11532
  usage_hint: USAGE_HINT
11416
11533
  };
11417
11534
  if (superRequested) result["super_mode"] = true;
11418
11535
  return result;
11419
11536
  }
11537
+ function describeModerator(providers, spec) {
11538
+ const r = resolveModerator(providers, spec);
11539
+ const p = r.provider ?? Object.values(providers)[0];
11540
+ return {
11541
+ spec: spec ?? DEFAULT_MODERATOR,
11542
+ provider: p?.name ?? null,
11543
+ model: p?.model ?? null,
11544
+ available: r.provider !== void 0
11545
+ };
11546
+ }
11420
11547
 
11421
11548
  // src/tools/models.ts
11422
11549
  init_cjs_shims();
@@ -12977,7 +13104,7 @@ var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
12977
13104
  var DEFAULT_PACKAGE = "crosscheck-cli";
12978
13105
  var FETCH_TIMEOUT_MS = 3e3;
12979
13106
  function engineVersion() {
12980
- return true ? "0.2.30" : "0.0.0-dev";
13107
+ return true ? "0.2.31" : "0.0.0-dev";
12981
13108
  }
12982
13109
  function defaultUpdateCachePath() {
12983
13110
  const base = process.env["CROSSCHECK_DATA_DIR"] || import_node_path13.default.join(import_node_os2.default.homedir() || import_node_os2.default.tmpdir(), ".crosscheck");
@@ -13424,7 +13551,7 @@ var HELP_CATALOG = [
13424
13551
  primaryArg: "topic",
13425
13552
  summary: "Bounded multi-round debate between models, ending in a synthesized recommendation.",
13426
13553
  example: "xc debate should we adopt a monorepo?",
13427
- 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.`
13554
+ 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.`
13428
13555
  },
13429
13556
  {
13430
13557
  name: "coordinate",
@@ -13667,6 +13794,10 @@ function renderHelpCommand(entry) {
13667
13794
  }
13668
13795
 
13669
13796
  // src/tools/index.ts
13797
+ var MODERATOR_PARAM = {
13798
+ type: "string",
13799
+ 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`).'
13800
+ };
13670
13801
  function defineTool(opts) {
13671
13802
  return {
13672
13803
  name: opts.name,
@@ -13901,7 +14032,7 @@ function createTool(o, toolName, cheapDefault) {
13901
14032
  instruction: { type: "string" },
13902
14033
  documents: { type: "array", items: { type: "string" } },
13903
14034
  providers: { type: "array", items: { type: "string" } },
13904
- moderator: { type: "string" },
14035
+ moderator: MODERATOR_PARAM,
13905
14036
  constraints: { type: "string" },
13906
14037
  target_path: { type: "string" },
13907
14038
  session_id: { type: "string" },
@@ -13942,7 +14073,7 @@ function orchestrateTool(providers, allowlist, moderator, storage, pricing, brea
13942
14073
  dag: { type: "object", additionalProperties: true },
13943
14074
  context: { type: "string" },
13944
14075
  providers: { type: "array", items: { type: "string" } },
13945
- moderator: { type: "string" },
14076
+ moderator: MODERATOR_PARAM,
13946
14077
  fail_fast: { type: "boolean" },
13947
14078
  plan_only: { type: "boolean" },
13948
14079
  cheap_mode: { type: "boolean" },
@@ -14321,7 +14452,7 @@ function planTool(providers, allowlist, storage, breakers, transcriptsDir, repoR
14321
14452
  constraints: { type: "string" },
14322
14453
  context: { type: "string" },
14323
14454
  providers: { type: "array", items: { type: "string" } },
14324
- moderator: { type: "string" },
14455
+ moderator: MODERATOR_PARAM,
14325
14456
  session_id: { type: "string" },
14326
14457
  structured: { type: "boolean" },
14327
14458
  mode: { type: "string", enum: ["fast", "thorough"] },
@@ -14383,7 +14514,7 @@ function coordinateTool(providers, allowlist, innerCallers, storage, breakers, t
14383
14514
  providers: { type: "array", items: { type: "string" } },
14384
14515
  proposer: { type: "string" },
14385
14516
  synthesizer: { type: "string" },
14386
- moderator: { type: "string" },
14517
+ moderator: MODERATOR_PARAM,
14387
14518
  critics: { type: "array", items: { type: "string" } },
14388
14519
  session_id: { type: "string" },
14389
14520
  untrusted_input: { type: "boolean" },
@@ -14416,7 +14547,7 @@ function debateTool(providers, allowlist, innerCallers, storage, pricing, breake
14416
14547
  context: { type: "string" },
14417
14548
  max_rounds: { type: "integer", minimum: 1 },
14418
14549
  providers: { type: "array", items: { type: "string" } },
14419
- moderator: { type: "string" },
14550
+ moderator: MODERATOR_PARAM,
14420
14551
  session_id: { type: "string" },
14421
14552
  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.' },
14422
14553
  structured: { type: "boolean" },
@@ -15419,8 +15550,8 @@ function mapConfig(raw, sourcePath) {
15419
15550
  if (pa === null) result.providerAllowlist = null;
15420
15551
  else if (strArr(pa) !== void 0) result.providerAllowlist = strArr(pa);
15421
15552
  }
15422
- const moderator = str(raw["moderator"]);
15423
- if (moderator) result.moderatorDefault = moderator.toLowerCase();
15553
+ const moderator = parseModeratorSpec(raw["moderator"]);
15554
+ if (moderator) result.moderatorDefault = formatModeratorSpec(moderator);
15424
15555
  const bench = asObj(raw["bench"]);
15425
15556
  const goldens = bench ? str(bench["goldens_dir"]) : void 0;
15426
15557
  if (goldens) result.benchGoldensDir = goldens;
@@ -15624,7 +15755,9 @@ async function main() {
15624
15755
  if (configFile?.providerAllowlist !== void 0) {
15625
15756
  serverOpts.providerAllowlist = configFile.providerAllowlist;
15626
15757
  }
15627
- if (configFile?.moderatorDefault) serverOpts.moderatorDefault = configFile.moderatorDefault;
15758
+ const envModerator = parseModeratorSpec(process.env["CROSSCHECK_MODERATOR"]);
15759
+ const moderatorDefault = envModerator ? formatModeratorSpec(envModerator) : configFile?.moderatorDefault;
15760
+ if (moderatorDefault) serverOpts.moderatorDefault = moderatorDefault;
15628
15761
  if (configFile?.benchGoldensDir) serverOpts.benchGoldensDir = configFile.benchGoldensDir;
15629
15762
  if (configFile?.eventsPath) serverOpts.eventsPath = configFile.eventsPath;
15630
15763
  await connectAndServe(transport, serverOpts);