pullfrog 0.0.205 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -108399,7 +108399,11 @@ function resolveCliModel(slug2) {
108399
108399
  var PULLFROG_DIVIDER = "<!-- PULLFROG_DIVIDER_DO_NOT_REMOVE_PLZ -->";
108400
108400
  var FROG_LOGO = `<a href="https://pullfrog.com"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pullfrog.com/logos/frog-white-full-18px.png"><img src="https://pullfrog.com/logos/frog-green-full-18px.png" width="9px" height="9px" style="vertical-align: middle; " alt="Pullfrog"></picture></a>`;
108401
108401
  function formatModelLabel(slug2) {
108402
- const alias = resolveDisplayAlias(slug2);
108402
+ const alias = resolveDisplayAlias(slug2) ?? // reverse-lookup: when the caller passes an effective model (proxy or
108403
+ // resolved target like "openrouter/anthropic/claude-opus-4.7") instead of
108404
+ // a stored alias slug, find the alias whose resolve target matches so we
108405
+ // still render a friendly display name.
108406
+ modelAliases.find((a) => a.resolve === slug2 || a.openRouterResolve === slug2);
108403
108407
  if (!alias) return `\`${slug2}\``;
108404
108408
  return alias.isFree ? `\`${alias.displayName}\` (free)` : `\`${alias.displayName}\``;
108405
108409
  }
@@ -108474,10 +108478,13 @@ var defaultShouldRetry = (error49) => {
108474
108478
  return error49.name === "AbortError" || error49.message.includes("fetch failed") || error49.message.includes("ECONNRESET") || error49.message.includes("ETIMEDOUT");
108475
108479
  };
108476
108480
  async function retry(fn2, options = {}) {
108477
- const maxAttempts = options.maxAttempts ?? 3;
108478
- const delayMs = options.delayMs ?? 1e3;
108479
108481
  const shouldRetry = options.shouldRetry ?? defaultShouldRetry;
108480
108482
  const label = options.label ?? "operation";
108483
+ const delays = options.delaysMs ? Array.from(options.delaysMs) : Array.from(
108484
+ { length: (options.maxAttempts ?? 3) - 1 },
108485
+ (_2, i) => (options.delayMs ?? 1e3) * (i + 1)
108486
+ );
108487
+ const maxAttempts = delays.length + 1;
108481
108488
  let lastError;
108482
108489
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
108483
108490
  try {
@@ -108487,7 +108494,7 @@ async function retry(fn2, options = {}) {
108487
108494
  if (attempt === maxAttempts || !shouldRetry(error49)) {
108488
108495
  throw error49;
108489
108496
  }
108490
- const delay2 = delayMs * attempt;
108497
+ const delay2 = delays[attempt - 1];
108491
108498
  log.info(`\xBB ${label} failed (attempt ${attempt}/${maxAttempts}), retrying in ${delay2}ms...`);
108492
108499
  await sleep(delay2);
108493
108500
  }
@@ -108501,7 +108508,6 @@ var STRING_KEYS = [
108501
108508
  "issueNodeId",
108502
108509
  "reviewNodeId",
108503
108510
  "planCommentNodeId",
108504
- "summaryCommentNodeId",
108505
108511
  "summarySnapshot"
108506
108512
  ];
108507
108513
  var NUMBER_KEYS = [
@@ -142526,7 +142532,7 @@ var import_semver = __toESM(require_semver2(), 1);
142526
142532
  // package.json
142527
142533
  var package_default = {
142528
142534
  name: "pullfrog",
142529
- version: "0.0.205",
142535
+ version: "0.1.0",
142530
142536
  type: "module",
142531
142537
  bin: {
142532
142538
  pullfrog: "dist/cli.mjs",
@@ -143654,6 +143660,12 @@ function getHttpStatus(err) {
143654
143660
  const status = err.status;
143655
143661
  return typeof status === "number" ? status : void 0;
143656
143662
  }
143663
+ function isTransientReviewError(err) {
143664
+ if (getHttpStatus(err) !== 422) return false;
143665
+ const msg = err instanceof Error ? err.message : String(err);
143666
+ return /internal error occurred, please try again/i.test(msg);
143667
+ }
143668
+ var TRANSIENT_REVIEW_RETRY_DELAYS_MS = [1e3, 3e3];
143657
143669
  function commentableLinesForFile(patch) {
143658
143670
  const right = /* @__PURE__ */ new Set();
143659
143671
  const left = /* @__PURE__ */ new Set();
@@ -143921,12 +143933,26 @@ function CreatePullRequestReviewTool(ctx) {
143921
143933
  }
143922
143934
  let result;
143923
143935
  try {
143924
- result = body ? await createAndSubmitWithFooter(ctx, params, {
143925
- body,
143926
- approved: approved ?? false,
143927
- hasComments: (params.comments?.length ?? 0) > 0
143928
- }) : await createReviewWithStrandedRecovery(ctx, params);
143936
+ result = await retry(
143937
+ () => body ? createAndSubmitWithFooter(ctx, params, {
143938
+ body,
143939
+ approved: approved ?? false,
143940
+ hasComments: (params.comments?.length ?? 0) > 0
143941
+ }) : createReviewWithStrandedRecovery(ctx, params),
143942
+ {
143943
+ delaysMs: TRANSIENT_REVIEW_RETRY_DELAYS_MS,
143944
+ shouldRetry: isTransientReviewError,
143945
+ label: "review submission"
143946
+ }
143947
+ );
143929
143948
  } catch (err) {
143949
+ if (isTransientReviewError(err)) {
143950
+ const rawMsg2 = err instanceof Error ? err.message : String(err);
143951
+ throw new Error(
143952
+ `GitHub returned a transient 422 "internal error" on the reviews endpoint after ${TRANSIENT_REVIEW_RETRY_DELAYS_MS.length + 1} attempts. This is a GitHub-side issue, not a problem with your review content. Do NOT modify or drop inline comments \u2014 their content is not the cause. Wait ~30 seconds and call this tool once more with the SAME arguments. If it still fails, submit a body-only review (move all inline feedback into \`body\` as text) so nothing is lost. GitHub said: ${rawMsg2}`,
143953
+ { cause: err }
143954
+ );
143955
+ }
143930
143956
  if (getHttpStatus(err) !== 422 || !params.comments?.length) throw err;
143931
143957
  const details = params.comments.map((c2) => {
143932
143958
  const line = c2.line ?? 0;
@@ -146899,6 +146925,10 @@ function detectProviderError(text) {
146899
146925
  }
146900
146926
  return null;
146901
146927
  }
146928
+ var ROUTER_KEYLIMIT_EXHAUSTED_PATTERN = /requires more credits.*?fewer max_tokens|requested up to \d+ tokens.*?can only afford/is;
146929
+ function isRouterKeylimitExhaustedError(text) {
146930
+ return ROUTER_KEYLIMIT_EXHAUSTED_PATTERN.test(text);
146931
+ }
146902
146932
 
146903
146933
  // utils/skills.ts
146904
146934
  import { spawnSync as spawnSync5 } from "node:child_process";
@@ -147703,6 +147733,7 @@ async function installOpencodeCli() {
147703
147733
  installDependencies: true
147704
147734
  });
147705
147735
  }
147736
+ var PULLFROG_OPENCODE_OUTPUT_LIMIT = 5e3;
147706
147737
  function buildSecurityConfig(ctx, model) {
147707
147738
  const config3 = {
147708
147739
  permission: {
@@ -148218,6 +148249,7 @@ var opencode = agent({
148218
148249
  ...homeEnv,
148219
148250
  OPENCODE_CONFIG_CONTENT: buildSecurityConfig(ctx, model),
148220
148251
  OPENCODE_PERMISSION: permissionOverride,
148252
+ OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX: PULLFROG_OPENCODE_OUTPUT_LIMIT.toString(),
148221
148253
  GOOGLE_GENERATIVE_AI_API_KEY: process.env.GOOGLE_GENERATIVE_AI_API_KEY || process.env.GEMINI_API_KEY
148222
148254
  };
148223
148255
  const repoDir = process.cwd();
@@ -153635,6 +153667,24 @@ function formatBillingErrorSummary(error49, owner) {
153635
153667
  `[Add a card \u2192](${billingConsoleUrl(owner, "model-access")})`
153636
153668
  ].join("\n");
153637
153669
  }
153670
+ if (error49.code === "router_balance_exhausted") {
153671
+ return [
153672
+ "**Your Pullfrog Router balance is exhausted.**",
153673
+ "",
153674
+ "You have a card on file but auto-reload is disabled, so runs paused once your balance went past the overdraft buffer.",
153675
+ "",
153676
+ `[Top up balance \u2192](${billingConsoleUrl(owner, "billing")}) \xB7 [Enable auto-reload \u2192](${billingConsoleUrl(owner, "model-access")})`
153677
+ ].join("\n");
153678
+ }
153679
+ if (error49.code === "router_keylimit_exhausted") {
153680
+ return [
153681
+ "**This run was cut short \u2014 your Pullfrog Router balance ran out mid-run.**",
153682
+ "",
153683
+ "OpenRouter stopped the agent because the per-run budget was exhausted. Your wallet is now negative; top up or enable auto-reload to keep runs flowing.",
153684
+ "",
153685
+ `[Top up balance \u2192](${billingConsoleUrl(owner, "billing")}) \xB7 [Enable auto-reload \u2192](${billingConsoleUrl(owner, "model-access")})`
153686
+ ].join("\n");
153687
+ }
153638
153688
  if (error49.needsReauthentication) {
153639
153689
  const code = error49.declineCode ?? "authentication_required";
153640
153690
  return [
@@ -153875,6 +153925,7 @@ async function main() {
153875
153925
  setGitAuthServer(gitAuthServer);
153876
153926
  const resolvedModel = payload.proxyModel ? void 0 : resolveModel({ slug: payload.model });
153877
153927
  const agent2 = resolveAgent({ model: resolvedModel });
153928
+ toolState.model = payload.proxyModel ?? resolvedModel ?? payload.model;
153878
153929
  validateAgentApiKey({
153879
153930
  agent: agent2,
153880
153931
  model: payload.proxyModel ?? resolvedModel ?? payload.model,
@@ -154118,8 +154169,9 @@ ${instructions.user}` : null,
154118
154169
  todoTracker?.cancel();
154119
154170
  killTrackedChildren();
154120
154171
  log.error(errorMessage);
154172
+ const billingError = isRouterKeylimitExhaustedError(errorMessage) ? new BillingError(errorMessage, { code: "router_keylimit_exhausted" }) : null;
154121
154173
  try {
154122
- const errorSummary = `### \u274C Pullfrog failed
154174
+ const errorSummary = billingError ? formatBillingErrorSummary(billingError, runContext.repo.owner) : `### \u274C Pullfrog failed
154123
154175
 
154124
154176
  \`\`\`
154125
154177
  ${errorMessage}
@@ -154130,7 +154182,8 @@ ${errorMessage}
154130
154182
  } catch {
154131
154183
  }
154132
154184
  try {
154133
- await reportErrorToComment({ toolState, error: errorMessage });
154185
+ const commentBody = billingError ? formatBillingErrorSummary(billingError, runContext.repo.owner) : errorMessage;
154186
+ await reportErrorToComment({ toolState, error: commentBody });
154134
154187
  } catch {
154135
154188
  }
154136
154189
  if (toolContext) {
@@ -155983,7 +156036,7 @@ async function run2() {
155983
156036
  }
155984
156037
 
155985
156038
  // cli.ts
155986
- var VERSION10 = "0.0.205";
156039
+ var VERSION10 = "0.1.0";
155987
156040
  var bin = basename2(process.argv[1] || "");
155988
156041
  var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
155989
156042
  var rawArgs = process.argv.slice(2);
package/dist/index.js CHANGED
@@ -108116,7 +108116,11 @@ function resolveCliModel(slug2) {
108116
108116
  var PULLFROG_DIVIDER = "<!-- PULLFROG_DIVIDER_DO_NOT_REMOVE_PLZ -->";
108117
108117
  var FROG_LOGO = `<a href="https://pullfrog.com"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pullfrog.com/logos/frog-white-full-18px.png"><img src="https://pullfrog.com/logos/frog-green-full-18px.png" width="9px" height="9px" style="vertical-align: middle; " alt="Pullfrog"></picture></a>`;
108118
108118
  function formatModelLabel(slug2) {
108119
- const alias = resolveDisplayAlias(slug2);
108119
+ const alias = resolveDisplayAlias(slug2) ?? // reverse-lookup: when the caller passes an effective model (proxy or
108120
+ // resolved target like "openrouter/anthropic/claude-opus-4.7") instead of
108121
+ // a stored alias slug, find the alias whose resolve target matches so we
108122
+ // still render a friendly display name.
108123
+ modelAliases.find((a) => a.resolve === slug2 || a.openRouterResolve === slug2);
108120
108124
  if (!alias) return `\`${slug2}\``;
108121
108125
  return alias.isFree ? `\`${alias.displayName}\` (free)` : `\`${alias.displayName}\``;
108122
108126
  }
@@ -108191,10 +108195,13 @@ var defaultShouldRetry = (error49) => {
108191
108195
  return error49.name === "AbortError" || error49.message.includes("fetch failed") || error49.message.includes("ECONNRESET") || error49.message.includes("ETIMEDOUT");
108192
108196
  };
108193
108197
  async function retry(fn2, options = {}) {
108194
- const maxAttempts = options.maxAttempts ?? 3;
108195
- const delayMs = options.delayMs ?? 1e3;
108196
108198
  const shouldRetry = options.shouldRetry ?? defaultShouldRetry;
108197
108199
  const label = options.label ?? "operation";
108200
+ const delays = options.delaysMs ? Array.from(options.delaysMs) : Array.from(
108201
+ { length: (options.maxAttempts ?? 3) - 1 },
108202
+ (_, i) => (options.delayMs ?? 1e3) * (i + 1)
108203
+ );
108204
+ const maxAttempts = delays.length + 1;
108198
108205
  let lastError;
108199
108206
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
108200
108207
  try {
@@ -108204,7 +108211,7 @@ async function retry(fn2, options = {}) {
108204
108211
  if (attempt === maxAttempts || !shouldRetry(error49)) {
108205
108212
  throw error49;
108206
108213
  }
108207
- const delay2 = delayMs * attempt;
108214
+ const delay2 = delays[attempt - 1];
108208
108215
  log.info(`\xBB ${label} failed (attempt ${attempt}/${maxAttempts}), retrying in ${delay2}ms...`);
108209
108216
  await sleep(delay2);
108210
108217
  }
@@ -108218,7 +108225,6 @@ var STRING_KEYS = [
108218
108225
  "issueNodeId",
108219
108226
  "reviewNodeId",
108220
108227
  "planCommentNodeId",
108221
- "summaryCommentNodeId",
108222
108228
  "summarySnapshot"
108223
108229
  ];
108224
108230
  var NUMBER_KEYS = [
@@ -142243,7 +142249,7 @@ var import_semver = __toESM(require_semver2(), 1);
142243
142249
  // package.json
142244
142250
  var package_default = {
142245
142251
  name: "pullfrog",
142246
- version: "0.0.205",
142252
+ version: "0.1.0",
142247
142253
  type: "module",
142248
142254
  bin: {
142249
142255
  pullfrog: "dist/cli.mjs",
@@ -143371,6 +143377,12 @@ function getHttpStatus(err) {
143371
143377
  const status = err.status;
143372
143378
  return typeof status === "number" ? status : void 0;
143373
143379
  }
143380
+ function isTransientReviewError(err) {
143381
+ if (getHttpStatus(err) !== 422) return false;
143382
+ const msg = err instanceof Error ? err.message : String(err);
143383
+ return /internal error occurred, please try again/i.test(msg);
143384
+ }
143385
+ var TRANSIENT_REVIEW_RETRY_DELAYS_MS = [1e3, 3e3];
143374
143386
  function commentableLinesForFile(patch) {
143375
143387
  const right = /* @__PURE__ */ new Set();
143376
143388
  const left = /* @__PURE__ */ new Set();
@@ -143638,12 +143650,26 @@ function CreatePullRequestReviewTool(ctx) {
143638
143650
  }
143639
143651
  let result;
143640
143652
  try {
143641
- result = body ? await createAndSubmitWithFooter(ctx, params, {
143642
- body,
143643
- approved: approved ?? false,
143644
- hasComments: (params.comments?.length ?? 0) > 0
143645
- }) : await createReviewWithStrandedRecovery(ctx, params);
143653
+ result = await retry(
143654
+ () => body ? createAndSubmitWithFooter(ctx, params, {
143655
+ body,
143656
+ approved: approved ?? false,
143657
+ hasComments: (params.comments?.length ?? 0) > 0
143658
+ }) : createReviewWithStrandedRecovery(ctx, params),
143659
+ {
143660
+ delaysMs: TRANSIENT_REVIEW_RETRY_DELAYS_MS,
143661
+ shouldRetry: isTransientReviewError,
143662
+ label: "review submission"
143663
+ }
143664
+ );
143646
143665
  } catch (err) {
143666
+ if (isTransientReviewError(err)) {
143667
+ const rawMsg2 = err instanceof Error ? err.message : String(err);
143668
+ throw new Error(
143669
+ `GitHub returned a transient 422 "internal error" on the reviews endpoint after ${TRANSIENT_REVIEW_RETRY_DELAYS_MS.length + 1} attempts. This is a GitHub-side issue, not a problem with your review content. Do NOT modify or drop inline comments \u2014 their content is not the cause. Wait ~30 seconds and call this tool once more with the SAME arguments. If it still fails, submit a body-only review (move all inline feedback into \`body\` as text) so nothing is lost. GitHub said: ${rawMsg2}`,
143670
+ { cause: err }
143671
+ );
143672
+ }
143647
143673
  if (getHttpStatus(err) !== 422 || !params.comments?.length) throw err;
143648
143674
  const details = params.comments.map((c) => {
143649
143675
  const line = c.line ?? 0;
@@ -146616,6 +146642,10 @@ function detectProviderError(text) {
146616
146642
  }
146617
146643
  return null;
146618
146644
  }
146645
+ var ROUTER_KEYLIMIT_EXHAUSTED_PATTERN = /requires more credits.*?fewer max_tokens|requested up to \d+ tokens.*?can only afford/is;
146646
+ function isRouterKeylimitExhaustedError(text) {
146647
+ return ROUTER_KEYLIMIT_EXHAUSTED_PATTERN.test(text);
146648
+ }
146619
146649
 
146620
146650
  // utils/skills.ts
146621
146651
  import { spawnSync as spawnSync5 } from "node:child_process";
@@ -147420,6 +147450,7 @@ async function installOpencodeCli() {
147420
147450
  installDependencies: true
147421
147451
  });
147422
147452
  }
147453
+ var PULLFROG_OPENCODE_OUTPUT_LIMIT = 5e3;
147423
147454
  function buildSecurityConfig(ctx, model) {
147424
147455
  const config3 = {
147425
147456
  permission: {
@@ -147935,6 +147966,7 @@ var opencode = agent({
147935
147966
  ...homeEnv,
147936
147967
  OPENCODE_CONFIG_CONTENT: buildSecurityConfig(ctx, model),
147937
147968
  OPENCODE_PERMISSION: permissionOverride,
147969
+ OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX: PULLFROG_OPENCODE_OUTPUT_LIMIT.toString(),
147938
147970
  GOOGLE_GENERATIVE_AI_API_KEY: process.env.GOOGLE_GENERATIVE_AI_API_KEY || process.env.GEMINI_API_KEY
147939
147971
  };
147940
147972
  const repoDir = process.cwd();
@@ -153352,6 +153384,24 @@ function formatBillingErrorSummary(error49, owner) {
153352
153384
  `[Add a card \u2192](${billingConsoleUrl(owner, "model-access")})`
153353
153385
  ].join("\n");
153354
153386
  }
153387
+ if (error49.code === "router_balance_exhausted") {
153388
+ return [
153389
+ "**Your Pullfrog Router balance is exhausted.**",
153390
+ "",
153391
+ "You have a card on file but auto-reload is disabled, so runs paused once your balance went past the overdraft buffer.",
153392
+ "",
153393
+ `[Top up balance \u2192](${billingConsoleUrl(owner, "billing")}) \xB7 [Enable auto-reload \u2192](${billingConsoleUrl(owner, "model-access")})`
153394
+ ].join("\n");
153395
+ }
153396
+ if (error49.code === "router_keylimit_exhausted") {
153397
+ return [
153398
+ "**This run was cut short \u2014 your Pullfrog Router balance ran out mid-run.**",
153399
+ "",
153400
+ "OpenRouter stopped the agent because the per-run budget was exhausted. Your wallet is now negative; top up or enable auto-reload to keep runs flowing.",
153401
+ "",
153402
+ `[Top up balance \u2192](${billingConsoleUrl(owner, "billing")}) \xB7 [Enable auto-reload \u2192](${billingConsoleUrl(owner, "model-access")})`
153403
+ ].join("\n");
153404
+ }
153355
153405
  if (error49.needsReauthentication) {
153356
153406
  const code = error49.declineCode ?? "authentication_required";
153357
153407
  return [
@@ -153592,6 +153642,7 @@ async function main() {
153592
153642
  setGitAuthServer(gitAuthServer);
153593
153643
  const resolvedModel = payload.proxyModel ? void 0 : resolveModel({ slug: payload.model });
153594
153644
  const agent2 = resolveAgent({ model: resolvedModel });
153645
+ toolState.model = payload.proxyModel ?? resolvedModel ?? payload.model;
153595
153646
  validateAgentApiKey({
153596
153647
  agent: agent2,
153597
153648
  model: payload.proxyModel ?? resolvedModel ?? payload.model,
@@ -153835,8 +153886,9 @@ ${instructions.user}` : null,
153835
153886
  todoTracker?.cancel();
153836
153887
  killTrackedChildren();
153837
153888
  log.error(errorMessage);
153889
+ const billingError = isRouterKeylimitExhaustedError(errorMessage) ? new BillingError(errorMessage, { code: "router_keylimit_exhausted" }) : null;
153838
153890
  try {
153839
- const errorSummary = `### \u274C Pullfrog failed
153891
+ const errorSummary = billingError ? formatBillingErrorSummary(billingError, runContext.repo.owner) : `### \u274C Pullfrog failed
153840
153892
 
153841
153893
  \`\`\`
153842
153894
  ${errorMessage}
@@ -153847,7 +153899,8 @@ ${errorMessage}
153847
153899
  } catch {
153848
153900
  }
153849
153901
  try {
153850
- await reportErrorToComment({ toolState, error: errorMessage });
153902
+ const commentBody = billingError ? formatBillingErrorSummary(billingError, runContext.repo.owner) : errorMessage;
153903
+ await reportErrorToComment({ toolState, error: commentBody });
153851
153904
  } catch {
153852
153905
  }
153853
153906
  if (toolContext) {
package/dist/internal.js CHANGED
@@ -805,7 +805,11 @@ var modes = computeModes("opencode");
805
805
  var PULLFROG_DIVIDER = "<!-- PULLFROG_DIVIDER_DO_NOT_REMOVE_PLZ -->";
806
806
  var FROG_LOGO = `<a href="https://pullfrog.com"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pullfrog.com/logos/frog-white-full-18px.png"><img src="https://pullfrog.com/logos/frog-green-full-18px.png" width="9px" height="9px" style="vertical-align: middle; " alt="Pullfrog"></picture></a>`;
807
807
  function formatModelLabel(slug) {
808
- const alias = resolveDisplayAlias(slug);
808
+ const alias = resolveDisplayAlias(slug) ?? // reverse-lookup: when the caller passes an effective model (proxy or
809
+ // resolved target like "openrouter/anthropic/claude-opus-4.7") instead of
810
+ // a stored alias slug, find the alias whose resolve target matches so we
811
+ // still render a friendly display name.
812
+ modelAliases.find((a) => a.resolve === slug || a.openRouterResolve === slug);
809
813
  if (!alias) return `\`${slug}\``;
810
814
  return alias.isFree ? `\`${alias.displayName}\` (free)` : `\`${alias.displayName}\``;
811
815
  }
@@ -1,5 +1,18 @@
1
1
  import type { RestEndpointMethodTypes } from "@octokit/rest";
2
2
  import type { ToolContext } from "./server.ts";
3
+ /**
4
+ * detect GitHub's generic server-side 422 ("An internal error occurred,
5
+ * please try again.") that sometimes fires on `POST /pulls/{n}/reviews`.
6
+ *
7
+ * the body is stable across occurrences and distinct from every other 422
8
+ * cause we care about (anchor validation, body length, malformed suggestion
9
+ * blocks) — those all cite the specific problem. treating this as a
10
+ * transient server error unlocks bounded in-tool retry instead of surfacing
11
+ * it to the agent with the generic "likely causes (1)(2)(3)" prompt, which
12
+ * induces whack-a-mole comment dropping on content that was never the issue.
13
+ */
14
+ export declare function isTransientReviewError(err: unknown): boolean;
15
+ export declare const TRANSIENT_REVIEW_RETRY_DELAYS_MS: number[];
3
16
  export type CommentableLines = {
4
17
  RIGHT: Set<number>;
5
18
  LEFT: Set<number>;
@@ -5,7 +5,7 @@ import type { ToolContext } from "../mcp/server.ts";
5
5
  * are created during the run. Strings only (GraphQL node IDs).
6
6
  * Keep in sync with `STRING_FIELDS` in `app/api/workflow-run/[runId]/route.ts`.
7
7
  */
8
- export type WorkflowRunArtifactPatchKey = "prNodeId" | "issueNodeId" | "reviewNodeId" | "planCommentNodeId" | "summaryCommentNodeId" | "summarySnapshot";
8
+ export type WorkflowRunArtifactPatchKey = "prNodeId" | "issueNodeId" | "reviewNodeId" | "planCommentNodeId" | "summarySnapshot";
9
9
  /**
10
10
  * Usage fields — aggregated across all agent calls and PATCHed once at
11
11
  * end-of-run. Token counts are Int4 on the DB side (ample for any realistic
@@ -1 +1,2 @@
1
1
  export declare function detectProviderError(text: string): string | null;
2
+ export declare function isRouterKeylimitExhaustedError(text: string): boolean;
@@ -1,6 +1,12 @@
1
1
  export type RetryOptions = {
2
2
  maxAttempts?: number;
3
3
  delayMs?: number;
4
+ /**
5
+ * explicit delay schedule — one entry per retry (length N ⇒ N+1 attempts).
6
+ * when set, overrides `maxAttempts` and `delayMs`. e.g. `[1_000, 3_000]`
7
+ * means up to 3 attempts, sleeping 1s before retry 2 and 3s before retry 3.
8
+ */
9
+ delaysMs?: readonly number[];
4
10
  shouldRetry?: (error: unknown) => boolean;
5
11
  label?: string;
6
12
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pullfrog",
3
- "version": "0.0.205",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "pullfrog": "dist/cli.mjs",