pullfrog 0.0.200 → 0.0.202

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
@@ -4076,8 +4076,8 @@ var require_util2 = __commonJS({
4076
4076
  function createDeferredPromise() {
4077
4077
  let res;
4078
4078
  let rej;
4079
- const promise2 = new Promise((resolve2, reject) => {
4080
- res = resolve2;
4079
+ const promise2 = new Promise((resolve3, reject) => {
4080
+ res = resolve3;
4081
4081
  rej = reject;
4082
4082
  });
4083
4083
  return { promise: promise2, resolve: res, reject: rej };
@@ -5581,8 +5581,8 @@ Content-Type: ${value2.type || "application/octet-stream"}\r
5581
5581
  });
5582
5582
  }
5583
5583
  });
5584
- const busboyResolve = new Promise((resolve2, reject) => {
5585
- busboy.on("finish", resolve2);
5584
+ const busboyResolve = new Promise((resolve3, reject) => {
5585
+ busboy.on("finish", resolve3);
5586
5586
  busboy.on("error", (err) => reject(new TypeError(err)));
5587
5587
  });
5588
5588
  if (this.body !== null) for await (const chunk of consumeBody(this[kState].body)) busboy.write(chunk);
@@ -6116,9 +6116,9 @@ var require_dispatcher_base = __commonJS({
6116
6116
  }
6117
6117
  close(callback) {
6118
6118
  if (callback === void 0) {
6119
- return new Promise((resolve2, reject) => {
6119
+ return new Promise((resolve3, reject) => {
6120
6120
  this.close((err, data) => {
6121
- return err ? reject(err) : resolve2(data);
6121
+ return err ? reject(err) : resolve3(data);
6122
6122
  });
6123
6123
  });
6124
6124
  }
@@ -6156,12 +6156,12 @@ var require_dispatcher_base = __commonJS({
6156
6156
  err = null;
6157
6157
  }
6158
6158
  if (callback === void 0) {
6159
- return new Promise((resolve2, reject) => {
6159
+ return new Promise((resolve3, reject) => {
6160
6160
  this.destroy(err, (err2, data) => {
6161
6161
  return err2 ? (
6162
6162
  /* istanbul ignore next: should never error */
6163
6163
  reject(err2)
6164
- ) : resolve2(data);
6164
+ ) : resolve3(data);
6165
6165
  });
6166
6166
  });
6167
6167
  }
@@ -7221,16 +7221,16 @@ var require_client = __commonJS({
7221
7221
  return this[kNeedDrain] < 2;
7222
7222
  }
7223
7223
  async [kClose]() {
7224
- return new Promise((resolve2) => {
7224
+ return new Promise((resolve3) => {
7225
7225
  if (!this[kSize]) {
7226
- resolve2(null);
7226
+ resolve3(null);
7227
7227
  } else {
7228
- this[kClosedResolve] = resolve2;
7228
+ this[kClosedResolve] = resolve3;
7229
7229
  }
7230
7230
  });
7231
7231
  }
7232
7232
  async [kDestroy](err) {
7233
- return new Promise((resolve2) => {
7233
+ return new Promise((resolve3) => {
7234
7234
  const requests = this[kQueue].splice(this[kPendingIdx]);
7235
7235
  for (let i = 0; i < requests.length; i++) {
7236
7236
  const request2 = requests[i];
@@ -7241,7 +7241,7 @@ var require_client = __commonJS({
7241
7241
  this[kClosedResolve]();
7242
7242
  this[kClosedResolve] = null;
7243
7243
  }
7244
- resolve2();
7244
+ resolve3();
7245
7245
  };
7246
7246
  if (this[kHTTP2Session] != null) {
7247
7247
  util2.destroy(this[kHTTP2Session], err);
@@ -7821,7 +7821,7 @@ var require_client = __commonJS({
7821
7821
  });
7822
7822
  }
7823
7823
  try {
7824
- const socket = await new Promise((resolve2, reject) => {
7824
+ const socket = await new Promise((resolve3, reject) => {
7825
7825
  client[kConnector]({
7826
7826
  host,
7827
7827
  hostname: hostname4,
@@ -7833,7 +7833,7 @@ var require_client = __commonJS({
7833
7833
  if (err) {
7834
7834
  reject(err);
7835
7835
  } else {
7836
- resolve2(socket2);
7836
+ resolve3(socket2);
7837
7837
  }
7838
7838
  });
7839
7839
  });
@@ -8457,12 +8457,12 @@ upgrade: ${upgrade}\r
8457
8457
  cb();
8458
8458
  }
8459
8459
  }
8460
- const waitForDrain = () => new Promise((resolve2, reject) => {
8460
+ const waitForDrain = () => new Promise((resolve3, reject) => {
8461
8461
  assert3(callback === null);
8462
8462
  if (socket[kError]) {
8463
8463
  reject(socket[kError]);
8464
8464
  } else {
8465
- callback = resolve2;
8465
+ callback = resolve3;
8466
8466
  }
8467
8467
  });
8468
8468
  if (client[kHTTPConnVersion] === "h2") {
@@ -8807,8 +8807,8 @@ var require_pool_base = __commonJS({
8807
8807
  if (this[kQueue].isEmpty()) {
8808
8808
  return Promise.all(this[kClients].map((c) => c.close()));
8809
8809
  } else {
8810
- return new Promise((resolve2) => {
8811
- this[kClosedResolve] = resolve2;
8810
+ return new Promise((resolve3) => {
8811
+ this[kClosedResolve] = resolve3;
8812
8812
  });
8813
8813
  }
8814
8814
  }
@@ -9386,7 +9386,7 @@ var require_readable = __commonJS({
9386
9386
  if (this.closed) {
9387
9387
  return Promise.resolve(null);
9388
9388
  }
9389
- return new Promise((resolve2, reject) => {
9389
+ return new Promise((resolve3, reject) => {
9390
9390
  const signalListenerCleanup = signal ? util2.addAbortListener(signal, () => {
9391
9391
  this.destroy();
9392
9392
  }) : noop4;
@@ -9395,7 +9395,7 @@ var require_readable = __commonJS({
9395
9395
  if (signal && signal.aborted) {
9396
9396
  reject(signal.reason || Object.assign(new Error("The operation was aborted"), { name: "AbortError" }));
9397
9397
  } else {
9398
- resolve2(null);
9398
+ resolve3(null);
9399
9399
  }
9400
9400
  }).on("error", noop4).on("data", function(chunk) {
9401
9401
  limit -= chunk.length;
@@ -9417,11 +9417,11 @@ var require_readable = __commonJS({
9417
9417
  throw new TypeError("unusable");
9418
9418
  }
9419
9419
  assert3(!stream[kConsume]);
9420
- return new Promise((resolve2, reject) => {
9420
+ return new Promise((resolve3, reject) => {
9421
9421
  stream[kConsume] = {
9422
9422
  type: type2,
9423
9423
  stream,
9424
- resolve: resolve2,
9424
+ resolve: resolve3,
9425
9425
  reject,
9426
9426
  length: 0,
9427
9427
  body: []
@@ -9456,12 +9456,12 @@ var require_readable = __commonJS({
9456
9456
  }
9457
9457
  }
9458
9458
  function consumeEnd(consume2) {
9459
- const { type: type2, body, resolve: resolve2, stream, length } = consume2;
9459
+ const { type: type2, body, resolve: resolve3, stream, length } = consume2;
9460
9460
  try {
9461
9461
  if (type2 === "text") {
9462
- resolve2(toUSVString(Buffer.concat(body)));
9462
+ resolve3(toUSVString(Buffer.concat(body)));
9463
9463
  } else if (type2 === "json") {
9464
- resolve2(JSON.parse(Buffer.concat(body)));
9464
+ resolve3(JSON.parse(Buffer.concat(body)));
9465
9465
  } else if (type2 === "arrayBuffer") {
9466
9466
  const dst = new Uint8Array(length);
9467
9467
  let pos = 0;
@@ -9469,12 +9469,12 @@ var require_readable = __commonJS({
9469
9469
  dst.set(buf, pos);
9470
9470
  pos += buf.byteLength;
9471
9471
  }
9472
- resolve2(dst.buffer);
9472
+ resolve3(dst.buffer);
9473
9473
  } else if (type2 === "blob") {
9474
9474
  if (!Blob2) {
9475
9475
  Blob2 = __require("buffer").Blob;
9476
9476
  }
9477
- resolve2(new Blob2(body, { type: stream[kContentType] }));
9477
+ resolve3(new Blob2(body, { type: stream[kContentType] }));
9478
9478
  }
9479
9479
  consumeFinish(consume2);
9480
9480
  } catch (err) {
@@ -9729,9 +9729,9 @@ var require_api_request = __commonJS({
9729
9729
  };
9730
9730
  function request2(opts, callback) {
9731
9731
  if (callback === void 0) {
9732
- return new Promise((resolve2, reject) => {
9732
+ return new Promise((resolve3, reject) => {
9733
9733
  request2.call(this, opts, (err, data) => {
9734
- return err ? reject(err) : resolve2(data);
9734
+ return err ? reject(err) : resolve3(data);
9735
9735
  });
9736
9736
  });
9737
9737
  }
@@ -9904,9 +9904,9 @@ var require_api_stream = __commonJS({
9904
9904
  };
9905
9905
  function stream(opts, factory, callback) {
9906
9906
  if (callback === void 0) {
9907
- return new Promise((resolve2, reject) => {
9907
+ return new Promise((resolve3, reject) => {
9908
9908
  stream.call(this, opts, factory, (err, data) => {
9909
- return err ? reject(err) : resolve2(data);
9909
+ return err ? reject(err) : resolve3(data);
9910
9910
  });
9911
9911
  });
9912
9912
  }
@@ -10187,9 +10187,9 @@ var require_api_upgrade = __commonJS({
10187
10187
  };
10188
10188
  function upgrade(opts, callback) {
10189
10189
  if (callback === void 0) {
10190
- return new Promise((resolve2, reject) => {
10190
+ return new Promise((resolve3, reject) => {
10191
10191
  upgrade.call(this, opts, (err, data) => {
10192
- return err ? reject(err) : resolve2(data);
10192
+ return err ? reject(err) : resolve3(data);
10193
10193
  });
10194
10194
  });
10195
10195
  }
@@ -10278,9 +10278,9 @@ var require_api_connect = __commonJS({
10278
10278
  };
10279
10279
  function connect(opts, callback) {
10280
10280
  if (callback === void 0) {
10281
- return new Promise((resolve2, reject) => {
10281
+ return new Promise((resolve3, reject) => {
10282
10282
  connect.call(this, opts, (err, data) => {
10283
- return err ? reject(err) : resolve2(data);
10283
+ return err ? reject(err) : resolve3(data);
10284
10284
  });
10285
10285
  });
10286
10286
  }
@@ -13902,7 +13902,7 @@ var require_fetch = __commonJS({
13902
13902
  async function dispatch({ body }) {
13903
13903
  const url4 = requestCurrentURL(request2);
13904
13904
  const agent2 = fetchParams.controller.dispatcher;
13905
- return new Promise((resolve2, reject) => agent2.dispatch(
13905
+ return new Promise((resolve3, reject) => agent2.dispatch(
13906
13906
  {
13907
13907
  path: url4.pathname + url4.search,
13908
13908
  origin: url4.origin,
@@ -13978,7 +13978,7 @@ var require_fetch = __commonJS({
13978
13978
  }
13979
13979
  }
13980
13980
  }
13981
- resolve2({
13981
+ resolve3({
13982
13982
  status,
13983
13983
  statusText,
13984
13984
  headersList: headers[kHeadersList],
@@ -14021,7 +14021,7 @@ var require_fetch = __commonJS({
14021
14021
  const val = headersList[n + 1].toString("latin1");
14022
14022
  headers[kHeadersList].append(key, val);
14023
14023
  }
14024
- resolve2({
14024
+ resolve3({
14025
14025
  status,
14026
14026
  statusText: STATUS_CODES[status],
14027
14027
  headersList: headers[kHeadersList],
@@ -17375,11 +17375,11 @@ var require_lib = __commonJS({
17375
17375
  };
17376
17376
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
17377
17377
  function adopt(value2) {
17378
- return value2 instanceof P ? value2 : new P(function(resolve2) {
17379
- resolve2(value2);
17378
+ return value2 instanceof P ? value2 : new P(function(resolve3) {
17379
+ resolve3(value2);
17380
17380
  });
17381
17381
  }
17382
- return new (P || (P = Promise))(function(resolve2, reject) {
17382
+ return new (P || (P = Promise))(function(resolve3, reject) {
17383
17383
  function fulfilled(value2) {
17384
17384
  try {
17385
17385
  step(generator.next(value2));
@@ -17395,7 +17395,7 @@ var require_lib = __commonJS({
17395
17395
  }
17396
17396
  }
17397
17397
  function step(result) {
17398
- result.done ? resolve2(result.value) : adopt(result.value).then(fulfilled, rejected);
17398
+ result.done ? resolve3(result.value) : adopt(result.value).then(fulfilled, rejected);
17399
17399
  }
17400
17400
  step((generator = generator.apply(thisArg, _arguments || [])).next());
17401
17401
  });
@@ -17481,26 +17481,26 @@ var require_lib = __commonJS({
17481
17481
  }
17482
17482
  readBody() {
17483
17483
  return __awaiter(this, void 0, void 0, function* () {
17484
- return new Promise((resolve2) => __awaiter(this, void 0, void 0, function* () {
17484
+ return new Promise((resolve3) => __awaiter(this, void 0, void 0, function* () {
17485
17485
  let output = Buffer.alloc(0);
17486
17486
  this.message.on("data", (chunk) => {
17487
17487
  output = Buffer.concat([output, chunk]);
17488
17488
  });
17489
17489
  this.message.on("end", () => {
17490
- resolve2(output.toString());
17490
+ resolve3(output.toString());
17491
17491
  });
17492
17492
  }));
17493
17493
  });
17494
17494
  }
17495
17495
  readBodyBuffer() {
17496
17496
  return __awaiter(this, void 0, void 0, function* () {
17497
- return new Promise((resolve2) => __awaiter(this, void 0, void 0, function* () {
17497
+ return new Promise((resolve3) => __awaiter(this, void 0, void 0, function* () {
17498
17498
  const chunks = [];
17499
17499
  this.message.on("data", (chunk) => {
17500
17500
  chunks.push(chunk);
17501
17501
  });
17502
17502
  this.message.on("end", () => {
17503
- resolve2(Buffer.concat(chunks));
17503
+ resolve3(Buffer.concat(chunks));
17504
17504
  });
17505
17505
  }));
17506
17506
  });
@@ -17709,14 +17709,14 @@ var require_lib = __commonJS({
17709
17709
  */
17710
17710
  requestRaw(info2, data) {
17711
17711
  return __awaiter(this, void 0, void 0, function* () {
17712
- return new Promise((resolve2, reject) => {
17712
+ return new Promise((resolve3, reject) => {
17713
17713
  function callbackForResult(err, res) {
17714
17714
  if (err) {
17715
17715
  reject(err);
17716
17716
  } else if (!res) {
17717
17717
  reject(new Error("Unknown error"));
17718
17718
  } else {
17719
- resolve2(res);
17719
+ resolve3(res);
17720
17720
  }
17721
17721
  }
17722
17722
  this.requestRawWithCallback(info2, data, callbackForResult);
@@ -17898,12 +17898,12 @@ var require_lib = __commonJS({
17898
17898
  return __awaiter(this, void 0, void 0, function* () {
17899
17899
  retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber);
17900
17900
  const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber);
17901
- return new Promise((resolve2) => setTimeout(() => resolve2(), ms));
17901
+ return new Promise((resolve3) => setTimeout(() => resolve3(), ms));
17902
17902
  });
17903
17903
  }
17904
17904
  _processResponse(res, options) {
17905
17905
  return __awaiter(this, void 0, void 0, function* () {
17906
- return new Promise((resolve2, reject) => __awaiter(this, void 0, void 0, function* () {
17906
+ return new Promise((resolve3, reject) => __awaiter(this, void 0, void 0, function* () {
17907
17907
  const statusCode = res.message.statusCode || 0;
17908
17908
  const response = {
17909
17909
  statusCode,
@@ -17911,7 +17911,7 @@ var require_lib = __commonJS({
17911
17911
  headers: {}
17912
17912
  };
17913
17913
  if (statusCode === HttpCodes.NotFound) {
17914
- resolve2(response);
17914
+ resolve3(response);
17915
17915
  }
17916
17916
  function dateTimeDeserializer(key, value2) {
17917
17917
  if (typeof value2 === "string") {
@@ -17950,7 +17950,7 @@ var require_lib = __commonJS({
17950
17950
  err.result = response.result;
17951
17951
  reject(err);
17952
17952
  } else {
17953
- resolve2(response);
17953
+ resolve3(response);
17954
17954
  }
17955
17955
  }));
17956
17956
  });
@@ -17967,11 +17967,11 @@ var require_auth = __commonJS({
17967
17967
  "use strict";
17968
17968
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
17969
17969
  function adopt(value2) {
17970
- return value2 instanceof P ? value2 : new P(function(resolve2) {
17971
- resolve2(value2);
17970
+ return value2 instanceof P ? value2 : new P(function(resolve3) {
17971
+ resolve3(value2);
17972
17972
  });
17973
17973
  }
17974
- return new (P || (P = Promise))(function(resolve2, reject) {
17974
+ return new (P || (P = Promise))(function(resolve3, reject) {
17975
17975
  function fulfilled(value2) {
17976
17976
  try {
17977
17977
  step(generator.next(value2));
@@ -17987,7 +17987,7 @@ var require_auth = __commonJS({
17987
17987
  }
17988
17988
  }
17989
17989
  function step(result) {
17990
- result.done ? resolve2(result.value) : adopt(result.value).then(fulfilled, rejected);
17990
+ result.done ? resolve3(result.value) : adopt(result.value).then(fulfilled, rejected);
17991
17991
  }
17992
17992
  step((generator = generator.apply(thisArg, _arguments || [])).next());
17993
17993
  });
@@ -18071,11 +18071,11 @@ var require_oidc_utils = __commonJS({
18071
18071
  "use strict";
18072
18072
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
18073
18073
  function adopt(value2) {
18074
- return value2 instanceof P ? value2 : new P(function(resolve2) {
18075
- resolve2(value2);
18074
+ return value2 instanceof P ? value2 : new P(function(resolve3) {
18075
+ resolve3(value2);
18076
18076
  });
18077
18077
  }
18078
- return new (P || (P = Promise))(function(resolve2, reject) {
18078
+ return new (P || (P = Promise))(function(resolve3, reject) {
18079
18079
  function fulfilled(value2) {
18080
18080
  try {
18081
18081
  step(generator.next(value2));
@@ -18091,7 +18091,7 @@ var require_oidc_utils = __commonJS({
18091
18091
  }
18092
18092
  }
18093
18093
  function step(result) {
18094
- result.done ? resolve2(result.value) : adopt(result.value).then(fulfilled, rejected);
18094
+ result.done ? resolve3(result.value) : adopt(result.value).then(fulfilled, rejected);
18095
18095
  }
18096
18096
  step((generator = generator.apply(thisArg, _arguments || [])).next());
18097
18097
  });
@@ -18169,11 +18169,11 @@ var require_summary = __commonJS({
18169
18169
  "use strict";
18170
18170
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
18171
18171
  function adopt(value2) {
18172
- return value2 instanceof P ? value2 : new P(function(resolve2) {
18173
- resolve2(value2);
18172
+ return value2 instanceof P ? value2 : new P(function(resolve3) {
18173
+ resolve3(value2);
18174
18174
  });
18175
18175
  }
18176
- return new (P || (P = Promise))(function(resolve2, reject) {
18176
+ return new (P || (P = Promise))(function(resolve3, reject) {
18177
18177
  function fulfilled(value2) {
18178
18178
  try {
18179
18179
  step(generator.next(value2));
@@ -18189,7 +18189,7 @@ var require_summary = __commonJS({
18189
18189
  }
18190
18190
  }
18191
18191
  function step(result) {
18192
- result.done ? resolve2(result.value) : adopt(result.value).then(fulfilled, rejected);
18192
+ result.done ? resolve3(result.value) : adopt(result.value).then(fulfilled, rejected);
18193
18193
  }
18194
18194
  step((generator = generator.apply(thisArg, _arguments || [])).next());
18195
18195
  });
@@ -18535,11 +18535,11 @@ var require_io_util = __commonJS({
18535
18535
  };
18536
18536
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
18537
18537
  function adopt(value2) {
18538
- return value2 instanceof P ? value2 : new P(function(resolve2) {
18539
- resolve2(value2);
18538
+ return value2 instanceof P ? value2 : new P(function(resolve3) {
18539
+ resolve3(value2);
18540
18540
  });
18541
18541
  }
18542
- return new (P || (P = Promise))(function(resolve2, reject) {
18542
+ return new (P || (P = Promise))(function(resolve3, reject) {
18543
18543
  function fulfilled(value2) {
18544
18544
  try {
18545
18545
  step(generator.next(value2));
@@ -18555,7 +18555,7 @@ var require_io_util = __commonJS({
18555
18555
  }
18556
18556
  }
18557
18557
  function step(result) {
18558
- result.done ? resolve2(result.value) : adopt(result.value).then(fulfilled, rejected);
18558
+ result.done ? resolve3(result.value) : adopt(result.value).then(fulfilled, rejected);
18559
18559
  }
18560
18560
  step((generator = generator.apply(thisArg, _arguments || [])).next());
18561
18561
  });
@@ -18708,11 +18708,11 @@ var require_io = __commonJS({
18708
18708
  };
18709
18709
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
18710
18710
  function adopt(value2) {
18711
- return value2 instanceof P ? value2 : new P(function(resolve2) {
18712
- resolve2(value2);
18711
+ return value2 instanceof P ? value2 : new P(function(resolve3) {
18712
+ resolve3(value2);
18713
18713
  });
18714
18714
  }
18715
- return new (P || (P = Promise))(function(resolve2, reject) {
18715
+ return new (P || (P = Promise))(function(resolve3, reject) {
18716
18716
  function fulfilled(value2) {
18717
18717
  try {
18718
18718
  step(generator.next(value2));
@@ -18728,7 +18728,7 @@ var require_io = __commonJS({
18728
18728
  }
18729
18729
  }
18730
18730
  function step(result) {
18731
- result.done ? resolve2(result.value) : adopt(result.value).then(fulfilled, rejected);
18731
+ result.done ? resolve3(result.value) : adopt(result.value).then(fulfilled, rejected);
18732
18732
  }
18733
18733
  step((generator = generator.apply(thisArg, _arguments || [])).next());
18734
18734
  });
@@ -18956,11 +18956,11 @@ var require_toolrunner = __commonJS({
18956
18956
  };
18957
18957
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
18958
18958
  function adopt(value2) {
18959
- return value2 instanceof P ? value2 : new P(function(resolve2) {
18960
- resolve2(value2);
18959
+ return value2 instanceof P ? value2 : new P(function(resolve3) {
18960
+ resolve3(value2);
18961
18961
  });
18962
18962
  }
18963
- return new (P || (P = Promise))(function(resolve2, reject) {
18963
+ return new (P || (P = Promise))(function(resolve3, reject) {
18964
18964
  function fulfilled(value2) {
18965
18965
  try {
18966
18966
  step(generator.next(value2));
@@ -18976,7 +18976,7 @@ var require_toolrunner = __commonJS({
18976
18976
  }
18977
18977
  }
18978
18978
  function step(result) {
18979
- result.done ? resolve2(result.value) : adopt(result.value).then(fulfilled, rejected);
18979
+ result.done ? resolve3(result.value) : adopt(result.value).then(fulfilled, rejected);
18980
18980
  }
18981
18981
  step((generator = generator.apply(thisArg, _arguments || [])).next());
18982
18982
  });
@@ -19204,7 +19204,7 @@ var require_toolrunner = __commonJS({
19204
19204
  this.toolPath = path3.resolve(process.cwd(), this.options.cwd || process.cwd(), this.toolPath);
19205
19205
  }
19206
19206
  this.toolPath = yield io.which(this.toolPath, true);
19207
- return new Promise((resolve2, reject) => __awaiter(this, void 0, void 0, function* () {
19207
+ return new Promise((resolve3, reject) => __awaiter(this, void 0, void 0, function* () {
19208
19208
  this._debug(`exec tool: ${this.toolPath}`);
19209
19209
  this._debug("arguments:");
19210
19210
  for (const arg of this.args) {
@@ -19287,7 +19287,7 @@ var require_toolrunner = __commonJS({
19287
19287
  if (error49) {
19288
19288
  reject(error49);
19289
19289
  } else {
19290
- resolve2(exitCode);
19290
+ resolve3(exitCode);
19291
19291
  }
19292
19292
  });
19293
19293
  if (this.options.input) {
@@ -19440,11 +19440,11 @@ var require_exec = __commonJS({
19440
19440
  };
19441
19441
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
19442
19442
  function adopt(value2) {
19443
- return value2 instanceof P ? value2 : new P(function(resolve2) {
19444
- resolve2(value2);
19443
+ return value2 instanceof P ? value2 : new P(function(resolve3) {
19444
+ resolve3(value2);
19445
19445
  });
19446
19446
  }
19447
- return new (P || (P = Promise))(function(resolve2, reject) {
19447
+ return new (P || (P = Promise))(function(resolve3, reject) {
19448
19448
  function fulfilled(value2) {
19449
19449
  try {
19450
19450
  step(generator.next(value2));
@@ -19460,7 +19460,7 @@ var require_exec = __commonJS({
19460
19460
  }
19461
19461
  }
19462
19462
  function step(result) {
19463
- result.done ? resolve2(result.value) : adopt(result.value).then(fulfilled, rejected);
19463
+ result.done ? resolve3(result.value) : adopt(result.value).then(fulfilled, rejected);
19464
19464
  }
19465
19465
  step((generator = generator.apply(thisArg, _arguments || [])).next());
19466
19466
  });
@@ -19551,11 +19551,11 @@ var require_platform = __commonJS({
19551
19551
  };
19552
19552
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
19553
19553
  function adopt(value2) {
19554
- return value2 instanceof P ? value2 : new P(function(resolve2) {
19555
- resolve2(value2);
19554
+ return value2 instanceof P ? value2 : new P(function(resolve3) {
19555
+ resolve3(value2);
19556
19556
  });
19557
19557
  }
19558
- return new (P || (P = Promise))(function(resolve2, reject) {
19558
+ return new (P || (P = Promise))(function(resolve3, reject) {
19559
19559
  function fulfilled(value2) {
19560
19560
  try {
19561
19561
  step(generator.next(value2));
@@ -19571,7 +19571,7 @@ var require_platform = __commonJS({
19571
19571
  }
19572
19572
  }
19573
19573
  function step(result) {
19574
- result.done ? resolve2(result.value) : adopt(result.value).then(fulfilled, rejected);
19574
+ result.done ? resolve3(result.value) : adopt(result.value).then(fulfilled, rejected);
19575
19575
  }
19576
19576
  step((generator = generator.apply(thisArg, _arguments || [])).next());
19577
19577
  });
@@ -19670,11 +19670,11 @@ var require_core = __commonJS({
19670
19670
  };
19671
19671
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
19672
19672
  function adopt(value2) {
19673
- return value2 instanceof P ? value2 : new P(function(resolve2) {
19674
- resolve2(value2);
19673
+ return value2 instanceof P ? value2 : new P(function(resolve3) {
19674
+ resolve3(value2);
19675
19675
  });
19676
19676
  }
19677
- return new (P || (P = Promise))(function(resolve2, reject) {
19677
+ return new (P || (P = Promise))(function(resolve3, reject) {
19678
19678
  function fulfilled(value2) {
19679
19679
  try {
19680
19680
  step(generator.next(value2));
@@ -19690,7 +19690,7 @@ var require_core = __commonJS({
19690
19690
  }
19691
19691
  }
19692
19692
  function step(result) {
19693
- result.done ? resolve2(result.value) : adopt(result.value).then(fulfilled, rejected);
19693
+ result.done ? resolve3(result.value) : adopt(result.value).then(fulfilled, rejected);
19694
19694
  }
19695
19695
  step((generator = generator.apply(thisArg, _arguments || [])).next());
19696
19696
  });
@@ -45381,8 +45381,8 @@ var require_resolve = __commonJS({
45381
45381
  }
45382
45382
  return count;
45383
45383
  }
45384
- function getFullPath(resolver, id = "", normalize2) {
45385
- if (normalize2 !== false)
45384
+ function getFullPath(resolver, id = "", normalize3) {
45385
+ if (normalize3 !== false)
45386
45386
  id = normalizeId(id);
45387
45387
  const p = resolver.parse(id);
45388
45388
  return _getFullPath(resolver, p);
@@ -46130,7 +46130,7 @@ var require_compile = __commonJS({
46130
46130
  const schOrFunc = root.refs[ref];
46131
46131
  if (schOrFunc)
46132
46132
  return schOrFunc;
46133
- let _sch = resolve2.call(this, root, ref);
46133
+ let _sch = resolve3.call(this, root, ref);
46134
46134
  if (_sch === void 0) {
46135
46135
  const schema2 = (_a2 = root.localRefs) === null || _a2 === void 0 ? void 0 : _a2[ref];
46136
46136
  const { schemaId } = this.opts;
@@ -46157,7 +46157,7 @@ var require_compile = __commonJS({
46157
46157
  function sameSchemaEnv(s1, s2) {
46158
46158
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
46159
46159
  }
46160
- function resolve2(root, ref) {
46160
+ function resolve3(root, ref) {
46161
46161
  let sch;
46162
46162
  while (typeof (sch = this.refs[ref]) == "string")
46163
46163
  ref = sch;
@@ -46722,7 +46722,7 @@ var require_fast_uri = __commonJS({
46722
46722
  "use strict";
46723
46723
  var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils4();
46724
46724
  var { SCHEMES, getSchemeHandler } = require_schemes();
46725
- function normalize2(uri, options) {
46725
+ function normalize3(uri, options) {
46726
46726
  if (typeof uri === "string") {
46727
46727
  uri = /** @type {T} */
46728
46728
  serialize(parse5(uri, options), options);
@@ -46732,7 +46732,7 @@ var require_fast_uri = __commonJS({
46732
46732
  }
46733
46733
  return uri;
46734
46734
  }
46735
- function resolve2(baseURI, relativeURI, options) {
46735
+ function resolve3(baseURI, relativeURI, options) {
46736
46736
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
46737
46737
  const resolved = resolveComponent(parse5(baseURI, schemelessOptions), parse5(relativeURI, schemelessOptions), schemelessOptions, true);
46738
46738
  schemelessOptions.skipEscape = true;
@@ -46958,8 +46958,8 @@ var require_fast_uri = __commonJS({
46958
46958
  }
46959
46959
  var fastUri = {
46960
46960
  SCHEMES,
46961
- normalize: normalize2,
46962
- resolve: resolve2,
46961
+ normalize: normalize3,
46962
+ resolve: resolve3,
46963
46963
  resolveComponent,
46964
46964
  equal,
46965
46965
  serialize,
@@ -52535,9 +52535,9 @@ var require_dispatcher_base2 = __commonJS({
52535
52535
  }
52536
52536
  close(callback) {
52537
52537
  if (callback === void 0) {
52538
- return new Promise((resolve2, reject) => {
52538
+ return new Promise((resolve3, reject) => {
52539
52539
  this.close((err, data) => {
52540
- return err ? reject(err) : resolve2(data);
52540
+ return err ? reject(err) : resolve3(data);
52541
52541
  });
52542
52542
  });
52543
52543
  }
@@ -52575,9 +52575,9 @@ var require_dispatcher_base2 = __commonJS({
52575
52575
  err = null;
52576
52576
  }
52577
52577
  if (callback === void 0) {
52578
- return new Promise((resolve2, reject) => {
52578
+ return new Promise((resolve3, reject) => {
52579
52579
  this.destroy(err, (err2, data) => {
52580
- return err2 ? reject(err2) : resolve2(data);
52580
+ return err2 ? reject(err2) : resolve3(data);
52581
52581
  });
52582
52582
  });
52583
52583
  }
@@ -56053,8 +56053,8 @@ var require_promise = __commonJS({
56053
56053
  function createDeferredPromise() {
56054
56054
  let res;
56055
56055
  let rej;
56056
- const promise2 = new Promise((resolve2, reject) => {
56057
- res = resolve2;
56056
+ const promise2 = new Promise((resolve3, reject) => {
56057
+ res = resolve3;
56058
56058
  rej = reject;
56059
56059
  });
56060
56060
  return { promise: promise2, resolve: res, reject: rej };
@@ -57352,12 +57352,12 @@ upgrade: ${upgrade}\r
57352
57352
  cb();
57353
57353
  }
57354
57354
  }
57355
- const waitForDrain = () => new Promise((resolve2, reject) => {
57355
+ const waitForDrain = () => new Promise((resolve3, reject) => {
57356
57356
  assert3(callback === null);
57357
57357
  if (socket[kError]) {
57358
57358
  reject(socket[kError]);
57359
57359
  } else {
57360
- callback = resolve2;
57360
+ callback = resolve3;
57361
57361
  }
57362
57362
  });
57363
57363
  socket.on("close", onDrain).on("drain", onDrain);
@@ -58198,12 +58198,12 @@ var require_client_h2 = __commonJS({
58198
58198
  cb();
58199
58199
  }
58200
58200
  }
58201
- const waitForDrain = () => new Promise((resolve2, reject) => {
58201
+ const waitForDrain = () => new Promise((resolve3, reject) => {
58202
58202
  assert3(callback === null);
58203
58203
  if (socket[kError]) {
58204
58204
  reject(socket[kError]);
58205
58205
  } else {
58206
- callback = resolve2;
58206
+ callback = resolve3;
58207
58207
  }
58208
58208
  });
58209
58209
  h2stream.on("close", onDrain).on("drain", onDrain);
@@ -58511,16 +58511,16 @@ var require_client2 = __commonJS({
58511
58511
  return this[kNeedDrain] < 2;
58512
58512
  }
58513
58513
  [kClose]() {
58514
- return new Promise((resolve2) => {
58514
+ return new Promise((resolve3) => {
58515
58515
  if (this[kSize]) {
58516
- this[kClosedResolve] = resolve2;
58516
+ this[kClosedResolve] = resolve3;
58517
58517
  } else {
58518
- resolve2(null);
58518
+ resolve3(null);
58519
58519
  }
58520
58520
  });
58521
58521
  }
58522
58522
  [kDestroy](err) {
58523
- return new Promise((resolve2) => {
58523
+ return new Promise((resolve3) => {
58524
58524
  const requests = this[kQueue].splice(this[kPendingIdx]);
58525
58525
  for (let i = 0; i < requests.length; i++) {
58526
58526
  const request2 = requests[i];
@@ -58531,7 +58531,7 @@ var require_client2 = __commonJS({
58531
58531
  this[kClosedResolve]();
58532
58532
  this[kClosedResolve] = null;
58533
58533
  }
58534
- resolve2(null);
58534
+ resolve3(null);
58535
58535
  };
58536
58536
  if (this[kHTTPContext]) {
58537
58537
  this[kHTTPContext].destroy(err, callback);
@@ -58928,8 +58928,8 @@ var require_pool_base2 = __commonJS({
58928
58928
  }
58929
58929
  return Promise.all(closeAll);
58930
58930
  } else {
58931
- return new Promise((resolve2) => {
58932
- this[kClosedResolve] = resolve2;
58931
+ return new Promise((resolve3) => {
58932
+ this[kClosedResolve] = resolve3;
58933
58933
  });
58934
58934
  }
58935
58935
  }
@@ -60458,7 +60458,7 @@ var require_readable2 = __commonJS({
60458
60458
  if (this._readableState.closeEmitted) {
60459
60459
  return Promise.resolve(null);
60460
60460
  }
60461
- return new Promise((resolve2, reject) => {
60461
+ return new Promise((resolve3, reject) => {
60462
60462
  if (this[kContentLength] && this[kContentLength] > limit || this[kBytesRead] > limit) {
60463
60463
  this.destroy(new AbortError2());
60464
60464
  }
@@ -60472,11 +60472,11 @@ var require_readable2 = __commonJS({
60472
60472
  if (signal.aborted) {
60473
60473
  reject(signal.reason ?? new AbortError2());
60474
60474
  } else {
60475
- resolve2(null);
60475
+ resolve3(null);
60476
60476
  }
60477
60477
  });
60478
60478
  } else {
60479
- this.on("close", resolve2);
60479
+ this.on("close", resolve3);
60480
60480
  }
60481
60481
  this.on("error", noop4).on("data", () => {
60482
60482
  if (this[kBytesRead] > limit) {
@@ -60504,7 +60504,7 @@ var require_readable2 = __commonJS({
60504
60504
  }
60505
60505
  function consume(stream, type2) {
60506
60506
  assert3(!stream[kConsume]);
60507
- return new Promise((resolve2, reject) => {
60507
+ return new Promise((resolve3, reject) => {
60508
60508
  if (isUnusable(stream)) {
60509
60509
  const rState = stream._readableState;
60510
60510
  if (rState.destroyed && rState.closeEmitted === false) {
@@ -60519,7 +60519,7 @@ var require_readable2 = __commonJS({
60519
60519
  stream[kConsume] = {
60520
60520
  type: type2,
60521
60521
  stream,
60522
- resolve: resolve2,
60522
+ resolve: resolve3,
60523
60523
  reject,
60524
60524
  length: 0,
60525
60525
  body: []
@@ -60593,18 +60593,18 @@ var require_readable2 = __commonJS({
60593
60593
  return buffer;
60594
60594
  }
60595
60595
  function consumeEnd(consume2, encoding) {
60596
- const { type: type2, body, resolve: resolve2, stream, length } = consume2;
60596
+ const { type: type2, body, resolve: resolve3, stream, length } = consume2;
60597
60597
  try {
60598
60598
  if (type2 === "text") {
60599
- resolve2(chunksDecode(body, length, encoding));
60599
+ resolve3(chunksDecode(body, length, encoding));
60600
60600
  } else if (type2 === "json") {
60601
- resolve2(JSON.parse(chunksDecode(body, length, encoding)));
60601
+ resolve3(JSON.parse(chunksDecode(body, length, encoding)));
60602
60602
  } else if (type2 === "arrayBuffer") {
60603
- resolve2(chunksConcat(body, length).buffer);
60603
+ resolve3(chunksConcat(body, length).buffer);
60604
60604
  } else if (type2 === "blob") {
60605
- resolve2(new Blob(body, { type: stream[kContentType] }));
60605
+ resolve3(new Blob(body, { type: stream[kContentType] }));
60606
60606
  } else if (type2 === "bytes") {
60607
- resolve2(chunksConcat(body, length));
60607
+ resolve3(chunksConcat(body, length));
60608
60608
  }
60609
60609
  consumeFinish(consume2);
60610
60610
  } catch (err) {
@@ -60794,9 +60794,9 @@ var require_api_request2 = __commonJS({
60794
60794
  };
60795
60795
  function request2(opts, callback) {
60796
60796
  if (callback === void 0) {
60797
- return new Promise((resolve2, reject) => {
60797
+ return new Promise((resolve3, reject) => {
60798
60798
  request2.call(this, opts, (err, data) => {
60799
- return err ? reject(err) : resolve2(data);
60799
+ return err ? reject(err) : resolve3(data);
60800
60800
  });
60801
60801
  });
60802
60802
  }
@@ -61008,9 +61008,9 @@ var require_api_stream2 = __commonJS({
61008
61008
  };
61009
61009
  function stream(opts, factory, callback) {
61010
61010
  if (callback === void 0) {
61011
- return new Promise((resolve2, reject) => {
61011
+ return new Promise((resolve3, reject) => {
61012
61012
  stream.call(this, opts, factory, (err, data) => {
61013
- return err ? reject(err) : resolve2(data);
61013
+ return err ? reject(err) : resolve3(data);
61014
61014
  });
61015
61015
  });
61016
61016
  }
@@ -61298,9 +61298,9 @@ var require_api_upgrade2 = __commonJS({
61298
61298
  };
61299
61299
  function upgrade(opts, callback) {
61300
61300
  if (callback === void 0) {
61301
- return new Promise((resolve2, reject) => {
61301
+ return new Promise((resolve3, reject) => {
61302
61302
  upgrade.call(this, opts, (err, data) => {
61303
- return err ? reject(err) : resolve2(data);
61303
+ return err ? reject(err) : resolve3(data);
61304
61304
  });
61305
61305
  });
61306
61306
  }
@@ -61393,9 +61393,9 @@ var require_api_connect2 = __commonJS({
61393
61393
  };
61394
61394
  function connect(opts, callback) {
61395
61395
  if (callback === void 0) {
61396
- return new Promise((resolve2, reject) => {
61396
+ return new Promise((resolve3, reject) => {
61397
61397
  connect.call(this, opts, (err, data) => {
61398
- return err ? reject(err) : resolve2(data);
61398
+ return err ? reject(err) : resolve3(data);
61399
61399
  });
61400
61400
  });
61401
61401
  }
@@ -62663,7 +62663,7 @@ var require_snapshot_recorder = __commonJS({
62663
62663
  "node_modules/.pnpm/undici@7.22.0/node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
62664
62664
  "use strict";
62665
62665
  var { writeFile: writeFile2, readFile, mkdir } = __require("node:fs/promises");
62666
- var { dirname: dirname3, resolve: resolve2 } = __require("node:path");
62666
+ var { dirname: dirname3, resolve: resolve3 } = __require("node:path");
62667
62667
  var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("node:timers");
62668
62668
  var { InvalidArgumentError, UndiciError } = require_errors4();
62669
62669
  var { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require_snapshot_utils();
@@ -62864,7 +62864,7 @@ var require_snapshot_recorder = __commonJS({
62864
62864
  throw new InvalidArgumentError("Snapshot path is required");
62865
62865
  }
62866
62866
  try {
62867
- const data = await readFile(resolve2(path3), "utf8");
62867
+ const data = await readFile(resolve3(path3), "utf8");
62868
62868
  const parsed2 = JSON.parse(data);
62869
62869
  if (Array.isArray(parsed2)) {
62870
62870
  this.#snapshots.clear();
@@ -62893,7 +62893,7 @@ var require_snapshot_recorder = __commonJS({
62893
62893
  if (!path3) {
62894
62894
  throw new InvalidArgumentError("Snapshot path is required");
62895
62895
  }
62896
- const resolvedPath = resolve2(path3);
62896
+ const resolvedPath = resolve3(path3);
62897
62897
  await mkdir(dirname3(resolvedPath), { recursive: true });
62898
62898
  const data = Array.from(this.#snapshots.entries()).map(([hash2, snapshot2]) => ({
62899
62899
  hash: hash2,
@@ -69470,7 +69470,7 @@ var require_fetch2 = __commonJS({
69470
69470
  function dispatch({ body }) {
69471
69471
  const url4 = requestCurrentURL(request2);
69472
69472
  const agent2 = fetchParams.controller.dispatcher;
69473
- return new Promise((resolve2, reject) => agent2.dispatch(
69473
+ return new Promise((resolve3, reject) => agent2.dispatch(
69474
69474
  {
69475
69475
  path: url4.pathname + url4.search,
69476
69476
  origin: url4.origin,
@@ -69550,7 +69550,7 @@ var require_fetch2 = __commonJS({
69550
69550
  }
69551
69551
  }
69552
69552
  const onError = this.onError.bind(this);
69553
- resolve2({
69553
+ resolve3({
69554
69554
  status,
69555
69555
  statusText,
69556
69556
  headersList,
@@ -69603,7 +69603,7 @@ var require_fetch2 = __commonJS({
69603
69603
  headersList.append(headerName, String(value2), true);
69604
69604
  }
69605
69605
  }
69606
- resolve2({
69606
+ resolve3({
69607
69607
  status,
69608
69608
  statusText: STATUS_CODES[status],
69609
69609
  headersList,
@@ -69619,7 +69619,7 @@ var require_fetch2 = __commonJS({
69619
69619
  for (let i = 0; i < rawHeaders.length; i += 2) {
69620
69620
  headersList.append(bufferToLowerCasedHeaderName(rawHeaders[i]), rawHeaders[i + 1].toString("latin1"), true);
69621
69621
  }
69622
- resolve2({
69622
+ resolve3({
69623
69623
  status,
69624
69624
  statusText: STATUS_CODES[status],
69625
69625
  headersList,
@@ -97475,14 +97475,14 @@ var require_turndown_cjs = __commonJS({
97475
97475
  } else if (node2.nodeType === 1) {
97476
97476
  replacement = replacementForNode.call(self2, node2);
97477
97477
  }
97478
- return join14(output, replacement);
97478
+ return join15(output, replacement);
97479
97479
  }, "");
97480
97480
  }
97481
97481
  function postProcess(output) {
97482
97482
  var self2 = this;
97483
97483
  this.rules.forEach(function(rule) {
97484
97484
  if (typeof rule.append === "function") {
97485
- output = join14(output, rule.append(self2.options));
97485
+ output = join15(output, rule.append(self2.options));
97486
97486
  }
97487
97487
  });
97488
97488
  return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
@@ -97494,7 +97494,7 @@ var require_turndown_cjs = __commonJS({
97494
97494
  if (whitespace.leading || whitespace.trailing) content = content.trim();
97495
97495
  return whitespace.leading + rule.replacement(content, node2, this.options) + whitespace.trailing;
97496
97496
  }
97497
- function join14(output, replacement) {
97497
+ function join15(output, replacement) {
97498
97498
  var s1 = trimTrailingNewlines(output);
97499
97499
  var s2 = trimLeadingNewlines(replacement);
97500
97500
  var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
@@ -97961,8 +97961,8 @@ var require_light = __commonJS({
97961
97961
  return this.Promise.resolve();
97962
97962
  }
97963
97963
  yieldLoop(t = 0) {
97964
- return new this.Promise(function(resolve2, reject) {
97965
- return setTimeout(resolve2, t);
97964
+ return new this.Promise(function(resolve3, reject) {
97965
+ return setTimeout(resolve3, t);
97966
97966
  });
97967
97967
  }
97968
97968
  computePenalty() {
@@ -98173,15 +98173,15 @@ var require_light = __commonJS({
98173
98173
  return this._queue.length === 0;
98174
98174
  }
98175
98175
  async _tryToRun() {
98176
- var args2, cb, error49, reject, resolve2, returned, task;
98176
+ var args2, cb, error49, reject, resolve3, returned, task;
98177
98177
  if (this._running < 1 && this._queue.length > 0) {
98178
98178
  this._running++;
98179
- ({ task, args: args2, resolve: resolve2, reject } = this._queue.shift());
98179
+ ({ task, args: args2, resolve: resolve3, reject } = this._queue.shift());
98180
98180
  cb = await (async function() {
98181
98181
  try {
98182
98182
  returned = await task(...args2);
98183
98183
  return function() {
98184
- return resolve2(returned);
98184
+ return resolve3(returned);
98185
98185
  };
98186
98186
  } catch (error1) {
98187
98187
  error49 = error1;
@@ -98196,13 +98196,13 @@ var require_light = __commonJS({
98196
98196
  }
98197
98197
  }
98198
98198
  schedule(task, ...args2) {
98199
- var promise2, reject, resolve2;
98200
- resolve2 = reject = null;
98199
+ var promise2, reject, resolve3;
98200
+ resolve3 = reject = null;
98201
98201
  promise2 = new this.Promise(function(_resolve, _reject) {
98202
- resolve2 = _resolve;
98202
+ resolve3 = _resolve;
98203
98203
  return reject = _reject;
98204
98204
  });
98205
- this._queue.push({ task, args: args2, resolve: resolve2, reject });
98205
+ this._queue.push({ task, args: args2, resolve: resolve3, reject });
98206
98206
  this._tryToRun();
98207
98207
  return promise2;
98208
98208
  }
@@ -98603,14 +98603,14 @@ var require_light = __commonJS({
98603
98603
  counts = this._states.counts;
98604
98604
  return counts[0] + counts[1] + counts[2] + counts[3] === at;
98605
98605
  };
98606
- return new this.Promise((resolve2, reject) => {
98606
+ return new this.Promise((resolve3, reject) => {
98607
98607
  if (finished()) {
98608
- return resolve2();
98608
+ return resolve3();
98609
98609
  } else {
98610
98610
  return this.on("done", () => {
98611
98611
  if (finished()) {
98612
98612
  this.removeAllListeners("done");
98613
- return resolve2();
98613
+ return resolve3();
98614
98614
  }
98615
98615
  });
98616
98616
  }
@@ -98703,9 +98703,9 @@ var require_light = __commonJS({
98703
98703
  options = parser$5.load(options, this.jobDefaults);
98704
98704
  }
98705
98705
  task = (...args3) => {
98706
- return new this.Promise(function(resolve2, reject) {
98706
+ return new this.Promise(function(resolve3, reject) {
98707
98707
  return fn2(...args3, function(...args4) {
98708
- return (args4[0] != null ? reject : resolve2)(args4);
98708
+ return (args4[0] != null ? reject : resolve3)(args4);
98709
98709
  });
98710
98710
  });
98711
98711
  };
@@ -98925,6 +98925,8 @@ var require_fast_content_type_parse = __commonJS({
98925
98925
 
98926
98926
  // main.ts
98927
98927
  var core6 = __toESM(require_core(), 1);
98928
+ import { existsSync as existsSync6, readdirSync } from "node:fs";
98929
+ import { join as join14 } from "node:path";
98928
98930
 
98929
98931
  // node_modules/.pnpm/@ark+util@0.56.0/node_modules/@ark/util/out/arrays.js
98930
98932
  var liftArray = (data) => Array.isArray(data) ? data : [data];
@@ -105243,10 +105245,10 @@ var BaseScope = class {
105243
105245
  });
105244
105246
  };
105245
105247
  lazyResolutions = [];
105246
- lazilyResolve(resolve2, syntheticAlias) {
105248
+ lazilyResolve(resolve3, syntheticAlias) {
105247
105249
  const node2 = this.node("alias", {
105248
105250
  reference: syntheticAlias ?? "synthetic",
105249
- resolve: resolve2
105251
+ resolve: resolve3
105250
105252
  }, { prereduced: true });
105251
105253
  if (!this.resolved)
105252
105254
  this.lazyResolutions.push(node2);
@@ -107396,6 +107398,81 @@ var core = __toESM(require_core(), 1);
107396
107398
  var import_table = __toESM(require_src(), 1);
107397
107399
  import { AsyncLocalStorage } from "node:async_hooks";
107398
107400
 
107401
+ // agents/shared.ts
107402
+ import { execFileSync } from "node:child_process";
107403
+ var MAX_STDERR_LINES = 20;
107404
+ var MAX_COMMIT_RETRIES = 3;
107405
+ function getGitStatus() {
107406
+ try {
107407
+ return execFileSync("git", ["status", "--porcelain"], {
107408
+ encoding: "utf-8",
107409
+ timeout: 1e4
107410
+ }).trim();
107411
+ } catch {
107412
+ return "";
107413
+ }
107414
+ }
107415
+ function buildCommitPrompt(_agentId, status) {
107416
+ return [
107417
+ `UNCOMMITTED CHANGES \u2014 the working tree is dirty. push all changes to a pull request (new or existing). \`git status\` must be clean before you finish.`,
107418
+ "",
107419
+ "```",
107420
+ status,
107421
+ "```"
107422
+ ].join("\n");
107423
+ }
107424
+ var agent = (input) => {
107425
+ return {
107426
+ ...input,
107427
+ run: async (ctx) => {
107428
+ log.debug(`\xBB payload: ${JSON.stringify(ctx.payload, null, 2)}`);
107429
+ return input.run(ctx);
107430
+ }
107431
+ };
107432
+ };
107433
+ function formatCostUsd(costUsd) {
107434
+ return costUsd.toFixed(4);
107435
+ }
107436
+ function mergeAgentUsage(a, b) {
107437
+ if (!a && !b) return void 0;
107438
+ if (!a) return { ...b };
107439
+ if (!b) return { ...a };
107440
+ const cacheRead = (a.cacheReadTokens ?? 0) + (b.cacheReadTokens ?? 0);
107441
+ const cacheWrite = (a.cacheWriteTokens ?? 0) + (b.cacheWriteTokens ?? 0);
107442
+ const cost = (a.costUsd ?? 0) + (b.costUsd ?? 0);
107443
+ return {
107444
+ agent: a.agent,
107445
+ inputTokens: a.inputTokens + b.inputTokens,
107446
+ outputTokens: a.outputTokens + b.outputTokens,
107447
+ cacheReadTokens: cacheRead > 0 ? cacheRead : void 0,
107448
+ cacheWriteTokens: cacheWrite > 0 ? cacheWrite : void 0,
107449
+ costUsd: cost > 0 ? cost : void 0
107450
+ };
107451
+ }
107452
+ function logTokenTable(t) {
107453
+ const total = t.input + t.cacheRead + t.cacheWrite + t.output;
107454
+ const costUsd = typeof t.costUsd === "number" && t.costUsd > 0 ? t.costUsd : void 0;
107455
+ const headerRow = [
107456
+ { data: "Input", header: true },
107457
+ { data: "Cache Read", header: true },
107458
+ { data: "Cache Write", header: true },
107459
+ { data: "Output", header: true },
107460
+ { data: "Total", header: true }
107461
+ ];
107462
+ const dataRow = [
107463
+ String(t.input),
107464
+ String(t.cacheRead),
107465
+ String(t.cacheWrite),
107466
+ String(t.output),
107467
+ String(total)
107468
+ ];
107469
+ if (costUsd !== void 0) {
107470
+ headerRow.push({ data: "Cost ($)", header: true });
107471
+ dataRow.push(formatCostUsd(costUsd));
107472
+ }
107473
+ log.table([headerRow, dataRow]);
107474
+ }
107475
+
107399
107476
  // utils/globals.ts
107400
107477
  import { existsSync } from "node:fs";
107401
107478
  var isCloudflareSandbox = !!process.env.CLOUDFLARE_APPLICATION_ID && !!process.env.SANDBOX_VERSION;
@@ -107592,20 +107669,26 @@ function formatJsonValue(value2) {
107592
107669
  }
107593
107670
  function formatUsageSummary(entries) {
107594
107671
  if (entries.length === 0) return "";
107595
- const header = "| Agent | Input | Output | Cache Read | Cache Write |";
107596
- const separatorRow = "| --- | ---: | ---: | ---: | ---: |";
107672
+ const header = "| Agent | Input | Cache Read | Cache Write | Output | Total | Cost ($) |";
107673
+ const separatorRow = "| --- | ---: | ---: | ---: | ---: | ---: | ---: |";
107597
107674
  const fmt = (n) => n.toLocaleString("en-US");
107675
+ const nonCachedInput = (e) => Math.max(0, e.inputTokens - (e.cacheReadTokens ?? 0) - (e.cacheWriteTokens ?? 0));
107676
+ const totalFor = (e) => nonCachedInput(e) + (e.cacheReadTokens ?? 0) + (e.cacheWriteTokens ?? 0) + e.outputTokens;
107677
+ const costCell = (e) => typeof e.costUsd === "number" && e.costUsd > 0 ? formatCostUsd(e.costUsd) : "\u2014";
107598
107678
  const rows = entries.map(
107599
- (e) => `| ${e.agent} | ${fmt(e.inputTokens)} | ${fmt(e.outputTokens)} | ${fmt(e.cacheReadTokens ?? 0)} | ${fmt(e.cacheWriteTokens ?? 0)} |`
107679
+ (e) => `| ${e.agent} | ${fmt(nonCachedInput(e))} | ${fmt(e.cacheReadTokens ?? 0)} | ${fmt(e.cacheWriteTokens ?? 0)} | ${fmt(e.outputTokens)} | ${fmt(totalFor(e))} | ${costCell(e)} |`
107600
107680
  );
107601
107681
  const totalsRows = [];
107602
107682
  if (entries.length > 1) {
107603
- const totalInput = entries.reduce((sum, e) => sum + e.inputTokens, 0);
107683
+ const totalInput = entries.reduce((sum, e) => sum + nonCachedInput(e), 0);
107604
107684
  const totalOutput = entries.reduce((sum, e) => sum + e.outputTokens, 0);
107605
107685
  const totalCacheRead = entries.reduce((sum, e) => sum + (e.cacheReadTokens ?? 0), 0);
107606
107686
  const totalCacheWrite = entries.reduce((sum, e) => sum + (e.cacheWriteTokens ?? 0), 0);
107687
+ const grandTotal = totalInput + totalCacheRead + totalCacheWrite + totalOutput;
107688
+ const totalCostUsd = entries.reduce((sum, e) => sum + (e.costUsd ?? 0), 0);
107689
+ const totalCostCell = totalCostUsd > 0 ? `**${formatCostUsd(totalCostUsd)}**` : "\u2014";
107607
107690
  totalsRows.push(
107608
- `| **Total** | **${fmt(totalInput)}** | **${fmt(totalOutput)}** | **${fmt(totalCacheRead)}** | **${fmt(totalCacheWrite)}** |`
107691
+ `| **Total** | **${fmt(totalInput)}** | **${fmt(totalCacheRead)}** | **${fmt(totalCacheWrite)}** | **${fmt(totalOutput)}** | **${fmt(grandTotal)}** | ${totalCostCell} |`
107609
107692
  );
107610
107693
  }
107611
107694
  return [
@@ -107648,7 +107731,7 @@ var providers = {
107648
107731
  models: {
107649
107732
  "claude-opus": {
107650
107733
  displayName: "Claude Opus",
107651
- resolve: "anthropic/claude-opus-4-6",
107734
+ resolve: "anthropic/claude-opus-4-7",
107652
107735
  openRouterResolve: "openrouter/anthropic/claude-opus-4.6",
107653
107736
  preferred: true
107654
107737
  },
@@ -107676,7 +107759,7 @@ var providers = {
107676
107759
  },
107677
107760
  "gpt-codex-mini": {
107678
107761
  displayName: "GPT Codex Mini",
107679
- resolve: "openai/codex-mini-latest",
107762
+ resolve: "openai/gpt-5.1-codex-mini",
107680
107763
  openRouterResolve: "openrouter/openai/gpt-5.1-codex-mini"
107681
107764
  },
107682
107765
  o3: {
@@ -107766,7 +107849,7 @@ var providers = {
107766
107849
  },
107767
107850
  "claude-opus": {
107768
107851
  displayName: "Claude Opus",
107769
- resolve: "opencode/claude-opus-4-6",
107852
+ resolve: "opencode/claude-opus-4-7",
107770
107853
  openRouterResolve: "openrouter/anthropic/claude-opus-4.6"
107771
107854
  },
107772
107855
  "claude-sonnet": {
@@ -108044,22 +108127,35 @@ async function retry(fn2, options = {}) {
108044
108127
  }
108045
108128
 
108046
108129
  // utils/patchWorkflowRunFields.ts
108047
- var ARTIFACT_PATCH_KEYS = [
108130
+ var STRING_KEYS = [
108048
108131
  "prNodeId",
108049
108132
  "issueNodeId",
108050
108133
  "reviewNodeId",
108051
108134
  "planCommentNodeId",
108052
108135
  "summaryCommentNodeId"
108053
108136
  ];
108137
+ var NUMBER_KEYS = [
108138
+ "inputTokens",
108139
+ "outputTokens",
108140
+ "cacheReadTokens",
108141
+ "cacheWriteTokens",
108142
+ "costUsd"
108143
+ ];
108054
108144
  async function patchWorkflowRunFields(ctx, fields) {
108055
108145
  if (ctx.runId === void 0 || !ctx.apiToken) return;
108056
108146
  const body = {};
108057
- for (const key of ARTIFACT_PATCH_KEYS) {
108147
+ for (const key of STRING_KEYS) {
108058
108148
  const value2 = fields[key];
108059
108149
  if (typeof value2 === "string" && value2.length > 0) {
108060
108150
  body[key] = value2;
108061
108151
  }
108062
108152
  }
108153
+ for (const key of NUMBER_KEYS) {
108154
+ const value2 = fields[key];
108155
+ if (typeof value2 === "number" && Number.isFinite(value2) && value2 >= 0) {
108156
+ body[key] = value2;
108157
+ }
108158
+ }
108063
108159
  if (Object.keys(body).length === 0) return;
108064
108160
  try {
108065
108161
  await retry(
@@ -108086,6 +108182,38 @@ async function patchWorkflowRunFields(ctx, fields) {
108086
108182
  log.warning(`patchWorkflowRunFields exhausted retries: ${error49}`);
108087
108183
  }
108088
108184
  }
108185
+ var INT4_MAX = 2147483647;
108186
+ function clampInt(value2, field) {
108187
+ if (value2 > INT4_MAX) {
108188
+ log.warning(
108189
+ `aggregateUsage: ${field}=${value2} exceeds INT4_MAX (${INT4_MAX}) \u2014 clamping so the rest of the usage row still persists.`
108190
+ );
108191
+ return INT4_MAX;
108192
+ }
108193
+ return value2;
108194
+ }
108195
+ function aggregateUsage(entries) {
108196
+ if (entries.length === 0) return {};
108197
+ const sum = entries.reduce(
108198
+ (acc, e) => ({
108199
+ inputTokens: acc.inputTokens + e.inputTokens,
108200
+ outputTokens: acc.outputTokens + e.outputTokens,
108201
+ cacheReadTokens: acc.cacheReadTokens + (e.cacheReadTokens ?? 0),
108202
+ cacheWriteTokens: acc.cacheWriteTokens + (e.cacheWriteTokens ?? 0),
108203
+ costUsd: acc.costUsd + (e.costUsd ?? 0)
108204
+ }),
108205
+ { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costUsd: 0 }
108206
+ );
108207
+ const out = {};
108208
+ if (sum.inputTokens > 0) out.inputTokens = clampInt(sum.inputTokens, "inputTokens");
108209
+ if (sum.outputTokens > 0) out.outputTokens = clampInt(sum.outputTokens, "outputTokens");
108210
+ if (sum.cacheReadTokens > 0)
108211
+ out.cacheReadTokens = clampInt(sum.cacheReadTokens, "cacheReadTokens");
108212
+ if (sum.cacheWriteTokens > 0)
108213
+ out.cacheWriteTokens = clampInt(sum.cacheWriteTokens, "cacheWriteTokens");
108214
+ if (sum.costUsd > 0) out.costUsd = sum.costUsd;
108215
+ return out;
108216
+ }
108089
108217
 
108090
108218
  // node_modules/.pnpm/@toon-format+toon@1.4.0/node_modules/@toon-format/toon/dist/index.mjs
108091
108219
  var LIST_ITEM_MARKER = "-";
@@ -108442,6 +108570,126 @@ function resolveOptions(options) {
108442
108570
  };
108443
108571
  }
108444
108572
 
108573
+ // mcp/geminiSanitizer.ts
108574
+ function parseStringEnumBranch(item) {
108575
+ if (!item || typeof item !== "object") return null;
108576
+ const record3 = item;
108577
+ if (Array.isArray(record3.enum)) {
108578
+ const strings = record3.enum.filter((v) => typeof v === "string");
108579
+ return strings.length === record3.enum.length && strings.length > 0 ? { values: strings } : null;
108580
+ }
108581
+ if (typeof record3.const === "string") {
108582
+ return { values: [record3.const] };
108583
+ }
108584
+ return null;
108585
+ }
108586
+ function collapseStringUnion(branches) {
108587
+ const values = [];
108588
+ for (const item of branches) {
108589
+ const parsed2 = parseStringEnumBranch(item);
108590
+ if (!parsed2) return null;
108591
+ values.push(...parsed2.values);
108592
+ }
108593
+ if (values.length === 0) return null;
108594
+ return { type: "string", enum: [...new Set(values)] };
108595
+ }
108596
+ function sanitizeForGemini(schema2) {
108597
+ if (!schema2 || typeof schema2 !== "object") return schema2;
108598
+ if (Array.isArray(schema2)) return schema2.map(sanitizeForGemini);
108599
+ const source = schema2;
108600
+ if (Array.isArray(source.enum) && typeof source.type !== "string") {
108601
+ const allStrings = source.enum.every((v) => typeof v === "string");
108602
+ if (allStrings) {
108603
+ const result = { type: "string", enum: source.enum };
108604
+ if (typeof source.description === "string") result.description = source.description;
108605
+ return result;
108606
+ }
108607
+ }
108608
+ for (const unionKey of ["anyOf", "oneOf"]) {
108609
+ const branches = source[unionKey];
108610
+ if (Array.isArray(branches) && branches.length > 0) {
108611
+ const collapsed = collapseStringUnion(branches);
108612
+ if (collapsed) {
108613
+ const result = { ...collapsed };
108614
+ if (typeof source.description === "string") result.description = source.description;
108615
+ return result;
108616
+ }
108617
+ }
108618
+ }
108619
+ if (Array.isArray(source.anyOf) || Array.isArray(source.oneOf)) {
108620
+ const result = {};
108621
+ if (Array.isArray(source.anyOf)) result.anyOf = source.anyOf.map(sanitizeForGemini);
108622
+ if (Array.isArray(source.oneOf)) result.oneOf = source.oneOf.map(sanitizeForGemini);
108623
+ return result;
108624
+ }
108625
+ const sanitized = {};
108626
+ for (const [key, value2] of Object.entries(source)) {
108627
+ if (key === "$schema") continue;
108628
+ if (key === "$defs") {
108629
+ sanitized.definitions = sanitizeForGemini(value2);
108630
+ continue;
108631
+ }
108632
+ sanitized[key] = sanitizeForGemini(value2);
108633
+ }
108634
+ return sanitized;
108635
+ }
108636
+ function wrapJsonSchemaProducer(producer) {
108637
+ return new Proxy(producer, {
108638
+ get(target, prop, receiver) {
108639
+ const value2 = Reflect.get(target, prop, receiver);
108640
+ if ((prop === "input" || prop === "output") && typeof value2 === "function") {
108641
+ const fn2 = value2;
108642
+ return (...args2) => sanitizeForGemini(fn2.apply(target, args2));
108643
+ }
108644
+ return value2;
108645
+ }
108646
+ });
108647
+ }
108648
+ function wrapStandard(standard) {
108649
+ return new Proxy(standard, {
108650
+ get(target, prop, receiver) {
108651
+ if (prop === "jsonSchema") {
108652
+ const value2 = Reflect.get(target, prop, receiver);
108653
+ if (value2 && typeof value2 === "object") {
108654
+ return wrapJsonSchemaProducer(value2);
108655
+ }
108656
+ return value2;
108657
+ }
108658
+ return Reflect.get(target, prop, receiver);
108659
+ }
108660
+ });
108661
+ }
108662
+ function wrapSchemaForGemini(schema2) {
108663
+ return new Proxy(schema2, {
108664
+ get(target, prop, receiver) {
108665
+ if (prop === "~standard") {
108666
+ const value2 = Reflect.get(target, prop, receiver);
108667
+ if (value2 && typeof value2 === "object") {
108668
+ return wrapStandard(value2);
108669
+ }
108670
+ return value2;
108671
+ }
108672
+ if (prop === "toJsonSchema") {
108673
+ const method = Reflect.get(target, prop, receiver);
108674
+ if (typeof method === "function") {
108675
+ return () => sanitizeForGemini(method.call(target));
108676
+ }
108677
+ return method;
108678
+ }
108679
+ return Reflect.get(target, prop, receiver);
108680
+ }
108681
+ });
108682
+ }
108683
+ function sanitizeToolForGemini(tool2) {
108684
+ if (!tool2.parameters) return tool2;
108685
+ return { ...tool2, parameters: wrapSchemaForGemini(tool2.parameters) };
108686
+ }
108687
+ function isGeminiRouted(ctx) {
108688
+ const effective = ctx.payload.proxyModel ?? ctx.resolvedModel ?? ctx.payload.model;
108689
+ if (!effective) return false;
108690
+ return effective.toLowerCase().includes("gemini");
108691
+ }
108692
+
108445
108693
  // mcp/shared.ts
108446
108694
  var tool = (toolDef) => toolDef;
108447
108695
  var handleToolSuccess = (data) => {
@@ -108477,9 +108725,10 @@ var execute = (fn2, toolName) => {
108477
108725
  };
108478
108726
  return _fn;
108479
108727
  };
108480
- var addTools = (_ctx, server, tools) => {
108728
+ var addTools = (ctx, server, tools) => {
108729
+ const shouldSanitize = isGeminiRouted(ctx);
108481
108730
  for (const tool2 of tools) {
108482
- server.addTool(tool2);
108731
+ server.addTool(shouldSanitize ? sanitizeToolForGemini(tool2) : tool2);
108483
108732
  }
108484
108733
  return server;
108485
108734
  };
@@ -108728,8 +108977,9 @@ function ReportProgressTool(ctx) {
108728
108977
  if (!params.target_plan_comment && ctx.toolState.todoTracker) {
108729
108978
  ctx.toolState.todoTracker.cancel();
108730
108979
  await ctx.toolState.todoTracker.settled();
108731
- ctx.toolState.todoTracker.completeInProgress();
108732
- const collapsible = ctx.toolState.todoTracker.renderCollapsible();
108980
+ const collapsible = ctx.toolState.todoTracker.renderCollapsible({
108981
+ completeInProgress: true
108982
+ });
108733
108983
  if (collapsible) {
108734
108984
  body = `${body}
108735
108985
 
@@ -109116,8 +109366,27 @@ import { performance as performance3 } from "node:perf_hooks";
109116
109366
 
109117
109367
  // utils/activity.ts
109118
109368
  import { performance as performance2 } from "node:perf_hooks";
109369
+ function isMonitorDebugEnabled() {
109370
+ return process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || process.env.LOG_LEVEL === "debug";
109371
+ }
109119
109372
  var DEFAULT_ACTIVITY_TIMEOUT_MS = 3e5;
109120
109373
  var DEFAULT_ACTIVITY_CHECK_INTERVAL_MS = 5e3;
109374
+ var DEBUG_TS_PREFIX = /^(?:\[\d{4}-\d{2}-\d{2}T[^\]]+\]\s+)?/.source;
109375
+ var ACTIVITY_NOISE_PATTERNS = [
109376
+ new RegExp(`${DEBUG_TS_PREFIX}\\[mcp-proxy\\]`),
109377
+ new RegExp(`${DEBUG_TS_PREFIX}\xBB provider error detected`),
109378
+ new RegExp(`${DEBUG_TS_PREFIX}\\[DEBUG\\]\\s+(?:spawn|process) activity `),
109379
+ /^::debug::(?:spawn|process) activity /
109380
+ ];
109381
+ function isActivityNoise(chunk) {
109382
+ const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
109383
+ if (!text.trim()) return true;
109384
+ return text.split("\n").every((line) => {
109385
+ const trimmed = line.trim();
109386
+ if (!trimmed) return true;
109387
+ return ACTIVITY_NOISE_PATTERNS.some((pattern) => pattern.test(trimmed));
109388
+ });
109389
+ }
109121
109390
  var _lastActivity = performance2.now();
109122
109391
  function markActivity() {
109123
109392
  _lastActivity = performance2.now();
@@ -109127,7 +109396,9 @@ function getIdleMs() {
109127
109396
  }
109128
109397
  function wrapWrite(original, onActivity) {
109129
109398
  const wrapped = (chunk, encodingOrCb, cb) => {
109130
- onActivity();
109399
+ if (!isActivityNoise(chunk)) {
109400
+ onActivity();
109401
+ }
109131
109402
  if (typeof encodingOrCb === "function") {
109132
109403
  return original(chunk, encodingOrCb);
109133
109404
  }
@@ -109141,10 +109412,15 @@ function startProcessOutputMonitor(ctx) {
109141
109412
  const originalStderrWrite = process.stderr.write.bind(process.stderr);
109142
109413
  process.stdout.write = wrapWrite(originalStdoutWrite, markActivity);
109143
109414
  process.stderr.write = wrapWrite(originalStderrWrite, markActivity);
109144
- log.debug(`process activity monitor started: timeout=${ctx.timeoutMs}ms`);
109415
+ const debugBypass = (msg) => {
109416
+ if (!isMonitorDebugEnabled()) return;
109417
+ originalStdoutWrite(`[${(/* @__PURE__ */ new Date()).toISOString()}] [DEBUG] ${msg}
109418
+ `);
109419
+ };
109420
+ debugBypass(`process activity monitor started: timeout=${ctx.timeoutMs}ms`);
109145
109421
  const intervalId = setInterval(() => {
109146
109422
  const idleMs = getIdleMs();
109147
- log.debug(`process activity check: idle=${idleMs}ms / ${ctx.timeoutMs}ms`);
109423
+ debugBypass(`process activity check: idle=${idleMs}ms / ${ctx.timeoutMs}ms`);
109148
109424
  if (timedOut || idleMs <= ctx.timeoutMs) return;
109149
109425
  timedOut = true;
109150
109426
  ctx.onTimeout(idleMs);
@@ -109172,12 +109448,26 @@ function createProcessOutputActivityTimeout(ctx) {
109172
109448
  if (monitor) {
109173
109449
  monitor.stop();
109174
109450
  }
109175
- rejectFn(new Error(`activity timeout: no output for ${idleSec}s`));
109451
+ const reject = rejectFn;
109452
+ rejectFn = null;
109453
+ reject(new Error(`activity timeout: no output for ${idleSec}s`));
109176
109454
  }
109177
109455
  });
109178
109456
  return {
109179
109457
  promise: promise2,
109180
- stop: monitor.stop
109458
+ // stop() also disarms forceReject so a late safety-net fire can't reject
109459
+ // the promise after the run has already succeeded.
109460
+ stop: () => {
109461
+ monitor?.stop();
109462
+ rejectFn = null;
109463
+ },
109464
+ forceReject: (reason) => {
109465
+ if (!rejectFn) return;
109466
+ monitor?.stop();
109467
+ const reject = rejectFn;
109468
+ rejectFn = null;
109469
+ reject(new Error(reason));
109470
+ }
109181
109471
  };
109182
109472
  }
109183
109473
 
@@ -109207,6 +109497,16 @@ function exitWithSignal(signal) {
109207
109497
  }
109208
109498
 
109209
109499
  // utils/subprocess.ts
109500
+ var SPAWN_TIMEOUT_CODE = "E_SPAWN_TIMEOUT";
109501
+ var SPAWN_ACTIVITY_TIMEOUT_CODE = "E_SPAWN_ACTIVITY_TIMEOUT";
109502
+ var SpawnTimeoutError = class extends Error {
109503
+ code;
109504
+ constructor(message, code) {
109505
+ super(message);
109506
+ this.name = "SpawnTimeoutError";
109507
+ this.code = code;
109508
+ }
109509
+ };
109210
109510
  var activeChildren = /* @__PURE__ */ new Map();
109211
109511
  var externalSignalHandler = null;
109212
109512
  function trackChild(options) {
@@ -109252,7 +109552,7 @@ async function spawn(options) {
109252
109552
  const startTime = performance3.now();
109253
109553
  let stdoutBuffer = "";
109254
109554
  let stderrBuffer = "";
109255
- return new Promise((resolve2, reject) => {
109555
+ return new Promise((resolve3, reject) => {
109256
109556
  const child = nodeSpawn(options.cmd, options.args, {
109257
109557
  env: options.env || {
109258
109558
  PATH: process.env.PATH || "",
@@ -109263,15 +109563,17 @@ async function spawn(options) {
109263
109563
  });
109264
109564
  trackChild({ child });
109265
109565
  let timeoutId;
109566
+ let sigkillEscalatorId;
109266
109567
  let activityCheckIntervalId;
109267
109568
  let isTimedOut = false;
109268
109569
  let isActivityTimedOut = false;
109269
109570
  let lastActivityTime = performance3.now();
109571
+ let killedAtIdleMs;
109270
109572
  if (options.timeout) {
109271
109573
  timeoutId = setTimeout(() => {
109272
109574
  isTimedOut = true;
109273
109575
  child.kill("SIGTERM");
109274
- setTimeout(() => {
109576
+ sigkillEscalatorId = setTimeout(() => {
109275
109577
  if (!child.killed) {
109276
109578
  child.kill("SIGKILL");
109277
109579
  }
@@ -109289,12 +109591,20 @@ async function spawn(options) {
109289
109591
  );
109290
109592
  if (idleMs > activityTimeoutMs) {
109291
109593
  isActivityTimedOut = true;
109594
+ killedAtIdleMs = idleMs;
109292
109595
  const idleSec = Math.round(idleMs / 1e3);
109293
109596
  log.info(
109294
109597
  `no output for ${idleSec}s from pid=${child.pid} (${options.cmd}), killing process`
109295
109598
  );
109296
109599
  child.kill("SIGKILL");
109297
109600
  clearInterval(activityCheckIntervalId);
109601
+ try {
109602
+ options.onActivityTimeout?.();
109603
+ } catch (err) {
109604
+ log.debug(
109605
+ `spawn onActivityTimeout handler threw: ${err instanceof Error ? err.message : String(err)}`
109606
+ );
109607
+ }
109298
109608
  }
109299
109609
  }, DEFAULT_ACTIVITY_CHECK_INTERVAL_MS);
109300
109610
  }
@@ -109316,24 +109626,41 @@ async function spawn(options) {
109316
109626
  options.onStderr?.(chunk);
109317
109627
  });
109318
109628
  }
109319
- child.on("close", (exitCode) => {
109629
+ child.on("close", (exitCode, signal) => {
109320
109630
  const durationMs = performance3.now() - startTime;
109321
109631
  untrackChild(child);
109322
109632
  if (timeoutId) clearTimeout(timeoutId);
109633
+ if (sigkillEscalatorId) clearTimeout(sigkillEscalatorId);
109323
109634
  if (activityCheckIntervalId) clearInterval(activityCheckIntervalId);
109324
109635
  if (isTimedOut) {
109325
- reject(new Error(`process timed out after ${options.timeout}ms`));
109636
+ reject(
109637
+ new SpawnTimeoutError(`process timed out after ${options.timeout}ms`, SPAWN_TIMEOUT_CODE)
109638
+ );
109326
109639
  return;
109327
109640
  }
109328
109641
  if (isActivityTimedOut) {
109329
- const idleSec = Math.round((performance3.now() - lastActivityTime) / 1e3);
109330
- reject(new Error(`activity timeout: no output for ${idleSec}s`));
109642
+ const idleMs = killedAtIdleMs ?? performance3.now() - lastActivityTime;
109643
+ const idleSec = Math.round(idleMs / 1e3);
109644
+ reject(
109645
+ new SpawnTimeoutError(
109646
+ `activity timeout: no output for ${idleSec}s`,
109647
+ SPAWN_ACTIVITY_TIMEOUT_CODE
109648
+ )
109649
+ );
109331
109650
  return;
109332
109651
  }
109333
- resolve2({
109652
+ let resolvedExitCode = exitCode ?? 0;
109653
+ let resolvedStderr = stderrBuffer;
109654
+ if (exitCode === null && signal) {
109655
+ const killMsg = `[spawn] ${options.cmd}: killed by signal ${signal}`;
109656
+ resolvedStderr = resolvedStderr ? `${resolvedStderr}
109657
+ ${killMsg}` : killMsg;
109658
+ resolvedExitCode = 1;
109659
+ }
109660
+ resolve3({
109334
109661
  stdout: stdoutBuffer,
109335
- stderr: stderrBuffer,
109336
- exitCode: exitCode || 0,
109662
+ stderr: resolvedStderr,
109663
+ exitCode: resolvedExitCode,
109337
109664
  durationMs
109338
109665
  });
109339
109666
  });
@@ -109341,9 +109668,13 @@ async function spawn(options) {
109341
109668
  const durationMs = performance3.now() - startTime;
109342
109669
  untrackChild(child);
109343
109670
  if (timeoutId) clearTimeout(timeoutId);
109671
+ if (sigkillEscalatorId) clearTimeout(sigkillEscalatorId);
109344
109672
  if (activityCheckIntervalId) clearInterval(activityCheckIntervalId);
109345
- console.error(`[spawn] process spawn error: ${error49.message}`);
109346
- resolve2({
109673
+ const errMsg = `[spawn] ${options.cmd}: ${error49.message}`;
109674
+ console.error(errMsg);
109675
+ stderrBuffer = stderrBuffer ? `${stderrBuffer}
109676
+ ${errMsg}` : errMsg;
109677
+ resolve3({
109347
109678
  stdout: stdoutBuffer,
109348
109679
  stderr: stderrBuffer,
109349
109680
  exitCode: 1,
@@ -114110,7 +114441,7 @@ var Protocol = class {
114110
114441
  return;
114111
114442
  }
114112
114443
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
114113
- await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
114444
+ await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
114114
114445
  options?.signal?.throwIfAborted();
114115
114446
  }
114116
114447
  } catch (error49) {
@@ -114127,7 +114458,7 @@ var Protocol = class {
114127
114458
  */
114128
114459
  request(request2, resultSchema, options) {
114129
114460
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
114130
- return new Promise((resolve2, reject) => {
114461
+ return new Promise((resolve3, reject) => {
114131
114462
  const earlyReject = (error49) => {
114132
114463
  reject(error49);
114133
114464
  };
@@ -114205,7 +114536,7 @@ var Protocol = class {
114205
114536
  if (!parseResult.success) {
114206
114537
  reject(parseResult.error);
114207
114538
  } else {
114208
- resolve2(parseResult.data);
114539
+ resolve3(parseResult.data);
114209
114540
  }
114210
114541
  } catch (error49) {
114211
114542
  reject(error49);
@@ -114466,12 +114797,12 @@ var Protocol = class {
114466
114797
  }
114467
114798
  } catch {
114468
114799
  }
114469
- return new Promise((resolve2, reject) => {
114800
+ return new Promise((resolve3, reject) => {
114470
114801
  if (signal.aborted) {
114471
114802
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
114472
114803
  return;
114473
114804
  }
114474
- const timeoutId = setTimeout(resolve2, interval);
114805
+ const timeoutId = setTimeout(resolve3, interval);
114475
114806
  signal.addEventListener("abort", () => {
114476
114807
  clearTimeout(timeoutId);
114477
114808
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -115341,12 +115672,12 @@ var StdioServerTransport = class {
115341
115672
  this.onclose?.();
115342
115673
  }
115343
115674
  send(message) {
115344
- return new Promise((resolve2) => {
115675
+ return new Promise((resolve3) => {
115345
115676
  const json4 = serializeMessage(message);
115346
115677
  if (this._stdout.write(json4)) {
115347
- resolve2();
115678
+ resolve3();
115348
115679
  } else {
115349
- this._stdout.once("drain", resolve2);
115680
+ this._stdout.once("drain", resolve3);
115350
115681
  }
115351
115682
  });
115352
115683
  }
@@ -136374,12 +136705,12 @@ var require_schemes2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
136374
136705
  var require_fast_uri2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
136375
136706
  const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils5();
136376
136707
  const { SCHEMES, getSchemeHandler } = require_schemes2();
136377
- function normalize2(uri$2, options) {
136708
+ function normalize3(uri$2, options) {
136378
136709
  if (typeof uri$2 === "string") uri$2 = serialize(parse5(uri$2, options), options);
136379
136710
  else if (typeof uri$2 === "object") uri$2 = parse5(serialize(uri$2, options), options);
136380
136711
  return uri$2;
136381
136712
  }
136382
- function resolve2(baseURI, relativeURI, options) {
136713
+ function resolve3(baseURI, relativeURI, options) {
136383
136714
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
136384
136715
  const resolved = resolveComponent(parse5(baseURI, schemelessOptions), parse5(relativeURI, schemelessOptions), schemelessOptions, true);
136385
136716
  schemelessOptions.skipEscape = true;
@@ -136552,8 +136883,8 @@ var require_fast_uri2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
136552
136883
  }
136553
136884
  const fastUri = {
136554
136885
  SCHEMES,
136555
- normalize: normalize2,
136556
- resolve: resolve2,
136886
+ normalize: normalize3,
136887
+ resolve: resolve3,
136557
136888
  resolveComponent,
136558
136889
  equal: equal$1,
136559
136890
  serialize,
@@ -140141,7 +140472,7 @@ ${error49 instanceof Error ? error49.stack : JSON.stringify(error49)}`
140141
140472
  new Error(`Connection is in ${this.#connectionState} state`)
140142
140473
  );
140143
140474
  }
140144
- return new Promise((resolve2, reject) => {
140475
+ return new Promise((resolve3, reject) => {
140145
140476
  const timeout = setTimeout(() => {
140146
140477
  reject(
140147
140478
  new Error(
@@ -140151,7 +140482,7 @@ ${error49 instanceof Error ? error49.stack : JSON.stringify(error49)}`
140151
140482
  }, 5e3);
140152
140483
  this.once("ready", () => {
140153
140484
  clearTimeout(timeout);
140154
- resolve2();
140485
+ resolve3();
140155
140486
  });
140156
140487
  this.once("error", (event) => {
140157
140488
  clearTimeout(timeout);
@@ -140598,7 +140929,7 @@ ${error49 instanceof Error ? error49.stack : JSON.stringify(error49)}`
140598
140929
  }
140599
140930
  });
140600
140931
  if (this.#needsEventLoopFlush) {
140601
- await new Promise((resolve2) => setImmediate(resolve2));
140932
+ await new Promise((resolve3) => setImmediate(resolve3));
140602
140933
  }
140603
140934
  } catch (progressError) {
140604
140935
  this.#logger.warn(
@@ -140656,7 +140987,7 @@ ${error49 instanceof Error ? error49.stack : JSON.stringify(error49)}`
140656
140987
  }
140657
140988
  });
140658
140989
  if (this.#needsEventLoopFlush) {
140659
- await new Promise((resolve2) => setImmediate(resolve2));
140990
+ await new Promise((resolve3) => setImmediate(resolve3));
140660
140991
  }
140661
140992
  } catch (streamError) {
140662
140993
  this.#logger.warn(
@@ -141661,7 +141992,7 @@ function formatMcpToolRef(agentId, toolName) {
141661
141992
  switch (agentId) {
141662
141993
  case "claude":
141663
141994
  return `mcp__${pullfrogMcpName}__${toolName}`;
141664
- case "opentoad":
141995
+ case "opencode":
141665
141996
  return `${pullfrogMcpName}_${toolName}`;
141666
141997
  default:
141667
141998
  return agentId;
@@ -141669,7 +142000,7 @@ function formatMcpToolRef(agentId, toolName) {
141669
142000
  }
141670
142001
 
141671
142002
  // utils/browser.ts
141672
- import { execFileSync, spawnSync } from "node:child_process";
142003
+ import { execFileSync as execFileSync2, spawnSync } from "node:child_process";
141673
142004
  import { existsSync as existsSync4 } from "node:fs";
141674
142005
  import { dirname } from "node:path";
141675
142006
 
@@ -141684,12 +142015,77 @@ var SENSITIVE_PATTERNS = [
141684
142015
  function isSensitiveEnvName(key) {
141685
142016
  return SENSITIVE_PATTERNS.some((p) => p.test(key));
141686
142017
  }
142018
+ var SAFE_ENV_PREFIXES = ["GITHUB_", "RUNNER_", "JAVA_HOME_", "GOROOT_"];
142019
+ var SAFE_ENV_NAMES = /* @__PURE__ */ new Set([
142020
+ // system
142021
+ "CI",
142022
+ "HOME",
142023
+ "LANG",
142024
+ "LOGNAME",
142025
+ "PATH",
142026
+ "SHELL",
142027
+ "SHLVL",
142028
+ "TERM",
142029
+ "TMPDIR",
142030
+ "TZ",
142031
+ "USER",
142032
+ "XDG_CONFIG_HOME",
142033
+ "XDG_RUNTIME_DIR",
142034
+ "DEBIAN_FRONTEND",
142035
+ // runner image toolchain
142036
+ "ACCEPT_EULA",
142037
+ "AGENT_TOOLSDIRECTORY",
142038
+ "ANDROID_HOME",
142039
+ "ANDROID_NDK",
142040
+ "ANDROID_NDK_HOME",
142041
+ "ANDROID_NDK_LATEST_HOME",
142042
+ "ANDROID_NDK_ROOT",
142043
+ "ANDROID_SDK_ROOT",
142044
+ "ANT_HOME",
142045
+ "AZURE_EXTENSION_DIR",
142046
+ "BOOTSTRAP_HASKELL_NONINTERACTIVE",
142047
+ "CHROME_BIN",
142048
+ "CHROMEWEBDRIVER",
142049
+ "CONDA",
142050
+ "DOTNET_MULTILEVEL_LOOKUP",
142051
+ "DOTNET_NOLOGO",
142052
+ "DOTNET_SKIP_FIRST_TIME_EXPERIENCE",
142053
+ "EDGEWEBDRIVER",
142054
+ "GECKOWEBDRIVER",
142055
+ "GHCUP_INSTALL_BASE_PREFIX",
142056
+ "GRADLE_HOME",
142057
+ "JAVA_HOME",
142058
+ "HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS",
142059
+ "HOMEBREW_NO_AUTO_UPDATE",
142060
+ "ImageOS",
142061
+ "ImageVersion",
142062
+ "NVM_DIR",
142063
+ "PIPX_BIN_DIR",
142064
+ "PIPX_HOME",
142065
+ "PSModulePath",
142066
+ "SELENIUM_JAR_PATH",
142067
+ "SGX_AESM_ADDR",
142068
+ "SWIFT_PATH",
142069
+ "VCPKG_INSTALLATION_ROOT"
142070
+ ]);
142071
+ var _userAllowlist = null;
142072
+ function setEnvAllowlist(raw2) {
142073
+ const names = raw2.split("\n").map((line) => line.trim()).filter(Boolean);
142074
+ _userAllowlist = new Set(names);
142075
+ }
142076
+ function isSafeEnvVar(key) {
142077
+ if (SAFE_ENV_NAMES.has(key)) return true;
142078
+ return SAFE_ENV_PREFIXES.some((p) => key.startsWith(p));
142079
+ }
141687
142080
  function filterEnv() {
141688
142081
  const filtered = {};
141689
142082
  for (const [key, value2] of Object.entries(process.env)) {
141690
142083
  if (value2 === void 0) continue;
141691
- if (isSensitiveEnvName(key)) continue;
141692
- filtered[key] = value2;
142084
+ const userAllowed = _userAllowlist?.has(key) ?? false;
142085
+ if (isSensitiveEnvName(key) && !userAllowed) continue;
142086
+ if (isSafeEnvVar(key) || userAllowed) {
142087
+ filtered[key] = value2;
142088
+ }
141693
142089
  }
141694
142090
  return filtered;
141695
142091
  }
@@ -141709,7 +142105,7 @@ var import_semver = __toESM(require_semver2(), 1);
141709
142105
  // package.json
141710
142106
  var package_default = {
141711
142107
  name: "pullfrog",
141712
- version: "0.0.200",
142108
+ version: "0.0.202",
141713
142109
  type: "module",
141714
142110
  bin: {
141715
142111
  pullfrog: "dist/cli.mjs",
@@ -141721,6 +142117,7 @@ var package_default = {
141721
142117
  ],
141722
142118
  scripts: {
141723
142119
  test: "vitest",
142120
+ "test:catalog": "vitest run --config vitest.main.config.ts",
141724
142121
  typecheck: "tsc --noEmit",
141725
142122
  build: "node esbuild.config.js && tsc -p tsconfig.exports.json",
141726
142123
  "check:entrypoints": "node scripts/check-entrypoint-imports.ts",
@@ -141733,7 +142130,7 @@ var package_default = {
141733
142130
  },
141734
142131
  devDependencies: {
141735
142132
  "@actions/core": "^1.11.1",
141736
- "@anthropic-ai/claude-code": "2.1.85",
142133
+ "@anthropic-ai/claude-code": "2.1.112",
141737
142134
  "@ark/fs": "0.56.0",
141738
142135
  "@ark/util": "0.56.0",
141739
142136
  "@clack/prompts": "^1.2.0",
@@ -141857,7 +142254,7 @@ function ensureBrowserDaemon(toolState) {
141857
142254
  log.info("agent-browser installed");
141858
142255
  let binDir;
141859
142256
  try {
141860
- const binPath = execFileSync("which", ["agent-browser"], { encoding: "utf-8" }).trim();
142257
+ const binPath = execFileSync2("which", ["agent-browser"], { encoding: "utf-8" }).trim();
141861
142258
  binDir = dirname(binPath);
141862
142259
  log.info(`agent-browser binary: ${binPath}`);
141863
142260
  } catch {
@@ -141904,9 +142301,271 @@ function closeBrowserDaemon(toolState) {
141904
142301
  }
141905
142302
 
141906
142303
  // mcp/checkout.ts
142304
+ import { createHash as createHash2 } from "node:crypto";
141907
142305
  import { writeFileSync } from "node:fs";
141908
142306
  import { join as join3 } from "node:path";
141909
142307
 
142308
+ // utils/diffCoverage.ts
142309
+ import { isAbsolute, normalize as normalize2, resolve } from "node:path";
142310
+ function countLines(params) {
142311
+ const content = params.content;
142312
+ if (content.length === 0) return 0;
142313
+ return content.split("\n").length;
142314
+ }
142315
+ function parseDiffTocEntries(params) {
142316
+ const lines = params.toc.split("\n");
142317
+ const entries = [];
142318
+ for (const line of lines) {
142319
+ const match3 = line.match(/^- (.+) (?:→|->) lines (\d+)-(\d+)(?: · diff-[0-9a-f]+)?$/);
142320
+ if (!match3) continue;
142321
+ const startLine = Number.parseInt(match3[2], 10);
142322
+ const endLine = Number.parseInt(match3[3], 10);
142323
+ if (!Number.isFinite(startLine) || !Number.isFinite(endLine)) continue;
142324
+ entries.push({ filename: match3[1], startLine, endLine });
142325
+ }
142326
+ return entries;
142327
+ }
142328
+ function createDiffCoverageState(params) {
142329
+ return {
142330
+ diffPath: params.diffPath,
142331
+ totalLines: params.totalLines,
142332
+ tocEntries: parseDiffTocEntries({ toc: params.toc }),
142333
+ coveredRanges: [],
142334
+ coveragePreflightRan: false
142335
+ };
142336
+ }
142337
+ function recordDiffReadFromToolUse(params) {
142338
+ const state = params.state;
142339
+ if (!state) return false;
142340
+ if (!isReadTool(params.toolName)) return false;
142341
+ const readTarget = extractReadTarget({ input: params.input });
142342
+ if (!readTarget) return false;
142343
+ const normalizedReadPath = normalizePath({ path: readTarget.path, cwd: params.cwd });
142344
+ const normalizedDiffPath = normalize2(state.diffPath);
142345
+ if (normalizedReadPath !== normalizedDiffPath) return false;
142346
+ const range2 = resolveReadRange({
142347
+ totalLines: state.totalLines,
142348
+ offset: readTarget.offset,
142349
+ limit: readTarget.limit,
142350
+ startLine: readTarget.startLine,
142351
+ endLine: readTarget.endLine,
142352
+ offsetBase: resolveOffsetBase({ toolName: params.toolName })
142353
+ });
142354
+ if (!range2) return false;
142355
+ state.coveredRanges = mergeRanges({ ranges: state.coveredRanges, nextRange: range2 });
142356
+ return true;
142357
+ }
142358
+ function getDiffCoverageBreakdown(params) {
142359
+ const state = params.state;
142360
+ const coveredRanges = mergeRangesList({ ranges: state.coveredRanges });
142361
+ const unreadRanges = invertRanges({ totalLines: state.totalLines, coveredRanges });
142362
+ const coveredLines = countLinesInRanges({ ranges: coveredRanges });
142363
+ const unreadLines = Math.max(0, state.totalLines - coveredLines);
142364
+ const coveragePercent = state.totalLines ? Number((coveredLines / state.totalLines * 100).toFixed(2)) : 100;
142365
+ const files = [];
142366
+ for (const entry of state.tocEntries) {
142367
+ const fileRange = { startLine: entry.startLine, endLine: entry.endLine };
142368
+ const coveredInFile = intersectRangesWithRange({ ranges: coveredRanges, target: fileRange });
142369
+ const unreadInFile = intersectRangesWithRange({ ranges: unreadRanges, target: fileRange });
142370
+ const totalFileLines = Math.max(0, entry.endLine - entry.startLine + 1);
142371
+ const fileCoveredLines = countLinesInRanges({ ranges: coveredInFile });
142372
+ files.push({
142373
+ filename: entry.filename,
142374
+ startLine: entry.startLine,
142375
+ endLine: entry.endLine,
142376
+ totalLines: totalFileLines,
142377
+ coveredLines: fileCoveredLines,
142378
+ coveredRanges: coveredInFile,
142379
+ unreadRanges: unreadInFile
142380
+ });
142381
+ }
142382
+ return {
142383
+ totalLines: state.totalLines,
142384
+ coveredLines,
142385
+ unreadLines,
142386
+ coveragePercent,
142387
+ coveredRanges,
142388
+ unreadRanges,
142389
+ files
142390
+ };
142391
+ }
142392
+ function renderDiffCoverageBreakdown(params) {
142393
+ const breakdown = params.breakdown;
142394
+ const lines = [];
142395
+ lines.push(`diff coverage report for \`${params.diffPath}\``);
142396
+ lines.push(
142397
+ `overall: ${breakdown.coveredLines}/${breakdown.totalLines} lines read (${breakdown.coveragePercent}%), unread: ${breakdown.unreadLines}`
142398
+ );
142399
+ lines.push(`covered ranges: ${formatRanges({ ranges: breakdown.coveredRanges })}`);
142400
+ lines.push(`unread ranges: ${formatRanges({ ranges: breakdown.unreadRanges })}`);
142401
+ lines.push("");
142402
+ lines.push("per-file TOC coverage:");
142403
+ for (const file2 of breakdown.files) {
142404
+ const filePercent = file2.totalLines ? Number((file2.coveredLines / file2.totalLines * 100).toFixed(2)) : 100;
142405
+ lines.push(
142406
+ `- ${file2.filename} (toc lines ${file2.startLine}-${file2.endLine}): ${file2.coveredLines}/${file2.totalLines} lines read (${filePercent}%)`
142407
+ );
142408
+ lines.push(` read: ${formatRanges({ ranges: file2.coveredRanges })}`);
142409
+ lines.push(` unread: ${formatRanges({ ranges: file2.unreadRanges })}`);
142410
+ }
142411
+ return lines.join("\n");
142412
+ }
142413
+ function resolveOffsetBase(params) {
142414
+ const lower2 = params.toolName.toLowerCase();
142415
+ if (lower2 === "readfile" || lower2.endsWith(".readfile")) {
142416
+ return "one";
142417
+ }
142418
+ return "zero";
142419
+ }
142420
+ function isReadTool(toolName) {
142421
+ const lower2 = toolName.toLowerCase();
142422
+ if (lower2 === "read" || lower2 === "readfile") return true;
142423
+ if (lower2.endsWith(".read") || lower2.endsWith(".readfile")) return true;
142424
+ return false;
142425
+ }
142426
+ function extractReadTarget(params) {
142427
+ const inputRecord = asRecord(params.input);
142428
+ if (!inputRecord) return null;
142429
+ const direct = extractReadTargetFromRecord({ record: inputRecord });
142430
+ if (direct) return direct;
142431
+ const nestedCandidates = [inputRecord.args, inputRecord.params, inputRecord.input];
142432
+ for (const candidate of nestedCandidates) {
142433
+ const nestedRecord = asRecord(candidate);
142434
+ if (!nestedRecord) continue;
142435
+ const nested = extractReadTargetFromRecord({ record: nestedRecord });
142436
+ if (nested) return nested;
142437
+ }
142438
+ return null;
142439
+ }
142440
+ function extractReadTargetFromRecord(params) {
142441
+ const record3 = params.record;
142442
+ const pathValue = readString({ value: record3.path }) ?? readString({ value: record3.file_path }) ?? readString({ value: record3.filePath }) ?? readString({ value: record3.filepath }) ?? readString({ value: record3.file }) ?? readString({ value: record3.target_file });
142443
+ if (!pathValue) return null;
142444
+ const offset = readNumber({ value: record3.offset });
142445
+ const limit = readNumber({ value: record3.limit });
142446
+ const startLine = readNumber({ value: record3.start_line }) ?? readNumber({ value: record3.startLine }) ?? readNumber({ value: record3.line_start });
142447
+ const endLine = readNumber({ value: record3.end_line }) ?? readNumber({ value: record3.endLine }) ?? readNumber({ value: record3.line_end });
142448
+ return { path: pathValue, offset, limit, startLine, endLine };
142449
+ }
142450
+ function resolveReadRange(params) {
142451
+ const totalLines = params.totalLines;
142452
+ if (totalLines <= 0) return null;
142453
+ if (params.startLine !== void 0 || params.endLine !== void 0) {
142454
+ const rawStart = params.startLine ?? 1;
142455
+ const rawEnd = params.endLine ?? totalLines;
142456
+ const startLine2 = clampLine({ value: rawStart, totalLines });
142457
+ const endLine2 = clampLine({ value: rawEnd, totalLines });
142458
+ if (endLine2 < startLine2) return null;
142459
+ return { startLine: startLine2, endLine: endLine2 };
142460
+ }
142461
+ let startLine = 1;
142462
+ if (params.offset !== void 0) {
142463
+ if (params.offset >= 0) {
142464
+ const normalizedOffset = params.offsetBase === "zero" ? params.offset + 1 : params.offset === 0 ? 1 : params.offset;
142465
+ startLine = clampLine({ value: normalizedOffset, totalLines });
142466
+ } else {
142467
+ startLine = clampLine({ value: totalLines + params.offset + 1, totalLines });
142468
+ }
142469
+ }
142470
+ let endLine = totalLines;
142471
+ if (params.limit !== void 0) {
142472
+ if (params.limit <= 0) return null;
142473
+ endLine = clampLine({ value: startLine + params.limit - 1, totalLines });
142474
+ }
142475
+ if (endLine < startLine) return null;
142476
+ return { startLine, endLine };
142477
+ }
142478
+ function normalizePath(params) {
142479
+ if (isAbsolute(params.path)) return normalize2(params.path);
142480
+ return normalize2(resolve(params.cwd, params.path));
142481
+ }
142482
+ function mergeRanges(params) {
142483
+ return mergeRangesList({ ranges: [...params.ranges, params.nextRange] });
142484
+ }
142485
+ function mergeRangesList(params) {
142486
+ if (params.ranges.length === 0) return [];
142487
+ const sorted = [...params.ranges].sort((a, b) => a.startLine - b.startLine);
142488
+ const merged = [];
142489
+ for (const range2 of sorted) {
142490
+ const last = merged[merged.length - 1];
142491
+ if (!last) {
142492
+ merged.push({ startLine: range2.startLine, endLine: range2.endLine });
142493
+ continue;
142494
+ }
142495
+ if (range2.startLine <= last.endLine + 1) {
142496
+ if (range2.endLine > last.endLine) {
142497
+ last.endLine = range2.endLine;
142498
+ }
142499
+ continue;
142500
+ }
142501
+ merged.push({ startLine: range2.startLine, endLine: range2.endLine });
142502
+ }
142503
+ return merged;
142504
+ }
142505
+ function invertRanges(params) {
142506
+ if (params.totalLines <= 0) return [];
142507
+ if (params.coveredRanges.length === 0) {
142508
+ return [{ startLine: 1, endLine: params.totalLines }];
142509
+ }
142510
+ const unread = [];
142511
+ let cursor = 1;
142512
+ for (const range2 of params.coveredRanges) {
142513
+ if (cursor < range2.startLine) {
142514
+ unread.push({ startLine: cursor, endLine: range2.startLine - 1 });
142515
+ }
142516
+ cursor = Math.max(cursor, range2.endLine + 1);
142517
+ }
142518
+ if (cursor <= params.totalLines) {
142519
+ unread.push({ startLine: cursor, endLine: params.totalLines });
142520
+ }
142521
+ return unread;
142522
+ }
142523
+ function intersectRangesWithRange(params) {
142524
+ const intersections = [];
142525
+ for (const range2 of params.ranges) {
142526
+ if (range2.endLine < params.target.startLine) continue;
142527
+ if (range2.startLine > params.target.endLine) continue;
142528
+ const startLine = Math.max(range2.startLine, params.target.startLine);
142529
+ const endLine = Math.min(range2.endLine, params.target.endLine);
142530
+ if (endLine >= startLine) {
142531
+ intersections.push({ startLine, endLine });
142532
+ }
142533
+ }
142534
+ return intersections;
142535
+ }
142536
+ function countLinesInRanges(params) {
142537
+ let total = 0;
142538
+ for (const range2 of params.ranges) {
142539
+ total += range2.endLine - range2.startLine + 1;
142540
+ }
142541
+ return total;
142542
+ }
142543
+ function formatRanges(params) {
142544
+ if (params.ranges.length === 0) return "none";
142545
+ return params.ranges.map((range2) => `${range2.startLine}-${range2.endLine}`).join(", ");
142546
+ }
142547
+ function clampLine(params) {
142548
+ if (params.value < 1) return 1;
142549
+ if (params.value > params.totalLines) return params.totalLines;
142550
+ return params.value;
142551
+ }
142552
+ function asRecord(value2) {
142553
+ if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return null;
142554
+ return Object.fromEntries(Object.entries(value2));
142555
+ }
142556
+ function readString(params) {
142557
+ if (typeof params.value === "string") return params.value;
142558
+ return void 0;
142559
+ }
142560
+ function readNumber(params) {
142561
+ if (typeof params.value === "number" && Number.isFinite(params.value)) return params.value;
142562
+ if (typeof params.value === "string") {
142563
+ const parsed2 = Number.parseInt(params.value, 10);
142564
+ if (Number.isFinite(parsed2)) return parsed2;
142565
+ }
142566
+ return void 0;
142567
+ }
142568
+
141910
142569
  // utils/gitAuth.ts
141911
142570
  import { execSync } from "node:child_process";
141912
142571
  import { createHash } from "node:crypto";
@@ -141920,7 +142579,7 @@ function resolveGit() {
141920
142579
  const resolvedPath = realpathSync(whichPath);
141921
142580
  const sha256 = hashFile(resolvedPath);
141922
142581
  gitBinary = { path: resolvedPath, sha256 };
141923
- log.info(`git binary: ${resolvedPath} (sha256: ${sha256.slice(0, 12)}...)`);
142582
+ log.debug(`\xBB git binary: ${resolvedPath} (sha256: ${sha256.slice(0, 12)}...)`);
141924
142583
  }
141925
142584
  function verifyGitBinary() {
141926
142585
  if (!gitBinary) {
@@ -141999,29 +142658,43 @@ async function $git(subcommand, args2, options) {
141999
142658
  }
142000
142659
 
142001
142660
  // lifecycle.ts
142002
- var LIFECYCLE_HOOK_TIMEOUT_MS = 12e4;
142661
+ var LIFECYCLE_HOOK_TIMEOUT_MS = 6e5;
142003
142662
 
142004
142663
  // utils/lifecycle.ts
142005
142664
  async function executeLifecycleHook(params) {
142006
- if (!params.script) return;
142665
+ if (!params.script) return {};
142007
142666
  log.info(`\xBB executing ${params.event} lifecycle hook...`);
142008
- const result = await spawn({
142009
- cmd: "bash",
142010
- args: ["-c", params.script],
142011
- env: process.env,
142012
- timeout: LIFECYCLE_HOOK_TIMEOUT_MS,
142013
- activityTimeout: 0,
142014
- onStdout: (chunk) => process.stdout.write(chunk),
142015
- onStderr: (chunk) => process.stderr.write(chunk)
142016
- });
142017
- if (result.exitCode !== 0) {
142018
- const output = result.stderr || result.stdout;
142019
- throw new Error(
142020
- `lifecycle hook '${params.event}' failed with exit code ${result.exitCode}:
142021
- ${output}`
142022
- );
142667
+ try {
142668
+ const result = await spawn({
142669
+ cmd: "bash",
142670
+ args: ["-c", params.script],
142671
+ env: process.env,
142672
+ timeout: LIFECYCLE_HOOK_TIMEOUT_MS,
142673
+ activityTimeout: 0,
142674
+ onStdout: (chunk) => process.stdout.write(chunk),
142675
+ onStderr: (chunk) => process.stderr.write(chunk)
142676
+ });
142677
+ if (result.exitCode !== 0) {
142678
+ const output = (result.stderr || result.stdout).trim();
142679
+ return {
142680
+ warning: `lifecycle hook '${params.event}' failed with exit code ${result.exitCode}. output: ${output || "(empty)"}. retry the operation if the failure looks flaky (network blips, transient rate limits). do NOT retry if the script is broken (missing commands, syntax errors) or the error is persistent.`
142681
+ };
142682
+ }
142683
+ log.info(`\xBB ${params.event} lifecycle hook completed successfully`);
142684
+ return {};
142685
+ } catch (err) {
142686
+ const isTimeout = err instanceof SpawnTimeoutError && (err.code === SPAWN_TIMEOUT_CODE || err.code === SPAWN_ACTIVITY_TIMEOUT_CODE);
142687
+ if (isTimeout) {
142688
+ const minutes = Math.round(LIFECYCLE_HOOK_TIMEOUT_MS / 6e4);
142689
+ return {
142690
+ warning: `lifecycle hook '${params.event}' timed out after ${minutes}min. do NOT retry \u2014 the script is likely hung or doing too much work. ask the repo owner to simplify the hook (e.g. move long-running work out of the hook, add caching, or split it).`
142691
+ };
142692
+ }
142693
+ const msg = err instanceof Error ? err.message : String(err);
142694
+ return {
142695
+ warning: `lifecycle hook '${params.event}' failed to spawn: ${msg}. this is likely a transient failure \u2014 retry the operation.`
142696
+ };
142023
142697
  }
142024
- log.info(`\xBB ${params.event} lifecycle hook completed successfully`);
142025
142698
  }
142026
142699
 
142027
142700
  // utils/shell.ts
@@ -142171,6 +142844,787 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
142171
142844
  return hasChanges ? out : null;
142172
142845
  }
142173
142846
 
142847
+ // mcp/git.ts
142848
+ function getPushDestination(branch, storedDest) {
142849
+ if (storedDest && storedDest.localBranch === branch) {
142850
+ log.debug(`using stored push destination: ${storedDest.remoteName}/${storedDest.remoteBranch}`);
142851
+ const url4 = $("git", ["remote", "get-url", "--push", storedDest.remoteName], {
142852
+ log: false
142853
+ }).trim();
142854
+ return { remoteName: storedDest.remoteName, remoteBranch: storedDest.remoteBranch, url: url4 };
142855
+ }
142856
+ try {
142857
+ const pushRemote = $("git", ["config", `branch.${branch}.pushRemote`], { log: false }).trim();
142858
+ const merge4 = $("git", ["config", `branch.${branch}.merge`], { log: false }).trim();
142859
+ const remoteBranch = merge4.replace(/^refs\/heads\//, "");
142860
+ const url4 = $("git", ["remote", "get-url", "--push", pushRemote], { log: false }).trim();
142861
+ return { remoteName: pushRemote, remoteBranch, url: url4 };
142862
+ } catch {
142863
+ log.debug(`no push config for ${branch}, falling back to origin/${branch}`);
142864
+ const url4 = $("git", ["remote", "get-url", "--push", "origin"], { log: false }).trim();
142865
+ return { remoteName: "origin", remoteBranch: branch, url: url4 };
142866
+ }
142867
+ }
142868
+ function normalizeUrl(url4) {
142869
+ return url4.replace(/\.git$/, "").toLowerCase();
142870
+ }
142871
+ function rejectIfLeadingDash(value2, kind) {
142872
+ if (value2.startsWith("-")) {
142873
+ throw new Error(`Blocked: ${kind} '${value2}' starts with '-' \u2014 git could parse it as a flag.`);
142874
+ }
142875
+ }
142876
+ var SYMBOLIC_REFS = /* @__PURE__ */ new Set(["HEAD", "FETCH_HEAD", "ORIG_HEAD", "MERGE_HEAD"]);
142877
+ function rejectSpecialRef(value2, kind) {
142878
+ rejectIfLeadingDash(value2, kind);
142879
+ if (value2.startsWith("refs/")) {
142880
+ throw new Error(
142881
+ `Blocked: ${kind} '${value2}' is a fully-qualified ref path. Use a bare branch name (e.g. 'feature/foo' or 'main'), not a 'refs/heads/...' form.`
142882
+ );
142883
+ }
142884
+ if (SYMBOLIC_REFS.has(value2)) {
142885
+ throw new Error(
142886
+ `Blocked: ${kind} '${value2}' is a git symbolic ref, not a branch name. Pass the resolved branch name (e.g. 'main'), or omit branchName to push the current branch.`
142887
+ );
142888
+ }
142889
+ const BAD = /[:+^~?*[\\\s]/;
142890
+ const badMatch = value2.match(BAD);
142891
+ if (badMatch) {
142892
+ throw new Error(
142893
+ `Blocked: ${kind} '${value2}' contains '${badMatch[0]}', which git interprets as refspec/revision syntax, not as part of a branch name.`
142894
+ );
142895
+ }
142896
+ }
142897
+ function validateTagName(tag) {
142898
+ rejectIfLeadingDash(tag, "tag");
142899
+ if (!/^[A-Za-z0-9._/-]+$/.test(tag)) {
142900
+ throw new Error(
142901
+ `Blocked: tag '${tag}' contains characters that could be parsed as a refspec or flag. Tags must match [A-Za-z0-9._/-]+.`
142902
+ );
142903
+ }
142904
+ }
142905
+ function validatePushDestination(ctx, branch) {
142906
+ const pushUrl = ctx.toolState.pushUrl;
142907
+ if (!pushUrl) throw new Error("pushUrl not set - setupGit must run before push_branch");
142908
+ const dest = getPushDestination(branch, ctx.toolState.pushDest);
142909
+ if (normalizeUrl(dest.url) !== normalizeUrl(pushUrl)) {
142910
+ throw new Error(
142911
+ `Push blocked: destination does not match expected repository.
142912
+ Expected: ${pushUrl}
142913
+ Actual: ${dest.url}
142914
+ Git configuration may have been tampered with.`
142915
+ );
142916
+ }
142917
+ return dest;
142918
+ }
142919
+ var PushBranch = type({
142920
+ branchName: type.string.describe("The branch name to push (defaults to current branch)").optional(),
142921
+ force: type.boolean.describe("Force push (use with caution)").default(false)
142922
+ });
142923
+ function PushBranchTool(ctx) {
142924
+ const defaultBranch = ctx.repo.data.default_branch || "main";
142925
+ const pushPermission = ctx.payload.push;
142926
+ return tool({
142927
+ name: "push_branch",
142928
+ description: "Push the current branch to the remote repository. Omit branchName to push the current branch (recommended). If specifying branchName, use the LOCAL branch name (e.g., 'pr-1'), not the remote branch name. The correct remote and remote branch are determined automatically from branch config set by checkout_pr. Requires a clean working tree. Runs the repository prepush hook (if configured) before the network push \u2014 hook failure means tests/lint or similar in that script failed, not necessarily a Pullfrog timeout. Never force push unless explicitly requested. Pushes to the default branch are blocked in restricted mode.",
142929
+ parameters: PushBranch,
142930
+ execute: execute(async ({ branchName, force }) => {
142931
+ if (pushPermission === "disabled") {
142932
+ throw new Error("Push is disabled. This repository is configured for read-only access.");
142933
+ }
142934
+ const branch = branchName || $("git", ["rev-parse", "--abbrev-ref", "HEAD"], { log: false });
142935
+ rejectSpecialRef(branch, "branch");
142936
+ const status = $("git", ["status", "--porcelain"], { log: false });
142937
+ if (status) {
142938
+ throw new Error(
142939
+ `push blocked: working tree is not clean (tracked changes and/or untracked files). commit, discard, or remove stray artifacts before pushing.
142940
+
142941
+ git status:
142942
+ ${status}`
142943
+ );
142944
+ }
142945
+ const pushDest = validatePushDestination(ctx, branch);
142946
+ if (pushPermission === "restricted" && pushDest.remoteBranch === defaultBranch) {
142947
+ throw new Error(
142948
+ `Push blocked: cannot push directly to default branch '${pushDest.remoteBranch}'. Create a feature branch and open a PR instead.`
142949
+ );
142950
+ }
142951
+ const refspec = branch === pushDest.remoteBranch ? branch : `${branch}:${pushDest.remoteBranch}`;
142952
+ const pushArgs = force ? ["--force", "-u", pushDest.remoteName, refspec] : ["-u", pushDest.remoteName, refspec];
142953
+ const prepushHook = await executeLifecycleHook({
142954
+ event: "prepush",
142955
+ script: ctx.prepushScript
142956
+ });
142957
+ if (prepushHook.warning) {
142958
+ throw new Error(prepushHook.warning);
142959
+ }
142960
+ const postHookStatus = $("git", ["status", "--porcelain"], { log: false });
142961
+ if (postHookStatus) {
142962
+ throw new Error(
142963
+ `push blocked: the prepush hook modified the working tree. those changes are not included in the push. commit or discard them (or change the hook to not mutate tracked files) before retrying.
142964
+
142965
+ git status:
142966
+ ${postHookStatus}`
142967
+ );
142968
+ }
142969
+ log.debug(`pushing ${branch} to ${pushDest.remoteName}/${pushDest.remoteBranch}`);
142970
+ if (force) {
142971
+ log.warning(`force pushing - this will overwrite remote history`);
142972
+ }
142973
+ try {
142974
+ await $git("push", pushArgs, {
142975
+ token: ctx.gitToken
142976
+ });
142977
+ } catch (err) {
142978
+ const msg = err instanceof Error ? err.message : String(err);
142979
+ if (msg.includes("fetch first") || msg.includes("non-fast-forward")) {
142980
+ const integrateStep = ctx.payload.shell === "disabled" ? `2. use the git tool to merge the remote branch into yours: git({ command: "merge", args: ["origin/${pushDest.remoteBranch}"] })` : `2. use the git tool to rebase or merge your changes on top: git({ command: "merge", args: ["origin/${pushDest.remoteBranch}"] }) (or 'rebase')`;
142981
+ throw new Error(
142982
+ `push rejected: the remote branch '${pushDest.remoteBranch}' has new commits you don't have locally.
142983
+
142984
+ to resolve this:
142985
+ 1. use git_fetch to fetch the remote branch: git_fetch({ ref: "${pushDest.remoteBranch}" })
142986
+ ${integrateStep}
142987
+ 3. resolve any merge conflicts if needed
142988
+ 4. retry push_branch`
142989
+ );
142990
+ }
142991
+ throw err;
142992
+ }
142993
+ return {
142994
+ success: true,
142995
+ branch,
142996
+ remoteBranch: pushDest.remoteBranch,
142997
+ remote: pushDest.remoteName,
142998
+ force,
142999
+ message: `successfully pushed ${branch} to ${pushDest.remoteName}/${pushDest.remoteBranch}`
143000
+ };
143001
+ })
143002
+ });
143003
+ }
143004
+ var AUTH_REQUIRED_REDIRECT = {
143005
+ push: "use the push_branch tool instead \u2014 it handles authentication and permission checks.",
143006
+ fetch: "use the git_fetch tool instead \u2014 it handles authentication.",
143007
+ pull: "use git_fetch to fetch the remote ref, then call this git tool with command 'merge' locally.",
143008
+ clone: "the repository is already cloned. use checkout_pr for PR branches."
143009
+ };
143010
+ var NOSHELL_BLOCKED_SUBCOMMANDS = {
143011
+ config: "Blocked: git config can set up filter drivers or hooks that execute arbitrary code.",
143012
+ submodule: "Blocked: git submodule can reference malicious repositories and execute code on update.",
143013
+ "update-index": "Blocked: git update-index can modify index entries in ways that bypass file protections.",
143014
+ "filter-branch": "Blocked: git filter-branch executes arbitrary code on repository history.",
143015
+ replace: "Blocked: git replace can redirect object lookups.",
143016
+ // subcommands that accept --exec or similar flags for arbitrary code execution
143017
+ rebase: "Blocked: git rebase --exec can execute arbitrary shell commands. Use 'merge' instead to integrate remote changes.",
143018
+ bisect: "Blocked: git bisect run can execute arbitrary shell commands. Bisect by hand (bisect start/good/bad/reset) is not available through this tool either \u2014 ask the user to run the bisect if needed.",
143019
+ // difftool/mergetool exist to shell out to external diff/merge programs.
143020
+ // both accept `--extcmd` / `-x` (difftool) or configured tool commands
143021
+ // (mergetool) that run arbitrary code. NOSHELL_BLOCKED_ARGS catches the
143022
+ // long `--extcmd` form, but not the `-x` short form — and globally blocking
143023
+ // `-x` would false-positive on `git cherry-pick -x`. block the subcommands
143024
+ // wholesale instead; neither has a meaningful use in an automated agent
143025
+ // workflow (agents use `git diff` / `git show` for diffs and resolve
143026
+ // conflicts via file edits, not a TUI merge tool).
143027
+ difftool: "Blocked: git difftool runs an external diff program via --extcmd/-x or configured tool and can execute arbitrary shell commands. Use 'diff' (or 'show' for single commits) to inspect changes \u2014 those output directly and don't invoke an external tool.",
143028
+ mergetool: "Blocked: git mergetool runs an external merge program configured via mergetool.<name>.cmd and can execute arbitrary shell commands. Resolve conflicts by editing the files directly (conflict markers are written into the working tree) and then commit."
143029
+ };
143030
+ var NOSHELL_BLOCKED_ARGS = ["--exec", "--extcmd", "--upload-pack", "--receive-pack"];
143031
+ var COLLAPSE_THRESHOLD = 200;
143032
+ var subcommandPattern = regex("^[a-z][a-z0-9-]*$");
143033
+ var Git = type({
143034
+ command: type(subcommandPattern).describe("Git command (e.g., 'status', 'log', 'diff')"),
143035
+ args: type.string.array().describe("Additional arguments for the git command").optional()
143036
+ });
143037
+ function GitTool(ctx) {
143038
+ return tool({
143039
+ name: "git",
143040
+ description: "Run git commands. For push/fetch, use the dedicated MCP tools (push_branch, git_fetch). git pull is not available \u2014 use git_fetch then this tool with command 'merge'.",
143041
+ parameters: Git,
143042
+ execute: execute(async (params) => {
143043
+ const command = params.command;
143044
+ const args2 = params.args ?? [];
143045
+ const redirect = AUTH_REQUIRED_REDIRECT[command];
143046
+ if (redirect) {
143047
+ throw new Error(`git ${command} is not available through this tool \u2014 ${redirect}`);
143048
+ }
143049
+ if (ctx.payload.shell === "disabled") {
143050
+ const blocked = NOSHELL_BLOCKED_SUBCOMMANDS[command];
143051
+ if (blocked) {
143052
+ throw new Error(blocked);
143053
+ }
143054
+ for (const arg of args2) {
143055
+ const isBlocked = NOSHELL_BLOCKED_ARGS.some(
143056
+ (flag) => arg === flag || arg.startsWith(flag + "=")
143057
+ );
143058
+ if (isBlocked) {
143059
+ throw new Error(
143060
+ `Blocked: '${arg}' flag can execute arbitrary code and is not allowed.`
143061
+ );
143062
+ }
143063
+ }
143064
+ }
143065
+ const output = $("git", [command, ...args2], { log: false });
143066
+ const lineCount = output.split("\n").length;
143067
+ if (lineCount > COLLAPSE_THRESHOLD) {
143068
+ log.group(`git ${command} output (${lineCount} lines)`, () => {
143069
+ log.info(output);
143070
+ });
143071
+ } else if (output) {
143072
+ log.info(output);
143073
+ }
143074
+ return { success: true, output };
143075
+ })
143076
+ });
143077
+ }
143078
+ var GitFetch = type({
143079
+ ref: type.string.describe("Ref to fetch: branch name, tag, or 'pull/N/head' for PRs"),
143080
+ depth: type.number.describe("Fetch depth (for shallow clones)").optional()
143081
+ });
143082
+ function GitFetchTool(ctx) {
143083
+ return tool({
143084
+ name: "git_fetch",
143085
+ description: "Fetch refs from remote repository. Use this instead of git fetch directly.",
143086
+ parameters: GitFetch,
143087
+ execute: execute(async (params) => {
143088
+ rejectIfLeadingDash(params.ref, "ref");
143089
+ const fetchArgs = ["--no-tags", "origin", params.ref];
143090
+ if (params.depth !== void 0) {
143091
+ fetchArgs.push(`--depth=${params.depth}`);
143092
+ }
143093
+ await $git("fetch", fetchArgs, {
143094
+ token: ctx.gitToken
143095
+ });
143096
+ return { success: true, ref: params.ref };
143097
+ })
143098
+ });
143099
+ }
143100
+ var DeleteBranch = type({
143101
+ branchName: type.string.describe("Remote branch to delete")
143102
+ });
143103
+ function DeleteBranchTool(ctx) {
143104
+ const pushPermission = ctx.payload.push;
143105
+ const defaultBranch = ctx.repo.data.default_branch || "main";
143106
+ return tool({
143107
+ name: "delete_branch",
143108
+ description: "Delete a remote branch. Requires push: enabled permission. Deletion of the repository's default branch is always blocked regardless of permission mode.",
143109
+ parameters: DeleteBranch,
143110
+ execute: execute(async (params) => {
143111
+ if (pushPermission !== "enabled") {
143112
+ throw new Error(
143113
+ "Branch deletion requires push: enabled permission. Current mode only allows pushing to non-protected branches."
143114
+ );
143115
+ }
143116
+ rejectSpecialRef(params.branchName, "branchName");
143117
+ if (params.branchName === defaultBranch) {
143118
+ throw new Error(
143119
+ `Blocked: cannot delete the default branch '${defaultBranch}'. If you really need to delete or rename it, do it manually via the repository settings.`
143120
+ );
143121
+ }
143122
+ await $git("push", ["origin", "--delete", `refs/heads/${params.branchName}`], {
143123
+ token: ctx.gitToken
143124
+ });
143125
+ return { success: true, deleted: params.branchName };
143126
+ })
143127
+ });
143128
+ }
143129
+ var PushTags = type({
143130
+ tag: type.string.describe("Tag name to push"),
143131
+ force: type.boolean.describe("Force push the tag").default(false)
143132
+ });
143133
+ function PushTagsTool(ctx) {
143134
+ const pushPermission = ctx.payload.push;
143135
+ return tool({
143136
+ name: "push_tags",
143137
+ description: "Push a tag to remote. Requires push: enabled permission.",
143138
+ parameters: PushTags,
143139
+ execute: execute(async (params) => {
143140
+ if (pushPermission !== "enabled") {
143141
+ throw new Error(
143142
+ "Tag pushing requires push: enabled permission. Current mode only allows pushing branches."
143143
+ );
143144
+ }
143145
+ validateTagName(params.tag);
143146
+ const pushArgs = [...params.force ? ["-f"] : [], "origin", `refs/tags/${params.tag}`];
143147
+ await $git("push", pushArgs, {
143148
+ token: ctx.gitToken
143149
+ });
143150
+ return { success: true, tag: params.tag };
143151
+ })
143152
+ });
143153
+ }
143154
+
143155
+ // mcp/review.ts
143156
+ function getHttpStatus(err) {
143157
+ if (typeof err !== "object" || err === null) return void 0;
143158
+ const status = err.status;
143159
+ return typeof status === "number" ? status : void 0;
143160
+ }
143161
+ function commentableLinesForFile(patch) {
143162
+ const right = /* @__PURE__ */ new Set();
143163
+ const left = /* @__PURE__ */ new Set();
143164
+ if (!patch) return { RIGHT: right, LEFT: left };
143165
+ let oldLine = 0;
143166
+ let newLine = 0;
143167
+ for (const line of patch.split("\n")) {
143168
+ const hunk = line.match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
143169
+ if (hunk) {
143170
+ oldLine = parseInt(hunk[1], 10);
143171
+ newLine = parseInt(hunk[2], 10);
143172
+ continue;
143173
+ }
143174
+ const changeType = line[0];
143175
+ if (changeType === "+") {
143176
+ right.add(newLine);
143177
+ newLine++;
143178
+ } else if (changeType === "-") {
143179
+ left.add(oldLine);
143180
+ oldLine++;
143181
+ } else if (changeType === " ") {
143182
+ right.add(newLine);
143183
+ left.add(oldLine);
143184
+ newLine++;
143185
+ oldLine++;
143186
+ }
143187
+ }
143188
+ return { RIGHT: right, LEFT: left };
143189
+ }
143190
+ async function buildCommentableMap(ctx, pullNumber) {
143191
+ const cached4 = ctx.toolState.commentableLinesByFile;
143192
+ const cachedFor = ctx.toolState.commentableLinesPullNumber;
143193
+ const cachedSha = ctx.toolState.commentableLinesCheckoutSha;
143194
+ const currentSha = ctx.toolState.checkoutSha;
143195
+ if (cached4 && cachedFor === pullNumber && cachedSha && cachedSha === currentSha) return cached4;
143196
+ const files = await ctx.octokit.paginate(ctx.octokit.rest.pulls.listFiles, {
143197
+ owner: ctx.repo.owner,
143198
+ repo: ctx.repo.name,
143199
+ pull_number: pullNumber,
143200
+ per_page: 100
143201
+ });
143202
+ const map2 = /* @__PURE__ */ new Map();
143203
+ for (const file2 of files) {
143204
+ map2.set(file2.filename, commentableLinesForFile(file2.patch));
143205
+ }
143206
+ return map2;
143207
+ }
143208
+ function validateInlineComments(comments, map2) {
143209
+ const valid = [];
143210
+ const dropped = [];
143211
+ for (const c of comments) {
143212
+ const side = c.side === "LEFT" ? "LEFT" : "RIGHT";
143213
+ const line = c.line ?? 0;
143214
+ const startLine = c.start_line ?? line;
143215
+ const lines = map2.get(c.path);
143216
+ const record3 = (reason) => {
143217
+ const entry = { path: c.path, line, side, reason };
143218
+ if (c.start_line != null) entry.startLine = c.start_line;
143219
+ dropped.push(entry);
143220
+ };
143221
+ if (!lines) {
143222
+ record3(`file not in PR diff`);
143223
+ continue;
143224
+ }
143225
+ if (lines.LEFT.size === 0 && lines.RIGHT.size === 0) {
143226
+ record3(`file has no textual diff (binary, pure rename, or mode change)`);
143227
+ continue;
143228
+ }
143229
+ const anchors = lines[side];
143230
+ if (!anchors.has(line)) {
143231
+ record3(`line ${line} (${side}) is not inside a diff hunk`);
143232
+ continue;
143233
+ }
143234
+ if (c.start_line != null && c.start_line > line) {
143235
+ record3(
143236
+ `start_line ${c.start_line} is after line ${line} \u2014 ranges must satisfy start_line <= line`
143237
+ );
143238
+ continue;
143239
+ }
143240
+ if (startLine !== line && !anchors.has(startLine)) {
143241
+ record3(`start_line ${startLine} (${side}) is not inside a diff hunk`);
143242
+ continue;
143243
+ }
143244
+ valid.push(c);
143245
+ }
143246
+ return { valid, dropped };
143247
+ }
143248
+ var MAX_DROPPED_COMMENT_LINES = 50;
143249
+ function reviewSkipDecision(params) {
143250
+ if (params.body || params.hasComments) return null;
143251
+ if (!params.approved) {
143252
+ return {
143253
+ kind: "no-issues",
143254
+ reason: "no issues found \u2014 nothing to post"
143255
+ };
143256
+ }
143257
+ if (!params.prApproveEnabled) {
143258
+ return {
143259
+ kind: "empty-downgraded-approve",
143260
+ reason: "approve requested but prApproveEnabled is disabled; no feedback body or comments to post as a COMMENT review instead"
143261
+ };
143262
+ }
143263
+ return null;
143264
+ }
143265
+ function formatDroppedCommentsNote(dropped) {
143266
+ const renderEntry = (d) => {
143267
+ const range2 = d.startLine != null && d.startLine !== d.line ? `${d.startLine}-${d.line}` : `${d.line}`;
143268
+ return `- \`${d.path}:${range2}\` (${d.side}) \u2014 ${d.reason}`;
143269
+ };
143270
+ const shown = dropped.slice(0, MAX_DROPPED_COMMENT_LINES).map(renderEntry);
143271
+ const remainder = dropped.length - shown.length;
143272
+ if (remainder > 0) shown.push(`- \u2026and ${remainder} more dropped comment(s) not shown`);
143273
+ return `
143274
+
143275
+ ---
143276
+
143277
+ **Note:** ${dropped.length} inline comment(s) dropped because they did not anchor to lines inside the PR diff:
143278
+ ` + shown.join("\n");
143279
+ }
143280
+ var CreatePullRequestReview = type({
143281
+ pull_number: type.number.describe("The pull request number to review"),
143282
+ body: type.string.describe(
143283
+ "1-2 sentence high-level summary with urgency level, critical callouts, and feedback about code outside the diff. Specific feedback on diff lines goes in 'comments' array."
143284
+ ).optional(),
143285
+ approved: type.boolean.describe(
143286
+ "Set to true to submit as an approval. ONLY when the review contains no actionable feedback \u2014 neither inline comments nor actionable content in the body. Defaults to false (comment-only review). Rejections are not supported."
143287
+ ).optional(),
143288
+ commit_id: type.string.describe("Optional SHA of the commit being reviewed. Defaults to latest.").optional(),
143289
+ comments: type({
143290
+ path: type.string.describe(
143291
+ "The file path to comment on (relative to repo root). Must be a file that appears in the PR diff."
143292
+ ),
143293
+ line: type.number.describe(
143294
+ "Line number to comment on. For multi-line ranges, this is the end line. Use NEW column from diff format."
143295
+ ),
143296
+ side: type.enumerated("LEFT", "RIGHT").describe(
143297
+ "Side of the diff: LEFT (old code, lines starting with -) or RIGHT (new code, lines starting with + or unchanged). Defaults to RIGHT."
143298
+ ).optional(),
143299
+ body: type.string.describe("Explanatory comment text (optional if suggestion is provided)").optional(),
143300
+ suggestion: type.string.describe(
143301
+ "Full replacement code for the line range [start_line, line]. MUST preserve the exact indentation of the original code."
143302
+ ).optional(),
143303
+ start_line: type.number.describe(
143304
+ "Start line for multi-line comment ranges. Omit for single-line comments. The range [start_line, line] defines which lines a suggestion replaces."
143305
+ ).optional()
143306
+ }).array().describe(
143307
+ "Inline comments on lines within diff hunks. Feedback about code outside the diff goes in 'body' instead."
143308
+ ).optional()
143309
+ });
143310
+ function CreatePullRequestReviewTool(ctx) {
143311
+ return tool({
143312
+ name: "create_pull_request_review",
143313
+ description: `Submit a review for an existing pull request. Each call creates a permanent, visible review on the PR \u2014 NEVER submit test or diagnostic reviews. Reviews with no body AND no comments are silently skipped (nothing to post). IMPORTANT: 95%+ of feedback should be in 'comments' array with file paths and line numbers. Only use 'body' for a 1-2 sentence summary with urgency and critical callouts. Use 'suggestion' to propose replacement code - MUST preserve exact indentation of original code. The first submission may error once with a one-time diff-coverage nudge listing unread TOC regions \u2014 retry with the same arguments and the pre-flight will not block again. Example replacing lines 42-44 (3 lines) with 5 lines: { path: 'src/api.ts', start_line: 42, line: 44, suggestion: ' const result = await fetch(url);\\n if (!result.ok) {\\n log.error(result.status);\\n throw new Error("request failed");\\n }' } CONSTRAINT: Inline comments can ONLY target files and lines that appear in the PR diff. Comments anchored outside a diff hunk are dropped automatically (with a note appended to the review body) \u2014 the rest of the review still posts.`,
143314
+ parameters: CreatePullRequestReview,
143315
+ execute: execute(async ({ pull_number, body, approved, commit_id, comments = [] }) => {
143316
+ if (body) body = fixDoubleEscapedString(body);
143317
+ ctx.toolState.issueNumber = pull_number;
143318
+ const skip = reviewSkipDecision({
143319
+ approved: approved ?? false,
143320
+ body,
143321
+ hasComments: comments.length > 0,
143322
+ prApproveEnabled: ctx.prApproveEnabled
143323
+ });
143324
+ if (skip) {
143325
+ log.info(`skipping review submission: ${skip.reason}`);
143326
+ return { success: true, skipped: true, reason: skip.reason };
143327
+ }
143328
+ let event = approved ? "APPROVE" : "COMMENT";
143329
+ if (event === "APPROVE" && !ctx.prApproveEnabled) {
143330
+ log.info("prApproveEnabled is disabled \u2014 downgrading APPROVE to COMMENT");
143331
+ event = "COMMENT";
143332
+ }
143333
+ const params = {
143334
+ owner: ctx.repo.owner,
143335
+ repo: ctx.repo.name,
143336
+ pull_number,
143337
+ event
143338
+ };
143339
+ let latestHeadSha;
143340
+ if (commit_id) {
143341
+ params.commit_id = commit_id;
143342
+ } else {
143343
+ const pr = await ctx.octokit.rest.pulls.get({
143344
+ owner: ctx.repo.owner,
143345
+ repo: ctx.repo.name,
143346
+ pull_number
143347
+ });
143348
+ latestHeadSha = pr.data.head.sha;
143349
+ params.commit_id = ctx.toolState.checkoutSha ?? latestHeadSha;
143350
+ if (ctx.toolState.checkoutSha && latestHeadSha !== ctx.toolState.checkoutSha) {
143351
+ log.info(
143352
+ `anchoring review to checkout ${ctx.toolState.checkoutSha.slice(0, 7)} (HEAD is now ${latestHeadSha.slice(0, 7)})`
143353
+ );
143354
+ }
143355
+ }
143356
+ runDiffCoveragePreflight({ ctx });
143357
+ const reviewComments = comments.map((comment) => {
143358
+ let commentBody = fixDoubleEscapedString(comment.body || "");
143359
+ if (comment.suggestion !== void 0) {
143360
+ const suggestionBlock = "```suggestion\n" + comment.suggestion + "\n```";
143361
+ commentBody = commentBody ? commentBody + "\n\n" + suggestionBlock : suggestionBlock;
143362
+ }
143363
+ const side = comment.side || "RIGHT";
143364
+ const reviewComment = {
143365
+ path: comment.path,
143366
+ line: comment.line,
143367
+ body: commentBody,
143368
+ side
143369
+ };
143370
+ if (comment.start_line != null && comment.start_line !== comment.line) {
143371
+ reviewComment.start_line = comment.start_line;
143372
+ reviewComment.start_side = side;
143373
+ }
143374
+ return reviewComment;
143375
+ });
143376
+ let droppedComments = [];
143377
+ if (reviewComments.length > 0) {
143378
+ const commentableMap = await buildCommentableMap(ctx, pull_number);
143379
+ const validation = validateInlineComments(reviewComments, commentableMap);
143380
+ droppedComments = validation.dropped;
143381
+ if (droppedComments.length > 0) {
143382
+ log.info(
143383
+ `dropping ${droppedComments.length}/${reviewComments.length} inline comment(s) that do not anchor to PR diff lines`
143384
+ );
143385
+ }
143386
+ params.comments = validation.valid;
143387
+ }
143388
+ if (droppedComments.length > 0) {
143389
+ const note = formatDroppedCommentsNote(droppedComments);
143390
+ body = body ? body + note : note.replace(/^\n\n/, "");
143391
+ }
143392
+ if (!approved && !body && !params.comments?.length) {
143393
+ log.info("review has no body and all inline comments were dropped \u2014 skipping submission");
143394
+ return {
143395
+ success: true,
143396
+ skipped: true,
143397
+ reason: "all inline comments were invalid \u2014 nothing to post",
143398
+ droppedComments
143399
+ };
143400
+ }
143401
+ let result;
143402
+ try {
143403
+ result = body ? await createAndSubmitWithFooter(ctx, params, {
143404
+ body,
143405
+ approved: approved ?? false,
143406
+ hasComments: (params.comments?.length ?? 0) > 0
143407
+ }) : await createReviewWithStrandedRecovery(ctx, params);
143408
+ } catch (err) {
143409
+ if (getHttpStatus(err) !== 422 || !params.comments?.length) throw err;
143410
+ const details = params.comments.map((c) => {
143411
+ const line = c.line ?? 0;
143412
+ const startLine = c.start_line ?? line;
143413
+ const range2 = startLine !== line ? `${startLine}-${line}` : `${line}`;
143414
+ return `${c.path}:${range2} (${c.side ?? "RIGHT"})`;
143415
+ });
143416
+ const rawMsg = err instanceof Error ? err.message : String(err);
143417
+ const checkoutRef = formatMcpToolRef(ctx.agentId, "checkout_pr");
143418
+ throw new Error(
143419
+ `GitHub rejected the review with 422 even after pre-validation. Likely causes (check "GitHub said" below to narrow down): (1) new commits pushed after pre-validation \u2014 call \`${checkoutRef}\` again to refresh the diff snapshot, then resubmit; (2) the review body exceeded GitHub's ~65KB limit \u2014 shorten it and retry; (3) a \`suggestion\` block is malformed (missing backticks, extra backticks, or wrong indentation) \u2014 inspect the affected comments below. If none apply, move the failing comments into the review body as text so the rest still posts. Affected comments: ${details.join(", ")}. GitHub said: ${rawMsg}`,
143420
+ { cause: err }
143421
+ );
143422
+ }
143423
+ log.debug(`createReview response: ${JSON.stringify(result.data)}`);
143424
+ if (!result.data.id) {
143425
+ throw new Error(`createReview returned invalid data: ${JSON.stringify(result.data)}`);
143426
+ }
143427
+ const reviewId = result.data.id;
143428
+ const reviewNodeId = result.data.node_id;
143429
+ const actuallyReviewedSha = ctx.toolState.checkoutSha ?? params.commit_id;
143430
+ ctx.toolState.review = {
143431
+ id: reviewId,
143432
+ nodeId: reviewNodeId,
143433
+ reviewedSha: actuallyReviewedSha
143434
+ };
143435
+ if (ctx.toolState.checkoutSha && latestHeadSha && latestHeadSha !== ctx.toolState.checkoutSha) {
143436
+ const fromSha = ctx.toolState.checkoutSha;
143437
+ const toSha = latestHeadSha;
143438
+ ctx.toolState.beforeSha = fromSha;
143439
+ ctx.toolState.checkoutSha = toSha;
143440
+ log.info(
143441
+ `new commits detected during review: ${fromSha.slice(0, 7)}..${toSha.slice(0, 7)}`
143442
+ );
143443
+ return {
143444
+ success: true,
143445
+ reviewId,
143446
+ html_url: result.data.html_url,
143447
+ state: result.data.state,
143448
+ user: result.data.user?.login,
143449
+ submitted_at: result.data.submitted_at,
143450
+ droppedComments: droppedComments.length > 0 ? droppedComments : void 0,
143451
+ newCommits: {
143452
+ from: fromSha,
143453
+ to: toSha,
143454
+ instructions: `new commits were pushed while you were reviewing. call \`${formatMcpToolRef(ctx.agentId, "checkout_pr")}\` again to fetch the latest version \u2014 it will compute the incremental diff automatically. submit another review covering only the new changes. do not repeat feedback from your previous review.`
143455
+ }
143456
+ };
143457
+ }
143458
+ return {
143459
+ success: true,
143460
+ reviewId,
143461
+ html_url: result.data.html_url,
143462
+ state: result.data.state,
143463
+ user: result.data.user?.login,
143464
+ submitted_at: result.data.submitted_at,
143465
+ droppedComments: droppedComments.length > 0 ? droppedComments : void 0
143466
+ };
143467
+ })
143468
+ });
143469
+ }
143470
+ function runDiffCoveragePreflight(params) {
143471
+ const coverageState = params.ctx.toolState.diffCoverage;
143472
+ if (!coverageState) {
143473
+ log.debug("diff coverage pre-flight skipped: no diffCoverage state present in toolState");
143474
+ return;
143475
+ }
143476
+ if (coverageState.coveragePreflightRan) {
143477
+ log.debug("diff coverage pre-flight skipped: already ran in this session");
143478
+ return;
143479
+ }
143480
+ coverageState.coveragePreflightRan = true;
143481
+ log.debug(
143482
+ `diff coverage pre-flight start: diffPath=${coverageState.diffPath}, totalLines=${coverageState.totalLines}, tocEntries=${coverageState.tocEntries.length}, coveredRanges=${coverageState.coveredRanges.length}`
143483
+ );
143484
+ const breakdown = getDiffCoverageBreakdown({ state: coverageState });
143485
+ const unread = [];
143486
+ let unreadLines = 0;
143487
+ for (const file2 of breakdown.files) {
143488
+ if (file2.unreadRanges.length === 0) continue;
143489
+ const rangesText = file2.unreadRanges.map((range2) => `${range2.startLine}-${range2.endLine}`).join(", ");
143490
+ const fileUnreadLines = countLinesInRanges({ ranges: file2.unreadRanges });
143491
+ unread.push({ path: file2.filename, ranges: rangesText, unreadLines: fileUnreadLines });
143492
+ unreadLines += fileUnreadLines;
143493
+ }
143494
+ coverageState.lastBreakdown = renderDiffCoverageBreakdown({
143495
+ diffPath: coverageState.diffPath,
143496
+ breakdown
143497
+ });
143498
+ log.debug(
143499
+ `diff coverage pre-flight breakdown: coveredLines=${breakdown.coveredLines}, unreadLines=${unreadLines}`
143500
+ );
143501
+ if (unreadLines === 0) {
143502
+ log.debug("diff coverage pre-flight passed: no unread regions");
143503
+ return;
143504
+ }
143505
+ log.info(
143506
+ `diff coverage pre-flight nudge: unread lines=${unreadLines}, unread files=${unread.length}`
143507
+ );
143508
+ const unreadText = unread.map((entry) => `- ${entry.path} (${entry.unreadLines} lines, ${entry.ranges})`).join("\n");
143509
+ throw new Error(
143510
+ `diff coverage pre-flight: some TOC regions were not read before review submission. this is a one-time nudge \u2014 optionally read the ranges below from ${coverageState.diffPath}, then call create_pull_request_review again with the same arguments. this pre-flight will not block again in this review session.
143511
+
143512
+ unread TOC regions:
143513
+ ${unreadText}
143514
+
143515
+ ${coverageState.lastBreakdown}`
143516
+ );
143517
+ }
143518
+ async function clearStrandedPendingReview(ctx, params) {
143519
+ const originalErr = params.originalErr;
143520
+ const msg = originalErr instanceof Error ? originalErr.message.toLowerCase() : "";
143521
+ if (getHttpStatus(originalErr) !== 422 || !msg.includes("pending review")) throw originalErr;
143522
+ const reviews = await ctx.octokit.paginate(ctx.octokit.rest.pulls.listReviews, {
143523
+ owner: params.owner,
143524
+ repo: params.repo,
143525
+ pull_number: params.pull_number,
143526
+ per_page: 100
143527
+ }).catch((listErr) => {
143528
+ log.info(
143529
+ `\xBB listReviews failed during pending-review cleanup, surfacing original 422: ${listErr instanceof Error ? listErr.message : String(listErr)}`
143530
+ );
143531
+ throw originalErr;
143532
+ });
143533
+ const leftover = reviews.find((r) => r.state === "PENDING");
143534
+ if (!leftover?.id) throw originalErr;
143535
+ log.info(
143536
+ `\xBB clearing leftover pending review ${leftover.id} (likely stranded by a killed prior run)`
143537
+ );
143538
+ try {
143539
+ await ctx.octokit.rest.pulls.deletePendingReview({
143540
+ owner: params.owner,
143541
+ repo: params.repo,
143542
+ pull_number: params.pull_number,
143543
+ review_id: leftover.id
143544
+ });
143545
+ } catch (cleanupErr) {
143546
+ const cleanupStatus = getHttpStatus(cleanupErr);
143547
+ if (cleanupStatus !== 404 && cleanupStatus !== 422) throw cleanupErr;
143548
+ log.debug(`\xBB delete of leftover pending ${leftover.id} no-op (status ${cleanupStatus})`);
143549
+ }
143550
+ }
143551
+ async function createReviewWithStrandedRecovery(ctx, params) {
143552
+ try {
143553
+ return await ctx.octokit.rest.pulls.createReview(params);
143554
+ } catch (err) {
143555
+ await clearStrandedPendingReview(ctx, {
143556
+ owner: params.owner,
143557
+ repo: params.repo,
143558
+ pull_number: params.pull_number,
143559
+ originalErr: err
143560
+ });
143561
+ return await ctx.octokit.rest.pulls.createReview(params);
143562
+ }
143563
+ }
143564
+ async function createAndSubmitWithFooter(ctx, params, opts) {
143565
+ const { event: _, ...pendingParams } = params;
143566
+ let pending;
143567
+ try {
143568
+ pending = await ctx.octokit.rest.pulls.createReview(pendingParams);
143569
+ } catch (err) {
143570
+ await clearStrandedPendingReview(ctx, {
143571
+ owner: params.owner,
143572
+ repo: params.repo,
143573
+ pull_number: params.pull_number,
143574
+ originalErr: err
143575
+ });
143576
+ pending = await ctx.octokit.rest.pulls.createReview(pendingParams);
143577
+ }
143578
+ if (!pending.data.id) {
143579
+ throw new Error(`createReview returned invalid data: ${JSON.stringify(pending.data)}`);
143580
+ }
143581
+ try {
143582
+ const customParts = [];
143583
+ if (!opts.approved) {
143584
+ const apiUrl = getApiUrl();
143585
+ if (opts.hasComments) {
143586
+ const fixAllUrl = `${apiUrl}/trigger/${ctx.repo.owner}/${ctx.repo.name}/${params.pull_number}?action=fix&review_id=${pending.data.id}`;
143587
+ const fixApprovedUrl = `${apiUrl}/trigger/${ctx.repo.owner}/${ctx.repo.name}/${params.pull_number}?action=fix-approved&review_id=${pending.data.id}`;
143588
+ customParts.push(`[Fix all \u2794](${fixAllUrl})`, `[Fix \u{1F44D}s \u2794](${fixApprovedUrl})`);
143589
+ } else {
143590
+ const fixUrl = `${apiUrl}/trigger/${ctx.repo.owner}/${ctx.repo.name}/${params.pull_number}?action=fix&review_id=${pending.data.id}`;
143591
+ customParts.push(`[Fix it \u2794](${fixUrl})`);
143592
+ }
143593
+ }
143594
+ const footer = buildPullfrogFooter({
143595
+ workflowRun: ctx.runId ? { owner: ctx.repo.owner, repo: ctx.repo.name, runId: ctx.runId, jobId: ctx.jobId } : void 0,
143596
+ customParts,
143597
+ model: ctx.toolState.model
143598
+ });
143599
+ return await ctx.octokit.rest.pulls.submitReview({
143600
+ owner: params.owner,
143601
+ repo: params.repo,
143602
+ pull_number: params.pull_number,
143603
+ review_id: pending.data.id,
143604
+ event: params.event,
143605
+ body: opts.body + footer
143606
+ });
143607
+ } catch (err) {
143608
+ try {
143609
+ await ctx.octokit.rest.pulls.deletePendingReview({
143610
+ owner: params.owner,
143611
+ repo: params.repo,
143612
+ pull_number: params.pull_number,
143613
+ review_id: pending.data.id
143614
+ });
143615
+ log.debug(`\xBB deleted leftover pending review ${pending.data.id} after failure`);
143616
+ } catch (cleanupErr) {
143617
+ log.debug(
143618
+ `\xBB failed to delete pending review ${pending.data.id}: ${cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)}`
143619
+ );
143620
+ }
143621
+ throw err;
143622
+ }
143623
+ }
143624
+ async function reportReviewNodeId(ctx, params) {
143625
+ await patchWorkflowRunFields(ctx, { reviewNodeId: params.nodeId });
143626
+ }
143627
+
142174
143628
  // mcp/checkout.ts
142175
143629
  function formatFilesWithLineNumbers(files) {
142176
143630
  const output = [];
@@ -142237,7 +143691,10 @@ function formatFilesWithLineNumbers(files) {
142237
143691
  }
142238
143692
  const tocLines = [`## Files (${files.length})`];
142239
143693
  for (const entry of tocEntries) {
142240
- tocLines.push(`- ${entry.filename} \u2192 lines ${entry.startLine}-${entry.endLine}`);
143694
+ const anchor = createHash2("sha256").update(entry.filename).digest("hex");
143695
+ tocLines.push(
143696
+ `- ${entry.filename} \u2192 lines ${entry.startLine}-${entry.endLine} \xB7 diff-${anchor}`
143697
+ );
142241
143698
  }
142242
143699
  tocLines.push("");
142243
143700
  tocLines.push("---");
@@ -142259,7 +143716,7 @@ async function fetchAndFormatPrDiff(ctx, pullNumber) {
142259
143716
  pull_number: pullNumber,
142260
143717
  per_page: 100
142261
143718
  });
142262
- return formatFilesWithLineNumbers(files);
143719
+ return { ...formatFilesWithLineNumbers(files), files };
142263
143720
  }
142264
143721
  async function createTempBranch(params) {
142265
143722
  const response = await params.octokit.rest.git.createRef({
@@ -142326,6 +143783,8 @@ async function ensureBeforeShaReachable(params) {
142326
143783
  async function checkoutPrBranch(pr, params) {
142327
143784
  const { octokit, owner, name, gitToken, toolState, beforeSha } = params;
142328
143785
  log.info(`\xBB checking out PR #${pr.number}...`);
143786
+ rejectIfLeadingDash(pr.baseRef, "PR base ref");
143787
+ rejectIfLeadingDash(pr.headRef, "PR head ref");
142329
143788
  const isFork = pr.headRepoFullName !== pr.baseRepoFullName;
142330
143789
  const localBranch = `pr-${pr.number}`;
142331
143790
  const isShallow = $("git", ["rev-parse", "--is-shallow-repository"], { log: false }).trim() === "true";
@@ -142336,7 +143795,7 @@ async function checkoutPrBranch(pr, params) {
142336
143795
  if (!alreadyOnBranch) {
142337
143796
  $("git", ["checkout", "-B", pr.baseRef, `origin/${pr.baseRef}`], { log: false });
142338
143797
  log.debug(`\xBB fetching PR #${pr.number} (${localBranch})...`);
142339
- await $git("fetch", ["--no-tags", "origin", `pull/${pr.number}/head:${localBranch}`], {
143798
+ await $git("fetch", ["--no-tags", "origin", `+pull/${pr.number}/head:${localBranch}`], {
142340
143799
  token: gitToken
142341
143800
  });
142342
143801
  $("git", ["checkout", localBranch], { log: false });
@@ -142419,10 +143878,11 @@ async function checkoutPrBranch(pr, params) {
142419
143878
  remoteBranch: pr.headRef,
142420
143879
  localBranch
142421
143880
  };
142422
- await executeLifecycleHook({
143881
+ const postCheckoutHook = await executeLifecycleHook({
142423
143882
  event: "post-checkout",
142424
143883
  script: params.postCheckoutScript
142425
143884
  });
143885
+ return { hookWarning: postCheckoutHook.warning };
142426
143886
  }
142427
143887
  function CheckoutPrTool(ctx) {
142428
143888
  return tool({
@@ -142448,7 +143908,7 @@ function CheckoutPrTool(ctx) {
142448
143908
  baseRepoFullName: prResponse.data.base.repo.full_name,
142449
143909
  maintainerCanModify: prResponse.data.maintainer_can_modify
142450
143910
  };
142451
- await checkoutPrBranch(pr, {
143911
+ const checkoutResult = await checkoutPrBranch(pr, {
142452
143912
  octokit: ctx.octokit,
142453
143913
  owner: ctx.repo.owner,
142454
143914
  name: ctx.repo.name,
@@ -142491,11 +143951,49 @@ ${diffPreview}`);
142491
143951
  const diffPath = join3(tempDir, `pr-${pull_number}-${headShort}.diff`);
142492
143952
  writeFileSync(diffPath, formatResult.content);
142493
143953
  log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
143954
+ ctx.toolState.diffCoverage = createDiffCoverageState({
143955
+ diffPath,
143956
+ totalLines: countLines({ content: formatResult.content }),
143957
+ toc: formatResult.toc
143958
+ });
143959
+ log.debug(
143960
+ `\xBB diff coverage initialized: diffPath=${diffPath}, totalLines=${ctx.toolState.diffCoverage.totalLines}, tocEntries=${ctx.toolState.diffCoverage.tocEntries.length}`
143961
+ );
143962
+ const cached4 = /* @__PURE__ */ new Map();
143963
+ for (const file2 of formatResult.files) {
143964
+ cached4.set(file2.filename, commentableLinesForFile(file2.patch));
143965
+ }
143966
+ ctx.toolState.commentableLinesByFile = cached4;
143967
+ ctx.toolState.commentableLinesPullNumber = pull_number;
143968
+ ctx.toolState.commentableLinesCheckoutSha = ctx.toolState.checkoutSha;
142494
143969
  const incrementalInstructions = incrementalDiffPath ? ` IMPORTANT: incrementalDiffPath contains ONLY the changes since the last reviewed version (computed via range-diff). you MUST read incrementalDiffPath FIRST to understand what changed, then use diffPath for full PR context. do NOT skip the incremental diff.` : "";
143970
+ const COMMIT_LOG_MAX = 200;
143971
+ const baseRange = `origin/${pr.baseRef}..HEAD`;
143972
+ let commitCount = 0;
143973
+ let commitLog = "";
143974
+ let commitLogUnavailable = false;
143975
+ try {
143976
+ commitCount = parseInt(
143977
+ $("git", ["rev-list", "--count", baseRange], { log: false }).trim() || "0",
143978
+ 10
143979
+ );
143980
+ commitLog = $("git", ["log", "--oneline", `--max-count=${COMMIT_LOG_MAX}`, baseRange], {
143981
+ log: false
143982
+ });
143983
+ } catch (err) {
143984
+ commitLogUnavailable = true;
143985
+ log.debug(
143986
+ `\xBB unable to compute commit metadata for ${baseRange}: ${err instanceof Error ? err.message : String(err)}`
143987
+ );
143988
+ }
143989
+ const commitLogTruncated = commitCount > COMMIT_LOG_MAX;
143990
+ const hookWarningInstructions = checkoutResult.hookWarning ? ` HOOK WARNING: the post-checkout lifecycle hook reported a non-fatal failure (see hookWarning). decide whether to retry based on the guidance in that field before proceeding.` : "";
143991
+ const commitLogInstructions = commitLogUnavailable ? ` NOTE: commit metadata is partial (base ref unreachable, likely a shallow fetch). commitCount/commitLog may be 0/empty or incomplete; treat them as "unknown" rather than "no commits", and use \`git log\` directly if you need the full history.` : commitLogTruncated ? ` NOTE: commitLog was capped at ${COMMIT_LOG_MAX} entries out of ${commitCount} commits; use \`git log\` directly if you need the full history.` : "";
142495
143992
  return {
142496
143993
  success: true,
142497
143994
  number: prResponse.data.number,
142498
143995
  title: prResponse.data.title,
143996
+ body: prResponse.data.body,
142499
143997
  base: pr.baseRef,
142500
143998
  localBranch: `pr-${pull_number}`,
142501
143999
  remoteBranch: `refs/heads/${pr.headRef}`,
@@ -142506,7 +144004,12 @@ ${diffPreview}`);
142506
144004
  diffPath,
142507
144005
  incrementalDiffPath,
142508
144006
  toc: formatResult.toc,
142509
- instructions: `the diff file at diffPath contains a table of contents (TOC) at the top listing every changed file with its line range. use the line ranges to read specific files from the diff instead of reading the entire file. for example, if the TOC says "src/foo.ts \u2192 lines 5-42", read lines 5-42 from diffPath to see that file's changes. review files selectively based on relevance rather than reading everything sequentially. the local branch is 'localBranch' (pr-{number}), not the remote branch name. when pushing, omit branchName to use the current branch. do not use remoteBranch as a local branch name.` + incrementalInstructions
144007
+ commitCount,
144008
+ commitLog,
144009
+ commitLogTruncated,
144010
+ commitLogUnavailable,
144011
+ hookWarning: checkoutResult.hookWarning,
144012
+ instructions: `the diff file at diffPath contains a table of contents (TOC) at the top listing every changed file with its line range. use the TOC line ranges as your checklist and read specific files from the diff instead of reading the entire file. for example, if the TOC says "src/foo.ts \u2192 lines 5-42", read lines 5-42 from diffPath to see that file's changes. review files selectively based on relevance rather than reading everything sequentially. to inspect the PR's changed files, use diffPath \u2014 do NOT run \`git diff <base>..<head>\` to re-derive what's already in diffPath. the formatted diff with line numbers is authoritative. \`git log\` and \`git diff --stat\` are fine for commit-range overview, and \`git diff\` / \`git diff --cached\` are fine for inspecting *your own* uncommitted changes \u2014 but PR review content MUST come from diffPath. before your review is submitted, a one-time coverage pre-flight may error listing unread TOC regions. retry the same create_pull_request_review call to proceed \u2014 optionally after reading the listed ranges. the pre-flight will not block again this session. the local branch is 'localBranch' (pr-{number}), not the remote branch name. when pushing, omit branchName to use the current branch. do not use remoteBranch as a local branch name.` + incrementalInstructions + hookWarningInstructions + commitLogInstructions
142510
144013
  };
142511
144014
  })
142512
144015
  });
@@ -142734,244 +144237,6 @@ function CommitInfoTool(ctx) {
142734
144237
  });
142735
144238
  }
142736
144239
 
142737
- // mcp/git.ts
142738
- function getPushDestination(branch, storedDest) {
142739
- if (storedDest && storedDest.localBranch === branch) {
142740
- log.debug(`using stored push destination: ${storedDest.remoteName}/${storedDest.remoteBranch}`);
142741
- const url4 = $("git", ["remote", "get-url", "--push", storedDest.remoteName], {
142742
- log: false
142743
- }).trim();
142744
- return { remoteName: storedDest.remoteName, remoteBranch: storedDest.remoteBranch, url: url4 };
142745
- }
142746
- try {
142747
- const pushRemote = $("git", ["config", `branch.${branch}.pushRemote`], { log: false }).trim();
142748
- const merge4 = $("git", ["config", `branch.${branch}.merge`], { log: false }).trim();
142749
- const remoteBranch = merge4.replace(/^refs\/heads\//, "");
142750
- const url4 = $("git", ["remote", "get-url", "--push", pushRemote], { log: false }).trim();
142751
- return { remoteName: pushRemote, remoteBranch, url: url4 };
142752
- } catch {
142753
- log.debug(`no push config for ${branch}, falling back to origin/${branch}`);
142754
- const url4 = $("git", ["remote", "get-url", "--push", "origin"], { log: false }).trim();
142755
- return { remoteName: "origin", remoteBranch: branch, url: url4 };
142756
- }
142757
- }
142758
- function normalizeUrl(url4) {
142759
- return url4.replace(/\.git$/, "").toLowerCase();
142760
- }
142761
- function validatePushDestination(ctx, branch) {
142762
- const pushUrl = ctx.toolState.pushUrl;
142763
- if (!pushUrl) throw new Error("pushUrl not set - setupGit must run before push_branch");
142764
- const dest = getPushDestination(branch, ctx.toolState.pushDest);
142765
- if (normalizeUrl(dest.url) !== normalizeUrl(pushUrl)) {
142766
- throw new Error(
142767
- `Push blocked: destination does not match expected repository.
142768
- Expected: ${pushUrl}
142769
- Actual: ${dest.url}
142770
- Git configuration may have been tampered with.`
142771
- );
142772
- }
142773
- return dest;
142774
- }
142775
- var PushBranch = type({
142776
- branchName: type.string.describe("The branch name to push (defaults to current branch)").optional(),
142777
- force: type.boolean.describe("Force push (use with caution)").default(false)
142778
- });
142779
- function PushBranchTool(ctx) {
142780
- const defaultBranch = ctx.repo.data.default_branch || "main";
142781
- const pushPermission = ctx.payload.push;
142782
- return tool({
142783
- name: "push_branch",
142784
- description: "Push the current branch to the remote repository. Omit branchName to push the current branch (recommended). If specifying branchName, use the LOCAL branch name (e.g., 'pr-1'), not the remote branch name. The correct remote and remote branch are determined automatically from branch config set by checkout_pr. Requires a clean working tree. Runs the repository prepush hook (if configured) before the network push \u2014 hook failure means tests/lint or similar in that script failed, not necessarily a Pullfrog timeout. Never force push unless explicitly requested. Pushes to the default branch are blocked in restricted mode.",
142785
- parameters: PushBranch,
142786
- execute: execute(async ({ branchName, force }) => {
142787
- if (pushPermission === "disabled") {
142788
- throw new Error("Push is disabled. This repository is configured for read-only access.");
142789
- }
142790
- const branch = branchName || $("git", ["rev-parse", "--abbrev-ref", "HEAD"], { log: false });
142791
- const status = $("git", ["status", "--porcelain"], { log: false });
142792
- if (status) {
142793
- throw new Error(
142794
- `push blocked: working tree is not clean (tracked changes and/or untracked files). commit, discard, or remove stray artifacts before pushing.
142795
-
142796
- git status:
142797
- ${status}`
142798
- );
142799
- }
142800
- const pushDest = validatePushDestination(ctx, branch);
142801
- if (pushPermission === "restricted" && pushDest.remoteBranch === defaultBranch) {
142802
- throw new Error(
142803
- `Push blocked: cannot push directly to default branch '${pushDest.remoteBranch}'. Create a feature branch and open a PR instead.`
142804
- );
142805
- }
142806
- const refspec = branch === pushDest.remoteBranch ? branch : `${branch}:${pushDest.remoteBranch}`;
142807
- const pushArgs = force ? ["--force", "-u", pushDest.remoteName, refspec] : ["-u", pushDest.remoteName, refspec];
142808
- await executeLifecycleHook({ event: "prepush", script: ctx.prepushScript });
142809
- log.debug(`pushing ${branch} to ${pushDest.remoteName}/${pushDest.remoteBranch}`);
142810
- if (force) {
142811
- log.warning(`force pushing - this will overwrite remote history`);
142812
- }
142813
- try {
142814
- await $git("push", pushArgs, {
142815
- token: ctx.gitToken
142816
- });
142817
- } catch (err) {
142818
- const msg = err instanceof Error ? err.message : String(err);
142819
- if (msg.includes("fetch first") || msg.includes("non-fast-forward")) {
142820
- throw new Error(
142821
- `push rejected: the remote branch '${pushDest.remoteBranch}' has new commits you don't have locally.
142822
-
142823
- to resolve this:
142824
- 1. use git_fetch to fetch the remote branch: git_fetch({ ref: "${pushDest.remoteBranch}" })
142825
- 2. use the git tool to rebase your changes: git({ subcommand: "rebase", args: ["origin/${pushDest.remoteBranch}"] })
142826
- 3. resolve any merge conflicts if needed
142827
- 4. retry push_branch`
142828
- );
142829
- }
142830
- throw err;
142831
- }
142832
- return {
142833
- success: true,
142834
- branch,
142835
- remoteBranch: pushDest.remoteBranch,
142836
- remote: pushDest.remoteName,
142837
- force,
142838
- message: `successfully pushed ${branch} to ${pushDest.remoteName}/${pushDest.remoteBranch}`
142839
- };
142840
- })
142841
- });
142842
- }
142843
- var AUTH_REQUIRED_REDIRECT = {
142844
- push: "use the push_branch tool instead \u2014 it handles authentication and permission checks.",
142845
- fetch: "use the git_fetch tool instead \u2014 it handles authentication.",
142846
- pull: "use git_fetch to fetch the remote ref, then use this git tool with subcommand 'merge' or 'rebase' locally.",
142847
- clone: "the repository is already cloned. use checkout_pr for PR branches."
142848
- };
142849
- var NOSHELL_BLOCKED_SUBCOMMANDS = {
142850
- config: "Blocked: git config can set up filter drivers or hooks that execute arbitrary code.",
142851
- submodule: "Blocked: git submodule can reference malicious repositories and execute code on update.",
142852
- "update-index": "Blocked: git update-index can modify index entries in ways that bypass file protections.",
142853
- "filter-branch": "Blocked: git filter-branch executes arbitrary code on repository history.",
142854
- replace: "Blocked: git replace can redirect object lookups.",
142855
- // subcommands that accept --exec or similar flags for arbitrary code execution
142856
- rebase: "Blocked: git rebase --exec can execute arbitrary shell commands.",
142857
- bisect: "Blocked: git bisect run can execute arbitrary shell commands."
142858
- };
142859
- var NOSHELL_BLOCKED_ARGS = ["--exec", "--extcmd", "--upload-pack", "--receive-pack"];
142860
- var COLLAPSE_THRESHOLD = 200;
142861
- var subcommandPattern = regex("^[a-z][a-z0-9-]*$");
142862
- var Git = type({
142863
- subcommand: type(subcommandPattern).describe("Git subcommand (e.g., 'status', 'log', 'diff')"),
142864
- args: type.string.array().describe("Additional arguments for the git command").optional()
142865
- });
142866
- function GitTool(ctx) {
142867
- return tool({
142868
- name: "git",
142869
- description: "Run git commands. For push/fetch/pull, use the dedicated MCP tools instead (push_branch, git_fetch).",
142870
- parameters: Git,
142871
- execute: execute(async (params) => {
142872
- const subcommand = params.subcommand;
142873
- const args2 = params.args ?? [];
142874
- const redirect = AUTH_REQUIRED_REDIRECT[subcommand];
142875
- if (redirect) {
142876
- throw new Error(`git ${subcommand} is not available through this tool \u2014 ${redirect}`);
142877
- }
142878
- if (ctx.payload.shell === "disabled") {
142879
- const blocked = NOSHELL_BLOCKED_SUBCOMMANDS[subcommand];
142880
- if (blocked) {
142881
- throw new Error(blocked);
142882
- }
142883
- for (const arg of args2) {
142884
- const isBlocked = NOSHELL_BLOCKED_ARGS.some(
142885
- (flag) => arg === flag || arg.startsWith(flag + "=")
142886
- );
142887
- if (isBlocked) {
142888
- throw new Error(
142889
- `Blocked: '${arg}' flag can execute arbitrary code and is not allowed.`
142890
- );
142891
- }
142892
- }
142893
- }
142894
- const output = $("git", [subcommand, ...args2], { log: false });
142895
- const lineCount = output.split("\n").length;
142896
- if (lineCount > COLLAPSE_THRESHOLD) {
142897
- log.group(`git ${subcommand} output (${lineCount} lines)`, () => {
142898
- log.info(output);
142899
- });
142900
- } else if (output) {
142901
- log.info(output);
142902
- }
142903
- return { success: true, output };
142904
- })
142905
- });
142906
- }
142907
- var GitFetch = type({
142908
- ref: type.string.describe("Ref to fetch: branch name, tag, or 'pull/N/head' for PRs"),
142909
- depth: type.number.describe("Fetch depth (for shallow clones)").optional()
142910
- });
142911
- function GitFetchTool(ctx) {
142912
- return tool({
142913
- name: "git_fetch",
142914
- description: "Fetch refs from remote repository. Use this instead of git fetch directly.",
142915
- parameters: GitFetch,
142916
- execute: execute(async (params) => {
142917
- const fetchArgs = ["--no-tags", "origin", params.ref];
142918
- if (params.depth !== void 0) {
142919
- fetchArgs.push(`--depth=${params.depth}`);
142920
- }
142921
- await $git("fetch", fetchArgs, {
142922
- token: ctx.gitToken
142923
- });
142924
- return { success: true, ref: params.ref };
142925
- })
142926
- });
142927
- }
142928
- var DeleteBranch = type({
142929
- branchName: type.string.describe("Remote branch to delete")
142930
- });
142931
- function DeleteBranchTool(ctx) {
142932
- const pushPermission = ctx.payload.push;
142933
- return tool({
142934
- name: "delete_branch",
142935
- description: "Delete a remote branch. Requires push: enabled permission.",
142936
- parameters: DeleteBranch,
142937
- execute: execute(async (params) => {
142938
- if (pushPermission !== "enabled") {
142939
- throw new Error(
142940
- "Branch deletion requires push: enabled permission. Current mode only allows pushing to non-protected branches."
142941
- );
142942
- }
142943
- await $git("push", ["origin", "--delete", params.branchName], {
142944
- token: ctx.gitToken
142945
- });
142946
- return { success: true, deleted: params.branchName };
142947
- })
142948
- });
142949
- }
142950
- var PushTags = type({
142951
- tag: type.string.describe("Tag name to push"),
142952
- force: type.boolean.describe("Force push the tag").default(false)
142953
- });
142954
- function PushTagsTool(ctx) {
142955
- const pushPermission = ctx.payload.push;
142956
- return tool({
142957
- name: "push_tags",
142958
- description: "Push a tag to remote. Requires push: enabled permission.",
142959
- parameters: PushTags,
142960
- execute: execute(async (params) => {
142961
- if (pushPermission !== "enabled") {
142962
- throw new Error(
142963
- "Tag pushing requires push: enabled permission. Current mode only allows pushing branches."
142964
- );
142965
- }
142966
- const pushArgs = [...params.force ? ["-f"] : [], "origin", `refs/tags/${params.tag}`];
142967
- await $git("push", pushArgs, {
142968
- token: ctx.gitToken
142969
- });
142970
- return { success: true, tag: params.tag };
142971
- })
142972
- });
142973
- }
142974
-
142975
144240
  // mcp/issue.ts
142976
144241
  var Issue = type({
142977
144242
  title: type.string.describe("the title of the issue"),
@@ -143434,221 +144699,6 @@ function PullRequestInfoTool(ctx) {
143434
144699
  });
143435
144700
  }
143436
144701
 
143437
- // mcp/review.ts
143438
- function getHttpStatus(err) {
143439
- if (typeof err !== "object" || err === null) return void 0;
143440
- const status = err.status;
143441
- return typeof status === "number" ? status : void 0;
143442
- }
143443
- var CreatePullRequestReview = type({
143444
- pull_number: type.number.describe("The pull request number to review"),
143445
- body: type.string.describe(
143446
- "1-2 sentence high-level summary with urgency level, critical callouts, and feedback about code outside the diff. Specific feedback on diff lines goes in 'comments' array."
143447
- ).optional(),
143448
- approved: type.boolean.describe(
143449
- "Set to true to submit as an approval. ONLY when the review contains no actionable feedback \u2014 neither inline comments nor actionable content in the body. Defaults to false (comment-only review). Rejections are not supported."
143450
- ).optional(),
143451
- commit_id: type.string.describe("Optional SHA of the commit being reviewed. Defaults to latest.").optional(),
143452
- comments: type({
143453
- path: type.string.describe(
143454
- "The file path to comment on (relative to repo root). Must be a file that appears in the PR diff."
143455
- ),
143456
- line: type.number.describe(
143457
- "Line number to comment on. For multi-line ranges, this is the end line. Use NEW column from diff format."
143458
- ),
143459
- side: type.enumerated("LEFT", "RIGHT").describe(
143460
- "Side of the diff: LEFT (old code, lines starting with -) or RIGHT (new code, lines starting with + or unchanged). Defaults to RIGHT."
143461
- ).optional(),
143462
- body: type.string.describe("Explanatory comment text (optional if suggestion is provided)").optional(),
143463
- suggestion: type.string.describe(
143464
- "Full replacement code for the line range [start_line, line]. MUST preserve the exact indentation of the original code."
143465
- ).optional(),
143466
- start_line: type.number.describe(
143467
- "Start line for multi-line comment ranges. Omit for single-line comments. The range [start_line, line] defines which lines a suggestion replaces."
143468
- ).optional()
143469
- }).array().describe(
143470
- "Inline comments on lines within diff hunks. Feedback about code outside the diff goes in 'body' instead."
143471
- ).optional()
143472
- });
143473
- function CreatePullRequestReviewTool(ctx) {
143474
- return tool({
143475
- name: "create_pull_request_review",
143476
- description: `Submit a review for an existing pull request. Each call creates a permanent, visible review on the PR \u2014 NEVER submit test or diagnostic reviews. Reviews with no body AND no comments are silently skipped (nothing to post). IMPORTANT: 95%+ of feedback should be in 'comments' array with file paths and line numbers. Only use 'body' for a 1-2 sentence summary with urgency and critical callouts. Use 'suggestion' to propose replacement code - MUST preserve exact indentation of original code. Example replacing lines 42-44 (3 lines) with 5 lines: { path: 'src/api.ts', start_line: 42, line: 44, suggestion: ' const result = await fetch(url);\\n if (!result.ok) {\\n log.error(result.status);\\n throw new Error("request failed");\\n }' } CONSTRAINT: Inline comments can ONLY target files and lines that appear in the PR diff. If GitHub rejects comments due to incorrect line numbers, re-read the diff and retry.`,
143477
- parameters: CreatePullRequestReview,
143478
- execute: execute(async ({ pull_number, body, approved, commit_id, comments = [] }) => {
143479
- if (body) body = fixDoubleEscapedString(body);
143480
- if (body && ctx.toolState.selectedMode === "Review" && ctx.toolState.todoTracker) {
143481
- ctx.toolState.todoTracker.cancel();
143482
- await ctx.toolState.todoTracker.settled();
143483
- ctx.toolState.todoTracker.completeInProgress();
143484
- const collapsible = ctx.toolState.todoTracker.renderCollapsible();
143485
- if (collapsible) {
143486
- body = `${body}
143487
-
143488
- ${collapsible}`;
143489
- }
143490
- }
143491
- ctx.toolState.issueNumber = pull_number;
143492
- if (!approved && !body && comments.length === 0) {
143493
- log.info(
143494
- "review has no body and no inline comments \u2014 skipping submission (no issues found)"
143495
- );
143496
- return {
143497
- success: true,
143498
- skipped: true,
143499
- reason: "no issues found \u2014 nothing to post"
143500
- };
143501
- }
143502
- let event = approved ? "APPROVE" : "COMMENT";
143503
- if (event === "APPROVE" && !ctx.prApproveEnabled) {
143504
- log.info("prApproveEnabled is disabled \u2014 downgrading APPROVE to COMMENT");
143505
- event = "COMMENT";
143506
- }
143507
- const params = {
143508
- owner: ctx.repo.owner,
143509
- repo: ctx.repo.name,
143510
- pull_number,
143511
- event
143512
- };
143513
- let latestHeadSha;
143514
- if (commit_id) {
143515
- params.commit_id = commit_id;
143516
- } else {
143517
- const pr = await ctx.octokit.rest.pulls.get({
143518
- owner: ctx.repo.owner,
143519
- repo: ctx.repo.name,
143520
- pull_number
143521
- });
143522
- latestHeadSha = pr.data.head.sha;
143523
- params.commit_id = ctx.toolState.checkoutSha ?? latestHeadSha;
143524
- if (ctx.toolState.checkoutSha && latestHeadSha !== ctx.toolState.checkoutSha) {
143525
- log.info(
143526
- `anchoring review to checkout ${ctx.toolState.checkoutSha.slice(0, 7)} (HEAD is now ${latestHeadSha.slice(0, 7)})`
143527
- );
143528
- }
143529
- }
143530
- const reviewComments = comments.map((comment) => {
143531
- let commentBody = fixDoubleEscapedString(comment.body || "");
143532
- if (comment.suggestion !== void 0) {
143533
- const suggestionBlock = "```suggestion\n" + comment.suggestion + "\n```";
143534
- commentBody = commentBody ? commentBody + "\n\n" + suggestionBlock : suggestionBlock;
143535
- }
143536
- const side = comment.side || "RIGHT";
143537
- const reviewComment = {
143538
- path: comment.path,
143539
- line: comment.line,
143540
- body: commentBody,
143541
- side
143542
- };
143543
- if (comment.start_line != null && comment.start_line !== comment.line) {
143544
- reviewComment.start_line = comment.start_line;
143545
- reviewComment.start_side = side;
143546
- }
143547
- return reviewComment;
143548
- });
143549
- if (reviewComments.length > 0) {
143550
- params.comments = reviewComments;
143551
- }
143552
- let result;
143553
- try {
143554
- result = body ? await createAndSubmitWithFooter(ctx, params, {
143555
- body,
143556
- approved: approved ?? false,
143557
- hasComments: reviewComments.length > 0
143558
- }) : await ctx.octokit.rest.pulls.createReview(params);
143559
- } catch (err) {
143560
- if (getHttpStatus(err) !== 422 || !params.comments?.length) throw err;
143561
- const details = params.comments.map((c) => {
143562
- const line = c.line ?? 0;
143563
- const startLine = c.start_line ?? line;
143564
- const range2 = startLine !== line ? `${startLine}-${line}` : `${line}`;
143565
- return `${c.path}:${range2} (${c.side ?? "RIGHT"})`;
143566
- });
143567
- throw new Error(
143568
- `GitHub rejected inline comment(s) with "Line could not be resolved". This usually means the diff changed since you last read it (new commits pushed). Re-read the diff to get current line numbers, or move failing comments to the review body. Affected: ${details.join(", ")}`
143569
- );
143570
- }
143571
- log.debug(`createReview response: ${JSON.stringify(result.data)}`);
143572
- if (!result.data.id) {
143573
- throw new Error(`createReview returned invalid data: ${JSON.stringify(result.data)}`);
143574
- }
143575
- const reviewId = result.data.id;
143576
- const reviewNodeId = result.data.node_id;
143577
- const actuallyReviewedSha = ctx.toolState.checkoutSha ?? params.commit_id;
143578
- ctx.toolState.review = {
143579
- id: reviewId,
143580
- nodeId: reviewNodeId,
143581
- reviewedSha: actuallyReviewedSha
143582
- };
143583
- if (ctx.toolState.checkoutSha && latestHeadSha && latestHeadSha !== ctx.toolState.checkoutSha) {
143584
- const fromSha = ctx.toolState.checkoutSha;
143585
- const toSha = latestHeadSha;
143586
- ctx.toolState.beforeSha = fromSha;
143587
- ctx.toolState.checkoutSha = toSha;
143588
- log.info(
143589
- `new commits detected during review: ${fromSha.slice(0, 7)}..${toSha.slice(0, 7)}`
143590
- );
143591
- return {
143592
- success: true,
143593
- reviewId,
143594
- html_url: result.data.html_url,
143595
- state: result.data.state,
143596
- user: result.data.user?.login,
143597
- submitted_at: result.data.submitted_at,
143598
- newCommits: {
143599
- from: fromSha,
143600
- to: toSha,
143601
- instructions: `new commits were pushed while you were reviewing. call \`${formatMcpToolRef(ctx.agentId, "checkout_pr")}\` again to fetch the latest version \u2014 it will compute the incremental diff automatically. submit another review covering only the new changes. do not repeat feedback from your previous review.`
143602
- }
143603
- };
143604
- }
143605
- return {
143606
- success: true,
143607
- reviewId,
143608
- html_url: result.data.html_url,
143609
- state: result.data.state,
143610
- user: result.data.user?.login,
143611
- submitted_at: result.data.submitted_at
143612
- };
143613
- })
143614
- });
143615
- }
143616
- async function createAndSubmitWithFooter(ctx, params, opts) {
143617
- const { event: _, ...pendingParams } = params;
143618
- const pending = await ctx.octokit.rest.pulls.createReview(pendingParams);
143619
- if (!pending.data.id) {
143620
- throw new Error(`createReview returned invalid data: ${JSON.stringify(pending.data)}`);
143621
- }
143622
- const customParts = [];
143623
- if (!opts.approved) {
143624
- const apiUrl = getApiUrl();
143625
- if (opts.hasComments) {
143626
- const fixAllUrl = `${apiUrl}/trigger/${ctx.repo.owner}/${ctx.repo.name}/${params.pull_number}?action=fix&review_id=${pending.data.id}`;
143627
- const fixApprovedUrl = `${apiUrl}/trigger/${ctx.repo.owner}/${ctx.repo.name}/${params.pull_number}?action=fix-approved&review_id=${pending.data.id}`;
143628
- customParts.push(`[Fix all \u2794](${fixAllUrl})`, `[Fix \u{1F44D}s \u2794](${fixApprovedUrl})`);
143629
- } else {
143630
- const fixUrl = `${apiUrl}/trigger/${ctx.repo.owner}/${ctx.repo.name}/${params.pull_number}?action=fix&review_id=${pending.data.id}`;
143631
- customParts.push(`[Fix it \u2794](${fixUrl})`);
143632
- }
143633
- }
143634
- const footer = buildPullfrogFooter({
143635
- workflowRun: ctx.runId ? { owner: ctx.repo.owner, repo: ctx.repo.name, runId: ctx.runId, jobId: ctx.jobId } : void 0,
143636
- customParts,
143637
- model: ctx.toolState.model
143638
- });
143639
- return ctx.octokit.rest.pulls.submitReview({
143640
- owner: params.owner,
143641
- repo: params.repo,
143642
- pull_number: params.pull_number,
143643
- review_id: pending.data.id,
143644
- event: params.event,
143645
- body: opts.body + footer
143646
- });
143647
- }
143648
- async function reportReviewNodeId(ctx, params) {
143649
- await patchWorkflowRunFields(ctx, { reviewNodeId: params.nodeId });
143650
- }
143651
-
143652
144702
  // mcp/reviewComments.ts
143653
144703
  import { writeFileSync as writeFileSync4 } from "node:fs";
143654
144704
  import { join as join6 } from "node:path";
@@ -143696,7 +144746,7 @@ query ($owner: String!, $name: String!, $prNumber: Int!) {
143696
144746
  }
143697
144747
  }
143698
144748
  `;
143699
- function countLines(str) {
144749
+ function countLines2(str) {
143700
144750
  let count = 1;
143701
144751
  let index = -1;
143702
144752
  while ((index = str.indexOf("\n", index + 1)) !== -1) {
@@ -143838,13 +144888,13 @@ function formatReviewThreads(threadBlocks, header) {
143838
144888
  const reviewBodyLines = [];
143839
144889
  if (header.reviewBody) {
143840
144890
  reviewBodyLines.push("## Review Body", "", header.reviewBody, "");
143841
- currentLine += reviewBodyLines.reduce((sum, line) => sum + countLines(line), 0);
144891
+ currentLine += reviewBodyLines.reduce((sum, line) => sum + countLines2(line), 0);
143842
144892
  }
143843
144893
  const tocEntries = [];
143844
144894
  const threadLines = [];
143845
144895
  for (const block of threadBlocks) {
143846
144896
  const startLine = currentLine;
143847
- const actualLineCount = block.content.reduce((sum, line) => sum + countLines(line), 0);
144897
+ const actualLineCount = block.content.reduce((sum, line) => sum + countLines2(line), 0);
143848
144898
  const endLine = currentLine + actualLineCount - 1;
143849
144899
  tocEntries.push(`- ${block.path}:${block.lineRange} \u2192 lines ${startLine}-${endLine}`);
143850
144900
  threadLines.push(...block.content);
@@ -144167,7 +145217,7 @@ GitHub's markdown parser requires a blank line between ALL block-level elements.
144167
145217
  Rules:
144168
145218
  - \`##\` titles and key-change bullet lead-ins are plain-language summaries; backtick only actual code tokens (files, types, functions) where they appear in the title
144169
145219
  - ALL variable names, identifiers, and file names in body text must be in backticks
144170
- - ALL file references MUST link to the PR Files Changed view. Compute anchors by running \`echo -n 'path/to/file.ts' | sha256sum\` via shell for each file. NEVER fabricate hex strings \u2014 run the actual command. If shell is unavailable, omit the #diff- anchor rather than guessing.
145220
+ - ALL file references MUST link to the PR Files Changed view. Use the \`diff-<hex>\` anchor precomputed next to each filename in the \`checkout_pr\` TOC \u2014 do NOT run \`sha256sum\` or any other shell command to compute anchors. NEVER fabricate hex strings. If a file is not in the TOC, omit the \`#diff-\` anchor rather than guessing.
144171
145221
  - Add <br/> before each ## heading for visual spacing. Do NOT use horizontal rules (---)
144172
145222
  - Do NOT include raw diff stats like '+123 / -45' or line counts
144173
145223
  - Do NOT include code blocks or repeat diff contents
@@ -144242,7 +145292,7 @@ ${learningsStep(t, 6)}`
144242
145292
  description: "Review code, PRs, or implementations; provide feedback or suggestions; identify issues; or check code quality, style, and correctness",
144243
145293
  prompt: `### Checklist
144244
145294
 
144245
- 1. Checkout the PR via \`${t("checkout_pr")}\` \u2014 this returns PR metadata and a \`diffPath\`. Read the diff to identify the major areas of change.
145295
+ 1. Checkout the PR via \`${t("checkout_pr")}\` \u2014 this returns PR metadata and a \`diffPath\`. read the diff TOC first and treat its file line ranges as your coverage checklist.
144246
145296
 
144247
145297
  2. For each area of change:
144248
145298
  - read the diff and trace data flow, check boundaries, and verify assumptions
@@ -144259,6 +145309,7 @@ ${learningsStep(t, 6)}`
144259
145309
  4. Submit \u2014 ALWAYS submit exactly one review via \`${t("create_pull_request_review")}\`.
144260
145310
  Do NOT call \`report_progress\` \u2014 the review is the final record and the progress
144261
145311
  comment will be cleaned up automatically.
145312
+ 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.
144262
145313
 
144263
145314
  - **critical issues** (blocks merge \u2014 bugs, security, data loss):
144264
145315
  \`approved: false\`. Body begins with a GitHub alert blockquote, e.g.:
@@ -144276,7 +145327,7 @@ ${learningsStep(t, 6)}`
144276
145327
  description: "Re-review a PR after new commits are pushed; focus on new changes since the last review",
144277
145328
  prompt: `### Checklist
144278
145329
 
144279
- 1. Checkout the PR via \`${t("checkout_pr")}\` \u2014 this returns PR metadata, \`diffPath\` (full diff), and \`incrementalDiffPath\` (changes since last reviewed version, if available).
145330
+ 1. Checkout the PR via \`${t("checkout_pr")}\` \u2014 this returns PR metadata, \`diffPath\` (full diff), and \`incrementalDiffPath\` (changes since last reviewed version, if available). read the diff TOC first and use its line ranges as your coverage checklist.
144280
145331
 
144281
145332
  2. If \`incrementalDiffPath\` is present, read it to see what changed since the last review. This is a range-diff that isolates the net changes, filtering out base branch noise. If not present, fall back to reviewing the full PR diff.
144282
145333
 
@@ -144300,6 +145351,7 @@ ${learningsStep(t, 6)}`
144300
145351
  - in some cases you may receive a complete diff for the whole pull request instead of an incremental one. when this happens, you will need to determine what changes have happened since Pullfrog's most recent review.
144301
145352
 
144302
145353
  7. Submit \u2014 Do NOT call \`report_progress\` or \`create_issue_comment\` \u2014 the review is the final record and the progress comment will be cleaned up automatically. the review body always includes the reviewed changes from step 6a. append \`Prior review feedback:\\n\` with the checklist from step 6b only if any prior comments were addressed. Follow these rules:
145354
+ - 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.
144303
145355
  - IF NO NEW ISSUES, NON-SUBSTANTIVE CHANGES ONLY (trivial formatting, import reordering, comment tweaks): do NOT submit a review. Do NOT call \`report_progress\`. Exit \u2014 the progress comment will be cleaned up automatically.
144304
145356
  - ELSE IF NEW CRITICAL ISSUES (blocks merge): call \`${t("create_pull_request_review")}\` with \`approved: false\`, all comments, and the review body. body opens with a GitHub alert blockquote (e.g. \`> [!CAUTION]\\n> This PR introduces ...\`), then the reviewed changes summary and prior feedback (if any).
144305
145357
  - ELSE IF NEW RECOMMENDED CHANGES (non-critical): call \`${t("create_pull_request_review")}\` with \`approved: false\`, all comments, and the review body. body opens with \`> [!IMPORTANT]\\n> ...\` alert, then the reviewed changes summary and prior feedback (if any).
@@ -144404,7 +145456,7 @@ ${PR_SUMMARY_FORMAT}`
144404
145456
  }
144405
145457
  ];
144406
145458
  }
144407
- var modes = computeModes("opentoad");
145459
+ var modes = computeModes("opencode");
144408
145460
 
144409
145461
  // mcp/selectMode.ts
144410
145462
  var SelectModeParams = type({
@@ -144607,13 +145659,19 @@ function detectSandboxMethod() {
144607
145659
  } catch {
144608
145660
  }
144609
145661
  detectedSandboxMethod = "none";
144610
- log.info("PID namespace isolation not available - falling back to env filtering only");
145662
+ log.info("PID namespace isolation not available");
144611
145663
  return "none";
144612
145664
  }
144613
145665
  var PROC_CLEANUP = "umount /proc 2>/dev/null; umount /proc 2>/dev/null; mount -t proc proc /proc 2>/dev/null;";
144614
145666
  function spawnShell(params) {
144615
145667
  const spawnOpts = { env: params.env, cwd: params.cwd, stdio: params.stdio, detached: true };
144616
145668
  const sandboxMethod = detectSandboxMethod();
145669
+ const ci = process.env.CI === "true";
145670
+ if (ci && sandboxMethod === "none") {
145671
+ throw new Error(
145672
+ "pid namespace isolation is required in CI but unavailable (both unshare and sudo unshare failed)"
145673
+ );
145674
+ }
144617
145675
  if (sandboxMethod === "unshare") {
144618
145676
  return spawn2(
144619
145677
  "unshare",
@@ -144761,11 +145819,11 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
144761
145819
  await killProcessGroup(proc);
144762
145820
  }
144763
145821
  }, timeout);
144764
- const exitCode = await new Promise((resolve2) => {
145822
+ const exitCode = await new Promise((resolve3) => {
144765
145823
  const done = (code) => {
144766
145824
  exited = true;
144767
145825
  clearTimeout(timeoutId);
144768
- resolve2(code);
145826
+ resolve3(code);
144769
145827
  };
144770
145828
  proc.on("exit", done);
144771
145829
  proc.on("error", () => done(null));
@@ -144904,12 +145962,12 @@ function readEnvPort() {
144904
145962
  return parsed2;
144905
145963
  }
144906
145964
  function isPortAvailable(port) {
144907
- return new Promise((resolve2) => {
145965
+ return new Promise((resolve3) => {
144908
145966
  const server = createServer();
144909
145967
  server.unref();
144910
- server.once("error", () => resolve2(false));
145968
+ server.once("error", () => resolve3(false));
144911
145969
  server.once("listening", () => {
144912
- server.close(() => resolve2(true));
145970
+ server.close(() => resolve3(true));
144913
145971
  });
144914
145972
  server.listen(port, mcpHost);
144915
145973
  });
@@ -145049,9 +146107,12 @@ async function killBackgroundProcesses(toolState) {
145049
146107
  async function startMcpHttpServer(ctx, options) {
145050
146108
  const tools = buildOrchestratorTools(ctx, options?.outputSchema);
145051
146109
  const startResult = await selectMcpPort(ctx, tools);
146110
+ let disposed = false;
145052
146111
  return {
145053
146112
  url: startResult.url,
145054
146113
  [Symbol.asyncDispose]: async () => {
146114
+ if (disposed) return;
146115
+ disposed = true;
145055
146116
  closeBrowserDaemon(ctx.toolState);
145056
146117
  await killBackgroundProcesses(ctx.toolState);
145057
146118
  await startResult.server.stop();
@@ -145246,39 +146307,6 @@ var ThinkingTimer = class {
145246
146307
  }
145247
146308
  };
145248
146309
 
145249
- // agents/shared.ts
145250
- import { execFileSync as execFileSync2 } from "node:child_process";
145251
- var MAX_STDERR_LINES = 20;
145252
- var MAX_COMMIT_RETRIES = 3;
145253
- function getGitStatus() {
145254
- try {
145255
- return execFileSync2("git", ["status", "--porcelain"], {
145256
- encoding: "utf-8",
145257
- timeout: 1e4
145258
- }).trim();
145259
- } catch {
145260
- return "";
145261
- }
145262
- }
145263
- function buildCommitPrompt(_agentId, status) {
145264
- return [
145265
- `UNCOMMITTED CHANGES \u2014 the working tree is dirty. push all changes to a pull request (new or existing). \`git status\` must be clean before you finish.`,
145266
- "",
145267
- "```",
145268
- status,
145269
- "```"
145270
- ].join("\n");
145271
- }
145272
- var agent = (input) => {
145273
- return {
145274
- ...input,
145275
- run: async (ctx) => {
145276
- log.debug(`\xBB payload: ${JSON.stringify(ctx.payload, null, 2)}`);
145277
- return input.run(ctx);
145278
- }
145279
- };
145280
- };
145281
-
145282
146310
  // agents/claude.ts
145283
146311
  async function installClaudeCli() {
145284
146312
  return await installFromNpmTarball({
@@ -145317,6 +146345,7 @@ async function runClaude(params) {
145317
146345
  let finalOutput = "";
145318
146346
  let sessionId;
145319
146347
  let accumulatedTokens = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
146348
+ let accumulatedCostUsd = 0;
145320
146349
  let tokensLogged = false;
145321
146350
  function buildUsage() {
145322
146351
  const totalInput = accumulatedTokens.input + accumulatedTokens.cacheRead + accumulatedTokens.cacheWrite;
@@ -145325,7 +146354,8 @@ async function runClaude(params) {
145325
146354
  inputTokens: totalInput,
145326
146355
  outputTokens: accumulatedTokens.output,
145327
146356
  cacheReadTokens: accumulatedTokens.cacheRead || void 0,
145328
- cacheWriteTokens: accumulatedTokens.cacheWrite || void 0
146357
+ cacheWriteTokens: accumulatedTokens.cacheWrite || void 0,
146358
+ costUsd: accumulatedCostUsd > 0 ? accumulatedCostUsd : void 0
145329
146359
  } : void 0;
145330
146360
  }
145331
146361
  const handlers2 = {
@@ -145342,6 +146372,12 @@ async function runClaude(params) {
145342
146372
  finalOutput = message;
145343
146373
  } else if (block.type === "tool_use") {
145344
146374
  const toolName = block.name || "unknown";
146375
+ if (params.onToolUse) {
146376
+ params.onToolUse({
146377
+ toolName,
146378
+ input: block.input
146379
+ });
146380
+ }
145345
146381
  thinkingTimer.markToolCall();
145346
146382
  log.toolCall({ toolName, input: block.input || {} });
145347
146383
  if (toolName.includes("report_progress") && params.todoTracker) {
@@ -145357,6 +146393,8 @@ async function runClaude(params) {
145357
146393
  if (msgUsage) {
145358
146394
  accumulatedTokens.input += msgUsage.input_tokens || 0;
145359
146395
  accumulatedTokens.output += msgUsage.output_tokens || 0;
146396
+ accumulatedTokens.cacheRead += msgUsage.cache_read_input_tokens || 0;
146397
+ accumulatedTokens.cacheWrite += msgUsage.cache_creation_input_tokens || 0;
145360
146398
  }
145361
146399
  },
145362
146400
  user: (event) => {
@@ -145387,19 +146425,18 @@ async function runClaude(params) {
145387
146425
  const cacheRead = usage?.cache_read_input_tokens || 0;
145388
146426
  const cacheWrite = usage?.cache_creation_input_tokens || 0;
145389
146427
  const outputTokens = usage?.output_tokens || 0;
145390
- const totalInput = inputTokens + cacheRead + cacheWrite;
146428
+ const costUsd = typeof event.total_cost_usd === "number" && Number.isFinite(event.total_cost_usd) ? event.total_cost_usd : 0;
145391
146429
  accumulatedTokens = { input: inputTokens, output: outputTokens, cacheRead, cacheWrite };
146430
+ accumulatedCostUsd = costUsd;
145392
146431
  log.info(`\xBB ${params.label} result: subtype=${subtype}, turns=${numTurns}`);
145393
146432
  if (!tokensLogged) {
145394
- log.table([
145395
- [
145396
- { data: "Input", header: true },
145397
- { data: "Cache Read", header: true },
145398
- { data: "Cache Write", header: true },
145399
- { data: "Output", header: true }
145400
- ],
145401
- [String(totalInput), String(cacheRead), String(cacheWrite), String(outputTokens)]
145402
- ]);
146433
+ logTokenTable({
146434
+ input: inputTokens,
146435
+ cacheRead,
146436
+ cacheWrite,
146437
+ output: outputTokens,
146438
+ costUsd
146439
+ });
145403
146440
  tokensLogged = true;
145404
146441
  }
145405
146442
  } else if (subtype === "error_max_turns") {
@@ -145434,6 +146471,7 @@ async function runClaude(params) {
145434
146471
  cwd: params.cwd,
145435
146472
  env: params.env,
145436
146473
  activityTimeout: 3e5,
146474
+ onActivityTimeout: params.onActivityTimeout,
145437
146475
  stdio: ["ignore", "pipe", "pipe"],
145438
146476
  onStdout: async (chunk) => {
145439
146477
  const text = chunk.toString();
@@ -145445,25 +146483,33 @@ async function runClaude(params) {
145445
146483
  for (const line of lines) {
145446
146484
  const trimmed = line.trim();
145447
146485
  if (!trimmed) continue;
146486
+ let event;
145448
146487
  try {
145449
- const event = JSON.parse(trimmed);
145450
- eventCount++;
145451
- log.debug(JSON.stringify(event, null, 2));
145452
- const timeSinceLastActivity = getIdleMs();
145453
- if (timeSinceLastActivity > 1e4) {
145454
- log.info(
145455
- `\xBB no activity for ${(timeSinceLastActivity / 1e3).toFixed(1)}s (${params.label} may be processing internally) (${eventCount} events processed so far)`
145456
- );
145457
- }
145458
- markActivity();
145459
- const handler2 = handlers2[event.type];
145460
- if (handler2) {
145461
- handler2(event);
145462
- } else {
145463
- log.debug(`\xBB ${params.label} event (unhandled): type=${event.type}`);
145464
- }
146488
+ event = JSON.parse(trimmed);
145465
146489
  } catch {
145466
146490
  log.debug(`\xBB non-JSON stdout line: ${trimmed.substring(0, 200)}`);
146491
+ continue;
146492
+ }
146493
+ eventCount++;
146494
+ log.debug(JSON.stringify(event, null, 2));
146495
+ const timeSinceLastActivity = getIdleMs();
146496
+ if (timeSinceLastActivity > 1e4) {
146497
+ log.info(
146498
+ `\xBB no activity for ${(timeSinceLastActivity / 1e3).toFixed(1)}s (${params.label} may be processing internally) (${eventCount} events processed so far)`
146499
+ );
146500
+ }
146501
+ markActivity();
146502
+ const handler2 = handlers2[event.type];
146503
+ if (!handler2) {
146504
+ log.debug(`\xBB ${params.label} event (unhandled): type=${event.type}`);
146505
+ continue;
146506
+ }
146507
+ try {
146508
+ handler2(event);
146509
+ } catch (err) {
146510
+ log.info(
146511
+ `\xBB ${params.label} handler for type=${event.type} threw: ${err instanceof Error ? err.message : String(err)}`
146512
+ );
145467
146513
  }
145468
146514
  }
145469
146515
  },
@@ -145497,16 +146543,9 @@ async function runClaude(params) {
145497
146543
  if (stderrContext) log.info(`\xBB last stderr output:
145498
146544
  ${stderrContext}`);
145499
146545
  }
145500
- if (!tokensLogged && (accumulatedTokens.input > 0 || accumulatedTokens.output > 0)) {
145501
- const totalTokens = accumulatedTokens.input + accumulatedTokens.output;
145502
- log.table([
145503
- [
145504
- { data: "Input Tokens", header: true },
145505
- { data: "Output Tokens", header: true },
145506
- { data: "Total Tokens", header: true }
145507
- ],
145508
- [String(accumulatedTokens.input), String(accumulatedTokens.output), String(totalTokens)]
145509
- ]);
146546
+ if (!tokensLogged && (accumulatedTokens.input > 0 || accumulatedTokens.output > 0 || accumulatedTokens.cacheRead > 0 || accumulatedTokens.cacheWrite > 0)) {
146547
+ logTokenTable({ ...accumulatedTokens, costUsd: accumulatedCostUsd });
146548
+ tokensLogged = true;
145510
146549
  }
145511
146550
  const usage = buildUsage();
145512
146551
  if (result.exitCode !== 0) {
@@ -145539,7 +146578,7 @@ ${stderrContext}`);
145539
146578
  params.todoTracker?.cancel();
145540
146579
  const duration4 = performance6.now() - startTime;
145541
146580
  const errorMessage = error49 instanceof Error ? error49.message : String(error49);
145542
- const isActivityTimeout = errorMessage.includes("activity timeout");
146581
+ const isActivityTimeout = error49 instanceof SpawnTimeoutError && error49.code === SPAWN_ACTIVITY_TIMEOUT_CODE;
145543
146582
  const stderrContext = recentStderr.slice(-10).join("\n");
145544
146583
  const diagnosis = lastProviderError ? `likely cause: ${lastProviderError}` : eventCount === 0 ? "Claude produced 0 stdout events - check if the API is reachable" : `${eventCount} events were processed before the hang`;
145545
146584
  log.info(
@@ -145630,8 +146669,7 @@ var claude = agent({
145630
146669
  "--effort",
145631
146670
  effort,
145632
146671
  "--disallowedTools",
145633
- "Bash",
145634
- "Agent(Bash)"
146672
+ "Bash,Agent(Bash)"
145635
146673
  ];
145636
146674
  if (model) {
145637
146675
  baseArgs.push("--model", model);
@@ -145644,11 +146682,19 @@ var claude = agent({
145644
146682
  log.info(`\xBB effort: ${effort}`);
145645
146683
  log.debug(`\xBB starting Pullfrog (Claude Code): node ${baseArgs.join(" ")}`);
145646
146684
  log.debug(`\xBB working directory: ${repoDir}`);
145647
- const runParams = { label: "Pullfrog", cwd: repoDir, env: env2, todoTracker: ctx.todoTracker };
146685
+ const runParams = {
146686
+ label: "Pullfrog",
146687
+ cwd: repoDir,
146688
+ env: env2,
146689
+ todoTracker: ctx.todoTracker,
146690
+ onActivityTimeout: ctx.onActivityTimeout,
146691
+ onToolUse: ctx.onToolUse
146692
+ };
145648
146693
  let result = await runClaude({
145649
146694
  ...runParams,
145650
146695
  args: [...baseArgs, "-p", ctx.instructions.full]
145651
146696
  });
146697
+ let aggregatedUsage = result.usage;
145652
146698
  for (let attempt = 0; attempt < MAX_COMMIT_RETRIES; attempt++) {
145653
146699
  if (!result.success || !result.sessionId) break;
145654
146700
  const status = getGitStatus();
@@ -145665,12 +146711,13 @@ ${status}`);
145665
146711
  result.sessionId
145666
146712
  ]
145667
146713
  });
146714
+ aggregatedUsage = mergeAgentUsage(aggregatedUsage, result.usage);
145668
146715
  }
145669
- return result;
146716
+ return { ...result, usage: aggregatedUsage };
145670
146717
  }
145671
146718
  });
145672
146719
 
145673
- // agents/opentoad.ts
146720
+ // agents/opencode.ts
145674
146721
  import { execFileSync as execFileSync4 } from "node:child_process";
145675
146722
  import { mkdirSync as mkdirSync4 } from "node:fs";
145676
146723
  import { join as join10 } from "node:path";
@@ -145748,6 +146795,7 @@ async function runOpenCode(params) {
145748
146795
  const thinkingTimer = new ThinkingTimer();
145749
146796
  let finalOutput = "";
145750
146797
  let accumulatedTokens = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
146798
+ let accumulatedCostUsd = 0;
145751
146799
  let tokensLogged = false;
145752
146800
  const toolCallTimings = /* @__PURE__ */ new Map();
145753
146801
  let currentStepId = null;
@@ -145760,7 +146808,8 @@ async function runOpenCode(params) {
145760
146808
  inputTokens: totalInput,
145761
146809
  outputTokens: accumulatedTokens.output,
145762
146810
  cacheReadTokens: accumulatedTokens.cacheRead || void 0,
145763
- cacheWriteTokens: accumulatedTokens.cacheWrite || void 0
146811
+ cacheWriteTokens: accumulatedTokens.cacheWrite || void 0,
146812
+ costUsd: accumulatedCostUsd > 0 ? accumulatedCostUsd : void 0
145764
146813
  } : void 0;
145765
146814
  }
145766
146815
  const handlers2 = {
@@ -145771,6 +146820,7 @@ async function runOpenCode(params) {
145771
146820
  log.debug(`\xBB ${params.label} init event (full): ${JSON.stringify(event)}`);
145772
146821
  finalOutput = "";
145773
146822
  accumulatedTokens = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
146823
+ accumulatedCostUsd = 0;
145774
146824
  tokensLogged = false;
145775
146825
  },
145776
146826
  message: (event) => {
@@ -145815,6 +146865,9 @@ async function runOpenCode(params) {
145815
146865
  accumulatedTokens.cacheRead += eventTokens.cache?.read || 0;
145816
146866
  accumulatedTokens.cacheWrite += eventTokens.cache?.write || 0;
145817
146867
  }
146868
+ if (typeof event.part?.cost === "number" && Number.isFinite(event.part.cost)) {
146869
+ accumulatedCostUsd += event.part.cost;
146870
+ }
145818
146871
  if (currentStepId === stepId) {
145819
146872
  currentStepId = null;
145820
146873
  currentStepType = null;
@@ -145832,6 +146885,12 @@ async function runOpenCode(params) {
145832
146885
  if (stepHistory.length > 0) {
145833
146886
  stepHistory[stepHistory.length - 1].toolCalls.push(toolName);
145834
146887
  }
146888
+ if (params.onToolUse) {
146889
+ params.onToolUse({
146890
+ toolName,
146891
+ input: event.part?.state?.input
146892
+ });
146893
+ }
145835
146894
  thinkingTimer.markToolCall();
145836
146895
  log.toolCall({ toolName, input: event.part?.state?.input || {} });
145837
146896
  if (event.part?.state?.status === "completed" && event.part.state.output) {
@@ -145887,19 +146946,9 @@ async function runOpenCode(params) {
145887
146946
  if (event.status === "error") {
145888
146947
  log.info(`\xBB ${params.label} failed: ${JSON.stringify(event)}`);
145889
146948
  } else {
145890
- const inputTokens = event.stats?.input_tokens || accumulatedTokens.input || 0;
145891
- const outputTokens = event.stats?.output_tokens || accumulatedTokens.output || 0;
145892
- const totalTokens = event.stats?.total_tokens || inputTokens + outputTokens;
145893
146949
  log.info(`\xBB run complete: tool_calls=${toolCalls}, duration=${duration4}ms`);
145894
- if ((inputTokens > 0 || outputTokens > 0) && !tokensLogged) {
145895
- log.table([
145896
- [
145897
- { data: "Input Tokens", header: true },
145898
- { data: "Output Tokens", header: true },
145899
- { data: "Total Tokens", header: true }
145900
- ],
145901
- [String(inputTokens), String(outputTokens), String(totalTokens)]
145902
- ]);
146950
+ if ((accumulatedTokens.input > 0 || accumulatedTokens.output > 0 || accumulatedTokens.cacheRead > 0 || accumulatedTokens.cacheWrite > 0) && !tokensLogged) {
146951
+ logTokenTable({ ...accumulatedTokens, costUsd: accumulatedCostUsd });
145903
146952
  tokensLogged = true;
145904
146953
  }
145905
146954
  }
@@ -145916,6 +146965,7 @@ async function runOpenCode(params) {
145916
146965
  cwd: params.cwd,
145917
146966
  env: params.env,
145918
146967
  activityTimeout: 3e5,
146968
+ onActivityTimeout: params.onActivityTimeout,
145919
146969
  stdio: ["ignore", "pipe", "pipe"],
145920
146970
  onStdout: async (chunk) => {
145921
146971
  const text = chunk.toString();
@@ -145927,29 +146977,37 @@ async function runOpenCode(params) {
145927
146977
  for (const line of lines) {
145928
146978
  const trimmed = line.trim();
145929
146979
  if (!trimmed) continue;
146980
+ let event;
145930
146981
  try {
145931
- const event = JSON.parse(trimmed);
145932
- eventCount++;
145933
- log.debug(JSON.stringify(event, null, 2));
145934
- const timeSinceLastActivity = getIdleMs();
145935
- if (timeSinceLastActivity > 1e4) {
145936
- const activeToolCalls = toolCallTimings.size;
145937
- const toolCallInfo = activeToolCalls > 0 ? ` (waiting for ${activeToolCalls} tool call${activeToolCalls > 1 ? "s" : ""})` : ` (${params.label} may be processing internally - LLM calls, planning, etc.)`;
145938
- log.info(
145939
- `\xBB no activity for ${(timeSinceLastActivity / 1e3).toFixed(1)}s${toolCallInfo} (${eventCount} events processed so far)`
145940
- );
145941
- }
145942
- markActivity();
145943
- const handler2 = handlers2[event.type];
145944
- if (handler2) {
145945
- await handler2(event);
145946
- } else {
145947
- log.info(
145948
- `\xBB ${params.label} event (unhandled): type=${event.type}, data=${JSON.stringify(event).substring(0, 500)}`
145949
- );
145950
- }
146982
+ event = JSON.parse(trimmed);
145951
146983
  } catch {
145952
146984
  log.debug(`\xBB non-JSON stdout line: ${trimmed.substring(0, 200)}`);
146985
+ continue;
146986
+ }
146987
+ eventCount++;
146988
+ log.debug(JSON.stringify(event, null, 2));
146989
+ const timeSinceLastActivity = getIdleMs();
146990
+ if (timeSinceLastActivity > 1e4) {
146991
+ const activeToolCalls = toolCallTimings.size;
146992
+ const toolCallInfo = activeToolCalls > 0 ? ` (waiting for ${activeToolCalls} tool call${activeToolCalls > 1 ? "s" : ""})` : ` (${params.label} may be processing internally - LLM calls, planning, etc.)`;
146993
+ log.info(
146994
+ `\xBB no activity for ${(timeSinceLastActivity / 1e3).toFixed(1)}s${toolCallInfo} (${eventCount} events processed so far)`
146995
+ );
146996
+ }
146997
+ markActivity();
146998
+ const handler2 = handlers2[event.type];
146999
+ if (!handler2) {
147000
+ log.info(
147001
+ `\xBB ${params.label} event (unhandled): type=${event.type}, data=${JSON.stringify(event).substring(0, 500)}`
147002
+ );
147003
+ continue;
147004
+ }
147005
+ try {
147006
+ await handler2(event);
147007
+ } catch (err) {
147008
+ log.info(
147009
+ `\xBB ${params.label} handler for type=${event.type} threw: ${err instanceof Error ? err.message : String(err)}`
147010
+ );
145953
147011
  }
145954
147012
  }
145955
147013
  },
@@ -145983,16 +147041,9 @@ async function runOpenCode(params) {
145983
147041
  if (stderrContext) log.info(`\xBB last stderr output:
145984
147042
  ${stderrContext}`);
145985
147043
  }
145986
- if (!tokensLogged && (accumulatedTokens.input > 0 || accumulatedTokens.output > 0)) {
145987
- const totalTokens = accumulatedTokens.input + accumulatedTokens.output;
145988
- log.table([
145989
- [
145990
- { data: "Input Tokens", header: true },
145991
- { data: "Output Tokens", header: true },
145992
- { data: "Total Tokens", header: true }
145993
- ],
145994
- [String(accumulatedTokens.input), String(accumulatedTokens.output), String(totalTokens)]
145995
- ]);
147044
+ if (!tokensLogged && (accumulatedTokens.input > 0 || accumulatedTokens.output > 0 || accumulatedTokens.cacheRead > 0 || accumulatedTokens.cacheWrite > 0)) {
147045
+ logTokenTable({ ...accumulatedTokens, costUsd: accumulatedCostUsd });
147046
+ tokensLogged = true;
145996
147047
  }
145997
147048
  const usage = buildUsage();
145998
147049
  if (result.exitCode !== 0) {
@@ -146018,7 +147069,7 @@ ${stderrContext}`);
146018
147069
  params.todoTracker?.cancel();
146019
147070
  const duration4 = performance7.now() - startTime;
146020
147071
  const errorMessage = error49 instanceof Error ? error49.message : String(error49);
146021
- const isActivityTimeout = errorMessage.includes("activity timeout");
147072
+ const isActivityTimeout = error49 instanceof SpawnTimeoutError && error49.code === SPAWN_ACTIVITY_TIMEOUT_CODE;
146022
147073
  const stderrContext = recentStderr.slice(-10).join("\n");
146023
147074
  const diagnosis = lastProviderError ? `likely cause: ${lastProviderError}` : eventCount === 0 ? "OpenCode produced 0 stdout events - check if the model provider is reachable" : `${eventCount} events were processed before the hang`;
146024
147075
  log.info(
@@ -146038,8 +147089,8 @@ ${stderrContext}`
146038
147089
  };
146039
147090
  }
146040
147091
  }
146041
- var opentoad = agent({
146042
- name: "opentoad",
147092
+ var opencode = agent({
147093
+ name: "opencode",
146043
147094
  install: installOpencodeCli,
146044
147095
  run: async (ctx) => {
146045
147096
  const cliPath = await installOpencodeCli();
@@ -146075,12 +147126,15 @@ var opentoad = agent({
146075
147126
  cliPath,
146076
147127
  cwd: repoDir,
146077
147128
  env: env2,
146078
- todoTracker: ctx.todoTracker
147129
+ todoTracker: ctx.todoTracker,
147130
+ onActivityTimeout: ctx.onActivityTimeout,
147131
+ onToolUse: ctx.onToolUse
146079
147132
  };
146080
147133
  let result = await runOpenCode({
146081
147134
  ...runParams,
146082
147135
  args: [...baseArgs, ctx.instructions.full]
146083
147136
  });
147137
+ let aggregatedUsage = result.usage;
146084
147138
  for (let attempt = 0; attempt < MAX_COMMIT_RETRIES; attempt++) {
146085
147139
  if (!result.success) break;
146086
147140
  const status = getGitStatus();
@@ -146089,15 +147143,16 @@ var opentoad = agent({
146089
147143
  ${status}`);
146090
147144
  result = await runOpenCode({
146091
147145
  ...runParams,
146092
- args: [...baseArgs, "--continue", buildCommitPrompt("opentoad", status)]
147146
+ args: [...baseArgs, "--continue", buildCommitPrompt("opencode", status)]
146093
147147
  });
147148
+ aggregatedUsage = mergeAgentUsage(aggregatedUsage, result.usage);
146094
147149
  }
146095
- return result;
147150
+ return { ...result, usage: aggregatedUsage };
146096
147151
  }
146097
147152
  });
146098
147153
 
146099
147154
  // agents/index.ts
146100
- var agents = { claude, opentoad };
147155
+ var agents = { claude, opencode };
146101
147156
 
146102
147157
  // utils/agent.ts
146103
147158
  function hasEnvVar(name) {
@@ -146110,13 +147165,11 @@ function hasClaudeCodeAuth() {
146110
147165
  function resolveModel(ctx) {
146111
147166
  const envModel = process.env.PULLFROG_MODEL?.trim();
146112
147167
  if (envModel) {
146113
- log.info(`\xBB model: ${envModel} (override via PULLFROG_MODEL)`);
146114
- return envModel;
147168
+ return resolveCliModel(envModel) ?? envModel;
146115
147169
  }
146116
147170
  if (ctx.slug) {
146117
147171
  const resolved = resolveCliModel(ctx.slug);
146118
147172
  if (resolved) {
146119
- log.info(`\xBB model: ${resolved} (resolved from ${ctx.slug})`);
146120
147173
  return resolved;
146121
147174
  }
146122
147175
  log.warning(`\xBB unknown model slug "${ctx.slug}" \u2014 agent will auto-select`);
@@ -146127,7 +147180,6 @@ function resolveAgent(ctx) {
146127
147180
  const envAgent = process.env.PULLFROG_AGENT?.trim();
146128
147181
  if (envAgent) {
146129
147182
  if (envAgent in agents) {
146130
- log.info(`\xBB agent: ${envAgent} (override via PULLFROG_AGENT)`);
146131
147183
  return agents[envAgent];
146132
147184
  }
146133
147185
  log.warning(`\xBB unknown PULLFROG_AGENT="${envAgent}" \u2014 falling through to auto-select`);
@@ -146136,13 +147188,12 @@ function resolveAgent(ctx) {
146136
147188
  try {
146137
147189
  const provider2 = getModelProvider(ctx.model);
146138
147190
  if (provider2 === "anthropic" && hasClaudeCodeAuth()) {
146139
- log.info(`\xBB agent: claude (auto-selected for ${ctx.model})`);
146140
147191
  return agents.claude;
146141
147192
  }
146142
147193
  } catch {
146143
147194
  }
146144
147195
  }
146145
- return agents.opentoad;
147196
+ return agents.opencode;
146146
147197
  }
146147
147198
 
146148
147199
  // utils/apiKeys.ts
@@ -150356,9 +151407,9 @@ async function startGitAuthServer(tmpdir3) {
150356
151407
  res.writeHead(409, { "Content-Type": "text/plain" });
150357
151408
  res.end("compromised");
150358
151409
  });
150359
- await new Promise((resolve2, reject) => {
151410
+ await new Promise((resolve3, reject) => {
150360
151411
  server.on("error", reject);
150361
- server.listen(0, "127.0.0.1", () => resolve2());
151412
+ server.listen(0, "127.0.0.1", () => resolve3());
150362
151413
  });
150363
151414
  const rawAddr = server.address();
150364
151415
  if (!rawAddr || typeof rawAddr === "string") {
@@ -150402,7 +151453,7 @@ async function startGitAuthServer(tmpdir3) {
150402
151453
  clearTimeout(entry.timeout);
150403
151454
  }
150404
151455
  codes.clear();
150405
- await new Promise((resolve2) => server.close(() => resolve2()));
151456
+ await new Promise((resolve3) => server.close(() => resolve3()));
150406
151457
  log.debug("git auth server closed");
150407
151458
  }
150408
151459
  return {
@@ -150589,7 +151640,7 @@ MCP servers provide tools you can call. Inspect your available MCP servers at st
150589
151640
 
150590
151641
  ### Git
150591
151642
 
150592
- Use \`${t("git")}\` for local git commands (status, log, diff, add, commit, checkout, branch, merge, etc.). For operations requiring remote authentication, use the dedicated MCP tools:
151643
+ Use \`${t("git")}\` for local git commands (status, log, add, commit, checkout, branch, merge, etc.). When reviewing a PR, do NOT re-derive the PR diff via \`git diff <base>..<head>\` \u2014 the diffPath returned by \`${t("checkout_pr")}\` is authoritative. \`git log\` and \`git diff --stat\` are fine for commit-range overview; \`git diff\` / \`git diff --cached\` are fine for inspecting your *own* uncommitted changes. For operations requiring remote authentication, use the dedicated MCP tools:
150593
151644
  - \`${t("push_branch")}\` - push current or specified branch
150594
151645
  - \`${t("git_fetch")}\` - fetch refs from remote
150595
151646
  - \`${t("checkout_pr")}\` - checkout a PR branch (fetches and configures push for forks)
@@ -150630,6 +151681,8 @@ Never use \`sleep\` to wait for commands to complete. Commands run synchronously
150630
151681
 
150631
151682
  When posting comments via ${pullfrogMcpName}, write as a professional team member would. Your final comments should be polished and actionable \u2014 do not include intermediate reasoning like "I'll now look at the code" or "Let me respond to the question."
150632
151683
 
151684
+ When embedding images (e.g. uploaded screenshots) in comments or PR bodies, always use markdown image syntax: \`![description](url)\`. Never paste a naked URL \u2014 it will not render as an image.
151685
+
150633
151686
  ### Progress reporting
150634
151687
 
150635
151688
  **Task list**: at the start of every run, create an internal task list based on the steps in your current mode. Update it as you complete each step. The system automatically renders this list to the progress comment \u2014 you do not need to call \`report_progress\` for this.
@@ -150765,7 +151818,7 @@ function normalizeEnv() {
150765
151818
 
150766
151819
  // utils/payload.ts
150767
151820
  var core4 = __toESM(require_core(), 1);
150768
- import { isAbsolute, resolve } from "node:path";
151821
+ import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
150769
151822
 
150770
151823
  // utils/versioning.ts
150771
151824
  var import_semver2 = __toESM(require_semver2(), 1);
@@ -150819,8 +151872,8 @@ function isPayloadEvent(value2) {
150819
151872
  function resolveCwd(cwd) {
150820
151873
  const workspace = process.env.GITHUB_WORKSPACE;
150821
151874
  if (!cwd) return workspace;
150822
- if (isAbsolute(cwd)) return cwd;
150823
- return workspace ? resolve(workspace, cwd) : cwd;
151875
+ if (isAbsolute2(cwd)) return cwd;
151876
+ return workspace ? resolve2(workspace, cwd) : cwd;
150824
151877
  }
150825
151878
  function resolvePromptInput() {
150826
151879
  const prompt = core4.getInput("prompt", { required: true });
@@ -150999,7 +152052,8 @@ var defaultSettings = {
150999
152052
  shell: "restricted",
151000
152053
  prApproveEnabled: false,
151001
152054
  modeInstructions: {},
151002
- learnings: null
152055
+ learnings: null,
152056
+ envAllowlist: null
151003
152057
  };
151004
152058
  var defaultRunContext = {
151005
152059
  settings: defaultSettings,
@@ -151079,7 +152133,7 @@ async function resolveRunContextData(params) {
151079
152133
  }
151080
152134
 
151081
152135
  // utils/setup.ts
151082
- import { execSync as execSync3 } from "node:child_process";
152136
+ import { execFileSync as execFileSync5, execSync as execSync3 } from "node:child_process";
151083
152137
  import { mkdtempSync } from "node:fs";
151084
152138
  import { tmpdir as tmpdir2 } from "node:os";
151085
152139
  import { join as join13 } from "node:path";
@@ -151089,6 +152143,51 @@ function createTempDirectory() {
151089
152143
  log.info(`\xBB created temp dir at ${sharedTempDir}`);
151090
152144
  return sharedTempDir;
151091
152145
  }
152146
+ function envScopedToRepo() {
152147
+ const scoped = { ...process.env };
152148
+ for (const key of Object.keys(scoped)) {
152149
+ if (key.startsWith("GIT_")) delete scoped[key];
152150
+ }
152151
+ return scoped;
152152
+ }
152153
+ function removeIncludeIfEntries(repoDir) {
152154
+ const env2 = envScopedToRepo();
152155
+ let configOutput;
152156
+ try {
152157
+ configOutput = execSync3("git config --local --get-regexp -z ^includeif\\.", {
152158
+ cwd: repoDir,
152159
+ encoding: "utf-8",
152160
+ stdio: "pipe",
152161
+ env: env2
152162
+ });
152163
+ } catch {
152164
+ log.debug("\xBB no includeIf credential entries to remove");
152165
+ return;
152166
+ }
152167
+ const seen = /* @__PURE__ */ new Set();
152168
+ for (const entry of configOutput.split("\0")) {
152169
+ if (!entry) continue;
152170
+ const nl = entry.indexOf("\n");
152171
+ const key = nl === -1 ? entry : entry.slice(0, nl);
152172
+ if (!key || seen.has(key)) continue;
152173
+ seen.add(key);
152174
+ try {
152175
+ execFileSync5("git", ["config", "--local", "--unset-all", key], {
152176
+ cwd: repoDir,
152177
+ stdio: "pipe",
152178
+ env: env2
152179
+ });
152180
+ } catch (error49) {
152181
+ log.debug(
152182
+ `\xBB failed to unset ${key}: ${error49 instanceof Error ? error49.message : String(error49)}`
152183
+ );
152184
+ }
152185
+ }
152186
+ if (seen.size > 0)
152187
+ log.info(
152188
+ `\xBB removed ${seen.size} includeIf credential ${seen.size === 1 ? "entry" : "entries"}`
152189
+ );
152190
+ }
151092
152191
  async function setupGit(params) {
151093
152192
  const repoDir = process.cwd();
151094
152193
  log.info("\xBB setting up git configuration...");
@@ -151135,24 +152234,7 @@ async function setupGit(params) {
151135
152234
  } catch {
151136
152235
  log.debug("\xBB no existing authentication headers to remove");
151137
152236
  }
151138
- try {
151139
- const configOutput = execSync3("git config --local --get-regexp ^includeif\\.", {
151140
- cwd: repoDir,
151141
- encoding: "utf-8",
151142
- stdio: "pipe"
151143
- });
151144
- for (const line of configOutput.trim().split("\n")) {
151145
- const key = line.split(" ")[0];
151146
- if (!key) continue;
151147
- execSync3(`git config --local --unset "${key}"`, {
151148
- cwd: repoDir,
151149
- stdio: "pipe"
151150
- });
151151
- }
151152
- log.info("\xBB removed includeIf credential entries");
151153
- } catch {
151154
- log.debug("\xBB no includeIf credential entries to remove");
151155
- }
152237
+ removeIncludeIfEntries(repoDir);
151156
152238
  const originUrl = `https://github.com/${params.owner}/${params.name}.git`;
151157
152239
  $("git", ["remote", "set-url", "origin", originUrl], { cwd: repoDir });
151158
152240
  params.toolState.pushUrl = originUrl;
@@ -151171,6 +152253,13 @@ function parseTimeString(input) {
151171
152253
  const seconds = parseInt(match3[3] || "0", 10);
151172
152254
  return (hours * 3600 + minutes * 60 + seconds) * 1e3;
151173
152255
  }
152256
+ var TIMEOUT_MAX_MS = 2147483647;
152257
+ function resolveTimeoutMs(input) {
152258
+ if (!input) return null;
152259
+ const parsed2 = parseTimeString(input);
152260
+ if (parsed2 === null || parsed2 <= 0 || parsed2 > TIMEOUT_MAX_MS) return null;
152261
+ return parsed2;
152262
+ }
151174
152263
 
151175
152264
  // utils/todoTracking.ts
151176
152265
  function isValidTodoStatus(value2) {
@@ -151273,9 +152362,12 @@ function createTodoTracker(onUpdate) {
151273
152362
  if (item.status === "in_progress") item.status = "completed";
151274
152363
  }
151275
152364
  },
151276
- renderCollapsible() {
152365
+ renderCollapsible(options) {
151277
152366
  if (state.size === 0) return "";
151278
- const todos = Array.from(state.values());
152367
+ const shouldCompleteInProgress = options?.completeInProgress === true;
152368
+ const todos = Array.from(state.values()).map(
152369
+ (item) => shouldCompleteInProgress && item.status === "in_progress" ? { ...item, status: "completed" } : item
152370
+ );
151279
152371
  const completed = todos.filter((t) => t.status === "completed").length;
151280
152372
  const markdown = renderTodoMarkdown(todos);
151281
152373
  return `<details>
@@ -151335,6 +152427,32 @@ function resolveOutputSchema() {
151335
152427
  log.info("\xBB structured output schema provided \u2014 output will be required");
151336
152428
  return parsed2;
151337
152429
  }
152430
+ function resolveTimeoutForLog(timeout) {
152431
+ if (!timeout) return "1h (default)";
152432
+ if (timeout === TIMEOUT_DISABLED) return "none (disabled)";
152433
+ return timeout;
152434
+ }
152435
+ function resolveModelForLog(ctx) {
152436
+ const envModel = process.env.PULLFROG_MODEL?.trim();
152437
+ if (envModel) return `${envModel} (override via PULLFROG_MODEL)`;
152438
+ if (ctx.payload.proxyModel) return `${ctx.payload.proxyModel} (proxy)`;
152439
+ if (ctx.resolvedModel && ctx.payload.model && ctx.payload.model !== ctx.resolvedModel) {
152440
+ return `${ctx.resolvedModel} (resolved from ${ctx.payload.model})`;
152441
+ }
152442
+ if (ctx.resolvedModel) return ctx.resolvedModel;
152443
+ if (ctx.payload.model) return `${ctx.payload.model} (unresolved)`;
152444
+ return "auto";
152445
+ }
152446
+ function resolveAgentForLog(ctx) {
152447
+ const envAgent = process.env.PULLFROG_AGENT?.trim();
152448
+ if (envAgent && envAgent === ctx.agentName) {
152449
+ return `${ctx.agentName} (override via PULLFROG_AGENT)`;
152450
+ }
152451
+ if (ctx.agentName === "claude" && ctx.resolvedModel) {
152452
+ return `${ctx.agentName} (auto-selected for ${ctx.resolvedModel})`;
152453
+ }
152454
+ return ctx.agentName;
152455
+ }
151338
152456
  async function mintProxyKey(ctx) {
151339
152457
  try {
151340
152458
  process.env.ACTIONS_ID_TOKEN_REQUEST_URL = ctx.oidcCredentials.requestUrl;
@@ -151394,6 +152512,7 @@ async function main() {
151394
152512
  }
151395
152513
  const timer = new Timer();
151396
152514
  let activityTimeout = null;
152515
+ let safetyNetTimer;
151397
152516
  const resolvedPromptInput = resolvePromptInput();
151398
152517
  const toolState = initToolState({
151399
152518
  progressCommentId: typeof resolvedPromptInput !== "string" ? resolvedPromptInput.progressCommentId : void 0
@@ -151413,6 +152532,9 @@ async function main() {
151413
152532
  const count = Object.keys(runContext.dbSecrets).length;
151414
152533
  if (count > 0) log.info(`\xBB ${count} db secret(s) loaded`);
151415
152534
  }
152535
+ if (runContext.repoSettings.envAllowlist) {
152536
+ setEnvAllowlist(runContext.repoSettings.envAllowlist);
152537
+ }
151416
152538
  const payload = resolvePayload(resolvedPromptInput, runContext.repoSettings);
151417
152539
  toolState.model = payload.model;
151418
152540
  if (payload.event.trigger === "pull_request_synchronize") {
@@ -151477,10 +152599,13 @@ async function main() {
151477
152599
  postCheckoutScript: runContext.repoSettings.postCheckoutScript
151478
152600
  });
151479
152601
  timer.checkpoint("git");
151480
- await executeLifecycleHook({
152602
+ const setupHook = await executeLifecycleHook({
151481
152603
  event: "setup",
151482
152604
  script: runContext.repoSettings.setupScript
151483
152605
  });
152606
+ if (setupHook.warning) {
152607
+ throw new Error(setupHook.warning);
152608
+ }
151484
152609
  timer.checkpoint("lifecycleHooks::setup");
151485
152610
  const agentId = agent2.name;
151486
152611
  const modes2 = [...computeModes(agentId), ...runContext.repoSettings.modes];
@@ -151502,17 +152627,22 @@ async function main() {
151502
152627
  runId: runInfo.runId,
151503
152628
  jobId: runInfo.jobId,
151504
152629
  mcpServerUrl: "",
151505
- tmpdir: tmpdir3
152630
+ tmpdir: tmpdir3,
152631
+ resolvedModel
151506
152632
  };
151507
152633
  const mcpHttpServer = __using(_stack, await startMcpHttpServer(toolContext, { outputSchema }), true);
151508
152634
  toolContext.mcpServerUrl = mcpHttpServer.url;
151509
152635
  log.info(`\xBB MCP server started at ${mcpHttpServer.url}`);
151510
152636
  timer.checkpoint("mcpServer");
151511
152637
  startInstallation(toolContext);
151512
- if (payload.model) log.info(`\xBB model: ${payload.model}`);
151513
- if (payload.timeout) log.info(`\xBB timeout: ${payload.timeout}`);
152638
+ const modelForLog = resolveModelForLog({ payload, resolvedModel });
152639
+ const agentForLog = resolveAgentForLog({ agentName: agent2.name, resolvedModel });
152640
+ const timeoutForLog = resolveTimeoutForLog(payload.timeout);
152641
+ log.info(`\xBB model: ${modelForLog}`);
152642
+ log.info(`\xBB agent: ${agentForLog}`);
151514
152643
  log.info(`\xBB push: ${payload.push}`);
151515
152644
  log.info(`\xBB shell: ${payload.shell}`);
152645
+ log.info(`\xBB timeout: ${timeoutForLog}`);
151516
152646
  const instructions = resolveInstructions({
151517
152647
  payload,
151518
152648
  repo: runContext.repo,
@@ -151534,6 +152664,18 @@ ${instructions.user}` : null,
151534
152664
  log.group("View full prompt", () => {
151535
152665
  log.info(instructions.full);
151536
152666
  });
152667
+ if (agentId === "opencode") {
152668
+ const pluginDir = join14(process.cwd(), ".opencode", "plugin");
152669
+ const hasPlugins = existsSync6(pluginDir) && readdirSync(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
152670
+ if (hasPlugins && toolState.dependencyInstallation?.promise) {
152671
+ log.info(
152672
+ "\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"
152673
+ );
152674
+ await toolState.dependencyInstallation.promise.catch(() => {
152675
+ });
152676
+ timer.checkpoint("awaitDepsForPlugins");
152677
+ }
152678
+ }
151537
152679
  activityTimeout = createProcessOutputActivityTimeout({
151538
152680
  timeoutMs: DEFAULT_ACTIVITY_TIMEOUT_MS,
151539
152681
  checkIntervalMs: DEFAULT_ACTIVITY_CHECK_INTERVAL_MS
@@ -151549,24 +152691,62 @@ ${instructions.user}` : null,
151549
152691
  }
151550
152692
  });
151551
152693
  toolState.todoTracker = todoTracker;
152694
+ let innerTimeoutFired = false;
152695
+ const onInnerActivityTimeout = () => {
152696
+ if (innerTimeoutFired) return;
152697
+ innerTimeoutFired = true;
152698
+ log.info(
152699
+ "\xBB inner activity timeout fired \u2014 stopping MCP server and starting 5min safety-net timer"
152700
+ );
152701
+ mcpHttpServer[Symbol.asyncDispose]().catch((err) => {
152702
+ log.debug(
152703
+ `mcp server stop after inner kill failed: ${err instanceof Error ? err.message : String(err)}`
152704
+ );
152705
+ });
152706
+ safetyNetTimer = setTimeout(
152707
+ () => {
152708
+ activityTimeout?.forceReject(
152709
+ "agent still pending 5min after inner activity kill \u2014 forcing exit"
152710
+ );
152711
+ },
152712
+ 5 * 60 * 1e3
152713
+ );
152714
+ safetyNetTimer.unref?.();
152715
+ };
151552
152716
  const agentPromise = agent2.run({
151553
152717
  payload,
151554
152718
  resolvedModel,
151555
152719
  mcpServerUrl: mcpHttpServer.url,
151556
152720
  tmpdir: tmpdir3,
151557
152721
  instructions,
151558
- todoTracker
152722
+ todoTracker,
152723
+ onActivityTimeout: onInnerActivityTimeout,
152724
+ onToolUse: (event) => {
152725
+ const wasTracked = recordDiffReadFromToolUse({
152726
+ state: toolState.diffCoverage,
152727
+ toolName: event.toolName,
152728
+ input: event.input,
152729
+ cwd: process.cwd()
152730
+ });
152731
+ if (!wasTracked) return;
152732
+ const trackedRanges = toolState.diffCoverage?.coveredRanges ?? [];
152733
+ log.debug(
152734
+ `\xBB diff coverage tracked from tool ${event.toolName} (${trackedRanges.length} merged range${trackedRanges.length === 1 ? "" : "s"})`
152735
+ );
152736
+ }
152737
+ });
152738
+ agentPromise.catch(() => {
151559
152739
  });
151560
152740
  let result;
151561
152741
  if (payload.timeout === TIMEOUT_DISABLED) {
151562
152742
  result = await Promise.race([agentPromise, activityTimeout.promise]);
151563
152743
  } else {
151564
- const parsed2 = payload.timeout ? parseTimeString(payload.timeout) : null;
151565
- if (payload.timeout && parsed2 === null) {
151566
- log.warning(`invalid timeout format "${payload.timeout}", using default 1h`);
152744
+ const usable = resolveTimeoutMs(payload.timeout);
152745
+ if (payload.timeout && usable === null) {
152746
+ log.warning(`invalid timeout "${payload.timeout}" (use --notimeout to disable), using 1h`);
151567
152747
  }
151568
- const timeoutMs = parsed2 ?? 36e5;
151569
- const actualTimeout = parsed2 !== null ? payload.timeout : "1h";
152748
+ const timeoutMs = usable ?? 36e5;
152749
+ const actualTimeout = usable !== null ? payload.timeout : "1h";
151570
152750
  let timeoutId;
151571
152751
  const timeoutPromise = new Promise((_3, reject) => {
151572
152752
  timeoutId = setTimeout(() => {
@@ -151628,7 +152808,14 @@ ${instructions.user}` : null,
151628
152808
  killTrackedChildren();
151629
152809
  log.error(errorMessage);
151630
152810
  try {
151631
- await writeJobSummary(toolState);
152811
+ const errorSummary = `### \u274C Pullfrog failed
152812
+
152813
+ \`\`\`
152814
+ ${errorMessage}
152815
+ \`\`\``;
152816
+ const usageSummary = formatUsageSummary(toolState.usageEntries);
152817
+ const parts = [errorSummary, toolState.lastProgressBody, usageSummary].filter(Boolean);
152818
+ await writeSummary(parts.join("\n\n"));
151632
152819
  } catch {
151633
152820
  }
151634
152821
  try {
@@ -151646,8 +152833,21 @@ ${instructions.user}` : null,
151646
152833
  };
151647
152834
  } finally {
151648
152835
  activityTimeout?.stop();
152836
+ if (safetyNetTimer) clearTimeout(safetyNetTimer);
151649
152837
  if (usageSummaryPath) {
151650
- await writeGitHubUsageSummaryToFile(usageSummaryPath);
152838
+ try {
152839
+ await writeGitHubUsageSummaryToFile(usageSummaryPath);
152840
+ } catch (err) {
152841
+ log.debug(
152842
+ `failed to write usage summary to ${usageSummaryPath}: ${err instanceof Error ? err.message : String(err)}`
152843
+ );
152844
+ }
152845
+ }
152846
+ if (toolContext) {
152847
+ const patch = aggregateUsage(toolState.usageEntries);
152848
+ if (Object.keys(patch).length > 0) {
152849
+ await patchWorkflowRunFields(toolContext, patch);
152850
+ }
151651
152851
  }
151652
152852
  }
151653
152853
  } catch (_2) {