pullfrog 0.1.61 → 0.1.63
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 +101 -32
- package/dist/index.js +100 -31
- package/dist/utils/providerErrors.d.ts +6 -0
- package/dist/utils/runErrorRenderer.d.ts +7 -7
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -107293,6 +107293,9 @@ function isProviderNoRoutableEndpoints(text) {
|
|
|
107293
107293
|
function isOpenRouterKeyLimitExceeded(text) {
|
|
107294
107294
|
return /Key limit exceeded \(total limit\)/i.test(text) || /openrouter\.ai\/[^\s"')]*\/keys\//i.test(text);
|
|
107295
107295
|
}
|
|
107296
|
+
function isProviderMissingCredential(text) {
|
|
107297
|
+
return /API key is missing\. Pass it using the/i.test(text) || /Missing Authentication header/i.test(text);
|
|
107298
|
+
}
|
|
107296
107299
|
function isTransientUpstreamError(text) {
|
|
107297
107300
|
return /API Error:\s*5\d\d/i.test(text) || /\bOverloaded\b/.test(text) || /"error_type"\s*:\s*"(?:provider_unavailable|timeout)"/i.test(text) || /Streaming response failed:\s*\[5\d\d\]/i.test(text);
|
|
107298
107301
|
}
|
|
@@ -107355,7 +107358,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
107355
107358
|
// package.json
|
|
107356
107359
|
var package_default = {
|
|
107357
107360
|
name: "pullfrog",
|
|
107358
|
-
version: "0.1.
|
|
107361
|
+
version: "0.1.63",
|
|
107359
107362
|
type: "module",
|
|
107360
107363
|
bin: {
|
|
107361
107364
|
pullfrog: "dist/cli.mjs",
|
|
@@ -108446,7 +108449,6 @@ function getUnsubmittedReview(toolState, expectsReviewOutput2 = toolState.hadPro
|
|
|
108446
108449
|
}
|
|
108447
108450
|
function expectsReviewOutput(ctx) {
|
|
108448
108451
|
if (ctx.toolState.hadProgressComment) return true;
|
|
108449
|
-
if (ctx.payload.progressComments) return false;
|
|
108450
108452
|
return ctx.payload.event.silent !== true && ctx.payload.event.issue_number !== void 0;
|
|
108451
108453
|
}
|
|
108452
108454
|
function buildStopHookPrompt(failure2) {
|
|
@@ -108661,6 +108663,7 @@ async function runPostRunRetryLoop(params) {
|
|
|
108661
108663
|
result = preResume;
|
|
108662
108664
|
break;
|
|
108663
108665
|
}
|
|
108666
|
+
if (onlySummaryStale) result = { ...result, output: preResume.output || result.output };
|
|
108664
108667
|
gateResumeCount++;
|
|
108665
108668
|
}
|
|
108666
108669
|
if (gateResumeCount > 0 && result.success && hasPostRunIssues(finalIssues)) {
|
|
@@ -116005,24 +116008,23 @@ async function installOpencodeCli(params) {
|
|
|
116005
116008
|
var AUTO_SELECT_WARNING = "select a model explicitly in the Pullfrog console (https://pullfrog.com/console) to avoid this \u2014 if the picker is locked, this run had no Pullfrog Router funding: add a card or top up your credit.";
|
|
116006
116009
|
function autoSelectModel() {
|
|
116007
116010
|
const authorized2 = getAuthorizedModels();
|
|
116008
|
-
|
|
116009
|
-
|
|
116010
|
-
|
|
116011
|
-
|
|
116012
|
-
|
|
116013
|
-
|
|
116014
|
-
|
|
116015
|
-
|
|
116016
|
-
log2.info(
|
|
116017
|
-
`\xBB model: ${match3.resolve} (auto-selected${match3.preferred ? " \u2014 preferred" : ""} curated match)`
|
|
116018
|
-
);
|
|
116019
|
-
log2.warning(`\xBB model auto-selected. ${AUTO_SELECT_WARNING}`);
|
|
116020
|
-
return match3.resolve;
|
|
116021
|
-
}
|
|
116011
|
+
const selectable = modelAliases.filter((a2) => !a2.hidden && !a2.fallback && !a2.routing);
|
|
116012
|
+
const servable = selectable.filter((a2) => {
|
|
116013
|
+
const envVars = getModelEnvVars(a2.resolve);
|
|
116014
|
+
return envVars.length === 0 || envVars.some((name) => process.env[name]);
|
|
116015
|
+
});
|
|
116016
|
+
const pick4 = (list) => list.find((a2) => a2.preferred) ?? list[0];
|
|
116017
|
+
const match3 = pick4(servable.filter((a2) => authorized2.has(a2.resolve))) ?? pick4(servable);
|
|
116018
|
+
if (match3) {
|
|
116022
116019
|
log2.info(
|
|
116023
|
-
`\xBB
|
|
116020
|
+
`\xBB model: ${match3.resolve} (auto-selected${match3.preferred ? " \u2014 preferred" : ""} curated match)`
|
|
116024
116021
|
);
|
|
116022
|
+
log2.warning(`\xBB model auto-selected. ${AUTO_SELECT_WARNING}`);
|
|
116023
|
+
return match3.resolve;
|
|
116025
116024
|
}
|
|
116025
|
+
log2.info(
|
|
116026
|
+
`\xBB opencode has ${authorized2.size} models but none match curated aliases and none hold a credential \u2014 letting OpenCode auto-select: ${[...authorized2].join(", ")}`
|
|
116027
|
+
);
|
|
116026
116028
|
log2.warning(`\xBB no model resolved. letting OpenCode auto-select. ${AUTO_SELECT_WARNING}`);
|
|
116027
116029
|
return void 0;
|
|
116028
116030
|
}
|
|
@@ -116235,7 +116237,7 @@ function isModelOutput(ctx, part) {
|
|
|
116235
116237
|
async function dispatchEvent(ctx, event) {
|
|
116236
116238
|
if (event.type === "message.part.updated") {
|
|
116237
116239
|
ctx.lastEventAt = performance7.now();
|
|
116238
|
-
if (isModelOutput(ctx, event.properties.part)) ctx.sawModelOutput = true;
|
|
116240
|
+
if (isModelOutput(ctx, event.properties.part)) ctx.diagnostic.sawModelOutput = true;
|
|
116239
116241
|
await onPartUpdated(ctx, event.properties.part);
|
|
116240
116242
|
return;
|
|
116241
116243
|
}
|
|
@@ -116381,7 +116383,8 @@ async function runPromptTurn(ctx, params) {
|
|
|
116381
116383
|
const turn = ctx.currentTurn;
|
|
116382
116384
|
const part = { type: "text", text: params.text };
|
|
116383
116385
|
ctx.promptMessageID = void 0;
|
|
116384
|
-
ctx.sawModelOutput = false;
|
|
116386
|
+
ctx.diagnostic.sawModelOutput = false;
|
|
116387
|
+
ctx.diagnostic.idleSec = void 0;
|
|
116385
116388
|
let assistant;
|
|
116386
116389
|
let returnedParts;
|
|
116387
116390
|
let networkError = null;
|
|
@@ -116441,7 +116444,7 @@ async function runPromptTurn(ctx, params) {
|
|
|
116441
116444
|
usage
|
|
116442
116445
|
};
|
|
116443
116446
|
}
|
|
116444
|
-
if (!ctx.sawModelOutput && finalText.trim().length === 0 && !usage) {
|
|
116447
|
+
if (!ctx.diagnostic.sawModelOutput && finalText.trim().length === 0 && !usage) {
|
|
116445
116448
|
return {
|
|
116446
116449
|
success: false,
|
|
116447
116450
|
output: finalText,
|
|
@@ -116566,19 +116569,18 @@ function startInnerActivityWatchdog(params) {
|
|
|
116566
116569
|
const id = setInterval(() => {
|
|
116567
116570
|
if (fired) return;
|
|
116568
116571
|
const idleMs = performance7.now() - params.ctx.lastEventAt;
|
|
116569
|
-
const compiledMs = params.ctx.sawModelOutput ? params.timeoutMs : AGENT_FIRST_EVENT_TIMEOUT_MS;
|
|
116572
|
+
const compiledMs = params.ctx.diagnostic.sawModelOutput ? params.timeoutMs : AGENT_FIRST_EVENT_TIMEOUT_MS;
|
|
116570
116573
|
const budgetMs = everFired ? compiledMs : watchdogBudgetMs(
|
|
116571
116574
|
compiledMs,
|
|
116572
|
-
params.ctx.sawModelOutput ? "PULLFROG_E2E_ACTIVITY_TIMEOUT_MS" : "PULLFROG_E2E_FIRST_EVENT_TIMEOUT_MS"
|
|
116575
|
+
params.ctx.diagnostic.sawModelOutput ? "PULLFROG_E2E_ACTIVITY_TIMEOUT_MS" : "PULLFROG_E2E_FIRST_EVENT_TIMEOUT_MS"
|
|
116573
116576
|
);
|
|
116574
116577
|
if (idleMs <= budgetMs) return;
|
|
116575
116578
|
fired = true;
|
|
116576
116579
|
everFired = true;
|
|
116577
116580
|
const idleSec = Math.round(idleMs / 1e3);
|
|
116578
116581
|
params.ctx.diagnostic.idleSec = idleSec;
|
|
116579
|
-
params.ctx.diagnostic.sawModelOutput = params.ctx.sawModelOutput;
|
|
116580
116582
|
log2.info(
|
|
116581
|
-
params.ctx.sawModelOutput ? `\xBB no opencode events for ${idleSec}s \u2014 aborting in-flight prompt and notifying harness` : `\xBB no opencode events for ${idleSec}s \u2014 the provider never returned a first token; aborting in-flight prompt and notifying harness`
|
|
116583
|
+
params.ctx.diagnostic.sawModelOutput ? `\xBB no opencode events for ${idleSec}s \u2014 aborting in-flight prompt and notifying harness` : `\xBB no opencode events for ${idleSec}s \u2014 the provider never returned a first token; aborting in-flight prompt and notifying harness`
|
|
116582
116584
|
);
|
|
116583
116585
|
params.abortTurn();
|
|
116584
116586
|
try {
|
|
@@ -116722,7 +116724,6 @@ var opencode = agent({
|
|
|
116722
116724
|
currentTurn: null,
|
|
116723
116725
|
eventCount: 0,
|
|
116724
116726
|
lastEventAt: performance7.now(),
|
|
116725
|
-
sawModelOutput: false,
|
|
116726
116727
|
promptMessageID: void 0,
|
|
116727
116728
|
taskDispatchByCallID: /* @__PURE__ */ new Map(),
|
|
116728
116729
|
loggedToolCallIDs: /* @__PURE__ */ new Set(),
|
|
@@ -116786,7 +116787,9 @@ var opencode = agent({
|
|
|
116786
116787
|
);
|
|
116787
116788
|
};
|
|
116788
116789
|
const standDownIfRecovered = (turn) => {
|
|
116789
|
-
if (watchdog.firedThisTurn()
|
|
116790
|
+
if (!watchdog.firedThisTurn() || !turn.success) return turn;
|
|
116791
|
+
runnerCtx.diagnostic.idleSec = void 0;
|
|
116792
|
+
ctx.onTurnRecovered?.();
|
|
116790
116793
|
return turn;
|
|
116791
116794
|
};
|
|
116792
116795
|
const result = await attempt(text);
|
|
@@ -188634,8 +188637,8 @@ function validatePushDestination(repoState, branch, cwd) {
|
|
|
188634
188637
|
throw new Error(
|
|
188635
188638
|
`Push blocked: destination does not match expected repository.
|
|
188636
188639
|
Expected: ${pushUrl}
|
|
188637
|
-
Actual: ${dest.url}
|
|
188638
|
-
Git configuration may have been tampered with.`
|
|
188640
|
+
Actual: remote '${dest.remoteName}' -> ${dest.url}
|
|
188641
|
+
Git configuration may have been tampered with, or a url.*.insteadOf rewrite is in effect.`
|
|
188639
188642
|
);
|
|
188640
188643
|
}
|
|
188641
188644
|
return dest;
|
|
@@ -190757,6 +190760,9 @@ async function configureRepoGit(params) {
|
|
|
190757
190760
|
removeIncludeIfEntries(repoDir);
|
|
190758
190761
|
const originUrl = `https://github.com/${params.owner}/${params.name}.git`;
|
|
190759
190762
|
$("git", ["remote", "set-url", "origin", originUrl], { cwd: repoDir });
|
|
190763
|
+
$("git", ["config", "--local", "--replace-all", "remote.origin.pushurl", originUrl], {
|
|
190764
|
+
cwd: repoDir
|
|
190765
|
+
});
|
|
190760
190766
|
repoState.pushUrl = originUrl;
|
|
190761
190767
|
$("git", ["config", "--local", "credential.helper", ""], { cwd: repoDir });
|
|
190762
190768
|
repoState.initialHead = captureInitialHead(repoDir);
|
|
@@ -191095,6 +191101,9 @@ async function checkoutPrBranch(pr, params) {
|
|
|
191095
191101
|
$("git", ["remote", "set-url", remoteName, forkUrl], { log: false });
|
|
191096
191102
|
log2.debug(`\xBB updated remote '${remoteName}' for fork ${pr.headRepoFullName}`);
|
|
191097
191103
|
}
|
|
191104
|
+
$("git", ["config", "--local", "--replace-all", `remote.${remoteName}.pushurl`, forkUrl], {
|
|
191105
|
+
log: false
|
|
191106
|
+
});
|
|
191098
191107
|
$("git", ["config", `branch.${localBranch}.pushRemote`, remoteName], { log: false });
|
|
191099
191108
|
$("git", ["config", `branch.${localBranch}.merge`, `refs/heads/${pr.headRef}`], { log: false });
|
|
191100
191109
|
log2.debug(`\xBB configured branch '${localBranch}' to push to '${remoteName}/${pr.headRef}'`);
|
|
@@ -196268,6 +196277,43 @@ function resolveCwd(cwd) {
|
|
|
196268
196277
|
if (isAbsolute2(cwd)) return cwd;
|
|
196269
196278
|
return workspace ? resolve2(workspace, cwd) : cwd;
|
|
196270
196279
|
}
|
|
196280
|
+
var DISPATCH_PAYLOAD_KEY = '"~pullfrog"';
|
|
196281
|
+
function sliceBalancedObject(text, open) {
|
|
196282
|
+
let depth = 0;
|
|
196283
|
+
let inString = false;
|
|
196284
|
+
for (let i2 = open; i2 < text.length; i2++) {
|
|
196285
|
+
const char = text[i2];
|
|
196286
|
+
if (inString) {
|
|
196287
|
+
if (char === "\\") i2++;
|
|
196288
|
+
else if (char === '"') inString = false;
|
|
196289
|
+
continue;
|
|
196290
|
+
}
|
|
196291
|
+
if (char === '"') inString = true;
|
|
196292
|
+
else if (char === "{") depth++;
|
|
196293
|
+
else if (char === "}" && --depth === 0) return text.slice(open, i2 + 1);
|
|
196294
|
+
}
|
|
196295
|
+
return void 0;
|
|
196296
|
+
}
|
|
196297
|
+
function findEmbeddedDispatchPayload(text) {
|
|
196298
|
+
const limit = text.lastIndexOf(DISPATCH_PAYLOAD_KEY);
|
|
196299
|
+
if (limit === -1) return void 0;
|
|
196300
|
+
for (let open = text.indexOf("{"); open !== -1 && open < limit; open = text.indexOf("{", open + 1)) {
|
|
196301
|
+
const candidate = sliceBalancedObject(text, open);
|
|
196302
|
+
if (!candidate) continue;
|
|
196303
|
+
try {
|
|
196304
|
+
const parsed2 = JSON.parse(candidate);
|
|
196305
|
+
if (JsonPayload.allows(parsed2)) return parsed2;
|
|
196306
|
+
} catch {
|
|
196307
|
+
}
|
|
196308
|
+
}
|
|
196309
|
+
return void 0;
|
|
196310
|
+
}
|
|
196311
|
+
function assertDispatchPayloadIntact(promptInput) {
|
|
196312
|
+
if (!findEmbeddedDispatchPayload(promptInput)) return;
|
|
196313
|
+
throw new Error(
|
|
196314
|
+
"this workflow wraps `${{ inputs.prompt }}` in other text, so Pullfrog's dispatch payload can't be read and the run would lose its pull request context, its progress comment, and its standing instructions. in `.github/workflows/pullfrog.yml`, set `prompt:` to exactly `${{ inputs.prompt }}`, and move your own preamble into Standing instructions in the Pullfrog console."
|
|
196315
|
+
);
|
|
196316
|
+
}
|
|
196271
196317
|
function resolvePromptInput() {
|
|
196272
196318
|
const promptInput = core7.getInput("prompt");
|
|
196273
196319
|
const promptFile = core7.getInput("prompt_file");
|
|
@@ -196284,9 +196330,9 @@ function resolvePromptInput() {
|
|
|
196284
196330
|
try {
|
|
196285
196331
|
parsed2 = JSON.parse(promptInput);
|
|
196286
196332
|
} catch {
|
|
196287
|
-
return promptInput;
|
|
196288
196333
|
}
|
|
196289
196334
|
if (!parsed2 || typeof parsed2 !== "object" || !("~pullfrog" in parsed2)) {
|
|
196335
|
+
assertDispatchPayloadIntact(promptInput);
|
|
196290
196336
|
return promptInput;
|
|
196291
196337
|
}
|
|
196292
196338
|
const jsonPayload = JsonPayload.assert(parsed2);
|
|
@@ -196916,7 +196962,7 @@ async function handleAgentResult(ctx) {
|
|
|
196916
196962
|
const mode = toolState.selectedMode;
|
|
196917
196963
|
const isReviewMode = mode === "Review" || mode === "IncrementalReview";
|
|
196918
196964
|
const payload = ctx.toolContext.payload;
|
|
196919
|
-
const hasCommentTarget = toolState.hadProgressComment ||
|
|
196965
|
+
const hasCommentTarget = toolState.hadProgressComment || payload.event.issue_number !== void 0;
|
|
196920
196966
|
if (!isReviewMode && !toolState.wasUpdated && hasCommentTarget && !ctx.silent) {
|
|
196921
196967
|
const tracker = toolState.todoTracker;
|
|
196922
196968
|
if (tracker) {
|
|
@@ -197350,6 +197396,19 @@ ${input.errorMessage}
|
|
|
197350
197396
|
\`\`\``
|
|
197351
197397
|
].join("\n");
|
|
197352
197398
|
}
|
|
197399
|
+
function formatProviderMissingCredential(input) {
|
|
197400
|
+
return [
|
|
197401
|
+
"**No credential reached the model provider.**",
|
|
197402
|
+
"",
|
|
197403
|
+
"The run started without a key for the provider it ended up using, so the request was refused before any model work happened. Nothing was billed, and no key of yours was rejected \u2014 add a provider key, or pick a model you already have one for.",
|
|
197404
|
+
"",
|
|
197405
|
+
`[Configure model \u2192](${getApiUrl()}/console/${input.owner}/${input.name})`,
|
|
197406
|
+
"",
|
|
197407
|
+
`\`\`\`
|
|
197408
|
+
${input.errorMessage}
|
|
197409
|
+
\`\`\``
|
|
197410
|
+
].join("\n");
|
|
197411
|
+
}
|
|
197353
197412
|
function formatProviderModelNotFoundSummary(input) {
|
|
197354
197413
|
return `The configured model is no longer available in OpenCode's catalog. Pick a different model in the Pullfrog console for \`${input.owner}/${input.name}\`, or contact support if this persists.
|
|
197355
197414
|
|
|
@@ -197395,6 +197454,16 @@ ${body}`, comment: body };
|
|
|
197395
197454
|
});
|
|
197396
197455
|
return { summary: `### \u274C Pullfrog failed
|
|
197397
197456
|
|
|
197457
|
+
${body}`, comment: body };
|
|
197458
|
+
}
|
|
197459
|
+
if (isProviderMissingCredential(input.errorMessage)) {
|
|
197460
|
+
const body = formatProviderMissingCredential({
|
|
197461
|
+
owner: input.repo.owner,
|
|
197462
|
+
name: input.repo.name,
|
|
197463
|
+
errorMessage: input.errorMessage
|
|
197464
|
+
});
|
|
197465
|
+
return { summary: `### \u274C Pullfrog failed
|
|
197466
|
+
|
|
197398
197467
|
${body}`, comment: body };
|
|
197399
197468
|
}
|
|
197400
197469
|
const apiKeySource = hangBody ?? input.errorMessage;
|
|
@@ -197441,7 +197510,7 @@ ${body}`, comment: body };
|
|
|
197441
197510
|
${body}`, comment: body };
|
|
197442
197511
|
}
|
|
197443
197512
|
if (hangBody) {
|
|
197444
|
-
const explained = input.agentDiagnostic?.lastProviderError !== void 0;
|
|
197513
|
+
const explained = input.agentDiagnostic?.lastProviderError !== void 0 || input.agentDiagnostic?.sawModelOutput === false;
|
|
197445
197514
|
return {
|
|
197446
197515
|
summary: `### \u274C Pullfrog failed
|
|
197447
197516
|
|
|
@@ -199234,7 +199303,7 @@ async function runCli4(input) {
|
|
|
199234
199303
|
}
|
|
199235
199304
|
|
|
199236
199305
|
// cli.ts
|
|
199237
|
-
var VERSION10 = "0.1.
|
|
199306
|
+
var VERSION10 = "0.1.63";
|
|
199238
199307
|
var bin = basename2(process.argv[1] || "");
|
|
199239
199308
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
199240
199309
|
var rawArgs = process.argv.slice(2);
|
package/dist/index.js
CHANGED
|
@@ -105126,6 +105126,9 @@ function isProviderNoRoutableEndpoints(text) {
|
|
|
105126
105126
|
function isOpenRouterKeyLimitExceeded(text) {
|
|
105127
105127
|
return /Key limit exceeded \(total limit\)/i.test(text) || /openrouter\.ai\/[^\s"')]*\/keys\//i.test(text);
|
|
105128
105128
|
}
|
|
105129
|
+
function isProviderMissingCredential(text) {
|
|
105130
|
+
return /API key is missing\. Pass it using the/i.test(text) || /Missing Authentication header/i.test(text);
|
|
105131
|
+
}
|
|
105129
105132
|
function isTransientUpstreamError(text) {
|
|
105130
105133
|
return /API Error:\s*5\d\d/i.test(text) || /\bOverloaded\b/.test(text) || /"error_type"\s*:\s*"(?:provider_unavailable|timeout)"/i.test(text) || /Streaming response failed:\s*\[5\d\d\]/i.test(text);
|
|
105131
105134
|
}
|
|
@@ -105188,7 +105191,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
105188
105191
|
// package.json
|
|
105189
105192
|
var package_default = {
|
|
105190
105193
|
name: "pullfrog",
|
|
105191
|
-
version: "0.1.
|
|
105194
|
+
version: "0.1.63",
|
|
105192
105195
|
type: "module",
|
|
105193
105196
|
bin: {
|
|
105194
105197
|
pullfrog: "dist/cli.mjs",
|
|
@@ -106279,7 +106282,6 @@ function getUnsubmittedReview(toolState, expectsReviewOutput2 = toolState.hadPro
|
|
|
106279
106282
|
}
|
|
106280
106283
|
function expectsReviewOutput(ctx) {
|
|
106281
106284
|
if (ctx.toolState.hadProgressComment) return true;
|
|
106282
|
-
if (ctx.payload.progressComments) return false;
|
|
106283
106285
|
return ctx.payload.event.silent !== true && ctx.payload.event.issue_number !== void 0;
|
|
106284
106286
|
}
|
|
106285
106287
|
function buildStopHookPrompt(failure2) {
|
|
@@ -106494,6 +106496,7 @@ async function runPostRunRetryLoop(params) {
|
|
|
106494
106496
|
result = preResume;
|
|
106495
106497
|
break;
|
|
106496
106498
|
}
|
|
106499
|
+
if (onlySummaryStale) result = { ...result, output: preResume.output || result.output };
|
|
106497
106500
|
gateResumeCount++;
|
|
106498
106501
|
}
|
|
106499
106502
|
if (gateResumeCount > 0 && result.success && hasPostRunIssues(finalIssues)) {
|
|
@@ -113885,24 +113888,23 @@ async function installOpencodeCli(params) {
|
|
|
113885
113888
|
var AUTO_SELECT_WARNING = "select a model explicitly in the Pullfrog console (https://pullfrog.com/console) to avoid this \u2014 if the picker is locked, this run had no Pullfrog Router funding: add a card or top up your credit.";
|
|
113886
113889
|
function autoSelectModel() {
|
|
113887
113890
|
const authorized2 = getAuthorizedModels();
|
|
113888
|
-
|
|
113889
|
-
|
|
113890
|
-
|
|
113891
|
-
|
|
113892
|
-
|
|
113893
|
-
|
|
113894
|
-
|
|
113895
|
-
|
|
113896
|
-
log.info(
|
|
113897
|
-
`\xBB model: ${match3.resolve} (auto-selected${match3.preferred ? " \u2014 preferred" : ""} curated match)`
|
|
113898
|
-
);
|
|
113899
|
-
log.warning(`\xBB model auto-selected. ${AUTO_SELECT_WARNING}`);
|
|
113900
|
-
return match3.resolve;
|
|
113901
|
-
}
|
|
113891
|
+
const selectable = modelAliases.filter((a) => !a.hidden && !a.fallback && !a.routing);
|
|
113892
|
+
const servable = selectable.filter((a) => {
|
|
113893
|
+
const envVars = getModelEnvVars(a.resolve);
|
|
113894
|
+
return envVars.length === 0 || envVars.some((name) => process.env[name]);
|
|
113895
|
+
});
|
|
113896
|
+
const pick4 = (list) => list.find((a) => a.preferred) ?? list[0];
|
|
113897
|
+
const match3 = pick4(servable.filter((a) => authorized2.has(a.resolve))) ?? pick4(servable);
|
|
113898
|
+
if (match3) {
|
|
113902
113899
|
log.info(
|
|
113903
|
-
`\xBB
|
|
113900
|
+
`\xBB model: ${match3.resolve} (auto-selected${match3.preferred ? " \u2014 preferred" : ""} curated match)`
|
|
113904
113901
|
);
|
|
113902
|
+
log.warning(`\xBB model auto-selected. ${AUTO_SELECT_WARNING}`);
|
|
113903
|
+
return match3.resolve;
|
|
113905
113904
|
}
|
|
113905
|
+
log.info(
|
|
113906
|
+
`\xBB opencode has ${authorized2.size} models but none match curated aliases and none hold a credential \u2014 letting OpenCode auto-select: ${[...authorized2].join(", ")}`
|
|
113907
|
+
);
|
|
113906
113908
|
log.warning(`\xBB no model resolved. letting OpenCode auto-select. ${AUTO_SELECT_WARNING}`);
|
|
113907
113909
|
return void 0;
|
|
113908
113910
|
}
|
|
@@ -114115,7 +114117,7 @@ function isModelOutput(ctx, part) {
|
|
|
114115
114117
|
async function dispatchEvent(ctx, event) {
|
|
114116
114118
|
if (event.type === "message.part.updated") {
|
|
114117
114119
|
ctx.lastEventAt = performance7.now();
|
|
114118
|
-
if (isModelOutput(ctx, event.properties.part)) ctx.sawModelOutput = true;
|
|
114120
|
+
if (isModelOutput(ctx, event.properties.part)) ctx.diagnostic.sawModelOutput = true;
|
|
114119
114121
|
await onPartUpdated(ctx, event.properties.part);
|
|
114120
114122
|
return;
|
|
114121
114123
|
}
|
|
@@ -114261,7 +114263,8 @@ async function runPromptTurn(ctx, params) {
|
|
|
114261
114263
|
const turn = ctx.currentTurn;
|
|
114262
114264
|
const part = { type: "text", text: params.text };
|
|
114263
114265
|
ctx.promptMessageID = void 0;
|
|
114264
|
-
ctx.sawModelOutput = false;
|
|
114266
|
+
ctx.diagnostic.sawModelOutput = false;
|
|
114267
|
+
ctx.diagnostic.idleSec = void 0;
|
|
114265
114268
|
let assistant;
|
|
114266
114269
|
let returnedParts;
|
|
114267
114270
|
let networkError = null;
|
|
@@ -114321,7 +114324,7 @@ async function runPromptTurn(ctx, params) {
|
|
|
114321
114324
|
usage
|
|
114322
114325
|
};
|
|
114323
114326
|
}
|
|
114324
|
-
if (!ctx.sawModelOutput && finalText.trim().length === 0 && !usage) {
|
|
114327
|
+
if (!ctx.diagnostic.sawModelOutput && finalText.trim().length === 0 && !usage) {
|
|
114325
114328
|
return {
|
|
114326
114329
|
success: false,
|
|
114327
114330
|
output: finalText,
|
|
@@ -114446,19 +114449,18 @@ function startInnerActivityWatchdog(params) {
|
|
|
114446
114449
|
const id = setInterval(() => {
|
|
114447
114450
|
if (fired) return;
|
|
114448
114451
|
const idleMs = performance7.now() - params.ctx.lastEventAt;
|
|
114449
|
-
const compiledMs = params.ctx.sawModelOutput ? params.timeoutMs : AGENT_FIRST_EVENT_TIMEOUT_MS;
|
|
114452
|
+
const compiledMs = params.ctx.diagnostic.sawModelOutput ? params.timeoutMs : AGENT_FIRST_EVENT_TIMEOUT_MS;
|
|
114450
114453
|
const budgetMs = everFired ? compiledMs : watchdogBudgetMs(
|
|
114451
114454
|
compiledMs,
|
|
114452
|
-
params.ctx.sawModelOutput ? "PULLFROG_E2E_ACTIVITY_TIMEOUT_MS" : "PULLFROG_E2E_FIRST_EVENT_TIMEOUT_MS"
|
|
114455
|
+
params.ctx.diagnostic.sawModelOutput ? "PULLFROG_E2E_ACTIVITY_TIMEOUT_MS" : "PULLFROG_E2E_FIRST_EVENT_TIMEOUT_MS"
|
|
114453
114456
|
);
|
|
114454
114457
|
if (idleMs <= budgetMs) return;
|
|
114455
114458
|
fired = true;
|
|
114456
114459
|
everFired = true;
|
|
114457
114460
|
const idleSec = Math.round(idleMs / 1e3);
|
|
114458
114461
|
params.ctx.diagnostic.idleSec = idleSec;
|
|
114459
|
-
params.ctx.diagnostic.sawModelOutput = params.ctx.sawModelOutput;
|
|
114460
114462
|
log.info(
|
|
114461
|
-
params.ctx.sawModelOutput ? `\xBB no opencode events for ${idleSec}s \u2014 aborting in-flight prompt and notifying harness` : `\xBB no opencode events for ${idleSec}s \u2014 the provider never returned a first token; aborting in-flight prompt and notifying harness`
|
|
114463
|
+
params.ctx.diagnostic.sawModelOutput ? `\xBB no opencode events for ${idleSec}s \u2014 aborting in-flight prompt and notifying harness` : `\xBB no opencode events for ${idleSec}s \u2014 the provider never returned a first token; aborting in-flight prompt and notifying harness`
|
|
114462
114464
|
);
|
|
114463
114465
|
params.abortTurn();
|
|
114464
114466
|
try {
|
|
@@ -114602,7 +114604,6 @@ var opencode = agent({
|
|
|
114602
114604
|
currentTurn: null,
|
|
114603
114605
|
eventCount: 0,
|
|
114604
114606
|
lastEventAt: performance7.now(),
|
|
114605
|
-
sawModelOutput: false,
|
|
114606
114607
|
promptMessageID: void 0,
|
|
114607
114608
|
taskDispatchByCallID: /* @__PURE__ */ new Map(),
|
|
114608
114609
|
loggedToolCallIDs: /* @__PURE__ */ new Set(),
|
|
@@ -114666,7 +114667,9 @@ var opencode = agent({
|
|
|
114666
114667
|
);
|
|
114667
114668
|
};
|
|
114668
114669
|
const standDownIfRecovered = (turn) => {
|
|
114669
|
-
if (watchdog.firedThisTurn()
|
|
114670
|
+
if (!watchdog.firedThisTurn() || !turn.success) return turn;
|
|
114671
|
+
runnerCtx.diagnostic.idleSec = void 0;
|
|
114672
|
+
ctx.onTurnRecovered?.();
|
|
114670
114673
|
return turn;
|
|
114671
114674
|
};
|
|
114672
114675
|
const result = await attempt(text);
|
|
@@ -186514,8 +186517,8 @@ function validatePushDestination(repoState, branch, cwd) {
|
|
|
186514
186517
|
throw new Error(
|
|
186515
186518
|
`Push blocked: destination does not match expected repository.
|
|
186516
186519
|
Expected: ${pushUrl}
|
|
186517
|
-
Actual: ${dest.url}
|
|
186518
|
-
Git configuration may have been tampered with.`
|
|
186520
|
+
Actual: remote '${dest.remoteName}' -> ${dest.url}
|
|
186521
|
+
Git configuration may have been tampered with, or a url.*.insteadOf rewrite is in effect.`
|
|
186519
186522
|
);
|
|
186520
186523
|
}
|
|
186521
186524
|
return dest;
|
|
@@ -188637,6 +188640,9 @@ async function configureRepoGit(params) {
|
|
|
188637
188640
|
removeIncludeIfEntries(repoDir);
|
|
188638
188641
|
const originUrl = `https://github.com/${params.owner}/${params.name}.git`;
|
|
188639
188642
|
$("git", ["remote", "set-url", "origin", originUrl], { cwd: repoDir });
|
|
188643
|
+
$("git", ["config", "--local", "--replace-all", "remote.origin.pushurl", originUrl], {
|
|
188644
|
+
cwd: repoDir
|
|
188645
|
+
});
|
|
188640
188646
|
repoState.pushUrl = originUrl;
|
|
188641
188647
|
$("git", ["config", "--local", "credential.helper", ""], { cwd: repoDir });
|
|
188642
188648
|
repoState.initialHead = captureInitialHead(repoDir);
|
|
@@ -188975,6 +188981,9 @@ async function checkoutPrBranch(pr, params) {
|
|
|
188975
188981
|
$("git", ["remote", "set-url", remoteName, forkUrl], { log: false });
|
|
188976
188982
|
log.debug(`\xBB updated remote '${remoteName}' for fork ${pr.headRepoFullName}`);
|
|
188977
188983
|
}
|
|
188984
|
+
$("git", ["config", "--local", "--replace-all", `remote.${remoteName}.pushurl`, forkUrl], {
|
|
188985
|
+
log: false
|
|
188986
|
+
});
|
|
188978
188987
|
$("git", ["config", `branch.${localBranch}.pushRemote`, remoteName], { log: false });
|
|
188979
188988
|
$("git", ["config", `branch.${localBranch}.merge`, `refs/heads/${pr.headRef}`], { log: false });
|
|
188980
188989
|
log.debug(`\xBB configured branch '${localBranch}' to push to '${remoteName}/${pr.headRef}'`);
|
|
@@ -194148,6 +194157,43 @@ function resolveCwd(cwd) {
|
|
|
194148
194157
|
if (isAbsolute2(cwd)) return cwd;
|
|
194149
194158
|
return workspace ? resolve2(workspace, cwd) : cwd;
|
|
194150
194159
|
}
|
|
194160
|
+
var DISPATCH_PAYLOAD_KEY = '"~pullfrog"';
|
|
194161
|
+
function sliceBalancedObject(text, open) {
|
|
194162
|
+
let depth = 0;
|
|
194163
|
+
let inString = false;
|
|
194164
|
+
for (let i = open; i < text.length; i++) {
|
|
194165
|
+
const char = text[i];
|
|
194166
|
+
if (inString) {
|
|
194167
|
+
if (char === "\\") i++;
|
|
194168
|
+
else if (char === '"') inString = false;
|
|
194169
|
+
continue;
|
|
194170
|
+
}
|
|
194171
|
+
if (char === '"') inString = true;
|
|
194172
|
+
else if (char === "{") depth++;
|
|
194173
|
+
else if (char === "}" && --depth === 0) return text.slice(open, i + 1);
|
|
194174
|
+
}
|
|
194175
|
+
return void 0;
|
|
194176
|
+
}
|
|
194177
|
+
function findEmbeddedDispatchPayload(text) {
|
|
194178
|
+
const limit = text.lastIndexOf(DISPATCH_PAYLOAD_KEY);
|
|
194179
|
+
if (limit === -1) return void 0;
|
|
194180
|
+
for (let open = text.indexOf("{"); open !== -1 && open < limit; open = text.indexOf("{", open + 1)) {
|
|
194181
|
+
const candidate = sliceBalancedObject(text, open);
|
|
194182
|
+
if (!candidate) continue;
|
|
194183
|
+
try {
|
|
194184
|
+
const parsed2 = JSON.parse(candidate);
|
|
194185
|
+
if (JsonPayload.allows(parsed2)) return parsed2;
|
|
194186
|
+
} catch {
|
|
194187
|
+
}
|
|
194188
|
+
}
|
|
194189
|
+
return void 0;
|
|
194190
|
+
}
|
|
194191
|
+
function assertDispatchPayloadIntact(promptInput) {
|
|
194192
|
+
if (!findEmbeddedDispatchPayload(promptInput)) return;
|
|
194193
|
+
throw new Error(
|
|
194194
|
+
"this workflow wraps `${{ inputs.prompt }}` in other text, so Pullfrog's dispatch payload can't be read and the run would lose its pull request context, its progress comment, and its standing instructions. in `.github/workflows/pullfrog.yml`, set `prompt:` to exactly `${{ inputs.prompt }}`, and move your own preamble into Standing instructions in the Pullfrog console."
|
|
194195
|
+
);
|
|
194196
|
+
}
|
|
194151
194197
|
function resolvePromptInput() {
|
|
194152
194198
|
const promptInput = core7.getInput("prompt");
|
|
194153
194199
|
const promptFile = core7.getInput("prompt_file");
|
|
@@ -194164,9 +194210,9 @@ function resolvePromptInput() {
|
|
|
194164
194210
|
try {
|
|
194165
194211
|
parsed2 = JSON.parse(promptInput);
|
|
194166
194212
|
} catch {
|
|
194167
|
-
return promptInput;
|
|
194168
194213
|
}
|
|
194169
194214
|
if (!parsed2 || typeof parsed2 !== "object" || !("~pullfrog" in parsed2)) {
|
|
194215
|
+
assertDispatchPayloadIntact(promptInput);
|
|
194170
194216
|
return promptInput;
|
|
194171
194217
|
}
|
|
194172
194218
|
const jsonPayload = JsonPayload.assert(parsed2);
|
|
@@ -194796,7 +194842,7 @@ async function handleAgentResult(ctx) {
|
|
|
194796
194842
|
const mode = toolState.selectedMode;
|
|
194797
194843
|
const isReviewMode = mode === "Review" || mode === "IncrementalReview";
|
|
194798
194844
|
const payload = ctx.toolContext.payload;
|
|
194799
|
-
const hasCommentTarget = toolState.hadProgressComment ||
|
|
194845
|
+
const hasCommentTarget = toolState.hadProgressComment || payload.event.issue_number !== void 0;
|
|
194800
194846
|
if (!isReviewMode && !toolState.wasUpdated && hasCommentTarget && !ctx.silent) {
|
|
194801
194847
|
const tracker = toolState.todoTracker;
|
|
194802
194848
|
if (tracker) {
|
|
@@ -195230,6 +195276,19 @@ ${input.errorMessage}
|
|
|
195230
195276
|
\`\`\``
|
|
195231
195277
|
].join("\n");
|
|
195232
195278
|
}
|
|
195279
|
+
function formatProviderMissingCredential(input) {
|
|
195280
|
+
return [
|
|
195281
|
+
"**No credential reached the model provider.**",
|
|
195282
|
+
"",
|
|
195283
|
+
"The run started without a key for the provider it ended up using, so the request was refused before any model work happened. Nothing was billed, and no key of yours was rejected \u2014 add a provider key, or pick a model you already have one for.",
|
|
195284
|
+
"",
|
|
195285
|
+
`[Configure model \u2192](${getApiUrl()}/console/${input.owner}/${input.name})`,
|
|
195286
|
+
"",
|
|
195287
|
+
`\`\`\`
|
|
195288
|
+
${input.errorMessage}
|
|
195289
|
+
\`\`\``
|
|
195290
|
+
].join("\n");
|
|
195291
|
+
}
|
|
195233
195292
|
function formatProviderModelNotFoundSummary(input) {
|
|
195234
195293
|
return `The configured model is no longer available in OpenCode's catalog. Pick a different model in the Pullfrog console for \`${input.owner}/${input.name}\`, or contact support if this persists.
|
|
195235
195294
|
|
|
@@ -195275,6 +195334,16 @@ ${body}`, comment: body };
|
|
|
195275
195334
|
});
|
|
195276
195335
|
return { summary: `### \u274C Pullfrog failed
|
|
195277
195336
|
|
|
195337
|
+
${body}`, comment: body };
|
|
195338
|
+
}
|
|
195339
|
+
if (isProviderMissingCredential(input.errorMessage)) {
|
|
195340
|
+
const body = formatProviderMissingCredential({
|
|
195341
|
+
owner: input.repo.owner,
|
|
195342
|
+
name: input.repo.name,
|
|
195343
|
+
errorMessage: input.errorMessage
|
|
195344
|
+
});
|
|
195345
|
+
return { summary: `### \u274C Pullfrog failed
|
|
195346
|
+
|
|
195278
195347
|
${body}`, comment: body };
|
|
195279
195348
|
}
|
|
195280
195349
|
const apiKeySource = hangBody ?? input.errorMessage;
|
|
@@ -195321,7 +195390,7 @@ ${body}`, comment: body };
|
|
|
195321
195390
|
${body}`, comment: body };
|
|
195322
195391
|
}
|
|
195323
195392
|
if (hangBody) {
|
|
195324
|
-
const explained = input.agentDiagnostic?.lastProviderError !== void 0;
|
|
195393
|
+
const explained = input.agentDiagnostic?.lastProviderError !== void 0 || input.agentDiagnostic?.sawModelOutput === false;
|
|
195325
195394
|
return {
|
|
195326
195395
|
summary: `### \u274C Pullfrog failed
|
|
195327
195396
|
|
|
@@ -50,6 +50,12 @@ export declare function isProviderNoRoutableEndpoints(text: string): boolean;
|
|
|
50
50
|
* …/settings/credits`) carries no `/keys/` path and correctly stays out.
|
|
51
51
|
*/
|
|
52
52
|
export declare function isOpenRouterKeyLimitExceeded(text: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* the provider was reached with NO credential at all — the AI SDK's shared `loadApiKey`
|
|
55
|
+
* refusal, so it is provider-agnostic. NOT `isApiKeyAuthError`, which is a key we hold
|
|
56
|
+
* being rejected: "rotate your key" names a credential that does not exist here.
|
|
57
|
+
*/
|
|
58
|
+
export declare function isProviderMissingCredential(text: string): boolean;
|
|
53
59
|
/**
|
|
54
60
|
* The upstream is having a moment: Anthropic's `API Error: 529 Overloaded`,
|
|
55
61
|
* OpenRouter's `provider_unavailable` / `timeout`, OpenCode Zen's `Streaming
|
|
@@ -45,12 +45,12 @@
|
|
|
45
45
|
* `"activity timeout"` or `"agent still pending"` AND none of the
|
|
46
46
|
* above matched. The harness keeps structured diagnostic state on
|
|
47
47
|
* `toolState.agentDiagnostic`; `formatAgentHangBody` renders that into
|
|
48
|
-
* the job summary. The PR comment
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
48
|
+
* the job summary. The PR comment keeps that same body whenever the hang
|
|
49
|
+
* is EXPLAINED — a classified provider error (#778, #1183) or a provider
|
|
50
|
+
* that never returned a first token — and otherwise collapses to a
|
|
51
|
+
* one-line `**Run failed.** [View the logs →]`, because watchdog jargon,
|
|
52
|
+
* event counts and a benign stderr tail are operator-grade detail that
|
|
53
|
+
* only alarm the average user.
|
|
54
54
|
*
|
|
55
55
|
* 6. Default — the job summary gets a plain-English lead sentence plus the
|
|
56
56
|
* raw error in a fenced code block under the `### ❌ Pullfrog failed`
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
*
|
|
60
60
|
* Net: the actionable classifications (billing, API-key, model-not-found,
|
|
61
61
|
* no-provider-available, context-overflow, transient-upstream) render identical
|
|
62
|
-
* bodies on both surfaces; the non-actionable ones (hang,
|
|
62
|
+
* bodies on both surfaces; the non-actionable ones (unexplained hang,
|
|
63
63
|
* generic) keep the forensics in the Actions job summary and show a calm
|
|
64
64
|
* one-liner in the PR comment, whose footer already carries Pullfrog
|
|
65
65
|
* branding + rerun links.
|