github-router 0.3.289 → 0.3.293

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-Dx8TBPE8.js} +124 -34
  2. package/dist/attribution-settings-Dx8TBPE8.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-BoAd_L2W.js} +118 -59
  9. package/dist/claude-BoAd_L2W.js.map +1 -0
  10. package/dist/{codex-DRW0yb8x.js → codex-BdJgGT6Q.js} +5 -5
  11. package/dist/{codex-DRW0yb8x.js.map → codex-BdJgGT6Q.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-CK2b_cTt.js +2 -0
  15. package/dist/{gate-discovery-Cz6kwIVG.js → gate-discovery-C30rfnyo.js} +5 -5
  16. package/dist/{gate-discovery-Cz6kwIVG.js.map → gate-discovery-C30rfnyo.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-OnfK3BxE.js} +5 -5
  31. package/dist/{internal-stop-hook-Dvkppwo7.js.map → internal-stop-hook-OnfK3BxE.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-DhI7ZPSx.js} +569 -136
  55. package/dist/peer-mcp-personas-DhI7ZPSx.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-CZJ4EWls.js} +4 -4
  61. package/dist/{provision-B53wbHwa.js.map → provision-CZJ4EWls.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-RxWYzoOl.js} +12 -12
  65. package/dist/{serve-aZCEYFe5.js.map → serve-RxWYzoOl.js.map} +1 -1
  66. package/dist/{server-setup-DlztZAGT.js → server-setup-DbvbW5Ve.js} +627 -85
  67. package/dist/server-setup-DbvbW5Ve.js.map +1 -0
  68. package/dist/{start-DwNiXv5N.js → start-BNcGsXsY.js} +3 -3
  69. package/dist/{start-DwNiXv5N.js.map → start-BNcGsXsY.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`.
29724
+ *
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.
29440
29728
  *
29441
- * Step 3 returns the LITERAL constant rather than walking the OpenAI frontier
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
@@ -29540,6 +29840,18 @@ On tasks longer than a few steps, call advisor at least once before committing t
29540
29840
  Give the advice serious weight. If you follow a step and it fails empirically, or you have primary-source evidence that contradicts a specific claim (the file says X, the code does Y), adapt. A passing self-test is not evidence the advice is wrong -- it's evidence your test doesn't check what the advice is checking.
29541
29841
 
29542
29842
  If you've already retrieved data pointing one way and the advisor points another: don't silently switch. Surface the conflict in one more advisor call -- "I found X, you suggest Y, which constraint breaks the tie?" The advisor saw your evidence but may have underweighted it; a reconcile call is cheaper than committing to the wrong branch.`;
29843
+ /** Fast-profile lead-only policy. Unlike the standard Claude Code instructions
29844
+ * above, this makes consultation optional and leaves decision ownership with
29845
+ * the Luna lead. Fast Task subagents never receive an advisor tool at all. */
29846
+ const FAST_ADVISOR_TOOL_INSTRUCTIONS = `# Advisor Tool
29847
+
29848
+ You have access to an optional, transcript-aware \`advisor\` tool. It takes no parameters and returns non-binding consultation. You remain responsible for every decision.
29849
+
29850
+ Use advisor only when a focused, consequential uncertainty remains after direct investigation: conflicting evidence, a materially changed assumption, a genuinely non-converging approach, a hard-to-reverse trade-off, or an explicit request for a fresh perspective. State the precise uncertainty in your response immediately before calling it.
29851
+
29852
+ Do not call advisor for routine progress, while waiting on a subagent, after ordinary tool output, for a fact that code or a command can verify, to obtain planner approval or reviewer verification, or as a ritual before implementation or completion.
29853
+
29854
+ Treat the result as advice, not authority. Weigh it against the user's intent, verified repository evidence, planner output, and reviewer findings. You may consult again when materially new evidence creates a different question or directly conflicts with earlier advice.`;
29543
29855
  const ADVISOR_OPT_OUT_ENV = "CLAUDE_CODE_DISABLE_ADVISOR_TOOL";
29544
29856
  /**
29545
29857
  * Detect whether the request asked for ADVISOR (incoming
@@ -29572,8 +29884,8 @@ function isAdvisorRequested(rawBetaHeader) {
29572
29884
  * client-shape `server_tool_use{name:"advisor"}` + `advisor_tool_result`
29573
29885
  * blocks the client expects.
29574
29886
  */
29575
- function injectAdvisorTool(rawBody) {
29576
- if (rawBody.includes(`"name":"__anthropic_advisor"`) && !rawBody.includes("\"advisor_")) return rawBody;
29887
+ function injectAdvisorTool(rawBody, instructions = ADVISOR_TOOL_INSTRUCTIONS) {
29888
+ if (instructions === ADVISOR_TOOL_INSTRUCTIONS && rawBody.includes(`"name":"__anthropic_advisor"`) && !rawBody.includes("\"advisor_")) return rawBody;
29577
29889
  let parsed;
29578
29890
  try {
29579
29891
  parsed = JSON.parse(rawBody);
@@ -29588,10 +29900,14 @@ function injectAdvisorTool(rawBody) {
29588
29900
  });
29589
29901
  const stripped = tools.length !== rawTools.length;
29590
29902
  const alreadyInjected = tools.some((t) => t?.name === ADVISOR_INTERNAL_TOOL_NAME);
29591
- if (alreadyInjected && !stripped) return rawBody;
29592
- parsed.tools = alreadyInjected ? tools : [...tools, {
29903
+ const needsDescriptionUpdate = alreadyInjected && tools.some((t) => t?.name === "__anthropic_advisor" && t.description !== instructions);
29904
+ if (alreadyInjected && !stripped && !needsDescriptionUpdate) return rawBody;
29905
+ parsed.tools = alreadyInjected ? tools.map((tool) => tool?.name === "__anthropic_advisor" ? {
29906
+ ...tool,
29907
+ description: instructions
29908
+ } : tool) : [...tools, {
29593
29909
  name: ADVISOR_INTERNAL_TOOL_NAME,
29594
- description: ADVISOR_TOOL_INSTRUCTIONS,
29910
+ description: instructions,
29595
29911
  input_schema: {
29596
29912
  type: "object",
29597
29913
  properties: {},
@@ -29732,9 +30048,9 @@ function truncateTailToUnits(text, maxUnits, measure) {
29732
30048
  * Anthropic's own ADVISOR ("see the whole task + every tool call +
29733
30049
  * every result").
29734
30050
  */
29735
- async function runAdvisor(conversation, advisorModel, advisorEffort, signal, advisorEscalated = false) {
30051
+ async function runAdvisor(conversation, advisorModel, advisorEffort, signal, advisorEscalated = false, fastProfile = false) {
29736
30052
  if (signal?.aborted) throw new Error("advisor call aborted before dispatch");
29737
- const advisorSystem = "You are an expert advisor reviewing an in-progress Claude Code session. The transcript below is the work-in-progress (turns numbered, with tool calls and results inlined). Read carefully and provide concrete, actionable advice on the next step or course-correction. Be specific — cite the parts of the transcript you're responding to. If the assistant is on the right track, say so explicitly. If they're stuck or off-track, name the specific assumption or step to revisit. Aim for 2-5 paragraphs of substantive guidance." + (advisorEscalated ? " The requesting agent is running a lighter, faster model than you. Give a directive recommendation and commit to the decision rather than laying out options for it to weigh." : "");
30053
+ const advisorSystem = "You are an expert advisor reviewing an in-progress Claude Code session. The transcript below is the work-in-progress (turns numbered, with tool calls and results inlined). Read carefully and provide concrete, actionable advice on the next step or course-correction. Be specific — cite the parts of the transcript you're responding to. If the assistant is on the right track, say so explicitly. If they're stuck or off-track, name the specific assumption or step to revisit. Aim for 2-5 paragraphs of substantive guidance." + (fastProfile ? " You are a non-binding consultant to the primary lead. Analyze the focused uncertainty that prompted this call and provide a recommendation, its assumptions, material risks, credible alternatives, confidence, and any evidence gap that should be resolved. Do not approve, veto, dictate, or take ownership of the workflow; the lead will weigh your advice against the user's intent and verified evidence." : "") + (advisorEscalated && !fastProfile ? " The requesting agent is running a lighter, faster model than you. Give a directive recommendation and commit to the decision rather than laying out options for it to weigh." : "");
29738
30054
  const resolvedAdvisorModel = resolveModel(advisorModel);
29739
30055
  let measure;
29740
30056
  let maxUnits;
@@ -29749,7 +30065,8 @@ async function runAdvisor(conversation, advisorModel, advisorEffort, signal, adv
29749
30065
  maxUnits = ADVISOR_MAX_CONVERSATION_CHARS;
29750
30066
  }
29751
30067
  const conversationText = renderConversationAsText(conversation, maxUnits, measure);
29752
- if (advisorUsesResponses(resolvedAdvisorModel)) {
30068
+ const transport = advisorTransport(resolvedAdvisorModel);
30069
+ if (transport === "responses") {
29753
30070
  const payload = applyResponsesCachePolicy({
29754
30071
  model: resolvedAdvisorModel,
29755
30072
  instructions: advisorSystem,
@@ -29784,6 +30101,27 @@ async function runAdvisor(conversation, advisorModel, advisorEffort, signal, adv
29784
30101
  if (!text) throw new Error(`Advisor model ${resolvedAdvisorModel} returned empty assistant output`);
29785
30102
  return text;
29786
30103
  }
30104
+ if (transport === "chat") {
30105
+ const ladder = advertisedEffortLadder(resolvedAdvisorModel);
30106
+ const chatPayload = {
30107
+ model: resolvedAdvisorModel,
30108
+ messages: [{
30109
+ role: "system",
30110
+ content: advisorSystem
30111
+ }, {
30112
+ role: "user",
30113
+ content: conversationText
30114
+ }],
30115
+ stream: false,
30116
+ ...ladder ? { reasoning_effort: advisorEffort } : {}
30117
+ };
30118
+ const text = (await withTransientRetry(() => createChatCompletions(chatPayload, void 0, signal), {
30119
+ signal,
30120
+ label: resolvedAdvisorModel
30121
+ })).choices?.[0]?.message?.content;
30122
+ if (typeof text !== "string" || text.length === 0) throw new Error(`Advisor model ${resolvedAdvisorModel} returned empty response`);
30123
+ return text;
30124
+ }
29787
30125
  const advisorEntry = state.models?.data?.find((m) => m.id === resolvedAdvisorModel);
29788
30126
  const limits = advisorEntry?.capabilities?.limits;
29789
30127
  const maxTokens = limits?.max_non_streaming_output_tokens ?? limits?.max_output_tokens ?? ADVISOR_FALLBACK_MAX_OUTPUT_TOKENS;
@@ -29812,20 +30150,51 @@ async function runAdvisor(conversation, advisorModel, advisorEffort, signal, adv
29812
30150
  /**
29813
30151
  * Derive a spec-compliant `srvtoolu_*` id for a client-facing
29814
30152
  * `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}`;
30153
+ * from the upstream model's tool-call id.
30154
+ *
30155
+ * TOTAL never throws. Two paths:
30156
+ *
30157
+ * 1. A real Anthropic `toolu_*` id whose suffix is already in the
30158
+ * `^[a-zA-Z0-9_]+$` charset: `srvtoolu_<suffix>`, byte-for-byte
30159
+ * identical to the historical (Claude-lead) behavior.
30160
+ * 2. Anything else a Responses `call_*` id (the fast Luna profile's
30161
+ * lead, once its `tool_use{__anthropic_advisor}` block is synthesized
30162
+ * by the anthropic-translate shim from a Copilot `/responses` tool
30163
+ * call), a hyphenated or otherwise non-conforming id, an empty string,
30164
+ * unicode, or a corrupt id — sanitize to the Anthropic charset and
30165
+ * prefix with `fallbackIndex` (the caller's per-block synthetic stream
30166
+ * index, unique within one `buildAdvisorStream` run) so two different
30167
+ * raw ids that happen to sanitize to the same string can never
30168
+ * collide. `fallbackIndex` is REQUIRED for this path's uniqueness
30169
+ * guarantee — callers must pass a value that is unique per call within
30170
+ * one advisor stream (every call site does: `myIndex` from the
30171
+ * turn processor's monotonic `nextSyntheticIndex`).
30172
+ *
30173
+ * This function ONLY has to produce a valid, deterministic, collision-free
30174
+ * LABEL — the original raw id is preserved separately for Copilot replay
30175
+ * (`CapturedBlock.advisorReplay.id`), never reconstructed from the derived
30176
+ * client id. That is what makes totality safe: there is no bijective-decode
30177
+ * requirement on this function itself, only on the (id, clientId) pairing a
30178
+ * caller keeps alongside it.
30179
+ *
30180
+ * Historically this threw "advisor tool_use id is not round-trippable" for
30181
+ * any non-`toolu_` shape. That was correct for a Claude-only advisor lead —
30182
+ * Copilot's native `/v1/messages` never emits anything else — but became a
30183
+ * live defect once the advisor loop could run on a non-Claude (Luna) lead
30184
+ * shimmed through `/responses`: `responses-egress.ts` forwards a Responses
30185
+ * `call_*` id VERBATIM as the synthesized `tool_use.id` (see
30186
+ * `makeToolUseId` — it only synthesizes a `toolu_*` id when the upstream id
30187
+ * is EMPTY), so the advisor's `tool_use{__anthropic_advisor}` block on that
30188
+ * lead legitimately carries a `call_*` id and the throw fired on every
30189
+ * single advisor call.
30190
+ */
30191
+ function toClientServerToolUseId(id, fallbackIndex) {
30192
+ if (id.startsWith("toolu_")) {
30193
+ const suffix = id.slice(6);
30194
+ if (/^[a-zA-Z0-9_]+$/.test(suffix)) return `srvtoolu_${suffix}`;
30195
+ }
30196
+ const sanitized = id.replace(/[^a-zA-Z0-9_]/g, "_");
30197
+ return `srvtoolu_gen${fallbackIndex}${sanitized.length > 0 ? `_${sanitized}` : ""}`;
29829
30198
  }
29830
30199
  /**
29831
30200
  * Build an SSE event line in the canonical Anthropic shape:
@@ -29837,6 +30206,35 @@ function sseEvent(type, data) {
29837
30206
  return `event: ${type}\ndata: ${JSON.stringify(data)}\n\n`;
29838
30207
  }
29839
30208
  /**
30209
+ * The default `continueTurn` for `buildAdvisorStream`: native Claude
30210
+ * passthrough (`createMessages`) plus signed-thinking-history repair-and-retry.
30211
+ * Extracted verbatim from the loop body so the behavior is byte-identical to
30212
+ * before `continueTurn` became injectable, and so a non-Claude
30213
+ * `continueTurn` (the fast Luna profile's shim-backed one) can omit this
30214
+ * Claude-only repair path entirely rather than inherit dead code that would
30215
+ * never fire for it.
30216
+ */
30217
+ async function defaultContinueTurn(body, signal, requestHeaders) {
30218
+ let continuationSend = JSON.stringify(body);
30219
+ const knownRepair = repairKnownThinkingHistory(continuationSend);
30220
+ if (knownRepair) continuationSend = knownRepair.body;
30221
+ try {
30222
+ return await createMessages(continuationSend, requestHeaders, signal, true);
30223
+ } catch (continuationError) {
30224
+ if (!(continuationError instanceof HTTPError)) throw continuationError;
30225
+ const errorBody = await continuationError.response.clone().text().catch(() => "");
30226
+ const outcome = repairRejectedThinkingHistory(continuationSend, errorBody);
30227
+ if (!outcome.ok) {
30228
+ consola.warn(`Advisor continuation thinking-history repair declined: ${formatThinkingRepairDecline(outcome.decline)}`);
30229
+ throw continuationError;
30230
+ }
30231
+ consola.warn(`Advisor continuation: retrying without rejected thinking blocks: message=${outcome.repair.messageIndex} removed_blocks=${outcome.repair.removedBlocks}`);
30232
+ const response = await createMessages(outcome.repair.body, requestHeaders, signal, true);
30233
+ rememberThinkingHistoryRepair(outcome.repair.fingerprint);
30234
+ return response;
30235
+ }
30236
+ }
30237
+ /**
29840
30238
  * The streaming translate-loop. Returns a ReadableStream<Uint8Array>
29841
30239
  * suitable to wrap with Hono's c.body() / new Response().
29842
30240
  *
@@ -29855,6 +30253,8 @@ function buildAdvisorStream(opts) {
29855
30253
  const advisorModel = opts.advisorModel ?? "gpt-5.6-sol";
29856
30254
  const advisorEffort = opts.advisorEffort ?? "xhigh";
29857
30255
  const advisorEscalated = opts.advisorEscalated ?? false;
30256
+ const advisorFastProfile = opts.advisorFastProfile ?? false;
30257
+ const continueTurn = opts.continueTurn ?? ((body, signal) => defaultContinueTurn(body, signal, opts.requestHeaders));
29858
30258
  const aborter = opts.externalAborter ?? new AbortController();
29859
30259
  let conversation = [...opts.initialConversation];
29860
30260
  return new ReadableStream({
@@ -30104,7 +30504,7 @@ function buildAdvisorStream(opts) {
30104
30504
  const advisorConversation = conversation;
30105
30505
  const advisorTexts = await Promise.all(advisorToolUses.map(async () => {
30106
30506
  try {
30107
- return await runAdvisor(advisorConversation, advisorModel, advisorEffort, aborter.signal, advisorEscalated);
30507
+ return await runAdvisor(advisorConversation, advisorModel, advisorEffort, aborter.signal, advisorEscalated, advisorFastProfile);
30108
30508
  } catch (err) {
30109
30509
  if (aborter.signal.aborted) throw err;
30110
30510
  const msg = err instanceof Error ? err.message : String(err);
@@ -30152,27 +30552,11 @@ function buildAdvisorStream(opts) {
30152
30552
  }))
30153
30553
  });
30154
30554
  if (aborter.signal.aborted) return;
30155
- let continuationSend = JSON.stringify({
30555
+ response = await continueTurn({
30156
30556
  ...opts.baseBody,
30157
30557
  messages: conversation,
30158
30558
  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
- }
30559
+ }, aborter.signal);
30176
30560
  }
30177
30561
  if (aborter.signal.aborted) return;
30178
30562
  const finalIndex = nextSyntheticIndex++;
@@ -31988,6 +32372,17 @@ const ADVISOR_PARAMS = Type$1.Object({ concern: Type$1.String({
31988
32372
  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
32373
  minLength: 1
31990
32374
  }) });
32375
+ /**
32376
+ * Fixed reasoning effort for the WORKER'S own `advisor` tool call (distinct
32377
+ * from the server-side ADVISOR mechanism's `ADVISOR_DEFAULT_EFFORT` in
32378
+ * `src/services/advisor/advisor.ts`, which now follows the Claude Code
32379
+ * effort picker and defaults to `high`). This tool has no picker to follow —
32380
+ * it is a single fixed-effort consultation a worker triggers explicitly, not
32381
+ * a per-request value derived from client input — so it keeps its own
32382
+ * historical constant rather than sharing one that started varying for an
32383
+ * unrelated reason.
32384
+ */
32385
+ const WORKER_ADVISOR_EFFORT = "xhigh";
31991
32386
  /** Advisor transcript budget — leaves headroom in the advisor's
31992
32387
  * context window after the system prompt + concern + reasoning
31993
32388
  * overhead. Truncate-from-front so the most recent turn (where the
@@ -32089,7 +32484,7 @@ function advisorTool(getMessages) {
32089
32484
  }]
32090
32485
  }],
32091
32486
  stream: false,
32092
- reasoning: { effort: ADVISOR_DEFAULT_EFFORT }
32487
+ reasoning: { effort: WORKER_ADVISOR_EFFORT }
32093
32488
  }, { workload: "reusable-prefix" });
32094
32489
  const text = extractResponsesText(await createResponses(payload, void 0, signal, true));
32095
32490
  if (!text) throw new Error("advisor returned empty output");
@@ -35172,6 +35567,17 @@ function buildAgentPrompt(persona, opts) {
35172
35567
  */
35173
35568
  function buildPeerAwarenessSnippet(opts) {
35174
35569
  const key = (g) => opts.groupKeys?.[g] ?? GROUP_META[g].preferredKey;
35570
+ if (opts.profile === "fast") {
35571
+ const fastPeersKey = key("peers");
35572
+ const fastSearchKey = key("search");
35573
+ return [
35574
+ "## Peer review and advisor",
35575
+ "",
35576
+ `This is the fast launch profile. Advisor is an optional, non-binding, lead-only transcript-aware sounding board for consequential unresolved uncertainty or a genuinely stuck path, not routine progress, waiting, verification, approval, or completion. \`mcp__${fastPeersKey}__oracle\` is exact Opus 5 (1M/high), a stateless last-resort consultant available to the lead, reviewer, and planner.`,
35577
+ "",
35578
+ `\`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.` : ""}`
35579
+ ].join("\n");
35580
+ }
35175
35581
  const peersKey = key("peers");
35176
35582
  const searchKey = key("search");
35177
35583
  const workersKey = key("workers");
@@ -35225,6 +35631,12 @@ function buildPeerAwarenessSnippet(opts) {
35225
35631
  */
35226
35632
  function buildPeerAwarenessSummary(opts) {
35227
35633
  const key = (g) => opts.groupKeys?.[g] ?? GROUP_META[g].preferredKey;
35634
+ if (opts.profile === "fast") return [
35635
+ "## Injected capabilities (summary)",
35636
+ "",
35637
+ "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.",
35638
+ `Advisor is optional, non-binding, transcript-aware, and lead-only; use it for consequential unresolved uncertainty, not routine progress or workflow gates. \`mcp__${key("peers")}__oracle\` is exact Opus 5 (1M/high), stateless and last resort for the lead, reviewer, and planner. \`mcp__${key("search")}__code\` and \`mcp__${key("search")}__web\` provide search.${opts.browseAvailable ? ` \`mcp__${key("browser")}__*\` provides the opt-in browser.` : ""}`
35639
+ ].join("\n");
35228
35640
  const renderNative = (name) => {
35229
35641
  const modelId = opts.nativeAgentModels?.[name];
35230
35642
  if (!modelId) return `\`${name}\``;
@@ -35259,6 +35671,22 @@ function buildPeerAwarenessSummary(opts) {
35259
35671
  return lines.join("\n");
35260
35672
  }
35261
35673
  /**
35674
+ * Translate a `toolNameHttp`-keyed persona allowlist (the currency
35675
+ * `LaunchProfileDescriptor.personaAllowlist` uses, since that is what the MCP
35676
+ * boundary's `tools/call` narrowing filters on) into the `agentName`-keyed
35677
+ * allowlist `personasFor`'s `agentAllowlist` consumes (since that is the key
35678
+ * `buildPeerAgentDefinitions` uses to build subagent `.md` files). The two
35679
+ * identifiers differ (`gemini_critic` vs `gemini-critic`), so a caller wiring
35680
+ * a launch profile's persona restriction into subagent generation needs this
35681
+ * translation rather than assuming the sets are interchangeable.
35682
+ */
35683
+ function agentNamesForToolAllowlist(toolAllowlist) {
35684
+ const allow = toolAllowlist instanceof Set ? toolAllowlist : new Set(toolAllowlist);
35685
+ const names = /* @__PURE__ */ new Set();
35686
+ for (const p of [...PERSONAS_READ, ...PERSONAS_WRITE]) if (allow.has(p.toolNameHttp)) names.add(p.agentName);
35687
+ return names;
35688
+ }
35689
+ /**
35262
35690
  * Applies the resolved Gemini review model to a persona requiring the Gemini
35263
35691
  * catalog: swaps `.model` and rewrites every literal occurrence of the
35264
35692
  * default id in `.description` so the two never disagree about which model
@@ -35283,8 +35711,10 @@ function resolveGeminiPersona(p, geminiModel) {
35283
35711
  }
35284
35712
  /** Convenience: every persona that should be registered for the given mode. */
35285
35713
  function personasFor(opts) {
35714
+ const allow = opts.agentAllowlist == null ? void 0 : opts.agentAllowlist instanceof Set ? opts.agentAllowlist : new Set(opts.agentAllowlist);
35286
35715
  const result = [];
35287
35716
  for (const p of PERSONAS_READ) {
35717
+ if (allow && !allow.has(p.agentName)) continue;
35288
35718
  if (p.requiresGeminiCatalog) {
35289
35719
  if (!opts.geminiAvailable) continue;
35290
35720
  result.push(resolveGeminiPersona(p, opts.geminiModel));
@@ -35292,7 +35722,10 @@ function personasFor(opts) {
35292
35722
  }
35293
35723
  result.push(p);
35294
35724
  }
35295
- if (opts.codexCli) for (const p of PERSONAS_WRITE) result.push(p);
35725
+ if (opts.codexCli) for (const p of PERSONAS_WRITE) {
35726
+ if (allow && !allow.has(p.agentName)) continue;
35727
+ result.push(p);
35728
+ }
35296
35729
  return result;
35297
35730
  }
35298
35731
  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 +36908,6 @@ function enumerateInjectedMcpToolNames(groupKeys, opts = {}) {
36475
36908
  return [...new Set(names)];
36476
36909
  }
36477
36910
  //#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 };
36911
+ export { UNKNOWN_EFFORT_ANCHOR as $, provisionTreeSitterAssets as $t, assetFor as A, shimDefaultsToXhigh as At, resolveAdvisorEffort as B, createResponses as Bt, buildEnv as C, withOneMSuffix as Cn, resolveGeminiReviewModel as Ct, toolbeltSkipSet as D, scribeModel as Dt, toolbeltEnabled as E, scoutModel as Et, FAST_ADVISOR_TOOL_INSTRUCTIONS as F, registerLaunch as Ft, repairRejectedThinkingHistory as G, normalizeOpenAIUsage as Gt, formatThinkingRepairDecline as H, MAX_RESPONSE_BODY_BYTES as Ht, buildAdvisorStream as I, unregisterLaunch as It, isControllerClosedError as J, colbertDegradedWarning as Jt, buildAnthropicErrorEvent as K, provisionBrowserAssets as Kt, injectAdvisorTool as L, assembleResponsesPayload as Lt, searchWeb as M, createMessages as Mt, ADVISOR_INTERNAL_TOOL_NAME as N, getTokenCount as Nt, vscodeRipgrepPath as O, standInToolEnabled as Ot, ADVISOR_TOOL_INSTRUCTIONS as P, findLaunchBySecret as Pt, EFFORT_ORDER as Q, warmTreeSitterPool as Qt, isAdvisorRequested as R, warnOnTokenPriceDrift as Rt, runWorkerAgent as S, oneMContextDisabled as Sn, nativeSubagentModel as St, buildToolbeltAwareness as T, withInstallLock as Tn, reviewerModel as Tt, rememberThinkingHistoryRepair as U, readResponseBodyCapped as Ut, resolveAdvisorModel as V, createChatCompletions as Vt, repairKnownThinkingHistory as W, parseJsonOrDiagnose as Wt, readIteratorWithTimeout as X, extractTarGzMember as Xt, logStreamError as Y, provisionAndIndexColbert as Yt, relayAnthropicStream as Z, extractZipMember as Zt, TEST_DEFAULT_MODEL as _, upstreamAllowH2 as _n, fastScoutModel as _t, buildAgentPrompt as a, BUDGET_SMALL_FAST_CATALOG_ID as an, FAST_REVIEWER_EFFORT as at, resolveModeDefaults as b, pickEndpoint as bn, generalPurposeFastModel as bt, enumerateInjectedMcpToolNames as c, DEFAULT_CODEX_MODEL as cn, artifactToolsEnabled as ct, DEFAULT_MODEL_CHAIN as d, UPSTREAM_FETCH_TIMEOUT_MS as dn, browserCompoundToolsEnabled as dt, CONDENSED_OPERATING_SEQUENCE as en, bucketEffort as et, EXPLORE_DEFAULT_MODEL as f, UPSTREAM_INACTIVITY_TIMEOUT_MS as fn, browserToolsEnabled as ft, REVIEW_DEFAULT_MODEL as g, resolveLeadSlugArg as gn, fastReviewerModel as gt, PLAN_DEFAULT_MODEL as h, pickClaudeDefault as hn, fastPlannerModel as ht, assertMcpToolSurfaceConsistent as i, toolbeltPathOverride as in, FAST_PLANNER_EFFORT as it, satisfiesMinVersion as j, countTokens as jt, TOOLBELT_TOOLS$1 as k, workerToolsEnabled as kt, personasFor as l, DEFAULT_CODEX_MODEL_FALLBACKS as ln, brainstormModel as lt, IMPLEMENT_DEFAULT_MODEL as m, isBudgetClaudeLead as mn, fastOracleModel as mt, MCP_GROUPS as n, shouldUseInsecureTls as nn, handleMcpDelete as nt, buildPeerAwarenessSnippet as o, BUDGET_SMALL_FAST_SLUG as on, FAST_SCOUT_EFFORT as ot, EXPLORE_DEFAULT_THINKING as p, generateRandomPort as pn, fastImplementerModel as pt, buildOpenAIErrorEvent as q, hasSupportedBrowserInstalled as qt, agentNamesForToolAllowlist as r, collapsePathKeys as rn, handleMcpPost as rt, buildPeerAwarenessSummary as s, DEFAULT_CLAUDE_MODEL_FALLBACKS as sn, agentToolsEnabled as st, GROUP_META as t, DEFINITION_OF_GREATNESS as tn, clampEffort as tt, BROWSE_DEFAULT_MODEL as u, DEFAULT_PORT as un, browseAgentEnabled as ut, appendPlanReminder as v, upstreamMaxConnections as vn, fleetToolsEnabled as vt, availableToolCommands as w, withOneMSuffixForLead as wn, reviewerFastModel as wt, resolveWorkerRunOpts as x, catalogAdvertises1M as xn, implementerFastModel as xt, resolveDefaultModel as y, classifyMessagesRoute as yn, geminiAvailable as yt, isFastProfileLead as z, resolveMcpToolTimeoutMs as zt };
36479
36912
 
36480
- //# sourceMappingURL=peer-mcp-personas-Bd56EmiO.js.map
36913
+ //# sourceMappingURL=peer-mcp-personas-DhI7ZPSx.js.map