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.
- package/dist/{attribution-settings-Cmz2jt7P.js → attribution-settings-CpLUCi8R.js} +124 -34
- package/dist/attribution-settings-CpLUCi8R.js.map +1 -0
- package/dist/{auth-DG4vh8-F.js → auth-BwUHopJz.js} +3 -3
- package/dist/{auth-DG4vh8-F.js.map → auth-BwUHopJz.js.map} +1 -1
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/{check-usage-BqN7mBYv.js → check-usage-BTda5753.js} +4 -4
- package/dist/{check-usage-BqN7mBYv.js.map → check-usage-BTda5753.js.map} +1 -1
- package/dist/{claude-C-9xFI4b.js → claude-_DYGKCw8.js} +99 -48
- package/dist/claude-_DYGKCw8.js.map +1 -0
- package/dist/{codex-DRW0yb8x.js → codex-rTJ8jW5G.js} +5 -5
- package/dist/{codex-DRW0yb8x.js.map → codex-rTJ8jW5G.js.map} +1 -1
- package/dist/{debug-B5TjPTTH.js → debug-B3UrZTHQ.js} +2 -2
- package/dist/{debug-B5TjPTTH.js.map → debug-B3UrZTHQ.js.map} +1 -1
- package/dist/engine-C9axTIu7.js +2 -0
- package/dist/{gate-discovery-Cz6kwIVG.js → gate-discovery-Bjar5dgv.js} +5 -5
- package/dist/{gate-discovery-Cz6kwIVG.js.map → gate-discovery-Bjar5dgv.js.map} +1 -1
- package/dist/{get-copilot-usage-BjA0nyGR.js → get-copilot-usage-CRrf1ZSC.js} +2 -2
- package/dist/{get-copilot-usage-BjA0nyGR.js.map → get-copilot-usage-CRrf1ZSC.js.map} +1 -1
- package/dist/{internal-artifact-open-BskmUpnb.js → internal-artifact-open-BsEQRvDi.js} +2 -2
- package/dist/{internal-artifact-open-BskmUpnb.js.map → internal-artifact-open-BsEQRvDi.js.map} +1 -1
- package/dist/{internal-first-mate-guard-5XEHMaqy.js → internal-first-mate-guard-CVFFJriA.js} +3 -3
- package/dist/{internal-first-mate-guard-5XEHMaqy.js.map → internal-first-mate-guard-CVFFJriA.js.map} +1 -1
- package/dist/{internal-first-mate-guard-DHDQ6hFz.js → internal-first-mate-guard-DJdX6t48.js} +1 -1
- package/dist/{internal-plan-review-BUuMx4ku.js → internal-plan-review-8TKDLco6.js} +3 -3
- package/dist/{internal-plan-review-BUuMx4ku.js.map → internal-plan-review-8TKDLco6.js.map} +1 -1
- package/dist/{internal-prompt-submit-CQQ15xdO.js → internal-prompt-submit-Da7pxqua.js} +4 -4
- package/dist/{internal-prompt-submit-CQQ15xdO.js.map → internal-prompt-submit-Da7pxqua.js.map} +1 -1
- package/dist/{internal-session-bind-D04W2yWI.js → internal-session-bind-BOFytA1f.js} +2 -2
- package/dist/{internal-session-bind-D04W2yWI.js.map → internal-session-bind-BOFytA1f.js.map} +1 -1
- package/dist/{internal-stop-hook-Dvkppwo7.js → internal-stop-hook-DrX2xlj0.js} +5 -5
- package/dist/{internal-stop-hook-Dvkppwo7.js.map → internal-stop-hook-DrX2xlj0.js.map} +1 -1
- package/dist/{internal-stop-review-CdByyJLc.js → internal-stop-review-CdouacHL.js} +2 -2
- package/dist/{internal-stop-review-CdByyJLc.js.map → internal-stop-review-CdouacHL.js.map} +1 -1
- package/dist/{internal-worker-guard-BIPN6Rv9.js → internal-worker-guard-Bx-itoP8.js} +2 -2
- package/dist/{internal-worker-guard-BIPN6Rv9.js.map → internal-worker-guard-Bx-itoP8.js.map} +1 -1
- package/dist/{internal-workspace-header-BKqejstG.js → internal-workspace-header-8WT0iB5K.js} +2 -2
- package/dist/{internal-workspace-header-BKqejstG.js.map → internal-workspace-header-8WT0iB5K.js.map} +1 -1
- package/dist/lifecycle-C8fOsQke.js +2 -0
- package/dist/lifecycle-D4Yc1aap.js +2 -0
- package/dist/{lifecycle-SXaWssN9.js → lifecycle-LeSfa7wH.js} +2 -2
- package/dist/{lifecycle-SXaWssN9.js.map → lifecycle-LeSfa7wH.js.map} +1 -1
- package/dist/{lifecycle-DbM29FLK.js → lifecycle-nuOHfwgj.js} +2 -2
- package/dist/{lifecycle-DbM29FLK.js.map → lifecycle-nuOHfwgj.js.map} +1 -1
- package/dist/main.js +17 -17
- package/dist/{mcp-workspace-header-DRCCWlOi.js → mcp-workspace-header-q34H_4wL.js} +2 -2
- package/dist/{mcp-workspace-header-DRCCWlOi.js.map → mcp-workspace-header-q34H_4wL.js.map} +1 -1
- package/dist/{models-Dz8d_SnI.js → models-hhJcrZhr.js} +3 -3
- package/dist/{models-Dz8d_SnI.js.map → models-hhJcrZhr.js.map} +1 -1
- package/dist/{orchestration-BrJwZxMN.js → orchestration-pzbrKkgD.js} +2 -2
- package/dist/{orchestration-BrJwZxMN.js.map → orchestration-pzbrKkgD.js.map} +1 -1
- package/dist/{paths-D7_SAaIQ.js → paths-BH4J7slC.js} +4 -4
- package/dist/{paths-D7_SAaIQ.js.map → paths-BH4J7slC.js.map} +1 -1
- package/dist/paths-DJZoXfAS.js +2 -0
- package/dist/{peer-mcp-personas-Bd56EmiO.js → peer-mcp-personas-CHbl6MwM.js} +544 -128
- package/dist/peer-mcp-personas-CHbl6MwM.js.map +1 -0
- package/dist/{plan-review-hook-CVZsG9MZ.js → plan-review-hook-CfcanA7_.js} +3 -3
- package/dist/{plan-review-hook-CVZsG9MZ.js.map → plan-review-hook-CfcanA7_.js.map} +1 -1
- package/dist/{prompt-submit-hook-BW92FX2D.js → prompt-submit-hook-Bqf9ORgb.js} +3 -3
- package/dist/{prompt-submit-hook-BW92FX2D.js.map → prompt-submit-hook-Bqf9ORgb.js.map} +1 -1
- package/dist/{provision-B53wbHwa.js → provision-BYFd9nPK.js} +4 -4
- package/dist/{provision-B53wbHwa.js.map → provision-BYFd9nPK.js.map} +1 -1
- package/dist/{self-invocation-CP_SOkrr.js → self-invocation-DhO1Z8iD.js} +2 -2
- package/dist/{self-invocation-CP_SOkrr.js.map → self-invocation-DhO1Z8iD.js.map} +1 -1
- package/dist/{serve-aZCEYFe5.js → serve-BWMxDLnD.js} +12 -12
- package/dist/{serve-aZCEYFe5.js.map → serve-BWMxDLnD.js.map} +1 -1
- package/dist/{server-setup-DlztZAGT.js → server-setup-CqlaZukJ.js} +609 -74
- package/dist/server-setup-CqlaZukJ.js.map +1 -0
- package/dist/{start-DwNiXv5N.js → start-5MgGT4IF.js} +3 -3
- package/dist/{start-DwNiXv5N.js.map → start-5MgGT4IF.js.map} +1 -1
- package/dist/{stop-gate-hook-DriRc9xN.js → stop-gate-hook-BiBp5aGm.js} +3 -3
- package/dist/{stop-gate-hook-DriRc9xN.js.map → stop-gate-hook-BiBp5aGm.js.map} +1 -1
- package/dist/{stop-gate-policy-DMPanpoR.js → stop-gate-policy-BGd6b5hR.js} +2 -2
- package/dist/{stop-gate-policy-DMPanpoR.js.map → stop-gate-policy-BGd6b5hR.js.map} +1 -1
- package/dist/{token-BGCjZwtj.js → token-8drORhXg.js} +33 -3
- package/dist/token-8drORhXg.js.map +1 -0
- package/dist/{worker-dispatch-BCTMyNE-.js → worker-dispatch-D5fGroNr.js} +2 -2
- package/dist/{worker-dispatch-BCTMyNE-.js.map → worker-dispatch-D5fGroNr.js.map} +1 -1
- package/package.json +1 -1
- package/dist/attribution-settings-Cmz2jt7P.js.map +0 -1
- package/dist/claude-C-9xFI4b.js.map +0 -1
- package/dist/engine-iEqGdx6T.js +0 -2
- package/dist/lifecycle-BTodQvn4.js +0 -2
- package/dist/lifecycle-C7JYNz-F.js +0 -2
- package/dist/paths-CTr59UC6.js +0 -2
- package/dist/peer-mcp-personas-Bd56EmiO.js.map +0 -1
- package/dist/server-setup-DlztZAGT.js.map +0 -1
- 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-
|
|
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-
|
|
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-
|
|
7
|
-
import { t as MCP_WORKSPACE_HEADER } from "./mcp-workspace-header-
|
|
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-
|
|
10
|
-
import { f as resolveSealedGate, m as runGateChecks, p as sealedGateIds } from "./stop-gate-hook-
|
|
11
|
-
import { t as liveExec } from "./orchestration-
|
|
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
|
-
/**
|
|
451
|
-
*
|
|
452
|
-
|
|
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` → `
|
|
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.
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
* (
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
*
|
|
486
|
-
*
|
|
487
|
-
*
|
|
488
|
-
*
|
|
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(
|
|
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-
|
|
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:
|
|
24252
|
-
out:
|
|
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:
|
|
24264
|
-
out:
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
27933
|
-
|
|
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)
|
|
29339
|
-
*
|
|
29340
|
-
*
|
|
29341
|
-
*
|
|
29342
|
-
*
|
|
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
|
-
/**
|
|
29362
|
-
*
|
|
29363
|
-
*
|
|
29364
|
-
*
|
|
29365
|
-
*
|
|
29366
|
-
*
|
|
29367
|
-
*
|
|
29368
|
-
*
|
|
29369
|
-
|
|
29370
|
-
|
|
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.
|
|
29439
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
29505
|
-
*
|
|
29506
|
-
*
|
|
29507
|
-
*
|
|
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
|
-
|
|
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
|
|
29522
|
-
return clampEffort(
|
|
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
|
-
|
|
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
|
|
29816
|
-
*
|
|
29817
|
-
*
|
|
29818
|
-
*
|
|
29819
|
-
*
|
|
29820
|
-
*
|
|
29821
|
-
*
|
|
29822
|
-
*
|
|
29823
|
-
|
|
29824
|
-
|
|
29825
|
-
|
|
29826
|
-
|
|
29827
|
-
|
|
29828
|
-
|
|
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
|
-
|
|
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:
|
|
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)
|
|
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 {
|
|
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-
|
|
36896
|
+
//# sourceMappingURL=peer-mcp-personas-CHbl6MwM.js.map
|