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/index.js
CHANGED
|
@@ -142414,7 +142414,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
142414
142414
|
// package.json
|
|
142415
142415
|
var package_default = {
|
|
142416
142416
|
name: "pullfrog",
|
|
142417
|
-
version: "0.1.
|
|
142417
|
+
version: "0.1.8",
|
|
142418
142418
|
type: "module",
|
|
142419
142419
|
bin: {
|
|
142420
142420
|
pullfrog: "dist/cli.mjs",
|
|
@@ -142882,6 +142882,51 @@ function readNumber(params) {
|
|
|
142882
142882
|
import { execSync } from "node:child_process";
|
|
142883
142883
|
import { createHash } from "node:crypto";
|
|
142884
142884
|
import { readFileSync as readFileSync2, realpathSync, unlinkSync } from "node:fs";
|
|
142885
|
+
|
|
142886
|
+
// utils/shell.ts
|
|
142887
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
142888
|
+
function $(cmd, args2, options) {
|
|
142889
|
+
const encoding = options?.encoding ?? "utf-8";
|
|
142890
|
+
const env2 = resolveEnv(options?.env);
|
|
142891
|
+
const result = spawnSync2(cmd, args2, {
|
|
142892
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
142893
|
+
encoding,
|
|
142894
|
+
cwd: options?.cwd,
|
|
142895
|
+
env: env2
|
|
142896
|
+
});
|
|
142897
|
+
const stdout = result.stdout ?? "";
|
|
142898
|
+
const stderr = result.stderr ?? "";
|
|
142899
|
+
if (options?.log !== false) {
|
|
142900
|
+
const canWriteToStdout = process.stdout.isTTY === true;
|
|
142901
|
+
if (stdout) {
|
|
142902
|
+
if (canWriteToStdout) {
|
|
142903
|
+
process.stdout.write(stdout);
|
|
142904
|
+
} else {
|
|
142905
|
+
process.stderr.write(stdout);
|
|
142906
|
+
}
|
|
142907
|
+
}
|
|
142908
|
+
if (stderr) {
|
|
142909
|
+
process.stderr.write(stderr);
|
|
142910
|
+
}
|
|
142911
|
+
}
|
|
142912
|
+
if (result.status !== 0) {
|
|
142913
|
+
const errorResult = {
|
|
142914
|
+
status: result.status ?? -1,
|
|
142915
|
+
stdout,
|
|
142916
|
+
stderr
|
|
142917
|
+
};
|
|
142918
|
+
if (options?.onError) {
|
|
142919
|
+
options.onError(errorResult);
|
|
142920
|
+
return stdout.trim();
|
|
142921
|
+
}
|
|
142922
|
+
throw new Error(
|
|
142923
|
+
`Command failed with exit code ${errorResult.status}: ${stderr || "Unknown error"}`
|
|
142924
|
+
);
|
|
142925
|
+
}
|
|
142926
|
+
return stdout.trim();
|
|
142927
|
+
}
|
|
142928
|
+
|
|
142929
|
+
// utils/gitAuth.ts
|
|
142885
142930
|
var gitBinary;
|
|
142886
142931
|
function hashFile(path3) {
|
|
142887
142932
|
return createHash("sha256").update(readFileSync2(path3)).digest("hex");
|
|
@@ -142973,6 +143018,27 @@ ${stdout}` : stderr || stdout || "(no output)";
|
|
|
142973
143018
|
}
|
|
142974
143019
|
}
|
|
142975
143020
|
}
|
|
143021
|
+
var SHALLOW_UNREACHABLE_PATTERNS = [
|
|
143022
|
+
/Could not read [a-f0-9]{40,64}/,
|
|
143023
|
+
/remote did not send all necessary objects/
|
|
143024
|
+
];
|
|
143025
|
+
var DEEPEN_RETRY_DEPTH = 1e3;
|
|
143026
|
+
async function $gitFetchWithDeepen(args2, options, label) {
|
|
143027
|
+
try {
|
|
143028
|
+
return await $git("fetch", args2, options);
|
|
143029
|
+
} catch (err) {
|
|
143030
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
143031
|
+
const isShallowUnreachable = SHALLOW_UNREACHABLE_PATTERNS.some((p) => p.test(msg));
|
|
143032
|
+
if (!isShallowUnreachable) throw err;
|
|
143033
|
+
const isShallow = $("git", ["rev-parse", "--is-shallow-repository"], { log: false }).trim() === "true";
|
|
143034
|
+
if (!isShallow) throw err;
|
|
143035
|
+
log.info(
|
|
143036
|
+
`\xBB ${label ?? "git fetch"} hit shallow-unreachable error, retrying with --deepen=${DEEPEN_RETRY_DEPTH}`
|
|
143037
|
+
);
|
|
143038
|
+
const retryArgs = args2.filter((a) => !a.startsWith("--depth="));
|
|
143039
|
+
return await $git("fetch", [`--deepen=${DEEPEN_RETRY_DEPTH}`, ...retryArgs], options);
|
|
143040
|
+
}
|
|
143041
|
+
}
|
|
142976
143042
|
|
|
142977
143043
|
// lifecycle.ts
|
|
142978
143044
|
var LIFECYCLE_HOOK_TIMEOUT_MS = 6e5;
|
|
@@ -143014,49 +143080,6 @@ async function executeLifecycleHook(params) {
|
|
|
143014
143080
|
}
|
|
143015
143081
|
}
|
|
143016
143082
|
|
|
143017
|
-
// utils/shell.ts
|
|
143018
|
-
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
143019
|
-
function $(cmd, args2, options) {
|
|
143020
|
-
const encoding = options?.encoding ?? "utf-8";
|
|
143021
|
-
const env2 = resolveEnv(options?.env);
|
|
143022
|
-
const result = spawnSync2(cmd, args2, {
|
|
143023
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
143024
|
-
encoding,
|
|
143025
|
-
cwd: options?.cwd,
|
|
143026
|
-
env: env2
|
|
143027
|
-
});
|
|
143028
|
-
const stdout = result.stdout ?? "";
|
|
143029
|
-
const stderr = result.stderr ?? "";
|
|
143030
|
-
if (options?.log !== false) {
|
|
143031
|
-
const canWriteToStdout = process.stdout.isTTY === true;
|
|
143032
|
-
if (stdout) {
|
|
143033
|
-
if (canWriteToStdout) {
|
|
143034
|
-
process.stdout.write(stdout);
|
|
143035
|
-
} else {
|
|
143036
|
-
process.stderr.write(stdout);
|
|
143037
|
-
}
|
|
143038
|
-
}
|
|
143039
|
-
if (stderr) {
|
|
143040
|
-
process.stderr.write(stderr);
|
|
143041
|
-
}
|
|
143042
|
-
}
|
|
143043
|
-
if (result.status !== 0) {
|
|
143044
|
-
const errorResult = {
|
|
143045
|
-
status: result.status ?? -1,
|
|
143046
|
-
stdout,
|
|
143047
|
-
stderr
|
|
143048
|
-
};
|
|
143049
|
-
if (options?.onError) {
|
|
143050
|
-
options.onError(errorResult);
|
|
143051
|
-
return stdout.trim();
|
|
143052
|
-
}
|
|
143053
|
-
throw new Error(
|
|
143054
|
-
`Command failed with exit code ${errorResult.status}: ${stderr || "Unknown error"}`
|
|
143055
|
-
);
|
|
143056
|
-
}
|
|
143057
|
-
return stdout.trim();
|
|
143058
|
-
}
|
|
143059
|
-
|
|
143060
143083
|
// utils/rangeDiff.ts
|
|
143061
143084
|
function computeIncrementalDiff(params) {
|
|
143062
143085
|
try {
|
|
@@ -143451,11 +143474,6 @@ var GitFetch = type({
|
|
|
143451
143474
|
ref: type.string.describe("Ref to fetch: branch name, tag, or 'pull/N/head' for PRs"),
|
|
143452
143475
|
depth: type.number.describe("Fetch depth (for shallow clones)").optional()
|
|
143453
143476
|
});
|
|
143454
|
-
var SHALLOW_UNREACHABLE_PATTERNS = [
|
|
143455
|
-
/Could not read [a-f0-9]{40,64}/,
|
|
143456
|
-
/remote did not send all necessary objects/
|
|
143457
|
-
];
|
|
143458
|
-
var DEEPEN_RETRY_DEPTH = 1e3;
|
|
143459
143477
|
function GitFetchTool(ctx) {
|
|
143460
143478
|
return tool({
|
|
143461
143479
|
name: "git_fetch",
|
|
@@ -143467,20 +143485,7 @@ function GitFetchTool(ctx) {
|
|
|
143467
143485
|
if (params.depth !== void 0) {
|
|
143468
143486
|
fetchArgs.push(`--depth=${params.depth}`);
|
|
143469
143487
|
}
|
|
143470
|
-
|
|
143471
|
-
await $git("fetch", fetchArgs, { token: ctx.gitToken });
|
|
143472
|
-
} catch (err) {
|
|
143473
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
143474
|
-
const isShallowUnreachable = SHALLOW_UNREACHABLE_PATTERNS.some((p) => p.test(msg));
|
|
143475
|
-
const isShallow = isShallowUnreachable && $("git", ["rev-parse", "--is-shallow-repository"], { log: false }).trim() === "true";
|
|
143476
|
-
if (!isShallow) throw err;
|
|
143477
|
-
log.info(
|
|
143478
|
-
`\xBB git_fetch hit shallow-unreachable error, retrying with --deepen=${DEEPEN_RETRY_DEPTH}`
|
|
143479
|
-
);
|
|
143480
|
-
await $git("fetch", [`--deepen=${DEEPEN_RETRY_DEPTH}`, "--no-tags", "origin", params.ref], {
|
|
143481
|
-
token: ctx.gitToken
|
|
143482
|
-
});
|
|
143483
|
-
}
|
|
143488
|
+
await $gitFetchWithDeepen(fetchArgs, { token: ctx.gitToken }, "git_fetch");
|
|
143484
143489
|
return { success: true, ref: params.ref };
|
|
143485
143490
|
})
|
|
143486
143491
|
});
|
|
@@ -144204,10 +144209,10 @@ async function ensureBeforeShaReachable(params) {
|
|
|
144204
144209
|
sha: params.sha,
|
|
144205
144210
|
ref: tempBranch
|
|
144206
144211
|
}), true);
|
|
144207
|
-
await $
|
|
144208
|
-
"fetch",
|
|
144212
|
+
await $gitFetchWithDeepen(
|
|
144209
144213
|
["--no-tags", ...params.isShallow ? ["--depth=1"] : [], "origin", tempBranch],
|
|
144210
|
-
{ token: params.gitToken }
|
|
144214
|
+
{ token: params.gitToken },
|
|
144215
|
+
`before_sha temp branch ${tempBranch}`
|
|
144211
144216
|
);
|
|
144212
144217
|
log.debug(`\xBB fetched before_sha via temp branch ${tempBranch}`);
|
|
144213
144218
|
return true;
|
|
@@ -144283,16 +144288,22 @@ async function checkoutPrBranch(pr, params) {
|
|
|
144283
144288
|
toolState.checkoutSha = $("git", ["rev-parse", "HEAD"], { log: false }).trim();
|
|
144284
144289
|
const alreadyOnBranch = toolState.checkoutSha === pr.headSha;
|
|
144285
144290
|
log.debug(`\xBB fetching base branch (${pr.baseRef})...`);
|
|
144286
|
-
await $
|
|
144291
|
+
await $gitFetchWithDeepen(
|
|
144292
|
+
["--no-tags", "origin", pr.baseRef],
|
|
144293
|
+
{ token: gitToken },
|
|
144294
|
+
`base branch ${pr.baseRef}`
|
|
144295
|
+
);
|
|
144287
144296
|
if (!alreadyOnBranch) {
|
|
144288
144297
|
$("git", ["checkout", "-B", pr.baseRef, `origin/${pr.baseRef}`], { log: false });
|
|
144289
144298
|
log.debug(`\xBB fetching PR #${pr.number} (${localBranch})...`);
|
|
144290
144299
|
await retry(
|
|
144291
144300
|
async () => {
|
|
144292
144301
|
try {
|
|
144293
|
-
await $
|
|
144294
|
-
|
|
144295
|
-
|
|
144302
|
+
await $gitFetchWithDeepen(
|
|
144303
|
+
["--no-tags", "origin", `+pull/${pr.number}/head:${localBranch}`],
|
|
144304
|
+
{ token: gitToken },
|
|
144305
|
+
`PR #${pr.number}`
|
|
144306
|
+
);
|
|
144296
144307
|
} catch (e) {
|
|
144297
144308
|
const msg = e instanceof Error ? e.message : String(e);
|
|
144298
144309
|
if (PULL_REF_MISSING_PATTERN.test(msg)) {
|
|
@@ -144393,134 +144404,159 @@ async function checkoutPrBranch(pr, params) {
|
|
|
144393
144404
|
});
|
|
144394
144405
|
return { hookWarning: postCheckoutHook.warning };
|
|
144395
144406
|
}
|
|
144407
|
+
var inFlightCheckouts = /* @__PURE__ */ new Map();
|
|
144396
144408
|
function CheckoutPrTool(ctx) {
|
|
144409
|
+
const runCheckout = async (pull_number) => {
|
|
144410
|
+
const prResponse = await ctx.octokit.rest.pulls.get({
|
|
144411
|
+
owner: ctx.repo.owner,
|
|
144412
|
+
repo: ctx.repo.name,
|
|
144413
|
+
pull_number
|
|
144414
|
+
});
|
|
144415
|
+
const headRepo = prResponse.data.head.repo;
|
|
144416
|
+
if (!headRepo) {
|
|
144417
|
+
throw new Error(`PR #${pull_number} source repository was deleted`);
|
|
144418
|
+
}
|
|
144419
|
+
const pr = {
|
|
144420
|
+
number: pull_number,
|
|
144421
|
+
headSha: prResponse.data.head.sha,
|
|
144422
|
+
headRef: prResponse.data.head.ref,
|
|
144423
|
+
headRepoFullName: headRepo.full_name,
|
|
144424
|
+
baseRef: prResponse.data.base.ref,
|
|
144425
|
+
baseRepoFullName: prResponse.data.base.repo.full_name,
|
|
144426
|
+
maintainerCanModify: prResponse.data.maintainer_can_modify
|
|
144427
|
+
};
|
|
144428
|
+
const checkoutResult = await checkoutPrBranch(pr, {
|
|
144429
|
+
octokit: ctx.octokit,
|
|
144430
|
+
owner: ctx.repo.owner,
|
|
144431
|
+
name: ctx.repo.name,
|
|
144432
|
+
gitToken: ctx.gitToken,
|
|
144433
|
+
toolState: ctx.toolState,
|
|
144434
|
+
shell: ctx.payload.shell,
|
|
144435
|
+
postCheckoutScript: ctx.postCheckoutScript,
|
|
144436
|
+
beforeSha: ctx.toolState.beforeSha
|
|
144437
|
+
});
|
|
144438
|
+
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
144439
|
+
if (!tempDir) {
|
|
144440
|
+
throw new Error(
|
|
144441
|
+
"PULLFROG_TEMP_DIR not set - checkout_pr must run in pullfrog action context"
|
|
144442
|
+
);
|
|
144443
|
+
}
|
|
144444
|
+
const headShort = ctx.toolState.checkoutSha.slice(0, 7);
|
|
144445
|
+
let incrementalDiffPath;
|
|
144446
|
+
if (ctx.toolState.beforeSha && ctx.toolState.checkoutSha) {
|
|
144447
|
+
const beforeShort = ctx.toolState.beforeSha.slice(0, 7);
|
|
144448
|
+
const incremental = computeIncrementalDiff({
|
|
144449
|
+
baseBranch: pr.baseRef,
|
|
144450
|
+
beforeSha: ctx.toolState.beforeSha,
|
|
144451
|
+
headSha: ctx.toolState.checkoutSha
|
|
144452
|
+
});
|
|
144453
|
+
if (incremental) {
|
|
144454
|
+
incrementalDiffPath = join3(
|
|
144455
|
+
tempDir,
|
|
144456
|
+
`pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
|
|
144457
|
+
);
|
|
144458
|
+
writeFileSync(incrementalDiffPath, incremental);
|
|
144459
|
+
log.info(
|
|
144460
|
+
`\xBB incremental diff computed (${incremental.length} bytes) \u2192 ${incrementalDiffPath}`
|
|
144461
|
+
);
|
|
144462
|
+
}
|
|
144463
|
+
}
|
|
144464
|
+
const formatResult = await fetchAndFormatPrDiff(ctx, pull_number);
|
|
144465
|
+
const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
|
|
144466
|
+
log.debug(`formatted diff preview (first 100 lines):
|
|
144467
|
+
${diffPreview}`);
|
|
144468
|
+
const diffPath = join3(tempDir, `pr-${pull_number}-${headShort}.diff`);
|
|
144469
|
+
writeFileSync(diffPath, formatResult.content);
|
|
144470
|
+
log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
|
|
144471
|
+
ctx.toolState.diffCoverage = createDiffCoverageState({
|
|
144472
|
+
diffPath,
|
|
144473
|
+
totalLines: countLines({ content: formatResult.content }),
|
|
144474
|
+
toc: formatResult.toc,
|
|
144475
|
+
previous: ctx.toolState.diffCoverage
|
|
144476
|
+
});
|
|
144477
|
+
log.debug(
|
|
144478
|
+
`\xBB diff coverage initialized: diffPath=${diffPath}, totalLines=${ctx.toolState.diffCoverage.totalLines}, tocEntries=${ctx.toolState.diffCoverage.tocEntries.length}`
|
|
144479
|
+
);
|
|
144480
|
+
const cached4 = /* @__PURE__ */ new Map();
|
|
144481
|
+
for (const file2 of formatResult.files) {
|
|
144482
|
+
cached4.set(file2.filename, commentableLinesForFile(file2.patch));
|
|
144483
|
+
}
|
|
144484
|
+
ctx.toolState.commentableLinesByFile = cached4;
|
|
144485
|
+
ctx.toolState.commentableLinesPullNumber = pull_number;
|
|
144486
|
+
ctx.toolState.commentableLinesCheckoutSha = ctx.toolState.checkoutSha;
|
|
144487
|
+
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.` : "";
|
|
144488
|
+
const COMMIT_LOG_MAX = 200;
|
|
144489
|
+
const baseRange = `origin/${pr.baseRef}..HEAD`;
|
|
144490
|
+
let commitCount = 0;
|
|
144491
|
+
let commitLog = "";
|
|
144492
|
+
let commitLogUnavailable = false;
|
|
144493
|
+
try {
|
|
144494
|
+
commitCount = parseInt(
|
|
144495
|
+
$("git", ["rev-list", "--count", baseRange], { log: false }).trim() || "0",
|
|
144496
|
+
10
|
|
144497
|
+
);
|
|
144498
|
+
commitLog = $("git", ["log", "--oneline", `--max-count=${COMMIT_LOG_MAX}`, baseRange], {
|
|
144499
|
+
log: false
|
|
144500
|
+
});
|
|
144501
|
+
} catch (err) {
|
|
144502
|
+
commitLogUnavailable = true;
|
|
144503
|
+
log.debug(
|
|
144504
|
+
`\xBB unable to compute commit metadata for ${baseRange}: ${err instanceof Error ? err.message : String(err)}`
|
|
144505
|
+
);
|
|
144506
|
+
}
|
|
144507
|
+
const commitLogTruncated = commitCount > COMMIT_LOG_MAX;
|
|
144508
|
+
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.` : "";
|
|
144509
|
+
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.` : "";
|
|
144510
|
+
return {
|
|
144511
|
+
success: true,
|
|
144512
|
+
number: prResponse.data.number,
|
|
144513
|
+
title: prResponse.data.title,
|
|
144514
|
+
body: prResponse.data.body,
|
|
144515
|
+
base: pr.baseRef,
|
|
144516
|
+
localBranch: `pr-${pull_number}`,
|
|
144517
|
+
remoteBranch: `refs/heads/${pr.headRef}`,
|
|
144518
|
+
isFork: pr.headRepoFullName !== pr.baseRepoFullName,
|
|
144519
|
+
maintainerCanModify: pr.maintainerCanModify,
|
|
144520
|
+
url: prResponse.data.html_url,
|
|
144521
|
+
headRepo: pr.headRepoFullName,
|
|
144522
|
+
diffPath,
|
|
144523
|
+
incrementalDiffPath,
|
|
144524
|
+
toc: formatResult.toc,
|
|
144525
|
+
commitCount,
|
|
144526
|
+
commitLog,
|
|
144527
|
+
commitLogTruncated,
|
|
144528
|
+
commitLogUnavailable,
|
|
144529
|
+
hookWarning: checkoutResult.hookWarning,
|
|
144530
|
+
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
|
|
144531
|
+
};
|
|
144532
|
+
};
|
|
144397
144533
|
return tool({
|
|
144398
144534
|
name: "checkout_pr",
|
|
144399
144535
|
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.",
|
|
144400
144536
|
parameters: CheckoutPr,
|
|
144401
144537
|
execute: execute(async ({ pull_number }) => {
|
|
144402
|
-
const
|
|
144403
|
-
|
|
144404
|
-
|
|
144405
|
-
|
|
144406
|
-
}
|
|
144407
|
-
const
|
|
144408
|
-
if (
|
|
144409
|
-
|
|
144410
|
-
|
|
144411
|
-
|
|
144412
|
-
|
|
144413
|
-
|
|
144414
|
-
headRef: prResponse.data.head.ref,
|
|
144415
|
-
headRepoFullName: headRepo.full_name,
|
|
144416
|
-
baseRef: prResponse.data.base.ref,
|
|
144417
|
-
baseRepoFullName: prResponse.data.base.repo.full_name,
|
|
144418
|
-
maintainerCanModify: prResponse.data.maintainer_can_modify
|
|
144419
|
-
};
|
|
144420
|
-
const checkoutResult = await checkoutPrBranch(pr, {
|
|
144421
|
-
octokit: ctx.octokit,
|
|
144422
|
-
owner: ctx.repo.owner,
|
|
144423
|
-
name: ctx.repo.name,
|
|
144424
|
-
gitToken: ctx.gitToken,
|
|
144425
|
-
toolState: ctx.toolState,
|
|
144426
|
-
shell: ctx.payload.shell,
|
|
144427
|
-
postCheckoutScript: ctx.postCheckoutScript,
|
|
144428
|
-
beforeSha: ctx.toolState.beforeSha
|
|
144429
|
-
});
|
|
144430
|
-
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
144431
|
-
if (!tempDir) {
|
|
144432
|
-
throw new Error(
|
|
144433
|
-
"PULLFROG_TEMP_DIR not set - checkout_pr must run in pullfrog action context"
|
|
144434
|
-
);
|
|
144435
|
-
}
|
|
144436
|
-
const headShort = ctx.toolState.checkoutSha.slice(0, 7);
|
|
144437
|
-
let incrementalDiffPath;
|
|
144438
|
-
if (ctx.toolState.beforeSha && ctx.toolState.checkoutSha) {
|
|
144439
|
-
const beforeShort = ctx.toolState.beforeSha.slice(0, 7);
|
|
144440
|
-
const incremental = computeIncrementalDiff({
|
|
144441
|
-
baseBranch: pr.baseRef,
|
|
144442
|
-
beforeSha: ctx.toolState.beforeSha,
|
|
144443
|
-
headSha: ctx.toolState.checkoutSha
|
|
144444
|
-
});
|
|
144445
|
-
if (incremental) {
|
|
144446
|
-
incrementalDiffPath = join3(
|
|
144447
|
-
tempDir,
|
|
144448
|
-
`pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
|
|
144449
|
-
);
|
|
144450
|
-
writeFileSync(incrementalDiffPath, incremental);
|
|
144451
|
-
log.info(
|
|
144452
|
-
`\xBB incremental diff computed (${incremental.length} bytes) \u2192 ${incrementalDiffPath}`
|
|
144538
|
+
const inFlight = inFlightCheckouts.get(pull_number);
|
|
144539
|
+
if (inFlight) {
|
|
144540
|
+
log.info(`\xBB checkout_pr({pull_number:${pull_number}}) already in flight \u2014 sharing result`);
|
|
144541
|
+
return inFlight;
|
|
144542
|
+
}
|
|
144543
|
+
const current = ctx.toolState.issueNumber;
|
|
144544
|
+
if (current !== void 0 && current !== pull_number) {
|
|
144545
|
+
const dirty = $("git", ["status", "--porcelain"], { log: false }).trim();
|
|
144546
|
+
if (dirty) {
|
|
144547
|
+
throw new Error(
|
|
144548
|
+
`cannot checkout PR #${pull_number} while the working tree has uncommitted changes. commit, push, or discard them before switching. dirty paths:
|
|
144549
|
+
${dirty}`
|
|
144453
144550
|
);
|
|
144454
144551
|
}
|
|
144455
144552
|
}
|
|
144456
|
-
const
|
|
144457
|
-
|
|
144458
|
-
log.debug(`formatted diff preview (first 100 lines):
|
|
144459
|
-
${diffPreview}`);
|
|
144460
|
-
const diffPath = join3(tempDir, `pr-${pull_number}-${headShort}.diff`);
|
|
144461
|
-
writeFileSync(diffPath, formatResult.content);
|
|
144462
|
-
log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
|
|
144463
|
-
ctx.toolState.diffCoverage = createDiffCoverageState({
|
|
144464
|
-
diffPath,
|
|
144465
|
-
totalLines: countLines({ content: formatResult.content }),
|
|
144466
|
-
toc: formatResult.toc,
|
|
144467
|
-
previous: ctx.toolState.diffCoverage
|
|
144468
|
-
});
|
|
144469
|
-
log.debug(
|
|
144470
|
-
`\xBB diff coverage initialized: diffPath=${diffPath}, totalLines=${ctx.toolState.diffCoverage.totalLines}, tocEntries=${ctx.toolState.diffCoverage.tocEntries.length}`
|
|
144471
|
-
);
|
|
144472
|
-
const cached4 = /* @__PURE__ */ new Map();
|
|
144473
|
-
for (const file2 of formatResult.files) {
|
|
144474
|
-
cached4.set(file2.filename, commentableLinesForFile(file2.patch));
|
|
144475
|
-
}
|
|
144476
|
-
ctx.toolState.commentableLinesByFile = cached4;
|
|
144477
|
-
ctx.toolState.commentableLinesPullNumber = pull_number;
|
|
144478
|
-
ctx.toolState.commentableLinesCheckoutSha = ctx.toolState.checkoutSha;
|
|
144479
|
-
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.` : "";
|
|
144480
|
-
const COMMIT_LOG_MAX = 200;
|
|
144481
|
-
const baseRange = `origin/${pr.baseRef}..HEAD`;
|
|
144482
|
-
let commitCount = 0;
|
|
144483
|
-
let commitLog = "";
|
|
144484
|
-
let commitLogUnavailable = false;
|
|
144553
|
+
const promise2 = runCheckout(pull_number);
|
|
144554
|
+
inFlightCheckouts.set(pull_number, promise2);
|
|
144485
144555
|
try {
|
|
144486
|
-
|
|
144487
|
-
|
|
144488
|
-
|
|
144489
|
-
);
|
|
144490
|
-
commitLog = $("git", ["log", "--oneline", `--max-count=${COMMIT_LOG_MAX}`, baseRange], {
|
|
144491
|
-
log: false
|
|
144492
|
-
});
|
|
144493
|
-
} catch (err) {
|
|
144494
|
-
commitLogUnavailable = true;
|
|
144495
|
-
log.debug(
|
|
144496
|
-
`\xBB unable to compute commit metadata for ${baseRange}: ${err instanceof Error ? err.message : String(err)}`
|
|
144497
|
-
);
|
|
144556
|
+
return await promise2;
|
|
144557
|
+
} finally {
|
|
144558
|
+
inFlightCheckouts.delete(pull_number);
|
|
144498
144559
|
}
|
|
144499
|
-
const commitLogTruncated = commitCount > COMMIT_LOG_MAX;
|
|
144500
|
-
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.` : "";
|
|
144501
|
-
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.` : "";
|
|
144502
|
-
return {
|
|
144503
|
-
success: true,
|
|
144504
|
-
number: prResponse.data.number,
|
|
144505
|
-
title: prResponse.data.title,
|
|
144506
|
-
body: prResponse.data.body,
|
|
144507
|
-
base: pr.baseRef,
|
|
144508
|
-
localBranch: `pr-${pull_number}`,
|
|
144509
|
-
remoteBranch: `refs/heads/${pr.headRef}`,
|
|
144510
|
-
isFork: pr.headRepoFullName !== pr.baseRepoFullName,
|
|
144511
|
-
maintainerCanModify: pr.maintainerCanModify,
|
|
144512
|
-
url: prResponse.data.html_url,
|
|
144513
|
-
headRepo: pr.headRepoFullName,
|
|
144514
|
-
diffPath,
|
|
144515
|
-
incrementalDiffPath,
|
|
144516
|
-
toc: formatResult.toc,
|
|
144517
|
-
commitCount,
|
|
144518
|
-
commitLog,
|
|
144519
|
-
commitLogTruncated,
|
|
144520
|
-
commitLogUnavailable,
|
|
144521
|
-
hookWarning: checkoutResult.hookWarning,
|
|
144522
|
-
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
|
|
144523
|
-
};
|
|
144524
144560
|
})
|
|
144525
144561
|
});
|
|
144526
144562
|
}
|
|
@@ -145916,6 +145952,15 @@ function getTempDir() {
|
|
|
145916
145952
|
}
|
|
145917
145953
|
return tempDir;
|
|
145918
145954
|
}
|
|
145955
|
+
var MAX_OUTPUT_CHARS = 5e3;
|
|
145956
|
+
function capOutput(output) {
|
|
145957
|
+
if (output.length <= MAX_OUTPUT_CHARS) return output;
|
|
145958
|
+
const fullPath = join7(getTempDir(), `shell-${randomUUID2().slice(0, 8)}.log`);
|
|
145959
|
+
writeFileSync5(fullPath, output);
|
|
145960
|
+
const elided = output.length - MAX_OUTPUT_CHARS;
|
|
145961
|
+
return `... [${elided} chars truncated; full output saved to ${fullPath}] ...
|
|
145962
|
+
${output.slice(-MAX_OUTPUT_CHARS)}`;
|
|
145963
|
+
}
|
|
145919
145964
|
function isGitCommand(command) {
|
|
145920
145965
|
const trimmed = command.trim();
|
|
145921
145966
|
if (trimmed === "git" || trimmed.startsWith("git ")) return true;
|
|
@@ -145934,6 +145979,8 @@ Use this tool to:
|
|
|
145934
145979
|
- Execute build tools (npm, pnpm, cargo, make, etc.)
|
|
145935
145980
|
- Run tests and linters
|
|
145936
145981
|
|
|
145982
|
+
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.
|
|
145983
|
+
|
|
145937
145984
|
Do NOT use this tool for git commands \u2014 use the dedicated git tools instead.`,
|
|
145938
145985
|
parameters: ShellParams,
|
|
145939
145986
|
execute: execute(async (params) => {
|
|
@@ -146024,12 +146071,13 @@ ${stderr}` : stderr : stdout;
|
|
|
146024
146071
|
output = output ? `${output}
|
|
146025
146072
|
[timed out after ${timeout}ms]` : `[timed out after ${timeout}ms]`;
|
|
146026
146073
|
const finalExitCode = exitCode ?? (timedOut ? 124 : -1);
|
|
146074
|
+
const trimmed = output.trim();
|
|
146027
146075
|
if (finalExitCode !== 0) {
|
|
146028
146076
|
log.info(`shell command failed with exit code ${finalExitCode}: ${params.command}`);
|
|
146029
|
-
if (
|
|
146077
|
+
if (trimmed) log.info(`output: ${trimmed}`);
|
|
146030
146078
|
}
|
|
146031
146079
|
return {
|
|
146032
|
-
output:
|
|
146080
|
+
output: capOutput(trimmed),
|
|
146033
146081
|
exit_code: finalExitCode,
|
|
146034
146082
|
timed_out: timedOut
|
|
146035
146083
|
};
|
|
@@ -146902,12 +146950,38 @@ var PROVIDER_ERROR_PATTERNS = [
|
|
|
146902
146950
|
// around `limit` rejects keys like `time_limit` or `field_limit`.
|
|
146903
146951
|
{ regex: /["']?\blimit\b["']?\s*:\s*0\b/, label: "zero quota" }
|
|
146904
146952
|
];
|
|
146905
|
-
|
|
146953
|
+
var EXCERPT_MAX_BYTES = 600;
|
|
146954
|
+
var LINES_BEFORE = 1;
|
|
146955
|
+
var LINES_AFTER = 2;
|
|
146956
|
+
function findProviderErrorMatch(text) {
|
|
146906
146957
|
for (const entry of PROVIDER_ERROR_PATTERNS) {
|
|
146907
|
-
|
|
146958
|
+
const m = entry.regex.exec(text);
|
|
146959
|
+
if (!m) continue;
|
|
146960
|
+
return { label: entry.label, excerpt: extractExcerpt(text, m.index) };
|
|
146908
146961
|
}
|
|
146909
146962
|
return null;
|
|
146910
146963
|
}
|
|
146964
|
+
function extractExcerpt(text, matchIndex) {
|
|
146965
|
+
const lineStart = text.lastIndexOf("\n", matchIndex - 1) + 1;
|
|
146966
|
+
const lineEndRaw = text.indexOf("\n", matchIndex);
|
|
146967
|
+
const lineEnd = lineEndRaw === -1 ? text.length : lineEndRaw;
|
|
146968
|
+
let start = lineStart;
|
|
146969
|
+
for (let i = 0; i < LINES_BEFORE && start > 0; i++) {
|
|
146970
|
+
const prev = text.lastIndexOf("\n", start - 2);
|
|
146971
|
+
start = prev < 0 ? 0 : prev + 1;
|
|
146972
|
+
}
|
|
146973
|
+
let end = lineEnd;
|
|
146974
|
+
for (let i = 0; i < LINES_AFTER && end < text.length; i++) {
|
|
146975
|
+
const next2 = text.indexOf("\n", end + 1);
|
|
146976
|
+
end = next2 < 0 ? text.length : next2;
|
|
146977
|
+
}
|
|
146978
|
+
let excerpt = text.slice(start, end);
|
|
146979
|
+
if (excerpt.length > EXCERPT_MAX_BYTES) {
|
|
146980
|
+
excerpt = text.slice(lineStart, lineEnd);
|
|
146981
|
+
if (excerpt.length > EXCERPT_MAX_BYTES) excerpt = excerpt.slice(0, EXCERPT_MAX_BYTES);
|
|
146982
|
+
}
|
|
146983
|
+
return excerpt.trim();
|
|
146984
|
+
}
|
|
146911
146985
|
var ROUTER_KEYLIMIT_EXHAUSTED_PATTERN = /requires more credits.*?fewer max_tokens|requested up to \d+ tokens.*?can only afford/is;
|
|
146912
146986
|
function isRouterKeylimitExhaustedError(text) {
|
|
146913
146987
|
return ROUTER_KEYLIMIT_EXHAUSTED_PATTERN.test(text);
|
|
@@ -147612,10 +147686,10 @@ async function runClaude(params) {
|
|
|
147612
147686
|
if (!trimmed) return;
|
|
147613
147687
|
recentStderr.push(trimmed);
|
|
147614
147688
|
if (recentStderr.length > MAX_STDERR_LINES) recentStderr.shift();
|
|
147615
|
-
const
|
|
147616
|
-
if (
|
|
147617
|
-
lastProviderError =
|
|
147618
|
-
log.info(`\xBB provider error detected (${
|
|
147689
|
+
const match3 = findProviderErrorMatch(trimmed);
|
|
147690
|
+
if (match3) {
|
|
147691
|
+
lastProviderError = match3.label;
|
|
147692
|
+
log.info(`\xBB provider error detected (${match3.label}): ${match3.excerpt}`);
|
|
147619
147693
|
} else {
|
|
147620
147694
|
log.debug(trimmed);
|
|
147621
147695
|
}
|
|
@@ -147833,6 +147907,68 @@ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "node:f
|
|
|
147833
147907
|
import { join as join11 } from "node:path";
|
|
147834
147908
|
import { performance as performance7 } from "node:perf_hooks";
|
|
147835
147909
|
|
|
147910
|
+
// utils/agentHangReport.ts
|
|
147911
|
+
var MAX_STDERR_BYTES = 3e3;
|
|
147912
|
+
function formatAgentHangBody(input) {
|
|
147913
|
+
if (!input.diagnostic) return null;
|
|
147914
|
+
const verb = input.isHang ? "stalled" : "failed";
|
|
147915
|
+
const cause = input.diagnostic.lastProviderError ? ` \u2014 likely cause: \`${input.diagnostic.lastProviderError}\`` : "";
|
|
147916
|
+
const headline = `**${input.diagnostic.label} ${verb}**${cause}`;
|
|
147917
|
+
const explanation = formatExplanation({
|
|
147918
|
+
isHang: input.isHang,
|
|
147919
|
+
errorMessage: input.errorMessage
|
|
147920
|
+
});
|
|
147921
|
+
const parts = [headline, "", `${explanation} ${formatEventsPart(input.diagnostic)}`];
|
|
147922
|
+
const tail = renderStderrTail(input.diagnostic.recentStderr);
|
|
147923
|
+
if (tail) {
|
|
147924
|
+
const fence = pickFence(tail);
|
|
147925
|
+
parts.push(
|
|
147926
|
+
"",
|
|
147927
|
+
"<details><summary>Recent agent stderr</summary>",
|
|
147928
|
+
"",
|
|
147929
|
+
fence,
|
|
147930
|
+
tail,
|
|
147931
|
+
fence,
|
|
147932
|
+
"",
|
|
147933
|
+
"</details>"
|
|
147934
|
+
);
|
|
147935
|
+
}
|
|
147936
|
+
return parts.join("\n");
|
|
147937
|
+
}
|
|
147938
|
+
function formatExplanation(input) {
|
|
147939
|
+
if (!input.isHang) return `The agent exited unexpectedly: ${input.errorMessage}`;
|
|
147940
|
+
const idleSec = parseIdleSec(input.errorMessage);
|
|
147941
|
+
if (idleSec === void 0) {
|
|
147942
|
+
return "The agent stopped emitting events and was killed by the activity-timeout watchdog.";
|
|
147943
|
+
}
|
|
147944
|
+
return `The agent stopped emitting events for ${idleSec}s and was killed by the activity-timeout watchdog.`;
|
|
147945
|
+
}
|
|
147946
|
+
function parseIdleSec(message) {
|
|
147947
|
+
const match3 = /no output for (\d+)s/.exec(message);
|
|
147948
|
+
return match3 ? Number(match3[1]) : void 0;
|
|
147949
|
+
}
|
|
147950
|
+
function formatEventsPart(diagnostic) {
|
|
147951
|
+
if (diagnostic.eventCount > 0) {
|
|
147952
|
+
return `${diagnostic.eventCount} events were processed before the failure.`;
|
|
147953
|
+
}
|
|
147954
|
+
if (diagnostic.lastProviderError) return "No events were emitted before the failure.";
|
|
147955
|
+
return "No events were emitted \u2014 check whether the model provider is reachable.";
|
|
147956
|
+
}
|
|
147957
|
+
function renderStderrTail(lines) {
|
|
147958
|
+
if (lines.length === 0) return "";
|
|
147959
|
+
const joined = lines.join("\n");
|
|
147960
|
+
if (joined.length <= MAX_STDERR_BYTES) return joined;
|
|
147961
|
+
return `... (older lines truncated)
|
|
147962
|
+
${joined.slice(-MAX_STDERR_BYTES)}`;
|
|
147963
|
+
}
|
|
147964
|
+
function pickFence(content) {
|
|
147965
|
+
let max = 0;
|
|
147966
|
+
for (const match3 of content.matchAll(/`+/g)) {
|
|
147967
|
+
if (match3[0].length > max) max = match3[0].length;
|
|
147968
|
+
}
|
|
147969
|
+
return "`".repeat(Math.max(3, max + 1));
|
|
147970
|
+
}
|
|
147971
|
+
|
|
147836
147972
|
// agents/opencodePlugin.ts
|
|
147837
147973
|
var PULLFROG_BUS_EVENT_TYPE = "pullfrog_bus_event";
|
|
147838
147974
|
var PULLFROG_OPENCODE_PLUGIN_FILENAME = "pullfrog-events.ts";
|
|
@@ -148225,8 +148361,7 @@ async function runOpenCode(params) {
|
|
|
148225
148361
|
log.debug(withLabel(label, ` output: ${event.part.state.output}`));
|
|
148226
148362
|
}
|
|
148227
148363
|
if (event.part?.state?.status === "error") {
|
|
148228
|
-
|
|
148229
|
-
log.info(withLabel(label, `\xBB tool call failed: ${errorMsg}`));
|
|
148364
|
+
log.info(withLabel(label, `\xBB tool call failed: ${event.part.state.error}`));
|
|
148230
148365
|
}
|
|
148231
148366
|
if (toolName.includes("report_progress") && params.todoTracker) {
|
|
148232
148367
|
log.debug("\xBB report_progress detected, disabling todo tracking");
|
|
@@ -148238,19 +148373,20 @@ async function runOpenCode(params) {
|
|
|
148238
148373
|
},
|
|
148239
148374
|
tool_result: (event) => {
|
|
148240
148375
|
const toolId = event.part?.callID || event.tool_id;
|
|
148241
|
-
const
|
|
148242
|
-
const
|
|
148376
|
+
const state = event.part?.state;
|
|
148377
|
+
const status = state?.status ?? event.status ?? "unknown";
|
|
148378
|
+
const payload = state?.status === "completed" ? state.output : state?.status === "error" ? state.error : event.output;
|
|
148243
148379
|
const label = eventLabel(event);
|
|
148244
148380
|
timerFor(label).markToolResult();
|
|
148245
148381
|
if (taskDispatchByCallID.size > 0 || pendingTaskDispatches.length > 0) {
|
|
148246
148382
|
if (toolId && taskDispatchByCallID.has(toolId)) {
|
|
148247
148383
|
const dispatch = taskDispatchByCallID.get(toolId);
|
|
148248
|
-
if (dispatch) emitSubagentFinished(dispatch, status,
|
|
148384
|
+
if (dispatch) emitSubagentFinished(dispatch, status, payload, "exact");
|
|
148249
148385
|
} else {
|
|
148250
148386
|
const callIDIsKnownNonTask = toolId ? knownNonTaskCallIDs.has(toolId) : false;
|
|
148251
148387
|
if (!callIDIsKnownNonTask && pendingTaskDispatches.length > 0) {
|
|
148252
148388
|
const dispatch = pendingTaskDispatches[0];
|
|
148253
|
-
emitSubagentFinished(dispatch, status,
|
|
148389
|
+
emitSubagentFinished(dispatch, status, payload, "fifo");
|
|
148254
148390
|
}
|
|
148255
148391
|
}
|
|
148256
148392
|
}
|
|
@@ -148266,13 +148402,8 @@ async function runOpenCode(params) {
|
|
|
148266
148402
|
`\xBB ${params.label} tool_result${stepContext}: id=${toolId}, status=${status}, duration=${Math.round(toolDuration)}ms`
|
|
148267
148403
|
)
|
|
148268
148404
|
);
|
|
148269
|
-
if (
|
|
148270
|
-
log.debug(
|
|
148271
|
-
withLabel(
|
|
148272
|
-
label,
|
|
148273
|
-
` output: ${typeof output2 === "string" ? output2 : JSON.stringify(output2)}`
|
|
148274
|
-
)
|
|
148275
|
-
);
|
|
148405
|
+
if (payload) {
|
|
148406
|
+
log.debug(withLabel(label, ` output: ${payload}`));
|
|
148276
148407
|
}
|
|
148277
148408
|
if (toolDuration > 5e3) {
|
|
148278
148409
|
log.info(
|
|
@@ -148285,11 +148416,9 @@ async function runOpenCode(params) {
|
|
|
148285
148416
|
}
|
|
148286
148417
|
}
|
|
148287
148418
|
if (status === "error") {
|
|
148288
|
-
|
|
148289
|
-
|
|
148290
|
-
|
|
148291
|
-
const outputStr = typeof output2 === "string" ? output2 : JSON.stringify(output2);
|
|
148292
|
-
log.debug(withLabel(label, `tool output: ${outputStr}`));
|
|
148419
|
+
log.info(withLabel(label, `\xBB tool call failed: ${payload ?? "(no error message)"}`));
|
|
148420
|
+
} else if (payload) {
|
|
148421
|
+
log.debug(withLabel(label, `tool output: ${payload}`));
|
|
148293
148422
|
}
|
|
148294
148423
|
},
|
|
148295
148424
|
error: (event) => {
|
|
@@ -148366,6 +148495,13 @@ async function runOpenCode(params) {
|
|
|
148366
148495
|
const recentStderr = [];
|
|
148367
148496
|
let lastProviderError = null;
|
|
148368
148497
|
let agentErrorEvent = null;
|
|
148498
|
+
const diagnostic = {
|
|
148499
|
+
label: params.label,
|
|
148500
|
+
recentStderr,
|
|
148501
|
+
lastProviderError: void 0,
|
|
148502
|
+
eventCount: 0
|
|
148503
|
+
};
|
|
148504
|
+
params.toolState.agentDiagnostic = diagnostic;
|
|
148369
148505
|
const output = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
|
|
148370
148506
|
let stdoutBuffer = "";
|
|
148371
148507
|
try {
|
|
@@ -148414,6 +148550,7 @@ async function runOpenCode(params) {
|
|
|
148414
148550
|
continue;
|
|
148415
148551
|
}
|
|
148416
148552
|
eventCount++;
|
|
148553
|
+
diagnostic.eventCount = eventCount;
|
|
148417
148554
|
log.debug(JSON.stringify(event, null, 2));
|
|
148418
148555
|
const timeSinceLastActivity = getIdleMs();
|
|
148419
148556
|
if (timeSinceLastActivity > 1e4) {
|
|
@@ -148445,10 +148582,11 @@ async function runOpenCode(params) {
|
|
|
148445
148582
|
if (!trimmed) return;
|
|
148446
148583
|
recentStderr.push(trimmed);
|
|
148447
148584
|
if (recentStderr.length > MAX_STDERR_LINES) recentStderr.shift();
|
|
148448
|
-
const
|
|
148449
|
-
if (
|
|
148450
|
-
lastProviderError =
|
|
148451
|
-
|
|
148585
|
+
const match3 = findProviderErrorMatch(trimmed);
|
|
148586
|
+
if (match3) {
|
|
148587
|
+
lastProviderError = match3.label;
|
|
148588
|
+
diagnostic.lastProviderError = match3.label;
|
|
148589
|
+
log.info(`\xBB provider error detected (${match3.label}): ${match3.excerpt}`);
|
|
148452
148590
|
} else {
|
|
148453
148591
|
log.debug(trimmed);
|
|
148454
148592
|
}
|
|
@@ -148538,10 +148676,11 @@ ${stderrContext}`);
|
|
|
148538
148676
|
`\xBB recent stderr (last ${Math.min(recentStderr.length, 10)} lines):
|
|
148539
148677
|
${stderrContext}`
|
|
148540
148678
|
);
|
|
148679
|
+
const body = formatAgentHangBody({ diagnostic, isHang: isActivityTimeout, errorMessage });
|
|
148541
148680
|
return {
|
|
148542
148681
|
success: false,
|
|
148543
148682
|
output: finalOutput || output.toString(),
|
|
148544
|
-
error: `${errorMessage} [${diagnosis}]`,
|
|
148683
|
+
error: body ?? `${errorMessage} [${diagnosis}]`,
|
|
148545
148684
|
usage: buildUsage()
|
|
148546
148685
|
};
|
|
148547
148686
|
}
|
|
@@ -148593,6 +148732,7 @@ var opencode = agent({
|
|
|
148593
148732
|
cliPath,
|
|
148594
148733
|
cwd: repoDir,
|
|
148595
148734
|
env: env2,
|
|
148735
|
+
toolState: ctx.toolState,
|
|
148596
148736
|
todoTracker: ctx.todoTracker,
|
|
148597
148737
|
onActivityTimeout: ctx.onActivityTimeout,
|
|
148598
148738
|
onToolUse: ctx.onToolUse
|
|
@@ -154764,24 +154904,29 @@ ${instructions.user}` : null,
|
|
|
154764
154904
|
killTrackedChildren();
|
|
154765
154905
|
log.error(errorMessage);
|
|
154766
154906
|
const billingError = isRouterKeylimitExhaustedError(errorMessage) ? new BillingError(errorMessage, { code: "router_keylimit_exhausted" }) : null;
|
|
154767
|
-
const
|
|
154907
|
+
const isHang = errorMessage.startsWith("activity timeout") || errorMessage.startsWith("agent still pending");
|
|
154908
|
+
const hangBody = isHang ? formatAgentHangBody({ diagnostic: toolState.agentDiagnostic, isHang: true, errorMessage }) : null;
|
|
154909
|
+
const apiKeySource = hangBody ?? errorMessage;
|
|
154910
|
+
const apiKeyErrorSummary = !billingError && isApiKeyAuthError(apiKeySource) ? formatApiKeyErrorSummary({
|
|
154768
154911
|
owner: runContext.repo.owner,
|
|
154769
154912
|
name: runContext.repo.name,
|
|
154770
|
-
raw:
|
|
154913
|
+
raw: apiKeySource
|
|
154771
154914
|
}) : null;
|
|
154772
154915
|
try {
|
|
154773
|
-
const errorSummary = billingError ? formatBillingErrorSummary(billingError, runContext.repo.owner) : apiKeyErrorSummary ?? `### \u274C Pullfrog failed
|
|
154916
|
+
const errorSummary = billingError ? formatBillingErrorSummary(billingError, runContext.repo.owner) : apiKeyErrorSummary ?? (hangBody ? `### \u274C Pullfrog failed
|
|
154917
|
+
|
|
154918
|
+
${hangBody}` : `### \u274C Pullfrog failed
|
|
154774
154919
|
|
|
154775
154920
|
\`\`\`
|
|
154776
154921
|
${errorMessage}
|
|
154777
|
-
|
|
154922
|
+
\`\`\``);
|
|
154778
154923
|
const usageSummary = formatUsageSummary(toolState.usageEntries);
|
|
154779
154924
|
const parts = [errorSummary, toolState.lastProgressBody, usageSummary].filter(Boolean);
|
|
154780
154925
|
await writeSummary(parts.join("\n\n"));
|
|
154781
154926
|
} catch {
|
|
154782
154927
|
}
|
|
154783
154928
|
try {
|
|
154784
|
-
const commentBody = billingError ? formatBillingErrorSummary(billingError, runContext.repo.owner) : apiKeyErrorSummary ?? errorMessage;
|
|
154929
|
+
const commentBody = billingError ? formatBillingErrorSummary(billingError, runContext.repo.owner) : apiKeyErrorSummary ?? hangBody ?? errorMessage;
|
|
154785
154930
|
await reportErrorToComment({ toolState, error: commentBody });
|
|
154786
154931
|
} catch {
|
|
154787
154932
|
}
|