github-router 0.3.176 → 0.3.178

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.
@@ -1,6 +1,6 @@
1
- import { t as PATHS } from "./paths-CTlT1nTo.js";
2
- import { d as runCommandCapture, l as parseBoolEnv, n as isPidAlive, o as trackChild, p as runManagedExeCapture, r as registerColbertExitHandlers, t as getColbertInstanceUuid, u as resolveExecutable } from "./lifecycle-ChPBRt6K.js";
3
- import { i as registerExitHandlers, n as getInstanceUuid, r as recordWorkerRepo, t as WorktreeRegistry } from "./lifecycle-BUXxiltc.js";
1
+ import { t as PATHS } from "./paths-BZPgxKUl.js";
2
+ import { d as runCommandCapture, l as parseBoolEnv, n as isPidAlive, o as trackChild, p as runManagedExeCapture, r as registerColbertExitHandlers, t as getColbertInstanceUuid, u as resolveExecutable } from "./lifecycle-DrDEjx_c.js";
3
+ import { i as registerExitHandlers, n as getInstanceUuid, r as recordWorkerRepo, t as WorktreeRegistry } from "./lifecycle-Dvr3pGGG.js";
4
4
  import { createRequire } from "node:module";
5
5
  import consola from "consola";
6
6
  import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
@@ -603,7 +603,7 @@ function filterBetaHeader(value) {
603
603
  * dashes, insert dash at letter→digit boundaries, and collapse repeated
604
604
  * dashes. E.g. "gpt5.3-codex" → "gpt-5-3-codex", "GPT-5.3-Codex" → "gpt-5-3-codex".
605
605
  */
606
- function normalizeModelId(id) {
606
+ function normalizeModelId$1(id) {
607
607
  return id.toLowerCase().replace(/\./g, "-").replace(/([a-z])(\d)/g, "$1-$2").replace(/-{2,}/g, "-");
608
608
  }
609
609
  /**
@@ -651,8 +651,8 @@ function resolveModel(modelId) {
651
651
  return codexModels[0].id;
652
652
  }
653
653
  }
654
- const normalized = normalizeModelId(modelId);
655
- const normMatch = models.find((m) => normalizeModelId(m.id) === normalized);
654
+ const normalized = normalizeModelId$1(modelId);
655
+ const normMatch = models.find((m) => normalizeModelId$1(m.id) === normalized);
656
656
  if (normMatch) return normMatch.id;
657
657
  const dateStripped = modelId.replace(/^(claude-[\w.-]+)-20\d{6}$/i, "$1");
658
658
  if (dateStripped !== modelId) {
@@ -12076,6 +12076,14 @@ var TreeSitterPool = class {
12076
12076
  * entirely and force the in-process path, rather than churning spawn→crash
12077
12077
  * forever. */
12078
12078
  crashCount = 0;
12079
+ workersSpawned = 0;
12080
+ /** Test-only observability for crash/respawn assertions. */
12081
+ __workerLifecycleForTest() {
12082
+ return {
12083
+ crashes: this.crashCount,
12084
+ spawned: this.workersSpawned
12085
+ };
12086
+ }
12079
12087
  queue = [];
12080
12088
  inflight = /* @__PURE__ */ new Map();
12081
12089
  constructor() {
@@ -12123,6 +12131,7 @@ var TreeSitterPool = class {
12123
12131
  let worker;
12124
12132
  try {
12125
12133
  worker = new Worker(this.workerPath);
12134
+ this.workersSpawned += 1;
12126
12135
  } catch (err) {
12127
12136
  consola.debug(`[code_search] tree-sitter worker spawn failed: ${err.message}`);
12128
12137
  resolve(null);
@@ -12225,8 +12234,14 @@ var TreeSitterPool = class {
12225
12234
  if (!job) break;
12226
12235
  pw.busyJobId = job.id;
12227
12236
  this.inflight.set(job.id, job);
12237
+ const injectCrash = _testCrashOnceArmed;
12238
+ const reqToPost = injectCrash ? {
12239
+ ...job.req,
12240
+ testCrash: true
12241
+ } : job.req;
12228
12242
  try {
12229
- pw.worker.postMessage(job.req);
12243
+ pw.worker.postMessage(reqToPost);
12244
+ if (injectCrash) _testCrashOnceArmed = false;
12230
12245
  } catch (err) {
12231
12246
  consola.debug(`[code_search] tree-sitter worker postMessage failed: ${err.message}`);
12232
12247
  this.inflight.delete(job.id);
@@ -12398,6 +12413,7 @@ function resolveWorkerPath() {
12398
12413
  }
12399
12414
  let _pool = null;
12400
12415
  let _shutdownRegistered = false;
12416
+ let _testCrashOnceArmed = false;
12401
12417
  /**
12402
12418
  * The pool is ON by default for real (non-CI) runs and OFF under CI.
12403
12419
  *
@@ -12650,6 +12666,10 @@ const WALL_TIME_MS = 3e4;
12650
12666
  * comfortable headroom for ~5-10 files even on cold cache.
12651
12667
  */
12652
12668
  const STRUCTURAL_BUDGET_MS = 200;
12669
+ let _structuralBudgetTestOverride = null;
12670
+ function structuralBudgetMs() {
12671
+ return _structuralBudgetTestOverride ?? STRUCTURAL_BUDGET_MS;
12672
+ }
12653
12673
  const STRUCTURAL_TOPN_FULL = 50;
12654
12674
  const STRUCTURAL_TOPN_FAST = 10;
12655
12675
  /**
@@ -13957,7 +13977,7 @@ async function searchCode(rawInput, externalSignal) {
13957
13977
  })).filter((e) => e.index >= 0),
13958
13978
  workspaceRoot: ws.canonical,
13959
13979
  topN,
13960
- budgetMs: STRUCTURAL_BUDGET_MS,
13980
+ budgetMs: structuralBudgetMs(),
13961
13981
  signal: ac.signal
13962
13982
  });
13963
13983
  structuralOutlines = structural.outlinesByFile;
@@ -16881,7 +16901,7 @@ function logAudit$1(record) {
16881
16901
  try {
16882
16902
  const fs$2 = await import("node:fs/promises");
16883
16903
  const path$1 = await import("node:path");
16884
- const { PATHS: PATHS$1 } = await import("./paths-DN3O54iE.js");
16904
+ const { PATHS: PATHS$1 } = await import("./paths-CDYvyApX.js");
16885
16905
  const dir = path$1.join(PATHS$1.APP_DIR, "browser-mcp");
16886
16906
  await fs$2.mkdir(dir, { recursive: true });
16887
16907
  const line = JSON.stringify({
@@ -23491,6 +23511,33 @@ async function countTokens(body, extraHeaders, callerSignal, retryTransient = fa
23491
23511
  return response;
23492
23512
  }
23493
23513
 
23514
+ //#endregion
23515
+ //#region src/lib/openai-frontier.ts
23516
+ /**
23517
+ * OpenAI frontier models + shim effort policy.
23518
+ *
23519
+ * Dependency-free leaf so BOTH `mcp-capabilities.ts` (model SELECTION) and the
23520
+ * hot `anthropic-translate` shim path (effort POLICY) can import without pulling
23521
+ * the heavy mcp-capabilities transitive deps (colbert/browser/worker) into the
23522
+ * shim. The selection list and the effort-policy set are deliberately SEPARATE
23523
+ * constants (same members today) so a future frontier model added for selection
23524
+ * does not silently inherit the xhigh effort default.
23525
+ */
23526
+ /** Preference-ordered OpenAI frontier reasoning models (SELECTION list). */
23527
+ const OPENAI_FRONTIER_MODELS = ["gpt-5.6-sol", "gpt-5.5"];
23528
+ /** Models whose shim DEFAULT reasoning effort is xhigh (effort POLICY set). */
23529
+ const XHIGH_DEFAULT_SHIM_MODELS = ["gpt-5.6-sol", "gpt-5.5"];
23530
+ /** Normalize a model id for policy comparison: strip a leading `vendor/`
23531
+ * prefix and any trailing `[...]` decoration(s) (e.g. `[1m]`, `[1m][beta]`)
23532
+ * plus trailing whitespace. */
23533
+ function normalizeModelId(id) {
23534
+ return (id.includes("/") ? id.slice(id.lastIndexOf("/") + 1) : id).replace(/(?:\[[^\]]*\])+\s*$/, "");
23535
+ }
23536
+ /** True iff `id` (after normalization) is in the xhigh effort-policy set. */
23537
+ function shimDefaultsToXhigh(id) {
23538
+ return XHIGH_DEFAULT_SHIM_MODELS.includes(normalizeModelId(id));
23539
+ }
23540
+
23494
23541
  //#endregion
23495
23542
  //#region src/lib/mcp-capabilities.ts
23496
23543
  /**
@@ -23521,14 +23568,6 @@ function geminiAvailable(source = state) {
23521
23568
  return models.some((m) => /^gemini-3\..*pro/i.test(m.id));
23522
23569
  }
23523
23570
  /**
23524
- * OpenAI frontier reasoning models in preference order. `gpt-5.6-sol` is the
23525
- * current default; `gpt-5.5` is retained as a fallback. Both share the same
23526
- * `pro_plus/business/enterprise/max` restriction tier, so the fallback only
23527
- * matters during a rollout-lag window where the newer slug hasn't yet appeared
23528
- * in the account's catalog.
23529
- */
23530
- const OPENAI_FRONTIER_MODELS = ["gpt-5.6-sol", "gpt-5.5"];
23531
- /**
23532
23571
  * First available OpenAI frontier model in the live catalog (prefer
23533
23572
  * `gpt-5.6-sol`, fall back to `gpt-5.5`). Returns undefined when neither is
23534
23573
  * present. With `requireToolCalls`, only returns a model whose catalog entry
@@ -23552,9 +23591,11 @@ function standInToolEnabled() {
23552
23591
  const hasGeminiPro = geminiAvailable();
23553
23592
  return hasOpenAi && hasOpus && hasGeminiPro;
23554
23593
  }
23555
- /** Return the native implementer subagent model iff it is live with tool calls.
23556
- * Prefers `gpt-5.6-sol`, falls back to `gpt-5.5`. */
23557
- function implementerSubagentModel() {
23594
+ /** Return the model for the native OpenAI subagents (implementer, debugger,
23595
+ * qa-engineer) iff it is live with tool calls. Prefers `gpt-5.6-sol`, falls
23596
+ * back to `gpt-5.5`. One gate governs all three — they need the same frontier
23597
+ * model. */
23598
+ function nativeSubagentModel() {
23558
23599
  return resolveOpenAiFrontier({ requireToolCalls: true });
23559
23600
  }
23560
23601
  /**
@@ -24063,9 +24104,9 @@ function jsonPathPreflightCap(body, scope) {
24063
24104
  const decision = typeof args.decision === "string" ? args.decision : "";
24064
24105
  const optionsRaw = Array.isArray(args.options) ? args.options : [];
24065
24106
  const standInContext = typeof args.context === "string" ? args.context : "";
24066
- if (!decision || optionsRaw.length === 0) return void 0;
24107
+ if (!decision || optionsRaw.length === 0 || !standInContext.trim()) return void 0;
24067
24108
  const briefBytes$1 = Buffer.byteLength(decision + JSON.stringify(optionsRaw) + standInContext, "utf8");
24068
- const STAND_IN_CAP_BYTES = 6 * 1024;
24109
+ const STAND_IN_CAP_BYTES = 32 * 1024;
24069
24110
  if (briefBytes$1 > STAND_IN_CAP_BYTES) return rpcResult(body.id, toolError(`pre-flight rejected: stand_in on a ${briefBytes$1}-byte input is predicted to exceed the JSON tools/call timeout (cap=${STAND_IN_CAP_BYTES} bytes). stand_in runs two sequential voting rounds across three frontier models — wall-clock is typically 2-3 minutes regardless of input size. Send Accept: text/event-stream to use the SSE path which bypasses this cap, or trim the decision/options/context.`));
24070
24111
  return;
24071
24112
  }
@@ -28018,12 +28059,15 @@ Respond with ONLY a single JSON object — no prose, no markdown fences, no prea
28018
28059
  "choice": "<option.id>" | null,
28019
28060
  "confidence": <number between 0.0 and 1.0>,
28020
28061
  "reasoning": "<one short sentence>",
28021
- "need_more_info": "<what context is missing, if you cannot decide>"
28062
+ "need_more_info": "<what context is missing, if you cannot decide>",
28063
+ "alternative": "<a concrete unlisted option — ONLY if every provided option is inadequate>"
28022
28064
  }
28023
28065
 
28024
28066
  Calibration rules:
28025
28067
  - "confidence" reflects how sure you are this is the better option (not how confident you are in your prose). 0.5 = coin flip. 0.9 = clear winner. Be honestly calibrated; the orchestrator weighs your number directly.
28026
28068
  - If the question is genuinely under-specified — you'd need information you don't have to choose well — set "choice": null AND populate "need_more_info" with the specific gap. Do NOT guess.
28069
+ - The caller curated these options; default to choosing among them. Only if a provided option is actively harmful, or clearly dominated by an obvious unlisted option, set "choice": null AND put that concrete option in "alternative" (one sentence). This is distinct from "need_more_info" (which is about missing context, not a better option). Prefer choosing over proposing — do not invent an alternative to avoid committing.
28070
+ - On an abstention, populate at most ONE escape channel: "need_more_info" OR "alternative", never both. If both seem to apply, use "need_more_info" — missing context takes precedence, because you can't reliably judge the options inadequate without it.
28027
28071
  - One sentence of reasoning. Not a paragraph.
28028
28072
  - The other two models will vote independently and you will see their votes in round 2. There is no benefit to anticipating what they'll pick; vote on the merits.
28029
28073
 
@@ -28036,16 +28080,19 @@ Same JSON schema as round 1:
28036
28080
  "choice": "<option.id>" | null,
28037
28081
  "confidence": <number between 0.0 and 1.0>,
28038
28082
  "reasoning": "<one short sentence>",
28039
- "need_more_info": "<gap, if any>"
28083
+ "need_more_info": "<gap, if any>",
28084
+ "alternative": "<a concrete unlisted option, if every provided option is inadequate>"
28040
28085
  }
28041
28086
 
28042
28087
  Calibration rules:
28043
28088
  - You may keep your round-1 vote OR change it. Do NOT change just to agree — agreement is not the goal, the right answer is. Capitulating to peer pressure when you still believe your original choice is better is a failure mode, not a success.
28044
28089
  - If a peer's reasoning identifies a consideration you missed or weighed wrong, update freely. The blind round was the anti-anchor mechanism; this round is where genuine evidence can move you.
28045
28090
  - If round 1 left you genuinely uncertain and peer reasoning hasn't resolved it, "choice": null is still the honest answer.
28091
+ - Keep using "alternative" only when every provided option is inadequate (choice null); it is not a way to dodge a decision the options already support.
28092
+ - On an abstention, populate at most ONE escape channel: "need_more_info" OR "alternative", never both. If both seem to apply, use "need_more_info" (missing context takes precedence).
28046
28093
 
28047
28094
  Output ONLY the JSON object.`;
28048
- const RETRY_PROMPT_SUFFIX = `\n\nYour previous response was not valid JSON matching the schema. Respond with ONLY the JSON object — no preamble, no markdown fences, no closing remarks. Schema reminder: {"choice": "<id>" | null, "confidence": 0.0-1.0, "reasoning": "<one sentence>", "need_more_info": "<gap, if any>"}`;
28095
+ const RETRY_PROMPT_SUFFIX = `\n\nYour previous response was not valid JSON matching the schema. Respond with ONLY the JSON object — no preamble, no markdown fences, no closing remarks. Schema reminder: {"choice": "<id>" | null, "confidence": 0.0-1.0, "reasoning": "<one sentence>", "need_more_info": "<gap, if any>", "alternative": "<unlisted option, if any>"}`;
28049
28096
  /**
28050
28097
  * Run the two-round stand-in protocol. Returns a structured verdict
28051
28098
  * envelope. Throws only on systemic failure (e.g., all three upstream
@@ -28053,71 +28100,66 @@ const RETRY_PROMPT_SUFFIX = `\n\nYour previous response was not valid JSON match
28053
28100
  * `VoteFailure` entries in the result.
28054
28101
  */
28055
28102
  async function runStandIn(input, signal) {
28103
+ const validIds = new Set(input.options.map((o) => o.id));
28056
28104
  const r1UserText = buildRound1UserText(input);
28057
- const r1 = await Promise.all(STAND_IN_MODELS.map((cfg) => callAndParse(cfg, SYSTEM_PROMPT_R1, r1UserText, signal)));
28105
+ const r1 = await Promise.all(STAND_IN_MODELS.map((cfg) => callAndParse(cfg, SYSTEM_PROMPT_R1, r1UserText, validIds, signal)));
28058
28106
  const successfulR1 = r1.filter((r) => isVote(r.vote));
28059
- if (successfulR1.length === STAND_IN_MODELS.length && successfulR1.every((r) => r.vote.needMoreInfo && r.vote.choice === null)) {
28060
- const gaps = successfulR1.map((r) => `- ${r.key}: ${r.vote.needMoreInfo}`).join("\n");
28061
- return {
28062
- verdict: "need_more_info",
28063
- recommendation: null,
28064
- confidence: 0,
28065
- votes: voteRecord(r1, null),
28066
- notes: `All three models reported they need more context to decide:\n${gaps}`
28067
- };
28068
- }
28107
+ const nmiR1 = gapAbstainVerdict(successfulR1, r1, null);
28108
+ if (nmiR1) return nmiR1;
28069
28109
  const r1Decision = aggregateVotes(successfulR1);
28070
- if (r1Decision.verdict === "consensus" && r1Decision.meanConfidence >= .8) return {
28110
+ if (r1Decision.verdict === "consensus" && r1Decision.meanConfidence >= .8) return withDerivedNotes({
28071
28111
  verdict: "consensus",
28072
28112
  recommendation: r1Decision.winner,
28073
28113
  confidence: round2(r1Decision.meanConfidence),
28074
28114
  votes: voteRecord(r1, null),
28075
28115
  notes: `All three models picked ${r1Decision.winner} in round 1 with high confidence (skipped round 2).`
28076
- };
28077
- if (successfulR1.length < 2) return {
28116
+ }, r1, null);
28117
+ if (successfulR1.length < 2) return withDerivedNotes({
28078
28118
  verdict: "no_consensus",
28079
28119
  recommendation: null,
28080
28120
  confidence: 0,
28081
28121
  votes: voteRecord(r1, null),
28082
28122
  notes: `Only ${successfulR1.length} of 3 models returned a parseable round-1 vote; insufficient signal to run round 2.`
28083
- };
28123
+ }, r1, null);
28084
28124
  const r2UserTextBase = buildRound2UserTextBase(input, r1);
28085
- const r2 = await Promise.all(STAND_IN_MODELS.map((cfg) => callAndParse(cfg, SYSTEM_PROMPT_R2, r2UserTextBase + `\n\nYou are ${cfg.key}. Reconsider and vote.`, signal)));
28125
+ const r2 = await Promise.all(STAND_IN_MODELS.map((cfg) => callAndParse(cfg, SYSTEM_PROMPT_R2, r2UserTextBase + `\n\nYou are ${cfg.key}. Reconsider and vote.`, validIds, signal)));
28086
28126
  const successfulR2 = r2.filter((r) => isVote(r.vote));
28087
- if (successfulR2.length < 2) return {
28127
+ if (successfulR2.length < 2) return withDerivedNotes({
28088
28128
  verdict: "no_consensus",
28089
28129
  recommendation: null,
28090
28130
  confidence: 0,
28091
28131
  votes: voteRecord(r1, r2),
28092
28132
  notes: `Only ${successfulR2.length} of 3 models returned a parseable round-2 vote; deferring to user.`
28093
- };
28133
+ }, r1, r2);
28134
+ const nmiR2 = gapAbstainVerdict(successfulR2, r1, r2);
28135
+ if (nmiR2) return nmiR2;
28094
28136
  const r2Decision = aggregateVotes(successfulR2);
28095
- if (r2Decision.verdict === "consensus") return {
28137
+ if (r2Decision.verdict === "consensus") return withDerivedNotes({
28096
28138
  verdict: "consensus",
28097
28139
  recommendation: r2Decision.winner,
28098
28140
  confidence: round2(r2Decision.meanConfidence),
28099
28141
  votes: voteRecord(r1, r2),
28100
28142
  notes: `All three models picked ${r2Decision.winner} in round 2.`
28101
- };
28143
+ }, r1, r2);
28102
28144
  if (r2Decision.verdict === "majority") {
28103
28145
  const dissenters = successfulR2.filter((r) => r.vote.choice !== r2Decision.winner).map((r) => `${r.key} picked ${r.vote.choice ?? "abstain"} (${r.vote.reasoning})`).join("; ");
28104
- return {
28146
+ return withDerivedNotes({
28105
28147
  verdict: "majority",
28106
28148
  recommendation: r2Decision.winner,
28107
28149
  confidence: round2(r2Decision.meanConfidence),
28108
28150
  votes: voteRecord(r1, r2),
28109
28151
  notes: `Majority (2 of 3) picked ${r2Decision.winner}. Dissent: ${dissenters}.`
28110
- };
28152
+ }, r1, r2);
28111
28153
  }
28112
- return {
28154
+ return withDerivedNotes({
28113
28155
  verdict: "no_consensus",
28114
28156
  recommendation: null,
28115
28157
  confidence: 0,
28116
28158
  votes: voteRecord(r1, r2),
28117
28159
  notes: `Models did not converge in round 2 (votes split). Defer to user.`
28118
- };
28160
+ }, r1, r2);
28119
28161
  }
28120
- async function callAndParse(cfg, instructions, userText, signal) {
28162
+ async function callAndParse(cfg, instructions, userText, validIds, signal) {
28121
28163
  const model = cfg.key === "gpt-5.6-sol" ? resolveOpenAiFrontier() ?? cfg.model : cfg.model;
28122
28164
  let raw;
28123
28165
  try {
@@ -28138,7 +28180,7 @@ async function callAndParse(cfg, instructions, userText, signal) {
28138
28180
  }
28139
28181
  };
28140
28182
  }
28141
- const first = tryParseVote(raw);
28183
+ const first = tryParseVote(raw, validIds);
28142
28184
  if (first.ok) return {
28143
28185
  key: cfg.key,
28144
28186
  vote: first.vote
@@ -28162,7 +28204,7 @@ async function callAndParse(cfg, instructions, userText, signal) {
28162
28204
  }
28163
28205
  };
28164
28206
  }
28165
- const second = tryParseVote(retryRaw);
28207
+ const second = tryParseVote(retryRaw, validIds);
28166
28208
  if (second.ok) return {
28167
28209
  key: cfg.key,
28168
28210
  vote: second.vote
@@ -28176,7 +28218,7 @@ async function callAndParse(cfg, instructions, userText, signal) {
28176
28218
  }
28177
28219
  };
28178
28220
  }
28179
- function tryParseVote(raw) {
28221
+ function tryParseVote(raw, validIds) {
28180
28222
  if (!raw || !raw.trim()) return {
28181
28223
  ok: false,
28182
28224
  error: "empty response"
@@ -28204,11 +28246,16 @@ function tryParseVote(raw) {
28204
28246
  error: "parsed value is not an object"
28205
28247
  };
28206
28248
  const obj = parsed;
28207
- const choice = obj.choice === null ? null : typeof obj.choice === "string" && obj.choice.length > 0 ? obj.choice : void 0;
28208
- if (choice === void 0) return {
28249
+ const rawChoice = obj.choice === null ? null : typeof obj.choice === "string" && obj.choice.length > 0 ? obj.choice : void 0;
28250
+ if (rawChoice === void 0) return {
28209
28251
  ok: false,
28210
28252
  error: "missing or invalid 'choice' field (string or null required)"
28211
28253
  };
28254
+ if (rawChoice !== null && !validIds.has(rawChoice)) return {
28255
+ ok: false,
28256
+ error: "'choice' must be one of the provided option ids or null"
28257
+ };
28258
+ const choice = rawChoice;
28212
28259
  const confidenceRaw = obj.confidence;
28213
28260
  const confidence = typeof confidenceRaw === "number" && Number.isFinite(confidenceRaw) ? Math.max(0, Math.min(1, confidenceRaw)) : void 0;
28214
28261
  if (confidence === void 0) return {
@@ -28220,13 +28267,15 @@ function tryParseVote(raw) {
28220
28267
  ok: false,
28221
28268
  error: "missing or empty 'reasoning' field"
28222
28269
  };
28270
+ const needMoreInfo = choice === null && typeof obj.need_more_info === "string" && obj.need_more_info.trim().length > 0 ? obj.need_more_info.trim() : void 0;
28223
28271
  return {
28224
28272
  ok: true,
28225
28273
  vote: {
28226
28274
  choice,
28227
28275
  confidence,
28228
28276
  reasoning,
28229
- needMoreInfo: typeof obj.need_more_info === "string" && obj.need_more_info.length > 0 ? obj.need_more_info : void 0
28277
+ needMoreInfo,
28278
+ alternative: choice === null && !needMoreInfo && typeof obj.alternative === "string" && obj.alternative.trim().length > 0 ? obj.alternative.trim() : void 0
28230
28279
  }
28231
28280
  };
28232
28281
  }
@@ -28289,13 +28338,70 @@ function buildRound2UserTextBase(input, r1) {
28289
28338
  for (const r of r1) if (isVote(r.vote)) {
28290
28339
  const choiceText = r.vote.choice === null ? "abstain" : r.vote.choice;
28291
28340
  const gapText = r.vote.needMoreInfo ? ` (needs: ${r.vote.needMoreInfo})` : "";
28292
- summaries.push(`- ${r.key} picked ${choiceText}, confidence ${r.vote.confidence.toFixed(2)}, reasoning: ${r.vote.reasoning}${gapText}`);
28341
+ const altText = r.vote.alternative ? ` [proposed unlisted alternative: ${r.vote.alternative}]` : "";
28342
+ summaries.push(`- ${r.key} picked ${choiceText}, confidence ${r.vote.confidence.toFixed(2)}, reasoning: ${r.vote.reasoning}${gapText}${altText}`);
28293
28343
  } else summaries.push(`- ${r.key} did not return a valid round-1 vote (${r.vote.error}).`);
28294
28344
  return base + "\n" + summaries.join("\n");
28295
28345
  }
28296
28346
  function isVote(v) {
28297
28347
  return !("error" in v);
28298
28348
  }
28349
+ function gapAbstainVerdict(successful, r1, r2) {
28350
+ const gapVotes = successful.filter((r) => r.vote.choice === null && r.vote.needMoreInfo);
28351
+ if (gapVotes.length < 2) return null;
28352
+ const gaps = gapVotes.map((r) => `- ${r.key}: ${r.vote.needMoreInfo}`).join("\n");
28353
+ const header = gapVotes.length === STAND_IN_MODELS.length ? "All three models reported they need more context to decide:" : `${gapVotes.length} of 3 models reported they need more context to decide:`;
28354
+ return withDerivedNotes({
28355
+ verdict: "need_more_info",
28356
+ recommendation: null,
28357
+ confidence: 0,
28358
+ votes: voteRecord(r1, r2),
28359
+ notes: `${header}\n${gaps}`
28360
+ }, r1, r2);
28361
+ }
28362
+ /**
28363
+ * Freshest parsed vote per model (round 2 if it parsed, else round 1) — the
28364
+ * basis for deriving alternative / gap notes without double-counting a model
28365
+ * across rounds.
28366
+ */
28367
+ function freshestVotes(r1, r2) {
28368
+ const out = [];
28369
+ for (const cfg of STAND_IN_MODELS) {
28370
+ const r2Entry = r2?.find((r) => r.key === cfg.key);
28371
+ const r1Entry = r1.find((r) => r.key === cfg.key);
28372
+ const vote = r2Entry && isVote(r2Entry.vote) ? r2Entry.vote : r1Entry && isVote(r1Entry.vote) ? r1Entry.vote : null;
28373
+ if (vote) out.push({
28374
+ key: cfg.key,
28375
+ vote
28376
+ });
28377
+ }
28378
+ return out;
28379
+ }
28380
+ /**
28381
+ * Append derived notes to a verdict WITHOUT touching the verdict / tally:
28382
+ * - panel-proposed unlisted `alternative`s (surfaced on every verdict);
28383
+ * - partial missing-context gaps (surfaced only on no_consensus — the
28384
+ * dedicated need_more_info path already lists its own gaps).
28385
+ * Purely additive to `notes`; never changes verdict / recommendation / isError.
28386
+ * This is what lets the alternative + partial-gap signals ride along while the
28387
+ * abstain and blind-R1 invariants stay untouched.
28388
+ */
28389
+ function withDerivedNotes(result, r1, r2) {
28390
+ const fresh = freshestVotes(r1, r2);
28391
+ const extras = [];
28392
+ const alts = fresh.filter((v) => v.vote.alternative);
28393
+ if (alts.length > 0) extras.push("The panel also flagged unlisted option(s):\n" + alts.map((v) => `- ${v.key}: ${v.vote.alternative}`).join("\n"));
28394
+ if (result.verdict === "no_consensus") {
28395
+ const gaps = fresh.filter((v) => v.vote.choice === null && v.vote.needMoreInfo);
28396
+ if (gaps.length > 0) extras.push("Some models cited missing context:\n" + gaps.map((v) => `- ${v.key}: ${v.vote.needMoreInfo}`).join("\n"));
28397
+ }
28398
+ if (extras.length === 0) return result;
28399
+ const notes = [result.notes, ...extras].filter(Boolean).join("\n\n");
28400
+ return {
28401
+ ...result,
28402
+ notes
28403
+ };
28404
+ }
28299
28405
  function voteRecord(r1, r2) {
28300
28406
  const record = {};
28301
28407
  for (const cfg of STAND_IN_MODELS) {
@@ -30492,6 +30598,9 @@ function buildAgentPrompt(persona, opts) {
30492
30598
  * of the live catalog). The raw `mcp__<workers>__*` tools are named only
30493
30599
  * as the guarded plumbing the dispatchers call, never as a main-agent
30494
30600
  * interface.
30601
+ * - Always names the implementer/debugger/qa-engineer native subagents
30602
+ * (they are injected unconditionally); the implementer-vs-`worker-implement`
30603
+ * contrast is added only when worker tools are available.
30495
30604
  * - Conditionally lists stand_in only when `standInAvailable`
30496
30605
  * (mirrors `standInToolEnabled()`).
30497
30606
  * - Conditionally lists gh-first-mate only when `agentToolsAvailable`
@@ -30523,7 +30632,8 @@ function buildPeerAwarenessSnippet(opts) {
30523
30632
  const codexCliClause = opts.codexCli ? " `mcp__codex-cli__codex` dispatches to `codex-implementer` (gpt-5.3-codex with workspace-write) for end-to-end coding tasks." : "";
30524
30633
  const para2Parts = [`\`mcp__${searchKey}__code\` is the one-stop code search (no extra model call). Its DEFAULT mode (or \`mode:"semantic"\`) ranks by MEANING via ColBERT over a per-workspace index, the first thing to reach for on intent/concept questions ("where is retry/backoff handled", "how does auth work"); when that index isn't ready it transparently falls back to lexical (the response \`source\` says which engine ran). Forced modes cover the rest: \`lexical\` (BM25F-ranked + tree-sitter, best for exact symbols), \`exact\`, \`regex\`, \`complete\` (exhaustive set), \`ast_pattern\`+\`ast_lang\` for multi-line AST shapes, \`scan\` for a whole-workspace symbol outline, \`multiline\` for cross-line regex. Multiple queries can run in a single turn. The index covers code-shaped files; for unstructured files (logs, \`.csv\`, \`.env*\`, config-only wiring), \`grep\`/\`glob\` still apply.`];
30525
30634
  if (opts.workerToolsAvailable) para2Parts.push(`\`worker-*\` are background Agent subagents (subagent_type) that run the matching worker in its own context and deliver the result as a completion notification, so a long run never blocks the turn: \`worker-explore\` (read-only research), \`worker-review\` (reads the code to verify a change or claim), \`worker-plan\` (ordered implementation plan), \`worker-implement\` (edit/write/bash; \`worktree: true\` isolates in a git worktree and returns the diff), \`worker-test\` (independent test author). The raw \`mcp__${workersKey}__*\` tools they call are guarded (a direct main-thread call is redirected to the matching agent); Workers themselves have \`code_search\`.`);
30526
- if (opts.workerToolsAvailable && opts.implementerAvailable) para2Parts.push(`For a bounded, well-scoped implementation, prefer the \`implementer\` subagent (Task, runs on gpt-5.6-sol) over \`worker-implement\`; reach for \`worker-implement\` only when you specifically need git-worktree isolation, parallel variants, or a throwaway experiment.`);
30635
+ para2Parts.push(`Three native subagents are always available (Task): \`implementer\` (bounded implementation), \`debugger\` (reproduce + isolate a failure's root cause), and \`qa-engineer\` (review + author/run tests), each in its own context so the lead's context stays free; on gpt-5.6-sol when in the catalog, else the lead's model.`);
30636
+ if (opts.workerToolsAvailable) para2Parts.push(`For a bounded, well-scoped implementation, prefer the \`implementer\` subagent over \`worker-implement\`; reach for \`worker-implement\` only when you specifically need git-worktree isolation, parallel variants, or a throwaway experiment.`);
30527
30637
  if (opts.workerToolsAvailable) para2Parts.push(`\`mcp__${orchestrateKey}__decompose\` composes an open-ended ask into a typed, VERIFIED workflow IR (a strong driver decorrelated by a cross-lab critic, so the decompose step isn't a single point of failure), and \`mcp__${orchestrateKey}__run_workflow\` executes that IR through a frozen kernel delivering max(orchestrated, baseline) over a sealed executable gate, so it never ships worse than a plain single-model run. \`mcp__${orchestrateKey}__verify_workflow\` checks an IR's floor invariants before you run it, and \`mcp__${orchestrateKey}__attest_step\` audits that a finished run's producers were each checked by a different lab. They suit non-trivial, role-separated asks; a trivial ask does not need them.`);
30528
30638
  else para2Parts.push(`\`mcp__${orchestrateKey}__verify_workflow\` statically checks a workflow IR's floor invariants and \`mcp__${orchestrateKey}__attest_step\` audits a run's cross-lab lineage (the \`decompose\`/\`run_workflow\` composer + kernel need the worker backend, unavailable here).`);
30529
30639
  if (opts.workerToolsAvailable) {
@@ -31264,10 +31374,14 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
31264
31374
  toolNameHttp: "stand_in",
31265
31375
  group: "decide",
31266
31376
  capability: "stand_in",
31267
- description: "Three-lab away-mode decision tiebreak advisor for moments when the user is unavailable and the agent is stuck between two or more concrete options. It polls gpt-5.6-sol, Opus 4.7, and gemini-3.1-pro-preview across blind and informed voting rounds, then returns a ranked-choice verdict such as consensus, majority, no_consensus, or need_more_info. Use when work would otherwise halt on a bounded choice the user would normally make. Not for code review, open-ended exploration, single-model second opinions, or bypassing confirmation on irreversible actions such as push, delete, drop, or deploy; use peer-review-coordinator or the individual critics for review and still ask the user for destructive actions.",
31377
+ description: "Three-lab away-mode decision tiebreak advisor for moments when the user is unavailable and the agent is stuck between two or more concrete options. It polls gpt-5.6-sol, Opus 4.7, and gemini-3.1-pro-preview across blind and informed voting rounds, then returns a ranked-choice verdict such as consensus, majority, no_consensus, or need_more_info. Use when work would otherwise halt on a bounded choice the user would normally make. If every provided option is inadequate, the panel may flag a concrete better unlisted option in `notes` so you can re-invoke with a revised set. The three panel models are cold-start — no repo, transcript, or memory access — and see only your decision, options, and context, so the `context` argument must carry all the background they need to judge. Not for code review, open-ended exploration, single-model second opinions, or bypassing confirmation on irreversible actions such as push, delete, drop, or deploy; use peer-review-coordinator or the individual critics for review and still ask the user for destructive actions.",
31268
31378
  inputSchema: {
31269
31379
  type: "object",
31270
- required: ["decision", "options"],
31380
+ required: [
31381
+ "decision",
31382
+ "options",
31383
+ "context"
31384
+ ],
31271
31385
  additionalProperties: false,
31272
31386
  properties: {
31273
31387
  decision: {
@@ -31278,7 +31392,7 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
31278
31392
  type: "array",
31279
31393
  minItems: 2,
31280
31394
  maxItems: 6,
31281
- description: "2-6 concrete options for the panel to vote on. Caller-provided do NOT ask the panel to generate options. The verdict cites the chosen option by `id`.",
31395
+ description: "2-6 concrete options curated by the caller for the panel to vote on. The panel may surface a gated unlisted alternative in `notes`, but does not replace the caller's option set. The verdict cites the chosen option by `id`.",
31282
31396
  items: {
31283
31397
  type: "object",
31284
31398
  required: ["id", "summary"],
@@ -31301,7 +31415,7 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
31301
31415
  },
31302
31416
  context: {
31303
31417
  type: "string",
31304
- description: "Task / code background that informs the decision. Keep tight the input is capped at ~6KB total across decision + options + context."
31418
+ description: "REQUIRED. The three panel models are cold-start: no access to your repository, prior transcript, or memory they see ONLY this decision + options + context. Include everything needed to decide well: the constraints that matter, the relevant code or excerpts, prior decisions not to relitigate, and what a good outcome looks like. Thin context yields a weak verdict. (On the JSON transport a ~32KB size guard applies; the SSE transport has no such limit.)"
31305
31419
  }
31306
31420
  }
31307
31421
  },
@@ -31638,11 +31752,11 @@ async function runStandInToolCall(args, signal) {
31638
31752
  detail
31639
31753
  });
31640
31754
  }
31641
- const context = args.context === void 0 ? void 0 : typeof args.context === "string" ? args.context : null;
31642
- if (context === null) return {
31755
+ const context = typeof args.context === "string" ? args.context : "";
31756
+ if (!context.trim()) return {
31643
31757
  content: [{
31644
31758
  type: "text",
31645
- text: "stand_in: arguments.context must be a string when provided"
31759
+ text: "stand_in: arguments.context is required (non-empty string). The panel is cold-start and sees only decision + options + context; include the constraints, relevant code, and success criteria needed to decide."
31646
31760
  }],
31647
31761
  isError: true
31648
31762
  };
@@ -31658,5 +31772,5 @@ async function runStandInToolCall(args, signal) {
31658
31772
  }
31659
31773
 
31660
31774
  //#endregion
31661
- export { isAdvisorRequested as $, cacheModels as $t, liveExec as A, hasSupportedBrowserInstalled as At, withNoOutputRetry as B, DEFAULT_CODEX_MODEL_FALLBACKS as Bt, fileReviewDebounce as C, pickEndpoint as Ct, stopGateEnabledForRepo as D, readResponseBodyCapped as Dt, repoRoot as E, MAX_RESPONSE_BODY_BYTES as Et, IMPLEMENT_DEFAULT_MODEL as F, ArtifactClient as Ft, vscodeRipgrepPath as G, pickClaudeDefault as Gt, buildToolbeltAwareness as H, UPSTREAM_FETCH_TIMEOUT_MS as Ht, PLAN_DEFAULT_MODEL as I, collapsePathKeys as It, searchWeb as J, setupCopilotToken as Jt, TOOLBELT_TOOLS$1 as K, getPackageVersion as Kt, REVIEW_DEFAULT_MODEL as L, toolbeltPathOverride as Lt, BROWSE_DEFAULT_MODEL as M, extractTarGzMember as Mt, DEFAULT_MODEL as N, extractZipMember as Nt, stopReviewStateDir as O, parseJsonOrDiagnose as Ot, EXPLORE_DEFAULT_MODEL as P, shouldUseInsecureTls as Pt, injectAdvisorTool as Q, cacheCopilotVersion as Qt, appendPlanReminder as R, DEFAULT_CLAUDE_MODEL_FALLBACKS as Rt, fileLastPromptStore as S, resolveMcpToolTimeoutMs as St, repoFingerprint as T, createChatCompletions as Tt, toolbeltEnabled as U, UPSTREAM_INACTIVITY_TIMEOUT_MS as Ut, availableToolCommands as V, DEFAULT_PORT as Vt, toolbeltSkipSet as W, generateRandomPort as Wt, ADVISOR_TOOL_INSTRUCTIONS as X, setupGitHubToken as Xt, ADVISOR_INTERNAL_TOOL_NAME as Y, setupGitHubAgentToken as Yt, buildAdvisorStream as Z, tryRefreshAndRetry as Zt, stopGateId as _, workerToolsEnabled as _t, buildPeerAwarenessSnippet as a, sleep as an, relayAnthropicStream as at, fileBaselineStore as b, getTokenCount as bt, buildArtifactOpenHookCommand as c, HTTPError as cn, agentToolsEnabled as ct, captureLaunchBaseline as d, copilotBaseUrl as dn, browserCompoundToolsEnabled as dt, cacheVSCodeVersion as en, buildAnthropicErrorEvent as et, decideStopHook as f, copilotHeaders as fn, browserToolsEnabled as ft, stopGateDisabled as g, standInToolEnabled as gt, launchBaselineKey as h, implementerSubagentModel as ht, buildAgentPrompt as i, resolveModel as in, readIteratorWithTimeout as it, resolveSealedGate as j, provisionAndIndexColbert as jt, trustRepo as k, provisionBrowserAssets as kt, buildSessionBindHookCommand as l, forwardError as ln, artifactToolsEnabled as lt, injectStopHookIntoSettingsFile as m, state as mn, geminiAvailable as mt, MCP_GROUPS as n, isNullish as nn, isControllerClosedError as nt, buildPeerAwarenessSummary as o, getModels as on, handleMcpDelete as ot, fileBlockBudget as p, githubHeaders as pn, fleetToolsEnabled as pt, assetFor as q, withInstallLock as qt, assertMcpToolSurfaceConsistent as r, resolveCodexModel as rn, logStreamError as rt, personasFor as s, fetchWithTransientRetry as sn, handleMcpPost as st, GROUP_META as t, filterBetaHeader as tn, buildOpenAIErrorEvent as tt, buildStopHookCommand as u, GITHUB_API_BASE_URL as un, browseAgentEnabled as ut, stopGatePlanMode as v, countTokens as vt, isSubagentContext as w, createResponses as wt, fileFindingsStore as x, assembleResponsesPayload as xt, stopReviewEnabled as y, createMessages as yt, runWorkerAgent as z, DEFAULT_CODEX_MODEL as zt };
31662
- //# sourceMappingURL=peer-mcp-personas-BKkdfOyK.js.map
31775
+ export { isAdvisorRequested as $, cacheCopilotVersion as $t, liveExec as A, provisionBrowserAssets as At, withNoOutputRetry as B, DEFAULT_CODEX_MODEL as Bt, fileReviewDebounce as C, resolveMcpToolTimeoutMs as Ct, stopGateEnabledForRepo as D, MAX_RESPONSE_BODY_BYTES as Dt, repoRoot as E, createChatCompletions as Et, IMPLEMENT_DEFAULT_MODEL as F, shouldUseInsecureTls as Ft, vscodeRipgrepPath as G, generateRandomPort as Gt, buildToolbeltAwareness as H, DEFAULT_PORT as Ht, PLAN_DEFAULT_MODEL as I, ArtifactClient as It, searchWeb as J, withInstallLock as Jt, TOOLBELT_TOOLS$1 as K, pickClaudeDefault as Kt, REVIEW_DEFAULT_MODEL as L, collapsePathKeys as Lt, BROWSE_DEFAULT_MODEL as M, provisionAndIndexColbert as Mt, DEFAULT_MODEL as N, extractTarGzMember as Nt, stopReviewStateDir as O, readResponseBodyCapped as Ot, EXPLORE_DEFAULT_MODEL as P, extractZipMember as Pt, injectAdvisorTool as Q, tryRefreshAndRetry as Qt, appendPlanReminder as R, toolbeltPathOverride as Rt, fileLastPromptStore as S, assembleResponsesPayload as St, repoFingerprint as T, createResponses as Tt, toolbeltEnabled as U, UPSTREAM_FETCH_TIMEOUT_MS as Ut, availableToolCommands as V, DEFAULT_CODEX_MODEL_FALLBACKS as Vt, toolbeltSkipSet as W, UPSTREAM_INACTIVITY_TIMEOUT_MS as Wt, ADVISOR_TOOL_INSTRUCTIONS as X, setupGitHubAgentToken as Xt, ADVISOR_INTERNAL_TOOL_NAME as Y, setupCopilotToken as Yt, buildAdvisorStream as Z, setupGitHubToken as Zt, stopGateId as _, workerToolsEnabled as _t, buildPeerAwarenessSnippet as a, resolveModel as an, relayAnthropicStream as at, fileBaselineStore as b, createMessages as bt, buildArtifactOpenHookCommand as c, fetchWithTransientRetry as cn, agentToolsEnabled as ct, captureLaunchBaseline as d, GITHUB_API_BASE_URL as dn, browserCompoundToolsEnabled as dt, cacheModels as en, buildAnthropicErrorEvent as et, decideStopHook as f, copilotBaseUrl as fn, browserToolsEnabled as ft, stopGateDisabled as g, standInToolEnabled as gt, launchBaselineKey as h, state as hn, nativeSubagentModel as ht, buildAgentPrompt as i, resolveCodexModel as in, readIteratorWithTimeout as it, resolveSealedGate as j, hasSupportedBrowserInstalled as jt, trustRepo as k, parseJsonOrDiagnose as kt, buildSessionBindHookCommand as l, HTTPError as ln, artifactToolsEnabled as lt, injectStopHookIntoSettingsFile as m, githubHeaders as mn, geminiAvailable as mt, MCP_GROUPS as n, filterBetaHeader as nn, isControllerClosedError as nt, buildPeerAwarenessSummary as o, sleep as on, handleMcpDelete as ot, fileBlockBudget as p, copilotHeaders as pn, fleetToolsEnabled as pt, assetFor as q, getPackageVersion as qt, assertMcpToolSurfaceConsistent as r, isNullish as rn, logStreamError as rt, personasFor as s, getModels as sn, handleMcpPost as st, GROUP_META as t, cacheVSCodeVersion as tn, buildOpenAIErrorEvent as tt, buildStopHookCommand as u, forwardError as un, browseAgentEnabled as ut, stopGatePlanMode as v, shimDefaultsToXhigh as vt, isSubagentContext as w, pickEndpoint as wt, fileFindingsStore as x, getTokenCount as xt, stopReviewEnabled as y, countTokens as yt, runWorkerAgent as z, DEFAULT_CLAUDE_MODEL_FALLBACKS as zt };
31776
+ //# sourceMappingURL=peer-mcp-personas-B1-1mVPB.js.map