pullfrog 0.1.74 → 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/index.js CHANGED
@@ -28027,7 +28027,7 @@ var require_cross_spawn = __commonJS({
28027
28027
  enoent.hookChildProcess(spawned, parsed2);
28028
28028
  return spawned;
28029
28029
  }
28030
- function spawnSync7(command, args2, options) {
28030
+ function spawnSync8(command, args2, options) {
28031
28031
  const parsed2 = parse6(command, args2, options);
28032
28032
  const result = cp.spawnSync(parsed2.command, parsed2.args, parsed2.options);
28033
28033
  result.error = result.error || enoent.verifyENOENTSync(result.status, parsed2);
@@ -28035,7 +28035,7 @@ var require_cross_spawn = __commonJS({
28035
28035
  }
28036
28036
  module.exports = spawn4;
28037
28037
  module.exports.spawn = spawn4;
28038
- module.exports.sync = spawnSync7;
28038
+ module.exports.sync = spawnSync8;
28039
28039
  module.exports._parse = parse6;
28040
28040
  module.exports._enoent = enoent;
28041
28041
  }
@@ -109795,7 +109795,7 @@ var import_semver = __toESM(require_semver2(), 1);
109795
109795
  // package.json
109796
109796
  var package_default = {
109797
109797
  name: "pullfrog",
109798
- version: "0.1.74",
109798
+ version: "0.1.75",
109799
109799
  cliContractVersion: "1",
109800
109800
  type: "module",
109801
109801
  bin: {
@@ -176983,8 +176983,8 @@ function closeBrowserDaemon(toolState) {
176983
176983
 
176984
176984
  // mcp/checkout.ts
176985
176985
  import { createHash as createHash2 } from "node:crypto";
176986
- import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync9 } from "node:fs";
176987
- import { join as join14 } from "node:path";
176986
+ import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync10 } from "node:fs";
176987
+ import { join as join15 } from "node:path";
176988
176988
 
176989
176989
  // node_modules/.pnpm/@ark+schema@0.56.0/node_modules/@ark/schema/out/shared/errors.js
176990
176990
  var ArkError = class _ArkError extends CastableBase {
@@ -185256,6 +185256,960 @@ async function $gitFetchWithDeepen(args2, options, label) {
185256
185256
  }
185257
185257
  }
185258
185258
 
185259
+ // utils/lifecycle.ts
185260
+ import { spawnSync as spawnSync7 } from "node:child_process";
185261
+
185262
+ // mcp/shell.ts
185263
+ import { spawn as spawn3, spawnSync as spawnSync6 } from "node:child_process";
185264
+ import { randomUUID as randomUUID3 } from "node:crypto";
185265
+ import { closeSync, openSync, writeFileSync as writeFileSync8 } from "node:fs";
185266
+ import { userInfo as userInfo2 } from "node:os";
185267
+ import { join as join10 } from "node:path";
185268
+ import { setTimeout as sleep2 } from "node:timers/promises";
185269
+
185270
+ // node_modules/.pnpm/@toon-format+toon@1.4.0/node_modules/@toon-format/toon/dist/index.mjs
185271
+ var LIST_ITEM_MARKER = "-";
185272
+ var LIST_ITEM_PREFIX = "- ";
185273
+ var COMMA = ",";
185274
+ var PIPE = "|";
185275
+ var DOT = ".";
185276
+ var NULL_LITERAL = "null";
185277
+ var TRUE_LITERAL = "true";
185278
+ var FALSE_LITERAL = "false";
185279
+ var BACKSLASH = "\\";
185280
+ var DOUBLE_QUOTE = '"';
185281
+ var TAB = " ";
185282
+ var DELIMITERS = {
185283
+ comma: COMMA,
185284
+ tab: TAB,
185285
+ pipe: PIPE
185286
+ };
185287
+ var DEFAULT_DELIMITER = DELIMITERS.comma;
185288
+ function escapeString(value2) {
185289
+ 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`);
185290
+ }
185291
+ function isBooleanOrNullLiteral(token) {
185292
+ return token === TRUE_LITERAL || token === FALSE_LITERAL || token === NULL_LITERAL;
185293
+ }
185294
+ function normalizeValue(value2) {
185295
+ if (value2 === null) return null;
185296
+ if (typeof value2 === "string" || typeof value2 === "boolean") return value2;
185297
+ if (typeof value2 === "number") {
185298
+ if (Object.is(value2, -0)) return 0;
185299
+ if (!Number.isFinite(value2)) return null;
185300
+ return value2;
185301
+ }
185302
+ if (typeof value2 === "bigint") {
185303
+ if (value2 >= Number.MIN_SAFE_INTEGER && value2 <= Number.MAX_SAFE_INTEGER) return Number(value2);
185304
+ return value2.toString();
185305
+ }
185306
+ if (value2 instanceof Date) return value2.toISOString();
185307
+ if (Array.isArray(value2)) return value2.map(normalizeValue);
185308
+ if (value2 instanceof Set) return Array.from(value2).map(normalizeValue);
185309
+ if (value2 instanceof Map) return Object.fromEntries(Array.from(value2, ([k2, v]) => [String(k2), normalizeValue(v)]));
185310
+ if (isPlainObject6(value2)) {
185311
+ const normalized = {};
185312
+ for (const key in value2) if (Object.prototype.hasOwnProperty.call(value2, key)) normalized[key] = normalizeValue(value2[key]);
185313
+ return normalized;
185314
+ }
185315
+ return null;
185316
+ }
185317
+ function isJsonPrimitive(value2) {
185318
+ return value2 === null || typeof value2 === "string" || typeof value2 === "number" || typeof value2 === "boolean";
185319
+ }
185320
+ function isJsonArray(value2) {
185321
+ return Array.isArray(value2);
185322
+ }
185323
+ function isJsonObject4(value2) {
185324
+ return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
185325
+ }
185326
+ function isEmptyObject2(value2) {
185327
+ return Object.keys(value2).length === 0;
185328
+ }
185329
+ function isPlainObject6(value2) {
185330
+ if (value2 === null || typeof value2 !== "object") return false;
185331
+ const prototype = Object.getPrototypeOf(value2);
185332
+ return prototype === null || prototype === Object.prototype;
185333
+ }
185334
+ function isArrayOfPrimitives(value2) {
185335
+ return value2.length === 0 || value2.every((item) => isJsonPrimitive(item));
185336
+ }
185337
+ function isArrayOfArrays(value2) {
185338
+ return value2.length === 0 || value2.every((item) => isJsonArray(item));
185339
+ }
185340
+ function isArrayOfObjects(value2) {
185341
+ return value2.length === 0 || value2.every((item) => isJsonObject4(item));
185342
+ }
185343
+ function isValidUnquotedKey(key) {
185344
+ return /^[A-Z_][\w.]*$/i.test(key);
185345
+ }
185346
+ function isIdentifierSegment(key) {
185347
+ return /^[A-Z_]\w*$/i.test(key);
185348
+ }
185349
+ function isSafeUnquoted(value2, delimiter2 = DEFAULT_DELIMITER) {
185350
+ if (!value2) return false;
185351
+ if (value2 !== value2.trim()) return false;
185352
+ if (isBooleanOrNullLiteral(value2) || isNumericLike(value2)) return false;
185353
+ if (value2.includes(":")) return false;
185354
+ if (value2.includes('"') || value2.includes("\\")) return false;
185355
+ if (/[[\]{}]/.test(value2)) return false;
185356
+ if (/[\n\r\t]/.test(value2)) return false;
185357
+ if (value2.includes(delimiter2)) return false;
185358
+ if (value2.startsWith(LIST_ITEM_MARKER)) return false;
185359
+ return true;
185360
+ }
185361
+ function isNumericLike(value2) {
185362
+ return /^-?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i.test(value2) || /^0\d+$/.test(value2);
185363
+ }
185364
+ var QUOTED_KEY_MARKER = Symbol("quotedKey");
185365
+ function tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, flattenDepth) {
185366
+ if (options.keyFolding !== "safe") return;
185367
+ if (!isJsonObject4(value2)) return;
185368
+ const { segments, tail, leafValue } = collectSingleKeyChain(key, value2, flattenDepth ?? options.flattenDepth);
185369
+ if (segments.length < 2) return;
185370
+ if (!segments.every((seg) => isIdentifierSegment(seg))) return;
185371
+ const foldedKey = buildFoldedKey(segments);
185372
+ const absolutePath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
185373
+ if (siblings.includes(foldedKey)) return;
185374
+ if (rootLiteralKeys && rootLiteralKeys.has(absolutePath)) return;
185375
+ return {
185376
+ foldedKey,
185377
+ remainder: tail,
185378
+ leafValue,
185379
+ segmentCount: segments.length
185380
+ };
185381
+ }
185382
+ function collectSingleKeyChain(startKey, startValue, maxDepth) {
185383
+ const segments = [startKey];
185384
+ let currentValue = startValue;
185385
+ while (segments.length < maxDepth) {
185386
+ if (!isJsonObject4(currentValue)) break;
185387
+ const keys = Object.keys(currentValue);
185388
+ if (keys.length !== 1) break;
185389
+ const nextKey = keys[0];
185390
+ const nextValue = currentValue[nextKey];
185391
+ segments.push(nextKey);
185392
+ currentValue = nextValue;
185393
+ }
185394
+ if (!isJsonObject4(currentValue) || isEmptyObject2(currentValue)) return {
185395
+ segments,
185396
+ tail: void 0,
185397
+ leafValue: currentValue
185398
+ };
185399
+ return {
185400
+ segments,
185401
+ tail: currentValue,
185402
+ leafValue: currentValue
185403
+ };
185404
+ }
185405
+ function buildFoldedKey(segments) {
185406
+ return segments.join(DOT);
185407
+ }
185408
+ function encodePrimitive(value2, delimiter2) {
185409
+ if (value2 === null) return NULL_LITERAL;
185410
+ if (typeof value2 === "boolean") return String(value2);
185411
+ if (typeof value2 === "number") return String(value2);
185412
+ return encodeStringLiteral(value2, delimiter2);
185413
+ }
185414
+ function encodeStringLiteral(value2, delimiter2 = DEFAULT_DELIMITER) {
185415
+ if (isSafeUnquoted(value2, delimiter2)) return value2;
185416
+ return `${DOUBLE_QUOTE}${escapeString(value2)}${DOUBLE_QUOTE}`;
185417
+ }
185418
+ function encodeKey(key) {
185419
+ if (isValidUnquotedKey(key)) return key;
185420
+ return `${DOUBLE_QUOTE}${escapeString(key)}${DOUBLE_QUOTE}`;
185421
+ }
185422
+ function encodeAndJoinPrimitives(values, delimiter2 = DEFAULT_DELIMITER) {
185423
+ return values.map((v) => encodePrimitive(v, delimiter2)).join(delimiter2);
185424
+ }
185425
+ function formatHeader(length, options) {
185426
+ const key = options?.key;
185427
+ const fields = options?.fields;
185428
+ const delimiter2 = options?.delimiter ?? COMMA;
185429
+ let header = "";
185430
+ if (key) header += encodeKey(key);
185431
+ header += `[${length}${delimiter2 !== DEFAULT_DELIMITER ? delimiter2 : ""}]`;
185432
+ if (fields) {
185433
+ const quotedFields = fields.map((f) => encodeKey(f));
185434
+ header += `{${quotedFields.join(delimiter2)}}`;
185435
+ }
185436
+ header += ":";
185437
+ return header;
185438
+ }
185439
+ function* encodeJsonValue(value2, options, depth) {
185440
+ if (isJsonPrimitive(value2)) {
185441
+ const encodedPrimitive = encodePrimitive(value2, options.delimiter);
185442
+ if (encodedPrimitive !== "") yield encodedPrimitive;
185443
+ return;
185444
+ }
185445
+ if (isJsonArray(value2)) yield* encodeArrayLines(void 0, value2, depth, options);
185446
+ else if (isJsonObject4(value2)) yield* encodeObjectLines(value2, depth, options);
185447
+ }
185448
+ function* encodeObjectLines(value2, depth, options, rootLiteralKeys, pathPrefix, remainingDepth) {
185449
+ const keys = Object.keys(value2);
185450
+ if (depth === 0 && !rootLiteralKeys) rootLiteralKeys = new Set(keys.filter((k2) => k2.includes(".")));
185451
+ const effectiveFlattenDepth = remainingDepth ?? options.flattenDepth;
185452
+ for (const [key, val] of Object.entries(value2)) yield* encodeKeyValuePairLines(key, val, depth, options, keys, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
185453
+ }
185454
+ function* encodeKeyValuePairLines(key, value2, depth, options, siblings, rootLiteralKeys, pathPrefix, flattenDepth) {
185455
+ const currentPath = pathPrefix ? `${pathPrefix}${DOT}${key}` : key;
185456
+ const effectiveFlattenDepth = flattenDepth ?? options.flattenDepth;
185457
+ if (options.keyFolding === "safe" && siblings) {
185458
+ const foldResult = tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
185459
+ if (foldResult) {
185460
+ const { foldedKey, remainder, leafValue, segmentCount } = foldResult;
185461
+ const encodedFoldedKey = encodeKey(foldedKey);
185462
+ if (remainder === void 0) {
185463
+ if (isJsonPrimitive(leafValue)) {
185464
+ yield indentedLine(depth, `${encodedFoldedKey}: ${encodePrimitive(leafValue, options.delimiter)}`, options.indent);
185465
+ return;
185466
+ } else if (isJsonArray(leafValue)) {
185467
+ yield* encodeArrayLines(foldedKey, leafValue, depth, options);
185468
+ return;
185469
+ } else if (isJsonObject4(leafValue) && isEmptyObject2(leafValue)) {
185470
+ yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
185471
+ return;
185472
+ }
185473
+ }
185474
+ if (isJsonObject4(remainder)) {
185475
+ yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
185476
+ const remainingDepth = effectiveFlattenDepth - segmentCount;
185477
+ const foldedPath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
185478
+ yield* encodeObjectLines(remainder, depth + 1, options, rootLiteralKeys, foldedPath, remainingDepth);
185479
+ return;
185480
+ }
185481
+ }
185482
+ }
185483
+ const encodedKey = encodeKey(key);
185484
+ if (isJsonPrimitive(value2)) yield indentedLine(depth, `${encodedKey}: ${encodePrimitive(value2, options.delimiter)}`, options.indent);
185485
+ else if (isJsonArray(value2)) yield* encodeArrayLines(key, value2, depth, options);
185486
+ else if (isJsonObject4(value2)) {
185487
+ yield indentedLine(depth, `${encodedKey}:`, options.indent);
185488
+ if (!isEmptyObject2(value2)) yield* encodeObjectLines(value2, depth + 1, options, rootLiteralKeys, currentPath, effectiveFlattenDepth);
185489
+ }
185490
+ }
185491
+ function* encodeArrayLines(key, value2, depth, options) {
185492
+ if (value2.length === 0) {
185493
+ yield indentedLine(depth, formatHeader(0, {
185494
+ key,
185495
+ delimiter: options.delimiter
185496
+ }), options.indent);
185497
+ return;
185498
+ }
185499
+ if (isArrayOfPrimitives(value2)) {
185500
+ yield indentedLine(depth, encodeInlineArrayLine(value2, options.delimiter, key), options.indent);
185501
+ return;
185502
+ }
185503
+ if (isArrayOfArrays(value2)) {
185504
+ if (value2.every((arr) => isArrayOfPrimitives(arr))) {
185505
+ yield* encodeArrayOfArraysAsListItemsLines(key, value2, depth, options);
185506
+ return;
185507
+ }
185508
+ }
185509
+ if (isArrayOfObjects(value2)) {
185510
+ const header = extractTabularHeader(value2);
185511
+ if (header) yield* encodeArrayOfObjectsAsTabularLines(key, value2, header, depth, options);
185512
+ else yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
185513
+ return;
185514
+ }
185515
+ yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
185516
+ }
185517
+ function* encodeArrayOfArraysAsListItemsLines(prefix, values, depth, options) {
185518
+ yield indentedLine(depth, formatHeader(values.length, {
185519
+ key: prefix,
185520
+ delimiter: options.delimiter
185521
+ }), options.indent);
185522
+ for (const arr of values) if (isArrayOfPrimitives(arr)) {
185523
+ const arrayLine = encodeInlineArrayLine(arr, options.delimiter);
185524
+ yield indentedListItem(depth + 1, arrayLine, options.indent);
185525
+ }
185526
+ }
185527
+ function encodeInlineArrayLine(values, delimiter2, prefix) {
185528
+ const header = formatHeader(values.length, {
185529
+ key: prefix,
185530
+ delimiter: delimiter2
185531
+ });
185532
+ const joinedValue = encodeAndJoinPrimitives(values, delimiter2);
185533
+ if (values.length === 0) return header;
185534
+ return `${header} ${joinedValue}`;
185535
+ }
185536
+ function* encodeArrayOfObjectsAsTabularLines(prefix, rows, header, depth, options) {
185537
+ yield indentedLine(depth, formatHeader(rows.length, {
185538
+ key: prefix,
185539
+ fields: header,
185540
+ delimiter: options.delimiter
185541
+ }), options.indent);
185542
+ yield* writeTabularRowsLines(rows, header, depth + 1, options);
185543
+ }
185544
+ function extractTabularHeader(rows) {
185545
+ if (rows.length === 0) return;
185546
+ const firstRow = rows[0];
185547
+ const firstKeys = Object.keys(firstRow);
185548
+ if (firstKeys.length === 0) return;
185549
+ if (isTabularArray(rows, firstKeys)) return firstKeys;
185550
+ }
185551
+ function isTabularArray(rows, header) {
185552
+ for (const row of rows) {
185553
+ if (Object.keys(row).length !== header.length) return false;
185554
+ for (const key of header) {
185555
+ if (!(key in row)) return false;
185556
+ if (!isJsonPrimitive(row[key])) return false;
185557
+ }
185558
+ }
185559
+ return true;
185560
+ }
185561
+ function* writeTabularRowsLines(rows, header, depth, options) {
185562
+ for (const row of rows) yield indentedLine(depth, encodeAndJoinPrimitives(header.map((key) => row[key]), options.delimiter), options.indent);
185563
+ }
185564
+ function* encodeMixedArrayAsListItemsLines(prefix, items, depth, options) {
185565
+ yield indentedLine(depth, formatHeader(items.length, {
185566
+ key: prefix,
185567
+ delimiter: options.delimiter
185568
+ }), options.indent);
185569
+ for (const item of items) yield* encodeListItemValueLines(item, depth + 1, options);
185570
+ }
185571
+ function* encodeObjectAsListItemLines(obj, depth, options) {
185572
+ if (isEmptyObject2(obj)) {
185573
+ yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
185574
+ return;
185575
+ }
185576
+ const entries = Object.entries(obj);
185577
+ if (entries.length === 1) {
185578
+ const [key, value2] = entries[0];
185579
+ if (isJsonArray(value2) && isArrayOfObjects(value2)) {
185580
+ const header = extractTabularHeader(value2);
185581
+ if (header) {
185582
+ yield indentedListItem(depth, formatHeader(value2.length, {
185583
+ key,
185584
+ fields: header,
185585
+ delimiter: options.delimiter
185586
+ }), options.indent);
185587
+ yield* writeTabularRowsLines(value2, header, depth + 1, options);
185588
+ return;
185589
+ }
185590
+ }
185591
+ }
185592
+ yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
185593
+ yield* encodeObjectLines(obj, depth + 1, options);
185594
+ }
185595
+ function* encodeListItemValueLines(value2, depth, options) {
185596
+ if (isJsonPrimitive(value2)) yield indentedListItem(depth, encodePrimitive(value2, options.delimiter), options.indent);
185597
+ else if (isJsonArray(value2)) if (isArrayOfPrimitives(value2)) yield indentedListItem(depth, encodeInlineArrayLine(value2, options.delimiter), options.indent);
185598
+ else {
185599
+ yield indentedListItem(depth, formatHeader(value2.length, { delimiter: options.delimiter }), options.indent);
185600
+ for (const item of value2) yield* encodeListItemValueLines(item, depth + 1, options);
185601
+ }
185602
+ else if (isJsonObject4(value2)) yield* encodeObjectAsListItemLines(value2, depth, options);
185603
+ }
185604
+ function indentedLine(depth, content, indentSize) {
185605
+ return " ".repeat(indentSize * depth) + content;
185606
+ }
185607
+ function indentedListItem(depth, content, indentSize) {
185608
+ return indentedLine(depth, LIST_ITEM_PREFIX + content, indentSize);
185609
+ }
185610
+ function encode4(input2, options) {
185611
+ return Array.from(encodeLines(input2, options)).join("\n");
185612
+ }
185613
+ function encodeLines(input2, options) {
185614
+ return encodeJsonValue(normalizeValue(input2), resolveOptions(options), 0);
185615
+ }
185616
+ function resolveOptions(options) {
185617
+ return {
185618
+ indent: options?.indent ?? 2,
185619
+ delimiter: options?.delimiter ?? DEFAULT_DELIMITER,
185620
+ keyFolding: options?.keyFolding ?? "off",
185621
+ flattenDepth: options?.flattenDepth ?? Number.POSITIVE_INFINITY
185622
+ };
185623
+ }
185624
+
185625
+ // mcp/geminiSanitizer.ts
185626
+ function parseStringEnumBranch(item) {
185627
+ if (!item || typeof item !== "object") return null;
185628
+ const record3 = item;
185629
+ if (Array.isArray(record3.enum)) {
185630
+ const strings = record3.enum.filter((v) => typeof v === "string");
185631
+ return strings.length === record3.enum.length && strings.length > 0 ? { values: strings } : null;
185632
+ }
185633
+ if (typeof record3.const === "string") {
185634
+ return { values: [record3.const] };
185635
+ }
185636
+ return null;
185637
+ }
185638
+ function collapseStringUnion(branches) {
185639
+ const values = [];
185640
+ for (const item of branches) {
185641
+ const parsed2 = parseStringEnumBranch(item);
185642
+ if (!parsed2) return null;
185643
+ values.push(...parsed2.values);
185644
+ }
185645
+ if (values.length === 0) return null;
185646
+ return { type: "string", enum: [...new Set(values)] };
185647
+ }
185648
+ function sanitizeForGemini(schema2) {
185649
+ if (!schema2 || typeof schema2 !== "object") return schema2;
185650
+ if (Array.isArray(schema2)) return schema2.map(sanitizeForGemini);
185651
+ const source = schema2;
185652
+ if (Array.isArray(source.enum) && typeof source.type !== "string") {
185653
+ const allStrings = source.enum.every((v) => typeof v === "string");
185654
+ if (allStrings) {
185655
+ const result = { type: "string", enum: source.enum };
185656
+ if (typeof source.description === "string") result.description = source.description;
185657
+ return result;
185658
+ }
185659
+ }
185660
+ for (const unionKey of ["anyOf", "oneOf"]) {
185661
+ const branches = source[unionKey];
185662
+ if (Array.isArray(branches) && branches.length > 0) {
185663
+ const collapsed = collapseStringUnion(branches);
185664
+ if (collapsed) {
185665
+ const result = { ...collapsed };
185666
+ if (typeof source.description === "string") result.description = source.description;
185667
+ return result;
185668
+ }
185669
+ }
185670
+ }
185671
+ if (Array.isArray(source.anyOf) || Array.isArray(source.oneOf)) {
185672
+ const result = {};
185673
+ if (Array.isArray(source.anyOf)) result.anyOf = source.anyOf.map(sanitizeForGemini);
185674
+ if (Array.isArray(source.oneOf)) result.oneOf = source.oneOf.map(sanitizeForGemini);
185675
+ return result;
185676
+ }
185677
+ const sanitized = {};
185678
+ for (const [key, value2] of Object.entries(source)) {
185679
+ if (key === "$schema") continue;
185680
+ if (key === "$defs") {
185681
+ sanitized.definitions = sanitizeForGemini(value2);
185682
+ continue;
185683
+ }
185684
+ sanitized[key] = sanitizeForGemini(value2);
185685
+ }
185686
+ return sanitized;
185687
+ }
185688
+ function wrapJsonSchemaProducer(producer) {
185689
+ return new Proxy(producer, {
185690
+ get(target, prop, receiver) {
185691
+ const value2 = Reflect.get(target, prop, receiver);
185692
+ if ((prop === "input" || prop === "output") && typeof value2 === "function") {
185693
+ const fn2 = value2;
185694
+ return (...args2) => sanitizeForGemini(fn2.apply(target, args2));
185695
+ }
185696
+ return value2;
185697
+ }
185698
+ });
185699
+ }
185700
+ function wrapStandard(standard) {
185701
+ return new Proxy(standard, {
185702
+ get(target, prop, receiver) {
185703
+ if (prop === "jsonSchema") {
185704
+ const value2 = Reflect.get(target, prop, receiver);
185705
+ if (value2 && typeof value2 === "object") {
185706
+ return wrapJsonSchemaProducer(value2);
185707
+ }
185708
+ return value2;
185709
+ }
185710
+ return Reflect.get(target, prop, receiver);
185711
+ }
185712
+ });
185713
+ }
185714
+ function wrapSchemaForGemini(schema2) {
185715
+ return new Proxy(schema2, {
185716
+ get(target, prop, receiver) {
185717
+ if (prop === "~standard") {
185718
+ const value2 = Reflect.get(target, prop, receiver);
185719
+ if (value2 && typeof value2 === "object") {
185720
+ return wrapStandard(value2);
185721
+ }
185722
+ return value2;
185723
+ }
185724
+ if (prop === "toJsonSchema") {
185725
+ const method = Reflect.get(target, prop, receiver);
185726
+ if (typeof method === "function") {
185727
+ return () => sanitizeForGemini(method.call(target));
185728
+ }
185729
+ return method;
185730
+ }
185731
+ return Reflect.get(target, prop, receiver);
185732
+ }
185733
+ });
185734
+ }
185735
+ function sanitizeToolForGemini(tool2) {
185736
+ if (!tool2.parameters) return tool2;
185737
+ return { ...tool2, parameters: wrapSchemaForGemini(tool2.parameters) };
185738
+ }
185739
+ function isGeminiRouted(ctx) {
185740
+ const effective = ctx.payload.proxyModel ?? ctx.resolvedModel ?? ctx.payload.model;
185741
+ if (!effective) return true;
185742
+ const normalized = effective.toLowerCase();
185743
+ if (normalized.includes("gemini")) return true;
185744
+ if (!normalized.includes("/")) return true;
185745
+ return false;
185746
+ }
185747
+
185748
+ // mcp/shared.ts
185749
+ function getHttpStatus(err) {
185750
+ if (typeof err !== "object" || err === null || !("status" in err)) return void 0;
185751
+ return typeof err.status === "number" ? err.status : void 0;
185752
+ }
185753
+ var tool = (toolDef) => toolDef;
185754
+ var handleToolSuccess = (data) => {
185755
+ const text = typeof data === "string" ? data : encode4(data);
185756
+ return {
185757
+ content: [{ type: "text", text }]
185758
+ };
185759
+ };
185760
+ var handleToolError = (error62) => {
185761
+ const errorMessage = error62 instanceof Error ? error62.message : String(error62);
185762
+ return {
185763
+ content: [
185764
+ {
185765
+ type: "text",
185766
+ text: `Error: ${errorMessage}`
185767
+ }
185768
+ ],
185769
+ isError: true
185770
+ };
185771
+ };
185772
+ var execute = (fn2, toolName) => {
185773
+ const _fn = async (params) => {
185774
+ try {
185775
+ const result = await fn2(params);
185776
+ return handleToolSuccess(result);
185777
+ } catch (error62) {
185778
+ const errorMessage = error62 instanceof Error ? error62.message : String(error62);
185779
+ const prefix = toolName ? `[${toolName}]` : "tool";
185780
+ log.info(`${prefix} error: ${errorMessage}`);
185781
+ log.debug(`${prefix} params: ${formatJsonValue(params)}`);
185782
+ return handleToolError(error62);
185783
+ }
185784
+ };
185785
+ return _fn;
185786
+ };
185787
+ var addTools = (ctx, server, tools) => {
185788
+ const shouldSanitize = isGeminiRouted(ctx);
185789
+ for (const tool2 of tools) {
185790
+ server.addTool(shouldSanitize ? sanitizeToolForGemini(tool2) : tool2);
185791
+ }
185792
+ return server;
185793
+ };
185794
+
185795
+ // mcp/shell.ts
185796
+ var ShellParams = type({
185797
+ command: "string",
185798
+ // advisory and unread — no code path consumes it. kept in the schema only
185799
+ // because it nudges the model to state intent before running a command;
185800
+ // REQUIRING it cost a full rejected turn every time one was omitted. #1140.
185801
+ "description?": "string",
185802
+ "timeout?": type.number.describe(
185803
+ "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."
185804
+ ),
185805
+ "working_directory?": "string",
185806
+ "background?": "boolean"
185807
+ });
185808
+ var detectedSandboxMethod;
185809
+ var usernsProbeError;
185810
+ function getSandboxMethod() {
185811
+ return detectSandboxMethod();
185812
+ }
185813
+ function detectSandboxMethod() {
185814
+ if (detectedSandboxMethod !== void 0) {
185815
+ return detectedSandboxMethod;
185816
+ }
185817
+ if (process.env.CI !== "true") {
185818
+ detectedSandboxMethod = "none";
185819
+ log.debug("sandbox disabled (CI !== true)");
185820
+ return "none";
185821
+ }
185822
+ try {
185823
+ const result = spawnSync6("unshare", ["--pid", "--fork", "--mount-proc", "true"], {
185824
+ timeout: 5e3,
185825
+ stdio: "ignore"
185826
+ });
185827
+ if (result.status === 0) {
185828
+ detectedSandboxMethod = "unshare";
185829
+ log.debug("PID namespace isolation enabled (unprivileged unshare)");
185830
+ return "unshare";
185831
+ }
185832
+ } catch {
185833
+ }
185834
+ try {
185835
+ const result = spawnSync6("sudo", ["unshare", "--pid", "--fork", "--mount-proc", "true"], {
185836
+ timeout: 5e3,
185837
+ stdio: "ignore"
185838
+ });
185839
+ if (result.status === 0) {
185840
+ detectedSandboxMethod = "sudo-unshare";
185841
+ log.debug("PID namespace isolation enabled (sudo unshare)");
185842
+ return "sudo-unshare";
185843
+ }
185844
+ } catch {
185845
+ }
185846
+ try {
185847
+ const result = spawnSync6(
185848
+ "unshare",
185849
+ [
185850
+ "--user",
185851
+ "--map-root-user",
185852
+ "--pid",
185853
+ "--fork",
185854
+ "--mount",
185855
+ "setpriv",
185856
+ "--no-new-privs",
185857
+ "--bounding-set",
185858
+ "-all",
185859
+ "--inh-caps",
185860
+ "-all",
185861
+ "--ambient-caps",
185862
+ "-all",
185863
+ "true"
185864
+ ],
185865
+ { timeout: 5e3, stdio: ["ignore", "ignore", "pipe"] }
185866
+ );
185867
+ if (result.status === 0) {
185868
+ detectedSandboxMethod = "userns-unshare";
185869
+ log.debug("PID namespace isolation enabled (unprivileged userns unshare)");
185870
+ return "userns-unshare";
185871
+ }
185872
+ usernsProbeError = result.stderr?.toString().trim() || result.error?.message;
185873
+ } catch {
185874
+ }
185875
+ detectedSandboxMethod = "none";
185876
+ log.info("PID namespace isolation not available");
185877
+ return "none";
185878
+ }
185879
+ var PROC_CLEANUP = "umount /proc 2>/dev/null; umount /proc 2>/dev/null; mount -t proc proc /proc 2>/dev/null;";
185880
+ var SOCKET_CLEANUP = [
185881
+ "/var/run/docker.sock",
185882
+ "/run/docker.sock",
185883
+ "/var/run/podman/podman.sock",
185884
+ "/run/podman/podman.sock",
185885
+ "/run/containerd/containerd.sock",
185886
+ "/var/run/crio/crio.sock"
185887
+ ].map((path4) => `mount --bind /dev/null ${path4} 2>/dev/null;`).join(" ");
185888
+ function buildFsMounts(repoDir) {
185889
+ const escaped = repoDir.replace(/'/g, "'\\''");
185890
+ return [
185891
+ `mkdir -p /var/lib/pullfrog 2>/dev/null;`,
185892
+ `mount -t tmpfs tmpfs /var/lib/pullfrog 2>/dev/null;`,
185893
+ `[ -n "$RUNNER_TEMP" ] && [ -d "$RUNNER_TEMP/_runner_file_commands" ] && mount -t tmpfs tmpfs "$RUNNER_TEMP/_runner_file_commands" 2>/dev/null;`,
185894
+ `[ -e '${escaped}/.git' ] && mount --bind '${escaped}/.git' '${escaped}/.git' 2>/dev/null && mount -o remount,bind,ro '${escaped}/.git' 2>/dev/null;`,
185895
+ // the corepack shim dir sits FIRST on PATH (utils/packageManager.ts) and
185896
+ // PATH survives filterEnv, so a writable pm-bin lets sandboxed code plant
185897
+ // a `git`/`pnpm` that a later UNSANDBOXED spawn resolves with the parent
185898
+ // env. read-only closes that for every sandboxed command at once.
185899
+ //
185900
+ // the path is interpolated HERE rather than read as `$PULLFROG_TEMP_DIR`
185901
+ // inside the sandbox: that name is not on the `filterEnv` safe list, so in
185902
+ // restricted mode it expands to empty and the mount silently no-ops in the
185903
+ // one tier it exists to protect. `$RUNNER_TEMP` above is safe only because
185904
+ // `RUNNER_` is an allowed prefix.
185905
+ shimMount()
185906
+ ].filter(Boolean).join(" ");
185907
+ }
185908
+ function shimMount() {
185909
+ const tempDir = process.env.PULLFROG_TEMP_DIR;
185910
+ if (!tempDir) return "";
185911
+ const dir = join10(tempDir, "pm-bin").replace(/'/g, "'\\''");
185912
+ return `[ -d '${dir}' ] && mount --bind '${dir}' '${dir}' 2>/dev/null && mount -o remount,bind,ro '${dir}' 2>/dev/null;`;
185913
+ }
185914
+ var _repoRoot;
185915
+ function resolveRepoRoot() {
185916
+ if (_repoRoot) return _repoRoot;
185917
+ const fromEnv = process.env.GITHUB_WORKSPACE;
185918
+ if (fromEnv) {
185919
+ _repoRoot = fromEnv;
185920
+ return _repoRoot;
185921
+ }
185922
+ try {
185923
+ _repoRoot = spawnSync6("git", ["rev-parse", "--show-toplevel"], {
185924
+ cwd: process.cwd(),
185925
+ stdio: ["ignore", "pipe", "ignore"],
185926
+ encoding: "utf-8"
185927
+ }).stdout?.trim();
185928
+ } catch {
185929
+ }
185930
+ if (!_repoRoot) _repoRoot = process.cwd();
185931
+ return _repoRoot;
185932
+ }
185933
+ function spawnShell(params) {
185934
+ const spawnOpts = { env: params.env, cwd: params.cwd, stdio: params.stdio, detached: true };
185935
+ const sandboxMethod = detectSandboxMethod();
185936
+ const ci = process.env.CI === "true";
185937
+ if (ci && sandboxMethod === "none") {
185938
+ throw new Error(
185939
+ "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"
185940
+ );
185941
+ }
185942
+ const repoRoot = resolveRepoRoot();
185943
+ const fsMounts = buildFsMounts(repoRoot);
185944
+ if (sandboxMethod === "unshare") {
185945
+ return spawn3(
185946
+ "unshare",
185947
+ [
185948
+ "--pid",
185949
+ "--fork",
185950
+ "--mount-proc",
185951
+ "bash",
185952
+ "-c",
185953
+ `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} ${params.command}`
185954
+ ],
185955
+ spawnOpts
185956
+ );
185957
+ }
185958
+ if (sandboxMethod === "userns-unshare") {
185959
+ const escaped = params.command.replace(/'/g, "'\\''");
185960
+ return spawn3(
185961
+ "unshare",
185962
+ [
185963
+ "--user",
185964
+ "--map-root-user",
185965
+ "--pid",
185966
+ "--fork",
185967
+ "--mount",
185968
+ "bash",
185969
+ "-c",
185970
+ `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec setpriv --no-new-privs --bounding-set -all --inh-caps -all --ambient-caps -all bash -c '${escaped}'`
185971
+ ],
185972
+ spawnOpts
185973
+ );
185974
+ }
185975
+ if (sandboxMethod === "sudo-unshare") {
185976
+ const envArgs = [];
185977
+ for (const [k2, v] of Object.entries(params.env)) {
185978
+ if (v !== void 0) {
185979
+ envArgs.push(`${k2}=${v}`);
185980
+ }
185981
+ }
185982
+ const username = userInfo2().username;
185983
+ const pathRestore = 'export PATH="${SANDBOX_PATH:-$PATH}"; ';
185984
+ const escaped = (pathRestore + params.command).replace(/'/g, "'\\''");
185985
+ envArgs.push(`SANDBOX_PATH=${params.env.PATH ?? ""}`);
185986
+ return spawn3(
185987
+ "sudo",
185988
+ [
185989
+ "env",
185990
+ ...envArgs,
185991
+ "unshare",
185992
+ "--pid",
185993
+ "--fork",
185994
+ "--mount-proc",
185995
+ "bash",
185996
+ "-c",
185997
+ `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec su -p -s /bin/bash ${username} -c '${escaped}'`
185998
+ ],
185999
+ { ...spawnOpts, env: {} }
186000
+ );
186001
+ }
186002
+ return spawn3("bash", ["-c", params.command], spawnOpts);
186003
+ }
186004
+ async function killProcessGroup(proc) {
186005
+ if (!proc.pid) return;
186006
+ try {
186007
+ process.kill(-proc.pid, "SIGTERM");
186008
+ await new Promise((r) => setTimeout(r, 200));
186009
+ process.kill(-proc.pid, "SIGKILL");
186010
+ } catch {
186011
+ try {
186012
+ proc.kill("SIGKILL");
186013
+ } catch {
186014
+ }
186015
+ }
186016
+ }
186017
+ function getTempDir() {
186018
+ const tempDir = process.env.PULLFROG_TEMP_DIR;
186019
+ if (!tempDir) {
186020
+ throw new Error("PULLFROG_TEMP_DIR not set");
186021
+ }
186022
+ return tempDir;
186023
+ }
186024
+ var MAX_OUTPUT_CHARS = 5e3;
186025
+ async function runSandboxed(params) {
186026
+ const proc = spawnShell({
186027
+ command: params.command,
186028
+ env: params.env,
186029
+ cwd: params.cwd,
186030
+ stdio: ["ignore", "pipe", "pipe"]
186031
+ });
186032
+ const stdout = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
186033
+ const stderr = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
186034
+ let timedOut = false, exited = false, spawnError = "";
186035
+ proc.stdout?.on("data", (chunk) => {
186036
+ const text = chunk.toString();
186037
+ stdout.append(text);
186038
+ params.onStdout?.(text);
186039
+ });
186040
+ proc.stderr?.on("data", (chunk) => {
186041
+ const text = chunk.toString();
186042
+ stderr.append(text);
186043
+ params.onStderr?.(text);
186044
+ });
186045
+ const timeoutId = setTimeout(async () => {
186046
+ if (!exited) {
186047
+ timedOut = true;
186048
+ await killProcessGroup(proc);
186049
+ }
186050
+ }, params.timeout);
186051
+ const exitCode = await new Promise((resolve3) => {
186052
+ const done = (code) => {
186053
+ exited = true;
186054
+ clearTimeout(timeoutId);
186055
+ resolve3(code);
186056
+ };
186057
+ proc.on("exit", done);
186058
+ proc.on("error", (err) => {
186059
+ spawnError = err.message;
186060
+ done(null);
186061
+ });
186062
+ });
186063
+ proc.stdout?.destroy();
186064
+ proc.stderr?.destroy();
186065
+ const outText = stdout.toString();
186066
+ const errText = stderr.toString();
186067
+ let output2 = errText ? outText ? `${outText}
186068
+ ${errText}` : errText : outText;
186069
+ if (spawnError) output2 = output2 ? `${output2}
186070
+ ${spawnError}` : spawnError;
186071
+ if (timedOut)
186072
+ output2 = output2 ? `${output2}
186073
+ [timed out after ${params.timeout}ms]` : `[timed out after ${params.timeout}ms]`;
186074
+ return {
186075
+ output: output2.trim(),
186076
+ exitCode: exitCode ?? (timedOut ? 124 : -1),
186077
+ timedOut
186078
+ };
186079
+ }
186080
+ function capOutput(output2) {
186081
+ if (output2.length <= MAX_OUTPUT_CHARS) return output2;
186082
+ const fullPath = join10(getTempDir(), `shell-${randomUUID3().slice(0, 8)}.log`);
186083
+ writeFileSync8(fullPath, output2);
186084
+ const elided = output2.length - MAX_OUTPUT_CHARS;
186085
+ return `... [${elided} chars truncated; output saved to ${fullPath}] ...
186086
+ ${output2.slice(-MAX_OUTPUT_CHARS)}`;
186087
+ }
186088
+ function isGitCommand(command) {
186089
+ const trimmed = command.trim();
186090
+ if (trimmed === "git" || trimmed.startsWith("git ")) return true;
186091
+ if (trimmed.startsWith("sudo git")) return true;
186092
+ return /[;&|]\s*(?:sudo\s+)?git(?:\s|$)/.test(trimmed);
186093
+ }
186094
+ function ShellTool(ctx) {
186095
+ return tool({
186096
+ name: "shell",
186097
+ timeoutMs: 12e4,
186098
+ description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
186099
+
186100
+ Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
186101
+
186102
+ Use this tool to:
186103
+ - Run shell commands (ls, cat, grep, find, etc.)
186104
+ - Execute build tools (npm, pnpm, cargo, make, etc.)
186105
+ - Run tests and linters
186106
+
186107
+ 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.
186108
+
186109
+ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead.`,
186110
+ parameters: ShellParams,
186111
+ execute: execute(async (params) => {
186112
+ if (isGitCommand(params.command)) {
186113
+ throw new Error(
186114
+ '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'
186115
+ );
186116
+ }
186117
+ const timeout = Math.min(params.timeout ?? 3e4, 12e4);
186118
+ const cwd = params.working_directory ?? process.cwd();
186119
+ const env2 = resolveEnv(ctx.payload.shell === "enabled" ? "inherit" : "restricted");
186120
+ if (params.command.includes("agent-browser")) {
186121
+ const daemonError = ensureBrowserDaemon(ctx.toolState);
186122
+ if (daemonError) {
186123
+ return {
186124
+ output: `browser daemon unavailable: ${daemonError}`,
186125
+ exit_code: 1,
186126
+ timed_out: false
186127
+ };
186128
+ }
186129
+ const binDir = ctx.toolState.browserDaemon?.binDir;
186130
+ if (binDir) {
186131
+ env2.PATH = `${binDir}:${env2.PATH ?? ""}`;
186132
+ }
186133
+ }
186134
+ if (params.background) {
186135
+ const tempDir = getTempDir();
186136
+ const handle = `bg-${randomUUID3().slice(0, 8)}`;
186137
+ const outputPath = join10(tempDir, `${handle}.log`);
186138
+ const pidPath = join10(tempDir, `${handle}.pid`);
186139
+ const logFd = openSync(outputPath, "a");
186140
+ let proc;
186141
+ try {
186142
+ proc = spawnShell({
186143
+ command: params.command,
186144
+ env: env2,
186145
+ cwd,
186146
+ stdio: ["ignore", logFd, logFd]
186147
+ });
186148
+ } finally {
186149
+ closeSync(logFd);
186150
+ }
186151
+ if (!proc.pid) {
186152
+ throw new Error("failed to start background process");
186153
+ }
186154
+ proc.unref();
186155
+ writeFileSync8(pidPath, `${proc.pid}
186156
+ `);
186157
+ ctx.toolState.backgroundProcesses.set(handle, { pid: proc.pid, outputPath, pidPath });
186158
+ return {
186159
+ handle,
186160
+ outputPath,
186161
+ pidPath,
186162
+ message: `started background process ${handle} (pid ${proc.pid})`
186163
+ };
186164
+ }
186165
+ const result = await runSandboxed({ command: params.command, env: env2, cwd, timeout });
186166
+ if (result.exitCode !== 0) {
186167
+ log.info(`shell command failed with exit code ${result.exitCode}: ${params.command}`);
186168
+ if (result.output) log.info(`output: ${result.output}`);
186169
+ }
186170
+ return {
186171
+ output: capOutput(result.output),
186172
+ exit_code: result.exitCode,
186173
+ timed_out: result.timedOut
186174
+ };
186175
+ })
186176
+ });
186177
+ }
186178
+ var KillBackgroundParams = type({
186179
+ handle: type.string.describe("The handle of the background process to kill (e.g., bg-a1b2c3d4)")
186180
+ });
186181
+ function KillBackgroundTool(ctx) {
186182
+ return tool({
186183
+ name: "kill_background",
186184
+ mutates: true,
186185
+ description: `Kill a background process by its handle. Use this to stop dev servers or other long-running processes started with shell({ background: true }).`,
186186
+ parameters: KillBackgroundParams,
186187
+ execute: execute(async (params) => {
186188
+ const proc = ctx.toolState.backgroundProcesses.get(params.handle);
186189
+ if (!proc) {
186190
+ return {
186191
+ success: false,
186192
+ message: `no background process with handle ${params.handle}`
186193
+ };
186194
+ }
186195
+ try {
186196
+ process.kill(-proc.pid, "SIGTERM");
186197
+ } catch {
186198
+ }
186199
+ await sleep2(200);
186200
+ try {
186201
+ process.kill(-proc.pid, "SIGKILL");
186202
+ } catch {
186203
+ }
186204
+ ctx.toolState.backgroundProcesses.delete(params.handle);
186205
+ return {
186206
+ success: true,
186207
+ message: `killed background process ${params.handle} (pid ${proc.pid})`
186208
+ };
186209
+ })
186210
+ });
186211
+ }
186212
+
185259
186213
  // utils/lifecycle.ts
185260
186214
  function describeSetupFailure(failure2) {
185261
186215
  if (!failure2) return "";
@@ -185277,44 +186231,47 @@ ${failure2.output || "(empty)"}`;
185277
186231
  async function executeLifecycleHook(params) {
185278
186232
  if (!params.script) return {};
185279
186233
  log.info(`\xBB executing ${params.event} lifecycle hook...`);
186234
+ resolveTrustedGit();
185280
186235
  const preHookTrackedCount = params.normalizeWorkingTreeAfter ? (await runGitLines(["diff", "--name-only", "HEAD"])).length : 0;
185281
186236
  let result;
185282
186237
  try {
186238
+ let sandboxed;
185283
186239
  try {
185284
- const spawnResult = await spawn({
185285
- cmd: "bash",
185286
- args: ["-c", params.script],
185287
- env: process.env,
186240
+ sandboxed = await runSandboxed({
186241
+ command: params.script,
186242
+ env: resolveEnv(params.shell === "enabled" ? "inherit" : "restricted"),
186243
+ cwd: process.cwd(),
185288
186244
  timeout: LIFECYCLE_HOOK_TIMEOUT_MS,
185289
- activityTimeout: 0,
185290
186245
  onStdout: (chunk) => process.stdout.write(chunk),
185291
186246
  onStderr: (chunk) => process.stderr.write(chunk)
185292
186247
  });
185293
- if (spawnResult.exitCode !== 0) {
185294
- const output2 = (spawnResult.stderr || spawnResult.stdout).trim();
185295
- result = {
185296
- failure: { kind: "exit", output: output2, exitCode: spawnResult.exitCode },
185297
- warning: `lifecycle hook '${params.event}' failed with exit code ${spawnResult.exitCode}. output: ${output2 || "(empty)"}. retry the operation if the failure looks flaky (network blips, transient rate limits). do NOT retry if the script is broken (missing commands, syntax errors) or the error is persistent.`
185298
- };
185299
- } else {
185300
- log.info(`\xBB ${params.event} lifecycle hook completed successfully`);
185301
- result = {};
185302
- }
185303
186248
  } catch (err) {
185304
- const isTimeout = err instanceof SpawnTimeoutError && (err.code === SPAWN_TIMEOUT_CODE || err.code === SPAWN_ACTIVITY_TIMEOUT_CODE);
185305
- if (isTimeout) {
185306
- const minutes = Math.round(LIFECYCLE_HOOK_TIMEOUT_MS / 6e4);
185307
- result = {
185308
- failure: { kind: "timeout" },
185309
- warning: `lifecycle hook '${params.event}' timed out after ${minutes}min. do NOT retry \u2014 the script is likely hung or doing too much work. ask the repo owner to simplify the hook (e.g. move long-running work out of the hook, add caching, or split it).`
185310
- };
185311
- } else {
185312
- const msg = err instanceof Error ? err.message : String(err);
185313
- result = {
185314
- failure: { kind: "spawn", spawnError: msg },
185315
- warning: `lifecycle hook '${params.event}' failed to spawn: ${msg}. this is likely a transient failure \u2014 retry the operation.`
185316
- };
185317
- }
186249
+ const msg = err instanceof Error ? err.message : String(err);
186250
+ return {
186251
+ failure: { kind: "spawn", spawnError: msg },
186252
+ warning: `lifecycle hook '${params.event}' could not be sandboxed, so it did not run: ${msg}`
186253
+ };
186254
+ }
186255
+ if (sandboxed.timedOut) {
186256
+ const minutes = Math.round(LIFECYCLE_HOOK_TIMEOUT_MS / 6e4);
186257
+ result = {
186258
+ failure: { kind: "timeout" },
186259
+ 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).`
186260
+ };
186261
+ } else if (sandboxed.exitCode === -1) {
186262
+ result = {
186263
+ failure: { kind: "spawn", spawnError: sandboxed.output },
186264
+ warning: `lifecycle hook '${params.event}' failed to spawn: ${sandboxed.output}. this is likely a transient failure \u2014 retry the operation.`
186265
+ };
186266
+ } else if (sandboxed.exitCode !== 0) {
186267
+ const output2 = capOutput(sandboxed.output);
186268
+ result = {
186269
+ failure: { kind: "exit", output: output2, exitCode: sandboxed.exitCode },
186270
+ 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.`
186271
+ };
186272
+ } else {
186273
+ log.info(`\xBB ${params.event} lifecycle hook completed successfully`);
186274
+ result = {};
185318
186275
  }
185319
186276
  } finally {
185320
186277
  if (params.normalizeWorkingTreeAfter) {
@@ -185335,8 +186292,20 @@ async function normalizeWorkingTreeAfterHook(params) {
185335
186292
  await runGit(["restore", "--staged", "--worktree", "."]);
185336
186293
  log.info(`\xBB discarded ${trackedCount} tracked changes from ${params.event} hook`);
185337
186294
  }
186295
+ var trustedGit;
186296
+ function resolveTrustedGit() {
186297
+ if (trustedGit) return trustedGit;
186298
+ const resolved = spawnSync7("sh", ["-c", "command -v git"], { encoding: "utf-8" });
186299
+ trustedGit = resolved.stdout?.trim() || "git";
186300
+ return trustedGit;
186301
+ }
185338
186302
  async function runGit(args2) {
185339
- const result = await spawn({ cmd: "git", args: args2, env: process.env, activityTimeout: 0 });
186303
+ const result = await spawn({
186304
+ cmd: resolveTrustedGit(),
186305
+ args: args2,
186306
+ env: filterEnvForUntrustedCode(),
186307
+ activityTimeout: 0
186308
+ });
185340
186309
  if (result.exitCode !== 0) {
185341
186310
  throw new Error(
185342
186311
  `git ${args2.join(" ")} failed (exit ${result.exitCode}): ${result.stderr.trim() || "(no stderr)"}`
@@ -185456,7 +186425,7 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
185456
186425
 
185457
186426
  // yes/index.ts
185458
186427
  import { performance as performance8 } from "node:perf_hooks";
185459
- import { setTimeout as sleep2 } from "node:timers/promises";
186428
+ import { setTimeout as sleep3 } from "node:timers/promises";
185460
186429
 
185461
186430
  // node_modules/.pnpm/lru-cache@11.5.2/node_modules/lru-cache/dist/esm/node/index.min.js
185462
186431
  import { tracingChannel as G, channel as P } from "node:diagnostics_channel";
@@ -186250,7 +187219,7 @@ function _op(fn2, options) {
186250
187219
  );
186251
187220
  log2.info({ key, error: error62, attempt });
186252
187221
  }
186253
- await sleep2(delay2);
187222
+ await sleep3(delay2);
186254
187223
  }
186255
187224
  }
186256
187225
  }
@@ -186318,14 +187287,14 @@ function _op(fn2, options) {
186318
187287
  }
186319
187288
 
186320
187289
  // mcp/git.ts
186321
- import { randomUUID as randomUUID3 } from "node:crypto";
186322
- import { writeFileSync as writeFileSync8 } from "node:fs";
186323
- import { join as join12 } from "node:path";
187290
+ import { randomUUID as randomUUID4 } from "node:crypto";
187291
+ import { writeFileSync as writeFileSync9 } from "node:fs";
187292
+ import { join as join13 } from "node:path";
186324
187293
 
186325
187294
  // utils/apiCommit.ts
186326
187295
  import { execFileSync as execFileSync5 } from "node:child_process";
186327
187296
  import { lstat, readlink } from "node:fs/promises";
186328
- import { join as join10 } from "node:path";
187297
+ import { join as join11 } from "node:path";
186329
187298
  var GITHUB_API = "https://api.github.com";
186330
187299
  var MAX_BLOB_BYTES = 30 * 1024 * 1024;
186331
187300
  var BLOB_UPLOAD_CONCURRENCY = 8;
@@ -186390,7 +187359,7 @@ async function assertApiCommittable(files) {
186390
187359
  const root = getRepoRoot();
186391
187360
  const attrs = $(
186392
187361
  "git",
186393
- ["check-attr", "filter", "-z", "--", ...present.map((p) => join10(root, p))],
187362
+ ["check-attr", "filter", "-z", "--", ...present.map((p) => join11(root, p))],
186394
187363
  { log: false }
186395
187364
  );
186396
187365
  const parts = attrs.split("\0");
@@ -186402,7 +187371,7 @@ async function assertApiCommittable(files) {
186402
187371
  }
186403
187372
  }
186404
187373
  for (const path4 of present) {
186405
- const stat = await lstat(join10(root, path4));
187374
+ const stat = await lstat(join11(root, path4));
186406
187375
  if (stat.isDirectory()) {
186407
187376
  throw new Error(
186408
187377
  `'${path4}' is a directory (nested repository or submodule?) \u2014 signed commits only support files and symlinks.`
@@ -186411,7 +187380,7 @@ async function assertApiCommittable(files) {
186411
187380
  }
186412
187381
  }
186413
187382
  async function createBlobEntry(params) {
186414
- const absPath = join10(params.repoRoot, params.path);
187383
+ const absPath = join11(params.repoRoot, params.path);
186415
187384
  const stat = await lstat(absPath);
186416
187385
  if (stat.size > MAX_BLOB_BYTES) {
186417
187386
  throw new Error(
@@ -186591,7 +187560,7 @@ this usually means your local branch has commits that were never pushed \u2014 s
186591
187560
  var core5 = __toESM(require_core(), 1);
186592
187561
  import { createSign } from "node:crypto";
186593
187562
  import { rename, writeFile } from "node:fs/promises";
186594
- import { dirname as dirname3, join as join11 } from "node:path";
187563
+ import { dirname as dirname3, join as join12 } from "node:path";
186595
187564
 
186596
187565
  // node_modules/.pnpm/@octokit+plugin-throttling@11.0.5_@octokit+core@7.0.8/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
186597
187566
  var import_light = __toESM(require_light(), 1);
@@ -186963,7 +187932,7 @@ function lowercaseKeys(object5) {
186963
187932
  return newObj;
186964
187933
  }, {});
186965
187934
  }
186966
- function isPlainObject6(value2) {
187935
+ function isPlainObject7(value2) {
186967
187936
  if (typeof value2 !== "object" || value2 === null) return false;
186968
187937
  if (Object.prototype.toString.call(value2) !== "[object Object]") return false;
186969
187938
  const proto = Object.getPrototypeOf(value2);
@@ -186974,7 +187943,7 @@ function isPlainObject6(value2) {
186974
187943
  function mergeDeep(defaults, options) {
186975
187944
  const result = Object.assign({}, defaults);
186976
187945
  Object.keys(options).forEach((key) => {
186977
- if (isPlainObject6(options[key])) {
187946
+ if (isPlainObject7(options[key])) {
186978
187947
  if (!(key in defaults)) Object.assign(result, { [key]: options[key] });
186979
187948
  else result[key] = mergeDeep(defaults[key], options[key]);
186980
187949
  } else {
@@ -187261,7 +188230,7 @@ var NullObject = /* @__PURE__ */ (() => {
187261
188230
  return C;
187262
188231
  })();
187263
188232
  function parse5(header, options) {
187264
- const stopChar = options?.comma === true ? COMMA : 65536;
188233
+ const stopChar = options?.comma === true ? COMMA2 : 65536;
187265
188234
  const len = header.length;
187266
188235
  let index = skipOWS(header, options?.start ?? 0, len);
187267
188236
  const valueStart = index;
@@ -187279,7 +188248,7 @@ var SEMI = 59;
187279
188248
  var EQ = 61;
187280
188249
  var DQUOTE = 34;
187281
188250
  var BSLASH = 92;
187282
- var COMMA = 44;
188251
+ var COMMA2 = 44;
187283
188252
  function parseParameters(header, type2, index, len, stopChar) {
187284
188253
  const parameters = new NullObject();
187285
188254
  parameter: while (index < len) {
@@ -187738,7 +188707,7 @@ var defaults_default = {
187738
188707
  "user-agent": `octokit-request.js/${VERSION3} ${getUserAgent2()}`
187739
188708
  }
187740
188709
  };
187741
- function isPlainObject7(value2) {
188710
+ function isPlainObject8(value2) {
187742
188711
  if (typeof value2 !== "object" || value2 === null) return false;
187743
188712
  if (Object.prototype.toString.call(value2) !== "[object Object]") return false;
187744
188713
  const proto = Object.getPrototypeOf(value2);
@@ -187756,7 +188725,7 @@ async function fetchWrapper(requestOptions) {
187756
188725
  }
187757
188726
  const log3 = requestOptions.request?.log || console;
187758
188727
  const parseSuccessResponseBody = requestOptions.request?.parseSuccessResponseBody !== false;
187759
- const body = isPlainObject7(requestOptions.body) || Array.isArray(requestOptions.body) ? JSONStringify(requestOptions.body) : requestOptions.body;
188728
+ const body = isPlainObject8(requestOptions.body) || Array.isArray(requestOptions.body) ? JSONStringify(requestOptions.body) : requestOptions.body;
187760
188729
  const requestHeaders = Object.fromEntries(
187761
188730
  Object.entries(requestOptions.headers).map(([name, value2]) => [
187762
188731
  name,
@@ -191061,7 +192030,7 @@ function getGitHubUsageSummary() {
191061
192030
  }
191062
192031
  async function writeGitHubUsageSummaryToFile(path4) {
191063
192032
  const summary2 = getGitHubUsageSummary();
191064
- const tmpPath = join11(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
192033
+ const tmpPath = join12(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
191065
192034
  await writeFile(tmpPath, JSON.stringify(summary2));
191066
192035
  await rename(tmpPath, path4);
191067
192036
  }
@@ -191168,531 +192137,6 @@ function resolveRepoCtx(ctx, repo) {
191168
192137
  };
191169
192138
  }
191170
192139
 
191171
- // node_modules/.pnpm/@toon-format+toon@1.4.0/node_modules/@toon-format/toon/dist/index.mjs
191172
- var LIST_ITEM_MARKER = "-";
191173
- var LIST_ITEM_PREFIX = "- ";
191174
- var COMMA2 = ",";
191175
- var PIPE = "|";
191176
- var DOT = ".";
191177
- var NULL_LITERAL = "null";
191178
- var TRUE_LITERAL = "true";
191179
- var FALSE_LITERAL = "false";
191180
- var BACKSLASH = "\\";
191181
- var DOUBLE_QUOTE = '"';
191182
- var TAB = " ";
191183
- var DELIMITERS = {
191184
- comma: COMMA2,
191185
- tab: TAB,
191186
- pipe: PIPE
191187
- };
191188
- var DEFAULT_DELIMITER = DELIMITERS.comma;
191189
- function escapeString(value2) {
191190
- return value2.replace(/\\/g, `${BACKSLASH}${BACKSLASH}`).replace(/"/g, `${BACKSLASH}${DOUBLE_QUOTE}`).replace(/\n/g, `${BACKSLASH}n`).replace(/\r/g, `${BACKSLASH}r`).replace(/\t/g, `${BACKSLASH}t`);
191191
- }
191192
- function isBooleanOrNullLiteral(token) {
191193
- return token === TRUE_LITERAL || token === FALSE_LITERAL || token === NULL_LITERAL;
191194
- }
191195
- function normalizeValue(value2) {
191196
- if (value2 === null) return null;
191197
- if (typeof value2 === "string" || typeof value2 === "boolean") return value2;
191198
- if (typeof value2 === "number") {
191199
- if (Object.is(value2, -0)) return 0;
191200
- if (!Number.isFinite(value2)) return null;
191201
- return value2;
191202
- }
191203
- if (typeof value2 === "bigint") {
191204
- if (value2 >= Number.MIN_SAFE_INTEGER && value2 <= Number.MAX_SAFE_INTEGER) return Number(value2);
191205
- return value2.toString();
191206
- }
191207
- if (value2 instanceof Date) return value2.toISOString();
191208
- if (Array.isArray(value2)) return value2.map(normalizeValue);
191209
- if (value2 instanceof Set) return Array.from(value2).map(normalizeValue);
191210
- if (value2 instanceof Map) return Object.fromEntries(Array.from(value2, ([k2, v]) => [String(k2), normalizeValue(v)]));
191211
- if (isPlainObject8(value2)) {
191212
- const normalized = {};
191213
- for (const key in value2) if (Object.prototype.hasOwnProperty.call(value2, key)) normalized[key] = normalizeValue(value2[key]);
191214
- return normalized;
191215
- }
191216
- return null;
191217
- }
191218
- function isJsonPrimitive(value2) {
191219
- return value2 === null || typeof value2 === "string" || typeof value2 === "number" || typeof value2 === "boolean";
191220
- }
191221
- function isJsonArray(value2) {
191222
- return Array.isArray(value2);
191223
- }
191224
- function isJsonObject4(value2) {
191225
- return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
191226
- }
191227
- function isEmptyObject2(value2) {
191228
- return Object.keys(value2).length === 0;
191229
- }
191230
- function isPlainObject8(value2) {
191231
- if (value2 === null || typeof value2 !== "object") return false;
191232
- const prototype = Object.getPrototypeOf(value2);
191233
- return prototype === null || prototype === Object.prototype;
191234
- }
191235
- function isArrayOfPrimitives(value2) {
191236
- return value2.length === 0 || value2.every((item) => isJsonPrimitive(item));
191237
- }
191238
- function isArrayOfArrays(value2) {
191239
- return value2.length === 0 || value2.every((item) => isJsonArray(item));
191240
- }
191241
- function isArrayOfObjects(value2) {
191242
- return value2.length === 0 || value2.every((item) => isJsonObject4(item));
191243
- }
191244
- function isValidUnquotedKey(key) {
191245
- return /^[A-Z_][\w.]*$/i.test(key);
191246
- }
191247
- function isIdentifierSegment(key) {
191248
- return /^[A-Z_]\w*$/i.test(key);
191249
- }
191250
- function isSafeUnquoted(value2, delimiter2 = DEFAULT_DELIMITER) {
191251
- if (!value2) return false;
191252
- if (value2 !== value2.trim()) return false;
191253
- if (isBooleanOrNullLiteral(value2) || isNumericLike(value2)) return false;
191254
- if (value2.includes(":")) return false;
191255
- if (value2.includes('"') || value2.includes("\\")) return false;
191256
- if (/[[\]{}]/.test(value2)) return false;
191257
- if (/[\n\r\t]/.test(value2)) return false;
191258
- if (value2.includes(delimiter2)) return false;
191259
- if (value2.startsWith(LIST_ITEM_MARKER)) return false;
191260
- return true;
191261
- }
191262
- function isNumericLike(value2) {
191263
- return /^-?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i.test(value2) || /^0\d+$/.test(value2);
191264
- }
191265
- var QUOTED_KEY_MARKER = Symbol("quotedKey");
191266
- function tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, flattenDepth) {
191267
- if (options.keyFolding !== "safe") return;
191268
- if (!isJsonObject4(value2)) return;
191269
- const { segments, tail, leafValue } = collectSingleKeyChain(key, value2, flattenDepth ?? options.flattenDepth);
191270
- if (segments.length < 2) return;
191271
- if (!segments.every((seg) => isIdentifierSegment(seg))) return;
191272
- const foldedKey = buildFoldedKey(segments);
191273
- const absolutePath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
191274
- if (siblings.includes(foldedKey)) return;
191275
- if (rootLiteralKeys && rootLiteralKeys.has(absolutePath)) return;
191276
- return {
191277
- foldedKey,
191278
- remainder: tail,
191279
- leafValue,
191280
- segmentCount: segments.length
191281
- };
191282
- }
191283
- function collectSingleKeyChain(startKey, startValue, maxDepth) {
191284
- const segments = [startKey];
191285
- let currentValue = startValue;
191286
- while (segments.length < maxDepth) {
191287
- if (!isJsonObject4(currentValue)) break;
191288
- const keys = Object.keys(currentValue);
191289
- if (keys.length !== 1) break;
191290
- const nextKey = keys[0];
191291
- const nextValue = currentValue[nextKey];
191292
- segments.push(nextKey);
191293
- currentValue = nextValue;
191294
- }
191295
- if (!isJsonObject4(currentValue) || isEmptyObject2(currentValue)) return {
191296
- segments,
191297
- tail: void 0,
191298
- leafValue: currentValue
191299
- };
191300
- return {
191301
- segments,
191302
- tail: currentValue,
191303
- leafValue: currentValue
191304
- };
191305
- }
191306
- function buildFoldedKey(segments) {
191307
- return segments.join(DOT);
191308
- }
191309
- function encodePrimitive(value2, delimiter2) {
191310
- if (value2 === null) return NULL_LITERAL;
191311
- if (typeof value2 === "boolean") return String(value2);
191312
- if (typeof value2 === "number") return String(value2);
191313
- return encodeStringLiteral(value2, delimiter2);
191314
- }
191315
- function encodeStringLiteral(value2, delimiter2 = DEFAULT_DELIMITER) {
191316
- if (isSafeUnquoted(value2, delimiter2)) return value2;
191317
- return `${DOUBLE_QUOTE}${escapeString(value2)}${DOUBLE_QUOTE}`;
191318
- }
191319
- function encodeKey(key) {
191320
- if (isValidUnquotedKey(key)) return key;
191321
- return `${DOUBLE_QUOTE}${escapeString(key)}${DOUBLE_QUOTE}`;
191322
- }
191323
- function encodeAndJoinPrimitives(values, delimiter2 = DEFAULT_DELIMITER) {
191324
- return values.map((v) => encodePrimitive(v, delimiter2)).join(delimiter2);
191325
- }
191326
- function formatHeader(length, options) {
191327
- const key = options?.key;
191328
- const fields = options?.fields;
191329
- const delimiter2 = options?.delimiter ?? COMMA2;
191330
- let header = "";
191331
- if (key) header += encodeKey(key);
191332
- header += `[${length}${delimiter2 !== DEFAULT_DELIMITER ? delimiter2 : ""}]`;
191333
- if (fields) {
191334
- const quotedFields = fields.map((f) => encodeKey(f));
191335
- header += `{${quotedFields.join(delimiter2)}}`;
191336
- }
191337
- header += ":";
191338
- return header;
191339
- }
191340
- function* encodeJsonValue(value2, options, depth) {
191341
- if (isJsonPrimitive(value2)) {
191342
- const encodedPrimitive = encodePrimitive(value2, options.delimiter);
191343
- if (encodedPrimitive !== "") yield encodedPrimitive;
191344
- return;
191345
- }
191346
- if (isJsonArray(value2)) yield* encodeArrayLines(void 0, value2, depth, options);
191347
- else if (isJsonObject4(value2)) yield* encodeObjectLines(value2, depth, options);
191348
- }
191349
- function* encodeObjectLines(value2, depth, options, rootLiteralKeys, pathPrefix, remainingDepth) {
191350
- const keys = Object.keys(value2);
191351
- if (depth === 0 && !rootLiteralKeys) rootLiteralKeys = new Set(keys.filter((k2) => k2.includes(".")));
191352
- const effectiveFlattenDepth = remainingDepth ?? options.flattenDepth;
191353
- for (const [key, val] of Object.entries(value2)) yield* encodeKeyValuePairLines(key, val, depth, options, keys, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
191354
- }
191355
- function* encodeKeyValuePairLines(key, value2, depth, options, siblings, rootLiteralKeys, pathPrefix, flattenDepth) {
191356
- const currentPath = pathPrefix ? `${pathPrefix}${DOT}${key}` : key;
191357
- const effectiveFlattenDepth = flattenDepth ?? options.flattenDepth;
191358
- if (options.keyFolding === "safe" && siblings) {
191359
- const foldResult = tryFoldKeyChain(key, value2, siblings, options, rootLiteralKeys, pathPrefix, effectiveFlattenDepth);
191360
- if (foldResult) {
191361
- const { foldedKey, remainder, leafValue, segmentCount } = foldResult;
191362
- const encodedFoldedKey = encodeKey(foldedKey);
191363
- if (remainder === void 0) {
191364
- if (isJsonPrimitive(leafValue)) {
191365
- yield indentedLine(depth, `${encodedFoldedKey}: ${encodePrimitive(leafValue, options.delimiter)}`, options.indent);
191366
- return;
191367
- } else if (isJsonArray(leafValue)) {
191368
- yield* encodeArrayLines(foldedKey, leafValue, depth, options);
191369
- return;
191370
- } else if (isJsonObject4(leafValue) && isEmptyObject2(leafValue)) {
191371
- yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
191372
- return;
191373
- }
191374
- }
191375
- if (isJsonObject4(remainder)) {
191376
- yield indentedLine(depth, `${encodedFoldedKey}:`, options.indent);
191377
- const remainingDepth = effectiveFlattenDepth - segmentCount;
191378
- const foldedPath = pathPrefix ? `${pathPrefix}${DOT}${foldedKey}` : foldedKey;
191379
- yield* encodeObjectLines(remainder, depth + 1, options, rootLiteralKeys, foldedPath, remainingDepth);
191380
- return;
191381
- }
191382
- }
191383
- }
191384
- const encodedKey = encodeKey(key);
191385
- if (isJsonPrimitive(value2)) yield indentedLine(depth, `${encodedKey}: ${encodePrimitive(value2, options.delimiter)}`, options.indent);
191386
- else if (isJsonArray(value2)) yield* encodeArrayLines(key, value2, depth, options);
191387
- else if (isJsonObject4(value2)) {
191388
- yield indentedLine(depth, `${encodedKey}:`, options.indent);
191389
- if (!isEmptyObject2(value2)) yield* encodeObjectLines(value2, depth + 1, options, rootLiteralKeys, currentPath, effectiveFlattenDepth);
191390
- }
191391
- }
191392
- function* encodeArrayLines(key, value2, depth, options) {
191393
- if (value2.length === 0) {
191394
- yield indentedLine(depth, formatHeader(0, {
191395
- key,
191396
- delimiter: options.delimiter
191397
- }), options.indent);
191398
- return;
191399
- }
191400
- if (isArrayOfPrimitives(value2)) {
191401
- yield indentedLine(depth, encodeInlineArrayLine(value2, options.delimiter, key), options.indent);
191402
- return;
191403
- }
191404
- if (isArrayOfArrays(value2)) {
191405
- if (value2.every((arr) => isArrayOfPrimitives(arr))) {
191406
- yield* encodeArrayOfArraysAsListItemsLines(key, value2, depth, options);
191407
- return;
191408
- }
191409
- }
191410
- if (isArrayOfObjects(value2)) {
191411
- const header = extractTabularHeader(value2);
191412
- if (header) yield* encodeArrayOfObjectsAsTabularLines(key, value2, header, depth, options);
191413
- else yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
191414
- return;
191415
- }
191416
- yield* encodeMixedArrayAsListItemsLines(key, value2, depth, options);
191417
- }
191418
- function* encodeArrayOfArraysAsListItemsLines(prefix, values, depth, options) {
191419
- yield indentedLine(depth, formatHeader(values.length, {
191420
- key: prefix,
191421
- delimiter: options.delimiter
191422
- }), options.indent);
191423
- for (const arr of values) if (isArrayOfPrimitives(arr)) {
191424
- const arrayLine = encodeInlineArrayLine(arr, options.delimiter);
191425
- yield indentedListItem(depth + 1, arrayLine, options.indent);
191426
- }
191427
- }
191428
- function encodeInlineArrayLine(values, delimiter2, prefix) {
191429
- const header = formatHeader(values.length, {
191430
- key: prefix,
191431
- delimiter: delimiter2
191432
- });
191433
- const joinedValue = encodeAndJoinPrimitives(values, delimiter2);
191434
- if (values.length === 0) return header;
191435
- return `${header} ${joinedValue}`;
191436
- }
191437
- function* encodeArrayOfObjectsAsTabularLines(prefix, rows, header, depth, options) {
191438
- yield indentedLine(depth, formatHeader(rows.length, {
191439
- key: prefix,
191440
- fields: header,
191441
- delimiter: options.delimiter
191442
- }), options.indent);
191443
- yield* writeTabularRowsLines(rows, header, depth + 1, options);
191444
- }
191445
- function extractTabularHeader(rows) {
191446
- if (rows.length === 0) return;
191447
- const firstRow = rows[0];
191448
- const firstKeys = Object.keys(firstRow);
191449
- if (firstKeys.length === 0) return;
191450
- if (isTabularArray(rows, firstKeys)) return firstKeys;
191451
- }
191452
- function isTabularArray(rows, header) {
191453
- for (const row of rows) {
191454
- if (Object.keys(row).length !== header.length) return false;
191455
- for (const key of header) {
191456
- if (!(key in row)) return false;
191457
- if (!isJsonPrimitive(row[key])) return false;
191458
- }
191459
- }
191460
- return true;
191461
- }
191462
- function* writeTabularRowsLines(rows, header, depth, options) {
191463
- for (const row of rows) yield indentedLine(depth, encodeAndJoinPrimitives(header.map((key) => row[key]), options.delimiter), options.indent);
191464
- }
191465
- function* encodeMixedArrayAsListItemsLines(prefix, items, depth, options) {
191466
- yield indentedLine(depth, formatHeader(items.length, {
191467
- key: prefix,
191468
- delimiter: options.delimiter
191469
- }), options.indent);
191470
- for (const item of items) yield* encodeListItemValueLines(item, depth + 1, options);
191471
- }
191472
- function* encodeObjectAsListItemLines(obj, depth, options) {
191473
- if (isEmptyObject2(obj)) {
191474
- yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
191475
- return;
191476
- }
191477
- const entries = Object.entries(obj);
191478
- if (entries.length === 1) {
191479
- const [key, value2] = entries[0];
191480
- if (isJsonArray(value2) && isArrayOfObjects(value2)) {
191481
- const header = extractTabularHeader(value2);
191482
- if (header) {
191483
- yield indentedListItem(depth, formatHeader(value2.length, {
191484
- key,
191485
- fields: header,
191486
- delimiter: options.delimiter
191487
- }), options.indent);
191488
- yield* writeTabularRowsLines(value2, header, depth + 1, options);
191489
- return;
191490
- }
191491
- }
191492
- }
191493
- yield indentedLine(depth, LIST_ITEM_MARKER, options.indent);
191494
- yield* encodeObjectLines(obj, depth + 1, options);
191495
- }
191496
- function* encodeListItemValueLines(value2, depth, options) {
191497
- if (isJsonPrimitive(value2)) yield indentedListItem(depth, encodePrimitive(value2, options.delimiter), options.indent);
191498
- else if (isJsonArray(value2)) if (isArrayOfPrimitives(value2)) yield indentedListItem(depth, encodeInlineArrayLine(value2, options.delimiter), options.indent);
191499
- else {
191500
- yield indentedListItem(depth, formatHeader(value2.length, { delimiter: options.delimiter }), options.indent);
191501
- for (const item of value2) yield* encodeListItemValueLines(item, depth + 1, options);
191502
- }
191503
- else if (isJsonObject4(value2)) yield* encodeObjectAsListItemLines(value2, depth, options);
191504
- }
191505
- function indentedLine(depth, content, indentSize) {
191506
- return " ".repeat(indentSize * depth) + content;
191507
- }
191508
- function indentedListItem(depth, content, indentSize) {
191509
- return indentedLine(depth, LIST_ITEM_PREFIX + content, indentSize);
191510
- }
191511
- function encode4(input2, options) {
191512
- return Array.from(encodeLines(input2, options)).join("\n");
191513
- }
191514
- function encodeLines(input2, options) {
191515
- return encodeJsonValue(normalizeValue(input2), resolveOptions(options), 0);
191516
- }
191517
- function resolveOptions(options) {
191518
- return {
191519
- indent: options?.indent ?? 2,
191520
- delimiter: options?.delimiter ?? DEFAULT_DELIMITER,
191521
- keyFolding: options?.keyFolding ?? "off",
191522
- flattenDepth: options?.flattenDepth ?? Number.POSITIVE_INFINITY
191523
- };
191524
- }
191525
-
191526
- // mcp/geminiSanitizer.ts
191527
- function parseStringEnumBranch(item) {
191528
- if (!item || typeof item !== "object") return null;
191529
- const record3 = item;
191530
- if (Array.isArray(record3.enum)) {
191531
- const strings = record3.enum.filter((v) => typeof v === "string");
191532
- return strings.length === record3.enum.length && strings.length > 0 ? { values: strings } : null;
191533
- }
191534
- if (typeof record3.const === "string") {
191535
- return { values: [record3.const] };
191536
- }
191537
- return null;
191538
- }
191539
- function collapseStringUnion(branches) {
191540
- const values = [];
191541
- for (const item of branches) {
191542
- const parsed2 = parseStringEnumBranch(item);
191543
- if (!parsed2) return null;
191544
- values.push(...parsed2.values);
191545
- }
191546
- if (values.length === 0) return null;
191547
- return { type: "string", enum: [...new Set(values)] };
191548
- }
191549
- function sanitizeForGemini(schema2) {
191550
- if (!schema2 || typeof schema2 !== "object") return schema2;
191551
- if (Array.isArray(schema2)) return schema2.map(sanitizeForGemini);
191552
- const source = schema2;
191553
- if (Array.isArray(source.enum) && typeof source.type !== "string") {
191554
- const allStrings = source.enum.every((v) => typeof v === "string");
191555
- if (allStrings) {
191556
- const result = { type: "string", enum: source.enum };
191557
- if (typeof source.description === "string") result.description = source.description;
191558
- return result;
191559
- }
191560
- }
191561
- for (const unionKey of ["anyOf", "oneOf"]) {
191562
- const branches = source[unionKey];
191563
- if (Array.isArray(branches) && branches.length > 0) {
191564
- const collapsed = collapseStringUnion(branches);
191565
- if (collapsed) {
191566
- const result = { ...collapsed };
191567
- if (typeof source.description === "string") result.description = source.description;
191568
- return result;
191569
- }
191570
- }
191571
- }
191572
- if (Array.isArray(source.anyOf) || Array.isArray(source.oneOf)) {
191573
- const result = {};
191574
- if (Array.isArray(source.anyOf)) result.anyOf = source.anyOf.map(sanitizeForGemini);
191575
- if (Array.isArray(source.oneOf)) result.oneOf = source.oneOf.map(sanitizeForGemini);
191576
- return result;
191577
- }
191578
- const sanitized = {};
191579
- for (const [key, value2] of Object.entries(source)) {
191580
- if (key === "$schema") continue;
191581
- if (key === "$defs") {
191582
- sanitized.definitions = sanitizeForGemini(value2);
191583
- continue;
191584
- }
191585
- sanitized[key] = sanitizeForGemini(value2);
191586
- }
191587
- return sanitized;
191588
- }
191589
- function wrapJsonSchemaProducer(producer) {
191590
- return new Proxy(producer, {
191591
- get(target, prop, receiver) {
191592
- const value2 = Reflect.get(target, prop, receiver);
191593
- if ((prop === "input" || prop === "output") && typeof value2 === "function") {
191594
- const fn2 = value2;
191595
- return (...args2) => sanitizeForGemini(fn2.apply(target, args2));
191596
- }
191597
- return value2;
191598
- }
191599
- });
191600
- }
191601
- function wrapStandard(standard) {
191602
- return new Proxy(standard, {
191603
- get(target, prop, receiver) {
191604
- if (prop === "jsonSchema") {
191605
- const value2 = Reflect.get(target, prop, receiver);
191606
- if (value2 && typeof value2 === "object") {
191607
- return wrapJsonSchemaProducer(value2);
191608
- }
191609
- return value2;
191610
- }
191611
- return Reflect.get(target, prop, receiver);
191612
- }
191613
- });
191614
- }
191615
- function wrapSchemaForGemini(schema2) {
191616
- return new Proxy(schema2, {
191617
- get(target, prop, receiver) {
191618
- if (prop === "~standard") {
191619
- const value2 = Reflect.get(target, prop, receiver);
191620
- if (value2 && typeof value2 === "object") {
191621
- return wrapStandard(value2);
191622
- }
191623
- return value2;
191624
- }
191625
- if (prop === "toJsonSchema") {
191626
- const method = Reflect.get(target, prop, receiver);
191627
- if (typeof method === "function") {
191628
- return () => sanitizeForGemini(method.call(target));
191629
- }
191630
- return method;
191631
- }
191632
- return Reflect.get(target, prop, receiver);
191633
- }
191634
- });
191635
- }
191636
- function sanitizeToolForGemini(tool2) {
191637
- if (!tool2.parameters) return tool2;
191638
- return { ...tool2, parameters: wrapSchemaForGemini(tool2.parameters) };
191639
- }
191640
- function isGeminiRouted(ctx) {
191641
- const effective = ctx.payload.proxyModel ?? ctx.resolvedModel ?? ctx.payload.model;
191642
- if (!effective) return true;
191643
- const normalized = effective.toLowerCase();
191644
- if (normalized.includes("gemini")) return true;
191645
- if (!normalized.includes("/")) return true;
191646
- return false;
191647
- }
191648
-
191649
- // mcp/shared.ts
191650
- function getHttpStatus(err) {
191651
- if (typeof err !== "object" || err === null || !("status" in err)) return void 0;
191652
- return typeof err.status === "number" ? err.status : void 0;
191653
- }
191654
- var tool = (toolDef) => toolDef;
191655
- var handleToolSuccess = (data) => {
191656
- const text = typeof data === "string" ? data : encode4(data);
191657
- return {
191658
- content: [{ type: "text", text }]
191659
- };
191660
- };
191661
- var handleToolError = (error62) => {
191662
- const errorMessage = error62 instanceof Error ? error62.message : String(error62);
191663
- return {
191664
- content: [
191665
- {
191666
- type: "text",
191667
- text: `Error: ${errorMessage}`
191668
- }
191669
- ],
191670
- isError: true
191671
- };
191672
- };
191673
- var execute = (fn2, toolName) => {
191674
- const _fn = async (params) => {
191675
- try {
191676
- const result = await fn2(params);
191677
- return handleToolSuccess(result);
191678
- } catch (error62) {
191679
- const errorMessage = error62 instanceof Error ? error62.message : String(error62);
191680
- const prefix = toolName ? `[${toolName}]` : "tool";
191681
- log.info(`${prefix} error: ${errorMessage}`);
191682
- log.debug(`${prefix} params: ${formatJsonValue(params)}`);
191683
- return handleToolError(error62);
191684
- }
191685
- };
191686
- return _fn;
191687
- };
191688
- var addTools = (ctx, server, tools) => {
191689
- const shouldSanitize = isGeminiRouted(ctx);
191690
- for (const tool2 of tools) {
191691
- server.addTool(shouldSanitize ? sanitizeToolForGemini(tool2) : tool2);
191692
- }
191693
- return server;
191694
- };
191695
-
191696
192140
  // mcp/git.ts
191697
192141
  function getPushDestination(branch, storedDest, cwd) {
191698
192142
  if (storedDest && storedDest.localBranch === branch) {
@@ -191818,7 +192262,8 @@ async function runPrepushHook(ctx, retryTool) {
191818
192262
  if (!ctx.prepushScript) return false;
191819
192263
  const prepushHook = await executeLifecycleHook({
191820
192264
  event: "prepush",
191821
- script: ctx.prepushScript
192265
+ script: ctx.prepushScript,
192266
+ shell: ctx.payload.shell
191822
192267
  });
191823
192268
  if (prepushHook.failure) {
191824
192269
  ctx.toolState.prepushFailureCount += 1;
@@ -192213,8 +192658,8 @@ function countAhead(head, base, cwd) {
192213
192658
  function spillGitOutput(params) {
192214
192659
  const tempDir = process.env.PULLFROG_TEMP_DIR;
192215
192660
  if (!tempDir) throw new Error("PULLFROG_TEMP_DIR not set");
192216
- const outputPath = join12(tempDir, `git-${params.command}-${randomUUID3().slice(0, 8)}.txt`);
192217
- writeFileSync8(outputPath, params.output);
192661
+ const outputPath = join13(tempDir, `git-${params.command}-${randomUUID4().slice(0, 8)}.txt`);
192662
+ writeFileSync9(outputPath, params.output);
192218
192663
  const previewByLines = params.output.split("\n").slice(0, OVERFLOW_PREVIEW_LINES).join("\n");
192219
192664
  const preview = previewByLines.length <= OVERFLOW_PREVIEW_MAX_CHARS ? previewByLines : `${previewByLines.slice(0, OVERFLOW_PREVIEW_MAX_CHARS)}\u2026`;
192220
192665
  log.info(
@@ -193740,9 +194185,9 @@ async function reportReviewNodeId(ctx, params) {
193740
194185
  import { execFileSync as execFileSync6, execSync as execSync2 } from "node:child_process";
193741
194186
  import { mkdtempSync, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
193742
194187
  import { tmpdir as tmpdir2 } from "node:os";
193743
- import { join as join13 } from "node:path";
194188
+ import { join as join14 } from "node:path";
193744
194189
  function createTempDirectory() {
193745
- const sharedTempDir = mkdtempSync(join13(tmpdir2(), "pullfrog-"));
194190
+ const sharedTempDir = mkdtempSync(join14(tmpdir2(), "pullfrog-"));
193746
194191
  process.env.PULLFROG_TEMP_DIR = sharedTempDir;
193747
194192
  log.info(`\xBB created temp dir at ${sharedTempDir}`);
193748
194193
  return sharedTempDir;
@@ -193787,13 +194232,13 @@ function wipeRunnerLeakSurface() {
193787
194232
  return [];
193788
194233
  }
193789
194234
  };
193790
- const fileCommandsDir = join13(runnerTemp, "_runner_file_commands");
194235
+ const fileCommandsDir = join14(runnerTemp, "_runner_file_commands");
193791
194236
  for (const entry of listDir(fileCommandsDir)) {
193792
- tryUnlink(join13(fileCommandsDir, entry));
194237
+ tryUnlink(join14(fileCommandsDir, entry));
193793
194238
  }
193794
194239
  for (const entry of listDir(runnerTemp)) {
193795
194240
  if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
193796
- tryUnlink(join13(runnerTemp, entry));
194241
+ tryUnlink(join14(runnerTemp, entry));
193797
194242
  }
193798
194243
  }
193799
194244
  if (wiped.length > 0) {
@@ -194268,6 +194713,7 @@ async function checkoutPrBranch(pr, params) {
194268
194713
  const postCheckoutHook = await executeLifecycleHook({
194269
194714
  event: "post-checkout",
194270
194715
  script: params.postCheckoutScript,
194716
+ shell: params.shell,
194271
194717
  normalizeWorkingTreeAfter: true
194272
194718
  });
194273
194719
  return { hookWarning: postCheckoutHook.warning };
@@ -194349,11 +194795,11 @@ function CheckoutPrTool(ctx) {
194349
194795
  headSha: checkoutSha
194350
194796
  });
194351
194797
  if (incremental.status === "ok") {
194352
- incrementalDiffPath = join14(
194798
+ incrementalDiffPath = join15(
194353
194799
  tempDir,
194354
194800
  `pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
194355
194801
  );
194356
- writeFileSync9(incrementalDiffPath, incremental.diff);
194802
+ writeFileSync10(incrementalDiffPath, incremental.diff);
194357
194803
  log.info(
194358
194804
  `\xBB incremental diff computed (${incremental.diff.length} bytes) \u2192 ${incrementalDiffPath}`
194359
194805
  );
@@ -194366,8 +194812,8 @@ function CheckoutPrTool(ctx) {
194366
194812
  const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
194367
194813
  log.debug(`formatted diff preview (first 100 lines):
194368
194814
  ${diffPreview}`);
194369
- const diffPath = join14(tempDir, `pr-${pull_number}-${headShort}.diff`);
194370
- writeFileSync9(diffPath, formatResult.content);
194815
+ const diffPath = join15(tempDir, `pr-${pull_number}-${headShort}.diff`);
194816
+ writeFileSync10(diffPath, formatResult.content);
194371
194817
  log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
194372
194818
  primary.diffCoverage = createDiffCoverageState({
194373
194819
  diffPath,
@@ -194524,8 +194970,8 @@ ${dirty}`
194524
194970
  }
194525
194971
 
194526
194972
  // mcp/checkSuite.ts
194527
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync10 } from "node:fs";
194528
- import { join as join15 } from "node:path";
194973
+ import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync11 } from "node:fs";
194974
+ import { join as join16 } from "node:path";
194529
194975
  var GetCheckSuiteLogs = type({
194530
194976
  check_suite_id: type.number.describe("the id from check_suite.id")
194531
194977
  });
@@ -194621,7 +195067,7 @@ function GetCheckSuiteLogsTool(ctx) {
194621
195067
  if (!tempDir) {
194622
195068
  throw new Error("PULLFROG_TEMP_DIR not set");
194623
195069
  }
194624
- const logsDir = join15(tempDir, "ci-logs");
195070
+ const logsDir = join16(tempDir, "ci-logs");
194625
195071
  mkdirSync8(logsDir, { recursive: true });
194626
195072
  const jobResults = [];
194627
195073
  for (const run of failedRuns) {
@@ -194648,8 +195094,8 @@ function GetCheckSuiteLogsTool(ctx) {
194648
195094
  );
194649
195095
  }
194650
195096
  const logsText = await logsResult.text();
194651
- const logPath = join15(logsDir, `job-${job.id}.log`);
194652
- writeFileSync10(logPath, logsText);
195097
+ const logPath = join16(logsDir, `job-${job.id}.log`);
195098
+ writeFileSync11(logPath, logsText);
194653
195099
  const analysis = analyzeLog(logsText, 80);
194654
195100
  const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
194655
195101
  jobResults.push({
@@ -194697,8 +195143,8 @@ function GetCheckSuiteLogsTool(ctx) {
194697
195143
  }
194698
195144
 
194699
195145
  // mcp/commitInfo.ts
194700
- import { writeFileSync as writeFileSync11 } from "node:fs";
194701
- import { join as join16 } from "node:path";
195146
+ import { writeFileSync as writeFileSync12 } from "node:fs";
195147
+ import { join as join17 } from "node:path";
194702
195148
  var CommitInfo = type({
194703
195149
  sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
194704
195150
  });
@@ -194722,8 +195168,8 @@ function CommitInfoTool(ctx) {
194722
195168
  "PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
194723
195169
  );
194724
195170
  }
194725
- const diffFile = join16(tempDir, `commit-${sha.slice(0, 7)}.diff`);
194726
- writeFileSync11(diffFile, formatResult.content);
195171
+ const diffFile = join17(tempDir, `commit-${sha.slice(0, 7)}.diff`);
195172
+ writeFileSync12(diffFile, formatResult.content);
194727
195173
  log.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
194728
195174
  return {
194729
195175
  sha: data.sha,
@@ -194860,13 +195306,13 @@ import { performance as performance9 } from "node:perf_hooks";
194860
195306
 
194861
195307
  // prep/installNodeDependencies.ts
194862
195308
  import { existsSync as existsSync8 } from "node:fs";
194863
- import { join as join18 } from "node:path";
195309
+ import { join as join19 } from "node:path";
194864
195310
 
194865
195311
  // utils/packageManager.ts
194866
195312
  var import_semver2 = __toESM(require_semver2(), 1);
194867
195313
  import { existsSync as existsSync7 } from "node:fs";
194868
195314
  import { mkdir, readFile as readFile2 } from "node:fs/promises";
194869
- import { delimiter, join as join17 } from "node:path";
195315
+ import { delimiter, join as join18 } from "node:path";
194870
195316
  var SUPPORTED_NAMES = ["npm", "pnpm", "yarn", "bun"];
194871
195317
  var COREPACK_MANAGED = ["pnpm", "yarn"];
194872
195318
  function isSupported(name) {
@@ -194904,7 +195350,7 @@ function parseDevEnginesField(field) {
194904
195350
  };
194905
195351
  }
194906
195352
  async function resolvePackageManagerSpec(cwd) {
194907
- const pkgPath = join17(cwd, "package.json");
195353
+ const pkgPath = join18(cwd, "package.json");
194908
195354
  if (!existsSync7(pkgPath)) return null;
194909
195355
  let pkg;
194910
195356
  try {
@@ -194963,7 +195409,7 @@ async function currentVersion(name) {
194963
195409
  return result.stdout.trim();
194964
195410
  }
194965
195411
  function packageManagerBinDir(tmpdir3) {
194966
- return join17(tmpdir3, "pm-bin");
195412
+ return join18(tmpdir3, "pm-bin");
194967
195413
  }
194968
195414
  async function ensurePackageManager(params) {
194969
195415
  const spec = params.spec;
@@ -195026,7 +195472,7 @@ async function installFallback(name, installSpec) {
195026
195472
  });
195027
195473
  if (result.exitCode !== 0) return result.stderr || `failed to install ${name}`;
195028
195474
  if (name === "deno") {
195029
- const denoPath = join17(process.env.HOME || "", ".deno", "bin");
195475
+ const denoPath = join18(process.env.HOME || "", ".deno", "bin");
195030
195476
  process.env.PATH = `${denoPath}:${process.env.PATH}`;
195031
195477
  }
195032
195478
  log.info(`\xBB installed ${name}`);
@@ -195059,7 +195505,7 @@ async function isCommandAvailable2(command) {
195059
195505
  var installNodeDependencies = {
195060
195506
  name: "installNodeDependencies",
195061
195507
  shouldRun: () => {
195062
- const packageJsonPath = join18(process.cwd(), "package.json");
195508
+ const packageJsonPath = join19(process.cwd(), "package.json");
195063
195509
  return existsSync8(packageJsonPath);
195064
195510
  },
195065
195511
  run: async (options) => {
@@ -195155,12 +195601,12 @@ ${errorMessage}`]
195155
195601
 
195156
195602
  // prep/installPythonDependencies.ts
195157
195603
  import { existsSync as existsSync9, readFileSync as readFileSync5 } from "node:fs";
195158
- import { join as join19 } from "node:path";
195604
+ import { join as join20 } from "node:path";
195159
195605
  function declaresBuildSystem(path4) {
195160
195606
  return /^\s*\[\s*build-system\s*\]/m.test(readFileSync5(path4, "utf8"));
195161
195607
  }
195162
195608
  function configApplies(config3, cwd) {
195163
- const path4 = join19(cwd, config3.file);
195609
+ const path4 = join20(cwd, config3.file);
195164
195610
  return existsSync9(path4) && (config3.applies?.(path4) ?? true);
195165
195611
  }
195166
195612
  var PYTHON_CONFIGS = [
@@ -195484,411 +195930,6 @@ function AwaitDependencyInstallationTool(ctx) {
195484
195930
  // mcp/gh.ts
195485
195931
  import { mkdirSync as mkdirSync9 } from "node:fs";
195486
195932
  import { join as join21 } from "node:path";
195487
-
195488
- // mcp/shell.ts
195489
- import { spawn as spawn3, spawnSync as spawnSync6 } from "node:child_process";
195490
- import { randomUUID as randomUUID4 } from "node:crypto";
195491
- import { closeSync, openSync, writeFileSync as writeFileSync12 } from "node:fs";
195492
- import { userInfo as userInfo2 } from "node:os";
195493
- import { join as join20 } from "node:path";
195494
- import { setTimeout as sleep3 } from "node:timers/promises";
195495
- var ShellParams = type({
195496
- command: "string",
195497
- // advisory and unread — no code path consumes it. kept in the schema only
195498
- // because it nudges the model to state intent before running a command;
195499
- // REQUIRING it cost a full rejected turn every time one was omitted. #1140.
195500
- "description?": "string",
195501
- "timeout?": type.number.describe(
195502
- "Timeout in MILLISECONDS (not seconds). Default 30000 (30s), max 120000 (2m). e.g. timeout: 180000 for 3 minutes; timeout: 180 means 180ms and will kill the process almost immediately."
195503
- ),
195504
- "working_directory?": "string",
195505
- "background?": "boolean"
195506
- });
195507
- var detectedSandboxMethod;
195508
- var usernsProbeError;
195509
- function getSandboxMethod() {
195510
- return detectSandboxMethod();
195511
- }
195512
- function detectSandboxMethod() {
195513
- if (detectedSandboxMethod !== void 0) {
195514
- return detectedSandboxMethod;
195515
- }
195516
- if (process.env.CI !== "true") {
195517
- detectedSandboxMethod = "none";
195518
- log.debug("sandbox disabled (CI !== true)");
195519
- return "none";
195520
- }
195521
- try {
195522
- const result = spawnSync6("unshare", ["--pid", "--fork", "--mount-proc", "true"], {
195523
- timeout: 5e3,
195524
- stdio: "ignore"
195525
- });
195526
- if (result.status === 0) {
195527
- detectedSandboxMethod = "unshare";
195528
- log.debug("PID namespace isolation enabled (unprivileged unshare)");
195529
- return "unshare";
195530
- }
195531
- } catch {
195532
- }
195533
- try {
195534
- const result = spawnSync6("sudo", ["unshare", "--pid", "--fork", "--mount-proc", "true"], {
195535
- timeout: 5e3,
195536
- stdio: "ignore"
195537
- });
195538
- if (result.status === 0) {
195539
- detectedSandboxMethod = "sudo-unshare";
195540
- log.debug("PID namespace isolation enabled (sudo unshare)");
195541
- return "sudo-unshare";
195542
- }
195543
- } catch {
195544
- }
195545
- try {
195546
- const result = spawnSync6(
195547
- "unshare",
195548
- [
195549
- "--user",
195550
- "--map-root-user",
195551
- "--pid",
195552
- "--fork",
195553
- "--mount",
195554
- "setpriv",
195555
- "--no-new-privs",
195556
- "--bounding-set",
195557
- "-all",
195558
- "--inh-caps",
195559
- "-all",
195560
- "--ambient-caps",
195561
- "-all",
195562
- "true"
195563
- ],
195564
- { timeout: 5e3, stdio: ["ignore", "ignore", "pipe"] }
195565
- );
195566
- if (result.status === 0) {
195567
- detectedSandboxMethod = "userns-unshare";
195568
- log.debug("PID namespace isolation enabled (unprivileged userns unshare)");
195569
- return "userns-unshare";
195570
- }
195571
- usernsProbeError = result.stderr?.toString().trim() || result.error?.message;
195572
- } catch {
195573
- }
195574
- detectedSandboxMethod = "none";
195575
- log.info("PID namespace isolation not available");
195576
- return "none";
195577
- }
195578
- var PROC_CLEANUP = "umount /proc 2>/dev/null; umount /proc 2>/dev/null; mount -t proc proc /proc 2>/dev/null;";
195579
- var SOCKET_CLEANUP = [
195580
- "/var/run/docker.sock",
195581
- "/run/docker.sock",
195582
- "/var/run/podman/podman.sock",
195583
- "/run/podman/podman.sock",
195584
- "/run/containerd/containerd.sock",
195585
- "/var/run/crio/crio.sock"
195586
- ].map((path4) => `mount --bind /dev/null ${path4} 2>/dev/null;`).join(" ");
195587
- function buildFsMounts(repoDir) {
195588
- const escaped = repoDir.replace(/'/g, "'\\''");
195589
- return [
195590
- `mkdir -p /var/lib/pullfrog 2>/dev/null;`,
195591
- `mount -t tmpfs tmpfs /var/lib/pullfrog 2>/dev/null;`,
195592
- `[ -n "$RUNNER_TEMP" ] && [ -d "$RUNNER_TEMP/_runner_file_commands" ] && mount -t tmpfs tmpfs "$RUNNER_TEMP/_runner_file_commands" 2>/dev/null;`,
195593
- `[ -e '${escaped}/.git' ] && mount --bind '${escaped}/.git' '${escaped}/.git' 2>/dev/null && mount -o remount,bind,ro '${escaped}/.git' 2>/dev/null;`
195594
- ].join(" ");
195595
- }
195596
- var _repoRoot;
195597
- function resolveRepoRoot() {
195598
- if (_repoRoot) return _repoRoot;
195599
- const fromEnv = process.env.GITHUB_WORKSPACE;
195600
- if (fromEnv) {
195601
- _repoRoot = fromEnv;
195602
- return _repoRoot;
195603
- }
195604
- try {
195605
- _repoRoot = spawnSync6("git", ["rev-parse", "--show-toplevel"], {
195606
- cwd: process.cwd(),
195607
- stdio: ["ignore", "pipe", "ignore"],
195608
- encoding: "utf-8"
195609
- }).stdout?.trim();
195610
- } catch {
195611
- }
195612
- if (!_repoRoot) _repoRoot = process.cwd();
195613
- return _repoRoot;
195614
- }
195615
- function spawnShell(params) {
195616
- const spawnOpts = { env: params.env, cwd: params.cwd, stdio: params.stdio, detached: true };
195617
- const sandboxMethod = detectSandboxMethod();
195618
- const ci = process.env.CI === "true";
195619
- if (ci && sandboxMethod === "none") {
195620
- throw new Error(
195621
- "PID-namespace isolation is required in CI but unavailable: unprivileged unshare, sudo unshare, and userns-nested unshare all failed. " + (usernsProbeError ? `userns probe said: ${usernsProbeError}. ` : "") + "on a containerized / Kubernetes self-hosted runner the cause is usually one of: the pod's seccomp profile blocks user-namespace creation (Pod Security Standards baseline/restricted); the pod runs as root without CAP_SETFCAP, which the kernel requires to map uid 0; or the node kernel disallows unprivileged user namespaces (Ubuntu >= 23.10, Bottlerocket, Talos). elsewhere, check that `unshare` and `setpriv` (util-linux >= 2.31, which is what added --ambient-caps) are on PATH. see https://docs.pullfrog.com/security#self-hosted-runners"
195622
- );
195623
- }
195624
- const repoRoot = resolveRepoRoot();
195625
- const fsMounts = buildFsMounts(repoRoot);
195626
- if (sandboxMethod === "unshare") {
195627
- return spawn3(
195628
- "unshare",
195629
- [
195630
- "--pid",
195631
- "--fork",
195632
- "--mount-proc",
195633
- "bash",
195634
- "-c",
195635
- `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} ${params.command}`
195636
- ],
195637
- spawnOpts
195638
- );
195639
- }
195640
- if (sandboxMethod === "userns-unshare") {
195641
- const escaped = params.command.replace(/'/g, "'\\''");
195642
- return spawn3(
195643
- "unshare",
195644
- [
195645
- "--user",
195646
- "--map-root-user",
195647
- "--pid",
195648
- "--fork",
195649
- "--mount",
195650
- "bash",
195651
- "-c",
195652
- `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec setpriv --no-new-privs --bounding-set -all --inh-caps -all --ambient-caps -all bash -c '${escaped}'`
195653
- ],
195654
- spawnOpts
195655
- );
195656
- }
195657
- if (sandboxMethod === "sudo-unshare") {
195658
- const envArgs = [];
195659
- for (const [k2, v] of Object.entries(params.env)) {
195660
- if (v !== void 0) {
195661
- envArgs.push(`${k2}=${v}`);
195662
- }
195663
- }
195664
- const username = userInfo2().username;
195665
- const pathRestore = 'export PATH="${SANDBOX_PATH:-$PATH}"; ';
195666
- const escaped = (pathRestore + params.command).replace(/'/g, "'\\''");
195667
- envArgs.push(`SANDBOX_PATH=${params.env.PATH ?? ""}`);
195668
- return spawn3(
195669
- "sudo",
195670
- [
195671
- "env",
195672
- ...envArgs,
195673
- "unshare",
195674
- "--pid",
195675
- "--fork",
195676
- "--mount-proc",
195677
- "bash",
195678
- "-c",
195679
- `${PROC_CLEANUP} ${SOCKET_CLEANUP} ${fsMounts} exec su -p -s /bin/bash ${username} -c '${escaped}'`
195680
- ],
195681
- { ...spawnOpts, env: {} }
195682
- );
195683
- }
195684
- return spawn3("bash", ["-c", params.command], spawnOpts);
195685
- }
195686
- async function killProcessGroup(proc) {
195687
- if (!proc.pid) return;
195688
- try {
195689
- process.kill(-proc.pid, "SIGTERM");
195690
- await new Promise((r) => setTimeout(r, 200));
195691
- process.kill(-proc.pid, "SIGKILL");
195692
- } catch {
195693
- try {
195694
- proc.kill("SIGKILL");
195695
- } catch {
195696
- }
195697
- }
195698
- }
195699
- function getTempDir() {
195700
- const tempDir = process.env.PULLFROG_TEMP_DIR;
195701
- if (!tempDir) {
195702
- throw new Error("PULLFROG_TEMP_DIR not set");
195703
- }
195704
- return tempDir;
195705
- }
195706
- var MAX_OUTPUT_CHARS = 5e3;
195707
- async function runSandboxed(params) {
195708
- const proc = spawnShell({
195709
- command: params.command,
195710
- env: params.env,
195711
- cwd: params.cwd,
195712
- stdio: ["ignore", "pipe", "pipe"]
195713
- });
195714
- const stdout = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
195715
- const stderr = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
195716
- let timedOut = false, exited = false, spawnError = "";
195717
- proc.stdout?.on("data", (chunk) => {
195718
- stdout.append(chunk.toString());
195719
- });
195720
- proc.stderr?.on("data", (chunk) => {
195721
- stderr.append(chunk.toString());
195722
- });
195723
- const timeoutId = setTimeout(async () => {
195724
- if (!exited) {
195725
- timedOut = true;
195726
- await killProcessGroup(proc);
195727
- }
195728
- }, params.timeout);
195729
- const exitCode = await new Promise((resolve3) => {
195730
- const done = (code) => {
195731
- exited = true;
195732
- clearTimeout(timeoutId);
195733
- resolve3(code);
195734
- };
195735
- proc.on("exit", done);
195736
- proc.on("error", (err) => {
195737
- spawnError = err.message;
195738
- done(null);
195739
- });
195740
- });
195741
- proc.stdout?.destroy();
195742
- proc.stderr?.destroy();
195743
- const outText = stdout.toString();
195744
- const errText = stderr.toString();
195745
- let output2 = errText ? outText ? `${outText}
195746
- ${errText}` : errText : outText;
195747
- if (spawnError) output2 = output2 ? `${output2}
195748
- ${spawnError}` : spawnError;
195749
- if (timedOut)
195750
- output2 = output2 ? `${output2}
195751
- [timed out after ${params.timeout}ms]` : `[timed out after ${params.timeout}ms]`;
195752
- return {
195753
- output: output2.trim(),
195754
- exitCode: exitCode ?? (timedOut ? 124 : -1),
195755
- timedOut
195756
- };
195757
- }
195758
- function capOutput(output2) {
195759
- if (output2.length <= MAX_OUTPUT_CHARS) return output2;
195760
- const fullPath = join20(getTempDir(), `shell-${randomUUID4().slice(0, 8)}.log`);
195761
- writeFileSync12(fullPath, output2);
195762
- const elided = output2.length - MAX_OUTPUT_CHARS;
195763
- return `... [${elided} chars truncated; output saved to ${fullPath}] ...
195764
- ${output2.slice(-MAX_OUTPUT_CHARS)}`;
195765
- }
195766
- function isGitCommand(command) {
195767
- const trimmed = command.trim();
195768
- if (trimmed === "git" || trimmed.startsWith("git ")) return true;
195769
- if (trimmed.startsWith("sudo git")) return true;
195770
- return /[;&|]\s*(?:sudo\s+)?git(?:\s|$)/.test(trimmed);
195771
- }
195772
- function ShellTool(ctx) {
195773
- return tool({
195774
- name: "shell",
195775
- timeoutMs: 12e4,
195776
- description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
195777
-
195778
- Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
195779
-
195780
- Use this tool to:
195781
- - Run shell commands (ls, cat, grep, find, etc.)
195782
- - Execute build tools (npm, pnpm, cargo, make, etc.)
195783
- - Run tests and linters
195784
-
195785
- Output is capped at ${MAX_OUTPUT_CHARS} chars: if exceeded, only the tail is returned and the captured output is saved to a tempfile (path included in the response). Re-read the tempfile with cat/tail/grep when you need more.
195786
-
195787
- Do NOT use this tool for git commands \u2014 use the dedicated git tools instead.`,
195788
- parameters: ShellParams,
195789
- execute: execute(async (params) => {
195790
- if (isGitCommand(params.command)) {
195791
- throw new Error(
195792
- 'git commands are not allowed in the shell tool. use the dedicated git tools instead:\n- git: local operations, e.g. git({ command: "log", args: ["--oneline"] }) \u2014 `command` is the subcommand ONLY\n- push_branch: push to remote (handles authentication)\n- git_fetch: fetch from remote (handles authentication)\n- checkout_pr: check out PR branches'
195793
- );
195794
- }
195795
- const timeout = Math.min(params.timeout ?? 3e4, 12e4);
195796
- const cwd = params.working_directory ?? process.cwd();
195797
- const env2 = resolveEnv(ctx.payload.shell === "enabled" ? "inherit" : "restricted");
195798
- if (params.command.includes("agent-browser")) {
195799
- const daemonError = ensureBrowserDaemon(ctx.toolState);
195800
- if (daemonError) {
195801
- return {
195802
- output: `browser daemon unavailable: ${daemonError}`,
195803
- exit_code: 1,
195804
- timed_out: false
195805
- };
195806
- }
195807
- const binDir = ctx.toolState.browserDaemon?.binDir;
195808
- if (binDir) {
195809
- env2.PATH = `${binDir}:${env2.PATH ?? ""}`;
195810
- }
195811
- }
195812
- if (params.background) {
195813
- const tempDir = getTempDir();
195814
- const handle = `bg-${randomUUID4().slice(0, 8)}`;
195815
- const outputPath = join20(tempDir, `${handle}.log`);
195816
- const pidPath = join20(tempDir, `${handle}.pid`);
195817
- const logFd = openSync(outputPath, "a");
195818
- let proc;
195819
- try {
195820
- proc = spawnShell({
195821
- command: params.command,
195822
- env: env2,
195823
- cwd,
195824
- stdio: ["ignore", logFd, logFd]
195825
- });
195826
- } finally {
195827
- closeSync(logFd);
195828
- }
195829
- if (!proc.pid) {
195830
- throw new Error("failed to start background process");
195831
- }
195832
- proc.unref();
195833
- writeFileSync12(pidPath, `${proc.pid}
195834
- `);
195835
- ctx.toolState.backgroundProcesses.set(handle, { pid: proc.pid, outputPath, pidPath });
195836
- return {
195837
- handle,
195838
- outputPath,
195839
- pidPath,
195840
- message: `started background process ${handle} (pid ${proc.pid})`
195841
- };
195842
- }
195843
- const result = await runSandboxed({ command: params.command, env: env2, cwd, timeout });
195844
- if (result.exitCode !== 0) {
195845
- log.info(`shell command failed with exit code ${result.exitCode}: ${params.command}`);
195846
- if (result.output) log.info(`output: ${result.output}`);
195847
- }
195848
- return {
195849
- output: capOutput(result.output),
195850
- exit_code: result.exitCode,
195851
- timed_out: result.timedOut
195852
- };
195853
- })
195854
- });
195855
- }
195856
- var KillBackgroundParams = type({
195857
- handle: type.string.describe("The handle of the background process to kill (e.g., bg-a1b2c3d4)")
195858
- });
195859
- function KillBackgroundTool(ctx) {
195860
- return tool({
195861
- name: "kill_background",
195862
- mutates: true,
195863
- description: `Kill a background process by its handle. Use this to stop dev servers or other long-running processes started with shell({ background: true }).`,
195864
- parameters: KillBackgroundParams,
195865
- execute: execute(async (params) => {
195866
- const proc = ctx.toolState.backgroundProcesses.get(params.handle);
195867
- if (!proc) {
195868
- return {
195869
- success: false,
195870
- message: `no background process with handle ${params.handle}`
195871
- };
195872
- }
195873
- try {
195874
- process.kill(-proc.pid, "SIGTERM");
195875
- } catch {
195876
- }
195877
- await sleep3(200);
195878
- try {
195879
- process.kill(-proc.pid, "SIGKILL");
195880
- } catch {
195881
- }
195882
- ctx.toolState.backgroundProcesses.delete(params.handle);
195883
- return {
195884
- success: true,
195885
- message: `killed background process ${params.handle} (pid ${proc.pid})`
195886
- };
195887
- })
195888
- });
195889
- }
195890
-
195891
- // mcp/gh.ts
195892
195933
  var GH_TIMEOUT_MS = 12e4;
195893
195934
  var GhParams = type({
195894
195935
  args: type.string.array().describe(
@@ -201687,6 +201728,7 @@ async function main() {
201687
201728
  const setupHook = await executeLifecycleHook({
201688
201729
  event: "setup",
201689
201730
  script: runContext.repoSettings.setupScript,
201731
+ shell: payload.shell,
201690
201732
  normalizeWorkingTreeAfter: true
201691
201733
  });
201692
201734
  if (setupHook.warning) {