pullfrog 0.1.73 → 0.1.75

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.73",
123003
+ version: "0.1.75",
123004
123004
  cliContractVersion: "1",
123005
123005
  type: "module",
123006
123006
  bin: {
@@ -194109,8 +194109,8 @@ function closeBrowserDaemon(toolState) {
194109
194109
 
194110
194110
  // mcp/checkout.ts
194111
194111
  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";
194112
+ import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync11 } from "node:fs";
194113
+ import { join as join16 } from "node:path";
194114
194114
 
194115
194115
  // node_modules/.pnpm/@ark+schema@0.56.0/node_modules/@ark/schema/out/shared/errors.js
194116
194116
  var ArkError = class _ArkError extends CastableBase {
@@ -202382,6 +202382,960 @@ async function $gitFetchWithDeepen(args2, options, label) {
202382
202382
  }
202383
202383
  }
202384
202384
 
202385
+ // utils/lifecycle.ts
202386
+ import { spawnSync as spawnSync8 } from "node:child_process";
202387
+
202388
+ // mcp/shell.ts
202389
+ import { spawn as spawn5, spawnSync as spawnSync7 } from "node:child_process";
202390
+ import { randomUUID as randomUUID3 } from "node:crypto";
202391
+ import { closeSync, openSync, writeFileSync as writeFileSync9 } from "node:fs";
202392
+ import { userInfo as userInfo2 } from "node:os";
202393
+ import { join as join11 } from "node:path";
202394
+ import { setTimeout as sleep2 } from "node:timers/promises";
202395
+
202396
+ // node_modules/.pnpm/@toon-format+toon@1.4.0/node_modules/@toon-format/toon/dist/index.mjs
202397
+ var LIST_ITEM_MARKER = "-";
202398
+ var LIST_ITEM_PREFIX = "- ";
202399
+ var COMMA = ",";
202400
+ var PIPE = "|";
202401
+ var DOT = ".";
202402
+ var NULL_LITERAL = "null";
202403
+ var TRUE_LITERAL = "true";
202404
+ var FALSE_LITERAL = "false";
202405
+ var BACKSLASH = "\\";
202406
+ var DOUBLE_QUOTE = '"';
202407
+ var TAB = " ";
202408
+ var DELIMITERS = {
202409
+ comma: COMMA,
202410
+ tab: TAB,
202411
+ pipe: PIPE
202412
+ };
202413
+ var DEFAULT_DELIMITER = DELIMITERS.comma;
202414
+ function escapeString(value2) {
202415
+ 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`);
202416
+ }
202417
+ function isBooleanOrNullLiteral(token) {
202418
+ return token === TRUE_LITERAL || token === FALSE_LITERAL || token === NULL_LITERAL;
202419
+ }
202420
+ function normalizeValue(value2) {
202421
+ if (value2 === null) return null;
202422
+ if (typeof value2 === "string" || typeof value2 === "boolean") return value2;
202423
+ if (typeof value2 === "number") {
202424
+ if (Object.is(value2, -0)) return 0;
202425
+ if (!Number.isFinite(value2)) return null;
202426
+ return value2;
202427
+ }
202428
+ if (typeof value2 === "bigint") {
202429
+ if (value2 >= Number.MIN_SAFE_INTEGER && value2 <= Number.MAX_SAFE_INTEGER) return Number(value2);
202430
+ return value2.toString();
202431
+ }
202432
+ if (value2 instanceof Date) return value2.toISOString();
202433
+ if (Array.isArray(value2)) return value2.map(normalizeValue);
202434
+ if (value2 instanceof Set) return Array.from(value2).map(normalizeValue);
202435
+ if (value2 instanceof Map) return Object.fromEntries(Array.from(value2, ([k2, v]) => [String(k2), normalizeValue(v)]));
202436
+ if (isPlainObject7(value2)) {
202437
+ const normalized = {};
202438
+ for (const key in value2) if (Object.prototype.hasOwnProperty.call(value2, key)) normalized[key] = normalizeValue(value2[key]);
202439
+ return normalized;
202440
+ }
202441
+ return null;
202442
+ }
202443
+ function isJsonPrimitive(value2) {
202444
+ return value2 === null || typeof value2 === "string" || typeof value2 === "number" || typeof value2 === "boolean";
202445
+ }
202446
+ function isJsonArray(value2) {
202447
+ return Array.isArray(value2);
202448
+ }
202449
+ function isJsonObject4(value2) {
202450
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
202451
+ }
202452
+ function isEmptyObject2(value2) {
202453
+ return Object.keys(value2).length === 0;
202454
+ }
202455
+ function isPlainObject7(value2) {
202456
+ if (value2 === null || typeof value2 !== "object") return false;
202457
+ const prototype = Object.getPrototypeOf(value2);
202458
+ return prototype === null || prototype === Object.prototype;
202459
+ }
202460
+ function isArrayOfPrimitives(value2) {
202461
+ return value2.length === 0 || value2.every((item) => isJsonPrimitive(item));
202462
+ }
202463
+ function isArrayOfArrays(value2) {
202464
+ return value2.length === 0 || value2.every((item) => isJsonArray(item));
202465
+ }
202466
+ function isArrayOfObjects(value2) {
202467
+ return value2.length === 0 || value2.every((item) => isJsonObject4(item));
202468
+ }
202469
+ function isValidUnquotedKey(key) {
202470
+ return /^[A-Z_][\w.]*$/i.test(key);
202471
+ }
202472
+ function isIdentifierSegment(key) {
202473
+ return /^[A-Z_]\w*$/i.test(key);
202474
+ }
202475
+ function isSafeUnquoted(value2, delimiter3 = DEFAULT_DELIMITER) {
202476
+ if (!value2) return false;
202477
+ if (value2 !== value2.trim()) return false;
202478
+ if (isBooleanOrNullLiteral(value2) || isNumericLike(value2)) return false;
202479
+ if (value2.includes(":")) return false;
202480
+ if (value2.includes('"') || value2.includes("\\")) return false;
202481
+ if (/[[\]{}]/.test(value2)) return false;
202482
+ if (/[\n\r\t]/.test(value2)) return false;
202483
+ if (value2.includes(delimiter3)) return false;
202484
+ if (value2.startsWith(LIST_ITEM_MARKER)) return false;
202485
+ return true;
202486
+ }
202487
+ function isNumericLike(value2) {
202488
+ return /^-?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i.test(value2) || /^0\d+$/.test(value2);
202489
+ }
202490
+ var QUOTED_KEY_MARKER = Symbol("quotedKey");
202491
+ function tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, flattenDepth) {
202492
+ if (options.keyFolding !== "safe") return;
202493
+ if (!isJsonObject4(value2)) return;
202494
+ const { segments, tail, leafValue } = collectSingleKeyChain(key, value2, flattenDepth ?? options.flattenDepth);
202495
+ if (segments.length < 2) return;
202496
+ if (!segments.every((seg) => isIdentifierSegment(seg))) return;
202497
+ const foldedKey = buildFoldedKey(segments);
202498
+ const absolutePath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
202499
+ if (siblings.includes(foldedKey)) return;
202500
+ if (rootLiteralKeys && rootLiteralKeys.has(absolutePath)) return;
202501
+ return {
202502
+ foldedKey,
202503
+ remainder: tail,
202504
+ leafValue,
202505
+ segmentCount: segments.length
202506
+ };
202507
+ }
202508
+ function collectSingleKeyChain(startKey, startValue, maxDepth) {
202509
+ const segments = [startKey];
202510
+ let currentValue = startValue;
202511
+ while (segments.length < maxDepth) {
202512
+ if (!isJsonObject4(currentValue)) break;
202513
+ const keys = Object.keys(currentValue);
202514
+ if (keys.length !== 1) break;
202515
+ const nextKey = keys[0];
202516
+ const nextValue = currentValue[nextKey];
202517
+ segments.push(nextKey);
202518
+ currentValue = nextValue;
202519
+ }
202520
+ if (!isJsonObject4(currentValue) || isEmptyObject2(currentValue)) return {
202521
+ segments,
202522
+ tail: void 0,
202523
+ leafValue: currentValue
202524
+ };
202525
+ return {
202526
+ segments,
202527
+ tail: currentValue,
202528
+ leafValue: currentValue
202529
+ };
202530
+ }
202531
+ function buildFoldedKey(segments) {
202532
+ return segments.join(DOT);
202533
+ }
202534
+ function encodePrimitive(value2, delimiter3) {
202535
+ if (value2 === null) return NULL_LITERAL;
202536
+ if (typeof value2 === "boolean") return String(value2);
202537
+ if (typeof value2 === "number") return String(value2);
202538
+ return encodeStringLiteral(value2, delimiter3);
202539
+ }
202540
+ function encodeStringLiteral(value2, delimiter3 = DEFAULT_DELIMITER) {
202541
+ if (isSafeUnquoted(value2, delimiter3)) return value2;
202542
+ return `${DOUBLE_QUOTE}${escapeString(value2)}${DOUBLE_QUOTE}`;
202543
+ }
202544
+ function encodeKey(key) {
202545
+ if (isValidUnquotedKey(key)) return key;
202546
+ return `${DOUBLE_QUOTE}${escapeString(key)}${DOUBLE_QUOTE}`;
202547
+ }
202548
+ function encodeAndJoinPrimitives(values, delimiter3 = DEFAULT_DELIMITER) {
202549
+ return values.map((v) => encodePrimitive(v, delimiter3)).join(delimiter3);
202550
+ }
202551
+ function formatHeader(length, options) {
202552
+ const key = options?.key;
202553
+ const fields = options?.fields;
202554
+ const delimiter3 = options?.delimiter ?? COMMA;
202555
+ let header = "";
202556
+ if (key) header += encodeKey(key);
202557
+ header += `[${length}${delimiter3 !== DEFAULT_DELIMITER ? delimiter3 : ""}]`;
202558
+ if (fields) {
202559
+ const quotedFields = fields.map((f) => encodeKey(f));
202560
+ header += `{${quotedFields.join(delimiter3)}}`;
202561
+ }
202562
+ header += ":";
202563
+ return header;
202564
+ }
202565
+ function* encodeJsonValue(value2, options, depth) {
202566
+ if (isJsonPrimitive(value2)) {
202567
+ const encodedPrimitive = encodePrimitive(value2, options.delimiter);
202568
+ if (encodedPrimitive !== "") yield encodedPrimitive;
202569
+ return;
202570
+ }
202571
+ if (isJsonArray(value2)) yield* encodeArrayLines(void 0, value2, depth, options);
202572
+ else if (isJsonObject4(value2)) yield* encodeObjectLines(value2, depth, options);
202573
+ }
202574
+ function* encodeObjectLines(value2, depth, options, rootLiteralKeys, pathPrefix, remainingDepth) {
202575
+ const keys = Object.keys(value2);
202576
+ if (depth === 0 && !rootLiteralKeys) rootLiteralKeys = new Set(keys.filter((k2) => k2.includes(".")));
202577
+ const effectiveFlattenDepth = remainingDepth ?? options.flattenDepth;
202578
+ for (const [key, val] of Object.entries(value2)) yield* encodeKeyValuePairLines(key, val, depth, options, keys, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
202579
+ }
202580
+ function* encodeKeyValuePairLines(key, value2, depth, options, siblings, rootLiteralKeys, pathPrefix, flattenDepth) {
202581
+ const currentPath = pathPrefix ? `${pathPrefix}${DOT}${key}` : key;
202582
+ const effectiveFlattenDepth = flattenDepth ?? options.flattenDepth;
202583
+ if (options.keyFolding === "safe" && siblings) {
202584
+ const foldResult = tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
202585
+ if (foldResult) {
202586
+ const { foldedKey, remainder, leafValue, segmentCount } = foldResult;
202587
+ const encodedFoldedKey = encodeKey(foldedKey);
202588
+ if (remainder === void 0) {
202589
+ if (isJsonPrimitive(leafValue)) {
202590
+ yield indentedLine(depth, `${encodedFoldedKey}: ${encodePrimitive(leafValue, options.delimiter)}`, options.indent);
202591
+ return;
202592
+ } else if (isJsonArray(leafValue)) {
202593
+ yield* encodeArrayLines(foldedKey, leafValue, depth, options);
202594
+ return;
202595
+ } else if (isJsonObject4(leafValue) && isEmptyObject2(leafValue)) {
202596
+ yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
202597
+ return;
202598
+ }
202599
+ }
202600
+ if (isJsonObject4(remainder)) {
202601
+ yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
202602
+ const remainingDepth = effectiveFlattenDepth - segmentCount;
202603
+ const foldedPath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
202604
+ yield* encodeObjectLines(remainder, depth + 1, options, rootLiteralKeys, foldedPath, remainingDepth);
202605
+ return;
202606
+ }
202607
+ }
202608
+ }
202609
+ const encodedKey = encodeKey(key);
202610
+ if (isJsonPrimitive(value2)) yield indentedLine(depth, `${encodedKey}: ${encodePrimitive(value2, options.delimiter)}`, options.indent);
202611
+ else if (isJsonArray(value2)) yield* encodeArrayLines(key, value2, depth, options);
202612
+ else if (isJsonObject4(value2)) {
202613
+ yield indentedLine(depth, `${encodedKey}:`, options.indent);
202614
+ if (!isEmptyObject2(value2)) yield* encodeObjectLines(value2, depth + 1, options, rootLiteralKeys, currentPath, effectiveFlattenDepth);
202615
+ }
202616
+ }
202617
+ function* encodeArrayLines(key, value2, depth, options) {
202618
+ if (value2.length === 0) {
202619
+ yield indentedLine(depth, formatHeader(0, {
202620
+ key,
202621
+ delimiter: options.delimiter
202622
+ }), options.indent);
202623
+ return;
202624
+ }
202625
+ if (isArrayOfPrimitives(value2)) {
202626
+ yield indentedLine(depth, encodeInlineArrayLine(value2, options.delimiter, key), options.indent);
202627
+ return;
202628
+ }
202629
+ if (isArrayOfArrays(value2)) {
202630
+ if (value2.every((arr) => isArrayOfPrimitives(arr))) {
202631
+ yield* encodeArrayOfArraysAsListItemsLines(key, value2, depth, options);
202632
+ return;
202633
+ }
202634
+ }
202635
+ if (isArrayOfObjects(value2)) {
202636
+ const header = extractTabularHeader(value2);
202637
+ if (header) yield* encodeArrayOfObjectsAsTabularLines(key, value2, header, depth, options);
202638
+ else yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
202639
+ return;
202640
+ }
202641
+ yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
202642
+ }
202643
+ function* encodeArrayOfArraysAsListItemsLines(prefix, values, depth, options) {
202644
+ yield indentedLine(depth, formatHeader(values.length, {
202645
+ key: prefix,
202646
+ delimiter: options.delimiter
202647
+ }), options.indent);
202648
+ for (const arr of values) if (isArrayOfPrimitives(arr)) {
202649
+ const arrayLine = encodeInlineArrayLine(arr, options.delimiter);
202650
+ yield indentedListItem(depth + 1, arrayLine, options.indent);
202651
+ }
202652
+ }
202653
+ function encodeInlineArrayLine(values, delimiter3, prefix) {
202654
+ const header = formatHeader(values.length, {
202655
+ key: prefix,
202656
+ delimiter: delimiter3
202657
+ });
202658
+ const joinedValue = encodeAndJoinPrimitives(values, delimiter3);
202659
+ if (values.length === 0) return header;
202660
+ return `${header} ${joinedValue}`;
202661
+ }
202662
+ function* encodeArrayOfObjectsAsTabularLines(prefix, rows, header, depth, options) {
202663
+ yield indentedLine(depth, formatHeader(rows.length, {
202664
+ key: prefix,
202665
+ fields: header,
202666
+ delimiter: options.delimiter
202667
+ }), options.indent);
202668
+ yield* writeTabularRowsLines(rows, header, depth + 1, options);
202669
+ }
202670
+ function extractTabularHeader(rows) {
202671
+ if (rows.length === 0) return;
202672
+ const firstRow = rows[0];
202673
+ const firstKeys = Object.keys(firstRow);
202674
+ if (firstKeys.length === 0) return;
202675
+ if (isTabularArray(rows, firstKeys)) return firstKeys;
202676
+ }
202677
+ function isTabularArray(rows, header) {
202678
+ for (const row of rows) {
202679
+ if (Object.keys(row).length !== header.length) return false;
202680
+ for (const key of header) {
202681
+ if (!(key in row)) return false;
202682
+ if (!isJsonPrimitive(row[key])) return false;
202683
+ }
202684
+ }
202685
+ return true;
202686
+ }
202687
+ function* writeTabularRowsLines(rows, header, depth, options) {
202688
+ for (const row of rows) yield indentedLine(depth, encodeAndJoinPrimitives(header.map((key) => row[key]), options.delimiter), options.indent);
202689
+ }
202690
+ function* encodeMixedArrayAsListItemsLines(prefix, items, depth, options) {
202691
+ yield indentedLine(depth, formatHeader(items.length, {
202692
+ key: prefix,
202693
+ delimiter: options.delimiter
202694
+ }), options.indent);
202695
+ for (const item of items) yield* encodeListItemValueLines(item, depth + 1, options);
202696
+ }
202697
+ function* encodeObjectAsListItemLines(obj, depth, options) {
202698
+ if (isEmptyObject2(obj)) {
202699
+ yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
202700
+ return;
202701
+ }
202702
+ const entries = Object.entries(obj);
202703
+ if (entries.length === 1) {
202704
+ const [key, value2] = entries[0];
202705
+ if (isJsonArray(value2) && isArrayOfObjects(value2)) {
202706
+ const header = extractTabularHeader(value2);
202707
+ if (header) {
202708
+ yield indentedListItem(depth, formatHeader(value2.length, {
202709
+ key,
202710
+ fields: header,
202711
+ delimiter: options.delimiter
202712
+ }), options.indent);
202713
+ yield* writeTabularRowsLines(value2, header, depth + 1, options);
202714
+ return;
202715
+ }
202716
+ }
202717
+ }
202718
+ yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
202719
+ yield* encodeObjectLines(obj, depth + 1, options);
202720
+ }
202721
+ function* encodeListItemValueLines(value2, depth, options) {
202722
+ if (isJsonPrimitive(value2)) yield indentedListItem(depth, encodePrimitive(value2, options.delimiter), options.indent);
202723
+ else if (isJsonArray(value2)) if (isArrayOfPrimitives(value2)) yield indentedListItem(depth, encodeInlineArrayLine(value2, options.delimiter), options.indent);
202724
+ else {
202725
+ yield indentedListItem(depth, formatHeader(value2.length, { delimiter: options.delimiter }), options.indent);
202726
+ for (const item of value2) yield* encodeListItemValueLines(item, depth + 1, options);
202727
+ }
202728
+ else if (isJsonObject4(value2)) yield* encodeObjectAsListItemLines(value2, depth, options);
202729
+ }
202730
+ function indentedLine(depth, content, indentSize) {
202731
+ return " ".repeat(indentSize * depth) + content;
202732
+ }
202733
+ function indentedListItem(depth, content, indentSize) {
202734
+ return indentedLine(depth, LIST_ITEM_PREFIX + content, indentSize);
202735
+ }
202736
+ function encode6(input2, options) {
202737
+ return Array.from(encodeLines(input2, options)).join("\n");
202738
+ }
202739
+ function encodeLines(input2, options) {
202740
+ return encodeJsonValue(normalizeValue(input2), resolveOptions(options), 0);
202741
+ }
202742
+ function resolveOptions(options) {
202743
+ return {
202744
+ indent: options?.indent ?? 2,
202745
+ delimiter: options?.delimiter ?? DEFAULT_DELIMITER,
202746
+ keyFolding: options?.keyFolding ?? "off",
202747
+ flattenDepth: options?.flattenDepth ?? Number.POSITIVE_INFINITY
202748
+ };
202749
+ }
202750
+
202751
+ // mcp/geminiSanitizer.ts
202752
+ function parseStringEnumBranch(item) {
202753
+ if (!item || typeof item !== "object") return null;
202754
+ const record4 = item;
202755
+ if (Array.isArray(record4.enum)) {
202756
+ const strings = record4.enum.filter((v) => typeof v === "string");
202757
+ return strings.length === record4.enum.length && strings.length > 0 ? { values: strings } : null;
202758
+ }
202759
+ if (typeof record4.const === "string") {
202760
+ return { values: [record4.const] };
202761
+ }
202762
+ return null;
202763
+ }
202764
+ function collapseStringUnion(branches) {
202765
+ const values = [];
202766
+ for (const item of branches) {
202767
+ const parsed2 = parseStringEnumBranch(item);
202768
+ if (!parsed2) return null;
202769
+ values.push(...parsed2.values);
202770
+ }
202771
+ if (values.length === 0) return null;
202772
+ return { type: "string", enum: [...new Set(values)] };
202773
+ }
202774
+ function sanitizeForGemini(schema2) {
202775
+ if (!schema2 || typeof schema2 !== "object") return schema2;
202776
+ if (Array.isArray(schema2)) return schema2.map(sanitizeForGemini);
202777
+ const source = schema2;
202778
+ if (Array.isArray(source.enum) && typeof source.type !== "string") {
202779
+ const allStrings = source.enum.every((v) => typeof v === "string");
202780
+ if (allStrings) {
202781
+ const result = { type: "string", enum: source.enum };
202782
+ if (typeof source.description === "string") result.description = source.description;
202783
+ return result;
202784
+ }
202785
+ }
202786
+ for (const unionKey of ["anyOf", "oneOf"]) {
202787
+ const branches = source[unionKey];
202788
+ if (Array.isArray(branches) && branches.length > 0) {
202789
+ const collapsed = collapseStringUnion(branches);
202790
+ if (collapsed) {
202791
+ const result = { ...collapsed };
202792
+ if (typeof source.description === "string") result.description = source.description;
202793
+ return result;
202794
+ }
202795
+ }
202796
+ }
202797
+ if (Array.isArray(source.anyOf) || Array.isArray(source.oneOf)) {
202798
+ const result = {};
202799
+ if (Array.isArray(source.anyOf)) result.anyOf = source.anyOf.map(sanitizeForGemini);
202800
+ if (Array.isArray(source.oneOf)) result.oneOf = source.oneOf.map(sanitizeForGemini);
202801
+ return result;
202802
+ }
202803
+ const sanitized = {};
202804
+ for (const [key, value2] of Object.entries(source)) {
202805
+ if (key === "$schema") continue;
202806
+ if (key === "$defs") {
202807
+ sanitized.definitions = sanitizeForGemini(value2);
202808
+ continue;
202809
+ }
202810
+ sanitized[key] = sanitizeForGemini(value2);
202811
+ }
202812
+ return sanitized;
202813
+ }
202814
+ function wrapJsonSchemaProducer(producer) {
202815
+ return new Proxy(producer, {
202816
+ get(target, prop, receiver) {
202817
+ const value2 = Reflect.get(target, prop, receiver);
202818
+ if ((prop === "input" || prop === "output") && typeof value2 === "function") {
202819
+ const fn2 = value2;
202820
+ return (...args2) => sanitizeForGemini(fn2.apply(target, args2));
202821
+ }
202822
+ return value2;
202823
+ }
202824
+ });
202825
+ }
202826
+ function wrapStandard(standard) {
202827
+ return new Proxy(standard, {
202828
+ get(target, prop, receiver) {
202829
+ if (prop === "jsonSchema") {
202830
+ const value2 = Reflect.get(target, prop, receiver);
202831
+ if (value2 && typeof value2 === "object") {
202832
+ return wrapJsonSchemaProducer(value2);
202833
+ }
202834
+ return value2;
202835
+ }
202836
+ return Reflect.get(target, prop, receiver);
202837
+ }
202838
+ });
202839
+ }
202840
+ function wrapSchemaForGemini(schema2) {
202841
+ return new Proxy(schema2, {
202842
+ get(target, prop, receiver) {
202843
+ if (prop === "~standard") {
202844
+ const value2 = Reflect.get(target, prop, receiver);
202845
+ if (value2 && typeof value2 === "object") {
202846
+ return wrapStandard(value2);
202847
+ }
202848
+ return value2;
202849
+ }
202850
+ if (prop === "toJsonSchema") {
202851
+ const method = Reflect.get(target, prop, receiver);
202852
+ if (typeof method === "function") {
202853
+ return () => sanitizeForGemini(method.call(target));
202854
+ }
202855
+ return method;
202856
+ }
202857
+ return Reflect.get(target, prop, receiver);
202858
+ }
202859
+ });
202860
+ }
202861
+ function sanitizeToolForGemini(tool2) {
202862
+ if (!tool2.parameters) return tool2;
202863
+ return { ...tool2, parameters: wrapSchemaForGemini(tool2.parameters) };
202864
+ }
202865
+ function isGeminiRouted(ctx) {
202866
+ const effective = ctx.payload.proxyModel ?? ctx.resolvedModel ?? ctx.payload.model;
202867
+ if (!effective) return true;
202868
+ const normalized = effective.toLowerCase();
202869
+ if (normalized.includes("gemini")) return true;
202870
+ if (!normalized.includes("/")) return true;
202871
+ return false;
202872
+ }
202873
+
202874
+ // mcp/shared.ts
202875
+ function getHttpStatus(err) {
202876
+ if (typeof err !== "object" || err === null || !("status" in err)) return void 0;
202877
+ return typeof err.status === "number" ? err.status : void 0;
202878
+ }
202879
+ var tool = (toolDef) => toolDef;
202880
+ var handleToolSuccess = (data) => {
202881
+ const text = typeof data === "string" ? data : encode6(data);
202882
+ return {
202883
+ content: [{ type: "text", text }]
202884
+ };
202885
+ };
202886
+ var handleToolError = (error109) => {
202887
+ const errorMessage = error109 instanceof Error ? error109.message : String(error109);
202888
+ return {
202889
+ content: [
202890
+ {
202891
+ type: "text",
202892
+ text: `Error: ${errorMessage}`
202893
+ }
202894
+ ],
202895
+ isError: true
202896
+ };
202897
+ };
202898
+ var execute = (fn2, toolName) => {
202899
+ const _fn = async (params) => {
202900
+ try {
202901
+ const result = await fn2(params);
202902
+ return handleToolSuccess(result);
202903
+ } catch (error109) {
202904
+ const errorMessage = error109 instanceof Error ? error109.message : String(error109);
202905
+ const prefix = toolName ? `[${toolName}]` : "tool";
202906
+ log2.info(`${prefix} error: ${errorMessage}`);
202907
+ log2.debug(`${prefix} params: ${formatJsonValue(params)}`);
202908
+ return handleToolError(error109);
202909
+ }
202910
+ };
202911
+ return _fn;
202912
+ };
202913
+ var addTools = (ctx, server, tools) => {
202914
+ const shouldSanitize = isGeminiRouted(ctx);
202915
+ for (const tool2 of tools) {
202916
+ server.addTool(shouldSanitize ? sanitizeToolForGemini(tool2) : tool2);
202917
+ }
202918
+ return server;
202919
+ };
202920
+
202921
+ // mcp/shell.ts
202922
+ var ShellParams = type({
202923
+ command: "string",
202924
+ // advisory and unread — no code path consumes it. kept in the schema only
202925
+ // because it nudges the model to state intent before running a command;
202926
+ // REQUIRING it cost a full rejected turn every time one was omitted. #1140.
202927
+ "description?": "string",
202928
+ "timeout?": type.number.describe(
202929
+ "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."
202930
+ ),
202931
+ "working_directory?": "string",
202932
+ "background?": "boolean"
202933
+ });
202934
+ var detectedSandboxMethod;
202935
+ var usernsProbeError;
202936
+ function getSandboxMethod() {
202937
+ return detectSandboxMethod();
202938
+ }
202939
+ function detectSandboxMethod() {
202940
+ if (detectedSandboxMethod !== void 0) {
202941
+ return detectedSandboxMethod;
202942
+ }
202943
+ if (process.env.CI !== "true") {
202944
+ detectedSandboxMethod = "none";
202945
+ log2.debug("sandbox disabled (CI !== true)");
202946
+ return "none";
202947
+ }
202948
+ try {
202949
+ const result = spawnSync7("unshare", ["--pid", "--fork", "--mount-proc", "true"], {
202950
+ timeout: 5e3,
202951
+ stdio: "ignore"
202952
+ });
202953
+ if (result.status === 0) {
202954
+ detectedSandboxMethod = "unshare";
202955
+ log2.debug("PID namespace isolation enabled (unprivileged unshare)");
202956
+ return "unshare";
202957
+ }
202958
+ } catch {
202959
+ }
202960
+ try {
202961
+ const result = spawnSync7("sudo", ["unshare", "--pid", "--fork", "--mount-proc", "true"], {
202962
+ timeout: 5e3,
202963
+ stdio: "ignore"
202964
+ });
202965
+ if (result.status === 0) {
202966
+ detectedSandboxMethod = "sudo-unshare";
202967
+ log2.debug("PID namespace isolation enabled (sudo unshare)");
202968
+ return "sudo-unshare";
202969
+ }
202970
+ } catch {
202971
+ }
202972
+ try {
202973
+ const result = spawnSync7(
202974
+ "unshare",
202975
+ [
202976
+ "--user",
202977
+ "--map-root-user",
202978
+ "--pid",
202979
+ "--fork",
202980
+ "--mount",
202981
+ "setpriv",
202982
+ "--no-new-privs",
202983
+ "--bounding-set",
202984
+ "-all",
202985
+ "--inh-caps",
202986
+ "-all",
202987
+ "--ambient-caps",
202988
+ "-all",
202989
+ "true"
202990
+ ],
202991
+ { timeout: 5e3, stdio: ["ignore", "ignore", "pipe"] }
202992
+ );
202993
+ if (result.status === 0) {
202994
+ detectedSandboxMethod = "userns-unshare";
202995
+ log2.debug("PID namespace isolation enabled (unprivileged userns unshare)");
202996
+ return "userns-unshare";
202997
+ }
202998
+ usernsProbeError = result.stderr?.toString().trim() || result.error?.message;
202999
+ } catch {
203000
+ }
203001
+ detectedSandboxMethod = "none";
203002
+ log2.info("PID namespace isolation not available");
203003
+ return "none";
203004
+ }
203005
+ var PROC_CLEANUP = "umount /proc 2>/dev/null; umount /proc 2>/dev/null; mount -t proc proc /proc 2>/dev/null;";
203006
+ var SOCKET_CLEANUP = [
203007
+ "/var/run/docker.sock",
203008
+ "/run/docker.sock",
203009
+ "/var/run/podman/podman.sock",
203010
+ "/run/podman/podman.sock",
203011
+ "/run/containerd/containerd.sock",
203012
+ "/var/run/crio/crio.sock"
203013
+ ].map((path4) => `mount --bind /dev/null ${path4} 2>/dev/null;`).join(" ");
203014
+ function buildFsMounts(repoDir) {
203015
+ const escaped = repoDir.replace(/'/g, "'\\''");
203016
+ return [
203017
+ `mkdir -p /var/lib/pullfrog 2>/dev/null;`,
203018
+ `mount -t tmpfs tmpfs /var/lib/pullfrog 2>/dev/null;`,
203019
+ `[ -n "$RUNNER_TEMP" ] && [ -d "$RUNNER_TEMP/_runner_file_commands" ] && mount -t tmpfs tmpfs "$RUNNER_TEMP/_runner_file_commands" 2>/dev/null;`,
203020
+ `[ -e '${escaped}/.git' ] && mount --bind '${escaped}/.git' '${escaped}/.git' 2>/dev/null && mount -o remount,bind,ro '${escaped}/.git' 2>/dev/null;`,
203021
+ // the corepack shim dir sits FIRST on PATH (utils/packageManager.ts) and
203022
+ // PATH survives filterEnv, so a writable pm-bin lets sandboxed code plant
203023
+ // a `git`/`pnpm` that a later UNSANDBOXED spawn resolves with the parent
203024
+ // env. read-only closes that for every sandboxed command at once.
203025
+ //
203026
+ // the path is interpolated HERE rather than read as `$PULLFROG_TEMP_DIR`
203027
+ // inside the sandbox: that name is not on the `filterEnv` safe list, so in
203028
+ // restricted mode it expands to empty and the mount silently no-ops in the
203029
+ // one tier it exists to protect. `$RUNNER_TEMP` above is safe only because
203030
+ // `RUNNER_` is an allowed prefix.
203031
+ shimMount()
203032
+ ].filter(Boolean).join(" ");
203033
+ }
203034
+ function shimMount() {
203035
+ const tempDir = process.env.PULLFROG_TEMP_DIR;
203036
+ if (!tempDir) return "";
203037
+ const dir = join11(tempDir, "pm-bin").replace(/'/g, "'\\''");
203038
+ return `[ -d '${dir}' ] && mount --bind '${dir}' '${dir}' 2>/dev/null && mount -o remount,bind,ro '${dir}' 2>/dev/null;`;
203039
+ }
203040
+ var _repoRoot;
203041
+ function resolveRepoRoot() {
203042
+ if (_repoRoot) return _repoRoot;
203043
+ const fromEnv = process.env.GITHUB_WORKSPACE;
203044
+ if (fromEnv) {
203045
+ _repoRoot = fromEnv;
203046
+ return _repoRoot;
203047
+ }
203048
+ try {
203049
+ _repoRoot = spawnSync7("git", ["rev-parse", "--show-toplevel"], {
203050
+ cwd: process.cwd(),
203051
+ stdio: ["ignore", "pipe", "ignore"],
203052
+ encoding: "utf-8"
203053
+ }).stdout?.trim();
203054
+ } catch {
203055
+ }
203056
+ if (!_repoRoot) _repoRoot = process.cwd();
203057
+ return _repoRoot;
203058
+ }
203059
+ function spawnShell(params) {
203060
+ const spawnOpts = { env: params.env, cwd: params.cwd, stdio: params.stdio, detached: true };
203061
+ const sandboxMethod = detectSandboxMethod();
203062
+ const ci = process.env.CI === "true";
203063
+ if (ci && sandboxMethod === "none") {
203064
+ throw new Error(
203065
+ "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"
203066
+ );
203067
+ }
203068
+ const repoRoot = resolveRepoRoot();
203069
+ const fsMounts = buildFsMounts(repoRoot);
203070
+ if (sandboxMethod === "unshare") {
203071
+ return spawn5(
203072
+ "unshare",
203073
+ [
203074
+ "--pid",
203075
+ "--fork",
203076
+ "--mount-proc",
203077
+ "bash",
203078
+ "-c",
203079
+ `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} ${params.command}`
203080
+ ],
203081
+ spawnOpts
203082
+ );
203083
+ }
203084
+ if (sandboxMethod === "userns-unshare") {
203085
+ const escaped = params.command.replace(/'/g, "'\\''");
203086
+ return spawn5(
203087
+ "unshare",
203088
+ [
203089
+ "--user",
203090
+ "--map-root-user",
203091
+ "--pid",
203092
+ "--fork",
203093
+ "--mount",
203094
+ "bash",
203095
+ "-c",
203096
+ `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec setpriv --no-new-privs --bounding-set -all --inh-caps -all --ambient-caps -all bash -c '${escaped}'`
203097
+ ],
203098
+ spawnOpts
203099
+ );
203100
+ }
203101
+ if (sandboxMethod === "sudo-unshare") {
203102
+ const envArgs = [];
203103
+ for (const [k2, v] of Object.entries(params.env)) {
203104
+ if (v !== void 0) {
203105
+ envArgs.push(`${k2}=${v}`);
203106
+ }
203107
+ }
203108
+ const username = userInfo2().username;
203109
+ const pathRestore = 'export PATH="${SANDBOX_PATH:-$PATH}"; ';
203110
+ const escaped = (pathRestore + params.command).replace(/'/g, "'\\''");
203111
+ envArgs.push(`SANDBOX_PATH=${params.env.PATH ?? ""}`);
203112
+ return spawn5(
203113
+ "sudo",
203114
+ [
203115
+ "env",
203116
+ ...envArgs,
203117
+ "unshare",
203118
+ "--pid",
203119
+ "--fork",
203120
+ "--mount-proc",
203121
+ "bash",
203122
+ "-c",
203123
+ `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec su -p -s /bin/bash ${username} -c '${escaped}'`
203124
+ ],
203125
+ { ...spawnOpts, env: {} }
203126
+ );
203127
+ }
203128
+ return spawn5("bash", ["-c", params.command], spawnOpts);
203129
+ }
203130
+ async function killProcessGroup(proc) {
203131
+ if (!proc.pid) return;
203132
+ try {
203133
+ process.kill(-proc.pid, "SIGTERM");
203134
+ await new Promise((r2) => setTimeout(r2, 200));
203135
+ process.kill(-proc.pid, "SIGKILL");
203136
+ } catch {
203137
+ try {
203138
+ proc.kill("SIGKILL");
203139
+ } catch {
203140
+ }
203141
+ }
203142
+ }
203143
+ function getTempDir() {
203144
+ const tempDir = process.env.PULLFROG_TEMP_DIR;
203145
+ if (!tempDir) {
203146
+ throw new Error("PULLFROG_TEMP_DIR not set");
203147
+ }
203148
+ return tempDir;
203149
+ }
203150
+ var MAX_OUTPUT_CHARS = 5e3;
203151
+ async function runSandboxed(params) {
203152
+ const proc = spawnShell({
203153
+ command: params.command,
203154
+ env: params.env,
203155
+ cwd: params.cwd,
203156
+ stdio: ["ignore", "pipe", "pipe"]
203157
+ });
203158
+ const stdout2 = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
203159
+ const stderr = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
203160
+ let timedOut = false, exited = false, spawnError = "";
203161
+ proc.stdout?.on("data", (chunk) => {
203162
+ const text = chunk.toString();
203163
+ stdout2.append(text);
203164
+ params.onStdout?.(text);
203165
+ });
203166
+ proc.stderr?.on("data", (chunk) => {
203167
+ const text = chunk.toString();
203168
+ stderr.append(text);
203169
+ params.onStderr?.(text);
203170
+ });
203171
+ const timeoutId = setTimeout(async () => {
203172
+ if (!exited) {
203173
+ timedOut = true;
203174
+ await killProcessGroup(proc);
203175
+ }
203176
+ }, params.timeout);
203177
+ const exitCode = await new Promise((resolve3) => {
203178
+ const done = (code) => {
203179
+ exited = true;
203180
+ clearTimeout(timeoutId);
203181
+ resolve3(code);
203182
+ };
203183
+ proc.on("exit", done);
203184
+ proc.on("error", (err) => {
203185
+ spawnError = err.message;
203186
+ done(null);
203187
+ });
203188
+ });
203189
+ proc.stdout?.destroy();
203190
+ proc.stderr?.destroy();
203191
+ const outText = stdout2.toString();
203192
+ const errText = stderr.toString();
203193
+ let output2 = errText ? outText ? `${outText}
203194
+ ${errText}` : errText : outText;
203195
+ if (spawnError) output2 = output2 ? `${output2}
203196
+ ${spawnError}` : spawnError;
203197
+ if (timedOut)
203198
+ output2 = output2 ? `${output2}
203199
+ [timed out after ${params.timeout}ms]` : `[timed out after ${params.timeout}ms]`;
203200
+ return {
203201
+ output: output2.trim(),
203202
+ exitCode: exitCode ?? (timedOut ? 124 : -1),
203203
+ timedOut
203204
+ };
203205
+ }
203206
+ function capOutput(output2) {
203207
+ if (output2.length <= MAX_OUTPUT_CHARS) return output2;
203208
+ const fullPath = join11(getTempDir(), `shell-${randomUUID3().slice(0, 8)}.log`);
203209
+ writeFileSync9(fullPath, output2);
203210
+ const elided = output2.length - MAX_OUTPUT_CHARS;
203211
+ return `... [${elided} chars truncated; output saved to ${fullPath}] ...
203212
+ ${output2.slice(-MAX_OUTPUT_CHARS)}`;
203213
+ }
203214
+ function isGitCommand(command) {
203215
+ const trimmed = command.trim();
203216
+ if (trimmed === "git" || trimmed.startsWith("git ")) return true;
203217
+ if (trimmed.startsWith("sudo git")) return true;
203218
+ return /[;&|]\s*(?:sudo\s+)?git(?:\s|$)/.test(trimmed);
203219
+ }
203220
+ function ShellTool(ctx) {
203221
+ return tool({
203222
+ name: "shell",
203223
+ timeoutMs: 12e4,
203224
+ description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
203225
+
203226
+ Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
203227
+
203228
+ Use this tool to:
203229
+ - Run shell commands (ls, cat, grep, find, etc.)
203230
+ - Execute build tools (npm, pnpm, cargo, make, etc.)
203231
+ - Run tests and linters
203232
+
203233
+ 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.
203234
+
203235
+ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead.`,
203236
+ parameters: ShellParams,
203237
+ execute: execute(async (params) => {
203238
+ if (isGitCommand(params.command)) {
203239
+ throw new Error(
203240
+ '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'
203241
+ );
203242
+ }
203243
+ const timeout = Math.min(params.timeout ?? 3e4, 12e4);
203244
+ const cwd = params.working_directory ?? process.cwd();
203245
+ const env2 = resolveEnv(ctx.payload.shell === "enabled" ? "inherit" : "restricted");
203246
+ if (params.command.includes("agent-browser")) {
203247
+ const daemonError = ensureBrowserDaemon(ctx.toolState);
203248
+ if (daemonError) {
203249
+ return {
203250
+ output: `browser daemon unavailable: ${daemonError}`,
203251
+ exit_code: 1,
203252
+ timed_out: false
203253
+ };
203254
+ }
203255
+ const binDir = ctx.toolState.browserDaemon?.binDir;
203256
+ if (binDir) {
203257
+ env2.PATH = `${binDir}:${env2.PATH ?? ""}`;
203258
+ }
203259
+ }
203260
+ if (params.background) {
203261
+ const tempDir = getTempDir();
203262
+ const handle = `bg-${randomUUID3().slice(0, 8)}`;
203263
+ const outputPath = join11(tempDir, `${handle}.log`);
203264
+ const pidPath = join11(tempDir, `${handle}.pid`);
203265
+ const logFd = openSync(outputPath, "a");
203266
+ let proc;
203267
+ try {
203268
+ proc = spawnShell({
203269
+ command: params.command,
203270
+ env: env2,
203271
+ cwd,
203272
+ stdio: ["ignore", logFd, logFd]
203273
+ });
203274
+ } finally {
203275
+ closeSync(logFd);
203276
+ }
203277
+ if (!proc.pid) {
203278
+ throw new Error("failed to start background process");
203279
+ }
203280
+ proc.unref();
203281
+ writeFileSync9(pidPath, `${proc.pid}
203282
+ `);
203283
+ ctx.toolState.backgroundProcesses.set(handle, { pid: proc.pid, outputPath, pidPath });
203284
+ return {
203285
+ handle,
203286
+ outputPath,
203287
+ pidPath,
203288
+ message: `started background process ${handle} (pid ${proc.pid})`
203289
+ };
203290
+ }
203291
+ const result = await runSandboxed({ command: params.command, env: env2, cwd, timeout });
203292
+ if (result.exitCode !== 0) {
203293
+ log2.info(`shell command failed with exit code ${result.exitCode}: ${params.command}`);
203294
+ if (result.output) log2.info(`output: ${result.output}`);
203295
+ }
203296
+ return {
203297
+ output: capOutput(result.output),
203298
+ exit_code: result.exitCode,
203299
+ timed_out: result.timedOut
203300
+ };
203301
+ })
203302
+ });
203303
+ }
203304
+ var KillBackgroundParams = type({
203305
+ handle: type.string.describe("The handle of the background process to kill (e.g., bg-a1b2c3d4)")
203306
+ });
203307
+ function KillBackgroundTool(ctx) {
203308
+ return tool({
203309
+ name: "kill_background",
203310
+ mutates: true,
203311
+ description: `Kill a background process by its handle. Use this to stop dev servers or other long-running processes started with shell({ background: true }).`,
203312
+ parameters: KillBackgroundParams,
203313
+ execute: execute(async (params) => {
203314
+ const proc = ctx.toolState.backgroundProcesses.get(params.handle);
203315
+ if (!proc) {
203316
+ return {
203317
+ success: false,
203318
+ message: `no background process with handle ${params.handle}`
203319
+ };
203320
+ }
203321
+ try {
203322
+ process.kill(-proc.pid, "SIGTERM");
203323
+ } catch {
203324
+ }
203325
+ await sleep2(200);
203326
+ try {
203327
+ process.kill(-proc.pid, "SIGKILL");
203328
+ } catch {
203329
+ }
203330
+ ctx.toolState.backgroundProcesses.delete(params.handle);
203331
+ return {
203332
+ success: true,
203333
+ message: `killed background process ${params.handle} (pid ${proc.pid})`
203334
+ };
203335
+ })
203336
+ });
203337
+ }
203338
+
202385
203339
  // utils/lifecycle.ts
202386
203340
  function describeSetupFailure(failure2) {
202387
203341
  if (!failure2) return "";
@@ -202403,44 +203357,47 @@ ${failure2.output || "(empty)"}`;
202403
203357
  async function executeLifecycleHook(params) {
202404
203358
  if (!params.script) return {};
202405
203359
  log2.info(`\xBB executing ${params.event} lifecycle hook...`);
203360
+ resolveTrustedGit();
202406
203361
  const preHookTrackedCount = params.normalizeWorkingTreeAfter ? (await runGitLines(["diff", "--name-only", "HEAD"])).length : 0;
202407
203362
  let result;
202408
203363
  try {
203364
+ let sandboxed;
202409
203365
  try {
202410
- const spawnResult = await spawn3({
202411
- cmd: "bash",
202412
- args: ["-c", params.script],
202413
- env: process.env,
203366
+ sandboxed = await runSandboxed({
203367
+ command: params.script,
203368
+ env: resolveEnv(params.shell === "enabled" ? "inherit" : "restricted"),
203369
+ cwd: process.cwd(),
202414
203370
  timeout: LIFECYCLE_HOOK_TIMEOUT_MS,
202415
- activityTimeout: 0,
202416
203371
  onStdout: (chunk) => process.stdout.write(chunk),
202417
203372
  onStderr: (chunk) => process.stderr.write(chunk)
202418
203373
  });
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
203374
  } 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
- }
203375
+ const msg = err instanceof Error ? err.message : String(err);
203376
+ return {
203377
+ failure: { kind: "spawn", spawnError: msg },
203378
+ warning: `lifecycle hook '${params.event}' could not be sandboxed, so it did not run: ${msg}`
203379
+ };
203380
+ }
203381
+ if (sandboxed.timedOut) {
203382
+ const minutes = Math.round(LIFECYCLE_HOOK_TIMEOUT_MS / 6e4);
203383
+ result = {
203384
+ failure: { kind: "timeout" },
203385
+ 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).`
203386
+ };
203387
+ } else if (sandboxed.exitCode === -1) {
203388
+ result = {
203389
+ failure: { kind: "spawn", spawnError: sandboxed.output },
203390
+ warning: `lifecycle hook '${params.event}' failed to spawn: ${sandboxed.output}. this is likely a transient failure \u2014 retry the operation.`
203391
+ };
203392
+ } else if (sandboxed.exitCode !== 0) {
203393
+ const output2 = capOutput(sandboxed.output);
203394
+ result = {
203395
+ failure: { kind: "exit", output: output2, exitCode: sandboxed.exitCode },
203396
+ 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.`
203397
+ };
203398
+ } else {
203399
+ log2.info(`\xBB ${params.event} lifecycle hook completed successfully`);
203400
+ result = {};
202444
203401
  }
202445
203402
  } finally {
202446
203403
  if (params.normalizeWorkingTreeAfter) {
@@ -202461,8 +203418,20 @@ async function normalizeWorkingTreeAfterHook(params) {
202461
203418
  await runGit(["restore", "--staged", "--worktree", "."]);
202462
203419
  log2.info(`\xBB discarded ${trackedCount} tracked changes from ${params.event} hook`);
202463
203420
  }
203421
+ var trustedGit;
203422
+ function resolveTrustedGit() {
203423
+ if (trustedGit) return trustedGit;
203424
+ const resolved = spawnSync8("sh", ["-c", "command -v git"], { encoding: "utf-8" });
203425
+ trustedGit = resolved.stdout?.trim() || "git";
203426
+ return trustedGit;
203427
+ }
202464
203428
  async function runGit(args2) {
202465
- const result = await spawn3({ cmd: "git", args: args2, env: process.env, activityTimeout: 0 });
203429
+ const result = await spawn3({
203430
+ cmd: resolveTrustedGit(),
203431
+ args: args2,
203432
+ env: filterEnvForUntrustedCode(),
203433
+ activityTimeout: 0
203434
+ });
202466
203435
  if (result.exitCode !== 0) {
202467
203436
  throw new Error(
202468
203437
  `git ${args2.join(" ")} failed (exit ${result.exitCode}): ${result.stderr.trim() || "(no stderr)"}`
@@ -202582,7 +203551,7 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
202582
203551
 
202583
203552
  // yes/index.ts
202584
203553
  import { performance as performance8 } from "node:perf_hooks";
202585
- import { setTimeout as sleep2 } from "node:timers/promises";
203554
+ import { setTimeout as sleep3 } from "node:timers/promises";
202586
203555
 
202587
203556
  // node_modules/.pnpm/lru-cache@11.5.2/node_modules/lru-cache/dist/esm/node/index.min.js
202588
203557
  import { tracingChannel as G, channel as P } from "node:diagnostics_channel";
@@ -203376,7 +204345,7 @@ function _op(fn2, options) {
203376
204345
  );
203377
204346
  log3.info({ key, error: error109, attempt });
203378
204347
  }
203379
- await sleep2(delay2);
204348
+ await sleep3(delay2);
203380
204349
  }
203381
204350
  }
203382
204351
  }
@@ -203444,14 +204413,14 @@ function _op(fn2, options) {
203444
204413
  }
203445
204414
 
203446
204415
  // 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";
204416
+ import { randomUUID as randomUUID4 } from "node:crypto";
204417
+ import { writeFileSync as writeFileSync10 } from "node:fs";
204418
+ import { join as join14 } from "node:path";
203450
204419
 
203451
204420
  // utils/apiCommit.ts
203452
204421
  import { execFileSync as execFileSync6 } from "node:child_process";
203453
204422
  import { lstat, readlink } from "node:fs/promises";
203454
- import { join as join11 } from "node:path";
204423
+ import { join as join12 } from "node:path";
203455
204424
  var GITHUB_API = "https://api.github.com";
203456
204425
  var MAX_BLOB_BYTES = 30 * 1024 * 1024;
203457
204426
  var BLOB_UPLOAD_CONCURRENCY = 8;
@@ -203516,7 +204485,7 @@ async function assertApiCommittable(files) {
203516
204485
  const root = getRepoRoot();
203517
204486
  const attrs = $(
203518
204487
  "git",
203519
- ["check-attr", "filter", "-z", "--", ...present.map((p) => join11(root, p))],
204488
+ ["check-attr", "filter", "-z", "--", ...present.map((p) => join12(root, p))],
203520
204489
  { log: false }
203521
204490
  );
203522
204491
  const parts = attrs.split("\0");
@@ -203528,7 +204497,7 @@ async function assertApiCommittable(files) {
203528
204497
  }
203529
204498
  }
203530
204499
  for (const path4 of present) {
203531
- const stat = await lstat(join11(root, path4));
204500
+ const stat = await lstat(join12(root, path4));
203532
204501
  if (stat.isDirectory()) {
203533
204502
  throw new Error(
203534
204503
  `'${path4}' is a directory (nested repository or submodule?) \u2014 signed commits only support files and symlinks.`
@@ -203537,7 +204506,7 @@ async function assertApiCommittable(files) {
203537
204506
  }
203538
204507
  }
203539
204508
  async function createBlobEntry(params) {
203540
- const absPath = join11(params.repoRoot, params.path);
204509
+ const absPath = join12(params.repoRoot, params.path);
203541
204510
  const stat = await lstat(absPath);
203542
204511
  if (stat.size > MAX_BLOB_BYTES) {
203543
204512
  throw new Error(
@@ -203717,7 +204686,7 @@ this usually means your local branch has commits that were never pushed \u2014 s
203717
204686
  var core5 = __toESM(require_core(), 1);
203718
204687
  import { createSign } from "node:crypto";
203719
204688
  import { rename, writeFile } from "node:fs/promises";
203720
- import { dirname as dirname3, join as join12 } from "node:path";
204689
+ import { dirname as dirname3, join as join13 } from "node:path";
203721
204690
 
203722
204691
  // node_modules/.pnpm/@octokit+plugin-throttling@11.0.5_@octokit+core@7.0.8/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
203723
204692
  var import_light = __toESM(require_light(), 1);
@@ -204089,7 +205058,7 @@ function lowercaseKeys(object6) {
204089
205058
  return newObj;
204090
205059
  }, {});
204091
205060
  }
204092
- function isPlainObject7(value2) {
205061
+ function isPlainObject8(value2) {
204093
205062
  if (typeof value2 !== "object" || value2 === null) return false;
204094
205063
  if (Object.prototype.toString.call(value2) !== "[object Object]") return false;
204095
205064
  const proto = Object.getPrototypeOf(value2);
@@ -204100,7 +205069,7 @@ function isPlainObject7(value2) {
204100
205069
  function mergeDeep(defaults2, options) {
204101
205070
  const result = Object.assign({}, defaults2);
204102
205071
  Object.keys(options).forEach((key) => {
204103
- if (isPlainObject7(options[key])) {
205072
+ if (isPlainObject8(options[key])) {
204104
205073
  if (!(key in defaults2)) Object.assign(result, { [key]: options[key] });
204105
205074
  else result[key] = mergeDeep(defaults2[key], options[key]);
204106
205075
  } else {
@@ -204387,7 +205356,7 @@ var NullObject = /* @__PURE__ */ (() => {
204387
205356
  return C;
204388
205357
  })();
204389
205358
  function parse7(header, options) {
204390
- const stopChar = options?.comma === true ? COMMA : 65536;
205359
+ const stopChar = options?.comma === true ? COMMA2 : 65536;
204391
205360
  const len = header.length;
204392
205361
  let index = skipOWS(header, options?.start ?? 0, len);
204393
205362
  const valueStart = index;
@@ -204405,7 +205374,7 @@ var SEMI = 59;
204405
205374
  var EQ = 61;
204406
205375
  var DQUOTE = 34;
204407
205376
  var BSLASH = 92;
204408
- var COMMA = 44;
205377
+ var COMMA2 = 44;
204409
205378
  function parseParameters(header, type2, index, len, stopChar) {
204410
205379
  const parameters = new NullObject();
204411
205380
  parameter: while (index < len) {
@@ -204864,7 +205833,7 @@ var defaults_default = {
204864
205833
  "user-agent": `octokit-request.js/${VERSION3} ${getUserAgent2()}`
204865
205834
  }
204866
205835
  };
204867
- function isPlainObject8(value2) {
205836
+ function isPlainObject9(value2) {
204868
205837
  if (typeof value2 !== "object" || value2 === null) return false;
204869
205838
  if (Object.prototype.toString.call(value2) !== "[object Object]") return false;
204870
205839
  const proto = Object.getPrototypeOf(value2);
@@ -204882,7 +205851,7 @@ async function fetchWrapper(requestOptions) {
204882
205851
  }
204883
205852
  const log4 = requestOptions.request?.log || console;
204884
205853
  const parseSuccessResponseBody = requestOptions.request?.parseSuccessResponseBody !== false;
204885
- const body = isPlainObject8(requestOptions.body) || Array.isArray(requestOptions.body) ? JSONStringify(requestOptions.body) : requestOptions.body;
205854
+ const body = isPlainObject9(requestOptions.body) || Array.isArray(requestOptions.body) ? JSONStringify(requestOptions.body) : requestOptions.body;
204886
205855
  const requestHeaders = Object.fromEntries(
204887
205856
  Object.entries(requestOptions.headers).map(([name, value2]) => [
204888
205857
  name,
@@ -208187,7 +209156,7 @@ function getGitHubUsageSummary() {
208187
209156
  }
208188
209157
  async function writeGitHubUsageSummaryToFile(path4) {
208189
209158
  const summary2 = getGitHubUsageSummary();
208190
- const tmpPath = join12(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
209159
+ const tmpPath = join13(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
208191
209160
  await writeFile(tmpPath, JSON.stringify(summary2));
208192
209161
  await rename(tmpPath, path4);
208193
209162
  }
@@ -208294,531 +209263,6 @@ function resolveRepoCtx(ctx, repo) {
208294
209263
  };
208295
209264
  }
208296
209265
 
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
209266
  // mcp/git.ts
208823
209267
  function getPushDestination(branch, storedDest, cwd) {
208824
209268
  if (storedDest && storedDest.localBranch === branch) {
@@ -208944,7 +209388,8 @@ async function runPrepushHook(ctx, retryTool) {
208944
209388
  if (!ctx.prepushScript) return false;
208945
209389
  const prepushHook = await executeLifecycleHook({
208946
209390
  event: "prepush",
208947
- script: ctx.prepushScript
209391
+ script: ctx.prepushScript,
209392
+ shell: ctx.payload.shell
208948
209393
  });
208949
209394
  if (prepushHook.failure) {
208950
209395
  ctx.toolState.prepushFailureCount += 1;
@@ -209339,8 +209784,8 @@ function countAhead(head, base, cwd) {
209339
209784
  function spillGitOutput(params) {
209340
209785
  const tempDir = process.env.PULLFROG_TEMP_DIR;
209341
209786
  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);
209787
+ const outputPath = join14(tempDir, `git-${params.command}-${randomUUID4().slice(0, 8)}.txt`);
209788
+ writeFileSync10(outputPath, params.output);
209344
209789
  const previewByLines = params.output.split("\n").slice(0, OVERFLOW_PREVIEW_LINES).join("\n");
209345
209790
  const preview = previewByLines.length <= OVERFLOW_PREVIEW_MAX_CHARS ? previewByLines : `${previewByLines.slice(0, OVERFLOW_PREVIEW_MAX_CHARS)}\u2026`;
209346
209791
  log2.info(
@@ -210866,9 +211311,9 @@ async function reportReviewNodeId(ctx, params) {
210866
211311
  import { execFileSync as execFileSync7, execSync as execSync2 } from "node:child_process";
210867
211312
  import { mkdtempSync as mkdtempSync2, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
210868
211313
  import { tmpdir as tmpdir3 } from "node:os";
210869
- import { join as join14 } from "node:path";
211314
+ import { join as join15 } from "node:path";
210870
211315
  function createTempDirectory() {
210871
- const sharedTempDir = mkdtempSync2(join14(tmpdir3(), "pullfrog-"));
211316
+ const sharedTempDir = mkdtempSync2(join15(tmpdir3(), "pullfrog-"));
210872
211317
  process.env.PULLFROG_TEMP_DIR = sharedTempDir;
210873
211318
  log2.info(`\xBB created temp dir at ${sharedTempDir}`);
210874
211319
  return sharedTempDir;
@@ -210913,13 +211358,13 @@ function wipeRunnerLeakSurface() {
210913
211358
  return [];
210914
211359
  }
210915
211360
  };
210916
- const fileCommandsDir = join14(runnerTemp, "_runner_file_commands");
211361
+ const fileCommandsDir = join15(runnerTemp, "_runner_file_commands");
210917
211362
  for (const entry of listDir(fileCommandsDir)) {
210918
- tryUnlink(join14(fileCommandsDir, entry));
211363
+ tryUnlink(join15(fileCommandsDir, entry));
210919
211364
  }
210920
211365
  for (const entry of listDir(runnerTemp)) {
210921
211366
  if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
210922
- tryUnlink(join14(runnerTemp, entry));
211367
+ tryUnlink(join15(runnerTemp, entry));
210923
211368
  }
210924
211369
  }
210925
211370
  if (wiped.length > 0) {
@@ -211394,6 +211839,7 @@ async function checkoutPrBranch(pr, params) {
211394
211839
  const postCheckoutHook = await executeLifecycleHook({
211395
211840
  event: "post-checkout",
211396
211841
  script: params.postCheckoutScript,
211842
+ shell: params.shell,
211397
211843
  normalizeWorkingTreeAfter: true
211398
211844
  });
211399
211845
  return { hookWarning: postCheckoutHook.warning };
@@ -211475,11 +211921,11 @@ function CheckoutPrTool(ctx) {
211475
211921
  headSha: checkoutSha
211476
211922
  });
211477
211923
  if (incremental.status === "ok") {
211478
- incrementalDiffPath = join15(
211924
+ incrementalDiffPath = join16(
211479
211925
  tempDir,
211480
211926
  `pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
211481
211927
  );
211482
- writeFileSync10(incrementalDiffPath, incremental.diff);
211928
+ writeFileSync11(incrementalDiffPath, incremental.diff);
211483
211929
  log2.info(
211484
211930
  `\xBB incremental diff computed (${incremental.diff.length} bytes) \u2192 ${incrementalDiffPath}`
211485
211931
  );
@@ -211492,8 +211938,8 @@ function CheckoutPrTool(ctx) {
211492
211938
  const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
211493
211939
  log2.debug(`formatted diff preview (first 100 lines):
211494
211940
  ${diffPreview}`);
211495
- const diffPath = join15(tempDir, `pr-${pull_number}-${headShort}.diff`);
211496
- writeFileSync10(diffPath, formatResult.content);
211941
+ const diffPath = join16(tempDir, `pr-${pull_number}-${headShort}.diff`);
211942
+ writeFileSync11(diffPath, formatResult.content);
211497
211943
  log2.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
211498
211944
  primary.diffCoverage = createDiffCoverageState({
211499
211945
  diffPath,
@@ -211650,8 +212096,8 @@ ${dirty}`
211650
212096
  }
211651
212097
 
211652
212098
  // mcp/checkSuite.ts
211653
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync11 } from "node:fs";
211654
- import { join as join16 } from "node:path";
212099
+ import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync12 } from "node:fs";
212100
+ import { join as join17 } from "node:path";
211655
212101
  var GetCheckSuiteLogs = type({
211656
212102
  check_suite_id: type.number.describe("the id from check_suite.id")
211657
212103
  });
@@ -211747,7 +212193,7 @@ function GetCheckSuiteLogsTool(ctx) {
211747
212193
  if (!tempDir) {
211748
212194
  throw new Error("PULLFROG_TEMP_DIR not set");
211749
212195
  }
211750
- const logsDir = join16(tempDir, "ci-logs");
212196
+ const logsDir = join17(tempDir, "ci-logs");
211751
212197
  mkdirSync8(logsDir, { recursive: true });
211752
212198
  const jobResults = [];
211753
212199
  for (const run4 of failedRuns) {
@@ -211774,8 +212220,8 @@ function GetCheckSuiteLogsTool(ctx) {
211774
212220
  );
211775
212221
  }
211776
212222
  const logsText = await logsResult.text();
211777
- const logPath = join16(logsDir, `job-${job.id}.log`);
211778
- writeFileSync11(logPath, logsText);
212223
+ const logPath = join17(logsDir, `job-${job.id}.log`);
212224
+ writeFileSync12(logPath, logsText);
211779
212225
  const analysis = analyzeLog(logsText, 80);
211780
212226
  const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
211781
212227
  jobResults.push({
@@ -211823,8 +212269,8 @@ function GetCheckSuiteLogsTool(ctx) {
211823
212269
  }
211824
212270
 
211825
212271
  // mcp/commitInfo.ts
211826
- import { writeFileSync as writeFileSync12 } from "node:fs";
211827
- import { join as join17 } from "node:path";
212272
+ import { writeFileSync as writeFileSync13 } from "node:fs";
212273
+ import { join as join18 } from "node:path";
211828
212274
  var CommitInfo = type({
211829
212275
  sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
211830
212276
  });
@@ -211848,8 +212294,8 @@ function CommitInfoTool(ctx) {
211848
212294
  "PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
211849
212295
  );
211850
212296
  }
211851
- const diffFile = join17(tempDir, `commit-${sha.slice(0, 7)}.diff`);
211852
- writeFileSync12(diffFile, formatResult.content);
212297
+ const diffFile = join18(tempDir, `commit-${sha.slice(0, 7)}.diff`);
212298
+ writeFileSync13(diffFile, formatResult.content);
211853
212299
  log2.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
211854
212300
  return {
211855
212301
  sha: data.sha,
@@ -211986,13 +212432,13 @@ import { performance as performance9 } from "node:perf_hooks";
211986
212432
 
211987
212433
  // prep/installNodeDependencies.ts
211988
212434
  import { existsSync as existsSync10 } from "node:fs";
211989
- import { join as join19 } from "node:path";
212435
+ import { join as join20 } from "node:path";
211990
212436
 
211991
212437
  // utils/packageManager.ts
211992
212438
  var import_semver2 = __toESM(require_semver2(), 1);
211993
212439
  import { existsSync as existsSync9 } from "node:fs";
211994
212440
  import { mkdir, readFile as readFile3 } from "node:fs/promises";
211995
- import { delimiter as delimiter2, join as join18 } from "node:path";
212441
+ import { delimiter as delimiter2, join as join19 } from "node:path";
211996
212442
  var SUPPORTED_NAMES = ["npm", "pnpm", "yarn", "bun"];
211997
212443
  var COREPACK_MANAGED = ["pnpm", "yarn"];
211998
212444
  function isSupported(name) {
@@ -212030,7 +212476,7 @@ function parseDevEnginesField(field) {
212030
212476
  };
212031
212477
  }
212032
212478
  async function resolvePackageManagerSpec(cwd) {
212033
- const pkgPath = join18(cwd, "package.json");
212479
+ const pkgPath = join19(cwd, "package.json");
212034
212480
  if (!existsSync9(pkgPath)) return null;
212035
212481
  let pkg;
212036
212482
  try {
@@ -212089,7 +212535,7 @@ async function currentVersion(name) {
212089
212535
  return result.stdout.trim();
212090
212536
  }
212091
212537
  function packageManagerBinDir(tmpdir4) {
212092
- return join18(tmpdir4, "pm-bin");
212538
+ return join19(tmpdir4, "pm-bin");
212093
212539
  }
212094
212540
  async function ensurePackageManager(params) {
212095
212541
  const spec = params.spec;
@@ -212152,7 +212598,7 @@ async function installFallback(name, installSpec) {
212152
212598
  });
212153
212599
  if (result.exitCode !== 0) return result.stderr || `failed to install ${name}`;
212154
212600
  if (name === "deno") {
212155
- const denoPath = join18(process.env.HOME || "", ".deno", "bin");
212601
+ const denoPath = join19(process.env.HOME || "", ".deno", "bin");
212156
212602
  process.env.PATH = `${denoPath}:${process.env.PATH}`;
212157
212603
  }
212158
212604
  log2.info(`\xBB installed ${name}`);
@@ -212185,7 +212631,7 @@ async function isCommandAvailable2(command) {
212185
212631
  var installNodeDependencies = {
212186
212632
  name: "installNodeDependencies",
212187
212633
  shouldRun: () => {
212188
- const packageJsonPath = join19(process.cwd(), "package.json");
212634
+ const packageJsonPath = join20(process.cwd(), "package.json");
212189
212635
  return existsSync10(packageJsonPath);
212190
212636
  },
212191
212637
  run: async (options) => {
@@ -212281,12 +212727,12 @@ ${errorMessage}`]
212281
212727
 
212282
212728
  // prep/installPythonDependencies.ts
212283
212729
  import { existsSync as existsSync11, readFileSync as readFileSync7 } from "node:fs";
212284
- import { join as join20 } from "node:path";
212730
+ import { join as join21 } from "node:path";
212285
212731
  function declaresBuildSystem(path4) {
212286
212732
  return /^\s*\[\s*build-system\s*\]/m.test(readFileSync7(path4, "utf8"));
212287
212733
  }
212288
212734
  function configApplies(config4, cwd) {
212289
- const path4 = join20(cwd, config4.file);
212735
+ const path4 = join21(cwd, config4.file);
212290
212736
  return existsSync11(path4) && (config4.applies?.(path4) ?? true);
212291
212737
  }
212292
212738
  var PYTHON_CONFIGS = [
@@ -212610,407 +213056,6 @@ function AwaitDependencyInstallationTool(ctx) {
212610
213056
  // mcp/gh.ts
212611
213057
  import { mkdirSync as mkdirSync9 } from "node:fs";
212612
213058
  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
- let stdout2 = "", stderr = "", timedOut = false, exited = false, spawnError = "";
212841
- proc.stdout?.on("data", (chunk) => {
212842
- stdout2 += chunk.toString();
212843
- });
212844
- proc.stderr?.on("data", (chunk) => {
212845
- stderr += chunk.toString();
212846
- });
212847
- const timeoutId = setTimeout(async () => {
212848
- if (!exited) {
212849
- timedOut = true;
212850
- await killProcessGroup(proc);
212851
- }
212852
- }, params.timeout);
212853
- const exitCode = await new Promise((resolve3) => {
212854
- const done = (code) => {
212855
- exited = true;
212856
- clearTimeout(timeoutId);
212857
- resolve3(code);
212858
- };
212859
- proc.on("exit", done);
212860
- proc.on("error", (err) => {
212861
- spawnError = err.message;
212862
- done(null);
212863
- });
212864
- });
212865
- proc.stdout?.destroy();
212866
- proc.stderr?.destroy();
212867
- let output2 = stderr ? stdout2 ? `${stdout2}
212868
- ${stderr}` : stderr : stdout2;
212869
- if (spawnError) output2 = output2 ? `${output2}
212870
- ${spawnError}` : spawnError;
212871
- if (timedOut)
212872
- output2 = output2 ? `${output2}
212873
- [timed out after ${params.timeout}ms]` : `[timed out after ${params.timeout}ms]`;
212874
- return {
212875
- output: output2.trim(),
212876
- exitCode: exitCode ?? (timedOut ? 124 : -1),
212877
- timedOut
212878
- };
212879
- }
212880
- function capOutput(output2) {
212881
- if (output2.length <= MAX_OUTPUT_CHARS) return output2;
212882
- const fullPath = join21(getTempDir(), `shell-${randomUUID4().slice(0, 8)}.log`);
212883
- writeFileSync13(fullPath, output2);
212884
- const elided = output2.length - MAX_OUTPUT_CHARS;
212885
- return `... [${elided} chars truncated; full output saved to ${fullPath}] ...
212886
- ${output2.slice(-MAX_OUTPUT_CHARS)}`;
212887
- }
212888
- function isGitCommand(command) {
212889
- const trimmed = command.trim();
212890
- if (trimmed === "git" || trimmed.startsWith("git ")) return true;
212891
- if (trimmed.startsWith("sudo git")) return true;
212892
- return /[;&|]\s*(?:sudo\s+)?git(?:\s|$)/.test(trimmed);
212893
- }
212894
- function ShellTool(ctx) {
212895
- return tool({
212896
- name: "shell",
212897
- timeoutMs: 12e4,
212898
- description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
212899
-
212900
- Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
212901
-
212902
- Use this tool to:
212903
- - Run shell commands (ls, cat, grep, find, etc.)
212904
- - Execute build tools (npm, pnpm, cargo, make, etc.)
212905
- - Run tests and linters
212906
-
212907
- Output is capped at ${MAX_OUTPUT_CHARS} chars: if exceeded, only the tail is returned and the full body is saved to a tempfile (path included in the response). Re-read the tempfile with cat/tail/grep when you need more.
212908
-
212909
- Do NOT use this tool for git commands \u2014 use the dedicated git tools instead.`,
212910
- parameters: ShellParams,
212911
- execute: execute(async (params) => {
212912
- if (isGitCommand(params.command)) {
212913
- throw new Error(
212914
- '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'
212915
- );
212916
- }
212917
- const timeout = Math.min(params.timeout ?? 3e4, 12e4);
212918
- const cwd = params.working_directory ?? process.cwd();
212919
- const env2 = resolveEnv(ctx.payload.shell === "enabled" ? "inherit" : "restricted");
212920
- if (params.command.includes("agent-browser")) {
212921
- const daemonError = ensureBrowserDaemon(ctx.toolState);
212922
- if (daemonError) {
212923
- return {
212924
- output: `browser daemon unavailable: ${daemonError}`,
212925
- exit_code: 1,
212926
- timed_out: false
212927
- };
212928
- }
212929
- const binDir = ctx.toolState.browserDaemon?.binDir;
212930
- if (binDir) {
212931
- env2.PATH = `${binDir}:${env2.PATH ?? ""}`;
212932
- }
212933
- }
212934
- if (params.background) {
212935
- const tempDir = getTempDir();
212936
- const handle = `bg-${randomUUID4().slice(0, 8)}`;
212937
- const outputPath = join21(tempDir, `${handle}.log`);
212938
- const pidPath = join21(tempDir, `${handle}.pid`);
212939
- const logFd = openSync(outputPath, "a");
212940
- let proc;
212941
- try {
212942
- proc = spawnShell({
212943
- command: params.command,
212944
- env: env2,
212945
- cwd,
212946
- stdio: ["ignore", logFd, logFd]
212947
- });
212948
- } finally {
212949
- closeSync(logFd);
212950
- }
212951
- if (!proc.pid) {
212952
- throw new Error("failed to start background process");
212953
- }
212954
- proc.unref();
212955
- writeFileSync13(pidPath, `${proc.pid}
212956
- `);
212957
- ctx.toolState.backgroundProcesses.set(handle, { pid: proc.pid, outputPath, pidPath });
212958
- return {
212959
- handle,
212960
- outputPath,
212961
- pidPath,
212962
- message: `started background process ${handle} (pid ${proc.pid})`
212963
- };
212964
- }
212965
- const result = await runSandboxed({ command: params.command, env: env2, cwd, timeout });
212966
- if (result.exitCode !== 0) {
212967
- log2.info(`shell command failed with exit code ${result.exitCode}: ${params.command}`);
212968
- if (result.output) log2.info(`output: ${result.output}`);
212969
- }
212970
- return {
212971
- output: capOutput(result.output),
212972
- exit_code: result.exitCode,
212973
- timed_out: result.timedOut
212974
- };
212975
- })
212976
- });
212977
- }
212978
- var KillBackgroundParams = type({
212979
- handle: type.string.describe("The handle of the background process to kill (e.g., bg-a1b2c3d4)")
212980
- });
212981
- function KillBackgroundTool(ctx) {
212982
- return tool({
212983
- name: "kill_background",
212984
- mutates: true,
212985
- description: `Kill a background process by its handle. Use this to stop dev servers or other long-running processes started with shell({ background: true }).`,
212986
- parameters: KillBackgroundParams,
212987
- execute: execute(async (params) => {
212988
- const proc = ctx.toolState.backgroundProcesses.get(params.handle);
212989
- if (!proc) {
212990
- return {
212991
- success: false,
212992
- message: `no background process with handle ${params.handle}`
212993
- };
212994
- }
212995
- try {
212996
- process.kill(-proc.pid, "SIGTERM");
212997
- } catch {
212998
- }
212999
- await sleep3(200);
213000
- try {
213001
- process.kill(-proc.pid, "SIGKILL");
213002
- } catch {
213003
- }
213004
- ctx.toolState.backgroundProcesses.delete(params.handle);
213005
- return {
213006
- success: true,
213007
- message: `killed background process ${params.handle} (pid ${proc.pid})`
213008
- };
213009
- })
213010
- });
213011
- }
213012
-
213013
- // mcp/gh.ts
213014
213059
  var GH_TIMEOUT_MS = 12e4;
213015
213060
  var GhParams = type({
213016
213061
  args: type.string.array().describe(
@@ -218809,6 +218854,7 @@ async function main() {
218809
218854
  const setupHook = await executeLifecycleHook({
218810
218855
  event: "setup",
218811
218856
  script: runContext.repoSettings.setupScript,
218857
+ shell: payload.shell,
218812
218858
  normalizeWorkingTreeAfter: true
218813
218859
  });
218814
218860
  if (setupHook.warning) {
@@ -219684,7 +219730,7 @@ function resolveTarget2(repo) {
219684
219730
  return { owner: match3[1], repo: match3[2] };
219685
219731
  }
219686
219732
  function cliVersion() {
219687
- const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.73");
219733
+ const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.75");
219688
219734
  if (!parts) return "0.0.0";
219689
219735
  return `${Number(parts[1])}.${Number(parts[2])}.${Number(parts[3])}`;
219690
219736
  }
@@ -219851,7 +219897,7 @@ async function runCli8(input2) {
219851
219897
  }
219852
219898
 
219853
219899
  // cli.ts
219854
- var VERSION10 = "0.1.73";
219900
+ var VERSION10 = "0.1.75";
219855
219901
  var bin = basename2(process.argv[1] || "");
219856
219902
  var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
219857
219903
  var rawArgs = process.argv.slice(2);