pullfrog 0.1.17 → 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 +49 -46
- package/dist/index.js +48 -45
- 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",
|
|
@@ -108084,7 +108084,7 @@ function newTurn() {
|
|
|
108084
108084
|
};
|
|
108085
108085
|
}
|
|
108086
108086
|
async function consumeEvents(ctx, signal) {
|
|
108087
|
-
const result = await ctx.client.event.subscribe();
|
|
108087
|
+
const result = await ctx.client.event.subscribe({}, { signal });
|
|
108088
108088
|
for await (const event of result.stream) {
|
|
108089
108089
|
if (signal.aborted) break;
|
|
108090
108090
|
ctx.eventCount += 1;
|
|
@@ -151963,6 +151963,30 @@ function classifyPushError(msg) {
|
|
|
151963
151963
|
return "unknown";
|
|
151964
151964
|
}
|
|
151965
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
|
+
}
|
|
151966
151990
|
function PushBranchTool(ctx) {
|
|
151967
151991
|
const defaultBranch = ctx.repo.data.default_branch || "main";
|
|
151968
151992
|
const pushPermission = ctx.payload.push;
|
|
@@ -152030,48 +152054,23 @@ ${postHookStatus}`
|
|
|
152030
152054
|
if (force) {
|
|
152031
152055
|
log.warning(`force pushing - this will overwrite remote history`);
|
|
152032
152056
|
}
|
|
152033
|
-
|
|
152034
|
-
|
|
152035
|
-
|
|
152036
|
-
|
|
152037
|
-
|
|
152038
|
-
|
|
152039
|
-
|
|
152040
|
-
|
|
152041
|
-
log.info(`push succeeded on attempt ${attempt + 1}`);
|
|
152042
|
-
}
|
|
152043
|
-
pushed = true;
|
|
152044
|
-
break;
|
|
152045
|
-
} catch (err) {
|
|
152046
|
-
lastErr = err;
|
|
152047
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
152048
|
-
const kind = classifyPushError(msg);
|
|
152049
|
-
if (kind === "concurrent-push") {
|
|
152050
|
-
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')`;
|
|
152051
|
-
throw new Error(
|
|
152052
|
-
`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).
|
|
152053
152065
|
|
|
152054
152066
|
to resolve this:
|
|
152055
152067
|
1. use git_fetch to fetch the remote branch: git_fetch({ ref: "${pushDest.remoteBranch}" })
|
|
152056
152068
|
${integrateStep}
|
|
152057
152069
|
3. resolve any merge conflicts if needed
|
|
152058
152070
|
4. retry push_branch`
|
|
152059
|
-
|
|
152060
|
-
}
|
|
152061
|
-
if (kind === "transient" && attempt < TRANSIENT_RETRY_DELAYS_MS.length) {
|
|
152062
|
-
const baseDelay = TRANSIENT_RETRY_DELAYS_MS[attempt] ?? 5e3;
|
|
152063
|
-
const delay2 = Math.round(baseDelay * (0.75 + Math.random() * 0.5));
|
|
152064
|
-
log.info(
|
|
152065
|
-
`push attempt ${attempt + 1} failed (transient), retrying in ${delay2}ms: ${msg.slice(0, 300)}`
|
|
152066
|
-
);
|
|
152067
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
152068
|
-
continue;
|
|
152069
|
-
}
|
|
152070
|
-
throw err;
|
|
152071
|
+
);
|
|
152071
152072
|
}
|
|
152072
|
-
|
|
152073
|
-
if (!pushed) {
|
|
152074
|
-
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
152073
|
+
throw err;
|
|
152075
152074
|
}
|
|
152076
152075
|
const pushedSha = $2("git", ["rev-parse", "HEAD"], { log: false }).trim();
|
|
152077
152076
|
log.info(
|
|
@@ -152314,9 +152313,7 @@ function DeleteBranchTool(ctx) {
|
|
|
152314
152313
|
`Blocked: cannot delete the default branch '${defaultBranch}'. If you really need to delete or rename it, do it manually via the repository settings.`
|
|
152315
152314
|
);
|
|
152316
152315
|
}
|
|
152317
|
-
await
|
|
152318
|
-
token: ctx.gitToken
|
|
152319
|
-
});
|
|
152316
|
+
await pushWithRetry(["origin", "--delete", `refs/heads/${params.branchName}`], ctx.gitToken);
|
|
152320
152317
|
log.info(`\xBB deleted branch ${params.branchName}`);
|
|
152321
152318
|
return { success: true, deleted: params.branchName };
|
|
152322
152319
|
})
|
|
@@ -152340,9 +152337,7 @@ function PushTagsTool(ctx) {
|
|
|
152340
152337
|
}
|
|
152341
152338
|
validateTagName(params.tag);
|
|
152342
152339
|
const pushArgs = [...params.force ? ["-f"] : [], "origin", `refs/tags/${params.tag}`];
|
|
152343
|
-
await
|
|
152344
|
-
token: ctx.gitToken
|
|
152345
|
-
});
|
|
152340
|
+
await pushWithRetry(pushArgs, ctx.gitToken);
|
|
152346
152341
|
log.info(`\xBB pushed tag ${params.tag}`);
|
|
152347
152342
|
return { success: true, tag: params.tag };
|
|
152348
152343
|
})
|
|
@@ -161267,6 +161262,13 @@ function formatGenericFailure(errorMessage) {
|
|
|
161267
161262
|
"```"
|
|
161268
161263
|
].join("\n");
|
|
161269
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
|
+
}
|
|
161270
161272
|
var PROVIDER_BILLING_URLS = {
|
|
161271
161273
|
deepseek: "https://platform.deepseek.com/top_up",
|
|
161272
161274
|
anthropic: "https://console.anthropic.com/settings/billing",
|
|
@@ -161340,11 +161342,12 @@ ${body}`, comment: body };
|
|
|
161340
161342
|
return { summary: body, comment: body };
|
|
161341
161343
|
}
|
|
161342
161344
|
if (hangBody) {
|
|
161345
|
+
const isBillingExhausted = input.agentDiagnostic?.lastProviderError === "provider billing exhausted";
|
|
161343
161346
|
return {
|
|
161344
161347
|
summary: `### \u274C Pullfrog failed
|
|
161345
161348
|
|
|
161346
161349
|
${hangBody}`,
|
|
161347
|
-
comment: hangBody
|
|
161350
|
+
comment: isBillingExhausted ? hangBody : formatMinimalFailureComment(input.repo)
|
|
161348
161351
|
};
|
|
161349
161352
|
}
|
|
161350
161353
|
const genericBody = formatGenericFailure(input.errorMessage);
|
|
@@ -161352,7 +161355,7 @@ ${hangBody}`,
|
|
|
161352
161355
|
summary: `### \u274C Pullfrog failed
|
|
161353
161356
|
|
|
161354
161357
|
${genericBody}`,
|
|
161355
|
-
comment:
|
|
161358
|
+
comment: formatMinimalFailureComment(input.repo)
|
|
161356
161359
|
};
|
|
161357
161360
|
}
|
|
161358
161361
|
|
|
@@ -163002,7 +163005,7 @@ async function run2() {
|
|
|
163002
163005
|
}
|
|
163003
163006
|
|
|
163004
163007
|
// cli.ts
|
|
163005
|
-
var VERSION10 = "0.1.
|
|
163008
|
+
var VERSION10 = "0.1.18";
|
|
163006
163009
|
var bin = basename2(process.argv[1] || "");
|
|
163007
163010
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
163008
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",
|
|
@@ -106326,7 +106326,7 @@ function newTurn() {
|
|
|
106326
106326
|
};
|
|
106327
106327
|
}
|
|
106328
106328
|
async function consumeEvents(ctx, signal) {
|
|
106329
|
-
const result = await ctx.client.event.subscribe();
|
|
106329
|
+
const result = await ctx.client.event.subscribe({}, { signal });
|
|
106330
106330
|
for await (const event of result.stream) {
|
|
106331
106331
|
if (signal.aborted) break;
|
|
106332
106332
|
ctx.eventCount += 1;
|
|
@@ -150205,6 +150205,30 @@ function classifyPushError(msg) {
|
|
|
150205
150205
|
return "unknown";
|
|
150206
150206
|
}
|
|
150207
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
|
+
}
|
|
150208
150232
|
function PushBranchTool(ctx) {
|
|
150209
150233
|
const defaultBranch = ctx.repo.data.default_branch || "main";
|
|
150210
150234
|
const pushPermission = ctx.payload.push;
|
|
@@ -150272,48 +150296,23 @@ ${postHookStatus}`
|
|
|
150272
150296
|
if (force) {
|
|
150273
150297
|
log.warning(`force pushing - this will overwrite remote history`);
|
|
150274
150298
|
}
|
|
150275
|
-
|
|
150276
|
-
|
|
150277
|
-
|
|
150278
|
-
|
|
150279
|
-
|
|
150280
|
-
|
|
150281
|
-
|
|
150282
|
-
|
|
150283
|
-
log.info(`push succeeded on attempt ${attempt + 1}`);
|
|
150284
|
-
}
|
|
150285
|
-
pushed = true;
|
|
150286
|
-
break;
|
|
150287
|
-
} catch (err) {
|
|
150288
|
-
lastErr = err;
|
|
150289
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
150290
|
-
const kind = classifyPushError(msg);
|
|
150291
|
-
if (kind === "concurrent-push") {
|
|
150292
|
-
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')`;
|
|
150293
|
-
throw new Error(
|
|
150294
|
-
`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).
|
|
150295
150307
|
|
|
150296
150308
|
to resolve this:
|
|
150297
150309
|
1. use git_fetch to fetch the remote branch: git_fetch({ ref: "${pushDest.remoteBranch}" })
|
|
150298
150310
|
${integrateStep}
|
|
150299
150311
|
3. resolve any merge conflicts if needed
|
|
150300
150312
|
4. retry push_branch`
|
|
150301
|
-
|
|
150302
|
-
}
|
|
150303
|
-
if (kind === "transient" && attempt < TRANSIENT_RETRY_DELAYS_MS.length) {
|
|
150304
|
-
const baseDelay = TRANSIENT_RETRY_DELAYS_MS[attempt] ?? 5e3;
|
|
150305
|
-
const delay2 = Math.round(baseDelay * (0.75 + Math.random() * 0.5));
|
|
150306
|
-
log.info(
|
|
150307
|
-
`push attempt ${attempt + 1} failed (transient), retrying in ${delay2}ms: ${msg.slice(0, 300)}`
|
|
150308
|
-
);
|
|
150309
|
-
await new Promise((r) => setTimeout(r, delay2));
|
|
150310
|
-
continue;
|
|
150311
|
-
}
|
|
150312
|
-
throw err;
|
|
150313
|
+
);
|
|
150313
150314
|
}
|
|
150314
|
-
|
|
150315
|
-
if (!pushed) {
|
|
150316
|
-
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
150315
|
+
throw err;
|
|
150317
150316
|
}
|
|
150318
150317
|
const pushedSha = $("git", ["rev-parse", "HEAD"], { log: false }).trim();
|
|
150319
150318
|
log.info(
|
|
@@ -150556,9 +150555,7 @@ function DeleteBranchTool(ctx) {
|
|
|
150556
150555
|
`Blocked: cannot delete the default branch '${defaultBranch}'. If you really need to delete or rename it, do it manually via the repository settings.`
|
|
150557
150556
|
);
|
|
150558
150557
|
}
|
|
150559
|
-
await
|
|
150560
|
-
token: ctx.gitToken
|
|
150561
|
-
});
|
|
150558
|
+
await pushWithRetry(["origin", "--delete", `refs/heads/${params.branchName}`], ctx.gitToken);
|
|
150562
150559
|
log.info(`\xBB deleted branch ${params.branchName}`);
|
|
150563
150560
|
return { success: true, deleted: params.branchName };
|
|
150564
150561
|
})
|
|
@@ -150582,9 +150579,7 @@ function PushTagsTool(ctx) {
|
|
|
150582
150579
|
}
|
|
150583
150580
|
validateTagName(params.tag);
|
|
150584
150581
|
const pushArgs = [...params.force ? ["-f"] : [], "origin", `refs/tags/${params.tag}`];
|
|
150585
|
-
await
|
|
150586
|
-
token: ctx.gitToken
|
|
150587
|
-
});
|
|
150582
|
+
await pushWithRetry(pushArgs, ctx.gitToken);
|
|
150588
150583
|
log.info(`\xBB pushed tag ${params.tag}`);
|
|
150589
150584
|
return { success: true, tag: params.tag };
|
|
150590
150585
|
})
|
|
@@ -159509,6 +159504,13 @@ function formatGenericFailure(errorMessage) {
|
|
|
159509
159504
|
"```"
|
|
159510
159505
|
].join("\n");
|
|
159511
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
|
+
}
|
|
159512
159514
|
var PROVIDER_BILLING_URLS = {
|
|
159513
159515
|
deepseek: "https://platform.deepseek.com/top_up",
|
|
159514
159516
|
anthropic: "https://console.anthropic.com/settings/billing",
|
|
@@ -159582,11 +159584,12 @@ ${body}`, comment: body };
|
|
|
159582
159584
|
return { summary: body, comment: body };
|
|
159583
159585
|
}
|
|
159584
159586
|
if (hangBody) {
|
|
159587
|
+
const isBillingExhausted = input.agentDiagnostic?.lastProviderError === "provider billing exhausted";
|
|
159585
159588
|
return {
|
|
159586
159589
|
summary: `### \u274C Pullfrog failed
|
|
159587
159590
|
|
|
159588
159591
|
${hangBody}`,
|
|
159589
|
-
comment: hangBody
|
|
159592
|
+
comment: isBillingExhausted ? hangBody : formatMinimalFailureComment(input.repo)
|
|
159590
159593
|
};
|
|
159591
159594
|
}
|
|
159592
159595
|
const genericBody = formatGenericFailure(input.errorMessage);
|
|
@@ -159594,7 +159597,7 @@ ${hangBody}`,
|
|
|
159594
159597
|
summary: `### \u274C Pullfrog failed
|
|
159595
159598
|
|
|
159596
159599
|
${genericBody}`,
|
|
159597
|
-
comment:
|
|
159600
|
+
comment: formatMinimalFailureComment(input.repo)
|
|
159598
159601
|
};
|
|
159599
159602
|
}
|
|
159600
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 = {
|