github-router 0.3.289 → 0.3.292

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.
Files changed (87) hide show
  1. package/dist/{attribution-settings-Cmz2jt7P.js → attribution-settings-CpLUCi8R.js} +124 -34
  2. package/dist/attribution-settings-CpLUCi8R.js.map +1 -0
  3. package/dist/{auth-DG4vh8-F.js → auth-BwUHopJz.js} +3 -3
  4. package/dist/{auth-DG4vh8-F.js.map → auth-BwUHopJz.js.map} +1 -1
  5. package/dist/browser-ext/manifest.json +1 -1
  6. package/dist/{check-usage-BqN7mBYv.js → check-usage-BTda5753.js} +4 -4
  7. package/dist/{check-usage-BqN7mBYv.js.map → check-usage-BTda5753.js.map} +1 -1
  8. package/dist/{claude-C-9xFI4b.js → claude-_DYGKCw8.js} +99 -48
  9. package/dist/claude-_DYGKCw8.js.map +1 -0
  10. package/dist/{codex-DRW0yb8x.js → codex-rTJ8jW5G.js} +5 -5
  11. package/dist/{codex-DRW0yb8x.js.map → codex-rTJ8jW5G.js.map} +1 -1
  12. package/dist/{debug-B5TjPTTH.js → debug-B3UrZTHQ.js} +2 -2
  13. package/dist/{debug-B5TjPTTH.js.map → debug-B3UrZTHQ.js.map} +1 -1
  14. package/dist/engine-C9axTIu7.js +2 -0
  15. package/dist/{gate-discovery-Cz6kwIVG.js → gate-discovery-Bjar5dgv.js} +5 -5
  16. package/dist/{gate-discovery-Cz6kwIVG.js.map → gate-discovery-Bjar5dgv.js.map} +1 -1
  17. package/dist/{get-copilot-usage-BjA0nyGR.js → get-copilot-usage-CRrf1ZSC.js} +2 -2
  18. package/dist/{get-copilot-usage-BjA0nyGR.js.map → get-copilot-usage-CRrf1ZSC.js.map} +1 -1
  19. package/dist/{internal-artifact-open-BskmUpnb.js → internal-artifact-open-BsEQRvDi.js} +2 -2
  20. package/dist/{internal-artifact-open-BskmUpnb.js.map → internal-artifact-open-BsEQRvDi.js.map} +1 -1
  21. package/dist/{internal-first-mate-guard-5XEHMaqy.js → internal-first-mate-guard-CVFFJriA.js} +3 -3
  22. package/dist/{internal-first-mate-guard-5XEHMaqy.js.map → internal-first-mate-guard-CVFFJriA.js.map} +1 -1
  23. package/dist/{internal-first-mate-guard-DHDQ6hFz.js → internal-first-mate-guard-DJdX6t48.js} +1 -1
  24. package/dist/{internal-plan-review-BUuMx4ku.js → internal-plan-review-8TKDLco6.js} +3 -3
  25. package/dist/{internal-plan-review-BUuMx4ku.js.map → internal-plan-review-8TKDLco6.js.map} +1 -1
  26. package/dist/{internal-prompt-submit-CQQ15xdO.js → internal-prompt-submit-Da7pxqua.js} +4 -4
  27. package/dist/{internal-prompt-submit-CQQ15xdO.js.map → internal-prompt-submit-Da7pxqua.js.map} +1 -1
  28. package/dist/{internal-session-bind-D04W2yWI.js → internal-session-bind-BOFytA1f.js} +2 -2
  29. package/dist/{internal-session-bind-D04W2yWI.js.map → internal-session-bind-BOFytA1f.js.map} +1 -1
  30. package/dist/{internal-stop-hook-Dvkppwo7.js → internal-stop-hook-DrX2xlj0.js} +5 -5
  31. package/dist/{internal-stop-hook-Dvkppwo7.js.map → internal-stop-hook-DrX2xlj0.js.map} +1 -1
  32. package/dist/{internal-stop-review-CdByyJLc.js → internal-stop-review-CdouacHL.js} +2 -2
  33. package/dist/{internal-stop-review-CdByyJLc.js.map → internal-stop-review-CdouacHL.js.map} +1 -1
  34. package/dist/{internal-worker-guard-BIPN6Rv9.js → internal-worker-guard-Bx-itoP8.js} +2 -2
  35. package/dist/{internal-worker-guard-BIPN6Rv9.js.map → internal-worker-guard-Bx-itoP8.js.map} +1 -1
  36. package/dist/{internal-workspace-header-BKqejstG.js → internal-workspace-header-8WT0iB5K.js} +2 -2
  37. package/dist/{internal-workspace-header-BKqejstG.js.map → internal-workspace-header-8WT0iB5K.js.map} +1 -1
  38. package/dist/lifecycle-C8fOsQke.js +2 -0
  39. package/dist/lifecycle-D4Yc1aap.js +2 -0
  40. package/dist/{lifecycle-SXaWssN9.js → lifecycle-LeSfa7wH.js} +2 -2
  41. package/dist/{lifecycle-SXaWssN9.js.map → lifecycle-LeSfa7wH.js.map} +1 -1
  42. package/dist/{lifecycle-DbM29FLK.js → lifecycle-nuOHfwgj.js} +2 -2
  43. package/dist/{lifecycle-DbM29FLK.js.map → lifecycle-nuOHfwgj.js.map} +1 -1
  44. package/dist/main.js +17 -17
  45. package/dist/{mcp-workspace-header-DRCCWlOi.js → mcp-workspace-header-q34H_4wL.js} +2 -2
  46. package/dist/{mcp-workspace-header-DRCCWlOi.js.map → mcp-workspace-header-q34H_4wL.js.map} +1 -1
  47. package/dist/{models-Dz8d_SnI.js → models-hhJcrZhr.js} +3 -3
  48. package/dist/{models-Dz8d_SnI.js.map → models-hhJcrZhr.js.map} +1 -1
  49. package/dist/{orchestration-BrJwZxMN.js → orchestration-pzbrKkgD.js} +2 -2
  50. package/dist/{orchestration-BrJwZxMN.js.map → orchestration-pzbrKkgD.js.map} +1 -1
  51. package/dist/{paths-D7_SAaIQ.js → paths-BH4J7slC.js} +4 -4
  52. package/dist/{paths-D7_SAaIQ.js.map → paths-BH4J7slC.js.map} +1 -1
  53. package/dist/paths-DJZoXfAS.js +2 -0
  54. package/dist/{peer-mcp-personas-Bd56EmiO.js → peer-mcp-personas-CHbl6MwM.js} +544 -128
  55. package/dist/peer-mcp-personas-CHbl6MwM.js.map +1 -0
  56. package/dist/{plan-review-hook-CVZsG9MZ.js → plan-review-hook-CfcanA7_.js} +3 -3
  57. package/dist/{plan-review-hook-CVZsG9MZ.js.map → plan-review-hook-CfcanA7_.js.map} +1 -1
  58. package/dist/{prompt-submit-hook-BW92FX2D.js → prompt-submit-hook-Bqf9ORgb.js} +3 -3
  59. package/dist/{prompt-submit-hook-BW92FX2D.js.map → prompt-submit-hook-Bqf9ORgb.js.map} +1 -1
  60. package/dist/{provision-B53wbHwa.js → provision-BYFd9nPK.js} +4 -4
  61. package/dist/{provision-B53wbHwa.js.map → provision-BYFd9nPK.js.map} +1 -1
  62. package/dist/{self-invocation-CP_SOkrr.js → self-invocation-DhO1Z8iD.js} +2 -2
  63. package/dist/{self-invocation-CP_SOkrr.js.map → self-invocation-DhO1Z8iD.js.map} +1 -1
  64. package/dist/{serve-aZCEYFe5.js → serve-BWMxDLnD.js} +12 -12
  65. package/dist/{serve-aZCEYFe5.js.map → serve-BWMxDLnD.js.map} +1 -1
  66. package/dist/{server-setup-DlztZAGT.js → server-setup-CqlaZukJ.js} +609 -74
  67. package/dist/server-setup-CqlaZukJ.js.map +1 -0
  68. package/dist/{start-DwNiXv5N.js → start-5MgGT4IF.js} +3 -3
  69. package/dist/{start-DwNiXv5N.js.map → start-5MgGT4IF.js.map} +1 -1
  70. package/dist/{stop-gate-hook-DriRc9xN.js → stop-gate-hook-BiBp5aGm.js} +3 -3
  71. package/dist/{stop-gate-hook-DriRc9xN.js.map → stop-gate-hook-BiBp5aGm.js.map} +1 -1
  72. package/dist/{stop-gate-policy-DMPanpoR.js → stop-gate-policy-BGd6b5hR.js} +2 -2
  73. package/dist/{stop-gate-policy-DMPanpoR.js.map → stop-gate-policy-BGd6b5hR.js.map} +1 -1
  74. package/dist/{token-BGCjZwtj.js → token-8drORhXg.js} +33 -3
  75. package/dist/token-8drORhXg.js.map +1 -0
  76. package/dist/{worker-dispatch-BCTMyNE-.js → worker-dispatch-D5fGroNr.js} +2 -2
  77. package/dist/{worker-dispatch-BCTMyNE-.js.map → worker-dispatch-D5fGroNr.js.map} +1 -1
  78. package/package.json +1 -1
  79. package/dist/attribution-settings-Cmz2jt7P.js.map +0 -1
  80. package/dist/claude-C-9xFI4b.js.map +0 -1
  81. package/dist/engine-iEqGdx6T.js +0 -2
  82. package/dist/lifecycle-BTodQvn4.js +0 -2
  83. package/dist/lifecycle-C7JYNz-F.js +0 -2
  84. package/dist/paths-CTr59UC6.js +0 -2
  85. package/dist/peer-mcp-personas-Bd56EmiO.js.map +0 -1
  86. package/dist/server-setup-DlztZAGT.js.map +0 -1
  87. package/dist/token-BGCjZwtj.js.map +0 -1
@@ -1,14 +1,14 @@
1
1
  import { n as explicitPackageRoot } from "./package-root-B-osctCk.js";
2
2
  import { t as getPackageVersion } from "./version-C8x2hQrZ.js";
3
- import { t as PATHS } from "./paths-D7_SAaIQ.js";
4
- import { A as state, C as GITHUB_GRAPHQL_URL, D as githubAgentGraphQLHeaders, E as copilotVersion, O as githubAgentHeaders, S as GITHUB_API_BASE_URL, T as copilotHeaders, b as sanitizeTransportText, d as resolveModel, f as sleep$2, g as HTTPError, h as isAllowedCopilotHost, i as tryRefreshAndRetry, v as classifyTransportError, w as copilotBaseUrl, x as withTransientRetry, y as fetchWithTransientRetry } from "./token-BGCjZwtj.js";
3
+ import { t as PATHS } from "./paths-BH4J7slC.js";
4
+ import { A as state, C as GITHUB_GRAPHQL_URL, D as githubAgentGraphQLHeaders, E as copilotVersion, O as githubAgentHeaders, S as GITHUB_API_BASE_URL, T as copilotHeaders, b as sanitizeTransportText, d as resolveModel, f as sleep$2, g as HTTPError, h as isAllowedCopilotHost, i as tryRefreshAndRetry, v as classifyTransportError, w as copilotBaseUrl, x as withTransientRetry, y as fetchWithTransientRetry } from "./token-8drORhXg.js";
5
5
  import { a as resolveExecutable, c as runManagedExeCapture, i as parseIntEnv, l as spawnTaskkillBestEffort, r as parseBoolEnv } from "./exec-y8C_MU8A.js";
6
- import { i as registerExitHandlers$1, n as getInstanceUuid, r as recordWorkerRepo, t as WorktreeRegistry } from "./lifecycle-DbM29FLK.js";
7
- import { t as MCP_WORKSPACE_HEADER } from "./mcp-workspace-header-DRCCWlOi.js";
6
+ import { i as registerExitHandlers$1, n as getInstanceUuid, r as recordWorkerRepo, t as WorktreeRegistry } from "./lifecycle-nuOHfwgj.js";
7
+ import { t as MCP_WORKSPACE_HEADER } from "./mcp-workspace-header-q34H_4wL.js";
8
8
  import { n as ArtifactError, r as applyInsecureTls, t as ArtifactClient } from "./client-CQMfroGV.js";
9
- import { n as isPidAlive$1, r as registerColbertExitHandlers, s as trackChild, t as getColbertInstanceUuid } from "./lifecycle-SXaWssN9.js";
10
- import { f as resolveSealedGate, m as runGateChecks, p as sealedGateIds } from "./stop-gate-hook-DriRc9xN.js";
11
- import { t as liveExec } from "./orchestration-BrJwZxMN.js";
9
+ import { n as isPidAlive$1, r as registerColbertExitHandlers, s as trackChild, t as getColbertInstanceUuid } from "./lifecycle-LeSfa7wH.js";
10
+ import { f as resolveSealedGate, m as runGateChecks, p as sealedGateIds } from "./stop-gate-hook-BiBp5aGm.js";
11
+ import { t as liveExec } from "./orchestration-pzbrKkgD.js";
12
12
  import { createRequire } from "node:module";
13
13
  import consola from "consola";
14
14
  import { chmodSync, closeSync, constants, cpSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
@@ -447,9 +447,17 @@ const DEFAULT_CLAUDE_MODEL_FALLBACKS = [
447
447
  * variant" — defaulting safe-side preserves the pre-change behavior).
448
448
  */
449
449
  const DEFAULT_OPUS_FAMILY = "5";
450
- /** The lead `-m fast` selects. Anthropic-published dashed slug that is also the
451
- * Copilot catalog id verbatim, so `resolveModel` matches it exactly. */
452
- const BUDGET_LEAD_MODEL = "claude-sonnet-5";
450
+ /**
451
+ * The lead `-m fast` selects. `gpt-5.6-luna` a distinct Luna-driven
452
+ * profile (see `./launch-profile`), NOT a Claude Sonnet budget lead. This
453
+ * REPLACES the earlier `-m fast` → `claude-sonnet-5` mapping: `fast` now
454
+ * names a deliberately lean Luna surface (three native agents, one peer
455
+ * persona, `peers`/`search` MCP groups only), not "budget Sonnet with the
456
+ * full standard surface". `resolveLaunchProfile` in `./launch-profile`
457
+ * keys off the same raw `-m` argument this constant is selected by, so the
458
+ * two can never disagree about which launches count as "fast".
459
+ */
460
+ const FAST_LEAD_MODEL = "gpt-5.6-luna";
453
461
  /** Small/fast tier for a budget lead, in the two forms this codebase needs.
454
462
  *
455
463
  * `SLUG` is the Anthropic-published DASHED form and is what goes into
@@ -464,28 +472,29 @@ const BUDGET_SMALL_FAST_CATALOG_ID = "claude-haiku-4.5";
464
472
  /**
465
473
  * Resolve the `-m` argument to the lead slug to launch with.
466
474
  *
467
- * - `fast` → `BUDGET_LEAD_MODEL` (budget mode)
475
+ * - `fast` → `FAST_LEAD_MODEL` (the fast Luna profile — see
476
+ * `./launch-profile`, NOT the retired Sonnet budget lead)
468
477
  * - `N.M` → the best variant of that Opus family, via `pickClaudeDefault`
469
478
  * - a full slug → unchanged, including Copilot slugs a power user pins
470
479
  * - absent → the ordinary default
471
480
  *
472
481
  * Every branch is `[1m]`-decorated against the live catalog, by
473
482
  * `pickClaudeDefault` on the two Opus-family branches and by
474
- * `withOneMSuffixForLead` on the other two. Pinning a MODEL is not a request to
475
- * give up four fifths of its context window, which is what leaving the other
476
- * two bare amounted to: `claude-sonnet-5` advertises a 1M window, so `-m fast`
477
- * and `-m claude-sonnet-5` were both budgeted locally at Claude Code's 200K
478
- * default and auto-compacted at roughly a fifth of the real window. The
479
- * decoration is catalog-gated per model, so a genuinely 200K model
480
- * (`claude-haiku-4.5`) still comes back bare.
481
- *
482
- * `fast` resolves to an ordinary slug rather than setting a mode flag, because
483
- * budget mode is keyed off the RESOLVED lead everywhere it matters (the advisor
484
- * escalation, the delegation prose, the small/fast tier). `-m fast` and
485
- * `-m claude-sonnet-5` must therefore produce identical sessions, which a flag
486
- * only one of the two set would break. The shared decoration is part of that
487
- * identity: decorating one branch and not the other would reintroduce the
488
- * divergence through the context budget instead of through a flag.
483
+ * `withOneMSuffixForLead` on the other two. `gpt-5.6-luna` advertises a 1M
484
+ * window, so `-m fast` gets local 1M accounting exactly like every other
485
+ * branch here; the decoration is catalog-gated per model, so a genuinely
486
+ * 200K model (`claude-haiku-4.5`) still comes back bare.
487
+ *
488
+ * `fast` resolves to an ordinary slug rather than setting a mode flag
489
+ * `resolveLaunchProfile` (`./launch-profile`) is keyed off the SAME raw
490
+ * argument this function receives, so the two can never disagree about
491
+ * which launches are "fast". `isBudgetClaudeLead` (below) stays
492
+ * Claude-family-only and is UNRELATED to the fast profile: `gpt-5.6-luna`
493
+ * is not a Claude model, so `isBudgetClaudeLead(resolveLeadSlugArg("fast"))`
494
+ * is false the old Sonnet "budget lead" surfaces (advisor escalation,
495
+ * delegation prose, small/fast Haiku tier) simply don't engage for `-m
496
+ * fast` any more; the fast profile has its own separate roster/tier
497
+ * mechanism instead.
489
498
  *
490
499
  * Callers must keep treating any explicit `-m` as explicit: the
491
500
  * `DEFAULT_CLAUDE_MODEL_FALLBACKS` walk applies to the implicit-default path
@@ -494,7 +503,7 @@ const BUDGET_SMALL_FAST_CATALOG_ID = "claude-haiku-4.5";
494
503
  function resolveLeadSlugArg(modelArg) {
495
504
  const arg = modelArg?.trim();
496
505
  if (!arg) return pickClaudeDefault();
497
- if (arg.toLowerCase() === "fast") return withOneMSuffixForLead(BUDGET_LEAD_MODEL);
506
+ if (arg.toLowerCase() === "fast") return withOneMSuffixForLead(FAST_LEAD_MODEL);
498
507
  const opusFamilyShorthand = arg.match(/^(\d+\.\d+)$/)?.[1];
499
508
  if (opusFamilyShorthand) return pickClaudeDefault(opusFamilyShorthand);
500
509
  return withOneMSuffixForLead(arg);
@@ -18917,7 +18926,7 @@ function logAudit$1(record) {
18917
18926
  try {
18918
18927
  const fs = await import("node:fs/promises");
18919
18928
  const path = await import("node:path");
18920
- const { PATHS } = await import("./paths-CTr59UC6.js");
18929
+ const { PATHS } = await import("./paths-DJZoXfAS.js");
18921
18930
  const dir = path.join(PATHS.APP_DIR, "browser-mcp");
18922
18931
  await fs.mkdir(dir, { recursive: true });
18923
18932
  const line = JSON.stringify({
@@ -24248,8 +24257,8 @@ const FALLBACK_TOKEN_PRICES = Object.freeze({
24248
24257
  out: 2500
24249
24258
  },
24250
24259
  "gpt-5.6-sol": {
24251
- in: 500,
24252
- out: 3e3
24260
+ in: 200,
24261
+ out: 1e3
24253
24262
  },
24254
24263
  "grok-4.5": {
24255
24264
  in: 200,
@@ -24260,8 +24269,8 @@ const FALLBACK_TOKEN_PRICES = Object.freeze({
24260
24269
  out: 3e3
24261
24270
  },
24262
24271
  "gemini-3.6-flash": {
24263
- in: 150,
24264
- out: 750
24272
+ in: 75,
24273
+ out: 375
24265
24274
  },
24266
24275
  "gemini-3.5-flash": {
24267
24276
  in: 150,
@@ -26764,6 +26773,67 @@ function capToolResultText(content, capBytes) {
26764
26773
  ];
26765
26774
  }
26766
26775
  //#endregion
26776
+ //#region src/lib/launch-registry.ts
26777
+ /**
26778
+ * Register a new authenticated launch (a `github-router claude` process, or
26779
+ * `serve`'s per-repo session) in the keyed registry. Returns the stored
26780
+ * entry, including the generated `launchId` when the caller didn't supply
26781
+ * one.
26782
+ *
26783
+ * Callers are expected to mint `nonce` and `secret` as independent random
26784
+ * tokens (see `src/claude.ts` / `src/lib/serve/enhancements.ts`) — this
26785
+ * function stores whatever it's given without generating credentials
26786
+ * itself, so a caller cannot accidentally rely on it for randomness.
26787
+ */
26788
+ function registerLaunch(params) {
26789
+ const entry = {
26790
+ launchId: params.launchId ?? randomUUID(),
26791
+ nonce: params.nonce,
26792
+ secret: params.secret,
26793
+ profileId: params.profileId,
26794
+ allowedGroups: params.allowedGroups,
26795
+ allowedPersonas: params.allowedPersonas,
26796
+ createdAt: Date.now()
26797
+ };
26798
+ state.launchRegistry.set(entry.launchId, entry);
26799
+ return entry;
26800
+ }
26801
+ /** Remove one launch's entry. Idempotent — removing an already-removed or
26802
+ * never-registered id is a no-op. Called from the launch's own cleanup
26803
+ * path so a torn-down session's credentials stop authenticating. */
26804
+ function unregisterLaunch(launchId) {
26805
+ state.launchRegistry.delete(launchId);
26806
+ }
26807
+ /**
26808
+ * Constant-time string compare. Per-launch credentials are random tokens,
26809
+ * not secrets an attacker gets many guesses at over the network within one
26810
+ * process lifetime, so timing attacks aren't a realistic concern here — but
26811
+ * it costs nothing and matches the prior nonce-compare's posture.
26812
+ */
26813
+ function constantTimeStringEqual(a, b) {
26814
+ if (a.length !== b.length) return false;
26815
+ try {
26816
+ return timingSafeEqual(Buffer.from(a), Buffer.from(b));
26817
+ } catch {
26818
+ return false;
26819
+ }
26820
+ }
26821
+ /**
26822
+ * Find the launch whose `/mcp` bearer (`nonce`) matches. Linear scan over
26823
+ * `state.launchRegistry` — expected to hold a handful of entries at most
26824
+ * (one per concurrently running `claude`/`serve` session), so this is not a
26825
+ * hot-path concern. Returns undefined (never throws) when nothing matches,
26826
+ * including when the registry is empty (the "not enabled" case).
26827
+ */
26828
+ function findLaunchByNonce(nonce) {
26829
+ for (const entry of state.launchRegistry.values()) if (constantTimeStringEqual(entry.nonce, nonce)) return entry;
26830
+ }
26831
+ /** Find the launch whose `/v1/messages` identity-preflight bearer
26832
+ * (`secret`) matches. Mirrors `findLaunchByNonce`. */
26833
+ function findLaunchBySecret(secret) {
26834
+ for (const entry of state.launchRegistry.values()) if (constantTimeStringEqual(entry.secret, secret)) return entry;
26835
+ }
26836
+ //#endregion
26767
26837
  //#region src/lib/peer-attachments.ts
26768
26838
  /**
26769
26839
  * Server-side image loading for peer-critic attachments (`imagePaths`).
@@ -27631,6 +27701,67 @@ function generalPurposeFastModel() {
27631
27701
  minContextTokens: ONE_M_TOKENS
27632
27702
  });
27633
27703
  }
27704
+ const FAST_SCOUT_MODEL = "gpt-5.6-luna";
27705
+ const FAST_IMPLEMENTER_MODEL = "gpt-5.6-luna";
27706
+ /** Grok 4.6 advertises 500K total context / 372K max prompt, so it remains bare
27707
+ * and is gated by max_prompt_tokens rather than the 1M floor. */
27708
+ const FAST_REVIEWER_MODEL = "grok-4.6";
27709
+ const FAST_PLANNER_MODEL = "gpt-5.6-sol";
27710
+ const FAST_ORACLE_MODEL = "claude-opus-5";
27711
+ /** Fixed effort pins for the fast profile. */
27712
+ const FAST_SCOUT_EFFORT = "high";
27713
+ const FAST_REVIEWER_EFFORT = "medium";
27714
+ const FAST_PLANNER_EFFORT = "high";
27715
+ function fastScoutModel() {
27716
+ return firstPresentInCatalog([FAST_SCOUT_MODEL], {
27717
+ requireToolCalls: true,
27718
+ minContextTokens: ONE_M_TOKENS
27719
+ });
27720
+ }
27721
+ function fastImplementerModel() {
27722
+ return firstPresentInCatalog([FAST_IMPLEMENTER_MODEL], {
27723
+ requireToolCalls: true,
27724
+ minContextTokens: ONE_M_TOKENS
27725
+ });
27726
+ }
27727
+ function fastPlannerModel() {
27728
+ const id = firstPresentInCatalog([FAST_PLANNER_MODEL], {
27729
+ requireToolCalls: true,
27730
+ minContextTokens: ONE_M_TOKENS
27731
+ });
27732
+ if (!id) return void 0;
27733
+ const found = state.models?.data.find((m) => m.id === id);
27734
+ const efforts = found?.capabilities?.supports?.reasoning_effort;
27735
+ if (!Array.isArray(efforts) || !efforts.includes("high")) return void 0;
27736
+ if (pickEndpoint(found) !== "responses") return void 0;
27737
+ return id;
27738
+ }
27739
+ /** Gate Grok on the prompt limit that actually constrains pasted review input. */
27740
+ function fastReviewerModel() {
27741
+ const models = state.models?.data;
27742
+ if (!models) return void 0;
27743
+ const found = models.find((m) => m.id === FAST_REVIEWER_MODEL);
27744
+ if (!found) return void 0;
27745
+ if (found.capabilities?.supports?.tool_calls !== true) return void 0;
27746
+ const efforts = found.capabilities?.supports?.reasoning_effort;
27747
+ if (!Array.isArray(efforts) || !efforts.includes("medium")) return void 0;
27748
+ if ((found.capabilities?.limits?.max_prompt_tokens ?? 0) < 2e5) return void 0;
27749
+ if (pickEndpoint(found) !== "responses") return void 0;
27750
+ return FAST_REVIEWER_MODEL;
27751
+ }
27752
+ /** Exact Opus 5 only: the fast Oracle never inherits standard opus_critic's
27753
+ * older-family fallback. */
27754
+ function fastOracleModel() {
27755
+ const found = state.models?.data.find((m) => m.id === FAST_ORACLE_MODEL);
27756
+ if (!found) return void 0;
27757
+ if ((found.capabilities?.limits?.max_context_window_tokens ?? 0) < 1e6) return void 0;
27758
+ if ((found.capabilities?.limits?.max_prompt_tokens ?? 0) <= 0) return void 0;
27759
+ const efforts = found.capabilities?.supports?.reasoning_effort;
27760
+ if (!Array.isArray(efforts) || !efforts.includes("high")) return void 0;
27761
+ if (found.capabilities?.supports?.adaptive_thinking !== true) return void 0;
27762
+ if (!(found.supported_endpoints ?? []).some((endpoint) => endpoint === "/messages" || endpoint === "/v1/messages")) return void 0;
27763
+ return FAST_ORACLE_MODEL;
27764
+ }
27634
27765
  /**
27635
27766
  * Gate for the worker tools (`explore`, `review`, `implement`).
27636
27767
  *
@@ -27855,40 +27986,29 @@ function isLoopbackHost(host) {
27855
27986
  const hostname = idx >= 0 ? host.slice(0, idx) : host;
27856
27987
  return hostname === "127.0.0.1" || hostname === "localhost";
27857
27988
  }
27858
- /**
27859
- * Constant-time bearer compare. Random per-launch nonces aren't really
27860
- * timing-attackable in practice, but this costs nothing.
27861
- */
27862
- function nonceMatches(provided, expected) {
27863
- if (provided.length !== expected.length) return false;
27864
- const a = Buffer.from(provided);
27865
- const b = Buffer.from(expected);
27866
- try {
27867
- return timingSafeEqual(a, b);
27868
- } catch {
27869
- return false;
27870
- }
27871
- }
27872
27989
  function checkAuth(c) {
27873
27990
  if (!isLoopbackHost(c.req.header("host"))) return {
27874
27991
  ok: false,
27875
27992
  status: 403,
27876
27993
  reason: "non-loopback Host header rejected"
27877
27994
  };
27878
- const expected = state.peerMcpNonce;
27879
- if (!expected) return {
27995
+ if (state.launchRegistry.size === 0) return {
27880
27996
  ok: false,
27881
27997
  status: 401,
27882
27998
  reason: "/mcp not enabled in this proxy session"
27883
27999
  };
27884
28000
  const auth = c.req.header("authorization") ?? "";
27885
28001
  const m = /^Bearer\s+(.+)$/i.exec(auth);
27886
- if (!m || !nonceMatches(m[1], expected)) return {
28002
+ const launch = m ? findLaunchByNonce(m[1]) : void 0;
28003
+ if (!launch) return {
27887
28004
  ok: false,
27888
28005
  status: 401,
27889
28006
  reason: "missing or invalid Authorization bearer"
27890
28007
  };
27891
- return { ok: true };
28008
+ return {
28009
+ ok: true,
28010
+ launch
28011
+ };
27892
28012
  }
27893
28013
  /**
27894
28014
  * opus_critic's effective model, resolved against the live catalog.
@@ -27929,8 +28049,54 @@ function activePersonas() {
27929
28049
  };
27930
28050
  });
27931
28051
  }
27932
- function toolEntries(scope) {
27933
- const personaEntries = scope === "all" || scope === "peers" ? activePersonas().map((p) => ({
28052
+ function oracleToolEntry() {
28053
+ return {
28054
+ name: "oracle",
28055
+ description: "Fast-profile last-resort guidance from exact Opus 5 (1M context) at high effort. Stateless and tool-less: pass complete context plus one precise query only after the primary Luna path, Advisor, and the relevant reviewer/planner path remain stuck. It can advise or request missing information; it cannot inspect the repo, execute, merge, or authorize actions.",
28056
+ inputSchema: {
28057
+ type: "object",
28058
+ required: ["query", "context"],
28059
+ additionalProperties: false,
28060
+ properties: {
28061
+ query: {
28062
+ type: "string",
28063
+ description: "One precise unresolved question."
28064
+ },
28065
+ context: {
28066
+ type: "string",
28067
+ description: "Complete evidence and constraints needed to answer cold-start."
28068
+ }
28069
+ }
28070
+ }
28071
+ };
28072
+ }
28073
+ function toolEntries(scope, launch) {
28074
+ if (launch.profileId === "fast") {
28075
+ const entries = [];
28076
+ if ((scope === "all" || scope === "peers") && launch.allowedGroups?.has("peers") && launch.allowedPersonas?.has("oracle") && fastOracleModel()) entries.push(oracleToolEntry());
28077
+ for (const tool of NON_PERSONA_MCP_TOOLS) {
28078
+ if (scope !== "all" && tool.group !== scope) continue;
28079
+ if (!launch.allowedGroups?.has(tool.group)) continue;
28080
+ if (tool.group === "search") {
28081
+ entries.push({
28082
+ name: tool.toolNameHttp,
28083
+ description: tool.description,
28084
+ inputSchema: tool.inputSchema
28085
+ });
28086
+ continue;
28087
+ }
28088
+ if (tool.group !== "browser" || !browserToolsEnabled()) continue;
28089
+ if (tool.capability === "browser_compound" && !browserCompoundToolsEnabled()) continue;
28090
+ if (tool.capability === "browser_power" && !browserPowerToolsEnabled()) continue;
28091
+ entries.push({
28092
+ name: tool.toolNameHttp,
28093
+ description: tool.description,
28094
+ inputSchema: tool.inputSchema
28095
+ });
28096
+ }
28097
+ return entries;
28098
+ }
28099
+ const personaEntries = (!launch.allowedGroups || launch.allowedGroups.has("peers")) && (scope === "all" || scope === "peers") ? activePersonas().filter((p) => !launch.allowedPersonas || launch.allowedPersonas.has(p.toolNameHttp)).map((p) => ({
27934
28100
  name: p.toolNameHttp,
27935
28101
  description: p.description,
27936
28102
  inputSchema: {
@@ -27961,6 +28127,7 @@ function toolEntries(scope) {
27961
28127
  })) : [];
27962
28128
  const nonPersonaEntries = NON_PERSONA_MCP_TOOLS.filter((t) => {
27963
28129
  if (scope !== "all" && t.group !== scope) return false;
28130
+ if (launch.allowedGroups && !launch.allowedGroups.has(t.group)) return false;
27964
28131
  if (t.capability === "worker") return workerToolsEnabled();
27965
28132
  if (t.capability === "browse_agent") return browseAgentEnabled();
27966
28133
  if (t.capability === "stand_in") return standInToolEnabled();
@@ -28333,16 +28500,87 @@ function applySessionWorkspace(args, sessionWorkspace, tool) {
28333
28500
  }
28334
28501
  return "absent";
28335
28502
  }
28336
- async function handleToolsCall(body, scope, sessionWorkspace) {
28503
+ async function handleToolsCall(body, scope, launch, sessionWorkspace) {
28337
28504
  const params = body.params ?? {};
28338
28505
  const name = typeof params.name === "string" ? params.name : "";
28339
28506
  const args = params.arguments ?? {};
28340
28507
  if (!name) return rpcError(body.id, RPC_INVALID_PARAMS, "tools/call missing name");
28508
+ if (launch.profileId === "fast" && name === "oracle") {
28509
+ if (scope !== "all" && scope !== "peers" || !launch.allowedGroups?.has("peers") || !launch.allowedPersonas?.has("oracle") || !fastOracleModel()) return rpcError(body.id, RPC_METHOD_NOT_FOUND, `tools/call: unknown tool "${name}"`);
28510
+ const query = typeof args.query === "string" ? args.query.trim() : "";
28511
+ const context = typeof args.context === "string" ? args.context.trim() : "";
28512
+ if (!query || !context) return rpcError(body.id, RPC_INVALID_PARAMS, "tools/call: oracle requires non-empty arguments.query and arguments.context");
28513
+ const MAX_ORACLE_INPUT_BYTES = 262144;
28514
+ const oracleInput = `Query:\n${query}\n\nContext:\n${context}`;
28515
+ const inputBytes = Buffer.byteLength(oracleInput, "utf8");
28516
+ if (inputBytes > MAX_ORACLE_INPUT_BYTES) return rpcResult(body.id, toolError(`pre-flight rejected: oracle input is ${inputBytes} bytes, over the ${MAX_ORACLE_INPUT_BYTES}-byte cap; narrow the context without silently truncating it`));
28517
+ const oraclePersona = {
28518
+ agentName: "oracle",
28519
+ toolNameHttp: "oracle",
28520
+ model: "claude-opus-5",
28521
+ endpoint: "/v1/messages",
28522
+ description: "Fast-profile Oracle",
28523
+ baseInstructions: "You are Oracle, a stateless last-resort consultant. You have no tools or repository access. Answer only from the supplied context. Give focused guidance or ask for the exact missing information. Never claim to execute, approve, merge, or authorize an action.",
28524
+ agentPrompt: "",
28525
+ writeCapable: false,
28526
+ requiresHttp: true,
28527
+ allowedEfforts: ["high"],
28528
+ defaultEffort: "high"
28529
+ };
28530
+ const overflow = await predictedWindowOverflow(oraclePersona, oracleInput, void 0);
28531
+ if (overflow) return rpcResult(body.id, toolError(overflow));
28532
+ const release = acquireInFlightSlot();
28533
+ if (!release) return rpcResult(body.id, toolError(`Peer MCP queue full (${MAX_INFLIGHT_TOOLS_CALL} in-flight). Retry shortly.`));
28534
+ const startedAt = Date.now();
28535
+ const abortKey = body.id !== void 0 && body.id !== null ? body.id : void 0;
28536
+ const aborter = new AbortController();
28537
+ const inflightEntry = {
28538
+ aborter,
28539
+ release
28540
+ };
28541
+ if (abortKey !== void 0) inflightAborts.set(abortKey, inflightEntry);
28542
+ try {
28543
+ const text = await dispatchModelCall({
28544
+ model: "claude-opus-5",
28545
+ endpoint: "/v1/messages",
28546
+ instructions: oraclePersona.baseInstructions,
28547
+ userText: oracleInput,
28548
+ effort: "high",
28549
+ signal: aborter.signal
28550
+ });
28551
+ logTelemetry({
28552
+ name: "oracle",
28553
+ model: "claude-opus-5",
28554
+ durationMs: Date.now() - startedAt,
28555
+ result: "ok"
28556
+ });
28557
+ return rpcResult(body.id, { content: [{
28558
+ type: "text",
28559
+ text
28560
+ }] });
28561
+ } catch (err) {
28562
+ const message = err instanceof Error ? err.message : String(err);
28563
+ logTelemetry({
28564
+ name: "oracle",
28565
+ model: "claude-opus-5",
28566
+ durationMs: Date.now() - startedAt,
28567
+ result: "exception",
28568
+ errorMessage: message
28569
+ });
28570
+ return rpcResult(body.id, toolError(`oracle failed: ${message}`));
28571
+ } finally {
28572
+ if (abortKey !== void 0 && inflightAborts.get(abortKey) === inflightEntry) inflightAborts.delete(abortKey);
28573
+ release();
28574
+ }
28575
+ }
28576
+ if (launch.profileId === "fast" && PERSONAS_READ.some((p) => p.toolNameHttp === name)) return rpcError(body.id, RPC_METHOD_NOT_FOUND, `tools/call: unknown tool "${name}"`);
28341
28577
  const persona = activePersonas().find((p) => p.toolNameHttp === name);
28342
28578
  const nonPersonaTool = persona ? void 0 : NON_PERSONA_MCP_TOOLS.find((t) => t.toolNameHttp === name);
28343
28579
  if (!persona && !nonPersonaTool) return rpcError(body.id, RPC_METHOD_NOT_FOUND, `tools/call: unknown tool "${name}"`);
28344
28580
  const toolGroup = persona ? "peers" : nonPersonaTool.group;
28345
28581
  if (scope !== "all" && toolGroup !== scope) return rpcError(body.id, RPC_METHOD_NOT_FOUND, `tools/call: unknown tool "${name}"`);
28582
+ if (launch.allowedGroups && !launch.allowedGroups.has(toolGroup)) return rpcError(body.id, RPC_METHOD_NOT_FOUND, `tools/call: unknown tool "${name}"`);
28583
+ if (persona && launch.allowedPersonas && !launch.allowedPersonas.has(persona.toolNameHttp)) return rpcError(body.id, RPC_METHOD_NOT_FOUND, `tools/call: unknown tool "${name}"`);
28346
28584
  if (nonPersonaTool && nonPersonaTool.capability === "worker" && !workerToolsEnabled()) return rpcError(body.id, RPC_METHOD_NOT_FOUND, `tools/call: unknown tool "${name}"`);
28347
28585
  if (nonPersonaTool && nonPersonaTool.capability === "browse_agent" && !browseAgentEnabled()) return rpcError(body.id, RPC_METHOD_NOT_FOUND, `tools/call: unknown tool "${name}"`);
28348
28586
  if (nonPersonaTool && nonPersonaTool.capability === "stand_in" && !standInToolEnabled()) return rpcError(body.id, RPC_METHOD_NOT_FOUND, `tools/call: unknown tool "${name}"`);
@@ -28450,7 +28688,7 @@ function handleCancelledNotification(body) {
28450
28688
  }
28451
28689
  cancelInflight(requestId, "client requested cancellation");
28452
28690
  }
28453
- async function handleRpc(_c, body, scope, sessionWorkspace) {
28691
+ async function handleRpc(_c, body, scope, launch, sessionWorkspace) {
28454
28692
  if (body === null || typeof body !== "object" || Array.isArray(body)) return {
28455
28693
  status: 200,
28456
28694
  body: rpcError(null, RPC_INVALID_REQUEST, "jsonrpc 2.0 envelope required")
@@ -28492,7 +28730,7 @@ async function handleRpc(_c, body, scope, sessionWorkspace) {
28492
28730
  };
28493
28731
  return {
28494
28732
  status: 200,
28495
- body: rpcResult(body.id, { tools: toolEntries(scope) })
28733
+ body: rpcResult(body.id, { tools: toolEntries(scope, launch) })
28496
28734
  };
28497
28735
  case "tools/call":
28498
28736
  if (isNotification) return {
@@ -28501,7 +28739,7 @@ async function handleRpc(_c, body, scope, sessionWorkspace) {
28501
28739
  };
28502
28740
  return {
28503
28741
  status: 200,
28504
- body: await handleToolsCall(body, scope, sessionWorkspace)
28742
+ body: await handleToolsCall(body, scope, launch, sessionWorkspace)
28505
28743
  };
28506
28744
  case "resources/list":
28507
28745
  if (isNotification) return {
@@ -28581,6 +28819,7 @@ async function handleRpc(_c, body, scope, sessionWorkspace) {
28581
28819
  async function handleMcpPost(c, scopeArg = "all") {
28582
28820
  const auth = checkAuth(c);
28583
28821
  if (!auth.ok) return c.json(rpcError(null, RPC_INVALID_REQUEST, auth.reason), auth.status);
28822
+ const { launch } = auth;
28584
28823
  let scope;
28585
28824
  if (scopeArg === "all") scope = "all";
28586
28825
  else if (isMcpGroup(scopeArg)) scope = scopeArg;
@@ -28597,13 +28836,13 @@ async function handleMcpPost(c, scopeArg = "all") {
28597
28836
  const nm = typeof body.params?.name === "string" ? body.params.name : "?";
28598
28837
  process.stderr.write(`[peer-mcp] recv t=${Date.now()} name=${nm} scope=${scope} inflight=${currentInFlight()}\n`);
28599
28838
  }
28600
- if (typeof body === "object" && body !== null && !Array.isArray(body) && body.method === "tools/call" && acceptsEventStream(c.req.header("accept"))) return handleToolsCallSSE(body, scope, sessionWorkspace);
28839
+ if (typeof body === "object" && body !== null && !Array.isArray(body) && body.method === "tools/call" && acceptsEventStream(c.req.header("accept"))) return handleToolsCallSSE(body, scope, launch, sessionWorkspace);
28601
28840
  if (typeof body === "object" && body !== null && !Array.isArray(body) && body.method === "tools/call") {
28602
28841
  const preflight = jsonPathPreflightCap(body, scope);
28603
28842
  if (preflight) return c.json(preflight, 200);
28604
28843
  }
28605
28844
  try {
28606
- const { status, body: respBody } = await handleRpc(c, body, scope, sessionWorkspace);
28845
+ const { status, body: respBody } = await handleRpc(c, body, scope, launch, sessionWorkspace);
28607
28846
  if (respBody === null) return c.body(null, status);
28608
28847
  return c.json(respBody, status);
28609
28848
  } catch (err) {
@@ -28656,9 +28895,9 @@ function acceptsEventStream(accept) {
28656
28895
  * "Invalid state: Controller is already closed" race without warning.
28657
28896
  */
28658
28897
  const SSE_HEARTBEAT_INTERVAL_MS = 5e3;
28659
- async function handleToolsCallSSE(body, scope, sessionWorkspace) {
28898
+ async function handleToolsCallSSE(body, scope, launch, sessionWorkspace) {
28660
28899
  const encoder = new TextEncoder();
28661
- const callPromise = handleToolsCall(body, scope, sessionWorkspace);
28900
+ const callPromise = handleToolsCall(body, scope, launch, sessionWorkspace);
28662
28901
  let heartbeatHandle;
28663
28902
  const stream = new ReadableStream({
28664
28903
  async start(controller) {
@@ -29335,13 +29574,18 @@ const ADVISOR_INTERNAL_TOOL_NAME = "__anthropic_advisor";
29335
29574
  const ADVISOR_CLIENT_TOOL_NAME = "advisor";
29336
29575
  /** Default advisor model + reasoning effort. Per gemini-critic + user
29337
29576
  * direction: hardcode to a cross-lab model (gpt-5.6-sol — Copilot's
29338
- * /responses-only flagship) at xhigh effort. The cross-lab choice
29339
- * gives a true "second set of eyes" instead of the main model
29340
- * reviewing itself; xhigh effort buys the deep-dive reasoning that
29341
- * matches Anthropic's own ADVISOR (which uses a stronger reviewer
29342
- * model Opus 4.6/Sonnet 4.6 typically). */
29577
+ * /responses-only flagship). The cross-lab choice gives a true "second set
29578
+ * of eyes" instead of the main model reviewing itself.
29579
+ *
29580
+ * Effort default is `high`, not the historical `xhigh`: `resolveAdvisorEffort`
29581
+ * no longer floors the picked effort (see that function), so `high` is both
29582
+ * the default AND the lowest the advisor will ever think at when the picker
29583
+ * expresses no preference — a deliberate, user-approved cost/depth trade
29584
+ * applied uniformly across every advisor target (Sol, the Opus escalation,
29585
+ * and the fast-profile Gemini advisor below all read this same constant). */
29343
29586
  const ADVISOR_DEFAULT_MODEL = "gpt-5.6-sol";
29344
29587
  const ADVISOR_DEFAULT_EFFORT = "xhigh";
29588
+ const ADVISOR_MIN_EFFORT = "high";
29345
29589
  /** The Anthropic frontier model the advisor escalates to when the LEAD is a
29346
29590
  * lighter Claude tier (sonnet, haiku).
29347
29591
  *
@@ -29358,16 +29602,32 @@ const ADVISOR_DEFAULT_EFFORT = "xhigh";
29358
29602
  * the decorrelation instrument and are untouched. `GH_ROUTER_ADVISOR_MODEL`
29359
29603
  * keeps a cross-lab advisor one env var away for anyone who wants it back. */
29360
29604
  const ADVISOR_ESCALATION_MODEL = "claude-opus-5";
29361
- /** Floor for the advisor's reasoning effort.
29362
- *
29363
- * The advisor follows the Claude Code effort picker (see `resolveAdvisorEffort`)
29364
- * so dialing the picker down makes it cheaper, but it does NOT follow it all the
29365
- * way to the bottom of `EFFORT_ORDER`. The advisor fires a handful of times per
29366
- * session (`ADVISOR_MAX_TURNS`, typically 1-3), so it is not the budget line —
29367
- * the lead's own turns are while an advisor reasoning at `none`/`low` cannot
29368
- * do the job the consultation exists for. The picker therefore governs the
29369
- * `high..max` range. */
29370
- const ADVISOR_MIN_EFFORT = "high";
29605
+ /** The Advisor model for the fast Luna profile. Gemini 3.7 Flash is a
29606
+ * different lab from BOTH the Luna lead (OpenAI) and the fast profile's
29607
+ * `gemini-critic` persona shares this same model see
29608
+ * `docs/default-models.md` "Fast launch profile" for the roster this
29609
+ * belongs to. Kept distinct from `ADVISOR_DEFAULT_MODEL` so the two never
29610
+ * have to agree; `resolveAdvisorModel` picks between them purely on lead
29611
+ * identity, never model availability heuristics beyond a live-catalog
29612
+ * presence check (mirrors `shouldEscalateAdvisor`'s pattern). */
29613
+ const ADVISOR_FAST_PROFILE_MODEL = "gemini-3.7-flash";
29614
+ /**
29615
+ * True when `leadModel` names the fast-profile Luna lead (bare, or with the
29616
+ * `[1m]` context decoration `withOneMSuffixForLead` applies to it).
29617
+ */
29618
+ function isFastProfileLead(leadModel) {
29619
+ if (!leadModel) return false;
29620
+ const bare = leadModel.replace(/\[1m\]$/, "").trim();
29621
+ const lastSegment = bare.slice(bare.lastIndexOf("/") + 1);
29622
+ return bare === "gpt-5.6-luna" || lastSegment === "gpt-5.6-luna";
29623
+ }
29624
+ /** True when the live catalog actually carries `ADVISOR_FAST_PROFILE_MODEL`.
29625
+ * Mirrors `shouldEscalateAdvisor`'s catalog probe: never advertise a model
29626
+ * the account cannot reach, and fall back to the cross-lab default instead
29627
+ * of a hard failure when it's absent. */
29628
+ function fastProfileAdvisorAvailable() {
29629
+ return state.models?.data?.some((m) => m.id === "gemini-3.7-flash") ?? false;
29630
+ }
29371
29631
  /** Output cap for the Anthropic-branch advisor call when the catalog carries no
29372
29632
  * limits for the resolved model. The value the branch used unconditionally
29373
29633
  * before it became reachable, kept so a catalog-less path is no worse off. */
@@ -29399,6 +29659,28 @@ function advisorUsesResponses(resolvedAdvisorModel) {
29399
29659
  if (endpoints && endpoints.length > 0) return endpoints.some((e) => ADVISOR_RESPONSES_ENDPOINTS.has(e));
29400
29660
  return /^(gpt-|o\d|.*codex)/i.test(bare);
29401
29661
  }
29662
+ /**
29663
+ * Decide `advisorTransport` for a resolved advisor model id.
29664
+ *
29665
+ * Order matters: Claude identity is checked FIRST and wins even though
29666
+ * `claude-opus-5` also advertises `/chat/completions` in the live catalog —
29667
+ * the historical branch never sent Claude to chat, and this preserves that
29668
+ * byte-for-byte (reuses the SAME classifier `classifyMessagesRoute` uses for
29669
+ * the main `/v1/messages` shim fork, so the two surfaces cannot disagree
29670
+ * about what counts as a Claude model). Responses is checked next
29671
+ * (`advisorUsesResponses`, unchanged — catalog-first, name-regex fallback,
29672
+ * still exported and directly tested on its own). Anything else defaults to
29673
+ * chat, mirroring `pickEndpoint`'s "omits supported_endpoints => chat-eligible"
29674
+ * convention — the same convention `classifyMessagesRoute` relies on for a
29675
+ * lead model, applied here to the advisor's OWN model instead.
29676
+ */
29677
+ function advisorTransport(resolvedAdvisorModel) {
29678
+ const bare = resolvedAdvisorModel.slice(resolvedAdvisorModel.lastIndexOf("/") + 1);
29679
+ const entry = state.models?.data?.find((m) => m.id === resolvedAdvisorModel || m.id === bare);
29680
+ if (isClaudeModel(resolvedAdvisorModel, entry)) return "messages";
29681
+ if (advisorUsesResponses(resolvedAdvisorModel)) return "responses";
29682
+ return "chat";
29683
+ }
29402
29684
  /** True when the model advertises a usable reasoning-effort ladder. */
29403
29685
  function advertisedEffortLadder(resolvedAdvisorModel) {
29404
29686
  const supported = state.models?.data?.find((m) => m.id === resolvedAdvisorModel)?.capabilities?.supports?.reasoning_effort;
@@ -29435,10 +29717,16 @@ function shouldEscalateAdvisor(leadModel) {
29435
29717
  * Precedence:
29436
29718
  * 1. `GH_ROUTER_ADVISOR_MODEL` (trimmed) — the operator pin, checked first so
29437
29719
  * it works on every lead.
29438
- * 2. A lighter Claude lead with the escalation model in the catalog.
29439
- * 3. `ADVISOR_DEFAULT_MODEL`.
29720
+ * 2. An authenticated fast launch whose current lead is Luna, with
29721
+ * `ADVISOR_FAST_PROFILE_MODEL` present in the live catalog.
29722
+ * 3. A lighter Claude lead with the escalation model in the catalog.
29723
+ * 4. `ADVISOR_DEFAULT_MODEL`.
29440
29724
  *
29441
- * Step 3 returns the LITERAL constant rather than walking the OpenAI frontier
29725
+ * Steps 2 and 3 are mutually exclusive lead families (non-Claude Luna vs. a
29726
+ * lighter Claude tier) so their relative order does not matter functionally;
29727
+ * fast-profile is checked first only because it is the more specific match.
29728
+ *
29729
+ * Step 4 returns the LITERAL constant rather than walking the OpenAI frontier
29442
29730
  * chain. An Opus lead must resolve to exactly what it resolves to today, and a
29443
29731
  * frontier walk could yield `gpt-5.5` on a catalog missing `gpt-5.6-sol` —
29444
29732
  * a silent change to the one path that is required not to move.
@@ -29467,19 +29755,27 @@ function normalizeAdvisorPin(pinned) {
29467
29755
  const bare = pinned.slice(pinned.lastIndexOf("/") + 1);
29468
29756
  return bare !== pinned && models.some((m) => m.id === bare) ? bare : pinned;
29469
29757
  }
29470
- function resolveAdvisorModel(leadModel) {
29758
+ function resolveAdvisorModel(leadModel, fastProfile = false) {
29471
29759
  const pinned = process.env.GH_ROUTER_ADVISOR_MODEL?.trim();
29472
29760
  if (pinned) return {
29473
29761
  model: normalizeAdvisorPin(pinned),
29474
- escalated: false
29762
+ escalated: false,
29763
+ fastProfile: false
29764
+ };
29765
+ if (fastProfile && leadModel && isFastProfileLead(leadModel) && fastProfileAdvisorAvailable()) return {
29766
+ model: ADVISOR_FAST_PROFILE_MODEL,
29767
+ escalated: false,
29768
+ fastProfile: true
29475
29769
  };
29476
29770
  if (leadModel && shouldEscalateAdvisor(leadModel)) return {
29477
29771
  model: ADVISOR_ESCALATION_MODEL,
29478
- escalated: true
29772
+ escalated: true,
29773
+ fastProfile: false
29479
29774
  };
29480
29775
  return {
29481
29776
  model: ADVISOR_DEFAULT_MODEL,
29482
- escalated: false
29777
+ escalated: false,
29778
+ fastProfile: false
29483
29779
  };
29484
29780
  }
29485
29781
  /**
@@ -29501,13 +29797,16 @@ function resolveAdvisorModel(leadModel) {
29501
29797
  * 3. `ADVISOR_DEFAULT_EFFORT` — a request expressing no preference behaves
29502
29798
  * exactly as it did before the picker was honored at all.
29503
29799
  *
29504
- * Then floor, THEN clamp. The order is load-bearing: a model whose ceiling sits
29505
- * below `ADVISOR_MIN_EFFORT` must still receive a value it accepts, so the clamp
29506
- * is allowed to pull back under the floor. Flipping the two would forward an
29507
- * effort upstream rejects.
29800
+ * There is deliberately NO floor anymore (removed per the user-approved
29801
+ * "default high, no floor" change): the advisor follows the picker all the way
29802
+ * down as well as up, so an explicit `none`/`low` pick is honored rather than
29803
+ * clamped up to a minimum. The only remaining adjustment is the CEILING clamp
29804
+ * against the resolved advisor's own live `reasoning_effort` allowlist — a
29805
+ * model whose ladder tops out below the requested tier still needs to receive
29806
+ * something it accepts.
29508
29807
  */
29509
- function resolveAdvisorEffort(rawRequestBody, advisorModel) {
29510
- let requested = ADVISOR_DEFAULT_EFFORT;
29808
+ function resolveAdvisorEffort(rawRequestBody, advisorModel, fastProfile = false) {
29809
+ let requested = fastProfile ? "high" : ADVISOR_DEFAULT_EFFORT;
29511
29810
  if (rawRequestBody) try {
29512
29811
  const body = JSON.parse(rawRequestBody);
29513
29812
  const oc = body.output_config;
@@ -29516,10 +29815,11 @@ function resolveAdvisorEffort(rawRequestBody, advisorModel) {
29516
29815
  if (typeof explicit === "string" && EFFORT_ORDER.includes(explicit)) requested = explicit;
29517
29816
  else if (thinking && typeof thinking === "object" && thinking.type === "enabled") requested = bucketEffort(thinking.budget_tokens);
29518
29817
  } catch {}
29519
- const floored = EFFORT_ORDER.indexOf(requested) < EFFORT_ORDER.indexOf(ADVISOR_MIN_EFFORT) ? ADVISOR_MIN_EFFORT : requested;
29818
+ if (fastProfile) requested = "high";
29819
+ else if (EFFORT_ORDER.indexOf(requested) < EFFORT_ORDER.indexOf(ADVISOR_MIN_EFFORT)) requested = ADVISOR_MIN_EFFORT;
29520
29820
  const supported = state.models?.data?.find((m) => m.id === resolveModel(advisorModel))?.capabilities?.supports?.reasoning_effort;
29521
- if (!Array.isArray(supported) || supported.length === 0) return floored;
29522
- return clampEffort(floored, supported);
29821
+ if (!Array.isArray(supported) || supported.length === 0) return requested;
29822
+ return clampEffort(requested, supported);
29523
29823
  }
29524
29824
  /** ADVISOR_TOOL_INSTRUCTIONS verbatim from cc-backup
29525
29825
  * src/utils/advisor.ts — describes when the model should invoke
@@ -29749,7 +30049,8 @@ async function runAdvisor(conversation, advisorModel, advisorEffort, signal, adv
29749
30049
  maxUnits = ADVISOR_MAX_CONVERSATION_CHARS;
29750
30050
  }
29751
30051
  const conversationText = renderConversationAsText(conversation, maxUnits, measure);
29752
- if (advisorUsesResponses(resolvedAdvisorModel)) {
30052
+ const transport = advisorTransport(resolvedAdvisorModel);
30053
+ if (transport === "responses") {
29753
30054
  const payload = applyResponsesCachePolicy({
29754
30055
  model: resolvedAdvisorModel,
29755
30056
  instructions: advisorSystem,
@@ -29784,6 +30085,27 @@ async function runAdvisor(conversation, advisorModel, advisorEffort, signal, adv
29784
30085
  if (!text) throw new Error(`Advisor model ${resolvedAdvisorModel} returned empty assistant output`);
29785
30086
  return text;
29786
30087
  }
30088
+ if (transport === "chat") {
30089
+ const ladder = advertisedEffortLadder(resolvedAdvisorModel);
30090
+ const chatPayload = {
30091
+ model: resolvedAdvisorModel,
30092
+ messages: [{
30093
+ role: "system",
30094
+ content: advisorSystem
30095
+ }, {
30096
+ role: "user",
30097
+ content: conversationText
30098
+ }],
30099
+ stream: false,
30100
+ ...ladder ? { reasoning_effort: advisorEffort } : {}
30101
+ };
30102
+ const text = (await withTransientRetry(() => createChatCompletions(chatPayload, void 0, signal), {
30103
+ signal,
30104
+ label: resolvedAdvisorModel
30105
+ })).choices?.[0]?.message?.content;
30106
+ if (typeof text !== "string" || text.length === 0) throw new Error(`Advisor model ${resolvedAdvisorModel} returned empty response`);
30107
+ return text;
30108
+ }
29787
30109
  const advisorEntry = state.models?.data?.find((m) => m.id === resolvedAdvisorModel);
29788
30110
  const limits = advisorEntry?.capabilities?.limits;
29789
30111
  const maxTokens = limits?.max_non_streaming_output_tokens ?? limits?.max_output_tokens ?? ADVISOR_FALLBACK_MAX_OUTPUT_TOKENS;
@@ -29812,20 +30134,51 @@ async function runAdvisor(conversation, advisorModel, advisorEffort, signal, adv
29812
30134
  /**
29813
30135
  * Derive a spec-compliant `srvtoolu_*` id for a client-facing
29814
30136
  * `server_tool_use` (and matching `advisor_tool_result.tool_use_id`)
29815
- * from the upstream model's `toolu_*` id.
29816
- *
29817
- * Anthropic spec: `^srvtoolu_[a-zA-Z0-9_]+$`. If the upstream id
29818
- * suffix contains chars outside that charset (e.g., a hyphenated id
29819
- * from a non-Anthropic provider, or a corrupt id), fall back to a
29820
- * synthesized stable id keyed by the SSE block index. Defensive
29821
- * against edge cases that would otherwise emit a malformed block —
29822
- * spec violation in either direction is a 400.
29823
- */
29824
- function toClientServerToolUseId(id, _fallbackIndex) {
29825
- if (!id.startsWith("toolu_")) throw new Error("advisor tool_use id is not round-trippable");
29826
- const suffix = id.slice(6);
29827
- if (!/^[a-zA-Z0-9_]+$/.test(suffix)) throw new Error("advisor tool_use id is not round-trippable");
29828
- return `srvtoolu_${suffix}`;
30137
+ * from the upstream model's tool-call id.
30138
+ *
30139
+ * TOTAL never throws. Two paths:
30140
+ *
30141
+ * 1. A real Anthropic `toolu_*` id whose suffix is already in the
30142
+ * `^[a-zA-Z0-9_]+$` charset: `srvtoolu_<suffix>`, byte-for-byte
30143
+ * identical to the historical (Claude-lead) behavior.
30144
+ * 2. Anything else a Responses `call_*` id (the fast Luna profile's
30145
+ * lead, once its `tool_use{__anthropic_advisor}` block is synthesized
30146
+ * by the anthropic-translate shim from a Copilot `/responses` tool
30147
+ * call), a hyphenated or otherwise non-conforming id, an empty string,
30148
+ * unicode, or a corrupt id — sanitize to the Anthropic charset and
30149
+ * prefix with `fallbackIndex` (the caller's per-block synthetic stream
30150
+ * index, unique within one `buildAdvisorStream` run) so two different
30151
+ * raw ids that happen to sanitize to the same string can never
30152
+ * collide. `fallbackIndex` is REQUIRED for this path's uniqueness
30153
+ * guarantee — callers must pass a value that is unique per call within
30154
+ * one advisor stream (every call site does: `myIndex` from the
30155
+ * turn processor's monotonic `nextSyntheticIndex`).
30156
+ *
30157
+ * This function ONLY has to produce a valid, deterministic, collision-free
30158
+ * LABEL — the original raw id is preserved separately for Copilot replay
30159
+ * (`CapturedBlock.advisorReplay.id`), never reconstructed from the derived
30160
+ * client id. That is what makes totality safe: there is no bijective-decode
30161
+ * requirement on this function itself, only on the (id, clientId) pairing a
30162
+ * caller keeps alongside it.
30163
+ *
30164
+ * Historically this threw "advisor tool_use id is not round-trippable" for
30165
+ * any non-`toolu_` shape. That was correct for a Claude-only advisor lead —
30166
+ * Copilot's native `/v1/messages` never emits anything else — but became a
30167
+ * live defect once the advisor loop could run on a non-Claude (Luna) lead
30168
+ * shimmed through `/responses`: `responses-egress.ts` forwards a Responses
30169
+ * `call_*` id VERBATIM as the synthesized `tool_use.id` (see
30170
+ * `makeToolUseId` — it only synthesizes a `toolu_*` id when the upstream id
30171
+ * is EMPTY), so the advisor's `tool_use{__anthropic_advisor}` block on that
30172
+ * lead legitimately carries a `call_*` id and the throw fired on every
30173
+ * single advisor call.
30174
+ */
30175
+ function toClientServerToolUseId(id, fallbackIndex) {
30176
+ if (id.startsWith("toolu_")) {
30177
+ const suffix = id.slice(6);
30178
+ if (/^[a-zA-Z0-9_]+$/.test(suffix)) return `srvtoolu_${suffix}`;
30179
+ }
30180
+ const sanitized = id.replace(/[^a-zA-Z0-9_]/g, "_");
30181
+ return `srvtoolu_gen${fallbackIndex}${sanitized.length > 0 ? `_${sanitized}` : ""}`;
29829
30182
  }
29830
30183
  /**
29831
30184
  * Build an SSE event line in the canonical Anthropic shape:
@@ -29837,6 +30190,35 @@ function sseEvent(type, data) {
29837
30190
  return `event: ${type}\ndata: ${JSON.stringify(data)}\n\n`;
29838
30191
  }
29839
30192
  /**
30193
+ * The default `continueTurn` for `buildAdvisorStream`: native Claude
30194
+ * passthrough (`createMessages`) plus signed-thinking-history repair-and-retry.
30195
+ * Extracted verbatim from the loop body so the behavior is byte-identical to
30196
+ * before `continueTurn` became injectable, and so a non-Claude
30197
+ * `continueTurn` (the fast Luna profile's shim-backed one) can omit this
30198
+ * Claude-only repair path entirely rather than inherit dead code that would
30199
+ * never fire for it.
30200
+ */
30201
+ async function defaultContinueTurn(body, signal, requestHeaders) {
30202
+ let continuationSend = JSON.stringify(body);
30203
+ const knownRepair = repairKnownThinkingHistory(continuationSend);
30204
+ if (knownRepair) continuationSend = knownRepair.body;
30205
+ try {
30206
+ return await createMessages(continuationSend, requestHeaders, signal, true);
30207
+ } catch (continuationError) {
30208
+ if (!(continuationError instanceof HTTPError)) throw continuationError;
30209
+ const errorBody = await continuationError.response.clone().text().catch(() => "");
30210
+ const outcome = repairRejectedThinkingHistory(continuationSend, errorBody);
30211
+ if (!outcome.ok) {
30212
+ consola.warn(`Advisor continuation thinking-history repair declined: ${formatThinkingRepairDecline(outcome.decline)}`);
30213
+ throw continuationError;
30214
+ }
30215
+ consola.warn(`Advisor continuation: retrying without rejected thinking blocks: message=${outcome.repair.messageIndex} removed_blocks=${outcome.repair.removedBlocks}`);
30216
+ const response = await createMessages(outcome.repair.body, requestHeaders, signal, true);
30217
+ rememberThinkingHistoryRepair(outcome.repair.fingerprint);
30218
+ return response;
30219
+ }
30220
+ }
30221
+ /**
29840
30222
  * The streaming translate-loop. Returns a ReadableStream<Uint8Array>
29841
30223
  * suitable to wrap with Hono's c.body() / new Response().
29842
30224
  *
@@ -29855,6 +30237,7 @@ function buildAdvisorStream(opts) {
29855
30237
  const advisorModel = opts.advisorModel ?? "gpt-5.6-sol";
29856
30238
  const advisorEffort = opts.advisorEffort ?? "xhigh";
29857
30239
  const advisorEscalated = opts.advisorEscalated ?? false;
30240
+ const continueTurn = opts.continueTurn ?? ((body, signal) => defaultContinueTurn(body, signal, opts.requestHeaders));
29858
30241
  const aborter = opts.externalAborter ?? new AbortController();
29859
30242
  let conversation = [...opts.initialConversation];
29860
30243
  return new ReadableStream({
@@ -30152,27 +30535,11 @@ function buildAdvisorStream(opts) {
30152
30535
  }))
30153
30536
  });
30154
30537
  if (aborter.signal.aborted) return;
30155
- let continuationSend = JSON.stringify({
30538
+ response = await continueTurn({
30156
30539
  ...opts.baseBody,
30157
30540
  messages: conversation,
30158
30541
  stream: true
30159
- });
30160
- const knownRepair = repairKnownThinkingHistory(continuationSend);
30161
- if (knownRepair) continuationSend = knownRepair.body;
30162
- try {
30163
- response = await createMessages(continuationSend, opts.requestHeaders, aborter.signal, true);
30164
- } catch (continuationError) {
30165
- if (!(continuationError instanceof HTTPError)) throw continuationError;
30166
- const errorBody = await continuationError.response.clone().text().catch(() => "");
30167
- const outcome = repairRejectedThinkingHistory(continuationSend, errorBody);
30168
- if (!outcome.ok) {
30169
- consola.warn(`Advisor continuation thinking-history repair declined: ${formatThinkingRepairDecline(outcome.decline)}`);
30170
- throw continuationError;
30171
- }
30172
- consola.warn(`Advisor continuation: retrying without rejected thinking blocks: message=${outcome.repair.messageIndex} removed_blocks=${outcome.repair.removedBlocks}`);
30173
- response = await createMessages(outcome.repair.body, opts.requestHeaders, aborter.signal, true);
30174
- rememberThinkingHistoryRepair(outcome.repair.fingerprint);
30175
- }
30542
+ }, aborter.signal);
30176
30543
  }
30177
30544
  if (aborter.signal.aborted) return;
30178
30545
  const finalIndex = nextSyntheticIndex++;
@@ -31988,6 +32355,17 @@ const ADVISOR_PARAMS = Type$1.Object({ concern: Type$1.String({
31988
32355
  description: "What you want a second pair of eyes on — your current approach, the blocker you're stuck on, or the decision you're about to commit. Required: the advisor needs a focal point.",
31989
32356
  minLength: 1
31990
32357
  }) });
32358
+ /**
32359
+ * Fixed reasoning effort for the WORKER'S own `advisor` tool call (distinct
32360
+ * from the server-side ADVISOR mechanism's `ADVISOR_DEFAULT_EFFORT` in
32361
+ * `src/services/advisor/advisor.ts`, which now follows the Claude Code
32362
+ * effort picker and defaults to `high`). This tool has no picker to follow —
32363
+ * it is a single fixed-effort consultation a worker triggers explicitly, not
32364
+ * a per-request value derived from client input — so it keeps its own
32365
+ * historical constant rather than sharing one that started varying for an
32366
+ * unrelated reason.
32367
+ */
32368
+ const WORKER_ADVISOR_EFFORT = "xhigh";
31991
32369
  /** Advisor transcript budget — leaves headroom in the advisor's
31992
32370
  * context window after the system prompt + concern + reasoning
31993
32371
  * overhead. Truncate-from-front so the most recent turn (where the
@@ -32089,7 +32467,7 @@ function advisorTool(getMessages) {
32089
32467
  }]
32090
32468
  }],
32091
32469
  stream: false,
32092
- reasoning: { effort: ADVISOR_DEFAULT_EFFORT }
32470
+ reasoning: { effort: WORKER_ADVISOR_EFFORT }
32093
32471
  }, { workload: "reusable-prefix" });
32094
32472
  const text = extractResponsesText(await createResponses(payload, void 0, signal, true));
32095
32473
  if (!text) throw new Error("advisor returned empty output");
@@ -35172,6 +35550,17 @@ function buildAgentPrompt(persona, opts) {
35172
35550
  */
35173
35551
  function buildPeerAwarenessSnippet(opts) {
35174
35552
  const key = (g) => opts.groupKeys?.[g] ?? GROUP_META[g].preferredKey;
35553
+ if (opts.profile === "fast") {
35554
+ const fastPeersKey = key("peers");
35555
+ const fastSearchKey = key("search");
35556
+ return [
35557
+ "## Peer review and advisor",
35558
+ "",
35559
+ `This is the fast launch profile. \`mcp__${fastPeersKey}__oracle\` is exact Opus 5 (1M/high), a stateless last-resort consultant after the primary Luna path, Advisor, and reviewer/planner remain stuck. Advisor is the transcript-aware brainstorming, sounding-board, fresh-look, uncertainty, and stuck path.`,
35560
+ "",
35561
+ `\`mcp__${fastSearchKey}__code\` is semantic-first code search and \`mcp__${fastSearchKey}__web\` surfaces citable sources. Native Task roster: \`scout\` (broad discovery), \`implementer\` (mechanical implementation), \`reviewer\` (repo-aware verification/reproduction), and \`planner\` (Sol plan consultant/approver after Luna's draft). Before implementation obtain planner approval; before declaring done run relevant tests and ask reviewer to verify.${opts.browseAvailable ? ` \`mcp__${key("browser")}__*\` is the opt-in browser surface.` : ""}`
35562
+ ].join("\n");
35563
+ }
35175
35564
  const peersKey = key("peers");
35176
35565
  const searchKey = key("search");
35177
35566
  const workersKey = key("workers");
@@ -35225,6 +35614,12 @@ function buildPeerAwarenessSnippet(opts) {
35225
35614
  */
35226
35615
  function buildPeerAwarenessSummary(opts) {
35227
35616
  const key = (g) => opts.groupKeys?.[g] ?? GROUP_META[g].preferredKey;
35617
+ if (opts.profile === "fast") return [
35618
+ "## Injected capabilities (summary)",
35619
+ "",
35620
+ "Fast launch profile. Task roster: `scout`, `implementer`, `reviewer`, `planner`. Luna investigates and drafts; `planner` must approve before implementation. Before declaring done, run relevant tests and ask `reviewer` to verify.",
35621
+ `Advisor is the transcript-aware brainstorming/sounding-board/fresh-look path. \`mcp__${key("peers")}__oracle\` is exact Opus 5 (1M/high), stateless and last resort. \`mcp__${key("search")}__code\` and \`mcp__${key("search")}__web\` provide search.${opts.browseAvailable ? ` \`mcp__${key("browser")}__*\` provides the opt-in browser.` : ""}`
35622
+ ].join("\n");
35228
35623
  const renderNative = (name) => {
35229
35624
  const modelId = opts.nativeAgentModels?.[name];
35230
35625
  if (!modelId) return `\`${name}\``;
@@ -35259,6 +35654,22 @@ function buildPeerAwarenessSummary(opts) {
35259
35654
  return lines.join("\n");
35260
35655
  }
35261
35656
  /**
35657
+ * Translate a `toolNameHttp`-keyed persona allowlist (the currency
35658
+ * `LaunchProfileDescriptor.personaAllowlist` uses, since that is what the MCP
35659
+ * boundary's `tools/call` narrowing filters on) into the `agentName`-keyed
35660
+ * allowlist `personasFor`'s `agentAllowlist` consumes (since that is the key
35661
+ * `buildPeerAgentDefinitions` uses to build subagent `.md` files). The two
35662
+ * identifiers differ (`gemini_critic` vs `gemini-critic`), so a caller wiring
35663
+ * a launch profile's persona restriction into subagent generation needs this
35664
+ * translation rather than assuming the sets are interchangeable.
35665
+ */
35666
+ function agentNamesForToolAllowlist(toolAllowlist) {
35667
+ const allow = toolAllowlist instanceof Set ? toolAllowlist : new Set(toolAllowlist);
35668
+ const names = /* @__PURE__ */ new Set();
35669
+ for (const p of [...PERSONAS_READ, ...PERSONAS_WRITE]) if (allow.has(p.toolNameHttp)) names.add(p.agentName);
35670
+ return names;
35671
+ }
35672
+ /**
35262
35673
  * Applies the resolved Gemini review model to a persona requiring the Gemini
35263
35674
  * catalog: swaps `.model` and rewrites every literal occurrence of the
35264
35675
  * default id in `.description` so the two never disagree about which model
@@ -35283,8 +35694,10 @@ function resolveGeminiPersona(p, geminiModel) {
35283
35694
  }
35284
35695
  /** Convenience: every persona that should be registered for the given mode. */
35285
35696
  function personasFor(opts) {
35697
+ const allow = opts.agentAllowlist == null ? void 0 : opts.agentAllowlist instanceof Set ? opts.agentAllowlist : new Set(opts.agentAllowlist);
35286
35698
  const result = [];
35287
35699
  for (const p of PERSONAS_READ) {
35700
+ if (allow && !allow.has(p.agentName)) continue;
35288
35701
  if (p.requiresGeminiCatalog) {
35289
35702
  if (!opts.geminiAvailable) continue;
35290
35703
  result.push(resolveGeminiPersona(p, opts.geminiModel));
@@ -35292,7 +35705,10 @@ function personasFor(opts) {
35292
35705
  }
35293
35706
  result.push(p);
35294
35707
  }
35295
- if (opts.codexCli) for (const p of PERSONAS_WRITE) result.push(p);
35708
+ if (opts.codexCli) for (const p of PERSONAS_WRITE) {
35709
+ if (allow && !allow.has(p.agentName)) continue;
35710
+ result.push(p);
35711
+ }
35296
35712
  return result;
35297
35713
  }
35298
35714
  const WEB_SEARCH_DESCRIPTION = "Web search via GitHub Copilot's MCP that returns answer text plus source URLs the caller can cite. It accepts a natural-language `query`; the upstream provider rewrites for the search index and the handler formats any references as markdown links. Use for current external information such as API documentation, error-message diagnosis, upstream issue searches, and claims that need web sources. Not for local repository discovery or code navigation, use code, Read, Grep, or Glob for workspace content. Prefer it over the built-in WebSearch when source URLs are needed or the built-in surface is geographically constrained.";
@@ -36475,6 +36891,6 @@ function enumerateInjectedMcpToolNames(groupKeys, opts = {}) {
36475
36891
  return [...new Set(names)];
36476
36892
  }
36477
36893
  //#endregion
36478
- export { handleMcpDelete as $, UPSTREAM_INACTIVITY_TIMEOUT_MS as $t, satisfiesMinVersion as A, readResponseBodyCapped as At, rememberThinkingHistoryRepair as B, provisionTreeSitterAssets as Bt, availableToolCommands as C, getTokenCount as Ct, vscodeRipgrepPath as D, createResponses as Dt, toolbeltSkipSet as E, resolveMcpToolTimeoutMs as Et, injectAdvisorTool as F, colbertDegradedWarning as Ft, isControllerClosedError as G, toolbeltPathOverride as Gt, repairRejectedThinkingHistory as H, DEFINITION_OF_GREATNESS as Ht, isAdvisorRequested as I, provisionAndIndexColbert as It, relayAnthropicStream as J, DEFAULT_CLAUDE_MODEL_FALLBACKS as Jt, logStreamError as K, BUDGET_SMALL_FAST_CATALOG_ID as Kt, resolveAdvisorEffort as L, extractTarGzMember as Lt, ADVISOR_INTERNAL_TOOL_NAME as M, normalizeOpenAIUsage as Mt, ADVISOR_TOOL_INSTRUCTIONS as N, provisionBrowserAssets as Nt, TOOLBELT_TOOLS$1 as O, createChatCompletions as Ot, buildAdvisorStream as P, hasSupportedBrowserInstalled as Pt, clampEffort as Q, UPSTREAM_FETCH_TIMEOUT_MS as Qt, resolveAdvisorModel as R, extractZipMember as Rt, buildEnv as S, createMessages as St, toolbeltEnabled as T, warnOnTokenPriceDrift as Tt, buildAnthropicErrorEvent as U, shouldUseInsecureTls as Ut, repairKnownThinkingHistory as V, CONDENSED_OPERATING_SEQUENCE as Vt, buildOpenAIErrorEvent as W, collapsePathKeys as Wt, UNKNOWN_EFFORT_ANCHOR as X, DEFAULT_CODEX_MODEL_FALLBACKS as Xt, EFFORT_ORDER as Y, DEFAULT_CODEX_MODEL as Yt, bucketEffort as Z, DEFAULT_PORT as Zt, appendPlanReminder as _, scribeModel as _t, buildPeerAwarenessSnippet as a, upstreamMaxConnections as an, browseAgentEnabled as at, resolveWorkerRunOpts as b, shimDefaultsToXhigh as bt, personasFor as c, withOneMSuffixForLead as cn, fleetToolsEnabled as ct, EXPLORE_DEFAULT_MODEL as d, implementerFastModel as dt, generateRandomPort as en, handleMcpPost as et, EXPLORE_DEFAULT_THINKING as f, nativeSubagentModel as ft, TEST_DEFAULT_MODEL as g, scoutModel as gt, REVIEW_DEFAULT_MODEL as h, reviewerModel as ht, buildAgentPrompt as i, upstreamAllowH2 as in, brainstormModel as it, searchWeb as j, parseJsonOrDiagnose as jt, assetFor as k, MAX_RESPONSE_BODY_BYTES as kt, BROWSE_DEFAULT_MODEL as l, withInstallLock as ln, geminiAvailable as lt, PLAN_DEFAULT_MODEL as m, reviewerFastModel as mt, MCP_GROUPS as n, pickClaudeDefault as nn, agentToolsEnabled as nt, buildPeerAwarenessSummary as o, classifyMessagesRoute as on, browserCompoundToolsEnabled as ot, IMPLEMENT_DEFAULT_MODEL as p, resolveGeminiReviewModel as pt, readIteratorWithTimeout as q, BUDGET_SMALL_FAST_SLUG as qt, assertMcpToolSurfaceConsistent as r, resolveLeadSlugArg as rn, artifactToolsEnabled as rt, enumerateInjectedMcpToolNames as s, withOneMSuffix as sn, browserToolsEnabled as st, GROUP_META as t, isBudgetClaudeLead as tn, REVIEW_FAST_DEFAULT_MODEL as tt, DEFAULT_MODEL_CHAIN as u, generalPurposeFastModel as ut, resolveDefaultModel as v, standInToolEnabled as vt, buildToolbeltAwareness as w, assembleResponsesPayload as wt, runWorkerAgent as x, countTokens as xt, resolveModeDefaults as y, workerToolsEnabled as yt, formatThinkingRepairDecline as z, warmTreeSitterPool as zt };
36894
+ export { bucketEffort as $, CONDENSED_OPERATING_SEQUENCE as $t, assetFor as A, countTokens as At, resolveAdvisorModel as B, createChatCompletions as Bt, buildEnv as C, withOneMSuffixForLead as Cn, reviewerFastModel as Ct, toolbeltSkipSet as D, standInToolEnabled as Dt, toolbeltEnabled as E, scribeModel as Et, buildAdvisorStream as F, unregisterLaunch as Ft, buildAnthropicErrorEvent as G, provisionBrowserAssets as Gt, rememberThinkingHistoryRepair as H, readResponseBodyCapped as Ht, injectAdvisorTool as I, assembleResponsesPayload as It, logStreamError as J, provisionAndIndexColbert as Jt, buildOpenAIErrorEvent as K, hasSupportedBrowserInstalled as Kt, isAdvisorRequested as L, warnOnTokenPriceDrift as Lt, searchWeb as M, getTokenCount as Mt, ADVISOR_INTERNAL_TOOL_NAME as N, findLaunchBySecret as Nt, vscodeRipgrepPath as O, workerToolsEnabled as Ot, ADVISOR_TOOL_INSTRUCTIONS as P, registerLaunch as Pt, UNKNOWN_EFFORT_ANCHOR as Q, provisionTreeSitterAssets as Qt, isFastProfileLead as R, resolveMcpToolTimeoutMs as Rt, runWorkerAgent as S, withOneMSuffix as Sn, resolveGeminiReviewModel as St, buildToolbeltAwareness as T, scoutModel as Tt, repairKnownThinkingHistory as U, parseJsonOrDiagnose as Ut, formatThinkingRepairDecline as V, MAX_RESPONSE_BODY_BYTES as Vt, repairRejectedThinkingHistory as W, normalizeOpenAIUsage as Wt, relayAnthropicStream as X, extractZipMember as Xt, readIteratorWithTimeout as Y, extractTarGzMember as Yt, EFFORT_ORDER as Z, warmTreeSitterPool as Zt, TEST_DEFAULT_MODEL as _, upstreamMaxConnections as _n, fleetToolsEnabled as _t, buildAgentPrompt as a, BUDGET_SMALL_FAST_SLUG as an, FAST_SCOUT_EFFORT as at, resolveModeDefaults as b, catalogAdvertises1M as bn, implementerFastModel as bt, enumerateInjectedMcpToolNames as c, DEFAULT_CODEX_MODEL_FALLBACKS as cn, brainstormModel as ct, DEFAULT_MODEL_CHAIN as d, UPSTREAM_INACTIVITY_TIMEOUT_MS as dn, browserToolsEnabled as dt, DEFINITION_OF_GREATNESS as en, clampEffort as et, EXPLORE_DEFAULT_MODEL as f, generateRandomPort as fn, fastImplementerModel as ft, REVIEW_DEFAULT_MODEL as g, upstreamAllowH2 as gn, fastScoutModel as gt, PLAN_DEFAULT_MODEL as h, resolveLeadSlugArg as hn, fastReviewerModel as ht, assertMcpToolSurfaceConsistent as i, BUDGET_SMALL_FAST_CATALOG_ID as in, FAST_REVIEWER_EFFORT as it, satisfiesMinVersion as j, createMessages as jt, TOOLBELT_TOOLS$1 as k, shimDefaultsToXhigh as kt, personasFor as l, DEFAULT_PORT as ln, browseAgentEnabled as lt, IMPLEMENT_DEFAULT_MODEL as m, pickClaudeDefault as mn, fastPlannerModel as mt, MCP_GROUPS as n, collapsePathKeys as nn, handleMcpPost as nt, buildPeerAwarenessSnippet as o, DEFAULT_CLAUDE_MODEL_FALLBACKS as on, agentToolsEnabled as ot, EXPLORE_DEFAULT_THINKING as p, isBudgetClaudeLead as pn, fastOracleModel as pt, isControllerClosedError as q, colbertDegradedWarning as qt, agentNamesForToolAllowlist as r, toolbeltPathOverride as rn, FAST_PLANNER_EFFORT as rt, buildPeerAwarenessSummary as s, DEFAULT_CODEX_MODEL as sn, artifactToolsEnabled as st, GROUP_META as t, shouldUseInsecureTls as tn, handleMcpDelete as tt, BROWSE_DEFAULT_MODEL as u, UPSTREAM_FETCH_TIMEOUT_MS as un, browserCompoundToolsEnabled as ut, appendPlanReminder as v, classifyMessagesRoute as vn, geminiAvailable as vt, availableToolCommands as w, withInstallLock as wn, reviewerModel as wt, resolveWorkerRunOpts as x, oneMContextDisabled as xn, nativeSubagentModel as xt, resolveDefaultModel as y, pickEndpoint as yn, generalPurposeFastModel as yt, resolveAdvisorEffort as z, createResponses as zt };
36479
36895
 
36480
- //# sourceMappingURL=peer-mcp-personas-Bd56EmiO.js.map
36896
+ //# sourceMappingURL=peer-mcp-personas-CHbl6MwM.js.map