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/cli.mjs CHANGED
@@ -28300,7 +28300,7 @@ var require_cross_spawn = __commonJS({
28300
28300
  enoent.hookChildProcess(spawned, parsed2);
28301
28301
  return spawned;
28302
28302
  }
28303
- function spawnSync8(command, args2, options) {
28303
+ function spawnSync9(command, args2, options) {
28304
28304
  const parsed2 = parse8(command, args2, options);
28305
28305
  const result = cp.spawnSync(parsed2.command, parsed2.args, parsed2.options);
28306
28306
  result.error = result.error || enoent.verifyENOENTSync(result.status, parsed2);
@@ -28308,7 +28308,7 @@ var require_cross_spawn = __commonJS({
28308
28308
  }
28309
28309
  module.exports = spawn6;
28310
28310
  module.exports.spawn = spawn6;
28311
- module.exports.sync = spawnSync8;
28311
+ module.exports.sync = spawnSync9;
28312
28312
  module.exports._parse = parse8;
28313
28313
  module.exports._enoent = enoent;
28314
28314
  }
@@ -123000,7 +123000,7 @@ var import_picocolors = __toESM(require_picocolors(), 1);
123000
123000
  // package.json
123001
123001
  var package_default = {
123002
123002
  name: "pullfrog",
123003
- version: "0.1.74",
123003
+ version: "0.1.76",
123004
123004
  cliContractVersion: "1",
123005
123005
  type: "module",
123006
123006
  bin: {
@@ -124108,6 +124108,20 @@ var repoConfigFields = [
124108
124108
  nullable: true,
124109
124109
  description: "additional instructions for planning"
124110
124110
  },
124111
+ {
124112
+ key: "prompts.address-reviews",
124113
+ field: "AddressReviews",
124114
+ kind: "prompt",
124115
+ nullable: true,
124116
+ description: "additional instructions for addressing review feedback"
124117
+ },
124118
+ {
124119
+ key: "prompts.fix-ci",
124120
+ field: "Fix",
124121
+ kind: "prompt",
124122
+ nullable: true,
124123
+ description: "additional instructions for fixing CI failures"
124124
+ },
124111
124125
  { key: "mention.enabled", field: "mention", kind: "toggle", description: "respond to mentions" },
124112
124126
  {
124113
124127
  key: "mention.instructions",
@@ -126138,6 +126152,17 @@ var OPENAI_COMPATIBLE_API_KEY_ENV = "OPENAI_COMPATIBLE_API_KEY";
126138
126152
  var OPENAI_COMPATIBLE_MODEL_ENV = "OPENAI_COMPATIBLE_MODEL";
126139
126153
  var OPENAI_COMPATIBLE_CONTEXT_ENV = "OPENAI_COMPATIBLE_CONTEXT";
126140
126154
  var OPENAI_COMPATIBLE_MAX_OUTPUT_ENV = "OPENAI_COMPATIBLE_MAX_OUTPUT";
126155
+ var PROVIDER_GATEWAY_URL_ENV = {
126156
+ anthropic: "ANTHROPIC_BASE_URL",
126157
+ openai: "OPENAI_BASE_URL"
126158
+ };
126159
+ function getProviderGatewayUrl(specifier) {
126160
+ const slashIndex = specifier?.indexOf("/") ?? -1;
126161
+ if (!specifier || slashIndex <= 0) return void 0;
126162
+ const envVar = PROVIDER_GATEWAY_URL_ENV[specifier.slice(0, slashIndex)];
126163
+ if (!envVar) return void 0;
126164
+ return process.env[envVar]?.trim().replace(/\/$/, "") || void 0;
126165
+ }
126141
126166
  function isBedrockAnthropicId(bedrockModelId) {
126142
126167
  return bedrockModelId.toLowerCase().split(/[./:]/).includes("anthropic");
126143
126168
  }
@@ -127644,26 +127669,24 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
127644
127669
 
127645
127670
  2. Checkout the PR branch via \`${t2("checkout_pr")}\`.
127646
127671
 
127647
- 3. Fetch review comments via \`${t2("get_review_comments")}\`.
127672
+ 3. Fetch review comments via \`${t2("get_review_comments")}\` and read the full conversation in each relevant thread.
127648
127673
 
127649
- 4. For each comment:
127650
- - understand the feedback
127674
+ 4. For each thread:
127675
+ - **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.
127676
+ - under \`address_scope: mentions\`, unmentioned threads are context only unless the user's review-body request explicitly includes them
127651
127677
  - **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.
127652
- - 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.
127653
- - if the request stands, make the code change using your native tools; otherwise reply explaining why
127678
+ - 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.
127679
+ - if intent permits a code change and the finding stands, implement it using your native tools; otherwise reply without editing
127654
127680
  - record what was done (or why nothing was done)
127655
127681
 
127656
127682
  5. Quality check:
127657
- - re-call \`${t2("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.
127658
- - 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
127659
- - ${commitStep}
127683
+ - re-call \`${t2("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.
127684
+ - if code changed, test and review the diff, remove unintended changes or bloat, then ${commitStep}
127660
127685
 
127661
- 6. Finalize. Reply + resolve are paired write actions: do BOTH or NEITHER for each thread.
127662
- - ${finalizeStep} (same push/prepush guidance as Build mode in *SYSTEM*)
127663
- - **if the push/commit fails**, call \`${t2("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.
127664
- - **once the fix is live on the remote**, for each thread you acted on:
127665
- - reply ONCE via \`${t2("reply_to_review_comment")}\`. The \`comment_id\` parameter takes the root comment's numeric \`id=\` (from the first \`comment author=...\` tag in the \`${t2("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.
127666
- - **immediately** call \`${t2("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.
127686
+ 6. Finalize:
127687
+ - **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.
127688
+ - reply ONCE per relevant thread via \`${t2("reply_to_review_comment")}\`, including when no code changed. The \`comment_id\` is the root comment's numeric \`id=\`, NOT the \`thread=\` value from \`${t2("get_review_comments")}\`. The runtime dedupes identical bodies within a session.
127689
+ - resolve a thread via \`${t2("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.
127667
127690
  - call \`${t2("report_progress")}\` with a brief summary. If your last \`${t2("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.`
127668
127691
  },
127669
127692
  // Review and IncrementalReview route the minimum reviewfrog specialists
@@ -129640,6 +129663,19 @@ function warnIfNativeEditsUnavailable(ctx) {
129640
129663
  "\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."
129641
129664
  );
129642
129665
  }
129666
+ var CODEX_GATEWAY_PROVIDER = "pullfrog-gateway";
129667
+ function gatewayProvider(gatewayUrl) {
129668
+ if (!gatewayUrl) return [];
129669
+ return [
129670
+ `[model_providers.${CODEX_GATEWAY_PROVIDER}]`,
129671
+ 'name = "OpenAI gateway"',
129672
+ `base_url = ${JSON.stringify(gatewayUrl)}`,
129673
+ 'wire_api = "responses"',
129674
+ // where the harness puts the run's OPENAI_API_KEY
129675
+ 'env_key = "CODEX_API_KEY"',
129676
+ ""
129677
+ ];
129678
+ }
129643
129679
  function writeCodexConfig(params) {
129644
129680
  const shellEnabled = params.ctx.payload.shell === "enabled";
129645
129681
  const sandboxMode = params.ctx.payload.push === "disabled" ? "read-only" : "workspace-write";
@@ -129656,6 +129692,7 @@ function writeCodexConfig(params) {
129656
129692
  'web_search = "live"',
129657
129693
  ...params.model ? [`model = ${JSON.stringify(params.model)}`] : [],
129658
129694
  ...params.effortRung ? [`model_reasoning_effort = ${JSON.stringify(params.effortRung)}`] : [],
129695
+ ...params.gatewayUrl ? [`model_provider = ${JSON.stringify(CODEX_GATEWAY_PROVIDER)}`] : [],
129659
129696
  "",
129660
129697
  "[features]",
129661
129698
  tomlBool(CODEX_DISABLED_FEATURES, false),
@@ -129685,6 +129722,7 @@ function writeCodexConfig(params) {
129685
129722
  "tool_timeout_sec = 660",
129686
129723
  "",
129687
129724
  ...repoMcpApprovals(repoDir),
129725
+ ...gatewayProvider(params.gatewayUrl),
129688
129726
  // UNTRUSTED, deliberately, and this one line is the whole config-precedence
129689
129727
  // boundary. Codex loads `<repo>/.codex/config.toml` as a layer that
129690
129728
  // OUTRANKS ours (project 25 vs user 20), and its denylist strips none of
@@ -129953,7 +129991,11 @@ var codex = agent({
129953
129991
  ctx,
129954
129992
  codexHome,
129955
129993
  model: resolveCodexModel(ctx),
129956
- effortRung: effort.rung && CODEX_EFFORTS.includes(effort.rung) ? effort.rung : void 0
129994
+ effortRung: effort.rung && CODEX_EFFORTS.includes(effort.rung) ? effort.rung : void 0,
129995
+ // keyed off OpenAI, not the resolved model: codex serves nothing else, an
129996
+ // auto-selected run has no model, and a ChatGPT-subscription token is
129997
+ // valid only at ChatGPT's own backend
129998
+ gatewayUrl: codexHomeAuth ? void 0 : getProviderGatewayUrl("openai/")
129957
129999
  });
129958
130000
  if (codexHomeAuth) {
129959
130001
  core3.saveState(
@@ -135720,6 +135762,14 @@ function openAICompatibleProvider(model) {
135720
135762
  }
135721
135763
  };
135722
135764
  }
135765
+ var OPENCODE_GATEWAY_PATH = { anthropic: "/v1" };
135766
+ function providerGatewayOverride(model) {
135767
+ const gatewayUrl = getProviderGatewayUrl(model);
135768
+ if (!model || !gatewayUrl) return {};
135769
+ const provider2 = model.slice(0, model.indexOf("/"));
135770
+ const baseURL = `${gatewayUrl}${OPENCODE_GATEWAY_PATH[provider2] ?? ""}`;
135771
+ return { [provider2]: { options: { baseURL } } };
135772
+ }
135723
135773
  function azureProvider(model) {
135724
135774
  const prefix = `${AZURE_PROVIDER}/`;
135725
135775
  if (!model?.startsWith(prefix)) return {};
@@ -135835,7 +135885,8 @@ function buildSecurityConfig(ctx, model) {
135835
135885
  provider: {
135836
135886
  openrouter: { models: kimiOpenRouterProviderOverrides() },
135837
135887
  ...openAICompatibleProvider(model),
135838
- ...azureProvider(model)
135888
+ ...azureProvider(model),
135889
+ ...providerGatewayOverride(model)
135839
135890
  }
135840
135891
  };
135841
135892
  if (model) {
@@ -194109,8 +194160,8 @@ function closeBrowserDaemon(toolState) {
194109
194160
 
194110
194161
  // mcp/checkout.ts
194111
194162
  import { createHash as createHash2 } from "node:crypto";
194112
- import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync10 } from "node:fs";
194113
- import { join as join15 } from "node:path";
194163
+ import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync11 } from "node:fs";
194164
+ import { join as join16 } from "node:path";
194114
194165
 
194115
194166
  // node_modules/.pnpm/@ark+schema@0.56.0/node_modules/@ark/schema/out/shared/errors.js
194116
194167
  var ArkError = class _ArkError extends CastableBase {
@@ -202382,6 +202433,960 @@ async function $gitFetchWithDeepen(args2, options, label) {
202382
202433
  }
202383
202434
  }
202384
202435
 
202436
+ // utils/lifecycle.ts
202437
+ import { spawnSync as spawnSync8 } from "node:child_process";
202438
+
202439
+ // mcp/shell.ts
202440
+ import { spawn as spawn5, spawnSync as spawnSync7 } from "node:child_process";
202441
+ import { randomUUID as randomUUID3 } from "node:crypto";
202442
+ import { closeSync, openSync, writeFileSync as writeFileSync9 } from "node:fs";
202443
+ import { userInfo as userInfo2 } from "node:os";
202444
+ import { join as join11 } from "node:path";
202445
+ import { setTimeout as sleep2 } from "node:timers/promises";
202446
+
202447
+ // node_modules/.pnpm/@toon-format+toon@1.4.0/node_modules/@toon-format/toon/dist/index.mjs
202448
+ var LIST_ITEM_MARKER = "-";
202449
+ var LIST_ITEM_PREFIX = "- ";
202450
+ var COMMA = ",";
202451
+ var PIPE = "|";
202452
+ var DOT = ".";
202453
+ var NULL_LITERAL = "null";
202454
+ var TRUE_LITERAL = "true";
202455
+ var FALSE_LITERAL = "false";
202456
+ var BACKSLASH = "\\";
202457
+ var DOUBLE_QUOTE = '"';
202458
+ var TAB = " ";
202459
+ var DELIMITERS = {
202460
+ comma: COMMA,
202461
+ tab: TAB,
202462
+ pipe: PIPE
202463
+ };
202464
+ var DEFAULT_DELIMITER = DELIMITERS.comma;
202465
+ function escapeString(value2) {
202466
+ 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`);
202467
+ }
202468
+ function isBooleanOrNullLiteral(token) {
202469
+ return token === TRUE_LITERAL || token === FALSE_LITERAL || token === NULL_LITERAL;
202470
+ }
202471
+ function normalizeValue(value2) {
202472
+ if (value2 === null) return null;
202473
+ if (typeof value2 === "string" || typeof value2 === "boolean") return value2;
202474
+ if (typeof value2 === "number") {
202475
+ if (Object.is(value2, -0)) return 0;
202476
+ if (!Number.isFinite(value2)) return null;
202477
+ return value2;
202478
+ }
202479
+ if (typeof value2 === "bigint") {
202480
+ if (value2 >= Number.MIN_SAFE_INTEGER && value2 <= Number.MAX_SAFE_INTEGER) return Number(value2);
202481
+ return value2.toString();
202482
+ }
202483
+ if (value2 instanceof Date) return value2.toISOString();
202484
+ if (Array.isArray(value2)) return value2.map(normalizeValue);
202485
+ if (value2 instanceof Set) return Array.from(value2).map(normalizeValue);
202486
+ if (value2 instanceof Map) return Object.fromEntries(Array.from(value2, ([k2, v]) => [String(k2), normalizeValue(v)]));
202487
+ if (isPlainObject7(value2)) {
202488
+ const normalized = {};
202489
+ for (const key in value2) if (Object.prototype.hasOwnProperty.call(value2, key)) normalized[key] = normalizeValue(value2[key]);
202490
+ return normalized;
202491
+ }
202492
+ return null;
202493
+ }
202494
+ function isJsonPrimitive(value2) {
202495
+ return value2 === null || typeof value2 === "string" || typeof value2 === "number" || typeof value2 === "boolean";
202496
+ }
202497
+ function isJsonArray(value2) {
202498
+ return Array.isArray(value2);
202499
+ }
202500
+ function isJsonObject4(value2) {
202501
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
202502
+ }
202503
+ function isEmptyObject2(value2) {
202504
+ return Object.keys(value2).length === 0;
202505
+ }
202506
+ function isPlainObject7(value2) {
202507
+ if (value2 === null || typeof value2 !== "object") return false;
202508
+ const prototype = Object.getPrototypeOf(value2);
202509
+ return prototype === null || prototype === Object.prototype;
202510
+ }
202511
+ function isArrayOfPrimitives(value2) {
202512
+ return value2.length === 0 || value2.every((item) => isJsonPrimitive(item));
202513
+ }
202514
+ function isArrayOfArrays(value2) {
202515
+ return value2.length === 0 || value2.every((item) => isJsonArray(item));
202516
+ }
202517
+ function isArrayOfObjects(value2) {
202518
+ return value2.length === 0 || value2.every((item) => isJsonObject4(item));
202519
+ }
202520
+ function isValidUnquotedKey(key) {
202521
+ return /^[A-Z_][\w.]*$/i.test(key);
202522
+ }
202523
+ function isIdentifierSegment(key) {
202524
+ return /^[A-Z_]\w*$/i.test(key);
202525
+ }
202526
+ function isSafeUnquoted(value2, delimiter3 = DEFAULT_DELIMITER) {
202527
+ if (!value2) return false;
202528
+ if (value2 !== value2.trim()) return false;
202529
+ if (isBooleanOrNullLiteral(value2) || isNumericLike(value2)) return false;
202530
+ if (value2.includes(":")) return false;
202531
+ if (value2.includes('"') || value2.includes("\\")) return false;
202532
+ if (/[[\]{}]/.test(value2)) return false;
202533
+ if (/[\n\r\t]/.test(value2)) return false;
202534
+ if (value2.includes(delimiter3)) return false;
202535
+ if (value2.startsWith(LIST_ITEM_MARKER)) return false;
202536
+ return true;
202537
+ }
202538
+ function isNumericLike(value2) {
202539
+ return /^-?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i.test(value2) || /^0\d+$/.test(value2);
202540
+ }
202541
+ var QUOTED_KEY_MARKER = Symbol("quotedKey");
202542
+ function tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, flattenDepth) {
202543
+ if (options.keyFolding !== "safe") return;
202544
+ if (!isJsonObject4(value2)) return;
202545
+ const { segments, tail, leafValue } = collectSingleKeyChain(key, value2, flattenDepth ?? options.flattenDepth);
202546
+ if (segments.length < 2) return;
202547
+ if (!segments.every((seg) => isIdentifierSegment(seg))) return;
202548
+ const foldedKey = buildFoldedKey(segments);
202549
+ const absolutePath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
202550
+ if (siblings.includes(foldedKey)) return;
202551
+ if (rootLiteralKeys && rootLiteralKeys.has(absolutePath)) return;
202552
+ return {
202553
+ foldedKey,
202554
+ remainder: tail,
202555
+ leafValue,
202556
+ segmentCount: segments.length
202557
+ };
202558
+ }
202559
+ function collectSingleKeyChain(startKey, startValue, maxDepth) {
202560
+ const segments = [startKey];
202561
+ let currentValue = startValue;
202562
+ while (segments.length < maxDepth) {
202563
+ if (!isJsonObject4(currentValue)) break;
202564
+ const keys = Object.keys(currentValue);
202565
+ if (keys.length !== 1) break;
202566
+ const nextKey = keys[0];
202567
+ const nextValue = currentValue[nextKey];
202568
+ segments.push(nextKey);
202569
+ currentValue = nextValue;
202570
+ }
202571
+ if (!isJsonObject4(currentValue) || isEmptyObject2(currentValue)) return {
202572
+ segments,
202573
+ tail: void 0,
202574
+ leafValue: currentValue
202575
+ };
202576
+ return {
202577
+ segments,
202578
+ tail: currentValue,
202579
+ leafValue: currentValue
202580
+ };
202581
+ }
202582
+ function buildFoldedKey(segments) {
202583
+ return segments.join(DOT);
202584
+ }
202585
+ function encodePrimitive(value2, delimiter3) {
202586
+ if (value2 === null) return NULL_LITERAL;
202587
+ if (typeof value2 === "boolean") return String(value2);
202588
+ if (typeof value2 === "number") return String(value2);
202589
+ return encodeStringLiteral(value2, delimiter3);
202590
+ }
202591
+ function encodeStringLiteral(value2, delimiter3 = DEFAULT_DELIMITER) {
202592
+ if (isSafeUnquoted(value2, delimiter3)) return value2;
202593
+ return `${DOUBLE_QUOTE}${escapeString(value2)}${DOUBLE_QUOTE}`;
202594
+ }
202595
+ function encodeKey(key) {
202596
+ if (isValidUnquotedKey(key)) return key;
202597
+ return `${DOUBLE_QUOTE}${escapeString(key)}${DOUBLE_QUOTE}`;
202598
+ }
202599
+ function encodeAndJoinPrimitives(values, delimiter3 = DEFAULT_DELIMITER) {
202600
+ return values.map((v) => encodePrimitive(v, delimiter3)).join(delimiter3);
202601
+ }
202602
+ function formatHeader(length, options) {
202603
+ const key = options?.key;
202604
+ const fields = options?.fields;
202605
+ const delimiter3 = options?.delimiter ?? COMMA;
202606
+ let header = "";
202607
+ if (key) header += encodeKey(key);
202608
+ header += `[${length}${delimiter3 !== DEFAULT_DELIMITER ? delimiter3 : ""}]`;
202609
+ if (fields) {
202610
+ const quotedFields = fields.map((f) => encodeKey(f));
202611
+ header += `{${quotedFields.join(delimiter3)}}`;
202612
+ }
202613
+ header += ":";
202614
+ return header;
202615
+ }
202616
+ function* encodeJsonValue(value2, options, depth) {
202617
+ if (isJsonPrimitive(value2)) {
202618
+ const encodedPrimitive = encodePrimitive(value2, options.delimiter);
202619
+ if (encodedPrimitive !== "") yield encodedPrimitive;
202620
+ return;
202621
+ }
202622
+ if (isJsonArray(value2)) yield* encodeArrayLines(void 0, value2, depth, options);
202623
+ else if (isJsonObject4(value2)) yield* encodeObjectLines(value2, depth, options);
202624
+ }
202625
+ function* encodeObjectLines(value2, depth, options, rootLiteralKeys, pathPrefix, remainingDepth) {
202626
+ const keys = Object.keys(value2);
202627
+ if (depth === 0 && !rootLiteralKeys) rootLiteralKeys = new Set(keys.filter((k2) => k2.includes(".")));
202628
+ const effectiveFlattenDepth = remainingDepth ?? options.flattenDepth;
202629
+ for (const [key, val] of Object.entries(value2)) yield* encodeKeyValuePairLines(key, val, depth, options, keys, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
202630
+ }
202631
+ function* encodeKeyValuePairLines(key, value2, depth, options, siblings, rootLiteralKeys, pathPrefix, flattenDepth) {
202632
+ const currentPath = pathPrefix ? `${pathPrefix}${DOT}${key}` : key;
202633
+ const effectiveFlattenDepth = flattenDepth ?? options.flattenDepth;
202634
+ if (options.keyFolding === "safe" && siblings) {
202635
+ const foldResult = tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
202636
+ if (foldResult) {
202637
+ const { foldedKey, remainder, leafValue, segmentCount } = foldResult;
202638
+ const encodedFoldedKey = encodeKey(foldedKey);
202639
+ if (remainder === void 0) {
202640
+ if (isJsonPrimitive(leafValue)) {
202641
+ yield indentedLine(depth, `${encodedFoldedKey}: ${encodePrimitive(leafValue, options.delimiter)}`, options.indent);
202642
+ return;
202643
+ } else if (isJsonArray(leafValue)) {
202644
+ yield* encodeArrayLines(foldedKey, leafValue, depth, options);
202645
+ return;
202646
+ } else if (isJsonObject4(leafValue) && isEmptyObject2(leafValue)) {
202647
+ yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
202648
+ return;
202649
+ }
202650
+ }
202651
+ if (isJsonObject4(remainder)) {
202652
+ yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
202653
+ const remainingDepth = effectiveFlattenDepth - segmentCount;
202654
+ const foldedPath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
202655
+ yield* encodeObjectLines(remainder, depth + 1, options, rootLiteralKeys, foldedPath, remainingDepth);
202656
+ return;
202657
+ }
202658
+ }
202659
+ }
202660
+ const encodedKey = encodeKey(key);
202661
+ if (isJsonPrimitive(value2)) yield indentedLine(depth, `${encodedKey}: ${encodePrimitive(value2, options.delimiter)}`, options.indent);
202662
+ else if (isJsonArray(value2)) yield* encodeArrayLines(key, value2, depth, options);
202663
+ else if (isJsonObject4(value2)) {
202664
+ yield indentedLine(depth, `${encodedKey}:`, options.indent);
202665
+ if (!isEmptyObject2(value2)) yield* encodeObjectLines(value2, depth + 1, options, rootLiteralKeys, currentPath, effectiveFlattenDepth);
202666
+ }
202667
+ }
202668
+ function* encodeArrayLines(key, value2, depth, options) {
202669
+ if (value2.length === 0) {
202670
+ yield indentedLine(depth, formatHeader(0, {
202671
+ key,
202672
+ delimiter: options.delimiter
202673
+ }), options.indent);
202674
+ return;
202675
+ }
202676
+ if (isArrayOfPrimitives(value2)) {
202677
+ yield indentedLine(depth, encodeInlineArrayLine(value2, options.delimiter, key), options.indent);
202678
+ return;
202679
+ }
202680
+ if (isArrayOfArrays(value2)) {
202681
+ if (value2.every((arr) => isArrayOfPrimitives(arr))) {
202682
+ yield* encodeArrayOfArraysAsListItemsLines(key, value2, depth, options);
202683
+ return;
202684
+ }
202685
+ }
202686
+ if (isArrayOfObjects(value2)) {
202687
+ const header = extractTabularHeader(value2);
202688
+ if (header) yield* encodeArrayOfObjectsAsTabularLines(key, value2, header, depth, options);
202689
+ else yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
202690
+ return;
202691
+ }
202692
+ yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
202693
+ }
202694
+ function* encodeArrayOfArraysAsListItemsLines(prefix, values, depth, options) {
202695
+ yield indentedLine(depth, formatHeader(values.length, {
202696
+ key: prefix,
202697
+ delimiter: options.delimiter
202698
+ }), options.indent);
202699
+ for (const arr of values) if (isArrayOfPrimitives(arr)) {
202700
+ const arrayLine = encodeInlineArrayLine(arr, options.delimiter);
202701
+ yield indentedListItem(depth + 1, arrayLine, options.indent);
202702
+ }
202703
+ }
202704
+ function encodeInlineArrayLine(values, delimiter3, prefix) {
202705
+ const header = formatHeader(values.length, {
202706
+ key: prefix,
202707
+ delimiter: delimiter3
202708
+ });
202709
+ const joinedValue = encodeAndJoinPrimitives(values, delimiter3);
202710
+ if (values.length === 0) return header;
202711
+ return `${header} ${joinedValue}`;
202712
+ }
202713
+ function* encodeArrayOfObjectsAsTabularLines(prefix, rows, header, depth, options) {
202714
+ yield indentedLine(depth, formatHeader(rows.length, {
202715
+ key: prefix,
202716
+ fields: header,
202717
+ delimiter: options.delimiter
202718
+ }), options.indent);
202719
+ yield* writeTabularRowsLines(rows, header, depth + 1, options);
202720
+ }
202721
+ function extractTabularHeader(rows) {
202722
+ if (rows.length === 0) return;
202723
+ const firstRow = rows[0];
202724
+ const firstKeys = Object.keys(firstRow);
202725
+ if (firstKeys.length === 0) return;
202726
+ if (isTabularArray(rows, firstKeys)) return firstKeys;
202727
+ }
202728
+ function isTabularArray(rows, header) {
202729
+ for (const row of rows) {
202730
+ if (Object.keys(row).length !== header.length) return false;
202731
+ for (const key of header) {
202732
+ if (!(key in row)) return false;
202733
+ if (!isJsonPrimitive(row[key])) return false;
202734
+ }
202735
+ }
202736
+ return true;
202737
+ }
202738
+ function* writeTabularRowsLines(rows, header, depth, options) {
202739
+ for (const row of rows) yield indentedLine(depth, encodeAndJoinPrimitives(header.map((key) => row[key]), options.delimiter), options.indent);
202740
+ }
202741
+ function* encodeMixedArrayAsListItemsLines(prefix, items, depth, options) {
202742
+ yield indentedLine(depth, formatHeader(items.length, {
202743
+ key: prefix,
202744
+ delimiter: options.delimiter
202745
+ }), options.indent);
202746
+ for (const item of items) yield* encodeListItemValueLines(item, depth + 1, options);
202747
+ }
202748
+ function* encodeObjectAsListItemLines(obj, depth, options) {
202749
+ if (isEmptyObject2(obj)) {
202750
+ yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
202751
+ return;
202752
+ }
202753
+ const entries = Object.entries(obj);
202754
+ if (entries.length === 1) {
202755
+ const [key, value2] = entries[0];
202756
+ if (isJsonArray(value2) && isArrayOfObjects(value2)) {
202757
+ const header = extractTabularHeader(value2);
202758
+ if (header) {
202759
+ yield indentedListItem(depth, formatHeader(value2.length, {
202760
+ key,
202761
+ fields: header,
202762
+ delimiter: options.delimiter
202763
+ }), options.indent);
202764
+ yield* writeTabularRowsLines(value2, header, depth + 1, options);
202765
+ return;
202766
+ }
202767
+ }
202768
+ }
202769
+ yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
202770
+ yield* encodeObjectLines(obj, depth + 1, options);
202771
+ }
202772
+ function* encodeListItemValueLines(value2, depth, options) {
202773
+ if (isJsonPrimitive(value2)) yield indentedListItem(depth, encodePrimitive(value2, options.delimiter), options.indent);
202774
+ else if (isJsonArray(value2)) if (isArrayOfPrimitives(value2)) yield indentedListItem(depth, encodeInlineArrayLine(value2, options.delimiter), options.indent);
202775
+ else {
202776
+ yield indentedListItem(depth, formatHeader(value2.length, { delimiter: options.delimiter }), options.indent);
202777
+ for (const item of value2) yield* encodeListItemValueLines(item, depth + 1, options);
202778
+ }
202779
+ else if (isJsonObject4(value2)) yield* encodeObjectAsListItemLines(value2, depth, options);
202780
+ }
202781
+ function indentedLine(depth, content, indentSize) {
202782
+ return " ".repeat(indentSize * depth) + content;
202783
+ }
202784
+ function indentedListItem(depth, content, indentSize) {
202785
+ return indentedLine(depth, LIST_ITEM_PREFIX + content, indentSize);
202786
+ }
202787
+ function encode6(input2, options) {
202788
+ return Array.from(encodeLines(input2, options)).join("\n");
202789
+ }
202790
+ function encodeLines(input2, options) {
202791
+ return encodeJsonValue(normalizeValue(input2), resolveOptions(options), 0);
202792
+ }
202793
+ function resolveOptions(options) {
202794
+ return {
202795
+ indent: options?.indent ?? 2,
202796
+ delimiter: options?.delimiter ?? DEFAULT_DELIMITER,
202797
+ keyFolding: options?.keyFolding ?? "off",
202798
+ flattenDepth: options?.flattenDepth ?? Number.POSITIVE_INFINITY
202799
+ };
202800
+ }
202801
+
202802
+ // mcp/geminiSanitizer.ts
202803
+ function parseStringEnumBranch(item) {
202804
+ if (!item || typeof item !== "object") return null;
202805
+ const record4 = item;
202806
+ if (Array.isArray(record4.enum)) {
202807
+ const strings = record4.enum.filter((v) => typeof v === "string");
202808
+ return strings.length === record4.enum.length && strings.length > 0 ? { values: strings } : null;
202809
+ }
202810
+ if (typeof record4.const === "string") {
202811
+ return { values: [record4.const] };
202812
+ }
202813
+ return null;
202814
+ }
202815
+ function collapseStringUnion(branches) {
202816
+ const values = [];
202817
+ for (const item of branches) {
202818
+ const parsed2 = parseStringEnumBranch(item);
202819
+ if (!parsed2) return null;
202820
+ values.push(...parsed2.values);
202821
+ }
202822
+ if (values.length === 0) return null;
202823
+ return { type: "string", enum: [...new Set(values)] };
202824
+ }
202825
+ function sanitizeForGemini(schema2) {
202826
+ if (!schema2 || typeof schema2 !== "object") return schema2;
202827
+ if (Array.isArray(schema2)) return schema2.map(sanitizeForGemini);
202828
+ const source = schema2;
202829
+ if (Array.isArray(source.enum) && typeof source.type !== "string") {
202830
+ const allStrings = source.enum.every((v) => typeof v === "string");
202831
+ if (allStrings) {
202832
+ const result = { type: "string", enum: source.enum };
202833
+ if (typeof source.description === "string") result.description = source.description;
202834
+ return result;
202835
+ }
202836
+ }
202837
+ for (const unionKey of ["anyOf", "oneOf"]) {
202838
+ const branches = source[unionKey];
202839
+ if (Array.isArray(branches) && branches.length > 0) {
202840
+ const collapsed = collapseStringUnion(branches);
202841
+ if (collapsed) {
202842
+ const result = { ...collapsed };
202843
+ if (typeof source.description === "string") result.description = source.description;
202844
+ return result;
202845
+ }
202846
+ }
202847
+ }
202848
+ if (Array.isArray(source.anyOf) || Array.isArray(source.oneOf)) {
202849
+ const result = {};
202850
+ if (Array.isArray(source.anyOf)) result.anyOf = source.anyOf.map(sanitizeForGemini);
202851
+ if (Array.isArray(source.oneOf)) result.oneOf = source.oneOf.map(sanitizeForGemini);
202852
+ return result;
202853
+ }
202854
+ const sanitized = {};
202855
+ for (const [key, value2] of Object.entries(source)) {
202856
+ if (key === "$schema") continue;
202857
+ if (key === "$defs") {
202858
+ sanitized.definitions = sanitizeForGemini(value2);
202859
+ continue;
202860
+ }
202861
+ sanitized[key] = sanitizeForGemini(value2);
202862
+ }
202863
+ return sanitized;
202864
+ }
202865
+ function wrapJsonSchemaProducer(producer) {
202866
+ return new Proxy(producer, {
202867
+ get(target, prop, receiver) {
202868
+ const value2 = Reflect.get(target, prop, receiver);
202869
+ if ((prop === "input" || prop === "output") && typeof value2 === "function") {
202870
+ const fn2 = value2;
202871
+ return (...args2) => sanitizeForGemini(fn2.apply(target, args2));
202872
+ }
202873
+ return value2;
202874
+ }
202875
+ });
202876
+ }
202877
+ function wrapStandard(standard) {
202878
+ return new Proxy(standard, {
202879
+ get(target, prop, receiver) {
202880
+ if (prop === "jsonSchema") {
202881
+ const value2 = Reflect.get(target, prop, receiver);
202882
+ if (value2 && typeof value2 === "object") {
202883
+ return wrapJsonSchemaProducer(value2);
202884
+ }
202885
+ return value2;
202886
+ }
202887
+ return Reflect.get(target, prop, receiver);
202888
+ }
202889
+ });
202890
+ }
202891
+ function wrapSchemaForGemini(schema2) {
202892
+ return new Proxy(schema2, {
202893
+ get(target, prop, receiver) {
202894
+ if (prop === "~standard") {
202895
+ const value2 = Reflect.get(target, prop, receiver);
202896
+ if (value2 && typeof value2 === "object") {
202897
+ return wrapStandard(value2);
202898
+ }
202899
+ return value2;
202900
+ }
202901
+ if (prop === "toJsonSchema") {
202902
+ const method = Reflect.get(target, prop, receiver);
202903
+ if (typeof method === "function") {
202904
+ return () => sanitizeForGemini(method.call(target));
202905
+ }
202906
+ return method;
202907
+ }
202908
+ return Reflect.get(target, prop, receiver);
202909
+ }
202910
+ });
202911
+ }
202912
+ function sanitizeToolForGemini(tool2) {
202913
+ if (!tool2.parameters) return tool2;
202914
+ return { ...tool2, parameters: wrapSchemaForGemini(tool2.parameters) };
202915
+ }
202916
+ function isGeminiRouted(ctx) {
202917
+ const effective = ctx.payload.proxyModel ?? ctx.resolvedModel ?? ctx.payload.model;
202918
+ if (!effective) return true;
202919
+ const normalized = effective.toLowerCase();
202920
+ if (normalized.includes("gemini")) return true;
202921
+ if (!normalized.includes("/")) return true;
202922
+ return false;
202923
+ }
202924
+
202925
+ // mcp/shared.ts
202926
+ function getHttpStatus(err) {
202927
+ if (typeof err !== "object" || err === null || !("status" in err)) return void 0;
202928
+ return typeof err.status === "number" ? err.status : void 0;
202929
+ }
202930
+ var tool = (toolDef) => toolDef;
202931
+ var handleToolSuccess = (data) => {
202932
+ const text = typeof data === "string" ? data : encode6(data);
202933
+ return {
202934
+ content: [{ type: "text", text }]
202935
+ };
202936
+ };
202937
+ var handleToolError = (error109) => {
202938
+ const errorMessage = error109 instanceof Error ? error109.message : String(error109);
202939
+ return {
202940
+ content: [
202941
+ {
202942
+ type: "text",
202943
+ text: `Error: ${errorMessage}`
202944
+ }
202945
+ ],
202946
+ isError: true
202947
+ };
202948
+ };
202949
+ var execute = (fn2, toolName) => {
202950
+ const _fn = async (params) => {
202951
+ try {
202952
+ const result = await fn2(params);
202953
+ return handleToolSuccess(result);
202954
+ } catch (error109) {
202955
+ const errorMessage = error109 instanceof Error ? error109.message : String(error109);
202956
+ const prefix = toolName ? `[${toolName}]` : "tool";
202957
+ log2.info(`${prefix} error: ${errorMessage}`);
202958
+ log2.debug(`${prefix} params: ${formatJsonValue(params)}`);
202959
+ return handleToolError(error109);
202960
+ }
202961
+ };
202962
+ return _fn;
202963
+ };
202964
+ var addTools = (ctx, server, tools) => {
202965
+ const shouldSanitize = isGeminiRouted(ctx);
202966
+ for (const tool2 of tools) {
202967
+ server.addTool(shouldSanitize ? sanitizeToolForGemini(tool2) : tool2);
202968
+ }
202969
+ return server;
202970
+ };
202971
+
202972
+ // mcp/shell.ts
202973
+ var ShellParams = type({
202974
+ command: "string",
202975
+ // advisory and unread — no code path consumes it. kept in the schema only
202976
+ // because it nudges the model to state intent before running a command;
202977
+ // REQUIRING it cost a full rejected turn every time one was omitted. #1140.
202978
+ "description?": "string",
202979
+ "timeout?": type.number.describe(
202980
+ "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."
202981
+ ),
202982
+ "working_directory?": "string",
202983
+ "background?": "boolean"
202984
+ });
202985
+ var detectedSandboxMethod;
202986
+ var usernsProbeError;
202987
+ function getSandboxMethod() {
202988
+ return detectSandboxMethod();
202989
+ }
202990
+ function detectSandboxMethod() {
202991
+ if (detectedSandboxMethod !== void 0) {
202992
+ return detectedSandboxMethod;
202993
+ }
202994
+ if (process.env.CI !== "true") {
202995
+ detectedSandboxMethod = "none";
202996
+ log2.debug("sandbox disabled (CI !== true)");
202997
+ return "none";
202998
+ }
202999
+ try {
203000
+ const result = spawnSync7("unshare", ["--pid", "--fork", "--mount-proc", "true"], {
203001
+ timeout: 5e3,
203002
+ stdio: "ignore"
203003
+ });
203004
+ if (result.status === 0) {
203005
+ detectedSandboxMethod = "unshare";
203006
+ log2.debug("PID namespace isolation enabled (unprivileged unshare)");
203007
+ return "unshare";
203008
+ }
203009
+ } catch {
203010
+ }
203011
+ try {
203012
+ const result = spawnSync7("sudo", ["unshare", "--pid", "--fork", "--mount-proc", "true"], {
203013
+ timeout: 5e3,
203014
+ stdio: "ignore"
203015
+ });
203016
+ if (result.status === 0) {
203017
+ detectedSandboxMethod = "sudo-unshare";
203018
+ log2.debug("PID namespace isolation enabled (sudo unshare)");
203019
+ return "sudo-unshare";
203020
+ }
203021
+ } catch {
203022
+ }
203023
+ try {
203024
+ const result = spawnSync7(
203025
+ "unshare",
203026
+ [
203027
+ "--user",
203028
+ "--map-root-user",
203029
+ "--pid",
203030
+ "--fork",
203031
+ "--mount",
203032
+ "setpriv",
203033
+ "--no-new-privs",
203034
+ "--bounding-set",
203035
+ "-all",
203036
+ "--inh-caps",
203037
+ "-all",
203038
+ "--ambient-caps",
203039
+ "-all",
203040
+ "true"
203041
+ ],
203042
+ { timeout: 5e3, stdio: ["ignore", "ignore", "pipe"] }
203043
+ );
203044
+ if (result.status === 0) {
203045
+ detectedSandboxMethod = "userns-unshare";
203046
+ log2.debug("PID namespace isolation enabled (unprivileged userns unshare)");
203047
+ return "userns-unshare";
203048
+ }
203049
+ usernsProbeError = result.stderr?.toString().trim() || result.error?.message;
203050
+ } catch {
203051
+ }
203052
+ detectedSandboxMethod = "none";
203053
+ log2.info("PID namespace isolation not available");
203054
+ return "none";
203055
+ }
203056
+ var PROC_CLEANUP = "umount /proc 2>/dev/null; umount /proc 2>/dev/null; mount -t proc proc /proc 2>/dev/null;";
203057
+ var SOCKET_CLEANUP = [
203058
+ "/var/run/docker.sock",
203059
+ "/run/docker.sock",
203060
+ "/var/run/podman/podman.sock",
203061
+ "/run/podman/podman.sock",
203062
+ "/run/containerd/containerd.sock",
203063
+ "/var/run/crio/crio.sock"
203064
+ ].map((path4) => `mount --bind /dev/null ${path4} 2>/dev/null;`).join(" ");
203065
+ function buildFsMounts(repoDir) {
203066
+ const escaped = repoDir.replace(/'/g, "'\\''");
203067
+ return [
203068
+ `mkdir -p /var/lib/pullfrog 2>/dev/null;`,
203069
+ `mount -t tmpfs tmpfs /var/lib/pullfrog 2>/dev/null;`,
203070
+ `[ -n "$RUNNER_TEMP" ] && [ -d "$RUNNER_TEMP/_runner_file_commands" ] && mount -t tmpfs tmpfs "$RUNNER_TEMP/_runner_file_commands" 2>/dev/null;`,
203071
+ `[ -e '${escaped}/.git' ] && mount --bind '${escaped}/.git' '${escaped}/.git' 2>/dev/null && mount -o remount,bind,ro '${escaped}/.git' 2>/dev/null;`,
203072
+ // the corepack shim dir sits FIRST on PATH (utils/packageManager.ts) and
203073
+ // PATH survives filterEnv, so a writable pm-bin lets sandboxed code plant
203074
+ // a `git`/`pnpm` that a later UNSANDBOXED spawn resolves with the parent
203075
+ // env. read-only closes that for every sandboxed command at once.
203076
+ //
203077
+ // the path is interpolated HERE rather than read as `$PULLFROG_TEMP_DIR`
203078
+ // inside the sandbox: that name is not on the `filterEnv` safe list, so in
203079
+ // restricted mode it expands to empty and the mount silently no-ops in the
203080
+ // one tier it exists to protect. `$RUNNER_TEMP` above is safe only because
203081
+ // `RUNNER_` is an allowed prefix.
203082
+ shimMount()
203083
+ ].filter(Boolean).join(" ");
203084
+ }
203085
+ function shimMount() {
203086
+ const tempDir = process.env.PULLFROG_TEMP_DIR;
203087
+ if (!tempDir) return "";
203088
+ const dir = join11(tempDir, "pm-bin").replace(/'/g, "'\\''");
203089
+ return `[ -d '${dir}' ] && mount --bind '${dir}' '${dir}' 2>/dev/null && mount -o remount,bind,ro '${dir}' 2>/dev/null;`;
203090
+ }
203091
+ var _repoRoot;
203092
+ function resolveRepoRoot() {
203093
+ if (_repoRoot) return _repoRoot;
203094
+ const fromEnv = process.env.GITHUB_WORKSPACE;
203095
+ if (fromEnv) {
203096
+ _repoRoot = fromEnv;
203097
+ return _repoRoot;
203098
+ }
203099
+ try {
203100
+ _repoRoot = spawnSync7("git", ["rev-parse", "--show-toplevel"], {
203101
+ cwd: process.cwd(),
203102
+ stdio: ["ignore", "pipe", "ignore"],
203103
+ encoding: "utf-8"
203104
+ }).stdout?.trim();
203105
+ } catch {
203106
+ }
203107
+ if (!_repoRoot) _repoRoot = process.cwd();
203108
+ return _repoRoot;
203109
+ }
203110
+ function spawnShell(params) {
203111
+ const spawnOpts = { env: params.env, cwd: params.cwd, stdio: params.stdio, detached: true };
203112
+ const sandboxMethod = detectSandboxMethod();
203113
+ const ci = process.env.CI === "true";
203114
+ if (ci && sandboxMethod === "none") {
203115
+ throw new Error(
203116
+ "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"
203117
+ );
203118
+ }
203119
+ const repoRoot = resolveRepoRoot();
203120
+ const fsMounts = buildFsMounts(repoRoot);
203121
+ if (sandboxMethod === "unshare") {
203122
+ return spawn5(
203123
+ "unshare",
203124
+ [
203125
+ "--pid",
203126
+ "--fork",
203127
+ "--mount-proc",
203128
+ "bash",
203129
+ "-c",
203130
+ `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} ${params.command}`
203131
+ ],
203132
+ spawnOpts
203133
+ );
203134
+ }
203135
+ if (sandboxMethod === "userns-unshare") {
203136
+ const escaped = params.command.replace(/'/g, "'\\''");
203137
+ return spawn5(
203138
+ "unshare",
203139
+ [
203140
+ "--user",
203141
+ "--map-root-user",
203142
+ "--pid",
203143
+ "--fork",
203144
+ "--mount",
203145
+ "bash",
203146
+ "-c",
203147
+ `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec setpriv --no-new-privs --bounding-set -all --inh-caps -all --ambient-caps -all bash -c '${escaped}'`
203148
+ ],
203149
+ spawnOpts
203150
+ );
203151
+ }
203152
+ if (sandboxMethod === "sudo-unshare") {
203153
+ const envArgs = [];
203154
+ for (const [k2, v] of Object.entries(params.env)) {
203155
+ if (v !== void 0) {
203156
+ envArgs.push(`${k2}=${v}`);
203157
+ }
203158
+ }
203159
+ const username = userInfo2().username;
203160
+ const pathRestore = 'export PATH="${SANDBOX_PATH:-$PATH}"; ';
203161
+ const escaped = (pathRestore + params.command).replace(/'/g, "'\\''");
203162
+ envArgs.push(`SANDBOX_PATH=${params.env.PATH ?? ""}`);
203163
+ return spawn5(
203164
+ "sudo",
203165
+ [
203166
+ "env",
203167
+ ...envArgs,
203168
+ "unshare",
203169
+ "--pid",
203170
+ "--fork",
203171
+ "--mount-proc",
203172
+ "bash",
203173
+ "-c",
203174
+ `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec su -p -s /bin/bash ${username} -c '${escaped}'`
203175
+ ],
203176
+ { ...spawnOpts, env: {} }
203177
+ );
203178
+ }
203179
+ return spawn5("bash", ["-c", params.command], spawnOpts);
203180
+ }
203181
+ async function killProcessGroup(proc) {
203182
+ if (!proc.pid) return;
203183
+ try {
203184
+ process.kill(-proc.pid, "SIGTERM");
203185
+ await new Promise((r2) => setTimeout(r2, 200));
203186
+ process.kill(-proc.pid, "SIGKILL");
203187
+ } catch {
203188
+ try {
203189
+ proc.kill("SIGKILL");
203190
+ } catch {
203191
+ }
203192
+ }
203193
+ }
203194
+ function getTempDir() {
203195
+ const tempDir = process.env.PULLFROG_TEMP_DIR;
203196
+ if (!tempDir) {
203197
+ throw new Error("PULLFROG_TEMP_DIR not set");
203198
+ }
203199
+ return tempDir;
203200
+ }
203201
+ var MAX_OUTPUT_CHARS = 5e3;
203202
+ async function runSandboxed(params) {
203203
+ const proc = spawnShell({
203204
+ command: params.command,
203205
+ env: params.env,
203206
+ cwd: params.cwd,
203207
+ stdio: ["ignore", "pipe", "pipe"]
203208
+ });
203209
+ const stdout2 = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
203210
+ const stderr = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
203211
+ let timedOut = false, exited = false, spawnError = "";
203212
+ proc.stdout?.on("data", (chunk) => {
203213
+ const text = chunk.toString();
203214
+ stdout2.append(text);
203215
+ params.onStdout?.(text);
203216
+ });
203217
+ proc.stderr?.on("data", (chunk) => {
203218
+ const text = chunk.toString();
203219
+ stderr.append(text);
203220
+ params.onStderr?.(text);
203221
+ });
203222
+ const timeoutId = setTimeout(async () => {
203223
+ if (!exited) {
203224
+ timedOut = true;
203225
+ await killProcessGroup(proc);
203226
+ }
203227
+ }, params.timeout);
203228
+ const exitCode = await new Promise((resolve3) => {
203229
+ const done = (code) => {
203230
+ exited = true;
203231
+ clearTimeout(timeoutId);
203232
+ resolve3(code);
203233
+ };
203234
+ proc.on("exit", done);
203235
+ proc.on("error", (err) => {
203236
+ spawnError = err.message;
203237
+ done(null);
203238
+ });
203239
+ });
203240
+ proc.stdout?.destroy();
203241
+ proc.stderr?.destroy();
203242
+ const outText = stdout2.toString();
203243
+ const errText = stderr.toString();
203244
+ let output2 = errText ? outText ? `${outText}
203245
+ ${errText}` : errText : outText;
203246
+ if (spawnError) output2 = output2 ? `${output2}
203247
+ ${spawnError}` : spawnError;
203248
+ if (timedOut)
203249
+ output2 = output2 ? `${output2}
203250
+ [timed out after ${params.timeout}ms]` : `[timed out after ${params.timeout}ms]`;
203251
+ return {
203252
+ output: output2.trim(),
203253
+ exitCode: exitCode ?? (timedOut ? 124 : -1),
203254
+ timedOut
203255
+ };
203256
+ }
203257
+ function capOutput(output2) {
203258
+ if (output2.length <= MAX_OUTPUT_CHARS) return output2;
203259
+ const fullPath = join11(getTempDir(), `shell-${randomUUID3().slice(0, 8)}.log`);
203260
+ writeFileSync9(fullPath, output2);
203261
+ const elided = output2.length - MAX_OUTPUT_CHARS;
203262
+ return `... [${elided} chars truncated; output saved to ${fullPath}] ...
203263
+ ${output2.slice(-MAX_OUTPUT_CHARS)}`;
203264
+ }
203265
+ function isGitCommand(command) {
203266
+ const trimmed = command.trim();
203267
+ if (trimmed === "git" || trimmed.startsWith("git ")) return true;
203268
+ if (trimmed.startsWith("sudo git")) return true;
203269
+ return /[;&|]\s*(?:sudo\s+)?git(?:\s|$)/.test(trimmed);
203270
+ }
203271
+ function ShellTool(ctx) {
203272
+ return tool({
203273
+ name: "shell",
203274
+ timeoutMs: 12e4,
203275
+ description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
203276
+
203277
+ Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
203278
+
203279
+ Use this tool to:
203280
+ - Run shell commands (ls, cat, grep, find, etc.)
203281
+ - Execute build tools (npm, pnpm, cargo, make, etc.)
203282
+ - Run tests and linters
203283
+
203284
+ 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.
203285
+
203286
+ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead.`,
203287
+ parameters: ShellParams,
203288
+ execute: execute(async (params) => {
203289
+ if (isGitCommand(params.command)) {
203290
+ throw new Error(
203291
+ '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'
203292
+ );
203293
+ }
203294
+ const timeout = Math.min(params.timeout ?? 3e4, 12e4);
203295
+ const cwd = params.working_directory ?? process.cwd();
203296
+ const env2 = resolveEnv(ctx.payload.shell === "enabled" ? "inherit" : "restricted");
203297
+ if (params.command.includes("agent-browser")) {
203298
+ const daemonError = ensureBrowserDaemon(ctx.toolState);
203299
+ if (daemonError) {
203300
+ return {
203301
+ output: `browser daemon unavailable: ${daemonError}`,
203302
+ exit_code: 1,
203303
+ timed_out: false
203304
+ };
203305
+ }
203306
+ const binDir = ctx.toolState.browserDaemon?.binDir;
203307
+ if (binDir) {
203308
+ env2.PATH = `${binDir}:${env2.PATH ?? ""}`;
203309
+ }
203310
+ }
203311
+ if (params.background) {
203312
+ const tempDir = getTempDir();
203313
+ const handle = `bg-${randomUUID3().slice(0, 8)}`;
203314
+ const outputPath = join11(tempDir, `${handle}.log`);
203315
+ const pidPath = join11(tempDir, `${handle}.pid`);
203316
+ const logFd = openSync(outputPath, "a");
203317
+ let proc;
203318
+ try {
203319
+ proc = spawnShell({
203320
+ command: params.command,
203321
+ env: env2,
203322
+ cwd,
203323
+ stdio: ["ignore", logFd, logFd]
203324
+ });
203325
+ } finally {
203326
+ closeSync(logFd);
203327
+ }
203328
+ if (!proc.pid) {
203329
+ throw new Error("failed to start background process");
203330
+ }
203331
+ proc.unref();
203332
+ writeFileSync9(pidPath, `${proc.pid}
203333
+ `);
203334
+ ctx.toolState.backgroundProcesses.set(handle, { pid: proc.pid, outputPath, pidPath });
203335
+ return {
203336
+ handle,
203337
+ outputPath,
203338
+ pidPath,
203339
+ message: `started background process ${handle} (pid ${proc.pid})`
203340
+ };
203341
+ }
203342
+ const result = await runSandboxed({ command: params.command, env: env2, cwd, timeout });
203343
+ if (result.exitCode !== 0) {
203344
+ log2.info(`shell command failed with exit code ${result.exitCode}: ${params.command}`);
203345
+ if (result.output) log2.info(`output: ${result.output}`);
203346
+ }
203347
+ return {
203348
+ output: capOutput(result.output),
203349
+ exit_code: result.exitCode,
203350
+ timed_out: result.timedOut
203351
+ };
203352
+ })
203353
+ });
203354
+ }
203355
+ var KillBackgroundParams = type({
203356
+ handle: type.string.describe("The handle of the background process to kill (e.g., bg-a1b2c3d4)")
203357
+ });
203358
+ function KillBackgroundTool(ctx) {
203359
+ return tool({
203360
+ name: "kill_background",
203361
+ mutates: true,
203362
+ description: `Kill a background process by its handle. Use this to stop dev servers or other long-running processes started with shell({ background: true }).`,
203363
+ parameters: KillBackgroundParams,
203364
+ execute: execute(async (params) => {
203365
+ const proc = ctx.toolState.backgroundProcesses.get(params.handle);
203366
+ if (!proc) {
203367
+ return {
203368
+ success: false,
203369
+ message: `no background process with handle ${params.handle}`
203370
+ };
203371
+ }
203372
+ try {
203373
+ process.kill(-proc.pid, "SIGTERM");
203374
+ } catch {
203375
+ }
203376
+ await sleep2(200);
203377
+ try {
203378
+ process.kill(-proc.pid, "SIGKILL");
203379
+ } catch {
203380
+ }
203381
+ ctx.toolState.backgroundProcesses.delete(params.handle);
203382
+ return {
203383
+ success: true,
203384
+ message: `killed background process ${params.handle} (pid ${proc.pid})`
203385
+ };
203386
+ })
203387
+ });
203388
+ }
203389
+
202385
203390
  // utils/lifecycle.ts
202386
203391
  function describeSetupFailure(failure2) {
202387
203392
  if (!failure2) return "";
@@ -202403,44 +203408,47 @@ ${failure2.output || "(empty)"}`;
202403
203408
  async function executeLifecycleHook(params) {
202404
203409
  if (!params.script) return {};
202405
203410
  log2.info(`\xBB executing ${params.event} lifecycle hook...`);
203411
+ resolveTrustedGit();
202406
203412
  const preHookTrackedCount = params.normalizeWorkingTreeAfter ? (await runGitLines(["diff", "--name-only", "HEAD"])).length : 0;
202407
203413
  let result;
202408
203414
  try {
203415
+ let sandboxed;
202409
203416
  try {
202410
- const spawnResult = await spawn3({
202411
- cmd: "bash",
202412
- args: ["-c", params.script],
202413
- env: process.env,
203417
+ sandboxed = await runSandboxed({
203418
+ command: params.script,
203419
+ env: resolveEnv(params.shell === "enabled" ? "inherit" : "restricted"),
203420
+ cwd: process.cwd(),
202414
203421
  timeout: LIFECYCLE_HOOK_TIMEOUT_MS,
202415
- activityTimeout: 0,
202416
203422
  onStdout: (chunk) => process.stdout.write(chunk),
202417
203423
  onStderr: (chunk) => process.stderr.write(chunk)
202418
203424
  });
202419
- if (spawnResult.exitCode !== 0) {
202420
- const output2 = (spawnResult.stderr || spawnResult.stdout).trim();
202421
- result = {
202422
- failure: { kind: "exit", output: output2, exitCode: spawnResult.exitCode },
202423
- 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.`
202424
- };
202425
- } else {
202426
- log2.info(`\xBB ${params.event} lifecycle hook completed successfully`);
202427
- result = {};
202428
- }
202429
203425
  } catch (err) {
202430
- const isTimeout = err instanceof SpawnTimeoutError && (err.code === SPAWN_TIMEOUT_CODE || err.code === SPAWN_ACTIVITY_TIMEOUT_CODE);
202431
- if (isTimeout) {
202432
- const minutes = Math.round(LIFECYCLE_HOOK_TIMEOUT_MS / 6e4);
202433
- result = {
202434
- failure: { kind: "timeout" },
202435
- 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).`
202436
- };
202437
- } else {
202438
- const msg = err instanceof Error ? err.message : String(err);
202439
- result = {
202440
- failure: { kind: "spawn", spawnError: msg },
202441
- warning: `lifecycle hook '${params.event}' failed to spawn: ${msg}. this is likely a transient failure \u2014 retry the operation.`
202442
- };
202443
- }
203426
+ const msg = err instanceof Error ? err.message : String(err);
203427
+ return {
203428
+ failure: { kind: "spawn", spawnError: msg },
203429
+ warning: `lifecycle hook '${params.event}' could not be sandboxed, so it did not run: ${msg}`
203430
+ };
203431
+ }
203432
+ if (sandboxed.timedOut) {
203433
+ const minutes = Math.round(LIFECYCLE_HOOK_TIMEOUT_MS / 6e4);
203434
+ result = {
203435
+ failure: { kind: "timeout" },
203436
+ 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).`
203437
+ };
203438
+ } else if (sandboxed.exitCode === -1) {
203439
+ result = {
203440
+ failure: { kind: "spawn", spawnError: sandboxed.output },
203441
+ warning: `lifecycle hook '${params.event}' failed to spawn: ${sandboxed.output}. this is likely a transient failure \u2014 retry the operation.`
203442
+ };
203443
+ } else if (sandboxed.exitCode !== 0) {
203444
+ const output2 = capOutput(sandboxed.output);
203445
+ result = {
203446
+ failure: { kind: "exit", output: output2, exitCode: sandboxed.exitCode },
203447
+ 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.`
203448
+ };
203449
+ } else {
203450
+ log2.info(`\xBB ${params.event} lifecycle hook completed successfully`);
203451
+ result = {};
202444
203452
  }
202445
203453
  } finally {
202446
203454
  if (params.normalizeWorkingTreeAfter) {
@@ -202461,8 +203469,20 @@ async function normalizeWorkingTreeAfterHook(params) {
202461
203469
  await runGit(["restore", "--staged", "--worktree", "."]);
202462
203470
  log2.info(`\xBB discarded ${trackedCount} tracked changes from ${params.event} hook`);
202463
203471
  }
203472
+ var trustedGit;
203473
+ function resolveTrustedGit() {
203474
+ if (trustedGit) return trustedGit;
203475
+ const resolved = spawnSync8("sh", ["-c", "command -v git"], { encoding: "utf-8" });
203476
+ trustedGit = resolved.stdout?.trim() || "git";
203477
+ return trustedGit;
203478
+ }
202464
203479
  async function runGit(args2) {
202465
- const result = await spawn3({ cmd: "git", args: args2, env: process.env, activityTimeout: 0 });
203480
+ const result = await spawn3({
203481
+ cmd: resolveTrustedGit(),
203482
+ args: args2,
203483
+ env: filterEnvForUntrustedCode(),
203484
+ activityTimeout: 0
203485
+ });
202466
203486
  if (result.exitCode !== 0) {
202467
203487
  throw new Error(
202468
203488
  `git ${args2.join(" ")} failed (exit ${result.exitCode}): ${result.stderr.trim() || "(no stderr)"}`
@@ -202582,7 +203602,7 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
202582
203602
 
202583
203603
  // yes/index.ts
202584
203604
  import { performance as performance8 } from "node:perf_hooks";
202585
- import { setTimeout as sleep2 } from "node:timers/promises";
203605
+ import { setTimeout as sleep3 } from "node:timers/promises";
202586
203606
 
202587
203607
  // node_modules/.pnpm/lru-cache@11.5.2/node_modules/lru-cache/dist/esm/node/index.min.js
202588
203608
  import { tracingChannel as G, channel as P } from "node:diagnostics_channel";
@@ -203376,7 +204396,7 @@ function _op(fn2, options) {
203376
204396
  );
203377
204397
  log3.info({ key, error: error109, attempt });
203378
204398
  }
203379
- await sleep2(delay2);
204399
+ await sleep3(delay2);
203380
204400
  }
203381
204401
  }
203382
204402
  }
@@ -203444,14 +204464,14 @@ function _op(fn2, options) {
203444
204464
  }
203445
204465
 
203446
204466
  // mcp/git.ts
203447
- import { randomUUID as randomUUID3 } from "node:crypto";
203448
- import { writeFileSync as writeFileSync9 } from "node:fs";
203449
- import { join as join13 } from "node:path";
204467
+ import { randomUUID as randomUUID4 } from "node:crypto";
204468
+ import { writeFileSync as writeFileSync10 } from "node:fs";
204469
+ import { join as join14 } from "node:path";
203450
204470
 
203451
204471
  // utils/apiCommit.ts
203452
204472
  import { execFileSync as execFileSync6 } from "node:child_process";
203453
204473
  import { lstat, readlink } from "node:fs/promises";
203454
- import { join as join11 } from "node:path";
204474
+ import { join as join12 } from "node:path";
203455
204475
  var GITHUB_API = "https://api.github.com";
203456
204476
  var MAX_BLOB_BYTES = 30 * 1024 * 1024;
203457
204477
  var BLOB_UPLOAD_CONCURRENCY = 8;
@@ -203516,7 +204536,7 @@ async function assertApiCommittable(files) {
203516
204536
  const root = getRepoRoot();
203517
204537
  const attrs = $(
203518
204538
  "git",
203519
- ["check-attr", "filter", "-z", "--", ...present.map((p) => join11(root, p))],
204539
+ ["check-attr", "filter", "-z", "--", ...present.map((p) => join12(root, p))],
203520
204540
  { log: false }
203521
204541
  );
203522
204542
  const parts = attrs.split("\0");
@@ -203528,7 +204548,7 @@ async function assertApiCommittable(files) {
203528
204548
  }
203529
204549
  }
203530
204550
  for (const path4 of present) {
203531
- const stat = await lstat(join11(root, path4));
204551
+ const stat = await lstat(join12(root, path4));
203532
204552
  if (stat.isDirectory()) {
203533
204553
  throw new Error(
203534
204554
  `'${path4}' is a directory (nested repository or submodule?) \u2014 signed commits only support files and symlinks.`
@@ -203537,7 +204557,7 @@ async function assertApiCommittable(files) {
203537
204557
  }
203538
204558
  }
203539
204559
  async function createBlobEntry(params) {
203540
- const absPath = join11(params.repoRoot, params.path);
204560
+ const absPath = join12(params.repoRoot, params.path);
203541
204561
  const stat = await lstat(absPath);
203542
204562
  if (stat.size > MAX_BLOB_BYTES) {
203543
204563
  throw new Error(
@@ -203717,7 +204737,7 @@ this usually means your local branch has commits that were never pushed \u2014 s
203717
204737
  var core5 = __toESM(require_core(), 1);
203718
204738
  import { createSign } from "node:crypto";
203719
204739
  import { rename, writeFile } from "node:fs/promises";
203720
- import { dirname as dirname3, join as join12 } from "node:path";
204740
+ import { dirname as dirname3, join as join13 } from "node:path";
203721
204741
 
203722
204742
  // node_modules/.pnpm/@octokit+plugin-throttling@11.0.5_@octokit+core@7.0.8/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
203723
204743
  var import_light = __toESM(require_light(), 1);
@@ -204089,7 +205109,7 @@ function lowercaseKeys(object6) {
204089
205109
  return newObj;
204090
205110
  }, {});
204091
205111
  }
204092
- function isPlainObject7(value2) {
205112
+ function isPlainObject8(value2) {
204093
205113
  if (typeof value2 !== "object" || value2 === null) return false;
204094
205114
  if (Object.prototype.toString.call(value2) !== "[object Object]") return false;
204095
205115
  const proto = Object.getPrototypeOf(value2);
@@ -204100,7 +205120,7 @@ function isPlainObject7(value2) {
204100
205120
  function mergeDeep(defaults2, options) {
204101
205121
  const result = Object.assign({}, defaults2);
204102
205122
  Object.keys(options).forEach((key) => {
204103
- if (isPlainObject7(options[key])) {
205123
+ if (isPlainObject8(options[key])) {
204104
205124
  if (!(key in defaults2)) Object.assign(result, { [key]: options[key] });
204105
205125
  else result[key] = mergeDeep(defaults2[key], options[key]);
204106
205126
  } else {
@@ -204387,7 +205407,7 @@ var NullObject = /* @__PURE__ */ (() => {
204387
205407
  return C;
204388
205408
  })();
204389
205409
  function parse7(header, options) {
204390
- const stopChar = options?.comma === true ? COMMA : 65536;
205410
+ const stopChar = options?.comma === true ? COMMA2 : 65536;
204391
205411
  const len = header.length;
204392
205412
  let index = skipOWS(header, options?.start ?? 0, len);
204393
205413
  const valueStart = index;
@@ -204405,7 +205425,7 @@ var SEMI = 59;
204405
205425
  var EQ = 61;
204406
205426
  var DQUOTE = 34;
204407
205427
  var BSLASH = 92;
204408
- var COMMA = 44;
205428
+ var COMMA2 = 44;
204409
205429
  function parseParameters(header, type2, index, len, stopChar) {
204410
205430
  const parameters = new NullObject();
204411
205431
  parameter: while (index < len) {
@@ -204864,7 +205884,7 @@ var defaults_default = {
204864
205884
  "user-agent": `octokit-request.js/${VERSION3} ${getUserAgent2()}`
204865
205885
  }
204866
205886
  };
204867
- function isPlainObject8(value2) {
205887
+ function isPlainObject9(value2) {
204868
205888
  if (typeof value2 !== "object" || value2 === null) return false;
204869
205889
  if (Object.prototype.toString.call(value2) !== "[object Object]") return false;
204870
205890
  const proto = Object.getPrototypeOf(value2);
@@ -204882,7 +205902,7 @@ async function fetchWrapper(requestOptions) {
204882
205902
  }
204883
205903
  const log4 = requestOptions.request?.log || console;
204884
205904
  const parseSuccessResponseBody = requestOptions.request?.parseSuccessResponseBody !== false;
204885
- const body = isPlainObject8(requestOptions.body) || Array.isArray(requestOptions.body) ? JSONStringify(requestOptions.body) : requestOptions.body;
205905
+ const body = isPlainObject9(requestOptions.body) || Array.isArray(requestOptions.body) ? JSONStringify(requestOptions.body) : requestOptions.body;
204886
205906
  const requestHeaders = Object.fromEntries(
204887
205907
  Object.entries(requestOptions.headers).map(([name, value2]) => [
204888
205908
  name,
@@ -208187,7 +209207,7 @@ function getGitHubUsageSummary() {
208187
209207
  }
208188
209208
  async function writeGitHubUsageSummaryToFile(path4) {
208189
209209
  const summary2 = getGitHubUsageSummary();
208190
- const tmpPath = join12(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
209210
+ const tmpPath = join13(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
208191
209211
  await writeFile(tmpPath, JSON.stringify(summary2));
208192
209212
  await rename(tmpPath, path4);
208193
209213
  }
@@ -208294,531 +209314,6 @@ function resolveRepoCtx(ctx, repo) {
208294
209314
  };
208295
209315
  }
208296
209316
 
208297
- // node_modules/.pnpm/@toon-format+toon@1.4.0/node_modules/@toon-format/toon/dist/index.mjs
208298
- var LIST_ITEM_MARKER = "-";
208299
- var LIST_ITEM_PREFIX = "- ";
208300
- var COMMA2 = ",";
208301
- var PIPE = "|";
208302
- var DOT = ".";
208303
- var NULL_LITERAL = "null";
208304
- var TRUE_LITERAL = "true";
208305
- var FALSE_LITERAL = "false";
208306
- var BACKSLASH = "\\";
208307
- var DOUBLE_QUOTE = '"';
208308
- var TAB = " ";
208309
- var DELIMITERS = {
208310
- comma: COMMA2,
208311
- tab: TAB,
208312
- pipe: PIPE
208313
- };
208314
- var DEFAULT_DELIMITER = DELIMITERS.comma;
208315
- function escapeString(value2) {
208316
- 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`);
208317
- }
208318
- function isBooleanOrNullLiteral(token) {
208319
- return token === TRUE_LITERAL || token === FALSE_LITERAL || token === NULL_LITERAL;
208320
- }
208321
- function normalizeValue(value2) {
208322
- if (value2 === null) return null;
208323
- if (typeof value2 === "string" || typeof value2 === "boolean") return value2;
208324
- if (typeof value2 === "number") {
208325
- if (Object.is(value2, -0)) return 0;
208326
- if (!Number.isFinite(value2)) return null;
208327
- return value2;
208328
- }
208329
- if (typeof value2 === "bigint") {
208330
- if (value2 >= Number.MIN_SAFE_INTEGER && value2 <= Number.MAX_SAFE_INTEGER) return Number(value2);
208331
- return value2.toString();
208332
- }
208333
- if (value2 instanceof Date) return value2.toISOString();
208334
- if (Array.isArray(value2)) return value2.map(normalizeValue);
208335
- if (value2 instanceof Set) return Array.from(value2).map(normalizeValue);
208336
- if (value2 instanceof Map) return Object.fromEntries(Array.from(value2, ([k2, v]) => [String(k2), normalizeValue(v)]));
208337
- if (isPlainObject9(value2)) {
208338
- const normalized = {};
208339
- for (const key in value2) if (Object.prototype.hasOwnProperty.call(value2, key)) normalized[key] = normalizeValue(value2[key]);
208340
- return normalized;
208341
- }
208342
- return null;
208343
- }
208344
- function isJsonPrimitive(value2) {
208345
- return value2 === null || typeof value2 === "string" || typeof value2 === "number" || typeof value2 === "boolean";
208346
- }
208347
- function isJsonArray(value2) {
208348
- return Array.isArray(value2);
208349
- }
208350
- function isJsonObject4(value2) {
208351
- return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
208352
- }
208353
- function isEmptyObject2(value2) {
208354
- return Object.keys(value2).length === 0;
208355
- }
208356
- function isPlainObject9(value2) {
208357
- if (value2 === null || typeof value2 !== "object") return false;
208358
- const prototype = Object.getPrototypeOf(value2);
208359
- return prototype === null || prototype === Object.prototype;
208360
- }
208361
- function isArrayOfPrimitives(value2) {
208362
- return value2.length === 0 || value2.every((item) => isJsonPrimitive(item));
208363
- }
208364
- function isArrayOfArrays(value2) {
208365
- return value2.length === 0 || value2.every((item) => isJsonArray(item));
208366
- }
208367
- function isArrayOfObjects(value2) {
208368
- return value2.length === 0 || value2.every((item) => isJsonObject4(item));
208369
- }
208370
- function isValidUnquotedKey(key) {
208371
- return /^[A-Z_][\w.]*$/i.test(key);
208372
- }
208373
- function isIdentifierSegment(key) {
208374
- return /^[A-Z_]\w*$/i.test(key);
208375
- }
208376
- function isSafeUnquoted(value2, delimiter3 = DEFAULT_DELIMITER) {
208377
- if (!value2) return false;
208378
- if (value2 !== value2.trim()) return false;
208379
- if (isBooleanOrNullLiteral(value2) || isNumericLike(value2)) return false;
208380
- if (value2.includes(":")) return false;
208381
- if (value2.includes('"') || value2.includes("\\")) return false;
208382
- if (/[[\]{}]/.test(value2)) return false;
208383
- if (/[\n\r\t]/.test(value2)) return false;
208384
- if (value2.includes(delimiter3)) return false;
208385
- if (value2.startsWith(LIST_ITEM_MARKER)) return false;
208386
- return true;
208387
- }
208388
- function isNumericLike(value2) {
208389
- return /^-?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i.test(value2) || /^0\d+$/.test(value2);
208390
- }
208391
- var QUOTED_KEY_MARKER = Symbol("quotedKey");
208392
- function tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, flattenDepth) {
208393
- if (options.keyFolding !== "safe") return;
208394
- if (!isJsonObject4(value2)) return;
208395
- const { segments, tail, leafValue } = collectSingleKeyChain(key, value2, flattenDepth ?? options.flattenDepth);
208396
- if (segments.length < 2) return;
208397
- if (!segments.every((seg) => isIdentifierSegment(seg))) return;
208398
- const foldedKey = buildFoldedKey(segments);
208399
- const absolutePath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
208400
- if (siblings.includes(foldedKey)) return;
208401
- if (rootLiteralKeys && rootLiteralKeys.has(absolutePath)) return;
208402
- return {
208403
- foldedKey,
208404
- remainder: tail,
208405
- leafValue,
208406
- segmentCount: segments.length
208407
- };
208408
- }
208409
- function collectSingleKeyChain(startKey, startValue, maxDepth) {
208410
- const segments = [startKey];
208411
- let currentValue = startValue;
208412
- while (segments.length < maxDepth) {
208413
- if (!isJsonObject4(currentValue)) break;
208414
- const keys = Object.keys(currentValue);
208415
- if (keys.length !== 1) break;
208416
- const nextKey = keys[0];
208417
- const nextValue = currentValue[nextKey];
208418
- segments.push(nextKey);
208419
- currentValue = nextValue;
208420
- }
208421
- if (!isJsonObject4(currentValue) || isEmptyObject2(currentValue)) return {
208422
- segments,
208423
- tail: void 0,
208424
- leafValue: currentValue
208425
- };
208426
- return {
208427
- segments,
208428
- tail: currentValue,
208429
- leafValue: currentValue
208430
- };
208431
- }
208432
- function buildFoldedKey(segments) {
208433
- return segments.join(DOT);
208434
- }
208435
- function encodePrimitive(value2, delimiter3) {
208436
- if (value2 === null) return NULL_LITERAL;
208437
- if (typeof value2 === "boolean") return String(value2);
208438
- if (typeof value2 === "number") return String(value2);
208439
- return encodeStringLiteral(value2, delimiter3);
208440
- }
208441
- function encodeStringLiteral(value2, delimiter3 = DEFAULT_DELIMITER) {
208442
- if (isSafeUnquoted(value2, delimiter3)) return value2;
208443
- return `${DOUBLE_QUOTE}${escapeString(value2)}${DOUBLE_QUOTE}`;
208444
- }
208445
- function encodeKey(key) {
208446
- if (isValidUnquotedKey(key)) return key;
208447
- return `${DOUBLE_QUOTE}${escapeString(key)}${DOUBLE_QUOTE}`;
208448
- }
208449
- function encodeAndJoinPrimitives(values, delimiter3 = DEFAULT_DELIMITER) {
208450
- return values.map((v) => encodePrimitive(v, delimiter3)).join(delimiter3);
208451
- }
208452
- function formatHeader(length, options) {
208453
- const key = options?.key;
208454
- const fields = options?.fields;
208455
- const delimiter3 = options?.delimiter ?? COMMA2;
208456
- let header = "";
208457
- if (key) header += encodeKey(key);
208458
- header += `[${length}${delimiter3 !== DEFAULT_DELIMITER ? delimiter3 : ""}]`;
208459
- if (fields) {
208460
- const quotedFields = fields.map((f) => encodeKey(f));
208461
- header += `{${quotedFields.join(delimiter3)}}`;
208462
- }
208463
- header += ":";
208464
- return header;
208465
- }
208466
- function* encodeJsonValue(value2, options, depth) {
208467
- if (isJsonPrimitive(value2)) {
208468
- const encodedPrimitive = encodePrimitive(value2, options.delimiter);
208469
- if (encodedPrimitive !== "") yield encodedPrimitive;
208470
- return;
208471
- }
208472
- if (isJsonArray(value2)) yield* encodeArrayLines(void 0, value2, depth, options);
208473
- else if (isJsonObject4(value2)) yield* encodeObjectLines(value2, depth, options);
208474
- }
208475
- function* encodeObjectLines(value2, depth, options, rootLiteralKeys, pathPrefix, remainingDepth) {
208476
- const keys = Object.keys(value2);
208477
- if (depth === 0 && !rootLiteralKeys) rootLiteralKeys = new Set(keys.filter((k2) => k2.includes(".")));
208478
- const effectiveFlattenDepth = remainingDepth ?? options.flattenDepth;
208479
- for (const [key, val] of Object.entries(value2)) yield* encodeKeyValuePairLines(key, val, depth, options, keys, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
208480
- }
208481
- function* encodeKeyValuePairLines(key, value2, depth, options, siblings, rootLiteralKeys, pathPrefix, flattenDepth) {
208482
- const currentPath = pathPrefix ? `${pathPrefix}${DOT}${key}` : key;
208483
- const effectiveFlattenDepth = flattenDepth ?? options.flattenDepth;
208484
- if (options.keyFolding === "safe" && siblings) {
208485
- const foldResult = tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
208486
- if (foldResult) {
208487
- const { foldedKey, remainder, leafValue, segmentCount } = foldResult;
208488
- const encodedFoldedKey = encodeKey(foldedKey);
208489
- if (remainder === void 0) {
208490
- if (isJsonPrimitive(leafValue)) {
208491
- yield indentedLine(depth, `${encodedFoldedKey}: ${encodePrimitive(leafValue, options.delimiter)}`, options.indent);
208492
- return;
208493
- } else if (isJsonArray(leafValue)) {
208494
- yield* encodeArrayLines(foldedKey, leafValue, depth, options);
208495
- return;
208496
- } else if (isJsonObject4(leafValue) && isEmptyObject2(leafValue)) {
208497
- yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
208498
- return;
208499
- }
208500
- }
208501
- if (isJsonObject4(remainder)) {
208502
- yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
208503
- const remainingDepth = effectiveFlattenDepth - segmentCount;
208504
- const foldedPath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
208505
- yield* encodeObjectLines(remainder, depth + 1, options, rootLiteralKeys, foldedPath, remainingDepth);
208506
- return;
208507
- }
208508
- }
208509
- }
208510
- const encodedKey = encodeKey(key);
208511
- if (isJsonPrimitive(value2)) yield indentedLine(depth, `${encodedKey}: ${encodePrimitive(value2, options.delimiter)}`, options.indent);
208512
- else if (isJsonArray(value2)) yield* encodeArrayLines(key, value2, depth, options);
208513
- else if (isJsonObject4(value2)) {
208514
- yield indentedLine(depth, `${encodedKey}:`, options.indent);
208515
- if (!isEmptyObject2(value2)) yield* encodeObjectLines(value2, depth + 1, options, rootLiteralKeys, currentPath, effectiveFlattenDepth);
208516
- }
208517
- }
208518
- function* encodeArrayLines(key, value2, depth, options) {
208519
- if (value2.length === 0) {
208520
- yield indentedLine(depth, formatHeader(0, {
208521
- key,
208522
- delimiter: options.delimiter
208523
- }), options.indent);
208524
- return;
208525
- }
208526
- if (isArrayOfPrimitives(value2)) {
208527
- yield indentedLine(depth, encodeInlineArrayLine(value2, options.delimiter, key), options.indent);
208528
- return;
208529
- }
208530
- if (isArrayOfArrays(value2)) {
208531
- if (value2.every((arr) => isArrayOfPrimitives(arr))) {
208532
- yield* encodeArrayOfArraysAsListItemsLines(key, value2, depth, options);
208533
- return;
208534
- }
208535
- }
208536
- if (isArrayOfObjects(value2)) {
208537
- const header = extractTabularHeader(value2);
208538
- if (header) yield* encodeArrayOfObjectsAsTabularLines(key, value2, header, depth, options);
208539
- else yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
208540
- return;
208541
- }
208542
- yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
208543
- }
208544
- function* encodeArrayOfArraysAsListItemsLines(prefix, values, depth, options) {
208545
- yield indentedLine(depth, formatHeader(values.length, {
208546
- key: prefix,
208547
- delimiter: options.delimiter
208548
- }), options.indent);
208549
- for (const arr of values) if (isArrayOfPrimitives(arr)) {
208550
- const arrayLine = encodeInlineArrayLine(arr, options.delimiter);
208551
- yield indentedListItem(depth + 1, arrayLine, options.indent);
208552
- }
208553
- }
208554
- function encodeInlineArrayLine(values, delimiter3, prefix) {
208555
- const header = formatHeader(values.length, {
208556
- key: prefix,
208557
- delimiter: delimiter3
208558
- });
208559
- const joinedValue = encodeAndJoinPrimitives(values, delimiter3);
208560
- if (values.length === 0) return header;
208561
- return `${header} ${joinedValue}`;
208562
- }
208563
- function* encodeArrayOfObjectsAsTabularLines(prefix, rows, header, depth, options) {
208564
- yield indentedLine(depth, formatHeader(rows.length, {
208565
- key: prefix,
208566
- fields: header,
208567
- delimiter: options.delimiter
208568
- }), options.indent);
208569
- yield* writeTabularRowsLines(rows, header, depth + 1, options);
208570
- }
208571
- function extractTabularHeader(rows) {
208572
- if (rows.length === 0) return;
208573
- const firstRow = rows[0];
208574
- const firstKeys = Object.keys(firstRow);
208575
- if (firstKeys.length === 0) return;
208576
- if (isTabularArray(rows, firstKeys)) return firstKeys;
208577
- }
208578
- function isTabularArray(rows, header) {
208579
- for (const row of rows) {
208580
- if (Object.keys(row).length !== header.length) return false;
208581
- for (const key of header) {
208582
- if (!(key in row)) return false;
208583
- if (!isJsonPrimitive(row[key])) return false;
208584
- }
208585
- }
208586
- return true;
208587
- }
208588
- function* writeTabularRowsLines(rows, header, depth, options) {
208589
- for (const row of rows) yield indentedLine(depth, encodeAndJoinPrimitives(header.map((key) => row[key]), options.delimiter), options.indent);
208590
- }
208591
- function* encodeMixedArrayAsListItemsLines(prefix, items, depth, options) {
208592
- yield indentedLine(depth, formatHeader(items.length, {
208593
- key: prefix,
208594
- delimiter: options.delimiter
208595
- }), options.indent);
208596
- for (const item of items) yield* encodeListItemValueLines(item, depth + 1, options);
208597
- }
208598
- function* encodeObjectAsListItemLines(obj, depth, options) {
208599
- if (isEmptyObject2(obj)) {
208600
- yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
208601
- return;
208602
- }
208603
- const entries = Object.entries(obj);
208604
- if (entries.length === 1) {
208605
- const [key, value2] = entries[0];
208606
- if (isJsonArray(value2) && isArrayOfObjects(value2)) {
208607
- const header = extractTabularHeader(value2);
208608
- if (header) {
208609
- yield indentedListItem(depth, formatHeader(value2.length, {
208610
- key,
208611
- fields: header,
208612
- delimiter: options.delimiter
208613
- }), options.indent);
208614
- yield* writeTabularRowsLines(value2, header, depth + 1, options);
208615
- return;
208616
- }
208617
- }
208618
- }
208619
- yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
208620
- yield* encodeObjectLines(obj, depth + 1, options);
208621
- }
208622
- function* encodeListItemValueLines(value2, depth, options) {
208623
- if (isJsonPrimitive(value2)) yield indentedListItem(depth, encodePrimitive(value2, options.delimiter), options.indent);
208624
- else if (isJsonArray(value2)) if (isArrayOfPrimitives(value2)) yield indentedListItem(depth, encodeInlineArrayLine(value2, options.delimiter), options.indent);
208625
- else {
208626
- yield indentedListItem(depth, formatHeader(value2.length, { delimiter: options.delimiter }), options.indent);
208627
- for (const item of value2) yield* encodeListItemValueLines(item, depth + 1, options);
208628
- }
208629
- else if (isJsonObject4(value2)) yield* encodeObjectAsListItemLines(value2, depth, options);
208630
- }
208631
- function indentedLine(depth, content, indentSize) {
208632
- return " ".repeat(indentSize * depth) + content;
208633
- }
208634
- function indentedListItem(depth, content, indentSize) {
208635
- return indentedLine(depth, LIST_ITEM_PREFIX + content, indentSize);
208636
- }
208637
- function encode6(input2, options) {
208638
- return Array.from(encodeLines(input2, options)).join("\n");
208639
- }
208640
- function encodeLines(input2, options) {
208641
- return encodeJsonValue(normalizeValue(input2), resolveOptions(options), 0);
208642
- }
208643
- function resolveOptions(options) {
208644
- return {
208645
- indent: options?.indent ?? 2,
208646
- delimiter: options?.delimiter ?? DEFAULT_DELIMITER,
208647
- keyFolding: options?.keyFolding ?? "off",
208648
- flattenDepth: options?.flattenDepth ?? Number.POSITIVE_INFINITY
208649
- };
208650
- }
208651
-
208652
- // mcp/geminiSanitizer.ts
208653
- function parseStringEnumBranch(item) {
208654
- if (!item || typeof item !== "object") return null;
208655
- const record4 = item;
208656
- if (Array.isArray(record4.enum)) {
208657
- const strings = record4.enum.filter((v) => typeof v === "string");
208658
- return strings.length === record4.enum.length && strings.length > 0 ? { values: strings } : null;
208659
- }
208660
- if (typeof record4.const === "string") {
208661
- return { values: [record4.const] };
208662
- }
208663
- return null;
208664
- }
208665
- function collapseStringUnion(branches) {
208666
- const values = [];
208667
- for (const item of branches) {
208668
- const parsed2 = parseStringEnumBranch(item);
208669
- if (!parsed2) return null;
208670
- values.push(...parsed2.values);
208671
- }
208672
- if (values.length === 0) return null;
208673
- return { type: "string", enum: [...new Set(values)] };
208674
- }
208675
- function sanitizeForGemini(schema2) {
208676
- if (!schema2 || typeof schema2 !== "object") return schema2;
208677
- if (Array.isArray(schema2)) return schema2.map(sanitizeForGemini);
208678
- const source = schema2;
208679
- if (Array.isArray(source.enum) && typeof source.type !== "string") {
208680
- const allStrings = source.enum.every((v) => typeof v === "string");
208681
- if (allStrings) {
208682
- const result = { type: "string", enum: source.enum };
208683
- if (typeof source.description === "string") result.description = source.description;
208684
- return result;
208685
- }
208686
- }
208687
- for (const unionKey of ["anyOf", "oneOf"]) {
208688
- const branches = source[unionKey];
208689
- if (Array.isArray(branches) && branches.length > 0) {
208690
- const collapsed = collapseStringUnion(branches);
208691
- if (collapsed) {
208692
- const result = { ...collapsed };
208693
- if (typeof source.description === "string") result.description = source.description;
208694
- return result;
208695
- }
208696
- }
208697
- }
208698
- if (Array.isArray(source.anyOf) || Array.isArray(source.oneOf)) {
208699
- const result = {};
208700
- if (Array.isArray(source.anyOf)) result.anyOf = source.anyOf.map(sanitizeForGemini);
208701
- if (Array.isArray(source.oneOf)) result.oneOf = source.oneOf.map(sanitizeForGemini);
208702
- return result;
208703
- }
208704
- const sanitized = {};
208705
- for (const [key, value2] of Object.entries(source)) {
208706
- if (key === "$schema") continue;
208707
- if (key === "$defs") {
208708
- sanitized.definitions = sanitizeForGemini(value2);
208709
- continue;
208710
- }
208711
- sanitized[key] = sanitizeForGemini(value2);
208712
- }
208713
- return sanitized;
208714
- }
208715
- function wrapJsonSchemaProducer(producer) {
208716
- return new Proxy(producer, {
208717
- get(target, prop, receiver) {
208718
- const value2 = Reflect.get(target, prop, receiver);
208719
- if ((prop === "input" || prop === "output") && typeof value2 === "function") {
208720
- const fn2 = value2;
208721
- return (...args2) => sanitizeForGemini(fn2.apply(target, args2));
208722
- }
208723
- return value2;
208724
- }
208725
- });
208726
- }
208727
- function wrapStandard(standard) {
208728
- return new Proxy(standard, {
208729
- get(target, prop, receiver) {
208730
- if (prop === "jsonSchema") {
208731
- const value2 = Reflect.get(target, prop, receiver);
208732
- if (value2 && typeof value2 === "object") {
208733
- return wrapJsonSchemaProducer(value2);
208734
- }
208735
- return value2;
208736
- }
208737
- return Reflect.get(target, prop, receiver);
208738
- }
208739
- });
208740
- }
208741
- function wrapSchemaForGemini(schema2) {
208742
- return new Proxy(schema2, {
208743
- get(target, prop, receiver) {
208744
- if (prop === "~standard") {
208745
- const value2 = Reflect.get(target, prop, receiver);
208746
- if (value2 && typeof value2 === "object") {
208747
- return wrapStandard(value2);
208748
- }
208749
- return value2;
208750
- }
208751
- if (prop === "toJsonSchema") {
208752
- const method = Reflect.get(target, prop, receiver);
208753
- if (typeof method === "function") {
208754
- return () => sanitizeForGemini(method.call(target));
208755
- }
208756
- return method;
208757
- }
208758
- return Reflect.get(target, prop, receiver);
208759
- }
208760
- });
208761
- }
208762
- function sanitizeToolForGemini(tool2) {
208763
- if (!tool2.parameters) return tool2;
208764
- return { ...tool2, parameters: wrapSchemaForGemini(tool2.parameters) };
208765
- }
208766
- function isGeminiRouted(ctx) {
208767
- const effective = ctx.payload.proxyModel ?? ctx.resolvedModel ?? ctx.payload.model;
208768
- if (!effective) return true;
208769
- const normalized = effective.toLowerCase();
208770
- if (normalized.includes("gemini")) return true;
208771
- if (!normalized.includes("/")) return true;
208772
- return false;
208773
- }
208774
-
208775
- // mcp/shared.ts
208776
- function getHttpStatus(err) {
208777
- if (typeof err !== "object" || err === null || !("status" in err)) return void 0;
208778
- return typeof err.status === "number" ? err.status : void 0;
208779
- }
208780
- var tool = (toolDef) => toolDef;
208781
- var handleToolSuccess = (data) => {
208782
- const text = typeof data === "string" ? data : encode6(data);
208783
- return {
208784
- content: [{ type: "text", text }]
208785
- };
208786
- };
208787
- var handleToolError = (error109) => {
208788
- const errorMessage = error109 instanceof Error ? error109.message : String(error109);
208789
- return {
208790
- content: [
208791
- {
208792
- type: "text",
208793
- text: `Error: ${errorMessage}`
208794
- }
208795
- ],
208796
- isError: true
208797
- };
208798
- };
208799
- var execute = (fn2, toolName) => {
208800
- const _fn = async (params) => {
208801
- try {
208802
- const result = await fn2(params);
208803
- return handleToolSuccess(result);
208804
- } catch (error109) {
208805
- const errorMessage = error109 instanceof Error ? error109.message : String(error109);
208806
- const prefix = toolName ? `[${toolName}]` : "tool";
208807
- log2.info(`${prefix} error: ${errorMessage}`);
208808
- log2.debug(`${prefix} params: ${formatJsonValue(params)}`);
208809
- return handleToolError(error109);
208810
- }
208811
- };
208812
- return _fn;
208813
- };
208814
- var addTools = (ctx, server, tools) => {
208815
- const shouldSanitize = isGeminiRouted(ctx);
208816
- for (const tool2 of tools) {
208817
- server.addTool(shouldSanitize ? sanitizeToolForGemini(tool2) : tool2);
208818
- }
208819
- return server;
208820
- };
208821
-
208822
209317
  // mcp/git.ts
208823
209318
  function getPushDestination(branch, storedDest, cwd) {
208824
209319
  if (storedDest && storedDest.localBranch === branch) {
@@ -208944,7 +209439,8 @@ async function runPrepushHook(ctx, retryTool) {
208944
209439
  if (!ctx.prepushScript) return false;
208945
209440
  const prepushHook = await executeLifecycleHook({
208946
209441
  event: "prepush",
208947
- script: ctx.prepushScript
209442
+ script: ctx.prepushScript,
209443
+ shell: ctx.payload.shell
208948
209444
  });
208949
209445
  if (prepushHook.failure) {
208950
209446
  ctx.toolState.prepushFailureCount += 1;
@@ -209339,8 +209835,8 @@ function countAhead(head, base, cwd) {
209339
209835
  function spillGitOutput(params) {
209340
209836
  const tempDir = process.env.PULLFROG_TEMP_DIR;
209341
209837
  if (!tempDir) throw new Error("PULLFROG_TEMP_DIR not set");
209342
- const outputPath = join13(tempDir, `git-${params.command}-${randomUUID3().slice(0, 8)}.txt`);
209343
- writeFileSync9(outputPath, params.output);
209838
+ const outputPath = join14(tempDir, `git-${params.command}-${randomUUID4().slice(0, 8)}.txt`);
209839
+ writeFileSync10(outputPath, params.output);
209344
209840
  const previewByLines = params.output.split("\n").slice(0, OVERFLOW_PREVIEW_LINES).join("\n");
209345
209841
  const preview = previewByLines.length <= OVERFLOW_PREVIEW_MAX_CHARS ? previewByLines : `${previewByLines.slice(0, OVERFLOW_PREVIEW_MAX_CHARS)}\u2026`;
209346
209842
  log2.info(
@@ -210866,9 +211362,9 @@ async function reportReviewNodeId(ctx, params) {
210866
211362
  import { execFileSync as execFileSync7, execSync as execSync2 } from "node:child_process";
210867
211363
  import { mkdtempSync as mkdtempSync2, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
210868
211364
  import { tmpdir as tmpdir3 } from "node:os";
210869
- import { join as join14 } from "node:path";
211365
+ import { join as join15 } from "node:path";
210870
211366
  function createTempDirectory() {
210871
- const sharedTempDir = mkdtempSync2(join14(tmpdir3(), "pullfrog-"));
211367
+ const sharedTempDir = mkdtempSync2(join15(tmpdir3(), "pullfrog-"));
210872
211368
  process.env.PULLFROG_TEMP_DIR = sharedTempDir;
210873
211369
  log2.info(`\xBB created temp dir at ${sharedTempDir}`);
210874
211370
  return sharedTempDir;
@@ -210913,13 +211409,13 @@ function wipeRunnerLeakSurface() {
210913
211409
  return [];
210914
211410
  }
210915
211411
  };
210916
- const fileCommandsDir = join14(runnerTemp, "_runner_file_commands");
211412
+ const fileCommandsDir = join15(runnerTemp, "_runner_file_commands");
210917
211413
  for (const entry of listDir(fileCommandsDir)) {
210918
- tryUnlink(join14(fileCommandsDir, entry));
211414
+ tryUnlink(join15(fileCommandsDir, entry));
210919
211415
  }
210920
211416
  for (const entry of listDir(runnerTemp)) {
210921
211417
  if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
210922
- tryUnlink(join14(runnerTemp, entry));
211418
+ tryUnlink(join15(runnerTemp, entry));
210923
211419
  }
210924
211420
  }
210925
211421
  if (wiped.length > 0) {
@@ -211394,6 +211890,7 @@ async function checkoutPrBranch(pr, params) {
211394
211890
  const postCheckoutHook = await executeLifecycleHook({
211395
211891
  event: "post-checkout",
211396
211892
  script: params.postCheckoutScript,
211893
+ shell: params.shell,
211397
211894
  normalizeWorkingTreeAfter: true
211398
211895
  });
211399
211896
  return { hookWarning: postCheckoutHook.warning };
@@ -211475,11 +211972,11 @@ function CheckoutPrTool(ctx) {
211475
211972
  headSha: checkoutSha
211476
211973
  });
211477
211974
  if (incremental.status === "ok") {
211478
- incrementalDiffPath = join15(
211975
+ incrementalDiffPath = join16(
211479
211976
  tempDir,
211480
211977
  `pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
211481
211978
  );
211482
- writeFileSync10(incrementalDiffPath, incremental.diff);
211979
+ writeFileSync11(incrementalDiffPath, incremental.diff);
211483
211980
  log2.info(
211484
211981
  `\xBB incremental diff computed (${incremental.diff.length} bytes) \u2192 ${incrementalDiffPath}`
211485
211982
  );
@@ -211492,8 +211989,8 @@ function CheckoutPrTool(ctx) {
211492
211989
  const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
211493
211990
  log2.debug(`formatted diff preview (first 100 lines):
211494
211991
  ${diffPreview}`);
211495
- const diffPath = join15(tempDir, `pr-${pull_number}-${headShort}.diff`);
211496
- writeFileSync10(diffPath, formatResult.content);
211992
+ const diffPath = join16(tempDir, `pr-${pull_number}-${headShort}.diff`);
211993
+ writeFileSync11(diffPath, formatResult.content);
211497
211994
  log2.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
211498
211995
  primary.diffCoverage = createDiffCoverageState({
211499
211996
  diffPath,
@@ -211650,8 +212147,8 @@ ${dirty}`
211650
212147
  }
211651
212148
 
211652
212149
  // mcp/checkSuite.ts
211653
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync11 } from "node:fs";
211654
- import { join as join16 } from "node:path";
212150
+ import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync12 } from "node:fs";
212151
+ import { join as join17 } from "node:path";
211655
212152
  var GetCheckSuiteLogs = type({
211656
212153
  check_suite_id: type.number.describe("the id from check_suite.id")
211657
212154
  });
@@ -211747,7 +212244,7 @@ function GetCheckSuiteLogsTool(ctx) {
211747
212244
  if (!tempDir) {
211748
212245
  throw new Error("PULLFROG_TEMP_DIR not set");
211749
212246
  }
211750
- const logsDir = join16(tempDir, "ci-logs");
212247
+ const logsDir = join17(tempDir, "ci-logs");
211751
212248
  mkdirSync8(logsDir, { recursive: true });
211752
212249
  const jobResults = [];
211753
212250
  for (const run4 of failedRuns) {
@@ -211774,8 +212271,8 @@ function GetCheckSuiteLogsTool(ctx) {
211774
212271
  );
211775
212272
  }
211776
212273
  const logsText = await logsResult.text();
211777
- const logPath = join16(logsDir, `job-${job.id}.log`);
211778
- writeFileSync11(logPath, logsText);
212274
+ const logPath = join17(logsDir, `job-${job.id}.log`);
212275
+ writeFileSync12(logPath, logsText);
211779
212276
  const analysis = analyzeLog(logsText, 80);
211780
212277
  const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
211781
212278
  jobResults.push({
@@ -211823,8 +212320,8 @@ function GetCheckSuiteLogsTool(ctx) {
211823
212320
  }
211824
212321
 
211825
212322
  // mcp/commitInfo.ts
211826
- import { writeFileSync as writeFileSync12 } from "node:fs";
211827
- import { join as join17 } from "node:path";
212323
+ import { writeFileSync as writeFileSync13 } from "node:fs";
212324
+ import { join as join18 } from "node:path";
211828
212325
  var CommitInfo = type({
211829
212326
  sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
211830
212327
  });
@@ -211848,8 +212345,8 @@ function CommitInfoTool(ctx) {
211848
212345
  "PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
211849
212346
  );
211850
212347
  }
211851
- const diffFile = join17(tempDir, `commit-${sha.slice(0, 7)}.diff`);
211852
- writeFileSync12(diffFile, formatResult.content);
212348
+ const diffFile = join18(tempDir, `commit-${sha.slice(0, 7)}.diff`);
212349
+ writeFileSync13(diffFile, formatResult.content);
211853
212350
  log2.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
211854
212351
  return {
211855
212352
  sha: data.sha,
@@ -211986,13 +212483,13 @@ import { performance as performance9 } from "node:perf_hooks";
211986
212483
 
211987
212484
  // prep/installNodeDependencies.ts
211988
212485
  import { existsSync as existsSync10 } from "node:fs";
211989
- import { join as join19 } from "node:path";
212486
+ import { join as join20 } from "node:path";
211990
212487
 
211991
212488
  // utils/packageManager.ts
211992
212489
  var import_semver2 = __toESM(require_semver2(), 1);
211993
212490
  import { existsSync as existsSync9 } from "node:fs";
211994
212491
  import { mkdir, readFile as readFile3 } from "node:fs/promises";
211995
- import { delimiter as delimiter2, join as join18 } from "node:path";
212492
+ import { delimiter as delimiter2, join as join19 } from "node:path";
211996
212493
  var SUPPORTED_NAMES = ["npm", "pnpm", "yarn", "bun"];
211997
212494
  var COREPACK_MANAGED = ["pnpm", "yarn"];
211998
212495
  function isSupported(name) {
@@ -212030,7 +212527,7 @@ function parseDevEnginesField(field) {
212030
212527
  };
212031
212528
  }
212032
212529
  async function resolvePackageManagerSpec(cwd) {
212033
- const pkgPath = join18(cwd, "package.json");
212530
+ const pkgPath = join19(cwd, "package.json");
212034
212531
  if (!existsSync9(pkgPath)) return null;
212035
212532
  let pkg;
212036
212533
  try {
@@ -212089,7 +212586,7 @@ async function currentVersion(name) {
212089
212586
  return result.stdout.trim();
212090
212587
  }
212091
212588
  function packageManagerBinDir(tmpdir4) {
212092
- return join18(tmpdir4, "pm-bin");
212589
+ return join19(tmpdir4, "pm-bin");
212093
212590
  }
212094
212591
  async function ensurePackageManager(params) {
212095
212592
  const spec = params.spec;
@@ -212152,7 +212649,7 @@ async function installFallback(name, installSpec) {
212152
212649
  });
212153
212650
  if (result.exitCode !== 0) return result.stderr || `failed to install ${name}`;
212154
212651
  if (name === "deno") {
212155
- const denoPath = join18(process.env.HOME || "", ".deno", "bin");
212652
+ const denoPath = join19(process.env.HOME || "", ".deno", "bin");
212156
212653
  process.env.PATH = `${denoPath}:${process.env.PATH}`;
212157
212654
  }
212158
212655
  log2.info(`\xBB installed ${name}`);
@@ -212185,7 +212682,7 @@ async function isCommandAvailable2(command) {
212185
212682
  var installNodeDependencies = {
212186
212683
  name: "installNodeDependencies",
212187
212684
  shouldRun: () => {
212188
- const packageJsonPath = join19(process.cwd(), "package.json");
212685
+ const packageJsonPath = join20(process.cwd(), "package.json");
212189
212686
  return existsSync10(packageJsonPath);
212190
212687
  },
212191
212688
  run: async (options) => {
@@ -212281,12 +212778,12 @@ ${errorMessage}`]
212281
212778
 
212282
212779
  // prep/installPythonDependencies.ts
212283
212780
  import { existsSync as existsSync11, readFileSync as readFileSync7 } from "node:fs";
212284
- import { join as join20 } from "node:path";
212781
+ import { join as join21 } from "node:path";
212285
212782
  function declaresBuildSystem(path4) {
212286
212783
  return /^\s*\[\s*build-system\s*\]/m.test(readFileSync7(path4, "utf8"));
212287
212784
  }
212288
212785
  function configApplies(config4, cwd) {
212289
- const path4 = join20(cwd, config4.file);
212786
+ const path4 = join21(cwd, config4.file);
212290
212787
  return existsSync11(path4) && (config4.applies?.(path4) ?? true);
212291
212788
  }
212292
212789
  var PYTHON_CONFIGS = [
@@ -212610,411 +213107,6 @@ function AwaitDependencyInstallationTool(ctx) {
212610
213107
  // mcp/gh.ts
212611
213108
  import { mkdirSync as mkdirSync9 } from "node:fs";
212612
213109
  import { join as join22 } from "node:path";
212613
-
212614
- // mcp/shell.ts
212615
- import { spawn as spawn5, spawnSync as spawnSync7 } from "node:child_process";
212616
- import { randomUUID as randomUUID4 } from "node:crypto";
212617
- import { closeSync, openSync, writeFileSync as writeFileSync13 } from "node:fs";
212618
- import { userInfo as userInfo2 } from "node:os";
212619
- import { join as join21 } from "node:path";
212620
- import { setTimeout as sleep3 } from "node:timers/promises";
212621
- var ShellParams = type({
212622
- command: "string",
212623
- // advisory and unread — no code path consumes it. kept in the schema only
212624
- // because it nudges the model to state intent before running a command;
212625
- // REQUIRING it cost a full rejected turn every time one was omitted. #1140.
212626
- "description?": "string",
212627
- "timeout?": type.number.describe(
212628
- "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."
212629
- ),
212630
- "working_directory?": "string",
212631
- "background?": "boolean"
212632
- });
212633
- var detectedSandboxMethod;
212634
- var usernsProbeError;
212635
- function getSandboxMethod() {
212636
- return detectSandboxMethod();
212637
- }
212638
- function detectSandboxMethod() {
212639
- if (detectedSandboxMethod !== void 0) {
212640
- return detectedSandboxMethod;
212641
- }
212642
- if (process.env.CI !== "true") {
212643
- detectedSandboxMethod = "none";
212644
- log2.debug("sandbox disabled (CI !== true)");
212645
- return "none";
212646
- }
212647
- try {
212648
- const result = spawnSync7("unshare", ["--pid", "--fork", "--mount-proc", "true"], {
212649
- timeout: 5e3,
212650
- stdio: "ignore"
212651
- });
212652
- if (result.status === 0) {
212653
- detectedSandboxMethod = "unshare";
212654
- log2.debug("PID namespace isolation enabled (unprivileged unshare)");
212655
- return "unshare";
212656
- }
212657
- } catch {
212658
- }
212659
- try {
212660
- const result = spawnSync7("sudo", ["unshare", "--pid", "--fork", "--mount-proc", "true"], {
212661
- timeout: 5e3,
212662
- stdio: "ignore"
212663
- });
212664
- if (result.status === 0) {
212665
- detectedSandboxMethod = "sudo-unshare";
212666
- log2.debug("PID namespace isolation enabled (sudo unshare)");
212667
- return "sudo-unshare";
212668
- }
212669
- } catch {
212670
- }
212671
- try {
212672
- const result = spawnSync7(
212673
- "unshare",
212674
- [
212675
- "--user",
212676
- "--map-root-user",
212677
- "--pid",
212678
- "--fork",
212679
- "--mount",
212680
- "setpriv",
212681
- "--no-new-privs",
212682
- "--bounding-set",
212683
- "-all",
212684
- "--inh-caps",
212685
- "-all",
212686
- "--ambient-caps",
212687
- "-all",
212688
- "true"
212689
- ],
212690
- { timeout: 5e3, stdio: ["ignore", "ignore", "pipe"] }
212691
- );
212692
- if (result.status === 0) {
212693
- detectedSandboxMethod = "userns-unshare";
212694
- log2.debug("PID namespace isolation enabled (unprivileged userns unshare)");
212695
- return "userns-unshare";
212696
- }
212697
- usernsProbeError = result.stderr?.toString().trim() || result.error?.message;
212698
- } catch {
212699
- }
212700
- detectedSandboxMethod = "none";
212701
- log2.info("PID namespace isolation not available");
212702
- return "none";
212703
- }
212704
- var PROC_CLEANUP = "umount /proc 2>/dev/null; umount /proc 2>/dev/null; mount -t proc proc /proc 2>/dev/null;";
212705
- var SOCKET_CLEANUP = [
212706
- "/var/run/docker.sock",
212707
- "/run/docker.sock",
212708
- "/var/run/podman/podman.sock",
212709
- "/run/podman/podman.sock",
212710
- "/run/containerd/containerd.sock",
212711
- "/var/run/crio/crio.sock"
212712
- ].map((path4) => `mount --bind /dev/null ${path4} 2>/dev/null;`).join(" ");
212713
- function buildFsMounts(repoDir) {
212714
- const escaped = repoDir.replace(/'/g, "'\\''");
212715
- return [
212716
- `mkdir -p /var/lib/pullfrog 2>/dev/null;`,
212717
- `mount -t tmpfs tmpfs /var/lib/pullfrog 2>/dev/null;`,
212718
- `[ -n "$RUNNER_TEMP" ] && [ -d "$RUNNER_TEMP/_runner_file_commands" ] && mount -t tmpfs tmpfs "$RUNNER_TEMP/_runner_file_commands" 2>/dev/null;`,
212719
- `[ -e '${escaped}/.git' ] && mount --bind '${escaped}/.git' '${escaped}/.git' 2>/dev/null && mount -o remount,bind,ro '${escaped}/.git' 2>/dev/null;`
212720
- ].join(" ");
212721
- }
212722
- var _repoRoot;
212723
- function resolveRepoRoot() {
212724
- if (_repoRoot) return _repoRoot;
212725
- const fromEnv = process.env.GITHUB_WORKSPACE;
212726
- if (fromEnv) {
212727
- _repoRoot = fromEnv;
212728
- return _repoRoot;
212729
- }
212730
- try {
212731
- _repoRoot = spawnSync7("git", ["rev-parse", "--show-toplevel"], {
212732
- cwd: process.cwd(),
212733
- stdio: ["ignore", "pipe", "ignore"],
212734
- encoding: "utf-8"
212735
- }).stdout?.trim();
212736
- } catch {
212737
- }
212738
- if (!_repoRoot) _repoRoot = process.cwd();
212739
- return _repoRoot;
212740
- }
212741
- function spawnShell(params) {
212742
- const spawnOpts = { env: params.env, cwd: params.cwd, stdio: params.stdio, detached: true };
212743
- const sandboxMethod = detectSandboxMethod();
212744
- const ci = process.env.CI === "true";
212745
- if (ci && sandboxMethod === "none") {
212746
- throw new Error(
212747
- "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"
212748
- );
212749
- }
212750
- const repoRoot = resolveRepoRoot();
212751
- const fsMounts = buildFsMounts(repoRoot);
212752
- if (sandboxMethod === "unshare") {
212753
- return spawn5(
212754
- "unshare",
212755
- [
212756
- "--pid",
212757
- "--fork",
212758
- "--mount-proc",
212759
- "bash",
212760
- "-c",
212761
- `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} ${params.command}`
212762
- ],
212763
- spawnOpts
212764
- );
212765
- }
212766
- if (sandboxMethod === "userns-unshare") {
212767
- const escaped = params.command.replace(/'/g, "'\\''");
212768
- return spawn5(
212769
- "unshare",
212770
- [
212771
- "--user",
212772
- "--map-root-user",
212773
- "--pid",
212774
- "--fork",
212775
- "--mount",
212776
- "bash",
212777
- "-c",
212778
- `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec setpriv --no-new-privs --bounding-set -all --inh-caps -all --ambient-caps -all bash -c '${escaped}'`
212779
- ],
212780
- spawnOpts
212781
- );
212782
- }
212783
- if (sandboxMethod === "sudo-unshare") {
212784
- const envArgs = [];
212785
- for (const [k2, v] of Object.entries(params.env)) {
212786
- if (v !== void 0) {
212787
- envArgs.push(`${k2}=${v}`);
212788
- }
212789
- }
212790
- const username = userInfo2().username;
212791
- const pathRestore = 'export PATH="${SANDBOX_PATH:-$PATH}"; ';
212792
- const escaped = (pathRestore + params.command).replace(/'/g, "'\\''");
212793
- envArgs.push(`SANDBOX_PATH=${params.env.PATH ?? ""}`);
212794
- return spawn5(
212795
- "sudo",
212796
- [
212797
- "env",
212798
- ...envArgs,
212799
- "unshare",
212800
- "--pid",
212801
- "--fork",
212802
- "--mount-proc",
212803
- "bash",
212804
- "-c",
212805
- `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec su -p -s /bin/bash ${username} -c '${escaped}'`
212806
- ],
212807
- { ...spawnOpts, env: {} }
212808
- );
212809
- }
212810
- return spawn5("bash", ["-c", params.command], spawnOpts);
212811
- }
212812
- async function killProcessGroup(proc) {
212813
- if (!proc.pid) return;
212814
- try {
212815
- process.kill(-proc.pid, "SIGTERM");
212816
- await new Promise((r2) => setTimeout(r2, 200));
212817
- process.kill(-proc.pid, "SIGKILL");
212818
- } catch {
212819
- try {
212820
- proc.kill("SIGKILL");
212821
- } catch {
212822
- }
212823
- }
212824
- }
212825
- function getTempDir() {
212826
- const tempDir = process.env.PULLFROG_TEMP_DIR;
212827
- if (!tempDir) {
212828
- throw new Error("PULLFROG_TEMP_DIR not set");
212829
- }
212830
- return tempDir;
212831
- }
212832
- var MAX_OUTPUT_CHARS = 5e3;
212833
- async function runSandboxed(params) {
212834
- const proc = spawnShell({
212835
- command: params.command,
212836
- env: params.env,
212837
- cwd: params.cwd,
212838
- stdio: ["ignore", "pipe", "pipe"]
212839
- });
212840
- const stdout2 = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
212841
- const stderr = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
212842
- let timedOut = false, exited = false, spawnError = "";
212843
- proc.stdout?.on("data", (chunk) => {
212844
- stdout2.append(chunk.toString());
212845
- });
212846
- proc.stderr?.on("data", (chunk) => {
212847
- stderr.append(chunk.toString());
212848
- });
212849
- const timeoutId = setTimeout(async () => {
212850
- if (!exited) {
212851
- timedOut = true;
212852
- await killProcessGroup(proc);
212853
- }
212854
- }, params.timeout);
212855
- const exitCode = await new Promise((resolve3) => {
212856
- const done = (code) => {
212857
- exited = true;
212858
- clearTimeout(timeoutId);
212859
- resolve3(code);
212860
- };
212861
- proc.on("exit", done);
212862
- proc.on("error", (err) => {
212863
- spawnError = err.message;
212864
- done(null);
212865
- });
212866
- });
212867
- proc.stdout?.destroy();
212868
- proc.stderr?.destroy();
212869
- const outText = stdout2.toString();
212870
- const errText = stderr.toString();
212871
- let output2 = errText ? outText ? `${outText}
212872
- ${errText}` : errText : outText;
212873
- if (spawnError) output2 = output2 ? `${output2}
212874
- ${spawnError}` : spawnError;
212875
- if (timedOut)
212876
- output2 = output2 ? `${output2}
212877
- [timed out after ${params.timeout}ms]` : `[timed out after ${params.timeout}ms]`;
212878
- return {
212879
- output: output2.trim(),
212880
- exitCode: exitCode ?? (timedOut ? 124 : -1),
212881
- timedOut
212882
- };
212883
- }
212884
- function capOutput(output2) {
212885
- if (output2.length <= MAX_OUTPUT_CHARS) return output2;
212886
- const fullPath = join21(getTempDir(), `shell-${randomUUID4().slice(0, 8)}.log`);
212887
- writeFileSync13(fullPath, output2);
212888
- const elided = output2.length - MAX_OUTPUT_CHARS;
212889
- return `... [${elided} chars truncated; output saved to ${fullPath}] ...
212890
- ${output2.slice(-MAX_OUTPUT_CHARS)}`;
212891
- }
212892
- function isGitCommand(command) {
212893
- const trimmed = command.trim();
212894
- if (trimmed === "git" || trimmed.startsWith("git ")) return true;
212895
- if (trimmed.startsWith("sudo git")) return true;
212896
- return /[;&|]\s*(?:sudo\s+)?git(?:\s|$)/.test(trimmed);
212897
- }
212898
- function ShellTool(ctx) {
212899
- return tool({
212900
- name: "shell",
212901
- timeoutMs: 12e4,
212902
- description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
212903
-
212904
- Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
212905
-
212906
- Use this tool to:
212907
- - Run shell commands (ls, cat, grep, find, etc.)
212908
- - Execute build tools (npm, pnpm, cargo, make, etc.)
212909
- - Run tests and linters
212910
-
212911
- 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.
212912
-
212913
- Do NOT use this tool for git commands \u2014 use the dedicated git tools instead.`,
212914
- parameters: ShellParams,
212915
- execute: execute(async (params) => {
212916
- if (isGitCommand(params.command)) {
212917
- throw new Error(
212918
- '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'
212919
- );
212920
- }
212921
- const timeout = Math.min(params.timeout ?? 3e4, 12e4);
212922
- const cwd = params.working_directory ?? process.cwd();
212923
- const env2 = resolveEnv(ctx.payload.shell === "enabled" ? "inherit" : "restricted");
212924
- if (params.command.includes("agent-browser")) {
212925
- const daemonError = ensureBrowserDaemon(ctx.toolState);
212926
- if (daemonError) {
212927
- return {
212928
- output: `browser daemon unavailable: ${daemonError}`,
212929
- exit_code: 1,
212930
- timed_out: false
212931
- };
212932
- }
212933
- const binDir = ctx.toolState.browserDaemon?.binDir;
212934
- if (binDir) {
212935
- env2.PATH = `${binDir}:${env2.PATH ?? ""}`;
212936
- }
212937
- }
212938
- if (params.background) {
212939
- const tempDir = getTempDir();
212940
- const handle = `bg-${randomUUID4().slice(0, 8)}`;
212941
- const outputPath = join21(tempDir, `${handle}.log`);
212942
- const pidPath = join21(tempDir, `${handle}.pid`);
212943
- const logFd = openSync(outputPath, "a");
212944
- let proc;
212945
- try {
212946
- proc = spawnShell({
212947
- command: params.command,
212948
- env: env2,
212949
- cwd,
212950
- stdio: ["ignore", logFd, logFd]
212951
- });
212952
- } finally {
212953
- closeSync(logFd);
212954
- }
212955
- if (!proc.pid) {
212956
- throw new Error("failed to start background process");
212957
- }
212958
- proc.unref();
212959
- writeFileSync13(pidPath, `${proc.pid}
212960
- `);
212961
- ctx.toolState.backgroundProcesses.set(handle, { pid: proc.pid, outputPath, pidPath });
212962
- return {
212963
- handle,
212964
- outputPath,
212965
- pidPath,
212966
- message: `started background process ${handle} (pid ${proc.pid})`
212967
- };
212968
- }
212969
- const result = await runSandboxed({ command: params.command, env: env2, cwd, timeout });
212970
- if (result.exitCode !== 0) {
212971
- log2.info(`shell command failed with exit code ${result.exitCode}: ${params.command}`);
212972
- if (result.output) log2.info(`output: ${result.output}`);
212973
- }
212974
- return {
212975
- output: capOutput(result.output),
212976
- exit_code: result.exitCode,
212977
- timed_out: result.timedOut
212978
- };
212979
- })
212980
- });
212981
- }
212982
- var KillBackgroundParams = type({
212983
- handle: type.string.describe("The handle of the background process to kill (e.g., bg-a1b2c3d4)")
212984
- });
212985
- function KillBackgroundTool(ctx) {
212986
- return tool({
212987
- name: "kill_background",
212988
- mutates: true,
212989
- description: `Kill a background process by its handle. Use this to stop dev servers or other long-running processes started with shell({ background: true }).`,
212990
- parameters: KillBackgroundParams,
212991
- execute: execute(async (params) => {
212992
- const proc = ctx.toolState.backgroundProcesses.get(params.handle);
212993
- if (!proc) {
212994
- return {
212995
- success: false,
212996
- message: `no background process with handle ${params.handle}`
212997
- };
212998
- }
212999
- try {
213000
- process.kill(-proc.pid, "SIGTERM");
213001
- } catch {
213002
- }
213003
- await sleep3(200);
213004
- try {
213005
- process.kill(-proc.pid, "SIGKILL");
213006
- } catch {
213007
- }
213008
- ctx.toolState.backgroundProcesses.delete(params.handle);
213009
- return {
213010
- success: true,
213011
- message: `killed background process ${params.handle} (pid ${proc.pid})`
213012
- };
213013
- })
213014
- });
213015
- }
213016
-
213017
- // mcp/gh.ts
213018
213110
  var GH_TIMEOUT_MS = 12e4;
213019
213111
  var GhParams = type({
213020
213112
  args: type.string.array().describe(
@@ -214265,7 +214357,7 @@ function GetReviewCommentsTool(ctx) {
214265
214357
  threadCount: threadBlocks.length,
214266
214358
  commentsPath,
214267
214359
  toc: formatted.toc,
214268
- 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.`
214360
+ 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.`
214269
214361
  };
214270
214362
  })
214271
214363
  });
@@ -214388,8 +214480,7 @@ An existing plan comment was found for this issue. Update that comment with the
214388
214480
  };
214389
214481
  }
214390
214482
  var modeInstructionParent = {
214391
- IncrementalReview: "Review",
214392
- Fix: "Build"
214483
+ IncrementalReview: "Review"
214393
214484
  };
214394
214485
  function buildOrchestratorGuidance(ctx, mode, overrideGuidance) {
214395
214486
  const hardcoded = overrideGuidance ?? mode.prompt ?? "";
@@ -215601,18 +215692,13 @@ function hasEnvVar4(name) {
215601
215692
  const value2 = process.env[name];
215602
215693
  return typeof value2 === "string" && value2.length > 0;
215603
215694
  }
215604
- var HOST_OVERRIDES = {
215605
- ANTHROPIC_API_KEY: "ANTHROPIC_BASE_URL",
215606
- CLAUDE_CODE_OAUTH_TOKEN: "ANTHROPIC_BASE_URL"
215607
- };
215608
215695
  function envVarsFor(model) {
215609
215696
  return model.includes("/") ? getModelEnvVars(model) : [];
215610
215697
  }
215611
215698
  async function checkOne(params) {
215612
215699
  const value2 = process.env[params.envVar];
215613
215700
  if (!value2) return null;
215614
- const hostOverride = HOST_OVERRIDES[params.envVar];
215615
- if (hostOverride && hasEnvVar4(hostOverride)) return null;
215701
+ if (getProviderGatewayUrl(params.model)) return null;
215616
215702
  if (params.envVar === "CLAUDE_CODE_OAUTH_TOKEN") {
215617
215703
  const preflight = await preflightClaudeSubscription({
215618
215704
  token: value2,
@@ -218813,6 +218899,7 @@ async function main() {
218813
218899
  const setupHook = await executeLifecycleHook({
218814
218900
  event: "setup",
218815
218901
  script: runContext.repoSettings.setupScript,
218902
+ shell: payload.shell,
218816
218903
  normalizeWorkingTreeAfter: true
218817
218904
  });
218818
218905
  if (setupHook.warning) {
@@ -219688,7 +219775,7 @@ function resolveTarget2(repo) {
219688
219775
  return { owner: match3[1], repo: match3[2] };
219689
219776
  }
219690
219777
  function cliVersion() {
219691
- const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.74");
219778
+ const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.76");
219692
219779
  if (!parts) return "0.0.0";
219693
219780
  return `${Number(parts[1])}.${Number(parts[2])}.${Number(parts[3])}`;
219694
219781
  }
@@ -219855,7 +219942,7 @@ async function runCli8(input2) {
219855
219942
  }
219856
219943
 
219857
219944
  // cli.ts
219858
- var VERSION10 = "0.1.74";
219945
+ var VERSION10 = "0.1.76";
219859
219946
  var bin = basename2(process.argv[1] || "");
219860
219947
  var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
219861
219948
  var rawArgs = process.argv.slice(2);