pullfrog 0.1.7 → 0.1.8
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 +369 -224
- package/dist/index.js +368 -223
- package/dist/mcp/shell.d.ts +5 -0
- package/dist/toolState.d.ts +2 -0
- package/dist/utils/agentHangReport.d.ts +38 -0
- package/dist/utils/gitAuth.d.ts +27 -0
- package/dist/utils/providerErrors.d.ts +11 -0
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -142697,7 +142697,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
142697
142697
|
// package.json
|
|
142698
142698
|
var package_default = {
|
|
142699
142699
|
name: "pullfrog",
|
|
142700
|
-
version: "0.1.
|
|
142700
|
+
version: "0.1.8",
|
|
142701
142701
|
type: "module",
|
|
142702
142702
|
bin: {
|
|
142703
142703
|
pullfrog: "dist/cli.mjs",
|
|
@@ -143165,6 +143165,51 @@ function readNumber(params) {
|
|
|
143165
143165
|
import { execSync } from "node:child_process";
|
|
143166
143166
|
import { createHash } from "node:crypto";
|
|
143167
143167
|
import { readFileSync as readFileSync2, realpathSync, unlinkSync } from "node:fs";
|
|
143168
|
+
|
|
143169
|
+
// utils/shell.ts
|
|
143170
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
143171
|
+
function $(cmd, args2, options) {
|
|
143172
|
+
const encoding = options?.encoding ?? "utf-8";
|
|
143173
|
+
const env2 = resolveEnv(options?.env);
|
|
143174
|
+
const result = spawnSync2(cmd, args2, {
|
|
143175
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
143176
|
+
encoding,
|
|
143177
|
+
cwd: options?.cwd,
|
|
143178
|
+
env: env2
|
|
143179
|
+
});
|
|
143180
|
+
const stdout = result.stdout ?? "";
|
|
143181
|
+
const stderr = result.stderr ?? "";
|
|
143182
|
+
if (options?.log !== false) {
|
|
143183
|
+
const canWriteToStdout = process.stdout.isTTY === true;
|
|
143184
|
+
if (stdout) {
|
|
143185
|
+
if (canWriteToStdout) {
|
|
143186
|
+
process.stdout.write(stdout);
|
|
143187
|
+
} else {
|
|
143188
|
+
process.stderr.write(stdout);
|
|
143189
|
+
}
|
|
143190
|
+
}
|
|
143191
|
+
if (stderr) {
|
|
143192
|
+
process.stderr.write(stderr);
|
|
143193
|
+
}
|
|
143194
|
+
}
|
|
143195
|
+
if (result.status !== 0) {
|
|
143196
|
+
const errorResult = {
|
|
143197
|
+
status: result.status ?? -1,
|
|
143198
|
+
stdout,
|
|
143199
|
+
stderr
|
|
143200
|
+
};
|
|
143201
|
+
if (options?.onError) {
|
|
143202
|
+
options.onError(errorResult);
|
|
143203
|
+
return stdout.trim();
|
|
143204
|
+
}
|
|
143205
|
+
throw new Error(
|
|
143206
|
+
`Command failed with exit code ${errorResult.status}: ${stderr || "Unknown error"}`
|
|
143207
|
+
);
|
|
143208
|
+
}
|
|
143209
|
+
return stdout.trim();
|
|
143210
|
+
}
|
|
143211
|
+
|
|
143212
|
+
// utils/gitAuth.ts
|
|
143168
143213
|
var gitBinary;
|
|
143169
143214
|
function hashFile(path3) {
|
|
143170
143215
|
return createHash("sha256").update(readFileSync2(path3)).digest("hex");
|
|
@@ -143256,6 +143301,27 @@ ${stdout}` : stderr || stdout || "(no output)";
|
|
|
143256
143301
|
}
|
|
143257
143302
|
}
|
|
143258
143303
|
}
|
|
143304
|
+
var SHALLOW_UNREACHABLE_PATTERNS = [
|
|
143305
|
+
/Could not read [a-f0-9]{40,64}/,
|
|
143306
|
+
/remote did not send all necessary objects/
|
|
143307
|
+
];
|
|
143308
|
+
var DEEPEN_RETRY_DEPTH = 1e3;
|
|
143309
|
+
async function $gitFetchWithDeepen(args2, options, label) {
|
|
143310
|
+
try {
|
|
143311
|
+
return await $git("fetch", args2, options);
|
|
143312
|
+
} catch (err) {
|
|
143313
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
143314
|
+
const isShallowUnreachable = SHALLOW_UNREACHABLE_PATTERNS.some((p2) => p2.test(msg));
|
|
143315
|
+
if (!isShallowUnreachable) throw err;
|
|
143316
|
+
const isShallow = $("git", ["rev-parse", "--is-shallow-repository"], { log: false }).trim() === "true";
|
|
143317
|
+
if (!isShallow) throw err;
|
|
143318
|
+
log.info(
|
|
143319
|
+
`\xBB ${label ?? "git fetch"} hit shallow-unreachable error, retrying with --deepen=${DEEPEN_RETRY_DEPTH}`
|
|
143320
|
+
);
|
|
143321
|
+
const retryArgs = args2.filter((a) => !a.startsWith("--depth="));
|
|
143322
|
+
return await $git("fetch", [`--deepen=${DEEPEN_RETRY_DEPTH}`, ...retryArgs], options);
|
|
143323
|
+
}
|
|
143324
|
+
}
|
|
143259
143325
|
|
|
143260
143326
|
// lifecycle.ts
|
|
143261
143327
|
var LIFECYCLE_HOOK_TIMEOUT_MS = 6e5;
|
|
@@ -143297,49 +143363,6 @@ async function executeLifecycleHook(params) {
|
|
|
143297
143363
|
}
|
|
143298
143364
|
}
|
|
143299
143365
|
|
|
143300
|
-
// utils/shell.ts
|
|
143301
|
-
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
143302
|
-
function $(cmd, args2, options) {
|
|
143303
|
-
const encoding = options?.encoding ?? "utf-8";
|
|
143304
|
-
const env2 = resolveEnv(options?.env);
|
|
143305
|
-
const result = spawnSync2(cmd, args2, {
|
|
143306
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
143307
|
-
encoding,
|
|
143308
|
-
cwd: options?.cwd,
|
|
143309
|
-
env: env2
|
|
143310
|
-
});
|
|
143311
|
-
const stdout = result.stdout ?? "";
|
|
143312
|
-
const stderr = result.stderr ?? "";
|
|
143313
|
-
if (options?.log !== false) {
|
|
143314
|
-
const canWriteToStdout = process.stdout.isTTY === true;
|
|
143315
|
-
if (stdout) {
|
|
143316
|
-
if (canWriteToStdout) {
|
|
143317
|
-
process.stdout.write(stdout);
|
|
143318
|
-
} else {
|
|
143319
|
-
process.stderr.write(stdout);
|
|
143320
|
-
}
|
|
143321
|
-
}
|
|
143322
|
-
if (stderr) {
|
|
143323
|
-
process.stderr.write(stderr);
|
|
143324
|
-
}
|
|
143325
|
-
}
|
|
143326
|
-
if (result.status !== 0) {
|
|
143327
|
-
const errorResult = {
|
|
143328
|
-
status: result.status ?? -1,
|
|
143329
|
-
stdout,
|
|
143330
|
-
stderr
|
|
143331
|
-
};
|
|
143332
|
-
if (options?.onError) {
|
|
143333
|
-
options.onError(errorResult);
|
|
143334
|
-
return stdout.trim();
|
|
143335
|
-
}
|
|
143336
|
-
throw new Error(
|
|
143337
|
-
`Command failed with exit code ${errorResult.status}: ${stderr || "Unknown error"}`
|
|
143338
|
-
);
|
|
143339
|
-
}
|
|
143340
|
-
return stdout.trim();
|
|
143341
|
-
}
|
|
143342
|
-
|
|
143343
143366
|
// utils/rangeDiff.ts
|
|
143344
143367
|
function computeIncrementalDiff(params) {
|
|
143345
143368
|
try {
|
|
@@ -143734,11 +143757,6 @@ var GitFetch = type({
|
|
|
143734
143757
|
ref: type.string.describe("Ref to fetch: branch name, tag, or 'pull/N/head' for PRs"),
|
|
143735
143758
|
depth: type.number.describe("Fetch depth (for shallow clones)").optional()
|
|
143736
143759
|
});
|
|
143737
|
-
var SHALLOW_UNREACHABLE_PATTERNS = [
|
|
143738
|
-
/Could not read [a-f0-9]{40,64}/,
|
|
143739
|
-
/remote did not send all necessary objects/
|
|
143740
|
-
];
|
|
143741
|
-
var DEEPEN_RETRY_DEPTH = 1e3;
|
|
143742
143760
|
function GitFetchTool(ctx) {
|
|
143743
143761
|
return tool({
|
|
143744
143762
|
name: "git_fetch",
|
|
@@ -143750,20 +143768,7 @@ function GitFetchTool(ctx) {
|
|
|
143750
143768
|
if (params.depth !== void 0) {
|
|
143751
143769
|
fetchArgs.push(`--depth=${params.depth}`);
|
|
143752
143770
|
}
|
|
143753
|
-
|
|
143754
|
-
await $git("fetch", fetchArgs, { token: ctx.gitToken });
|
|
143755
|
-
} catch (err) {
|
|
143756
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
143757
|
-
const isShallowUnreachable = SHALLOW_UNREACHABLE_PATTERNS.some((p2) => p2.test(msg));
|
|
143758
|
-
const isShallow = isShallowUnreachable && $("git", ["rev-parse", "--is-shallow-repository"], { log: false }).trim() === "true";
|
|
143759
|
-
if (!isShallow) throw err;
|
|
143760
|
-
log.info(
|
|
143761
|
-
`\xBB git_fetch hit shallow-unreachable error, retrying with --deepen=${DEEPEN_RETRY_DEPTH}`
|
|
143762
|
-
);
|
|
143763
|
-
await $git("fetch", [`--deepen=${DEEPEN_RETRY_DEPTH}`, "--no-tags", "origin", params.ref], {
|
|
143764
|
-
token: ctx.gitToken
|
|
143765
|
-
});
|
|
143766
|
-
}
|
|
143771
|
+
await $gitFetchWithDeepen(fetchArgs, { token: ctx.gitToken }, "git_fetch");
|
|
143767
143772
|
return { success: true, ref: params.ref };
|
|
143768
143773
|
})
|
|
143769
143774
|
});
|
|
@@ -144487,10 +144492,10 @@ async function ensureBeforeShaReachable(params) {
|
|
|
144487
144492
|
sha: params.sha,
|
|
144488
144493
|
ref: tempBranch
|
|
144489
144494
|
}), true);
|
|
144490
|
-
await $
|
|
144491
|
-
"fetch",
|
|
144495
|
+
await $gitFetchWithDeepen(
|
|
144492
144496
|
["--no-tags", ...params.isShallow ? ["--depth=1"] : [], "origin", tempBranch],
|
|
144493
|
-
{ token: params.gitToken }
|
|
144497
|
+
{ token: params.gitToken },
|
|
144498
|
+
`before_sha temp branch ${tempBranch}`
|
|
144494
144499
|
);
|
|
144495
144500
|
log.debug(`\xBB fetched before_sha via temp branch ${tempBranch}`);
|
|
144496
144501
|
return true;
|
|
@@ -144566,16 +144571,22 @@ async function checkoutPrBranch(pr, params) {
|
|
|
144566
144571
|
toolState.checkoutSha = $("git", ["rev-parse", "HEAD"], { log: false }).trim();
|
|
144567
144572
|
const alreadyOnBranch = toolState.checkoutSha === pr.headSha;
|
|
144568
144573
|
log.debug(`\xBB fetching base branch (${pr.baseRef})...`);
|
|
144569
|
-
await $
|
|
144574
|
+
await $gitFetchWithDeepen(
|
|
144575
|
+
["--no-tags", "origin", pr.baseRef],
|
|
144576
|
+
{ token: gitToken },
|
|
144577
|
+
`base branch ${pr.baseRef}`
|
|
144578
|
+
);
|
|
144570
144579
|
if (!alreadyOnBranch) {
|
|
144571
144580
|
$("git", ["checkout", "-B", pr.baseRef, `origin/${pr.baseRef}`], { log: false });
|
|
144572
144581
|
log.debug(`\xBB fetching PR #${pr.number} (${localBranch})...`);
|
|
144573
144582
|
await retry(
|
|
144574
144583
|
async () => {
|
|
144575
144584
|
try {
|
|
144576
|
-
await $
|
|
144577
|
-
|
|
144578
|
-
|
|
144585
|
+
await $gitFetchWithDeepen(
|
|
144586
|
+
["--no-tags", "origin", `+pull/${pr.number}/head:${localBranch}`],
|
|
144587
|
+
{ token: gitToken },
|
|
144588
|
+
`PR #${pr.number}`
|
|
144589
|
+
);
|
|
144579
144590
|
} catch (e) {
|
|
144580
144591
|
const msg = e instanceof Error ? e.message : String(e);
|
|
144581
144592
|
if (PULL_REF_MISSING_PATTERN.test(msg)) {
|
|
@@ -144676,134 +144687,159 @@ async function checkoutPrBranch(pr, params) {
|
|
|
144676
144687
|
});
|
|
144677
144688
|
return { hookWarning: postCheckoutHook.warning };
|
|
144678
144689
|
}
|
|
144690
|
+
var inFlightCheckouts = /* @__PURE__ */ new Map();
|
|
144679
144691
|
function CheckoutPrTool(ctx) {
|
|
144692
|
+
const runCheckout = async (pull_number) => {
|
|
144693
|
+
const prResponse = await ctx.octokit.rest.pulls.get({
|
|
144694
|
+
owner: ctx.repo.owner,
|
|
144695
|
+
repo: ctx.repo.name,
|
|
144696
|
+
pull_number
|
|
144697
|
+
});
|
|
144698
|
+
const headRepo = prResponse.data.head.repo;
|
|
144699
|
+
if (!headRepo) {
|
|
144700
|
+
throw new Error(`PR #${pull_number} source repository was deleted`);
|
|
144701
|
+
}
|
|
144702
|
+
const pr = {
|
|
144703
|
+
number: pull_number,
|
|
144704
|
+
headSha: prResponse.data.head.sha,
|
|
144705
|
+
headRef: prResponse.data.head.ref,
|
|
144706
|
+
headRepoFullName: headRepo.full_name,
|
|
144707
|
+
baseRef: prResponse.data.base.ref,
|
|
144708
|
+
baseRepoFullName: prResponse.data.base.repo.full_name,
|
|
144709
|
+
maintainerCanModify: prResponse.data.maintainer_can_modify
|
|
144710
|
+
};
|
|
144711
|
+
const checkoutResult = await checkoutPrBranch(pr, {
|
|
144712
|
+
octokit: ctx.octokit,
|
|
144713
|
+
owner: ctx.repo.owner,
|
|
144714
|
+
name: ctx.repo.name,
|
|
144715
|
+
gitToken: ctx.gitToken,
|
|
144716
|
+
toolState: ctx.toolState,
|
|
144717
|
+
shell: ctx.payload.shell,
|
|
144718
|
+
postCheckoutScript: ctx.postCheckoutScript,
|
|
144719
|
+
beforeSha: ctx.toolState.beforeSha
|
|
144720
|
+
});
|
|
144721
|
+
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
144722
|
+
if (!tempDir) {
|
|
144723
|
+
throw new Error(
|
|
144724
|
+
"PULLFROG_TEMP_DIR not set - checkout_pr must run in pullfrog action context"
|
|
144725
|
+
);
|
|
144726
|
+
}
|
|
144727
|
+
const headShort = ctx.toolState.checkoutSha.slice(0, 7);
|
|
144728
|
+
let incrementalDiffPath;
|
|
144729
|
+
if (ctx.toolState.beforeSha && ctx.toolState.checkoutSha) {
|
|
144730
|
+
const beforeShort = ctx.toolState.beforeSha.slice(0, 7);
|
|
144731
|
+
const incremental = computeIncrementalDiff({
|
|
144732
|
+
baseBranch: pr.baseRef,
|
|
144733
|
+
beforeSha: ctx.toolState.beforeSha,
|
|
144734
|
+
headSha: ctx.toolState.checkoutSha
|
|
144735
|
+
});
|
|
144736
|
+
if (incremental) {
|
|
144737
|
+
incrementalDiffPath = join3(
|
|
144738
|
+
tempDir,
|
|
144739
|
+
`pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
|
|
144740
|
+
);
|
|
144741
|
+
writeFileSync(incrementalDiffPath, incremental);
|
|
144742
|
+
log.info(
|
|
144743
|
+
`\xBB incremental diff computed (${incremental.length} bytes) \u2192 ${incrementalDiffPath}`
|
|
144744
|
+
);
|
|
144745
|
+
}
|
|
144746
|
+
}
|
|
144747
|
+
const formatResult = await fetchAndFormatPrDiff(ctx, pull_number);
|
|
144748
|
+
const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
|
|
144749
|
+
log.debug(`formatted diff preview (first 100 lines):
|
|
144750
|
+
${diffPreview}`);
|
|
144751
|
+
const diffPath = join3(tempDir, `pr-${pull_number}-${headShort}.diff`);
|
|
144752
|
+
writeFileSync(diffPath, formatResult.content);
|
|
144753
|
+
log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
|
|
144754
|
+
ctx.toolState.diffCoverage = createDiffCoverageState({
|
|
144755
|
+
diffPath,
|
|
144756
|
+
totalLines: countLines({ content: formatResult.content }),
|
|
144757
|
+
toc: formatResult.toc,
|
|
144758
|
+
previous: ctx.toolState.diffCoverage
|
|
144759
|
+
});
|
|
144760
|
+
log.debug(
|
|
144761
|
+
`\xBB diff coverage initialized: diffPath=${diffPath}, totalLines=${ctx.toolState.diffCoverage.totalLines}, tocEntries=${ctx.toolState.diffCoverage.tocEntries.length}`
|
|
144762
|
+
);
|
|
144763
|
+
const cached4 = /* @__PURE__ */ new Map();
|
|
144764
|
+
for (const file2 of formatResult.files) {
|
|
144765
|
+
cached4.set(file2.filename, commentableLinesForFile(file2.patch));
|
|
144766
|
+
}
|
|
144767
|
+
ctx.toolState.commentableLinesByFile = cached4;
|
|
144768
|
+
ctx.toolState.commentableLinesPullNumber = pull_number;
|
|
144769
|
+
ctx.toolState.commentableLinesCheckoutSha = ctx.toolState.checkoutSha;
|
|
144770
|
+
const incrementalInstructions = incrementalDiffPath ? ` IMPORTANT: incrementalDiffPath contains ONLY the changes since the last reviewed version (computed via range-diff). you MUST read incrementalDiffPath FIRST to understand what changed, then use diffPath for full PR context. do NOT skip the incremental diff.` : "";
|
|
144771
|
+
const COMMIT_LOG_MAX = 200;
|
|
144772
|
+
const baseRange = `origin/${pr.baseRef}..HEAD`;
|
|
144773
|
+
let commitCount = 0;
|
|
144774
|
+
let commitLog = "";
|
|
144775
|
+
let commitLogUnavailable = false;
|
|
144776
|
+
try {
|
|
144777
|
+
commitCount = parseInt(
|
|
144778
|
+
$("git", ["rev-list", "--count", baseRange], { log: false }).trim() || "0",
|
|
144779
|
+
10
|
|
144780
|
+
);
|
|
144781
|
+
commitLog = $("git", ["log", "--oneline", `--max-count=${COMMIT_LOG_MAX}`, baseRange], {
|
|
144782
|
+
log: false
|
|
144783
|
+
});
|
|
144784
|
+
} catch (err) {
|
|
144785
|
+
commitLogUnavailable = true;
|
|
144786
|
+
log.debug(
|
|
144787
|
+
`\xBB unable to compute commit metadata for ${baseRange}: ${err instanceof Error ? err.message : String(err)}`
|
|
144788
|
+
);
|
|
144789
|
+
}
|
|
144790
|
+
const commitLogTruncated = commitCount > COMMIT_LOG_MAX;
|
|
144791
|
+
const hookWarningInstructions = checkoutResult.hookWarning ? ` HOOK WARNING: the post-checkout lifecycle hook reported a non-fatal failure (see hookWarning). decide whether to retry based on the guidance in that field before proceeding.` : "";
|
|
144792
|
+
const commitLogInstructions = commitLogUnavailable ? ` NOTE: commit metadata is partial (base ref unreachable, likely a shallow fetch). commitCount/commitLog may be 0/empty or incomplete; treat them as "unknown" rather than "no commits", and use \`git log\` directly if you need the full history.` : commitLogTruncated ? ` NOTE: commitLog was capped at ${COMMIT_LOG_MAX} entries out of ${commitCount} commits; use \`git log\` directly if you need the full history.` : "";
|
|
144793
|
+
return {
|
|
144794
|
+
success: true,
|
|
144795
|
+
number: prResponse.data.number,
|
|
144796
|
+
title: prResponse.data.title,
|
|
144797
|
+
body: prResponse.data.body,
|
|
144798
|
+
base: pr.baseRef,
|
|
144799
|
+
localBranch: `pr-${pull_number}`,
|
|
144800
|
+
remoteBranch: `refs/heads/${pr.headRef}`,
|
|
144801
|
+
isFork: pr.headRepoFullName !== pr.baseRepoFullName,
|
|
144802
|
+
maintainerCanModify: pr.maintainerCanModify,
|
|
144803
|
+
url: prResponse.data.html_url,
|
|
144804
|
+
headRepo: pr.headRepoFullName,
|
|
144805
|
+
diffPath,
|
|
144806
|
+
incrementalDiffPath,
|
|
144807
|
+
toc: formatResult.toc,
|
|
144808
|
+
commitCount,
|
|
144809
|
+
commitLog,
|
|
144810
|
+
commitLogTruncated,
|
|
144811
|
+
commitLogUnavailable,
|
|
144812
|
+
hookWarning: checkoutResult.hookWarning,
|
|
144813
|
+
instructions: `the diff file at diffPath contains a table of contents (TOC) at the top listing every changed file with its line range. use the TOC line ranges as your checklist and read specific files from the diff instead of reading the entire file. for example, if the TOC says "src/foo.ts \u2192 lines 5-42", read lines 5-42 from diffPath to see that file's changes. review files selectively based on relevance rather than reading everything sequentially. to inspect the PR's changed files, use diffPath \u2014 do NOT run \`git diff <base>..<head>\` to re-derive what's already in diffPath. the formatted diff with line numbers is authoritative. \`git log\` and \`git diff --stat\` are fine for commit-range overview, and \`git diff\` / \`git diff --cached\` are fine for inspecting *your own* uncommitted changes \u2014 but PR review content MUST come from diffPath. before your review is submitted, a one-time coverage pre-flight may error listing unread TOC regions. retry the same create_pull_request_review call to proceed \u2014 optionally after reading the listed ranges. the pre-flight will not block again this session. the local branch is 'localBranch' (pr-{number}), not the remote branch name. when pushing, omit branchName to use the current branch. do not use remoteBranch as a local branch name.` + incrementalInstructions + hookWarningInstructions + commitLogInstructions
|
|
144814
|
+
};
|
|
144815
|
+
};
|
|
144680
144816
|
return tool({
|
|
144681
144817
|
name: "checkout_pr",
|
|
144682
144818
|
description: "Checkout a pull request branch locally. This fetches the PR branch and sets up push configuration for fork PRs. Returns diffPath pointing to the formatted diff file. Example: `checkout_pr({ pull_number: 1234 })`. Transient fetch timeouts are common \u2014 retry the same call up to a few times before treating the failure as terminal. If the error mentions `.git/shallow.lock: File exists` or `.git/index.lock: File exists`, that's a stale lock from a prior timed-out fetch \u2014 remove it via the shell tool (`rm -f .git/shallow.lock .git/index.lock`) and retry.",
|
|
144683
144819
|
parameters: CheckoutPr,
|
|
144684
144820
|
execute: execute(async ({ pull_number }) => {
|
|
144685
|
-
const
|
|
144686
|
-
|
|
144687
|
-
|
|
144688
|
-
|
|
144689
|
-
}
|
|
144690
|
-
const
|
|
144691
|
-
if (
|
|
144692
|
-
|
|
144693
|
-
|
|
144694
|
-
|
|
144695
|
-
|
|
144696
|
-
|
|
144697
|
-
headRef: prResponse.data.head.ref,
|
|
144698
|
-
headRepoFullName: headRepo.full_name,
|
|
144699
|
-
baseRef: prResponse.data.base.ref,
|
|
144700
|
-
baseRepoFullName: prResponse.data.base.repo.full_name,
|
|
144701
|
-
maintainerCanModify: prResponse.data.maintainer_can_modify
|
|
144702
|
-
};
|
|
144703
|
-
const checkoutResult = await checkoutPrBranch(pr, {
|
|
144704
|
-
octokit: ctx.octokit,
|
|
144705
|
-
owner: ctx.repo.owner,
|
|
144706
|
-
name: ctx.repo.name,
|
|
144707
|
-
gitToken: ctx.gitToken,
|
|
144708
|
-
toolState: ctx.toolState,
|
|
144709
|
-
shell: ctx.payload.shell,
|
|
144710
|
-
postCheckoutScript: ctx.postCheckoutScript,
|
|
144711
|
-
beforeSha: ctx.toolState.beforeSha
|
|
144712
|
-
});
|
|
144713
|
-
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
144714
|
-
if (!tempDir) {
|
|
144715
|
-
throw new Error(
|
|
144716
|
-
"PULLFROG_TEMP_DIR not set - checkout_pr must run in pullfrog action context"
|
|
144717
|
-
);
|
|
144718
|
-
}
|
|
144719
|
-
const headShort = ctx.toolState.checkoutSha.slice(0, 7);
|
|
144720
|
-
let incrementalDiffPath;
|
|
144721
|
-
if (ctx.toolState.beforeSha && ctx.toolState.checkoutSha) {
|
|
144722
|
-
const beforeShort = ctx.toolState.beforeSha.slice(0, 7);
|
|
144723
|
-
const incremental = computeIncrementalDiff({
|
|
144724
|
-
baseBranch: pr.baseRef,
|
|
144725
|
-
beforeSha: ctx.toolState.beforeSha,
|
|
144726
|
-
headSha: ctx.toolState.checkoutSha
|
|
144727
|
-
});
|
|
144728
|
-
if (incremental) {
|
|
144729
|
-
incrementalDiffPath = join3(
|
|
144730
|
-
tempDir,
|
|
144731
|
-
`pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
|
|
144732
|
-
);
|
|
144733
|
-
writeFileSync(incrementalDiffPath, incremental);
|
|
144734
|
-
log.info(
|
|
144735
|
-
`\xBB incremental diff computed (${incremental.length} bytes) \u2192 ${incrementalDiffPath}`
|
|
144821
|
+
const inFlight = inFlightCheckouts.get(pull_number);
|
|
144822
|
+
if (inFlight) {
|
|
144823
|
+
log.info(`\xBB checkout_pr({pull_number:${pull_number}}) already in flight \u2014 sharing result`);
|
|
144824
|
+
return inFlight;
|
|
144825
|
+
}
|
|
144826
|
+
const current = ctx.toolState.issueNumber;
|
|
144827
|
+
if (current !== void 0 && current !== pull_number) {
|
|
144828
|
+
const dirty = $("git", ["status", "--porcelain"], { log: false }).trim();
|
|
144829
|
+
if (dirty) {
|
|
144830
|
+
throw new Error(
|
|
144831
|
+
`cannot checkout PR #${pull_number} while the working tree has uncommitted changes. commit, push, or discard them before switching. dirty paths:
|
|
144832
|
+
${dirty}`
|
|
144736
144833
|
);
|
|
144737
144834
|
}
|
|
144738
144835
|
}
|
|
144739
|
-
const
|
|
144740
|
-
|
|
144741
|
-
log.debug(`formatted diff preview (first 100 lines):
|
|
144742
|
-
${diffPreview}`);
|
|
144743
|
-
const diffPath = join3(tempDir, `pr-${pull_number}-${headShort}.diff`);
|
|
144744
|
-
writeFileSync(diffPath, formatResult.content);
|
|
144745
|
-
log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
|
|
144746
|
-
ctx.toolState.diffCoverage = createDiffCoverageState({
|
|
144747
|
-
diffPath,
|
|
144748
|
-
totalLines: countLines({ content: formatResult.content }),
|
|
144749
|
-
toc: formatResult.toc,
|
|
144750
|
-
previous: ctx.toolState.diffCoverage
|
|
144751
|
-
});
|
|
144752
|
-
log.debug(
|
|
144753
|
-
`\xBB diff coverage initialized: diffPath=${diffPath}, totalLines=${ctx.toolState.diffCoverage.totalLines}, tocEntries=${ctx.toolState.diffCoverage.tocEntries.length}`
|
|
144754
|
-
);
|
|
144755
|
-
const cached4 = /* @__PURE__ */ new Map();
|
|
144756
|
-
for (const file2 of formatResult.files) {
|
|
144757
|
-
cached4.set(file2.filename, commentableLinesForFile(file2.patch));
|
|
144758
|
-
}
|
|
144759
|
-
ctx.toolState.commentableLinesByFile = cached4;
|
|
144760
|
-
ctx.toolState.commentableLinesPullNumber = pull_number;
|
|
144761
|
-
ctx.toolState.commentableLinesCheckoutSha = ctx.toolState.checkoutSha;
|
|
144762
|
-
const incrementalInstructions = incrementalDiffPath ? ` IMPORTANT: incrementalDiffPath contains ONLY the changes since the last reviewed version (computed via range-diff). you MUST read incrementalDiffPath FIRST to understand what changed, then use diffPath for full PR context. do NOT skip the incremental diff.` : "";
|
|
144763
|
-
const COMMIT_LOG_MAX = 200;
|
|
144764
|
-
const baseRange = `origin/${pr.baseRef}..HEAD`;
|
|
144765
|
-
let commitCount = 0;
|
|
144766
|
-
let commitLog = "";
|
|
144767
|
-
let commitLogUnavailable = false;
|
|
144836
|
+
const promise2 = runCheckout(pull_number);
|
|
144837
|
+
inFlightCheckouts.set(pull_number, promise2);
|
|
144768
144838
|
try {
|
|
144769
|
-
|
|
144770
|
-
|
|
144771
|
-
|
|
144772
|
-
);
|
|
144773
|
-
commitLog = $("git", ["log", "--oneline", `--max-count=${COMMIT_LOG_MAX}`, baseRange], {
|
|
144774
|
-
log: false
|
|
144775
|
-
});
|
|
144776
|
-
} catch (err) {
|
|
144777
|
-
commitLogUnavailable = true;
|
|
144778
|
-
log.debug(
|
|
144779
|
-
`\xBB unable to compute commit metadata for ${baseRange}: ${err instanceof Error ? err.message : String(err)}`
|
|
144780
|
-
);
|
|
144839
|
+
return await promise2;
|
|
144840
|
+
} finally {
|
|
144841
|
+
inFlightCheckouts.delete(pull_number);
|
|
144781
144842
|
}
|
|
144782
|
-
const commitLogTruncated = commitCount > COMMIT_LOG_MAX;
|
|
144783
|
-
const hookWarningInstructions = checkoutResult.hookWarning ? ` HOOK WARNING: the post-checkout lifecycle hook reported a non-fatal failure (see hookWarning). decide whether to retry based on the guidance in that field before proceeding.` : "";
|
|
144784
|
-
const commitLogInstructions = commitLogUnavailable ? ` NOTE: commit metadata is partial (base ref unreachable, likely a shallow fetch). commitCount/commitLog may be 0/empty or incomplete; treat them as "unknown" rather than "no commits", and use \`git log\` directly if you need the full history.` : commitLogTruncated ? ` NOTE: commitLog was capped at ${COMMIT_LOG_MAX} entries out of ${commitCount} commits; use \`git log\` directly if you need the full history.` : "";
|
|
144785
|
-
return {
|
|
144786
|
-
success: true,
|
|
144787
|
-
number: prResponse.data.number,
|
|
144788
|
-
title: prResponse.data.title,
|
|
144789
|
-
body: prResponse.data.body,
|
|
144790
|
-
base: pr.baseRef,
|
|
144791
|
-
localBranch: `pr-${pull_number}`,
|
|
144792
|
-
remoteBranch: `refs/heads/${pr.headRef}`,
|
|
144793
|
-
isFork: pr.headRepoFullName !== pr.baseRepoFullName,
|
|
144794
|
-
maintainerCanModify: pr.maintainerCanModify,
|
|
144795
|
-
url: prResponse.data.html_url,
|
|
144796
|
-
headRepo: pr.headRepoFullName,
|
|
144797
|
-
diffPath,
|
|
144798
|
-
incrementalDiffPath,
|
|
144799
|
-
toc: formatResult.toc,
|
|
144800
|
-
commitCount,
|
|
144801
|
-
commitLog,
|
|
144802
|
-
commitLogTruncated,
|
|
144803
|
-
commitLogUnavailable,
|
|
144804
|
-
hookWarning: checkoutResult.hookWarning,
|
|
144805
|
-
instructions: `the diff file at diffPath contains a table of contents (TOC) at the top listing every changed file with its line range. use the TOC line ranges as your checklist and read specific files from the diff instead of reading the entire file. for example, if the TOC says "src/foo.ts \u2192 lines 5-42", read lines 5-42 from diffPath to see that file's changes. review files selectively based on relevance rather than reading everything sequentially. to inspect the PR's changed files, use diffPath \u2014 do NOT run \`git diff <base>..<head>\` to re-derive what's already in diffPath. the formatted diff with line numbers is authoritative. \`git log\` and \`git diff --stat\` are fine for commit-range overview, and \`git diff\` / \`git diff --cached\` are fine for inspecting *your own* uncommitted changes \u2014 but PR review content MUST come from diffPath. before your review is submitted, a one-time coverage pre-flight may error listing unread TOC regions. retry the same create_pull_request_review call to proceed \u2014 optionally after reading the listed ranges. the pre-flight will not block again this session. the local branch is 'localBranch' (pr-{number}), not the remote branch name. when pushing, omit branchName to use the current branch. do not use remoteBranch as a local branch name.` + incrementalInstructions + hookWarningInstructions + commitLogInstructions
|
|
144806
|
-
};
|
|
144807
144843
|
})
|
|
144808
144844
|
});
|
|
144809
144845
|
}
|
|
@@ -146199,6 +146235,15 @@ function getTempDir() {
|
|
|
146199
146235
|
}
|
|
146200
146236
|
return tempDir;
|
|
146201
146237
|
}
|
|
146238
|
+
var MAX_OUTPUT_CHARS = 5e3;
|
|
146239
|
+
function capOutput(output) {
|
|
146240
|
+
if (output.length <= MAX_OUTPUT_CHARS) return output;
|
|
146241
|
+
const fullPath = join7(getTempDir(), `shell-${randomUUID2().slice(0, 8)}.log`);
|
|
146242
|
+
writeFileSync5(fullPath, output);
|
|
146243
|
+
const elided = output.length - MAX_OUTPUT_CHARS;
|
|
146244
|
+
return `... [${elided} chars truncated; full output saved to ${fullPath}] ...
|
|
146245
|
+
${output.slice(-MAX_OUTPUT_CHARS)}`;
|
|
146246
|
+
}
|
|
146202
146247
|
function isGitCommand(command) {
|
|
146203
146248
|
const trimmed = command.trim();
|
|
146204
146249
|
if (trimmed === "git" || trimmed.startsWith("git ")) return true;
|
|
@@ -146217,6 +146262,8 @@ Use this tool to:
|
|
|
146217
146262
|
- Execute build tools (npm, pnpm, cargo, make, etc.)
|
|
146218
146263
|
- Run tests and linters
|
|
146219
146264
|
|
|
146265
|
+
Output is capped at ${MAX_OUTPUT_CHARS} chars: if exceeded, only the tail is returned and the full body is saved to a tempfile (path included in the response). Re-read the tempfile with cat/tail/grep when you need more.
|
|
146266
|
+
|
|
146220
146267
|
Do NOT use this tool for git commands \u2014 use the dedicated git tools instead.`,
|
|
146221
146268
|
parameters: ShellParams,
|
|
146222
146269
|
execute: execute(async (params) => {
|
|
@@ -146307,12 +146354,13 @@ ${stderr}` : stderr : stdout;
|
|
|
146307
146354
|
output = output ? `${output}
|
|
146308
146355
|
[timed out after ${timeout}ms]` : `[timed out after ${timeout}ms]`;
|
|
146309
146356
|
const finalExitCode = exitCode ?? (timedOut ? 124 : -1);
|
|
146357
|
+
const trimmed = output.trim();
|
|
146310
146358
|
if (finalExitCode !== 0) {
|
|
146311
146359
|
log.info(`shell command failed with exit code ${finalExitCode}: ${params.command}`);
|
|
146312
|
-
if (
|
|
146360
|
+
if (trimmed) log.info(`output: ${trimmed}`);
|
|
146313
146361
|
}
|
|
146314
146362
|
return {
|
|
146315
|
-
output:
|
|
146363
|
+
output: capOutput(trimmed),
|
|
146316
146364
|
exit_code: finalExitCode,
|
|
146317
146365
|
timed_out: timedOut
|
|
146318
146366
|
};
|
|
@@ -147185,12 +147233,38 @@ var PROVIDER_ERROR_PATTERNS = [
|
|
|
147185
147233
|
// around `limit` rejects keys like `time_limit` or `field_limit`.
|
|
147186
147234
|
{ regex: /["']?\blimit\b["']?\s*:\s*0\b/, label: "zero quota" }
|
|
147187
147235
|
];
|
|
147188
|
-
|
|
147236
|
+
var EXCERPT_MAX_BYTES = 600;
|
|
147237
|
+
var LINES_BEFORE = 1;
|
|
147238
|
+
var LINES_AFTER = 2;
|
|
147239
|
+
function findProviderErrorMatch(text) {
|
|
147189
147240
|
for (const entry of PROVIDER_ERROR_PATTERNS) {
|
|
147190
|
-
|
|
147241
|
+
const m = entry.regex.exec(text);
|
|
147242
|
+
if (!m) continue;
|
|
147243
|
+
return { label: entry.label, excerpt: extractExcerpt(text, m.index) };
|
|
147191
147244
|
}
|
|
147192
147245
|
return null;
|
|
147193
147246
|
}
|
|
147247
|
+
function extractExcerpt(text, matchIndex) {
|
|
147248
|
+
const lineStart = text.lastIndexOf("\n", matchIndex - 1) + 1;
|
|
147249
|
+
const lineEndRaw = text.indexOf("\n", matchIndex);
|
|
147250
|
+
const lineEnd = lineEndRaw === -1 ? text.length : lineEndRaw;
|
|
147251
|
+
let start = lineStart;
|
|
147252
|
+
for (let i = 0; i < LINES_BEFORE && start > 0; i++) {
|
|
147253
|
+
const prev = text.lastIndexOf("\n", start - 2);
|
|
147254
|
+
start = prev < 0 ? 0 : prev + 1;
|
|
147255
|
+
}
|
|
147256
|
+
let end = lineEnd;
|
|
147257
|
+
for (let i = 0; i < LINES_AFTER && end < text.length; i++) {
|
|
147258
|
+
const next2 = text.indexOf("\n", end + 1);
|
|
147259
|
+
end = next2 < 0 ? text.length : next2;
|
|
147260
|
+
}
|
|
147261
|
+
let excerpt = text.slice(start, end);
|
|
147262
|
+
if (excerpt.length > EXCERPT_MAX_BYTES) {
|
|
147263
|
+
excerpt = text.slice(lineStart, lineEnd);
|
|
147264
|
+
if (excerpt.length > EXCERPT_MAX_BYTES) excerpt = excerpt.slice(0, EXCERPT_MAX_BYTES);
|
|
147265
|
+
}
|
|
147266
|
+
return excerpt.trim();
|
|
147267
|
+
}
|
|
147194
147268
|
var ROUTER_KEYLIMIT_EXHAUSTED_PATTERN = /requires more credits.*?fewer max_tokens|requested up to \d+ tokens.*?can only afford/is;
|
|
147195
147269
|
function isRouterKeylimitExhaustedError(text) {
|
|
147196
147270
|
return ROUTER_KEYLIMIT_EXHAUSTED_PATTERN.test(text);
|
|
@@ -147895,10 +147969,10 @@ async function runClaude(params) {
|
|
|
147895
147969
|
if (!trimmed) return;
|
|
147896
147970
|
recentStderr.push(trimmed);
|
|
147897
147971
|
if (recentStderr.length > MAX_STDERR_LINES) recentStderr.shift();
|
|
147898
|
-
const
|
|
147899
|
-
if (
|
|
147900
|
-
lastProviderError =
|
|
147901
|
-
log.info(`\xBB provider error detected (${
|
|
147972
|
+
const match3 = findProviderErrorMatch(trimmed);
|
|
147973
|
+
if (match3) {
|
|
147974
|
+
lastProviderError = match3.label;
|
|
147975
|
+
log.info(`\xBB provider error detected (${match3.label}): ${match3.excerpt}`);
|
|
147902
147976
|
} else {
|
|
147903
147977
|
log.debug(trimmed);
|
|
147904
147978
|
}
|
|
@@ -148116,6 +148190,68 @@ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "node:f
|
|
|
148116
148190
|
import { join as join11 } from "node:path";
|
|
148117
148191
|
import { performance as performance7 } from "node:perf_hooks";
|
|
148118
148192
|
|
|
148193
|
+
// utils/agentHangReport.ts
|
|
148194
|
+
var MAX_STDERR_BYTES = 3e3;
|
|
148195
|
+
function formatAgentHangBody(input) {
|
|
148196
|
+
if (!input.diagnostic) return null;
|
|
148197
|
+
const verb = input.isHang ? "stalled" : "failed";
|
|
148198
|
+
const cause = input.diagnostic.lastProviderError ? ` \u2014 likely cause: \`${input.diagnostic.lastProviderError}\`` : "";
|
|
148199
|
+
const headline = `**${input.diagnostic.label} ${verb}**${cause}`;
|
|
148200
|
+
const explanation = formatExplanation({
|
|
148201
|
+
isHang: input.isHang,
|
|
148202
|
+
errorMessage: input.errorMessage
|
|
148203
|
+
});
|
|
148204
|
+
const parts = [headline, "", `${explanation} ${formatEventsPart(input.diagnostic)}`];
|
|
148205
|
+
const tail = renderStderrTail(input.diagnostic.recentStderr);
|
|
148206
|
+
if (tail) {
|
|
148207
|
+
const fence = pickFence(tail);
|
|
148208
|
+
parts.push(
|
|
148209
|
+
"",
|
|
148210
|
+
"<details><summary>Recent agent stderr</summary>",
|
|
148211
|
+
"",
|
|
148212
|
+
fence,
|
|
148213
|
+
tail,
|
|
148214
|
+
fence,
|
|
148215
|
+
"",
|
|
148216
|
+
"</details>"
|
|
148217
|
+
);
|
|
148218
|
+
}
|
|
148219
|
+
return parts.join("\n");
|
|
148220
|
+
}
|
|
148221
|
+
function formatExplanation(input) {
|
|
148222
|
+
if (!input.isHang) return `The agent exited unexpectedly: ${input.errorMessage}`;
|
|
148223
|
+
const idleSec = parseIdleSec(input.errorMessage);
|
|
148224
|
+
if (idleSec === void 0) {
|
|
148225
|
+
return "The agent stopped emitting events and was killed by the activity-timeout watchdog.";
|
|
148226
|
+
}
|
|
148227
|
+
return `The agent stopped emitting events for ${idleSec}s and was killed by the activity-timeout watchdog.`;
|
|
148228
|
+
}
|
|
148229
|
+
function parseIdleSec(message) {
|
|
148230
|
+
const match3 = /no output for (\d+)s/.exec(message);
|
|
148231
|
+
return match3 ? Number(match3[1]) : void 0;
|
|
148232
|
+
}
|
|
148233
|
+
function formatEventsPart(diagnostic) {
|
|
148234
|
+
if (diagnostic.eventCount > 0) {
|
|
148235
|
+
return `${diagnostic.eventCount} events were processed before the failure.`;
|
|
148236
|
+
}
|
|
148237
|
+
if (diagnostic.lastProviderError) return "No events were emitted before the failure.";
|
|
148238
|
+
return "No events were emitted \u2014 check whether the model provider is reachable.";
|
|
148239
|
+
}
|
|
148240
|
+
function renderStderrTail(lines) {
|
|
148241
|
+
if (lines.length === 0) return "";
|
|
148242
|
+
const joined = lines.join("\n");
|
|
148243
|
+
if (joined.length <= MAX_STDERR_BYTES) return joined;
|
|
148244
|
+
return `... (older lines truncated)
|
|
148245
|
+
${joined.slice(-MAX_STDERR_BYTES)}`;
|
|
148246
|
+
}
|
|
148247
|
+
function pickFence(content) {
|
|
148248
|
+
let max = 0;
|
|
148249
|
+
for (const match3 of content.matchAll(/`+/g)) {
|
|
148250
|
+
if (match3[0].length > max) max = match3[0].length;
|
|
148251
|
+
}
|
|
148252
|
+
return "`".repeat(Math.max(3, max + 1));
|
|
148253
|
+
}
|
|
148254
|
+
|
|
148119
148255
|
// agents/opencodePlugin.ts
|
|
148120
148256
|
var PULLFROG_BUS_EVENT_TYPE = "pullfrog_bus_event";
|
|
148121
148257
|
var PULLFROG_OPENCODE_PLUGIN_FILENAME = "pullfrog-events.ts";
|
|
@@ -148508,8 +148644,7 @@ async function runOpenCode(params) {
|
|
|
148508
148644
|
log.debug(withLabel(label, ` output: ${event.part.state.output}`));
|
|
148509
148645
|
}
|
|
148510
148646
|
if (event.part?.state?.status === "error") {
|
|
148511
|
-
|
|
148512
|
-
log.info(withLabel(label, `\xBB tool call failed: ${errorMsg}`));
|
|
148647
|
+
log.info(withLabel(label, `\xBB tool call failed: ${event.part.state.error}`));
|
|
148513
148648
|
}
|
|
148514
148649
|
if (toolName.includes("report_progress") && params.todoTracker) {
|
|
148515
148650
|
log.debug("\xBB report_progress detected, disabling todo tracking");
|
|
@@ -148521,19 +148656,20 @@ async function runOpenCode(params) {
|
|
|
148521
148656
|
},
|
|
148522
148657
|
tool_result: (event) => {
|
|
148523
148658
|
const toolId = event.part?.callID || event.tool_id;
|
|
148524
|
-
const
|
|
148525
|
-
const
|
|
148659
|
+
const state = event.part?.state;
|
|
148660
|
+
const status = state?.status ?? event.status ?? "unknown";
|
|
148661
|
+
const payload = state?.status === "completed" ? state.output : state?.status === "error" ? state.error : event.output;
|
|
148526
148662
|
const label = eventLabel(event);
|
|
148527
148663
|
timerFor(label).markToolResult();
|
|
148528
148664
|
if (taskDispatchByCallID.size > 0 || pendingTaskDispatches.length > 0) {
|
|
148529
148665
|
if (toolId && taskDispatchByCallID.has(toolId)) {
|
|
148530
148666
|
const dispatch = taskDispatchByCallID.get(toolId);
|
|
148531
|
-
if (dispatch) emitSubagentFinished(dispatch, status,
|
|
148667
|
+
if (dispatch) emitSubagentFinished(dispatch, status, payload, "exact");
|
|
148532
148668
|
} else {
|
|
148533
148669
|
const callIDIsKnownNonTask = toolId ? knownNonTaskCallIDs.has(toolId) : false;
|
|
148534
148670
|
if (!callIDIsKnownNonTask && pendingTaskDispatches.length > 0) {
|
|
148535
148671
|
const dispatch = pendingTaskDispatches[0];
|
|
148536
|
-
emitSubagentFinished(dispatch, status,
|
|
148672
|
+
emitSubagentFinished(dispatch, status, payload, "fifo");
|
|
148537
148673
|
}
|
|
148538
148674
|
}
|
|
148539
148675
|
}
|
|
@@ -148549,13 +148685,8 @@ async function runOpenCode(params) {
|
|
|
148549
148685
|
`\xBB ${params.label} tool_result${stepContext}: id=${toolId}, status=${status}, duration=${Math.round(toolDuration)}ms`
|
|
148550
148686
|
)
|
|
148551
148687
|
);
|
|
148552
|
-
if (
|
|
148553
|
-
log.debug(
|
|
148554
|
-
withLabel(
|
|
148555
|
-
label,
|
|
148556
|
-
` output: ${typeof output2 === "string" ? output2 : JSON.stringify(output2)}`
|
|
148557
|
-
)
|
|
148558
|
-
);
|
|
148688
|
+
if (payload) {
|
|
148689
|
+
log.debug(withLabel(label, ` output: ${payload}`));
|
|
148559
148690
|
}
|
|
148560
148691
|
if (toolDuration > 5e3) {
|
|
148561
148692
|
log.info(
|
|
@@ -148568,11 +148699,9 @@ async function runOpenCode(params) {
|
|
|
148568
148699
|
}
|
|
148569
148700
|
}
|
|
148570
148701
|
if (status === "error") {
|
|
148571
|
-
|
|
148572
|
-
|
|
148573
|
-
|
|
148574
|
-
const outputStr = typeof output2 === "string" ? output2 : JSON.stringify(output2);
|
|
148575
|
-
log.debug(withLabel(label, `tool output: ${outputStr}`));
|
|
148702
|
+
log.info(withLabel(label, `\xBB tool call failed: ${payload ?? "(no error message)"}`));
|
|
148703
|
+
} else if (payload) {
|
|
148704
|
+
log.debug(withLabel(label, `tool output: ${payload}`));
|
|
148576
148705
|
}
|
|
148577
148706
|
},
|
|
148578
148707
|
error: (event) => {
|
|
@@ -148649,6 +148778,13 @@ async function runOpenCode(params) {
|
|
|
148649
148778
|
const recentStderr = [];
|
|
148650
148779
|
let lastProviderError = null;
|
|
148651
148780
|
let agentErrorEvent = null;
|
|
148781
|
+
const diagnostic = {
|
|
148782
|
+
label: params.label,
|
|
148783
|
+
recentStderr,
|
|
148784
|
+
lastProviderError: void 0,
|
|
148785
|
+
eventCount: 0
|
|
148786
|
+
};
|
|
148787
|
+
params.toolState.agentDiagnostic = diagnostic;
|
|
148652
148788
|
const output = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
|
|
148653
148789
|
let stdoutBuffer = "";
|
|
148654
148790
|
try {
|
|
@@ -148697,6 +148833,7 @@ async function runOpenCode(params) {
|
|
|
148697
148833
|
continue;
|
|
148698
148834
|
}
|
|
148699
148835
|
eventCount++;
|
|
148836
|
+
diagnostic.eventCount = eventCount;
|
|
148700
148837
|
log.debug(JSON.stringify(event, null, 2));
|
|
148701
148838
|
const timeSinceLastActivity = getIdleMs();
|
|
148702
148839
|
if (timeSinceLastActivity > 1e4) {
|
|
@@ -148728,10 +148865,11 @@ async function runOpenCode(params) {
|
|
|
148728
148865
|
if (!trimmed) return;
|
|
148729
148866
|
recentStderr.push(trimmed);
|
|
148730
148867
|
if (recentStderr.length > MAX_STDERR_LINES) recentStderr.shift();
|
|
148731
|
-
const
|
|
148732
|
-
if (
|
|
148733
|
-
lastProviderError =
|
|
148734
|
-
|
|
148868
|
+
const match3 = findProviderErrorMatch(trimmed);
|
|
148869
|
+
if (match3) {
|
|
148870
|
+
lastProviderError = match3.label;
|
|
148871
|
+
diagnostic.lastProviderError = match3.label;
|
|
148872
|
+
log.info(`\xBB provider error detected (${match3.label}): ${match3.excerpt}`);
|
|
148735
148873
|
} else {
|
|
148736
148874
|
log.debug(trimmed);
|
|
148737
148875
|
}
|
|
@@ -148821,10 +148959,11 @@ ${stderrContext}`);
|
|
|
148821
148959
|
`\xBB recent stderr (last ${Math.min(recentStderr.length, 10)} lines):
|
|
148822
148960
|
${stderrContext}`
|
|
148823
148961
|
);
|
|
148962
|
+
const body = formatAgentHangBody({ diagnostic, isHang: isActivityTimeout, errorMessage });
|
|
148824
148963
|
return {
|
|
148825
148964
|
success: false,
|
|
148826
148965
|
output: finalOutput || output.toString(),
|
|
148827
|
-
error: `${errorMessage} [${diagnosis}]`,
|
|
148966
|
+
error: body ?? `${errorMessage} [${diagnosis}]`,
|
|
148828
148967
|
usage: buildUsage()
|
|
148829
148968
|
};
|
|
148830
148969
|
}
|
|
@@ -148876,6 +149015,7 @@ var opencode = agent({
|
|
|
148876
149015
|
cliPath,
|
|
148877
149016
|
cwd: repoDir,
|
|
148878
149017
|
env: env2,
|
|
149018
|
+
toolState: ctx.toolState,
|
|
148879
149019
|
todoTracker: ctx.todoTracker,
|
|
148880
149020
|
onActivityTimeout: ctx.onActivityTimeout,
|
|
148881
149021
|
onToolUse: ctx.onToolUse
|
|
@@ -155047,24 +155187,29 @@ ${instructions.user}` : null,
|
|
|
155047
155187
|
killTrackedChildren();
|
|
155048
155188
|
log.error(errorMessage);
|
|
155049
155189
|
const billingError = isRouterKeylimitExhaustedError(errorMessage) ? new BillingError(errorMessage, { code: "router_keylimit_exhausted" }) : null;
|
|
155050
|
-
const
|
|
155190
|
+
const isHang = errorMessage.startsWith("activity timeout") || errorMessage.startsWith("agent still pending");
|
|
155191
|
+
const hangBody = isHang ? formatAgentHangBody({ diagnostic: toolState.agentDiagnostic, isHang: true, errorMessage }) : null;
|
|
155192
|
+
const apiKeySource = hangBody ?? errorMessage;
|
|
155193
|
+
const apiKeyErrorSummary = !billingError && isApiKeyAuthError(apiKeySource) ? formatApiKeyErrorSummary({
|
|
155051
155194
|
owner: runContext.repo.owner,
|
|
155052
155195
|
name: runContext.repo.name,
|
|
155053
|
-
raw:
|
|
155196
|
+
raw: apiKeySource
|
|
155054
155197
|
}) : null;
|
|
155055
155198
|
try {
|
|
155056
|
-
const errorSummary = billingError ? formatBillingErrorSummary(billingError, runContext.repo.owner) : apiKeyErrorSummary ?? `### \u274C Pullfrog failed
|
|
155199
|
+
const errorSummary = billingError ? formatBillingErrorSummary(billingError, runContext.repo.owner) : apiKeyErrorSummary ?? (hangBody ? `### \u274C Pullfrog failed
|
|
155200
|
+
|
|
155201
|
+
${hangBody}` : `### \u274C Pullfrog failed
|
|
155057
155202
|
|
|
155058
155203
|
\`\`\`
|
|
155059
155204
|
${errorMessage}
|
|
155060
|
-
|
|
155205
|
+
\`\`\``);
|
|
155061
155206
|
const usageSummary = formatUsageSummary(toolState.usageEntries);
|
|
155062
155207
|
const parts = [errorSummary, toolState.lastProgressBody, usageSummary].filter(Boolean);
|
|
155063
155208
|
await writeSummary(parts.join("\n\n"));
|
|
155064
155209
|
} catch {
|
|
155065
155210
|
}
|
|
155066
155211
|
try {
|
|
155067
|
-
const commentBody = billingError ? formatBillingErrorSummary(billingError, runContext.repo.owner) : apiKeyErrorSummary ?? errorMessage;
|
|
155212
|
+
const commentBody = billingError ? formatBillingErrorSummary(billingError, runContext.repo.owner) : apiKeyErrorSummary ?? hangBody ?? errorMessage;
|
|
155068
155213
|
await reportErrorToComment({ toolState, error: commentBody });
|
|
155069
155214
|
} catch {
|
|
155070
155215
|
}
|
|
@@ -156923,7 +157068,7 @@ async function run2() {
|
|
|
156923
157068
|
}
|
|
156924
157069
|
|
|
156925
157070
|
// cli.ts
|
|
156926
|
-
var VERSION10 = "0.1.
|
|
157071
|
+
var VERSION10 = "0.1.8";
|
|
156927
157072
|
var bin = basename2(process.argv[1] || "");
|
|
156928
157073
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
156929
157074
|
var rawArgs = process.argv.slice(2);
|