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.
@@ -2471,6 +2471,7 @@ Routing rules:
2471
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\`.
2472
2472
  3. Preserve the user's wording verbatim in the argument \u2014 do not paraphrase, summarize, or "clean up" the prompt before forwarding.
2473
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.
2474
2475
 
2475
2476
  Routable tool names: ${ROUTABLE_TOOLS.join(", ")}.
2476
2477
 
@@ -2508,7 +2509,7 @@ import { z } from "zod";
2508
2509
  // src/server-meta.ts
2509
2510
  init_esm_shims();
2510
2511
  var SERVER_NAME = "crosscheck-agent";
2511
- var SERVER_VERSION = true ? "0.2.30" : "0.0.0-dev";
2512
+ var SERVER_VERSION = true ? "0.2.31" : "0.0.0-dev";
2512
2513
 
2513
2514
  // src/tools/audit.ts
2514
2515
  init_esm_shims();
@@ -4142,6 +4143,231 @@ function numberOrNull(v) {
4142
4143
  // src/tools/audit.ts
4143
4144
  import { performance as performance2 } from "perf_hooks";
4144
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
+
4145
4371
  // src/core/co-reason.ts
4146
4372
  init_esm_shims();
4147
4373
  var CO_REASON_MODEL = "gpt-6-sol";
@@ -4320,83 +4546,6 @@ function selectForDifficulty(args) {
4320
4546
  };
4321
4547
  }
4322
4548
 
4323
- // src/core/reasoning-upgrade.ts
4324
- init_esm_shims();
4325
- var UPGRADE_MODEL = "claude-fable-5";
4326
- var UPGRADE_LABEL = "Fable 5";
4327
- function isUpgradeRequested(args) {
4328
- if (args["upgrade"] === true) return true;
4329
- const reasoning = args["reasoning"];
4330
- if (typeof reasoning === "string" && reasoning.trim().toLowerCase() === "max") {
4331
- return true;
4332
- }
4333
- const model = args["model"];
4334
- if (typeof model === "string") {
4335
- const m = model.trim().toLowerCase();
4336
- if (m === "fable5" || m === "fable-5" || m === "fable" || m === UPGRADE_MODEL) {
4337
- return true;
4338
- }
4339
- }
4340
- return false;
4341
- }
4342
- var HEAVY_TERMS = [
4343
- /\bprov(e|ing|en)\b|\bproof\b|\btheorem\b|\bderiv(e|ation)\b/i,
4344
- /\barchitect(ure|ing)?\b|\bdesign\s+(a|the|an)\b|\bhigh[-\s]?level\s+design\b/i,
4345
- /\btrade[-\s]?offs?\b|\bpros?\s+and\s+cons?\b|\bweigh\b/i,
4346
- /\boptimi[sz]e\b|\bcomplexity\b|\bO\(|\balgorithm(ic)?\b/i,
4347
- /\brace\s+condition\b|\bconcurren(t|cy)\b|\bdeadlock\b|\bdistributed\b|\bconsensus\b/i,
4348
- /\broot[-\s]?cause\b|\bdebug(ging)?\b|\bwhy\s+(does|is|would|might)\b/i,
4349
- /\bsecurity\b|\bthreat\s+model\b|\bexploit\b|\bvulnerabilit(y|ies)\b/i,
4350
- /\bmigrat(e|ion)\b|\brefactor\b|\bre[-\s]?architect\b/i,
4351
- /\binvariant\b|\bcorrectness\b|\bedge\s+cases?\b|\bformal(ly)?\b/i,
4352
- /\bstrateg(y|ic|ies)\b|\bplan\s+(the|a|an|out)\b|\broadmap\b/i,
4353
- /\breason\s+(through|about)\b|\bstep[-\s]?by[-\s]?step\b|\bthink\s+deeply\b/i,
4354
- /\bnp[-\s]?(hard|complete)\b|\bgraph\b|\bstate\s+machine\b|\bconstraint\b/i
4355
- ];
4356
- var DIFFICULTY_TERMS = /\b(hard|tricky|subtle|complex|complicated|non[-\s]?trivial|thorny|gnarly|difficult|deep)\b/i;
4357
- function assessReasoning(text) {
4358
- const s = typeof text === "string" ? text : "";
4359
- const signals = [];
4360
- let score = 0;
4361
- let termHits = 0;
4362
- for (const re of HEAVY_TERMS) {
4363
- if (re.test(s)) termHits += 1;
4364
- }
4365
- if (termHits > 0) {
4366
- const add = Math.min(termHits, 4);
4367
- score += add;
4368
- signals.push(`${termHits} reasoning cue${termHits === 1 ? "" : "s"}`);
4369
- }
4370
- if (DIFFICULTY_TERMS.test(s)) {
4371
- score += 1;
4372
- signals.push("explicit difficulty marker");
4373
- }
4374
- const words = s.split(/\s+/).filter(Boolean).length;
4375
- if (words >= 120) {
4376
- score += 2;
4377
- signals.push("long, detailed prompt");
4378
- } else if (words >= 50) {
4379
- score += 1;
4380
- signals.push("substantial prompt");
4381
- }
4382
- const constraints = (s.match(/\b(must|should|need to|ensure|require[ds]?|constraint)\b/gi) ?? []).length + (s.match(/(^|\n)\s*(?:[-*]|\d+[.)])\s+/g) ?? []).length;
4383
- if (constraints >= 3) {
4384
- score += 1;
4385
- signals.push(`${constraints} constraints/steps`);
4386
- }
4387
- const heavy = score >= 3;
4388
- const reason = heavy ? `Task looks reasoning-heavy (${signals.join(", ") || "score " + score}).` : `Task looks routine (score ${score}).`;
4389
- return { heavy, score, reason, signals };
4390
- }
4391
- function buildSuggestedUpgrade(toolName, assessment) {
4392
- return {
4393
- model: UPGRADE_MODEL,
4394
- label: UPGRADE_LABEL,
4395
- reason: assessment.reason,
4396
- 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.`
4397
- };
4398
- }
4399
-
4400
4549
  // src/tools/audit.ts
4401
4550
  var AUDIT_OBVIOUS_FAILURE_HIGH = 0.3;
4402
4551
  var AUDIT_OBVIOUS_FAILURE_MED = 0.2;
@@ -4694,9 +4843,14 @@ ${rubricText}`;
4694
4843
  );
4695
4844
  }
4696
4845
  const upgradeRequested = isUpgradeRequested(args);
4697
- const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
4698
- const auditorForCall = upgraded ? retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL) : auditor;
4699
- 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
+ );
4700
4854
  const coReason = await coReasonThenInject({
4701
4855
  provider: coProvider,
4702
4856
  messages: msgs,
@@ -4779,15 +4933,16 @@ ${rubricText}`;
4779
4933
  if (upgraded) {
4780
4934
  result["reasoning_upgrade"] = {
4781
4935
  applied: true,
4782
- model: UPGRADE_MODEL,
4783
- label: UPGRADE_LABEL,
4936
+ provider: auditorForCall.name,
4937
+ model: auditorForCall.model,
4938
+ label: seat.label ?? auditorForCall.model,
4784
4939
  seat: "auditor",
4785
4940
  ...coReasoned ? { co_reasoner: { model: coProvider.model, label: coReasonLabel(coProvider.model) } } : {}
4786
4941
  };
4787
4942
  } else if (upgradeRequested) {
4788
4943
  result["reasoning_upgrade"] = {
4789
4944
  applied: false,
4790
- reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
4945
+ reason: seat.skipReason ?? "the auditor couldn't be upgraded."
4791
4946
  };
4792
4947
  } else {
4793
4948
  const assessment = assessReasoning(
@@ -4870,7 +5025,7 @@ function pickAuditor(providers, exclude, explicit, moderatorName, allowlist, che
4870
5025
  }
4871
5026
  }
4872
5027
  }
4873
- const mod = providers[moderatorName.toLowerCase()];
5028
+ const mod = resolveModerator(providers, moderatorName).provider;
4874
5029
  if (mod !== void 0 && !exclude.has(mod.name) && (allowlist === null || allowlist.includes(mod.name))) {
4875
5030
  return { auditor: mod, reason: null };
4876
5031
  }
@@ -5195,9 +5350,7 @@ function pickPanelJudge(providers, moderatorName, pricing) {
5195
5350
  return entry.model && entry.model !== base.model ? retargetProvider(base, entry.model) : base;
5196
5351
  }
5197
5352
  }
5198
- const mod = providers[moderatorName.toLowerCase()];
5199
- if (mod !== void 0) return mod;
5200
- return null;
5353
+ return resolveModerator(providers, moderatorName).provider ?? null;
5201
5354
  }
5202
5355
  var AGREEMENT_CHECK_SCHEMA = {
5203
5356
  type: "object",
@@ -5515,81 +5668,6 @@ async function pickAutoPanel(storage, purpose, n, providers, allowlist) {
5515
5668
  return { providers: ranked.slice(0, Math.max(1, n)), coldStart: true };
5516
5669
  }
5517
5670
 
5518
- // src/core/super-mode.ts
5519
- init_esm_shims();
5520
- import { AsyncLocalStorage } from "async_hooks";
5521
- var SUPER_MODELS = {
5522
- // Fable 5.1 verified present on the account; 5 as the fallback for accounts
5523
- // that haven't been granted 5.1 yet.
5524
- anthropic: ["claude-fable-5-1", UPGRADE_MODEL],
5525
- // Astra is live (verified against the account's model list 2026-09-23).
5526
- // The fallback is named explicitly rather than reusing CO_REASON_MODEL:
5527
- // that constant means "OpenAI's top reasoning model" for the max-reasoning
5528
- // co-reasoner, which is a different question from "what should super fall
5529
- // back to". Sharing the symbol silently coupled two unrelated decisions.
5530
- openai: ["gpt-6-astra", "gpt-6-sol"],
5531
- // Explicitly left as-is per product decision. Note grok-4-latest is not in
5532
- // xAI's published model list, but it resolves in practice — real panel calls
5533
- // on it succeeded as recently as 2026-08-30 — so it is an unlisted alias
5534
- // rather than a dead id.
5535
- xai: [DEFAULT_MODELS["xai"]],
5536
- // 3.8 Flash is now BOTH the everyday default and the super pick for Gemini,
5537
- // so this can no longer borrow DEFAULT_MODELS as its fallback — that would
5538
- // repeat the same model and retry one that just failed. Named explicitly.
5539
- gemini: ["gemini-3.8-flash", "gemini-3.1-pro-preview"],
5540
- kimi: [DEFAULT_MODELS["kimi"]],
5541
- qwen: [DEFAULT_MODELS["qwen"]]
5542
- };
5543
- var SUPER_ENV_VARS = {
5544
- anthropic: "ANTHROPIC_SUPER_MODEL",
5545
- openai: "OPENAI_SUPER_MODEL",
5546
- xai: "XAI_SUPER_MODEL",
5547
- gemini: "GEMINI_SUPER_MODEL",
5548
- kimi: "KIMI_SUPER_MODEL",
5549
- qwen: "QWEN_SUPER_MODEL",
5550
- mistral: "MISTRAL_SUPER_MODEL",
5551
- groq: "GROQ_SUPER_MODEL",
5552
- deepseek: "DEEPSEEK_SUPER_MODEL"
5553
- };
5554
- function parseChain(v) {
5555
- if (!v) return [];
5556
- const out = [];
5557
- for (const raw of v.split(",")) {
5558
- const m = raw.trim();
5559
- if (m && !out.includes(m)) out.push(m);
5560
- }
5561
- return out;
5562
- }
5563
- var superEnvStore = new AsyncLocalStorage();
5564
- var currentEnv = () => superEnvStore.getStore() ?? process.env;
5565
- function superChainFor(provider, env = currentEnv()) {
5566
- const varName = SUPER_ENV_VARS[provider];
5567
- const user = varName ? parseChain(env[varName]) : [];
5568
- if (user.length > 0) {
5569
- const fallbacks = varName ? parseChain(env[`${varName}_FALLBACKS`]) : [];
5570
- return [...user, ...fallbacks.filter((m) => !user.includes(m))];
5571
- }
5572
- return SUPER_MODELS[provider] ?? [];
5573
- }
5574
- function superPrimary(provider, env = currentEnv()) {
5575
- return superChainFor(provider, env)[0];
5576
- }
5577
- var SUPER_PROVIDER_NAMES = Object.keys(SUPER_MODELS);
5578
- function isSuperRequested(args) {
5579
- return args["super"] === true;
5580
- }
5581
- function retargetForSuper(p, env = currentEnv()) {
5582
- const chain = superChainFor(p.name, env);
5583
- if (chain.length === 0) return p;
5584
- const [primary, ...fallbacks] = chain;
5585
- return retargetProvider(p, primary, fallbacks);
5586
- }
5587
- var SUPER_BUDGET_MULTIPLIER = 3;
5588
- var SUPER_BUDGET_CEILING = 16e3;
5589
- function superBudget(base) {
5590
- return Math.min(base * SUPER_BUDGET_MULTIPLIER, SUPER_BUDGET_CEILING);
5591
- }
5592
-
5593
5671
  // src/core/session-memory.ts
5594
5672
  init_esm_shims();
5595
5673
  var SESSION_MEMORY_DEFAULT_LIMIT = 50;
@@ -7982,8 +8060,9 @@ async function runOrchestrate(args, opts) {
7982
8060
  blocked
7983
8061
  };
7984
8062
  }
7985
- const moderatorName = typeof args["moderator"] === "string" && args["moderator"] ? args["moderator"] : opts.moderator ?? "anthropic";
7986
- 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);
7987
8066
  const failFast = boolArg2(args["fail_fast"], false);
7988
8067
  const planOnly = boolArg2(args["plan_only"], false);
7989
8068
  const cheapMode = boolArg2(args["cheap_mode"], opts.ctx?.cheap_mode ?? false);
@@ -8467,11 +8546,17 @@ ${recombineSys}` : recombineSys },
8467
8546
  let synthProvider = moderator;
8468
8547
  let synthModel = null;
8469
8548
  const upgradeRequested = isUpgradeRequested(args);
8470
- 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;
8471
8556
  if (upgraded) {
8472
- synthProvider = retargetProvider(opts.providers["anthropic"], UPGRADE_MODEL);
8473
- synthModel = `anthropic/${UPGRADE_MODEL}`;
8474
- } else if (cheapMode && opts.pricing) {
8557
+ synthProvider = seat.provider;
8558
+ synthModel = `${seat.provider.name}/${seat.provider.model}`;
8559
+ } else if (cheapMode && opts.pricing && !moderatorChosen) {
8475
8560
  const pick = selectForDifficulty({
8476
8561
  pricing: opts.pricing,
8477
8562
  tier: "med",
@@ -8497,7 +8582,11 @@ ${recombineSys}` : recombineSys },
8497
8582
  provider: synthProvider.name
8498
8583
  } }
8499
8584
  );
8500
- const coProvider = pickCoReasoner(opts.providers, upgradeRequested);
8585
+ const coProvider = pickCoReasoner(
8586
+ opts.providers,
8587
+ upgradeRequested,
8588
+ synthProvider.name === "openai" ? synthProvider.model : void 0
8589
+ );
8501
8590
  const coReason = await coReasonThenInject({
8502
8591
  provider: coProvider,
8503
8592
  messages: recMsgs,
@@ -8547,7 +8636,8 @@ ${recombineSys}` : recombineSys },
8547
8636
  missing,
8548
8637
  partial,
8549
8638
  fail_fast: failFast,
8550
- cheap_mode: cheapMode
8639
+ cheap_mode: cheapMode,
8640
+ moderator: { provider: moderator.name, model: moderator.model }
8551
8641
  };
8552
8642
  if (personaMeta.used !== null) result["persona"] = personaMeta;
8553
8643
  if (synthModel) result["synth_model"] = synthModel;
@@ -8555,15 +8645,16 @@ ${recombineSys}` : recombineSys },
8555
8645
  if (upgraded) {
8556
8646
  result["reasoning_upgrade"] = {
8557
8647
  applied: true,
8558
- model: UPGRADE_MODEL,
8559
- label: UPGRADE_LABEL,
8648
+ provider: seat.provider.name,
8649
+ model: seat.provider.model,
8650
+ label: seat.label ?? seat.provider.model,
8560
8651
  seat: "recombine/synthesis",
8561
8652
  ...coReasoned ? { co_reasoner: { model: coProvider.model, label: coReasonLabel(coProvider.model) } } : {}
8562
8653
  };
8563
8654
  } else if (upgradeRequested) {
8564
8655
  result["reasoning_upgrade"] = {
8565
8656
  applied: false,
8566
- reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
8657
+ reason: seat.skipReason ?? "the moderator couldn't be upgraded."
8567
8658
  };
8568
8659
  } else if (goal) {
8569
8660
  const assessment = assessReasoning(goal);
@@ -9748,13 +9839,24 @@ async function runCoordinate(args, opts) {
9748
9839
  };
9749
9840
  }
9750
9841
  const proposerName = typeof args["proposer"] === "string" && args["proposer"] ? args["proposer"] : selected[0].name;
9751
- 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
+ );
9752
9848
  const proposer = opts.providers[proposerName.toLowerCase()] ?? selected[0];
9753
- const synth = opts.providers[synthName.toLowerCase()] ?? proposer;
9849
+ const synth = resolvedSynth.provider ?? proposer;
9754
9850
  const proposerForCall = superRequested ? retargetForSuper(proposer) : proposer;
9755
9851
  const upgradeRequested = isUpgradeRequested(args) || superRequested;
9756
- const upgraded = upgradeRequested && opts.providers["anthropic"] !== void 0;
9757
- 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;
9758
9860
  let critics = [];
9759
9861
  if (Array.isArray(args["critics"])) {
9760
9862
  for (const n of args["critics"]) {
@@ -9905,7 +10007,11 @@ CRITIQUES:
9905
10007
  ${critiqueBlock}`
9906
10008
  }
9907
10009
  ];
9908
- const coProvider = pickCoReasoner(opts.providers, upgradeRequested);
10010
+ const coProvider = pickCoReasoner(
10011
+ opts.providers,
10012
+ upgradeRequested,
10013
+ synthForCall.name === "openai" ? synthForCall.model : void 0
10014
+ );
9909
10015
  const coReason = await coReasonThenInject({
9910
10016
  provider: coProvider,
9911
10017
  messages: synthMessages,
@@ -9950,7 +10056,8 @@ ${critiqueBlock}`
9950
10056
  critique_answers: scannedCritiques,
9951
10057
  critique_structured: critiqueStructured,
9952
10058
  synthesis_answer: synthAns,
9953
- synthesis_structured: synthObj
10059
+ synthesis_structured: synthObj,
10060
+ moderator: { provider: synthForCall.name, model: synthForCall.model }
9954
10061
  };
9955
10062
  if (synthErrs.length > 0) result["synthesis_errors"] = synthErrs;
9956
10063
  if (canaryLeaks.length > 0) result["canary_leaks"] = canaryLeaks;
@@ -9968,15 +10075,16 @@ ${critiqueBlock}`
9968
10075
  result["reasoning_upgrade"] = {
9969
10076
  applied: true,
9970
10077
  // The model that actually ran the seat, not the one we'd have picked.
9971
- model: superRequested ? superPrimary("anthropic") ?? UPGRADE_MODEL : UPGRADE_MODEL,
9972
- label: UPGRADE_LABEL,
10078
+ provider: synthForCall.name,
10079
+ model: synthForCall.model,
10080
+ label: seat.label ?? synthForCall.model,
9973
10081
  seat: "synthesizer",
9974
10082
  ...coReasonAns ? { co_reasoner: { model: coProvider.model, label: coReasonLabel(coProvider.model) } } : {}
9975
10083
  };
9976
10084
  } else if (upgradeRequested) {
9977
10085
  result["reasoning_upgrade"] = {
9978
10086
  applied: false,
9979
- reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
10087
+ reason: seat.skipReason ?? "the synthesizer couldn't be upgraded."
9980
10088
  };
9981
10089
  } else {
9982
10090
  const assessment = assessReasoning(topic);
@@ -10587,10 +10695,12 @@ async function runDebate(args, opts) {
10587
10695
  const earlyStopThreshold = Number.isFinite(earlyStopThresholdRaw) ? earlyStopThresholdRaw : 0.7;
10588
10696
  let earlyStoppedRound = null;
10589
10697
  let agreementBlock = null;
10590
- const moderatorName = typeof args["moderator"] === "string" && args["moderator"] ? args["moderator"] : opts.moderator ?? "anthropic";
10591
- 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];
10592
10701
  const upgradeRequested = isUpgradeRequested(args) || superRequested;
10593
- 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;
10594
10704
  for (let rnd = 1; rnd <= maxRounds; rnd++) {
10595
10705
  const roundMessages = [
10596
10706
  {
@@ -10675,8 +10785,10 @@ ${prior.body}` });
10675
10785
  let personaMeta = null;
10676
10786
  let coReasoned = false;
10677
10787
  let coReasonerModel = null;
10788
+ let moderatorUsed = null;
10678
10789
  if (moderator) {
10679
- 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 };
10680
10792
  const condensed = buildModeratorTranscript(transcript);
10681
10793
  const persona = buildPersonaInjection({
10682
10794
  toolName: "debate",
@@ -10702,7 +10814,7 @@ TRANSCRIPT:
10702
10814
  ${condensed}`
10703
10815
  }
10704
10816
  ];
10705
- const openAiSeat = selected.find((p) => p.name === "openai")?.model;
10817
+ const openAiSeat = synthProvider.name === "openai" ? synthProvider.model : selected.find((p) => p.name === "openai")?.model;
10706
10818
  const coProvider = pickCoReasoner(opts.providers, upgradeRequested, openAiSeat);
10707
10819
  const coReason = await coReasonThenInject({
10708
10820
  provider: coProvider,
@@ -10762,20 +10874,22 @@ ${condensed}`
10762
10874
  transcript,
10763
10875
  synthesis
10764
10876
  };
10877
+ if (moderatorUsed) result["moderator"] = moderatorUsed;
10765
10878
  if (personaMeta && personaMeta.used !== null) result["persona"] = personaMeta;
10766
10879
  if (compactionMeta) result["round_compaction"] = compactionMeta;
10767
10880
  if (upgraded) {
10768
10881
  result["reasoning_upgrade"] = {
10769
10882
  applied: true,
10770
- model: superRequested ? superPrimary("anthropic") ?? UPGRADE_MODEL : UPGRADE_MODEL,
10771
- label: UPGRADE_LABEL,
10883
+ provider: seat.provider.name,
10884
+ model: seat.provider.model,
10885
+ label: seat.label ?? seat.provider.model,
10772
10886
  seat: "moderator synthesis",
10773
10887
  ...coReasoned && coReasonerModel ? { co_reasoner: { model: coReasonerModel, label: coReasonLabel(coReasonerModel) } } : {}
10774
10888
  };
10775
10889
  } else if (upgradeRequested) {
10776
10890
  result["reasoning_upgrade"] = {
10777
10891
  applied: false,
10778
- reason: `no Anthropic key available \u2014 the ${UPGRADE_LABEL} upgrade needs it.`
10892
+ reason: seat?.skipReason ?? "no moderator was available for the upgrade."
10779
10893
  };
10780
10894
  } else {
10781
10895
  const assessment = assessReasoning(topic);
@@ -11407,12 +11521,25 @@ function runListProviders(args, opts) {
11407
11521
  }
11408
11522
  const result = {
11409
11523
  providers,
11410
- 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),
11411
11528
  usage_hint: USAGE_HINT
11412
11529
  };
11413
11530
  if (superRequested) result["super_mode"] = true;
11414
11531
  return result;
11415
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
+ }
11416
11543
 
11417
11544
  // src/tools/models.ts
11418
11545
  init_esm_shims();
@@ -12973,7 +13100,7 @@ var CHECK_INTERVAL_SECONDS = 3 * 24 * 60 * 60;
12973
13100
  var DEFAULT_PACKAGE = "crosscheck-cli";
12974
13101
  var FETCH_TIMEOUT_MS = 3e3;
12975
13102
  function engineVersion() {
12976
- return true ? "0.2.30" : "0.0.0-dev";
13103
+ return true ? "0.2.31" : "0.0.0-dev";
12977
13104
  }
12978
13105
  function defaultUpdateCachePath() {
12979
13106
  const base = process.env["CROSSCHECK_DATA_DIR"] || path10.join(os.homedir() || os.tmpdir(), ".crosscheck");
@@ -13420,7 +13547,7 @@ var HELP_CATALOG = [
13420
13547
  primaryArg: "topic",
13421
13548
  summary: "Bounded multi-round debate between models, ending in a synthesized recommendation.",
13422
13549
  example: "xc debate should we adopt a monorepo?",
13423
- 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.`
13424
13551
  },
13425
13552
  {
13426
13553
  name: "coordinate",
@@ -13663,6 +13790,10 @@ function renderHelpCommand(entry) {
13663
13790
  }
13664
13791
 
13665
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
+ };
13666
13797
  function defineTool(opts) {
13667
13798
  return {
13668
13799
  name: opts.name,
@@ -13897,7 +14028,7 @@ function createTool(o, toolName, cheapDefault) {
13897
14028
  instruction: { type: "string" },
13898
14029
  documents: { type: "array", items: { type: "string" } },
13899
14030
  providers: { type: "array", items: { type: "string" } },
13900
- moderator: { type: "string" },
14031
+ moderator: MODERATOR_PARAM,
13901
14032
  constraints: { type: "string" },
13902
14033
  target_path: { type: "string" },
13903
14034
  session_id: { type: "string" },
@@ -13938,7 +14069,7 @@ function orchestrateTool(providers, allowlist, moderator, storage, pricing, brea
13938
14069
  dag: { type: "object", additionalProperties: true },
13939
14070
  context: { type: "string" },
13940
14071
  providers: { type: "array", items: { type: "string" } },
13941
- moderator: { type: "string" },
14072
+ moderator: MODERATOR_PARAM,
13942
14073
  fail_fast: { type: "boolean" },
13943
14074
  plan_only: { type: "boolean" },
13944
14075
  cheap_mode: { type: "boolean" },
@@ -14317,7 +14448,7 @@ function planTool(providers, allowlist, storage, breakers, transcriptsDir, repoR
14317
14448
  constraints: { type: "string" },
14318
14449
  context: { type: "string" },
14319
14450
  providers: { type: "array", items: { type: "string" } },
14320
- moderator: { type: "string" },
14451
+ moderator: MODERATOR_PARAM,
14321
14452
  session_id: { type: "string" },
14322
14453
  structured: { type: "boolean" },
14323
14454
  mode: { type: "string", enum: ["fast", "thorough"] },
@@ -14379,7 +14510,7 @@ function coordinateTool(providers, allowlist, innerCallers, storage, breakers, t
14379
14510
  providers: { type: "array", items: { type: "string" } },
14380
14511
  proposer: { type: "string" },
14381
14512
  synthesizer: { type: "string" },
14382
- moderator: { type: "string" },
14513
+ moderator: MODERATOR_PARAM,
14383
14514
  critics: { type: "array", items: { type: "string" } },
14384
14515
  session_id: { type: "string" },
14385
14516
  untrusted_input: { type: "boolean" },
@@ -14412,7 +14543,7 @@ function debateTool(providers, allowlist, innerCallers, storage, pricing, breake
14412
14543
  context: { type: "string" },
14413
14544
  max_rounds: { type: "integer", minimum: 1 },
14414
14545
  providers: { type: "array", items: { type: "string" } },
14415
- moderator: { type: "string" },
14546
+ moderator: MODERATOR_PARAM,
14416
14547
  session_id: { type: "string" },
14417
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.' },
14418
14549
  structured: { type: "boolean" },
@@ -15415,8 +15546,8 @@ function mapConfig(raw, sourcePath) {
15415
15546
  if (pa === null) result.providerAllowlist = null;
15416
15547
  else if (strArr(pa) !== void 0) result.providerAllowlist = strArr(pa);
15417
15548
  }
15418
- const moderator = str(raw["moderator"]);
15419
- if (moderator) result.moderatorDefault = moderator.toLowerCase();
15549
+ const moderator = parseModeratorSpec(raw["moderator"]);
15550
+ if (moderator) result.moderatorDefault = formatModeratorSpec(moderator);
15420
15551
  const bench = asObj(raw["bench"]);
15421
15552
  const goldens = bench ? str(bench["goldens_dir"]) : void 0;
15422
15553
  if (goldens) result.benchGoldensDir = goldens;
@@ -15620,7 +15751,9 @@ async function main() {
15620
15751
  if (configFile?.providerAllowlist !== void 0) {
15621
15752
  serverOpts.providerAllowlist = configFile.providerAllowlist;
15622
15753
  }
15623
- 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;
15624
15757
  if (configFile?.benchGoldensDir) serverOpts.benchGoldensDir = configFile.benchGoldensDir;
15625
15758
  if (configFile?.eventsPath) serverOpts.eventsPath = configFile.eventsPath;
15626
15759
  await connectAndServe(transport, serverOpts);