github-router 0.3.152 → 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-D0tJ_tms.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-DyEXZu2z.js";
3
- import { i as registerExitHandlers, n as getInstanceUuid, r as recordWorkerRepo, t as WorktreeRegistry } from "./lifecycle-DGvk4z63.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-DhLJ9bLG.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 {}
@@ -26082,17 +26312,28 @@ function buildAgentPrompt(persona, opts) {
26082
26312
  * anchors disguised as description ("cheapest first move", "saves them
26083
26313
  * the discovery step", "waste wall-clock"). Pure capability inventory.
26084
26314
  *
26315
+ * Wording budget (minimal sufficient guidance, NOT sentence-count parity):
26316
+ * each tool/group gets only the wording needed for correct, safe, high-value
26317
+ * use; extra wording must earn its attention cost. "Importance" shows up via
26318
+ * cost-of-misuse / ambiguity / invocation complexity, not proportional length
26319
+ * (a critical-but-simple tool can be one clause). When editing this snippet or
26320
+ * any injected guidance, re-check the whole surface for balance rather than
26321
+ * only expanding whatever was last touched.
26322
+ *
26085
26323
  * Surface contract (regression-pinned in tests/peer-mcp-personas.test.ts):
26086
26324
  * - Always lists codex_critic, codex_reviewer, opus_critic, advisor,
26087
26325
  * peer-review-coordinator, and the subagent-inheritance fact (the
26088
26326
  * load-bearing UX claim: spawned subagents inherit the peer-MCP
26089
26327
  * toolset via the mirrored `.claude.json`).
26090
26328
  * - Conditionally lists gemini_critic only when `geminiAvailable`.
26091
- * - Conditionally lists worker_explore / worker_implement /
26092
- * "Workers themselves have code_search" only when
26093
- * `workerToolsAvailable` (mirrors `workerToolsEnabled()` in
26094
- * src/routes/mcp/handler.ts so the snippet never names a tool gated
26095
- * out of the live catalog).
26329
+ * - Conditionally lists the `worker-*` background dispatcher subagents
26330
+ * (worker-explore / worker-review / worker-plan / worker-implement /
26331
+ * worker-test), the non-blocking-guard fact, and "Workers themselves
26332
+ * have code_search" only when `workerToolsAvailable` (mirrors
26333
+ * `workerToolsEnabled()` so the snippet never names a surface gated out
26334
+ * of the live catalog). The raw `mcp__<workers>__*` tools are named only
26335
+ * as the guarded plumbing the dispatchers call, never as a main-agent
26336
+ * interface.
26096
26337
  * - Conditionally lists stand_in only when `standInAvailable`
26097
26338
  * (mirrors `standInToolEnabled()`).
26098
26339
  * - Conditionally lists gh-first-mate only when `agentToolsAvailable`
@@ -26120,7 +26361,7 @@ function buildPeerAwarenessSnippet(opts) {
26120
26361
  criticList.push("`opus_critic` (Opus 4.7)");
26121
26362
  const codexCliClause = opts.codexCli ? " `mcp__codex-cli__codex` dispatches to `codex-implementer` (gpt-5.3-codex with workspace-write) for end-to-end coding tasks." : "";
26122
26363
  const para2Parts = [`\`mcp__${searchKey}__code\` is the one-stop code search (no extra model call). Its DEFAULT mode (or \`mode:"semantic"\`) ranks by MEANING via ColBERT over a per-workspace index, the first thing to reach for on intent/concept questions ("where is retry/backoff handled", "how does auth work"); when that index isn't ready it transparently falls back to lexical (the response \`source\` says which engine ran). Forced modes cover the rest: \`lexical\` (BM25F-ranked + tree-sitter, best for exact symbols), \`exact\`, \`regex\`, \`complete\` (exhaustive set), \`ast_pattern\`+\`ast_lang\` for multi-line AST shapes, \`scan\` for a whole-workspace symbol outline, \`multiline\` for cross-line regex. Multiple queries can run in a single turn. The index covers code-shaped files; for unstructured files (logs, \`.csv\`, \`.env*\`, config-only wiring), \`grep\`/\`glob\` still apply.`];
26123
- if (opts.workerToolsAvailable) para2Parts.push(`\`mcp__${workersKey}__explore\` runs a Gemini-backed read-only worker that returns a summary, using its own context rather than yours; concurrent launches share the \`MAX_INFLIGHT_TOOLS_CALL\` cap (default 128) with operator traffic.`, `\`mcp__${workersKey}__review\` is the same worker framed as a code reviewer that reads the code itself to verify a change or claim, reporting findings with severity, so it checks context the \`peers\` critics (stateless calls on the pasted artifact) cannot.`, `\`mcp__${workersKey}__plan\` is the same read-only worker framed as a planner: from a task + acceptance criteria it returns an ordered implementation plan.`, `\`mcp__${workersKey}__implement\` is the same worker with edit/write/bash; \`worktree: true\` runs it in an isolated git worktree and returns the diff.`, `\`mcp__${workersKey}__test\` is a write-capable worker framed as an independent test author: it authors tests that try to break the implementation and reports pass/fail, never editing the implementation to make them pass.`, "Workers themselves have `code_search` in their toolset.");
26364
+ if (opts.workerToolsAvailable) para2Parts.push(`\`worker-*\` are background Agent subagents (subagent_type) that run the matching worker in its own context and deliver the result as a completion notification, so a long run never blocks the turn: \`worker-explore\` (read-only research), \`worker-review\` (reads the code to verify a change or claim), \`worker-plan\` (ordered implementation plan), \`worker-implement\` (edit/write/bash; \`worktree: true\` isolates in a git worktree and returns the diff), \`worker-test\` (independent test author). The raw \`mcp__${workersKey}__*\` tools they call are guarded (a direct main-thread call is redirected to the matching agent); Workers themselves have \`code_search\`.`);
26124
26365
  if (opts.workerToolsAvailable) para2Parts.push(`\`mcp__${orchestrateKey}__decompose\` composes an open-ended ask into a typed, VERIFIED workflow IR (a strong driver decorrelated by a cross-lab critic, so the decompose step isn't a single point of failure), and \`mcp__${orchestrateKey}__run_workflow\` executes that IR through a frozen kernel delivering max(orchestrated, baseline) over a sealed executable gate, so it never ships worse than a plain single-model run. \`mcp__${orchestrateKey}__verify_workflow\` checks an IR's floor invariants before you run it, and \`mcp__${orchestrateKey}__attest_step\` audits that a finished run's producers were each checked by a different lab. They suit non-trivial, role-separated asks; a trivial ask does not need them.`);
26125
26366
  else para2Parts.push(`\`mcp__${orchestrateKey}__verify_workflow\` statically checks a workflow IR's floor invariants and \`mcp__${orchestrateKey}__attest_step\` audits a run's cross-lab lineage (the \`decompose\`/\`run_workflow\` composer + kernel need the worker backend, unavailable here).`);
26126
26367
  if (opts.workerToolsAvailable) {
@@ -26358,7 +26599,7 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
26358
26599
  toolNameHttp: "explore",
26359
26600
  group: "workers",
26360
26601
  capability: "worker",
26361
- description: "Read-only investigation by an autonomous worker (Pi runtime; default model `gpt-5.4-mini` at xhigh reasoning, override via the `model` arg with any Copilot-catalog model that advertises `tool_calls`). Tools: read, glob, grep, code_search (semantic-first), web_search, fetch_url, advisor (consult a stronger cross-lab model), update_plan (planning checklist), and toolbelt (run a read-only analysis CLI: rg/fd/jq/yq/sg/gron/tokei/difft/git). The worker's system prompt sandboxes it and gives one-line descriptions of each tool, so brief it on the investigation, not on tool semantics. Offloads bounded research that would otherwise eat your context window — the worker plans its own tool calls and returns a single text answer. Examples: \"find files matching X then summarize\", \"how does library Y handle Z\", \"survey this codebase for usages of deprecated API\".",
26602
+ description: "Runs as the background `worker-explore` agent. Dispatch via the Agent tool (subagent_type: worker-explore) so your turn is never blocked; the result arrives as a completion notification. Read-only investigation by an autonomous worker (Pi runtime; default model `gpt-5.4-mini` at xhigh reasoning, override via the `model` arg with any Copilot-catalog model that advertises `tool_calls`). Tools: read, glob, grep, code_search (semantic-first), web_search, fetch_url, advisor (consult a stronger cross-lab model), update_plan (planning checklist), and toolbelt (run a read-only analysis CLI: rg/fd/jq/yq/sg/gron/tokei/difft/git). The worker's system prompt sandboxes it and gives one-line descriptions of each tool, so brief it on the investigation, not on tool semantics. Offloads bounded research that would otherwise eat your context window — the worker plans its own tool calls and returns a single text answer. Examples: \"find files matching X then summarize\", \"how does library Y handle Z\", \"survey this codebase for usages of deprecated API\".",
26362
26603
  inputSchema: {
26363
26604
  type: "object",
26364
26605
  required: ["prompt"],
@@ -26387,6 +26628,10 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
26387
26628
  workspace: {
26388
26629
  type: "string",
26389
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."
26390
26635
  }
26391
26636
  }
26392
26637
  },
@@ -26402,7 +26647,7 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
26402
26647
  toolNameHttp: "implement",
26403
26648
  group: "workers",
26404
26649
  capability: "worker",
26405
- description: "Delegates a scoped coding task to an autonomous worker (Pi runtime; default model `gpt-5.5` at xhigh reasoning, override via the `model` arg with any Copilot-catalog model that advertises `tool_calls`). Tools: the explore read-only set (read, glob, grep, code_search, web_search, fetch_url, advisor, update_plan, toolbelt) plus edit, write, bash, and codex_review (code review by codex-reviewer / gpt-5.3-codex). The worker's system prompt sandboxes it and gives one-line descriptions of each tool, so brief it on the task, not on tool semantics. With `worktree: false` (default) edits in place — concurrent worker_implement calls and Claude's own edits to the same files will race. With `worktree: true` runs in an isolated git worktree and returns the diff for review. HARD ERROR if true and the workspace is not a git repository.",
26650
+ description: "Runs as the background `worker-implement` agent. Dispatch via the Agent tool (subagent_type: worker-implement) so your turn is never blocked; the result arrives as a completion notification. Delegates a scoped coding task to an autonomous worker (Pi runtime; default model `gpt-5.5` at xhigh reasoning, override via the `model` arg with any Copilot-catalog model that advertises `tool_calls`). Tools: the explore read-only set (read, glob, grep, code_search, web_search, fetch_url, advisor, update_plan, toolbelt) plus edit, write, bash, and codex_review (code review by codex-reviewer / gpt-5.3-codex). The worker's system prompt sandboxes it and gives one-line descriptions of each tool, so brief it on the task, not on tool semantics. With `worktree: false` (default) edits in place — concurrent worker_implement calls and Claude's own edits to the same files will race. With `worktree: true` runs in an isolated git worktree and returns the diff for review. HARD ERROR if true and the workspace is not a git repository.",
26406
26651
  inputSchema: {
26407
26652
  type: "object",
26408
26653
  required: ["prompt"],
@@ -26435,6 +26680,10 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
26435
26680
  workspace: {
26436
26681
  type: "string",
26437
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."
26438
26687
  }
26439
26688
  }
26440
26689
  },
@@ -26450,7 +26699,7 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
26450
26699
  toolNameHttp: "review",
26451
26700
  group: "workers",
26452
26701
  capability: "worker",
26453
- description: "Read-only code review by an autonomous worker (Pi runtime; default model `gpt-5.5` at xhigh reasoning, override via `model` with any Copilot-catalog model that advertises `tool_calls`). Same read-only toolset as `explore` (read, glob, grep, code_search, web_search, fetch_url, advisor, update_plan, toolbelt) — it CANNOT edit — but the worker is framed as a reviewer: it verifies correctness against the actual code itself rather than trusting a claim, and reports findings (bugs, edge cases, security / concurrency / resource risks, missing handling) with a severity and `file:line`. Brief it with the change / diff / claim to verify (paste it, or name the files) — it reads the code to confirm, so you get a self-verifying second opinion that doesn't depend on you having pre-extracted the relevant code. Unlike the `peers` critics (single stateless model calls on the artifact you paste), this worker can navigate the repo to check surrounding context for itself.",
26702
+ description: "Runs as the background `worker-review` agent. Dispatch via the Agent tool (subagent_type: worker-review) so your turn is never blocked; the result arrives as a completion notification. Read-only code review by an autonomous worker (Pi runtime; default model `gpt-5.5` at xhigh reasoning, override via `model` with any Copilot-catalog model that advertises `tool_calls`). Same read-only toolset as `explore` (read, glob, grep, code_search, web_search, fetch_url, advisor, update_plan, toolbelt) — it CANNOT edit — but the worker is framed as a reviewer: it verifies correctness against the actual code itself rather than trusting a claim, and reports findings (bugs, edge cases, security / concurrency / resource risks, missing handling) with a severity and `file:line`. Brief it with the change / diff / claim to verify (paste it, or name the files) — it reads the code to confirm, so you get a self-verifying second opinion that doesn't depend on you having pre-extracted the relevant code. Unlike the `peers` critics (single stateless model calls on the artifact you paste), this worker can navigate the repo to check surrounding context for itself.",
26454
26703
  inputSchema: {
26455
26704
  type: "object",
26456
26705
  required: ["prompt"],
@@ -26479,6 +26728,10 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
26479
26728
  workspace: {
26480
26729
  type: "string",
26481
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."
26482
26735
  }
26483
26736
  }
26484
26737
  },
@@ -26494,7 +26747,7 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
26494
26747
  toolNameHttp: "plan",
26495
26748
  group: "workers",
26496
26749
  capability: "worker",
26497
- description: "Read-only implementation planning by an autonomous worker (Pi runtime; default model `claude-opus-4.8`, override via `model` with any Copilot-catalog model that advertises `tool_calls`). Same read-only toolset as `explore` (read, glob, grep, code_search, web_search, fetch_url, advisor, update_plan, toolbelt) — it CANNOT edit — but the worker is framed as a planner: from the task and acceptance criteria it produces a concrete, ordered implementation plan (the files to change, the approach, the key risks, and how each acceptance criterion will be verified), grounded by reading the actual code. Brief it with the task and any acceptance criteria; it returns a single plan, not code.",
26750
+ description: "Runs as the background `worker-plan` agent. Dispatch via the Agent tool (subagent_type: worker-plan) so your turn is never blocked; the result arrives as a completion notification. Read-only implementation planning by an autonomous worker (Pi runtime; default model `claude-opus-4.8`, override via `model` with any Copilot-catalog model that advertises `tool_calls`). Same read-only toolset as `explore` (read, glob, grep, code_search, web_search, fetch_url, advisor, update_plan, toolbelt) — it CANNOT edit — but the worker is framed as a planner: from the task and acceptance criteria it produces a concrete, ordered implementation plan (the files to change, the approach, the key risks, and how each acceptance criterion will be verified), grounded by reading the actual code. Brief it with the task and any acceptance criteria; it returns a single plan, not code.",
26498
26751
  inputSchema: {
26499
26752
  type: "object",
26500
26753
  required: ["prompt"],
@@ -26523,6 +26776,10 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
26523
26776
  workspace: {
26524
26777
  type: "string",
26525
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."
26526
26783
  }
26527
26784
  }
26528
26785
  },
@@ -26538,7 +26795,7 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
26538
26795
  toolNameHttp: "test",
26539
26796
  group: "workers",
26540
26797
  capability: "worker",
26541
- description: "Independent adversarial test authoring by an autonomous worker (Pi runtime; default model `gpt-5.5` at xhigh reasoning, override via `model` with any Copilot-catalog model that advertises `tool_calls`). Same read+write toolset as `implement` (the explore set plus edit, write, bash, codex_review). The worker is framed as an INDEPENDENT test author that did NOT write the code under test: from the task and acceptance criteria it writes tests that try to BREAK the implementation (edge cases, error paths, the acceptance criteria as executable checks), runs them, and reports which pass and fail — it does NOT modify the implementation to make tests pass. With `worktree: true` runs in an isolated git worktree and returns the diff; HARD ERROR if true and the workspace is not a git repository.",
26798
+ description: "Runs as the background `worker-test` agent. Dispatch via the Agent tool (subagent_type: worker-test) so your turn is never blocked; the result arrives as a completion notification. Independent adversarial test authoring by an autonomous worker (Pi runtime; default model `gpt-5.5` at xhigh reasoning, override via `model` with any Copilot-catalog model that advertises `tool_calls`). Same read+write toolset as `implement` (the explore set plus edit, write, bash, codex_review). The worker is framed as an INDEPENDENT test author that did NOT write the code under test: from the task and acceptance criteria it writes tests that try to BREAK the implementation (edge cases, error paths, the acceptance criteria as executable checks), runs them, and reports which pass and fail — it does NOT modify the implementation to make tests pass. With `worktree: true` runs in an isolated git worktree and returns the diff; HARD ERROR if true and the workspace is not a git repository.",
26542
26799
  inputSchema: {
26543
26800
  type: "object",
26544
26801
  required: ["prompt"],
@@ -26571,6 +26828,10 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
26571
26828
  workspace: {
26572
26829
  type: "string",
26573
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."
26574
26835
  }
26575
26836
  }
26576
26837
  },
@@ -26785,7 +27046,7 @@ const NON_PERSONA_MCP_TOOLS = Object.freeze([
26785
27046
  toolNameHttp: "browse",
26786
27047
  group: "workers",
26787
27048
  capability: "browse_agent",
26788
- description: "A Pi-driven autonomous browser agent (gpt-5.4-mini) that drives a real browser to accomplish `task` and returns the result. Runs in its own context to preserve the lead's window (raw DOM / page snapshots stay inside the agent). Pass `sessionId` to continue a prior session (its id is returned appended to the result as `[browse session: <id>]`); omit it for a fresh isolated session. Multiple concurrent calls run as parallel sessions on the one shared browser. Examples: \"find the cheapest flight LHR-JFK next Tuesday\", \"log into the dashboard and read the current MRR\", \"summarize the top 3 HN front-page stories\".",
27049
+ description: "Runs as the background `worker-browse` agent. Dispatch via the Agent tool (subagent_type: worker-browse) so your turn is never blocked; the result arrives as a completion notification. A Pi-driven autonomous browser agent (gpt-5.4-mini) that drives a real browser to accomplish `task` and returns the result. Runs in its own context to preserve the lead's window (raw DOM / page snapshots stay inside the agent). Pass `sessionId` to continue a prior session (its id is returned appended to the result as `[browse session: <id>]`); omit it for a fresh isolated session. Multiple concurrent calls run as parallel sessions on the one shared browser. Examples: \"find the cheapest flight LHR-JFK next Tuesday\", \"log into the dashboard and read the current MRR\", \"summarize the top 3 HN front-page stories\".",
26789
27050
  inputSchema: {
26790
27051
  type: "object",
26791
27052
  required: ["task"],
@@ -26975,6 +27236,23 @@ async function runWorkerToolCall(call) {
26975
27236
  };
26976
27237
  workspace = args.workspace;
26977
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
+ }
26978
27256
  const result = await runWorkerAgent({
26979
27257
  mode,
26980
27258
  prompt,
@@ -26982,12 +27260,13 @@ async function runWorkerToolCall(call) {
26982
27260
  model,
26983
27261
  thinking,
26984
27262
  worktree,
27263
+ maxWallClockMs,
26985
27264
  signal
26986
27265
  });
26987
27266
  return {
26988
27267
  content: [{
26989
27268
  type: "text",
26990
- text: result.text
27269
+ text: `${clampNote}${result.text}`
26991
27270
  }],
26992
27271
  isError: result.isError
26993
27272
  };
@@ -27189,5 +27468,5 @@ async function runStandInToolCall(args, signal) {
27189
27468
  }
27190
27469
 
27191
27470
  //#endregion
27192
- export { logStreamError as $, copilotBaseUrl as $t, BROWSE_DEFAULT_MODEL as A, UPSTREAM_FETCH_TIMEOUT_MS as At, toolbeltEnabled as B, cacheCopilotVersion as Bt, repoFingerprint as C, ArtifactClient as Ct, trustRepo as D, DEFAULT_CODEX_MODEL as Dt, stopReviewStateDir as E, DEFAULT_CLAUDE_MODEL_FALLBACKS as Et, appendPlanReminder as F, withInstallLock as Ft, searchWeb as G, resolveCodexModel as Gt, vscodeRipgrepPath as H, cacheVSCodeVersion as Ht, runWorkerAgent as I, setupCopilotToken as It, buildAdvisorStream as J, getModels as Jt, ADVISOR_INTERNAL_TOOL_NAME as K, resolveModel as Kt, withNoOutputRetry as L, setupGitHubAgentToken as Lt, IMPLEMENT_DEFAULT_MODEL as M, generateRandomPort as Mt, PLAN_DEFAULT_MODEL as N, pickClaudeDefault as Nt, resolveSealedGate as O, DEFAULT_CODEX_MODEL_FALLBACKS as Ot, REVIEW_DEFAULT_MODEL as P, getPackageVersion as Pt, isControllerClosedError as Q, GITHUB_API_BASE_URL as Qt, availableToolCommands as R, setupGitHubToken as Rt, isSubagentContext as S, shouldUseInsecureTls as St, stopGateEnabledForRepo as T, toolbeltPathOverride as Tt, TOOLBELT_TOOLS$1 as U, filterBetaHeader as Ut, toolbeltSkipSet as V, cacheModels as Vt, assetFor as W, isNullish as Wt, isAdvisorRequested as X, HTTPError as Xt, injectAdvisorTool as Y, fetchWithTransientRetry as Yt, buildOpenAIErrorEvent as Z, forwardError as Zt, stopReviewEnabled as _, provisionBrowserAssets as _t, buildPeerAwarenessSnippet as a, browserToolsEnabled as at, fileLastPromptStore as b, extractTarGzMember as bt, buildSessionBindHookCommand as c, workerToolsEnabled as ct, decideStopHook as d, getTokenCount as dt, copilotHeaders as en, readIteratorWithTimeout as et, fileBlockBudget as f, createResponses as ft, stopGateId as g, parseJsonOrDiagnose as gt, stopGateDisabled as h, readResponseBodyCapped as ht, buildAgentPrompt as i, agentToolsEnabled as it, DEFAULT_MODEL as j, UPSTREAM_INACTIVITY_TIMEOUT_MS as jt, liveExec as k, DEFAULT_PORT as kt, buildStopHookCommand as l, countTokens as lt, launchBaselineKey as m, MAX_RESPONSE_BODY_BYTES as mt, MCP_GROUPS as n, state as nn, handleMcpDelete as nt, personasFor as o, fleetToolsEnabled as ot, injectStopHookIntoSettingsFile as p, createChatCompletions as pt, ADVISOR_TOOL_INSTRUCTIONS as q, sleep as qt, assertMcpToolSurfaceConsistent as r, handleMcpPost as rt, buildArtifactOpenHookCommand as s, standInToolEnabled as st, GROUP_META as t, githubHeaders as tn, relayAnthropicStream as tt, captureLaunchBaseline as u, createMessages as ut, fileBaselineStore as v, hasSupportedBrowserInstalled as vt, repoRoot as w, collapsePathKeys as wt, fileReviewDebounce as x, extractZipMember as xt, fileFindingsStore as y, provisionAndIndexColbert as yt, buildToolbeltAwareness as z, tryRefreshAndRetry as zt };
27193
- //# sourceMappingURL=peer-mcp-personas-BQVOxB1i.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