github-router 0.3.153 → 0.3.162

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.
@@ -1,6 +1,6 @@
1
- import { t as PATHS } from "./paths-Bt7sqiVr.js";
2
- import { d as runCommandCapture, l as parseBoolEnv, n as isPidAlive, o as trackChild, p as runManagedExeCapture, r as registerColbertExitHandlers, t as getColbertInstanceUuid, u as resolveExecutable } from "./lifecycle-VTQI28wT.js";
3
- import { i as registerExitHandlers, n as getInstanceUuid, r as recordWorkerRepo, t as WorktreeRegistry } from "./lifecycle-C4k0pEvn.js";
1
+ import { t as PATHS } from "./paths-CTlT1nTo.js";
2
+ import { d as runCommandCapture, l as parseBoolEnv, n as isPidAlive, o as trackChild, p as runManagedExeCapture, r as registerColbertExitHandlers, t as getColbertInstanceUuid, u as resolveExecutable } from "./lifecycle-ChPBRt6K.js";
3
+ import { i as registerExitHandlers, n as getInstanceUuid, r as recordWorkerRepo, t as WorktreeRegistry } from "./lifecycle-BUXxiltc.js";
4
4
  import { createRequire } from "node:module";
5
5
  import consola from "consola";
6
6
  import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
@@ -12858,7 +12858,7 @@ function logAudit$1(record) {
12858
12858
  try {
12859
12859
  const fs$2 = await import("node:fs/promises");
12860
12860
  const path$1 = await import("node:path");
12861
- const { PATHS: PATHS$1 } = await import("./paths-C9Nkr_NK.js");
12861
+ const { PATHS: PATHS$1 } = await import("./paths-DN3O54iE.js");
12862
12862
  const dir = path$1.join(PATHS$1.APP_DIR, "browser-mcp");
12863
12863
  await fs$2.mkdir(dir, { recursive: true });
12864
12864
  const line = JSON.stringify({
@@ -16818,7 +16818,7 @@ const runtimeBuffer = globalThis.Buffer;
16818
16818
  //#endregion
16819
16819
  //#region src/lib/worker-agent/budget.ts
16820
16820
  const DEFAULT_MAX_TURNS = 500;
16821
- const DEFAULT_MAX_WALLCLOCK_MS = 30 * 6e4;
16821
+ const DEFAULT_MAX_WALLCLOCK_MS = 360 * 6e4;
16822
16822
  const DEFAULT_MAX_TOOL_BYTES = 16 * 1024 * 1024;
16823
16823
  const DEFAULT_MAX_TOOL_CALLS = 250;
16824
16824
  const DEFAULT_MAX_REPEATED_CALLS = 3;
@@ -16850,6 +16850,44 @@ function envInt(name) {
16850
16850
  return n;
16851
16851
  }
16852
16852
  /**
16853
+ * Default MCP per-tool-call timeout the launcher injects as
16854
+ * `MCP_TIMEOUT` / `MCP_TOOL_TIMEOUT` (see `server-setup.ts`). 6h15m —
16855
+ * one `MCP_TIMEOUT_HEADROOM_MS` above the 6h worker wall-clock default so a
16856
+ * non-converging worker aborts gracefully (partial work + `[halted:
16857
+ * wallclock]`) a full headroom before the harness hard-kills the call.
16858
+ */
16859
+ const DEFAULT_MCP_TOOL_TIMEOUT_MS = 225e5;
16860
+ /**
16861
+ * Teardown headroom between the worker wall-clock ceiling and the MCP
16862
+ * per-tool-call timeout — the graceful-abort + result-delivery budget. The
16863
+ * worker wall-clock (default AND any per-call override) is clamped to
16864
+ * `MCP_TOOL_TIMEOUT − MCP_TIMEOUT_HEADROOM_MS` so a worker is never silently
16865
+ * hard-killed by the harness.
16866
+ */
16867
+ const MCP_TIMEOUT_HEADROOM_MS = 15 * 6e4;
16868
+ /**
16869
+ * The MCP per-tool-call timeout the proxy injects into the spawned CLI, in ms.
16870
+ * Positive-integer override via `GH_ROUTER_MCP_TOOL_TIMEOUT_MS`; falls back to
16871
+ * `DEFAULT_MCP_TOOL_TIMEOUT_MS` on unset/garbage input (same lenient parse as
16872
+ * the worker-budget env overrides). Single source of truth for both
16873
+ * `server-setup.ts` (which stringifies it into the child env) and the worker
16874
+ * MCP boundary (which clamps a per-call wall-clock under it).
16875
+ */
16876
+ function resolveMcpToolTimeoutMs() {
16877
+ return envInt("GH_ROUTER_MCP_TOOL_TIMEOUT_MS") ?? DEFAULT_MCP_TOOL_TIMEOUT_MS;
16878
+ }
16879
+ /**
16880
+ * The maximum wall-clock a single worker call may be granted: the MCP
16881
+ * tool-call timeout minus the teardown headroom. A per-call `maxWallClockMs`
16882
+ * override above this is clamped down to it (see `runWorkerToolCall`) so the
16883
+ * worker always aborts gracefully at least `MCP_TIMEOUT_HEADROOM_MS` before the
16884
+ * harness would hard-kill the MCP call. The 6h default equals this ceiling on
16885
+ * the default MCP timeout (22_500_000 − 900_000 === 21_600_000).
16886
+ */
16887
+ function workerWallClockCeilingMs() {
16888
+ return resolveMcpToolTimeoutMs() - MCP_TIMEOUT_HEADROOM_MS;
16889
+ }
16890
+ /**
16853
16891
  * Resolve a `BudgetConfig` from defaults + env overrides + caller-
16854
16892
  * supplied overrides. Caller overrides win; env wins over defaults.
16855
16893
  *
@@ -17330,6 +17368,148 @@ async function acquireWorkerSlot(signal) {
17330
17368
  };
17331
17369
  }
17332
17370
 
17371
+ //#endregion
17372
+ //#region src/services/copilot/responses-request.ts
17373
+ /**
17374
+ * Copilot's `/responses` endpoint rejects a positive `max_output_tokens` below
17375
+ * 16 with an HTTP 400 (verified live on gpt-5.5 and gpt-5.3-codex):
17376
+ * Invalid 'max_output_tokens': integer below minimum value. Expected a value
17377
+ * >= 16, but got 1 instead.
17378
+ * Anthropic's `/v1/messages` allows any `max_tokens >= 1`, so a valid low
17379
+ * Anthropic request (`max_tokens` 1..15) would otherwise 400 on the Responses
17380
+ * path. Clamp a positive sub-16 value UP to this minimum; leave `undefined`
17381
+ * and normal (>= 16) values EXACTLY as-is so the worker hot path and all
17382
+ * normal requests stay byte-identical. The chat path has NO such minimum
17383
+ * (gemini / `/chat/completions` accepts small values, verified HTTP 200), so
17384
+ * this clamp lives only here, never in `chat-request.ts`.
17385
+ */
17386
+ const RESPONSES_MIN_MAX_OUTPUT_TOKENS = 16;
17387
+ /** Build the `data:` URI (base64) or return the verbatim URL for an image part. */
17388
+ function imageUrlFor(part) {
17389
+ if (typeof part.url === "string" && part.url.length > 0) return part.url;
17390
+ return `data:${part.mimeType ?? "image/png"};base64,${part.data ?? ""}`;
17391
+ }
17392
+ /**
17393
+ * Map a document part to a Responses `input_file` item. A base64 document is
17394
+ * wrapped into a `data:<mime>;base64,<data>` `file_data` URI (the verified-working
17395
+ * Copilot shape — gpt-5.5 reads it); a URL document carries `file_url`. Returns
17396
+ * null when neither source is present (malformed) so it's dropped, not emitted
17397
+ * as an invalid item.
17398
+ */
17399
+ function documentInputFile(part) {
17400
+ const filename = part.filename ?? "document.pdf";
17401
+ if (typeof part.data === "string" && part.data.length > 0) return {
17402
+ type: "input_file",
17403
+ filename,
17404
+ file_data: `data:${part.mimeType ?? "application/pdf"};base64,${part.data}`
17405
+ };
17406
+ if (typeof part.url === "string" && part.url.length > 0) return {
17407
+ type: "input_file",
17408
+ filename,
17409
+ file_url: part.url
17410
+ };
17411
+ return null;
17412
+ }
17413
+ function joinText(parts) {
17414
+ let s = "";
17415
+ for (const p of parts) if (p.type === "text") s += p.text;
17416
+ return s;
17417
+ }
17418
+ function neutralUserToResponses(m) {
17419
+ if (typeof m.content === "string") return [{
17420
+ role: "user",
17421
+ content: m.content
17422
+ }];
17423
+ if (!m.content.some((c) => c.type === "image" || c.type === "document")) return [{
17424
+ role: "user",
17425
+ content: joinText(m.content)
17426
+ }];
17427
+ const parts = [];
17428
+ for (const c of m.content) if (c.type === "text") parts.push({
17429
+ type: "input_text",
17430
+ text: c.text
17431
+ });
17432
+ else if (c.type === "image") parts.push({
17433
+ type: "input_image",
17434
+ image_url: imageUrlFor(c)
17435
+ });
17436
+ else if (c.type === "document") {
17437
+ const item = documentInputFile(c);
17438
+ if (item) parts.push(item);
17439
+ }
17440
+ return [{
17441
+ role: "user",
17442
+ content: parts
17443
+ }];
17444
+ }
17445
+ function neutralAssistantToResponses(m) {
17446
+ const items = [];
17447
+ let buffer = "";
17448
+ const flush = () => {
17449
+ if (buffer.length === 0) return;
17450
+ items.push({
17451
+ role: "assistant",
17452
+ content: [{
17453
+ type: "output_text",
17454
+ text: buffer
17455
+ }]
17456
+ });
17457
+ buffer = "";
17458
+ };
17459
+ for (const c of m.content) if (c.type === "text") buffer += c.text;
17460
+ else if (c.type === "toolCall") {
17461
+ flush();
17462
+ items.push({
17463
+ type: "function_call",
17464
+ call_id: c.id,
17465
+ name: c.name,
17466
+ arguments: JSON.stringify(c.arguments ?? {})
17467
+ });
17468
+ }
17469
+ flush();
17470
+ return items;
17471
+ }
17472
+ /** Translate one neutral message into zero-or-more Responses input items. */
17473
+ function neutralMessageToResponsesInput(m) {
17474
+ if (m.role === "user") return neutralUserToResponses(m);
17475
+ if (m.role === "assistant") return neutralAssistantToResponses(m);
17476
+ return [{
17477
+ type: "function_call_output",
17478
+ call_id: m.toolCallId,
17479
+ output: m.output
17480
+ }];
17481
+ }
17482
+ function neutralToolsToResponses(tools) {
17483
+ if (!tools || tools.length === 0) return void 0;
17484
+ return tools.map((t) => ({
17485
+ type: "function",
17486
+ name: t.name,
17487
+ description: t.description,
17488
+ parameters: t.parameters
17489
+ }));
17490
+ }
17491
+ /** Assemble the full Responses payload from the neutral request shape. */
17492
+ function assembleResponsesPayload(opts) {
17493
+ const input = [];
17494
+ for (const m of opts.messages) for (const item of neutralMessageToResponsesInput(m)) input.push(item);
17495
+ const payload = {
17496
+ model: opts.model,
17497
+ input,
17498
+ stream: opts.stream
17499
+ };
17500
+ if (opts.instructions) payload.instructions = opts.instructions;
17501
+ const tools = neutralToolsToResponses(opts.tools);
17502
+ if (tools && tools.length > 0) {
17503
+ payload.tools = tools;
17504
+ payload.tool_choice = opts.toolChoice ?? "auto";
17505
+ }
17506
+ if (opts.reasoningEffort && opts.reasoningEffort !== "off") payload.reasoning = { effort: opts.reasoningEffort };
17507
+ if (typeof opts.maxOutputTokens === "number" && opts.maxOutputTokens > 0) payload.max_output_tokens = Math.max(opts.maxOutputTokens, RESPONSES_MIN_MAX_OUTPUT_TOKENS);
17508
+ if (opts.stopSequences && opts.stopSequences.length > 0) payload.stop = [...opts.stopSequences];
17509
+ if (opts.parallelToolCalls === false) payload.parallel_tool_calls = false;
17510
+ return payload;
17511
+ }
17512
+
17333
17513
  //#endregion
17334
17514
  //#region src/lib/worker-agent/context-budget.ts
17335
17515
  /**
@@ -17943,86 +18123,68 @@ async function runResponsesStreamLoop(stream, context, opts, options) {
17943
18123
  });
17944
18124
  }
17945
18125
  function buildResponsesPayload(context, resolved) {
17946
- const input = [];
17947
- for (const m of context.messages) for (const item of translateMessageToResponses(m)) input.push(item);
17948
- const payload = {
18126
+ const messages = [];
18127
+ for (const m of context.messages) {
18128
+ const neutral = piMessageToNeutral(m);
18129
+ if (neutral) messages.push(neutral);
18130
+ }
18131
+ return assembleResponsesPayload({
17949
18132
  model: resolved.modelId,
17950
- input,
18133
+ instructions: context.systemPrompt || void 0,
18134
+ messages,
18135
+ tools: piToolsToNeutral(context.tools),
18136
+ reasoningEffort: resolved.thinking,
17951
18137
  stream: true
17952
- };
17953
- if (context.systemPrompt) payload.instructions = context.systemPrompt;
17954
- const tools = translateToolsToResponses(context.tools);
17955
- if (tools && tools.length > 0) {
17956
- payload.tools = tools;
17957
- payload.tool_choice = "auto";
17958
- }
17959
- if (resolved.thinking !== "off") payload.reasoning = { effort: resolved.thinking };
17960
- return payload;
17961
- }
17962
- function translateMessageToResponses(m) {
17963
- if (m.role === "user") return translateUserToResponses(m);
17964
- if (m.role === "assistant") return translateAssistantToResponses(m);
17965
- if (m.role === "toolResult") return [{
17966
- type: "function_call_output",
17967
- call_id: m.toolCallId,
17968
- output: joinTextParts(m.content)
17969
- }];
17970
- return [];
17971
- }
17972
- function translateUserToResponses(m) {
17973
- if (typeof m.content === "string") return [{
17974
- role: "user",
17975
- content: m.content
17976
- }];
17977
- if (!m.content.some((c) => c.type === "image")) return [{
17978
- role: "user",
17979
- content: joinTextParts(m.content)
17980
- }];
17981
- const parts = [];
17982
- for (const c of m.content) if (c.type === "text") parts.push({
17983
- type: "input_text",
17984
- text: c.text
17985
- });
17986
- else if (c.type === "image") parts.push({
17987
- type: "input_image",
17988
- image_url: `data:${c.mimeType};base64,${c.data}`
17989
18138
  });
17990
- return [{
17991
- role: "user",
17992
- content: parts
17993
- }];
17994
18139
  }
17995
- function translateAssistantToResponses(m) {
17996
- const items = [];
17997
- let buffer = "";
17998
- const flush = () => {
17999
- if (buffer.length === 0) return;
18000
- items.push({
18001
- role: "assistant",
18002
- content: [{
18003
- type: "output_text",
18004
- text: buffer
18005
- }]
18140
+ function piMessageToNeutral(m) {
18141
+ if (m.role === "user") {
18142
+ if (typeof m.content === "string") return {
18143
+ role: "user",
18144
+ content: m.content
18145
+ };
18146
+ const parts = [];
18147
+ for (const c of m.content) if (c.type === "text") parts.push({
18148
+ type: "text",
18149
+ text: c.text
18006
18150
  });
18007
- buffer = "";
18008
- };
18009
- for (const c of m.content) if (c.type === "text") buffer += c.text;
18010
- else if (c.type === "toolCall") {
18011
- flush();
18012
- items.push({
18013
- type: "function_call",
18014
- call_id: c.id,
18151
+ else if (c.type === "image") parts.push({
18152
+ type: "image",
18153
+ mimeType: c.mimeType,
18154
+ data: c.data
18155
+ });
18156
+ return {
18157
+ role: "user",
18158
+ content: parts
18159
+ };
18160
+ }
18161
+ if (m.role === "assistant") {
18162
+ const parts = [];
18163
+ for (const c of m.content) if (c.type === "text") parts.push({
18164
+ type: "text",
18165
+ text: c.text
18166
+ });
18167
+ else if (c.type === "toolCall") parts.push({
18168
+ type: "toolCall",
18169
+ id: c.id,
18015
18170
  name: c.name,
18016
- arguments: JSON.stringify(c.arguments ?? {})
18171
+ arguments: c.arguments
18017
18172
  });
18173
+ return {
18174
+ role: "assistant",
18175
+ content: parts
18176
+ };
18018
18177
  }
18019
- flush();
18020
- return items;
18178
+ if (m.role === "toolResult") return {
18179
+ role: "toolResult",
18180
+ toolCallId: m.toolCallId,
18181
+ output: joinTextParts(m.content)
18182
+ };
18183
+ return null;
18021
18184
  }
18022
- function translateToolsToResponses(tools) {
18185
+ function piToolsToNeutral(tools) {
18023
18186
  if (!tools || tools.length === 0) return void 0;
18024
18187
  return tools.map((t) => ({
18025
- type: "function",
18026
18188
  name: t.name,
18027
18189
  description: t.description,
18028
18190
  parameters: t.parameters
@@ -19301,6 +19463,15 @@ function standInToolEnabled() {
19301
19463
  const hasGeminiPro = models.some((m) => /^gemini-3\..*pro/i.test(m.id));
19302
19464
  return hasGpt55 && hasOpus && hasGeminiPro;
19303
19465
  }
19466
+ const IMPLEMENTER_SUBAGENT_MODEL = "gpt-5.5";
19467
+ /** Return the native implementer subagent model iff it is live with tool calls. */
19468
+ function implementerSubagentModel() {
19469
+ const models = state.models?.data;
19470
+ if (!models) return void 0;
19471
+ const found = models.find((m) => m.id === IMPLEMENTER_SUBAGENT_MODEL);
19472
+ if (!found) return void 0;
19473
+ return found.capabilities?.supports?.tool_calls === true ? IMPLEMENTER_SUBAGENT_MODEL : void 0;
19474
+ }
19304
19475
  /**
19305
19476
  * Gate for the worker tools (`explore`, `review`, `implement`).
19306
19477
  *
@@ -23252,7 +23423,16 @@ async function createWorktree(workspaceAbs, opts) {
23252
23423
  ])).stdout;
23253
23424
  } catch {}
23254
23425
  const lineCount = stat$1.split(/\r?\n/).filter((l) => l.length > 0).length;
23255
- return `[diff truncated at 256KB; ${Math.max(0, lineCount - 1)} files changed]\n${stat$1}`;
23426
+ const summary = `[diff truncated at 256KB; ${Math.max(0, lineCount - 1)} files changed]\n${stat$1}`;
23427
+ let savedPath = null;
23428
+ let saveError = null;
23429
+ try {
23430
+ savedPath = await saveOverflowPatch(dir);
23431
+ } catch (err) {
23432
+ saveError = err.message;
23433
+ }
23434
+ if (savedPath !== null) return `${summary}\nFull patch (git apply-able; includes binary blobs) saved to: ${savedPath}`;
23435
+ return `${summary}\n[full patch save failed: ${saveError ?? "unknown"}; only the summary above is available]`;
23256
23436
  };
23257
23437
  return {
23258
23438
  dir,
@@ -23261,6 +23441,43 @@ async function createWorktree(workspaceAbs, opts) {
23261
23441
  remove
23262
23442
  };
23263
23443
  }
23444
+ /**
23445
+ * Persist the FULL `git diff --binary --full-index HEAD` of `dir` to a
23446
+ * durable, router-owned file under `PATHS.WORKER_DIFFS_DIR` and return its
23447
+ * absolute path.
23448
+ *
23449
+ * Called by `finalize()` ONLY when the inline diff overflows
23450
+ * `DIFF_CAP_BYTES`: the worktree is removed immediately after finalize, so a
23451
+ * truncated inline diff would otherwise lose the actual patch forever. The
23452
+ * durable dir lives under the app dir — never inside the worktree (deleted)
23453
+ * nor the user's repo (don't pollute it).
23454
+ *
23455
+ * `--binary` keeps binary blobs recoverable (git base85-encodes them into
23456
+ * the patch) and `--full-index` writes exact 40-char object indexes, so the
23457
+ * saved patch reconstructs the change precisely under `git apply`. Uses the
23458
+ * same `execFileP` git mechanism the rest of `finalize()` uses (shell:false).
23459
+ *
23460
+ * Unique filename `<pid>-<8hex>.patch` — collision-free across concurrent
23461
+ * workers and repeated finalize calls.
23462
+ */
23463
+ async function saveOverflowPatch(dir) {
23464
+ const patch = await execFileP("git", [
23465
+ "-C",
23466
+ dir,
23467
+ "diff",
23468
+ "--binary",
23469
+ "--full-index",
23470
+ "HEAD"
23471
+ ], { maxBuffer: 256 * 1024 * 1024 });
23472
+ await fs.mkdir(PATHS.WORKER_DIFFS_DIR, { recursive: true });
23473
+ const name = `${process$1.pid}-${randomBytes(4).toString("hex")}.patch`;
23474
+ const patchPath = nodePath.join(PATHS.WORKER_DIFFS_DIR, name);
23475
+ await fs.writeFile(patchPath, patch.stdout, {
23476
+ mode: 384,
23477
+ flag: "wx"
23478
+ });
23479
+ return patchPath;
23480
+ }
23264
23481
 
23265
23482
  //#endregion
23266
23483
  //#region src/lib/worker-agent/engine.ts
@@ -23275,30 +23492,40 @@ async function createWorktree(workspaceAbs, opts) {
23275
23492
  */
23276
23493
  const WORKTREE_REGISTRY = new WorktreeRegistry();
23277
23494
  registerExitHandlers(WORKTREE_REGISTRY);
23278
- /** Default model + thinking for the `explore` mode. `gpt-5.4-mini` at
23279
- * `xhigh` — fast, cheap, 400k-context, tool-call-capable, with tight
23280
- * function-calling-loop discipline.
23281
- *
23282
- * HISTORY / CAVEAT: earlier iterations used `gemini-3.1-pro-preview` then
23283
- * `gemini-3.5-flash`; both flash defaults early-stopped with empty turns
23284
- * on the function-calling loop (read a file then end the turn with no
23285
- * summary), which the single no-output retry couldn't reliably recover.
23286
- * `gpt-5.4-mini` does not show that pathology and is the proven `browse`
23287
- * default. Routed through `/responses` by the stream-fn endpoint split.
23288
- *
23289
- * Exported so the MCP handler + the gate (`workerToolsEnabled`) read the
23290
- * same constant — drift would ship a tool whose docs/gate disagree with
23291
- * its runtime default. Caller can override per call via the `model` arg. */
23495
+ /** Worker-availability GATE sentinel + final fallback. `gpt-5.4-mini` — cheap,
23496
+ * broadly-available, tool-call-capable, 400k-context, with tight
23497
+ * function-calling-loop discipline (earlier gemini-flash cheap defaults
23498
+ * early-stopped with empty turns on the function-calling loop; gpt-5.4-mini
23499
+ * does not). Exported and aliased as `WORKER_DEFAULT_MODEL`:
23500
+ * `workerToolsEnabled()` gates the ENTIRE worker surface on this id being
23501
+ * present with `tool_calls`. It is no longer `explore`'s default (see
23502
+ * `EXPLORE_DEFAULT_MODEL`) it stays the gate sentinel because it's the
23503
+ * cheapest broadly-present tool-caller, and the fallback for any unmatched
23504
+ * mode. */
23292
23505
  const DEFAULT_MODEL = "gpt-5.4-mini";
23293
23506
  const DEFAULT_THINKING = "xhigh";
23294
- /** Default model + thinking for the READ-ONLY `review` mode. `gpt-5.5` at
23295
- * `xhigh` — the strongest reasoning tier, 1M+ context, so the reviewer
23296
- * has full headroom to verify correctness against the actual code. Same
23297
- * model as `implement`; like it, this is NOT a `workerToolsEnabled` gate
23298
- * input — if absent (e.g. a non-enterprise tier) `review` errors helpfully
23299
- * at call time rather than vanishing the whole worker surface. Caller can
23300
- * override per call via the `model` arg. */
23301
- const REVIEW_DEFAULT_MODEL = "gpt-5.5";
23507
+ /** Default model for the READ-ONLY `explore` mode. `claude-sonnet-5` at `xhigh`
23508
+ * (via `DEFAULT_THINKING`)a strong, NATIVE (no-shim) tool-caller for repo
23509
+ * research. Native Claude models run as workers over `/chat/completions`, the
23510
+ * same path proven by `PLAN_DEFAULT_MODEL` (claude-opus-4.8). Like `implement`'s
23511
+ * gpt-5.5 this is NOT a `workerToolsEnabled` gate input — if absent (e.g. a
23512
+ * non-enterprise tier) `explore` errors helpfully at call time rather than
23513
+ * vanishing the whole worker surface. The caller (the main model) overrides
23514
+ * BOTH the model and the reasoning per call via the `model` / `thinking` args. */
23515
+ const EXPLORE_DEFAULT_MODEL = "claude-sonnet-5";
23516
+ /** Default model + thinking for the READ-ONLY `review` mode.
23517
+ * `gemini-3.1-pro-preview` at `xhigh` (clamped to `high` at call time — gemini
23518
+ * advertises no xhigh). DELIBERATELY DECORRELATED FROM THE IMPLEMENTER: bounded
23519
+ * implementation now defaults to gpt-5.5 (OpenAI) — both the `implement` worker
23520
+ * and the native `implementer` subagent — and the main orchestrator is Opus
23521
+ * (Anthropic), so review runs on a THIRD lab (Google) to maximize blind-spot
23522
+ * diversity. A reviewer sharing the implementer's lab catches a correlated slice
23523
+ * of defects; a cross-lab reviewer is the point of the review step. Like
23524
+ * `implement`, this is NOT a `workerToolsEnabled` gate input — if absent (e.g. a
23525
+ * non-enterprise tier) `review` errors helpfully at call time rather than
23526
+ * vanishing the whole worker surface. Caller can override per call via the
23527
+ * `model` arg (e.g. `claude-opus-4.8` for an Anthropic-lab reviewer). */
23528
+ const REVIEW_DEFAULT_MODEL = "gemini-3.1-pro-preview";
23302
23529
  const REVIEW_DEFAULT_THINKING = "xhigh";
23303
23530
  /** Default model + thinking for the READ+WRITE `implement` mode. `gpt-5.5`
23304
23531
  * at `xhigh` — the strongest reasoning tier in the catalog, 1M+ context,
@@ -23428,7 +23655,8 @@ async function runWorkerAgentOnce(opts) {
23428
23655
  const isPlan = opts.mode === "plan";
23429
23656
  const isReview = opts.mode === "review";
23430
23657
  const isWriteCapable = opts.mode === "implement" || opts.mode === "test";
23431
- const defaultModel = isBrowse ? BROWSE_DEFAULT_MODEL : isPlan ? PLAN_DEFAULT_MODEL : isReview ? REVIEW_DEFAULT_MODEL : isWriteCapable ? IMPLEMENT_DEFAULT_MODEL : DEFAULT_MODEL;
23658
+ const isExplore = opts.mode === "explore";
23659
+ const defaultModel = isBrowse ? BROWSE_DEFAULT_MODEL : isPlan ? PLAN_DEFAULT_MODEL : isReview ? REVIEW_DEFAULT_MODEL : isWriteCapable ? IMPLEMENT_DEFAULT_MODEL : isExplore ? EXPLORE_DEFAULT_MODEL : DEFAULT_MODEL;
23432
23660
  const defaultThinking = isBrowse ? BROWSE_DEFAULT_THINKING : isPlan ? PLAN_DEFAULT_THINKING : isReview ? REVIEW_DEFAULT_THINKING : isWriteCapable ? IMPLEMENT_DEFAULT_THINKING : DEFAULT_THINKING;
23433
23661
  const resolved = resolveModelAndThinking({
23434
23662
  model: opts.model ?? defaultModel,
@@ -23467,7 +23695,7 @@ async function runWorkerAgentOnce(opts) {
23467
23695
  };
23468
23696
  }
23469
23697
  else ws = makeNoWorktreeHandle(workspaceAbs);
23470
- const budget = new Budget();
23698
+ const budget = new Budget({ maxWallClockMs: opts.maxWallClockMs });
23471
23699
  const agentHolder = {};
23472
23700
  const planState = createPlanState();
23473
23701
  const getMessages = () => agentHolder.agent?.state.messages ?? [];
@@ -23566,7 +23794,7 @@ async function runWorkerAgentOnce(opts) {
23566
23794
  } catch {}
23567
23795
  const text = isBrowse ? terminalText ?? finalText : diff ? `${finalText}\n\n${diff}` : finalText;
23568
23796
  if (lastStopReason === "error") return {
23569
- text: (terminalText ?? finalText).trim() || "Worker run failed before producing an answer — the model's input likely overflowed (a large tool result), or the upstream errored. Retry with a narrower task: target a specific section / file / element rather than reading everything at once.",
23797
+ text: [(terminalText ?? finalText).trim() || "Worker run failed before producing an answer — the model's input likely overflowed (a large tool result), or the upstream errored. Retry with a narrower task: target a specific section / file / element rather than reading everything at once.", diff].filter(Boolean).join("\n\n"),
23570
23798
  isError: true
23571
23799
  };
23572
23800
  if (!text.trim()) return {
@@ -23576,9 +23804,11 @@ async function runWorkerAgentOnce(opts) {
23576
23804
  return { text };
23577
23805
  } catch (err) {
23578
23806
  let diff = "";
23579
- if (err instanceof WorkerAbort) try {
23807
+ try {
23580
23808
  diff = await ws.finalize();
23581
- } catch {}
23809
+ } catch (err$1) {
23810
+ diff = `[diff capture failed: ${err$1.message}]`;
23811
+ }
23582
23812
  try {
23583
23813
  await ws.remove();
23584
23814
  } catch {}
@@ -26398,6 +26628,10 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
26398
26628
  workspace: {
26399
26629
  type: "string",
26400
26630
  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)."
26631
+ },
26632
+ maxWallClockMs: {
26633
+ type: "integer",
26634
+ description: "Optional per-call wall-clock budget in ms; default 6h (21600000). Clamped just under the MCP tool-call ceiling (the injected MCP tool-call timeout minus a 15-min teardown headroom) so the worker aborts gracefully with its partial work rather than being hard-killed; the effective value is reported in the result when a larger value is clamped down."
26401
26635
  }
26402
26636
  }
26403
26637
  },
@@ -26446,6 +26680,10 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
26446
26680
  workspace: {
26447
26681
  type: "string",
26448
26682
  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). For worktree:true, must be inside a git repo."
26683
+ },
26684
+ maxWallClockMs: {
26685
+ type: "integer",
26686
+ description: "Optional per-call wall-clock budget in ms; default 6h (21600000). Clamped just under the MCP tool-call ceiling (the injected MCP tool-call timeout minus a 15-min teardown headroom) so the worker aborts gracefully with its partial work rather than being hard-killed; the effective value is reported in the result when a larger value is clamped down."
26449
26687
  }
26450
26688
  }
26451
26689
  },
@@ -26490,6 +26728,10 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
26490
26728
  workspace: {
26491
26729
  type: "string",
26492
26730
  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)."
26731
+ },
26732
+ maxWallClockMs: {
26733
+ type: "integer",
26734
+ description: "Optional per-call wall-clock budget in ms; default 6h (21600000). Clamped just under the MCP tool-call ceiling (the injected MCP tool-call timeout minus a 15-min teardown headroom) so the worker aborts gracefully with its partial work rather than being hard-killed; the effective value is reported in the result when a larger value is clamped down."
26493
26735
  }
26494
26736
  }
26495
26737
  },
@@ -26534,6 +26776,10 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
26534
26776
  workspace: {
26535
26777
  type: "string",
26536
26778
  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)."
26779
+ },
26780
+ maxWallClockMs: {
26781
+ type: "integer",
26782
+ description: "Optional per-call wall-clock budget in ms; default 6h (21600000). Clamped just under the MCP tool-call ceiling (the injected MCP tool-call timeout minus a 15-min teardown headroom) so the worker aborts gracefully with its partial work rather than being hard-killed; the effective value is reported in the result when a larger value is clamped down."
26537
26783
  }
26538
26784
  }
26539
26785
  },
@@ -26582,6 +26828,10 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
26582
26828
  workspace: {
26583
26829
  type: "string",
26584
26830
  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). For worktree:true, must be inside a git repo."
26831
+ },
26832
+ maxWallClockMs: {
26833
+ type: "integer",
26834
+ description: "Optional per-call wall-clock budget in ms; default 6h (21600000). Clamped just under the MCP tool-call ceiling (the injected MCP tool-call timeout minus a 15-min teardown headroom) so the worker aborts gracefully with its partial work rather than being hard-killed; the effective value is reported in the result when a larger value is clamped down."
26585
26835
  }
26586
26836
  }
26587
26837
  },
@@ -26986,6 +27236,23 @@ async function runWorkerToolCall(call) {
26986
27236
  };
26987
27237
  workspace = args.workspace;
26988
27238
  }
27239
+ let maxWallClockMs;
27240
+ let clampNote = "";
27241
+ if (args.maxWallClockMs !== void 0) {
27242
+ const raw = args.maxWallClockMs;
27243
+ if (typeof raw !== "number" || !Number.isInteger(raw) || raw <= 0) return {
27244
+ content: [{
27245
+ type: "text",
27246
+ text: `worker_${mode}: arguments.maxWallClockMs must be a positive integer (milliseconds) when provided`
27247
+ }],
27248
+ isError: true
27249
+ };
27250
+ const ceiling = workerWallClockCeilingMs();
27251
+ maxWallClockMs = Math.min(raw, ceiling);
27252
+ if (raw > ceiling) clampNote = `[note: maxWallClockMs ${raw} exceeds the per-call ceiling; clamped to ${ceiling} ms (the MCP tool-call timeout ${resolveMcpToolTimeoutMs()} ms minus the ${MCP_TIMEOUT_HEADROOM_MS} ms teardown headroom) so the worker aborts gracefully rather than being hard-killed mid-run.]
27253
+
27254
+ `;
27255
+ }
26989
27256
  const result = await runWorkerAgent({
26990
27257
  mode,
26991
27258
  prompt,
@@ -26993,12 +27260,13 @@ async function runWorkerToolCall(call) {
26993
27260
  model,
26994
27261
  thinking,
26995
27262
  worktree,
27263
+ maxWallClockMs,
26996
27264
  signal
26997
27265
  });
26998
27266
  return {
26999
27267
  content: [{
27000
27268
  type: "text",
27001
- text: result.text
27269
+ text: `${clampNote}${result.text}`
27002
27270
  }],
27003
27271
  isError: result.isError
27004
27272
  };
@@ -27200,5 +27468,5 @@ async function runStandInToolCall(args, signal) {
27200
27468
  }
27201
27469
 
27202
27470
  //#endregion
27203
- export { logStreamError as $, GITHUB_API_BASE_URL as $t, BROWSE_DEFAULT_MODEL as A, DEFAULT_PORT as At, toolbeltEnabled as B, tryRefreshAndRetry as Bt, repoFingerprint as C, shouldUseInsecureTls as Ct, trustRepo as D, DEFAULT_CLAUDE_MODEL_FALLBACKS as Dt, stopReviewStateDir as E, toolbeltPathOverride as Et, appendPlanReminder as F, getPackageVersion as Ft, searchWeb as G, isNullish as Gt, vscodeRipgrepPath as H, cacheModels as Ht, runWorkerAgent as I, withInstallLock as It, buildAdvisorStream as J, sleep as Jt, ADVISOR_INTERNAL_TOOL_NAME as K, resolveCodexModel as Kt, withNoOutputRetry as L, setupCopilotToken as Lt, IMPLEMENT_DEFAULT_MODEL as M, UPSTREAM_INACTIVITY_TIMEOUT_MS as Mt, PLAN_DEFAULT_MODEL as N, generateRandomPort as Nt, resolveSealedGate as O, DEFAULT_CODEX_MODEL as Ot, REVIEW_DEFAULT_MODEL as P, pickClaudeDefault as Pt, isControllerClosedError as Q, forwardError as Qt, availableToolCommands as R, setupGitHubAgentToken as Rt, isSubagentContext as S, extractZipMember as St, stopGateEnabledForRepo as T, collapsePathKeys as Tt, TOOLBELT_TOOLS$1 as U, cacheVSCodeVersion as Ut, toolbeltSkipSet as V, cacheCopilotVersion as Vt, assetFor as W, filterBetaHeader as Wt, isAdvisorRequested as X, fetchWithTransientRetry as Xt, injectAdvisorTool as Y, getModels as Yt, buildOpenAIErrorEvent as Z, HTTPError as Zt, stopReviewEnabled as _, parseJsonOrDiagnose as _t, buildPeerAwarenessSnippet as a, browseAgentEnabled as at, fileLastPromptStore as b, provisionAndIndexColbert as bt, buildSessionBindHookCommand as c, standInToolEnabled as ct, decideStopHook as d, createMessages as dt, copilotBaseUrl as en, readIteratorWithTimeout as et, fileBlockBudget as f, getTokenCount as ft, stopGateId as g, readResponseBodyCapped as gt, stopGateDisabled as h, MAX_RESPONSE_BODY_BYTES as ht, buildAgentPrompt as i, agentToolsEnabled as it, DEFAULT_MODEL as j, UPSTREAM_FETCH_TIMEOUT_MS as jt, liveExec as k, DEFAULT_CODEX_MODEL_FALLBACKS as kt, buildStopHookCommand as l, workerToolsEnabled as lt, launchBaselineKey as m, createChatCompletions as mt, MCP_GROUPS as n, githubHeaders as nn, handleMcpDelete as nt, personasFor as o, browserToolsEnabled as ot, injectStopHookIntoSettingsFile as p, createResponses as pt, ADVISOR_TOOL_INSTRUCTIONS as q, resolveModel as qt, assertMcpToolSurfaceConsistent as r, state as rn, handleMcpPost as rt, buildArtifactOpenHookCommand as s, fleetToolsEnabled as st, GROUP_META as t, copilotHeaders as tn, relayAnthropicStream as tt, captureLaunchBaseline as u, countTokens as ut, fileBaselineStore as v, provisionBrowserAssets as vt, repoRoot as w, ArtifactClient as wt, fileReviewDebounce as x, extractTarGzMember as xt, fileFindingsStore as y, hasSupportedBrowserInstalled as yt, buildToolbeltAwareness as z, setupGitHubToken as zt };
27204
- //# sourceMappingURL=peer-mcp-personas-DMM1akDa.js.map
27471
+ export { buildOpenAIErrorEvent as $, resolveModel as $t, BROWSE_DEFAULT_MODEL as A, ArtifactClient as At, buildToolbeltAwareness as B, pickClaudeDefault as Bt, repoFingerprint as C, parseJsonOrDiagnose as Ct, trustRepo as D, extractTarGzMember as Dt, stopReviewStateDir as E, provisionAndIndexColbert as Et, REVIEW_DEFAULT_MODEL as F, DEFAULT_CODEX_MODEL_FALLBACKS as Ft, assetFor as G, setupGitHubToken as Gt, toolbeltSkipSet as H, withInstallLock as Ht, appendPlanReminder as I, DEFAULT_PORT as It, ADVISOR_TOOL_INSTRUCTIONS as J, cacheModels as Jt, searchWeb as K, tryRefreshAndRetry as Kt, runWorkerAgent as L, UPSTREAM_FETCH_TIMEOUT_MS as Lt, EXPLORE_DEFAULT_MODEL as M, toolbeltPathOverride as Mt, IMPLEMENT_DEFAULT_MODEL as N, DEFAULT_CLAUDE_MODEL_FALLBACKS as Nt, resolveSealedGate as O, extractZipMember as Ot, PLAN_DEFAULT_MODEL as P, DEFAULT_CODEX_MODEL as Pt, buildAnthropicErrorEvent as Q, resolveCodexModel as Qt, withNoOutputRetry as R, UPSTREAM_INACTIVITY_TIMEOUT_MS as Rt, isSubagentContext as S, readResponseBodyCapped as St, stopGateEnabledForRepo as T, hasSupportedBrowserInstalled as Tt, vscodeRipgrepPath as U, setupCopilotToken as Ut, toolbeltEnabled as V, getPackageVersion as Vt, TOOLBELT_TOOLS$1 as W, setupGitHubAgentToken as Wt, injectAdvisorTool as X, filterBetaHeader as Xt, buildAdvisorStream as Y, cacheVSCodeVersion as Yt, isAdvisorRequested as Z, isNullish as Zt, stopReviewEnabled as _, resolveMcpToolTimeoutMs as _t, buildPeerAwarenessSnippet as a, GITHUB_API_BASE_URL as an, handleMcpPost as at, fileLastPromptStore as b, createChatCompletions as bt, buildSessionBindHookCommand as c, githubHeaders as cn, browserToolsEnabled as ct, decideStopHook as d, standInToolEnabled as dt, sleep as en, isControllerClosedError as et, fileBlockBudget as f, workerToolsEnabled as ft, stopGateId as g, assembleResponsesPayload as gt, stopGateDisabled as h, getTokenCount as ht, buildAgentPrompt as i, forwardError as in, handleMcpDelete as it, DEFAULT_MODEL as j, collapsePathKeys as jt, liveExec as k, shouldUseInsecureTls as kt, buildStopHookCommand as l, state as ln, fleetToolsEnabled as lt, launchBaselineKey as m, createMessages as mt, MCP_GROUPS as n, fetchWithTransientRetry as nn, readIteratorWithTimeout as nt, personasFor as o, copilotBaseUrl as on, agentToolsEnabled as ot, injectStopHookIntoSettingsFile as p, countTokens as pt, ADVISOR_INTERNAL_TOOL_NAME as q, cacheCopilotVersion as qt, assertMcpToolSurfaceConsistent as r, HTTPError as rn, relayAnthropicStream as rt, buildArtifactOpenHookCommand as s, copilotHeaders as sn, browseAgentEnabled as st, GROUP_META as t, getModels as tn, logStreamError as tt, captureLaunchBaseline as u, implementerSubagentModel as ut, fileBaselineStore as v, pickEndpoint as vt, repoRoot as w, provisionBrowserAssets as wt, fileReviewDebounce as x, MAX_RESPONSE_BODY_BYTES as xt, fileFindingsStore as y, createResponses as yt, availableToolCommands as z, generateRandomPort as zt };
27472
+ //# sourceMappingURL=peer-mcp-personas-Bfq3FPKc.js.map