github-router 0.3.310 → 0.3.312

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 (29) hide show
  1. package/dist/{attribution-settings-n3zk0BR5.js → attribution-settings-os6aa-ck.js} +133 -23
  2. package/dist/attribution-settings-os6aa-ck.js.map +1 -0
  3. package/dist/browser-ext/manifest.json +1 -1
  4. package/dist/{claude-B0XJn7SY.js → claude-CHObdsb5.js} +111 -42
  5. package/dist/claude-CHObdsb5.js.map +1 -0
  6. package/dist/{codex-B2AyAp1I.js → codex-CUQklRRv.js} +4 -4
  7. package/dist/{codex-B2AyAp1I.js.map → codex-CUQklRRv.js.map} +1 -1
  8. package/dist/engine-DyzGsCUb.js +2 -0
  9. package/dist/{gate-discovery-Vr1ZKmB9.js → gate-discovery-_flEdhYo.js} +2 -2
  10. package/dist/{gate-discovery-Vr1ZKmB9.js.map → gate-discovery-_flEdhYo.js.map} +1 -1
  11. package/dist/{internal-stop-hook-BXHOU8L9.js → internal-stop-hook-BJ3o61-L.js} +2 -2
  12. package/dist/{internal-stop-hook-BXHOU8L9.js.map → internal-stop-hook-BJ3o61-L.js.map} +1 -1
  13. package/dist/main.js +5 -5
  14. package/dist/{peer-mcp-personas-CBJbxpNT.js → peer-mcp-personas-DklYru_1.js} +238 -59
  15. package/dist/peer-mcp-personas-DklYru_1.js.map +1 -0
  16. package/dist/{provision-D_fn8Ry4.js → provision-BYdIsKcp.js} +2 -2
  17. package/dist/{provision-D_fn8Ry4.js.map → provision-BYdIsKcp.js.map} +1 -1
  18. package/dist/{serve-FPj_k6Af.js → serve-B70Kczq6.js} +5 -5
  19. package/dist/{serve-FPj_k6Af.js.map → serve-B70Kczq6.js.map} +1 -1
  20. package/dist/{server-setup-m5vGQ3k8.js → server-setup-DX27fdcf.js} +275 -29
  21. package/dist/server-setup-DX27fdcf.js.map +1 -0
  22. package/dist/{start-Bw--i3Rz.js → start-JbcSzClY.js} +3 -3
  23. package/dist/{start-Bw--i3Rz.js.map → start-JbcSzClY.js.map} +1 -1
  24. package/package.json +1 -1
  25. package/dist/attribution-settings-n3zk0BR5.js.map +0 -1
  26. package/dist/claude-B0XJn7SY.js.map +0 -1
  27. package/dist/engine-BLetfq6a.js +0 -2
  28. package/dist/peer-mcp-personas-CBJbxpNT.js.map +0 -1
  29. package/dist/server-setup-m5vGQ3k8.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { $ as UNKNOWN_EFFORT_ANCHOR, At as shimDefaultsToXhigh, B as resolveAdvisorEffort, Bt as warnOnTokenPriceDrift, Cn as oneMContextDisabled, D as toolbeltEnabled, En as withInstallLock, F as ADVISOR_TOOL_INSTRUCTIONS, Ft as getTokenizerFromModel, G as repairRejectedThinkingHistory, Gt as readResponseBodyCapped, H as formatThinkingRepairDecline, Ht as createResponses, I as FAST_ADVISOR_TOOL_INSTRUCTIONS, It as findLaunchBySecret, J as isControllerClosedError, K as buildAnthropicErrorEvent, Kt as parseJsonOrDiagnose, L as buildAdvisorStream, Mt as createMessages, N as searchWeb, Nt as getTextTokenCount, P as ADVISOR_INTERNAL_TOOL_NAME, Pt as getTokenCount, Q as EFFORT_ORDER, R as injectAdvisorTool, Sn as catalogAdvertises1M, Tn as withOneMSuffixForLead, U as rememberThinkingHistoryRepair, Ut as createChatCompletions, V as resolveAdvisorModel, Vt as resolveMcpToolTimeoutMs, W as repairKnownThinkingHistory, Wt as MAX_RESPONSE_BODY_BYTES, X as readIteratorWithTimeout, Y as logStreamError, Z as relayAnthropicStream, bn as upstreamMaxConnections, cn as BUDGET_SMALL_FAST_SLUG, et as bucketEffort, gn as isBudgetClaudeLead, hn as generateRandomPort, i as assertMcpToolSurfaceConsistent, it as agentToolsEnabled, jt as countTokens, mn as UPSTREAM_INACTIVITY_TIMEOUT_MS, nt as handleMcpDelete, on as toolbeltPathOverride, pn as UPSTREAM_FETCH_TIMEOUT_MS, q as buildOpenAIErrorEvent, qt as normalizeOpenAIUsage, rt as handleMcpPost, tn as provisionTreeSitterAssets, tt as clampEffort, wn as withOneMSuffix, xn as classifyMessagesRoute, yn as upstreamAllowH2, z as isAdvisorRequested, zt as assembleResponsesPayload } from "./peer-mcp-personas-CBJbxpNT.js";
1
+ import { $ as UNKNOWN_EFFORT_ANCHOR, An as CHEAP_PROFILE_MODELS, B as resolveAdvisorEffort, Cn as upstreamMaxConnections, D as toolbeltEnabled, Dn as withOneMSuffix, En as oneMContextDisabled, F as ADVISOR_TOOL_INSTRUCTIONS, Fn as withInstallLock, Ft as createMessages, G as repairRejectedThinkingHistory, Gt as createResponses, H as formatThinkingRepairDecline, Ht as assembleResponsesPayload, I as FAST_ADVISOR_TOOL_INSTRUCTIONS, It as getTextTokenCount, J as isControllerClosedError, Jt as readResponseBodyCapped, K as buildAnthropicErrorEvent, Kt as createChatCompletions, L as buildAdvisorStream, Lt as getTokenCount, N as searchWeb, Nt as shimDefaultsToXhigh, On as withOneMSuffixForLead, P as ADVISOR_INTERNAL_TOOL_NAME, Pn as CHEAP_PROFILE_SUBAGENT_CONTEXT_TOKENS, Pt as countTokens, Q as EFFORT_ORDER, R as injectAdvisorTool, Rt as getTokenizerFromModel, Sn as upstreamAllowH2, Tn as catalogAdvertises1M, U as rememberThinkingHistoryRepair, Ut as warnOnTokenPriceDrift, V as resolveAdvisorModel, W as repairKnownThinkingHistory, Wt as resolveMcpToolTimeoutMs, X as readIteratorWithTimeout, Xt as normalizeOpenAIUsage, Y as logStreamError, Yt as parseJsonOrDiagnose, Z as relayAnthropicStream, _n as UPSTREAM_INACTIVITY_TIMEOUT_MS, dn as BUDGET_SMALL_FAST_SLUG, et as bucketEffort, gn as UPSTREAM_FETCH_TIMEOUT_MS, i as assertMcpToolSurfaceConsistent, in as provisionTreeSitterAssets, it as agentToolsEnabled, jn as CHEAP_PROFILE_NATIVE_AGENT_NAMES, kn as CHEAP_PROFILE_ADVISOR_CLIENT_MODEL, ln as toolbeltPathOverride, nt as handleMcpDelete, q as buildOpenAIErrorEvent, qt as MAX_RESPONSE_BODY_BYTES, rt as handleMcpPost, tt as clampEffort, vn as generateRandomPort, wn as classifyMessagesRoute, yn as isBudgetClaudeLead, z as isAdvisorRequested, zt as findLaunchBySecret } from "./peer-mcp-personas-DklYru_1.js";
2
2
  import { t as getPackageVersion } from "./version-C8x2hQrZ.js";
3
3
  import { i as ensurePaths, t as PATHS } from "./paths-De1Qh9Q9.js";
4
4
  import { t as state } from "./state-B8adscCv.js";
@@ -380,6 +380,46 @@ const FAST_PROFILE = Object.freeze({
380
380
  hasCoordinator: false
381
381
  });
382
382
  /**
383
+ * The `-m cheap1m` roster: the named successor of the original cheap launch.
384
+ * Identical to `cheap` except the Gemini leader keeps its full 1M window
385
+ * (the `-m cheap1m` lead slug is `[1m]`-decorated) and the `astra` peer
386
+ * remains in the persona allowlist next to Oracle. Oracle and Astra run at
387
+ * the 200K default window like every cheap-family role. Hard-denies match
388
+ * fast's: core workers, `orchestrate`, `decide`, `fleet`, and `first-mate`.
389
+ */
390
+ const CHEAP1M_PROFILE = Object.freeze({
391
+ id: "cheap1m",
392
+ nativeRoster: new Set(CHEAP_PROFILE_NATIVE_AGENT_NAMES),
393
+ personaAllowlist: /* @__PURE__ */ new Set(["oracle", "astra"]),
394
+ allowedGroups: /* @__PURE__ */ new Set([
395
+ "peers",
396
+ "search",
397
+ "workers",
398
+ "browser"
399
+ ]),
400
+ hasCoordinator: false
401
+ });
402
+ /**
403
+ * The `-m cheap` roster: the exact `fast` surface and groups, minus the 1M
404
+ * accounting decoration on the LEAD as well (`-m cheap` selects the BARE
405
+ * `gemini-3.8-flash` slug, so the leader runs at the 200K default window
406
+ * too), plus a cheaper `grok-4.6`/medium Oracle and NO `astra` peer.
407
+ * Hard-denies match fast's: core workers, `orchestrate`, `decide`, `fleet`,
408
+ * and `first-mate`.
409
+ */
410
+ const CHEAP_PROFILE = Object.freeze({
411
+ id: "cheap",
412
+ nativeRoster: new Set(CHEAP_PROFILE_NATIVE_AGENT_NAMES),
413
+ personaAllowlist: /* @__PURE__ */ new Set(["oracle"]),
414
+ allowedGroups: /* @__PURE__ */ new Set([
415
+ "peers",
416
+ "search",
417
+ "workers",
418
+ "browser"
419
+ ]),
420
+ hasCoordinator: false
421
+ });
422
+ /**
383
423
  * The `-m max` profile: Sol/Luna-led, browse-only workers, and explicit
384
424
  * cross-lab peer names. The descriptor is a hard projection for bound launch
385
425
  * requests; unbound/BYO traffic remains standard because it has no registry
@@ -412,6 +452,8 @@ const MAX_PROFILE = Object.freeze({
412
452
  function profileDescriptor(id) {
413
453
  if (id === "fast") return FAST_PROFILE;
414
454
  if (id === "max") return MAX_PROFILE;
455
+ if (id === "cheap1m") return CHEAP1M_PROFILE;
456
+ if (id === "cheap") return CHEAP_PROFILE;
415
457
  return STANDARD_PROFILE;
416
458
  }
417
459
  /**
@@ -429,6 +471,8 @@ function resolveLaunchProfile(modelArg) {
429
471
  const arg = modelArg?.trim().toLowerCase();
430
472
  if (arg === "fast") return "fast";
431
473
  if (arg === "max") return "max";
474
+ if (arg === "cheap1m") return "cheap1m";
475
+ if (arg === "cheap") return "cheap";
432
476
  return "standard";
433
477
  }
434
478
  function validateMaxProfileLaunch(catalog) {
@@ -643,6 +687,104 @@ function validateFastProfilePrerequisites(catalog) {
643
687
  function formatFastPrerequisiteFailure(missing) {
644
688
  return "github-router claude -m fast requires the following live-catalog capabilities, which this account's catalog does not fully provide:\n" + missing.map((m) => ` - ${m}`).join("\n") + "\n\nFalling back or silently dropping an agent is not supported for the fast profile's exact roster. Run plain `github-router claude` instead.";
645
689
  }
690
+ /** The subagent window floor: every cheap subagent must at least EXCEED the
691
+ * Claude Code default budget it runs at. Unlike fast, the catalog's real
692
+ * window is irrelevant to what the client sends (bare slug = 200K either
693
+ * way); this floor only guarantees the billed model isn't tiny. */
694
+ const CHEAP_SUBAGENT_MIN_CONTEXT_TOKENS = CHEAP_PROFILE_SUBAGENT_CONTEXT_TOKENS;
695
+ /**
696
+ * Shared cheap-family roster prerequisite check, parameterized only by the
697
+ * LEAD's context gate. Both `-m cheap` (200K lead floor) and `-m cheap1m`
698
+ * (1M lead window) validate the EXACT same five-agent roster: the lead gate
699
+ * is the only requirement that differs between the two cheap siblings, and
700
+ * every subagent runs at the 200K default window either way. A non-1M
701
+ * luna/sol/sonnet/grok is acceptable as long as the roster models advertise
702
+ * tool calls, the fixed effort, and a supported endpoint.
703
+ */
704
+ function collectCheapPrerequisiteMissing(catalog, leadGateTokens, leadGateMessage) {
705
+ const missing = [];
706
+ const gemini = findModel(catalog, CHEAP_PROFILE_MODELS.lead);
707
+ if (!gemini) missing.push(`${CHEAP_PROFILE_MODELS.lead}: absent from the live catalog`);
708
+ else {
709
+ if (!hasToolCalls(gemini)) missing.push(`${CHEAP_PROFILE_MODELS.lead}: does not advertise tool_calls`);
710
+ if (!hasContextAtLeast(gemini, leadGateTokens)) missing.push(leadGateMessage(CHEAP_PROFILE_MODELS.lead));
711
+ if (!supportsEffort(gemini, "high")) missing.push(`${CHEAP_PROFILE_MODELS.lead}: does not advertise a "high" reasoning effort`);
712
+ if (!supportsEndpoint(gemini, "chat")) missing.push(`${CHEAP_PROFILE_MODELS.lead}: does not advertise a supported chat-completions endpoint`);
713
+ }
714
+ const luna = findModel(catalog, CHEAP_PROFILE_MODELS.explore);
715
+ if (!luna) missing.push(`${CHEAP_PROFILE_MODELS.explore}: absent from the live catalog`);
716
+ else {
717
+ if (!hasToolCalls(luna)) missing.push(`${CHEAP_PROFILE_MODELS.explore}: does not advertise tool_calls`);
718
+ if (!hasContextAtLeast(luna, CHEAP_SUBAGENT_MIN_CONTEXT_TOKENS)) missing.push(`${CHEAP_PROFILE_MODELS.explore}: advertised context window is below the 200K subagent floor`);
719
+ if (!supportsEffort(luna, "high") || !supportsEffort(luna, "max")) missing.push(`${CHEAP_PROFILE_MODELS.explore}: does not advertise both "high" and "max" reasoning effort`);
720
+ if (!supportsEndpoint(luna, "responses")) missing.push(`${CHEAP_PROFILE_MODELS.explore}: does not advertise a supported Responses endpoint`);
721
+ }
722
+ const sol = findModel(catalog, CHEAP_PROFILE_MODELS.plan);
723
+ if (!sol) missing.push(`${CHEAP_PROFILE_MODELS.plan}: absent from the live catalog`);
724
+ else {
725
+ if (!hasToolCalls(sol)) missing.push(`${CHEAP_PROFILE_MODELS.plan}: does not advertise tool_calls`);
726
+ if (!hasContextAtLeast(sol, CHEAP_SUBAGENT_MIN_CONTEXT_TOKENS)) missing.push(`${CHEAP_PROFILE_MODELS.plan}: advertised context window is below the 200K subagent floor`);
727
+ if (!supportsEffort(sol, "high")) missing.push(`${CHEAP_PROFILE_MODELS.plan}: does not advertise a "high" reasoning effort`);
728
+ if (!supportsEndpoint(sol, "responses")) missing.push(`${CHEAP_PROFILE_MODELS.plan}: does not advertise a supported Responses endpoint`);
729
+ }
730
+ const reviewer = findModel(catalog, CHEAP_PROFILE_MODELS.reviewer);
731
+ if (!reviewer) missing.push(`${CHEAP_PROFILE_MODELS.reviewer}: absent from the live catalog`);
732
+ else {
733
+ if (!hasToolCalls(reviewer)) missing.push(`${CHEAP_PROFILE_MODELS.reviewer}: does not advertise tool_calls`);
734
+ if (!hasContextAtLeast(reviewer, CHEAP_SUBAGENT_MIN_CONTEXT_TOKENS)) missing.push(`${CHEAP_PROFILE_MODELS.reviewer}: advertised context window is below the 200K subagent floor`);
735
+ if (!supportsEffort(reviewer, "max")) missing.push(`${CHEAP_PROFILE_MODELS.reviewer}: does not advertise a "max" reasoning effort`);
736
+ if (!supportsEndpoint(reviewer, "responses")) missing.push(`${CHEAP_PROFILE_MODELS.reviewer}: does not advertise a supported Responses endpoint`);
737
+ }
738
+ const grok = findModel(catalog, CHEAP_PROFILE_MODELS.oracle);
739
+ if (!grok) missing.push(`${CHEAP_PROFILE_MODELS.oracle}: absent from the live catalog`);
740
+ else {
741
+ if (!hasContextAtLeast(grok, CHEAP_SUBAGENT_MIN_CONTEXT_TOKENS)) missing.push(`${CHEAP_PROFILE_MODELS.oracle}: advertised context window is below the 200K subagent floor`);
742
+ if (!supportsEffort(grok, "medium")) missing.push(`${CHEAP_PROFILE_MODELS.oracle}: does not advertise a "medium" reasoning effort`);
743
+ if (!hasUsablePromptMetadata(grok)) missing.push(`${CHEAP_PROFILE_MODELS.oracle}: no usable max_prompt_tokens metadata`);
744
+ if (!supportsEndpoint(grok, "responses")) missing.push(`${CHEAP_PROFILE_MODELS.oracle}: does not advertise a supported Responses endpoint`);
745
+ }
746
+ return missing;
747
+ }
748
+ /**
749
+ * Validate the live Copilot catalog for `-m cheap1m` (the named successor of
750
+ * the original cheap launch): the Gemini leader must still advertise 1M, so
751
+ * the lead keeps its full window while every subagent runs at the 200K
752
+ * default. Roster and capability checks are otherwise identical to `cheap`.
753
+ */
754
+ function validateCheap1mProfilePrerequisites(catalog) {
755
+ const missing = collectCheapPrerequisiteMissing(catalog, FAST_REQUIRED_CONTEXT_TOKENS, (id) => `${id}: advertised context window is below 1M (leader window)`);
756
+ return {
757
+ ok: missing.length === 0,
758
+ missing
759
+ };
760
+ }
761
+ /**
762
+ * Validate the live Copilot catalog for `-m cheap`: the Gemini leader runs
763
+ * at the 200K DEFAULT window (bare slug), so the lead only needs to clear
764
+ * the same 200K floor as every subagent — unlike `cheap1m`, which keeps the
765
+ * 1M leader window. Roster and capability checks are otherwise identical.
766
+ */
767
+ function validateCheapProfilePrerequisites(catalog) {
768
+ const missing = collectCheapPrerequisiteMissing(catalog, CHEAP_SUBAGENT_MIN_CONTEXT_TOKENS, (id) => `${id}: advertised context window is below the 200K lead floor`);
769
+ return {
770
+ ok: missing.length === 0,
771
+ missing
772
+ };
773
+ }
774
+ /**
775
+ * Format `validateCheap1mProfilePrerequisites`'s failure list into the launch
776
+ * error message: every missing/invalid model, plus the rollback command.
777
+ */
778
+ function formatCheap1mPrerequisiteFailure(missing) {
779
+ return "github-router claude -m cheap1m requires the following live-catalog capabilities, which this account's catalog does not fully provide:\n" + missing.map((m) => ` - ${m}`).join("\n") + "\n\nFalling back or silently dropping an agent is not supported for the cheap1m profile's exact roster. Run plain `github-router claude` instead.";
780
+ }
781
+ /**
782
+ * Format `validateCheapProfilePrerequisites`'s failure list into the launch
783
+ * error message: every missing/invalid model, plus the rollback command.
784
+ */
785
+ function formatCheapPrerequisiteFailure(missing) {
786
+ return "github-router claude -m cheap requires the following live-catalog capabilities, which this account's catalog does not fully provide:\n" + missing.map((m) => ` - ${m}`).join("\n") + "\n\nFalling back or silently dropping an agent is not supported for the cheap profile's exact roster. Run plain `github-router claude` instead.";
787
+ }
646
788
  //#endregion
647
789
  //#region src/lib/file-log-reporter.ts
648
790
  const MAX_LOG_BYTES = 1048576;
@@ -981,6 +1123,32 @@ const MAX_PICKER_MODELS = Object.freeze([
981
1123
  behavesAs: "claude-opus-5"
982
1124
  }
983
1125
  ]);
1126
+ const CHEAP_PICKER_MODELS = Object.freeze([
1127
+ {
1128
+ id: "gpt-5.6-sol",
1129
+ label: "GPT-5.6 Sol",
1130
+ behavesAs: "claude-opus-5",
1131
+ neverOneM: true
1132
+ },
1133
+ {
1134
+ id: "gpt-5.6-luna",
1135
+ label: "GPT-5.6 Luna",
1136
+ behavesAs: "claude-opus-5",
1137
+ neverOneM: true
1138
+ },
1139
+ {
1140
+ id: "gemini-3.8-flash",
1141
+ label: "Gemini 3.8 Flash",
1142
+ behavesAs: "claude-sonnet-5",
1143
+ neverOneM: true
1144
+ },
1145
+ {
1146
+ id: "grok-4.6",
1147
+ label: "Grok 4.6",
1148
+ behavesAs: "claude-sonnet-5",
1149
+ neverOneM: true
1150
+ }
1151
+ ]);
984
1152
  /**
985
1153
  * Return the ordered, profile-specific `/model` rows supported by the live
986
1154
  * Copilot catalog. Fast intentionally shares Standard's four-row inventory;
@@ -998,7 +1166,7 @@ function selectableModelsInCatalog(profile) {
998
1166
  const catalog = state.models?.data;
999
1167
  if (!catalog || catalog.length === 0) return [];
1000
1168
  const present = new Set(catalog.map((entry) => entry.id));
1001
- return (profile === "max" ? MAX_PICKER_MODELS : STANDARD_PICKER_MODELS).filter((entry) => present.has(entry.id)).map((entry) => ({
1169
+ return (profile === "max" ? MAX_PICKER_MODELS : profile === "cheap" || profile === "cheap1m" ? CHEAP_PICKER_MODELS : STANDARD_PICKER_MODELS).filter((entry) => present.has(entry.id)).map((entry) => ({
1002
1170
  model: entry.neverOneM ? entry.id : withOneMSuffix(entry.id),
1003
1171
  label: entry.label,
1004
1172
  behavesAs: entry.behavesAs
@@ -1938,7 +2106,7 @@ function collectToolFieldKeys(body) {
1938
2106
  //#endregion
1939
2107
  //#region package.json
1940
2108
  var name = "github-router";
1941
- var version = "0.3.310";
2109
+ var version = "0.3.312";
1942
2110
  //#endregion
1943
2111
  //#region src/lib/approval.ts
1944
2112
  const awaitApproval = async () => {
@@ -2004,8 +2172,9 @@ async function doCheck(state, ticket) {
2004
2172
  * of silently switching Tier A off.
2005
2173
  */
2006
2174
  const CLIENT_REDUNDANCY_MARKERS = /* @__PURE__ */ new Set(["Wasted call — file unchanged since your last Read. Refer to that earlier tool_result instead."]);
2007
- const DEFAULT_NUDGE_AT = 4;
2008
- const DEFAULT_ABORT_AT = 7;
2175
+ const DEFAULT_NUDGE_AT = 3;
2176
+ const DEFAULT_WARN_AT = 7;
2177
+ const DEFAULT_ABORT_AT = 17;
2009
2178
  /**
2010
2179
  * Results are compared by equality, and a single `read` can return megabytes.
2011
2180
  * Anything longer than this is compared by digest instead so a comparison stays
@@ -2032,6 +2201,9 @@ function envThreshold(key, fallback) {
2032
2201
  function loopNudgeAt() {
2033
2202
  return envThreshold("GH_ROUTER_LOOP_NUDGE_AT", DEFAULT_NUDGE_AT);
2034
2203
  }
2204
+ function loopWarnAt() {
2205
+ return envThreshold("GH_ROUTER_LOOP_WARN_AT", DEFAULT_WARN_AT);
2206
+ }
2035
2207
  function loopAbortAt() {
2036
2208
  return envThreshold("GH_ROUTER_LOOP_ABORT_AT", DEFAULT_ABORT_AT);
2037
2209
  }
@@ -2120,8 +2292,13 @@ function turnSignature(turn) {
2120
2292
  */
2121
2293
  function detectToolLoop(turns, options = {}) {
2122
2294
  const nudgeAt = options.nudgeAt ?? loopNudgeAt();
2295
+ const warnAt = options.warnAt ?? loopWarnAt();
2123
2296
  const abortAt = options.abortAt ?? loopAbortAt();
2124
- const active = [nudgeAt, abortAt].filter((n) => n > 0);
2297
+ const active = [
2298
+ nudgeAt,
2299
+ warnAt,
2300
+ abortAt
2301
+ ].filter((n) => n > 0);
2125
2302
  if (active.length === 0 || turns.length === 0) return NO_LOOP;
2126
2303
  const limit = Math.max(...active);
2127
2304
  const last = turns[turns.length - 1];
@@ -2151,6 +2328,12 @@ function detectToolLoop(turns, options = {}) {
2151
2328
  toolName
2152
2329
  };
2153
2330
  }
2331
+ if (warnAt > 0 && repeats >= warnAt) return {
2332
+ action: "warn",
2333
+ tier: markerRun ? "A" : "B",
2334
+ repeats,
2335
+ toolName
2336
+ };
2154
2337
  if (nudgeAt > 0 && repeats >= nudgeAt) return {
2155
2338
  action: "nudge",
2156
2339
  tier: markerRun ? "A" : "B",
@@ -2165,11 +2348,15 @@ function uniqueToolName(turn) {
2165
2348
  }
2166
2349
  /** Text injected as a sibling block when a loop is suspected but not certain. */
2167
2350
  function nudgeText(verdict) {
2168
- return `[github-router] The same ${verdict.toolName ? `\`${verdict.toolName}\` call` : "tool call"} has now been repeated ${verdict.repeats}× in a row with an identical result. Nothing has changed and nothing will change by repeating it. Use the result you already have, try a materially different approach, or stop and report what you found.`;
2351
+ return `[github-router loop-guard] You have repeated the same ${verdict.toolName ? `\`${verdict.toolName}\` call` : "tool call"} ${verdict.repeats}× in a row with an identical result. Pause and ask yourself: Are you doing the right thing? What is your hypothesis? If the current approach is not producing new information, vary your parameters, use a different tool, or inspect surrounding context.`;
2352
+ }
2353
+ /** Text injected as a stronger warning sibling block before halting. */
2354
+ function warnText(verdict) {
2355
+ return `[github-router loop-guard WARNING] You have now repeated the exact same ${verdict.toolName ? `\`${verdict.toolName}\` call` : "tool call"} ${verdict.repeats}× with zero change in outcome. Continuing to repeat this call is actively failing. Stop repeating this action immediately. Step back, re-evaluate your assumptions, switch to an alternative tool or approach, or state what is blocking you.`;
2169
2356
  }
2170
2357
  /** Message returned to the client when the loop is aborted. */
2171
2358
  function abortMessage(verdict) {
2172
- return `Request blocked by github-router: the same ${verdict.toolName ? `\`${verdict.toolName}\` call` : "tool call"} has been repeated ${verdict.repeats}× consecutively with an identical result and no intervening progress. Continuing would burn inference with no possible change in outcome. Vary the call or end the turn. Tune or disable this with GH_ROUTER_LOOP_ABORT_AT.`;
2359
+ return `Request halted by github-router loop-guard: the same ${verdict.toolName ? `\`${verdict.toolName}\` call` : "tool call"} has been repeated ${verdict.repeats}× consecutively with no progress. github-router has halted this turn to prevent runaway token burn and preserve your session. Continuing would burn inference with no possible change in outcome. Resume immediately by typing a new direction in the prompt. Tune or disable with GH_ROUTER_LOOP_ABORT_AT.`;
2173
2360
  }
2174
2361
  /**
2175
2362
  * Cheap pre-check so a request with no tool traffic never pays for a parse.
@@ -2415,9 +2602,10 @@ function guardAnthropicBody(rawBody) {
2415
2602
  message: abortMessage(verdict),
2416
2603
  verdict
2417
2604
  };
2418
- if (!injectAnthropicNudge(parsed, nudgeText(verdict))) return NO_ACTION;
2605
+ const text = verdict.action === "warn" ? warnText(verdict) : nudgeText(verdict);
2606
+ if (!injectAnthropicNudge(parsed, text)) return NO_ACTION;
2419
2607
  return {
2420
- action: "nudge",
2608
+ action: verdict.action,
2421
2609
  body: JSON.stringify(parsed),
2422
2610
  verdict
2423
2611
  };
@@ -2432,9 +2620,9 @@ function guardChatPayload(payload) {
2432
2620
  message: abortMessage(verdict),
2433
2621
  verdict
2434
2622
  };
2435
- if (!injectChatNudge(payload, nudgeText(verdict))) return NO_ACTION;
2623
+ if (!injectChatNudge(payload, verdict.action === "warn" ? warnText(verdict) : nudgeText(verdict))) return NO_ACTION;
2436
2624
  return {
2437
- action: "nudge",
2625
+ action: verdict.action,
2438
2626
  verdict
2439
2627
  };
2440
2628
  }
@@ -2448,9 +2636,9 @@ function guardResponsesPayload(payload) {
2448
2636
  message: abortMessage(verdict),
2449
2637
  verdict
2450
2638
  };
2451
- if (!injectResponsesNudge(payload, nudgeText(verdict))) return NO_ACTION;
2639
+ if (!injectResponsesNudge(payload, verdict.action === "warn" ? warnText(verdict) : nudgeText(verdict))) return NO_ACTION;
2452
2640
  return {
2453
- action: "nudge",
2641
+ action: verdict.action,
2454
2642
  verdict
2455
2643
  };
2456
2644
  }
@@ -4890,10 +5078,16 @@ function preprocessMaxRequest(rawBody, launch, subagentRequest = false) {
4890
5078
  Object.freeze([MAX_LUNA_HIGH_ALIAS_ID, MAX_LUNA_MAX_ALIAS_ID]);
4891
5079
  //#endregion
4892
5080
  //#region src/lib/fast-request-preprocess.ts
5081
+ const cheapFamily = (profileId) => profileId === "cheap" || profileId === "cheap1m";
4893
5082
  /**
4894
- * Apply authenticated fast-profile model and effort policy before ordinary model
4895
- * resolution. Synthetic aliases are refused outside an authenticated fast
4896
- * launch, so raw/BYO traffic cannot opt itself into private profile semantics.
5083
+ * Apply authenticated fast/cheap-profile model and effort policy before
5084
+ * ordinary model resolution. Synthetic aliases are refused outside an
5085
+ * authenticated fast or cheap launch, so raw/BYO traffic cannot opt itself
5086
+ * into private profile semantics. The cheap family shares fast's effort
5087
+ * mapping (same model-to-effort rows, just bare subagent slugs at the
5088
+ * wiring layer), so this preprocess is shared. Note the reviewer differs:
5089
+ * fast reviews on Sonnet 5/xhigh while cheap reviews on Luna/max — both
5090
+ * rows exist here, so each profile's reviewer resolves to its fixed effort.
4897
5091
  */
4898
5092
  function preprocessFastRequest(rawBody, launch, subagentRequest = false) {
4899
5093
  if (launch?.profileId === "max") return preprocessMaxRequest(rawBody, launch, subagentRequest);
@@ -4918,13 +5112,13 @@ function preprocessFastRequest(rawBody, launch, subagentRequest = false) {
4918
5112
  retiredAlias: originalModel
4919
5113
  };
4920
5114
  const alias = resolveModelAlias(originalModel);
4921
- if (alias && (launch?.profileId !== "fast" || isMaxModelAlias(originalModel))) return {
5115
+ if (alias && (launch?.profileId !== "fast" && !cheapFamily(launch?.profileId) || isMaxModelAlias(originalModel))) return {
4922
5116
  body: rawBody,
4923
5117
  originalModel,
4924
5118
  modified: false,
4925
5119
  rejectedAlias: originalModel
4926
5120
  };
4927
- if (launch?.profileId !== "fast") return {
5121
+ if (launch?.profileId !== "fast" && !cheapFamily(launch?.profileId)) return {
4928
5122
  body: rawBody,
4929
5123
  originalModel,
4930
5124
  modified: false
@@ -5398,15 +5592,19 @@ async function handleCompletion(c) {
5398
5592
  const advisorRequested = isAdvisorRequested(incomingBeta);
5399
5593
  const fastProfileRequest = identity.launch?.profileId === "fast";
5400
5594
  const maxProfileRequest = identity.launch?.profileId === "max";
5595
+ const cheapProfileRequest = identity.launch?.profileId === "cheap" || identity.launch?.profileId === "cheap1m";
5401
5596
  const subagentRequest = Boolean(c.req.header("x-claude-code-agent-id"));
5402
5597
  const fastSubagentRequest = fastProfileRequest && subagentRequest;
5403
5598
  const maxSubagentRequest = maxProfileRequest && subagentRequest;
5599
+ const cheapSubagentRequest = cheapProfileRequest && subagentRequest;
5404
5600
  const fastLeadAdvisor = fastProfileRequest && !fastSubagentRequest;
5405
5601
  const maxLeadAdvisor = maxProfileRequest && !maxSubagentRequest;
5406
- const advisorEnabled = advisorRequested && !fastSubagentRequest && !maxSubagentRequest;
5602
+ const cheapLeadAdvisor = cheapProfileRequest && !cheapSubagentRequest;
5603
+ const advisorEnabled = advisorRequested && !fastSubagentRequest && !maxSubagentRequest && !cheapSubagentRequest;
5407
5604
  const fastAdvisorEnabled = fastLeadAdvisor && advisorRequested && hasNonEmptyTools(rawBody);
5408
5605
  const maxAdvisorEnabled = maxLeadAdvisor && advisorRequested && hasNonEmptyTools(rawBody);
5409
- const advisorBehaviorEnabled = fastLeadAdvisor ? fastAdvisorEnabled : maxLeadAdvisor ? maxAdvisorEnabled : advisorEnabled;
5606
+ const cheapAdvisorEnabled = cheapLeadAdvisor && advisorRequested && hasNonEmptyTools(rawBody);
5607
+ const advisorBehaviorEnabled = fastLeadAdvisor ? fastAdvisorEnabled : cheapLeadAdvisor ? cheapAdvisorEnabled : maxLeadAdvisor ? maxAdvisorEnabled : advisorEnabled;
5410
5608
  let fastAdvisorChoice;
5411
5609
  let maxAdvisorChoice;
5412
5610
  if (maxAdvisorEnabled) {
@@ -5445,9 +5643,33 @@ async function handleCompletion(c) {
5445
5643
  }, 503);
5446
5644
  }
5447
5645
  }
5646
+ if (cheapAdvisorEnabled) {
5647
+ const mismatch = fastAdvisorMetadataMismatch(rawBody);
5648
+ const relaunchProfile = identity.launch?.profileId === "cheap1m" ? "cheap1m" : "cheap";
5649
+ if (mismatch) return c.json({
5650
+ type: "error",
5651
+ error: {
5652
+ type: "invalid_request_error",
5653
+ message: `Cheap Advisor model mismatch: ${mismatch}. Run \`/advisor ${CHEAP_PROFILE_ADVISOR_CLIENT_MODEL}\` to restore the fixed cheap profile, or relaunch with \`github-router claude -m ${relaunchProfile}\`.`
5654
+ }
5655
+ }, 400, { "x-should-retry": "false" });
5656
+ try {
5657
+ fastAdvisorChoice = resolveAdvisorModel(void 0, true);
5658
+ } catch (error) {
5659
+ return c.json({
5660
+ type: "error",
5661
+ error: {
5662
+ type: "api_error",
5663
+ message: error instanceof Error ? error.message : String(error)
5664
+ }
5665
+ }, 503);
5666
+ }
5667
+ }
5448
5668
  const fastPreprocess = preprocessFastRequest(rawBody, identity.launch, subagentRequest);
5449
5669
  if (fastPreprocess.retiredAlias || fastPreprocess.rejectedAlias || fastPreprocess.rejectedModel) {
5450
- const message = fastPreprocess.retiredAlias ? `Router-owned model alias ${JSON.stringify(fastPreprocess.retiredAlias)} belongs to a retired Fast role and is no longer valid. Relaunch or select a current Fast role.` : identity.launch?.profileId === "max" ? maxRequestError(fastPreprocess) ?? "Invalid max request" : fastPreprocess.rejectedAlias ? `Router-owned model alias ${JSON.stringify(fastPreprocess.rejectedAlias)} is valid only for an authenticated -m fast launch.` : `Model ${JSON.stringify(fastPreprocess.rejectedModel)} is outside the fixed -m fast model set.`;
5670
+ const profileId = identity.launch?.profileId;
5671
+ const profileLabel = profileId === "cheap" || profileId === "cheap1m" ? "cheap" : "fast";
5672
+ const message = fastPreprocess.retiredAlias ? `Router-owned model alias ${JSON.stringify(fastPreprocess.retiredAlias)} belongs to a retired Fast role and is no longer valid. Relaunch or select a current Fast role.` : profileId === "max" ? maxRequestError(fastPreprocess) ?? "Invalid max request" : fastPreprocess.rejectedAlias ? `Router-owned model alias ${JSON.stringify(fastPreprocess.rejectedAlias)} is valid only for an authenticated -m ${profileLabel} launch.` : `Model ${JSON.stringify(fastPreprocess.rejectedModel)} is outside the fixed -m ${profileLabel} model set.`;
5451
5673
  return c.json({
5452
5674
  type: "error",
5453
5675
  error: {
@@ -5493,7 +5715,7 @@ async function handleCompletion(c) {
5493
5715
  parsedBase = JSON.parse(promptWindowBody);
5494
5716
  } catch {}
5495
5717
  const wantsStream = parsedBase?.stream === true;
5496
- if ((fastAdvisorEnabled || maxAdvisorEnabled) && wantsStream) {
5718
+ if ((fastAdvisorEnabled || cheapAdvisorEnabled || maxAdvisorEnabled) && wantsStream) {
5497
5719
  const initialConversation = Array.isArray(parsedBase.messages) ? parsedBase.messages : [];
5498
5720
  const parsedInitial = parseAnthropicRequest(parsedBase, modelId, selectedModel);
5499
5721
  const translatedAdvisorAborter = new AbortController();
@@ -5516,6 +5738,11 @@ async function handleCompletion(c) {
5516
5738
  effort: maxAdvisorChoice.effort,
5517
5739
  escalated: false,
5518
5740
  fastProfile: false
5741
+ } : cheapAdvisorEnabled ? {
5742
+ model: fastAdvisorChoice.model,
5743
+ effort: resolveAdvisorEffort(rawBody, fastAdvisorChoice.model, true),
5744
+ escalated: fastAdvisorChoice.escalated,
5745
+ fastProfile: true
5519
5746
  } : {
5520
5747
  model: fastAdvisorChoice.model,
5521
5748
  effort: resolveAdvisorEffort(rawBody, fastAdvisorChoice.model, true),
@@ -5531,6 +5758,7 @@ async function handleCompletion(c) {
5531
5758
  advisorEscalated: advisorChoice.escalated,
5532
5759
  advisorFastProfile: advisorChoice.fastProfile,
5533
5760
  advisorMaxProfile: maxAdvisorEnabled,
5761
+ advisorCheapProfile: cheapAdvisorEnabled,
5534
5762
  advisorEffort: advisorChoice.effort,
5535
5763
  externalAborter: translatedAdvisorAborter,
5536
5764
  continueTurn: makeShimContinueTurn(endpoint, {
@@ -5653,9 +5881,10 @@ async function handleCompletion(c) {
5653
5881
  requestHeaders,
5654
5882
  advisorModel: advisorChoice.model,
5655
5883
  advisorEscalated: advisorChoice.escalated,
5656
- advisorFastProfile: fastLeadAdvisor,
5884
+ advisorFastProfile: fastLeadAdvisor || cheapLeadAdvisor,
5657
5885
  advisorMaxProfile: maxAdvisorEnabled,
5658
- advisorEffort: maxAdvisorChoice ? maxAdvisorChoice.effort : resolveAdvisorEffort(rawBody, advisorChoice.model, fastLeadAdvisor),
5886
+ advisorCheapProfile: cheapLeadAdvisor,
5887
+ advisorEffort: maxAdvisorChoice ? maxAdvisorChoice.effort : resolveAdvisorEffort(rawBody, advisorChoice.model, fastLeadAdvisor || cheapLeadAdvisor),
5659
5888
  externalAborter: advisorAborter
5660
5889
  }), {
5661
5890
  status: response.status,
@@ -7046,8 +7275,9 @@ function getClaudeCodeEnvVars(serverUrl, model, launchProfileId = "standard", pi
7046
7275
  if (process.env.MCP_TOOL_TIMEOUT === void 0) vars.MCP_TOOL_TIMEOUT = mcpToolTimeoutMs;
7047
7276
  const isFastProfile = launchProfileId === "fast";
7048
7277
  const isMaxProfile = launchProfileId === "max";
7049
- const smallFastModel = isMaxProfile ? MAX_LUNA_HIGH_ALIAS_ID : isFastProfile ? LUNA_HAIKU_ALIAS_ID : isBudgetClaudeLead(model) && (state.models?.data?.some((m) => m.id === "claude-haiku-4.5") ?? false) ? BUDGET_SMALL_FAST_SLUG : "claude-sonnet-5";
7050
- if (process.env.ANTHROPIC_SMALL_FAST_MODEL === void 0) vars.ANTHROPIC_SMALL_FAST_MODEL = isFastProfile || isMaxProfile ? oneMSuffixForAlias(smallFastModel) : smallFastModel;
7278
+ const isCheapProfile = launchProfileId === "cheap" || launchProfileId === "cheap1m";
7279
+ const smallFastModel = isMaxProfile ? MAX_LUNA_HIGH_ALIAS_ID : isFastProfile ? LUNA_HAIKU_ALIAS_ID : isCheapProfile ? LUNA_HAIKU_ALIAS_ID : isBudgetClaudeLead(model) && (state.models?.data?.some((m) => m.id === "claude-haiku-4.5") ?? false) ? BUDGET_SMALL_FAST_SLUG : "claude-sonnet-5";
7280
+ if (process.env.ANTHROPIC_SMALL_FAST_MODEL === void 0) vars.ANTHROPIC_SMALL_FAST_MODEL = (isFastProfile || isMaxProfile) && !isCheapProfile ? oneMSuffixForAlias(smallFastModel) : smallFastModel;
7051
7281
  const seedTierRow = (modelKey, nameKey, bareSlug) => {
7052
7282
  if (process.env[modelKey] !== void 0) return;
7053
7283
  vars[modelKey] = withOneMSuffixForLead(bareSlug);
@@ -7058,6 +7288,11 @@ function getClaudeCodeEnvVars(serverUrl, model, launchProfileId = "standard", pi
7058
7288
  vars[modelKey] = oneMSuffixForAlias(aliasId);
7059
7289
  if (process.env[nameKey] === void 0) vars[nameKey] = displayName;
7060
7290
  };
7291
+ const seedCheapAliasTierRow = (modelKey, nameKey, aliasId, displayName) => {
7292
+ if (process.env[modelKey] !== void 0) return;
7293
+ vars[modelKey] = aliasId;
7294
+ if (process.env[nameKey] === void 0) vars[nameKey] = displayName;
7295
+ };
7061
7296
  if (isMaxProfile) {
7062
7297
  const seedMaxRow = (modelKey, nameKey, id) => {
7063
7298
  if (process.env[modelKey] !== void 0) return;
@@ -7071,6 +7306,17 @@ function getClaudeCodeEnvVars(serverUrl, model, launchProfileId = "standard", pi
7071
7306
  vars.ANTHROPIC_CUSTOM_MODEL_OPTION = oneMSuffixForMaxModel(MAX_PROFILE_MODELS.sol);
7072
7307
  if (process.env.ANTHROPIC_CUSTOM_MODEL_OPTION_NAME === void 0) vars.ANTHROPIC_CUSTOM_MODEL_OPTION_NAME = "GPT-5.6 Sol";
7073
7308
  }
7309
+ } else if (isCheapProfile) {
7310
+ seedCheapAliasTierRow("ANTHROPIC_DEFAULT_SONNET_MODEL", "ANTHROPIC_DEFAULT_SONNET_MODEL_NAME", LUNA_SONNET_ALIAS_ID, "GPT-5.6 Luna (xhigh)");
7311
+ seedCheapAliasTierRow("ANTHROPIC_DEFAULT_HAIKU_MODEL", "ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME", LUNA_HAIKU_ALIAS_ID, "GPT-5.6 Luna (high)");
7312
+ const cheapAliasCapabilities = "effort,xhigh_effort,max_effort,thinking,adaptive_thinking,interleaved_thinking";
7313
+ if (process.env.ANTHROPIC_DEFAULT_SONNET_MODEL_SUPPORTED_CAPABILITIES === void 0) vars.ANTHROPIC_DEFAULT_SONNET_MODEL_SUPPORTED_CAPABILITIES = cheapAliasCapabilities;
7314
+ if (process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL_SUPPORTED_CAPABILITIES === void 0) vars.ANTHROPIC_DEFAULT_HAIKU_MODEL_SUPPORTED_CAPABILITIES = cheapAliasCapabilities;
7315
+ if (process.env.ANTHROPIC_CUSTOM_MODEL_OPTION === void 0) {
7316
+ vars.ANTHROPIC_CUSTOM_MODEL_OPTION = LUNA_DRIVER_ALIAS_ID;
7317
+ if (process.env.ANTHROPIC_CUSTOM_MODEL_OPTION_NAME === void 0) vars.ANTHROPIC_CUSTOM_MODEL_OPTION_NAME = "GPT-5.6 Luna (max)";
7318
+ if (process.env.ANTHROPIC_CUSTOM_MODEL_OPTION_SUPPORTED_CAPABILITIES === void 0) vars.ANTHROPIC_CUSTOM_MODEL_OPTION_SUPPORTED_CAPABILITIES = cheapAliasCapabilities;
7319
+ }
7074
7320
  } else if (isFastProfile) {
7075
7321
  seedFastAliasTierRow("ANTHROPIC_DEFAULT_SONNET_MODEL", "ANTHROPIC_DEFAULT_SONNET_MODEL_NAME", LUNA_SONNET_ALIAS_ID, "GPT-5.6 Luna (xhigh)");
7076
7322
  seedFastAliasTierRow("ANTHROPIC_DEFAULT_HAIKU_MODEL", "ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME", LUNA_HAIKU_ALIAS_ID, "GPT-5.6 Luna (high)");
@@ -7177,6 +7423,6 @@ function getCodexEnvVars(serverUrl) {
7177
7423
  return vars;
7178
7424
  }
7179
7425
  //#endregion
7180
- export { validateMaxProfileLaunch as _, sharedServerArgs as a, updateClaude as b, stopKeepAwake as c, enableFileLogging as d, LUNA_SCOUT_ALIAS_ID as f, validateFastProfilePrerequisites as g, resolveLaunchProfile as h, setupAndServe as i, injectModelPickerSettingsFile as l, profileDescriptor as m, getCodexEnvVars as n, LAUNCH_SECRET_HEADER as o, formatFastPrerequisiteFailure as p, parseSharedArgs as r, startKeepAwake as s, getClaudeCodeEnvVars as t, listModelsForEndpoint as u, runSelfUpdate as v, checkClaudeVersion as y };
7426
+ export { checkClaudeVersion as C, runSelfUpdate as S, resolveLaunchProfile as _, sharedServerArgs as a, validateFastProfilePrerequisites as b, stopKeepAwake as c, enableFileLogging as d, LUNA_SCOUT_ALIAS_ID as f, profileDescriptor as g, formatFastPrerequisiteFailure as h, setupAndServe as i, injectModelPickerSettingsFile as l, formatCheapPrerequisiteFailure as m, getCodexEnvVars as n, LAUNCH_SECRET_HEADER as o, formatCheap1mPrerequisiteFailure as p, parseSharedArgs as r, startKeepAwake as s, getClaudeCodeEnvVars as t, listModelsForEndpoint as u, validateCheap1mProfilePrerequisites as v, updateClaude as w, validateMaxProfileLaunch as x, validateCheapProfilePrerequisites as y };
7181
7427
 
7182
- //# sourceMappingURL=server-setup-m5vGQ3k8.js.map
7428
+ //# sourceMappingURL=server-setup-DX27fdcf.js.map