pullfrog 0.1.11 → 0.1.12
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 +341 -195
- package/dist/external.d.ts +1 -1
- package/dist/index.js +340 -194
- package/dist/internal/index.d.ts +1 -1
- package/dist/internal.js +38 -2
- package/dist/mcp/checkout.d.ts +1 -1
- package/dist/models.d.ts +1 -0
- package/dist/toolState.d.ts +7 -0
- package/dist/utils/byokFallback.d.ts +2 -3
- package/dist/utils/codexHome.d.ts +0 -8
- package/dist/utils/runLifecycle.d.ts +7 -4
- package/dist/utils/setup.d.ts +44 -0
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -100664,7 +100664,7 @@ var import_arg2 = __toESM(require_arg(), 1);
|
|
|
100664
100664
|
import { dirname as dirname6 } from "node:path";
|
|
100665
100665
|
|
|
100666
100666
|
// main.ts
|
|
100667
|
-
import { existsSync as existsSync7, readdirSync } from "node:fs";
|
|
100667
|
+
import { existsSync as existsSync7, readdirSync as readdirSync2 } from "node:fs";
|
|
100668
100668
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
100669
100669
|
import { join as join20 } from "node:path";
|
|
100670
100670
|
|
|
@@ -109727,6 +109727,11 @@ var providers = {
|
|
|
109727
109727
|
resolve: "opencode/kimi-k2.6",
|
|
109728
109728
|
openRouterResolve: "openrouter/moonshotai/kimi-k2.6"
|
|
109729
109729
|
},
|
|
109730
|
+
"minimax-m2.5": {
|
|
109731
|
+
displayName: "MiniMax M2.5",
|
|
109732
|
+
resolve: "opencode/minimax-m2.5",
|
|
109733
|
+
openRouterResolve: "openrouter/minimax/minimax-m2.5"
|
|
109734
|
+
},
|
|
109730
109735
|
"gpt-5-nano": {
|
|
109731
109736
|
displayName: "GPT Nano",
|
|
109732
109737
|
resolve: "opencode/gpt-5-nano",
|
|
@@ -109743,7 +109748,9 @@ var providers = {
|
|
|
109743
109748
|
displayName: "MiniMax M2.5",
|
|
109744
109749
|
resolve: "opencode/minimax-m2.5-free",
|
|
109745
109750
|
envVars: [],
|
|
109746
|
-
isFree: true
|
|
109751
|
+
isFree: true,
|
|
109752
|
+
fallback: "opencode/big-pickle",
|
|
109753
|
+
hidden: true
|
|
109747
109754
|
}
|
|
109748
109755
|
}
|
|
109749
109756
|
}),
|
|
@@ -109881,6 +109888,11 @@ var providers = {
|
|
|
109881
109888
|
displayName: "Kimi K2",
|
|
109882
109889
|
resolve: "openrouter/moonshotai/kimi-k2.6",
|
|
109883
109890
|
openRouterResolve: "openrouter/moonshotai/kimi-k2.6"
|
|
109891
|
+
},
|
|
109892
|
+
"minimax-m2.5": {
|
|
109893
|
+
displayName: "MiniMax M2.5",
|
|
109894
|
+
resolve: "openrouter/minimax/minimax-m2.5",
|
|
109895
|
+
openRouterResolve: "openrouter/minimax/minimax-m2.5"
|
|
109884
109896
|
}
|
|
109885
109897
|
}
|
|
109886
109898
|
})
|
|
@@ -109925,6 +109937,11 @@ var modelAliases = Object.entries(providers).flatMap(
|
|
|
109925
109937
|
hidden: def.hidden ?? false
|
|
109926
109938
|
}))
|
|
109927
109939
|
);
|
|
109940
|
+
var defaultProxyAlias = modelAliases.find((a) => a.slug === "moonshotai/kimi-k2");
|
|
109941
|
+
if (!defaultProxyAlias?.openRouterResolve) {
|
|
109942
|
+
throw new Error("DEFAULT_PROXY_MODEL: moonshotai/kimi-k2 missing openRouterResolve");
|
|
109943
|
+
}
|
|
109944
|
+
var DEFAULT_PROXY_MODEL = defaultProxyAlias.openRouterResolve;
|
|
109928
109945
|
var MAX_FALLBACK_DEPTH = 10;
|
|
109929
109946
|
function resolveDisplayAlias(slug2) {
|
|
109930
109947
|
let current = slug2;
|
|
@@ -144232,7 +144249,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
144232
144249
|
// package.json
|
|
144233
144250
|
var package_default = {
|
|
144234
144251
|
name: "pullfrog",
|
|
144235
|
-
version: "0.1.
|
|
144252
|
+
version: "0.1.12",
|
|
144236
144253
|
type: "module",
|
|
144237
144254
|
bin: {
|
|
144238
144255
|
pullfrog: "dist/cli.mjs",
|
|
@@ -144430,8 +144447,8 @@ function closeBrowserDaemon(toolState) {
|
|
|
144430
144447
|
|
|
144431
144448
|
// mcp/checkout.ts
|
|
144432
144449
|
import { createHash as createHash2 } from "node:crypto";
|
|
144433
|
-
import { statSync, unlinkSync as
|
|
144434
|
-
import { join as
|
|
144450
|
+
import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
144451
|
+
import { join as join5 } from "node:path";
|
|
144435
144452
|
|
|
144436
144453
|
// utils/diffCoverage.ts
|
|
144437
144454
|
import { isAbsolute, normalize as normalize2, resolve } from "node:path";
|
|
@@ -145943,6 +145960,183 @@ async function reportReviewNodeId(ctx, params) {
|
|
|
145943
145960
|
await patchWorkflowRunFields(ctx, { reviewNodeId: params.nodeId });
|
|
145944
145961
|
}
|
|
145945
145962
|
|
|
145963
|
+
// utils/setup.ts
|
|
145964
|
+
import { execFileSync as execFileSync4, execSync as execSync2 } from "node:child_process";
|
|
145965
|
+
import { mkdtempSync as mkdtempSync2, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
|
|
145966
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
145967
|
+
import { join as join4 } from "node:path";
|
|
145968
|
+
function createTempDirectory() {
|
|
145969
|
+
const sharedTempDir = mkdtempSync2(join4(tmpdir2(), "pullfrog-"));
|
|
145970
|
+
process.env.PULLFROG_TEMP_DIR = sharedTempDir;
|
|
145971
|
+
log.info(`\xBB created temp dir at ${sharedTempDir}`);
|
|
145972
|
+
return sharedTempDir;
|
|
145973
|
+
}
|
|
145974
|
+
function wipeRunnerLeakSurface() {
|
|
145975
|
+
const runnerTemp = process.env.RUNNER_TEMP;
|
|
145976
|
+
if (!runnerTemp) return;
|
|
145977
|
+
const preserve = /* @__PURE__ */ new Set();
|
|
145978
|
+
for (const envVar of [
|
|
145979
|
+
"GITHUB_OUTPUT",
|
|
145980
|
+
"GITHUB_ENV",
|
|
145981
|
+
"GITHUB_PATH",
|
|
145982
|
+
"GITHUB_STATE",
|
|
145983
|
+
"GITHUB_STEP_SUMMARY"
|
|
145984
|
+
]) {
|
|
145985
|
+
const path3 = process.env[envVar];
|
|
145986
|
+
if (!path3) continue;
|
|
145987
|
+
try {
|
|
145988
|
+
preserve.add(realpathSync2(path3));
|
|
145989
|
+
} catch {
|
|
145990
|
+
preserve.add(path3);
|
|
145991
|
+
}
|
|
145992
|
+
}
|
|
145993
|
+
const wiped = [];
|
|
145994
|
+
const tryUnlink = (path3) => {
|
|
145995
|
+
let resolved = path3;
|
|
145996
|
+
try {
|
|
145997
|
+
resolved = realpathSync2(path3);
|
|
145998
|
+
} catch {
|
|
145999
|
+
}
|
|
146000
|
+
if (preserve.has(resolved) || preserve.has(path3)) return;
|
|
146001
|
+
try {
|
|
146002
|
+
unlinkSync2(path3);
|
|
146003
|
+
wiped.push(path3);
|
|
146004
|
+
} catch {
|
|
146005
|
+
}
|
|
146006
|
+
};
|
|
146007
|
+
const listDir = (dir) => {
|
|
146008
|
+
try {
|
|
146009
|
+
return readdirSync(dir);
|
|
146010
|
+
} catch {
|
|
146011
|
+
return [];
|
|
146012
|
+
}
|
|
146013
|
+
};
|
|
146014
|
+
const fileCommandsDir = join4(runnerTemp, "_runner_file_commands");
|
|
146015
|
+
for (const entry of listDir(fileCommandsDir)) {
|
|
146016
|
+
tryUnlink(join4(fileCommandsDir, entry));
|
|
146017
|
+
}
|
|
146018
|
+
for (const entry of listDir(runnerTemp)) {
|
|
146019
|
+
if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
|
|
146020
|
+
tryUnlink(join4(runnerTemp, entry));
|
|
146021
|
+
}
|
|
146022
|
+
}
|
|
146023
|
+
if (wiped.length > 0) {
|
|
146024
|
+
log.info(`\xBB wiped ${wiped.length} leak-surface file(s) from $RUNNER_TEMP`);
|
|
146025
|
+
log.debug(`\xBB wiped paths: ${wiped.join(", ")}`);
|
|
146026
|
+
}
|
|
146027
|
+
}
|
|
146028
|
+
function envScopedToRepo() {
|
|
146029
|
+
const scoped = { ...process.env };
|
|
146030
|
+
for (const key of Object.keys(scoped)) {
|
|
146031
|
+
if (key.startsWith("GIT_")) delete scoped[key];
|
|
146032
|
+
}
|
|
146033
|
+
return scoped;
|
|
146034
|
+
}
|
|
146035
|
+
function removeIncludeIfEntries(repoDir) {
|
|
146036
|
+
const env2 = envScopedToRepo();
|
|
146037
|
+
let configOutput;
|
|
146038
|
+
try {
|
|
146039
|
+
configOutput = execSync2("git config --local --get-regexp -z ^includeif\\.", {
|
|
146040
|
+
cwd: repoDir,
|
|
146041
|
+
encoding: "utf-8",
|
|
146042
|
+
stdio: "pipe",
|
|
146043
|
+
env: env2
|
|
146044
|
+
});
|
|
146045
|
+
} catch {
|
|
146046
|
+
log.debug("\xBB no includeIf credential entries to remove");
|
|
146047
|
+
return;
|
|
146048
|
+
}
|
|
146049
|
+
const seen = /* @__PURE__ */ new Set();
|
|
146050
|
+
for (const entry of configOutput.split("\0")) {
|
|
146051
|
+
if (!entry) continue;
|
|
146052
|
+
const nl = entry.indexOf("\n");
|
|
146053
|
+
const key = nl === -1 ? entry : entry.slice(0, nl);
|
|
146054
|
+
if (!key || seen.has(key)) continue;
|
|
146055
|
+
seen.add(key);
|
|
146056
|
+
try {
|
|
146057
|
+
execFileSync4("git", ["config", "--local", "--unset-all", key], {
|
|
146058
|
+
cwd: repoDir,
|
|
146059
|
+
stdio: "pipe",
|
|
146060
|
+
env: env2
|
|
146061
|
+
});
|
|
146062
|
+
} catch (error49) {
|
|
146063
|
+
log.debug(
|
|
146064
|
+
`\xBB failed to unset ${key}: ${error49 instanceof Error ? error49.message : String(error49)}`
|
|
146065
|
+
);
|
|
146066
|
+
}
|
|
146067
|
+
}
|
|
146068
|
+
if (seen.size > 0)
|
|
146069
|
+
log.info(
|
|
146070
|
+
`\xBB removed ${seen.size} includeIf credential ${seen.size === 1 ? "entry" : "entries"}`
|
|
146071
|
+
);
|
|
146072
|
+
}
|
|
146073
|
+
async function setupGit(params) {
|
|
146074
|
+
const repoDir = process.cwd();
|
|
146075
|
+
log.info("\xBB setting up git configuration...");
|
|
146076
|
+
try {
|
|
146077
|
+
let currentEmail = "";
|
|
146078
|
+
try {
|
|
146079
|
+
currentEmail = execSync2("git config user.email", {
|
|
146080
|
+
cwd: repoDir,
|
|
146081
|
+
stdio: "pipe",
|
|
146082
|
+
encoding: "utf-8"
|
|
146083
|
+
}).trim();
|
|
146084
|
+
} catch {
|
|
146085
|
+
}
|
|
146086
|
+
const shouldSetDefaults = !currentEmail || currentEmail === "github-actions[bot]@users.noreply.github.com";
|
|
146087
|
+
if (shouldSetDefaults) {
|
|
146088
|
+
execSync2('git config --local user.email "226033991+pullfrog[bot]@users.noreply.github.com"', {
|
|
146089
|
+
cwd: repoDir,
|
|
146090
|
+
stdio: "pipe"
|
|
146091
|
+
});
|
|
146092
|
+
execSync2('git config --local user.name "pullfrog[bot]"', {
|
|
146093
|
+
cwd: repoDir,
|
|
146094
|
+
stdio: "pipe"
|
|
146095
|
+
});
|
|
146096
|
+
log.debug("\xBB git user configured (using defaults)");
|
|
146097
|
+
} else {
|
|
146098
|
+
log.debug(`\xBB git user already configured (${currentEmail}), skipping`);
|
|
146099
|
+
}
|
|
146100
|
+
if (params.shell === "disabled") {
|
|
146101
|
+
execSync2("git config --local core.hooksPath /dev/null", {
|
|
146102
|
+
cwd: repoDir,
|
|
146103
|
+
stdio: "pipe"
|
|
146104
|
+
});
|
|
146105
|
+
log.debug("\xBB git hooks disabled (shell=disabled)");
|
|
146106
|
+
}
|
|
146107
|
+
} catch (error49) {
|
|
146108
|
+
log.info(`Failed to set git config: ${error49 instanceof Error ? error49.message : String(error49)}`);
|
|
146109
|
+
}
|
|
146110
|
+
try {
|
|
146111
|
+
execSync2("git config --local --unset-all http.https://github.com/.extraheader", {
|
|
146112
|
+
cwd: repoDir,
|
|
146113
|
+
stdio: "pipe"
|
|
146114
|
+
});
|
|
146115
|
+
log.info("\xBB removed existing authentication headers");
|
|
146116
|
+
} catch {
|
|
146117
|
+
log.debug("\xBB no existing authentication headers to remove");
|
|
146118
|
+
}
|
|
146119
|
+
removeIncludeIfEntries(repoDir);
|
|
146120
|
+
const originUrl = `https://github.com/${params.owner}/${params.name}.git`;
|
|
146121
|
+
$2("git", ["remote", "set-url", "origin", originUrl], { cwd: repoDir });
|
|
146122
|
+
params.toolState.pushUrl = originUrl;
|
|
146123
|
+
$2("git", ["config", "--local", "credential.helper", ""], { cwd: repoDir });
|
|
146124
|
+
params.toolState.initialHead = captureInitialHead(repoDir);
|
|
146125
|
+
log.info("\xBB git authentication configured");
|
|
146126
|
+
}
|
|
146127
|
+
function captureInitialHead(repoDir) {
|
|
146128
|
+
try {
|
|
146129
|
+
const name = $2("git", ["symbolic-ref", "--short", "HEAD"], {
|
|
146130
|
+
cwd: repoDir,
|
|
146131
|
+
log: false
|
|
146132
|
+
}).trim();
|
|
146133
|
+
if (name) return { kind: "branch", name };
|
|
146134
|
+
} catch {
|
|
146135
|
+
}
|
|
146136
|
+
const sha = $2("git", ["rev-parse", "HEAD"], { cwd: repoDir, log: false }).trim();
|
|
146137
|
+
return { kind: "detached", sha };
|
|
146138
|
+
}
|
|
146139
|
+
|
|
145946
146140
|
// mcp/checkout.ts
|
|
145947
146141
|
function formatFilesWithLineNumbers(files) {
|
|
145948
146142
|
const output = [];
|
|
@@ -146117,7 +146311,7 @@ function cleanupStaleGitLocks() {
|
|
|
146117
146311
|
}
|
|
146118
146312
|
if (now - mtimeMs < STALE_LOCK_AGE_MS) continue;
|
|
146119
146313
|
try {
|
|
146120
|
-
|
|
146314
|
+
unlinkSync3(relPath);
|
|
146121
146315
|
log.warning(`\xBB removed stale ${relPath} from prior run`);
|
|
146122
146316
|
} catch (e) {
|
|
146123
146317
|
log.debug(
|
|
@@ -146276,6 +146470,15 @@ async function checkoutPrBranch(pr, params) {
|
|
|
146276
146470
|
return { hookWarning: postCheckoutHook.warning };
|
|
146277
146471
|
}
|
|
146278
146472
|
var inFlightCheckouts = /* @__PURE__ */ new Map();
|
|
146473
|
+
function headsEqual(a, b) {
|
|
146474
|
+
if (a.kind === "branch" && b.kind === "branch") return a.name === b.name;
|
|
146475
|
+
if (a.kind === "detached" && b.kind === "detached") return a.sha === b.sha;
|
|
146476
|
+
return false;
|
|
146477
|
+
}
|
|
146478
|
+
function describeHead(h) {
|
|
146479
|
+
if (h.kind === "branch") return `branch \`${h.name}\``;
|
|
146480
|
+
return `detached HEAD \`${h.sha}\``;
|
|
146481
|
+
}
|
|
146279
146482
|
function CheckoutPrTool(ctx) {
|
|
146280
146483
|
const runCheckout = async (pull_number) => {
|
|
146281
146484
|
const prResponse = await ctx.octokit.rest.pulls.get({
|
|
@@ -146322,7 +146525,7 @@ function CheckoutPrTool(ctx) {
|
|
|
146322
146525
|
headSha: ctx.toolState.checkoutSha
|
|
146323
146526
|
});
|
|
146324
146527
|
if (incremental) {
|
|
146325
|
-
incrementalDiffPath =
|
|
146528
|
+
incrementalDiffPath = join5(
|
|
146326
146529
|
tempDir,
|
|
146327
146530
|
`pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
|
|
146328
146531
|
);
|
|
@@ -146336,7 +146539,7 @@ function CheckoutPrTool(ctx) {
|
|
|
146336
146539
|
const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
|
|
146337
146540
|
log.debug(`formatted diff preview (first 100 lines):
|
|
146338
146541
|
${diffPreview}`);
|
|
146339
|
-
const diffPath =
|
|
146542
|
+
const diffPath = join5(tempDir, `pr-${pull_number}-${headShort}.diff`);
|
|
146340
146543
|
writeFileSync2(diffPath, formatResult.content);
|
|
146341
146544
|
log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
|
|
146342
146545
|
ctx.toolState.diffCoverage = createDiffCoverageState({
|
|
@@ -146403,7 +146606,8 @@ ${diffPreview}`);
|
|
|
146403
146606
|
};
|
|
146404
146607
|
return tool({
|
|
146405
146608
|
name: "checkout_pr",
|
|
146406
|
-
|
|
146609
|
+
timeoutMs: 6e5,
|
|
146610
|
+
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 })`. Large repos can take several minutes \u2014 wait for the call to finish; do not treat a slow response as failure. If you see `MCP error -32001: Request timed out`, retry the same call without touching git lock files first \u2014 that error is a client-side abort. If the retry then reports `.git/shallow.lock: File exists` or `.git/index.lock: File exists`, remove those lock files via the shell tool and retry again.",
|
|
146407
146611
|
parameters: CheckoutPr,
|
|
146408
146612
|
execute: execute(async ({ pull_number }) => {
|
|
146409
146613
|
const inFlight = inFlightCheckouts.get(pull_number);
|
|
@@ -146411,13 +146615,23 @@ ${diffPreview}`);
|
|
|
146411
146615
|
log.info(`\xBB checkout_pr({pull_number:${pull_number}}) already in flight \u2014 sharing result`);
|
|
146412
146616
|
return inFlight;
|
|
146413
146617
|
}
|
|
146414
|
-
const
|
|
146415
|
-
if (
|
|
146416
|
-
|
|
146417
|
-
|
|
146418
|
-
throw new Error(
|
|
146419
|
-
`cannot checkout PR #${pull_number} while the working tree has uncommitted changes. commit, push, or discard them before switching. dirty paths:
|
|
146618
|
+
const dirty = $2("git", ["status", "--porcelain"], { log: false }).trim();
|
|
146619
|
+
if (dirty) {
|
|
146620
|
+
throw new Error(
|
|
146621
|
+
`cannot checkout PR #${pull_number} while the working tree has uncommitted changes. commit (then push if needed), or discard with \`git restore --staged --worktree .\` / \`git clean -fd\` before retrying. this refusal is unconditional \u2014 even re-checking-out the PR you're already on is refused, because shared-working-tree subagents make carry-forward edits unsafe. dirty paths:
|
|
146420
146622
|
${dirty}`
|
|
146623
|
+
);
|
|
146624
|
+
}
|
|
146625
|
+
const initialHead = ctx.toolState.initialHead;
|
|
146626
|
+
if (initialHead) {
|
|
146627
|
+
const currentHead = captureInitialHead(process.cwd());
|
|
146628
|
+
const targetBranch = `pr-${pull_number}`;
|
|
146629
|
+
const onTarget = currentHead.kind === "branch" && currentHead.name === targetBranch;
|
|
146630
|
+
const onInitial = headsEqual(currentHead, initialHead);
|
|
146631
|
+
if (!onTarget && !onInitial) {
|
|
146632
|
+
const recoverCmd = initialHead.kind === "branch" ? `git checkout ${initialHead.name}` : `git checkout ${initialHead.sha}`;
|
|
146633
|
+
throw new Error(
|
|
146634
|
+
`cannot checkout PR #${pull_number} from ${describeHead(currentHead)}. the only sanctioned HEAD positions for checkout_pr are the run-entry HEAD (${describeHead(initialHead)}) or the target PR's branch (\`${targetBranch}\`, idempotent re-checkout). recover with \`${recoverCmd}\` first \u2014 if that would carry uncommitted work along, commit or discard it (\`git restore --staged --worktree .\` / \`git clean -fd\`) before switching. routing around this via the \`git\` tool's \`checkout\`/\`switch\` subcommands is not sanctioned: this guard exists to prevent the shared-working-tree cross-PR clobber pattern from the zed-industries/cloud (2026-05-18) incident.`
|
|
146421
146635
|
);
|
|
146422
146636
|
}
|
|
146423
146637
|
}
|
|
@@ -146434,7 +146648,7 @@ ${dirty}`
|
|
|
146434
146648
|
|
|
146435
146649
|
// mcp/checkSuite.ts
|
|
146436
146650
|
import { mkdirSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
146437
|
-
import { join as
|
|
146651
|
+
import { join as join6 } from "node:path";
|
|
146438
146652
|
var GetCheckSuiteLogs = type({
|
|
146439
146653
|
check_suite_id: type.number.describe("the id from check_suite.id")
|
|
146440
146654
|
});
|
|
@@ -146530,7 +146744,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
146530
146744
|
if (!tempDir) {
|
|
146531
146745
|
throw new Error("PULLFROG_TEMP_DIR not set");
|
|
146532
146746
|
}
|
|
146533
|
-
const logsDir =
|
|
146747
|
+
const logsDir = join6(tempDir, "ci-logs");
|
|
146534
146748
|
mkdirSync(logsDir, { recursive: true });
|
|
146535
146749
|
const jobResults = [];
|
|
146536
146750
|
for (const run4 of failedRuns) {
|
|
@@ -146557,7 +146771,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
146557
146771
|
);
|
|
146558
146772
|
}
|
|
146559
146773
|
const logsText = await logsResult.text();
|
|
146560
|
-
const logPath =
|
|
146774
|
+
const logPath = join6(logsDir, `job-${job.id}.log`);
|
|
146561
146775
|
writeFileSync3(logPath, logsText);
|
|
146562
146776
|
const analysis = analyzeLog(logsText, 80);
|
|
146563
146777
|
const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
|
|
@@ -146607,7 +146821,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
146607
146821
|
|
|
146608
146822
|
// mcp/commitInfo.ts
|
|
146609
146823
|
import { writeFileSync as writeFileSync4 } from "node:fs";
|
|
146610
|
-
import { join as
|
|
146824
|
+
import { join as join7 } from "node:path";
|
|
146611
146825
|
var CommitInfo = type({
|
|
146612
146826
|
sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
|
|
146613
146827
|
});
|
|
@@ -146631,7 +146845,7 @@ function CommitInfoTool(ctx) {
|
|
|
146631
146845
|
"PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
|
|
146632
146846
|
);
|
|
146633
146847
|
}
|
|
146634
|
-
const diffFile =
|
|
146848
|
+
const diffFile = join7(tempDir, `commit-${sha.slice(0, 7)}.diff`);
|
|
146635
146849
|
writeFileSync4(diffFile, formatResult.content);
|
|
146636
146850
|
log.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
|
|
146637
146851
|
return {
|
|
@@ -147089,7 +147303,7 @@ function PullRequestInfoTool(ctx) {
|
|
|
147089
147303
|
|
|
147090
147304
|
// mcp/reviewComments.ts
|
|
147091
147305
|
import { writeFileSync as writeFileSync5 } from "node:fs";
|
|
147092
|
-
import { join as
|
|
147306
|
+
import { join as join8 } from "node:path";
|
|
147093
147307
|
var REVIEW_THREADS_QUERY = `
|
|
147094
147308
|
query ($owner: String!, $name: String!, $prNumber: Int!) {
|
|
147095
147309
|
repository(owner: $owner, name: $name) {
|
|
@@ -147481,7 +147695,7 @@ function GetReviewCommentsTool(ctx) {
|
|
|
147481
147695
|
throw new Error("PULLFROG_TEMP_DIR not set");
|
|
147482
147696
|
}
|
|
147483
147697
|
const filename = `review-${params.review_id}-threads.md`;
|
|
147484
|
-
const commentsPath =
|
|
147698
|
+
const commentsPath = join8(tempDir, filename);
|
|
147485
147699
|
writeFileSync5(commentsPath, formatted.content);
|
|
147486
147700
|
log.debug(`wrote ${threadBlocks.length} threads to ${commentsPath}`);
|
|
147487
147701
|
return {
|
|
@@ -147710,7 +147924,7 @@ import { spawn as spawn4, spawnSync as spawnSync3 } from "node:child_process";
|
|
|
147710
147924
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
147711
147925
|
import { closeSync, openSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
147712
147926
|
import { userInfo } from "node:os";
|
|
147713
|
-
import { join as
|
|
147927
|
+
import { join as join9 } from "node:path";
|
|
147714
147928
|
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
147715
147929
|
var ShellParams = type({
|
|
147716
147930
|
command: "string",
|
|
@@ -147843,7 +148057,7 @@ function getTempDir() {
|
|
|
147843
148057
|
var MAX_OUTPUT_CHARS = 5e3;
|
|
147844
148058
|
function capOutput(output) {
|
|
147845
148059
|
if (output.length <= MAX_OUTPUT_CHARS) return output;
|
|
147846
|
-
const fullPath =
|
|
148060
|
+
const fullPath = join9(getTempDir(), `shell-${randomUUID2().slice(0, 8)}.log`);
|
|
147847
148061
|
writeFileSync6(fullPath, output);
|
|
147848
148062
|
const elided = output.length - MAX_OUTPUT_CHARS;
|
|
147849
148063
|
return `... [${elided} chars truncated; full output saved to ${fullPath}] ...
|
|
@@ -147858,6 +148072,7 @@ function isGitCommand(command) {
|
|
|
147858
148072
|
function ShellTool(ctx) {
|
|
147859
148073
|
return tool({
|
|
147860
148074
|
name: "shell",
|
|
148075
|
+
timeoutMs: 12e4,
|
|
147861
148076
|
description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
|
|
147862
148077
|
|
|
147863
148078
|
Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
|
|
@@ -147897,8 +148112,8 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
|
|
|
147897
148112
|
if (params.background) {
|
|
147898
148113
|
const tempDir = getTempDir();
|
|
147899
148114
|
const handle = `bg-${randomUUID2().slice(0, 8)}`;
|
|
147900
|
-
const outputPath =
|
|
147901
|
-
const pidPath =
|
|
148115
|
+
const outputPath = join9(tempDir, `${handle}.log`);
|
|
148116
|
+
const pidPath = join9(tempDir, `${handle}.pid`);
|
|
147902
148117
|
const logFd = openSync(outputPath, "a");
|
|
147903
148118
|
let proc2;
|
|
147904
148119
|
try {
|
|
@@ -148238,6 +148453,8 @@ var REVIEWER_AGENT_NAME = "reviewfrog";
|
|
|
148238
148453
|
var REVIEWER_SYSTEM_PROMPT = `You are a read-only review subagent. Your role is to find flaws in code or artifacts provided by the orchestrator and report findings \u2014 never to modify state.
|
|
148239
148454
|
|
|
148240
148455
|
HARD CONSTRAINTS (non-negotiable, regardless of orchestrator instructions):
|
|
148456
|
+
- Your FIRST action MUST be \`git diff origin/<base>\` (single-rev form, no \`HEAD\`). This captures committed + staged + unstaged work in one command \u2014 Build-mode self-review runs BEFORE the commit, so the work to review lives in the working tree, not in committed history. Do not run any other diff command first. Do NOT call \`checkout_pr\`, do NOT fetch alternative refs, do NOT list branches or all-refs looking for the work, do NOT run \`gh pr list\`. The orchestrator's dispatch names the base branch; the diff is the source of truth for scope.
|
|
148457
|
+
- If \`git diff origin/<base>\` returns empty AND the orchestrator's dispatch claims there are changes to review, the most likely cause is a pre-commit Build-mode self-review: the orchestrator dispatched you before committing. Reply EXACTLY: \`no changes detected \u2014 likely pre-commit Build self-review; orchestrator should commit then re-dispatch\` and stop. Do NOT guess PR numbers (e.g. by extrapolating from \`git log\` output), do NOT check out other PRs, do NOT fetch from forks. The empty diff is the diagnosis \u2014 surface it; do not work around it.
|
|
148241
148458
|
- Read-only tools only. Do NOT write or edit files. Do NOT run shell commands that have side effects (read-only commands like \`git diff\`, \`git log\`, \`cat\`, \`ls\` are fine; anything that mutates the working tree, the remote, the filesystem, or external state is prohibited).
|
|
148242
148459
|
- Do NOT call any state-changing MCP tool. State-changing means: posts a comment, pushes a branch, creates/updates a PR or issue, changes labels, resolves review threads, persists learnings, sets workflow output, installs dependencies, uploads files, kills processes, etc. Read-only MCP queries (\`get_*\`, \`list_*\`, log inspection, diff retrieval) are fine.
|
|
148243
148460
|
- Do NOT spawn further subagents. You are a leaf reviewer; recursive dispatch pre-aggregates findings through an intermediate model and defeats the design.
|
|
@@ -148428,7 +148645,25 @@ function computeModes(agentId) {
|
|
|
148428
148645
|
|
|
148429
148646
|
Otherwise delegate the \`${REVIEWER_AGENT_NAME}\` subagent to review your diff with fresh eyes against YOUR TASK. The subagent's baked-in system prompt enforces a non-mutative + non-recursive contract: read-only file/search/web tools and read-only MCP queries only; no writes, shell side effects, state-changing MCP calls, or nested subagent dispatch. Enforcement is prose-only \u2014 restate the constraint in your dispatch instructions and do not relax it.
|
|
148430
148647
|
|
|
148431
|
-
|
|
148648
|
+
Compose your \`${REVIEWER_AGENT_NAME}\` dispatch prompt using this template verbatim, substituting the \`<...>\` placeholders. The preamble aligns the orchestrator side of the dispatch contract with the reviewer's baked-in system prompt \u2014 both ends say the same thing about where the work lives and what to do on an empty diff.
|
|
148649
|
+
|
|
148650
|
+
\`\`\`
|
|
148651
|
+
## What you're reviewing
|
|
148652
|
+
This is a PRE-COMMIT Build-mode self-review. The work to review lives in the working tree (uncommitted), NOT in committed history.
|
|
148653
|
+
|
|
148654
|
+
Branch: <branch> (off <base>)
|
|
148655
|
+
Canonical diff command: git diff origin/<base>
|
|
148656
|
+
|
|
148657
|
+
If that command returns empty, treat it as "no changes \u2014 nothing to review" and stop per your system prompt. Do not search for the work elsewhere.
|
|
148658
|
+
|
|
148659
|
+
## Your task
|
|
148660
|
+
<YOUR TASK content>
|
|
148661
|
+
|
|
148662
|
+
## Build-phase failures
|
|
148663
|
+
<tight summary \u2014 what broke, root cause, the fix \u2014 or "no build-phase failures">
|
|
148664
|
+
\`\`\`
|
|
148665
|
+
|
|
148666
|
+
Follow the template with the diff content (\`git diff origin/<base-branch>\`, single-rev form \u2014 \`main...HEAD\` and \`--cached\` both miss the uncommitted edits self-review runs on) and your task brief. Instruct the subagent to flag bugs, logic errors, missing edge cases, gaps between request and diff, and unintended changes.
|
|
148432
148667
|
|
|
148433
148668
|
Delegation + research discipline (distilled from \`/anneal\` canonical \u2014 these are codified learnings from many review rounds, not theoretical best practices):
|
|
148434
148669
|
- Do NOT summarize what you implemented \u2014 that biases the subagent toward validating the shape of your solution rather than questioning it.
|
|
@@ -148827,21 +149062,21 @@ function initToolState(params) {
|
|
|
148827
149062
|
}
|
|
148828
149063
|
|
|
148829
149064
|
// agents/claude.ts
|
|
148830
|
-
import { execFileSync as
|
|
149065
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
148831
149066
|
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync9 } from "node:fs";
|
|
148832
|
-
import { join as
|
|
149067
|
+
import { join as join13 } from "node:path";
|
|
148833
149068
|
import { performance as performance6 } from "node:perf_hooks";
|
|
148834
149069
|
|
|
148835
149070
|
// utils/install.ts
|
|
148836
149071
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
148837
149072
|
import { chmodSync, createWriteStream, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "node:fs";
|
|
148838
|
-
import { join as
|
|
149073
|
+
import { join as join10 } from "node:path";
|
|
148839
149074
|
import { pipeline } from "node:stream/promises";
|
|
148840
149075
|
async function installFromNpmTarball(params) {
|
|
148841
149076
|
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
148842
149077
|
if (!tempDir) throw new Error("PULLFROG_TEMP_DIR is not set");
|
|
148843
|
-
const extractedDir =
|
|
148844
|
-
const cliPath =
|
|
149078
|
+
const extractedDir = join10(tempDir, "package");
|
|
149079
|
+
const cliPath = join10(extractedDir, params.executablePath);
|
|
148845
149080
|
if (existsSync5(cliPath)) {
|
|
148846
149081
|
log.debug(`\xBB using cached binary at ${cliPath}`);
|
|
148847
149082
|
return cliPath;
|
|
@@ -148866,7 +149101,7 @@ async function installFromNpmTarball(params) {
|
|
|
148866
149101
|
}
|
|
148867
149102
|
}
|
|
148868
149103
|
log.debug(`\xBB installing ${params.packageName}@${resolvedVersion}...`);
|
|
148869
|
-
const tarballPath =
|
|
149104
|
+
const tarballPath = join10(tempDir, "package.tgz");
|
|
148870
149105
|
const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org";
|
|
148871
149106
|
let tarballUrl;
|
|
148872
149107
|
if (params.packageName.startsWith("@")) {
|
|
@@ -149005,16 +149240,16 @@ function isRouterKeylimitExhaustedError(text) {
|
|
|
149005
149240
|
// utils/skills.ts
|
|
149006
149241
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
149007
149242
|
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync7 } from "node:fs";
|
|
149008
|
-
import { tmpdir as
|
|
149009
|
-
import { dirname as dirname2, join as
|
|
149243
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
149244
|
+
import { dirname as dirname2, join as join11 } from "node:path";
|
|
149010
149245
|
import { fileURLToPath } from "node:url";
|
|
149011
149246
|
var skillsVersion = getDevDependencyVersion("skills");
|
|
149012
149247
|
var BUNDLED_SKILL_NAMES = ["git-archaeology"];
|
|
149013
149248
|
function resolveSkillPath(name) {
|
|
149014
149249
|
const here = dirname2(fileURLToPath(import.meta.url));
|
|
149015
149250
|
const candidates = [
|
|
149016
|
-
|
|
149017
|
-
|
|
149251
|
+
join11(here, "..", "skills", name, "SKILL.md"),
|
|
149252
|
+
join11(here, "skills", name, "SKILL.md")
|
|
149018
149253
|
];
|
|
149019
149254
|
for (const candidate of candidates) {
|
|
149020
149255
|
if (existsSync6(candidate)) return candidate;
|
|
@@ -149026,9 +149261,9 @@ function installBundledSkills(params) {
|
|
|
149026
149261
|
for (const name of BUNDLED_SKILL_NAMES) {
|
|
149027
149262
|
const content = readFileSync5(resolveSkillPath(name), "utf8");
|
|
149028
149263
|
for (const targetDir of SKILL_TARGET_DIRS) {
|
|
149029
|
-
const skillDir =
|
|
149264
|
+
const skillDir = join11(params.home, targetDir, name);
|
|
149030
149265
|
mkdirSync3(skillDir, { recursive: true });
|
|
149031
|
-
writeFileSync7(
|
|
149266
|
+
writeFileSync7(join11(skillDir, "SKILL.md"), content);
|
|
149032
149267
|
}
|
|
149033
149268
|
}
|
|
149034
149269
|
log.success(`installed bundled skills: ${BUNDLED_SKILL_NAMES.join(", ")}`);
|
|
@@ -149049,7 +149284,7 @@ function addSkill(params) {
|
|
|
149049
149284
|
"-y"
|
|
149050
149285
|
],
|
|
149051
149286
|
{
|
|
149052
|
-
cwd:
|
|
149287
|
+
cwd: tmpdir3(),
|
|
149053
149288
|
env: { ...process.env, ...params.env },
|
|
149054
149289
|
stdio: "pipe",
|
|
149055
149290
|
timeout: 3e4
|
|
@@ -149134,7 +149369,7 @@ var ThinkingTimer = class {
|
|
|
149134
149369
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
149135
149370
|
import { mkdirSync as mkdirSync4, rmSync as rmSync2, writeFileSync as writeFileSync8 } from "node:fs";
|
|
149136
149371
|
import { homedir } from "node:os";
|
|
149137
|
-
import { join as
|
|
149372
|
+
import { join as join12 } from "node:path";
|
|
149138
149373
|
var VERTEX_SERVICE_ACCOUNT_JSON_ENV = "VERTEX_SERVICE_ACCOUNT_JSON";
|
|
149139
149374
|
var GOOGLE_APPLICATION_CREDENTIALS_ENV = "GOOGLE_APPLICATION_CREDENTIALS";
|
|
149140
149375
|
var GOOGLE_CLOUD_PROJECT_ENV = "GOOGLE_CLOUD_PROJECT";
|
|
@@ -149163,7 +149398,7 @@ function readProjectIdFromVertexServiceAccountJson() {
|
|
|
149163
149398
|
}
|
|
149164
149399
|
function createSecretDir() {
|
|
149165
149400
|
const base = process.env.PULLFROG_SECRET_HOME || process.env.HOME || homedir();
|
|
149166
|
-
const secretDir =
|
|
149401
|
+
const secretDir = join12(base, ".pullfrog", "secrets", randomUUID3());
|
|
149167
149402
|
mkdirSync4(secretDir, { recursive: true, mode: 448 });
|
|
149168
149403
|
return secretDir;
|
|
149169
149404
|
}
|
|
@@ -149172,7 +149407,7 @@ function materializeVertexCredentials(params) {
|
|
|
149172
149407
|
const blob = process.env[VERTEX_SERVICE_ACCOUNT_JSON_ENV];
|
|
149173
149408
|
if (!blob) return void 0;
|
|
149174
149409
|
const secretDir = createSecretDir();
|
|
149175
|
-
const credentialsPath =
|
|
149410
|
+
const credentialsPath = join12(secretDir, "vertex-sa.json");
|
|
149176
149411
|
writeFileSync8(credentialsPath, blob, { mode: 384 });
|
|
149177
149412
|
process.env[GOOGLE_APPLICATION_CREDENTIALS_ENV] = credentialsPath;
|
|
149178
149413
|
const projectId = readProjectIdFromVertexServiceAccountJson();
|
|
@@ -149184,6 +149419,7 @@ function materializeVertexCredentials(params) {
|
|
|
149184
149419
|
function cleanupVertexCredentials(credentials) {
|
|
149185
149420
|
if (!credentials) return;
|
|
149186
149421
|
rmSync2(credentials.secretDir, { recursive: true, force: true });
|
|
149422
|
+
delete process.env[GOOGLE_APPLICATION_CREDENTIALS_ENV];
|
|
149187
149423
|
}
|
|
149188
149424
|
function applyClaudeVertexEnv(env2) {
|
|
149189
149425
|
env2.CLAUDE_CODE_USE_VERTEX = "1";
|
|
@@ -149497,9 +149733,9 @@ async function installClaudeCli() {
|
|
|
149497
149733
|
});
|
|
149498
149734
|
}
|
|
149499
149735
|
function writeMcpConfig(ctx) {
|
|
149500
|
-
const configDir =
|
|
149736
|
+
const configDir = join13(ctx.tmpdir, ".claude");
|
|
149501
149737
|
mkdirSync5(configDir, { recursive: true });
|
|
149502
|
-
const configPath =
|
|
149738
|
+
const configPath = join13(configDir, "mcp.json");
|
|
149503
149739
|
writeFileSync9(
|
|
149504
149740
|
configPath,
|
|
149505
149741
|
JSON.stringify({
|
|
@@ -149925,8 +150161,8 @@ function installManagedSettings(ctx) {
|
|
|
149925
150161
|
if (process.env.CI !== "true") return;
|
|
149926
150162
|
const content = JSON.stringify(buildManagedSettings(ctx), null, 2);
|
|
149927
150163
|
try {
|
|
149928
|
-
|
|
149929
|
-
|
|
150164
|
+
execFileSync5("sudo", ["mkdir", "-p", MANAGED_SETTINGS_DIR]);
|
|
150165
|
+
execFileSync5("sudo", ["tee", MANAGED_SETTINGS_PATH], {
|
|
149930
150166
|
input: content,
|
|
149931
150167
|
stdio: ["pipe", "ignore", "pipe"]
|
|
149932
150168
|
});
|
|
@@ -149948,15 +150184,15 @@ var claude = agent({
|
|
|
149948
150184
|
const model = !specifier ? void 0 : isBedrockRoute ? specifier : isVertexRoute2 ? void 0 : stripProviderPrefix(specifier);
|
|
149949
150185
|
const homeEnv = {
|
|
149950
150186
|
HOME: ctx.tmpdir,
|
|
149951
|
-
XDG_CONFIG_HOME:
|
|
150187
|
+
XDG_CONFIG_HOME: join13(ctx.tmpdir, ".config")
|
|
149952
150188
|
};
|
|
149953
|
-
mkdirSync5(
|
|
150189
|
+
mkdirSync5(join13(homeEnv.XDG_CONFIG_HOME, "claude"), { recursive: true });
|
|
149954
150190
|
const agentBrowserVersion = getDevDependencyVersion("agent-browser");
|
|
149955
150191
|
addSkill({
|
|
149956
150192
|
ref: `vercel-labs/agent-browser@v${agentBrowserVersion}`,
|
|
149957
150193
|
skill: "agent-browser",
|
|
149958
150194
|
env: homeEnv,
|
|
149959
|
-
agent: "claude"
|
|
150195
|
+
agent: "claude-code"
|
|
149960
150196
|
});
|
|
149961
150197
|
installBundledSkills({ home: homeEnv.HOME });
|
|
149962
150198
|
const mcpConfigPath = writeMcpConfig(ctx);
|
|
@@ -150035,7 +150271,7 @@ var claude = agent({
|
|
|
150035
150271
|
// agents/opencode_v2.ts
|
|
150036
150272
|
var core2 = __toESM(require_core(), 1);
|
|
150037
150273
|
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync11 } from "node:fs";
|
|
150038
|
-
import { join as
|
|
150274
|
+
import { join as join15 } from "node:path";
|
|
150039
150275
|
import { performance as performance7 } from "node:perf_hooks";
|
|
150040
150276
|
|
|
150041
150277
|
// utils/agentHangReport.ts
|
|
@@ -150136,7 +150372,7 @@ function formatBillingExhaustedBody(diagnostic) {
|
|
|
150136
150372
|
// utils/codexHome.ts
|
|
150137
150373
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync10 } from "node:fs";
|
|
150138
150374
|
import { homedir as homedir2 } from "node:os";
|
|
150139
|
-
import { join as
|
|
150375
|
+
import { join as join14 } from "node:path";
|
|
150140
150376
|
var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
|
|
150141
150377
|
function installCodexAuth() {
|
|
150142
150378
|
const raw2 = process.env[CODEX_AUTH_ENV];
|
|
@@ -150146,9 +150382,9 @@ function installCodexAuth() {
|
|
|
150146
150382
|
log.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
|
|
150147
150383
|
return null;
|
|
150148
150384
|
}
|
|
150149
|
-
const xdgDataHome =
|
|
150150
|
-
const opencodeDir =
|
|
150151
|
-
const authPath =
|
|
150385
|
+
const xdgDataHome = join14(homedir2(), ".local", "share");
|
|
150386
|
+
const opencodeDir = join14(xdgDataHome, "opencode");
|
|
150387
|
+
const authPath = join14(opencodeDir, "auth.json");
|
|
150152
150388
|
const opencodeAuth = {
|
|
150153
150389
|
openai: {
|
|
150154
150390
|
type: "oauth",
|
|
@@ -150276,7 +150512,7 @@ export default async function pullfrogEventsPlugin() {
|
|
|
150276
150512
|
`;
|
|
150277
150513
|
|
|
150278
150514
|
// agents/opencodeShared.ts
|
|
150279
|
-
import { execFileSync as
|
|
150515
|
+
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
150280
150516
|
|
|
150281
150517
|
// agents/subagentModels.ts
|
|
150282
150518
|
function deriveSubagentModels(orchestratorSpec) {
|
|
@@ -150325,7 +150561,7 @@ async function installOpencodeCli(params) {
|
|
|
150325
150561
|
var AUTO_SELECT_WARNING = "select a model explicitly in the Pullfrog console (https://pullfrog.com/console) to avoid this.";
|
|
150326
150562
|
function getOpenCodeModels(cliPath) {
|
|
150327
150563
|
try {
|
|
150328
|
-
const output =
|
|
150564
|
+
const output = execFileSync6(cliPath, ["models"], {
|
|
150329
150565
|
encoding: "utf-8",
|
|
150330
150566
|
timeout: 3e4,
|
|
150331
150567
|
env: process.env
|
|
@@ -150822,13 +151058,13 @@ var opencode = agent({
|
|
|
150822
151058
|
const model = vertexModel ?? (isBedrockRoute ? `amazon-bedrock/${rawModel}` : rawModel);
|
|
150823
151059
|
const homeEnv = {
|
|
150824
151060
|
HOME: ctx.tmpdir,
|
|
150825
|
-
XDG_CONFIG_HOME:
|
|
151061
|
+
XDG_CONFIG_HOME: join15(ctx.tmpdir, ".config")
|
|
150826
151062
|
};
|
|
150827
|
-
mkdirSync7(
|
|
150828
|
-
const opencodePluginDir =
|
|
151063
|
+
mkdirSync7(join15(homeEnv.XDG_CONFIG_HOME, "opencode"), { recursive: true });
|
|
151064
|
+
const opencodePluginDir = join15(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
|
|
150829
151065
|
mkdirSync7(opencodePluginDir, { recursive: true });
|
|
150830
151066
|
writeFileSync11(
|
|
150831
|
-
|
|
151067
|
+
join15(opencodePluginDir, PULLFROG_OPENCODE_PLUGIN_FILENAME),
|
|
150832
151068
|
PULLFROG_OPENCODE_PLUGIN_SOURCE
|
|
150833
151069
|
);
|
|
150834
151070
|
const agentBrowserVersion = getDevDependencyVersion("agent-browser");
|
|
@@ -151190,7 +151426,7 @@ async function fetchBodyHtml(ctx) {
|
|
|
151190
151426
|
}
|
|
151191
151427
|
|
|
151192
151428
|
// utils/byokFallback.ts
|
|
151193
|
-
var FREE_FALLBACK_SLUG = "opencode/
|
|
151429
|
+
var FREE_FALLBACK_SLUG = "opencode/big-pickle";
|
|
151194
151430
|
function selectFallbackModelIfNeeded(input) {
|
|
151195
151431
|
if (input.proxyModel) return { fallback: false };
|
|
151196
151432
|
if (!input.resolvedModel) return { fallback: false };
|
|
@@ -151208,7 +151444,7 @@ function selectFallbackModelIfNeeded(input) {
|
|
|
151208
151444
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
151209
151445
|
import { writeFileSync as writeFileSync12 } from "node:fs";
|
|
151210
151446
|
import { createServer as createServer2 } from "node:http";
|
|
151211
|
-
import { join as
|
|
151447
|
+
import { join as join16 } from "node:path";
|
|
151212
151448
|
var CODE_TTL_MS = 5 * 60 * 1e3;
|
|
151213
151449
|
var TAMPER_WINDOW_MS = 6e4;
|
|
151214
151450
|
function revokeGitHubToken(token) {
|
|
@@ -151280,7 +151516,7 @@ async function startGitAuthServer(tmpdir4) {
|
|
|
151280
151516
|
function writeAskpassScript(code) {
|
|
151281
151517
|
const scriptId = randomUUID4();
|
|
151282
151518
|
const scriptName = `askpass-${scriptId}.js`;
|
|
151283
|
-
const scriptPath =
|
|
151519
|
+
const scriptPath = join16(tmpdir4, scriptName);
|
|
151284
151520
|
const content = [
|
|
151285
151521
|
`#!/usr/bin/env node`,
|
|
151286
151522
|
`var a=process.argv[2]||"";`,
|
|
@@ -151319,7 +151555,7 @@ async function startGitAuthServer(tmpdir4) {
|
|
|
151319
151555
|
var core3 = __toESM(require_core(), 1);
|
|
151320
151556
|
import { createSign } from "node:crypto";
|
|
151321
151557
|
import { rename, writeFile } from "node:fs/promises";
|
|
151322
|
-
import { dirname as dirname3, join as
|
|
151558
|
+
import { dirname as dirname3, join as join17 } from "node:path";
|
|
151323
151559
|
|
|
151324
151560
|
// node_modules/.pnpm/@octokit+plugin-throttling@11.0.3_@octokit+core@7.0.5/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
|
|
151325
151561
|
var import_light = __toESM(require_light(), 1);
|
|
@@ -155177,7 +155413,7 @@ function getGitHubUsageSummary() {
|
|
|
155177
155413
|
}
|
|
155178
155414
|
async function writeGitHubUsageSummaryToFile(path3) {
|
|
155179
155415
|
const summary2 = getGitHubUsageSummary();
|
|
155180
|
-
const tmpPath =
|
|
155416
|
+
const tmpPath = join17(dirname3(path3), `.usage-summary-${process.pid}.tmp`);
|
|
155181
155417
|
await writeFile(tmpPath, JSON.stringify(summary2));
|
|
155182
155418
|
await rename(tmpPath, path3);
|
|
155183
155419
|
}
|
|
@@ -155228,7 +155464,7 @@ function createOctokit(token) {
|
|
|
155228
155464
|
}
|
|
155229
155465
|
|
|
155230
155466
|
// utils/instructions.ts
|
|
155231
|
-
import { execSync as
|
|
155467
|
+
import { execSync as execSync3 } from "node:child_process";
|
|
155232
155468
|
function buildRuntimeContext(ctx) {
|
|
155233
155469
|
const {
|
|
155234
155470
|
"~pullfrog": _2,
|
|
@@ -155240,7 +155476,7 @@ function buildRuntimeContext(ctx) {
|
|
|
155240
155476
|
} = ctx.payload;
|
|
155241
155477
|
let gitStatus;
|
|
155242
155478
|
try {
|
|
155243
|
-
gitStatus =
|
|
155479
|
+
gitStatus = execSync3("git status --short", { encoding: "utf-8", stdio: "pipe" }).trim() || "(clean)";
|
|
155244
155480
|
} catch {
|
|
155245
155481
|
}
|
|
155246
155482
|
const data = {
|
|
@@ -155577,7 +155813,7 @@ function resolveInstructions(ctx) {
|
|
|
155577
155813
|
|
|
155578
155814
|
// utils/learnings.ts
|
|
155579
155815
|
import { mkdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
155580
|
-
import { dirname as dirname4, join as
|
|
155816
|
+
import { dirname as dirname4, join as join18 } from "node:path";
|
|
155581
155817
|
|
|
155582
155818
|
// utils/learningsTruncate.ts
|
|
155583
155819
|
var MAX_LEARNINGS_LENGTH = 1e5;
|
|
@@ -155594,7 +155830,7 @@ function truncateAtLineBoundary(body, cap) {
|
|
|
155594
155830
|
// utils/learnings.ts
|
|
155595
155831
|
var LEARNINGS_FILE_NAME = "pullfrog-learnings.md";
|
|
155596
155832
|
function learningsFilePath(tmpdir4) {
|
|
155597
|
-
return
|
|
155833
|
+
return join18(tmpdir4, LEARNINGS_FILE_NAME);
|
|
155598
155834
|
}
|
|
155599
155835
|
async function seedLearningsFile(params) {
|
|
155600
155836
|
const path3 = learningsFilePath(params.tmpdir);
|
|
@@ -156274,7 +156510,7 @@ async function runProxyResolution(ctx) {
|
|
|
156274
156510
|
|
|
156275
156511
|
// utils/prSummary.ts
|
|
156276
156512
|
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
156277
|
-
import { dirname as dirname5, join as
|
|
156513
|
+
import { dirname as dirname5, join as join19 } from "node:path";
|
|
156278
156514
|
var SUMMARY_FILE_NAME = "pullfrog-summary.md";
|
|
156279
156515
|
var SUMMARY_SCAFFOLD = `# PR summary
|
|
156280
156516
|
|
|
@@ -156284,7 +156520,7 @@ var SUMMARY_SCAFFOLD = `# PR summary
|
|
|
156284
156520
|
var MIN_SNAPSHOT_LENGTH = 60;
|
|
156285
156521
|
var MAX_SNAPSHOT_LENGTH = 32768;
|
|
156286
156522
|
function summaryFilePath(tmpdir4) {
|
|
156287
|
-
return
|
|
156523
|
+
return join19(tmpdir4, SUMMARY_FILE_NAME);
|
|
156288
156524
|
}
|
|
156289
156525
|
async function seedSummaryFile(params) {
|
|
156290
156526
|
const path3 = summaryFilePath(params.tmpdir);
|
|
@@ -156478,6 +156714,16 @@ async function resolveRunContextData(params) {
|
|
|
156478
156714
|
}
|
|
156479
156715
|
|
|
156480
156716
|
// utils/runErrorRenderer.ts
|
|
156717
|
+
function isProviderModelNotFoundError(message) {
|
|
156718
|
+
return message.includes("ProviderModelNotFoundError");
|
|
156719
|
+
}
|
|
156720
|
+
function formatProviderModelNotFoundSummary(input) {
|
|
156721
|
+
return `Pullfrog's free fallback model is no longer available in OpenCode's catalog. Add an API key for your configured model in the Pullfrog console for \`${input.owner}/${input.name}\`, or contact support if this persists.
|
|
156722
|
+
|
|
156723
|
+
\`\`\`
|
|
156724
|
+
${input.raw}
|
|
156725
|
+
\`\`\``;
|
|
156726
|
+
}
|
|
156481
156727
|
function renderRunError(input) {
|
|
156482
156728
|
const billingError = isRouterKeylimitExhaustedError(input.errorMessage) ? new BillingError(input.errorMessage, { code: "router_keylimit_exhausted" }) : null;
|
|
156483
156729
|
if (billingError) {
|
|
@@ -156499,6 +156745,14 @@ function renderRunError(input) {
|
|
|
156499
156745
|
if (apiKeyErrorSummary) {
|
|
156500
156746
|
return { summary: apiKeyErrorSummary, comment: apiKeyErrorSummary };
|
|
156501
156747
|
}
|
|
156748
|
+
if (isProviderModelNotFoundError(input.errorMessage)) {
|
|
156749
|
+
const body = formatProviderModelNotFoundSummary({
|
|
156750
|
+
owner: input.repo.owner,
|
|
156751
|
+
name: input.repo.name,
|
|
156752
|
+
raw: input.errorMessage
|
|
156753
|
+
});
|
|
156754
|
+
return { summary: body, comment: body };
|
|
156755
|
+
}
|
|
156502
156756
|
if (hangBody) {
|
|
156503
156757
|
return {
|
|
156504
156758
|
summary: `### \u274C Pullfrog failed
|
|
@@ -156600,16 +156854,17 @@ async function persistRunArtifacts(toolContext) {
|
|
|
156600
156854
|
}
|
|
156601
156855
|
async function finalizeSuccessRun(input) {
|
|
156602
156856
|
await persistRunArtifacts(input.toolContext);
|
|
156603
|
-
|
|
156604
|
-
|
|
156605
|
-
|
|
156606
|
-
|
|
156607
|
-
|
|
156608
|
-
|
|
156609
|
-
|
|
156610
|
-
|
|
156611
|
-
|
|
156612
|
-
|
|
156857
|
+
const rendered = !input.result.success ? renderRunError({
|
|
156858
|
+
errorMessage: input.result.error || "agent run failed",
|
|
156859
|
+
repo: input.repo,
|
|
156860
|
+
agentDiagnostic: input.toolState.agentDiagnostic
|
|
156861
|
+
}) : null;
|
|
156862
|
+
if (rendered && input.toolState.progressComment) {
|
|
156863
|
+
await reportErrorToComment({ toolState: input.toolState, error: rendered.comment }).catch(
|
|
156864
|
+
(error49) => {
|
|
156865
|
+
log.debug(`failure error report failed: ${error49}`);
|
|
156866
|
+
}
|
|
156867
|
+
);
|
|
156613
156868
|
}
|
|
156614
156869
|
if (input.result.success && input.toolState.progressComment && !input.toolState.finalSummaryWritten) {
|
|
156615
156870
|
await deleteProgressComment(input.toolContext).catch((error49) => {
|
|
@@ -156619,7 +156874,7 @@ async function finalizeSuccessRun(input) {
|
|
|
156619
156874
|
try {
|
|
156620
156875
|
const usageSummary = formatUsageSummary(input.toolState.usageEntries);
|
|
156621
156876
|
const body = input.toolState.lastProgressBody || input.result.output;
|
|
156622
|
-
const parts = [body, usageSummary].filter(Boolean);
|
|
156877
|
+
const parts = [rendered?.summary, body, usageSummary].filter(Boolean);
|
|
156623
156878
|
if (parts.length > 0) {
|
|
156624
156879
|
await writeSummary(parts.join("\n\n"));
|
|
156625
156880
|
}
|
|
@@ -156704,116 +156959,6 @@ function logRunStartup(ctx) {
|
|
|
156704
156959
|
log.info(`\xBB timeout: ${resolveTimeoutForLog(ctx.payload.timeout)}`);
|
|
156705
156960
|
}
|
|
156706
156961
|
|
|
156707
|
-
// utils/setup.ts
|
|
156708
|
-
import { execFileSync as execFileSync6, execSync as execSync3 } from "node:child_process";
|
|
156709
|
-
import { mkdtempSync as mkdtempSync2 } from "node:fs";
|
|
156710
|
-
import { tmpdir as tmpdir3 } from "node:os";
|
|
156711
|
-
import { join as join19 } from "node:path";
|
|
156712
|
-
function createTempDirectory() {
|
|
156713
|
-
const sharedTempDir = mkdtempSync2(join19(tmpdir3(), "pullfrog-"));
|
|
156714
|
-
process.env.PULLFROG_TEMP_DIR = sharedTempDir;
|
|
156715
|
-
log.info(`\xBB created temp dir at ${sharedTempDir}`);
|
|
156716
|
-
return sharedTempDir;
|
|
156717
|
-
}
|
|
156718
|
-
function envScopedToRepo() {
|
|
156719
|
-
const scoped = { ...process.env };
|
|
156720
|
-
for (const key of Object.keys(scoped)) {
|
|
156721
|
-
if (key.startsWith("GIT_")) delete scoped[key];
|
|
156722
|
-
}
|
|
156723
|
-
return scoped;
|
|
156724
|
-
}
|
|
156725
|
-
function removeIncludeIfEntries(repoDir) {
|
|
156726
|
-
const env2 = envScopedToRepo();
|
|
156727
|
-
let configOutput;
|
|
156728
|
-
try {
|
|
156729
|
-
configOutput = execSync3("git config --local --get-regexp -z ^includeif\\.", {
|
|
156730
|
-
cwd: repoDir,
|
|
156731
|
-
encoding: "utf-8",
|
|
156732
|
-
stdio: "pipe",
|
|
156733
|
-
env: env2
|
|
156734
|
-
});
|
|
156735
|
-
} catch {
|
|
156736
|
-
log.debug("\xBB no includeIf credential entries to remove");
|
|
156737
|
-
return;
|
|
156738
|
-
}
|
|
156739
|
-
const seen = /* @__PURE__ */ new Set();
|
|
156740
|
-
for (const entry of configOutput.split("\0")) {
|
|
156741
|
-
if (!entry) continue;
|
|
156742
|
-
const nl = entry.indexOf("\n");
|
|
156743
|
-
const key = nl === -1 ? entry : entry.slice(0, nl);
|
|
156744
|
-
if (!key || seen.has(key)) continue;
|
|
156745
|
-
seen.add(key);
|
|
156746
|
-
try {
|
|
156747
|
-
execFileSync6("git", ["config", "--local", "--unset-all", key], {
|
|
156748
|
-
cwd: repoDir,
|
|
156749
|
-
stdio: "pipe",
|
|
156750
|
-
env: env2
|
|
156751
|
-
});
|
|
156752
|
-
} catch (error49) {
|
|
156753
|
-
log.debug(
|
|
156754
|
-
`\xBB failed to unset ${key}: ${error49 instanceof Error ? error49.message : String(error49)}`
|
|
156755
|
-
);
|
|
156756
|
-
}
|
|
156757
|
-
}
|
|
156758
|
-
if (seen.size > 0)
|
|
156759
|
-
log.info(
|
|
156760
|
-
`\xBB removed ${seen.size} includeIf credential ${seen.size === 1 ? "entry" : "entries"}`
|
|
156761
|
-
);
|
|
156762
|
-
}
|
|
156763
|
-
async function setupGit(params) {
|
|
156764
|
-
const repoDir = process.cwd();
|
|
156765
|
-
log.info("\xBB setting up git configuration...");
|
|
156766
|
-
try {
|
|
156767
|
-
let currentEmail = "";
|
|
156768
|
-
try {
|
|
156769
|
-
currentEmail = execSync3("git config user.email", {
|
|
156770
|
-
cwd: repoDir,
|
|
156771
|
-
stdio: "pipe",
|
|
156772
|
-
encoding: "utf-8"
|
|
156773
|
-
}).trim();
|
|
156774
|
-
} catch {
|
|
156775
|
-
}
|
|
156776
|
-
const shouldSetDefaults = !currentEmail || currentEmail === "github-actions[bot]@users.noreply.github.com";
|
|
156777
|
-
if (shouldSetDefaults) {
|
|
156778
|
-
execSync3('git config --local user.email "226033991+pullfrog[bot]@users.noreply.github.com"', {
|
|
156779
|
-
cwd: repoDir,
|
|
156780
|
-
stdio: "pipe"
|
|
156781
|
-
});
|
|
156782
|
-
execSync3('git config --local user.name "pullfrog[bot]"', {
|
|
156783
|
-
cwd: repoDir,
|
|
156784
|
-
stdio: "pipe"
|
|
156785
|
-
});
|
|
156786
|
-
log.debug("\xBB git user configured (using defaults)");
|
|
156787
|
-
} else {
|
|
156788
|
-
log.debug(`\xBB git user already configured (${currentEmail}), skipping`);
|
|
156789
|
-
}
|
|
156790
|
-
if (params.shell === "disabled") {
|
|
156791
|
-
execSync3("git config --local core.hooksPath /dev/null", {
|
|
156792
|
-
cwd: repoDir,
|
|
156793
|
-
stdio: "pipe"
|
|
156794
|
-
});
|
|
156795
|
-
log.debug("\xBB git hooks disabled (shell=disabled)");
|
|
156796
|
-
}
|
|
156797
|
-
} catch (error49) {
|
|
156798
|
-
log.info(`Failed to set git config: ${error49 instanceof Error ? error49.message : String(error49)}`);
|
|
156799
|
-
}
|
|
156800
|
-
try {
|
|
156801
|
-
execSync3("git config --local --unset-all http.https://github.com/.extraheader", {
|
|
156802
|
-
cwd: repoDir,
|
|
156803
|
-
stdio: "pipe"
|
|
156804
|
-
});
|
|
156805
|
-
log.info("\xBB removed existing authentication headers");
|
|
156806
|
-
} catch {
|
|
156807
|
-
log.debug("\xBB no existing authentication headers to remove");
|
|
156808
|
-
}
|
|
156809
|
-
removeIncludeIfEntries(repoDir);
|
|
156810
|
-
const originUrl = `https://github.com/${params.owner}/${params.name}.git`;
|
|
156811
|
-
$2("git", ["remote", "set-url", "origin", originUrl], { cwd: repoDir });
|
|
156812
|
-
params.toolState.pushUrl = originUrl;
|
|
156813
|
-
$2("git", ["config", "--local", "credential.helper", ""], { cwd: repoDir });
|
|
156814
|
-
log.info("\xBB git authentication configured");
|
|
156815
|
-
}
|
|
156816
|
-
|
|
156817
156962
|
// utils/todoTracking.ts
|
|
156818
156963
|
function isValidTodoStatus(value2) {
|
|
156819
156964
|
return value2 === "pending" || value2 === "in_progress" || value2 === "completed" || value2 === "cancelled";
|
|
@@ -157021,6 +157166,7 @@ async function main() {
|
|
|
157021
157166
|
toolState.beforeSha = payload.event.before_sha;
|
|
157022
157167
|
}
|
|
157023
157168
|
const tokenRef = __using(_stack2, await resolveTokens({ push: payload.push }), true);
|
|
157169
|
+
wipeRunnerLeakSurface();
|
|
157024
157170
|
const oidcCredentials = process.env.ACTIONS_ID_TOKEN_REQUEST_URL && process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN ? {
|
|
157025
157171
|
requestUrl: process.env.ACTIONS_ID_TOKEN_REQUEST_URL,
|
|
157026
157172
|
requestToken: process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN
|
|
@@ -157190,7 +157336,7 @@ ${instructions.user}` : null,
|
|
|
157190
157336
|
});
|
|
157191
157337
|
if (agentId === "opencode") {
|
|
157192
157338
|
const pluginDir = join20(process.cwd(), ".opencode", "plugin");
|
|
157193
|
-
const hasPlugins = existsSync7(pluginDir) &&
|
|
157339
|
+
const hasPlugins = existsSync7(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
|
|
157194
157340
|
if (hasPlugins && toolState.dependencyInstallation?.promise) {
|
|
157195
157341
|
log.info(
|
|
157196
157342
|
"\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"
|
|
@@ -158238,7 +158384,7 @@ async function run2() {
|
|
|
158238
158384
|
}
|
|
158239
158385
|
|
|
158240
158386
|
// cli.ts
|
|
158241
|
-
var VERSION10 = "0.1.
|
|
158387
|
+
var VERSION10 = "0.1.12";
|
|
158242
158388
|
var bin = basename2(process.argv[1] || "");
|
|
158243
158389
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
158244
158390
|
var rawArgs = process.argv.slice(2);
|