pullfrog 0.1.16 → 0.1.18
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 +55 -47
- package/dist/index.js +54 -46
- package/dist/utils/runErrorRenderer.d.ts +18 -12
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -101880,7 +101880,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
101880
101880
|
// package.json
|
|
101881
101881
|
var package_default = {
|
|
101882
101882
|
name: "pullfrog",
|
|
101883
|
-
version: "0.1.
|
|
101883
|
+
version: "0.1.18",
|
|
101884
101884
|
type: "module",
|
|
101885
101885
|
bin: {
|
|
101886
101886
|
pullfrog: "dist/cli.mjs",
|
|
@@ -103582,6 +103582,11 @@ async function installClaudeCli() {
|
|
|
103582
103582
|
installDependencies: true
|
|
103583
103583
|
});
|
|
103584
103584
|
}
|
|
103585
|
+
var CLAUDE_EXEC_TOOLS = ["Bash", "Monitor", "REPL", "Workflow"];
|
|
103586
|
+
var CLAUDE_DISALLOWED_TOOLS = [
|
|
103587
|
+
...CLAUDE_EXEC_TOOLS,
|
|
103588
|
+
...CLAUDE_EXEC_TOOLS.map((t2) => `Agent(${t2})`)
|
|
103589
|
+
].join(",");
|
|
103585
103590
|
function writeMcpConfig(ctx) {
|
|
103586
103591
|
const configDir = join5(ctx.tmpdir, ".claude");
|
|
103587
103592
|
mkdirSync4(configDir, { recursive: true });
|
|
@@ -104104,7 +104109,7 @@ var claude = agent({
|
|
|
104104
104109
|
"--effort",
|
|
104105
104110
|
effort,
|
|
104106
104111
|
"--disallowedTools",
|
|
104107
|
-
|
|
104112
|
+
CLAUDE_DISALLOWED_TOOLS,
|
|
104108
104113
|
"--agents",
|
|
104109
104114
|
buildAgentsJson()
|
|
104110
104115
|
];
|
|
@@ -108079,7 +108084,7 @@ function newTurn() {
|
|
|
108079
108084
|
};
|
|
108080
108085
|
}
|
|
108081
108086
|
async function consumeEvents(ctx, signal) {
|
|
108082
|
-
const result = await ctx.client.event.subscribe();
|
|
108087
|
+
const result = await ctx.client.event.subscribe({}, { signal });
|
|
108083
108088
|
for await (const event of result.stream) {
|
|
108084
108089
|
if (signal.aborted) break;
|
|
108085
108090
|
ctx.eventCount += 1;
|
|
@@ -151958,6 +151963,30 @@ function classifyPushError(msg) {
|
|
|
151958
151963
|
return "unknown";
|
|
151959
151964
|
}
|
|
151960
151965
|
var TRANSIENT_RETRY_DELAYS_MS = [2e3, 5e3];
|
|
151966
|
+
async function pushWithRetry(args2, token) {
|
|
151967
|
+
let lastErr;
|
|
151968
|
+
for (let attempt = 0; attempt <= TRANSIENT_RETRY_DELAYS_MS.length; attempt++) {
|
|
151969
|
+
try {
|
|
151970
|
+
await $git("push", args2, { token });
|
|
151971
|
+
if (attempt > 0) log.info(`push succeeded on attempt ${attempt + 1}`);
|
|
151972
|
+
return;
|
|
151973
|
+
} catch (err) {
|
|
151974
|
+
lastErr = err;
|
|
151975
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
151976
|
+
if (classifyPushError(msg) === "transient" && attempt < TRANSIENT_RETRY_DELAYS_MS.length) {
|
|
151977
|
+
const baseDelay = TRANSIENT_RETRY_DELAYS_MS[attempt] ?? 5e3;
|
|
151978
|
+
const delay2 = Math.round(baseDelay * (0.75 + Math.random() * 0.5));
|
|
151979
|
+
log.info(
|
|
151980
|
+
`push attempt ${attempt + 1} failed (transient), retrying in ${delay2}ms: ${msg.slice(0, 300)}`
|
|
151981
|
+
);
|
|
151982
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
151983
|
+
continue;
|
|
151984
|
+
}
|
|
151985
|
+
throw err;
|
|
151986
|
+
}
|
|
151987
|
+
}
|
|
151988
|
+
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
151989
|
+
}
|
|
151961
151990
|
function PushBranchTool(ctx) {
|
|
151962
151991
|
const defaultBranch = ctx.repo.data.default_branch || "main";
|
|
151963
151992
|
const pushPermission = ctx.payload.push;
|
|
@@ -152025,48 +152054,23 @@ ${postHookStatus}`
|
|
|
152025
152054
|
if (force) {
|
|
152026
152055
|
log.warning(`force pushing - this will overwrite remote history`);
|
|
152027
152056
|
}
|
|
152028
|
-
|
|
152029
|
-
|
|
152030
|
-
|
|
152031
|
-
|
|
152032
|
-
|
|
152033
|
-
|
|
152034
|
-
|
|
152035
|
-
|
|
152036
|
-
log.info(`push succeeded on attempt ${attempt + 1}`);
|
|
152037
|
-
}
|
|
152038
|
-
pushed = true;
|
|
152039
|
-
break;
|
|
152040
|
-
} catch (err) {
|
|
152041
|
-
lastErr = err;
|
|
152042
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
152043
|
-
const kind = classifyPushError(msg);
|
|
152044
|
-
if (kind === "concurrent-push") {
|
|
152045
|
-
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')`;
|
|
152046
|
-
throw new Error(
|
|
152047
|
-
`push rejected: the remote branch '${pushDest.remoteBranch}' has new commits you don't have locally (often a concurrent push to the same branch).
|
|
152057
|
+
try {
|
|
152058
|
+
await pushWithRetry(pushArgs, ctx.gitToken);
|
|
152059
|
+
} catch (err) {
|
|
152060
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
152061
|
+
if (classifyPushError(msg) === "concurrent-push") {
|
|
152062
|
+
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')`;
|
|
152063
|
+
throw new Error(
|
|
152064
|
+
`push rejected: the remote branch '${pushDest.remoteBranch}' has new commits you don't have locally (often a concurrent push to the same branch).
|
|
152048
152065
|
|
|
152049
152066
|
to resolve this:
|
|
152050
152067
|
1. use git_fetch to fetch the remote branch: git_fetch({ ref: "${pushDest.remoteBranch}" })
|
|
152051
152068
|
${integrateStep}
|
|
152052
152069
|
3. resolve any merge conflicts if needed
|
|
152053
152070
|
4. retry push_branch`
|
|
152054
|
-
|
|
152055
|
-
}
|
|
152056
|
-
if (kind === "transient" && attempt < TRANSIENT_RETRY_DELAYS_MS.length) {
|
|
152057
|
-
const baseDelay = TRANSIENT_RETRY_DELAYS_MS[attempt] ?? 5e3;
|
|
152058
|
-
const delay2 = Math.round(baseDelay * (0.75 + Math.random() * 0.5));
|
|
152059
|
-
log.info(
|
|
152060
|
-
`push attempt ${attempt + 1} failed (transient), retrying in ${delay2}ms: ${msg.slice(0, 300)}`
|
|
152061
|
-
);
|
|
152062
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
152063
|
-
continue;
|
|
152064
|
-
}
|
|
152065
|
-
throw err;
|
|
152071
|
+
);
|
|
152066
152072
|
}
|
|
152067
|
-
|
|
152068
|
-
if (!pushed) {
|
|
152069
|
-
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
152073
|
+
throw err;
|
|
152070
152074
|
}
|
|
152071
152075
|
const pushedSha = $2("git", ["rev-parse", "HEAD"], { log: false }).trim();
|
|
152072
152076
|
log.info(
|
|
@@ -152309,9 +152313,7 @@ function DeleteBranchTool(ctx) {
|
|
|
152309
152313
|
`Blocked: cannot delete the default branch '${defaultBranch}'. If you really need to delete or rename it, do it manually via the repository settings.`
|
|
152310
152314
|
);
|
|
152311
152315
|
}
|
|
152312
|
-
await
|
|
152313
|
-
token: ctx.gitToken
|
|
152314
|
-
});
|
|
152316
|
+
await pushWithRetry(["origin", "--delete", `refs/heads/${params.branchName}`], ctx.gitToken);
|
|
152315
152317
|
log.info(`\xBB deleted branch ${params.branchName}`);
|
|
152316
152318
|
return { success: true, deleted: params.branchName };
|
|
152317
152319
|
})
|
|
@@ -152335,9 +152337,7 @@ function PushTagsTool(ctx) {
|
|
|
152335
152337
|
}
|
|
152336
152338
|
validateTagName(params.tag);
|
|
152337
152339
|
const pushArgs = [...params.force ? ["-f"] : [], "origin", `refs/tags/${params.tag}`];
|
|
152338
|
-
await
|
|
152339
|
-
token: ctx.gitToken
|
|
152340
|
-
});
|
|
152340
|
+
await pushWithRetry(pushArgs, ctx.gitToken);
|
|
152341
152341
|
log.info(`\xBB pushed tag ${params.tag}`);
|
|
152342
152342
|
return { success: true, tag: params.tag };
|
|
152343
152343
|
})
|
|
@@ -161262,6 +161262,13 @@ function formatGenericFailure(errorMessage) {
|
|
|
161262
161262
|
"```"
|
|
161263
161263
|
].join("\n");
|
|
161264
161264
|
}
|
|
161265
|
+
function formatMinimalFailureComment(repo) {
|
|
161266
|
+
const runId = process.env.GITHUB_RUN_ID;
|
|
161267
|
+
if (!runId) return "**Run failed.**";
|
|
161268
|
+
const server = process.env.GITHUB_SERVER_URL ?? "https://github.com";
|
|
161269
|
+
const url4 = `${server}/${repo.owner}/${repo.name}/actions/runs/${runId}`;
|
|
161270
|
+
return `**Run failed.** [View the logs \u2192](${url4})`;
|
|
161271
|
+
}
|
|
161265
161272
|
var PROVIDER_BILLING_URLS = {
|
|
161266
161273
|
deepseek: "https://platform.deepseek.com/top_up",
|
|
161267
161274
|
anthropic: "https://console.anthropic.com/settings/billing",
|
|
@@ -161335,11 +161342,12 @@ ${body}`, comment: body };
|
|
|
161335
161342
|
return { summary: body, comment: body };
|
|
161336
161343
|
}
|
|
161337
161344
|
if (hangBody) {
|
|
161345
|
+
const isBillingExhausted = input.agentDiagnostic?.lastProviderError === "provider billing exhausted";
|
|
161338
161346
|
return {
|
|
161339
161347
|
summary: `### \u274C Pullfrog failed
|
|
161340
161348
|
|
|
161341
161349
|
${hangBody}`,
|
|
161342
|
-
comment: hangBody
|
|
161350
|
+
comment: isBillingExhausted ? hangBody : formatMinimalFailureComment(input.repo)
|
|
161343
161351
|
};
|
|
161344
161352
|
}
|
|
161345
161353
|
const genericBody = formatGenericFailure(input.errorMessage);
|
|
@@ -161347,7 +161355,7 @@ ${hangBody}`,
|
|
|
161347
161355
|
summary: `### \u274C Pullfrog failed
|
|
161348
161356
|
|
|
161349
161357
|
${genericBody}`,
|
|
161350
|
-
comment:
|
|
161358
|
+
comment: formatMinimalFailureComment(input.repo)
|
|
161351
161359
|
};
|
|
161352
161360
|
}
|
|
161353
161361
|
|
|
@@ -162997,7 +163005,7 @@ async function run2() {
|
|
|
162997
163005
|
}
|
|
162998
163006
|
|
|
162999
163007
|
// cli.ts
|
|
163000
|
-
var VERSION10 = "0.1.
|
|
163008
|
+
var VERSION10 = "0.1.18";
|
|
163001
163009
|
var bin = basename2(process.argv[1] || "");
|
|
163002
163010
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
163003
163011
|
var rawArgs = process.argv.slice(2);
|
package/dist/index.js
CHANGED
|
@@ -100080,7 +100080,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
100080
100080
|
// package.json
|
|
100081
100081
|
var package_default = {
|
|
100082
100082
|
name: "pullfrog",
|
|
100083
|
-
version: "0.1.
|
|
100083
|
+
version: "0.1.18",
|
|
100084
100084
|
type: "module",
|
|
100085
100085
|
bin: {
|
|
100086
100086
|
pullfrog: "dist/cli.mjs",
|
|
@@ -101782,6 +101782,11 @@ async function installClaudeCli() {
|
|
|
101782
101782
|
installDependencies: true
|
|
101783
101783
|
});
|
|
101784
101784
|
}
|
|
101785
|
+
var CLAUDE_EXEC_TOOLS = ["Bash", "Monitor", "REPL", "Workflow"];
|
|
101786
|
+
var CLAUDE_DISALLOWED_TOOLS = [
|
|
101787
|
+
...CLAUDE_EXEC_TOOLS,
|
|
101788
|
+
...CLAUDE_EXEC_TOOLS.map((t) => `Agent(${t})`)
|
|
101789
|
+
].join(",");
|
|
101785
101790
|
function writeMcpConfig(ctx) {
|
|
101786
101791
|
const configDir = join4(ctx.tmpdir, ".claude");
|
|
101787
101792
|
mkdirSync4(configDir, { recursive: true });
|
|
@@ -102304,7 +102309,7 @@ var claude = agent({
|
|
|
102304
102309
|
"--effort",
|
|
102305
102310
|
effort,
|
|
102306
102311
|
"--disallowedTools",
|
|
102307
|
-
|
|
102312
|
+
CLAUDE_DISALLOWED_TOOLS,
|
|
102308
102313
|
"--agents",
|
|
102309
102314
|
buildAgentsJson()
|
|
102310
102315
|
];
|
|
@@ -106321,7 +106326,7 @@ function newTurn() {
|
|
|
106321
106326
|
};
|
|
106322
106327
|
}
|
|
106323
106328
|
async function consumeEvents(ctx, signal) {
|
|
106324
|
-
const result = await ctx.client.event.subscribe();
|
|
106329
|
+
const result = await ctx.client.event.subscribe({}, { signal });
|
|
106325
106330
|
for await (const event of result.stream) {
|
|
106326
106331
|
if (signal.aborted) break;
|
|
106327
106332
|
ctx.eventCount += 1;
|
|
@@ -150200,6 +150205,30 @@ function classifyPushError(msg) {
|
|
|
150200
150205
|
return "unknown";
|
|
150201
150206
|
}
|
|
150202
150207
|
var TRANSIENT_RETRY_DELAYS_MS = [2e3, 5e3];
|
|
150208
|
+
async function pushWithRetry(args2, token) {
|
|
150209
|
+
let lastErr;
|
|
150210
|
+
for (let attempt = 0; attempt <= TRANSIENT_RETRY_DELAYS_MS.length; attempt++) {
|
|
150211
|
+
try {
|
|
150212
|
+
await $git("push", args2, { token });
|
|
150213
|
+
if (attempt > 0) log.info(`push succeeded on attempt ${attempt + 1}`);
|
|
150214
|
+
return;
|
|
150215
|
+
} catch (err) {
|
|
150216
|
+
lastErr = err;
|
|
150217
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
150218
|
+
if (classifyPushError(msg) === "transient" && attempt < TRANSIENT_RETRY_DELAYS_MS.length) {
|
|
150219
|
+
const baseDelay = TRANSIENT_RETRY_DELAYS_MS[attempt] ?? 5e3;
|
|
150220
|
+
const delay2 = Math.round(baseDelay * (0.75 + Math.random() * 0.5));
|
|
150221
|
+
log.info(
|
|
150222
|
+
`push attempt ${attempt + 1} failed (transient), retrying in ${delay2}ms: ${msg.slice(0, 300)}`
|
|
150223
|
+
);
|
|
150224
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
150225
|
+
continue;
|
|
150226
|
+
}
|
|
150227
|
+
throw err;
|
|
150228
|
+
}
|
|
150229
|
+
}
|
|
150230
|
+
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
150231
|
+
}
|
|
150203
150232
|
function PushBranchTool(ctx) {
|
|
150204
150233
|
const defaultBranch = ctx.repo.data.default_branch || "main";
|
|
150205
150234
|
const pushPermission = ctx.payload.push;
|
|
@@ -150267,48 +150296,23 @@ ${postHookStatus}`
|
|
|
150267
150296
|
if (force) {
|
|
150268
150297
|
log.warning(`force pushing - this will overwrite remote history`);
|
|
150269
150298
|
}
|
|
150270
|
-
|
|
150271
|
-
|
|
150272
|
-
|
|
150273
|
-
|
|
150274
|
-
|
|
150275
|
-
|
|
150276
|
-
|
|
150277
|
-
|
|
150278
|
-
log.info(`push succeeded on attempt ${attempt + 1}`);
|
|
150279
|
-
}
|
|
150280
|
-
pushed = true;
|
|
150281
|
-
break;
|
|
150282
|
-
} catch (err) {
|
|
150283
|
-
lastErr = err;
|
|
150284
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
150285
|
-
const kind = classifyPushError(msg);
|
|
150286
|
-
if (kind === "concurrent-push") {
|
|
150287
|
-
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')`;
|
|
150288
|
-
throw new Error(
|
|
150289
|
-
`push rejected: the remote branch '${pushDest.remoteBranch}' has new commits you don't have locally (often a concurrent push to the same branch).
|
|
150299
|
+
try {
|
|
150300
|
+
await pushWithRetry(pushArgs, ctx.gitToken);
|
|
150301
|
+
} catch (err) {
|
|
150302
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
150303
|
+
if (classifyPushError(msg) === "concurrent-push") {
|
|
150304
|
+
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')`;
|
|
150305
|
+
throw new Error(
|
|
150306
|
+
`push rejected: the remote branch '${pushDest.remoteBranch}' has new commits you don't have locally (often a concurrent push to the same branch).
|
|
150290
150307
|
|
|
150291
150308
|
to resolve this:
|
|
150292
150309
|
1. use git_fetch to fetch the remote branch: git_fetch({ ref: "${pushDest.remoteBranch}" })
|
|
150293
150310
|
${integrateStep}
|
|
150294
150311
|
3. resolve any merge conflicts if needed
|
|
150295
150312
|
4. retry push_branch`
|
|
150296
|
-
|
|
150297
|
-
}
|
|
150298
|
-
if (kind === "transient" && attempt < TRANSIENT_RETRY_DELAYS_MS.length) {
|
|
150299
|
-
const baseDelay = TRANSIENT_RETRY_DELAYS_MS[attempt] ?? 5e3;
|
|
150300
|
-
const delay2 = Math.round(baseDelay * (0.75 + Math.random() * 0.5));
|
|
150301
|
-
log.info(
|
|
150302
|
-
`push attempt ${attempt + 1} failed (transient), retrying in ${delay2}ms: ${msg.slice(0, 300)}`
|
|
150303
|
-
);
|
|
150304
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
150305
|
-
continue;
|
|
150306
|
-
}
|
|
150307
|
-
throw err;
|
|
150313
|
+
);
|
|
150308
150314
|
}
|
|
150309
|
-
|
|
150310
|
-
if (!pushed) {
|
|
150311
|
-
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
150315
|
+
throw err;
|
|
150312
150316
|
}
|
|
150313
150317
|
const pushedSha = $("git", ["rev-parse", "HEAD"], { log: false }).trim();
|
|
150314
150318
|
log.info(
|
|
@@ -150551,9 +150555,7 @@ function DeleteBranchTool(ctx) {
|
|
|
150551
150555
|
`Blocked: cannot delete the default branch '${defaultBranch}'. If you really need to delete or rename it, do it manually via the repository settings.`
|
|
150552
150556
|
);
|
|
150553
150557
|
}
|
|
150554
|
-
await
|
|
150555
|
-
token: ctx.gitToken
|
|
150556
|
-
});
|
|
150558
|
+
await pushWithRetry(["origin", "--delete", `refs/heads/${params.branchName}`], ctx.gitToken);
|
|
150557
150559
|
log.info(`\xBB deleted branch ${params.branchName}`);
|
|
150558
150560
|
return { success: true, deleted: params.branchName };
|
|
150559
150561
|
})
|
|
@@ -150577,9 +150579,7 @@ function PushTagsTool(ctx) {
|
|
|
150577
150579
|
}
|
|
150578
150580
|
validateTagName(params.tag);
|
|
150579
150581
|
const pushArgs = [...params.force ? ["-f"] : [], "origin", `refs/tags/${params.tag}`];
|
|
150580
|
-
await
|
|
150581
|
-
token: ctx.gitToken
|
|
150582
|
-
});
|
|
150582
|
+
await pushWithRetry(pushArgs, ctx.gitToken);
|
|
150583
150583
|
log.info(`\xBB pushed tag ${params.tag}`);
|
|
150584
150584
|
return { success: true, tag: params.tag };
|
|
150585
150585
|
})
|
|
@@ -159504,6 +159504,13 @@ function formatGenericFailure(errorMessage) {
|
|
|
159504
159504
|
"```"
|
|
159505
159505
|
].join("\n");
|
|
159506
159506
|
}
|
|
159507
|
+
function formatMinimalFailureComment(repo) {
|
|
159508
|
+
const runId = process.env.GITHUB_RUN_ID;
|
|
159509
|
+
if (!runId) return "**Run failed.**";
|
|
159510
|
+
const server = process.env.GITHUB_SERVER_URL ?? "https://github.com";
|
|
159511
|
+
const url4 = `${server}/${repo.owner}/${repo.name}/actions/runs/${runId}`;
|
|
159512
|
+
return `**Run failed.** [View the logs \u2192](${url4})`;
|
|
159513
|
+
}
|
|
159507
159514
|
var PROVIDER_BILLING_URLS = {
|
|
159508
159515
|
deepseek: "https://platform.deepseek.com/top_up",
|
|
159509
159516
|
anthropic: "https://console.anthropic.com/settings/billing",
|
|
@@ -159577,11 +159584,12 @@ ${body}`, comment: body };
|
|
|
159577
159584
|
return { summary: body, comment: body };
|
|
159578
159585
|
}
|
|
159579
159586
|
if (hangBody) {
|
|
159587
|
+
const isBillingExhausted = input.agentDiagnostic?.lastProviderError === "provider billing exhausted";
|
|
159580
159588
|
return {
|
|
159581
159589
|
summary: `### \u274C Pullfrog failed
|
|
159582
159590
|
|
|
159583
159591
|
${hangBody}`,
|
|
159584
|
-
comment: hangBody
|
|
159592
|
+
comment: isBillingExhausted ? hangBody : formatMinimalFailureComment(input.repo)
|
|
159585
159593
|
};
|
|
159586
159594
|
}
|
|
159587
159595
|
const genericBody = formatGenericFailure(input.errorMessage);
|
|
@@ -159589,7 +159597,7 @@ ${hangBody}`,
|
|
|
159589
159597
|
summary: `### \u274C Pullfrog failed
|
|
159590
159598
|
|
|
159591
159599
|
${genericBody}`,
|
|
159592
|
-
comment:
|
|
159600
|
+
comment: formatMinimalFailureComment(input.repo)
|
|
159593
159601
|
};
|
|
159594
159602
|
}
|
|
159595
159603
|
|
|
@@ -30,18 +30,24 @@
|
|
|
30
30
|
* 5. Activity-timeout hang — `errorMessage` starts with
|
|
31
31
|
* `"activity timeout"` or `"agent still pending"` AND none of the
|
|
32
32
|
* above matched. The harness keeps structured diagnostic state on
|
|
33
|
-
* `toolState.agentDiagnostic`; `formatAgentHangBody` renders that
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* in
|
|
43
|
-
*
|
|
44
|
-
*
|
|
33
|
+
* `toolState.agentDiagnostic`; `formatAgentHangBody` renders that into
|
|
34
|
+
* the job summary. The PR comment instead collapses to a one-line
|
|
35
|
+
* `**Run failed.** [View the logs →]` — the watchdog jargon, event
|
|
36
|
+
* counts, and benign stderr tail are operator-grade detail that only
|
|
37
|
+
* alarm the average user. The one exception is a hang masking billing
|
|
38
|
+
* exhaustion (#778), where `formatAgentHangBody` emits an actionable
|
|
39
|
+
* top-up CTA that the comment keeps verbatim.
|
|
40
|
+
*
|
|
41
|
+
* 6. Default — the job summary gets a plain-English lead sentence plus the
|
|
42
|
+
* raw error in a fenced code block under the `### ❌ Pullfrog failed`
|
|
43
|
+
* banner; the PR comment collapses to the same one-line logs link as
|
|
44
|
+
* the hang case, since the raw internal string helps nobody on the PR.
|
|
45
|
+
*
|
|
46
|
+
* Net: the actionable classifications (billing, API-key, model-not-found)
|
|
47
|
+
* render identical bodies on both surfaces; the non-actionable ones (hang,
|
|
48
|
+
* generic) keep the forensics in the Actions job summary and show a calm
|
|
49
|
+
* one-liner in the PR comment, whose footer already carries Pullfrog
|
|
50
|
+
* branding + rerun links.
|
|
45
51
|
*/
|
|
46
52
|
import type { AgentDiagnostic } from "./agentHangReport.ts";
|
|
47
53
|
export type RenderedRunError = {
|