github-router 0.3.282 → 0.3.285

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 (36) hide show
  1. package/dist/{attribution-settings-srL3gBR9.js → attribution-settings-B8M2fvhz.js} +20 -8
  2. package/dist/{attribution-settings-srL3gBR9.js.map → attribution-settings-B8M2fvhz.js.map} +1 -1
  3. package/dist/browser-ext/manifest.json +1 -1
  4. package/dist/{claude-DJQjA5oQ.js → claude-e-JIQGhR.js} +12 -12
  5. package/dist/claude-e-JIQGhR.js.map +1 -0
  6. package/dist/{codex-BAXemWpJ.js → codex-DSnVZ9oK.js} +4 -4
  7. package/dist/{codex-BAXemWpJ.js.map → codex-DSnVZ9oK.js.map} +1 -1
  8. package/dist/engine-DMveEa9x.js +2 -0
  9. package/dist/{gate-discovery-BwtiYKvW.js → gate-discovery-BCFwLm0q.js} +2 -2
  10. package/dist/{gate-discovery-BwtiYKvW.js.map → gate-discovery-BCFwLm0q.js.map} +1 -1
  11. package/dist/{internal-first-mate-guard-CQdXWjNz.js → internal-first-mate-guard-B7p4NttK.js} +1 -1
  12. package/dist/{internal-first-mate-guard-DMgHg2s6.js → internal-first-mate-guard-DOgFVki5.js} +2 -2
  13. package/dist/{internal-first-mate-guard-DMgHg2s6.js.map → internal-first-mate-guard-DOgFVki5.js.map} +1 -1
  14. package/dist/{internal-stop-hook-514RJeHO.js → internal-stop-hook-Cf-7w3RH.js} +2 -2
  15. package/dist/{internal-stop-hook-514RJeHO.js.map → internal-stop-hook-Cf-7w3RH.js.map} +1 -1
  16. package/dist/{internal-worker-guard-VucpClSi.js → internal-worker-guard-CKgYFiFO.js} +2 -2
  17. package/dist/{internal-worker-guard-VucpClSi.js.map → internal-worker-guard-CKgYFiFO.js.map} +1 -1
  18. package/dist/main.js +7 -7
  19. package/dist/{peer-mcp-personas-l4P4s1fK.js → peer-mcp-personas-V6stFvpq.js} +689 -113
  20. package/dist/peer-mcp-personas-V6stFvpq.js.map +1 -0
  21. package/dist/{provision-DlT34zcf.js → provision-BpL6gZIt.js} +2 -2
  22. package/dist/{provision-DlT34zcf.js.map → provision-BpL6gZIt.js.map} +1 -1
  23. package/dist/{serve-orGUartp.js → serve-CXUf7RtJ.js} +11 -11
  24. package/dist/{serve-orGUartp.js.map → serve-CXUf7RtJ.js.map} +1 -1
  25. package/dist/{server-setup-BRBe8gW4.js → server-setup-Bppjt9xO.js} +21 -203
  26. package/dist/server-setup-Bppjt9xO.js.map +1 -0
  27. package/dist/{start-sutbbkwc.js → start-C1-jrHfU.js} +3 -3
  28. package/dist/{start-sutbbkwc.js.map → start-C1-jrHfU.js.map} +1 -1
  29. package/dist/token-CnlB0884.js.map +1 -1
  30. package/dist/{worker-dispatch-4O3IWtP0.js → worker-dispatch-zW8Zi69V.js} +8 -4
  31. package/dist/{worker-dispatch-4O3IWtP0.js.map → worker-dispatch-zW8Zi69V.js.map} +1 -1
  32. package/package.json +1 -1
  33. package/dist/claude-DJQjA5oQ.js.map +0 -1
  34. package/dist/engine-BvLgh1QI.js +0 -2
  35. package/dist/peer-mcp-personas-l4P4s1fK.js.map +0 -1
  36. package/dist/server-setup-BRBe8gW4.js.map +0 -1
@@ -163,6 +163,221 @@ function withOneMSuffix(id) {
163
163
  if (oneMContextDisabled()) return id;
164
164
  return catalogAdvertises1M(id) ? `${id}[1m]` : id;
165
165
  }
166
+ /**
167
+ * Decorate a slug the USER named — a `-m` argument or a launcher default — with
168
+ * `[1m]` iff the model it actually RESOLVES to serves >=1M context.
169
+ *
170
+ * The difference from `withOneMSuffix` is the resolution step, and it exists
171
+ * because the two functions are handed different kinds of string. Every
172
+ * `withOneMSuffix` caller already holds a concrete catalog id from a catalog
173
+ * walk, so an exact-id match is both sufficient and the safer rule: inferring a
174
+ * match there could attach `[1m]` to a sibling that does not serve 1M. A lead
175
+ * slug is the opposite case — it is whatever the user typed, or an
176
+ * Anthropic-published dashed slug like `claude-opus-4-8` that the catalog
177
+ * carries in dotted form. Exact-id matching answers "no 1M" for those purely
178
+ * because it never found the entry, which is the silent under-accounting this
179
+ * function exists to stop.
180
+ *
181
+ * Resolving first also picks up the `-1m` SIBLING shape for free:
182
+ * `resolveModel`'s opus family preference maps `claude-opus-4-7` onto
183
+ * `claude-opus-4.7-1m-internal` when that is what the tier carries, and the
184
+ * sibling's own advertised window then answers the question. That is the same
185
+ * dual-signal conclusion `pickClaudeDefault` reaches for the family shorthand,
186
+ * so the two paths cannot disagree about a family both can be asked about.
187
+ *
188
+ * Idempotent: a slug that already carries the bracket is returned unchanged, so
189
+ * a user who pins `-m claude-opus-5[1m]` by hand does not get `[1m][1m]`. That
190
+ * early return deliberately does NOT re-validate the pin against the catalog.
191
+ * `-m claude-haiku-4-5[1m]` therefore survives even though Haiku 4.5 is a 200K
192
+ * model — the same as before this function existed, and `resolveModel` already
193
+ * warns loudly about exactly that case. Stripping a bracket the user typed
194
+ * would be the surprising behaviour, and it would be the only place in the
195
+ * launcher that overrides an explicit `-m`.
196
+ *
197
+ * A repeat can still arrive from the CLIENT side rather than from here: the
198
+ * `/model` picker rows are seeded already decorated, and Claude Code's alias
199
+ * path appends its own bracket (`getDefaultSonnetModel() + '[1m]'`), so
200
+ * selecting `sonnet[1m]` puts `claude-sonnet-5[1m][1m]` on the wire. That
201
+ * resolves to the same bare id — `resolveModel`'s strip recurses — and Claude
202
+ * Code's own detector is unanchored, so local accounting is right too. Pinned
203
+ * by a regression test in `tests/lib-utils.test.ts`.
204
+ *
205
+ * Degrades the same safe direction as everything else here. An unpopulated
206
+ * catalog makes `resolveModel` a pass-through and `catalogAdvertises1M` false,
207
+ * so the slug stays bare and Claude Code accounts at its conservative 200K
208
+ * default — under-accounting, never overflow.
209
+ */
210
+ function withOneMSuffixForLead(slug) {
211
+ if (oneMContextDisabled()) return slug;
212
+ if (/\[1m\]$/i.test(slug)) return slug;
213
+ return catalogAdvertises1M(resolveModel(slug)) ? `${slug}[1m]` : slug;
214
+ }
215
+ //#endregion
216
+ //#region src/services/copilot/endpoint.ts
217
+ /**
218
+ * Catalog spellings that mean each of our two clients. Copilot is not
219
+ * self-consistent about the `/v1` prefix — the live catalog advertises
220
+ * `/v1/messages` prefixed but `/chat/completions` bare, and this repo's own
221
+ * fixtures carry both forms — so an exact-match on the bare spelling alone
222
+ * silently misses a real shape. `src/lib/model-validation.ts` already
223
+ * normalizes the same way (`ENDPOINT_ALIASES`); this keeps the two agreeing.
224
+ *
225
+ * Matching is EXACT against this set, never a suffix/`includes` test: a
226
+ * `ws:/responses` (websocket transport) entry is NOT the `/responses` HTTP
227
+ * client and must keep resolving to "serves neither".
228
+ */
229
+ const CHAT_ENDPOINTS = /* @__PURE__ */ new Set(["/chat/completions", "/v1/chat/completions"]);
230
+ const RESPONSES_ENDPOINTS = /* @__PURE__ */ new Set(["/responses", "/v1/responses"]);
231
+ /**
232
+ * Decide which endpoint to call for a model from its catalog
233
+ * `supported_endpoints`. Prefers `/chat/completions` when available (the
234
+ * simpler, more widely-supported shape) and falls back to `/responses` for
235
+ * models that ONLY serve the Responses API — the gpt-5.x family except
236
+ * `gpt-5-mini` / `gpt-5.4` (e.g. `gpt-5.4-mini`, `gpt-5.5`, the
237
+ * `*-codex` models). Returns undefined when the model serves neither, so a
238
+ * caller can skip it rather than 400 on `unsupported_api_for_model`.
239
+ *
240
+ * A model that OMITS `supported_endpoints` is treated as chat-eligible: the
241
+ * catalog historically omits the field for chat-default models, and
242
+ * excluding those would be a worse regression than the gap this guards.
243
+ */
244
+ function pickEndpoint(model) {
245
+ const eps = model.supported_endpoints;
246
+ if (!eps || eps.length === 0) return "chat";
247
+ if (eps.some((e) => CHAT_ENDPOINTS.has(e))) return "chat";
248
+ if (eps.some((e) => RESPONSES_ENDPOINTS.has(e))) return "responses";
249
+ }
250
+ /**
251
+ * `pickEndpoint` by model id against the live catalog, WITHOUT collapsing
252
+ * "absent from the catalog" into "serves neither of our endpoints".
253
+ *
254
+ * This function deliberately has no default. The predecessor
255
+ * (`endpointForModelId`) returned `pickEndpoint(found) ?? "chat"`, which
256
+ * coerced both cases to "chat" — defensible for an unknown id, silently wrong
257
+ * for a catalog model serving only, say, `/v1/messages`: the caller would drive
258
+ * it through the chat client and get an opaque upstream 400 with no local
259
+ * signal about the real cause. `src/lib/browser-mcp/compressor.ts` already
260
+ * treats that case correctly (`if (!endpoint) continue`); this makes the same
261
+ * distinction available to callers that resolve by id.
262
+ *
263
+ * Callers that legitimately want the chat default for an unknown id can still
264
+ * have it — they just have to write it, per case, on purpose.
265
+ */
266
+ function resolveEndpointForModelId(id) {
267
+ const found = state.models?.data?.find((m) => m.id === id);
268
+ if (!found) return { kind: "unknown-model" };
269
+ const endpoint = pickEndpoint(found);
270
+ if (endpoint) return {
271
+ kind: "endpoint",
272
+ endpoint
273
+ };
274
+ return {
275
+ kind: "unreachable",
276
+ endpoints: found.supported_endpoints ?? []
277
+ };
278
+ }
279
+ //#endregion
280
+ //#region src/lib/anthropic-translate/classifier.ts
281
+ /**
282
+ * Routing classifier for `POST /v1/messages`.
283
+ *
284
+ * Claude Code speaks the Anthropic Messages wire format. Copilot only serves
285
+ * Claude models on its native `/v1/messages` endpoint; a gpt/gemini request
286
+ * sent there 400s. This classifier decides, from the RESOLVED model id and its
287
+ * catalog metadata, whether a request stays on the native passthrough
288
+ * (`createMessages`) or is diverted to the translation shim.
289
+ *
290
+ * Non-regression is the whole point — every Claude model (opus / sonnet / haiku /
291
+ * any `claude-*` or Anthropic-vendored id) MUST return "claude-passthrough" so
292
+ * its bytes reach `createMessages` unchanged — even if future catalog metadata
293
+ * were to (wrongly) advertise a `/responses` endpoint for it. The Claude check
294
+ * is therefore keyed off identity (id / vendor / family), NOT the endpoint.
295
+ *
296
+ * Non-Claude models are diverted to the translation shim by the endpoint the
297
+ * catalog says they serve: `/responses` models (gpt-5.5, gpt-5.3-codex, …) take
298
+ * the Responses path (`responses-shim`), `/chat/completions` models (gemini,
299
+ * and any chat-default model) take the chat path (`chat-shim`). The decision is
300
+ * derived from `pickEndpoint` (catalog `supported_endpoints`), never a
301
+ * hardcoded slug list, so it generalizes. Copilot only serves Claude models on
302
+ * its native `/v1/messages`, so diverting every non-Claude model to a shim is
303
+ * correct — a non-Claude request sent to `/v1/messages` would 400.
304
+ */
305
+ /**
306
+ * Match "claude"/"anthropic" as a delimiter-bounded segment anywhere in a model
307
+ * id: at the start, or after a `/ _ . : -` path/version delimiter, and followed
308
+ * by end-of-string, another such delimiter, or a digit. This catches catalog
309
+ * aliases like `github/claude-3-7-sonnet` or `anthropic/…` whose vendor is
310
+ * "github" and whose family is empty — where the token only surfaces mid-id —
311
+ * while NOT firing on incidental substrings like `notclaude`. Deliberately
312
+ * over-inclusive at the boundaries: we fail CLOSED toward Claude (→ passthrough)
313
+ * so a real Claude model can never be diverted to the non-Claude shim.
314
+ */
315
+ const CLAUDE_ID_RE = /(^|[/_.:-])(claude|anthropic)(?=$|[/_.:-]|\d)/i;
316
+ /**
317
+ * True when the target is a Claude / Anthropic model. Matches on any of:
318
+ * catalog vendor containing "anthropic", capability family containing "claude",
319
+ * or a Claude/anthropic path-segment (see `CLAUDE_ID_RE`) in ANY id we hold —
320
+ * the resolved id (`modelId`), the pre-resolution request id (`originalModelId`),
321
+ * or the catalog entry's own id (`model.id`). Conservative by design: when in
322
+ * doubt it returns true so a Claude request can never be diverted to the shim.
323
+ */
324
+ function isClaudeModel(modelId, model, originalModelId) {
325
+ if (model) {
326
+ if ((model.vendor?.toLowerCase() ?? "").includes("anthropic")) return true;
327
+ if ((model.capabilities?.family?.toLowerCase() ?? "").includes("claude")) return true;
328
+ }
329
+ return [
330
+ modelId,
331
+ originalModelId,
332
+ model?.id
333
+ ].some((id) => typeof id === "string" && CLAUDE_ID_RE.test(id));
334
+ }
335
+ /**
336
+ * Decide the route for a resolved model id + its catalog entry.
337
+ *
338
+ * - No model id, or a Claude model → "claude-passthrough" (existing behaviour).
339
+ * - A non-Claude model whose catalog endpoint is `/responses` → "responses-shim".
340
+ * - A non-Claude model whose catalog endpoint is `/chat/completions` (gemini and
341
+ * any chat-default model) → "chat-shim".
342
+ * - A non-Claude model absent from the catalog (so we can't confirm an endpoint)
343
+ * → "claude-passthrough" (unchanged; we don't divert what we can't classify).
344
+ * - A non-Claude model that IS in the catalog and serves NEITHER of the two shim
345
+ * endpoints (`pickEndpoint` → undefined) → "claude-passthrough" as well.
346
+ *
347
+ * Those last two land on the same route but are NOT the same answer, and the
348
+ * coincidence is deliberate rather than a collapsed default (contrast
349
+ * `resolveEndpointForModelId`, whose callers must tell them apart because
350
+ * guessing there produces an opaque upstream 400). Here neither shim is even a
351
+ * candidate: a shim can only speak `/responses` or `/chat/completions`, so
352
+ * diverting a model that serves neither would 400 just as surely. Passthrough
353
+ * is the better default because it is sometimes RIGHT — a non-Claude catalog
354
+ * model advertising `/v1/messages` is served by exactly the endpoint
355
+ * passthrough uses. It also preserves this module's fail-CLOSED-toward-Claude
356
+ * invariant: an unclassifiable model is never diverted.
357
+ *
358
+ * KNOWN GAP (audited, deliberately not fixed here): a non-Claude model serving
359
+ * only something we cannot speak at all (say `/embeddings`) also lands on
360
+ * passthrough and will still 400 upstream — `logEndpointMismatch(modelId,
361
+ * "/v1/messages")` logs it at the passthrough seam, but no local error is
362
+ * raised. Closing that needs a change in `src/routes/messages/handler.ts`,
363
+ * which this seam does not own. It is strictly narrower than the defect fixed
364
+ * in `resolveEndpointForModelId`: no such model is reachable as a Claude Code
365
+ * `/v1/messages` target today, whereas the plan worker's `claude-opus-5`
366
+ * default is.
367
+ *
368
+ * `originalModelId` is the optional pre-resolution request id; when supplied it
369
+ * is checked for Claude-likeness alongside the resolved id so an alias that
370
+ * resolves to a non-Claude-looking id can't slip past.
371
+ */
372
+ function classifyMessagesRoute(modelId, model, originalModelId) {
373
+ if (!modelId) return "claude-passthrough";
374
+ if (isClaudeModel(modelId, model, originalModelId)) return "claude-passthrough";
375
+ if (!model) return "claude-passthrough";
376
+ const endpoint = pickEndpoint(model);
377
+ if (endpoint === "responses") return "responses-shim";
378
+ if (endpoint === "chat") return "chat-shim";
379
+ return "claude-passthrough";
380
+ }
166
381
  //#endregion
167
382
  //#region src/lib/port.ts
168
383
  const DEFAULT_PORT = 8787;
@@ -209,11 +424,22 @@ const DEFAULT_CLAUDE_MODEL_FALLBACKS = [
209
424
  * This helper detects the catalog state at launch and only opts in
210
425
  * when the backend can actually serve 1M.
211
426
  *
212
- * Sonnet/Haiku families are intentionally NOT given `[1m]` defaults
213
- * because Copilot has no 1M backend for them (and Anthropic-side
214
- * `modelSupports1M` doesn't list haiku at all). See
215
- * `src/lib/server-setup.ts:getClaudeCodeEnvVars` for the
216
- * `ANTHROPIC_DEFAULT_{SONNET,HAIKU,OPUS}_MODEL` tier defaults.
427
+ * This helper answers the question only for the OPUS families, because a
428
+ * family is what it is asked about (`-m 4.7` names no slug). Every other lead
429
+ * slug `-m fast`, a full slug a power user pins, the implicit budget lead —
430
+ * goes through `withOneMSuffixForLead` (`./one-m-context`) instead, which
431
+ * resolves the slug first and then reads the resolved entry's advertised
432
+ * window. The two agree wherever both can be asked: a family that resolves to a
433
+ * 1M backend is 1M by either route.
434
+ *
435
+ * A previous revision of this comment claimed Sonnet and Haiku were left bare
436
+ * because "Copilot has no 1M backend for them". That was true when it was
437
+ * written and is now false for Sonnet: the live catalog advertises
438
+ * `max_context_window_tokens: 1_000_000` on both `claude-sonnet-5` and
439
+ * `claude-sonnet-4.6` (Haiku 4.5 really is 200K, and is left bare by the same
440
+ * catalog check rather than by a hardcoded family rule). Nothing here is
441
+ * family-gated any more — the catalog decides per model, so the next family
442
+ * that ships 1M is picked up without an edit.
217
443
  *
218
444
  * Must be called AFTER `cacheModels()` has populated `state.models`.
219
445
  * Returns the bare slug if the catalog isn't populated (resolveModel
@@ -221,6 +447,88 @@ const DEFAULT_CLAUDE_MODEL_FALLBACKS = [
221
447
  * variant" — defaulting safe-side preserves the pre-change behavior).
222
448
  */
223
449
  const DEFAULT_OPUS_FAMILY = "5";
450
+ /** The lead `-m fast` selects. Anthropic-published dashed slug that is also the
451
+ * Copilot catalog id verbatim, so `resolveModel` matches it exactly. */
452
+ const BUDGET_LEAD_MODEL = "claude-sonnet-5";
453
+ /** Small/fast tier for a budget lead, in the two forms this codebase needs.
454
+ *
455
+ * `SLUG` is the Anthropic-published DASHED form and is what goes into
456
+ * `ANTHROPIC_SMALL_FAST_MODEL` / `ANTHROPIC_DEFAULT_HAIKU_MODEL`: Claude Code's
457
+ * `/model` registry is keyed on Anthropic slugs, and seeding Copilot's dotted
458
+ * id there reproduces the documented `claude-opus-5` failure where the picker
459
+ * silently falls back to an older model. `CATALOG_ID` is Copilot's DOTTED id
460
+ * and is what the presence probe must test, because that is the id the catalog
461
+ * actually carries. `resolveModel` bridges the two at request time. */
462
+ const BUDGET_SMALL_FAST_SLUG = "claude-haiku-4-5";
463
+ const BUDGET_SMALL_FAST_CATALOG_ID = "claude-haiku-4.5";
464
+ /**
465
+ * Resolve the `-m` argument to the lead slug to launch with.
466
+ *
467
+ * - `fast` → `BUDGET_LEAD_MODEL` (budget mode)
468
+ * - `N.M` → the best variant of that Opus family, via `pickClaudeDefault`
469
+ * - a full slug → unchanged, including Copilot slugs a power user pins
470
+ * - absent → the ordinary default
471
+ *
472
+ * Every branch is `[1m]`-decorated against the live catalog, by
473
+ * `pickClaudeDefault` on the two Opus-family branches and by
474
+ * `withOneMSuffixForLead` on the other two. Pinning a MODEL is not a request to
475
+ * give up four fifths of its context window, which is what leaving the other
476
+ * two bare amounted to: `claude-sonnet-5` advertises a 1M window, so `-m fast`
477
+ * and `-m claude-sonnet-5` were both budgeted locally at Claude Code's 200K
478
+ * default and auto-compacted at roughly a fifth of the real window. The
479
+ * decoration is catalog-gated per model, so a genuinely 200K model
480
+ * (`claude-haiku-4.5`) still comes back bare.
481
+ *
482
+ * `fast` resolves to an ordinary slug rather than setting a mode flag, because
483
+ * budget mode is keyed off the RESOLVED lead everywhere it matters (the advisor
484
+ * escalation, the delegation prose, the small/fast tier). `-m fast` and
485
+ * `-m claude-sonnet-5` must therefore produce identical sessions, which a flag
486
+ * only one of the two set would break. The shared decoration is part of that
487
+ * identity: decorating one branch and not the other would reintroduce the
488
+ * divergence through the context budget instead of through a flag.
489
+ *
490
+ * Callers must keep treating any explicit `-m` as explicit: the
491
+ * `DEFAULT_CLAUDE_MODEL_FALLBACKS` walk applies to the implicit-default path
492
+ * only, so it cannot override a requested family (or `fast`) with an older Opus.
493
+ */
494
+ function resolveLeadSlugArg(modelArg) {
495
+ const arg = modelArg?.trim();
496
+ if (!arg) return pickClaudeDefault();
497
+ if (arg.toLowerCase() === "fast") return withOneMSuffixForLead(BUDGET_LEAD_MODEL);
498
+ const opusFamilyShorthand = arg.match(/^(\d+\.\d+)$/)?.[1];
499
+ if (opusFamilyShorthand) return pickClaudeDefault(opusFamilyShorthand);
500
+ return withOneMSuffixForLead(arg);
501
+ }
502
+ /**
503
+ * True when `slug` names a Claude model that is NOT an Opus tier — the
504
+ * "budget lead" condition.
505
+ *
506
+ * Selecting sonnet or haiku as the lead is a decision to spend less while
507
+ * holding quality as far as possible, and three surfaces key off it: the
508
+ * advisor escalates to the Anthropic frontier (`resolveAdvisorModel`), the
509
+ * injected delegation prose puts the cheap agent tiers first
510
+ * (`buildNativeReachClauses`), and the small/fast tier drops to Haiku
511
+ * (`getClaudeCodeEnvVars`). One definition here so those three cannot disagree
512
+ * about what counts as a budget lead.
513
+ *
514
+ * Resolves before the family test so the Anthropic dashed form, Copilot's
515
+ * dotted form, and `pickClaudeDefault`'s literal `[1m]` suffix all classify
516
+ * alike. A non-Claude lead is not a budget lead: the concept is about picking a
517
+ * lighter tier WITHIN the Claude family, and the gpt/gemini shim models have
518
+ * their own cost profile that this switch says nothing about.
519
+ *
520
+ * CONTRACT: `slug` is an already-resolved LEAD SLUG, never a raw `-m` argument.
521
+ * `"fast"` and the `N.M` shorthand are not Claude slugs and would classify
522
+ * false here; run them through `resolveLeadSlugArg` first, which is what every
523
+ * caller does. Resolving internally instead would drag `pickClaudeDefault`'s
524
+ * catalog dependency into a pure predicate and make the same input answer
525
+ * differently before and after the catalog loads.
526
+ */
527
+ function isBudgetClaudeLead(slug) {
528
+ if (!slug) return false;
529
+ if (!isClaudeModel(slug)) return false;
530
+ return !/opus/i.test(resolveModel(slug));
531
+ }
224
532
  function pickClaudeDefault(opusFamily = DEFAULT_OPUS_FAMILY) {
225
533
  const dotted = opusFamily.replace(/-/g, ".");
226
534
  const bareSlug = `claude-opus-${dotted.replace(/\./g, "-")}`;
@@ -19985,70 +20293,6 @@ function detectAgentCall(input) {
19985
20293
  });
19986
20294
  }
19987
20295
  //#endregion
19988
- //#region src/services/copilot/endpoint.ts
19989
- /**
19990
- * Catalog spellings that mean each of our two clients. Copilot is not
19991
- * self-consistent about the `/v1` prefix — the live catalog advertises
19992
- * `/v1/messages` prefixed but `/chat/completions` bare, and this repo's own
19993
- * fixtures carry both forms — so an exact-match on the bare spelling alone
19994
- * silently misses a real shape. `src/lib/model-validation.ts` already
19995
- * normalizes the same way (`ENDPOINT_ALIASES`); this keeps the two agreeing.
19996
- *
19997
- * Matching is EXACT against this set, never a suffix/`includes` test: a
19998
- * `ws:/responses` (websocket transport) entry is NOT the `/responses` HTTP
19999
- * client and must keep resolving to "serves neither".
20000
- */
20001
- const CHAT_ENDPOINTS = /* @__PURE__ */ new Set(["/chat/completions", "/v1/chat/completions"]);
20002
- const RESPONSES_ENDPOINTS = /* @__PURE__ */ new Set(["/responses", "/v1/responses"]);
20003
- /**
20004
- * Decide which endpoint to call for a model from its catalog
20005
- * `supported_endpoints`. Prefers `/chat/completions` when available (the
20006
- * simpler, more widely-supported shape) and falls back to `/responses` for
20007
- * models that ONLY serve the Responses API — the gpt-5.x family except
20008
- * `gpt-5-mini` / `gpt-5.4` (e.g. `gpt-5.4-mini`, `gpt-5.5`, the
20009
- * `*-codex` models). Returns undefined when the model serves neither, so a
20010
- * caller can skip it rather than 400 on `unsupported_api_for_model`.
20011
- *
20012
- * A model that OMITS `supported_endpoints` is treated as chat-eligible: the
20013
- * catalog historically omits the field for chat-default models, and
20014
- * excluding those would be a worse regression than the gap this guards.
20015
- */
20016
- function pickEndpoint(model) {
20017
- const eps = model.supported_endpoints;
20018
- if (!eps || eps.length === 0) return "chat";
20019
- if (eps.some((e) => CHAT_ENDPOINTS.has(e))) return "chat";
20020
- if (eps.some((e) => RESPONSES_ENDPOINTS.has(e))) return "responses";
20021
- }
20022
- /**
20023
- * `pickEndpoint` by model id against the live catalog, WITHOUT collapsing
20024
- * "absent from the catalog" into "serves neither of our endpoints".
20025
- *
20026
- * This function deliberately has no default. The predecessor
20027
- * (`endpointForModelId`) returned `pickEndpoint(found) ?? "chat"`, which
20028
- * coerced both cases to "chat" — defensible for an unknown id, silently wrong
20029
- * for a catalog model serving only, say, `/v1/messages`: the caller would drive
20030
- * it through the chat client and get an opaque upstream 400 with no local
20031
- * signal about the real cause. `src/lib/browser-mcp/compressor.ts` already
20032
- * treats that case correctly (`if (!endpoint) continue`); this makes the same
20033
- * distinction available to callers that resolve by id.
20034
- *
20035
- * Callers that legitimately want the chat default for an unknown id can still
20036
- * have it — they just have to write it, per case, on purpose.
20037
- */
20038
- function resolveEndpointForModelId(id) {
20039
- const found = state.models?.data?.find((m) => m.id === id);
20040
- if (!found) return { kind: "unknown-model" };
20041
- const endpoint = pickEndpoint(found);
20042
- if (endpoint) return {
20043
- kind: "endpoint",
20044
- endpoint
20045
- };
20046
- return {
20047
- kind: "unreachable",
20048
- endpoints: found.supported_endpoints ?? []
20049
- };
20050
- }
20051
- //#endregion
20052
20296
  //#region src/lib/browser-mcp/compressor.ts
20053
20297
  /**
20054
20298
  * Static fallback chain for the inner compressor. Order is preference:
@@ -27627,8 +27871,26 @@ function logTelemetry(t) {
27627
27871
  function toolAcceptsWorkspace(tool) {
27628
27872
  return tool.capability === "worker" || tool.toolNameHttp === "code" || tool.toolNameHttp === "run_workflow";
27629
27873
  }
27874
+ /**
27875
+ * Fold the per-session `X-GH-Workspace` header into `args.workspace` when the
27876
+ * caller left it empty, and REPORT which of the two the tool ended up with.
27877
+ *
27878
+ * The return value is the load-bearing part. This function mutates `args`, so
27879
+ * once it has run a header-derived workspace is byte-indistinguishable from one
27880
+ * the caller chose — and those two cases warrant very different treatment. A
27881
+ * caller that named a directory has told us where it is; a header is a
27882
+ * connection-level default that may be stale (it is computed by a helper Claude
27883
+ * Code runs, and the calling agent may since have moved into a git worktree).
27884
+ * `runWorkerToolCall` uses the distinction to decide what to tell the caller
27885
+ * about the tree it actually ran in, so the provenance must survive the merge.
27886
+ */
27630
27887
  function applySessionWorkspace(args, sessionWorkspace, tool) {
27631
- if ((!tool || toolAcceptsWorkspace(tool)) && typeof sessionWorkspace === "string" && sessionWorkspace.length > 0 && path.isAbsolute(sessionWorkspace) && (args.workspace === void 0 || args.workspace === "")) args.workspace = sessionWorkspace;
27888
+ if (args.workspace !== void 0 && args.workspace !== "") return "argument";
27889
+ if ((!tool || toolAcceptsWorkspace(tool)) && typeof sessionWorkspace === "string" && sessionWorkspace.length > 0 && path.isAbsolute(sessionWorkspace)) {
27890
+ args.workspace = sessionWorkspace;
27891
+ return "session";
27892
+ }
27893
+ return "absent";
27632
27894
  }
27633
27895
  async function handleToolsCall(body, scope, sessionWorkspace) {
27634
27896
  const params = body.params ?? {};
@@ -27662,7 +27924,8 @@ async function handleToolsCall(body, scope, sessionWorkspace) {
27662
27924
  personaContext = typeof args.context === "string" ? args.context : void 0;
27663
27925
  if (args.imagePaths !== void 0) {
27664
27926
  if (!Array.isArray(args.imagePaths) || args.imagePaths.some((v) => typeof v !== "string")) return rpcError(body.id, RPC_INVALID_PARAMS, "tools/call: arguments.imagePaths must be an array of strings");
27665
- const loaded = await loadPeerImages(args.imagePaths, process.cwd());
27927
+ const imageRoot = typeof sessionWorkspace === "string" && sessionWorkspace.length > 0 && path.isAbsolute(sessionWorkspace) ? sessionWorkspace : process.cwd();
27928
+ const loaded = await loadPeerImages(args.imagePaths, imageRoot);
27666
27929
  if (!loaded.ok) return rpcError(body.id, RPC_INVALID_PARAMS, `tools/call: ${loaded.error}`);
27667
27930
  personaImages = loaded.images;
27668
27931
  }
@@ -27700,8 +27963,8 @@ async function handleToolsCall(body, scope, sessionWorkspace) {
27700
27963
  const telemetryName = persona ? persona.agentName : nonPersonaTool.toolNameHttp;
27701
27964
  const telemetryModel = persona ? persona.model : "(non-persona)";
27702
27965
  try {
27703
- if (nonPersonaTool) applySessionWorkspace(args, sessionWorkspace, nonPersonaTool);
27704
- const result = persona ? await callPersona(persona, personaPrompt, personaContext, personaEffort, aborter?.signal, personaImages) : await nonPersonaTool.handler(args, aborter?.signal);
27966
+ const workspaceSource = nonPersonaTool ? applySessionWorkspace(args, sessionWorkspace, nonPersonaTool) : "absent";
27967
+ const result = persona ? await callPersona(persona, personaPrompt, personaContext, personaEffort, aborter?.signal, personaImages) : await nonPersonaTool.handler(args, aborter?.signal, { workspaceSource });
27705
27968
  logTelemetry({
27706
27969
  name: telemetryName,
27707
27970
  model: telemetryModel,
@@ -28027,6 +28290,98 @@ function handleMcpDelete(c) {
28027
28290
  return c.body(null, 200);
28028
28291
  }
28029
28292
  //#endregion
28293
+ //#region src/lib/reasoning-effort.ts
28294
+ /**
28295
+ * Reasoning-effort bucketing + clamping shared by the `/v1/messages` handler
28296
+ * (adaptive-thinking translation) and the Anthropic-translation shim
28297
+ * (thinking-budget → Responses `reasoning.effort`).
28298
+ *
28299
+ * Extracted from `src/routes/messages/handler.ts` so a `~/lib/*` module can
28300
+ * depend on it without importing route code (and without forming a
28301
+ * handler → shim → handler import cycle). `handler.ts` re-exports these for
28302
+ * backward compatibility with existing imports/tests.
28303
+ */
28304
+ /**
28305
+ * Copilot's reasoning-effort tiers, lowest to highest.
28306
+ *
28307
+ * Both ends were added after the fact and both are load-bearing:
28308
+ *
28309
+ * `none` is advertised by every gpt-5.x entry in the live catalog. While it was
28310
+ * missing here it was treated as an UNRECOGNIZED value, so a client asking for
28311
+ * the MINIMUM on a model that does not offer it (gemini advertises only
28312
+ * low/medium/high) was anchored at the unknown-value tier and clamped to
28313
+ * `high` — the maximum. Listing it makes the clamp resolve to `low` instead,
28314
+ * which is what "nearest supported tier" should always have meant.
28315
+ *
28316
+ * `max` is advertised by `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`,
28317
+ * `claude-opus-5` and the 4.7/4.8 Opus lines, and Claude Code's effort picker
28318
+ * offers it for any model whose entry allows it. Listing it lets an explicit
28319
+ * selection pass through, and lets `clampEffort` land on it for a model that
28320
+ * advertises nothing lower.
28321
+ *
28322
+ * `bucketEffort` deliberately reaches neither end — see below.
28323
+ */
28324
+ const EFFORT_ORDER = [
28325
+ "none",
28326
+ "low",
28327
+ "medium",
28328
+ "high",
28329
+ "xhigh",
28330
+ "max"
28331
+ ];
28332
+ /** Anchor for an effort value that is not a recognized tier at all.
28333
+ *
28334
+ * Deliberately NOT the top of `EFFORT_ORDER`. Callers reach this only when the
28335
+ * incoming value is unrecognized, which is a guess — and resolving a guess to
28336
+ * the most expensive tier a model advertises would silently spend more than the
28337
+ * caller could have meant. Anchoring here and clamping DOWN keeps the behavior
28338
+ * identical to before `max` joined the ladder, while `max` stays reachable by
28339
+ * explicit, valid selection. */
28340
+ const UNKNOWN_EFFORT_ANCHOR = "xhigh";
28341
+ /**
28342
+ * Bucket a thinking budget into a Copilot reasoning-effort string.
28343
+ * `<2000`→low, `<8000`→medium, `<24000`→high, else→xhigh.
28344
+ * Defaults missing/non-numeric budgets to 8000 ("high").
28345
+ *
28346
+ * The ceiling stays at `xhigh` even though `max` exists: Anthropic's
28347
+ * `budget_tokens` is unbounded above, so any threshold chosen for a `max`
28348
+ * bucket would silently re-tier existing callers whose budgets already map to
28349
+ * `xhigh`. `max` is reachable only by explicit selection
28350
+ * (`output_config.effort`), which is an unambiguous request rather than an
28351
+ * inference from a token count.
28352
+ */
28353
+ function bucketEffort(budget) {
28354
+ const n = typeof budget === "number" && Number.isFinite(budget) ? budget : 8e3;
28355
+ if (n < 2e3) return "low";
28356
+ if (n < 8e3) return "medium";
28357
+ if (n < 24e3) return "high";
28358
+ return "xhigh";
28359
+ }
28360
+ /**
28361
+ * Clamp a bucketed effort to the closest value in `supported`. Ties resolve to
28362
+ * the lower-tier option (per EFFORT_ORDER).
28363
+ *
28364
+ * Iterates EFFORT_ORDER (canonical low→xhigh) so the first match on a given
28365
+ * distance is always the lower-tier value, regardless of input order in
28366
+ * `supported`.
28367
+ */
28368
+ function clampEffort(bucketed, supported) {
28369
+ if (supported.includes(bucketed)) return bucketed;
28370
+ const targetIdx = EFFORT_ORDER.indexOf(bucketed);
28371
+ let best;
28372
+ let bestDist = Infinity;
28373
+ for (let i = 0; i < EFFORT_ORDER.length; i++) {
28374
+ const value = EFFORT_ORDER[i];
28375
+ if (!supported.includes(value)) continue;
28376
+ const dist = Math.abs(i - targetIdx);
28377
+ if (dist < bestDist) {
28378
+ bestDist = dist;
28379
+ best = value;
28380
+ }
28381
+ }
28382
+ return best ?? bucketed;
28383
+ }
28384
+ //#endregion
28030
28385
  //#region src/lib/stream-relay.ts
28031
28386
  const ENCODER$1 = new TextEncoder();
28032
28387
  /**
@@ -28514,10 +28869,15 @@ function rememberThinkingHistoryRepair(fingerprint) {
28514
28869
  * re-call Copilot for the next turn — stream onto the SAME
28515
28870
  * SSE connection (no new message_start; the original one is
28516
28871
  * still open). Loop up to ADVISOR_MAX_TURNS times.
28517
- * 4. Cross-lab default: route the advisor call to a different model
28518
- * family than the main loop (gpt-5.6-sol by default) so the user gets
28519
- * a true "second set of eyes" instead of Opus reviewing Opus
28520
- * (gemini-critic finding).
28872
+ * 4. Lead-aware model choice: route the advisor call to a different model
28873
+ * family than the main loop (gpt-5.6-sol) so the user gets a true "second
28874
+ * set of eyes" instead of Opus reviewing Opus (gemini-critic finding). When
28875
+ * the LEAD is a lighter Claude tier the choice inverts and the advisor
28876
+ * escalates to `ADVISOR_ESCALATION_MODEL` instead — see that constant for
28877
+ * why trading the cross-lab property is the right call on that path.
28878
+ * 5. Effort follows the Claude Code effort picker (`resolveAdvisorEffort`)
28879
+ * rather than a hardcoded constant, floored so a low picker cannot render
28880
+ * the consultation useless.
28521
28881
  *
28522
28882
  * The translate-loop is bounded to a single user request — no
28523
28883
  * persistent state across requests is needed (unlike Phase G's
@@ -28541,6 +28901,185 @@ const ADVISOR_CLIENT_TOOL_NAME = "advisor";
28541
28901
  * model — Opus 4.6/Sonnet 4.6 typically). */
28542
28902
  const ADVISOR_DEFAULT_MODEL = "gpt-5.6-sol";
28543
28903
  const ADVISOR_DEFAULT_EFFORT = "xhigh";
28904
+ /** The Anthropic frontier model the advisor escalates to when the LEAD is a
28905
+ * lighter Claude tier (sonnet, haiku).
28906
+ *
28907
+ * Selecting a lighter lead is a decision to work on a budget while holding
28908
+ * quality: the lead does the legwork and escalates for direction. Without this,
28909
+ * a budget lead has no transcript-aware path to the strongest Anthropic
28910
+ * reasoner at all — `opus_critic` is stateless and sees one artifact, and the
28911
+ * `plan` worker is read-only and never sees the transcript.
28912
+ *
28913
+ * This deliberately trades the advisor's cross-lab property on that path. The
28914
+ * advisor is not this repo's review instrument: it catches drift and momentum
28915
+ * and inherits the lead's framing by design, while the fresh-context critics
28916
+ * (`codex_critic`, `gemini_critic`, `codex_reviewer`, `gemini_reviewer`) are
28917
+ * the decorrelation instrument and are untouched. `GH_ROUTER_ADVISOR_MODEL`
28918
+ * keeps a cross-lab advisor one env var away for anyone who wants it back. */
28919
+ const ADVISOR_ESCALATION_MODEL = "claude-opus-5";
28920
+ /** Floor for the advisor's reasoning effort.
28921
+ *
28922
+ * The advisor follows the Claude Code effort picker (see `resolveAdvisorEffort`)
28923
+ * so dialing the picker down makes it cheaper, but it does NOT follow it all the
28924
+ * way to the bottom of `EFFORT_ORDER`. The advisor fires a handful of times per
28925
+ * session (`ADVISOR_MAX_TURNS`, typically 1-3), so it is not the budget line —
28926
+ * the lead's own turns are — while an advisor reasoning at `none`/`low` cannot
28927
+ * do the job the consultation exists for. The picker therefore governs the
28928
+ * `high..max` range. */
28929
+ const ADVISOR_MIN_EFFORT = "high";
28930
+ /** Output cap for the Anthropic-branch advisor call when the catalog carries no
28931
+ * limits for the resolved model. The value the branch used unconditionally
28932
+ * before it became reachable, kept so a catalog-less path is no worse off. */
28933
+ const ADVISOR_FALLBACK_MAX_OUTPUT_TOKENS = 4096;
28934
+ /** Catalog spellings that mean the Responses API. Copilot is inconsistent about
28935
+ * the `/v1` prefix, so both are matched — mirroring `CHAT_ENDPOINTS` /
28936
+ * `RESPONSES_ENDPOINTS` in `src/services/copilot/endpoint.ts`. */
28937
+ const ADVISOR_RESPONSES_ENDPOINTS = /* @__PURE__ */ new Set(["/responses", "/v1/responses"]);
28938
+ /**
28939
+ * Which transport the advisor dispatches on: `/responses` (with
28940
+ * `reasoning.effort`) or `/v1/messages`.
28941
+ *
28942
+ * Catalog-first, name-regex second, and BOTH tests run against the bare id as
28943
+ * well as the given one. `pickEndpoint` is deliberately not reused: it answers
28944
+ * "chat or responses" for the two tool-calling clients and would send
28945
+ * `claude-opus-5` — which advertises `/v1/messages` AND `/chat/completions` — to
28946
+ * chat. The advisor's question is narrower: does this model serve `/responses`?
28947
+ *
28948
+ * The bare-id fallback is what makes `GH_ROUTER_ADVISOR_MODEL` safe. That pin is
28949
+ * accepted verbatim, so an operator can write a vendor-namespaced value like
28950
+ * `openai/gpt-5.6-sol`. Such an id is in no catalog and fails the start-anchored
28951
+ * name regex, so a catalog-only fix still posted it to `/v1/messages` and 400'd
28952
+ * — exported and directly tested for that exact input, because an earlier
28953
+ * version of this function claimed to handle it and did not.
28954
+ */
28955
+ function advisorUsesResponses(resolvedAdvisorModel) {
28956
+ const bare = resolvedAdvisorModel.slice(resolvedAdvisorModel.lastIndexOf("/") + 1);
28957
+ const endpoints = (state.models?.data?.find((m) => m.id === resolvedAdvisorModel || m.id === bare))?.supported_endpoints;
28958
+ if (endpoints && endpoints.length > 0) return endpoints.some((e) => ADVISOR_RESPONSES_ENDPOINTS.has(e));
28959
+ return /^(gpt-|o\d|.*codex)/i.test(bare);
28960
+ }
28961
+ /** True when the model advertises a usable reasoning-effort ladder. */
28962
+ function advertisedEffortLadder(resolvedAdvisorModel) {
28963
+ const supported = state.models?.data?.find((m) => m.id === resolvedAdvisorModel)?.capabilities?.supports?.reasoning_effort;
28964
+ return Array.isArray(supported) && supported.length > 0 ? supported : void 0;
28965
+ }
28966
+ /** True when the advisor should escalate to `ADVISOR_ESCALATION_MODEL` for this
28967
+ * lead: a Claude lead that is NOT already an Opus tier, on a catalog that
28968
+ * actually carries the escalation model.
28969
+ *
28970
+ * The catalog probe mirrors `standInToolEnabled`'s: never name a model the
28971
+ * account cannot reach. A non-Claude lead never gets here in practice (the
28972
+ * advisor tool is stripped for those before the request reaches this module),
28973
+ * but the check is explicit rather than assumed.
28974
+ *
28975
+ * The probe compares the BARE constant rather than `resolveModel`-ing it first,
28976
+ * which is deliberate and not an oversight: `claude-opus-5` is a single-segment
28977
+ * slug whose dashed and dotted spellings are identical, so resolution is a
28978
+ * no-op, and `resolveModel` WARNS on an id it cannot find — routing this probe
28979
+ * through it would emit that warning on every advisor request for anyone whose
28980
+ * catalog lacks opus-5, which is exactly the tier this returns false for.
28981
+ * `standInToolEnabled` compares the same id the same way. */
28982
+ function shouldEscalateAdvisor(leadModel) {
28983
+ if (!isBudgetClaudeLead(leadModel)) return false;
28984
+ return state.models?.data?.some((m) => m.id === "claude-opus-5") ?? false;
28985
+ }
28986
+ /**
28987
+ * Pick the advisor model for one request from the LEAD model that request is
28988
+ * running on.
28989
+ *
28990
+ * Resolved per request rather than at launch because the lead changes
28991
+ * mid-session via the `/model` picker; launch-time env plumbing would pin the
28992
+ * advisor to whatever was selected at spawn.
28993
+ *
28994
+ * Precedence:
28995
+ * 1. `GH_ROUTER_ADVISOR_MODEL` (trimmed) — the operator pin, checked first so
28996
+ * it works on every lead.
28997
+ * 2. A lighter Claude lead with the escalation model in the catalog.
28998
+ * 3. `ADVISOR_DEFAULT_MODEL`.
28999
+ *
29000
+ * Step 3 returns the LITERAL constant rather than walking the OpenAI frontier
29001
+ * chain. An Opus lead must resolve to exactly what it resolves to today, and a
29002
+ * frontier walk could yield `gpt-5.5` on a catalog missing `gpt-5.6-sol` —
29003
+ * a silent change to the one path that is required not to move.
29004
+ */
29005
+ /**
29006
+ * Map an operator pin onto the id the catalog actually carries.
29007
+ *
29008
+ * `GH_ROUTER_ADVISOR_MODEL` is free-form, and the natural thing to write is a
29009
+ * vendor-namespaced id like `openai/gpt-5.6-sol`. Copilot's catalog carries the
29010
+ * bare `gpt-5.6-sol`, so forwarding the namespaced form verbatim gets a 400
29011
+ * `model_not_supported` and the advisor silently degrades to its
29012
+ * "[Advisor unavailable: ...]" fallback — measured, not theorised: choosing the
29013
+ * transport correctly was NOT sufficient, because the id itself was still
29014
+ * wrong on the wire.
29015
+ *
29016
+ * An exact catalog hit wins first, so a real id containing a slash could never
29017
+ * be mangled. Only when the pin is absent from the catalog do we try its last
29018
+ * path segment, and only when THAT is present do we rewrite. A pin that matches
29019
+ * nothing is passed through untouched: the catalog may simply not be loaded
29020
+ * yet, and inventing an id would be worse than letting upstream reject it.
29021
+ */
29022
+ function normalizeAdvisorPin(pinned) {
29023
+ const models = state.models?.data;
29024
+ if (!models) return pinned;
29025
+ if (models.some((m) => m.id === pinned)) return pinned;
29026
+ const bare = pinned.slice(pinned.lastIndexOf("/") + 1);
29027
+ return bare !== pinned && models.some((m) => m.id === bare) ? bare : pinned;
29028
+ }
29029
+ function resolveAdvisorModel(leadModel) {
29030
+ const pinned = process.env.GH_ROUTER_ADVISOR_MODEL?.trim();
29031
+ if (pinned) return {
29032
+ model: normalizeAdvisorPin(pinned),
29033
+ escalated: false
29034
+ };
29035
+ if (leadModel && shouldEscalateAdvisor(leadModel)) return {
29036
+ model: ADVISOR_ESCALATION_MODEL,
29037
+ escalated: true
29038
+ };
29039
+ return {
29040
+ model: ADVISOR_DEFAULT_MODEL,
29041
+ escalated: false
29042
+ };
29043
+ }
29044
+ /**
29045
+ * Resolve the advisor's reasoning effort from the ORIGINAL request body, so the
29046
+ * advisor thinks at the level selected in the Claude Code effort picker instead
29047
+ * of a hardcoded constant.
29048
+ *
29049
+ * The source is the RAW pre-`resolveModelInBody` body, deliberately. By the time
29050
+ * the handler holds a parsed body, `translateThinking` has already bucketed
29051
+ * `thinking.budget_tokens` into `output_config.effort` AND clamped it to the
29052
+ * LEAD model's allowlist — so that value encodes "what the lead could do", not
29053
+ * "what the user picked". Re-clamping it against the advisor cannot recover the
29054
+ * difference: a `max` pick on a lead whose ceiling is `high` would reach an
29055
+ * xhigh-capable advisor as `high`.
29056
+ *
29057
+ * Precedence mirrors the repo-wide rule that an explicit client effort wins:
29058
+ * 1. `output_config.effort`
29059
+ * 2. `bucketEffort(thinking.budget_tokens)`
29060
+ * 3. `ADVISOR_DEFAULT_EFFORT` — a request expressing no preference behaves
29061
+ * exactly as it did before the picker was honored at all.
29062
+ *
29063
+ * Then floor, THEN clamp. The order is load-bearing: a model whose ceiling sits
29064
+ * below `ADVISOR_MIN_EFFORT` must still receive a value it accepts, so the clamp
29065
+ * is allowed to pull back under the floor. Flipping the two would forward an
29066
+ * effort upstream rejects.
29067
+ */
29068
+ function resolveAdvisorEffort(rawRequestBody, advisorModel) {
29069
+ let requested = ADVISOR_DEFAULT_EFFORT;
29070
+ if (rawRequestBody) try {
29071
+ const body = JSON.parse(rawRequestBody);
29072
+ const oc = body.output_config;
29073
+ const explicit = oc && typeof oc === "object" ? oc.effort : void 0;
29074
+ const thinking = body.thinking;
29075
+ if (typeof explicit === "string" && EFFORT_ORDER.includes(explicit)) requested = explicit;
29076
+ else if (thinking && typeof thinking === "object" && thinking.type === "enabled") requested = bucketEffort(thinking.budget_tokens);
29077
+ } catch {}
29078
+ const floored = EFFORT_ORDER.indexOf(requested) < EFFORT_ORDER.indexOf(ADVISOR_MIN_EFFORT) ? ADVISOR_MIN_EFFORT : requested;
29079
+ const supported = state.models?.data?.find((m) => m.id === resolveModel(advisorModel))?.capabilities?.supports?.reasoning_effort;
29080
+ if (!Array.isArray(supported) || supported.length === 0) return floored;
29081
+ return clampEffort(floored, supported);
29082
+ }
28544
29083
  /** ADVISOR_TOOL_INSTRUCTIONS verbatim from cc-backup
28545
29084
  * src/utils/advisor.ts — describes when the model should invoke
28546
29085
  * the advisor. Long-form prose; see source for justification. */
@@ -28636,8 +29175,12 @@ const ADVISOR_FALLBACK_MAX_TOKENS = 24e4;
28636
29175
  * our o200k count and Copilot's full-payload count. The transcript token
28637
29176
  * budget is `max_prompt_tokens - reserve`. Generous on purpose: a 400
28638
29177
  * `model_max_prompt_tokens_exceeded` degrades to a silent advisor
28639
- * fallback, and the marginal window we give up is irrelevant next to
28640
- * gpt-5.6-sol's ~1M. */
29178
+ * fallback, and the window given up is marginal against either advisor
29179
+ * model's real prompt window (`claude-opus-5` 936k, `gpt-5.6-sol` ~1M off
29180
+ * the live catalog). Sized as a fraction of the smaller of the two, not as
29181
+ * "irrelevant next to ~1M" — that framing assumed the advisor was always
29182
+ * the cheap side of the pair, which stopped being true once a budget lead
29183
+ * escalates to Opus. */
28641
29184
  const ADVISOR_PROMPT_TOKEN_RESERVE = 8e3;
28642
29185
  /**
28643
29186
  * Derive the TOKEN budget for the rendered transcript from the advisor
@@ -28748,9 +29291,9 @@ function truncateTailToUnits(text, maxUnits, measure) {
28748
29291
  * Anthropic's own ADVISOR ("see the whole task + every tool call +
28749
29292
  * every result").
28750
29293
  */
28751
- async function runAdvisor(conversation, advisorModel, advisorEffort, signal) {
29294
+ async function runAdvisor(conversation, advisorModel, advisorEffort, signal, advisorEscalated = false) {
28752
29295
  if (signal?.aborted) throw new Error("advisor call aborted before dispatch");
28753
- const advisorSystem = "You are an expert advisor reviewing an in-progress Claude Code session. The transcript below is the work-in-progress (turns numbered, with tool calls and results inlined). Read carefully and provide concrete, actionable advice on the next step or course-correction. Be specific — cite the parts of the transcript you're responding to. If the assistant is on the right track, say so explicitly. If they're stuck or off-track, name the specific assumption or step to revisit. Aim for 2-5 paragraphs of substantive guidance.";
29296
+ const advisorSystem = "You are an expert advisor reviewing an in-progress Claude Code session. The transcript below is the work-in-progress (turns numbered, with tool calls and results inlined). Read carefully and provide concrete, actionable advice on the next step or course-correction. Be specific — cite the parts of the transcript you're responding to. If the assistant is on the right track, say so explicitly. If they're stuck or off-track, name the specific assumption or step to revisit. Aim for 2-5 paragraphs of substantive guidance." + (advisorEscalated ? " The requesting agent is running a lighter, faster model than you. Give a directive recommendation and commit to the decision rather than laying out options for it to weigh." : "");
28754
29297
  const resolvedAdvisorModel = resolveModel(advisorModel);
28755
29298
  let measure;
28756
29299
  let maxUnits;
@@ -28765,7 +29308,7 @@ async function runAdvisor(conversation, advisorModel, advisorEffort, signal) {
28765
29308
  maxUnits = ADVISOR_MAX_CONVERSATION_CHARS;
28766
29309
  }
28767
29310
  const conversationText = renderConversationAsText(conversation, maxUnits, measure);
28768
- if (/^(gpt-|o\d|.*codex)/i.test(resolvedAdvisorModel)) {
29311
+ if (advisorUsesResponses(resolvedAdvisorModel)) {
28769
29312
  const payload = {
28770
29313
  model: resolvedAdvisorModel,
28771
29314
  instructions: advisorSystem,
@@ -28800,15 +29343,22 @@ async function runAdvisor(conversation, advisorModel, advisorEffort, signal) {
28800
29343
  if (!text) throw new Error(`Advisor model ${resolvedAdvisorModel} returned empty assistant output`);
28801
29344
  return text;
28802
29345
  }
29346
+ const advisorEntry = state.models?.data?.find((m) => m.id === resolvedAdvisorModel);
29347
+ const limits = advisorEntry?.capabilities?.limits;
29348
+ const maxTokens = limits?.max_non_streaming_output_tokens ?? limits?.max_output_tokens ?? ADVISOR_FALLBACK_MAX_OUTPUT_TOKENS;
28803
29349
  const advisorBody = JSON.stringify({
28804
29350
  model: resolvedAdvisorModel,
28805
- max_tokens: 4096,
29351
+ max_tokens: maxTokens,
28806
29352
  system: advisorSystem,
28807
29353
  messages: [{
28808
29354
  role: "user",
28809
29355
  content: conversationText
28810
29356
  }],
28811
- stream: false
29357
+ stream: false,
29358
+ ...advisorEntry?.capabilities?.supports?.adaptive_thinking ? {
29359
+ thinking: { type: "adaptive" },
29360
+ ...advertisedEffortLadder(resolvedAdvisorModel) ? { output_config: { effort: advisorEffort } } : {}
29361
+ } : {}
28812
29362
  });
28813
29363
  const json = await (await withTransientRetry(() => createMessages(advisorBody, {}, signal), {
28814
29364
  signal,
@@ -28863,6 +29413,7 @@ function sseEvent(type, data) {
28863
29413
  function buildAdvisorStream(opts) {
28864
29414
  const advisorModel = opts.advisorModel ?? "gpt-5.6-sol";
28865
29415
  const advisorEffort = opts.advisorEffort ?? "xhigh";
29416
+ const advisorEscalated = opts.advisorEscalated ?? false;
28866
29417
  const aborter = opts.externalAborter ?? new AbortController();
28867
29418
  let conversation = [...opts.initialConversation];
28868
29419
  return new ReadableStream({
@@ -29112,7 +29663,7 @@ function buildAdvisorStream(opts) {
29112
29663
  const advisorConversation = conversation;
29113
29664
  const advisorTexts = await Promise.all(advisorToolUses.map(async () => {
29114
29665
  try {
29115
- return await runAdvisor(advisorConversation, advisorModel, advisorEffort, aborter.signal);
29666
+ return await runAdvisor(advisorConversation, advisorModel, advisorEffort, aborter.signal, advisorEscalated);
29116
29667
  } catch (err) {
29117
29668
  if (aborter.signal.aborted) throw err;
29118
29669
  const msg = err instanceof Error ? err.message : String(err);
@@ -34568,7 +35119,7 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
34568
35119
  },
34569
35120
  workspace: {
34570
35121
  type: "string",
34571
- description: "Optional absolute path to the workspace the worker operates in. Defaults to the proxy's launch cwd. Use this when the parent agent has multiple workspaces open and the worker must operate in a specific one. Must be absolute (relative paths rejected)."
35122
+ description: "Absolute path of the directory the worker operates in. Pass YOUR OWN current working directory unless you were told to target a different one — the proxy is a separate, long-lived process and cannot see where you are, so it falls back to your session's directory, which is not necessarily yours (a sub-agent or a git worktree moves you without moving the session). Omitting it when the connection supplies nothing is an error rather than a guess. Must be absolute (relative paths rejected)."
34572
35123
  },
34573
35124
  maxWallClockMs: {
34574
35125
  type: "integer",
@@ -34576,11 +35127,12 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
34576
35127
  }
34577
35128
  }
34578
35129
  },
34579
- async handler(args, signal) {
35130
+ async handler(args, signal, ctx) {
34580
35131
  return runWorkerToolCall({
34581
35132
  mode: "explore",
34582
35133
  args,
34583
- signal
35134
+ signal,
35135
+ ctx
34584
35136
  });
34585
35137
  }
34586
35138
  },
@@ -34613,7 +35165,7 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
34613
35165
  },
34614
35166
  workspace: {
34615
35167
  type: "string",
34616
- description: "Optional absolute path to the workspace the worker operates in. Defaults to the proxy's launch cwd. Use this when the parent agent has multiple workspaces open and the worker must operate in a specific one. Must be absolute (relative paths rejected). Must be inside a git repo (implement always runs in a worktree)."
35168
+ description: "Absolute path of the directory the worker operates in. Pass YOUR OWN current working directory unless you were told to target a different one — the proxy is a separate, long-lived process and cannot see where you are, so it falls back to your session's directory, which is not necessarily yours (a sub-agent or a git worktree moves you without moving the session). Omitting it when the connection supplies nothing is an error rather than a guess. Must be absolute (relative paths rejected). Must be inside a git repo (implement always runs in a worktree)."
34617
35169
  },
34618
35170
  maxWallClockMs: {
34619
35171
  type: "integer",
@@ -34621,11 +35173,12 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
34621
35173
  }
34622
35174
  }
34623
35175
  },
34624
- async handler(args, signal) {
35176
+ async handler(args, signal, ctx) {
34625
35177
  return runWorkerToolCall({
34626
35178
  mode: "implement",
34627
35179
  args,
34628
- signal
35180
+ signal,
35181
+ ctx
34629
35182
  });
34630
35183
  }
34631
35184
  },
@@ -34633,7 +35186,7 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
34633
35186
  toolNameHttp: "review",
34634
35187
  group: "workers",
34635
35188
  capability: "worker",
34636
- description: "Runs as the background `worker-review` agent. Dispatch via the Agent tool (subagent_type: worker-review) so the turn is never blocked; the result arrives as a completion notification. Read-only code review by an autonomous worker (Pi runtime; default model `gemini-3.1-pro-preview`, default thinking xhigh clamped to high for that model, override via `model` with any Copilot-catalog model that advertises `tool_calls`). It has the same read-only toolset as explore and verifies claims against surrounding repository context before returning severity-ranked findings with `file:line` citations. Use for reviewing a change, diff, or correctness claim when the reviewer should read the code itself rather than trusting a pasted artifact. Not for architecture critique, implementation, or test authoring; use codex_critic or gemini_critic for design review, implement for edits, and test for independent test creation. Strictly read-only: it changes nothing on disk, and its returned text is the only artifact it produces — route to `implement` or `test` when a file actually needs to change. If the result is too large to relay it is truncated to a preview and the FULL text is written to a file, whose absolute path is in the returned text; read that file rather than re-running the worker.",
35189
+ description: "Runs as the background `worker-review` agent. Dispatch via the Agent tool (subagent_type: worker-review) so the turn is never blocked; the result arrives as a completion notification. Code review by an autonomous worker (Pi runtime; default model `gemini-3.1-pro-preview`, default thinking xhigh clamped to high for that model, override via `model` with any Copilot-catalog model that advertises `tool_calls`). It has explore's read-and-search tools PLUS `bash`, so it verifies claims by running them reproducing a failure or running the build or suite — rather than only reading, and returns severity-ranked findings with `file:line` citations. It gets no edit/write tools unless you pass `worktree: true`, but `bash` runs real commands in the workspace, so a build or test it invokes can touch the tree. Use for reviewing a change, diff, or correctness claim when the reviewer should read the code itself rather than trusting a pasted artifact. Not for architecture critique, implementation, or test authoring; use codex_critic or gemini_critic for design review, implement for edits, and test for independent test creation. If the result is too large to relay it is truncated to a preview and the FULL text is written to a file, whose absolute path is in the returned text; read that file rather than re-running the worker.",
34637
35190
  inputSchema: {
34638
35191
  type: "object",
34639
35192
  required: ["prompt"],
@@ -34652,9 +35205,13 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
34652
35205
  enum: WORKER_THINKING_LEVELS,
34653
35206
  description: "Optional reasoning depth (defaults to xhigh, clamped to high for the default review model). Silently clamped to the model's allowed range; \"off\" drops the parameter entirely."
34654
35207
  },
35208
+ worktree: {
35209
+ type: "boolean",
35210
+ description: "Optional. When true, the review runs in an isolated git worktree replaying the workspace's working tree (dirty tracked changes and untracked-not-ignored files), which additionally grants `edit`/`write` so the reviewer can author a throwaway probe test to prove a claim. Default false: the reviewer reads and runs commands in the workspace itself. Prefer the default when verifying needs the build to work — a fresh worktree does not carry IGNORED files, so installed dependencies are absent. Requires a git repository."
35211
+ },
34655
35212
  workspace: {
34656
35213
  type: "string",
34657
- description: "Optional absolute path to the workspace the worker operates in. Defaults to the proxy's launch cwd. Use this when the parent agent has multiple workspaces open and the worker must operate in a specific one. Must be absolute (relative paths rejected)."
35214
+ description: "Absolute path of the directory the worker operates in. Pass YOUR OWN current working directory unless you were told to target a different one — the proxy is a separate, long-lived process and cannot see where you are, so it falls back to your session's directory, which is not necessarily yours (a sub-agent or a git worktree moves you without moving the session). Omitting it when the connection supplies nothing is an error rather than a guess. Must be absolute (relative paths rejected)."
34658
35215
  },
34659
35216
  maxWallClockMs: {
34660
35217
  type: "integer",
@@ -34662,11 +35219,12 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
34662
35219
  }
34663
35220
  }
34664
35221
  },
34665
- async handler(args, signal) {
35222
+ async handler(args, signal, ctx) {
34666
35223
  return runWorkerToolCall({
34667
35224
  mode: "review",
34668
35225
  args,
34669
- signal
35226
+ signal,
35227
+ ctx
34670
35228
  });
34671
35229
  }
34672
35230
  },
@@ -34695,7 +35253,7 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
34695
35253
  },
34696
35254
  workspace: {
34697
35255
  type: "string",
34698
- description: "Optional absolute path to the workspace the worker operates in. Defaults to the proxy's launch cwd. Use this when the parent agent has multiple workspaces open and the worker must operate in a specific one. Must be absolute (relative paths rejected)."
35256
+ description: "Absolute path of the directory the worker operates in. Pass YOUR OWN current working directory unless you were told to target a different one — the proxy is a separate, long-lived process and cannot see where you are, so it falls back to your session's directory, which is not necessarily yours (a sub-agent or a git worktree moves you without moving the session). Omitting it when the connection supplies nothing is an error rather than a guess. Must be absolute (relative paths rejected)."
34699
35257
  },
34700
35258
  maxWallClockMs: {
34701
35259
  type: "integer",
@@ -34703,11 +35261,12 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
34703
35261
  }
34704
35262
  }
34705
35263
  },
34706
- async handler(args, signal) {
35264
+ async handler(args, signal, ctx) {
34707
35265
  return runWorkerToolCall({
34708
35266
  mode: "plan",
34709
35267
  args,
34710
- signal
35268
+ signal,
35269
+ ctx
34711
35270
  });
34712
35271
  }
34713
35272
  },
@@ -34740,7 +35299,7 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
34740
35299
  },
34741
35300
  workspace: {
34742
35301
  type: "string",
34743
- description: "Optional absolute path to the workspace the worker operates in. Defaults to the proxy's launch cwd. Use this when the parent agent has multiple workspaces open and the worker must operate in a specific one. Must be absolute (relative paths rejected). Must be inside a git repo (test always runs in a worktree)."
35302
+ description: "Absolute path of the directory the worker operates in. Pass YOUR OWN current working directory unless you were told to target a different one — the proxy is a separate, long-lived process and cannot see where you are, so it falls back to your session's directory, which is not necessarily yours (a sub-agent or a git worktree moves you without moving the session). Omitting it when the connection supplies nothing is an error rather than a guess. Must be absolute (relative paths rejected). Must be inside a git repo (test always runs in a worktree)."
34744
35303
  },
34745
35304
  maxWallClockMs: {
34746
35305
  type: "integer",
@@ -34748,11 +35307,12 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
34748
35307
  }
34749
35308
  }
34750
35309
  },
34751
- async handler(args, signal) {
35310
+ async handler(args, signal, ctx) {
34752
35311
  return runWorkerToolCall({
34753
35312
  mode: "test",
34754
35313
  args,
34755
- signal
35314
+ signal,
35315
+ ctx
34756
35316
  });
34757
35317
  }
34758
35318
  },
@@ -35075,10 +35635,12 @@ function assertMcpToolSurfaceConsistent() {
35075
35635
  /**
35076
35636
  * Shared closure body for the two worker MCP tools. Validates the
35077
35637
  * minimal arg shape (prompt required + optional knobs typed), then
35078
- * forwards to `runWorkerAgent`. Outside serve mode, `workspace` defaults
35079
- * to the proxy's launch cwd; serve mode requires an explicit/header-derived
35080
- * workspace. Callers can override via the optional `workspace` arg
35081
- * (absolute paths only enforced here). The engine performs every
35638
+ * forwards to `runWorkerAgent`. `workspace` comes from the caller's
35639
+ * argument or, failing that, the per-connection session header the
35640
+ * boundary folds in; with neither, the call is REFUSED rather than
35641
+ * defaulted to the proxy's launch cwd (see the resolution block below
35642
+ * for why that default was a bug, and `GH_ROUTER_ALLOW_PROXY_CWD_WORKSPACE`
35643
+ * for the raw-client escape hatch). The engine performs every
35082
35644
  * deeper validation (model existence, thinking clamp, worktree
35083
35645
  * provisioning, semaphore acquisition, workspace realpath +
35084
35646
  * accessibility) and never throws — its `{text, isError?}` envelope
@@ -35091,7 +35653,7 @@ function assertMcpToolSurfaceConsistent() {
35091
35653
  * client that ignores the schema.
35092
35654
  */
35093
35655
  async function runWorkerToolCall(call) {
35094
- const { mode, args, signal } = call;
35656
+ const { mode, args, signal, ctx } = call;
35095
35657
  const prompt = typeof args.prompt === "string" ? args.prompt : "";
35096
35658
  if (!prompt) return {
35097
35659
  content: [{
@@ -35123,7 +35685,7 @@ async function runWorkerToolCall(call) {
35123
35685
  }
35124
35686
  let worktree;
35125
35687
  let worktreeNote = "";
35126
- if (mode === "implement" || mode === "test") {
35688
+ if (mode === "implement" || mode === "test" || mode === "review") {
35127
35689
  if (args.worktree !== void 0 && typeof args.worktree !== "boolean") return {
35128
35690
  content: [{
35129
35691
  type: "text",
@@ -35131,11 +35693,16 @@ async function runWorkerToolCall(call) {
35131
35693
  }],
35132
35694
  isError: true
35133
35695
  };
35134
- worktree = true;
35135
- if (args.worktree === false) worktreeNote = `[note: worker_${mode} always runs in an isolated git worktree; the requested worktree:false was overridden. For in-place edits, use the \`implementer\` subagent.]
35696
+ if (mode === "review") worktree = args.worktree === true ? true : void 0;
35697
+ else {
35698
+ worktree = true;
35699
+ if (args.worktree === false) worktreeNote = `[note: worker_${mode} always runs in an isolated git worktree; the requested worktree:false was overridden. For in-place edits, use the \`implementer\` subagent.]
35136
35700
 
35137
35701
  `;
35702
+ }
35138
35703
  }
35704
+ const allowProxyCwd = process.env.GH_ROUTER_ALLOW_PROXY_CWD_WORKSPACE === "1";
35705
+ const callerGaveWorkspace = args.workspace !== void 0;
35139
35706
  let workspace;
35140
35707
  if (args.workspace !== void 0) {
35141
35708
  if (typeof args.workspace !== "string" || args.workspace.length === 0) return {
@@ -35160,7 +35727,16 @@ async function runWorkerToolCall(call) {
35160
35727
  }],
35161
35728
  isError: true
35162
35729
  };
35163
- else workspace = process.cwd();
35730
+ else if (allowProxyCwd) workspace = process.cwd();
35731
+ else return {
35732
+ content: [{
35733
+ type: "text",
35734
+ text: `worker_${mode}: a workspace is required. Nothing in this call said which directory to run in, and the proxy's own launch directory is not a safe guess — it is where the proxy was started, which may be a different checkout or git worktree than the one you are working in. Re-issue this call with \`workspace\` set to the absolute path of your current working directory. (Operators running a raw MCP client that cannot send one can restore the old launch-cwd default with GH_ROUTER_ALLOW_PROXY_CWD_WORKSPACE=1.)`
35735
+ }],
35736
+ isError: true
35737
+ };
35738
+ const effectiveSource = ctx?.workspaceSource ?? (callerGaveWorkspace ? "argument" : "absent");
35739
+ const workspaceNote = effectiveSource === "argument" ? "" : `[workspace: ${workspace} (${effectiveSource === "session" ? "from your session's working directory; pass `workspace` explicitly if you are running somewhere else, such as a git worktree" : "the proxy's launch directory"})]\n\n`;
35164
35740
  let maxWallClockMs;
35165
35741
  let clampNote = "";
35166
35742
  if (args.maxWallClockMs !== void 0) {
@@ -35188,7 +35764,7 @@ async function runWorkerToolCall(call) {
35188
35764
  maxWallClockMs,
35189
35765
  signal
35190
35766
  });
35191
- const notePrefix = `${clampNote}${worktreeNote}`;
35767
+ const notePrefix = `${workspaceNote}${clampNote}${worktreeNote}`;
35192
35768
  return {
35193
35769
  content: [{
35194
35770
  type: "text",
@@ -35421,6 +35997,6 @@ function enumerateInjectedMcpToolNames(groupKeys, opts = {}) {
35421
35997
  return [...new Set(names)];
35422
35998
  }
35423
35999
  //#endregion
35424
- export { browserCompoundToolsEnabled as $, satisfiesMinVersion as A, warmTreeSitterPool as At, repairRejectedThinkingHistory as B, DEFAULT_PORT as Bt, availableToolCommands as C, parseJsonOrDiagnose as Ct, vscodeRipgrepPath as D, provisionAndIndexColbert as Dt, toolbeltSkipSet as E, colbertDegradedWarning as Et, injectAdvisorTool as F, collapsePathKeys as Ft, readIteratorWithTimeout as G, upstreamAllowH2 as Gt, buildOpenAIErrorEvent as H, UPSTREAM_INACTIVITY_TIMEOUT_MS as Ht, isAdvisorRequested as I, toolbeltPathOverride as It, handleMcpPost as J, withInstallLock as Jt, relayAnthropicStream as K, upstreamMaxConnections as Kt, formatThinkingRepairDecline as L, DEFAULT_CLAUDE_MODEL_FALLBACKS as Lt, ADVISOR_INTERNAL_TOOL_NAME as M, CONDENSED_OPERATING_SEQUENCE as Mt, ADVISOR_TOOL_INSTRUCTIONS as N, DEFINITION_OF_GREATNESS as Nt, TOOLBELT_TOOLS$1 as O, extractTarGzMember as Ot, buildAdvisorStream as P, shouldUseInsecureTls as Pt, browseAgentEnabled as Q, rememberThinkingHistoryRepair as R, DEFAULT_CODEX_MODEL as Rt, buildEnv as S, readResponseBodyCapped as St, toolbeltEnabled as T, hasSupportedBrowserInstalled as Tt, isControllerClosedError as U, generateRandomPort as Ut, buildAnthropicErrorEvent as V, UPSTREAM_FETCH_TIMEOUT_MS as Vt, logStreamError as W, pickClaudeDefault as Wt, artifactToolsEnabled as X, agentToolsEnabled as Y, brainstormModel as Z, appendPlanReminder as _, resolveMcpToolTimeoutMs as _t, buildPeerAwarenessSnippet as a, nativeSubagentModel as at, resolveWorkerRunOpts as b, createChatCompletions as bt, personasFor as c, scribeModel as ct, EXPLORE_DEFAULT_MODEL as d, shimDefaultsToXhigh as dt, browserToolsEnabled as et, EXPLORE_DEFAULT_THINKING as f, countTokens as ft, TEST_DEFAULT_MODEL as g, warnOnTokenPriceDrift as gt, REVIEW_DEFAULT_MODEL as h, assembleResponsesPayload as ht, buildAgentPrompt as i, implementerFastModel as it, searchWeb as j, provisionTreeSitterAssets as jt, assetFor as k, extractZipMember as kt, BROWSE_DEFAULT_MODEL as l, standInToolEnabled as lt, PLAN_DEFAULT_MODEL as m, getTokenCount as mt, MCP_GROUPS as n, geminiAvailable as nt, buildPeerAwarenessSummary as o, reviewerModel as ot, IMPLEMENT_DEFAULT_MODEL as p, createMessages as pt, handleMcpDelete as q, withOneMSuffix as qt, assertMcpToolSurfaceConsistent as r, generalPurposeFastModel as rt, enumerateInjectedMcpToolNames as s, scoutModel as st, GROUP_META as t, fleetToolsEnabled as tt, DEFAULT_MODEL_CHAIN as u, workerToolsEnabled as ut, resolveDefaultModel as v, pickEndpoint as vt, buildToolbeltAwareness as w, provisionBrowserAssets as wt, runWorkerAgent as x, MAX_RESPONSE_BODY_BYTES as xt, resolveModeDefaults as y, createResponses as yt, repairKnownThinkingHistory as z, DEFAULT_CODEX_MODEL_FALLBACKS as zt };
36000
+ export { handleMcpDelete as $, resolveLeadSlugArg as $t, satisfiesMinVersion as A, hasSupportedBrowserInstalled as At, rememberThinkingHistoryRepair as B, collapsePathKeys as Bt, availableToolCommands as C, resolveMcpToolTimeoutMs as Ct, vscodeRipgrepPath as D, readResponseBodyCapped as Dt, toolbeltSkipSet as E, MAX_RESPONSE_BODY_BYTES as Et, injectAdvisorTool as F, warmTreeSitterPool as Ft, isControllerClosedError as G, DEFAULT_CODEX_MODEL as Gt, repairRejectedThinkingHistory as H, BUDGET_SMALL_FAST_CATALOG_ID as Ht, isAdvisorRequested as I, provisionTreeSitterAssets as It, relayAnthropicStream as J, UPSTREAM_FETCH_TIMEOUT_MS as Jt, logStreamError as K, DEFAULT_CODEX_MODEL_FALLBACKS as Kt, resolveAdvisorEffort as L, CONDENSED_OPERATING_SEQUENCE as Lt, ADVISOR_INTERNAL_TOOL_NAME as M, provisionAndIndexColbert as Mt, ADVISOR_TOOL_INSTRUCTIONS as N, extractTarGzMember as Nt, TOOLBELT_TOOLS$1 as O, parseJsonOrDiagnose as Ot, buildAdvisorStream as P, extractZipMember as Pt, clampEffort as Q, pickClaudeDefault as Qt, resolveAdvisorModel as R, DEFINITION_OF_GREATNESS as Rt, buildEnv as S, warnOnTokenPriceDrift as St, toolbeltEnabled as T, createChatCompletions as Tt, buildAnthropicErrorEvent as U, BUDGET_SMALL_FAST_SLUG as Ut, repairKnownThinkingHistory as V, toolbeltPathOverride as Vt, buildOpenAIErrorEvent as W, DEFAULT_CLAUDE_MODEL_FALLBACKS as Wt, UNKNOWN_EFFORT_ANCHOR as X, generateRandomPort as Xt, EFFORT_ORDER as Y, UPSTREAM_INACTIVITY_TIMEOUT_MS as Yt, bucketEffort as Z, isBudgetClaudeLead as Zt, appendPlanReminder as _, shimDefaultsToXhigh as _t, buildPeerAwarenessSnippet as a, withInstallLock as an, browserCompoundToolsEnabled as at, resolveWorkerRunOpts as b, getTokenCount as bt, personasFor as c, geminiAvailable as ct, EXPLORE_DEFAULT_MODEL as d, nativeSubagentModel as dt, upstreamAllowH2 as en, handleMcpPost as et, EXPLORE_DEFAULT_THINKING as f, reviewerModel as ft, TEST_DEFAULT_MODEL as g, workerToolsEnabled as gt, REVIEW_DEFAULT_MODEL as h, standInToolEnabled as ht, buildAgentPrompt as i, withOneMSuffixForLead as in, browseAgentEnabled as it, searchWeb as j, colbertDegradedWarning as jt, assetFor as k, provisionBrowserAssets as kt, BROWSE_DEFAULT_MODEL as l, generalPurposeFastModel as lt, PLAN_DEFAULT_MODEL as m, scribeModel as mt, MCP_GROUPS as n, classifyMessagesRoute as nn, artifactToolsEnabled as nt, buildPeerAwarenessSummary as o, browserToolsEnabled as ot, IMPLEMENT_DEFAULT_MODEL as p, scoutModel as pt, readIteratorWithTimeout as q, DEFAULT_PORT as qt, assertMcpToolSurfaceConsistent as r, withOneMSuffix as rn, brainstormModel as rt, enumerateInjectedMcpToolNames as s, fleetToolsEnabled as st, GROUP_META as t, upstreamMaxConnections as tn, agentToolsEnabled as tt, DEFAULT_MODEL_CHAIN as u, implementerFastModel as ut, resolveDefaultModel as v, countTokens as vt, buildToolbeltAwareness as w, createResponses as wt, runWorkerAgent as x, assembleResponsesPayload as xt, resolveModeDefaults as y, createMessages as yt, formatThinkingRepairDecline as z, shouldUseInsecureTls as zt };
35425
36001
 
35426
- //# sourceMappingURL=peer-mcp-personas-l4P4s1fK.js.map
36002
+ //# sourceMappingURL=peer-mcp-personas-V6stFvpq.js.map