pullfrog 0.1.74 → 0.1.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -28027,7 +28027,7 @@ var require_cross_spawn = __commonJS({
28027
28027
  enoent.hookChildProcess(spawned, parsed2);
28028
28028
  return spawned;
28029
28029
  }
28030
- function spawnSync7(command, args2, options) {
28030
+ function spawnSync8(command, args2, options) {
28031
28031
  const parsed2 = parse6(command, args2, options);
28032
28032
  const result = cp.spawnSync(parsed2.command, parsed2.args, parsed2.options);
28033
28033
  result.error = result.error || enoent.verifyENOENTSync(result.status, parsed2);
@@ -28035,7 +28035,7 @@ var require_cross_spawn = __commonJS({
28035
28035
  }
28036
28036
  module.exports = spawn4;
28037
28037
  module.exports.spawn = spawn4;
28038
- module.exports.sync = spawnSync7;
28038
+ module.exports.sync = spawnSync8;
28039
28039
  module.exports._parse = parse6;
28040
28040
  module.exports._enoent = enoent;
28041
28041
  }
@@ -108971,6 +108971,17 @@ var OPENAI_COMPATIBLE_API_KEY_ENV = "OPENAI_COMPATIBLE_API_KEY";
108971
108971
  var OPENAI_COMPATIBLE_MODEL_ENV = "OPENAI_COMPATIBLE_MODEL";
108972
108972
  var OPENAI_COMPATIBLE_CONTEXT_ENV = "OPENAI_COMPATIBLE_CONTEXT";
108973
108973
  var OPENAI_COMPATIBLE_MAX_OUTPUT_ENV = "OPENAI_COMPATIBLE_MAX_OUTPUT";
108974
+ var PROVIDER_GATEWAY_URL_ENV = {
108975
+ anthropic: "ANTHROPIC_BASE_URL",
108976
+ openai: "OPENAI_BASE_URL"
108977
+ };
108978
+ function getProviderGatewayUrl(specifier) {
108979
+ const slashIndex = specifier?.indexOf("/") ?? -1;
108980
+ if (!specifier || slashIndex <= 0) return void 0;
108981
+ const envVar = PROVIDER_GATEWAY_URL_ENV[specifier.slice(0, slashIndex)];
108982
+ if (!envVar) return void 0;
108983
+ return process.env[envVar]?.trim().replace(/\/$/, "") || void 0;
108984
+ }
108974
108985
  function isBedrockAnthropicId(bedrockModelId) {
108975
108986
  return bedrockModelId.toLowerCase().split(/[./:]/).includes("anthropic");
108976
108987
  }
@@ -109795,7 +109806,7 @@ var import_semver = __toESM(require_semver2(), 1);
109795
109806
  // package.json
109796
109807
  var package_default = {
109797
109808
  name: "pullfrog",
109798
- version: "0.1.74",
109809
+ version: "0.1.76",
109799
109810
  cliContractVersion: "1",
109800
109811
  type: "module",
109801
109812
  bin: {
@@ -110594,26 +110605,24 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
110594
110605
 
110595
110606
  2. Checkout the PR branch via \`${t("checkout_pr")}\`.
110596
110607
 
110597
- 3. Fetch review comments via \`${t("get_review_comments")}\`.
110608
+ 3. Fetch review comments via \`${t("get_review_comments")}\` and read the full conversation in each relevant thread.
110598
110609
 
110599
- 4. For each comment:
110600
- - understand the feedback
110610
+ 4. For each thread:
110611
+ - **check intent first**: questions, explanations, and disagreement call for a reply, not a code change. Change code only when explicitly requested. Automatic review feedback on Pullfrog-authored PRs (\`address_scope: all\`) remains actionable without a separate request; later replies can narrow or withdraw that permission.
110612
+ - under \`address_scope: mentions\`, unmentioned threads are context only unless the user's review-body request explicitly includes them
110601
110613
  - **verify the finding yourself** against the actual code before deciding whether to apply \u2014 every comment (human or agent) is a hypothesis, not a directive. agent reviewers especially are fallible.
110602
- - you are searching for a solution that is **complete, minimal, and elegant** \u2014 you may need to think hard to find it. do not over-engineer, do not be over-defensive, **do not write AI slop**. reviewers bias toward *recommending additions*, and that bias has a recognizable slop texture: defensive checks for impossible cases, extra abstractions used once, comments restating obvious code, tests asserting tautologies, "just-in-case" guards, error handlers for cases the type system already rules out. reject those. evaluate whether applying the finding would leave the code more **sound, correct, AND elegant**; two-out-of-three is a signal to look harder for a fix that gets all three. if a request would add bloat \u2014 ceremony without commensurate correctness benefit \u2014 push back in your reply rather than mechanically applying it.
110603
- - if the request stands, make the code change using your native tools; otherwise reply explaining why
110614
+ - keep requested changes **sound, minimal, and elegant**. Reject defensive checks for impossible cases, single-use abstractions, redundant comments, and tautological tests rather than mechanically applying reviewer suggestions.
110615
+ - if intent permits a code change and the finding stands, implement it using your native tools; otherwise reply without editing
110604
110616
  - record what was done (or why nothing was done)
110605
110617
 
110606
110618
  5. Quality check:
110607
- - re-call \`${t("get_review_comments")}\` with \`fresh: true\` before committing: reviewers keep commenting while you work, and any thread it now returns that you have not addressed is in scope for this run.
110608
- - test changes, then review the diff before committing \u2014 verify only intended changes are present, no debug artifacts remain, no fix turned out to be bloat in context (revert any that did), and the changes are clean enough that a senior engineer would approve without hesitation
110609
- - ${commitStep}
110619
+ - re-call \`${t("get_review_comments")}\` with \`fresh: true\` before finishing, even when only replying. Apply the same scope and intent checks to new comments; respect requests to stop or change direction before committing.
110620
+ - if code changed, test and review the diff, remove unintended changes or bloat, then ${commitStep}
110610
110621
 
110611
- 6. Finalize. Reply + resolve are paired write actions: do BOTH or NEITHER for each thread.
110612
- - ${finalizeStep} (same push/prepush guidance as Build mode in *SYSTEM*)
110613
- - **if the push/commit fails**, call \`${t("report_progress")}\` with the exact error and STOP \u2014 do NOT reply or resolve any thread until the fix is live on the remote. Resolving a thread without the fix landing misleads the reviewer.
110614
- - **once the fix is live on the remote**, for each thread you acted on:
110615
- - reply ONCE via \`${t("reply_to_review_comment")}\`. The \`comment_id\` parameter takes the root comment's numeric \`id=\` (from the first \`comment author=...\` tag in the \`${t("get_review_comments")}\` output) \u2014 NOT the \`thread=\` value; that's a separate GraphQL ID used by resolve. The runtime dedupes identical bodies within a session.
110616
- - **immediately** call \`${t("resolve_review_thread")}\` with that thread's \`thread=\` value as \`thread_id\`. Resolve every thread where you (a) made the requested code change in full \u2014 partial fixes leave the thread open \u2014 OR (b) replied with a substantive answer the user explicitly asked for. Do NOT resolve threads where you pushed back on the request and the disagreement is unresolved; leave those open for the human to mediate.
110622
+ 6. Finalize:
110623
+ - **only if code changed**, ${finalizeStep} (same push/prepush guidance as Build mode in *SYSTEM*). If push/commit fails, report the exact error and STOP without claiming a fix or resolving its threads.
110624
+ - reply ONCE per relevant thread via \`${t("reply_to_review_comment")}\`, including when no code changed. The \`comment_id\` is the root comment's numeric \`id=\`, NOT the \`thread=\` value from \`${t("get_review_comments")}\`. The runtime dedupes identical bodies within a session.
110625
+ - resolve a thread via \`${t("resolve_review_thread")}\` (using \`thread=\` as \`thread_id\`) only after its requested fix is fully on the remote, or the user explicitly asks to resolve it. Leave discussion-only replies, partial fixes, and disagreements open.
110617
110626
  - call \`${t("report_progress")}\` with a brief summary. If your last \`${t("get_review_comments")}\` turned up threads you did not address, name them there \u2014 a review arriving this late is suppressed as a duplicate and nothing else will pick it up.`
110618
110627
  },
110619
110628
  // Review and IncrementalReview route the minimum reviewfrog specialists
@@ -112514,6 +112523,19 @@ function warnIfNativeEditsUnavailable(ctx) {
112514
112523
  "\xBB this run cannot change files: codex's native write tool depends on bubblewrap, which Ubuntu 24.04 blocks (https://github.com/openai/codex/issues/29908), and the MCP shell is disabled for this repo. read-only work is unaffected \u2014 set `shell: restricted` if the agent needs to edit anything."
112515
112524
  );
112516
112525
  }
112526
+ var CODEX_GATEWAY_PROVIDER = "pullfrog-gateway";
112527
+ function gatewayProvider(gatewayUrl) {
112528
+ if (!gatewayUrl) return [];
112529
+ return [
112530
+ `[model_providers.${CODEX_GATEWAY_PROVIDER}]`,
112531
+ 'name = "OpenAI gateway"',
112532
+ `base_url = ${JSON.stringify(gatewayUrl)}`,
112533
+ 'wire_api = "responses"',
112534
+ // where the harness puts the run's OPENAI_API_KEY
112535
+ 'env_key = "CODEX_API_KEY"',
112536
+ ""
112537
+ ];
112538
+ }
112517
112539
  function writeCodexConfig(params) {
112518
112540
  const shellEnabled = params.ctx.payload.shell === "enabled";
112519
112541
  const sandboxMode = params.ctx.payload.push === "disabled" ? "read-only" : "workspace-write";
@@ -112530,6 +112552,7 @@ function writeCodexConfig(params) {
112530
112552
  'web_search = "live"',
112531
112553
  ...params.model ? [`model = ${JSON.stringify(params.model)}`] : [],
112532
112554
  ...params.effortRung ? [`model_reasoning_effort = ${JSON.stringify(params.effortRung)}`] : [],
112555
+ ...params.gatewayUrl ? [`model_provider = ${JSON.stringify(CODEX_GATEWAY_PROVIDER)}`] : [],
112533
112556
  "",
112534
112557
  "[features]",
112535
112558
  tomlBool(CODEX_DISABLED_FEATURES, false),
@@ -112559,6 +112582,7 @@ function writeCodexConfig(params) {
112559
112582
  "tool_timeout_sec = 660",
112560
112583
  "",
112561
112584
  ...repoMcpApprovals(repoDir),
112585
+ ...gatewayProvider(params.gatewayUrl),
112562
112586
  // UNTRUSTED, deliberately, and this one line is the whole config-precedence
112563
112587
  // boundary. Codex loads `<repo>/.codex/config.toml` as a layer that
112564
112588
  // OUTRANKS ours (project 25 vs user 20), and its denylist strips none of
@@ -112827,7 +112851,11 @@ var codex = agent({
112827
112851
  ctx,
112828
112852
  codexHome,
112829
112853
  model: resolveCodexModel(ctx),
112830
- effortRung: effort.rung && CODEX_EFFORTS.includes(effort.rung) ? effort.rung : void 0
112854
+ effortRung: effort.rung && CODEX_EFFORTS.includes(effort.rung) ? effort.rung : void 0,
112855
+ // keyed off OpenAI, not the resolved model: codex serves nothing else, an
112856
+ // auto-selected run has no model, and a ChatGPT-subscription token is
112857
+ // valid only at ChatGPT's own backend
112858
+ gatewayUrl: codexHomeAuth ? void 0 : getProviderGatewayUrl("openai/")
112831
112859
  });
112832
112860
  if (codexHomeAuth) {
112833
112861
  core3.saveState(
@@ -118594,6 +118622,14 @@ function openAICompatibleProvider(model) {
118594
118622
  }
118595
118623
  };
118596
118624
  }
118625
+ var OPENCODE_GATEWAY_PATH = { anthropic: "/v1" };
118626
+ function providerGatewayOverride(model) {
118627
+ const gatewayUrl = getProviderGatewayUrl(model);
118628
+ if (!model || !gatewayUrl) return {};
118629
+ const provider2 = model.slice(0, model.indexOf("/"));
118630
+ const baseURL = `${gatewayUrl}${OPENCODE_GATEWAY_PATH[provider2] ?? ""}`;
118631
+ return { [provider2]: { options: { baseURL } } };
118632
+ }
118597
118633
  function azureProvider(model) {
118598
118634
  const prefix = `${AZURE_PROVIDER}/`;
118599
118635
  if (!model?.startsWith(prefix)) return {};
@@ -118709,7 +118745,8 @@ function buildSecurityConfig(ctx, model) {
118709
118745
  provider: {
118710
118746
  openrouter: { models: kimiOpenRouterProviderOverrides() },
118711
118747
  ...openAICompatibleProvider(model),
118712
- ...azureProvider(model)
118748
+ ...azureProvider(model),
118749
+ ...providerGatewayOverride(model)
118713
118750
  }
118714
118751
  };
118715
118752
  if (model) {
@@ -176983,8 +177020,8 @@ function closeBrowserDaemon(toolState) {
176983
177020
 
176984
177021
  // mcp/checkout.ts
176985
177022
  import { createHash as createHash2 } from "node:crypto";
176986
- import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync9 } from "node:fs";
176987
- import { join as join14 } from "node:path";
177023
+ import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync10 } from "node:fs";
177024
+ import { join as join15 } from "node:path";
176988
177025
 
176989
177026
  // node_modules/.pnpm/@ark+schema@0.56.0/node_modules/@ark/schema/out/shared/errors.js
176990
177027
  var ArkError = class _ArkError extends CastableBase {
@@ -185256,6 +185293,960 @@ async function $gitFetchWithDeepen(args2, options, label) {
185256
185293
  }
185257
185294
  }
185258
185295
 
185296
+ // utils/lifecycle.ts
185297
+ import { spawnSync as spawnSync7 } from "node:child_process";
185298
+
185299
+ // mcp/shell.ts
185300
+ import { spawn as spawn3, spawnSync as spawnSync6 } from "node:child_process";
185301
+ import { randomUUID as randomUUID3 } from "node:crypto";
185302
+ import { closeSync, openSync, writeFileSync as writeFileSync8 } from "node:fs";
185303
+ import { userInfo as userInfo2 } from "node:os";
185304
+ import { join as join10 } from "node:path";
185305
+ import { setTimeout as sleep2 } from "node:timers/promises";
185306
+
185307
+ // node_modules/.pnpm/@toon-format+toon@1.4.0/node_modules/@toon-format/toon/dist/index.mjs
185308
+ var LIST_ITEM_MARKER = "-";
185309
+ var LIST_ITEM_PREFIX = "- ";
185310
+ var COMMA = ",";
185311
+ var PIPE = "|";
185312
+ var DOT = ".";
185313
+ var NULL_LITERAL = "null";
185314
+ var TRUE_LITERAL = "true";
185315
+ var FALSE_LITERAL = "false";
185316
+ var BACKSLASH = "\\";
185317
+ var DOUBLE_QUOTE = '"';
185318
+ var TAB = " ";
185319
+ var DELIMITERS = {
185320
+ comma: COMMA,
185321
+ tab: TAB,
185322
+ pipe: PIPE
185323
+ };
185324
+ var DEFAULT_DELIMITER = DELIMITERS.comma;
185325
+ function escapeString(value2) {
185326
+ return value2.replace(/\\/g, `${BACKSLASH}${BACKSLASH}`).replace(/"/g, `${BACKSLASH}${DOUBLE_QUOTE}`).replace(/\n/g, `${BACKSLASH}n`).replace(/\r/g, `${BACKSLASH}r`).replace(/\t/g, `${BACKSLASH}t`);
185327
+ }
185328
+ function isBooleanOrNullLiteral(token) {
185329
+ return token === TRUE_LITERAL || token === FALSE_LITERAL || token === NULL_LITERAL;
185330
+ }
185331
+ function normalizeValue(value2) {
185332
+ if (value2 === null) return null;
185333
+ if (typeof value2 === "string" || typeof value2 === "boolean") return value2;
185334
+ if (typeof value2 === "number") {
185335
+ if (Object.is(value2, -0)) return 0;
185336
+ if (!Number.isFinite(value2)) return null;
185337
+ return value2;
185338
+ }
185339
+ if (typeof value2 === "bigint") {
185340
+ if (value2 >= Number.MIN_SAFE_INTEGER && value2 <= Number.MAX_SAFE_INTEGER) return Number(value2);
185341
+ return value2.toString();
185342
+ }
185343
+ if (value2 instanceof Date) return value2.toISOString();
185344
+ if (Array.isArray(value2)) return value2.map(normalizeValue);
185345
+ if (value2 instanceof Set) return Array.from(value2).map(normalizeValue);
185346
+ if (value2 instanceof Map) return Object.fromEntries(Array.from(value2, ([k2, v]) => [String(k2), normalizeValue(v)]));
185347
+ if (isPlainObject6(value2)) {
185348
+ const normalized = {};
185349
+ for (const key in value2) if (Object.prototype.hasOwnProperty.call(value2, key)) normalized[key] = normalizeValue(value2[key]);
185350
+ return normalized;
185351
+ }
185352
+ return null;
185353
+ }
185354
+ function isJsonPrimitive(value2) {
185355
+ return value2 === null || typeof value2 === "string" || typeof value2 === "number" || typeof value2 === "boolean";
185356
+ }
185357
+ function isJsonArray(value2) {
185358
+ return Array.isArray(value2);
185359
+ }
185360
+ function isJsonObject4(value2) {
185361
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
185362
+ }
185363
+ function isEmptyObject2(value2) {
185364
+ return Object.keys(value2).length === 0;
185365
+ }
185366
+ function isPlainObject6(value2) {
185367
+ if (value2 === null || typeof value2 !== "object") return false;
185368
+ const prototype = Object.getPrototypeOf(value2);
185369
+ return prototype === null || prototype === Object.prototype;
185370
+ }
185371
+ function isArrayOfPrimitives(value2) {
185372
+ return value2.length === 0 || value2.every((item) => isJsonPrimitive(item));
185373
+ }
185374
+ function isArrayOfArrays(value2) {
185375
+ return value2.length === 0 || value2.every((item) => isJsonArray(item));
185376
+ }
185377
+ function isArrayOfObjects(value2) {
185378
+ return value2.length === 0 || value2.every((item) => isJsonObject4(item));
185379
+ }
185380
+ function isValidUnquotedKey(key) {
185381
+ return /^[A-Z_][\w.]*$/i.test(key);
185382
+ }
185383
+ function isIdentifierSegment(key) {
185384
+ return /^[A-Z_]\w*$/i.test(key);
185385
+ }
185386
+ function isSafeUnquoted(value2, delimiter2 = DEFAULT_DELIMITER) {
185387
+ if (!value2) return false;
185388
+ if (value2 !== value2.trim()) return false;
185389
+ if (isBooleanOrNullLiteral(value2) || isNumericLike(value2)) return false;
185390
+ if (value2.includes(":")) return false;
185391
+ if (value2.includes('"') || value2.includes("\\")) return false;
185392
+ if (/[[\]{}]/.test(value2)) return false;
185393
+ if (/[\n\r\t]/.test(value2)) return false;
185394
+ if (value2.includes(delimiter2)) return false;
185395
+ if (value2.startsWith(LIST_ITEM_MARKER)) return false;
185396
+ return true;
185397
+ }
185398
+ function isNumericLike(value2) {
185399
+ return /^-?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i.test(value2) || /^0\d+$/.test(value2);
185400
+ }
185401
+ var QUOTED_KEY_MARKER = Symbol("quotedKey");
185402
+ function tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, flattenDepth) {
185403
+ if (options.keyFolding !== "safe") return;
185404
+ if (!isJsonObject4(value2)) return;
185405
+ const { segments, tail, leafValue } = collectSingleKeyChain(key, value2, flattenDepth ?? options.flattenDepth);
185406
+ if (segments.length < 2) return;
185407
+ if (!segments.every((seg) => isIdentifierSegment(seg))) return;
185408
+ const foldedKey = buildFoldedKey(segments);
185409
+ const absolutePath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
185410
+ if (siblings.includes(foldedKey)) return;
185411
+ if (rootLiteralKeys && rootLiteralKeys.has(absolutePath)) return;
185412
+ return {
185413
+ foldedKey,
185414
+ remainder: tail,
185415
+ leafValue,
185416
+ segmentCount: segments.length
185417
+ };
185418
+ }
185419
+ function collectSingleKeyChain(startKey, startValue, maxDepth) {
185420
+ const segments = [startKey];
185421
+ let currentValue = startValue;
185422
+ while (segments.length < maxDepth) {
185423
+ if (!isJsonObject4(currentValue)) break;
185424
+ const keys = Object.keys(currentValue);
185425
+ if (keys.length !== 1) break;
185426
+ const nextKey = keys[0];
185427
+ const nextValue = currentValue[nextKey];
185428
+ segments.push(nextKey);
185429
+ currentValue = nextValue;
185430
+ }
185431
+ if (!isJsonObject4(currentValue) || isEmptyObject2(currentValue)) return {
185432
+ segments,
185433
+ tail: void 0,
185434
+ leafValue: currentValue
185435
+ };
185436
+ return {
185437
+ segments,
185438
+ tail: currentValue,
185439
+ leafValue: currentValue
185440
+ };
185441
+ }
185442
+ function buildFoldedKey(segments) {
185443
+ return segments.join(DOT);
185444
+ }
185445
+ function encodePrimitive(value2, delimiter2) {
185446
+ if (value2 === null) return NULL_LITERAL;
185447
+ if (typeof value2 === "boolean") return String(value2);
185448
+ if (typeof value2 === "number") return String(value2);
185449
+ return encodeStringLiteral(value2, delimiter2);
185450
+ }
185451
+ function encodeStringLiteral(value2, delimiter2 = DEFAULT_DELIMITER) {
185452
+ if (isSafeUnquoted(value2, delimiter2)) return value2;
185453
+ return `${DOUBLE_QUOTE}${escapeString(value2)}${DOUBLE_QUOTE}`;
185454
+ }
185455
+ function encodeKey(key) {
185456
+ if (isValidUnquotedKey(key)) return key;
185457
+ return `${DOUBLE_QUOTE}${escapeString(key)}${DOUBLE_QUOTE}`;
185458
+ }
185459
+ function encodeAndJoinPrimitives(values, delimiter2 = DEFAULT_DELIMITER) {
185460
+ return values.map((v) => encodePrimitive(v, delimiter2)).join(delimiter2);
185461
+ }
185462
+ function formatHeader(length, options) {
185463
+ const key = options?.key;
185464
+ const fields = options?.fields;
185465
+ const delimiter2 = options?.delimiter ?? COMMA;
185466
+ let header = "";
185467
+ if (key) header += encodeKey(key);
185468
+ header += `[${length}${delimiter2 !== DEFAULT_DELIMITER ? delimiter2 : ""}]`;
185469
+ if (fields) {
185470
+ const quotedFields = fields.map((f) => encodeKey(f));
185471
+ header += `{${quotedFields.join(delimiter2)}}`;
185472
+ }
185473
+ header += ":";
185474
+ return header;
185475
+ }
185476
+ function* encodeJsonValue(value2, options, depth) {
185477
+ if (isJsonPrimitive(value2)) {
185478
+ const encodedPrimitive = encodePrimitive(value2, options.delimiter);
185479
+ if (encodedPrimitive !== "") yield encodedPrimitive;
185480
+ return;
185481
+ }
185482
+ if (isJsonArray(value2)) yield* encodeArrayLines(void 0, value2, depth, options);
185483
+ else if (isJsonObject4(value2)) yield* encodeObjectLines(value2, depth, options);
185484
+ }
185485
+ function* encodeObjectLines(value2, depth, options, rootLiteralKeys, pathPrefix, remainingDepth) {
185486
+ const keys = Object.keys(value2);
185487
+ if (depth === 0 && !rootLiteralKeys) rootLiteralKeys = new Set(keys.filter((k2) => k2.includes(".")));
185488
+ const effectiveFlattenDepth = remainingDepth ?? options.flattenDepth;
185489
+ for (const [key, val] of Object.entries(value2)) yield* encodeKeyValuePairLines(key, val, depth, options, keys, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
185490
+ }
185491
+ function* encodeKeyValuePairLines(key, value2, depth, options, siblings, rootLiteralKeys, pathPrefix, flattenDepth) {
185492
+ const currentPath = pathPrefix ? `${pathPrefix}${DOT}${key}` : key;
185493
+ const effectiveFlattenDepth = flattenDepth ?? options.flattenDepth;
185494
+ if (options.keyFolding === "safe" && siblings) {
185495
+ const foldResult = tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
185496
+ if (foldResult) {
185497
+ const { foldedKey, remainder, leafValue, segmentCount } = foldResult;
185498
+ const encodedFoldedKey = encodeKey(foldedKey);
185499
+ if (remainder === void 0) {
185500
+ if (isJsonPrimitive(leafValue)) {
185501
+ yield indentedLine(depth, `${encodedFoldedKey}: ${encodePrimitive(leafValue, options.delimiter)}`, options.indent);
185502
+ return;
185503
+ } else if (isJsonArray(leafValue)) {
185504
+ yield* encodeArrayLines(foldedKey, leafValue, depth, options);
185505
+ return;
185506
+ } else if (isJsonObject4(leafValue) && isEmptyObject2(leafValue)) {
185507
+ yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
185508
+ return;
185509
+ }
185510
+ }
185511
+ if (isJsonObject4(remainder)) {
185512
+ yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
185513
+ const remainingDepth = effectiveFlattenDepth - segmentCount;
185514
+ const foldedPath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
185515
+ yield* encodeObjectLines(remainder, depth + 1, options, rootLiteralKeys, foldedPath, remainingDepth);
185516
+ return;
185517
+ }
185518
+ }
185519
+ }
185520
+ const encodedKey = encodeKey(key);
185521
+ if (isJsonPrimitive(value2)) yield indentedLine(depth, `${encodedKey}: ${encodePrimitive(value2, options.delimiter)}`, options.indent);
185522
+ else if (isJsonArray(value2)) yield* encodeArrayLines(key, value2, depth, options);
185523
+ else if (isJsonObject4(value2)) {
185524
+ yield indentedLine(depth, `${encodedKey}:`, options.indent);
185525
+ if (!isEmptyObject2(value2)) yield* encodeObjectLines(value2, depth + 1, options, rootLiteralKeys, currentPath, effectiveFlattenDepth);
185526
+ }
185527
+ }
185528
+ function* encodeArrayLines(key, value2, depth, options) {
185529
+ if (value2.length === 0) {
185530
+ yield indentedLine(depth, formatHeader(0, {
185531
+ key,
185532
+ delimiter: options.delimiter
185533
+ }), options.indent);
185534
+ return;
185535
+ }
185536
+ if (isArrayOfPrimitives(value2)) {
185537
+ yield indentedLine(depth, encodeInlineArrayLine(value2, options.delimiter, key), options.indent);
185538
+ return;
185539
+ }
185540
+ if (isArrayOfArrays(value2)) {
185541
+ if (value2.every((arr) => isArrayOfPrimitives(arr))) {
185542
+ yield* encodeArrayOfArraysAsListItemsLines(key, value2, depth, options);
185543
+ return;
185544
+ }
185545
+ }
185546
+ if (isArrayOfObjects(value2)) {
185547
+ const header = extractTabularHeader(value2);
185548
+ if (header) yield* encodeArrayOfObjectsAsTabularLines(key, value2, header, depth, options);
185549
+ else yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
185550
+ return;
185551
+ }
185552
+ yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
185553
+ }
185554
+ function* encodeArrayOfArraysAsListItemsLines(prefix, values, depth, options) {
185555
+ yield indentedLine(depth, formatHeader(values.length, {
185556
+ key: prefix,
185557
+ delimiter: options.delimiter
185558
+ }), options.indent);
185559
+ for (const arr of values) if (isArrayOfPrimitives(arr)) {
185560
+ const arrayLine = encodeInlineArrayLine(arr, options.delimiter);
185561
+ yield indentedListItem(depth + 1, arrayLine, options.indent);
185562
+ }
185563
+ }
185564
+ function encodeInlineArrayLine(values, delimiter2, prefix) {
185565
+ const header = formatHeader(values.length, {
185566
+ key: prefix,
185567
+ delimiter: delimiter2
185568
+ });
185569
+ const joinedValue = encodeAndJoinPrimitives(values, delimiter2);
185570
+ if (values.length === 0) return header;
185571
+ return `${header} ${joinedValue}`;
185572
+ }
185573
+ function* encodeArrayOfObjectsAsTabularLines(prefix, rows, header, depth, options) {
185574
+ yield indentedLine(depth, formatHeader(rows.length, {
185575
+ key: prefix,
185576
+ fields: header,
185577
+ delimiter: options.delimiter
185578
+ }), options.indent);
185579
+ yield* writeTabularRowsLines(rows, header, depth + 1, options);
185580
+ }
185581
+ function extractTabularHeader(rows) {
185582
+ if (rows.length === 0) return;
185583
+ const firstRow = rows[0];
185584
+ const firstKeys = Object.keys(firstRow);
185585
+ if (firstKeys.length === 0) return;
185586
+ if (isTabularArray(rows, firstKeys)) return firstKeys;
185587
+ }
185588
+ function isTabularArray(rows, header) {
185589
+ for (const row of rows) {
185590
+ if (Object.keys(row).length !== header.length) return false;
185591
+ for (const key of header) {
185592
+ if (!(key in row)) return false;
185593
+ if (!isJsonPrimitive(row[key])) return false;
185594
+ }
185595
+ }
185596
+ return true;
185597
+ }
185598
+ function* writeTabularRowsLines(rows, header, depth, options) {
185599
+ for (const row of rows) yield indentedLine(depth, encodeAndJoinPrimitives(header.map((key) => row[key]), options.delimiter), options.indent);
185600
+ }
185601
+ function* encodeMixedArrayAsListItemsLines(prefix, items, depth, options) {
185602
+ yield indentedLine(depth, formatHeader(items.length, {
185603
+ key: prefix,
185604
+ delimiter: options.delimiter
185605
+ }), options.indent);
185606
+ for (const item of items) yield* encodeListItemValueLines(item, depth + 1, options);
185607
+ }
185608
+ function* encodeObjectAsListItemLines(obj, depth, options) {
185609
+ if (isEmptyObject2(obj)) {
185610
+ yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
185611
+ return;
185612
+ }
185613
+ const entries = Object.entries(obj);
185614
+ if (entries.length === 1) {
185615
+ const [key, value2] = entries[0];
185616
+ if (isJsonArray(value2) && isArrayOfObjects(value2)) {
185617
+ const header = extractTabularHeader(value2);
185618
+ if (header) {
185619
+ yield indentedListItem(depth, formatHeader(value2.length, {
185620
+ key,
185621
+ fields: header,
185622
+ delimiter: options.delimiter
185623
+ }), options.indent);
185624
+ yield* writeTabularRowsLines(value2, header, depth + 1, options);
185625
+ return;
185626
+ }
185627
+ }
185628
+ }
185629
+ yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
185630
+ yield* encodeObjectLines(obj, depth + 1, options);
185631
+ }
185632
+ function* encodeListItemValueLines(value2, depth, options) {
185633
+ if (isJsonPrimitive(value2)) yield indentedListItem(depth, encodePrimitive(value2, options.delimiter), options.indent);
185634
+ else if (isJsonArray(value2)) if (isArrayOfPrimitives(value2)) yield indentedListItem(depth, encodeInlineArrayLine(value2, options.delimiter), options.indent);
185635
+ else {
185636
+ yield indentedListItem(depth, formatHeader(value2.length, { delimiter: options.delimiter }), options.indent);
185637
+ for (const item of value2) yield* encodeListItemValueLines(item, depth + 1, options);
185638
+ }
185639
+ else if (isJsonObject4(value2)) yield* encodeObjectAsListItemLines(value2, depth, options);
185640
+ }
185641
+ function indentedLine(depth, content, indentSize) {
185642
+ return " ".repeat(indentSize * depth) + content;
185643
+ }
185644
+ function indentedListItem(depth, content, indentSize) {
185645
+ return indentedLine(depth, LIST_ITEM_PREFIX + content, indentSize);
185646
+ }
185647
+ function encode4(input2, options) {
185648
+ return Array.from(encodeLines(input2, options)).join("\n");
185649
+ }
185650
+ function encodeLines(input2, options) {
185651
+ return encodeJsonValue(normalizeValue(input2), resolveOptions(options), 0);
185652
+ }
185653
+ function resolveOptions(options) {
185654
+ return {
185655
+ indent: options?.indent ?? 2,
185656
+ delimiter: options?.delimiter ?? DEFAULT_DELIMITER,
185657
+ keyFolding: options?.keyFolding ?? "off",
185658
+ flattenDepth: options?.flattenDepth ?? Number.POSITIVE_INFINITY
185659
+ };
185660
+ }
185661
+
185662
+ // mcp/geminiSanitizer.ts
185663
+ function parseStringEnumBranch(item) {
185664
+ if (!item || typeof item !== "object") return null;
185665
+ const record3 = item;
185666
+ if (Array.isArray(record3.enum)) {
185667
+ const strings = record3.enum.filter((v) => typeof v === "string");
185668
+ return strings.length === record3.enum.length && strings.length > 0 ? { values: strings } : null;
185669
+ }
185670
+ if (typeof record3.const === "string") {
185671
+ return { values: [record3.const] };
185672
+ }
185673
+ return null;
185674
+ }
185675
+ function collapseStringUnion(branches) {
185676
+ const values = [];
185677
+ for (const item of branches) {
185678
+ const parsed2 = parseStringEnumBranch(item);
185679
+ if (!parsed2) return null;
185680
+ values.push(...parsed2.values);
185681
+ }
185682
+ if (values.length === 0) return null;
185683
+ return { type: "string", enum: [...new Set(values)] };
185684
+ }
185685
+ function sanitizeForGemini(schema2) {
185686
+ if (!schema2 || typeof schema2 !== "object") return schema2;
185687
+ if (Array.isArray(schema2)) return schema2.map(sanitizeForGemini);
185688
+ const source = schema2;
185689
+ if (Array.isArray(source.enum) && typeof source.type !== "string") {
185690
+ const allStrings = source.enum.every((v) => typeof v === "string");
185691
+ if (allStrings) {
185692
+ const result = { type: "string", enum: source.enum };
185693
+ if (typeof source.description === "string") result.description = source.description;
185694
+ return result;
185695
+ }
185696
+ }
185697
+ for (const unionKey of ["anyOf", "oneOf"]) {
185698
+ const branches = source[unionKey];
185699
+ if (Array.isArray(branches) && branches.length > 0) {
185700
+ const collapsed = collapseStringUnion(branches);
185701
+ if (collapsed) {
185702
+ const result = { ...collapsed };
185703
+ if (typeof source.description === "string") result.description = source.description;
185704
+ return result;
185705
+ }
185706
+ }
185707
+ }
185708
+ if (Array.isArray(source.anyOf) || Array.isArray(source.oneOf)) {
185709
+ const result = {};
185710
+ if (Array.isArray(source.anyOf)) result.anyOf = source.anyOf.map(sanitizeForGemini);
185711
+ if (Array.isArray(source.oneOf)) result.oneOf = source.oneOf.map(sanitizeForGemini);
185712
+ return result;
185713
+ }
185714
+ const sanitized = {};
185715
+ for (const [key, value2] of Object.entries(source)) {
185716
+ if (key === "$schema") continue;
185717
+ if (key === "$defs") {
185718
+ sanitized.definitions = sanitizeForGemini(value2);
185719
+ continue;
185720
+ }
185721
+ sanitized[key] = sanitizeForGemini(value2);
185722
+ }
185723
+ return sanitized;
185724
+ }
185725
+ function wrapJsonSchemaProducer(producer) {
185726
+ return new Proxy(producer, {
185727
+ get(target, prop, receiver) {
185728
+ const value2 = Reflect.get(target, prop, receiver);
185729
+ if ((prop === "input" || prop === "output") && typeof value2 === "function") {
185730
+ const fn2 = value2;
185731
+ return (...args2) => sanitizeForGemini(fn2.apply(target, args2));
185732
+ }
185733
+ return value2;
185734
+ }
185735
+ });
185736
+ }
185737
+ function wrapStandard(standard) {
185738
+ return new Proxy(standard, {
185739
+ get(target, prop, receiver) {
185740
+ if (prop === "jsonSchema") {
185741
+ const value2 = Reflect.get(target, prop, receiver);
185742
+ if (value2 && typeof value2 === "object") {
185743
+ return wrapJsonSchemaProducer(value2);
185744
+ }
185745
+ return value2;
185746
+ }
185747
+ return Reflect.get(target, prop, receiver);
185748
+ }
185749
+ });
185750
+ }
185751
+ function wrapSchemaForGemini(schema2) {
185752
+ return new Proxy(schema2, {
185753
+ get(target, prop, receiver) {
185754
+ if (prop === "~standard") {
185755
+ const value2 = Reflect.get(target, prop, receiver);
185756
+ if (value2 && typeof value2 === "object") {
185757
+ return wrapStandard(value2);
185758
+ }
185759
+ return value2;
185760
+ }
185761
+ if (prop === "toJsonSchema") {
185762
+ const method = Reflect.get(target, prop, receiver);
185763
+ if (typeof method === "function") {
185764
+ return () => sanitizeForGemini(method.call(target));
185765
+ }
185766
+ return method;
185767
+ }
185768
+ return Reflect.get(target, prop, receiver);
185769
+ }
185770
+ });
185771
+ }
185772
+ function sanitizeToolForGemini(tool2) {
185773
+ if (!tool2.parameters) return tool2;
185774
+ return { ...tool2, parameters: wrapSchemaForGemini(tool2.parameters) };
185775
+ }
185776
+ function isGeminiRouted(ctx) {
185777
+ const effective = ctx.payload.proxyModel ?? ctx.resolvedModel ?? ctx.payload.model;
185778
+ if (!effective) return true;
185779
+ const normalized = effective.toLowerCase();
185780
+ if (normalized.includes("gemini")) return true;
185781
+ if (!normalized.includes("/")) return true;
185782
+ return false;
185783
+ }
185784
+
185785
+ // mcp/shared.ts
185786
+ function getHttpStatus(err) {
185787
+ if (typeof err !== "object" || err === null || !("status" in err)) return void 0;
185788
+ return typeof err.status === "number" ? err.status : void 0;
185789
+ }
185790
+ var tool = (toolDef) => toolDef;
185791
+ var handleToolSuccess = (data) => {
185792
+ const text = typeof data === "string" ? data : encode4(data);
185793
+ return {
185794
+ content: [{ type: "text", text }]
185795
+ };
185796
+ };
185797
+ var handleToolError = (error62) => {
185798
+ const errorMessage = error62 instanceof Error ? error62.message : String(error62);
185799
+ return {
185800
+ content: [
185801
+ {
185802
+ type: "text",
185803
+ text: `Error: ${errorMessage}`
185804
+ }
185805
+ ],
185806
+ isError: true
185807
+ };
185808
+ };
185809
+ var execute = (fn2, toolName) => {
185810
+ const _fn = async (params) => {
185811
+ try {
185812
+ const result = await fn2(params);
185813
+ return handleToolSuccess(result);
185814
+ } catch (error62) {
185815
+ const errorMessage = error62 instanceof Error ? error62.message : String(error62);
185816
+ const prefix = toolName ? `[${toolName}]` : "tool";
185817
+ log.info(`${prefix} error: ${errorMessage}`);
185818
+ log.debug(`${prefix} params: ${formatJsonValue(params)}`);
185819
+ return handleToolError(error62);
185820
+ }
185821
+ };
185822
+ return _fn;
185823
+ };
185824
+ var addTools = (ctx, server, tools) => {
185825
+ const shouldSanitize = isGeminiRouted(ctx);
185826
+ for (const tool2 of tools) {
185827
+ server.addTool(shouldSanitize ? sanitizeToolForGemini(tool2) : tool2);
185828
+ }
185829
+ return server;
185830
+ };
185831
+
185832
+ // mcp/shell.ts
185833
+ var ShellParams = type({
185834
+ command: "string",
185835
+ // advisory and unread — no code path consumes it. kept in the schema only
185836
+ // because it nudges the model to state intent before running a command;
185837
+ // REQUIRING it cost a full rejected turn every time one was omitted. #1140.
185838
+ "description?": "string",
185839
+ "timeout?": type.number.describe(
185840
+ "Timeout in MILLISECONDS (not seconds). Default 30000 (30s), max 120000 (2m). e.g. timeout: 180000 for 3 minutes; timeout: 180 means 180ms and will kill the process almost immediately."
185841
+ ),
185842
+ "working_directory?": "string",
185843
+ "background?": "boolean"
185844
+ });
185845
+ var detectedSandboxMethod;
185846
+ var usernsProbeError;
185847
+ function getSandboxMethod() {
185848
+ return detectSandboxMethod();
185849
+ }
185850
+ function detectSandboxMethod() {
185851
+ if (detectedSandboxMethod !== void 0) {
185852
+ return detectedSandboxMethod;
185853
+ }
185854
+ if (process.env.CI !== "true") {
185855
+ detectedSandboxMethod = "none";
185856
+ log.debug("sandbox disabled (CI !== true)");
185857
+ return "none";
185858
+ }
185859
+ try {
185860
+ const result = spawnSync6("unshare", ["--pid", "--fork", "--mount-proc", "true"], {
185861
+ timeout: 5e3,
185862
+ stdio: "ignore"
185863
+ });
185864
+ if (result.status === 0) {
185865
+ detectedSandboxMethod = "unshare";
185866
+ log.debug("PID namespace isolation enabled (unprivileged unshare)");
185867
+ return "unshare";
185868
+ }
185869
+ } catch {
185870
+ }
185871
+ try {
185872
+ const result = spawnSync6("sudo", ["unshare", "--pid", "--fork", "--mount-proc", "true"], {
185873
+ timeout: 5e3,
185874
+ stdio: "ignore"
185875
+ });
185876
+ if (result.status === 0) {
185877
+ detectedSandboxMethod = "sudo-unshare";
185878
+ log.debug("PID namespace isolation enabled (sudo unshare)");
185879
+ return "sudo-unshare";
185880
+ }
185881
+ } catch {
185882
+ }
185883
+ try {
185884
+ const result = spawnSync6(
185885
+ "unshare",
185886
+ [
185887
+ "--user",
185888
+ "--map-root-user",
185889
+ "--pid",
185890
+ "--fork",
185891
+ "--mount",
185892
+ "setpriv",
185893
+ "--no-new-privs",
185894
+ "--bounding-set",
185895
+ "-all",
185896
+ "--inh-caps",
185897
+ "-all",
185898
+ "--ambient-caps",
185899
+ "-all",
185900
+ "true"
185901
+ ],
185902
+ { timeout: 5e3, stdio: ["ignore", "ignore", "pipe"] }
185903
+ );
185904
+ if (result.status === 0) {
185905
+ detectedSandboxMethod = "userns-unshare";
185906
+ log.debug("PID namespace isolation enabled (unprivileged userns unshare)");
185907
+ return "userns-unshare";
185908
+ }
185909
+ usernsProbeError = result.stderr?.toString().trim() || result.error?.message;
185910
+ } catch {
185911
+ }
185912
+ detectedSandboxMethod = "none";
185913
+ log.info("PID namespace isolation not available");
185914
+ return "none";
185915
+ }
185916
+ var PROC_CLEANUP = "umount /proc 2>/dev/null; umount /proc 2>/dev/null; mount -t proc proc /proc 2>/dev/null;";
185917
+ var SOCKET_CLEANUP = [
185918
+ "/var/run/docker.sock",
185919
+ "/run/docker.sock",
185920
+ "/var/run/podman/podman.sock",
185921
+ "/run/podman/podman.sock",
185922
+ "/run/containerd/containerd.sock",
185923
+ "/var/run/crio/crio.sock"
185924
+ ].map((path4) => `mount --bind /dev/null ${path4} 2>/dev/null;`).join(" ");
185925
+ function buildFsMounts(repoDir) {
185926
+ const escaped = repoDir.replace(/'/g, "'\\''");
185927
+ return [
185928
+ `mkdir -p /var/lib/pullfrog 2>/dev/null;`,
185929
+ `mount -t tmpfs tmpfs /var/lib/pullfrog 2>/dev/null;`,
185930
+ `[ -n "$RUNNER_TEMP" ] && [ -d "$RUNNER_TEMP/_runner_file_commands" ] && mount -t tmpfs tmpfs "$RUNNER_TEMP/_runner_file_commands" 2>/dev/null;`,
185931
+ `[ -e '${escaped}/.git' ] && mount --bind '${escaped}/.git' '${escaped}/.git' 2>/dev/null && mount -o remount,bind,ro '${escaped}/.git' 2>/dev/null;`,
185932
+ // the corepack shim dir sits FIRST on PATH (utils/packageManager.ts) and
185933
+ // PATH survives filterEnv, so a writable pm-bin lets sandboxed code plant
185934
+ // a `git`/`pnpm` that a later UNSANDBOXED spawn resolves with the parent
185935
+ // env. read-only closes that for every sandboxed command at once.
185936
+ //
185937
+ // the path is interpolated HERE rather than read as `$PULLFROG_TEMP_DIR`
185938
+ // inside the sandbox: that name is not on the `filterEnv` safe list, so in
185939
+ // restricted mode it expands to empty and the mount silently no-ops in the
185940
+ // one tier it exists to protect. `$RUNNER_TEMP` above is safe only because
185941
+ // `RUNNER_` is an allowed prefix.
185942
+ shimMount()
185943
+ ].filter(Boolean).join(" ");
185944
+ }
185945
+ function shimMount() {
185946
+ const tempDir = process.env.PULLFROG_TEMP_DIR;
185947
+ if (!tempDir) return "";
185948
+ const dir = join10(tempDir, "pm-bin").replace(/'/g, "'\\''");
185949
+ return `[ -d '${dir}' ] && mount --bind '${dir}' '${dir}' 2>/dev/null && mount -o remount,bind,ro '${dir}' 2>/dev/null;`;
185950
+ }
185951
+ var _repoRoot;
185952
+ function resolveRepoRoot() {
185953
+ if (_repoRoot) return _repoRoot;
185954
+ const fromEnv = process.env.GITHUB_WORKSPACE;
185955
+ if (fromEnv) {
185956
+ _repoRoot = fromEnv;
185957
+ return _repoRoot;
185958
+ }
185959
+ try {
185960
+ _repoRoot = spawnSync6("git", ["rev-parse", "--show-toplevel"], {
185961
+ cwd: process.cwd(),
185962
+ stdio: ["ignore", "pipe", "ignore"],
185963
+ encoding: "utf-8"
185964
+ }).stdout?.trim();
185965
+ } catch {
185966
+ }
185967
+ if (!_repoRoot) _repoRoot = process.cwd();
185968
+ return _repoRoot;
185969
+ }
185970
+ function spawnShell(params) {
185971
+ const spawnOpts = { env: params.env, cwd: params.cwd, stdio: params.stdio, detached: true };
185972
+ const sandboxMethod = detectSandboxMethod();
185973
+ const ci = process.env.CI === "true";
185974
+ if (ci && sandboxMethod === "none") {
185975
+ throw new Error(
185976
+ "PID-namespace isolation is required in CI but unavailable: unprivileged unshare, sudo unshare, and userns-nested unshare all failed. " + (usernsProbeError ? `userns probe said: ${usernsProbeError}. ` : "") + "on a containerized / Kubernetes self-hosted runner the cause is usually one of: the pod's seccomp profile blocks user-namespace creation (Pod Security Standards baseline/restricted); the pod runs as root without CAP_SETFCAP, which the kernel requires to map uid 0; or the node kernel disallows unprivileged user namespaces (Ubuntu >= 23.10, Bottlerocket, Talos). elsewhere, check that `unshare` and `setpriv` (util-linux >= 2.31, which is what added --ambient-caps) are on PATH. see https://docs.pullfrog.com/security#self-hosted-runners"
185977
+ );
185978
+ }
185979
+ const repoRoot = resolveRepoRoot();
185980
+ const fsMounts = buildFsMounts(repoRoot);
185981
+ if (sandboxMethod === "unshare") {
185982
+ return spawn3(
185983
+ "unshare",
185984
+ [
185985
+ "--pid",
185986
+ "--fork",
185987
+ "--mount-proc",
185988
+ "bash",
185989
+ "-c",
185990
+ `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} ${params.command}`
185991
+ ],
185992
+ spawnOpts
185993
+ );
185994
+ }
185995
+ if (sandboxMethod === "userns-unshare") {
185996
+ const escaped = params.command.replace(/'/g, "'\\''");
185997
+ return spawn3(
185998
+ "unshare",
185999
+ [
186000
+ "--user",
186001
+ "--map-root-user",
186002
+ "--pid",
186003
+ "--fork",
186004
+ "--mount",
186005
+ "bash",
186006
+ "-c",
186007
+ `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec setpriv --no-new-privs --bounding-set -all --inh-caps -all --ambient-caps -all bash -c '${escaped}'`
186008
+ ],
186009
+ spawnOpts
186010
+ );
186011
+ }
186012
+ if (sandboxMethod === "sudo-unshare") {
186013
+ const envArgs = [];
186014
+ for (const [k2, v] of Object.entries(params.env)) {
186015
+ if (v !== void 0) {
186016
+ envArgs.push(`${k2}=${v}`);
186017
+ }
186018
+ }
186019
+ const username = userInfo2().username;
186020
+ const pathRestore = 'export PATH="${SANDBOX_PATH:-$PATH}"; ';
186021
+ const escaped = (pathRestore + params.command).replace(/'/g, "'\\''");
186022
+ envArgs.push(`SANDBOX_PATH=${params.env.PATH ?? ""}`);
186023
+ return spawn3(
186024
+ "sudo",
186025
+ [
186026
+ "env",
186027
+ ...envArgs,
186028
+ "unshare",
186029
+ "--pid",
186030
+ "--fork",
186031
+ "--mount-proc",
186032
+ "bash",
186033
+ "-c",
186034
+ `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec su -p -s /bin/bash ${username} -c '${escaped}'`
186035
+ ],
186036
+ { ...spawnOpts, env: {} }
186037
+ );
186038
+ }
186039
+ return spawn3("bash", ["-c", params.command], spawnOpts);
186040
+ }
186041
+ async function killProcessGroup(proc) {
186042
+ if (!proc.pid) return;
186043
+ try {
186044
+ process.kill(-proc.pid, "SIGTERM");
186045
+ await new Promise((r) => setTimeout(r, 200));
186046
+ process.kill(-proc.pid, "SIGKILL");
186047
+ } catch {
186048
+ try {
186049
+ proc.kill("SIGKILL");
186050
+ } catch {
186051
+ }
186052
+ }
186053
+ }
186054
+ function getTempDir() {
186055
+ const tempDir = process.env.PULLFROG_TEMP_DIR;
186056
+ if (!tempDir) {
186057
+ throw new Error("PULLFROG_TEMP_DIR not set");
186058
+ }
186059
+ return tempDir;
186060
+ }
186061
+ var MAX_OUTPUT_CHARS = 5e3;
186062
+ async function runSandboxed(params) {
186063
+ const proc = spawnShell({
186064
+ command: params.command,
186065
+ env: params.env,
186066
+ cwd: params.cwd,
186067
+ stdio: ["ignore", "pipe", "pipe"]
186068
+ });
186069
+ const stdout = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
186070
+ const stderr = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
186071
+ let timedOut = false, exited = false, spawnError = "";
186072
+ proc.stdout?.on("data", (chunk) => {
186073
+ const text = chunk.toString();
186074
+ stdout.append(text);
186075
+ params.onStdout?.(text);
186076
+ });
186077
+ proc.stderr?.on("data", (chunk) => {
186078
+ const text = chunk.toString();
186079
+ stderr.append(text);
186080
+ params.onStderr?.(text);
186081
+ });
186082
+ const timeoutId = setTimeout(async () => {
186083
+ if (!exited) {
186084
+ timedOut = true;
186085
+ await killProcessGroup(proc);
186086
+ }
186087
+ }, params.timeout);
186088
+ const exitCode = await new Promise((resolve3) => {
186089
+ const done = (code) => {
186090
+ exited = true;
186091
+ clearTimeout(timeoutId);
186092
+ resolve3(code);
186093
+ };
186094
+ proc.on("exit", done);
186095
+ proc.on("error", (err) => {
186096
+ spawnError = err.message;
186097
+ done(null);
186098
+ });
186099
+ });
186100
+ proc.stdout?.destroy();
186101
+ proc.stderr?.destroy();
186102
+ const outText = stdout.toString();
186103
+ const errText = stderr.toString();
186104
+ let output2 = errText ? outText ? `${outText}
186105
+ ${errText}` : errText : outText;
186106
+ if (spawnError) output2 = output2 ? `${output2}
186107
+ ${spawnError}` : spawnError;
186108
+ if (timedOut)
186109
+ output2 = output2 ? `${output2}
186110
+ [timed out after ${params.timeout}ms]` : `[timed out after ${params.timeout}ms]`;
186111
+ return {
186112
+ output: output2.trim(),
186113
+ exitCode: exitCode ?? (timedOut ? 124 : -1),
186114
+ timedOut
186115
+ };
186116
+ }
186117
+ function capOutput(output2) {
186118
+ if (output2.length <= MAX_OUTPUT_CHARS) return output2;
186119
+ const fullPath = join10(getTempDir(), `shell-${randomUUID3().slice(0, 8)}.log`);
186120
+ writeFileSync8(fullPath, output2);
186121
+ const elided = output2.length - MAX_OUTPUT_CHARS;
186122
+ return `... [${elided} chars truncated; output saved to ${fullPath}] ...
186123
+ ${output2.slice(-MAX_OUTPUT_CHARS)}`;
186124
+ }
186125
+ function isGitCommand(command) {
186126
+ const trimmed = command.trim();
186127
+ if (trimmed === "git" || trimmed.startsWith("git ")) return true;
186128
+ if (trimmed.startsWith("sudo git")) return true;
186129
+ return /[;&|]\s*(?:sudo\s+)?git(?:\s|$)/.test(trimmed);
186130
+ }
186131
+ function ShellTool(ctx) {
186132
+ return tool({
186133
+ name: "shell",
186134
+ timeoutMs: 12e4,
186135
+ description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
186136
+
186137
+ Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
186138
+
186139
+ Use this tool to:
186140
+ - Run shell commands (ls, cat, grep, find, etc.)
186141
+ - Execute build tools (npm, pnpm, cargo, make, etc.)
186142
+ - Run tests and linters
186143
+
186144
+ Output is capped at ${MAX_OUTPUT_CHARS} chars: if exceeded, only the tail is returned and the captured output is saved to a tempfile (path included in the response). Re-read the tempfile with cat/tail/grep when you need more.
186145
+
186146
+ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead.`,
186147
+ parameters: ShellParams,
186148
+ execute: execute(async (params) => {
186149
+ if (isGitCommand(params.command)) {
186150
+ throw new Error(
186151
+ 'git commands are not allowed in the shell tool. use the dedicated git tools instead:\n- git: local operations, e.g. git({ command: "log", args: ["--oneline"] }) \u2014 `command` is the subcommand ONLY\n- push_branch: push to remote (handles authentication)\n- git_fetch: fetch from remote (handles authentication)\n- checkout_pr: check out PR branches'
186152
+ );
186153
+ }
186154
+ const timeout = Math.min(params.timeout ?? 3e4, 12e4);
186155
+ const cwd = params.working_directory ?? process.cwd();
186156
+ const env2 = resolveEnv(ctx.payload.shell === "enabled" ? "inherit" : "restricted");
186157
+ if (params.command.includes("agent-browser")) {
186158
+ const daemonError = ensureBrowserDaemon(ctx.toolState);
186159
+ if (daemonError) {
186160
+ return {
186161
+ output: `browser daemon unavailable: ${daemonError}`,
186162
+ exit_code: 1,
186163
+ timed_out: false
186164
+ };
186165
+ }
186166
+ const binDir = ctx.toolState.browserDaemon?.binDir;
186167
+ if (binDir) {
186168
+ env2.PATH = `${binDir}:${env2.PATH ?? ""}`;
186169
+ }
186170
+ }
186171
+ if (params.background) {
186172
+ const tempDir = getTempDir();
186173
+ const handle = `bg-${randomUUID3().slice(0, 8)}`;
186174
+ const outputPath = join10(tempDir, `${handle}.log`);
186175
+ const pidPath = join10(tempDir, `${handle}.pid`);
186176
+ const logFd = openSync(outputPath, "a");
186177
+ let proc;
186178
+ try {
186179
+ proc = spawnShell({
186180
+ command: params.command,
186181
+ env: env2,
186182
+ cwd,
186183
+ stdio: ["ignore", logFd, logFd]
186184
+ });
186185
+ } finally {
186186
+ closeSync(logFd);
186187
+ }
186188
+ if (!proc.pid) {
186189
+ throw new Error("failed to start background process");
186190
+ }
186191
+ proc.unref();
186192
+ writeFileSync8(pidPath, `${proc.pid}
186193
+ `);
186194
+ ctx.toolState.backgroundProcesses.set(handle, { pid: proc.pid, outputPath, pidPath });
186195
+ return {
186196
+ handle,
186197
+ outputPath,
186198
+ pidPath,
186199
+ message: `started background process ${handle} (pid ${proc.pid})`
186200
+ };
186201
+ }
186202
+ const result = await runSandboxed({ command: params.command, env: env2, cwd, timeout });
186203
+ if (result.exitCode !== 0) {
186204
+ log.info(`shell command failed with exit code ${result.exitCode}: ${params.command}`);
186205
+ if (result.output) log.info(`output: ${result.output}`);
186206
+ }
186207
+ return {
186208
+ output: capOutput(result.output),
186209
+ exit_code: result.exitCode,
186210
+ timed_out: result.timedOut
186211
+ };
186212
+ })
186213
+ });
186214
+ }
186215
+ var KillBackgroundParams = type({
186216
+ handle: type.string.describe("The handle of the background process to kill (e.g., bg-a1b2c3d4)")
186217
+ });
186218
+ function KillBackgroundTool(ctx) {
186219
+ return tool({
186220
+ name: "kill_background",
186221
+ mutates: true,
186222
+ description: `Kill a background process by its handle. Use this to stop dev servers or other long-running processes started with shell({ background: true }).`,
186223
+ parameters: KillBackgroundParams,
186224
+ execute: execute(async (params) => {
186225
+ const proc = ctx.toolState.backgroundProcesses.get(params.handle);
186226
+ if (!proc) {
186227
+ return {
186228
+ success: false,
186229
+ message: `no background process with handle ${params.handle}`
186230
+ };
186231
+ }
186232
+ try {
186233
+ process.kill(-proc.pid, "SIGTERM");
186234
+ } catch {
186235
+ }
186236
+ await sleep2(200);
186237
+ try {
186238
+ process.kill(-proc.pid, "SIGKILL");
186239
+ } catch {
186240
+ }
186241
+ ctx.toolState.backgroundProcesses.delete(params.handle);
186242
+ return {
186243
+ success: true,
186244
+ message: `killed background process ${params.handle} (pid ${proc.pid})`
186245
+ };
186246
+ })
186247
+ });
186248
+ }
186249
+
185259
186250
  // utils/lifecycle.ts
185260
186251
  function describeSetupFailure(failure2) {
185261
186252
  if (!failure2) return "";
@@ -185277,44 +186268,47 @@ ${failure2.output || "(empty)"}`;
185277
186268
  async function executeLifecycleHook(params) {
185278
186269
  if (!params.script) return {};
185279
186270
  log.info(`\xBB executing ${params.event} lifecycle hook...`);
186271
+ resolveTrustedGit();
185280
186272
  const preHookTrackedCount = params.normalizeWorkingTreeAfter ? (await runGitLines(["diff", "--name-only", "HEAD"])).length : 0;
185281
186273
  let result;
185282
186274
  try {
186275
+ let sandboxed;
185283
186276
  try {
185284
- const spawnResult = await spawn({
185285
- cmd: "bash",
185286
- args: ["-c", params.script],
185287
- env: process.env,
186277
+ sandboxed = await runSandboxed({
186278
+ command: params.script,
186279
+ env: resolveEnv(params.shell === "enabled" ? "inherit" : "restricted"),
186280
+ cwd: process.cwd(),
185288
186281
  timeout: LIFECYCLE_HOOK_TIMEOUT_MS,
185289
- activityTimeout: 0,
185290
186282
  onStdout: (chunk) => process.stdout.write(chunk),
185291
186283
  onStderr: (chunk) => process.stderr.write(chunk)
185292
186284
  });
185293
- if (spawnResult.exitCode !== 0) {
185294
- const output2 = (spawnResult.stderr || spawnResult.stdout).trim();
185295
- result = {
185296
- failure: { kind: "exit", output: output2, exitCode: spawnResult.exitCode },
185297
- warning: `lifecycle hook '${params.event}' failed with exit code ${spawnResult.exitCode}. output: ${output2 || "(empty)"}. retry the operation if the failure looks flaky (network blips, transient rate limits). do NOT retry if the script is broken (missing commands, syntax errors) or the error is persistent.`
185298
- };
185299
- } else {
185300
- log.info(`\xBB ${params.event} lifecycle hook completed successfully`);
185301
- result = {};
185302
- }
185303
186285
  } catch (err) {
185304
- const isTimeout = err instanceof SpawnTimeoutError && (err.code === SPAWN_TIMEOUT_CODE || err.code === SPAWN_ACTIVITY_TIMEOUT_CODE);
185305
- if (isTimeout) {
185306
- const minutes = Math.round(LIFECYCLE_HOOK_TIMEOUT_MS / 6e4);
185307
- result = {
185308
- failure: { kind: "timeout" },
185309
- warning: `lifecycle hook '${params.event}' timed out after ${minutes}min. do NOT retry \u2014 the script is likely hung or doing too much work. ask the repo owner to simplify the hook (e.g. move long-running work out of the hook, add caching, or split it).`
185310
- };
185311
- } else {
185312
- const msg = err instanceof Error ? err.message : String(err);
185313
- result = {
185314
- failure: { kind: "spawn", spawnError: msg },
185315
- warning: `lifecycle hook '${params.event}' failed to spawn: ${msg}. this is likely a transient failure \u2014 retry the operation.`
185316
- };
185317
- }
186286
+ const msg = err instanceof Error ? err.message : String(err);
186287
+ return {
186288
+ failure: { kind: "spawn", spawnError: msg },
186289
+ warning: `lifecycle hook '${params.event}' could not be sandboxed, so it did not run: ${msg}`
186290
+ };
186291
+ }
186292
+ if (sandboxed.timedOut) {
186293
+ const minutes = Math.round(LIFECYCLE_HOOK_TIMEOUT_MS / 6e4);
186294
+ result = {
186295
+ failure: { kind: "timeout" },
186296
+ warning: `lifecycle hook '${params.event}' timed out after ${minutes}min. do NOT retry \u2014 the script is likely hung or doing too much work. ask the repo owner to simplify the hook (e.g. move long-running work out of the hook, add caching, or split it).`
186297
+ };
186298
+ } else if (sandboxed.exitCode === -1) {
186299
+ result = {
186300
+ failure: { kind: "spawn", spawnError: sandboxed.output },
186301
+ warning: `lifecycle hook '${params.event}' failed to spawn: ${sandboxed.output}. this is likely a transient failure \u2014 retry the operation.`
186302
+ };
186303
+ } else if (sandboxed.exitCode !== 0) {
186304
+ const output2 = capOutput(sandboxed.output);
186305
+ result = {
186306
+ failure: { kind: "exit", output: output2, exitCode: sandboxed.exitCode },
186307
+ warning: `lifecycle hook '${params.event}' failed with exit code ${sandboxed.exitCode}. output: ${output2 || "(empty)"}. retry the operation if the failure looks flaky (network blips, transient rate limits). do NOT retry if the script is broken (missing commands, syntax errors) or the error is persistent. if it failed on a missing environment variable, the repo's env allowlist has to name it \u2014 hooks run with the same filtered env as the shell tool.`
186308
+ };
186309
+ } else {
186310
+ log.info(`\xBB ${params.event} lifecycle hook completed successfully`);
186311
+ result = {};
185318
186312
  }
185319
186313
  } finally {
185320
186314
  if (params.normalizeWorkingTreeAfter) {
@@ -185335,8 +186329,20 @@ async function normalizeWorkingTreeAfterHook(params) {
185335
186329
  await runGit(["restore", "--staged", "--worktree", "."]);
185336
186330
  log.info(`\xBB discarded ${trackedCount} tracked changes from ${params.event} hook`);
185337
186331
  }
186332
+ var trustedGit;
186333
+ function resolveTrustedGit() {
186334
+ if (trustedGit) return trustedGit;
186335
+ const resolved = spawnSync7("sh", ["-c", "command -v git"], { encoding: "utf-8" });
186336
+ trustedGit = resolved.stdout?.trim() || "git";
186337
+ return trustedGit;
186338
+ }
185338
186339
  async function runGit(args2) {
185339
- const result = await spawn({ cmd: "git", args: args2, env: process.env, activityTimeout: 0 });
186340
+ const result = await spawn({
186341
+ cmd: resolveTrustedGit(),
186342
+ args: args2,
186343
+ env: filterEnvForUntrustedCode(),
186344
+ activityTimeout: 0
186345
+ });
185340
186346
  if (result.exitCode !== 0) {
185341
186347
  throw new Error(
185342
186348
  `git ${args2.join(" ")} failed (exit ${result.exitCode}): ${result.stderr.trim() || "(no stderr)"}`
@@ -185456,7 +186462,7 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
185456
186462
 
185457
186463
  // yes/index.ts
185458
186464
  import { performance as performance8 } from "node:perf_hooks";
185459
- import { setTimeout as sleep2 } from "node:timers/promises";
186465
+ import { setTimeout as sleep3 } from "node:timers/promises";
185460
186466
 
185461
186467
  // node_modules/.pnpm/lru-cache@11.5.2/node_modules/lru-cache/dist/esm/node/index.min.js
185462
186468
  import { tracingChannel as G, channel as P } from "node:diagnostics_channel";
@@ -186250,7 +187256,7 @@ function _op(fn2, options) {
186250
187256
  );
186251
187257
  log2.info({ key, error: error62, attempt });
186252
187258
  }
186253
- await sleep2(delay2);
187259
+ await sleep3(delay2);
186254
187260
  }
186255
187261
  }
186256
187262
  }
@@ -186318,14 +187324,14 @@ function _op(fn2, options) {
186318
187324
  }
186319
187325
 
186320
187326
  // mcp/git.ts
186321
- import { randomUUID as randomUUID3 } from "node:crypto";
186322
- import { writeFileSync as writeFileSync8 } from "node:fs";
186323
- import { join as join12 } from "node:path";
187327
+ import { randomUUID as randomUUID4 } from "node:crypto";
187328
+ import { writeFileSync as writeFileSync9 } from "node:fs";
187329
+ import { join as join13 } from "node:path";
186324
187330
 
186325
187331
  // utils/apiCommit.ts
186326
187332
  import { execFileSync as execFileSync5 } from "node:child_process";
186327
187333
  import { lstat, readlink } from "node:fs/promises";
186328
- import { join as join10 } from "node:path";
187334
+ import { join as join11 } from "node:path";
186329
187335
  var GITHUB_API = "https://api.github.com";
186330
187336
  var MAX_BLOB_BYTES = 30 * 1024 * 1024;
186331
187337
  var BLOB_UPLOAD_CONCURRENCY = 8;
@@ -186390,7 +187396,7 @@ async function assertApiCommittable(files) {
186390
187396
  const root = getRepoRoot();
186391
187397
  const attrs = $(
186392
187398
  "git",
186393
- ["check-attr", "filter", "-z", "--", ...present.map((p) => join10(root, p))],
187399
+ ["check-attr", "filter", "-z", "--", ...present.map((p) => join11(root, p))],
186394
187400
  { log: false }
186395
187401
  );
186396
187402
  const parts = attrs.split("\0");
@@ -186402,7 +187408,7 @@ async function assertApiCommittable(files) {
186402
187408
  }
186403
187409
  }
186404
187410
  for (const path4 of present) {
186405
- const stat = await lstat(join10(root, path4));
187411
+ const stat = await lstat(join11(root, path4));
186406
187412
  if (stat.isDirectory()) {
186407
187413
  throw new Error(
186408
187414
  `'${path4}' is a directory (nested repository or submodule?) \u2014 signed commits only support files and symlinks.`
@@ -186411,7 +187417,7 @@ async function assertApiCommittable(files) {
186411
187417
  }
186412
187418
  }
186413
187419
  async function createBlobEntry(params) {
186414
- const absPath = join10(params.repoRoot, params.path);
187420
+ const absPath = join11(params.repoRoot, params.path);
186415
187421
  const stat = await lstat(absPath);
186416
187422
  if (stat.size > MAX_BLOB_BYTES) {
186417
187423
  throw new Error(
@@ -186591,7 +187597,7 @@ this usually means your local branch has commits that were never pushed \u2014 s
186591
187597
  var core5 = __toESM(require_core(), 1);
186592
187598
  import { createSign } from "node:crypto";
186593
187599
  import { rename, writeFile } from "node:fs/promises";
186594
- import { dirname as dirname3, join as join11 } from "node:path";
187600
+ import { dirname as dirname3, join as join12 } from "node:path";
186595
187601
 
186596
187602
  // node_modules/.pnpm/@octokit+plugin-throttling@11.0.5_@octokit+core@7.0.8/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
186597
187603
  var import_light = __toESM(require_light(), 1);
@@ -186963,7 +187969,7 @@ function lowercaseKeys(object5) {
186963
187969
  return newObj;
186964
187970
  }, {});
186965
187971
  }
186966
- function isPlainObject6(value2) {
187972
+ function isPlainObject7(value2) {
186967
187973
  if (typeof value2 !== "object" || value2 === null) return false;
186968
187974
  if (Object.prototype.toString.call(value2) !== "[object Object]") return false;
186969
187975
  const proto = Object.getPrototypeOf(value2);
@@ -186974,7 +187980,7 @@ function isPlainObject6(value2) {
186974
187980
  function mergeDeep(defaults, options) {
186975
187981
  const result = Object.assign({}, defaults);
186976
187982
  Object.keys(options).forEach((key) => {
186977
- if (isPlainObject6(options[key])) {
187983
+ if (isPlainObject7(options[key])) {
186978
187984
  if (!(key in defaults)) Object.assign(result, { [key]: options[key] });
186979
187985
  else result[key] = mergeDeep(defaults[key], options[key]);
186980
187986
  } else {
@@ -187261,7 +188267,7 @@ var NullObject = /* @__PURE__ */ (() => {
187261
188267
  return C;
187262
188268
  })();
187263
188269
  function parse5(header, options) {
187264
- const stopChar = options?.comma === true ? COMMA : 65536;
188270
+ const stopChar = options?.comma === true ? COMMA2 : 65536;
187265
188271
  const len = header.length;
187266
188272
  let index = skipOWS(header, options?.start ?? 0, len);
187267
188273
  const valueStart = index;
@@ -187279,7 +188285,7 @@ var SEMI = 59;
187279
188285
  var EQ = 61;
187280
188286
  var DQUOTE = 34;
187281
188287
  var BSLASH = 92;
187282
- var COMMA = 44;
188288
+ var COMMA2 = 44;
187283
188289
  function parseParameters(header, type2, index, len, stopChar) {
187284
188290
  const parameters = new NullObject();
187285
188291
  parameter: while (index < len) {
@@ -187738,7 +188744,7 @@ var defaults_default = {
187738
188744
  "user-agent": `octokit-request.js/${VERSION3} ${getUserAgent2()}`
187739
188745
  }
187740
188746
  };
187741
- function isPlainObject7(value2) {
188747
+ function isPlainObject8(value2) {
187742
188748
  if (typeof value2 !== "object" || value2 === null) return false;
187743
188749
  if (Object.prototype.toString.call(value2) !== "[object Object]") return false;
187744
188750
  const proto = Object.getPrototypeOf(value2);
@@ -187756,7 +188762,7 @@ async function fetchWrapper(requestOptions) {
187756
188762
  }
187757
188763
  const log3 = requestOptions.request?.log || console;
187758
188764
  const parseSuccessResponseBody = requestOptions.request?.parseSuccessResponseBody !== false;
187759
- const body = isPlainObject7(requestOptions.body) || Array.isArray(requestOptions.body) ? JSONStringify(requestOptions.body) : requestOptions.body;
188765
+ const body = isPlainObject8(requestOptions.body) || Array.isArray(requestOptions.body) ? JSONStringify(requestOptions.body) : requestOptions.body;
187760
188766
  const requestHeaders = Object.fromEntries(
187761
188767
  Object.entries(requestOptions.headers).map(([name, value2]) => [
187762
188768
  name,
@@ -191061,7 +192067,7 @@ function getGitHubUsageSummary() {
191061
192067
  }
191062
192068
  async function writeGitHubUsageSummaryToFile(path4) {
191063
192069
  const summary2 = getGitHubUsageSummary();
191064
- const tmpPath = join11(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
192070
+ const tmpPath = join12(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
191065
192071
  await writeFile(tmpPath, JSON.stringify(summary2));
191066
192072
  await rename(tmpPath, path4);
191067
192073
  }
@@ -191168,531 +192174,6 @@ function resolveRepoCtx(ctx, repo) {
191168
192174
  };
191169
192175
  }
191170
192176
 
191171
- // node_modules/.pnpm/@toon-format+toon@1.4.0/node_modules/@toon-format/toon/dist/index.mjs
191172
- var LIST_ITEM_MARKER = "-";
191173
- var LIST_ITEM_PREFIX = "- ";
191174
- var COMMA2 = ",";
191175
- var PIPE = "|";
191176
- var DOT = ".";
191177
- var NULL_LITERAL = "null";
191178
- var TRUE_LITERAL = "true";
191179
- var FALSE_LITERAL = "false";
191180
- var BACKSLASH = "\\";
191181
- var DOUBLE_QUOTE = '"';
191182
- var TAB = " ";
191183
- var DELIMITERS = {
191184
- comma: COMMA2,
191185
- tab: TAB,
191186
- pipe: PIPE
191187
- };
191188
- var DEFAULT_DELIMITER = DELIMITERS.comma;
191189
- function escapeString(value2) {
191190
- return value2.replace(/\\/g, `${BACKSLASH}${BACKSLASH}`).replace(/"/g, `${BACKSLASH}${DOUBLE_QUOTE}`).replace(/\n/g, `${BACKSLASH}n`).replace(/\r/g, `${BACKSLASH}r`).replace(/\t/g, `${BACKSLASH}t`);
191191
- }
191192
- function isBooleanOrNullLiteral(token) {
191193
- return token === TRUE_LITERAL || token === FALSE_LITERAL || token === NULL_LITERAL;
191194
- }
191195
- function normalizeValue(value2) {
191196
- if (value2 === null) return null;
191197
- if (typeof value2 === "string" || typeof value2 === "boolean") return value2;
191198
- if (typeof value2 === "number") {
191199
- if (Object.is(value2, -0)) return 0;
191200
- if (!Number.isFinite(value2)) return null;
191201
- return value2;
191202
- }
191203
- if (typeof value2 === "bigint") {
191204
- if (value2 >= Number.MIN_SAFE_INTEGER && value2 <= Number.MAX_SAFE_INTEGER) return Number(value2);
191205
- return value2.toString();
191206
- }
191207
- if (value2 instanceof Date) return value2.toISOString();
191208
- if (Array.isArray(value2)) return value2.map(normalizeValue);
191209
- if (value2 instanceof Set) return Array.from(value2).map(normalizeValue);
191210
- if (value2 instanceof Map) return Object.fromEntries(Array.from(value2, ([k2, v]) => [String(k2), normalizeValue(v)]));
191211
- if (isPlainObject8(value2)) {
191212
- const normalized = {};
191213
- for (const key in value2) if (Object.prototype.hasOwnProperty.call(value2, key)) normalized[key] = normalizeValue(value2[key]);
191214
- return normalized;
191215
- }
191216
- return null;
191217
- }
191218
- function isJsonPrimitive(value2) {
191219
- return value2 === null || typeof value2 === "string" || typeof value2 === "number" || typeof value2 === "boolean";
191220
- }
191221
- function isJsonArray(value2) {
191222
- return Array.isArray(value2);
191223
- }
191224
- function isJsonObject4(value2) {
191225
- return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
191226
- }
191227
- function isEmptyObject2(value2) {
191228
- return Object.keys(value2).length === 0;
191229
- }
191230
- function isPlainObject8(value2) {
191231
- if (value2 === null || typeof value2 !== "object") return false;
191232
- const prototype = Object.getPrototypeOf(value2);
191233
- return prototype === null || prototype === Object.prototype;
191234
- }
191235
- function isArrayOfPrimitives(value2) {
191236
- return value2.length === 0 || value2.every((item) => isJsonPrimitive(item));
191237
- }
191238
- function isArrayOfArrays(value2) {
191239
- return value2.length === 0 || value2.every((item) => isJsonArray(item));
191240
- }
191241
- function isArrayOfObjects(value2) {
191242
- return value2.length === 0 || value2.every((item) => isJsonObject4(item));
191243
- }
191244
- function isValidUnquotedKey(key) {
191245
- return /^[A-Z_][\w.]*$/i.test(key);
191246
- }
191247
- function isIdentifierSegment(key) {
191248
- return /^[A-Z_]\w*$/i.test(key);
191249
- }
191250
- function isSafeUnquoted(value2, delimiter2 = DEFAULT_DELIMITER) {
191251
- if (!value2) return false;
191252
- if (value2 !== value2.trim()) return false;
191253
- if (isBooleanOrNullLiteral(value2) || isNumericLike(value2)) return false;
191254
- if (value2.includes(":")) return false;
191255
- if (value2.includes('"') || value2.includes("\\")) return false;
191256
- if (/[[\]{}]/.test(value2)) return false;
191257
- if (/[\n\r\t]/.test(value2)) return false;
191258
- if (value2.includes(delimiter2)) return false;
191259
- if (value2.startsWith(LIST_ITEM_MARKER)) return false;
191260
- return true;
191261
- }
191262
- function isNumericLike(value2) {
191263
- return /^-?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i.test(value2) || /^0\d+$/.test(value2);
191264
- }
191265
- var QUOTED_KEY_MARKER = Symbol("quotedKey");
191266
- function tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, flattenDepth) {
191267
- if (options.keyFolding !== "safe") return;
191268
- if (!isJsonObject4(value2)) return;
191269
- const { segments, tail, leafValue } = collectSingleKeyChain(key, value2, flattenDepth ?? options.flattenDepth);
191270
- if (segments.length < 2) return;
191271
- if (!segments.every((seg) => isIdentifierSegment(seg))) return;
191272
- const foldedKey = buildFoldedKey(segments);
191273
- const absolutePath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
191274
- if (siblings.includes(foldedKey)) return;
191275
- if (rootLiteralKeys && rootLiteralKeys.has(absolutePath)) return;
191276
- return {
191277
- foldedKey,
191278
- remainder: tail,
191279
- leafValue,
191280
- segmentCount: segments.length
191281
- };
191282
- }
191283
- function collectSingleKeyChain(startKey, startValue, maxDepth) {
191284
- const segments = [startKey];
191285
- let currentValue = startValue;
191286
- while (segments.length < maxDepth) {
191287
- if (!isJsonObject4(currentValue)) break;
191288
- const keys = Object.keys(currentValue);
191289
- if (keys.length !== 1) break;
191290
- const nextKey = keys[0];
191291
- const nextValue = currentValue[nextKey];
191292
- segments.push(nextKey);
191293
- currentValue = nextValue;
191294
- }
191295
- if (!isJsonObject4(currentValue) || isEmptyObject2(currentValue)) return {
191296
- segments,
191297
- tail: void 0,
191298
- leafValue: currentValue
191299
- };
191300
- return {
191301
- segments,
191302
- tail: currentValue,
191303
- leafValue: currentValue
191304
- };
191305
- }
191306
- function buildFoldedKey(segments) {
191307
- return segments.join(DOT);
191308
- }
191309
- function encodePrimitive(value2, delimiter2) {
191310
- if (value2 === null) return NULL_LITERAL;
191311
- if (typeof value2 === "boolean") return String(value2);
191312
- if (typeof value2 === "number") return String(value2);
191313
- return encodeStringLiteral(value2, delimiter2);
191314
- }
191315
- function encodeStringLiteral(value2, delimiter2 = DEFAULT_DELIMITER) {
191316
- if (isSafeUnquoted(value2, delimiter2)) return value2;
191317
- return `${DOUBLE_QUOTE}${escapeString(value2)}${DOUBLE_QUOTE}`;
191318
- }
191319
- function encodeKey(key) {
191320
- if (isValidUnquotedKey(key)) return key;
191321
- return `${DOUBLE_QUOTE}${escapeString(key)}${DOUBLE_QUOTE}`;
191322
- }
191323
- function encodeAndJoinPrimitives(values, delimiter2 = DEFAULT_DELIMITER) {
191324
- return values.map((v) => encodePrimitive(v, delimiter2)).join(delimiter2);
191325
- }
191326
- function formatHeader(length, options) {
191327
- const key = options?.key;
191328
- const fields = options?.fields;
191329
- const delimiter2 = options?.delimiter ?? COMMA2;
191330
- let header = "";
191331
- if (key) header += encodeKey(key);
191332
- header += `[${length}${delimiter2 !== DEFAULT_DELIMITER ? delimiter2 : ""}]`;
191333
- if (fields) {
191334
- const quotedFields = fields.map((f) => encodeKey(f));
191335
- header += `{${quotedFields.join(delimiter2)}}`;
191336
- }
191337
- header += ":";
191338
- return header;
191339
- }
191340
- function* encodeJsonValue(value2, options, depth) {
191341
- if (isJsonPrimitive(value2)) {
191342
- const encodedPrimitive = encodePrimitive(value2, options.delimiter);
191343
- if (encodedPrimitive !== "") yield encodedPrimitive;
191344
- return;
191345
- }
191346
- if (isJsonArray(value2)) yield* encodeArrayLines(void 0, value2, depth, options);
191347
- else if (isJsonObject4(value2)) yield* encodeObjectLines(value2, depth, options);
191348
- }
191349
- function* encodeObjectLines(value2, depth, options, rootLiteralKeys, pathPrefix, remainingDepth) {
191350
- const keys = Object.keys(value2);
191351
- if (depth === 0 && !rootLiteralKeys) rootLiteralKeys = new Set(keys.filter((k2) => k2.includes(".")));
191352
- const effectiveFlattenDepth = remainingDepth ?? options.flattenDepth;
191353
- for (const [key, val] of Object.entries(value2)) yield* encodeKeyValuePairLines(key, val, depth, options, keys, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
191354
- }
191355
- function* encodeKeyValuePairLines(key, value2, depth, options, siblings, rootLiteralKeys, pathPrefix, flattenDepth) {
191356
- const currentPath = pathPrefix ? `${pathPrefix}${DOT}${key}` : key;
191357
- const effectiveFlattenDepth = flattenDepth ?? options.flattenDepth;
191358
- if (options.keyFolding === "safe" && siblings) {
191359
- const foldResult = tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
191360
- if (foldResult) {
191361
- const { foldedKey, remainder, leafValue, segmentCount } = foldResult;
191362
- const encodedFoldedKey = encodeKey(foldedKey);
191363
- if (remainder === void 0) {
191364
- if (isJsonPrimitive(leafValue)) {
191365
- yield indentedLine(depth, `${encodedFoldedKey}: ${encodePrimitive(leafValue, options.delimiter)}`, options.indent);
191366
- return;
191367
- } else if (isJsonArray(leafValue)) {
191368
- yield* encodeArrayLines(foldedKey, leafValue, depth, options);
191369
- return;
191370
- } else if (isJsonObject4(leafValue) && isEmptyObject2(leafValue)) {
191371
- yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
191372
- return;
191373
- }
191374
- }
191375
- if (isJsonObject4(remainder)) {
191376
- yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
191377
- const remainingDepth = effectiveFlattenDepth - segmentCount;
191378
- const foldedPath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
191379
- yield* encodeObjectLines(remainder, depth + 1, options, rootLiteralKeys, foldedPath, remainingDepth);
191380
- return;
191381
- }
191382
- }
191383
- }
191384
- const encodedKey = encodeKey(key);
191385
- if (isJsonPrimitive(value2)) yield indentedLine(depth, `${encodedKey}: ${encodePrimitive(value2, options.delimiter)}`, options.indent);
191386
- else if (isJsonArray(value2)) yield* encodeArrayLines(key, value2, depth, options);
191387
- else if (isJsonObject4(value2)) {
191388
- yield indentedLine(depth, `${encodedKey}:`, options.indent);
191389
- if (!isEmptyObject2(value2)) yield* encodeObjectLines(value2, depth + 1, options, rootLiteralKeys, currentPath, effectiveFlattenDepth);
191390
- }
191391
- }
191392
- function* encodeArrayLines(key, value2, depth, options) {
191393
- if (value2.length === 0) {
191394
- yield indentedLine(depth, formatHeader(0, {
191395
- key,
191396
- delimiter: options.delimiter
191397
- }), options.indent);
191398
- return;
191399
- }
191400
- if (isArrayOfPrimitives(value2)) {
191401
- yield indentedLine(depth, encodeInlineArrayLine(value2, options.delimiter, key), options.indent);
191402
- return;
191403
- }
191404
- if (isArrayOfArrays(value2)) {
191405
- if (value2.every((arr) => isArrayOfPrimitives(arr))) {
191406
- yield* encodeArrayOfArraysAsListItemsLines(key, value2, depth, options);
191407
- return;
191408
- }
191409
- }
191410
- if (isArrayOfObjects(value2)) {
191411
- const header = extractTabularHeader(value2);
191412
- if (header) yield* encodeArrayOfObjectsAsTabularLines(key, value2, header, depth, options);
191413
- else yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
191414
- return;
191415
- }
191416
- yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
191417
- }
191418
- function* encodeArrayOfArraysAsListItemsLines(prefix, values, depth, options) {
191419
- yield indentedLine(depth, formatHeader(values.length, {
191420
- key: prefix,
191421
- delimiter: options.delimiter
191422
- }), options.indent);
191423
- for (const arr of values) if (isArrayOfPrimitives(arr)) {
191424
- const arrayLine = encodeInlineArrayLine(arr, options.delimiter);
191425
- yield indentedListItem(depth + 1, arrayLine, options.indent);
191426
- }
191427
- }
191428
- function encodeInlineArrayLine(values, delimiter2, prefix) {
191429
- const header = formatHeader(values.length, {
191430
- key: prefix,
191431
- delimiter: delimiter2
191432
- });
191433
- const joinedValue = encodeAndJoinPrimitives(values, delimiter2);
191434
- if (values.length === 0) return header;
191435
- return `${header} ${joinedValue}`;
191436
- }
191437
- function* encodeArrayOfObjectsAsTabularLines(prefix, rows, header, depth, options) {
191438
- yield indentedLine(depth, formatHeader(rows.length, {
191439
- key: prefix,
191440
- fields: header,
191441
- delimiter: options.delimiter
191442
- }), options.indent);
191443
- yield* writeTabularRowsLines(rows, header, depth + 1, options);
191444
- }
191445
- function extractTabularHeader(rows) {
191446
- if (rows.length === 0) return;
191447
- const firstRow = rows[0];
191448
- const firstKeys = Object.keys(firstRow);
191449
- if (firstKeys.length === 0) return;
191450
- if (isTabularArray(rows, firstKeys)) return firstKeys;
191451
- }
191452
- function isTabularArray(rows, header) {
191453
- for (const row of rows) {
191454
- if (Object.keys(row).length !== header.length) return false;
191455
- for (const key of header) {
191456
- if (!(key in row)) return false;
191457
- if (!isJsonPrimitive(row[key])) return false;
191458
- }
191459
- }
191460
- return true;
191461
- }
191462
- function* writeTabularRowsLines(rows, header, depth, options) {
191463
- for (const row of rows) yield indentedLine(depth, encodeAndJoinPrimitives(header.map((key) => row[key]), options.delimiter), options.indent);
191464
- }
191465
- function* encodeMixedArrayAsListItemsLines(prefix, items, depth, options) {
191466
- yield indentedLine(depth, formatHeader(items.length, {
191467
- key: prefix,
191468
- delimiter: options.delimiter
191469
- }), options.indent);
191470
- for (const item of items) yield* encodeListItemValueLines(item, depth + 1, options);
191471
- }
191472
- function* encodeObjectAsListItemLines(obj, depth, options) {
191473
- if (isEmptyObject2(obj)) {
191474
- yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
191475
- return;
191476
- }
191477
- const entries = Object.entries(obj);
191478
- if (entries.length === 1) {
191479
- const [key, value2] = entries[0];
191480
- if (isJsonArray(value2) && isArrayOfObjects(value2)) {
191481
- const header = extractTabularHeader(value2);
191482
- if (header) {
191483
- yield indentedListItem(depth, formatHeader(value2.length, {
191484
- key,
191485
- fields: header,
191486
- delimiter: options.delimiter
191487
- }), options.indent);
191488
- yield* writeTabularRowsLines(value2, header, depth + 1, options);
191489
- return;
191490
- }
191491
- }
191492
- }
191493
- yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
191494
- yield* encodeObjectLines(obj, depth + 1, options);
191495
- }
191496
- function* encodeListItemValueLines(value2, depth, options) {
191497
- if (isJsonPrimitive(value2)) yield indentedListItem(depth, encodePrimitive(value2, options.delimiter), options.indent);
191498
- else if (isJsonArray(value2)) if (isArrayOfPrimitives(value2)) yield indentedListItem(depth, encodeInlineArrayLine(value2, options.delimiter), options.indent);
191499
- else {
191500
- yield indentedListItem(depth, formatHeader(value2.length, { delimiter: options.delimiter }), options.indent);
191501
- for (const item of value2) yield* encodeListItemValueLines(item, depth + 1, options);
191502
- }
191503
- else if (isJsonObject4(value2)) yield* encodeObjectAsListItemLines(value2, depth, options);
191504
- }
191505
- function indentedLine(depth, content, indentSize) {
191506
- return " ".repeat(indentSize * depth) + content;
191507
- }
191508
- function indentedListItem(depth, content, indentSize) {
191509
- return indentedLine(depth, LIST_ITEM_PREFIX + content, indentSize);
191510
- }
191511
- function encode4(input2, options) {
191512
- return Array.from(encodeLines(input2, options)).join("\n");
191513
- }
191514
- function encodeLines(input2, options) {
191515
- return encodeJsonValue(normalizeValue(input2), resolveOptions(options), 0);
191516
- }
191517
- function resolveOptions(options) {
191518
- return {
191519
- indent: options?.indent ?? 2,
191520
- delimiter: options?.delimiter ?? DEFAULT_DELIMITER,
191521
- keyFolding: options?.keyFolding ?? "off",
191522
- flattenDepth: options?.flattenDepth ?? Number.POSITIVE_INFINITY
191523
- };
191524
- }
191525
-
191526
- // mcp/geminiSanitizer.ts
191527
- function parseStringEnumBranch(item) {
191528
- if (!item || typeof item !== "object") return null;
191529
- const record3 = item;
191530
- if (Array.isArray(record3.enum)) {
191531
- const strings = record3.enum.filter((v) => typeof v === "string");
191532
- return strings.length === record3.enum.length && strings.length > 0 ? { values: strings } : null;
191533
- }
191534
- if (typeof record3.const === "string") {
191535
- return { values: [record3.const] };
191536
- }
191537
- return null;
191538
- }
191539
- function collapseStringUnion(branches) {
191540
- const values = [];
191541
- for (const item of branches) {
191542
- const parsed2 = parseStringEnumBranch(item);
191543
- if (!parsed2) return null;
191544
- values.push(...parsed2.values);
191545
- }
191546
- if (values.length === 0) return null;
191547
- return { type: "string", enum: [...new Set(values)] };
191548
- }
191549
- function sanitizeForGemini(schema2) {
191550
- if (!schema2 || typeof schema2 !== "object") return schema2;
191551
- if (Array.isArray(schema2)) return schema2.map(sanitizeForGemini);
191552
- const source = schema2;
191553
- if (Array.isArray(source.enum) && typeof source.type !== "string") {
191554
- const allStrings = source.enum.every((v) => typeof v === "string");
191555
- if (allStrings) {
191556
- const result = { type: "string", enum: source.enum };
191557
- if (typeof source.description === "string") result.description = source.description;
191558
- return result;
191559
- }
191560
- }
191561
- for (const unionKey of ["anyOf", "oneOf"]) {
191562
- const branches = source[unionKey];
191563
- if (Array.isArray(branches) && branches.length > 0) {
191564
- const collapsed = collapseStringUnion(branches);
191565
- if (collapsed) {
191566
- const result = { ...collapsed };
191567
- if (typeof source.description === "string") result.description = source.description;
191568
- return result;
191569
- }
191570
- }
191571
- }
191572
- if (Array.isArray(source.anyOf) || Array.isArray(source.oneOf)) {
191573
- const result = {};
191574
- if (Array.isArray(source.anyOf)) result.anyOf = source.anyOf.map(sanitizeForGemini);
191575
- if (Array.isArray(source.oneOf)) result.oneOf = source.oneOf.map(sanitizeForGemini);
191576
- return result;
191577
- }
191578
- const sanitized = {};
191579
- for (const [key, value2] of Object.entries(source)) {
191580
- if (key === "$schema") continue;
191581
- if (key === "$defs") {
191582
- sanitized.definitions = sanitizeForGemini(value2);
191583
- continue;
191584
- }
191585
- sanitized[key] = sanitizeForGemini(value2);
191586
- }
191587
- return sanitized;
191588
- }
191589
- function wrapJsonSchemaProducer(producer) {
191590
- return new Proxy(producer, {
191591
- get(target, prop, receiver) {
191592
- const value2 = Reflect.get(target, prop, receiver);
191593
- if ((prop === "input" || prop === "output") && typeof value2 === "function") {
191594
- const fn2 = value2;
191595
- return (...args2) => sanitizeForGemini(fn2.apply(target, args2));
191596
- }
191597
- return value2;
191598
- }
191599
- });
191600
- }
191601
- function wrapStandard(standard) {
191602
- return new Proxy(standard, {
191603
- get(target, prop, receiver) {
191604
- if (prop === "jsonSchema") {
191605
- const value2 = Reflect.get(target, prop, receiver);
191606
- if (value2 && typeof value2 === "object") {
191607
- return wrapJsonSchemaProducer(value2);
191608
- }
191609
- return value2;
191610
- }
191611
- return Reflect.get(target, prop, receiver);
191612
- }
191613
- });
191614
- }
191615
- function wrapSchemaForGemini(schema2) {
191616
- return new Proxy(schema2, {
191617
- get(target, prop, receiver) {
191618
- if (prop === "~standard") {
191619
- const value2 = Reflect.get(target, prop, receiver);
191620
- if (value2 && typeof value2 === "object") {
191621
- return wrapStandard(value2);
191622
- }
191623
- return value2;
191624
- }
191625
- if (prop === "toJsonSchema") {
191626
- const method = Reflect.get(target, prop, receiver);
191627
- if (typeof method === "function") {
191628
- return () => sanitizeForGemini(method.call(target));
191629
- }
191630
- return method;
191631
- }
191632
- return Reflect.get(target, prop, receiver);
191633
- }
191634
- });
191635
- }
191636
- function sanitizeToolForGemini(tool2) {
191637
- if (!tool2.parameters) return tool2;
191638
- return { ...tool2, parameters: wrapSchemaForGemini(tool2.parameters) };
191639
- }
191640
- function isGeminiRouted(ctx) {
191641
- const effective = ctx.payload.proxyModel ?? ctx.resolvedModel ?? ctx.payload.model;
191642
- if (!effective) return true;
191643
- const normalized = effective.toLowerCase();
191644
- if (normalized.includes("gemini")) return true;
191645
- if (!normalized.includes("/")) return true;
191646
- return false;
191647
- }
191648
-
191649
- // mcp/shared.ts
191650
- function getHttpStatus(err) {
191651
- if (typeof err !== "object" || err === null || !("status" in err)) return void 0;
191652
- return typeof err.status === "number" ? err.status : void 0;
191653
- }
191654
- var tool = (toolDef) => toolDef;
191655
- var handleToolSuccess = (data) => {
191656
- const text = typeof data === "string" ? data : encode4(data);
191657
- return {
191658
- content: [{ type: "text", text }]
191659
- };
191660
- };
191661
- var handleToolError = (error62) => {
191662
- const errorMessage = error62 instanceof Error ? error62.message : String(error62);
191663
- return {
191664
- content: [
191665
- {
191666
- type: "text",
191667
- text: `Error: ${errorMessage}`
191668
- }
191669
- ],
191670
- isError: true
191671
- };
191672
- };
191673
- var execute = (fn2, toolName) => {
191674
- const _fn = async (params) => {
191675
- try {
191676
- const result = await fn2(params);
191677
- return handleToolSuccess(result);
191678
- } catch (error62) {
191679
- const errorMessage = error62 instanceof Error ? error62.message : String(error62);
191680
- const prefix = toolName ? `[${toolName}]` : "tool";
191681
- log.info(`${prefix} error: ${errorMessage}`);
191682
- log.debug(`${prefix} params: ${formatJsonValue(params)}`);
191683
- return handleToolError(error62);
191684
- }
191685
- };
191686
- return _fn;
191687
- };
191688
- var addTools = (ctx, server, tools) => {
191689
- const shouldSanitize = isGeminiRouted(ctx);
191690
- for (const tool2 of tools) {
191691
- server.addTool(shouldSanitize ? sanitizeToolForGemini(tool2) : tool2);
191692
- }
191693
- return server;
191694
- };
191695
-
191696
192177
  // mcp/git.ts
191697
192178
  function getPushDestination(branch, storedDest, cwd) {
191698
192179
  if (storedDest && storedDest.localBranch === branch) {
@@ -191818,7 +192299,8 @@ async function runPrepushHook(ctx, retryTool) {
191818
192299
  if (!ctx.prepushScript) return false;
191819
192300
  const prepushHook = await executeLifecycleHook({
191820
192301
  event: "prepush",
191821
- script: ctx.prepushScript
192302
+ script: ctx.prepushScript,
192303
+ shell: ctx.payload.shell
191822
192304
  });
191823
192305
  if (prepushHook.failure) {
191824
192306
  ctx.toolState.prepushFailureCount += 1;
@@ -192213,8 +192695,8 @@ function countAhead(head, base, cwd) {
192213
192695
  function spillGitOutput(params) {
192214
192696
  const tempDir = process.env.PULLFROG_TEMP_DIR;
192215
192697
  if (!tempDir) throw new Error("PULLFROG_TEMP_DIR not set");
192216
- const outputPath = join12(tempDir, `git-${params.command}-${randomUUID3().slice(0, 8)}.txt`);
192217
- writeFileSync8(outputPath, params.output);
192698
+ const outputPath = join13(tempDir, `git-${params.command}-${randomUUID4().slice(0, 8)}.txt`);
192699
+ writeFileSync9(outputPath, params.output);
192218
192700
  const previewByLines = params.output.split("\n").slice(0, OVERFLOW_PREVIEW_LINES).join("\n");
192219
192701
  const preview = previewByLines.length <= OVERFLOW_PREVIEW_MAX_CHARS ? previewByLines : `${previewByLines.slice(0, OVERFLOW_PREVIEW_MAX_CHARS)}\u2026`;
192220
192702
  log.info(
@@ -193740,9 +194222,9 @@ async function reportReviewNodeId(ctx, params) {
193740
194222
  import { execFileSync as execFileSync6, execSync as execSync2 } from "node:child_process";
193741
194223
  import { mkdtempSync, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
193742
194224
  import { tmpdir as tmpdir2 } from "node:os";
193743
- import { join as join13 } from "node:path";
194225
+ import { join as join14 } from "node:path";
193744
194226
  function createTempDirectory() {
193745
- const sharedTempDir = mkdtempSync(join13(tmpdir2(), "pullfrog-"));
194227
+ const sharedTempDir = mkdtempSync(join14(tmpdir2(), "pullfrog-"));
193746
194228
  process.env.PULLFROG_TEMP_DIR = sharedTempDir;
193747
194229
  log.info(`\xBB created temp dir at ${sharedTempDir}`);
193748
194230
  return sharedTempDir;
@@ -193787,13 +194269,13 @@ function wipeRunnerLeakSurface() {
193787
194269
  return [];
193788
194270
  }
193789
194271
  };
193790
- const fileCommandsDir = join13(runnerTemp, "_runner_file_commands");
194272
+ const fileCommandsDir = join14(runnerTemp, "_runner_file_commands");
193791
194273
  for (const entry of listDir(fileCommandsDir)) {
193792
- tryUnlink(join13(fileCommandsDir, entry));
194274
+ tryUnlink(join14(fileCommandsDir, entry));
193793
194275
  }
193794
194276
  for (const entry of listDir(runnerTemp)) {
193795
194277
  if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
193796
- tryUnlink(join13(runnerTemp, entry));
194278
+ tryUnlink(join14(runnerTemp, entry));
193797
194279
  }
193798
194280
  }
193799
194281
  if (wiped.length > 0) {
@@ -194268,6 +194750,7 @@ async function checkoutPrBranch(pr, params) {
194268
194750
  const postCheckoutHook = await executeLifecycleHook({
194269
194751
  event: "post-checkout",
194270
194752
  script: params.postCheckoutScript,
194753
+ shell: params.shell,
194271
194754
  normalizeWorkingTreeAfter: true
194272
194755
  });
194273
194756
  return { hookWarning: postCheckoutHook.warning };
@@ -194349,11 +194832,11 @@ function CheckoutPrTool(ctx) {
194349
194832
  headSha: checkoutSha
194350
194833
  });
194351
194834
  if (incremental.status === "ok") {
194352
- incrementalDiffPath = join14(
194835
+ incrementalDiffPath = join15(
194353
194836
  tempDir,
194354
194837
  `pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
194355
194838
  );
194356
- writeFileSync9(incrementalDiffPath, incremental.diff);
194839
+ writeFileSync10(incrementalDiffPath, incremental.diff);
194357
194840
  log.info(
194358
194841
  `\xBB incremental diff computed (${incremental.diff.length} bytes) \u2192 ${incrementalDiffPath}`
194359
194842
  );
@@ -194366,8 +194849,8 @@ function CheckoutPrTool(ctx) {
194366
194849
  const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
194367
194850
  log.debug(`formatted diff preview (first 100 lines):
194368
194851
  ${diffPreview}`);
194369
- const diffPath = join14(tempDir, `pr-${pull_number}-${headShort}.diff`);
194370
- writeFileSync9(diffPath, formatResult.content);
194852
+ const diffPath = join15(tempDir, `pr-${pull_number}-${headShort}.diff`);
194853
+ writeFileSync10(diffPath, formatResult.content);
194371
194854
  log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
194372
194855
  primary.diffCoverage = createDiffCoverageState({
194373
194856
  diffPath,
@@ -194524,8 +195007,8 @@ ${dirty}`
194524
195007
  }
194525
195008
 
194526
195009
  // mcp/checkSuite.ts
194527
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync10 } from "node:fs";
194528
- import { join as join15 } from "node:path";
195010
+ import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync11 } from "node:fs";
195011
+ import { join as join16 } from "node:path";
194529
195012
  var GetCheckSuiteLogs = type({
194530
195013
  check_suite_id: type.number.describe("the id from check_suite.id")
194531
195014
  });
@@ -194621,7 +195104,7 @@ function GetCheckSuiteLogsTool(ctx) {
194621
195104
  if (!tempDir) {
194622
195105
  throw new Error("PULLFROG_TEMP_DIR not set");
194623
195106
  }
194624
- const logsDir = join15(tempDir, "ci-logs");
195107
+ const logsDir = join16(tempDir, "ci-logs");
194625
195108
  mkdirSync8(logsDir, { recursive: true });
194626
195109
  const jobResults = [];
194627
195110
  for (const run of failedRuns) {
@@ -194648,8 +195131,8 @@ function GetCheckSuiteLogsTool(ctx) {
194648
195131
  );
194649
195132
  }
194650
195133
  const logsText = await logsResult.text();
194651
- const logPath = join15(logsDir, `job-${job.id}.log`);
194652
- writeFileSync10(logPath, logsText);
195134
+ const logPath = join16(logsDir, `job-${job.id}.log`);
195135
+ writeFileSync11(logPath, logsText);
194653
195136
  const analysis = analyzeLog(logsText, 80);
194654
195137
  const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
194655
195138
  jobResults.push({
@@ -194697,8 +195180,8 @@ function GetCheckSuiteLogsTool(ctx) {
194697
195180
  }
194698
195181
 
194699
195182
  // mcp/commitInfo.ts
194700
- import { writeFileSync as writeFileSync11 } from "node:fs";
194701
- import { join as join16 } from "node:path";
195183
+ import { writeFileSync as writeFileSync12 } from "node:fs";
195184
+ import { join as join17 } from "node:path";
194702
195185
  var CommitInfo = type({
194703
195186
  sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
194704
195187
  });
@@ -194722,8 +195205,8 @@ function CommitInfoTool(ctx) {
194722
195205
  "PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
194723
195206
  );
194724
195207
  }
194725
- const diffFile = join16(tempDir, `commit-${sha.slice(0, 7)}.diff`);
194726
- writeFileSync11(diffFile, formatResult.content);
195208
+ const diffFile = join17(tempDir, `commit-${sha.slice(0, 7)}.diff`);
195209
+ writeFileSync12(diffFile, formatResult.content);
194727
195210
  log.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
194728
195211
  return {
194729
195212
  sha: data.sha,
@@ -194860,13 +195343,13 @@ import { performance as performance9 } from "node:perf_hooks";
194860
195343
 
194861
195344
  // prep/installNodeDependencies.ts
194862
195345
  import { existsSync as existsSync8 } from "node:fs";
194863
- import { join as join18 } from "node:path";
195346
+ import { join as join19 } from "node:path";
194864
195347
 
194865
195348
  // utils/packageManager.ts
194866
195349
  var import_semver2 = __toESM(require_semver2(), 1);
194867
195350
  import { existsSync as existsSync7 } from "node:fs";
194868
195351
  import { mkdir, readFile as readFile2 } from "node:fs/promises";
194869
- import { delimiter, join as join17 } from "node:path";
195352
+ import { delimiter, join as join18 } from "node:path";
194870
195353
  var SUPPORTED_NAMES = ["npm", "pnpm", "yarn", "bun"];
194871
195354
  var COREPACK_MANAGED = ["pnpm", "yarn"];
194872
195355
  function isSupported(name) {
@@ -194904,7 +195387,7 @@ function parseDevEnginesField(field) {
194904
195387
  };
194905
195388
  }
194906
195389
  async function resolvePackageManagerSpec(cwd) {
194907
- const pkgPath = join17(cwd, "package.json");
195390
+ const pkgPath = join18(cwd, "package.json");
194908
195391
  if (!existsSync7(pkgPath)) return null;
194909
195392
  let pkg;
194910
195393
  try {
@@ -194963,7 +195446,7 @@ async function currentVersion(name) {
194963
195446
  return result.stdout.trim();
194964
195447
  }
194965
195448
  function packageManagerBinDir(tmpdir3) {
194966
- return join17(tmpdir3, "pm-bin");
195449
+ return join18(tmpdir3, "pm-bin");
194967
195450
  }
194968
195451
  async function ensurePackageManager(params) {
194969
195452
  const spec = params.spec;
@@ -195026,7 +195509,7 @@ async function installFallback(name, installSpec) {
195026
195509
  });
195027
195510
  if (result.exitCode !== 0) return result.stderr || `failed to install ${name}`;
195028
195511
  if (name === "deno") {
195029
- const denoPath = join17(process.env.HOME || "", ".deno", "bin");
195512
+ const denoPath = join18(process.env.HOME || "", ".deno", "bin");
195030
195513
  process.env.PATH = `${denoPath}:${process.env.PATH}`;
195031
195514
  }
195032
195515
  log.info(`\xBB installed ${name}`);
@@ -195059,7 +195542,7 @@ async function isCommandAvailable2(command) {
195059
195542
  var installNodeDependencies = {
195060
195543
  name: "installNodeDependencies",
195061
195544
  shouldRun: () => {
195062
- const packageJsonPath = join18(process.cwd(), "package.json");
195545
+ const packageJsonPath = join19(process.cwd(), "package.json");
195063
195546
  return existsSync8(packageJsonPath);
195064
195547
  },
195065
195548
  run: async (options) => {
@@ -195155,12 +195638,12 @@ ${errorMessage}`]
195155
195638
 
195156
195639
  // prep/installPythonDependencies.ts
195157
195640
  import { existsSync as existsSync9, readFileSync as readFileSync5 } from "node:fs";
195158
- import { join as join19 } from "node:path";
195641
+ import { join as join20 } from "node:path";
195159
195642
  function declaresBuildSystem(path4) {
195160
195643
  return /^\s*\[\s*build-system\s*\]/m.test(readFileSync5(path4, "utf8"));
195161
195644
  }
195162
195645
  function configApplies(config3, cwd) {
195163
- const path4 = join19(cwd, config3.file);
195646
+ const path4 = join20(cwd, config3.file);
195164
195647
  return existsSync9(path4) && (config3.applies?.(path4) ?? true);
195165
195648
  }
195166
195649
  var PYTHON_CONFIGS = [
@@ -195484,411 +195967,6 @@ function AwaitDependencyInstallationTool(ctx) {
195484
195967
  // mcp/gh.ts
195485
195968
  import { mkdirSync as mkdirSync9 } from "node:fs";
195486
195969
  import { join as join21 } from "node:path";
195487
-
195488
- // mcp/shell.ts
195489
- import { spawn as spawn3, spawnSync as spawnSync6 } from "node:child_process";
195490
- import { randomUUID as randomUUID4 } from "node:crypto";
195491
- import { closeSync, openSync, writeFileSync as writeFileSync12 } from "node:fs";
195492
- import { userInfo as userInfo2 } from "node:os";
195493
- import { join as join20 } from "node:path";
195494
- import { setTimeout as sleep3 } from "node:timers/promises";
195495
- var ShellParams = type({
195496
- command: "string",
195497
- // advisory and unread — no code path consumes it. kept in the schema only
195498
- // because it nudges the model to state intent before running a command;
195499
- // REQUIRING it cost a full rejected turn every time one was omitted. #1140.
195500
- "description?": "string",
195501
- "timeout?": type.number.describe(
195502
- "Timeout in MILLISECONDS (not seconds). Default 30000 (30s), max 120000 (2m). e.g. timeout: 180000 for 3 minutes; timeout: 180 means 180ms and will kill the process almost immediately."
195503
- ),
195504
- "working_directory?": "string",
195505
- "background?": "boolean"
195506
- });
195507
- var detectedSandboxMethod;
195508
- var usernsProbeError;
195509
- function getSandboxMethod() {
195510
- return detectSandboxMethod();
195511
- }
195512
- function detectSandboxMethod() {
195513
- if (detectedSandboxMethod !== void 0) {
195514
- return detectedSandboxMethod;
195515
- }
195516
- if (process.env.CI !== "true") {
195517
- detectedSandboxMethod = "none";
195518
- log.debug("sandbox disabled (CI !== true)");
195519
- return "none";
195520
- }
195521
- try {
195522
- const result = spawnSync6("unshare", ["--pid", "--fork", "--mount-proc", "true"], {
195523
- timeout: 5e3,
195524
- stdio: "ignore"
195525
- });
195526
- if (result.status === 0) {
195527
- detectedSandboxMethod = "unshare";
195528
- log.debug("PID namespace isolation enabled (unprivileged unshare)");
195529
- return "unshare";
195530
- }
195531
- } catch {
195532
- }
195533
- try {
195534
- const result = spawnSync6("sudo", ["unshare", "--pid", "--fork", "--mount-proc", "true"], {
195535
- timeout: 5e3,
195536
- stdio: "ignore"
195537
- });
195538
- if (result.status === 0) {
195539
- detectedSandboxMethod = "sudo-unshare";
195540
- log.debug("PID namespace isolation enabled (sudo unshare)");
195541
- return "sudo-unshare";
195542
- }
195543
- } catch {
195544
- }
195545
- try {
195546
- const result = spawnSync6(
195547
- "unshare",
195548
- [
195549
- "--user",
195550
- "--map-root-user",
195551
- "--pid",
195552
- "--fork",
195553
- "--mount",
195554
- "setpriv",
195555
- "--no-new-privs",
195556
- "--bounding-set",
195557
- "-all",
195558
- "--inh-caps",
195559
- "-all",
195560
- "--ambient-caps",
195561
- "-all",
195562
- "true"
195563
- ],
195564
- { timeout: 5e3, stdio: ["ignore", "ignore", "pipe"] }
195565
- );
195566
- if (result.status === 0) {
195567
- detectedSandboxMethod = "userns-unshare";
195568
- log.debug("PID namespace isolation enabled (unprivileged userns unshare)");
195569
- return "userns-unshare";
195570
- }
195571
- usernsProbeError = result.stderr?.toString().trim() || result.error?.message;
195572
- } catch {
195573
- }
195574
- detectedSandboxMethod = "none";
195575
- log.info("PID namespace isolation not available");
195576
- return "none";
195577
- }
195578
- var PROC_CLEANUP = "umount /proc 2>/dev/null; umount /proc 2>/dev/null; mount -t proc proc /proc 2>/dev/null;";
195579
- var SOCKET_CLEANUP = [
195580
- "/var/run/docker.sock",
195581
- "/run/docker.sock",
195582
- "/var/run/podman/podman.sock",
195583
- "/run/podman/podman.sock",
195584
- "/run/containerd/containerd.sock",
195585
- "/var/run/crio/crio.sock"
195586
- ].map((path4) => `mount --bind /dev/null ${path4} 2>/dev/null;`).join(" ");
195587
- function buildFsMounts(repoDir) {
195588
- const escaped = repoDir.replace(/'/g, "'\\''");
195589
- return [
195590
- `mkdir -p /var/lib/pullfrog 2>/dev/null;`,
195591
- `mount -t tmpfs tmpfs /var/lib/pullfrog 2>/dev/null;`,
195592
- `[ -n "$RUNNER_TEMP" ] && [ -d "$RUNNER_TEMP/_runner_file_commands" ] && mount -t tmpfs tmpfs "$RUNNER_TEMP/_runner_file_commands" 2>/dev/null;`,
195593
- `[ -e '${escaped}/.git' ] && mount --bind '${escaped}/.git' '${escaped}/.git' 2>/dev/null && mount -o remount,bind,ro '${escaped}/.git' 2>/dev/null;`
195594
- ].join(" ");
195595
- }
195596
- var _repoRoot;
195597
- function resolveRepoRoot() {
195598
- if (_repoRoot) return _repoRoot;
195599
- const fromEnv = process.env.GITHUB_WORKSPACE;
195600
- if (fromEnv) {
195601
- _repoRoot = fromEnv;
195602
- return _repoRoot;
195603
- }
195604
- try {
195605
- _repoRoot = spawnSync6("git", ["rev-parse", "--show-toplevel"], {
195606
- cwd: process.cwd(),
195607
- stdio: ["ignore", "pipe", "ignore"],
195608
- encoding: "utf-8"
195609
- }).stdout?.trim();
195610
- } catch {
195611
- }
195612
- if (!_repoRoot) _repoRoot = process.cwd();
195613
- return _repoRoot;
195614
- }
195615
- function spawnShell(params) {
195616
- const spawnOpts = { env: params.env, cwd: params.cwd, stdio: params.stdio, detached: true };
195617
- const sandboxMethod = detectSandboxMethod();
195618
- const ci = process.env.CI === "true";
195619
- if (ci && sandboxMethod === "none") {
195620
- throw new Error(
195621
- "PID-namespace isolation is required in CI but unavailable: unprivileged unshare, sudo unshare, and userns-nested unshare all failed. " + (usernsProbeError ? `userns probe said: ${usernsProbeError}. ` : "") + "on a containerized / Kubernetes self-hosted runner the cause is usually one of: the pod's seccomp profile blocks user-namespace creation (Pod Security Standards baseline/restricted); the pod runs as root without CAP_SETFCAP, which the kernel requires to map uid 0; or the node kernel disallows unprivileged user namespaces (Ubuntu >= 23.10, Bottlerocket, Talos). elsewhere, check that `unshare` and `setpriv` (util-linux >= 2.31, which is what added --ambient-caps) are on PATH. see https://docs.pullfrog.com/security#self-hosted-runners"
195622
- );
195623
- }
195624
- const repoRoot = resolveRepoRoot();
195625
- const fsMounts = buildFsMounts(repoRoot);
195626
- if (sandboxMethod === "unshare") {
195627
- return spawn3(
195628
- "unshare",
195629
- [
195630
- "--pid",
195631
- "--fork",
195632
- "--mount-proc",
195633
- "bash",
195634
- "-c",
195635
- `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} ${params.command}`
195636
- ],
195637
- spawnOpts
195638
- );
195639
- }
195640
- if (sandboxMethod === "userns-unshare") {
195641
- const escaped = params.command.replace(/'/g, "'\\''");
195642
- return spawn3(
195643
- "unshare",
195644
- [
195645
- "--user",
195646
- "--map-root-user",
195647
- "--pid",
195648
- "--fork",
195649
- "--mount",
195650
- "bash",
195651
- "-c",
195652
- `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec setpriv --no-new-privs --bounding-set -all --inh-caps -all --ambient-caps -all bash -c '${escaped}'`
195653
- ],
195654
- spawnOpts
195655
- );
195656
- }
195657
- if (sandboxMethod === "sudo-unshare") {
195658
- const envArgs = [];
195659
- for (const [k2, v] of Object.entries(params.env)) {
195660
- if (v !== void 0) {
195661
- envArgs.push(`${k2}=${v}`);
195662
- }
195663
- }
195664
- const username = userInfo2().username;
195665
- const pathRestore = 'export PATH="${SANDBOX_PATH:-$PATH}"; ';
195666
- const escaped = (pathRestore + params.command).replace(/'/g, "'\\''");
195667
- envArgs.push(`SANDBOX_PATH=${params.env.PATH ?? ""}`);
195668
- return spawn3(
195669
- "sudo",
195670
- [
195671
- "env",
195672
- ...envArgs,
195673
- "unshare",
195674
- "--pid",
195675
- "--fork",
195676
- "--mount-proc",
195677
- "bash",
195678
- "-c",
195679
- `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec su -p -s /bin/bash ${username} -c '${escaped}'`
195680
- ],
195681
- { ...spawnOpts, env: {} }
195682
- );
195683
- }
195684
- return spawn3("bash", ["-c", params.command], spawnOpts);
195685
- }
195686
- async function killProcessGroup(proc) {
195687
- if (!proc.pid) return;
195688
- try {
195689
- process.kill(-proc.pid, "SIGTERM");
195690
- await new Promise((r) => setTimeout(r, 200));
195691
- process.kill(-proc.pid, "SIGKILL");
195692
- } catch {
195693
- try {
195694
- proc.kill("SIGKILL");
195695
- } catch {
195696
- }
195697
- }
195698
- }
195699
- function getTempDir() {
195700
- const tempDir = process.env.PULLFROG_TEMP_DIR;
195701
- if (!tempDir) {
195702
- throw new Error("PULLFROG_TEMP_DIR not set");
195703
- }
195704
- return tempDir;
195705
- }
195706
- var MAX_OUTPUT_CHARS = 5e3;
195707
- async function runSandboxed(params) {
195708
- const proc = spawnShell({
195709
- command: params.command,
195710
- env: params.env,
195711
- cwd: params.cwd,
195712
- stdio: ["ignore", "pipe", "pipe"]
195713
- });
195714
- const stdout = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
195715
- const stderr = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
195716
- let timedOut = false, exited = false, spawnError = "";
195717
- proc.stdout?.on("data", (chunk) => {
195718
- stdout.append(chunk.toString());
195719
- });
195720
- proc.stderr?.on("data", (chunk) => {
195721
- stderr.append(chunk.toString());
195722
- });
195723
- const timeoutId = setTimeout(async () => {
195724
- if (!exited) {
195725
- timedOut = true;
195726
- await killProcessGroup(proc);
195727
- }
195728
- }, params.timeout);
195729
- const exitCode = await new Promise((resolve3) => {
195730
- const done = (code) => {
195731
- exited = true;
195732
- clearTimeout(timeoutId);
195733
- resolve3(code);
195734
- };
195735
- proc.on("exit", done);
195736
- proc.on("error", (err) => {
195737
- spawnError = err.message;
195738
- done(null);
195739
- });
195740
- });
195741
- proc.stdout?.destroy();
195742
- proc.stderr?.destroy();
195743
- const outText = stdout.toString();
195744
- const errText = stderr.toString();
195745
- let output2 = errText ? outText ? `${outText}
195746
- ${errText}` : errText : outText;
195747
- if (spawnError) output2 = output2 ? `${output2}
195748
- ${spawnError}` : spawnError;
195749
- if (timedOut)
195750
- output2 = output2 ? `${output2}
195751
- [timed out after ${params.timeout}ms]` : `[timed out after ${params.timeout}ms]`;
195752
- return {
195753
- output: output2.trim(),
195754
- exitCode: exitCode ?? (timedOut ? 124 : -1),
195755
- timedOut
195756
- };
195757
- }
195758
- function capOutput(output2) {
195759
- if (output2.length <= MAX_OUTPUT_CHARS) return output2;
195760
- const fullPath = join20(getTempDir(), `shell-${randomUUID4().slice(0, 8)}.log`);
195761
- writeFileSync12(fullPath, output2);
195762
- const elided = output2.length - MAX_OUTPUT_CHARS;
195763
- return `... [${elided} chars truncated; output saved to ${fullPath}] ...
195764
- ${output2.slice(-MAX_OUTPUT_CHARS)}`;
195765
- }
195766
- function isGitCommand(command) {
195767
- const trimmed = command.trim();
195768
- if (trimmed === "git" || trimmed.startsWith("git ")) return true;
195769
- if (trimmed.startsWith("sudo git")) return true;
195770
- return /[;&|]\s*(?:sudo\s+)?git(?:\s|$)/.test(trimmed);
195771
- }
195772
- function ShellTool(ctx) {
195773
- return tool({
195774
- name: "shell",
195775
- timeoutMs: 12e4,
195776
- description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
195777
-
195778
- Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
195779
-
195780
- Use this tool to:
195781
- - Run shell commands (ls, cat, grep, find, etc.)
195782
- - Execute build tools (npm, pnpm, cargo, make, etc.)
195783
- - Run tests and linters
195784
-
195785
- Output is capped at ${MAX_OUTPUT_CHARS} chars: if exceeded, only the tail is returned and the captured output is saved to a tempfile (path included in the response). Re-read the tempfile with cat/tail/grep when you need more.
195786
-
195787
- Do NOT use this tool for git commands \u2014 use the dedicated git tools instead.`,
195788
- parameters: ShellParams,
195789
- execute: execute(async (params) => {
195790
- if (isGitCommand(params.command)) {
195791
- throw new Error(
195792
- 'git commands are not allowed in the shell tool. use the dedicated git tools instead:\n- git: local operations, e.g. git({ command: "log", args: ["--oneline"] }) \u2014 `command` is the subcommand ONLY\n- push_branch: push to remote (handles authentication)\n- git_fetch: fetch from remote (handles authentication)\n- checkout_pr: check out PR branches'
195793
- );
195794
- }
195795
- const timeout = Math.min(params.timeout ?? 3e4, 12e4);
195796
- const cwd = params.working_directory ?? process.cwd();
195797
- const env2 = resolveEnv(ctx.payload.shell === "enabled" ? "inherit" : "restricted");
195798
- if (params.command.includes("agent-browser")) {
195799
- const daemonError = ensureBrowserDaemon(ctx.toolState);
195800
- if (daemonError) {
195801
- return {
195802
- output: `browser daemon unavailable: ${daemonError}`,
195803
- exit_code: 1,
195804
- timed_out: false
195805
- };
195806
- }
195807
- const binDir = ctx.toolState.browserDaemon?.binDir;
195808
- if (binDir) {
195809
- env2.PATH = `${binDir}:${env2.PATH ?? ""}`;
195810
- }
195811
- }
195812
- if (params.background) {
195813
- const tempDir = getTempDir();
195814
- const handle = `bg-${randomUUID4().slice(0, 8)}`;
195815
- const outputPath = join20(tempDir, `${handle}.log`);
195816
- const pidPath = join20(tempDir, `${handle}.pid`);
195817
- const logFd = openSync(outputPath, "a");
195818
- let proc;
195819
- try {
195820
- proc = spawnShell({
195821
- command: params.command,
195822
- env: env2,
195823
- cwd,
195824
- stdio: ["ignore", logFd, logFd]
195825
- });
195826
- } finally {
195827
- closeSync(logFd);
195828
- }
195829
- if (!proc.pid) {
195830
- throw new Error("failed to start background process");
195831
- }
195832
- proc.unref();
195833
- writeFileSync12(pidPath, `${proc.pid}
195834
- `);
195835
- ctx.toolState.backgroundProcesses.set(handle, { pid: proc.pid, outputPath, pidPath });
195836
- return {
195837
- handle,
195838
- outputPath,
195839
- pidPath,
195840
- message: `started background process ${handle} (pid ${proc.pid})`
195841
- };
195842
- }
195843
- const result = await runSandboxed({ command: params.command, env: env2, cwd, timeout });
195844
- if (result.exitCode !== 0) {
195845
- log.info(`shell command failed with exit code ${result.exitCode}: ${params.command}`);
195846
- if (result.output) log.info(`output: ${result.output}`);
195847
- }
195848
- return {
195849
- output: capOutput(result.output),
195850
- exit_code: result.exitCode,
195851
- timed_out: result.timedOut
195852
- };
195853
- })
195854
- });
195855
- }
195856
- var KillBackgroundParams = type({
195857
- handle: type.string.describe("The handle of the background process to kill (e.g., bg-a1b2c3d4)")
195858
- });
195859
- function KillBackgroundTool(ctx) {
195860
- return tool({
195861
- name: "kill_background",
195862
- mutates: true,
195863
- description: `Kill a background process by its handle. Use this to stop dev servers or other long-running processes started with shell({ background: true }).`,
195864
- parameters: KillBackgroundParams,
195865
- execute: execute(async (params) => {
195866
- const proc = ctx.toolState.backgroundProcesses.get(params.handle);
195867
- if (!proc) {
195868
- return {
195869
- success: false,
195870
- message: `no background process with handle ${params.handle}`
195871
- };
195872
- }
195873
- try {
195874
- process.kill(-proc.pid, "SIGTERM");
195875
- } catch {
195876
- }
195877
- await sleep3(200);
195878
- try {
195879
- process.kill(-proc.pid, "SIGKILL");
195880
- } catch {
195881
- }
195882
- ctx.toolState.backgroundProcesses.delete(params.handle);
195883
- return {
195884
- success: true,
195885
- message: `killed background process ${params.handle} (pid ${proc.pid})`
195886
- };
195887
- })
195888
- });
195889
- }
195890
-
195891
- // mcp/gh.ts
195892
195970
  var GH_TIMEOUT_MS = 12e4;
195893
195971
  var GhParams = type({
195894
195972
  args: type.string.array().describe(
@@ -197139,7 +197217,7 @@ function GetReviewCommentsTool(ctx) {
197139
197217
  threadCount: threadBlocks.length,
197140
197218
  commentsPath,
197141
197219
  toc: formatted.toc,
197142
- instructions: `the file at commentsPath contains ${threadBlocks.length} review threads with full conversation history. comments marked with * are from the target review (${params.review_id}); any other thread is open feedback on this PR that is also yours to address, including comments that landed after this run started. the TOC shows each thread's file:line and the line number where it appears in the file. to read a specific thread, use: grep -A 50 "^## <file:line>" ${commentsPath} (replace <file:line> with the path from the TOC, e.g. "^## action/utils/foo.ts:42"). address each thread in order, working through one file at a time.`
197220
+ instructions: `the file at commentsPath contains ${threadBlocks.length} review threads with full conversation history. comments marked with * are from the target review (${params.review_id}); any other thread is open feedback on this PR, including comments that landed after this run started. Being returned does not authorize a code change; follow the task's scope and each thread's latest request. the TOC shows each thread's file:line and the line number where it appears in the file. to read a specific thread, use: grep -A 50 "^## <file:line>" ${commentsPath} (replace <file:line> with the path from the TOC, e.g. "^## action/utils/foo.ts:42"). read each full thread before deciding whether it calls for a reply or a code change.`
197143
197221
  };
197144
197222
  })
197145
197223
  });
@@ -197262,8 +197340,7 @@ An existing plan comment was found for this issue. Update that comment with the
197262
197340
  };
197263
197341
  }
197264
197342
  var modeInstructionParent = {
197265
- IncrementalReview: "Review",
197266
- Fix: "Build"
197343
+ IncrementalReview: "Review"
197267
197344
  };
197268
197345
  function buildOrchestratorGuidance(ctx, mode, overrideGuidance) {
197269
197346
  const hardcoded = overrideGuidance ?? mode.prompt ?? "";
@@ -198475,18 +198552,13 @@ function hasEnvVar4(name) {
198475
198552
  const value2 = process.env[name];
198476
198553
  return typeof value2 === "string" && value2.length > 0;
198477
198554
  }
198478
- var HOST_OVERRIDES = {
198479
- ANTHROPIC_API_KEY: "ANTHROPIC_BASE_URL",
198480
- CLAUDE_CODE_OAUTH_TOKEN: "ANTHROPIC_BASE_URL"
198481
- };
198482
198555
  function envVarsFor(model) {
198483
198556
  return model.includes("/") ? getModelEnvVars(model) : [];
198484
198557
  }
198485
198558
  async function checkOne(params) {
198486
198559
  const value2 = process.env[params.envVar];
198487
198560
  if (!value2) return null;
198488
- const hostOverride = HOST_OVERRIDES[params.envVar];
198489
- if (hostOverride && hasEnvVar4(hostOverride)) return null;
198561
+ if (getProviderGatewayUrl(params.model)) return null;
198490
198562
  if (params.envVar === "CLAUDE_CODE_OAUTH_TOKEN") {
198491
198563
  const preflight = await preflightClaudeSubscription({
198492
198564
  token: value2,
@@ -201687,6 +201759,7 @@ async function main() {
201687
201759
  const setupHook = await executeLifecycleHook({
201688
201760
  event: "setup",
201689
201761
  script: runContext.repoSettings.setupScript,
201762
+ shell: payload.shell,
201690
201763
  normalizeWorkingTreeAfter: true
201691
201764
  });
201692
201765
  if (setupHook.warning) {