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 +67 -14
- package/dist/index.js +66 -13
- package/dist/internal.js +5 -1
- package/dist/mcp/review.d.ts +13 -0
- package/dist/utils/patchWorkflowRunFields.d.ts +1 -1
- package/dist/utils/providerErrors.d.ts +1 -0
- package/dist/utils/retry.d.ts +6 -0
- package/package.json +1 -1
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 =
|
|
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
|
|
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 =
|
|
143925
|
-
body,
|
|
143926
|
-
|
|
143927
|
-
|
|
143928
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
143642
|
-
body,
|
|
143643
|
-
|
|
143644
|
-
|
|
143645
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/mcp/review.d.ts
CHANGED
|
@@ -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" | "
|
|
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
|
package/dist/utils/retry.d.ts
CHANGED
|
@@ -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
|
};
|