pullfrog 0.0.203 → 0.0.204

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
@@ -107816,14 +107816,14 @@ var providers = {
107816
107816
  models: {
107817
107817
  grok: {
107818
107818
  displayName: "Grok",
107819
- resolve: "xai/grok-4",
107820
- openRouterResolve: "openrouter/x-ai/grok-4",
107819
+ resolve: "xai/grok-4.3",
107820
+ openRouterResolve: "openrouter/x-ai/grok-4.3",
107821
107821
  preferred: true
107822
107822
  },
107823
107823
  "grok-fast": {
107824
107824
  displayName: "Grok Fast",
107825
- resolve: "xai/grok-4-fast",
107826
- openRouterResolve: "openrouter/x-ai/grok-4-fast"
107825
+ resolve: "xai/grok-4-1-fast",
107826
+ openRouterResolve: "openrouter/x-ai/grok-4.1-fast"
107827
107827
  },
107828
107828
  "grok-code-fast": {
107829
107829
  displayName: "Grok Code Fast",
@@ -108030,8 +108030,8 @@ var providers = {
108030
108030
  },
108031
108031
  grok: {
108032
108032
  displayName: "Grok",
108033
- resolve: "openrouter/x-ai/grok-4",
108034
- openRouterResolve: "openrouter/x-ai/grok-4"
108033
+ resolve: "openrouter/x-ai/grok-4.3",
108034
+ openRouterResolve: "openrouter/x-ai/grok-4.3"
108035
108035
  },
108036
108036
  "deepseek-pro": {
108037
108037
  displayName: "DeepSeek Pro",
@@ -108300,6 +108300,93 @@ function aggregateUsage(entries) {
108300
108300
  return out;
108301
108301
  }
108302
108302
 
108303
+ // utils/progressComment.ts
108304
+ function parseProgressComment(raw2) {
108305
+ if (!raw2?.id) return void 0;
108306
+ const id = parseInt(raw2.id, 10);
108307
+ if (Number.isNaN(id) || id <= 0) return void 0;
108308
+ return { id, type: raw2.type };
108309
+ }
108310
+ async function updateProgressComment(ctx, comment, body) {
108311
+ const result = await (comment.type === "review" ? ctx.octokit.rest.pulls.updateReviewComment({
108312
+ owner: ctx.owner,
108313
+ repo: ctx.repo,
108314
+ comment_id: comment.id,
108315
+ body
108316
+ }) : ctx.octokit.rest.issues.updateComment({
108317
+ owner: ctx.owner,
108318
+ repo: ctx.repo,
108319
+ comment_id: comment.id,
108320
+ body
108321
+ }));
108322
+ return {
108323
+ id: result.data.id,
108324
+ body: result.data.body ?? void 0,
108325
+ html_url: result.data.html_url,
108326
+ node_id: result.data.node_id
108327
+ };
108328
+ }
108329
+ async function deleteProgressCommentApi(ctx, comment) {
108330
+ if (comment.type === "review") {
108331
+ await ctx.octokit.rest.pulls.deleteReviewComment({
108332
+ owner: ctx.owner,
108333
+ repo: ctx.repo,
108334
+ comment_id: comment.id
108335
+ });
108336
+ return;
108337
+ }
108338
+ await ctx.octokit.rest.issues.deleteComment({
108339
+ owner: ctx.owner,
108340
+ repo: ctx.repo,
108341
+ comment_id: comment.id
108342
+ });
108343
+ }
108344
+ async function createLeapingProgressComment(ctx, target, body) {
108345
+ if (target.kind === "reviewReply") {
108346
+ try {
108347
+ const result2 = await ctx.octokit.rest.pulls.createReplyForReviewComment({
108348
+ owner: ctx.owner,
108349
+ repo: ctx.repo,
108350
+ pull_number: target.pullNumber,
108351
+ comment_id: target.replyToCommentId,
108352
+ body
108353
+ });
108354
+ return {
108355
+ comment: { id: result2.data.id, type: "review" },
108356
+ body: result2.data.body ?? void 0,
108357
+ html_url: result2.data.html_url
108358
+ };
108359
+ } catch (error49) {
108360
+ console.warn(
108361
+ `[progressComment] review reply failed (parent ${target.replyToCommentId} on PR #${target.pullNumber}), falling back to issue comment:`,
108362
+ error49
108363
+ );
108364
+ const fallback = await ctx.octokit.rest.issues.createComment({
108365
+ owner: ctx.owner,
108366
+ repo: ctx.repo,
108367
+ issue_number: target.pullNumber,
108368
+ body
108369
+ });
108370
+ return {
108371
+ comment: { id: fallback.data.id, type: "issue" },
108372
+ body: fallback.data.body ?? void 0,
108373
+ html_url: fallback.data.html_url
108374
+ };
108375
+ }
108376
+ }
108377
+ const result = await ctx.octokit.rest.issues.createComment({
108378
+ owner: ctx.owner,
108379
+ repo: ctx.repo,
108380
+ issue_number: target.issueNumber,
108381
+ body
108382
+ });
108383
+ return {
108384
+ comment: { id: result.data.id, type: "issue" },
108385
+ body: result.data.body ?? void 0,
108386
+ html_url: result.data.html_url
108387
+ };
108388
+ }
108389
+
108303
108390
  // node_modules/.pnpm/@toon-format+toon@1.4.0/node_modules/@toon-format/toon/dist/index.mjs
108304
108391
  var LIST_ITEM_MARKER = "-";
108305
108392
  var LIST_ITEM_PREFIX = "- ";
@@ -108960,6 +109047,7 @@ async function reportProgress(ctx, params) {
108960
109047
  }
108961
109048
  const issueNumber = ctx.payload.event.issue_number ?? ctx.toolState.issueNumber;
108962
109049
  const isPlanMode = ctx.toolState.selectedMode === "Plan";
109050
+ const apiCtx = { octokit: ctx.octokit, owner: ctx.repo.owner, repo: ctx.repo.name };
108963
109051
  if (target_plan_comment === true && ctx.toolState.existingPlanCommentId === void 0) {
108964
109052
  log.warning("target_plan_comment requested but no existingPlanCommentId in tool state");
108965
109053
  }
@@ -108969,86 +109057,74 @@ async function reportProgress(ctx, params) {
108969
109057
  const bodyWithoutFooter = stripExistingFooter(body);
108970
109058
  const footer = buildCommentFooter(ctx, customParts);
108971
109059
  const bodyWithFooter = `${bodyWithoutFooter}${footer}`;
108972
- const result2 = await ctx.octokit.rest.issues.updateComment({
108973
- owner: ctx.repo.owner,
108974
- repo: ctx.repo.name,
108975
- comment_id: commentId,
108976
- body: bodyWithFooter
108977
- });
109060
+ const result = await updateProgressComment(
109061
+ apiCtx,
109062
+ { id: commentId, type: "issue" },
109063
+ bodyWithFooter
109064
+ );
108978
109065
  ctx.toolState.wasUpdated = true;
108979
- if (isPlanMode && result2.data.node_id) {
108980
- await patchWorkflowRunFields(ctx, { planCommentNodeId: result2.data.node_id });
109066
+ if (isPlanMode && result.node_id) {
109067
+ await patchWorkflowRunFields(ctx, { planCommentNodeId: result.node_id });
108981
109068
  }
108982
109069
  return {
108983
- commentId: result2.data.id,
108984
- url: result2.data.html_url,
108985
- body: result2.data.body || "",
109070
+ commentId: result.id,
109071
+ url: result.html_url,
109072
+ body: result.body || "",
108986
109073
  action: "updated"
108987
109074
  };
108988
109075
  }
108989
- const existingCommentId = ctx.toolState.progressCommentId;
108990
- if (existingCommentId) {
108991
- const customParts = isPlanMode && issueNumber !== void 0 ? [buildImplementPlanLink(ctx, issueNumber, existingCommentId)] : void 0;
109076
+ const existingComment = ctx.toolState.progressComment;
109077
+ if (existingComment) {
109078
+ const customParts = isPlanMode && issueNumber !== void 0 ? [buildImplementPlanLink(ctx, issueNumber, existingComment.id)] : void 0;
108992
109079
  const bodyWithoutFooter = stripExistingFooter(body);
108993
109080
  const footer = buildCommentFooter(ctx, customParts);
108994
109081
  const bodyWithFooter = `${bodyWithoutFooter}${footer}`;
108995
- const result2 = await ctx.octokit.rest.issues.updateComment({
108996
- owner: ctx.repo.owner,
108997
- repo: ctx.repo.name,
108998
- comment_id: existingCommentId,
108999
- body: bodyWithFooter
109000
- });
109082
+ const result = await updateProgressComment(apiCtx, existingComment, bodyWithFooter);
109001
109083
  ctx.toolState.wasUpdated = true;
109002
- if (isPlanMode && result2.data.node_id) {
109003
- await patchWorkflowRunFields(ctx, { planCommentNodeId: result2.data.node_id });
109084
+ if (isPlanMode && result.node_id) {
109085
+ await patchWorkflowRunFields(ctx, { planCommentNodeId: result.node_id });
109004
109086
  }
109005
109087
  return {
109006
- commentId: result2.data.id,
109007
- url: result2.data.html_url,
109008
- body: result2.data.body || "",
109088
+ commentId: result.id,
109089
+ url: result.html_url,
109090
+ body: result.body || "",
109009
109091
  action: "updated"
109010
109092
  };
109011
109093
  }
109012
- if (existingCommentId === null) {
109094
+ if (existingComment === null) {
109013
109095
  return { body, action: "skipped" };
109014
109096
  }
109015
109097
  if (issueNumber === void 0) {
109016
109098
  return { body, action: "skipped" };
109017
109099
  }
109018
109100
  const initialBody = addFooter(ctx, body);
109019
- const result = await ctx.octokit.rest.issues.createComment({
109020
- owner: ctx.repo.owner,
109021
- repo: ctx.repo.name,
109022
- issue_number: issueNumber,
109023
- body: initialBody
109024
- });
109025
- ctx.toolState.progressCommentId = result.data.id;
109101
+ const created = await createLeapingProgressComment(
109102
+ apiCtx,
109103
+ { kind: "issue", issueNumber },
109104
+ initialBody
109105
+ );
109106
+ ctx.toolState.progressComment = created.comment;
109026
109107
  ctx.toolState.wasUpdated = true;
109027
109108
  if (isPlanMode) {
109028
- const customParts = [buildImplementPlanLink(ctx, issueNumber, result.data.id)];
109109
+ const customParts = [buildImplementPlanLink(ctx, issueNumber, created.comment.id)];
109029
109110
  const bodyWithoutFooter = stripExistingFooter(body);
109030
109111
  const footer = buildCommentFooter(ctx, customParts);
109031
109112
  const bodyWithPlanLink = `${bodyWithoutFooter}${footer}`;
109032
- const updateResult = await ctx.octokit.rest.issues.updateComment({
109033
- owner: ctx.repo.owner,
109034
- repo: ctx.repo.name,
109035
- comment_id: result.data.id,
109036
- body: bodyWithPlanLink
109037
- });
109038
- if (updateResult.data.node_id) {
109039
- await patchWorkflowRunFields(ctx, { planCommentNodeId: updateResult.data.node_id });
109113
+ const updateResult = await updateProgressComment(apiCtx, created.comment, bodyWithPlanLink);
109114
+ if (updateResult.node_id) {
109115
+ await patchWorkflowRunFields(ctx, { planCommentNodeId: updateResult.node_id });
109040
109116
  }
109041
109117
  return {
109042
- commentId: updateResult.data.id,
109043
- url: updateResult.data.html_url,
109044
- body: updateResult.data.body || "",
109118
+ commentId: updateResult.id,
109119
+ url: updateResult.html_url,
109120
+ body: updateResult.body || "",
109045
109121
  action: "created"
109046
109122
  };
109047
109123
  }
109048
109124
  return {
109049
- commentId: result.data.id,
109050
- url: result.data.html_url,
109051
- body: result.data.body || "",
109125
+ commentId: created.comment.id,
109126
+ url: created.html_url,
109127
+ body: created.body || "",
109052
109128
  action: "created"
109053
109129
  };
109054
109130
  }
@@ -109093,23 +109169,22 @@ ${collapsible}`;
109093
109169
  });
109094
109170
  }
109095
109171
  async function deleteProgressComment(ctx) {
109096
- const existingCommentId = ctx.toolState.progressCommentId;
109097
- if (!existingCommentId) {
109172
+ const existing = ctx.toolState.progressComment;
109173
+ if (!existing) {
109098
109174
  return false;
109099
109175
  }
109100
109176
  try {
109101
- await ctx.octokit.rest.issues.deleteComment({
109102
- owner: ctx.repo.owner,
109103
- repo: ctx.repo.name,
109104
- comment_id: existingCommentId
109105
- });
109177
+ await deleteProgressCommentApi(
109178
+ { octokit: ctx.octokit, owner: ctx.repo.owner, repo: ctx.repo.name },
109179
+ existing
109180
+ );
109106
109181
  } catch (error49) {
109107
109182
  if (error49 instanceof Error && error49.message.includes("Not Found")) {
109108
109183
  } else {
109109
109184
  throw error49;
109110
109185
  }
109111
109186
  }
109112
- ctx.toolState.progressCommentId = null;
109187
+ ctx.toolState.progressComment = null;
109113
109188
  return true;
109114
109189
  }
109115
109190
  var ReplyToReviewComment = type({
@@ -142190,7 +142265,7 @@ var import_semver = __toESM(require_semver2(), 1);
142190
142265
  // package.json
142191
142266
  var package_default = {
142192
142267
  name: "pullfrog",
142193
- version: "0.0.203",
142268
+ version: "0.0.204",
142194
142269
  type: "module",
142195
142270
  bin: {
142196
142271
  pullfrog: "dist/cli.mjs",
@@ -142727,8 +142802,13 @@ async function $git(subcommand, args2, options) {
142727
142802
  }
142728
142803
  if (result.exitCode !== 0) {
142729
142804
  const stderr = result.stderr.trim();
142730
- log.info(`git ${subcommand} failed: ${stderr}`);
142731
- throw new Error(`git ${subcommand} failed: ${stderr}`);
142805
+ const stdout = result.stdout.trim();
142806
+ const detail = stderr && stdout ? `${stderr}
142807
+ --- stdout ---
142808
+ ${stdout}` : stderr || stdout || "(no output)";
142809
+ const message = `git ${subcommand} failed (exit ${result.exitCode}): ${detail}`;
142810
+ log.info(message);
142811
+ throw new Error(message);
142732
142812
  }
142733
142813
  return {
142734
142814
  stdout: result.stdout.trim(),
@@ -143005,6 +143085,34 @@ var PushBranch = type({
143005
143085
  branchName: type.string.describe("The branch name to push (defaults to current branch)").optional(),
143006
143086
  force: type.boolean.describe("Force push (use with caution)").default(false)
143007
143087
  });
143088
+ var CONCURRENT_PUSH_PATTERNS = ["fetch first", "non-fast-forward", "cannot lock ref"];
143089
+ var TRANSIENT_PATTERNS = [
143090
+ /RPC failed/i,
143091
+ /early EOF/,
143092
+ /the remote end hung up unexpectedly/,
143093
+ /Connection reset/i,
143094
+ /Could not resolve host/i,
143095
+ /Operation timed out/i,
143096
+ /HTTP\/2 stream \d+ was not closed cleanly/i,
143097
+ /unexpected disconnect while reading sideband packet/i,
143098
+ // libcurl HTTP 5xx surfaced by git over https. matches both the
143099
+ // libcurl-style "The requested URL returned error: 502" and the more
143100
+ // recent "HTTP 502" wording. most 4xx is intentionally excluded —
143101
+ // 401/403/404 indicate auth/permission problems that are not
143102
+ // retry-safe — but 429 (rate-limited / abuse detection) IS retry-safe
143103
+ // and GitHub occasionally surfaces it on git push, so it's included
143104
+ // explicitly below.
143105
+ /HTTP 5\d\d/,
143106
+ /returned error: 5\d\d/i,
143107
+ /HTTP 429/,
143108
+ /returned error: 429/i
143109
+ ];
143110
+ function classifyPushError(msg) {
143111
+ if (CONCURRENT_PUSH_PATTERNS.some((p) => msg.includes(p))) return "concurrent-push";
143112
+ if (TRANSIENT_PATTERNS.some((p) => p.test(msg))) return "transient";
143113
+ return "unknown";
143114
+ }
143115
+ var TRANSIENT_RETRY_DELAYS_MS = [2e3, 5e3];
143008
143116
  function PushBranchTool(ctx) {
143009
143117
  const defaultBranch = ctx.repo.data.default_branch || "main";
143010
143118
  const pushPermission = ctx.payload.push;
@@ -143055,25 +143163,48 @@ ${postHookStatus}`
143055
143163
  if (force) {
143056
143164
  log.warning(`force pushing - this will overwrite remote history`);
143057
143165
  }
143058
- try {
143059
- await $git("push", pushArgs, {
143060
- token: ctx.gitToken
143061
- });
143062
- } catch (err) {
143063
- const msg = err instanceof Error ? err.message : String(err);
143064
- if (msg.includes("fetch first") || msg.includes("non-fast-forward")) {
143065
- 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')`;
143066
- throw new Error(
143067
- `push rejected: the remote branch '${pushDest.remoteBranch}' has new commits you don't have locally.
143166
+ let lastErr;
143167
+ let pushed = false;
143168
+ for (let attempt = 0; attempt <= TRANSIENT_RETRY_DELAYS_MS.length; attempt++) {
143169
+ try {
143170
+ await $git("push", pushArgs, {
143171
+ token: ctx.gitToken
143172
+ });
143173
+ if (attempt > 0) {
143174
+ log.info(`push succeeded on attempt ${attempt + 1}`);
143175
+ }
143176
+ pushed = true;
143177
+ break;
143178
+ } catch (err) {
143179
+ lastErr = err;
143180
+ const msg = err instanceof Error ? err.message : String(err);
143181
+ const kind = classifyPushError(msg);
143182
+ if (kind === "concurrent-push") {
143183
+ 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')`;
143184
+ throw new Error(
143185
+ `push rejected: the remote branch '${pushDest.remoteBranch}' has new commits you don't have locally (often a concurrent push to the same branch).
143068
143186
 
143069
143187
  to resolve this:
143070
143188
  1. use git_fetch to fetch the remote branch: git_fetch({ ref: "${pushDest.remoteBranch}" })
143071
143189
  ${integrateStep}
143072
143190
  3. resolve any merge conflicts if needed
143073
143191
  4. retry push_branch`
143074
- );
143192
+ );
143193
+ }
143194
+ if (kind === "transient" && attempt < TRANSIENT_RETRY_DELAYS_MS.length) {
143195
+ const baseDelay = TRANSIENT_RETRY_DELAYS_MS[attempt] ?? 5e3;
143196
+ const delay2 = Math.round(baseDelay * (0.75 + Math.random() * 0.5));
143197
+ log.info(
143198
+ `push attempt ${attempt + 1} failed (transient), retrying in ${delay2}ms: ${msg.slice(0, 300)}`
143199
+ );
143200
+ await new Promise((r) => setTimeout(r, delay2));
143201
+ continue;
143202
+ }
143203
+ throw err;
143075
143204
  }
143076
- throw err;
143205
+ }
143206
+ if (!pushed) {
143207
+ throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
143077
143208
  }
143078
143209
  return {
143079
143210
  success: true,
@@ -145127,35 +145258,20 @@ async function getReviewThreads(input) {
145127
145258
  const username = input.approvedBy;
145128
145259
  return threadsForReview.filter((thread) => threadHasThumbsUpFrom(thread, username));
145129
145260
  }
145130
- async function getReviewData(input) {
145131
- const [review, threads] = await Promise.all([
145132
- input.octokit.rest.pulls.getReview({
145133
- owner: input.owner,
145134
- repo: input.name,
145135
- pull_number: input.pullNumber,
145136
- review_id: input.reviewId
145137
- }),
145138
- getReviewThreads(input)
145139
- ]);
145140
- const rawReviewBody = review.data.body;
145261
+ function formatReviewData(input) {
145262
+ const rawReviewBody = input.review.body;
145141
145263
  const reviewBody = rawReviewBody ? stripExistingFooter(rawReviewBody) : "";
145142
- const reviewer = review.data.user?.login ?? "unknown";
145143
- if (threads.length === 0 && !reviewBody) return void 0;
145264
+ const reviewer = input.review.user?.login ?? "unknown";
145265
+ if (input.threads.length === 0 && !reviewBody) return void 0;
145144
145266
  let threadBlocks = [];
145145
- if (threads.length > 0) {
145146
- const prFiles = await input.octokit.paginate(input.octokit.rest.pulls.listFiles, {
145147
- owner: input.owner,
145148
- repo: input.name,
145149
- pull_number: input.pullNumber,
145150
- per_page: 100
145151
- });
145267
+ if (input.threads.length > 0) {
145152
145268
  const filePatchMap = /* @__PURE__ */ new Map();
145153
- for (const file2 of prFiles) {
145269
+ for (const file2 of input.prFiles) {
145154
145270
  if (file2.patch) {
145155
145271
  filePatchMap.set(file2.filename, parseFilePatches(file2.patch));
145156
145272
  }
145157
145273
  }
145158
- threadBlocks = buildThreadBlocks(threads, filePatchMap, input.reviewId);
145274
+ threadBlocks = buildThreadBlocks(input.threads, filePatchMap, input.reviewId);
145159
145275
  }
145160
145276
  const formatted = formatReviewThreads(threadBlocks, {
145161
145277
  pullNumber: input.pullNumber,
@@ -145165,6 +145281,30 @@ async function getReviewData(input) {
145165
145281
  });
145166
145282
  return { threadBlocks, reviewer, formatted };
145167
145283
  }
145284
+ async function getReviewData(input) {
145285
+ const [review, threads] = await Promise.all([
145286
+ input.octokit.rest.pulls.getReview({
145287
+ owner: input.owner,
145288
+ repo: input.name,
145289
+ pull_number: input.pullNumber,
145290
+ review_id: input.reviewId
145291
+ }),
145292
+ getReviewThreads(input)
145293
+ ]);
145294
+ const prFiles = threads.length > 0 ? await input.octokit.paginate(input.octokit.rest.pulls.listFiles, {
145295
+ owner: input.owner,
145296
+ repo: input.name,
145297
+ pull_number: input.pullNumber,
145298
+ per_page: 100
145299
+ }) : [];
145300
+ return formatReviewData({
145301
+ review: review.data,
145302
+ threads,
145303
+ prFiles,
145304
+ pullNumber: input.pullNumber,
145305
+ reviewId: input.reviewId
145306
+ });
145307
+ }
145168
145308
  function GetReviewCommentsTool(ctx) {
145169
145309
  return tool({
145170
145310
  name: "get_review_comments",
@@ -146181,14 +146321,13 @@ function UploadFileTool(ctx) {
146181
146321
 
146182
146322
  // mcp/server.ts
146183
146323
  function initToolState(params) {
146184
- const parsed2 = params.progressCommentId ? parseInt(params.progressCommentId, 10) : NaN;
146185
- const resolvedId = Number.isNaN(parsed2) || parsed2 <= 0 ? void 0 : parsed2;
146186
- if (resolvedId) {
146187
- log.info(`\xBB using pre-created progress comment: ${resolvedId}`);
146324
+ const resolved = parseProgressComment(params.progressComment);
146325
+ if (resolved) {
146326
+ log.info(`\xBB using pre-created progress comment: ${resolved.id} (${resolved.type})`);
146188
146327
  }
146189
146328
  return {
146190
- progressCommentId: resolvedId,
146191
- hadProgressComment: !!resolvedId,
146329
+ progressComment: resolved,
146330
+ hadProgressComment: !!resolved,
146192
146331
  backgroundProcesses: /* @__PURE__ */ new Map(),
146193
146332
  usageEntries: []
146194
146333
  };
@@ -151925,8 +152064,8 @@ async function reportErrorToComment(ctx) {
151925
152064
  const formattedError = ctx.title ? `${ctx.title}
151926
152065
 
151927
152066
  ${ctx.error}` : ctx.error;
151928
- const commentId = ctx.toolState.progressCommentId;
151929
- if (!commentId) {
152067
+ const comment = ctx.toolState.progressComment;
152068
+ if (!comment) {
151930
152069
  return;
151931
152070
  }
151932
152071
  const repoContext = parseRepoContext();
@@ -151945,12 +152084,11 @@ ${ctx.error}` : ctx.error;
151945
152084
  customParts,
151946
152085
  model: ctx.toolState.model
151947
152086
  });
151948
- await octokit.rest.issues.updateComment({
151949
- owner: repoContext.owner,
151950
- repo: repoContext.name,
151951
- comment_id: commentId,
151952
- body: `${formattedError}${footer}`
151953
- });
152087
+ await updateProgressComment(
152088
+ { octokit, owner: repoContext.owner, repo: repoContext.name },
152089
+ comment,
152090
+ `${formattedError}${footer}`
152091
+ );
151954
152092
  ctx.toolState.wasUpdated = true;
151955
152093
  }
151956
152094
 
@@ -152450,7 +152588,10 @@ var JsonPayload = type({
152450
152588
  "eventInstructions?": "string",
152451
152589
  "event?": "object",
152452
152590
  "timeout?": "string | undefined",
152453
- "progressCommentId?": "string | undefined"
152591
+ "progressComment?": type({
152592
+ id: "string",
152593
+ type: "'issue' | 'review'"
152594
+ }).or("undefined")
152454
152595
  });
152455
152596
  var COLLABORATOR_PERMISSIONS = ["admin", "maintain", "write"];
152456
152597
  function isCollaborator(event) {
@@ -152532,7 +152673,7 @@ function resolvePayload(resolvedPromptInput, repoSettings) {
152532
152673
  event,
152533
152674
  timeout: inputs.timeout ?? jsonPayload?.timeout,
152534
152675
  cwd: resolveCwd(inputs.cwd),
152535
- progressCommentId: jsonPayload?.progressCommentId,
152676
+ progressComment: jsonPayload?.progressComment,
152536
152677
  // permissions: inputs > repoSettings > fallbacks
152537
152678
  push: inputs.push ?? repoSettings.push ?? "restricted",
152538
152679
  shell: resolvedShell,
@@ -152640,10 +152781,10 @@ async function handleAgentResult(ctx) {
152640
152781
  };
152641
152782
  }
152642
152783
 
152643
- // utils/runContextData.ts
152644
- var core5 = __toESM(require_core(), 1);
152645
-
152646
152784
  // utils/runContext.ts
152785
+ function isInfraCovered(params) {
152786
+ return params.isOss || params.plan === "payg";
152787
+ }
152647
152788
  var defaultSettings = {
152648
152789
  model: null,
152649
152790
  modes: [],
@@ -152661,7 +152802,8 @@ var defaultSettings = {
152661
152802
  var defaultRunContext = {
152662
152803
  settings: defaultSettings,
152663
152804
  apiToken: "",
152664
- oss: false
152805
+ oss: false,
152806
+ plan: "none"
152665
152807
  };
152666
152808
  async function fetchRunContext(params) {
152667
152809
  const timeoutMs = 3e4;
@@ -152700,6 +152842,7 @@ async function fetchRunContext(params) {
152700
152842
  },
152701
152843
  apiToken: data.apiToken,
152702
152844
  oss: data.oss ?? false,
152845
+ plan: data.plan ?? "none",
152703
152846
  proxyModel: data.proxyModel,
152704
152847
  dbSecrets: data.dbSecrets
152705
152848
  };
@@ -152710,6 +152853,7 @@ async function fetchRunContext(params) {
152710
152853
  }
152711
152854
 
152712
152855
  // utils/runContextData.ts
152856
+ var core5 = __toESM(require_core(), 1);
152713
152857
  async function resolveRunContextData(params) {
152714
152858
  log.info(`\xBB running Pullfrog v${package_default.version}...`);
152715
152859
  const repoContext = parseRepoContext();
@@ -152731,6 +152875,7 @@ async function resolveRunContextData(params) {
152731
152875
  repoSettings: runContext.settings,
152732
152876
  apiToken: runContext.apiToken,
152733
152877
  oss: runContext.oss,
152878
+ plan: runContext.plan,
152734
152879
  proxyModel: runContext.proxyModel,
152735
152880
  dbSecrets: runContext.dbSecrets
152736
152881
  };
@@ -153057,6 +153202,59 @@ function resolveAgentForLog(ctx) {
153057
153202
  }
153058
153203
  return ctx.agentName;
153059
153204
  }
153205
+ var BillingError = class extends Error {
153206
+ code;
153207
+ declineCode;
153208
+ needsReauthentication;
153209
+ constructor(message, opts = {}) {
153210
+ super(message);
153211
+ this.name = "BillingError";
153212
+ this.code = opts.code ?? null;
153213
+ this.declineCode = opts.declineCode ?? null;
153214
+ this.needsReauthentication = opts.needsReauthentication ?? false;
153215
+ }
153216
+ };
153217
+ var TransientError = class extends Error {
153218
+ constructor(message) {
153219
+ super(message);
153220
+ this.name = "TransientError";
153221
+ }
153222
+ };
153223
+ function formatBillingErrorSummary(error49) {
153224
+ if (error49.code === "router_requires_card") {
153225
+ return [
153226
+ "### \u26D4 Pullfrog Router requires a card",
153227
+ "",
153228
+ "This run was going to use Pullfrog Router, which bills at raw OpenRouter cost and needs a card on file. Runs won't proceed until a card is added.",
153229
+ "",
153230
+ "[Add a card \u2192](https://pullfrog.com/console#model-access) \u2014 your first $20 of Router usage is free."
153231
+ ].join("\n");
153232
+ }
153233
+ if (error49.needsReauthentication) {
153234
+ return [
153235
+ "### \u274C Pullfrog billing error \u2014 card requires 3DS on every charge",
153236
+ "",
153237
+ `Your card issuer requires a 3D Secure challenge on each off-session charge (\`${error49.declineCode ?? "authentication_required"}\`), which we can't run from the agent. Top up your Router credit balance manually \u2014 3DS runs interactively in Stripe Checkout, and subsequent runs draw from the prepaid balance without triggering another off-session charge.`,
153238
+ "",
153239
+ "[Top up your Router credit balance \u2192](https://pullfrog.com/console)"
153240
+ ].join("\n");
153241
+ }
153242
+ const codeSuffix = error49.declineCode ? ` (\`${error49.declineCode}\`)` : "";
153243
+ return `### \u274C Pullfrog billing error
153244
+
153245
+ ${error49.message}${codeSuffix}
153246
+
153247
+ [Manage billing \u2192](https://pullfrog.com/console)`;
153248
+ }
153249
+ function formatTransientErrorSummary(error49) {
153250
+ return [
153251
+ "### \u26A0\uFE0F Pullfrog temporarily unavailable",
153252
+ "",
153253
+ error49.message,
153254
+ "",
153255
+ "This is typically transient \u2014 the next dispatch should succeed. If it persists, check [status.pullfrog.com](https://status.pullfrog.com)."
153256
+ ].join("\n");
153257
+ }
153060
153258
  async function mintProxyKey(ctx) {
153061
153259
  try {
153062
153260
  process.env.ACTIONS_ID_TOKEN_REQUEST_URL = ctx.oidcCredentials.requestUrl;
@@ -153069,6 +153267,20 @@ async function mintProxyKey(ctx) {
153069
153267
  method: "POST",
153070
153268
  headers: { Authorization: `Bearer ${oidcToken}` }
153071
153269
  });
153270
+ if (response.status === 402) {
153271
+ const body = await response.json().catch(() => null);
153272
+ throw new BillingError(body?.error ?? "insufficient balance", {
153273
+ code: body?.code ?? null,
153274
+ declineCode: body?.declineCode ?? null,
153275
+ needsReauthentication: body?.needsReauthentication ?? false
153276
+ });
153277
+ }
153278
+ if (response.status === 503) {
153279
+ const body = await response.json().catch(() => null);
153280
+ throw new TransientError(
153281
+ body?.error ?? "billing service temporarily unavailable \u2014 retry shortly"
153282
+ );
153283
+ }
153072
153284
  if (!response.ok) {
153073
153285
  log.warning(`proxy key mint failed (${response.status})`);
153074
153286
  return null;
@@ -153076,6 +153288,8 @@ async function mintProxyKey(ctx) {
153076
153288
  const data = await response.json();
153077
153289
  return data.key;
153078
153290
  } catch (error49) {
153291
+ if (error49 instanceof BillingError) throw error49;
153292
+ if (error49 instanceof TransientError) throw error49;
153079
153293
  log.warning(`proxy key mint error: ${error49 instanceof Error ? error49.message : String(error49)}`);
153080
153294
  return null;
153081
153295
  } finally {
@@ -153085,19 +153299,19 @@ async function mintProxyKey(ctx) {
153085
153299
  }
153086
153300
  async function resolveProxyModel(ctx) {
153087
153301
  if (process.env.PULLFROG_MODEL?.trim()) return;
153088
- if (ctx.oss && ctx.proxyModel) {
153089
- if (!ctx.oidcCredentials) {
153090
- log.warning("\xBB oss repo but no OIDC credentials available \u2014 skipping proxy");
153091
- return;
153092
- }
153093
- const key = await mintProxyKey({ oidcCredentials: ctx.oidcCredentials });
153094
- if (!key) return;
153095
- process.env.OPENROUTER_API_KEY = key;
153096
- core6.setSecret(key);
153097
- ctx.payload.proxyModel = ctx.proxyModel;
153098
- log.info(`\xBB proxy: oss \u2192 ${ctx.proxyModel}`);
153302
+ const needsProxy = isInfraCovered({ isOss: ctx.oss, plan: ctx.plan }) && ctx.proxyModel;
153303
+ if (!needsProxy) return;
153304
+ if (!ctx.oidcCredentials) {
153305
+ log.warning("\xBB proxy requested but no OIDC credentials available \u2014 skipping");
153099
153306
  return;
153100
153307
  }
153308
+ const key = await mintProxyKey({ oidcCredentials: ctx.oidcCredentials });
153309
+ if (!key) return;
153310
+ process.env.OPENROUTER_API_KEY = key;
153311
+ core6.setSecret(key);
153312
+ ctx.payload.proxyModel = ctx.proxyModel;
153313
+ const label = ctx.oss ? "oss" : "router";
153314
+ log.info(`\xBB proxy: ${label} \u2192 ${ctx.proxyModel}`);
153101
153315
  }
153102
153316
  async function writeJobSummary(toolState) {
153103
153317
  const usageSummary = formatUsageSummary(toolState.usageEntries);
@@ -153119,7 +153333,7 @@ async function main() {
153119
153333
  let safetyNetTimer;
153120
153334
  const resolvedPromptInput = resolvePromptInput();
153121
153335
  const toolState = initToolState({
153122
- progressCommentId: typeof resolvedPromptInput !== "string" ? resolvedPromptInput.progressCommentId : void 0
153336
+ progressComment: typeof resolvedPromptInput !== "string" ? resolvedPromptInput.progressComment : void 0
153123
153337
  });
153124
153338
  resolveGit();
153125
153339
  const jobToken = getJobToken();
@@ -153153,12 +153367,33 @@ async function main() {
153153
153367
  delete process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
153154
153368
  delete process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
153155
153369
  }
153156
- await resolveProxyModel({
153157
- payload,
153158
- oss: runContext.oss,
153159
- proxyModel: runContext.proxyModel,
153160
- oidcCredentials
153161
- });
153370
+ try {
153371
+ await resolveProxyModel({
153372
+ payload,
153373
+ oss: runContext.oss,
153374
+ plan: runContext.plan,
153375
+ proxyModel: runContext.proxyModel,
153376
+ oidcCredentials
153377
+ });
153378
+ } catch (error49) {
153379
+ if (error49 instanceof BillingError) {
153380
+ const summary2 = formatBillingErrorSummary(error49);
153381
+ await writeSummary(summary2).catch(() => {
153382
+ });
153383
+ await reportErrorToComment({ toolState, error: summary2 }).catch(() => {
153384
+ });
153385
+ throw error49;
153386
+ }
153387
+ if (error49 instanceof TransientError) {
153388
+ const summary2 = formatTransientErrorSummary(error49);
153389
+ await writeSummary(summary2).catch(() => {
153390
+ });
153391
+ await reportErrorToComment({ toolState, error: summary2 }).catch(() => {
153392
+ });
153393
+ throw error49;
153394
+ }
153395
+ throw error49;
153396
+ }
153162
153397
  const octokit = createOctokit(tokenRef.mcpToken);
153163
153398
  const runInfo = await resolveRun({ octokit });
153164
153399
  let toolContext;
@@ -153232,6 +153467,8 @@ async function main() {
153232
153467
  jobId: runInfo.jobId,
153233
153468
  mcpServerUrl: "",
153234
153469
  tmpdir: tmpdir3,
153470
+ oss: runContext.oss,
153471
+ plan: runContext.plan,
153235
153472
  resolvedModel
153236
153473
  };
153237
153474
  const mcpHttpServer = __using(_stack, await startMcpHttpServer(toolContext, { outputSchema }), true);
@@ -153295,6 +153532,9 @@ ${instructions.user}` : null,
153295
153532
  }
153296
153533
  });
153297
153534
  toolState.todoTracker = todoTracker;
153535
+ onExitSignal(() => {
153536
+ todoTracker?.cancel();
153537
+ });
153298
153538
  let innerTimeoutFired = false;
153299
153539
  const onInnerActivityTimeout = () => {
153300
153540
  if (innerTimeoutFired) return;
@@ -153380,7 +153620,7 @@ ${instructions.user}` : null,
153380
153620
  });
153381
153621
  }
153382
153622
  const trackerWasLastWriter = todoTracker?.hasPublished && !toolState.finalSummaryWritten;
153383
- if (toolContext && toolState.progressCommentId && (!toolState.wasUpdated || trackerWasLastWriter)) {
153623
+ if (toolContext && toolState.progressComment && (!toolState.wasUpdated || trackerWasLastWriter)) {
153384
153624
  await deleteProgressComment(toolContext).catch((error49) => {
153385
153625
  log.debug(`stranded progress comment cleanup failed: ${error49}`);
153386
153626
  });