pullfrog 0.1.95 → 0.1.97

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
@@ -1415,9 +1415,9 @@ var require_timers = __commonJS({
1415
1415
  }
1416
1416
  }
1417
1417
  var Timeout = class {
1418
- constructor(callback, delay2, opaque) {
1418
+ constructor(callback, delay3, opaque) {
1419
1419
  this.callback = callback;
1420
- this.delay = delay2;
1420
+ this.delay = delay3;
1421
1421
  this.opaque = opaque;
1422
1422
  this.state = -2;
1423
1423
  this.refresh();
@@ -1436,8 +1436,8 @@ var require_timers = __commonJS({
1436
1436
  }
1437
1437
  };
1438
1438
  module.exports = {
1439
- setTimeout(callback, delay2, opaque) {
1440
- return delay2 < 1e3 ? setTimeout(callback, delay2, opaque) : new Timeout(callback, delay2, opaque);
1439
+ setTimeout(callback, delay3, opaque) {
1440
+ return delay3 < 1e3 ? setTimeout(callback, delay3, opaque) : new Timeout(callback, delay3, opaque);
1441
1441
  },
1442
1442
  clearTimeout(timeout) {
1443
1443
  if (timeout instanceof Timeout) {
@@ -5717,7 +5717,7 @@ var require_request = __commonJS({
5717
5717
  method,
5718
5718
  body,
5719
5719
  headers,
5720
- query,
5720
+ query: query2,
5721
5721
  idempotent,
5722
5722
  blocking,
5723
5723
  upgrade,
@@ -5794,7 +5794,7 @@ var require_request = __commonJS({
5794
5794
  this.completed = false;
5795
5795
  this.aborted = false;
5796
5796
  this.upgrade = upgrade || null;
5797
- this.path = query ? util2.buildURL(path4, query) : path4;
5797
+ this.path = query2 ? util2.buildURL(path4, query2) : path4;
5798
5798
  this.origin = origin;
5799
5799
  this.idempotent = idempotent == null ? method === "HEAD" || method === "GET" : idempotent;
5800
5800
  this.blocking = blocking == null ? false : blocking;
@@ -10508,13 +10508,13 @@ var require_mock_utils = __commonJS({
10508
10508
  }
10509
10509
  }
10510
10510
  function buildKey(opts) {
10511
- const { path: path4, method, body, headers, query } = opts;
10511
+ const { path: path4, method, body, headers, query: query2 } = opts;
10512
10512
  return {
10513
10513
  path: path4,
10514
10514
  method,
10515
10515
  body,
10516
10516
  headers,
10517
- query
10517
+ query: query2
10518
10518
  };
10519
10519
  }
10520
10520
  function generateKeyValues(data) {
@@ -10541,7 +10541,7 @@ var require_mock_utils = __commonJS({
10541
10541
  if (mockDispatch2.data.callback) {
10542
10542
  mockDispatch2.data = { ...mockDispatch2.data, ...mockDispatch2.data.callback(opts) };
10543
10543
  }
10544
- const { data: { statusCode, data, headers, trailers, error: error49 }, delay: delay2, persist } = mockDispatch2;
10544
+ const { data: { statusCode, data, headers, trailers, error: error49 }, delay: delay3, persist } = mockDispatch2;
10545
10545
  const { timesInvoked, times } = mockDispatch2;
10546
10546
  mockDispatch2.consumed = !persist && timesInvoked >= times;
10547
10547
  mockDispatch2.pending = timesInvoked < times;
@@ -10550,10 +10550,10 @@ var require_mock_utils = __commonJS({
10550
10550
  handler2.onError(error49);
10551
10551
  return true;
10552
10552
  }
10553
- if (typeof delay2 === "number" && delay2 > 0) {
10553
+ if (typeof delay3 === "number" && delay3 > 0) {
10554
10554
  setTimeout(() => {
10555
10555
  handleReply(this[kDispatches]);
10556
- }, delay2);
10556
+ }, delay3);
10557
10557
  } else {
10558
10558
  handleReply(this[kDispatches]);
10559
10559
  }
@@ -26388,8 +26388,8 @@ var require_coerce = __commonJS({
26388
26388
  const minor = match3[3] || "0";
26389
26389
  const patch = match3[4] || "0";
26390
26390
  const prerelease = options.includePrerelease && match3[5] ? `-${match3[5]}` : "";
26391
- const build3 = options.includePrerelease && match3[6] ? `+${match3[6]}` : "";
26392
- return parse6(`${major}.${minor}.${patch}${prerelease}${build3}`, options);
26391
+ const build4 = options.includePrerelease && match3[6] ? `+${match3[6]}` : "";
26392
+ return parse6(`${major}.${minor}.${patch}${prerelease}${build4}`, options);
26393
26393
  };
26394
26394
  module.exports = coerce;
26395
26395
  }
@@ -40308,9 +40308,9 @@ var require_timers2 = __commonJS({
40308
40308
  * before the specified function or code is executed.
40309
40309
  * @param {*} arg
40310
40310
  */
40311
- constructor(callback, delay2, arg) {
40311
+ constructor(callback, delay3, arg) {
40312
40312
  this._onTimeout = callback;
40313
- this._idleTimeout = delay2;
40313
+ this._idleTimeout = delay3;
40314
40314
  this._timerArg = arg;
40315
40315
  this.refresh();
40316
40316
  }
@@ -40355,8 +40355,8 @@ var require_timers2 = __commonJS({
40355
40355
  * when the timer expires.
40356
40356
  * @returns {NodeJS.Timeout|FastTimer}
40357
40357
  */
40358
- setTimeout(callback, delay2, arg) {
40359
- return delay2 <= RESOLUTION_MS ? setTimeout(callback, delay2, arg) : new FastTimer(callback, delay2, arg);
40358
+ setTimeout(callback, delay3, arg) {
40359
+ return delay3 <= RESOLUTION_MS ? setTimeout(callback, delay3, arg) : new FastTimer(callback, delay3, arg);
40360
40360
  },
40361
40361
  /**
40362
40362
  * The clearTimeout method cancels an instantiated Timer previously created
@@ -40382,8 +40382,8 @@ var require_timers2 = __commonJS({
40382
40382
  * when the timer expires.
40383
40383
  * @returns {FastTimer}
40384
40384
  */
40385
- setFastTimeout(callback, delay2, arg) {
40386
- return new FastTimer(callback, delay2, arg);
40385
+ setFastTimeout(callback, delay3, arg) {
40386
+ return new FastTimer(callback, delay3, arg);
40387
40387
  },
40388
40388
  /**
40389
40389
  * The clearTimeout method cancels an instantiated FastTimer previously
@@ -40409,8 +40409,8 @@ var require_timers2 = __commonJS({
40409
40409
  * @deprecated
40410
40410
  * @param {number} [delay=0] The delay in milliseconds to add to the now value.
40411
40411
  */
40412
- tick(delay2 = 0) {
40413
- fastNow += delay2 - RESOLUTION_MS + 1;
40412
+ tick(delay3 = 0) {
40413
+ fastNow += delay3 - RESOLUTION_MS + 1;
40414
40414
  onTick();
40415
40415
  onTick();
40416
40416
  },
@@ -42234,7 +42234,7 @@ var require_request3 = __commonJS({
42234
42234
  method,
42235
42235
  body,
42236
42236
  headers,
42237
- query,
42237
+ query: query2,
42238
42238
  idempotent,
42239
42239
  blocking,
42240
42240
  upgrade,
@@ -42326,7 +42326,7 @@ var require_request3 = __commonJS({
42326
42326
  this.completed = false;
42327
42327
  this.aborted = false;
42328
42328
  this.upgrade = upgrade || null;
42329
- this.path = query ? serializePathWithQuery(path4, query) : path4;
42329
+ this.path = query2 ? serializePathWithQuery(path4, query2) : path4;
42330
42330
  this.origin = origin;
42331
42331
  this.protocol = getProtocolFromUrlString(origin);
42332
42332
  this.idempotent = idempotent == null ? method === "HEAD" || method === "GET" : idempotent;
@@ -46893,21 +46893,21 @@ var require_client_h1 = __commonJS({
46893
46893
  this.connection = "";
46894
46894
  this.maxResponseSize = client2[kMaxResponseSize];
46895
46895
  }
46896
- setTimeout(delay2, type2) {
46897
- if (delay2 !== this.timeoutValue || type2 & USE_FAST_TIMER ^ this.timeoutType & USE_FAST_TIMER) {
46896
+ setTimeout(delay3, type2) {
46897
+ if (delay3 !== this.timeoutValue || type2 & USE_FAST_TIMER ^ this.timeoutType & USE_FAST_TIMER) {
46898
46898
  if (this.timeout) {
46899
46899
  timers.clearTimeout(this.timeout);
46900
46900
  this.timeout = null;
46901
46901
  }
46902
- if (delay2) {
46902
+ if (delay3) {
46903
46903
  if (type2 & USE_FAST_TIMER) {
46904
- this.timeout = timers.setFastTimeout(onParserTimeout, delay2, this.timeoutWeakRef);
46904
+ this.timeout = timers.setFastTimeout(onParserTimeout, delay3, this.timeoutWeakRef);
46905
46905
  } else {
46906
- this.timeout = setTimeout(onParserTimeout, delay2, this.timeoutWeakRef);
46906
+ this.timeout = setTimeout(onParserTimeout, delay3, this.timeoutWeakRef);
46907
46907
  this.timeout?.unref();
46908
46908
  }
46909
46909
  }
46910
- this.timeoutValue = delay2;
46910
+ this.timeoutValue = delay3;
46911
46911
  } else if (this.timeout) {
46912
46912
  if (this.timeout.refresh) {
46913
46913
  this.timeout.refresh();
@@ -52901,11 +52901,11 @@ var require_mock_utils2 = __commonJS({
52901
52901
  }
52902
52902
  return true;
52903
52903
  }
52904
- function normalizeSearchParams(query) {
52905
- if (typeof query !== "string") {
52906
- return query;
52904
+ function normalizeSearchParams(query2) {
52905
+ if (typeof query2 !== "string") {
52906
+ return query2;
52907
52907
  }
52908
- const originalQp = new URLSearchParams(query);
52908
+ const originalQp = new URLSearchParams(query2);
52909
52909
  const normalizedQp = new URLSearchParams();
52910
52910
  for (let [key, value2] of originalQp.entries()) {
52911
52911
  key = key.replace("[]", "");
@@ -53013,13 +53013,13 @@ var require_mock_utils2 = __commonJS({
53013
53013
  return path4;
53014
53014
  }
53015
53015
  function buildKey(opts) {
53016
- const { path: path4, method, body, headers, query } = opts;
53016
+ const { path: path4, method, body, headers, query: query2 } = opts;
53017
53017
  return {
53018
53018
  path: path4,
53019
53019
  method,
53020
53020
  body,
53021
53021
  headers,
53022
- query
53022
+ query: query2
53023
53023
  };
53024
53024
  }
53025
53025
  function generateKeyValues(data) {
@@ -53056,7 +53056,7 @@ var require_mock_utils2 = __commonJS({
53056
53056
  if (mockDispatch2.data.callback) {
53057
53057
  mockDispatch2.data = { ...mockDispatch2.data, ...mockDispatch2.data.callback(opts) };
53058
53058
  }
53059
- const { data: { statusCode, data, headers, trailers, error: error49 }, delay: delay2, persist } = mockDispatch2;
53059
+ const { data: { statusCode, data, headers, trailers, error: error49 }, delay: delay3, persist } = mockDispatch2;
53060
53060
  const { timesInvoked, times } = mockDispatch2;
53061
53061
  mockDispatch2.consumed = !persist && timesInvoked >= times;
53062
53062
  mockDispatch2.pending = timesInvoked < times;
@@ -53079,11 +53079,11 @@ var require_mock_utils2 = __commonJS({
53079
53079
  handler2.onError(err);
53080
53080
  }
53081
53081
  handler2.onConnect?.(abort, null);
53082
- if (typeof delay2 === "number" && delay2 > 0) {
53082
+ if (typeof delay3 === "number" && delay3 > 0) {
53083
53083
  timer = setTimeout(() => {
53084
53084
  timer = null;
53085
53085
  handleReply(this[kDispatches]);
53086
- }, delay2);
53086
+ }, delay3);
53087
53087
  } else {
53088
53088
  handleReply(this[kDispatches]);
53089
53089
  }
@@ -53453,13 +53453,13 @@ var require_mock_call_history = __commonJS({
53453
53453
  function makeFilterCalls(parameterName) {
53454
53454
  return (parameterValue, logs) => {
53455
53455
  if (typeof parameterValue === "string" || parameterValue == null) {
53456
- return logs.filter((log3) => {
53457
- return log3[parameterName] === parameterValue;
53456
+ return logs.filter((log2) => {
53457
+ return log2[parameterName] === parameterValue;
53458
53458
  });
53459
53459
  }
53460
53460
  if (parameterValue instanceof RegExp) {
53461
- return logs.filter((log3) => {
53462
- return parameterValue.test(log3[parameterName]);
53461
+ return logs.filter((log2) => {
53462
+ return parameterValue.test(log2[parameterName]);
53463
53463
  });
53464
53464
  }
53465
53465
  throw new InvalidArgumentError(`${parameterName} parameter should be one of string, regexp, undefined or null`);
@@ -53554,8 +53554,8 @@ var require_mock_call_history = __commonJS({
53554
53554
  return this.logs.filter(criteria);
53555
53555
  }
53556
53556
  if (criteria instanceof RegExp) {
53557
- return this.logs.filter((log3) => {
53558
- return criteria.test(log3.toString());
53557
+ return this.logs.filter((log2) => {
53558
+ return criteria.test(log2.toString());
53559
53559
  });
53560
53560
  }
53561
53561
  if (typeof criteria === "object" && criteria !== null) {
@@ -53605,13 +53605,13 @@ var require_mock_call_history = __commonJS({
53605
53605
  this.logs = [];
53606
53606
  }
53607
53607
  [kMockCallHistoryAddLog](requestInit) {
53608
- const log3 = new MockCallHistoryLog(requestInit);
53609
- this.logs.push(log3);
53610
- return log3;
53608
+ const log2 = new MockCallHistoryLog(requestInit);
53609
+ this.logs.push(log2);
53610
+ return log2;
53611
53611
  }
53612
53612
  *[Symbol.iterator]() {
53613
- for (const log3 of this.calls()) {
53614
- yield log3;
53613
+ for (const log2 of this.calls()) {
53614
+ yield log2;
53615
53615
  }
53616
53616
  }
53617
53617
  };
@@ -56023,18 +56023,18 @@ var require_cache2 = __commonJS({
56023
56023
  }
56024
56024
  }
56025
56025
  }
56026
- function makeDeduplicationKey(cacheKey2, excludeHeaders) {
56026
+ function makeDeduplicationKey(cacheKey3, excludeHeaders) {
56027
56027
  const headers = {};
56028
- if (cacheKey2.headers) {
56029
- const sortedHeaders = Object.keys(cacheKey2.headers).sort();
56028
+ if (cacheKey3.headers) {
56029
+ const sortedHeaders = Object.keys(cacheKey3.headers).sort();
56030
56030
  for (const header of sortedHeaders) {
56031
56031
  if (excludeHeaders?.has(header.toLowerCase())) {
56032
56032
  continue;
56033
56033
  }
56034
- headers[header] = cacheKey2.headers[header];
56034
+ headers[header] = cacheKey3.headers[header];
56035
56035
  }
56036
56036
  }
56037
- return JSON.stringify([cacheKey2.origin, cacheKey2.method, cacheKey2.path, headers]);
56037
+ return JSON.stringify([cacheKey3.origin, cacheKey3.method, cacheKey3.path, headers]);
56038
56038
  }
56039
56039
  module.exports = {
56040
56040
  makeCacheKey,
@@ -56608,7 +56608,7 @@ var require_cache_handler = __commonJS({
56608
56608
  }
56609
56609
  }
56610
56610
  }
56611
- function getSameOriginPath(cacheKey2, location) {
56611
+ function getSameOriginPath(cacheKey3, location) {
56612
56612
  if (typeof location !== "string") {
56613
56613
  return void 0;
56614
56614
  }
@@ -56616,8 +56616,8 @@ var require_cache_handler = __commonJS({
56616
56616
  let requestUrl;
56617
56617
  let locationUrl;
56618
56618
  try {
56619
- originUrl = new URL(cacheKey2.origin);
56620
- requestUrl = new URL(cacheKey2.path, originUrl);
56619
+ originUrl = new URL(cacheKey3.origin);
56620
+ requestUrl = new URL(cacheKey3.path, originUrl);
56621
56621
  locationUrl = new URL(location, requestUrl);
56622
56622
  } catch {
56623
56623
  return void 0;
@@ -56627,38 +56627,38 @@ var require_cache_handler = __commonJS({
56627
56627
  }
56628
56628
  return locationUrl.pathname + locationUrl.search;
56629
56629
  }
56630
- function deleteCachedUri(store, cacheKey2, path4) {
56630
+ function deleteCachedUri(store, cacheKey3, path4) {
56631
56631
  deleteCachedValue(store, {
56632
- ...cacheKey2,
56632
+ ...cacheKey3,
56633
56633
  path: path4
56634
56634
  });
56635
56635
  for (let i = 0; i < util2.safeHTTPMethods.length; i++) {
56636
56636
  const method = util2.safeHTTPMethods[i];
56637
- if (method !== cacheKey2.method) {
56637
+ if (method !== cacheKey3.method) {
56638
56638
  deleteCachedValue(store, {
56639
- ...cacheKey2,
56639
+ ...cacheKey3,
56640
56640
  method,
56641
56641
  path: path4
56642
56642
  });
56643
56643
  }
56644
56644
  }
56645
56645
  }
56646
- function deleteLocationTargets(store, cacheKey2, headerValue) {
56646
+ function deleteLocationTargets(store, cacheKey3, headerValue) {
56647
56647
  if (headerValue === void 0) {
56648
56648
  return;
56649
56649
  }
56650
56650
  const values = Array.isArray(headerValue) ? headerValue : [headerValue];
56651
56651
  for (let i = 0; i < values.length; i++) {
56652
- const path4 = getSameOriginPath(cacheKey2, values[i]);
56652
+ const path4 = getSameOriginPath(cacheKey3, values[i]);
56653
56653
  if (path4 !== void 0) {
56654
- deleteCachedUri(store, cacheKey2, path4);
56654
+ deleteCachedUri(store, cacheKey3, path4);
56655
56655
  }
56656
56656
  }
56657
56657
  }
56658
- function invalidateUnsafeRequest(store, cacheKey2, resHeaders) {
56659
- deleteCachedUri(store, cacheKey2, cacheKey2.path);
56660
- deleteLocationTargets(store, cacheKey2, resHeaders.location);
56661
- deleteLocationTargets(store, cacheKey2, resHeaders["content-location"]);
56658
+ function invalidateUnsafeRequest(store, cacheKey3, resHeaders) {
56659
+ deleteCachedUri(store, cacheKey3, cacheKey3.path);
56660
+ deleteLocationTargets(store, cacheKey3, resHeaders.location);
56661
+ deleteLocationTargets(store, cacheKey3, resHeaders["content-location"]);
56662
56662
  }
56663
56663
  var CacheHandler = class {
56664
56664
  /**
@@ -56690,11 +56690,11 @@ var require_cache_handler = __commonJS({
56690
56690
  * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} cacheKey
56691
56691
  * @param {import('../../types/dispatcher.d.ts').default.DispatchHandler} handler
56692
56692
  */
56693
- constructor({ store, type: type2, cacheByDefault }, cacheKey2, handler2) {
56693
+ constructor({ store, type: type2, cacheByDefault }, cacheKey3, handler2) {
56694
56694
  this.#store = store;
56695
56695
  this.#cacheType = type2;
56696
56696
  this.#cacheByDefault = cacheByDefault;
56697
- this.#cacheKey = cacheKey2;
56697
+ this.#cacheKey = cacheKey3;
56698
56698
  this.#handler = handler2;
56699
56699
  }
56700
56700
  onRequestStart(controller, context) {
@@ -56887,15 +56887,15 @@ var require_cache_handler = __commonJS({
56887
56887
  this.#handler.onResponseError?.(controller, err);
56888
56888
  }
56889
56889
  };
56890
- function deleteCachedValue(store, cacheKey2) {
56890
+ function deleteCachedValue(store, cacheKey3) {
56891
56891
  try {
56892
- store.delete(cacheKey2)?.catch?.(noop4);
56892
+ store.delete(cacheKey3)?.catch?.(noop4);
56893
56893
  } catch {
56894
56894
  }
56895
56895
  }
56896
- function deleteCachedValueIfNotModified(statusCode, store, cacheKey2) {
56896
+ function deleteCachedValueIfNotModified(statusCode, store, cacheKey3) {
56897
56897
  if (statusCode === 304) {
56898
- deleteCachedValue(store, cacheKey2);
56898
+ deleteCachedValue(store, cacheKey3);
56899
56899
  }
56900
56900
  }
56901
56901
  function revalidationResponseDisallowsCachedReuse(cacheType, resHeaders, cacheControlDirectives) {
@@ -57461,9 +57461,9 @@ var require_cache3 = __commonJS({
57461
57461
  function revalidationResponseUpdatesCacheControl(headers) {
57462
57462
  return headers["cache-control"] !== void 0;
57463
57463
  }
57464
- function deleteCachedValue(store, cacheKey2) {
57464
+ function deleteCachedValue(store, cacheKey3) {
57465
57465
  try {
57466
- store.delete(cacheKey2)?.catch?.(nop);
57466
+ store.delete(cacheKey3)?.catch?.(nop);
57467
57467
  } catch {
57468
57468
  }
57469
57469
  }
@@ -57515,7 +57515,7 @@ var require_cache3 = __commonJS({
57515
57515
  const staleWhileRevalidateExpiry = result.staleAt + staleWhileRevalidate * 1e3;
57516
57516
  return now <= staleWhileRevalidateExpiry;
57517
57517
  }
57518
- function handleUncachedResponse(dispatch, globalOpts, cacheKey2, handler2, opts, reqCacheControl) {
57518
+ function handleUncachedResponse(dispatch, globalOpts, cacheKey3, handler2, opts, reqCacheControl) {
57519
57519
  if (reqCacheControl?.["only-if-cached"]) {
57520
57520
  let aborted3 = false;
57521
57521
  try {
@@ -57543,7 +57543,7 @@ var require_cache3 = __commonJS({
57543
57543
  }
57544
57544
  return true;
57545
57545
  }
57546
- return dispatch(opts, new CacheHandler(globalOpts, cacheKey2, handler2));
57546
+ return dispatch(opts, new CacheHandler(globalOpts, cacheKey3, handler2));
57547
57547
  }
57548
57548
  function sendCachedValue(handler2, opts, result, age, context, isStale2) {
57549
57549
  const stream = util2.isStream(result.body) ? result.body : Readable2.from(result.body ?? []);
@@ -57599,20 +57599,20 @@ var require_cache3 = __commonJS({
57599
57599
  });
57600
57600
  }
57601
57601
  }
57602
- function handleResult2(dispatch, globalOpts, cacheKey2, handler2, opts, reqCacheControl, result) {
57602
+ function handleResult2(dispatch, globalOpts, cacheKey3, handler2, opts, reqCacheControl, result) {
57603
57603
  if (!result) {
57604
- return handleUncachedResponse(dispatch, globalOpts, cacheKey2, handler2, opts, reqCacheControl);
57604
+ return handleUncachedResponse(dispatch, globalOpts, cacheKey3, handler2, opts, reqCacheControl);
57605
57605
  }
57606
57606
  if (globalOpts.type === "shared" && Object.hasOwn(result.headers, "set-cookie")) {
57607
57607
  if (util2.isStream(result.body)) {
57608
57608
  result.body.on("error", nop).destroy();
57609
57609
  }
57610
- deleteCachedValue(globalOpts.store, cacheKey2);
57611
- return handleUncachedResponse(dispatch, globalOpts, cacheKey2, handler2, opts, reqCacheControl);
57610
+ deleteCachedValue(globalOpts.store, cacheKey3);
57611
+ return handleUncachedResponse(dispatch, globalOpts, cacheKey3, handler2, opts, reqCacheControl);
57612
57612
  }
57613
57613
  const now = Date.now();
57614
57614
  if (now > result.deleteAt) {
57615
- return dispatch(opts, new CacheHandler(globalOpts, cacheKey2, handler2));
57615
+ return dispatch(opts, new CacheHandler(globalOpts, cacheKey3, handler2));
57616
57616
  }
57617
57617
  const age = Math.round((now - result.cachedAt) / 1e3);
57618
57618
  const requestMaxAgeExpired = reqCacheControl?.["max-age"] !== void 0 && age >= reqCacheControl["max-age"];
@@ -57620,7 +57620,7 @@ var require_cache3 = __commonJS({
57620
57620
  const revalidate = requestMaxAgeExpired || needsRevalidation(result, reqCacheControl, opts);
57621
57621
  if (stale || revalidate) {
57622
57622
  if (util2.isStream(opts.body) && util2.bodyLength(opts.body) !== 0) {
57623
- return dispatch(opts, new CacheHandler(globalOpts, cacheKey2, handler2));
57623
+ return dispatch(opts, new CacheHandler(globalOpts, cacheKey3, handler2));
57624
57624
  }
57625
57625
  if (!revalidate && withinStaleWhileRevalidateWindow(result, globalOpts.type)) {
57626
57626
  sendCachedValue(handler2, opts, result, age, null, true);
@@ -57631,7 +57631,7 @@ var require_cache3 = __commonJS({
57631
57631
  ...opts,
57632
57632
  headers: headers2
57633
57633
  },
57634
- new CacheHandler(globalOpts, cacheKey2, {
57634
+ new CacheHandler(globalOpts, cacheKey3, {
57635
57635
  // Silent handler that just updates the cache
57636
57636
  onRequestStart() {
57637
57637
  },
@@ -57671,11 +57671,11 @@ var require_cache3 = __commonJS({
57671
57671
  if (util2.isStream(result.body)) {
57672
57672
  result.body.on("error", nop).destroy();
57673
57673
  }
57674
- deleteCachedValue(globalOpts.store, cacheKey2);
57675
- return dispatch(opts, new CacheHandler(globalOpts, cacheKey2, handler2));
57674
+ deleteCachedValue(globalOpts.store, cacheKey3);
57675
+ return dispatch(opts, new CacheHandler(globalOpts, cacheKey3, handler2));
57676
57676
  }
57677
57677
  if (revalidationResponseUpdatesCacheControl(headers2)) {
57678
- deleteCachedValue(globalOpts.store, cacheKey2);
57678
+ deleteCachedValue(globalOpts.store, cacheKey3);
57679
57679
  }
57680
57680
  }
57681
57681
  sendCachedValue(handler2, opts, result, age, context, stale);
@@ -57683,7 +57683,7 @@ var require_cache3 = __commonJS({
57683
57683
  result.body.on("error", nop).destroy();
57684
57684
  }
57685
57685
  },
57686
- new CacheHandler(globalOpts, cacheKey2, handler2),
57686
+ new CacheHandler(globalOpts, cacheKey3, handler2),
57687
57687
  withinStaleIfErrorThreshold
57688
57688
  )
57689
57689
  );
@@ -57758,16 +57758,16 @@ var require_cache3 = __commonJS({
57758
57758
  if (reqCacheControl?.["no-store"]) {
57759
57759
  return dispatch(opts2, handler2);
57760
57760
  }
57761
- const cacheKey2 = makeCacheKey(opts2);
57761
+ const cacheKey3 = makeCacheKey(opts2);
57762
57762
  if (!arrayIncludes(util2.safeHTTPMethods, opts2.method)) {
57763
- return dispatch(opts2, new CacheHandler(globalOpts, cacheKey2, handler2));
57763
+ return dispatch(opts2, new CacheHandler(globalOpts, cacheKey3, handler2));
57764
57764
  }
57765
- const result = store.get(cacheKey2);
57765
+ const result = store.get(cacheKey3);
57766
57766
  if (result && typeof result.then === "function") {
57767
57767
  return result.then((result2) => handleResult2(
57768
57768
  dispatch,
57769
57769
  globalOpts,
57770
- cacheKey2,
57770
+ cacheKey3,
57771
57771
  handler2,
57772
57772
  opts2,
57773
57773
  reqCacheControl,
@@ -57777,7 +57777,7 @@ var require_cache3 = __commonJS({
57777
57777
  return handleResult2(
57778
57778
  dispatch,
57779
57779
  globalOpts,
57780
- cacheKey2,
57780
+ cacheKey3,
57781
57781
  handler2,
57782
57782
  opts2,
57783
57783
  reqCacheControl,
@@ -58670,8 +58670,8 @@ var require_deduplicate = __commonJS({
58670
58670
  }
58671
58671
  }
58672
58672
  }
58673
- const cacheKey2 = makeCacheKey(opts2);
58674
- const dedupeKey = makeDeduplicationKey(cacheKey2, excludeHeaderNamesSet);
58673
+ const cacheKey3 = makeCacheKey(opts2);
58674
+ const dedupeKey = makeDeduplicationKey(cacheKey3, excludeHeaderNamesSet);
58675
58675
  const pendingHandler = pendingRequests.get(dedupeKey);
58676
58676
  if (pendingHandler) {
58677
58677
  if (pendingHandler.addWaitingHandler(handler2)) {
@@ -76276,8 +76276,8 @@ var require_core2 = __commonJS({
76276
76276
  return this;
76277
76277
  }
76278
76278
  case "object": {
76279
- const cacheKey2 = schemaKeyRef;
76280
- this._cache.delete(cacheKey2);
76279
+ const cacheKey3 = schemaKeyRef;
76280
+ this._cache.delete(cacheKey3);
76281
76281
  let id = schemaKeyRef[this.opts.schemaId];
76282
76282
  if (id) {
76283
76283
  id = (0, resolve_1.normalizeId)(id);
@@ -76442,11 +76442,11 @@ var require_core2 = __commonJS({
76442
76442
  Ajv5.ValidationError = validation_error_1.default;
76443
76443
  Ajv5.MissingRefError = ref_error_1.default;
76444
76444
  exports.default = Ajv5;
76445
- function checkOptions(checkOpts, options, msg, log3 = "error") {
76445
+ function checkOptions(checkOpts, options, msg, log2 = "error") {
76446
76446
  for (const key in checkOpts) {
76447
76447
  const opt = key;
76448
76448
  if (opt in options)
76449
- this.logger[log3](`${msg}: option ${key}. ${checkOpts[opt]}`);
76449
+ this.logger[log2](`${msg}: option ${key}. ${checkOpts[opt]}`);
76450
76450
  }
76451
76451
  }
76452
76452
  function getSchEnv(keyRef) {
@@ -98088,14 +98088,14 @@ var require_turndown_cjs = __commonJS({
98088
98088
  } else if (node2.nodeType === 1) {
98089
98089
  replacement = replacementForNode.call(self2, node2);
98090
98090
  }
98091
- return join28(output, replacement);
98091
+ return join29(output, replacement);
98092
98092
  }, "");
98093
98093
  }
98094
98094
  function postProcess(output) {
98095
98095
  var self2 = this;
98096
98096
  this.rules.forEach(function(rule) {
98097
98097
  if (typeof rule.append === "function") {
98098
- output = join28(output, rule.append(self2.options));
98098
+ output = join29(output, rule.append(self2.options));
98099
98099
  }
98100
98100
  });
98101
98101
  return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
@@ -98107,7 +98107,7 @@ var require_turndown_cjs = __commonJS({
98107
98107
  if (whitespace.leading || whitespace.trailing) content = content.trim();
98108
98108
  return whitespace.leading + rule.replacement(content, node2, this.options) + whitespace.trailing;
98109
98109
  }
98110
- function join28(output, replacement) {
98110
+ function join29(output, replacement) {
98111
98111
  var s1 = trimTrailingNewlines(output);
98112
98112
  var s2 = trimLeadingNewlines(replacement);
98113
98113
  var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
@@ -99714,7 +99714,7 @@ var require_node = __commonJS({
99714
99714
  var tty = __require("tty");
99715
99715
  var util2 = __require("util");
99716
99716
  exports.init = init;
99717
- exports.log = log3;
99717
+ exports.log = log2;
99718
99718
  exports.formatArgs = formatArgs2;
99719
99719
  exports.save = save;
99720
99720
  exports.load = load;
@@ -99849,7 +99849,7 @@ var require_node = __commonJS({
99849
99849
  }
99850
99850
  return (/* @__PURE__ */ new Date()).toISOString() + " ";
99851
99851
  }
99852
- function log3(...args2) {
99852
+ function log2(...args2) {
99853
99853
  return process.stderr.write(util2.formatWithOptions(exports.inspectOpts, ...args2) + "\n");
99854
99854
  }
99855
99855
  function save(namespaces) {
@@ -102898,9 +102898,9 @@ var init_file_type = __esm({
102898
102898
  });
102899
102899
 
102900
102900
  // main.ts
102901
- import { existsSync as existsSync10, readdirSync as readdirSync2 } from "node:fs";
102901
+ import { existsSync as existsSync11, readdirSync as readdirSync2 } from "node:fs";
102902
102902
  import { readFile as readFile5 } from "node:fs/promises";
102903
- import { join as join27 } from "node:path";
102903
+ import { join as join28 } from "node:path";
102904
102904
 
102905
102905
  // node_modules/.pnpm/package-manager-detector@1.8.0/node_modules/package-manager-detector/dist/commands.mjs
102906
102906
  function splitRunArgs(args2, valueFlags = []) {
@@ -103909,6 +103909,15 @@ var providers = {
103909
103909
  openRouterEffort: [],
103910
103910
  openRouterResolve: "openrouter/qwen/qwen3.8-flash"
103911
103911
  },
103912
+ // Zen lists Mistral Large 4 at half its list price for launch, level with
103913
+ // the routers. hidden until a published `pullfrog` resolves it, as there.
103914
+ "mistral-large": {
103915
+ displayName: "Mistral Large",
103916
+ resolve: "opencode/mistral-large-4",
103917
+ effort: ["none", "high"],
103918
+ openRouterResolve: "openrouter/mistralai/mistral-large-4-0",
103919
+ hidden: true
103920
+ },
103912
103921
  "gpt-5-nano": {
103913
103922
  displayName: "GPT Nano",
103914
103923
  resolve: "opencode/gpt-5.4-nano",
@@ -103957,12 +103966,12 @@ var providers = {
103957
103966
  fallback: "opencode/big-pickle"
103958
103967
  },
103959
103968
  // Zen's FREE stealth preview (listed 2026-09-23), the third after
103960
- // big-pickle and union-alpha. OpenRouter's listing: prompts and
103961
- // completions may be retained by the provider, not trained on. unlike the
103962
- // first two it publishes an effort ladder, so the gate mirrors one here.
103969
+ // big-pickle and union-alpha. Zen's docs: zero retention, no training.
103970
+ // unlike the first two it publishes an effort ladder, so the gate
103971
+ // mirrors one here.
103963
103972
  "space-bunny": {
103964
103973
  displayName: "Space Bunny",
103965
- description: "Stealth preview; prompts may be retained",
103974
+ description: "Stealth preview, free for a limited time",
103966
103975
  resolve: "opencode/space-bunny-free",
103967
103976
  effort: ["low", "medium", "high", "xhigh", "max"],
103968
103977
  // free to run, still gated on the provider's own OPENCODE_API_KEY —
@@ -103980,17 +103989,30 @@ var providers = {
103980
103989
  // see the big-pickle note above (#1077).
103981
103990
  isFree: true
103982
103991
  },
103983
- // Zen's FREE stealth preview (listed 2026-10-01), the fifth promo. Zen
103984
- // has not published its data terms yet, so the description claims none.
103992
+ // Zen's FREE stealth preview (listed 2026-10-01), the fifth promo. Zen's
103993
+ // docs: data may be used to improve the model during the free period.
103985
103994
  "fledge-alpha": {
103986
103995
  displayName: "Fledge Alpha",
103987
- description: "Stealth preview, free for a limited time",
103996
+ description: "Stealth preview; data may be used to improve it",
103988
103997
  resolve: "opencode/fledge-alpha-free",
103989
103998
  effort: ["low", "high", "max"],
103990
103999
  // free to run, still gated on the provider's own OPENCODE_API_KEY —
103991
104000
  // see the big-pickle note above (#1077).
103992
104001
  isFree: true
103993
104002
  },
104003
+ // Zen's FREE preview (listed 2026-10-06), the sixth promo, on Fledge
104004
+ // Alpha's terms. hidden until a published `pullfrog` resolves it.
104005
+ exo: {
104006
+ displayName: "Exo",
104007
+ description: "Preview; data may be used to improve it",
104008
+ resolve: "opencode/exo-free",
104009
+ // Zen publishes a single rung.
104010
+ effort: ["high"],
104011
+ // free to run, still gated on the provider's own OPENCODE_API_KEY —
104012
+ // see the big-pickle note above (#1077).
104013
+ isFree: true,
104014
+ hidden: true
104015
+ },
103994
104016
  // Zen's live free MiMo, and the second free row in a menu that big-pickle
103995
104017
  // was alone in since `mimo-v2-pro-free` lost its model.
103996
104018
  mimo: {
@@ -104053,7 +104075,11 @@ var providers = {
104053
104075
  "glm-flash": {
104054
104076
  displayName: "GLM Flash",
104055
104077
  resolve: "opencode-go/glm-5.3-flash",
104056
- effort: ["low", "high", "max"],
104078
+ // the Go endpoint intermittently rejects reasoning_effort ("native reasoning
104079
+ // control reasoning_effort is not allowed") mid-run, despite models.dev
104080
+ // advertising a ladder — so this route sends none.
104081
+ effort: [],
104082
+ openRouterEffort: ["low", "high", "max"],
104057
104083
  openRouterResolve: "openrouter/z-ai/glm-5.3-flash"
104058
104084
  },
104059
104085
  // legacy alias — the slug pinned a version instead of a brand tier and
@@ -104442,6 +104468,17 @@ var providers = {
104442
104468
  resolve: "openrouter/meta/muse-spark-1.3-contributor",
104443
104469
  effort: ["minimal", "low", "medium", "high", "xhigh", "max"],
104444
104470
  openRouterResolve: "openrouter/meta/muse-spark-1.3-contributor"
104471
+ },
104472
+ // Mistral's flagship (public preview, 2026-10-06), on the routers only:
104473
+ // the catalog carries no direct Mistral provider. out of the pickers
104474
+ // until a published `pullfrog` resolves the slug; see "A new alias
104475
+ // reaches the picker at merge".
104476
+ "mistral-large": {
104477
+ displayName: "Mistral Large",
104478
+ resolve: "openrouter/mistralai/mistral-large-4-0",
104479
+ effort: ["none", "high"],
104480
+ openRouterResolve: "openrouter/mistralai/mistral-large-4-0",
104481
+ hidden: true
104445
104482
  }
104446
104483
  }
104447
104484
  }),
@@ -104509,7 +104546,7 @@ var providers = {
104509
104546
  "deepseek-pro": {
104510
104547
  displayName: "DeepSeek Pro",
104511
104548
  resolve: "vercel/deepseek/deepseek-v4-pro-0813",
104512
- effort: ["none", "high", "max"]
104549
+ effort: ["none", "low", "high", "max"]
104513
104550
  },
104514
104551
  // the gateway's bare `deepseek-v4-flash` is the April preview — the same
104515
104552
  // fork trap as OpenRouter, with `deepseek-v4-flash-0731` beside it — so
@@ -104517,7 +104554,7 @@ var providers = {
104517
104554
  "deepseek-flash": {
104518
104555
  displayName: "DeepSeek Flash",
104519
104556
  resolve: "vercel/deepseek/deepseek-v4.1-flash",
104520
- effort: ["none", "high", "max"]
104557
+ effort: ["none", "low", "high", "max"]
104521
104558
  },
104522
104559
  glm: {
104523
104560
  displayName: "GLM",
@@ -104533,6 +104570,13 @@ var providers = {
104533
104570
  displayName: "Muse Spark",
104534
104571
  resolve: "vercel/meta/muse-spark-1.3",
104535
104572
  effort: ["minimal", "low", "medium", "high", "xhigh"]
104573
+ },
104574
+ // hidden until a published `pullfrog` resolves it, as on OpenRouter.
104575
+ "mistral-large": {
104576
+ displayName: "Mistral Large",
104577
+ resolve: "vercel/mistral/mistral-large-4",
104578
+ effort: ["none", "low", "medium", "high"],
104579
+ hidden: true
104536
104580
  }
104537
104581
  }
104538
104582
  })
@@ -104723,13 +104767,13 @@ for (const [name, ladder] of Object.entries({ ...PROVIDER_LADDERS, router: ROUTE
104723
104767
  if (terminal.size < 2) throw new Error(`${name} ladder runs every tier on ${[...terminal][0]}`);
104724
104768
  }
104725
104769
  var OSS_MODEL_ALLOWLIST = [
104726
- "deepseek/deepseek-flash",
104727
- "openai/gpt-luna",
104728
- "openrouter/minimax-m2.5",
104729
104770
  // opt-in: 0.86x the old default on the measured mix, and Meta trains on the
104730
104771
  // traffic — fine for public code on headless runs, the human decided
104731
104772
  // 2026-09-11. unmeasured on a real run; not the default until it is.
104732
- "meta/muse-spark-contributor"
104773
+ "meta/muse-spark-contributor",
104774
+ "deepseek/deepseek-flash",
104775
+ "openai/gpt-luna",
104776
+ "openrouter/minimax-m2.5"
104733
104777
  ];
104734
104778
  var OSS_RECOMMENDED_MODEL = AUTO_TIER_TARGET[AUTO_EFFICIENT];
104735
104779
  function ossFundedModelNames() {
@@ -105413,7 +105457,13 @@ var PROVIDER_ERROR_PATTERNS = [
105413
105457
  // …/settings/credits`) matched neither this nor `requires more credits`, so
105414
105458
  // the one run in that episode that was a genuinely empty wallet rendered with
105415
105459
  // no CTA at all while runs 1-6 rendered correctly.
105416
- { regex: /Insufficient (?:balance|credits)/i, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
105460
+ // `account funds` is Zen's (#1474).
105461
+ {
105462
+ regex: /Insufficient (?:balance|credits|account funds)/i,
105463
+ label: PROVIDER_BILLING_EXHAUSTED_LABEL
105464
+ },
105465
+ // OpenAI's `You have no credits remaining` (#1474).
105466
+ { regex: /no credits remaining/i, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
105417
105467
  { regex: /credit balance is too low/i, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
105418
105468
  // "spending cap" (Gemini) and "spending limit" (xAI, #1076) are the same
105419
105469
  // condition in two house styles — a configured ceiling was reached.
@@ -105534,6 +105584,9 @@ function isRouterKeylimitExhaustedError(text) {
105534
105584
  function isProviderBillingExhausted(text) {
105535
105585
  return findProviderErrorMatch(text)?.label === PROVIDER_BILLING_EXHAUSTED_LABEL;
105536
105586
  }
105587
+ function isProviderUsageLimit(text) {
105588
+ return findProviderErrorMatch(text)?.label === PROVIDER_USAGE_LIMIT_LABEL;
105589
+ }
105537
105590
  function isProviderNoRoutableEndpoints(text) {
105538
105591
  return findProviderErrorMatch(text)?.label === PROVIDER_NO_ENDPOINTS_LABEL;
105539
105592
  }
@@ -105605,7 +105658,7 @@ var import_semver = __toESM(require_semver2(), 1);
105605
105658
  // package.json
105606
105659
  var package_default = {
105607
105660
  name: "pullfrog",
105608
- version: "0.1.95",
105661
+ version: "0.1.97",
105609
105662
  cliContractVersion: "1",
105610
105663
  type: "module",
105611
105664
  bin: {
@@ -105671,7 +105724,7 @@ var package_default = {
105671
105724
  undici: "^7.22.0",
105672
105725
  vitest: "^4.0.17",
105673
105726
  yaml: "^2.8.2",
105674
- yes: "0.1.0",
105727
+ yes: "0.2.3",
105675
105728
  zod: "4.3.6"
105676
105729
  },
105677
105730
  repository: {
@@ -105839,6 +105892,9 @@ function trackChild(options) {
105839
105892
  function untrackChild(child) {
105840
105893
  activeChildren.delete(child);
105841
105894
  }
105895
+ function isTrackedChild(child) {
105896
+ return activeChildren.has(child);
105897
+ }
105842
105898
  function killTrackedChildren() {
105843
105899
  for (const entry of activeChildren) {
105844
105900
  const child = entry[0];
@@ -105852,6 +105908,7 @@ function killTrackedChildren() {
105852
105908
  }
105853
105909
  child.kill("SIGKILL");
105854
105910
  }
105911
+ activeChildren.clear();
105855
105912
  }
105856
105913
  var handlersInstalled = false;
105857
105914
  function installSignalHandler() {
@@ -105898,6 +105955,7 @@ ${this.buffer}`;
105898
105955
  };
105899
105956
  async function spawn(options) {
105900
105957
  const activityTimeoutMs = options.activityTimeout ?? DEFAULT_ACTIVITY_TIMEOUT_MS;
105958
+ options.signal?.throwIfAborted();
105901
105959
  installSignalHandler();
105902
105960
  const startTime = performance3.now();
105903
105961
  const retain = options.retain ?? "tail";
@@ -105933,17 +105991,18 @@ async function spawn(options) {
105933
105991
  let isActivityTimedOut = false;
105934
105992
  let lastActivityTime = performance3.now();
105935
105993
  let killedAtIdleMs;
105994
+ const terminate = () => {
105995
+ if (sigkillEscalatorId) return;
105996
+ killSelf("SIGTERM");
105997
+ sigkillEscalatorId = setTimeout(() => killSelf("SIGKILL"), 5e3);
105998
+ };
105936
105999
  if (options.timeout) {
105937
106000
  timeoutId = setTimeout(() => {
105938
106001
  isTimedOut = true;
105939
- killSelf("SIGTERM");
105940
- sigkillEscalatorId = setTimeout(() => {
105941
- if (!child.killed) {
105942
- killSelf("SIGKILL");
105943
- }
105944
- }, 5e3);
106002
+ terminate();
105945
106003
  }, options.timeout);
105946
106004
  }
106005
+ options.signal?.addEventListener("abort", terminate, { once: true });
105947
106006
  if (activityTimeoutMs > 0) {
105948
106007
  log.debug(
105949
106008
  `spawn activity timer: pid=${child.pid} cmd=${options.cmd} timeout=${activityTimeoutMs}ms`
@@ -106008,6 +106067,11 @@ async function spawn(options) {
106008
106067
  if (timeoutId) clearTimeout(timeoutId);
106009
106068
  if (sigkillEscalatorId) clearTimeout(sigkillEscalatorId);
106010
106069
  if (activityCheckIntervalId) clearInterval(activityCheckIntervalId);
106070
+ options.signal?.removeEventListener("abort", terminate);
106071
+ if (options.signal?.aborted) {
106072
+ reject(options.signal.reason);
106073
+ return;
106074
+ }
106011
106075
  if (isTimedOut) {
106012
106076
  reject(
106013
106077
  new SpawnTimeoutError(`process timed out after ${options.timeout}ms`, SPAWN_TIMEOUT_CODE)
@@ -106046,6 +106110,7 @@ ${killMsg}` : killMsg;
106046
106110
  if (timeoutId) clearTimeout(timeoutId);
106047
106111
  if (sigkillEscalatorId) clearTimeout(sigkillEscalatorId);
106048
106112
  if (activityCheckIntervalId) clearInterval(activityCheckIntervalId);
106113
+ options.signal?.removeEventListener("abort", terminate);
106049
106114
  const errMsg = `[spawn] ${options.cmd}: ${error49.message}`;
106050
106115
  console.error(errMsg);
106051
106116
  const existingStderr = stderrBuffer?.toString() ?? "";
@@ -106287,17 +106352,16 @@ Report findings clearly with file:line references and quoted evidence where poss
106287
106352
  // modes.ts
106288
106353
  var PR_SUMMARY_FORMAT = `### Default format
106289
106354
 
106290
- The body has at most three parts, in this order:
106355
+ The body has at most two parts, in this order:
106291
106356
 
106292
106357
  1. **Reviewed changes preamble** \u2014 a bolded \`**Reviewed changes**\` lead-in with one sentence on what was reviewed in this run (for \`IncrementalReview\`: what changed since the prior pullfrog review), then a bullet list of the substantive changes \u2014 short bolded title, one sentence each. A reviewer should understand the full reviewed scope from this list alone. Close the preamble with the metadata comment below.
106293
106358
  2. **Cross-cutting issue sections** (zero or more) \u2014 one \`### {emoji} {what's wrong, not what to do}\` heading per concern.
106294
- 3. **\`### \u2139\uFE0F Nitpicks\`** at the very bottom, if any \u2014 a flat bullet list, no technical-details block. Only for what has no runtime effect: a stale or wrong comment or doc, a misleading name, a typo, dead code, a missing test. Never formatting or whitespace.
106295
106359
 
106296
106360
  **Inline vs. body.** Concerns that anchor to a specific line go inline (the \`comments\` parameter), even when their implications are broad. Body \`### \` sections are reserved for concerns that have **no line to anchor to** \u2014 *absence* (something the diff should have done but didn't), *sequencing* (rollout / deletion / migration order), *design decisions only the human can make*, or *scope questions the diff raises but doesn't address*. With no non-anchorable concerns, the body is just the preamble + metadata.
106297
106361
 
106298
106362
  **Severity emoji** on every \`### \` heading, and nowhere else: \u{1F6A8} critical (blocks merge \u2014 data loss, security, broken core flow) \xB7 \u26A0\uFE0F important (must address before merging) \xB7 \u2139\uFE0F informational (mergeable as-is).
106299
106363
 
106300
- **Behavior decides placement, not fix size.** A finding that changes what the code does \u2014 a wrong value or message, a crash, a race, a leak, a missed cleanup, a slow path \u2014 gets its own inline comment (or a \`### \` section if no line anchors it) at its real severity, however small the fix. Never put it in Nitpicks: before submitting, re-read each Nitpicks bullet and move any that fails this test inline.
106364
+ **Behavior decides placement, not fix size.** A finding that changes what the code does \u2014 a wrong value or message, a crash, a race, a leak, a missed cleanup, a slow path \u2014 gets its own inline comment (or a \`### \` section if no line anchors it) at its real severity, however small the fix.
106301
106365
 
106302
106366
  **Blank line between every block-level element.** GitHub's markdown parser requires one before and after HTML tags (\`<details>\`, \`<summary>\`, \`<sub>\`, \`<br/>\`) \u2014 without it GitHub treats what follows as a continuation of the HTML block and renders your markdown as literal text. This is a parser quirk, not a style preference, and it permanently breaks the posted review.
106303
106367
 
@@ -106446,8 +106510,6 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
106446
106510
 
106447
106511
  3. **triage**: orient yourself on the PR \u2014 identify *what kind of thing this is* (domain it touches, seams it crosses, external contracts it depends on, user-facing surfaces it changes). pull as much context as you need to render a confident, well-grounded review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths, fetch related GitHub state. **you are the synthesizer** \u2014 never delegate understanding to subagents.
106448
106512
 
106449
- when the diff adds or changes a test, check that it can actually fail: a test that would still pass with the bug present is theatre, not coverage. the usual tell is a loose assertion standing where an exact one belongs \u2014 \`>=\` or a truthiness check over an expected value, or a snapshot that absorbs whatever it is handed. read the assertion against the behavior it claims to pin, not against whether it currently passes.
106450
-
106451
106513
  skip the deeper pass and submit a \`No new issues found.\` review per step 7 only when the diff has **no behavioral surface at all** \u2014 doc typos, whitespace/formatting, lockfile or generated-code regeneration, a mechanical rename whose only effect is import-path updates. line count is not the signal: a one-line change to auth, money, SQL, a comparison operator, a redirect, or a config default is not trivial.
106452
106514
 
106453
106515
  4. **specialist decision**: after reading the complete diff, name the questions you still cannot answer confidently yourself, and dispatch one \`${REVIEWER_AGENT_NAME}\` specialist per question. a question qualifies only when a specialist could return evidence that **changes your disposition** on the PR \u2014 generic requests for another look, extra confidence, or polish do not. most reviews need zero or one; some need several.
@@ -106471,8 +106533,6 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
106471
106533
 
106472
106534
  6. **aggregate & draft**: when specialist results land, merge findings; de-dup overlaps (two specialists catching the same issue = higher-confidence signal); trace each finding yourself before accepting it. drop praise, style preferences, speculative/unverified claims, findings about pre-existing code unrelated to the PR (heuristic: if the finding's root cause lives in lines this PR added or modified, it's in scope; otherwise drop unless the PR plausibly introduced or amplified the regression), and anything not actionable. also drop **bloat-shaped findings** \u2014 proposed fixes that would add defensive checks for cases that can't happen, abstractions used once, comments restating obvious code, tests asserting tautologies, or "just-in-case" guards. subagents are fallible and bias toward recommending changes; the bar for an actionable inline comment is sound + correct + elegant. recommending a change that improves only one of the three (or worse, degrades elegance to nominally improve correctness) makes the codebase worse, not better.
106473
106535
 
106474
- **Hunt for non-anchored concerns before drafting.** After collecting your anchored findings, deliberately scan for concerns that have no specific line to point at \u2014 typically: deletion / cleanup plans for code the diff replaces or shadows; rollout sequencing (what happens to in-flight state during deploy / revert?); scope questions that only the human can answer (e.g. is the legacy path going away or is this a long-term dual track?); architectural risks the diff opens up that aren't a single-line bug.
106475
-
106476
106536
  for surviving findings, draft inline comments with NEW line numbers from the diff \u2014 attach a \`<details>Technical details</details>\` block to any inline comment whose fix is non-trivial or has cross-file implications (see Inline technical details in the format below). every comment must be actionable, 2-3 sentences max in the visible part, one per root cause \u2014 name its other sites in that comment rather than repeating it. use GitHub permalink format for code references. for impact-analysis findings (stale references after rename/remove), report them in the review body ordered by severity (runtime breakage > incorrect docs > stale comments) rather than as inline comments unless they're anchored to a specific line.
106477
106537
 
106478
106538
  7. **submit**: ALWAYS submit exactly one review via \`${t("create_pull_request_review")}\`. Do NOT call \`report_progress\` \u2014 the review is the final record and the progress comment will be cleaned up automatically.
@@ -106494,7 +106554,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
106494
106554
  \`approved: false\`. Body opens with \`> [!CAUTION]\\n> This PR introduces ...\`, followed by the PR summary. Include all inline comments via \`comments\`.
106495
106555
  - **must-address non-critical findings** (real consequences if shipped \u2014 incorrect behavior in non-critical paths, missing validation on user input, regressions the author should fix before merge):
106496
106556
  \`approved: false\`. Body opens with \`> [!IMPORTANT]\\n> ...\`, followed by the PR summary. Reserve this tier for findings with concrete fallout \u2014 do NOT use \`[!IMPORTANT]\` for nits, style preferences, or "consider also" suggestions. Include all inline comments via \`comments\`.
106497
- - **minor suggestions only** (low-stakes findings, or Nitpicks only):
106557
+ - **minor suggestions only** (low-stakes findings):
106498
106558
  \`approved: false\`. Body opens with \`> \u2139\uFE0F No critical issues \u2014 minor suggestions inline.\\n\\n\` followed by the PR summary. Include all inline comments via \`comments\`. Vary the wording after the emoji to fit the review (e.g. "Minor suggestions only.", "Two rough edges worth a look."), but always keep the \u2139\uFE0F prefix and keep it short.
106499
106559
  - **informational observations** (mergeable as-is, nothing actionable \u2014 e.g. prior feedback addressed cleanly, calling out something noteworthy without recommending a change):
106500
106560
  \`approved: true\`. Body opens with \`> \u2705 No new issues found.\\n\\n\` followed by the PR summary. Do NOT include inline \`comments\` \u2014 the \u2705 signals "no action needed", which contradicts an actionable anchor; if a point is concrete enough to anchor to a line, downgrade the whole review to "minor suggestions only" (\`approved: false\`) instead.
@@ -106537,8 +106597,6 @@ ${PR_SUMMARY_FORMAT}`
106537
106597
 
106538
106598
  5. **triage**: orient on the *incremental* changes \u2014 domain, seams, external contracts, user-facing surfaces. pull as much context as you need to render a confident review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths. **you are the synthesizer.**
106539
106599
 
106540
- a test added or changed in this delta must be able to fail \u2014 one that would still pass with the bug present is theatre, not coverage. the tell is a loose assertion where an exact one belongs (\`>=\` or a truthiness check over an expected value, a snapshot that absorbs whatever it is handed).
106541
-
106542
106600
  skip the deeper pass and jump to step 10's non-substantive path (do NOT submit a review) only when the incremental changes have **no behavioral surface at all** \u2014 formatting, comment tweaks, import reordering, lockfile regen, a mechanical rename of import paths. line count is not the signal: a one-line change to auth, money, SQL, a comparison operator, a redirect, or a config default is not trivial.
106543
106601
 
106544
106602
  6. **specialist decision**: after covering the incremental and full diffs, name the questions about the new changes that you still cannot answer confidently yourself, and dispatch one \`${REVIEWER_AGENT_NAME}\` specialist per question. a question qualifies only when a specialist could return evidence that **changes your disposition** on the PR \u2014 generic requests for another look, extra confidence, or polish do not. most incremental reviews need zero or one, especially thread-reply re-reviews; some need several.
@@ -106562,11 +106620,9 @@ ${PR_SUMMARY_FORMAT}`
106562
106620
 
106563
106621
  8. **aggregate, draft, self-critique**: merge findings (yours + output from every specialist you dispatched); de-dup overlaps; trace each finding yourself. drop praise, style preferences, speculative/unverified claims, findings about pre-existing code unrelated to the new commits, anything not actionable, and anything that re-states prior review feedback (heuristic: if the finding's root cause lives in lines the *new commits* added or modified, it's in scope; otherwise drop). also drop **bloat-shaped findings** \u2014 proposed fixes that would add defensive checks for cases that can't happen, abstractions used once, comments restating obvious code, tests asserting tautologies, or "just-in-case" guards. subagents are fallible and bias toward recommending changes; the bar for an actionable inline comment is sound + correct + elegant. recommending a change that improves only one of the three (or degrades elegance to nominally improve correctness) makes the codebase worse, not better. To compute "lines the new commits added or modified": if \`incrementalDiffPath\` from step 2 is present, use it directly. Otherwise, take the prior Pullfrog review's \`commit_id\` (returned alongside each entry from \`${t("list_pull_request_reviews")}\` in step 4) and run \`git diff <prior-review-sha>..HEAD\` to isolate the lines added since that review.
106564
106622
 
106565
- **Hunt for non-anchored concerns before drafting.** After collecting your anchored findings, deliberately scan for concerns that have no specific line to point at \u2014 typically: deletion / cleanup plans for code the new commits replace or shadow; rollout sequencing (what happens to in-flight state during deploy / revert?); scope questions that only the human can answer (e.g. is the legacy path going away or is this a long-term dual track?); architectural risks the new commits open up that aren't a single-line bug.
106566
-
106567
106623
  draft inline comments with NEW line numbers from the full PR diff \u2014 attach a \`<details>Technical details</details>\` block to any inline comment whose fix is non-trivial or has cross-file implications (see Inline technical details in the format below). every comment must be actionable, 2-3 sentences max in the visible part, one per root cause \u2014 name its other sites in that comment rather than repeating it.
106568
106624
 
106569
- 9. **build the review body**: use the same default format as Review mode (preamble + optional cross-cutting \`### \` sections + optional \`### \u2139\uFE0F Nitpicks\`) \u2014 scoped to the **incremental delta**, not the full PR. The "Reviewed changes" bullets describe what changed since the prior pullfrog review (each bullet starts with a past-tense verb, e.g. \`- Extracted shared CLI runtime into a single module\`). Do NOT include a separate "Prior review feedback" checklist \u2014 that's tracked in the rolling PR summary snapshot for the next agent run, and surfacing it in the user-facing body is noise (changes that addressed prior feedback are already covered by the Reviewed-changes bullets). In some cases you may receive a complete diff for the whole PR instead of an incremental one; when this happens, determine what changed since Pullfrog's most recent review yourself before drafting bullets.
106625
+ 9. **build the review body**: use the same default format as Review mode (preamble + optional cross-cutting \`### \` sections) \u2014 scoped to the **incremental delta**, not the full PR. The "Reviewed changes" bullets describe what changed since the prior pullfrog review (each bullet starts with a past-tense verb, e.g. \`- Extracted shared CLI runtime into a single module\`). Do NOT include a separate "Prior review feedback" checklist \u2014 that's tracked in the rolling PR summary snapshot for the next agent run, and surfacing it in the user-facing body is noise (changes that addressed prior feedback are already covered by the Reviewed-changes bullets). In some cases you may receive a complete diff for the whole PR instead of an incremental one; when this happens, determine what changed since Pullfrog's most recent review yourself before drafting bullets.
106570
106626
 
106571
106627
  10. Submit \u2014 every run must end with EXACTLY ONE of \`${t("create_pull_request_review")}\` (substantive review) or \`${t("report_progress")}\` (no-review acknowledgement). do NOT call \`create_issue_comment\` for review output.
106572
106628
 
@@ -106574,10 +106630,10 @@ ${PR_SUMMARY_FORMAT}`
106574
106630
 
106575
106631
  Follow these rules:
106576
106632
  - note: the first create_pull_request_review submission may error with a one-time diff-coverage nudge listing unread TOC regions. retry the same call to proceed \u2014 optionally after reading the listed ranges. the pre-flight will not block again this session.
106577
- - IF NO NEW ISSUES, NON-SUBSTANTIVE CHANGES ONLY (trivial formatting, import reordering, comment tweaks): do NOT submit a review. Instead call \`${t("report_progress")}\` with a 1-2 sentence note explaining no review was warranted (e.g. "No new issues. Changes since last review are formatting-only."). this leaves a visible signal that the run completed.
106633
+ - IF NO NEW ISSUES, NON-SUBSTANTIVE CHANGES ONLY (trivial formatting, import reordering, comment tweaks): do NOT submit a review. Instead call \`${t("report_progress")}\` with \`no_review_needed: true\` and a 1-2 sentence note explaining no review was warranted (e.g. "No new issues. Changes since last review are formatting-only."). this leaves a visible signal that the run completed.
106578
106634
  - ELSE IF NEW CRITICAL ISSUES (blocks merge \u2014 bugs, security, data loss, broken core flows): call \`${t("create_pull_request_review")}\` with \`approved: false\`, all comments, and the review body. body opens with \`> [!CAUTION]\\n> This PR introduces ...\`, followed by the PR summary using the default format below.
106579
106635
  - ELSE IF NEW MUST-ADDRESS NON-CRITICAL FINDINGS (real consequences if shipped \u2014 incorrect behavior, missing validation, regressions the author should fix before merge): call \`${t("create_pull_request_review")}\` with \`approved: false\`, all comments, and the review body. body opens with \`> [!IMPORTANT]\\n> ...\`, followed by the PR summary using the default format below. Do NOT use this tier for nits, style preferences, or "consider also" suggestions.
106580
- - ELSE IF NEW MINOR SUGGESTIONS ONLY (low-stakes findings, or Nitpicks only): call \`${t("create_pull_request_review")}\` with \`approved: false\`, all comments, and the review body. body opens with \`> \u2139\uFE0F No critical issues \u2014 minor suggestions inline.\\n\\n\` (vary the wording after \u2139\uFE0F to fit the review), followed by the PR summary using the default format below.
106636
+ - ELSE IF NEW MINOR SUGGESTIONS ONLY (low-stakes findings): call \`${t("create_pull_request_review")}\` with \`approved: false\`, all comments, and the review body. body opens with \`> \u2139\uFE0F No critical issues \u2014 minor suggestions inline.\\n\\n\` (vary the wording after \u2139\uFE0F to fit the review), followed by the PR summary using the default format below.
106581
106637
  - ELSE IF INFORMATIONAL OBSERVATIONS (mergeable as-is, but worth surfacing \u2014 e.g. prior feedback addressed cleanly, or a noteworthy positive observation): call \`${t("create_pull_request_review")}\` with \`approved: true\`, NO inline comments, and the review body. body opens with \`> \u2705 No new issues found.\\n\\n\` (or similar friendly green opener), followed by the PR summary using the default format below. If a point is concrete enough to anchor to a line, downgrade the whole review to "minor suggestions only" (\`approved: false\`) instead \u2014 the \u2705 signals "no action needed", which contradicts an actionable anchor.
106582
106638
  - ELSE IF NO NEW ISSUES, SUBSTANTIVE CHANGES (new functionality, behavior changes, or fixes to prior review feedback): call \`${t("create_pull_request_review")}\` to create a PR review. If all previous reviews have been properly addressed and no new issues were discovered, set \`approved: true\`. body opens with \`> \u2705 No new issues found.\\n\\n\`, followed by the PR summary using the default format below.
106583
106639
 
@@ -106735,7 +106791,7 @@ function buildUnsubmittedReviewPrompt(mode) {
106735
106791
  "",
106736
106792
  "do exactly one of:",
106737
106793
  "- if you have findings: call `create_pull_request_review` now with your aggregated review (body + inline comments). the first call may error once with a diff-coverage nudge \u2014 retry the same call to proceed.",
106738
- "- if there are genuinely no actionable findings since the last review (e.g. only formatting / comment / lockfile changes): call `report_progress` with a 1-2 sentence summary explaining that no review was warranted.",
106794
+ "- if there are genuinely no actionable findings since the last review (e.g. only formatting / comment / lockfile changes): call `report_progress` with `no_review_needed: true` and a 1-2 sentence summary explaining that no review was warranted.",
106739
106795
  "",
106740
106796
  "do NOT stop again until one of those tools has been called successfully."
106741
106797
  ].join("\n");
@@ -107716,6 +107772,8 @@ var claude = agent({
107716
107772
  PWD: repoDir
107717
107773
  };
107718
107774
  env2.CLAUDE_CODE_AUTO_COMPACT_WINDOW ||= "500000";
107775
+ env2.CLAUDE_CODE_ENABLE_TODO_TOOLS = "1";
107776
+ env2.CLAUDE_CODE_ENABLE_TASKS = "0";
107719
107777
  if (isBedrockRoute) {
107720
107778
  env2.CLAUDE_CODE_USE_BEDROCK = "1";
107721
107779
  }
@@ -107792,7 +107850,7 @@ import { performance as performance6 } from "node:perf_hooks";
107792
107850
 
107793
107851
  // utils/codexHome.ts
107794
107852
  import { execFileSync as execFileSync3 } from "node:child_process";
107795
- import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync4 } from "node:fs";
107853
+ import { existsSync as existsSync4, mkdirSync as mkdirSync5, mkdtempSync, readFileSync as readFileSync2, writeFileSync as writeFileSync4 } from "node:fs";
107796
107854
  import { homedir as homedir2, userInfo } from "node:os";
107797
107855
  import { join as join5 } from "node:path";
107798
107856
 
@@ -107843,205 +107901,6 @@ function stringifyCodexAuthBody(body) {
107843
107901
  `;
107844
107902
  }
107845
107903
 
107846
- // utils/xaiOAuth.ts
107847
- function parseXaiAuthBody(raw2) {
107848
- let parsed2;
107849
- try {
107850
- parsed2 = JSON.parse(raw2);
107851
- } catch {
107852
- return null;
107853
- }
107854
- if (!parsed2 || typeof parsed2 !== "object") return null;
107855
- const v = parsed2;
107856
- if (v.auth_mode !== "grok") return null;
107857
- const tokens = v.tokens;
107858
- if (!tokens || typeof tokens !== "object") return null;
107859
- const t = tokens;
107860
- if (typeof t.access_token !== "string" || t.access_token.length === 0) return null;
107861
- if (typeof t.refresh_token !== "string" || t.refresh_token.length === 0) return null;
107862
- return {
107863
- auth_mode: "grok",
107864
- ...typeof v.refresh_rejected_at === "string" ? { refresh_rejected_at: v.refresh_rejected_at } : {},
107865
- tokens: { access_token: t.access_token, refresh_token: t.refresh_token },
107866
- ...typeof v.last_refresh === "string" ? { last_refresh: v.last_refresh } : {}
107867
- };
107868
- }
107869
-
107870
- // utils/codexHome.ts
107871
- var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
107872
- var XAI_AUTH_ENV = "GROK_AUTH_JSON";
107873
- var installedSubscriptionPaths = /* @__PURE__ */ new Map();
107874
- function clearInstalledSubscription(name) {
107875
- const path4 = installedSubscriptionPaths.get(name);
107876
- if (!path4 || !existsSync4(path4)) return;
107877
- const value2 = JSON.parse(readFileSync2(path4, "utf8"));
107878
- if (!value2 || typeof value2 !== "object") return;
107879
- if (name === "CODEX_AUTH_JSON" && "openai" in value2) delete value2.openai;
107880
- if (name === "GROK_AUTH_JSON" && "xai" in value2) delete value2.xai;
107881
- writeFileSync4(path4, `${JSON.stringify(value2)}
107882
- `, { mode: 384 });
107883
- installedSubscriptionPaths.delete(name);
107884
- }
107885
- var PULLFROG_DATA_DIR = "/var/lib/pullfrog";
107886
- function isRejectedChain(body) {
107887
- if (!body.refresh_rejected_at) return false;
107888
- log.warning(
107889
- `\xBB ${CODEX_AUTH_ENV} was rejected by OpenAI at ${body.refresh_rejected_at} and cannot be refreshed \u2014 re-run \`npx pullfrog auth codex\`. skipping it for this run.`
107890
- );
107891
- return true;
107892
- }
107893
- function installCodexAuth() {
107894
- const raw2 = process.env[CODEX_AUTH_ENV];
107895
- if (!raw2) return null;
107896
- const body = parseCodexAuthBody(raw2);
107897
- if (!body) {
107898
- log.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
107899
- return null;
107900
- }
107901
- if (isRejectedChain(body)) return null;
107902
- const expiresMs = decodeJwtExpMs(body.tokens.access_token) ?? 0;
107903
- const xdgDataHome = resolveDataHome();
107904
- if (!xdgDataHome) return null;
107905
- const opencodeDir = join5(xdgDataHome, "opencode");
107906
- const authPath = join5(opencodeDir, "auth.json");
107907
- writeOpenCodeAuthEntry({
107908
- opencodeDir,
107909
- authPath,
107910
- provider: "openai",
107911
- entry: {
107912
- type: "oauth",
107913
- refresh: body.tokens.refresh_token,
107914
- access: body.tokens.access_token,
107915
- expires: expiresMs,
107916
- ...body.tokens.account_id ? { accountId: body.tokens.account_id } : {}
107917
- }
107918
- });
107919
- process.env.XDG_DATA_HOME = xdgDataHome;
107920
- log.info(`\xBB installed Codex auth at ${authPath}`);
107921
- installedSubscriptionPaths.set(CODEX_AUTH_ENV, authPath);
107922
- return {
107923
- authPath,
107924
- xdgDataHome,
107925
- originalRefresh: body.tokens.refresh_token,
107926
- originalIdToken: body.tokens.id_token
107927
- };
107928
- }
107929
- function writeOpenCodeAuthEntry(params) {
107930
- let existing = {};
107931
- if (existsSync4(params.authPath)) {
107932
- try {
107933
- const parsed2 = JSON.parse(readFileSync2(params.authPath, "utf8"));
107934
- if (parsed2 && typeof parsed2 === "object") existing = parsed2;
107935
- } catch {
107936
- log.warning(`\xBB ${params.authPath} was unreadable; rewriting it`);
107937
- }
107938
- }
107939
- const merged = { ...existing, [params.provider]: params.entry };
107940
- mkdirSync5(params.opencodeDir, { recursive: true });
107941
- writeFileSync4(params.authPath, `${JSON.stringify(merged, null, 2)}
107942
- `, { mode: 384 });
107943
- }
107944
- function installXaiAuth() {
107945
- const raw2 = process.env[XAI_AUTH_ENV];
107946
- if (!raw2) return null;
107947
- const body = parseXaiAuthBody(raw2);
107948
- if (!body) {
107949
- log.warning(`\xBB ${XAI_AUTH_ENV} present but malformed; ignoring`);
107950
- return null;
107951
- }
107952
- if (isXaiRejectedChain(body)) return null;
107953
- const xdgDataHome = resolveDataHome();
107954
- if (!xdgDataHome) return null;
107955
- const opencodeDir = join5(xdgDataHome, "opencode");
107956
- const authPath = join5(opencodeDir, "auth.json");
107957
- writeOpenCodeAuthEntry({
107958
- opencodeDir,
107959
- authPath,
107960
- provider: "xai",
107961
- // opencode refreshes when `expires` is inside its skew window, and falls
107962
- // back to the JWT's own exp when the stored value is absent. Decoding it
107963
- // here means the first request uses the token we already hold instead of
107964
- // burning a rotation on startup.
107965
- entry: {
107966
- type: "oauth",
107967
- refresh: body.tokens.refresh_token,
107968
- access: body.tokens.access_token,
107969
- expires: decodeJwtExpMs(body.tokens.access_token) ?? 0
107970
- }
107971
- });
107972
- process.env.XDG_DATA_HOME = xdgDataHome;
107973
- log.info(`\xBB installed Grok auth at ${authPath}`);
107974
- installedSubscriptionPaths.set(XAI_AUTH_ENV, authPath);
107975
- return { authPath, xdgDataHome, originalRefresh: body.tokens.refresh_token };
107976
- }
107977
- function isXaiRejectedChain(body) {
107978
- if (!body.refresh_rejected_at) return false;
107979
- log.warning(
107980
- `\xBB ${XAI_AUTH_ENV} was rejected by xAI at ${body.refresh_rejected_at} and cannot be refreshed \u2014 re-run \`npx pullfrog auth grok\`. skipping it for this run.`
107981
- );
107982
- return true;
107983
- }
107984
- function installCodexHome() {
107985
- const raw2 = process.env[CODEX_AUTH_ENV];
107986
- if (!raw2) return null;
107987
- const body = parseCodexAuthBody(raw2);
107988
- if (!body) {
107989
- log.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
107990
- return null;
107991
- }
107992
- if (isRejectedChain(body)) return null;
107993
- if (!body.tokens.id_token) {
107994
- log.warning(
107995
- `\xBB ${CODEX_AUTH_ENV} carries no id_token \u2014 the codex CLI cannot load it. re-run \`npx pullfrog auth codex\` to mint a complete credential.`
107996
- );
107997
- return null;
107998
- }
107999
- const root = resolveDataHome();
108000
- if (!root) return null;
108001
- const codexHome = join5(root, "codex");
108002
- const authPath = join5(codexHome, "auth.json");
108003
- mkdirSync5(codexHome, { recursive: true });
108004
- writeFileSync4(authPath, stringifyCodexAuthBody(body), { mode: 384 });
108005
- log.info(`\xBB installed Codex auth at ${authPath}`);
108006
- return { codexHome, authPath, originalRefresh: body.tokens.refresh_token };
108007
- }
108008
- var dataHome;
108009
- var unhostedSubscriptions = /* @__PURE__ */ new Set();
108010
- function canInstallSubscription(name) {
108011
- return name !== CODEX_AUTH_ENV && name !== XAI_AUTH_ENV || resolveDataHome() !== null;
108012
- }
108013
- function resolveDataHome() {
108014
- if (process.env.CI !== "true") return join5(homedir2(), ".local", "share");
108015
- if (dataHome === void 0) dataHome = bootstrapPullfrogDataDir();
108016
- return dataHome;
108017
- }
108018
- function bootstrapPullfrogDataDir() {
108019
- const user = userInfo().username;
108020
- let primaryGroup;
108021
- try {
108022
- primaryGroup = execFileSync3("id", ["-gn", user], { stdio: "pipe", encoding: "utf-8" }).trim();
108023
- } catch {
108024
- primaryGroup = user;
108025
- }
108026
- try {
108027
- execFileSync3("sudo", ["-n", "mkdir", "-p", PULLFROG_DATA_DIR], { stdio: "pipe" });
108028
- execFileSync3("sudo", ["-n", "chown", `${user}:${primaryGroup}`, PULLFROG_DATA_DIR], {
108029
- stdio: "pipe"
108030
- });
108031
- execFileSync3("sudo", ["-n", "chmod", "700", PULLFROG_DATA_DIR], { stdio: "pipe" });
108032
- } catch (err) {
108033
- log.warning(
108034
- `\xBB subscription auth skipped: could not create ${PULLFROG_DATA_DIR} (${err instanceof Error ? err.message : String(err)}). it must live outside the agent sandbox's reach, which needs passwordless sudo on this runner; runs on a model that needs the subscription will report no key.`
108035
- );
108036
- for (const name of [CODEX_AUTH_ENV, XAI_AUTH_ENV]) {
108037
- if (process.env[name]) unhostedSubscriptions.add(name);
108038
- delete process.env[name];
108039
- }
108040
- return null;
108041
- }
108042
- return PULLFROG_DATA_DIR;
108043
- }
108044
-
108045
107904
  // utils/oauthWriteback.ts
108046
107905
  var core2 = __toESM(require_core(), 1);
108047
107906
 
@@ -110271,6 +110130,7 @@ async function apiFetch(options) {
110271
110130
 
110272
110131
  // utils/oauthWriteback.ts
110273
110132
  var OAUTH_WRITEBACK_STATE = "oauth_writeback";
110133
+ var SUBSCRIPTION_DATA_DIR_STATE = "subscription_data_dir";
110274
110134
 
110275
110135
  // utils/secretCommands.ts
110276
110136
  var core3 = __toESM(require_core(), 1);
@@ -110281,6 +110141,212 @@ function saveSecretState(name, value2) {
110281
110141
  if (isGitHubActions) core3.saveState(name, value2);
110282
110142
  }
110283
110143
 
110144
+ // utils/xaiOAuth.ts
110145
+ function parseXaiAuthBody(raw2) {
110146
+ let parsed2;
110147
+ try {
110148
+ parsed2 = JSON.parse(raw2);
110149
+ } catch {
110150
+ return null;
110151
+ }
110152
+ if (!parsed2 || typeof parsed2 !== "object") return null;
110153
+ const v = parsed2;
110154
+ if (v.auth_mode !== "grok") return null;
110155
+ const tokens = v.tokens;
110156
+ if (!tokens || typeof tokens !== "object") return null;
110157
+ const t = tokens;
110158
+ if (typeof t.access_token !== "string" || t.access_token.length === 0) return null;
110159
+ if (typeof t.refresh_token !== "string" || t.refresh_token.length === 0) return null;
110160
+ return {
110161
+ auth_mode: "grok",
110162
+ ...typeof v.refresh_rejected_at === "string" ? { refresh_rejected_at: v.refresh_rejected_at } : {},
110163
+ tokens: { access_token: t.access_token, refresh_token: t.refresh_token },
110164
+ ...typeof v.last_refresh === "string" ? { last_refresh: v.last_refresh } : {}
110165
+ };
110166
+ }
110167
+
110168
+ // utils/codexHome.ts
110169
+ var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
110170
+ var XAI_AUTH_ENV = "GROK_AUTH_JSON";
110171
+ var installedSubscriptionPaths = /* @__PURE__ */ new Map();
110172
+ var installedDataHome;
110173
+ function getSubscriptionDataHome() {
110174
+ return installedDataHome;
110175
+ }
110176
+ function clearInstalledSubscription(name) {
110177
+ const path4 = installedSubscriptionPaths.get(name);
110178
+ if (!path4 || !existsSync4(path4)) return;
110179
+ const value2 = JSON.parse(readFileSync2(path4, "utf8"));
110180
+ if (!value2 || typeof value2 !== "object") return;
110181
+ if (name === "CODEX_AUTH_JSON" && "openai" in value2) delete value2.openai;
110182
+ if (name === "GROK_AUTH_JSON" && "xai" in value2) delete value2.xai;
110183
+ writeFileSync4(path4, `${JSON.stringify(value2)}
110184
+ `, { mode: 384 });
110185
+ installedSubscriptionPaths.delete(name);
110186
+ }
110187
+ var PULLFROG_DATA_DIR = "/var/lib/pullfrog";
110188
+ function isRejectedChain(body) {
110189
+ if (!body.refresh_rejected_at) return false;
110190
+ log.warning(
110191
+ `\xBB ${CODEX_AUTH_ENV} was rejected by OpenAI at ${body.refresh_rejected_at} and cannot be refreshed \u2014 re-run \`npx pullfrog auth codex\`. skipping it for this run.`
110192
+ );
110193
+ return true;
110194
+ }
110195
+ function installCodexAuth() {
110196
+ const raw2 = process.env[CODEX_AUTH_ENV];
110197
+ if (!raw2) return null;
110198
+ const body = parseCodexAuthBody(raw2);
110199
+ if (!body) {
110200
+ log.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
110201
+ return null;
110202
+ }
110203
+ if (isRejectedChain(body)) return null;
110204
+ const expiresMs = decodeJwtExpMs(body.tokens.access_token) ?? 0;
110205
+ const xdgDataHome = resolveDataHome();
110206
+ if (!xdgDataHome) return null;
110207
+ const opencodeDir = join5(xdgDataHome, "opencode");
110208
+ const authPath = join5(opencodeDir, "auth.json");
110209
+ writeOpenCodeAuthEntry({
110210
+ opencodeDir,
110211
+ authPath,
110212
+ provider: "openai",
110213
+ entry: {
110214
+ type: "oauth",
110215
+ refresh: body.tokens.refresh_token,
110216
+ access: body.tokens.access_token,
110217
+ expires: expiresMs,
110218
+ ...body.tokens.account_id ? { accountId: body.tokens.account_id } : {}
110219
+ }
110220
+ });
110221
+ installedDataHome = xdgDataHome;
110222
+ log.info(`\xBB installed Codex auth at ${authPath}`);
110223
+ installedSubscriptionPaths.set(CODEX_AUTH_ENV, authPath);
110224
+ return {
110225
+ authPath,
110226
+ originalRefresh: body.tokens.refresh_token,
110227
+ originalIdToken: body.tokens.id_token
110228
+ };
110229
+ }
110230
+ function writeOpenCodeAuthEntry(params) {
110231
+ let existing = {};
110232
+ if (existsSync4(params.authPath)) {
110233
+ try {
110234
+ const parsed2 = JSON.parse(readFileSync2(params.authPath, "utf8"));
110235
+ if (parsed2 && typeof parsed2 === "object") existing = parsed2;
110236
+ } catch {
110237
+ log.warning(`\xBB ${params.authPath} was unreadable; rewriting it`);
110238
+ }
110239
+ }
110240
+ const merged = { ...existing, [params.provider]: params.entry };
110241
+ mkdirSync5(params.opencodeDir, { recursive: true });
110242
+ writeFileSync4(params.authPath, `${JSON.stringify(merged, null, 2)}
110243
+ `, { mode: 384 });
110244
+ }
110245
+ function installXaiAuth() {
110246
+ const raw2 = process.env[XAI_AUTH_ENV];
110247
+ if (!raw2) return null;
110248
+ const body = parseXaiAuthBody(raw2);
110249
+ if (!body) {
110250
+ log.warning(`\xBB ${XAI_AUTH_ENV} present but malformed; ignoring`);
110251
+ return null;
110252
+ }
110253
+ if (isXaiRejectedChain(body)) return null;
110254
+ const xdgDataHome = resolveDataHome();
110255
+ if (!xdgDataHome) return null;
110256
+ const opencodeDir = join5(xdgDataHome, "opencode");
110257
+ const authPath = join5(opencodeDir, "auth.json");
110258
+ writeOpenCodeAuthEntry({
110259
+ opencodeDir,
110260
+ authPath,
110261
+ provider: "xai",
110262
+ // opencode refreshes when `expires` is inside its skew window, and falls
110263
+ // back to the JWT's own exp when the stored value is absent. Decoding it
110264
+ // here means the first request uses the token we already hold instead of
110265
+ // burning a rotation on startup.
110266
+ entry: {
110267
+ type: "oauth",
110268
+ refresh: body.tokens.refresh_token,
110269
+ access: body.tokens.access_token,
110270
+ expires: decodeJwtExpMs(body.tokens.access_token) ?? 0
110271
+ }
110272
+ });
110273
+ installedDataHome = xdgDataHome;
110274
+ log.info(`\xBB installed Grok auth at ${authPath}`);
110275
+ installedSubscriptionPaths.set(XAI_AUTH_ENV, authPath);
110276
+ return { authPath, originalRefresh: body.tokens.refresh_token };
110277
+ }
110278
+ function isXaiRejectedChain(body) {
110279
+ if (!body.refresh_rejected_at) return false;
110280
+ log.warning(
110281
+ `\xBB ${XAI_AUTH_ENV} was rejected by xAI at ${body.refresh_rejected_at} and cannot be refreshed \u2014 re-run \`npx pullfrog auth grok\`. skipping it for this run.`
110282
+ );
110283
+ return true;
110284
+ }
110285
+ function installCodexHome() {
110286
+ const raw2 = process.env[CODEX_AUTH_ENV];
110287
+ if (!raw2) return null;
110288
+ const body = parseCodexAuthBody(raw2);
110289
+ if (!body) {
110290
+ log.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
110291
+ return null;
110292
+ }
110293
+ if (isRejectedChain(body)) return null;
110294
+ if (!body.tokens.id_token) {
110295
+ log.warning(
110296
+ `\xBB ${CODEX_AUTH_ENV} carries no id_token \u2014 the codex CLI cannot load it. re-run \`npx pullfrog auth codex\` to mint a complete credential.`
110297
+ );
110298
+ return null;
110299
+ }
110300
+ const root = resolveDataHome();
110301
+ if (!root) return null;
110302
+ const codexHome = join5(root, "codex");
110303
+ const authPath = join5(codexHome, "auth.json");
110304
+ mkdirSync5(codexHome, { recursive: true });
110305
+ writeFileSync4(authPath, stringifyCodexAuthBody(body), { mode: 384 });
110306
+ log.info(`\xBB installed Codex auth at ${authPath}`);
110307
+ return { codexHome, authPath, originalRefresh: body.tokens.refresh_token };
110308
+ }
110309
+ var dataHome;
110310
+ var unhostedSubscriptions = /* @__PURE__ */ new Set();
110311
+ function canInstallSubscription(name) {
110312
+ return name !== CODEX_AUTH_ENV && name !== XAI_AUTH_ENV || resolveDataHome() !== null;
110313
+ }
110314
+ function resolveDataHome() {
110315
+ if (process.env.CI !== "true") return join5(homedir2(), ".local", "share");
110316
+ if (dataHome === void 0) dataHome = bootstrapPullfrogDataDir();
110317
+ return dataHome;
110318
+ }
110319
+ function bootstrapPullfrogDataDir() {
110320
+ const user = userInfo().username;
110321
+ let primaryGroup;
110322
+ try {
110323
+ primaryGroup = execFileSync3("id", ["-gn", user], { stdio: "pipe", encoding: "utf-8" }).trim();
110324
+ } catch {
110325
+ primaryGroup = user;
110326
+ }
110327
+ try {
110328
+ execFileSync3("sudo", ["-n", "mkdir", "-p", PULLFROG_DATA_DIR], { stdio: "pipe" });
110329
+ execFileSync3("sudo", ["-n", "chown", `${user}:${primaryGroup}`, PULLFROG_DATA_DIR], {
110330
+ stdio: "pipe"
110331
+ });
110332
+ execFileSync3("sudo", ["-n", "chmod", "700", PULLFROG_DATA_DIR], { stdio: "pipe" });
110333
+ } catch (err) {
110334
+ log.warning(
110335
+ `\xBB subscription auth skipped: could not create ${PULLFROG_DATA_DIR} (${err instanceof Error ? err.message : String(err)}). it must live outside the agent sandbox's reach, which needs passwordless sudo on this runner; runs on a model that needs the subscription will report no key.`
110336
+ );
110337
+ for (const name of [CODEX_AUTH_ENV, XAI_AUTH_ENV]) {
110338
+ if (process.env[name]) unhostedSubscriptions.add(name);
110339
+ delete process.env[name];
110340
+ }
110341
+ return null;
110342
+ }
110343
+ const runDir = mkdtempSync(join5(PULLFROG_DATA_DIR, "run-"));
110344
+ if (process.env.RUNNER_ENVIRONMENT !== "github-hosted") {
110345
+ saveSecretState(SUBSCRIPTION_DATA_DIR_STATE, runDir);
110346
+ }
110347
+ return runDir;
110348
+ }
110349
+
110284
110350
  // utils/secrets.ts
110285
110351
  var SENSITIVE_PATTERNS = [
110286
110352
  /_KEY$/i,
@@ -110927,9 +110993,8 @@ var codex = agent({
110927
110993
  initialUsage: initial.usage,
110928
110994
  reflectionPrompt: buildReflectionPrompt(ctx.toolState),
110929
110995
  // a resume needs the thread codex recorded. without one there is nothing
110930
- // to continue, and `resume --last` would be worse than not resuming: it
110931
- // picks the newest recorded session in this cwd, which on a self-hosted
110932
- // runner with a shared CODEX_HOME is somebody else's run.
110996
+ // to continue, and `resume --last` could pick another run's session off CI,
110997
+ // where CODEX_HOME is the shared ~/.local/share/codex (in CI it is per run).
110933
110998
  canResume: (r) => r.threadId !== void 0,
110934
110999
  resume: async (c) => runCodex({
110935
111000
  ...runnerArgs,
@@ -110955,7 +111020,8 @@ var codex = agent({
110955
111020
  // agents/opencode.ts
110956
111021
  import { spawn as nodeSpawn2 } from "node:child_process";
110957
111022
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "node:fs";
110958
- import { join as join7 } from "node:path";
111023
+ import { totalmem } from "node:os";
111024
+ import { join as join8 } from "node:path";
110959
111025
  import { performance as performance7 } from "node:perf_hooks";
110960
111026
 
110961
111027
  // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/core/serverSentEvents.gen.js
@@ -111232,13 +111298,13 @@ var defaultPathSerializer = ({ path: path4, url: _url3 }) => {
111232
111298
  }
111233
111299
  return url4;
111234
111300
  };
111235
- var getUrl = ({ baseUrl, path: path4, query, querySerializer, url: _url3 }) => {
111301
+ var getUrl = ({ baseUrl, path: path4, query: query2, querySerializer, url: _url3 }) => {
111236
111302
  const pathUrl = _url3.startsWith("/") ? _url3 : `/${_url3}`;
111237
111303
  let url4 = (baseUrl ?? "") + pathUrl;
111238
111304
  if (path4) {
111239
111305
  url4 = defaultPathSerializer({ path: path4, url: url4 });
111240
111306
  }
111241
- let search2 = query ? querySerializer(query) : "";
111307
+ let search2 = query2 ? querySerializer(query2) : "";
111242
111308
  if (search2.startsWith("?")) {
111243
111309
  search2 = search2.substring(1);
111244
111310
  }
@@ -116306,9 +116372,9 @@ function rewrite(request2, values) {
116306
116372
  const value2 = pick2(request2.headers.get(name), key === "directory" ? values.directory : values.workspace, key === "directory" ? encodeURIComponent : void 0);
116307
116373
  if (!value2)
116308
116374
  continue;
116309
- for (const query of url4.pathname.startsWith("/api/") ? [key, `location[${key}]`] : [key]) {
116310
- if (!url4.searchParams.has(query)) {
116311
- url4.searchParams.set(query, value2);
116375
+ for (const query2 of url4.pathname.startsWith("/api/") ? [key, `location[${key}]`] : [key]) {
116376
+ if (!url4.searchParams.has(query2)) {
116377
+ url4.searchParams.set(query2, value2);
116312
116378
  }
116313
116379
  }
116314
116380
  changed = true;
@@ -116364,6 +116430,68 @@ var import_cross_spawn = __toESM(require_cross_spawn(), 1);
116364
116430
  // agents/opencode.ts
116365
116431
  var import_undici = __toESM(require_undici2(), 1);
116366
116432
 
116433
+ // utils/openCodeModels.ts
116434
+ import { spawnSync as spawnSync3 } from "node:child_process";
116435
+ import { join as join7 } from "node:path";
116436
+ var baseline;
116437
+ var authorized;
116438
+ var failure;
116439
+ var ANSI_PATTERN = /\[[0-9;]*m/g;
116440
+ function openCodeHomeEnv(tmpdir3) {
116441
+ return {
116442
+ HOME: tmpdir3,
116443
+ XDG_CONFIG_HOME: join7(tmpdir3, ".config"),
116444
+ XDG_DATA_HOME: getSubscriptionDataHome() ?? join7(tmpdir3, ".local", "share"),
116445
+ XDG_CACHE_HOME: join7(tmpdir3, ".cache"),
116446
+ XDG_STATE_HOME: join7(tmpdir3, ".local", "state")
116447
+ };
116448
+ }
116449
+ function readModels(params) {
116450
+ const result = spawnSync3(params.cliPath, ["models"], {
116451
+ encoding: "utf-8",
116452
+ timeout: 3e4,
116453
+ env: { ...process.env, ...openCodeHomeEnv(params.tmpdir) },
116454
+ stdio: ["ignore", "pipe", "pipe"]
116455
+ });
116456
+ if (result.status !== 0) {
116457
+ const stderr = (result.stderr ?? "").replace(ANSI_PATTERN, "").trim();
116458
+ failure = stderr || result.error?.message;
116459
+ log.debug(`\xBB \`opencode models\` failed (${result.status}): ${failure}`);
116460
+ return /* @__PURE__ */ new Set();
116461
+ }
116462
+ failure = void 0;
116463
+ return new Set(
116464
+ result.stdout.split("\n").map((line) => line.trim()).filter(Boolean)
116465
+ );
116466
+ }
116467
+ function getModelsFailure() {
116468
+ return failure;
116469
+ }
116470
+ function captureBaselineModels(params) {
116471
+ baseline = readModels(params);
116472
+ log.debug(`\xBB opencode baseline: ${baseline.size} models`);
116473
+ }
116474
+ function captureAuthorizedModels(params) {
116475
+ authorized = readModels(params);
116476
+ const base = baseline;
116477
+ if (base) {
116478
+ const diff = [...authorized].filter((m) => !base.has(m));
116479
+ if (diff.length > 0) {
116480
+ log.info(`\xBB BYOK auth enabled ${diff.length} model(s): ${diff.join(", ")}`);
116481
+ }
116482
+ }
116483
+ log.debug(`\xBB opencode authorized: ${authorized.size} models`);
116484
+ }
116485
+ function authorizeModel(model) {
116486
+ getAuthorizedModels().add(model);
116487
+ }
116488
+ function getAuthorizedModels() {
116489
+ if (!authorized) {
116490
+ throw new Error("getAuthorizedModels called before captureAuthorizedModels");
116491
+ }
116492
+ return authorized;
116493
+ }
116494
+
116367
116495
  // utils/worktree.ts
116368
116496
  async function dirtyTrackedPaths(params = {}) {
116369
116497
  const result = await spawn({
@@ -116480,7 +116608,8 @@ export default async function pullfrogEventsPlugin() {
116480
116608
  };
116481
116609
  }
116482
116610
  `;
116483
- function buildOpencodeSubagentGateSource(deniedTools) {
116611
+ function buildOpencodeSubagentGateSource(ctx) {
116612
+ const bashRefusal = ctx.payload.shell === "disabled" ? "shell execution is disabled for this run and no shell tool exists \u2014 use read, grep and glob instead." : `native bash is disabled in Pullfrog runs \u2014 call ${formatMcpToolRef("opencode", "shell")} with the same command instead.`;
116484
116613
  return `// AUTOGENERATED by Pullfrog. do not edit; it'll be overwritten on the next run.
116485
116614
  // hard-blocks state-mutating MCP tool calls from opencode subagents (PR
116486
116615
  // following pullfrog/app#796 \u2014 see action/agents/subagentToolGates.ts). see
@@ -116491,7 +116620,7 @@ function buildOpencodeSubagentGateSource(deniedTools) {
116491
116620
  // embedded at template-render time so the plugin source has no imports. the
116492
116621
  // denied set is derived from each tool's \`mutates\` flag \u2014 see
116493
116622
  // action/agents/subagentToolGates.ts.
116494
- const SUBAGENT_DENIED_TOOLS = new Set(${JSON.stringify(deniedTools)});
116623
+ const SUBAGENT_DENIED_TOOLS = new Set(${JSON.stringify(ctx.subagentDeniedTools)});
116495
116624
 
116496
116625
  // opencode presents MCP tools as \`pullfrog_<bare>\`; strip that to compare
116497
116626
  // against the canonical bare names in SUBAGENT_DENIED_TOOLS. native tools
@@ -116527,7 +116656,7 @@ export default async function pullfrogSubagentGatePlugin() {
116527
116656
  // above the sessionID check: the bash block must never depend on a
116528
116657
  // session id being present.
116529
116658
  if (tool === "bash") {
116530
- throw new Error("the native bash tool is disabled in Pullfrog runs.");
116659
+ throw new Error(${JSON.stringify(bashRefusal)});
116531
116660
  }
116532
116661
  if (!sessionID || !tool) return;
116533
116662
  if (orchestratorSessionID === undefined) orchestratorSessionID = sessionID;
@@ -116547,58 +116676,6 @@ export default async function pullfrogSubagentGatePlugin() {
116547
116676
  `;
116548
116677
  }
116549
116678
 
116550
- // utils/openCodeModels.ts
116551
- import { spawnSync as spawnSync3 } from "node:child_process";
116552
- var baseline;
116553
- var authorized;
116554
- var failure;
116555
- var ANSI_PATTERN = /\[[0-9;]*m/g;
116556
- function readModels(cliPath) {
116557
- const result = spawnSync3(cliPath, ["models"], {
116558
- encoding: "utf-8",
116559
- timeout: 3e4,
116560
- env: process.env,
116561
- stdio: ["ignore", "pipe", "pipe"]
116562
- });
116563
- if (result.status !== 0) {
116564
- const stderr = (result.stderr ?? "").replace(ANSI_PATTERN, "").trim();
116565
- failure = stderr || result.error?.message;
116566
- log.debug(`\xBB \`opencode models\` failed (${result.status}): ${failure}`);
116567
- return /* @__PURE__ */ new Set();
116568
- }
116569
- failure = void 0;
116570
- return new Set(
116571
- result.stdout.split("\n").map((line) => line.trim()).filter(Boolean)
116572
- );
116573
- }
116574
- function getModelsFailure() {
116575
- return failure;
116576
- }
116577
- function captureBaselineModels(cliPath) {
116578
- baseline = readModels(cliPath);
116579
- log.debug(`\xBB opencode baseline: ${baseline.size} models`);
116580
- }
116581
- function captureAuthorizedModels(cliPath) {
116582
- authorized = readModels(cliPath);
116583
- const base = baseline;
116584
- if (base) {
116585
- const diff = [...authorized].filter((m) => !base.has(m));
116586
- if (diff.length > 0) {
116587
- log.info(`\xBB BYOK auth enabled ${diff.length} model(s): ${diff.join(", ")}`);
116588
- }
116589
- }
116590
- log.debug(`\xBB opencode authorized: ${authorized.size} models`);
116591
- }
116592
- function authorizeModel(model) {
116593
- getAuthorizedModels().add(model);
116594
- }
116595
- function getAuthorizedModels() {
116596
- if (!authorized) {
116597
- throw new Error("getAuthorizedModels called before captureAuthorizedModels");
116598
- }
116599
- return authorized;
116600
- }
116601
-
116602
116679
  // agents/subagentModels.ts
116603
116680
  function deriveSubagentModels(orchestratorSpec) {
116604
116681
  if (!orchestratorSpec) return { reviewer: void 0 };
@@ -116828,11 +116905,12 @@ function bootOpencodeServer(params) {
116828
116905
  log.debug(`[opencode serve] ${text}`);
116829
116906
  });
116830
116907
  let closed = false;
116908
+ let exitCause;
116831
116909
  const close = async () => {
116832
116910
  if (closed) return;
116833
116911
  closed = true;
116834
116912
  untrackChild(proc);
116835
- if (proc.pid && !proc.killed) {
116913
+ if (proc.pid && proc.exitCode === null && proc.signalCode === null) {
116836
116914
  try {
116837
116915
  process.kill(-proc.pid, "SIGTERM");
116838
116916
  } catch {
@@ -116866,7 +116944,7 @@ function bootOpencodeServer(params) {
116866
116944
  if (match3?.[1]) {
116867
116945
  resolved = true;
116868
116946
  log.info(`\xBB opencode server up: ${match3[1]}`);
116869
- resolve3({ baseUrl: match3[1], proc, close, recentStderr });
116947
+ resolve3({ baseUrl: match3[1], proc, close, recentStderr, exitCause: () => exitCause });
116870
116948
  }
116871
116949
  }
116872
116950
  const lines = text.split("\n");
@@ -116883,6 +116961,11 @@ function bootOpencodeServer(params) {
116883
116961
  reject(new Error(`failed to spawn opencode serve: ${err.message}`));
116884
116962
  }
116885
116963
  });
116964
+ proc.once("exit", (code, signal) => {
116965
+ if (!resolved || !isTrackedChild(proc)) return;
116966
+ exitCause = describeServerExit(code, signal);
116967
+ log.warning(withServerStderr(`\xBB ${exitCause}`, recentStderr));
116968
+ });
116886
116969
  proc.once("close", (code, signal) => {
116887
116970
  if (!resolved) {
116888
116971
  const tail = recentStderr.slice(-5).join("\n");
@@ -116916,6 +116999,12 @@ function withServerStderr(message, recentStderr) {
116916
116999
  opencode server stderr:
116917
117000
  ${tail}` : message;
116918
117001
  }
117002
+ function describeServerExit(code, signal) {
117003
+ if (signal !== "SIGKILL") return `opencode server exited mid-run (${signal ?? `code ${code}`})`;
117004
+ const limit = process.constrainedMemory();
117005
+ const memoryGb = ((limit > 0 ? Math.min(limit, totalmem()) : totalmem()) / 1024 ** 3).toFixed(1);
117006
+ return `opencode server was killed (SIGKILL) mid-run, usually by the kernel OOM killer \u2014 this runner has ${memoryGb} GB of memory`;
117007
+ }
116919
117008
  function newTurn() {
116920
117009
  return {
116921
117010
  finalText: "",
@@ -116961,6 +117050,7 @@ async function dispatchEvent(ctx, event) {
116961
117050
  const err = event.properties.error;
116962
117051
  const message = err ? extractErrorMessage(err) : "(no error payload)";
116963
117052
  log.info(`\xBB ${ctx.label} session error: ${message}`);
117053
+ if (err) ctx.lastSessionError = message;
116964
117054
  return;
116965
117055
  }
116966
117056
  }
@@ -117099,6 +117189,7 @@ async function runPromptTurn(ctx, params) {
117099
117189
  ctx.promptMessageID = void 0;
117100
117190
  ctx.diagnostic.sawModelOutput = false;
117101
117191
  ctx.diagnostic.idleSec = void 0;
117192
+ ctx.lastSessionError = void 0;
117102
117193
  let assistant;
117103
117194
  let returnedParts;
117104
117195
  let networkError = null;
@@ -117141,12 +117232,14 @@ async function runPromptTurn(ctx, params) {
117141
117232
  costUsd: usage.costUsd
117142
117233
  });
117143
117234
  }
117144
- const diagnosis = ctx.diagnostic.lastProviderError ? ` \u2014 likely cause: ${ctx.diagnostic.lastProviderError}` : "";
117235
+ const cause = ctx.serverExitCause() ?? ctx.diagnostic.lastProviderError;
117236
+ const diagnosis = cause ? ` \u2014 likely cause: ${cause}` : "";
117237
+ const promptCause = ctx.serverExitCause() ?? (ctx.diagnostic.sawModelOutput ? void 0 : ctx.lastSessionError) ?? ctx.diagnostic.lastProviderError;
117145
117238
  if (networkError) {
117146
117239
  return {
117147
117240
  success: false,
117148
117241
  output: finalText,
117149
- error: params.signal.aborted ? `activity timeout: the model went silent and the turn was aborted by the activity watchdog (${networkError})${diagnosis}` : `opencode prompt failed: ${networkError}${diagnosis}`,
117242
+ error: params.signal.aborted ? `activity timeout: the model went silent and the turn was aborted by the activity watchdog (${networkError})${diagnosis}` : `opencode prompt failed: ${networkError}${promptCause ? ` \u2014 likely cause: ${promptCause}` : ""}`,
117150
117243
  usage
117151
117244
  };
117152
117245
  }
@@ -117338,15 +117431,14 @@ var opencode = agent({
117338
117431
  const isBedrockRoute = rawModel !== void 0 && bedrockModelId !== void 0 && bedrockModelId === rawModel;
117339
117432
  const vertexModel = resolveVertexOpenCodeModel(rawModel);
117340
117433
  const model = vertexModel ?? (isBedrockRoute ? `amazon-bedrock/${rawModel}` : rawModel);
117341
- const homeEnv = {
117342
- HOME: ctx.tmpdir,
117343
- XDG_CONFIG_HOME: join7(ctx.tmpdir, ".config")
117344
- };
117345
- const opencodePluginDir = join7(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
117434
+ const codexAuth = installCodexAuth();
117435
+ const xaiAuth = installXaiAuth();
117436
+ const homeEnv = openCodeHomeEnv(ctx.tmpdir);
117437
+ const opencodePluginDir = join8(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
117346
117438
  mkdirSync7(opencodePluginDir, { recursive: true });
117347
117439
  writeFileSync6(
117348
- join7(opencodePluginDir, PULLFROG_OPENCODE_GATE_PLUGIN_FILENAME),
117349
- buildOpencodeSubagentGateSource(ctx.subagentDeniedTools)
117440
+ join8(opencodePluginDir, PULLFROG_OPENCODE_GATE_PLUGIN_FILENAME),
117441
+ buildOpencodeSubagentGateSource(ctx)
117350
117442
  );
117351
117443
  const agentBrowserVersion = getDevDependencyVersion("agent-browser");
117352
117444
  addSkill({
@@ -117356,8 +117448,6 @@ var opencode = agent({
117356
117448
  agent: "opencode"
117357
117449
  });
117358
117450
  installBundledSkills({ home: homeEnv.HOME });
117359
- const codexAuth = installCodexAuth();
117360
- const xaiAuth = installXaiAuth();
117361
117451
  const permissionOverride = JSON.stringify({
117362
117452
  external_directory: { "*": "deny", "/tmp/*": "allow" },
117363
117453
  read: { "*": "allow", ...GIT_NATIVE_READ_DENY_OPENCODE },
@@ -117376,7 +117466,6 @@ var opencode = agent({
117376
117466
  delete env2.OPENCODE_EXPERIMENTAL_CODE_MODE;
117377
117467
  const writebacks = [];
117378
117468
  if (codexAuth) {
117379
- env2.XDG_DATA_HOME = codexAuth.xdgDataHome;
117380
117469
  delete env2.OPENAI_API_KEY;
117381
117470
  writebacks.push({
117382
117471
  secretName: "CODEX_AUTH_JSON",
@@ -117387,7 +117476,6 @@ var opencode = agent({
117387
117476
  });
117388
117477
  }
117389
117478
  if (xaiAuth) {
117390
- env2.XDG_DATA_HOME = xaiAuth.xdgDataHome;
117391
117479
  delete env2.XAI_API_KEY;
117392
117480
  writebacks.push({
117393
117481
  secretName: "GROK_AUTH_JSON",
@@ -117459,6 +117547,8 @@ var opencode = agent({
117459
117547
  loggedToolCallIDs: /* @__PURE__ */ new Set(),
117460
117548
  mcpToolCalls: 0,
117461
117549
  recentStderr: server.recentStderr,
117550
+ serverExitCause: server.exitCause,
117551
+ lastSessionError: void 0,
117462
117552
  diagnostic: {
117463
117553
  label: "Pullfrog",
117464
117554
  recentStderr: server.recentStderr,
@@ -122889,10 +122979,10 @@ function getMatch(pattern, exp) {
122889
122979
  }
122890
122980
  function parseQuery(pattern, options = {}) {
122891
122981
  return pattern.replace(/\\\|/g, ESCAPED_PIPE).split(OR_TOKEN).map((item) => {
122892
- const query = tokenize(item.replace(/\u0000/g, "|").trim()).filter((item2) => item2 && !!item2.trim());
122982
+ const query2 = tokenize(item.replace(/\u0000/g, "|").trim()).filter((item2) => item2 && !!item2.trim());
122893
122983
  const results = [];
122894
- for (let i = 0, len = query.length; i < len; i += 1) {
122895
- const queryItem = query[i];
122984
+ for (let i = 0, len = query2.length; i < len; i += 1) {
122985
+ const queryItem = query2[i];
122896
122986
  let found = false;
122897
122987
  let idx = -1;
122898
122988
  while (!found && ++idx < matchersLen) {
@@ -122940,8 +123030,8 @@ var ExtendedSearch = class {
122940
123030
  return options.useExtendedSearch;
122941
123031
  }
122942
123032
  searchIn(text) {
122943
- const query = this.query;
122944
- if (!query) return {
123033
+ const query2 = this.query;
123034
+ if (!query2) return {
122945
123035
  isMatch: false,
122946
123036
  score: 1
122947
123037
  };
@@ -122952,8 +123042,8 @@ var ExtendedSearch = class {
122952
123042
  const allIndices = [];
122953
123043
  let totalScore = 0;
122954
123044
  let hasInverse = false;
122955
- for (let i = 0, qLen = query.length; i < qLen; i += 1) {
122956
- const searchers = query[i];
123045
+ for (let i = 0, qLen = query2.length; i < qLen; i += 1) {
123046
+ const searchers = query2[i];
122957
123047
  allIndices.length = 0;
122958
123048
  numMatches = 0;
122959
123049
  hasInverse = false;
@@ -123009,26 +123099,26 @@ var KeyType = {
123009
123099
  PATH: "$path",
123010
123100
  PATTERN: "$val"
123011
123101
  };
123012
- var isExpression = (query) => !!(query[LogicalOperator.AND] || query[LogicalOperator.OR]);
123013
- var isPath = (query) => !!query[KeyType.PATH];
123014
- var isLeaf = (query) => !isArray2(query) && isObject2(query) && !isExpression(query);
123015
- var convertToExplicit = (query) => ({ [LogicalOperator.AND]: Object.keys(query).map((key) => ({ [key]: query[key] })) });
123016
- function parse3(query, options, { auto = true } = {}) {
123017
- const next2 = (query2) => {
123018
- if (isString(query2)) {
123102
+ var isExpression = (query2) => !!(query2[LogicalOperator.AND] || query2[LogicalOperator.OR]);
123103
+ var isPath = (query2) => !!query2[KeyType.PATH];
123104
+ var isLeaf = (query2) => !isArray2(query2) && isObject2(query2) && !isExpression(query2);
123105
+ var convertToExplicit = (query2) => ({ [LogicalOperator.AND]: Object.keys(query2).map((key) => ({ [key]: query2[key] })) });
123106
+ function parse3(query2, options, { auto = true } = {}) {
123107
+ const next2 = (query3) => {
123108
+ if (isString(query3)) {
123019
123109
  const obj = {
123020
123110
  keyId: null,
123021
- pattern: query2
123111
+ pattern: query3
123022
123112
  };
123023
- if (auto) obj.searcher = createSearcher(query2, options);
123113
+ if (auto) obj.searcher = createSearcher(query3, options);
123024
123114
  return obj;
123025
123115
  }
123026
- const keys = Object.keys(query2);
123027
- const isQueryPath = isPath(query2);
123028
- if (!isQueryPath && keys.length > 1 && !isExpression(query2)) return next2(convertToExplicit(query2));
123029
- if (isLeaf(query2)) {
123030
- const key = isQueryPath ? query2[KeyType.PATH] : keys[0];
123031
- const pattern = isQueryPath ? query2[KeyType.PATTERN] : query2[key];
123116
+ const keys = Object.keys(query3);
123117
+ const isQueryPath = isPath(query3);
123118
+ if (!isQueryPath && keys.length > 1 && !isExpression(query3)) return next2(convertToExplicit(query3));
123119
+ if (isLeaf(query3)) {
123120
+ const key = isQueryPath ? query3[KeyType.PATH] : keys[0];
123121
+ const pattern = isQueryPath ? query3[KeyType.PATTERN] : query3[key];
123032
123122
  if (!isString(pattern)) throw new Error(LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key));
123033
123123
  const obj = {
123034
123124
  keyId: createKeyId(key),
@@ -123042,15 +123132,15 @@ function parse3(query, options, { auto = true } = {}) {
123042
123132
  operator: keys[0]
123043
123133
  };
123044
123134
  keys.forEach((key) => {
123045
- const value2 = query2[key];
123135
+ const value2 = query3[key];
123046
123136
  if (isArray2(value2)) value2.forEach((item) => {
123047
123137
  node2.children.push(next2(item));
123048
123138
  });
123049
123139
  });
123050
123140
  return node2;
123051
123141
  };
123052
- if (!isExpression(query)) query = convertToExplicit(query);
123053
- return next2(query);
123142
+ if (!isExpression(query2)) query2 = convertToExplicit(query2);
123143
+ return next2(query2);
123054
123144
  }
123055
123145
  function computeScoreSingle(matches, { ignoreFieldNorm = Config3.ignoreFieldNorm }) {
123056
123146
  let totalScore = 1;
@@ -123346,13 +123436,13 @@ var Fuse = class {
123346
123436
  this._lastQuery = null;
123347
123437
  this._lastSearcher = null;
123348
123438
  }
123349
- _getSearcher(query) {
123350
- if (this._lastQuery === query) return this._lastSearcher;
123351
- const searcher = createSearcher(query, this._invertedIndex ? {
123439
+ _getSearcher(query2) {
123440
+ if (this._lastQuery === query2) return this._lastSearcher;
123441
+ const searcher = createSearcher(query2, this._invertedIndex ? {
123352
123442
  ...this.options,
123353
123443
  _invertedIndex: this._invertedIndex
123354
123444
  } : this.options);
123355
- this._lastQuery = query;
123445
+ this._lastQuery = query2;
123356
123446
  this._lastSearcher = searcher;
123357
123447
  return searcher;
123358
123448
  }
@@ -123421,10 +123511,10 @@ var Fuse = class {
123421
123511
  _normalizedKeys() {
123422
123512
  return this._myIndex.keys.map((key) => this._keyStore.get(key.id) || key);
123423
123513
  }
123424
- search(query, options) {
123514
+ search(query2, options) {
123425
123515
  const { limit = -1 } = options || {};
123426
123516
  const { includeMatches, includeScore, shouldSort, sortFn, ignoreFieldNorm } = this.options;
123427
- if (isString(query) && !query.trim()) {
123517
+ if (isString(query2) && !query2.trim()) {
123428
123518
  let docs = this._docs.map((item, idx) => ({
123429
123519
  item,
123430
123520
  refIndex: idx
@@ -123432,25 +123522,25 @@ var Fuse = class {
123432
123522
  if (isNumber(limit) && limit > -1) docs = docs.slice(0, limit);
123433
123523
  return docs;
123434
123524
  }
123435
- const useHeap = shouldSort && isNumber(limit) && limit > 0 && isString(query);
123525
+ const useHeap = shouldSort && isNumber(limit) && limit > 0 && isString(query2);
123436
123526
  const comparator = sortFn;
123437
123527
  const stable = (a, b) => comparator(a, b) || a.idx - b.idx;
123438
123528
  let results;
123439
123529
  if (useHeap) {
123440
123530
  const heap = new MaxHeap(limit, stable);
123441
- if (isString(this._docs[0])) this._searchStringList(query, {
123531
+ if (isString(this._docs[0])) this._searchStringList(query2, {
123442
123532
  heap,
123443
123533
  ignoreFieldNorm
123444
123534
  });
123445
- else this._searchObjectList(query, {
123535
+ else this._searchObjectList(query2, {
123446
123536
  heap,
123447
123537
  ignoreFieldNorm
123448
123538
  });
123449
123539
  results = heap.extractSorted();
123450
123540
  } else {
123451
- results = isString(query) ? isString(this._docs[0]) ? this._searchStringList(query) : this._searchObjectList(query) : this._searchLogical(query);
123541
+ results = isString(query2) ? isString(this._docs[0]) ? this._searchStringList(query2) : this._searchObjectList(query2) : this._searchLogical(query2);
123452
123542
  computeScore(results, { ignoreFieldNorm });
123453
- if (shouldSort) results.sort(isString(query) ? stable : comparator);
123543
+ if (shouldSort) results.sort(isString(query2) ? stable : comparator);
123454
123544
  if (isNumber(limit) && limit > -1) results = results.slice(0, limit);
123455
123545
  }
123456
123546
  return format(results, this._docs, {
@@ -123458,8 +123548,8 @@ var Fuse = class {
123458
123548
  includeScore
123459
123549
  });
123460
123550
  }
123461
- _searchStringList(query, { heap, ignoreFieldNorm } = {}) {
123462
- const searcher = this._getSearcher(query);
123551
+ _searchStringList(query2, { heap, ignoreFieldNorm } = {}) {
123552
+ const searcher = this._getSearcher(query2);
123463
123553
  const requireAllTokens = this.options.useTokenSearch && this.options.tokenMatch === "all";
123464
123554
  const { records } = this._myIndex;
123465
123555
  const results = heap ? null : [];
@@ -123494,8 +123584,8 @@ var Fuse = class {
123494
123584
  });
123495
123585
  return results;
123496
123586
  }
123497
- _searchLogical(query) {
123498
- const expression = parse3(query, this.options);
123587
+ _searchLogical(query2) {
123588
+ const expression = parse3(query2, this.options);
123499
123589
  const keys = this._normalizedKeys();
123500
123590
  const evaluate = (node2, item, idx) => {
123501
123591
  if (!("children" in node2)) {
@@ -123555,8 +123645,8 @@ var Fuse = class {
123555
123645
  });
123556
123646
  return results;
123557
123647
  }
123558
- _searchObjectList(query, { heap, ignoreFieldNorm } = {}) {
123559
- const searcher = this._getSearcher(query);
123648
+ _searchObjectList(query2, { heap, ignoreFieldNorm } = {}) {
123649
+ const searcher = this._getSearcher(query2);
123560
123650
  const requireAllTokens = this.options.useTokenSearch && this.options.tokenMatch === "all";
123561
123651
  const { records } = this._myIndex;
123562
123652
  const keys = this._normalizedKeys();
@@ -123783,10 +123873,10 @@ var getPattern = (label, next2) => {
123783
123873
  if (label === "*") return "*";
123784
123874
  const match3 = label.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
123785
123875
  if (match3) {
123786
- const cacheKey2 = `${label}#${next2}`;
123787
- if (!patternCache[cacheKey2]) {
123788
- if (match3[2]) patternCache[cacheKey2] = next2 && next2[0] !== ":" && next2[0] !== "*" ? [
123789
- cacheKey2,
123876
+ const cacheKey3 = `${label}#${next2}`;
123877
+ if (!patternCache[cacheKey3]) {
123878
+ if (match3[2]) patternCache[cacheKey3] = next2 && next2[0] !== ":" && next2[0] !== "*" ? [
123879
+ cacheKey3,
123790
123880
  match3[1],
123791
123881
  new RegExp(`^${match3[2]}(?=/${next2})`)
123792
123882
  ] : [
@@ -123794,13 +123884,13 @@ var getPattern = (label, next2) => {
123794
123884
  match3[1],
123795
123885
  new RegExp(`^${match3[2]}$`)
123796
123886
  ];
123797
- else patternCache[cacheKey2] = [
123887
+ else patternCache[cacheKey3] = [
123798
123888
  label,
123799
123889
  match3[1],
123800
123890
  true
123801
123891
  ];
123802
123892
  }
123803
- return patternCache[cacheKey2];
123893
+ return patternCache[cacheKey3];
123804
123894
  }
123805
123895
  return null;
123806
123896
  };
@@ -134289,9 +134379,9 @@ var require_schemes2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
134289
134379
  wsComponent.secure = void 0;
134290
134380
  }
134291
134381
  if (wsComponent.resourceName) {
134292
- const [path4, query] = wsComponent.resourceName.split("?");
134382
+ const [path4, query2] = wsComponent.resourceName.split("?");
134293
134383
  wsComponent.path = path4 && path4 !== "/" ? path4 : void 0;
134294
- wsComponent.query = query;
134384
+ wsComponent.query = query2;
134295
134385
  wsComponent.resourceName = void 0;
134296
134386
  }
134297
134387
  wsComponent.fragment = void 0;
@@ -134906,8 +134996,8 @@ var require_core$3 = /* @__PURE__ */ __commonJSMin(((exports) => {
134906
134996
  return this;
134907
134997
  }
134908
134998
  case "object": {
134909
- const cacheKey2 = schemaKeyRef;
134910
- this._cache.delete(cacheKey2);
134999
+ const cacheKey3 = schemaKeyRef;
135000
+ this._cache.delete(cacheKey3);
134911
135001
  let id = schemaKeyRef[this.opts.schemaId];
134912
135002
  if (id) {
134913
135003
  id = (0, resolve_1.normalizeId)(id);
@@ -135050,10 +135140,10 @@ var require_core$3 = /* @__PURE__ */ __commonJSMin(((exports) => {
135050
135140
  Ajv5.ValidationError = validation_error_1.default;
135051
135141
  Ajv5.MissingRefError = ref_error_1.default;
135052
135142
  exports.default = Ajv5;
135053
- function checkOptions(checkOpts, options, msg, log3 = "error") {
135143
+ function checkOptions(checkOpts, options, msg, log2 = "error") {
135054
135144
  for (const key in checkOpts) {
135055
135145
  const opt = key;
135056
- if (opt in options) this.logger[log3](`${msg}: option ${key}. ${checkOpts[opt]}`);
135146
+ if (opt in options) this.logger[log2](`${msg}: option ${key}. ${checkOpts[opt]}`);
135057
135147
  }
135058
135148
  }
135059
135149
  function getSchEnv(keyRef) {
@@ -148574,9 +148664,9 @@ var require_schemes3 = /* @__PURE__ */ __commonJSMin2(((exports, module) => {
148574
148664
  wsComponent.secure = void 0;
148575
148665
  }
148576
148666
  if (wsComponent.resourceName) {
148577
- const [path4, query] = wsComponent.resourceName.split("?");
148667
+ const [path4, query2] = wsComponent.resourceName.split("?");
148578
148668
  wsComponent.path = path4 && path4 !== "/" ? path4 : void 0;
148579
- wsComponent.query = query;
148669
+ wsComponent.query = query2;
148580
148670
  wsComponent.resourceName = void 0;
148581
148671
  }
148582
148672
  wsComponent.fragment = void 0;
@@ -149191,8 +149281,8 @@ var require_core$32 = /* @__PURE__ */ __commonJSMin2(((exports) => {
149191
149281
  return this;
149192
149282
  }
149193
149283
  case "object": {
149194
- const cacheKey2 = schemaKeyRef;
149195
- this._cache.delete(cacheKey2);
149284
+ const cacheKey3 = schemaKeyRef;
149285
+ this._cache.delete(cacheKey3);
149196
149286
  let id = schemaKeyRef[this.opts.schemaId];
149197
149287
  if (id) {
149198
149288
  id = (0, resolve_1.normalizeId)(id);
@@ -149335,10 +149425,10 @@ var require_core$32 = /* @__PURE__ */ __commonJSMin2(((exports) => {
149335
149425
  Ajv5.ValidationError = validation_error_1.default;
149336
149426
  Ajv5.MissingRefError = ref_error_1.default;
149337
149427
  exports.default = Ajv5;
149338
- function checkOptions(checkOpts, options, msg, log3 = "error") {
149428
+ function checkOptions(checkOpts, options, msg, log2 = "error") {
149339
149429
  for (const key in checkOpts) {
149340
149430
  const opt = key;
149341
- if (opt in options) this.logger[log3](`${msg}: option ${key}. ${checkOpts[opt]}`);
149431
+ if (opt in options) this.logger[log2](`${msg}: option ${key}. ${checkOpts[opt]}`);
149342
149432
  }
149343
149433
  }
149344
149434
  function getSchEnv(keyRef) {
@@ -160031,7 +160121,7 @@ var require_depd = /* @__PURE__ */ __commonJSMin3(((exports, module) => {
160031
160121
  if (!namespace) throw new TypeError("argument namespace is required");
160032
160122
  var file2 = callSiteLocation(getStack()[1])[0];
160033
160123
  function deprecate$1(message) {
160034
- log3.call(deprecate$1, message);
160124
+ log2.call(deprecate$1, message);
160035
160125
  }
160036
160126
  deprecate$1._file = file2;
160037
160127
  deprecate$1._ignored = isignored(namespace);
@@ -160053,7 +160143,7 @@ var require_depd = /* @__PURE__ */ __commonJSMin3(((exports, module) => {
160053
160143
  if (process.traceDeprecation) return true;
160054
160144
  return containsNamespace(process.env.TRACE_DEPRECATION || "", namespace);
160055
160145
  }
160056
- function log3(message, site) {
160146
+ function log2(message, site) {
160057
160147
  var haslisteners = eehaslisteners(process, "deprecation");
160058
160148
  if (!haslisteners && this._ignored) return;
160059
160149
  var caller;
@@ -160159,7 +160249,7 @@ var require_depd = /* @__PURE__ */ __commonJSMin3(((exports, module) => {
160159
160249
  var args2 = createArgumentsString(fn2.length);
160160
160250
  var site = callSiteLocation(getStack()[1]);
160161
160251
  site.name = fn2.name;
160162
- return new Function("fn", "log", "deprecate", "message", "site", '"use strict"\nreturn function (' + args2 + ") {log.call(deprecate, message, site)\nreturn fn.apply(this, arguments)\n}")(fn2, log3, this, message, site);
160252
+ return new Function("fn", "log", "deprecate", "message", "site", '"use strict"\nreturn function (' + args2 + ") {log.call(deprecate, message, site)\nreturn fn.apply(this, arguments)\n}")(fn2, log2, this, message, site);
160163
160253
  }
160164
160254
  function wrapproperty(obj, prop, message) {
160165
160255
  if (!obj || typeof obj !== "object" && typeof obj !== "function") throw new TypeError("argument obj must be object");
@@ -160173,11 +160263,11 @@ var require_depd = /* @__PURE__ */ __commonJSMin3(((exports, module) => {
160173
160263
  var get2 = descriptor.get;
160174
160264
  var set2 = descriptor.set;
160175
160265
  if (typeof get2 === "function") descriptor.get = function getter() {
160176
- log3.call(deprecate$1, message, site);
160266
+ log2.call(deprecate$1, message, site);
160177
160267
  return get2.apply(this, arguments);
160178
160268
  };
160179
160269
  if (typeof set2 === "function") descriptor.set = function setter() {
160180
- log3.call(deprecate$1, message, site);
160270
+ log2.call(deprecate$1, message, site);
160181
160271
  return set2.apply(this, arguments);
160182
160272
  };
160183
160273
  Object.defineProperty(obj, prop, descriptor);
@@ -171225,7 +171315,7 @@ ${error49 instanceof Error ? error49.stack : JSON.stringify(error49)}`
171225
171315
  );
171226
171316
  }
171227
171317
  };
171228
- const log3 = {
171318
+ const log2 = {
171229
171319
  debug: (message, context) => {
171230
171320
  this.#server.sendLoggingMessage({
171231
171321
  data: {
@@ -171293,7 +171383,7 @@ ${error49 instanceof Error ? error49.stack : JSON.stringify(error49)}`
171293
171383
  client: {
171294
171384
  version: this.#server.getClientVersion()
171295
171385
  },
171296
- log: log3,
171386
+ log: log2,
171297
171387
  reportProgress: reportProgress2,
171298
171388
  requestId: typeof request2.params?._meta?.requestId === "string" ? request2.params._meta.requestId : void 0,
171299
171389
  session: this.#auth,
@@ -172377,7 +172467,7 @@ function closeBrowserDaemon(toolState) {
172377
172467
  // mcp/checkout.ts
172378
172468
  import { createHash as createHash2 } from "node:crypto";
172379
172469
  import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync10 } from "node:fs";
172380
- import { join as join15 } from "node:path";
172470
+ import { join as join16 } from "node:path";
172381
172471
 
172382
172472
  // node_modules/.pnpm/@ark+schema@0.56.0/node_modules/@ark/schema/out/shared/errors.js
172383
172473
  var ArkError = class _ArkError extends CastableBase {
@@ -174378,8 +174468,8 @@ var defaultableMorphCache = {};
174378
174468
  var getDefaultableMorph = (node2) => {
174379
174469
  if (!node2.hasDefault())
174380
174470
  return;
174381
- const cacheKey2 = `{${node2.compiledKey}: ${node2.value.id} = ${defaultValueSerializer(node2.default)}}`;
174382
- return defaultableMorphCache[cacheKey2] ??= computeDefaultValueMorph(node2.key, node2.value, node2.default);
174471
+ const cacheKey3 = `{${node2.compiledKey}: ${node2.value.id} = ${defaultValueSerializer(node2.default)}}`;
174472
+ return defaultableMorphCache[cacheKey3] ??= computeDefaultValueMorph(node2.key, node2.value, node2.default);
174383
174473
  };
174384
174474
  var computeDefaultValueMorph = (key, value2, defaultInput) => {
174385
174475
  if (typeof defaultInput === "function") {
@@ -176552,15 +176642,15 @@ var getDefaultableMorphs = (node2) => {
176552
176642
  if (!node2.defaultables)
176553
176643
  return [];
176554
176644
  const morphs = [];
176555
- let cacheKey2 = "[";
176645
+ let cacheKey3 = "[";
176556
176646
  const lastDefaultableIndex = node2.prefixLength + node2.defaultablesLength - 1;
176557
176647
  for (let i = node2.prefixLength; i <= lastDefaultableIndex; i++) {
176558
176648
  const [elementNode, defaultValue] = node2.defaultables[i - node2.prefixLength];
176559
176649
  morphs.push(computeDefaultValueMorph(i, elementNode, defaultValue));
176560
- cacheKey2 += `${i}: ${elementNode.id} = ${defaultValueSerializer(defaultValue)}, `;
176650
+ cacheKey3 += `${i}: ${elementNode.id} = ${defaultValueSerializer(defaultValue)}, `;
176561
176651
  }
176562
- cacheKey2 += "]";
176563
- return defaultableMorphsCache[cacheKey2] ??= morphs;
176652
+ cacheKey3 += "]";
176653
+ return defaultableMorphsCache[cacheKey3] ??= morphs;
176564
176654
  };
176565
176655
  var Sequence = {
176566
176656
  implementation: implementation21,
@@ -177250,40 +177340,40 @@ var StructureNode = class extends BaseConstraint {
177250
177340
  };
177251
177341
  var defaultableMorphsCache2 = {};
177252
177342
  var constructStructuralMorphCacheKey = (node2) => {
177253
- let cacheKey2 = "";
177343
+ let cacheKey3 = "";
177254
177344
  for (let i = 0; i < node2.defaultable.length; i++)
177255
- cacheKey2 += node2.defaultable[i].defaultValueMorphRef;
177345
+ cacheKey3 += node2.defaultable[i].defaultValueMorphRef;
177256
177346
  if (node2.sequence?.defaultValueMorphsReference)
177257
- cacheKey2 += node2.sequence?.defaultValueMorphsReference;
177347
+ cacheKey3 += node2.sequence?.defaultValueMorphsReference;
177258
177348
  if (node2.undeclared === "delete") {
177259
- cacheKey2 += "delete !(";
177349
+ cacheKey3 += "delete !(";
177260
177350
  if (node2.required)
177261
177351
  for (const n of node2.required)
177262
- cacheKey2 += n.compiledKey + " | ";
177352
+ cacheKey3 += n.compiledKey + " | ";
177263
177353
  if (node2.optional)
177264
177354
  for (const n of node2.optional)
177265
- cacheKey2 += n.compiledKey + " | ";
177355
+ cacheKey3 += n.compiledKey + " | ";
177266
177356
  if (node2.index)
177267
177357
  for (const index of node2.index)
177268
- cacheKey2 += index.signature.id + " | ";
177358
+ cacheKey3 += index.signature.id + " | ";
177269
177359
  if (node2.sequence) {
177270
177360
  if (node2.sequence.maxLength === null)
177271
- cacheKey2 += intrinsic.nonNegativeIntegerString.id;
177361
+ cacheKey3 += intrinsic.nonNegativeIntegerString.id;
177272
177362
  else {
177273
177363
  for (let i = 0; i < node2.sequence.tuple.length; i++)
177274
- cacheKey2 += i + " | ";
177364
+ cacheKey3 += i + " | ";
177275
177365
  }
177276
177366
  }
177277
- cacheKey2 += ")";
177367
+ cacheKey3 += ")";
177278
177368
  }
177279
- return cacheKey2;
177369
+ return cacheKey3;
177280
177370
  };
177281
177371
  var getPossibleMorph = (node2) => {
177282
- const cacheKey2 = constructStructuralMorphCacheKey(node2);
177283
- if (!cacheKey2)
177372
+ const cacheKey3 = constructStructuralMorphCacheKey(node2);
177373
+ if (!cacheKey3)
177284
177374
  return void 0;
177285
- if (defaultableMorphsCache2[cacheKey2])
177286
- return defaultableMorphsCache2[cacheKey2];
177375
+ if (defaultableMorphsCache2[cacheKey3])
177376
+ return defaultableMorphsCache2[cacheKey3];
177287
177377
  const $arkStructuralMorph = (data, ctx) => {
177288
177378
  for (let i = 0; i < node2.defaultable.length; i++) {
177289
177379
  if (!(node2.defaultable[i].key in data))
@@ -177300,7 +177390,7 @@ var getPossibleMorph = (node2) => {
177300
177390
  }
177301
177391
  return data;
177302
177392
  };
177303
- return defaultableMorphsCache2[cacheKey2] = $arkStructuralMorph;
177393
+ return defaultableMorphsCache2[cacheKey3] = $arkStructuralMorph;
177304
177394
  };
177305
177395
  var precompileMorphs = (js, node2) => {
177306
177396
  const requiresContext = node2.defaultable.some((node3) => node3.defaultValueMorph.length === 2) || node2.sequence?.defaultValueMorphs.some((morph) => morph.length === 2);
@@ -179705,10 +179795,10 @@ var schema = ark.schema;
179705
179795
  var define2 = ark.define;
179706
179796
  var declare = ark.declare;
179707
179797
 
179708
- // node_modules/.pnpm/yes@0.1.0/node_modules/yes/dist/index.js
179798
+ // node_modules/.pnpm/yes@0.2.3_typescript@5.9.3/node_modules/yes/dist/index.js
179709
179799
  import { setTimeout as sleep2 } from "node:timers/promises";
179710
179800
 
179711
- // node_modules/.pnpm/yes@0.1.0/node_modules/yes/dist/cache/adapter.js
179801
+ // node_modules/.pnpm/yes@0.2.3_typescript@5.9.3/node_modules/yes/dist/cache/adapter.js
179712
179802
  var AdapterError = class extends Error {
179713
179803
  method;
179714
179804
  /** the full store key, or the op's prefix when the call covered many keys. never the raw input. */
@@ -180306,43 +180396,45 @@ var I = class d2 {
180306
180396
  }
180307
180397
  };
180308
180398
 
180309
- // node_modules/.pnpm/yes@0.1.0/node_modules/yes/dist/cache/memory.js
180310
- function memory(options = {}) {
180311
- const store = new I({ max: options.maxItems ?? 1e3 });
180399
+ // node_modules/.pnpm/yes@0.2.3_typescript@5.9.3/node_modules/yes/dist/cache/memory.js
180400
+ function memory({ maxItems = 1e3 } = {}) {
180401
+ const store = new I({ max: maxItems });
180312
180402
  return {
180313
180403
  get: (key) => store.get(key),
180314
180404
  peek: (key) => store.peek(key),
180315
- // the TTL rides on each entry rather than the whole cache, so ops with
180316
- // different lifetimes can share one store
180317
- set: (key, entry, ttlMs) => {
180318
- store.set(key, entry, { ttl: ttlMs });
180319
- },
180405
+ // the TTL rides on each entry, so ops with different lifetimes can share one store
180406
+ set: (key, entry, ttlMs) => store.set(key, entry, { ttl: ttlMs }),
180320
180407
  delete: (keys) => {
180321
180408
  for (const key of keys)
180322
180409
  store.delete(key);
180323
180410
  },
180324
- // a snapshot, so deleting while iterating is safe. lru-cache skips expired
180325
- // entries itself.
180411
+ // a snapshot, so deleting while iterating is safe
180326
180412
  list: (prefix) => [...store.entries()].filter(([key]) => key.startsWith(prefix))
180327
180413
  };
180328
180414
  }
180329
180415
 
180330
- // node_modules/.pnpm/yes@0.1.0/node_modules/yes/dist/key.js
180416
+ // node_modules/.pnpm/yes@0.2.3_typescript@5.9.3/node_modules/yes/dist/key.js
180331
180417
  import { hash as hash2 } from "node:crypto";
180332
180418
  function hashKey(value2) {
180333
- return hash2("sha1", serialize(value2, 0));
180419
+ return hash2("sha1", canonical(value2));
180420
+ }
180421
+ function canonical(value2) {
180422
+ return serialize(value2, 0);
180423
+ }
180424
+ function cacheKey2(value2) {
180425
+ if (value2 === null)
180426
+ throw new Error("null cannot be part of a cache key");
180427
+ return value2 === void 0 ? "~void" : hashKey(value2);
180334
180428
  }
180335
- var MAX_DEPTH = 64;
180336
180429
  function serialize(value2, depth) {
180337
180430
  switch (typeof value2) {
180338
- case "string":
180339
- return JSON.stringify(value2);
180340
180431
  case "number":
180341
- if (!Number.isFinite(value2))
180342
- throw new Error(`${value2} cannot be part of a cache key`);
180343
- return String(value2);
180432
+ if (Number.isFinite(value2))
180433
+ return String(value2);
180434
+ throw new Error(`${value2} cannot be part of a cache key`);
180435
+ case "string":
180344
180436
  case "boolean":
180345
- return value2 ? "true" : "false";
180437
+ return JSON.stringify(value2);
180346
180438
  case "undefined":
180347
180439
  throw new Error("undefined cannot be part of a cache key, except as an object property");
180348
180440
  case "object":
@@ -180352,9 +180444,8 @@ function serialize(value2, depth) {
180352
180444
  }
180353
180445
  if (value2 === null)
180354
180446
  return "null";
180355
- if (depth === MAX_DEPTH)
180356
- throw new Error(`an input nested more than ${MAX_DEPTH} levels deep, or circular`);
180357
- depth++;
180447
+ if (depth++ === 64)
180448
+ throw new Error("an input nested more than 64 levels deep, or circular");
180358
180449
  if (Array.isArray(value2)) {
180359
180450
  let out2 = "[";
180360
180451
  for (let i = 0; i < value2.length; i++)
@@ -180366,77 +180457,26 @@ function serialize(value2, depth) {
180366
180457
  const name = proto.constructor?.name || "class instance";
180367
180458
  throw new Error(`a ${name} cannot be part of a cache key: only JSON data can`);
180368
180459
  }
180369
- const record3 = value2;
180370
- const keys = Object.keys(record3).sort();
180371
180460
  let out = "";
180372
- for (const key of keys) {
180373
- const v = record3[key];
180461
+ for (const key of Object.keys(value2).sort()) {
180462
+ const v = value2[key];
180374
180463
  if (v !== void 0)
180375
180464
  out += `${out ? "," : ""}${JSON.stringify(key)}:${serialize(v, depth)}`;
180376
180465
  }
180377
180466
  return `{${out}}`;
180378
180467
  }
180379
180468
 
180380
- // node_modules/.pnpm/yes@0.1.0/node_modules/yes/dist/index.js
180381
- var VOID_KEY = "~void";
180382
- function getCacheKeyString(value2) {
180383
- if (value2 === void 0)
180384
- return VOID_KEY;
180385
- if (value2 === null) {
180386
- throw new Error("cache key cannot be null");
180387
- }
180388
- try {
180389
- return hashKey(value2);
180390
- } catch (error49) {
180391
- throw new Error(`cache key cannot be hashed: ${error49 instanceof Error ? error49.message : String(error49)}`);
180392
- }
180393
- }
180394
- function jitter(delayMs) {
180395
- const half = delayMs / 2;
180396
- return Math.round(half + Math.random() * half);
180397
- }
180398
- var DEFAULT_RETRY_AFTER_CAP_MS = 2e4;
180399
- function getErrorResponseHeaders(error49) {
180400
- if (error49 === null || typeof error49 !== "object")
180401
- return void 0;
180402
- const source = error49;
180403
- const headers = source.response?.headers ?? source.headers;
180404
- if (headers === null || typeof headers !== "object")
180405
- return void 0;
180406
- return headers;
180407
- }
180408
- function retryAfterMs(error49) {
180409
- const headers = getErrorResponseHeaders(error49);
180410
- if (!headers)
180411
- return void 0;
180412
- const retryAfter = headers["retry-after"];
180413
- if (typeof retryAfter === "string" && retryAfter.trim() !== "") {
180414
- const seconds = Number(retryAfter);
180415
- if (Number.isFinite(seconds))
180416
- return Math.max(0, seconds * 1e3);
180417
- const dateMs = Date.parse(retryAfter);
180418
- if (!Number.isNaN(dateMs))
180419
- return Math.max(0, dateMs - Date.now());
180420
- }
180421
- if (headers["x-ratelimit-remaining"] === "0") {
180422
- const reset = headers["x-ratelimit-reset"];
180423
- if (typeof reset === "string" && reset.trim() !== "") {
180424
- const resetEpoch = Number(reset);
180425
- if (Number.isFinite(resetEpoch))
180426
- return Math.max(0, resetEpoch * 1e3 - Date.now());
180427
- }
180428
- }
180429
- return void 0;
180469
+ // node_modules/.pnpm/yes@0.2.3_typescript@5.9.3/node_modules/yes/dist/index.js
180470
+ function delay2(schedule, attempt) {
180471
+ const d3 = schedule[attempt - 1];
180472
+ return d3 === void 0 ? -1 : Math.round(d3 / 2 + Math.random() * d3 / 2);
180430
180473
  }
180431
180474
  var ValidationError = class extends Error {
180432
- result;
180433
- issues;
180475
+ name = "ValidationError";
180434
180476
  constructor(result) {
180435
- const message = "message" in result && typeof result.message === "string" ? result.message : `validation failed: ${result.issues.map((i) => i.message).join(", ")}`;
180436
- super(message);
180437
- this.name = "ValidationError";
180438
- this.result = result;
180439
- this.issues = result.issues;
180477
+ const { message } = result;
180478
+ super(typeof message === "string" ? message : `validation failed: ${result.issues.map((i) => i.message).join(", ")}`);
180479
+ Object.assign(this, { result, issues: result.issues });
180440
180480
  }
180441
180481
  };
180442
180482
  async function assertSchema(schema2, value2) {
@@ -180445,322 +180485,212 @@ async function assertSchema(schema2, value2) {
180445
180485
  throw new ValidationError(result);
180446
180486
  return result.value;
180447
180487
  }
180448
- var log2 = {
180449
- info: (...args2) => console.log(...args2),
180450
- error: (...args2) => console.error(...args2),
180451
- prod: (...args2) => {
180452
- if (process.env.NODE_ENV === "production")
180453
- console.log(...args2);
180454
- }
180455
- };
180456
- function debugTrace(namePrefix, key) {
180457
- if (process.env.LOG_LEVEL !== "debug")
180458
- return void 0;
180459
- const prefix = `${namePrefix}[${(/* @__PURE__ */ new Date()).toISOString()}]`;
180460
- return {
180461
- start: performance.now(),
180462
- log: (message, data) => console.log(`${prefix} ${message}`, { key, ...data }),
180463
- since: (start) => `${Math.round(performance.now() - start)}ms`
180464
- };
180465
- }
180466
- function op(fnOrOptions, options) {
180467
- if (typeof fnOrOptions === "function") {
180468
- return _op(fnOrOptions, options ?? {});
180469
- }
180470
- const { input: inputSchema, output: outputSchema, run, ...opOptions } = fnOrOptions;
180471
- return _op(run, opOptions, { input: inputSchema, output: outputSchema });
180472
- }
180473
- var isThenable = (value2) => typeof value2?.then === "function";
180474
- function freshEntry(value2) {
180475
- if (value2 === null || typeof value2 !== "object" || !("value" in value2))
180476
- return void 0;
180477
- const expires = value2.expires;
180478
- return typeof expires === "number" && expires > Date.now() ? value2 : void 0;
180479
- }
180480
- var adapterMethods = ["get", "set", "delete", "list"];
180481
- function callAdapter(method, key, run) {
180482
- let result;
180483
- try {
180484
- result = run();
180485
- } catch (error49) {
180486
- throw new AdapterError(method, key, error49);
180487
- }
180488
- if (!isThenable(result))
180489
- return result;
180490
- return result.then(void 0, (error49) => {
180491
- throw new AdapterError(method, key, error49);
180492
- });
180493
- }
180494
- var DELETE_BATCH = 1e3;
180495
- function sweep(store, prefix, match3) {
180496
- const now = Date.now();
180497
- const keys = [];
180498
- let count = 0;
180499
- let matchError;
180500
- const take = ([key, entry]) => {
180501
- if (!(entry.expires > now))
180502
- return void keys.push(key);
180503
- let matched;
180488
+ function op(options) {
180489
+ return build3(options, true);
180490
+ }
180491
+ function build3(options, cacheMethods) {
180492
+ const { name, input: inputSchema, output: outputSchema, run, dedupe, ttl, cache: adapter } = options;
180493
+ const { retry } = options;
180494
+ const delayFor = Array.isArray(retry) ? (_, attempt) => delay2(retry, attempt) : retry;
180495
+ if (ttl !== void 0 && typeof ttl !== "function" && !isLifetime(ttl)) {
180496
+ throw new TypeError(`yes.op: ttl must be a positive number of milliseconds, got ${ttl}`);
180497
+ }
180498
+ const lifetime = typeof ttl === "function" ? ttl : () => ttl;
180499
+ if (adapter && (!name || name.includes(":"))) {
180500
+ throw new TypeError(`yes.op: an op with a cache adapter needs a name without ":", got ${JSON.stringify(name)}`);
180501
+ }
180502
+ if (adapter && ttl === void 0)
180503
+ throw new TypeError(`yes.op: a cache adapter does nothing without a ttl`);
180504
+ const store = ttl === void 0 ? void 0 : adapter ?? memory({ maxItems: options.maxItems });
180505
+ const prefix = adapter ? `yes:${name}:` : "";
180506
+ const label = name ? `[${name}] ` : "";
180507
+ const logCache = (event) => () => {
180508
+ if (name && store && process.env.NODE_ENV === "production")
180509
+ console.log(`${label}cache ${event}`);
180510
+ };
180511
+ const { cacheHit = logCache("hit"), cacheMiss = logCache("miss") } = options;
180512
+ const keyOf = (input) => {
180504
180513
  try {
180505
- matched = match3(entry.input);
180514
+ return cacheKey2(input);
180506
180515
  } catch (error49) {
180507
- matchError = { error: error49 };
180508
- throw error49;
180509
- }
180510
- if (matched) {
180511
- keys.push(key);
180512
- count++;
180516
+ throw new Error(`${label}${error49.message}. dedupe and ttl key the input, and a query always dedupes; a write is yes.mutation, which never keys.`, { cause: error49 });
180513
180517
  }
180514
180518
  };
180515
- const fail = (error49) => {
180516
- throw matchError ? matchError.error : new AdapterError("list", prefix, error49);
180519
+ const report = (attempt, message, error49, key) => {
180520
+ console.log(`${label}attempt ${attempt} ${message}`);
180521
+ const data = key === void 0 ? { error: error49, attempt } : { key, error: error49, attempt };
180522
+ console.log(...label ? [label, data] : [data]);
180517
180523
  };
180518
- const deleteFrom = (from) => {
180519
- for (let i = from; i < keys.length; i += DELETE_BATCH) {
180520
- const batch = keys.slice(i, i + DELETE_BATCH);
180521
- const deleted = callAdapter("delete", prefix, () => store.delete(batch));
180522
- if (isThenable(deleted))
180523
- return Promise.resolve(deleted).then(() => deleteFrom(i + DELETE_BATCH));
180524
- }
180525
- return count;
180526
- };
180527
- let listed;
180528
- try {
180529
- listed = store.list(prefix);
180530
- } catch (error49) {
180531
- return fail(error49);
180532
- }
180533
- if (!isThenable(listed) && Symbol.iterator in Object(listed)) {
180534
- try {
180535
- for (const item of listed)
180536
- take(item);
180537
- } catch (error49) {
180538
- fail(error49);
180539
- }
180540
- return deleteFrom(0);
180541
- }
180542
- return (async () => {
180543
- try {
180544
- for await (const item of await listed)
180545
- take(item);
180546
- } catch (error49) {
180547
- fail(error49);
180548
- }
180549
- return deleteFrom(0);
180550
- })();
180551
- }
180552
- function _op(fn2, options, schemas = {}) {
180553
- const { ttl, cache: adapter } = options;
180554
- const shouldCache = ttl !== void 0;
180555
- if (shouldCache && !(ttl > 0 && Number.isFinite(ttl))) {
180556
- throw new Error(`ttl must be a positive number of milliseconds, got ${ttl}`);
180557
- }
180558
- if (adapter) {
180559
- if (!options.name)
180560
- throw new Error(`an op with a cache adapter needs a name: its keys are "yes:<name>:<hash>"`);
180561
- if (options.name.includes(":")) {
180562
- throw new Error(`op name "${options.name}" cannot contain ":" with a cache adapter: its keys would overlap another op's`);
180563
- }
180564
- if (!shouldCache)
180565
- throw new Error(`${options.name}: a cache adapter does nothing without a ttl`);
180566
- if (options.maxItems !== void 0) {
180567
- throw new Error(`${options.name}: maxItems sizes the default memory store; size the adapter instead`);
180568
- }
180569
- const missing = adapterMethods.filter((method) => typeof adapter[method] !== "function");
180570
- if (missing.length) {
180571
- throw new Error(`${options.name}: cache is missing ${missing.join(", ")}; an adapter has get, set, delete and list (a Map or a Keyv is not one)`);
180572
- }
180573
- }
180574
- const defaultStore = shouldCache && !adapter ? memory({ maxItems: options.maxItems }) : void 0;
180575
- const store = adapter ?? defaultStore;
180576
- const prefix = adapter ? `yes:${options.name}:` : "";
180577
- const inFlightPromises = /* @__PURE__ */ new Map();
180578
- const namePrefix = options.name ? `[${options.name}] ` : "";
180579
- const defaultCacheHit = options.cacheHit === null ? () => {
180580
- } : options.cacheHit ?? (() => {
180581
- if (options.name && shouldCache)
180582
- log2.prod(`${namePrefix}cache hit`);
180583
- });
180584
- const defaultCacheMiss = options.cacheMiss === null ? () => {
180585
- } : options.cacheMiss ?? (() => {
180586
- if (options.name && shouldCache)
180587
- log2.prod(`${namePrefix}cache miss`);
180588
- });
180589
- const methodKey = (value2, method) => {
180590
- const toKey = (input) => {
180524
+ const attempts = async (input, ctx, key, trace) => {
180525
+ for (let attempt = 1; ; attempt++) {
180591
180526
  try {
180592
- return prefix + getCacheKeyString(input);
180593
- } catch (error49) {
180594
- const errorMessage = error49 instanceof Error ? error49.message : String(error49);
180595
- throw new Error(`failed to hash cache key for ${method}()${namePrefix ? ` in ${options.name}` : ""}: ${errorMessage}`);
180596
- }
180597
- };
180598
- if (!schemas.input)
180599
- return toKey(value2);
180600
- const result = schemas.input["~standard"].validate(value2);
180601
- if (result instanceof Promise)
180602
- return result.then((r) => toKey(r.issues ? value2 : r.value));
180603
- return toKey(result.issues ? value2 : result.value);
180604
- };
180605
- const cachedFn = async (rawInput, ctx) => {
180606
- const input = schemas.input ? await assertSchema(schemas.input, rawInput) : rawInput;
180607
- let key;
180608
- try {
180609
- key = getCacheKeyString(input);
180610
- } catch (error49) {
180611
- const errorMessage = error49 instanceof Error ? error49.message : String(error49);
180612
- throw new Error(`failed to create cache key${namePrefix ? ` for ${options.name}` : ""}: ${errorMessage}`);
180613
- }
180614
- const trace = debugTrace(namePrefix, key);
180615
- trace?.log("cache lookup started");
180616
- const inFlight = inFlightPromises.get(key);
180617
- if (inFlight) {
180618
- trace?.log("cache hit (in-flight)");
180619
- defaultCacheHit(key);
180620
- return inFlight;
180621
- }
180622
- const fullKey = store ? prefix + key : "";
180623
- let pendingRead;
180624
- if (store) {
180625
- let read2;
180626
- try {
180627
- read2 = store.get(fullKey);
180628
- } catch (error49) {
180629
- throw new AdapterError("get", fullKey, error49);
180630
- }
180631
- if (isThenable(read2)) {
180632
- pendingRead = read2.then(void 0, (error49) => {
180633
- throw new AdapterError("get", fullKey, error49);
180634
- });
180635
- } else {
180636
- const entry = store === defaultStore ? read2 : freshEntry(read2);
180637
- if (entry) {
180638
- trace?.log("cache hit (from store)", { runtime: trace.since(trace.start) });
180639
- defaultCacheHit(key);
180640
- return entry.value;
180527
+ const returned = await run(input, ctx);
180528
+ const result = outputSchema ? await assertSchema(outputSchema, returned) : returned;
180529
+ trace?.(`run resolved on attempt ${attempt}`);
180530
+ return result;
180531
+ } catch (thrown) {
180532
+ let error49 = thrown;
180533
+ let wait = -1;
180534
+ if (delayFor && !(error49 instanceof ValidationError)) {
180535
+ try {
180536
+ wait = delayFor(error49, attempt);
180537
+ } catch (stop2) {
180538
+ error49 = stop2;
180539
+ }
180540
+ }
180541
+ if (wait >= 0) {
180542
+ report(attempt, `failed, retrying in ${wait}ms`, error49, key);
180543
+ await sleep2(wait);
180544
+ continue;
180641
180545
  }
180546
+ if (attempt > 1)
180547
+ report(attempt, "failed, not retrying", error49, key);
180548
+ trace?.(`run failed on attempt ${attempt}`, { error: error49 });
180549
+ throw error49;
180642
180550
  }
180643
180551
  }
180644
- const retries = options.retries ?? [];
180645
- const execStart = trace ? performance.now() : 0;
180552
+ };
180553
+ const hit = (key, trace, from, value2) => {
180554
+ trace?.(`cache hit (${from})`);
180555
+ cacheHit?.(key);
180556
+ return value2;
180557
+ };
180558
+ const inFlight = /* @__PURE__ */ new Map();
180559
+ const call = async (rawInput, ctx) => {
180560
+ const input = inputSchema ? await assertSchema(inputSchema, rawInput) : rawInput;
180561
+ if (!dedupe && !store)
180562
+ return attempts(input, ctx, void 0, debugTrace(label));
180563
+ const key = keyOf(input);
180564
+ const trace = debugTrace(label, key);
180565
+ const shared = inFlight.get(key);
180566
+ if (shared)
180567
+ return hit(key, trace, "in-flight", shared);
180568
+ const fullKey = prefix + key;
180569
+ const read2 = store && callAdapter("get", fullKey, () => store.get(fullKey));
180570
+ const entry = freshEntry(read2);
180571
+ if (entry)
180572
+ return hit(key, trace, "from store", entry.value);
180646
180573
  const promise2 = (async () => {
180647
- if (pendingRead) {
180648
- const entry = freshEntry(await pendingRead);
180649
- if (entry) {
180650
- trace?.log("cache hit (from store)", { runtime: trace.since(trace.start) });
180651
- defaultCacheHit(key);
180652
- return entry.value;
180653
- }
180654
- }
180655
- trace?.log("cache miss, executing function");
180656
- defaultCacheMiss(key);
180657
- let result;
180658
- let lastError;
180659
- let attempt = 0;
180660
- for (; attempt <= retries.length; attempt++) {
180661
- try {
180662
- const returned = await fn2(input, ctx);
180663
- result = schemas.output ? await assertSchema(schemas.output, returned) : returned;
180664
- break;
180665
- } catch (error49) {
180666
- if (error49 instanceof ValidationError || options.rethrow?.(error49))
180667
- throw error49;
180668
- lastError = error49;
180669
- const isLastAttempt = attempt >= retries.length;
180670
- if (isLastAttempt) {
180671
- if (namePrefix) {
180672
- log2.info(`${namePrefix}attempt ${attempt + 1}/${retries.length + 1} failed, no more retries`);
180673
- log2.info(`${namePrefix}`, { key, error: error49, attempt });
180674
- } else {
180675
- log2.info(`attempt ${attempt + 1}/${retries.length + 1} failed, no more retries`);
180676
- log2.info({ key, error: error49, attempt });
180677
- }
180678
- } else {
180679
- let delay2 = retries[attempt];
180680
- if (options.retryAfter) {
180681
- const extract = options.retryAfter === true ? retryAfterMs : options.retryAfter;
180682
- const hint = extract(error49);
180683
- if (hint !== void 0) {
180684
- const cap = options.retryAfterCap ?? DEFAULT_RETRY_AFTER_CAP_MS;
180685
- delay2 = Math.max(delay2, Math.min(cap, hint));
180686
- }
180687
- }
180688
- delay2 = jitter(delay2);
180689
- if (namePrefix) {
180690
- log2.info(`${namePrefix}attempt ${attempt + 1}/${retries.length + 1} failed, retrying in ${delay2}ms`);
180691
- log2.info(`${namePrefix}`, { key, error: error49, attempt });
180692
- } else {
180693
- log2.info(`attempt ${attempt + 1}/${retries.length + 1} failed, retrying in ${delay2}ms`);
180694
- log2.info({ key, error: error49, attempt });
180695
- }
180696
- await sleep2(delay2);
180697
- }
180698
- }
180699
- }
180700
- if (attempt > retries.length) {
180701
- trace?.log("function failed after all retries", {
180702
- runtime: trace.since(execStart),
180703
- attempts: retries.length + 1,
180704
- error: lastError instanceof Error ? lastError.message : lastError
180705
- });
180706
- throw lastError;
180707
- }
180708
- const skip = options.skipCache ? options.skipCache(result) : false;
180709
- const storable = store !== void 0 && result !== null && result !== void 0 && !(result instanceof Error) && !skip;
180710
- let cached4 = false;
180711
- if (storable) {
180574
+ const stored = isThenable(read2) ? freshEntry(await read2) : void 0;
180575
+ if (stored)
180576
+ return hit(key, trace, "from store", stored.value);
180577
+ trace?.("cache miss, running");
180578
+ cacheMiss?.(key);
180579
+ const result = await attempts(input, ctx, key, trace);
180580
+ const ms = store && !options.skipCache?.(result) && result != null && !(result instanceof Error) && lifetime(result);
180581
+ if (store && isLifetime(ms)) {
180712
180582
  const created = Date.now();
180713
- const entry = { value: result, created, expires: created + ttl, input };
180714
180583
  try {
180715
- const written = callAdapter("set", fullKey, () => store.set(fullKey, entry, ttl));
180716
- if (isThenable(written))
180717
- await written;
180718
- cached4 = true;
180584
+ await store.set(fullKey, { value: result, created, expires: created + ms, input }, ms);
180719
180585
  } catch (error49) {
180720
- log2.error(`${namePrefix}cache write failed, returning the result uncached`, error49);
180586
+ console.error(`${label}cache write failed, returning the result uncached`, new AdapterError("set", fullKey, error49));
180721
180587
  }
180722
180588
  }
180723
- trace?.log("function executed successfully", { runtime: trace.since(execStart), attempt: attempt + 1, cached: cached4 });
180724
180589
  return result;
180725
180590
  })();
180726
- inFlightPromises.set(key, promise2);
180727
- try {
180728
- return await promise2;
180729
- } finally {
180730
- inFlightPromises.delete(key);
180591
+ if (!dedupe)
180592
+ return promise2;
180593
+ inFlight.set(key, promise2);
180594
+ return promise2.finally(() => inFlight.delete(key));
180595
+ };
180596
+ if (!cacheMethods)
180597
+ return call;
180598
+ const withKey = (input, use) => {
180599
+ const toKey = (result2) => use(prefix + keyOf(result2 && !result2.issues ? result2.value : input));
180600
+ const result = inputSchema?.["~standard"].validate(input);
180601
+ return result instanceof Promise ? result.then(toKey) : toKey(result);
180602
+ };
180603
+ return Object.assign(call, {
180604
+ clear: async (input) => {
180605
+ if (!store)
180606
+ return;
180607
+ if (input === void 0)
180608
+ await sweep(store, prefix, () => true);
180609
+ else
180610
+ await withKey(input, (key) => callAdapter("delete", key, () => store.delete([key])));
180611
+ },
180612
+ // the memory store peeks, so asking whether a key is cached does not change which key is evicted next
180613
+ has: async (input) => !!store && !!freshEntry(await withKey(input, (key) => callAdapter("get", key, () => "peek" in store ? store.peek(key) : store.get(key)))),
180614
+ // not `async`: on the memory store a predicate that throws throws here, not in a promise nobody awaits
180615
+ invalidate: (match3) => {
180616
+ const accepts = typeof match3 === "function" ? match3 : fieldsMatch(match3);
180617
+ return Promise.resolve(store ? sweep(store, prefix, accepts) : 0);
180731
180618
  }
180732
- };
180733
- cachedFn.clear = async (key) => {
180734
- if (!store)
180735
- return;
180736
- if (key === void 0) {
180737
- await sweep(store, prefix, () => true);
180738
- return;
180619
+ });
180620
+ }
180621
+ function query(options) {
180622
+ return op({ ...options, dedupe: true });
180623
+ }
180624
+ function mutation({ dedupe: _dedupe, ttl: _ttl, cache: _cache, ...options }) {
180625
+ return build3(options, false);
180626
+ }
180627
+ function debugTrace(label, key) {
180628
+ if (process.env.LOG_LEVEL !== "debug")
180629
+ return;
180630
+ const prefix = `${label}[${(/* @__PURE__ */ new Date()).toISOString()}]`;
180631
+ const start = performance.now();
180632
+ return (message, data) => console.log(`${prefix} ${message} +${Math.round(performance.now() - start)}ms`, { ...key && { key }, ...data });
180633
+ }
180634
+ var isLifetime = (ms) => typeof ms === "number" && ms > 0 && Number.isFinite(ms);
180635
+ function fieldsMatch(partial3) {
180636
+ const fields = Object.entries(partial3).map(([field, value2]) => [
180637
+ field,
180638
+ value2 === void 0 ? void 0 : canonical(value2)
180639
+ ]);
180640
+ return (input) => typeof input === "object" && input !== null && fields.every(([field, value2]) => {
180641
+ const actual = input[field];
180642
+ return actual === void 0 ? value2 === void 0 : value2 !== void 0 && canonical(actual) === value2;
180643
+ });
180644
+ }
180645
+ var isThenable = (value2) => typeof value2?.then === "function";
180646
+ function freshEntry(value2) {
180647
+ return typeof value2?.expires === "number" && value2.expires > Date.now() && "value" in value2 ? value2 : void 0;
180648
+ }
180649
+ function callAdapter(method, key, run) {
180650
+ try {
180651
+ const result = run();
180652
+ if (!isThenable(result))
180653
+ return result;
180654
+ return result.then(void 0, (error49) => {
180655
+ throw new AdapterError(method, key, error49);
180656
+ });
180657
+ } catch (error49) {
180658
+ throw new AdapterError(method, key, error49);
180659
+ }
180660
+ }
180661
+ function sweep(store, prefix, match3) {
180662
+ const purge = (listed2) => {
180663
+ const now = Date.now();
180664
+ const keys = [];
180665
+ let count = 0;
180666
+ for (const [key, { input, expires }] of listed2) {
180667
+ if (!(expires > now))
180668
+ keys.push(key);
180669
+ else if (match3(input)) {
180670
+ keys.push(key);
180671
+ count++;
180672
+ }
180739
180673
  }
180740
- let fullKey = methodKey(key, "clear");
180741
- if (typeof fullKey !== "string")
180742
- fullKey = await fullKey;
180743
- const target = fullKey;
180744
- await callAdapter("delete", target, () => store.delete([target]));
180745
- };
180746
- cachedFn.has = async (key) => {
180747
- if (!store)
180748
- return false;
180749
- let fullKey = methodKey(key, "has");
180750
- if (typeof fullKey !== "string")
180751
- fullKey = await fullKey;
180752
- const target = fullKey;
180753
- const memoryStore = store;
180754
- const read2 = callAdapter("get", target, () => memoryStore.peek ? memoryStore.peek(target) : store.get(target));
180755
- return freshEntry(await read2) !== void 0;
180756
- };
180757
- cachedFn.invalidate = (predicate) => {
180758
- if (!store)
180759
- return Promise.resolve(0);
180760
- const count = sweep(store, prefix, predicate);
180761
- return typeof count === "number" ? Promise.resolve(count) : count;
180674
+ const remove = (from) => {
180675
+ if (from >= keys.length)
180676
+ return count;
180677
+ const deleted = callAdapter("delete", prefix, () => store.delete(keys.slice(from, from + 1e3)));
180678
+ return isThenable(deleted) ? Promise.resolve(deleted).then(() => remove(from + 1e3)) : remove(from + 1e3);
180679
+ };
180680
+ return remove(0);
180762
180681
  };
180763
- return cachedFn;
180682
+ const listed = callAdapter("list", prefix, () => {
180683
+ const listed2 = store.list(prefix);
180684
+ if (Array.isArray(listed2))
180685
+ return listed2;
180686
+ return (async () => {
180687
+ const items = [];
180688
+ for await (const item of await listed2)
180689
+ items.push(item);
180690
+ return items;
180691
+ })();
180692
+ });
180693
+ return Array.isArray(listed) ? purge(listed) : listed.then(purge);
180764
180694
  }
180765
180695
 
180766
180696
  // utils/progressComment.ts
@@ -180908,13 +180838,14 @@ function ensureRepoState(toolState, init) {
180908
180838
  // utils/changeImpact.ts
180909
180839
  import { spawn as spawn2 } from "node:child_process";
180910
180840
  import { writeFileSync as writeFileSync7 } from "node:fs";
180911
- import { join as join8 } from "node:path";
180841
+ import { join as join9 } from "node:path";
180912
180842
  var MAX_CANDIDATES = 24;
180913
180843
  var MAX_ATOM_LENGTH = 128;
180914
180844
  var MAX_ATOMS = 12;
180915
180845
  var MAX_REFERENCES = 6;
180916
180846
  var MAX_EXCERPT = 160;
180917
180847
  var MAX_BYTES = 16e3;
180848
+ var LOOKUP_BUDGET_MS = 6e4;
180918
180849
  function isSymbolShaped(atom) {
180919
180850
  return atom.length >= 4 && atom.length <= MAX_ATOM_LENGTH && /[A-Z_]/.test(atom);
180920
180851
  }
@@ -181117,7 +181048,7 @@ function consumeGrepChunk(params) {
181117
181048
  async function lookupReferences(params) {
181118
181049
  const buckets = emptyBuckets(params.candidates);
181119
181050
  if (params.candidates.length === 0) return { buckets, error: void 0 };
181120
- const patterns = params.candidates.flatMap((candidate) => ["-e", candidate.atom]);
181051
+ const pattern = params.candidates.map((candidate) => candidate.atom).join("|");
181121
181052
  const child = spawn2(
181122
181053
  "git",
181123
181054
  [
@@ -181128,13 +181059,14 @@ async function lookupReferences(params) {
181128
181059
  "-z",
181129
181060
  "-I",
181130
181061
  "-w",
181131
- "-F",
181132
- ...patterns,
181062
+ "-E",
181063
+ "-e",
181064
+ pattern,
181133
181065
  params.treeish,
181134
181066
  "--",
181135
181067
  ":(top)"
181136
181068
  ],
181137
- { env: resolveEnv(void 0), stdio: ["ignore", "pipe", "pipe"] }
181069
+ { env: resolveEnv(void 0), stdio: ["ignore", "pipe", "pipe"], signal: params.signal }
181138
181070
  );
181139
181071
  const state = createGrepRecordState();
181140
181072
  const maxAtomLength = Math.max(...params.candidates.map((candidate) => candidate.atom.length));
@@ -181154,7 +181086,7 @@ async function lookupReferences(params) {
181154
181086
  child.stderr.resume();
181155
181087
  const status = await new Promise((resolve3) => {
181156
181088
  child.once("error", (error49) => {
181157
- spawnError = error49.message;
181089
+ spawnError = error49.cause instanceof Error ? error49.cause.message : error49.message;
181158
181090
  resolve3(null);
181159
181091
  });
181160
181092
  child.once("close", resolve3);
@@ -181242,8 +181174,10 @@ async function createChangeImpactArtifact(ctx, params) {
181242
181174
  const lookup2 = await lookupReferences({
181243
181175
  candidates: candidates.selected,
181244
181176
  addedLines: candidates.addedLines,
181245
- treeish: params.headSha
181177
+ treeish: params.headSha,
181178
+ signal: AbortSignal.any([params.signal, AbortSignal.timeout(LOOKUP_BUDGET_MS)])
181246
181179
  });
181180
+ params.signal.throwIfAborted();
181247
181181
  const artifact = formatArtifact({
181248
181182
  entries: candidates.selected.map((change) => {
181249
181183
  return { change, bucket: lookup2.buckets.get(change.atom) ?? { count: 0, samples: [] } };
@@ -181255,7 +181189,7 @@ async function createChangeImpactArtifact(ctx, params) {
181255
181189
  headSha: params.headSha,
181256
181190
  lookupError: lookup2.error
181257
181191
  });
181258
- const path4 = join8(ctx.tmpdir, `pr-${params.pullNumber}-${params.headSha.slice(0, 7)}-impact.md`);
181192
+ const path4 = join9(ctx.tmpdir, `pr-${params.pullNumber}-${params.headSha.slice(0, 7)}-impact.md`);
181259
181193
  writeFileSync7(path4, artifact.content);
181260
181194
  return {
181261
181195
  path: path4,
@@ -181509,7 +181443,7 @@ function readNumber(params) {
181509
181443
  import { execSync } from "node:child_process";
181510
181444
  import { createHash } from "node:crypto";
181511
181445
  import { readFileSync as readFileSync4, realpathSync, unlinkSync } from "node:fs";
181512
- import { join as join9 } from "node:path";
181446
+ import { join as join10 } from "node:path";
181513
181447
 
181514
181448
  // utils/shell.ts
181515
181449
  import { spawnSync as spawnSync5 } from "node:child_process";
@@ -181599,7 +181533,7 @@ function resolveHooksDir(cwd, gitPath) {
181599
181533
  cwd,
181600
181534
  log: false
181601
181535
  }).trim();
181602
- const hooksDir = join9(commonDir, "hooks");
181536
+ const hooksDir = join10(commonDir, "hooks");
181603
181537
  hooksDirCache.set(cwd, hooksDir);
181604
181538
  return hooksDir;
181605
181539
  }
@@ -181646,7 +181580,8 @@ async function $git(subcommand, args2, options) {
181646
181580
  GIT_CONFIG_COUNT: "0",
181647
181581
  GIT_CONFIG_PARAMETERS: ""
181648
181582
  },
181649
- activityTimeout: 0
181583
+ activityTimeout: 0,
181584
+ signal: options.signal
181650
181585
  });
181651
181586
  if (result.stderr.includes("askpass-compromised")) {
181652
181587
  log.info("askpass code was replayed after revoke \u2014 token has been revoked");
@@ -181713,9 +181648,10 @@ import { spawnSync as spawnSync7 } from "node:child_process";
181713
181648
  // mcp/shell.ts
181714
181649
  import { spawn as spawn3, spawnSync as spawnSync6 } from "node:child_process";
181715
181650
  import { randomUUID as randomUUID3 } from "node:crypto";
181716
- import { closeSync, openSync, writeFileSync as writeFileSync8 } from "node:fs";
181651
+ import { once } from "node:events";
181652
+ import { closeSync, existsSync as existsSync7, openSync, writeFileSync as writeFileSync8 } from "node:fs";
181717
181653
  import { userInfo as userInfo2 } from "node:os";
181718
- import { join as join10 } from "node:path";
181654
+ import { join as join11 } from "node:path";
181719
181655
  import { setTimeout as sleep3 } from "node:timers/promises";
181720
181656
 
181721
181657
  // node_modules/.pnpm/@toon-format+toon@1.4.0/node_modules/@toon-format/toon/dist/index.mjs
@@ -182359,7 +182295,7 @@ function buildFsMounts(repoDir) {
182359
182295
  function shimMount() {
182360
182296
  const tempDir = process.env.PULLFROG_TEMP_DIR;
182361
182297
  if (!tempDir) return "";
182362
- const dir = join10(tempDir, "pm-bin").replace(/'/g, "'\\''");
182298
+ const dir = join11(tempDir, "pm-bin").replace(/'/g, "'\\''");
182363
182299
  return `[ -d '${dir}' ] && mount --bind '${dir}' '${dir}' 2>/dev/null && mount -o remount,bind,ro '${dir}' 2>/dev/null;`;
182364
182300
  }
182365
182301
  var _repoRoot;
@@ -182530,7 +182466,7 @@ ${spawnError}` : spawnError;
182530
182466
  }
182531
182467
  function capOutput(output) {
182532
182468
  if (output.length <= MAX_OUTPUT_CHARS) return output;
182533
- const fullPath = join10(getTempDir(), `shell-${randomUUID3().slice(0, 8)}.log`);
182469
+ const fullPath = join11(getTempDir(), `shell-${randomUUID3().slice(0, 8)}.log`);
182534
182470
  writeFileSync8(fullPath, output);
182535
182471
  const elided = output.length - MAX_OUTPUT_CHARS;
182536
182472
  return `... [${elided} chars truncated; output saved to ${fullPath}] ...
@@ -182546,7 +182482,7 @@ function ShellTool(ctx) {
182546
182482
  return tool({
182547
182483
  name: "shell",
182548
182484
  timeoutMs: 12e4,
182549
- description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
182485
+ description: `Execute shell commands securely.${ctx.payload.shell === "restricted" ? " Environment is filtered to remove API keys and secrets." : ""}
182550
182486
 
182551
182487
  Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
182552
182488
 
@@ -182567,6 +182503,7 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
182567
182503
  }
182568
182504
  const timeout = Math.min(params.timeout ?? 3e4, 12e4);
182569
182505
  const cwd = params.working_directory ?? process.cwd();
182506
+ if (!existsSync7(cwd)) throw new Error(`working_directory does not exist: ${cwd}`);
182570
182507
  const env2 = resolveEnv(ctx.payload.shell === "enabled" ? "inherit" : "restricted");
182571
182508
  if (params.command.includes("agent-browser")) {
182572
182509
  const daemonError = ensureBrowserDaemon(ctx.toolState);
@@ -182585,8 +182522,8 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
182585
182522
  if (params.background) {
182586
182523
  const tempDir = getTempDir();
182587
182524
  const handle = `bg-${randomUUID3().slice(0, 8)}`;
182588
- const outputPath = join10(tempDir, `${handle}.log`);
182589
- const pidPath = join10(tempDir, `${handle}.pid`);
182525
+ const outputPath = join11(tempDir, `${handle}.log`);
182526
+ const pidPath = join11(tempDir, `${handle}.pid`);
182590
182527
  const logFd = openSync(outputPath, "a");
182591
182528
  let proc;
182592
182529
  try {
@@ -182600,7 +182537,8 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
182600
182537
  closeSync(logFd);
182601
182538
  }
182602
182539
  if (!proc.pid) {
182603
- throw new Error("failed to start background process");
182540
+ const [error49] = await once(proc, "error");
182541
+ throw new Error(`failed to start background process: ${error49.message}`);
182604
182542
  }
182605
182543
  proc.unref();
182606
182544
  writeFileSync8(pidPath, `${proc.pid}
@@ -182877,12 +182815,12 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
182877
182815
  // mcp/git.ts
182878
182816
  import { randomUUID as randomUUID4 } from "node:crypto";
182879
182817
  import { writeFileSync as writeFileSync9 } from "node:fs";
182880
- import { join as join13 } from "node:path";
182818
+ import { join as join14 } from "node:path";
182881
182819
 
182882
182820
  // utils/apiCommit.ts
182883
182821
  import { execFileSync as execFileSync5 } from "node:child_process";
182884
182822
  import { lstat, readlink } from "node:fs/promises";
182885
- import { join as join11 } from "node:path";
182823
+ import { join as join12 } from "node:path";
182886
182824
  var GITHUB_API = "https://api.github.com";
182887
182825
  var MAX_BLOB_BYTES = 30 * 1024 * 1024;
182888
182826
  var BLOB_UPLOAD_CONCURRENCY = 8;
@@ -182947,7 +182885,7 @@ async function assertApiCommittable(files) {
182947
182885
  const root = getRepoRoot();
182948
182886
  const attrs = $(
182949
182887
  "git",
182950
- ["check-attr", "filter", "-z", "--", ...present.map((p) => join11(root, p))],
182888
+ ["check-attr", "filter", "-z", "--", ...present.map((p) => join12(root, p))],
182951
182889
  { log: false }
182952
182890
  );
182953
182891
  const parts = attrs.split("\0");
@@ -182959,7 +182897,7 @@ async function assertApiCommittable(files) {
182959
182897
  }
182960
182898
  }
182961
182899
  for (const path4 of present) {
182962
- const stat = await lstat(join11(root, path4));
182900
+ const stat = await lstat(join12(root, path4));
182963
182901
  if (stat.isDirectory()) {
182964
182902
  throw new Error(
182965
182903
  `'${path4}' is a directory (nested repository or submodule?) \u2014 signed commits only support files and symlinks.`
@@ -182968,7 +182906,7 @@ async function assertApiCommittable(files) {
182968
182906
  }
182969
182907
  }
182970
182908
  async function createBlobEntry(params) {
182971
- const absPath = join11(params.repoRoot, params.path);
182909
+ const absPath = join12(params.repoRoot, params.path);
182972
182910
  const stat = await lstat(absPath);
182973
182911
  if (stat.size > MAX_BLOB_BYTES) {
182974
182912
  throw new Error(
@@ -183148,7 +183086,7 @@ this usually means your local branch has commits that were never pushed \u2014 s
183148
183086
  var core4 = __toESM(require_core(), 1);
183149
183087
  import { createSign } from "node:crypto";
183150
183088
  import { rename, writeFile } from "node:fs/promises";
183151
- import { dirname as dirname3, join as join12 } from "node:path";
183089
+ import { dirname as dirname3, join as join13 } from "node:path";
183152
183090
 
183153
183091
  // node_modules/.pnpm/@octokit+plugin-throttling@11.0.5_@octokit+core@7.0.8/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
183154
183092
  var import_light = __toESM(require_light(), 1);
@@ -184391,7 +184329,7 @@ async function fetchWrapper(requestOptions) {
184391
184329
  "fetch is not set. Please pass a fetch implementation as new Octokit({ request: { fetch }}). Learn more at https://github.com/octokit/octokit.js/#fetch-missing"
184392
184330
  );
184393
184331
  }
184394
- const log3 = requestOptions.request?.log || console;
184332
+ const log2 = requestOptions.request?.log || console;
184395
184333
  const parseSuccessResponseBody = requestOptions.request?.parseSuccessResponseBody !== false;
184396
184334
  const body = isPlainObject8(requestOptions.body) || Array.isArray(requestOptions.body) ? JSONStringify(requestOptions.body) : requestOptions.body;
184397
184335
  const requestHeaders = Object.fromEntries(
@@ -184449,7 +184387,7 @@ async function fetchWrapper(requestOptions) {
184449
184387
  if ("deprecation" in responseHeaders) {
184450
184388
  const matches = responseHeaders.link && responseHeaders.link.match(/<([^<>]+)>; rel="deprecation"/);
184451
184389
  const deprecationLink = matches && matches.pop();
184452
- log3.warn(
184390
+ log2.warn(
184453
184391
  `[@octokit/request] "${requestOptions.method} ${requestOptions.url}" is deprecated. It is scheduled to be removed on ${responseHeaders.sunset}${deprecationLink ? `. See ${deprecationLink}` : ""}`
184454
184392
  );
184455
184393
  }
@@ -184587,9 +184525,9 @@ var NON_VARIABLE_OPTIONS = [
184587
184525
  ];
184588
184526
  var FORBIDDEN_VARIABLE_OPTIONS = ["query", "method", "url"];
184589
184527
  var GHES_V3_SUFFIX_REGEX = /\/api\/v3\/?$/;
184590
- function graphql(request2, query, options) {
184528
+ function graphql(request2, query2, options) {
184591
184529
  if (options) {
184592
- if (typeof query === "string" && "query" in options) {
184530
+ if (typeof query2 === "string" && "query" in options) {
184593
184531
  return Promise.reject(
184594
184532
  new Error(`[@octokit/graphql] "query" cannot be used as variable name`)
184595
184533
  );
@@ -184603,7 +184541,7 @@ function graphql(request2, query, options) {
184603
184541
  );
184604
184542
  }
184605
184543
  }
184606
- const parsedOptions = typeof query === "string" ? Object.assign({ query }, options) : query;
184544
+ const parsedOptions = typeof query2 === "string" ? Object.assign({ query: query2 }, options) : query2;
184607
184545
  const requestOptions = Object.keys(
184608
184546
  parsedOptions
184609
184547
  ).reduce((result, key) => {
@@ -184638,8 +184576,8 @@ function graphql(request2, query, options) {
184638
184576
  }
184639
184577
  function withDefaults3(request2, newDefaults) {
184640
184578
  const newRequest2 = request2.defaults(newDefaults);
184641
- const newApi = (query, options) => {
184642
- return graphql(newRequest2, query, options);
184579
+ const newApi = (query2, options) => {
184580
+ return graphql(newRequest2, query2, options);
184643
184581
  };
184644
184582
  return Object.assign(newApi, {
184645
184583
  defaults: withDefaults3.bind(null, newRequest2),
@@ -187483,16 +187421,17 @@ function translateIdTokenError(error49) {
187483
187421
  if (!status) return error49;
187484
187422
  return new TokenExchangeError(Number(status), error49.message);
187485
187423
  }
187486
- var mintIdToken = op(
187487
- async () => {
187424
+ var mintIdToken = query({
187425
+ run: async () => {
187488
187426
  try {
187489
187427
  return await core4.getIDToken(OIDC_AUDIENCE);
187490
187428
  } catch (error49) {
187491
187429
  throw translateIdTokenError(error49);
187492
187430
  }
187493
187431
  },
187494
- { ttl: 6e4, name: "OIDC ID token" }
187495
- );
187432
+ ttl: 6e4,
187433
+ name: "OIDC ID token"
187434
+ });
187496
187435
  async function acquireTokenViaOIDC(opts) {
187497
187436
  const oidcToken = opts?.oidc ? await fetchIdTokenFromStash(opts.oidc) : await mintIdToken();
187498
187437
  const repos = [...opts?.repos ?? []];
@@ -187650,10 +187589,10 @@ function isTransientTokenError(error49) {
187650
187589
  }
187651
187590
  async function acquireNewToken(opts) {
187652
187591
  if (opts?.oidc || isOIDCAvailable()) {
187653
- return await op(() => acquireTokenViaOIDC(opts), {
187592
+ return await mutation({
187593
+ run: () => acquireTokenViaOIDC(opts),
187654
187594
  name: "token exchange",
187655
- retries: [1e3, 2e3],
187656
- rethrow: (error49) => !isTransientTokenError(error49)
187595
+ retry: (error49, attempt) => isTransientTokenError(error49) ? delay2([1e3, 2e3], attempt) : -1
187657
187596
  })();
187658
187597
  }
187659
187598
  if (process.env.GITHUB_ACTIONS === "true") {
@@ -187696,7 +187635,7 @@ function getGitHubUsageSummary() {
187696
187635
  }
187697
187636
  async function writeGitHubUsageSummaryToFile(path4) {
187698
187637
  const summary2 = getGitHubUsageSummary();
187699
- const tmpPath = join12(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
187638
+ const tmpPath = join13(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
187700
187639
  await writeFile(tmpPath, JSON.stringify(summary2));
187701
187640
  await rename(tmpPath, path4);
187702
187641
  }
@@ -187987,19 +187926,19 @@ async function pushWithRetry(args2, token, refreshGitToken, cwd = process.cwd())
187987
187926
  if ((kind === "transient" || kind === "transient-auth") && attempt < TRANSIENT_RETRY_DELAYS_MS.length) {
187988
187927
  if (kind === "transient-auth" && refreshGitToken) {
187989
187928
  currentToken = await refreshGitToken(currentToken);
187990
- const delay3 = Math.round(1e3 * (0.75 + Math.random() * 0.5));
187929
+ const delay4 = Math.round(1e3 * (0.75 + Math.random() * 0.5));
187991
187930
  log.info(
187992
- `push attempt ${attempt + 1} failed (auth), re-minted token, retrying in ${delay3}ms: ${msg.slice(0, 300)}`
187931
+ `push attempt ${attempt + 1} failed (auth), re-minted token, retrying in ${delay4}ms: ${msg.slice(0, 300)}`
187993
187932
  );
187994
- await new Promise((r) => setTimeout(r, delay3));
187933
+ await new Promise((r) => setTimeout(r, delay4));
187995
187934
  continue;
187996
187935
  }
187997
187936
  const baseDelay = TRANSIENT_RETRY_DELAYS_MS[attempt] ?? 5e3;
187998
- const delay2 = Math.round(baseDelay * (0.75 + Math.random() * 0.5));
187937
+ const delay3 = Math.round(baseDelay * (0.75 + Math.random() * 0.5));
187999
187938
  log.info(
188000
- `push attempt ${attempt + 1} failed (transient), retrying in ${delay2}ms: ${msg.slice(0, 300)}`
187939
+ `push attempt ${attempt + 1} failed (transient), retrying in ${delay3}ms: ${msg.slice(0, 300)}`
188001
187940
  );
188002
- await new Promise((r) => setTimeout(r, delay2));
187941
+ await new Promise((r) => setTimeout(r, delay3));
188003
187942
  continue;
188004
187943
  }
188005
187944
  throw err;
@@ -188324,7 +188263,7 @@ function countAhead(head, base, cwd) {
188324
188263
  function spillGitOutput(params) {
188325
188264
  const tempDir = process.env.PULLFROG_TEMP_DIR;
188326
188265
  if (!tempDir) throw new Error("PULLFROG_TEMP_DIR not set");
188327
- const outputPath = join13(tempDir, `git-${params.command}-${randomUUID4().slice(0, 8)}.txt`);
188266
+ const outputPath = join14(tempDir, `git-${params.command}-${randomUUID4().slice(0, 8)}.txt`);
188328
188267
  writeFileSync9(outputPath, params.output);
188329
188268
  const previewByLines = params.output.split("\n").slice(0, OVERFLOW_PREVIEW_LINES).join("\n");
188330
188269
  const preview = previewByLines.length <= OVERFLOW_PREVIEW_MAX_CHARS ? previewByLines : `${previewByLines.slice(0, OVERFLOW_PREVIEW_MAX_CHARS)}\u2026`;
@@ -188724,6 +188663,112 @@ function isTransientOctokitError(error49) {
188724
188663
  return isTransientNetworkError(error49);
188725
188664
  }
188726
188665
 
188666
+ // utils/runContext.ts
188667
+ import { randomUUID as randomUUID5 } from "node:crypto";
188668
+ var START_KEY = randomUUID5();
188669
+ var defaultSettings = {
188670
+ model: null,
188671
+ effort: null,
188672
+ modes: [],
188673
+ setupScript: null,
188674
+ postCheckoutScript: null,
188675
+ prepushScript: null,
188676
+ stopScript: null,
188677
+ push: "restricted",
188678
+ shell: "restricted",
188679
+ prApproveEnabled: false,
188680
+ reviewDrafts: false,
188681
+ autoMergeEnabled: false,
188682
+ codexAgent: false,
188683
+ signedCommits: false,
188684
+ repoIntelligence: false,
188685
+ progressComments: true,
188686
+ statusChecks: true,
188687
+ approvalCheck: false,
188688
+ modeInstructions: {},
188689
+ learnings: null,
188690
+ learningsHeadings: [],
188691
+ envAllowlist: null,
188692
+ xrepoBrief: null,
188693
+ xrepoLearnings: null,
188694
+ xrepoLearningsHeadings: []
188695
+ };
188696
+ var defaultRunContext = {
188697
+ settings: defaultSettings,
188698
+ apiToken: "",
188699
+ oss: false,
188700
+ plan: "none"
188701
+ };
188702
+ var unknownSecretsRunContext = {
188703
+ ...defaultRunContext,
188704
+ secretsUnavailable: true
188705
+ };
188706
+ async function fetchRunContext(params) {
188707
+ const timeoutMs = 3e4;
188708
+ const controller = new AbortController();
188709
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
188710
+ try {
188711
+ const headers = {
188712
+ Authorization: `Bearer ${params.token}`,
188713
+ "X-Pullfrog-Credential-Pools": "1"
188714
+ };
188715
+ if (params.oidcToken) {
188716
+ headers["X-GitHub-OIDC-Token"] = params.oidcToken;
188717
+ }
188718
+ const query2 = new URLSearchParams({ start: START_KEY });
188719
+ if (params.runType) query2.set("type", params.runType);
188720
+ if (params.plainPrompt) query2.set("prompt", "plain");
188721
+ if (params.routedTier) query2.set("tier", params.routedTier);
188722
+ const search2 = query2.toString();
188723
+ const response = await apiFetch({
188724
+ path: `/api/repo/${params.repoContext.owner}/${params.repoContext.name}/run-context${search2 ? `?${search2}` : ""}`,
188725
+ headers,
188726
+ signal: controller.signal
188727
+ });
188728
+ clearTimeout(timeoutId);
188729
+ if (response.status === 402) {
188730
+ const body = await response.json().catch(() => null);
188731
+ const reason = typeof body === "object" && body !== null && "reason" in body ? body.reason : null;
188732
+ const commercialRefused = reason === "subscription_unpaid" || reason === "subscription_ended" ? reason : "commercial";
188733
+ return { ...defaultRunContext, commercialRefused };
188734
+ }
188735
+ if (!response.ok) {
188736
+ return response.status >= 500 ? unknownSecretsRunContext : defaultRunContext;
188737
+ }
188738
+ const data = await response.json();
188739
+ if (data === null) {
188740
+ return defaultRunContext;
188741
+ }
188742
+ return {
188743
+ settings: {
188744
+ ...defaultSettings,
188745
+ ...data.settings,
188746
+ modes: data.settings?.modes ?? [],
188747
+ setupScript: data.settings?.setupScript ?? null,
188748
+ postCheckoutScript: data.settings?.postCheckoutScript ?? null,
188749
+ prepushScript: data.settings?.prepushScript ?? null,
188750
+ stopScript: data.settings?.stopScript ?? null,
188751
+ learningsHeadings: data.settings?.learningsHeadings ?? [],
188752
+ xrepoBrief: data.settings?.xrepoBrief ?? null,
188753
+ xrepoLearnings: data.settings?.xrepoLearnings ?? null,
188754
+ xrepoLearningsHeadings: data.settings?.xrepoLearningsHeadings ?? []
188755
+ },
188756
+ apiToken: data.apiToken,
188757
+ oss: data.oss ?? false,
188758
+ plan: data.plan ?? "none",
188759
+ proxyModel: data.proxyModel,
188760
+ dbSecrets: data.dbSecrets,
188761
+ credentialAccess: data.credentialAccess,
188762
+ secretsUnavailable: data.secretsUnavailable,
188763
+ routerUnfunded: data.routerUnfunded,
188764
+ trialFallback: data.trialFallback
188765
+ };
188766
+ } catch {
188767
+ clearTimeout(timeoutId);
188768
+ return unknownSecretsRunContext;
188769
+ }
188770
+ }
188771
+
188727
188772
  // utils/patchWorkflowRunFields.ts
188728
188773
  var STRING_KEYS = [
188729
188774
  "prNodeId",
@@ -188742,6 +188787,7 @@ var NUMBER_KEYS = [
188742
188787
  "cacheWriteTokens",
188743
188788
  "costUsd"
188744
188789
  ];
188790
+ var FLAG_KEYS = ["reviewSkipped"];
188745
188791
  async function patchWorkflowRunFields(ctx, fields) {
188746
188792
  if (ctx.runId === void 0 || !ctx.apiToken) return;
188747
188793
  if (ctx.toolState.workflowRunUnclaimed) return;
@@ -188758,10 +188804,17 @@ async function patchWorkflowRunFields(ctx, fields) {
188758
188804
  body[key] = value2;
188759
188805
  }
188760
188806
  }
188807
+ for (const key of FLAG_KEYS) {
188808
+ if (fields[key]) body[key] = true;
188809
+ }
188810
+ if (fields.succeeded !== void 0) body.succeeded = fields.succeeded;
188761
188811
  if (Object.keys(body).length === 0) return;
188812
+ body.startKey = START_KEY;
188813
+ const runAttempt = Number(process.env.GITHUB_RUN_ATTEMPT);
188814
+ if (Number.isInteger(runAttempt) && runAttempt >= 1) body.runAttempt = runAttempt;
188762
188815
  try {
188763
- await op(
188764
- async () => {
188816
+ await mutation({
188817
+ run: async () => {
188765
188818
  const response = await apiFetch({
188766
188819
  path: `/api/workflow-run/${ctx.runId}`,
188767
188820
  method: "PATCH",
@@ -188772,17 +188825,18 @@ async function patchWorkflowRunFields(ctx, fields) {
188772
188825
  body: JSON.stringify(body),
188773
188826
  signal: AbortSignal.timeout(1e4)
188774
188827
  });
188775
- if (response.status === 404) ctx.toolState.workflowRunUnclaimed = true;
188828
+ if (response.status === 404) {
188829
+ ctx.toolState.workflowRunUnclaimed = true;
188830
+ log.info(`\xBB Pullfrog has no record of run ${ctx.runId}; skipping run metadata updates`);
188831
+ return;
188832
+ }
188776
188833
  if (!response.ok) throw new Error(`PATCH workflow-run: ${response.status}`);
188777
188834
  },
188778
- {
188779
- retries: [2e3, 4e3],
188780
- name: "patchWorkflowRunFields",
188781
- // only retry transient network errors; explicit HTTP failures throw
188782
- // a status-bearing message and should fail fast.
188783
- rethrow: (error49) => !isTransientNetworkError(error49)
188784
- }
188785
- )();
188835
+ // only retry transient network errors; explicit HTTP failures throw
188836
+ // a status-bearing message and should fail fast.
188837
+ retry: (error49, attempt) => isTransientNetworkError(error49) ? delay2([2e3, 4e3], attempt) : -1,
188838
+ name: "patchWorkflowRunFields"
188839
+ })();
188786
188840
  } catch (error49) {
188787
188841
  log.warning(`patchWorkflowRunFields failed: ${error49}`);
188788
188842
  }
@@ -188955,6 +189009,9 @@ var ReportProgress = type({
188955
189009
  body: type.string.describe("the progress update content to share"),
188956
189010
  "target_plan_comment?": type("boolean").describe(
188957
189011
  "for revising an existing plan comment ONLY. set to true only when the PlanEdit checklist from select_mode tells you to (i.e. a prior plan comment was found for this issue). NEVER set on the initial plan post \u2014 the initial plan reuses the run's progress comment and is posted by calling report_progress without this flag."
189012
+ ),
189013
+ "no_review_needed?": type("boolean").describe(
189014
+ "IncrementalReview only: true when you judged the new commits need no review, never for a blocker."
188958
189015
  )
188959
189016
  });
188960
189017
  async function reportProgress(ctx, params) {
@@ -189082,6 +189139,9 @@ function ReportProgressTool(ctx) {
189082
189139
  description: 'Share progress on the associated GitHub issue/PR. The first call creates a comment; subsequent calls update it in place. Example: `report_progress({ body: "Implemented the auth check and added tests." })`. Call this at the end of every run with a brief final summary (1-3 sentences) unless the mode guidance instructs otherwise. The current task list is automatically appended in a collapsible section \u2014 do not restate individual steps.',
189083
189140
  parameters: ReportProgress,
189084
189141
  execute: execute(async (params) => {
189142
+ if (params.no_review_needed) {
189143
+ ctx.toolState.noReviewNeededSha = primaryRepoState(ctx.toolState).checkoutSha;
189144
+ }
189085
189145
  if (ctx.toolState.standaloneCommentId !== void 0 && !params.target_plan_comment) {
189086
189146
  ctx.toolState.lastProgressBody = params.body;
189087
189147
  return {
@@ -189613,18 +189673,15 @@ function CreatePullRequestReviewTool(ctx) {
189613
189673
  }
189614
189674
  let result;
189615
189675
  try {
189616
- result = await op(
189617
- () => body ? createAndSubmitWithFooter(ctx, params, {
189676
+ result = await mutation({
189677
+ run: () => body ? createAndSubmitWithFooter(ctx, params, {
189618
189678
  body,
189619
189679
  approved: approved ?? false,
189620
189680
  hasComments: (params.comments?.length ?? 0) > 0
189621
189681
  }) : createReviewWithStrandedRecovery(ctx, params),
189622
- {
189623
- retries: TRANSIENT_REVIEW_RETRY_DELAYS_MS,
189624
- rethrow: (err) => !isTransientReviewError(err),
189625
- name: "review submission"
189626
- }
189627
- )();
189682
+ retry: (error49, attempt) => isTransientReviewError(error49) ? delay2(TRANSIENT_REVIEW_RETRY_DELAYS_MS, attempt) : -1,
189683
+ name: "review submission"
189684
+ })();
189628
189685
  } catch (err) {
189629
189686
  if (ctx.toolState.approval) ctx.toolState.approval.wouldApprove = false;
189630
189687
  if (isTransientReviewError(err)) {
@@ -189861,11 +189918,11 @@ async function reportReviewNodeId(ctx, params) {
189861
189918
 
189862
189919
  // utils/setup.ts
189863
189920
  import { execFileSync as execFileSync6, execSync as execSync2 } from "node:child_process";
189864
- import { mkdtempSync, readdirSync, realpathSync as realpathSync2, rmSync as rmSync2, unlinkSync as unlinkSync2 } from "node:fs";
189921
+ import { mkdtempSync as mkdtempSync2, readdirSync, realpathSync as realpathSync2, rmSync as rmSync2, unlinkSync as unlinkSync2 } from "node:fs";
189865
189922
  import { tmpdir as tmpdir2 } from "node:os";
189866
- import { join as join14 } from "node:path";
189923
+ import { join as join15 } from "node:path";
189867
189924
  function createTempDirectory() {
189868
- const path4 = mkdtempSync(join14(tmpdir2(), "pullfrog-"));
189925
+ const path4 = mkdtempSync2(join15(tmpdir2(), "pullfrog-"));
189869
189926
  process.env.PULLFROG_TEMP_DIR = path4;
189870
189927
  log.info(`\xBB created temp dir at ${path4}`);
189871
189928
  return { path: path4, [Symbol.dispose]: () => removeTempDirectory(path4) };
@@ -189925,13 +189982,13 @@ function wipeRunnerLeakSurface() {
189925
189982
  return [];
189926
189983
  }
189927
189984
  };
189928
- const fileCommandsDir = join14(runnerTemp, "_runner_file_commands");
189985
+ const fileCommandsDir = join15(runnerTemp, "_runner_file_commands");
189929
189986
  for (const entry of listDir(fileCommandsDir)) {
189930
- tryUnlink(join14(fileCommandsDir, entry));
189987
+ tryUnlink(join15(fileCommandsDir, entry));
189931
189988
  }
189932
189989
  for (const entry of listDir(runnerTemp)) {
189933
189990
  if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
189934
- tryUnlink(join14(runnerTemp, entry));
189991
+ tryUnlink(join15(runnerTemp, entry));
189935
189992
  }
189936
189993
  }
189937
189994
  if (wiped.length > 0) {
@@ -190203,7 +190260,7 @@ async function ensureBeforeShaReachable(params) {
190203
190260
  "origin",
190204
190261
  tempBranch
190205
190262
  ],
190206
- { token: params.gitToken, refreshGitToken: params.refreshGitToken },
190263
+ { token: params.gitToken, refreshGitToken: params.refreshGitToken, signal: params.signal },
190207
190264
  `before_sha temp branch ${tempBranch}`
190208
190265
  );
190209
190266
  log.debug(`\xBB fetched before_sha via temp branch ${tempBranch}`);
@@ -190244,7 +190301,7 @@ function cleanupStaleGitLocks() {
190244
190301
  } catch {
190245
190302
  continue;
190246
190303
  }
190247
- if (now - mtimeMs < STALE_LOCK_AGE_MS) continue;
190304
+ if (now - mtimeMs < STALE_LOCK_AGE_MS || mtimeMs >= performance.timeOrigin) continue;
190248
190305
  try {
190249
190306
  unlinkSync3(relPath);
190250
190307
  log.warning(`\xBB removed stale ${relPath} from prior run`);
@@ -190277,7 +190334,7 @@ async function abortIfPullRequestMoved(args2) {
190277
190334
  );
190278
190335
  }
190279
190336
  async function checkoutPrBranch(pr, params) {
190280
- const { octokit, owner, name, gitToken, refreshGitToken, toolState, beforeSha } = params;
190337
+ const { octokit, owner, name, gitToken, refreshGitToken, toolState, beforeSha, signal } = params;
190281
190338
  rejectIfLeadingDash(pr.baseRef, "PR base ref");
190282
190339
  rejectIfLeadingDash(pr.headRef, "PR head ref");
190283
190340
  const repoState = requireRepoState(toolState, owner, name);
@@ -190291,18 +190348,18 @@ async function checkoutPrBranch(pr, params) {
190291
190348
  log.debug(`\xBB fetching base branch (${pr.baseRef})...`);
190292
190349
  await $gitFetchWithDeepen(
190293
190350
  ["--no-tags", "origin", pr.baseRef],
190294
- { token: gitToken, refreshGitToken },
190351
+ { token: gitToken, refreshGitToken, signal },
190295
190352
  `base branch ${pr.baseRef}`
190296
190353
  );
190297
190354
  if (!alreadyOnBranch) {
190298
190355
  $("git", ["checkout", "-B", pr.baseRef, `origin/${pr.baseRef}`], { log: false });
190299
190356
  log.debug(`\xBB fetching PR #${pr.number} (${localBranch})...`);
190300
- await op(
190301
- async () => {
190357
+ await mutation({
190358
+ run: async () => {
190302
190359
  try {
190303
190360
  await $gitFetchWithDeepen(
190304
190361
  ["--no-tags", "origin", `+pull/${pr.number}/head:${localBranch}`],
190305
- { token: gitToken, refreshGitToken },
190362
+ { token: gitToken, refreshGitToken, signal },
190306
190363
  `PR #${pr.number}`
190307
190364
  );
190308
190365
  } catch (e) {
@@ -190313,12 +190370,9 @@ async function checkoutPrBranch(pr, params) {
190313
190370
  throw e;
190314
190371
  }
190315
190372
  },
190316
- {
190317
- retries: PULL_REF_RETRY_DELAYS_MS,
190318
- name: `pull/${pr.number}/head fetch`,
190319
- rethrow: (e) => !PULL_REF_MISSING_PATTERN.test(e instanceof Error ? e.message : String(e))
190320
- }
190321
- )();
190373
+ retry: (error49, attempt) => PULL_REF_MISSING_PATTERN.test(error49 instanceof Error ? error49.message : String(error49)) ? delay2(PULL_REF_RETRY_DELAYS_MS, attempt) : -1,
190374
+ name: `pull/${pr.number}/head fetch`
190375
+ })();
190322
190376
  $("git", ["checkout", localBranch], { log: false });
190323
190377
  log.debug(`\xBB checked out PR #${pr.number}`);
190324
190378
  repoState.checkoutSha = $("git", ["rev-parse", "HEAD"], { log: false }).trim();
@@ -190330,7 +190384,8 @@ async function checkoutPrBranch(pr, params) {
190330
190384
  repo: name,
190331
190385
  gitToken,
190332
190386
  refreshGitToken,
190333
- isShallow
190387
+ isShallow,
190388
+ signal
190334
190389
  }) : false;
190335
190390
  if (isShallow) {
190336
190391
  let deepenDepth = 0;
@@ -190365,7 +190420,8 @@ async function checkoutPrBranch(pr, params) {
190365
190420
  if (deepenDepth) {
190366
190421
  log.debug(`\xBB deepening by ${deepenDepth} to reach merge base...`);
190367
190422
  await $git("fetch", [`--deepen=${deepenDepth}`, "--no-tags", "origin"], {
190368
- token: gitToken
190423
+ token: gitToken,
190424
+ signal
190369
190425
  });
190370
190426
  }
190371
190427
  }
@@ -190403,6 +190459,7 @@ async function checkoutPrBranch(pr, params) {
190403
190459
  remoteBranch: pr.headRef,
190404
190460
  localBranch
190405
190461
  };
190462
+ signal.throwIfAborted();
190406
190463
  const postCheckoutHook = await executeLifecycleHook({
190407
190464
  event: "post-checkout",
190408
190465
  script: params.postCheckoutScript,
@@ -190413,19 +190470,30 @@ async function checkoutPrBranch(pr, params) {
190413
190470
  }
190414
190471
  var inFlightCheckouts = /* @__PURE__ */ new Map();
190415
190472
  var CHECKOUT_DEADLINE_MS = 57e4;
190416
- function withCheckoutDeadline(promise2, pullNumber) {
190417
- let timer;
190418
- const deadline = new Promise((_, reject) => {
190419
- timer = setTimeout(
190473
+ var CHECKOUT_ABORT_GRACE_MS = 15e3;
190474
+ function withCheckoutDeadline(run, pullNumber) {
190475
+ const exceeded = `checkout_pr #${pullNumber} exceeded ${CHECKOUT_DEADLINE_MS / 1e3}s`;
190476
+ const retry = "retry the call to start a fresh attempt.";
190477
+ const controller = new AbortController();
190478
+ const abortTimer = setTimeout(
190479
+ () => controller.abort(new Error(`${exceeded} and was cancelled; ${retry}`)),
190480
+ CHECKOUT_DEADLINE_MS
190481
+ );
190482
+ let backstopTimer;
190483
+ const backstop = new Promise((_, reject) => {
190484
+ backstopTimer = setTimeout(
190420
190485
  () => reject(
190421
190486
  new Error(
190422
- `checkout_pr #${pullNumber} exceeded ${CHECKOUT_DEADLINE_MS / 1e3}s. the fetch may still be running in the background; retry the call to start a fresh attempt.`
190487
+ `${exceeded} and was asked to cancel, but a step that cannot be interrupted (the post-checkout hook or a GitHub API call) was still running ${CHECKOUT_ABORT_GRACE_MS / 1e3}s later. ${retry}`
190423
190488
  )
190424
190489
  ),
190425
- CHECKOUT_DEADLINE_MS
190490
+ CHECKOUT_DEADLINE_MS + CHECKOUT_ABORT_GRACE_MS
190426
190491
  );
190427
190492
  });
190428
- return Promise.race([promise2, deadline]).finally(() => clearTimeout(timer));
190493
+ return Promise.race([run(controller.signal), backstop]).finally(() => {
190494
+ clearTimeout(abortTimer);
190495
+ clearTimeout(backstopTimer);
190496
+ });
190429
190497
  }
190430
190498
  function headsEqual(a, b) {
190431
190499
  if (a.kind === "branch" && b.kind === "branch") return a.name === b.name;
@@ -190437,7 +190505,7 @@ function describeHead(h) {
190437
190505
  return `detached HEAD \`${h.sha}\``;
190438
190506
  }
190439
190507
  function CheckoutPrTool(ctx) {
190440
- const runCheckout = async (pull_number) => {
190508
+ const runCheckout = async (pull_number, signal) => {
190441
190509
  const prResponse = await ctx.octokit.rest.pulls.get({
190442
190510
  owner: ctx.repo.owner,
190443
190511
  repo: ctx.repo.name,
@@ -190466,7 +190534,8 @@ function CheckoutPrTool(ctx) {
190466
190534
  toolState: ctx.toolState,
190467
190535
  shell: ctx.payload.shell,
190468
190536
  postCheckoutScript: ctx.postCheckoutScript,
190469
- beforeSha: primary.beforeSha
190537
+ beforeSha: primary.beforeSha,
190538
+ signal
190470
190539
  });
190471
190540
  const tempDir = process.env.PULLFROG_TEMP_DIR;
190472
190541
  if (!tempDir) {
@@ -190488,7 +190557,7 @@ function CheckoutPrTool(ctx) {
190488
190557
  headSha: checkoutSha
190489
190558
  });
190490
190559
  if (incremental.status === "ok") {
190491
- incrementalDiffPath = join15(
190560
+ incrementalDiffPath = join16(
190492
190561
  tempDir,
190493
190562
  `pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
190494
190563
  );
@@ -190505,7 +190574,7 @@ function CheckoutPrTool(ctx) {
190505
190574
  const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
190506
190575
  log.debug(`formatted diff preview (first 100 lines):
190507
190576
  ${diffPreview}`);
190508
- const diffPath = join15(tempDir, `pr-${pull_number}-${headShort}.diff`);
190577
+ const diffPath = join16(tempDir, `pr-${pull_number}-${headShort}.diff`);
190509
190578
  writeFileSync10(diffPath, formatResult.content);
190510
190579
  log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
190511
190580
  primary.diffCoverage = createDiffCoverageState({
@@ -190545,7 +190614,8 @@ ${diffPreview}`);
190545
190614
  files: formatResult.files,
190546
190615
  pullNumber: pull_number,
190547
190616
  baseSha: prResponse.data.base.sha,
190548
- headSha: checkoutSha
190617
+ headSha: checkoutSha,
190618
+ signal
190549
190619
  });
190550
190620
  impactPath = impact.path;
190551
190621
  log.info(
@@ -190651,7 +190721,10 @@ ${dirty}`
190651
190721
  );
190652
190722
  }
190653
190723
  }
190654
- const promise2 = withCheckoutDeadline(runCheckout(pull_number), pull_number);
190724
+ const promise2 = withCheckoutDeadline(
190725
+ (signal) => runCheckout(pull_number, signal),
190726
+ pull_number
190727
+ );
190655
190728
  inFlightCheckouts.set(pull_number, promise2);
190656
190729
  try {
190657
190730
  return await promise2;
@@ -190664,7 +190737,7 @@ ${dirty}`
190664
190737
 
190665
190738
  // mcp/checkSuite.ts
190666
190739
  import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync11 } from "node:fs";
190667
- import { join as join16 } from "node:path";
190740
+ import { join as join17 } from "node:path";
190668
190741
  var GetCheckSuiteLogs = type({
190669
190742
  check_suite_id: type.number.describe("the id from check_suite.id")
190670
190743
  });
@@ -190760,7 +190833,7 @@ function GetCheckSuiteLogsTool(ctx) {
190760
190833
  if (!tempDir) {
190761
190834
  throw new Error("PULLFROG_TEMP_DIR not set");
190762
190835
  }
190763
- const logsDir = join16(tempDir, "ci-logs");
190836
+ const logsDir = join17(tempDir, "ci-logs");
190764
190837
  mkdirSync8(logsDir, { recursive: true });
190765
190838
  const jobResults = [];
190766
190839
  for (const run of failedRuns) {
@@ -190787,7 +190860,7 @@ function GetCheckSuiteLogsTool(ctx) {
190787
190860
  );
190788
190861
  }
190789
190862
  const logsText = await logsResult.text();
190790
- const logPath = join16(logsDir, `job-${job.id}.log`);
190863
+ const logPath = join17(logsDir, `job-${job.id}.log`);
190791
190864
  writeFileSync11(logPath, logsText);
190792
190865
  const analysis = analyzeLog(logsText, 80);
190793
190866
  const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
@@ -190837,7 +190910,7 @@ function GetCheckSuiteLogsTool(ctx) {
190837
190910
 
190838
190911
  // mcp/commitInfo.ts
190839
190912
  import { writeFileSync as writeFileSync12 } from "node:fs";
190840
- import { join as join17 } from "node:path";
190913
+ import { join as join18 } from "node:path";
190841
190914
  var CommitInfo = type({
190842
190915
  sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
190843
190916
  });
@@ -190861,7 +190934,7 @@ function CommitInfoTool(ctx) {
190861
190934
  "PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
190862
190935
  );
190863
190936
  }
190864
- const diffFile = join17(tempDir, `commit-${sha.slice(0, 7)}.diff`);
190937
+ const diffFile = join18(tempDir, `commit-${sha.slice(0, 7)}.diff`);
190865
190938
  writeFileSync12(diffFile, formatResult.content);
190866
190939
  log.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
190867
190940
  return {
@@ -190998,14 +191071,14 @@ function ReopenCurrentTool(ctx) {
190998
191071
  import { performance as performance8 } from "node:perf_hooks";
190999
191072
 
191000
191073
  // prep/installNodeDependencies.ts
191001
- import { existsSync as existsSync8 } from "node:fs";
191002
- import { join as join19 } from "node:path";
191074
+ import { existsSync as existsSync9 } from "node:fs";
191075
+ import { join as join20 } from "node:path";
191003
191076
 
191004
191077
  // utils/packageManager.ts
191005
191078
  var import_semver2 = __toESM(require_semver2(), 1);
191006
- import { existsSync as existsSync7 } from "node:fs";
191079
+ import { existsSync as existsSync8 } from "node:fs";
191007
191080
  import { mkdir, readFile as readFile2 } from "node:fs/promises";
191008
- import { delimiter, join as join18 } from "node:path";
191081
+ import { delimiter, join as join19 } from "node:path";
191009
191082
  var SUPPORTED_NAMES = ["npm", "pnpm", "yarn", "bun"];
191010
191083
  var COREPACK_MANAGED = ["pnpm", "yarn"];
191011
191084
  function isSupported(name) {
@@ -191043,8 +191116,8 @@ function parseDevEnginesField(field) {
191043
191116
  };
191044
191117
  }
191045
191118
  async function resolvePackageManagerSpec(cwd) {
191046
- const pkgPath = join18(cwd, "package.json");
191047
- if (!existsSync7(pkgPath)) return null;
191119
+ const pkgPath = join19(cwd, "package.json");
191120
+ if (!existsSync8(pkgPath)) return null;
191048
191121
  let pkg;
191049
191122
  try {
191050
191123
  pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
@@ -191102,7 +191175,7 @@ async function currentVersion(name) {
191102
191175
  return result.stdout.trim();
191103
191176
  }
191104
191177
  function packageManagerBinDir(tmpdir3) {
191105
- return join18(tmpdir3, "pm-bin");
191178
+ return join19(tmpdir3, "pm-bin");
191106
191179
  }
191107
191180
  async function ensurePackageManager(params) {
191108
191181
  const spec = params.spec;
@@ -191165,7 +191238,7 @@ async function installFallback(name, installSpec) {
191165
191238
  });
191166
191239
  if (result.exitCode !== 0) return result.stderr || `failed to install ${name}`;
191167
191240
  if (name === "deno") {
191168
- const denoPath = join18(process.env.HOME || "", ".deno", "bin");
191241
+ const denoPath = join19(process.env.HOME || "", ".deno", "bin");
191169
191242
  process.env.PATH = `${denoPath}:${process.env.PATH}`;
191170
191243
  }
191171
191244
  log.info(`\xBB installed ${name}`);
@@ -191198,8 +191271,8 @@ async function isCommandAvailable2(command) {
191198
191271
  var installNodeDependencies = {
191199
191272
  name: "installNodeDependencies",
191200
191273
  shouldRun: () => {
191201
- const packageJsonPath = join19(process.cwd(), "package.json");
191202
- return existsSync8(packageJsonPath);
191274
+ const packageJsonPath = join20(process.cwd(), "package.json");
191275
+ return existsSync9(packageJsonPath);
191203
191276
  },
191204
191277
  run: async (options) => {
191205
191278
  const declared = await resolvePackageManagerSpec(process.cwd());
@@ -191293,14 +191366,14 @@ ${errorMessage}`]
191293
191366
  };
191294
191367
 
191295
191368
  // prep/installPythonDependencies.ts
191296
- import { existsSync as existsSync9, readFileSync as readFileSync5 } from "node:fs";
191297
- import { join as join20 } from "node:path";
191369
+ import { existsSync as existsSync10, readFileSync as readFileSync5 } from "node:fs";
191370
+ import { join as join21 } from "node:path";
191298
191371
  function declaresBuildSystem(path4) {
191299
191372
  return /^\s*\[\s*build-system\s*\]/m.test(readFileSync5(path4, "utf8"));
191300
191373
  }
191301
191374
  function configApplies(config3, cwd) {
191302
- const path4 = join20(cwd, config3.file);
191303
- return existsSync9(path4) && (config3.applies?.(path4) ?? true);
191375
+ const path4 = join21(cwd, config3.file);
191376
+ return existsSync10(path4) && (config3.applies?.(path4) ?? true);
191304
191377
  }
191305
191378
  var PYTHON_CONFIGS = [
191306
191379
  {
@@ -191622,7 +191695,7 @@ function AwaitDependencyInstallationTool(ctx) {
191622
191695
 
191623
191696
  // mcp/gh.ts
191624
191697
  import { mkdirSync as mkdirSync9 } from "node:fs";
191625
- import { join as join21 } from "node:path";
191698
+ import { join as join22 } from "node:path";
191626
191699
  var GH_TIMEOUT_MS = 12e4;
191627
191700
  var GhParams = type({
191628
191701
  args: type.string.array().describe(
@@ -191644,7 +191717,7 @@ function GhTool(ctx) {
191644
191717
  description: `Run the GitHub CLI against ${ctx.repo.owner}/${ctx.repo.name}. Pass argv as an array \u2014 there is no shell, so pipes, redirects and \`&&\` do not work; use gh's own \`--json\`/\`--jq\` flags instead. \`gh api graphql\` is available. Authenticated with ${permissions ? formatPermissions(permissions) : "no permissions"} on this repo only; anything else 403s. Pushing, merging and ref writes are not available \u2014 use the git tools.`,
191645
191718
  parameters: GhParams,
191646
191719
  execute: execute(async (params) => {
191647
- const configDir = join21(ctx.tmpdir, "gh-config");
191720
+ const configDir = join22(ctx.tmpdir, "gh-config");
191648
191721
  mkdirSync9(configDir, { recursive: true });
191649
191722
  const command = ["gh", ...params.args.map(shellQuote)].join(" ");
191650
191723
  const result = await runSandboxed({
@@ -192368,7 +192441,7 @@ function PullRequestInfoTool(ctx) {
192368
192441
 
192369
192442
  // mcp/reviewComments.ts
192370
192443
  import { writeFileSync as writeFileSync13 } from "node:fs";
192371
- import { join as join22 } from "node:path";
192444
+ import { join as join23 } from "node:path";
192372
192445
  var COMMENTS_PER_THREAD = 50;
192373
192446
  var REVIEW_THREADS_QUERY = `
192374
192447
  query ($owner: String!, $name: String!, $prNumber: Int!, $cursor: String) {
@@ -192664,8 +192737,8 @@ function buildThreadBlocks(threads, filePatchMap, reviewId) {
192664
192737
  }
192665
192738
  return threadBlocks;
192666
192739
  }
192667
- var fetchAllReviewThreads = op(
192668
- async (key, ctx) => {
192740
+ var fetchAllReviewThreads = query({
192741
+ run: async (key, ctx) => {
192669
192742
  const threads = [];
192670
192743
  let cursor = null;
192671
192744
  for (let page = 0; page < 50; page += 1) {
@@ -192682,21 +192755,21 @@ var fetchAllReviewThreads = op(
192682
192755
  }
192683
192756
  return threads;
192684
192757
  },
192685
- { ttl: 6e4, name: "reviewThreads" }
192686
- );
192687
- var fetchPrFiles = op(
192688
- async (key, ctx) => ctx.octokit.paginate(ctx.octokit.rest.pulls.listFiles, {
192758
+ ttl: 6e4,
192759
+ name: "reviewThreads"
192760
+ });
192761
+ var fetchPrFiles = query({
192762
+ run: async (key, ctx) => ctx.octokit.paginate(ctx.octokit.rest.pulls.listFiles, {
192689
192763
  owner: key.owner,
192690
192764
  repo: key.name,
192691
192765
  pull_number: key.pullNumber,
192692
192766
  per_page: 100
192693
192767
  }),
192694
- { ttl: 6e4, name: "prFiles" }
192695
- );
192768
+ ttl: 6e4,
192769
+ name: "prFiles"
192770
+ });
192696
192771
  function invalidateReviewThreadCache(repo) {
192697
- fetchAllReviewThreads.invalidate(
192698
- (cached4) => cached4.owner === repo.owner && cached4.name === repo.name
192699
- );
192772
+ fetchAllReviewThreads.invalidate({ owner: repo.owner, name: repo.name });
192700
192773
  }
192701
192774
  async function getReviewThreads(input) {
192702
192775
  const allThreads = structuredClone(
@@ -192862,7 +192935,7 @@ function GetReviewCommentsTool(ctx) {
192862
192935
  throw new Error("PULLFROG_TEMP_DIR not set");
192863
192936
  }
192864
192937
  const filename = `review-${params.review_id}-threads.md`;
192865
- const commentsPath = join22(tempDir, filename);
192938
+ const commentsPath = join23(tempDir, filename);
192866
192939
  writeFileSync13(commentsPath, formatted.content);
192867
192940
  log.debug(`wrote ${threadBlocks.length} threads to ${commentsPath}`);
192868
192941
  return {
@@ -193232,7 +193305,7 @@ function UploadFileTool(ctx) {
193232
193305
 
193233
193306
  // mcp/xrepo.ts
193234
193307
  import { mkdirSync as mkdirSync10, rmSync as rmSync3 } from "node:fs";
193235
- import { join as join23 } from "node:path";
193308
+ import { join as join24 } from "node:path";
193236
193309
  function assertValidRepoName(name) {
193237
193310
  if (!/^[A-Za-z0-9._-]+$/.test(name) || name.startsWith("-") || name.includes("..")) {
193238
193311
  throw new Error(
@@ -193312,7 +193385,7 @@ function CheckoutRepoTool(ctx) {
193312
193385
  return { path: existing.dir, access: existing.access, note: "already checked out." };
193313
193386
  }
193314
193387
  const access = accessFor(ctx, repo);
193315
- const dir = join23(ctx.tmpdir, "xrepo", repo);
193388
+ const dir = join24(ctx.tmpdir, "xrepo", repo);
193316
193389
  mkdirSync10(dir, { recursive: true });
193317
193390
  const state = ensureRepoState(ctx.toolState, { owner, name: repo, dir, access });
193318
193391
  const rc = resolveRepoCtx(ctx, repo);
@@ -193423,7 +193496,7 @@ function buildCommonTools(ctx, outputSchema) {
193423
193496
  if (isStandalone || outputSchema) {
193424
193497
  tools.push(SetOutputTool(ctx, outputSchema));
193425
193498
  }
193426
- if (ctx.payload.shell === "restricted") {
193499
+ if (ctx.payload.shell !== "disabled") {
193427
193500
  tools.push(ShellTool(ctx));
193428
193501
  tools.push(KillBackgroundTool(ctx));
193429
193502
  }
@@ -193678,6 +193751,7 @@ function isByokSetupError(text) {
193678
193751
  var SECRETS_UNAVAILABLE_MARKER = "couldn't load your Pullfrog secrets";
193679
193752
  var ROUTER_UNFUNDED_MARKER = "your Pullfrog Router balance is empty";
193680
193753
  var CREDENTIAL_REJECTED_MARKER = "was rejected by its provider";
193754
+ var CREDENTIAL_POOL_REFUSED_MARKER = "No configured credential can serve this run";
193681
193755
  var NoUsableCredentialError = class extends Error {
193682
193756
  };
193683
193757
  function throwKeyError(params) {
@@ -193944,7 +194018,8 @@ function isOAuthCredentialExpiredError(text) {
193944
194018
  text
193945
194019
  ) || // the provider no longer recognises the token at all (#1086) — same dead
193946
194020
  // credential, phrased as a lookup miss rather than a state transition.
193947
- /Could not find the appropriate key in your authentication token/i.test(text) || /Token refresh failed/i.test(text)
194021
+ /Could not find the appropriate key in your authentication token/i.test(text) || // the reversed word order: `Encountered invalidated oauth token for user` (#1474).
194022
+ /(?:expired|invalidated|revoked) (?:OAuth|authentication) token/i.test(text) || /Token refresh failed/i.test(text)
193948
194023
  );
193949
194024
  }
193950
194025
  function isMalformedKeyError(text) {
@@ -193991,7 +194066,7 @@ function buildRejectedCredentialError(params) {
193991
194066
  ].join("\n");
193992
194067
  }
193993
194068
  const where = params.inPullfrogStore ? `[Update it in Pullfrog \u2192](${settingsUrl})` : `[Update the GitHub Actions secret \u2192](https://github.com/${params.owner}/${params.name}/settings/secrets/actions)`;
193994
- const keysUrl = providerKeysUrl(params.credential);
194069
+ const keysUrl = providerDashboard(params.credential)?.keys;
193995
194070
  const issue3 = keysUrl ? `[Issue a new key \u2192](${keysUrl}) \xB7 ` : "";
193996
194071
  return [
193997
194072
  `**Your \`${params.credential}\` ${CREDENTIAL_REJECTED_MARKER}**${detail}, so the agent never ran.`,
@@ -194001,12 +194076,43 @@ function buildRejectedCredentialError(params) {
194001
194076
  `${issue3}${where} \xB7 [Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/keys) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
194002
194077
  ].join("\n");
194003
194078
  }
194004
- function providerKeysUrl(credential) {
194079
+ function providerDashboard(credential) {
194005
194080
  for (const [id, provider2] of Object.entries(providers)) {
194006
- if (provider2.envVars.includes(credential)) return PROVIDER_DASHBOARDS[id]?.keys;
194081
+ if (provider2.envVars.includes(credential)) return PROVIDER_DASHBOARDS[id];
194007
194082
  }
194008
194083
  return void 0;
194009
194084
  }
194085
+ function buildCredentialPoolRefusedError(params) {
194086
+ const settingsUrl = `${getApiUrl()}/console/${params.owner}/${params.name}`;
194087
+ return [
194088
+ `**${CREDENTIAL_POOL_REFUSED_MARKER}.** Every credential configured for \`${params.model}\` was rejected or is out of quota, so the agent never ran.`,
194089
+ "",
194090
+ ...params.refused.map(
194091
+ (credential) => `- ${describeRefusal({ ...params, credential, settingsUrl })}`
194092
+ ),
194093
+ "",
194094
+ `[Model settings \u2192](${settingsUrl}) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
194095
+ ].join("\n");
194096
+ }
194097
+ function describeRefusal(params) {
194098
+ const credential = params.credential;
194099
+ const verdict = credential.verdict;
194100
+ const workflow = credential.source === "workflow";
194101
+ const head = `\`${credential.name}\` from ${workflow ? "the workflow" : `${credential.source} scope`}: \`${verdict.detail}\` \u2014`;
194102
+ const subscription = SUBSCRIPTION_CREDENTIALS[credential.name];
194103
+ const dashboard = providerDashboard(credential.name);
194104
+ if (verdict.status === "exhausted" && subscription) {
194105
+ const wait = verdict.resetAt ? `it resets at **${verdict.resetAt.toISOString().slice(0, 16).replace("T", " ")} UTC**; re-trigger after that` : "re-trigger after the limit resets";
194106
+ return `${head} ${wait}, or add an API key for this provider, which Pullfrog falls back to automatically.`;
194107
+ }
194108
+ if (verdict.status === "exhausted")
194109
+ return `${head} add credit or raise its quota with the provider.${dashboard ? ` [Top up \u2192](${dashboard.billing})` : ""}`;
194110
+ if (subscription)
194111
+ return `${head} re-authenticate with \`${subscription.command}\`, or switch this repo to a model you hold an API key for. [Re-authenticate \u2192](${subscription.docs})`;
194112
+ const where = workflow ? `[Update the GitHub Actions secret \u2192](https://github.com/${params.owner}/${params.name}/settings/secrets/actions)` : `[Update it in Pullfrog \u2192](${params.settingsUrl})`;
194113
+ const issue3 = dashboard?.keys ? `[Issue a new key \u2192](${dashboard.keys}) \xB7 ` : "";
194114
+ return `${head} issue a new key and update the copy Pullfrog uses. ${issue3}${where}`;
194115
+ }
194010
194116
  function formatApiKeyErrorSummary(params) {
194011
194117
  if (params.raw.includes(MISSING_KEY_MARKER)) {
194012
194118
  if (params.raw.startsWith(`**${MISSING_KEY_MARKER}**`)) return params.raw;
@@ -194255,11 +194361,11 @@ function formatBillingErrorSummary(error49, owner) {
194255
194361
  }
194256
194362
  if (error49.code === "router_monthly_limit") {
194257
194363
  return [
194258
- "**Pullfrog Router hit its monthly spend limit.**",
194364
+ "**Pullfrog Router is out of credit, and the monthly spend limit blocks the next auto-reload.**",
194259
194365
  "",
194260
- "Auto-reloads are paused for the rest of this UTC month. Ask your admin to raise the cap, or wait for it to reset at 00:00 UTC on the 1st.",
194366
+ "Top up by hand, ask your admin to raise the cap, or wait for it to reset at 00:00 UTC on the 1st.",
194261
194367
  "",
194262
- `[Adjust limit \u2192](${billingConsoleUrl(owner)})`
194368
+ `[Top up balance \u2192](${billingConsoleUrl(owner)}) \xB7 [Adjust limit \u2192](${billingConsoleUrl(owner)})`
194263
194369
  ].join("\n");
194264
194370
  }
194265
194371
  if (error49.code === "commercial_plan_required") {
@@ -194546,7 +194652,13 @@ var codexUsageSchema = external_exports.object({
194546
194652
  limit_reached: external_exports.boolean(),
194547
194653
  primary_window: codexWindowSchema,
194548
194654
  secondary_window: codexWindowSchema
194549
- }).optional()
194655
+ }).optional(),
194656
+ credits: external_exports.object({
194657
+ has_credits: external_exports.boolean().optional(),
194658
+ unlimited: external_exports.boolean().optional(),
194659
+ overage_limit_reached: external_exports.boolean().optional()
194660
+ }).nullish(),
194661
+ spend_control: external_exports.object({ reached: external_exports.boolean().optional() }).nullish()
194550
194662
  });
194551
194663
  var unknown4 = { status: "unknown" };
194552
194664
  async function readSubscriptionEndpoint(input) {
@@ -194589,9 +194701,12 @@ async function probeSubscription(input) {
194589
194701
  const body = await response.json().catch(() => null);
194590
194702
  if (!codex2) return unknown4;
194591
194703
  const parsed2 = codexUsageSchema.safeParse(body);
194592
- const limit = parsed2.success ? parsed2.data.rate_limit : void 0;
194593
- if (!limit) return unknown4;
194704
+ if (!parsed2.success || !parsed2.data.rate_limit) return unknown4;
194705
+ const limit = parsed2.data.rate_limit;
194594
194706
  if (limit.allowed && !limit.limit_reached) return { status: "usable" };
194707
+ const credits = parsed2.data.credits;
194708
+ if ((credits?.has_credits || credits?.unlimited) && !credits.overage_limit_reached && !parsed2.data.spend_control?.reached)
194709
+ return { status: "usable" };
194595
194710
  const windows = [limit.primary_window, limit.secondary_window];
194596
194711
  const full = windows.filter((window2) => (window2?.used_percent ?? 0) >= 100);
194597
194712
  const resets = (full.length ? full : windows).flatMap(
@@ -194639,15 +194754,24 @@ async function probeInference(input) {
194639
194754
  const error49 = external_exports.object({ error: external_exports.object({ code: external_exports.string() }) }).safeParse(await response.json().catch(() => null));
194640
194755
  return error49.success && error49.data.error.code === "insufficient_quota" ? { status: "exhausted", detail: "refused with 429: insufficient quota", resetAt: void 0 } : unknown4;
194641
194756
  }
194757
+ if (response?.status === 401 || response?.status === 403)
194758
+ return {
194759
+ status: "rejected",
194760
+ detail: `rejected with ${response.status}${await providerReason(response)}`
194761
+ };
194642
194762
  await response?.body?.cancel();
194643
194763
  if (!response) return unknown4;
194644
194764
  if (response.ok) return { status: "usable" };
194645
- if (response.status === 401 || response.status === 403)
194646
- return { status: "rejected", detail: `rejected with ${response.status}` };
194647
194765
  if (response.status === 402)
194648
194766
  return { status: "exhausted", detail: "refused with 402", resetAt: void 0 };
194649
194767
  return unknown4;
194650
194768
  }
194769
+ async function providerReason(response) {
194770
+ const body = external_exports.object({ error: external_exports.union([external_exports.string(), external_exports.object({ message: external_exports.string() })]) }).safeParse(await response.json().catch(() => null));
194771
+ if (!body.success) return "";
194772
+ const error49 = body.data.error;
194773
+ return `: ${(typeof error49 === "string" ? error49 : error49.message).slice(0, 300)}`;
194774
+ }
194651
194775
 
194652
194776
  // utils/credentialPool.ts
194653
194777
  var receipts = {};
@@ -194656,8 +194780,8 @@ function saveReceipts() {
194656
194780
  for (const receipt of Object.values(receipts)) maskSecret(receipt);
194657
194781
  saveSecretState("credential_receipts", JSON.stringify(receipts));
194658
194782
  }
194659
- var select = op(
194660
- async (input) => {
194783
+ var select = mutation({
194784
+ run: async (input) => {
194661
194785
  const response = await apiFetch({
194662
194786
  path: "/api/runtime/credentials",
194663
194787
  method: "POST",
@@ -194675,11 +194799,8 @@ var select = op(
194675
194799
  });
194676
194800
  return selectedCredentialSchema.parse(await response.json());
194677
194801
  },
194678
- {
194679
- retries: [250, 1e3],
194680
- rethrow: (error49) => error49 instanceof Error && typeof error49.cause === "number" && error49.cause >= 400 && error49.cause < 500
194681
- }
194682
- );
194802
+ retry: (error49, attempt) => error49 instanceof Error && typeof error49.cause === "number" && error49.cause >= 400 && error49.cause < 500 ? -1 : delay2([250, 1e3], attempt)
194803
+ });
194683
194804
  function subscriptionForModel(model) {
194684
194805
  if (!model.includes("/")) return null;
194685
194806
  const provider2 = getModelProvider(model);
@@ -194742,12 +194863,14 @@ async function selectConfiguredCredential(input) {
194742
194863
  const subscription = subscriptionForModel(model);
194743
194864
  const names = getModelEnvVars(model).filter((name) => name !== subscription);
194744
194865
  if (subscription) names.unshift(subscription);
194866
+ const opencodePinned = process.env.PULLFROG_AGENT?.trim() === "opencode";
194867
+ const presentable = (name) => canInstallSubscription(name) && !(opencodePinned && CLAUDE_CODE_ONLY_CREDENTIALS.includes(name));
194745
194868
  const candidates = access.candidates.filter(
194746
- (candidate) => names.includes(candidate.name) && canInstallSubscription(candidate.name)
194869
+ (candidate) => names.includes(candidate.name) && presentable(candidate.name)
194747
194870
  );
194748
194871
  if (!candidates.length) return false;
194749
194872
  const options = [
194750
- ...names.filter((name) => workflowCredentials[name] && canInstallSubscription(name)).map((name) => ({ name, candidate: void 0 })),
194873
+ ...names.filter((name) => workflowCredentials[name] && presentable(name)).map((name) => ({ name, candidate: void 0 })),
194751
194874
  ...candidates.map((candidate) => ({ name: candidate.name, candidate }))
194752
194875
  ].sort((a, b) => Number(b.name === subscription) - Number(a.name === subscription));
194753
194876
  const refused = [];
@@ -194762,11 +194885,16 @@ async function selectConfiguredCredential(input) {
194762
194885
  if (option.candidate) log.info(`\xBB selected ${option.name} from ${from}`);
194763
194886
  return true;
194764
194887
  }
194765
- refused.push(`${option.name} from ${from}: ${verdict.detail}`);
194888
+ refused.push({ name: option.name, source: option.candidate?.source ?? "workflow", verdict });
194766
194889
  log.info(`\xBB ${option.name} from ${from} unavailable; trying the next credential`);
194767
194890
  }
194768
194891
  throw new Error(
194769
- `all configured credentials for ${model} were rejected or exhausted (${refused.join("; ")}); no other model or provider was selected`
194892
+ buildCredentialPoolRefusedError({
194893
+ model,
194894
+ refused,
194895
+ owner: input.ctx.repo.owner,
194896
+ name: input.ctx.repo.name
194897
+ })
194770
194898
  );
194771
194899
  }
194772
194900
  async function probe(input) {
@@ -194804,10 +194932,10 @@ function activate(input) {
194804
194932
  }
194805
194933
 
194806
194934
  // utils/gitAuthServer.ts
194807
- import { randomUUID as randomUUID5 } from "node:crypto";
194935
+ import { randomUUID as randomUUID6 } from "node:crypto";
194808
194936
  import { writeFileSync as writeFileSync14 } from "node:fs";
194809
194937
  import { createServer as createServer3 } from "node:http";
194810
- import { join as join24 } from "node:path";
194938
+ import { join as join25 } from "node:path";
194811
194939
  var REVOKED_TRAP_MS = 6e4;
194812
194940
  function revokeGitHubToken(token) {
194813
194941
  fetch("https://api.github.com/installation/token", {
@@ -194862,7 +194990,7 @@ async function startGitAuthServer(tmpdir3) {
194862
194990
  const port = rawAddr.port;
194863
194991
  log.debug(`git auth server listening on 127.0.0.1:${port}`);
194864
194992
  function register7(token) {
194865
- const code = randomUUID5();
194993
+ const code = randomUUID6();
194866
194994
  codes.set(code, { token, state: "active" });
194867
194995
  return code;
194868
194996
  }
@@ -194874,9 +195002,9 @@ async function startGitAuthServer(tmpdir3) {
194874
195002
  entry.timeout.unref();
194875
195003
  }
194876
195004
  function writeAskpassScript(code) {
194877
- const scriptId = randomUUID5();
195005
+ const scriptId = randomUUID6();
194878
195006
  const scriptName = `askpass-${scriptId}.js`;
194879
- const scriptPath = join24(tmpdir3, scriptName);
195007
+ const scriptPath = join25(tmpdir3, scriptName);
194880
195008
  const content = [
194881
195009
  `#!/usr/bin/env node`,
194882
195010
  `var a=process.argv[2]||"";`,
@@ -194965,13 +195093,10 @@ function getShellInstructions(shell, t) {
194965
195093
 
194966
195094
  Shell command execution is DISABLED. Do not attempt to run shell commands.`;
194967
195095
  case "restricted":
194968
- return `### Shell commands
194969
-
194970
- Use the \`${t("shell")}\` MCP tool for all shell command execution. This tool provides a secure environment with filtered credentials. Do NOT use any native shell tool \u2014 it is disabled for security. For long-running processes (dev servers, watchers), use \`shell({ command, background: true })\`. Use \`${t("kill_background")}\` to stop background processes.`;
194971
195096
  case "enabled":
194972
195097
  return `### Shell commands
194973
195098
 
194974
- Use your native shell tool for shell command execution.`;
195099
+ Use the \`${t("shell")}\` MCP tool for all shell command execution.${shell === "restricted" ? " This tool provides a secure environment with filtered credentials. Do NOT use any native shell tool \u2014 it is disabled for security." : ""} For long-running processes (dev servers, watchers), use \`shell({ command, background: true })\`. Use \`${t("kill_background")}\` to stop background processes.`;
194975
195100
  default: {
194976
195101
  const _exhaustive = shell;
194977
195102
  return _exhaustive;
@@ -195328,7 +195453,7 @@ function resolveInstructions(ctx) {
195328
195453
 
195329
195454
  // utils/learnings.ts
195330
195455
  import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
195331
- import { dirname as dirname4, join as join25 } from "node:path";
195456
+ import { dirname as dirname4, join as join26 } from "node:path";
195332
195457
 
195333
195458
  // utils/learningsTruncate.ts
195334
195459
  var MAX_LEARNINGS_LENGTH = 1e5;
@@ -195346,10 +195471,10 @@ function truncateAtLineBoundary(body, cap) {
195346
195471
  var LEARNINGS_FILE_NAME = "pullfrog-learnings.md";
195347
195472
  var XREPO_LEARNINGS_FILE_NAME = "pullfrog-xrepo-learnings.md";
195348
195473
  function learningsFilePath(tmpdir3) {
195349
- return join25(tmpdir3, LEARNINGS_FILE_NAME);
195474
+ return join26(tmpdir3, LEARNINGS_FILE_NAME);
195350
195475
  }
195351
195476
  function xrepoLearningsFilePath(tmpdir3) {
195352
- return join25(tmpdir3, XREPO_LEARNINGS_FILE_NAME);
195477
+ return join26(tmpdir3, XREPO_LEARNINGS_FILE_NAME);
195353
195478
  }
195354
195479
  async function seedLearningsFile(params) {
195355
195480
  const path4 = learningsFilePath(params.tmpdir);
@@ -195900,7 +196025,9 @@ async function resolveTokens(params) {
195900
196025
  // commit-status check-runs for branch protection. the app already grants
195901
196026
  // checks:write; this scopes the MCP token up to use it.
195902
196027
  checks: "write",
195903
- actions: "read"
196028
+ // write (not read) so postReviewCleanup can dispatch the follow-up re-review
196029
+ // for commits pushed while a review was in flight; read 403s it.
196030
+ actions: "write"
195904
196031
  };
195905
196032
  const mcpToken = await acquireNewToken({
195906
196033
  repos: writeRepos,
@@ -196116,8 +196243,8 @@ async function mintProxyKey(ctx) {
196116
196243
  try {
196117
196244
  const headers = await buildProxyTokenHeaders(ctx);
196118
196245
  if (!headers) return null;
196119
- const response = await op(
196120
- async () => {
196246
+ const response = await mutation({
196247
+ run: async () => {
196121
196248
  const r = await apiFetch({ path: "/api/proxy-token", method: "POST", headers });
196122
196249
  if (r.status >= 500) {
196123
196250
  const body = await r.json().catch(() => null);
@@ -196127,12 +196254,9 @@ async function mintProxyKey(ctx) {
196127
196254
  }
196128
196255
  return r;
196129
196256
  },
196130
- {
196131
- name: "proxy key mint",
196132
- retries: [1e3, 2e3],
196133
- rethrow: (error49) => !(error49 instanceof TransientError)
196134
- }
196135
- )();
196257
+ name: "proxy key mint",
196258
+ retry: (error49, attempt) => error49 instanceof TransientError ? delay2([1e3, 2e3], attempt) : -1
196259
+ })();
196136
196260
  if (response.status === 402) {
196137
196261
  const body = await response.json().catch(() => null);
196138
196262
  throw new BillingError(body?.error ?? "insufficient balance", {
@@ -196165,10 +196289,10 @@ async function buildProxyTokenHeaders(ctx) {
196165
196289
  const fundingSource = ctx.fundingSource;
196166
196290
  if (ctx.oidcCredentials) {
196167
196291
  const creds = ctx.oidcCredentials;
196168
- const oidcToken = await op(() => fetchIdTokenFromStash(creds), {
196292
+ const oidcToken = await query({
196293
+ run: () => fetchIdTokenFromStash(creds),
196169
196294
  name: "ID token mint",
196170
- retries: [1e3, 2e3],
196171
- rethrow: (error49) => !isTransientTokenError(error49)
196295
+ retry: (error49, attempt) => isTransientTokenError(error49) ? delay2([1e3, 2e3], attempt) : -1
196172
196296
  })();
196173
196297
  return {
196174
196298
  Authorization: `Bearer ${oidcToken}`,
@@ -196289,7 +196413,7 @@ async function resolveTrialFallback(ctx) {
196289
196413
 
196290
196414
  // utils/prSummary.ts
196291
196415
  import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
196292
- import { dirname as dirname5, join as join26 } from "node:path";
196416
+ import { dirname as dirname5, join as join27 } from "node:path";
196293
196417
  var SUMMARY_FILE_NAME = "pullfrog-summary.md";
196294
196418
  var SUMMARY_SCAFFOLD = `# PR summary
196295
196419
 
@@ -196299,7 +196423,7 @@ var SUMMARY_SCAFFOLD = `# PR summary
196299
196423
  var MIN_SNAPSHOT_LENGTH = 60;
196300
196424
  var MAX_SNAPSHOT_LENGTH = 32768;
196301
196425
  function summaryFilePath(tmpdir3) {
196302
- return join26(tmpdir3, SUMMARY_FILE_NAME);
196426
+ return join27(tmpdir3, SUMMARY_FILE_NAME);
196303
196427
  }
196304
196428
  async function seedSummaryFile(params) {
196305
196429
  const path4 = summaryFilePath(params.tmpdir);
@@ -196415,109 +196539,6 @@ ${collapsible}` : output;
196415
196539
  };
196416
196540
  }
196417
196541
 
196418
- // utils/runContext.ts
196419
- var defaultSettings = {
196420
- model: null,
196421
- effort: null,
196422
- modes: [],
196423
- setupScript: null,
196424
- postCheckoutScript: null,
196425
- prepushScript: null,
196426
- stopScript: null,
196427
- push: "restricted",
196428
- shell: "restricted",
196429
- prApproveEnabled: false,
196430
- reviewDrafts: false,
196431
- autoMergeEnabled: false,
196432
- codexAgent: false,
196433
- signedCommits: false,
196434
- repoIntelligence: false,
196435
- progressComments: true,
196436
- statusChecks: true,
196437
- approvalCheck: false,
196438
- modeInstructions: {},
196439
- learnings: null,
196440
- learningsHeadings: [],
196441
- envAllowlist: null,
196442
- xrepoBrief: null,
196443
- xrepoLearnings: null,
196444
- xrepoLearningsHeadings: []
196445
- };
196446
- var defaultRunContext = {
196447
- settings: defaultSettings,
196448
- apiToken: "",
196449
- oss: false,
196450
- plan: "none"
196451
- };
196452
- var unknownSecretsRunContext = {
196453
- ...defaultRunContext,
196454
- secretsUnavailable: true
196455
- };
196456
- async function fetchRunContext(params) {
196457
- const timeoutMs = 3e4;
196458
- const controller = new AbortController();
196459
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
196460
- try {
196461
- const headers = {
196462
- Authorization: `Bearer ${params.token}`,
196463
- "X-Pullfrog-Credential-Pools": "1"
196464
- };
196465
- if (params.oidcToken) {
196466
- headers["X-GitHub-OIDC-Token"] = params.oidcToken;
196467
- }
196468
- const query = new URLSearchParams();
196469
- if (params.runType) query.set("type", params.runType);
196470
- if (params.routedTier) query.set("tier", params.routedTier);
196471
- const search2 = query.toString();
196472
- const response = await apiFetch({
196473
- path: `/api/repo/${params.repoContext.owner}/${params.repoContext.name}/run-context${search2 ? `?${search2}` : ""}`,
196474
- headers,
196475
- signal: controller.signal
196476
- });
196477
- clearTimeout(timeoutId);
196478
- if (response.status === 402) {
196479
- const body = await response.json().catch(() => null);
196480
- const reason = typeof body === "object" && body !== null && "reason" in body ? body.reason : null;
196481
- const commercialRefused = reason === "subscription_unpaid" || reason === "subscription_ended" ? reason : "commercial";
196482
- return { ...defaultRunContext, commercialRefused };
196483
- }
196484
- if (!response.ok) {
196485
- return response.status >= 500 ? unknownSecretsRunContext : defaultRunContext;
196486
- }
196487
- const data = await response.json();
196488
- if (data === null) {
196489
- return defaultRunContext;
196490
- }
196491
- return {
196492
- settings: {
196493
- ...defaultSettings,
196494
- ...data.settings,
196495
- modes: data.settings?.modes ?? [],
196496
- setupScript: data.settings?.setupScript ?? null,
196497
- postCheckoutScript: data.settings?.postCheckoutScript ?? null,
196498
- prepushScript: data.settings?.prepushScript ?? null,
196499
- stopScript: data.settings?.stopScript ?? null,
196500
- learningsHeadings: data.settings?.learningsHeadings ?? [],
196501
- xrepoBrief: data.settings?.xrepoBrief ?? null,
196502
- xrepoLearnings: data.settings?.xrepoLearnings ?? null,
196503
- xrepoLearningsHeadings: data.settings?.xrepoLearningsHeadings ?? []
196504
- },
196505
- apiToken: data.apiToken,
196506
- oss: data.oss ?? false,
196507
- plan: data.plan ?? "none",
196508
- proxyModel: data.proxyModel,
196509
- dbSecrets: data.dbSecrets,
196510
- credentialAccess: data.credentialAccess,
196511
- secretsUnavailable: data.secretsUnavailable,
196512
- routerUnfunded: data.routerUnfunded,
196513
- trialFallback: data.trialFallback
196514
- };
196515
- } catch {
196516
- clearTimeout(timeoutId);
196517
- return unknownSecretsRunContext;
196518
- }
196519
- }
196520
-
196521
196542
  // utils/runContextData.ts
196522
196543
  async function resolveRunContextData(params) {
196523
196544
  const actionRef = process.env.GITHUB_ACTION_REF;
@@ -196527,23 +196548,25 @@ async function resolveRunContextData(params) {
196527
196548
  const repoContext = parseRepoContext();
196528
196549
  let oidcToken;
196529
196550
  try {
196530
- oidcToken = await op(() => mintIdToken(), {
196551
+ oidcToken = await query({
196552
+ run: () => mintIdToken(),
196531
196553
  name: "OIDC mint",
196532
- retries: process.env.ACTIONS_ID_TOKEN_REQUEST_URL ? [200, 1e3] : []
196554
+ retry: process.env.ACTIONS_ID_TOKEN_REQUEST_URL ? [200, 1e3] : []
196533
196555
  })();
196534
196556
  } catch {
196535
196557
  }
196536
196558
  const [repoResponse, runContext] = await Promise.all([
196537
- op(() => params.octokit.repos.get({ owner: repoContext.owner, repo: repoContext.name }), {
196559
+ query({
196560
+ run: () => params.octokit.repos.get({ owner: repoContext.owner, repo: repoContext.name }),
196538
196561
  name: "repos.get",
196539
- retries: [100, 500],
196540
- rethrow: (error49) => !isTransientOctokitError(error49)
196562
+ retry: (error49, attempt) => isTransientOctokitError(error49) ? delay2([100, 500], attempt) : -1
196541
196563
  })(),
196542
196564
  fetchRunContext({
196543
196565
  token: params.token,
196544
196566
  repoContext,
196545
196567
  oidcToken,
196546
196568
  runType: params.runType,
196569
+ plainPrompt: params.plainPrompt,
196547
196570
  routedTier: params.routedTier
196548
196571
  })
196549
196572
  ]);
@@ -196669,7 +196692,7 @@ function formatBillingExhaustedBody(diagnostic) {
196669
196692
 
196670
196693
  // utils/runErrorRenderer.ts
196671
196694
  function isProviderModelNotFoundError(message) {
196672
- return message.includes("ProviderModelNotFoundError");
196695
+ return /ProviderModelNotFoundError|\bModel not found: /.test(message);
196673
196696
  }
196674
196697
  function isContextOverflowError(message) {
196675
196698
  return /Prompt is too long/i.test(message) || /Input exceeds context window/i.test(message) || /maximum context length is \d+ tokens/i.test(message) || /Session too large to compact/i.test(message);
@@ -196766,6 +196789,7 @@ function detectProviderId(message) {
196766
196789
  if (/requires more credits|Key limit exceeded \(total limit\)|openrouter\.ai/i.test(message)) {
196767
196790
  return "openrouter";
196768
196791
  }
196792
+ if (/platform\.openai\.com/i.test(message)) return "openai";
196769
196793
  return null;
196770
196794
  }
196771
196795
  var OPENROUTER_PRIVACY_URL = "https://openrouter.ai/settings/privacy";
@@ -196863,12 +196887,38 @@ function formatByokSetupSummary(input) {
196863
196887
  ].join("\n");
196864
196888
  }
196865
196889
  function formatProviderModelNotFoundSummary(input) {
196866
- return `The configured model is no longer available in OpenCode's catalog. Pick a different model in the Pullfrog console for \`${input.owner}/${input.name}\`, or contact support if this persists.
196890
+ return `The configured model is not in OpenCode's catalog. Pick a different model for \`${input.owner}/${input.name}\` where it is set \u2014 the Pullfrog console, the workflow's \`model\` input, or the \`PULLFROG_MODEL\` variable \u2014 or contact support if this persists.
196867
196891
 
196868
196892
  \`\`\`
196869
196893
  ${input.raw}
196870
196894
  \`\`\``;
196871
196895
  }
196896
+ function formatProviderUsageLimit(input) {
196897
+ return [
196898
+ "**The provider's usage limit for this account has been reached.**",
196899
+ "",
196900
+ "A subscription window resets on its own, so re-trigger after it does; a limit set on a pay-as-you-go workspace has to be raised. A model on another provider can run in the meantime.",
196901
+ "",
196902
+ `[Model settings \u2192](${getApiUrl()}/console/${input.owner}/${input.name}) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`,
196903
+ "",
196904
+ `\`\`\`
196905
+ ${input.raw}
196906
+ \`\`\``
196907
+ ].join("\n");
196908
+ }
196909
+ function formatProviderErrorBody(input) {
196910
+ return [
196911
+ "**The model provider returned an error, so this run stopped.**",
196912
+ "",
196913
+ "The error is below. If it names your key, plan or balance, fix that with the provider or pick another model before re-triggering.",
196914
+ "",
196915
+ `[Model settings \u2192](${getApiUrl()}/console/${input.owner}/${input.name}) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`,
196916
+ "",
196917
+ `\`\`\`
196918
+ ${input.raw}
196919
+ \`\`\``
196920
+ ].join("\n");
196921
+ }
196872
196922
  function renderRunError(input) {
196873
196923
  const billingError = input.routerActive && isRouterKeylimitExhaustedError(input.errorMessage) ? new BillingError(input.errorMessage, { code: "router_keylimit_exhausted" }) : null;
196874
196924
  if (billingError) {
@@ -196884,7 +196934,7 @@ function renderRunError(input) {
196884
196934
  if (input.errorMessage.includes(ROUTER_UNFUNDED_MARKER)) {
196885
196935
  return { summary: input.errorMessage, comment: input.errorMessage };
196886
196936
  }
196887
- if (input.errorMessage.includes(CREDENTIAL_REJECTED_MARKER)) {
196937
+ if (input.errorMessage.includes(CREDENTIAL_REJECTED_MARKER) || input.errorMessage.includes(CREDENTIAL_POOL_REFUSED_MARKER)) {
196888
196938
  return { summary: input.errorMessage, comment: input.errorMessage };
196889
196939
  }
196890
196940
  const isHang = input.errorMessage.startsWith("activity timeout") || input.errorMessage.startsWith("agent still pending");
@@ -196907,6 +196957,16 @@ ${body}`, comment: body };
196907
196957
  });
196908
196958
  return { summary: `### \u274C Pullfrog failed
196909
196959
 
196960
+ ${body}`, comment: body };
196961
+ }
196962
+ if (isProviderUsageLimit(input.errorMessage)) {
196963
+ const body = formatProviderUsageLimit({
196964
+ owner: input.repo.owner,
196965
+ name: input.repo.name,
196966
+ raw: input.errorMessage
196967
+ });
196968
+ return { summary: `### \u274C Pullfrog failed
196969
+
196910
196970
  ${body}`, comment: body };
196911
196971
  }
196912
196972
  if (isProviderMissingCredential(input.errorMessage)) {
@@ -197001,6 +197061,16 @@ ${hangBody}`,
197001
197061
  comment: explained ? hangBody : formatMinimalFailureComment(input.repo)
197002
197062
  };
197003
197063
  }
197064
+ if (input.errorMessage.startsWith("provider error: ")) {
197065
+ const body = formatProviderErrorBody({
197066
+ owner: input.repo.owner,
197067
+ name: input.repo.name,
197068
+ raw: input.errorMessage
197069
+ });
197070
+ return { summary: `### \u274C Pullfrog failed
197071
+
197072
+ ${body}`, comment: body };
197073
+ }
197004
197074
  const genericBody = formatGenericFailure(input.errorMessage);
197005
197075
  return {
197006
197076
  summary: `### \u274C Pullfrog failed
@@ -197080,17 +197150,14 @@ async function autoMergeAfterApprove(ctx) {
197080
197150
  });
197081
197151
  if (hasBlockingHumanReview(reviews)) return skip("human changes requested");
197082
197152
  try {
197083
- await op(
197084
- () => ctx.octokit.graphql(ENABLE_AUTO_MERGE, {
197153
+ await mutation({
197154
+ run: () => ctx.octokit.graphql(ENABLE_AUTO_MERGE, {
197085
197155
  pullRequestId: pr.data.node_id,
197086
197156
  headSha
197087
197157
  }),
197088
- {
197089
- name: "enablePullRequestAutoMerge",
197090
- retries: [500, 2e3],
197091
- rethrow: (error49) => !isTransientOctokitError(error49)
197092
- }
197093
- )();
197158
+ name: "enablePullRequestAutoMerge",
197159
+ retry: (error49, attempt) => isTransientOctokitError(error49) ? delay2([500, 2e3], attempt) : -1
197160
+ })();
197094
197161
  log.info(`autoMerge: pr=#${pullNumber} @ ${headSha.slice(0, 7)} \u2192 native auto-merge enabled`);
197095
197162
  await comment(
197096
197163
  "> \u2705 Approved \u2014 auto-merge enabled; GitHub will merge once required checks pass."
@@ -197128,7 +197195,17 @@ async function autoMergeAfterApprove(ctx) {
197128
197195
  var RE_REVIEW_PREAMBLE = "Incrementally re-review the new commits on this pull request. Use the IncrementalReview mode.";
197129
197196
  async function postReviewCleanup(ctx) {
197130
197197
  const review = ctx.toolState.review;
197131
- if (!review) return;
197198
+ if (!review) {
197199
+ const checkoutSha = primaryRepoState(ctx.toolState).checkoutSha;
197200
+ if (ctx.toolState.selectedMode === "IncrementalReview" && checkoutSha) {
197201
+ await patchWorkflowRunFields(ctx, { reviewSkipped: true });
197202
+ await bestEffort(
197203
+ () => dispatchFollowUpReReview(ctx, checkoutSha),
197204
+ "follow-up re-review dispatch"
197205
+ );
197206
+ }
197207
+ return;
197208
+ }
197132
197209
  delete ctx.toolState.review;
197133
197210
  await bestEffort(() => reportReviewNodeId(ctx, { nodeId: review.nodeId }), "reportReviewNodeId");
197134
197211
  if (review.reviewedSha) {
@@ -197181,20 +197258,17 @@ async function dispatchFollowUpReReview(ctx, reviewedSha) {
197181
197258
  eventInstructions: RE_REVIEW_PREAMBLE,
197182
197259
  event
197183
197260
  };
197184
- await op(
197185
- () => ctx.octokit.rest.actions.createWorkflowDispatch({
197261
+ await mutation({
197262
+ run: () => ctx.octokit.rest.actions.createWorkflowDispatch({
197186
197263
  owner: ctx.repo.owner,
197187
197264
  repo: ctx.repo.name,
197188
197265
  workflow_id: getCurrentWorkflowFilename(),
197189
197266
  ref: pr.data.base.repo.default_branch,
197190
197267
  inputs: { prompt: JSON.stringify(payload) }
197191
197268
  }),
197192
- {
197193
- name: "reReviewDispatch",
197194
- retries: [500, 2e3],
197195
- rethrow: (error49) => !isTransientOctokitError(error49)
197196
- }
197197
- )();
197269
+ name: "reReviewDispatch",
197270
+ retry: (error49, attempt) => isTransientOctokitError(error49) ? delay2([500, 2e3], attempt) : -1
197271
+ })();
197198
197272
  }
197199
197273
  function getCurrentWorkflowFilename() {
197200
197274
  const ref = process.env.GITHUB_WORKFLOW_REF ?? "";
@@ -197204,6 +197278,7 @@ function getCurrentWorkflowFilename() {
197204
197278
 
197205
197279
  // utils/runStatusCheck.ts
197206
197280
  var RUN_STATUS_CHECK_NAME = "pullfrog";
197281
+ var GITHUB_ACTIONS_APP_SLUG = "github-actions";
197207
197282
  var APPROVAL_CHECK_NAME = "pullfrog-approval";
197208
197283
  function parseCheckRunId(raw2) {
197209
197284
  if (!raw2?.id) return void 0;
@@ -197257,7 +197332,12 @@ function terminalOutput(params) {
197257
197332
  const review = params.reviewUrl ? `
197258
197333
 
197259
197334
  [View the review Pullfrog posted \u2192](${params.reviewUrl})` : "";
197260
- if (params.conclusion === "success") return { title: base.title, summary: base.summary + review };
197335
+ if (params.conclusion === "success") {
197336
+ const note = params.note ? `
197337
+
197338
+ ${params.note}` : "";
197339
+ return { title: base.title, summary: base.summary + review + note };
197340
+ }
197261
197341
  return {
197262
197342
  title: base.title,
197263
197343
  summary: base.summary + logsLine(params.detailsUrl) + review + disableCheckLine(params.owner, params.repo)
@@ -197275,7 +197355,8 @@ async function finalizeRunStatusCheck(params) {
197275
197355
  owner: params.owner,
197276
197356
  repo: params.repo,
197277
197357
  detailsUrl: params.detailsUrl,
197278
- reviewUrl: params.reviewUrl
197358
+ reviewUrl: params.reviewUrl,
197359
+ note: params.note
197279
197360
  })
197280
197361
  };
197281
197362
  if (params.detailsUrl) updateParams.details_url = params.detailsUrl;
@@ -197294,7 +197375,8 @@ async function createTerminalRunStatusCheck(params) {
197294
197375
  owner: params.owner,
197295
197376
  repo: params.repo,
197296
197377
  detailsUrl: params.detailsUrl,
197297
- reviewUrl: params.reviewUrl
197378
+ reviewUrl: params.reviewUrl,
197379
+ note: params.note
197298
197380
  })
197299
197381
  };
197300
197382
  if (params.detailsUrl) createParams.details_url = params.detailsUrl;
@@ -197310,6 +197392,8 @@ async function reportStatusChecks(ctx, params) {
197310
197392
  if (checkRunId === void 0 && !ctx.payload.runStatusCheck && !ctx.payload.approvalCheck) return;
197311
197393
  const conclusion = params.runSucceeded ? "success" : "failure";
197312
197394
  const detailsUrl = ctx.runId ? `https://github.com/${ctx.repo.owner}/${ctx.repo.name}/actions/runs/${ctx.runId}` : void 0;
197395
+ const approval = ctx.toolState.approval;
197396
+ const note = event.silent && !approval?.url ? ctx.toolState.lastProgressBody : void 0;
197313
197397
  if (checkRunId !== void 0) {
197314
197398
  await finalizeRunStatusCheck({
197315
197399
  octokit: ctx.octokit,
@@ -197318,11 +197402,12 @@ async function reportStatusChecks(ctx, params) {
197318
197402
  checkRunId,
197319
197403
  conclusion,
197320
197404
  detailsUrl,
197321
- reviewUrl: ctx.toolState.approval?.url
197405
+ reviewUrl: approval?.url,
197406
+ note
197322
197407
  }).then(() => log.info(`\xBB finalized ${RUN_STATUS_CHECK_NAME} check (${conclusion})`)).catch((err) => log.debug(`status checks: ${RUN_STATUS_CHECK_NAME} finalize failed: ${err}`));
197323
197408
  }
197324
- const approval = ctx.toolState.approval;
197325
- const needsApprovalCheck = ctx.payload.approvalCheck && params.runSucceeded && approval;
197409
+ const carriesApproval = !approval && ctx.toolState.selectedMode === "IncrementalReview";
197410
+ const needsApprovalCheck = ctx.payload.approvalCheck && params.runSucceeded && (approval !== void 0 || carriesApproval);
197326
197411
  const needsFallbackRunCheck = ctx.payload.runStatusCheck && checkRunId === void 0;
197327
197412
  if (!needsApprovalCheck && !needsFallbackRunCheck) return;
197328
197413
  let headSha;
@@ -197345,25 +197430,64 @@ async function reportStatusChecks(ctx, params) {
197345
197430
  headSha: primaryRepoState(ctx.toolState).checkoutSha ?? headSha,
197346
197431
  conclusion,
197347
197432
  detailsUrl,
197348
- reviewUrl: ctx.toolState.approval?.url
197433
+ reviewUrl: approval?.url,
197434
+ note
197349
197435
  }).then(() => log.info(`\xBB posted ${RUN_STATUS_CHECK_NAME} check (${conclusion})`)).catch((err) => log.debug(`status checks: ${RUN_STATUS_CHECK_NAME} post failed: ${err}`));
197350
197436
  }
197351
- if (!needsApprovalCheck || !approval) return;
197437
+ if (!needsApprovalCheck) return;
197438
+ const verdict = approval ? { wouldApprove: approval.wouldApprove, sha: approval.sha ?? headSha, carriedLine: "" } : await carriedApproval(ctx, pullNumber).catch((err) => {
197439
+ log.debug(`status checks: ${APPROVAL_CHECK_NAME} carry-forward failed: ${err}`);
197440
+ return void 0;
197441
+ });
197442
+ if (!verdict) return;
197352
197443
  const createParams = {
197353
197444
  owner: ctx.repo.owner,
197354
197445
  repo: ctx.repo.name,
197355
197446
  name: APPROVAL_CHECK_NAME,
197356
- head_sha: approval.sha ?? headSha,
197447
+ head_sha: verdict.sha,
197357
197448
  status: "completed",
197358
- conclusion: approval.wouldApprove ? "success" : "failure",
197449
+ conclusion: verdict.wouldApprove ? "success" : "failure",
197359
197450
  output: {
197360
- title: approval.wouldApprove ? "Pullfrog would approve" : "Pullfrog would not approve",
197361
- summary: approval.wouldApprove ? "Pullfrog has no outstanding review feedback on this PR." : "Pullfrog has outstanding review feedback or requested changes on this PR."
197451
+ title: verdict.wouldApprove ? "Pullfrog would approve" : "Pullfrog would not approve",
197452
+ summary: (verdict.wouldApprove ? "Pullfrog has no outstanding review feedback on this PR." : "Pullfrog has outstanding review feedback or requested changes on this PR.") + verdict.carriedLine
197362
197453
  }
197363
197454
  };
197364
197455
  if (detailsUrl) createParams.details_url = detailsUrl;
197365
197456
  await ctx.octokit.rest.checks.create(createParams).then(() => log.info(`\xBB posted ${APPROVAL_CHECK_NAME} check`)).catch((err) => log.debug(`status checks: ${APPROVAL_CHECK_NAME} post failed: ${err}`));
197366
197457
  }
197458
+ async function carriedApproval(ctx, pullNumber) {
197459
+ const primary = primaryRepoState(ctx.toolState);
197460
+ if (!primary.beforeSha || !primary.checkoutSha) return void 0;
197461
+ if (await latestApprovalConclusion(ctx, primary.checkoutSha) !== void 0) return void 0;
197462
+ const prior = await latestApprovalConclusion(ctx, primary.beforeSha);
197463
+ if (prior === void 0) return void 0;
197464
+ const from = primary.beforeSha.slice(0, 7);
197465
+ if (prior !== "success") {
197466
+ const carriedLine = `
197467
+
197468
+ No review covered this commit, so the verdict on ${from} carries forward.`;
197469
+ return { wouldApprove: false, sha: primary.checkoutSha, carriedLine };
197470
+ }
197471
+ if (ctx.toolState.noReviewNeededSha !== primary.checkoutSha) return void 0;
197472
+ return {
197473
+ wouldApprove: await countOutstandingPullfrogThreads(ctx, pullNumber) === 0,
197474
+ sha: primary.checkoutSha,
197475
+ carriedLine: `
197476
+
197477
+ Pullfrog found nothing to review in the commits since ${from}.`
197478
+ };
197479
+ }
197480
+ async function latestApprovalConclusion(ctx, ref) {
197481
+ const result = await ctx.octokit.rest.checks.listForRef({
197482
+ owner: ctx.repo.owner,
197483
+ repo: ctx.repo.name,
197484
+ ref,
197485
+ check_name: APPROVAL_CHECK_NAME,
197486
+ status: "completed"
197487
+ });
197488
+ const latest = result.data.check_runs.filter((run) => run.app?.slug !== GITHUB_ACTIONS_APP_SLUG).sort((a, b) => (b.completed_at ?? "").localeCompare(a.completed_at ?? ""))[0];
197489
+ return latest?.conclusion ?? void 0;
197490
+ }
197367
197491
 
197368
197492
  // utils/runLifecycle.ts
197369
197493
  async function persistRunArtifacts(toolContext) {
@@ -197702,6 +197826,7 @@ async function main() {
197702
197826
  octokit: initialOctokit,
197703
197827
  token: jobToken,
197704
197828
  runType: typeof resolvedPromptInput === "string" ? void 0 : resolvedPromptInput.type,
197829
+ plainPrompt: typeof resolvedPromptInput === "string",
197705
197830
  routedTier: typeof resolvedPromptInput === "string" ? void 0 : resolvedPromptInput.routing?.tier
197706
197831
  });
197707
197832
  timer.checkpoint("runContextData");
@@ -197756,7 +197881,7 @@ async function main() {
197756
197881
  const repoDir = payload.cwd;
197757
197882
  const preIntrospectionDirty = repoDir ? await dirtyTrackedPaths({ cwd: repoDir }) : null;
197758
197883
  const opencodeCliPath = await agents.opencode.install();
197759
- captureBaselineModels(opencodeCliPath);
197884
+ captureBaselineModels({ cliPath: opencodeCliPath, tmpdir: tmpdir3 });
197760
197885
  const injected = [];
197761
197886
  if (runContext.dbSecrets) {
197762
197887
  for (const [key, value2] of Object.entries(runContext.dbSecrets)) {
@@ -197773,7 +197898,7 @@ async function main() {
197773
197898
  await initializeCredentialPool({ ctx: runContext, slug: payload.model, injected });
197774
197899
  installCodexAuth();
197775
197900
  installXaiAuth();
197776
- captureAuthorizedModels(opencodeCliPath);
197901
+ captureAuthorizedModels({ cliPath: opencodeCliPath, tmpdir: tmpdir3 });
197777
197902
  if (preIntrospectionDirty && repoDir) {
197778
197903
  await restoreDirtiedSince({
197779
197904
  before: preIntrospectionDirty,
@@ -197798,7 +197923,7 @@ async function main() {
197798
197923
  oidc: oidcCredentials
197799
197924
  }), true);
197800
197925
  wipeRunnerLeakSurface();
197801
- if (payload.shell === "restricted" && getSandboxMethod() === "none") {
197926
+ if (payload.shell !== "disabled" && process.env.CI === "true" && getSandboxMethod() === "none") {
197802
197927
  payload.shell = "disabled";
197803
197928
  log.warning(
197804
197929
  "\xBB shell commands are disabled for this run: this runner provides no PID-namespace isolation (unprivileged unshare, sudo unshare and userns-nested unshare all failed). on a self-hosted Kubernetes/ARC runner, allow user-namespace creation in the pod's seccomp profile \u2014 see https://docs.pullfrog.com/security#self-hosted-runners"
@@ -197819,6 +197944,7 @@ async function main() {
197819
197944
  const octokit = createOctokit(tokenRef.mcpToken, getMcpTokenRefresh());
197820
197945
  const runInfo = await resolveRun({ octokit });
197821
197946
  let toolContext;
197947
+ let succeeded = false;
197822
197948
  let progressCallbackDisabled = false;
197823
197949
  let todoTracker;
197824
197950
  let vertexCredentials;
@@ -198110,8 +198236,8 @@ ${instructions.user}` : null,
198110
198236
  log.info(instructions.full);
198111
198237
  });
198112
198238
  if (agentId === "opencode") {
198113
- const pluginDir = join27(process.cwd(), ".opencode", "plugin");
198114
- const hasPlugins = existsSync10(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
198239
+ const pluginDir = join28(process.cwd(), ".opencode", "plugin");
198240
+ const hasPlugins = existsSync11(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
198115
198241
  if (hasPlugins && toolState.dependencyInstallation?.promise) {
198116
198242
  log.info(
198117
198243
  "\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"
@@ -198236,11 +198362,13 @@ ${instructions.user}` : null,
198236
198362
  );
198237
198363
  }
198238
198364
  await finalizeSuccessRun({ toolContext, toolState, result, repo: runContext.repo });
198239
- return await handleAgentResult({
198365
+ const outcome = await handleAgentResult({
198240
198366
  result,
198241
198367
  toolContext,
198242
198368
  silent: payload.event.silent ?? false
198243
198369
  });
198370
+ succeeded = outcome.success;
198371
+ return outcome;
198244
198372
  } catch (_2) {
198245
198373
  var _error2 = _2, _hasError2 = true;
198246
198374
  } finally {
@@ -198286,9 +198414,8 @@ ${instructions.user}` : null,
198286
198414
  if (toolState.model) patch.model = toolState.model;
198287
198415
  if (toolState.agent) patch.agent = toolState.agent;
198288
198416
  if (toolState.credential) patch.credential = toolState.credential;
198289
- if (Object.keys(patch).length > 0) {
198290
- await patchWorkflowRunFields(toolContext, patch);
198291
- }
198417
+ patch.succeeded = succeeded;
198418
+ await patchWorkflowRunFields(toolContext, patch);
198292
198419
  }
198293
198420
  cleanupVertexCredentials(vertexCredentials);
198294
198421
  }