pullfrog 0.1.11 → 0.1.13
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 +348 -196
- package/dist/external.d.ts +1 -1
- package/dist/index.js +347 -195
- 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.13",
|
|
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";
|
|
@@ -145103,7 +145120,13 @@ var TRANSIENT_PATTERNS = [
|
|
|
145103
145120
|
/HTTP 5\d\d/,
|
|
145104
145121
|
/returned error: 5\d\d/i,
|
|
145105
145122
|
/HTTP 429/,
|
|
145106
|
-
/returned error: 429/i
|
|
145123
|
+
/returned error: 429/i,
|
|
145124
|
+
// github installation tokens can 401 for seconds after minting while
|
|
145125
|
+
// replicating (@octokit/auth-app retries the same class). git push
|
|
145126
|
+
// surfaces it as "Invalid username or token", distinct from 403
|
|
145127
|
+
// permission denied — safe to backoff-retry with the same token.
|
|
145128
|
+
/Invalid username or token/,
|
|
145129
|
+
/Authentication failed for 'https:\/\/github\.com\//
|
|
145107
145130
|
];
|
|
145108
145131
|
function classifyPushError(msg) {
|
|
145109
145132
|
if (CONCURRENT_PUSH_PATTERNS.some((p2) => msg.includes(p2))) return "concurrent-push";
|
|
@@ -145943,6 +145966,183 @@ async function reportReviewNodeId(ctx, params) {
|
|
|
145943
145966
|
await patchWorkflowRunFields(ctx, { reviewNodeId: params.nodeId });
|
|
145944
145967
|
}
|
|
145945
145968
|
|
|
145969
|
+
// utils/setup.ts
|
|
145970
|
+
import { execFileSync as execFileSync4, execSync as execSync2 } from "node:child_process";
|
|
145971
|
+
import { mkdtempSync as mkdtempSync2, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
|
|
145972
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
145973
|
+
import { join as join4 } from "node:path";
|
|
145974
|
+
function createTempDirectory() {
|
|
145975
|
+
const sharedTempDir = mkdtempSync2(join4(tmpdir2(), "pullfrog-"));
|
|
145976
|
+
process.env.PULLFROG_TEMP_DIR = sharedTempDir;
|
|
145977
|
+
log.info(`\xBB created temp dir at ${sharedTempDir}`);
|
|
145978
|
+
return sharedTempDir;
|
|
145979
|
+
}
|
|
145980
|
+
function wipeRunnerLeakSurface() {
|
|
145981
|
+
const runnerTemp = process.env.RUNNER_TEMP;
|
|
145982
|
+
if (!runnerTemp) return;
|
|
145983
|
+
const preserve = /* @__PURE__ */ new Set();
|
|
145984
|
+
for (const envVar of [
|
|
145985
|
+
"GITHUB_OUTPUT",
|
|
145986
|
+
"GITHUB_ENV",
|
|
145987
|
+
"GITHUB_PATH",
|
|
145988
|
+
"GITHUB_STATE",
|
|
145989
|
+
"GITHUB_STEP_SUMMARY"
|
|
145990
|
+
]) {
|
|
145991
|
+
const path3 = process.env[envVar];
|
|
145992
|
+
if (!path3) continue;
|
|
145993
|
+
try {
|
|
145994
|
+
preserve.add(realpathSync2(path3));
|
|
145995
|
+
} catch {
|
|
145996
|
+
preserve.add(path3);
|
|
145997
|
+
}
|
|
145998
|
+
}
|
|
145999
|
+
const wiped = [];
|
|
146000
|
+
const tryUnlink = (path3) => {
|
|
146001
|
+
let resolved = path3;
|
|
146002
|
+
try {
|
|
146003
|
+
resolved = realpathSync2(path3);
|
|
146004
|
+
} catch {
|
|
146005
|
+
}
|
|
146006
|
+
if (preserve.has(resolved) || preserve.has(path3)) return;
|
|
146007
|
+
try {
|
|
146008
|
+
unlinkSync2(path3);
|
|
146009
|
+
wiped.push(path3);
|
|
146010
|
+
} catch {
|
|
146011
|
+
}
|
|
146012
|
+
};
|
|
146013
|
+
const listDir = (dir) => {
|
|
146014
|
+
try {
|
|
146015
|
+
return readdirSync(dir);
|
|
146016
|
+
} catch {
|
|
146017
|
+
return [];
|
|
146018
|
+
}
|
|
146019
|
+
};
|
|
146020
|
+
const fileCommandsDir = join4(runnerTemp, "_runner_file_commands");
|
|
146021
|
+
for (const entry of listDir(fileCommandsDir)) {
|
|
146022
|
+
tryUnlink(join4(fileCommandsDir, entry));
|
|
146023
|
+
}
|
|
146024
|
+
for (const entry of listDir(runnerTemp)) {
|
|
146025
|
+
if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
|
|
146026
|
+
tryUnlink(join4(runnerTemp, entry));
|
|
146027
|
+
}
|
|
146028
|
+
}
|
|
146029
|
+
if (wiped.length > 0) {
|
|
146030
|
+
log.info(`\xBB wiped ${wiped.length} leak-surface file(s) from $RUNNER_TEMP`);
|
|
146031
|
+
log.debug(`\xBB wiped paths: ${wiped.join(", ")}`);
|
|
146032
|
+
}
|
|
146033
|
+
}
|
|
146034
|
+
function envScopedToRepo() {
|
|
146035
|
+
const scoped = { ...process.env };
|
|
146036
|
+
for (const key of Object.keys(scoped)) {
|
|
146037
|
+
if (key.startsWith("GIT_")) delete scoped[key];
|
|
146038
|
+
}
|
|
146039
|
+
return scoped;
|
|
146040
|
+
}
|
|
146041
|
+
function removeIncludeIfEntries(repoDir) {
|
|
146042
|
+
const env2 = envScopedToRepo();
|
|
146043
|
+
let configOutput;
|
|
146044
|
+
try {
|
|
146045
|
+
configOutput = execSync2("git config --local --get-regexp -z ^includeif\\.", {
|
|
146046
|
+
cwd: repoDir,
|
|
146047
|
+
encoding: "utf-8",
|
|
146048
|
+
stdio: "pipe",
|
|
146049
|
+
env: env2
|
|
146050
|
+
});
|
|
146051
|
+
} catch {
|
|
146052
|
+
log.debug("\xBB no includeIf credential entries to remove");
|
|
146053
|
+
return;
|
|
146054
|
+
}
|
|
146055
|
+
const seen = /* @__PURE__ */ new Set();
|
|
146056
|
+
for (const entry of configOutput.split("\0")) {
|
|
146057
|
+
if (!entry) continue;
|
|
146058
|
+
const nl = entry.indexOf("\n");
|
|
146059
|
+
const key = nl === -1 ? entry : entry.slice(0, nl);
|
|
146060
|
+
if (!key || seen.has(key)) continue;
|
|
146061
|
+
seen.add(key);
|
|
146062
|
+
try {
|
|
146063
|
+
execFileSync4("git", ["config", "--local", "--unset-all", key], {
|
|
146064
|
+
cwd: repoDir,
|
|
146065
|
+
stdio: "pipe",
|
|
146066
|
+
env: env2
|
|
146067
|
+
});
|
|
146068
|
+
} catch (error49) {
|
|
146069
|
+
log.debug(
|
|
146070
|
+
`\xBB failed to unset ${key}: ${error49 instanceof Error ? error49.message : String(error49)}`
|
|
146071
|
+
);
|
|
146072
|
+
}
|
|
146073
|
+
}
|
|
146074
|
+
if (seen.size > 0)
|
|
146075
|
+
log.info(
|
|
146076
|
+
`\xBB removed ${seen.size} includeIf credential ${seen.size === 1 ? "entry" : "entries"}`
|
|
146077
|
+
);
|
|
146078
|
+
}
|
|
146079
|
+
async function setupGit(params) {
|
|
146080
|
+
const repoDir = process.cwd();
|
|
146081
|
+
log.info("\xBB setting up git configuration...");
|
|
146082
|
+
try {
|
|
146083
|
+
let currentEmail = "";
|
|
146084
|
+
try {
|
|
146085
|
+
currentEmail = execSync2("git config user.email", {
|
|
146086
|
+
cwd: repoDir,
|
|
146087
|
+
stdio: "pipe",
|
|
146088
|
+
encoding: "utf-8"
|
|
146089
|
+
}).trim();
|
|
146090
|
+
} catch {
|
|
146091
|
+
}
|
|
146092
|
+
const shouldSetDefaults = !currentEmail || currentEmail === "github-actions[bot]@users.noreply.github.com";
|
|
146093
|
+
if (shouldSetDefaults) {
|
|
146094
|
+
execSync2('git config --local user.email "226033991+pullfrog[bot]@users.noreply.github.com"', {
|
|
146095
|
+
cwd: repoDir,
|
|
146096
|
+
stdio: "pipe"
|
|
146097
|
+
});
|
|
146098
|
+
execSync2('git config --local user.name "pullfrog[bot]"', {
|
|
146099
|
+
cwd: repoDir,
|
|
146100
|
+
stdio: "pipe"
|
|
146101
|
+
});
|
|
146102
|
+
log.debug("\xBB git user configured (using defaults)");
|
|
146103
|
+
} else {
|
|
146104
|
+
log.debug(`\xBB git user already configured (${currentEmail}), skipping`);
|
|
146105
|
+
}
|
|
146106
|
+
if (params.shell === "disabled") {
|
|
146107
|
+
execSync2("git config --local core.hooksPath /dev/null", {
|
|
146108
|
+
cwd: repoDir,
|
|
146109
|
+
stdio: "pipe"
|
|
146110
|
+
});
|
|
146111
|
+
log.debug("\xBB git hooks disabled (shell=disabled)");
|
|
146112
|
+
}
|
|
146113
|
+
} catch (error49) {
|
|
146114
|
+
log.info(`Failed to set git config: ${error49 instanceof Error ? error49.message : String(error49)}`);
|
|
146115
|
+
}
|
|
146116
|
+
try {
|
|
146117
|
+
execSync2("git config --local --unset-all http.https://github.com/.extraheader", {
|
|
146118
|
+
cwd: repoDir,
|
|
146119
|
+
stdio: "pipe"
|
|
146120
|
+
});
|
|
146121
|
+
log.info("\xBB removed existing authentication headers");
|
|
146122
|
+
} catch {
|
|
146123
|
+
log.debug("\xBB no existing authentication headers to remove");
|
|
146124
|
+
}
|
|
146125
|
+
removeIncludeIfEntries(repoDir);
|
|
146126
|
+
const originUrl = `https://github.com/${params.owner}/${params.name}.git`;
|
|
146127
|
+
$2("git", ["remote", "set-url", "origin", originUrl], { cwd: repoDir });
|
|
146128
|
+
params.toolState.pushUrl = originUrl;
|
|
146129
|
+
$2("git", ["config", "--local", "credential.helper", ""], { cwd: repoDir });
|
|
146130
|
+
params.toolState.initialHead = captureInitialHead(repoDir);
|
|
146131
|
+
log.info("\xBB git authentication configured");
|
|
146132
|
+
}
|
|
146133
|
+
function captureInitialHead(repoDir) {
|
|
146134
|
+
try {
|
|
146135
|
+
const name = $2("git", ["symbolic-ref", "--short", "HEAD"], {
|
|
146136
|
+
cwd: repoDir,
|
|
146137
|
+
log: false
|
|
146138
|
+
}).trim();
|
|
146139
|
+
if (name) return { kind: "branch", name };
|
|
146140
|
+
} catch {
|
|
146141
|
+
}
|
|
146142
|
+
const sha = $2("git", ["rev-parse", "HEAD"], { cwd: repoDir, log: false }).trim();
|
|
146143
|
+
return { kind: "detached", sha };
|
|
146144
|
+
}
|
|
146145
|
+
|
|
145946
146146
|
// mcp/checkout.ts
|
|
145947
146147
|
function formatFilesWithLineNumbers(files) {
|
|
145948
146148
|
const output = [];
|
|
@@ -146117,7 +146317,7 @@ function cleanupStaleGitLocks() {
|
|
|
146117
146317
|
}
|
|
146118
146318
|
if (now - mtimeMs < STALE_LOCK_AGE_MS) continue;
|
|
146119
146319
|
try {
|
|
146120
|
-
|
|
146320
|
+
unlinkSync3(relPath);
|
|
146121
146321
|
log.warning(`\xBB removed stale ${relPath} from prior run`);
|
|
146122
146322
|
} catch (e) {
|
|
146123
146323
|
log.debug(
|
|
@@ -146276,6 +146476,15 @@ async function checkoutPrBranch(pr, params) {
|
|
|
146276
146476
|
return { hookWarning: postCheckoutHook.warning };
|
|
146277
146477
|
}
|
|
146278
146478
|
var inFlightCheckouts = /* @__PURE__ */ new Map();
|
|
146479
|
+
function headsEqual(a, b) {
|
|
146480
|
+
if (a.kind === "branch" && b.kind === "branch") return a.name === b.name;
|
|
146481
|
+
if (a.kind === "detached" && b.kind === "detached") return a.sha === b.sha;
|
|
146482
|
+
return false;
|
|
146483
|
+
}
|
|
146484
|
+
function describeHead(h) {
|
|
146485
|
+
if (h.kind === "branch") return `branch \`${h.name}\``;
|
|
146486
|
+
return `detached HEAD \`${h.sha}\``;
|
|
146487
|
+
}
|
|
146279
146488
|
function CheckoutPrTool(ctx) {
|
|
146280
146489
|
const runCheckout = async (pull_number) => {
|
|
146281
146490
|
const prResponse = await ctx.octokit.rest.pulls.get({
|
|
@@ -146322,7 +146531,7 @@ function CheckoutPrTool(ctx) {
|
|
|
146322
146531
|
headSha: ctx.toolState.checkoutSha
|
|
146323
146532
|
});
|
|
146324
146533
|
if (incremental) {
|
|
146325
|
-
incrementalDiffPath =
|
|
146534
|
+
incrementalDiffPath = join5(
|
|
146326
146535
|
tempDir,
|
|
146327
146536
|
`pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
|
|
146328
146537
|
);
|
|
@@ -146336,7 +146545,7 @@ function CheckoutPrTool(ctx) {
|
|
|
146336
146545
|
const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
|
|
146337
146546
|
log.debug(`formatted diff preview (first 100 lines):
|
|
146338
146547
|
${diffPreview}`);
|
|
146339
|
-
const diffPath =
|
|
146548
|
+
const diffPath = join5(tempDir, `pr-${pull_number}-${headShort}.diff`);
|
|
146340
146549
|
writeFileSync2(diffPath, formatResult.content);
|
|
146341
146550
|
log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
|
|
146342
146551
|
ctx.toolState.diffCoverage = createDiffCoverageState({
|
|
@@ -146403,7 +146612,8 @@ ${diffPreview}`);
|
|
|
146403
146612
|
};
|
|
146404
146613
|
return tool({
|
|
146405
146614
|
name: "checkout_pr",
|
|
146406
|
-
|
|
146615
|
+
timeoutMs: 6e5,
|
|
146616
|
+
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
146617
|
parameters: CheckoutPr,
|
|
146408
146618
|
execute: execute(async ({ pull_number }) => {
|
|
146409
146619
|
const inFlight = inFlightCheckouts.get(pull_number);
|
|
@@ -146411,13 +146621,23 @@ ${diffPreview}`);
|
|
|
146411
146621
|
log.info(`\xBB checkout_pr({pull_number:${pull_number}}) already in flight \u2014 sharing result`);
|
|
146412
146622
|
return inFlight;
|
|
146413
146623
|
}
|
|
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:
|
|
146624
|
+
const dirty = $2("git", ["status", "--porcelain"], { log: false }).trim();
|
|
146625
|
+
if (dirty) {
|
|
146626
|
+
throw new Error(
|
|
146627
|
+
`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
146628
|
${dirty}`
|
|
146629
|
+
);
|
|
146630
|
+
}
|
|
146631
|
+
const initialHead = ctx.toolState.initialHead;
|
|
146632
|
+
if (initialHead) {
|
|
146633
|
+
const currentHead = captureInitialHead(process.cwd());
|
|
146634
|
+
const targetBranch = `pr-${pull_number}`;
|
|
146635
|
+
const onTarget = currentHead.kind === "branch" && currentHead.name === targetBranch;
|
|
146636
|
+
const onInitial = headsEqual(currentHead, initialHead);
|
|
146637
|
+
if (!onTarget && !onInitial) {
|
|
146638
|
+
const recoverCmd = initialHead.kind === "branch" ? `git checkout ${initialHead.name}` : `git checkout ${initialHead.sha}`;
|
|
146639
|
+
throw new Error(
|
|
146640
|
+
`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
146641
|
);
|
|
146422
146642
|
}
|
|
146423
146643
|
}
|
|
@@ -146434,7 +146654,7 @@ ${dirty}`
|
|
|
146434
146654
|
|
|
146435
146655
|
// mcp/checkSuite.ts
|
|
146436
146656
|
import { mkdirSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
146437
|
-
import { join as
|
|
146657
|
+
import { join as join6 } from "node:path";
|
|
146438
146658
|
var GetCheckSuiteLogs = type({
|
|
146439
146659
|
check_suite_id: type.number.describe("the id from check_suite.id")
|
|
146440
146660
|
});
|
|
@@ -146530,7 +146750,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
146530
146750
|
if (!tempDir) {
|
|
146531
146751
|
throw new Error("PULLFROG_TEMP_DIR not set");
|
|
146532
146752
|
}
|
|
146533
|
-
const logsDir =
|
|
146753
|
+
const logsDir = join6(tempDir, "ci-logs");
|
|
146534
146754
|
mkdirSync(logsDir, { recursive: true });
|
|
146535
146755
|
const jobResults = [];
|
|
146536
146756
|
for (const run4 of failedRuns) {
|
|
@@ -146557,7 +146777,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
146557
146777
|
);
|
|
146558
146778
|
}
|
|
146559
146779
|
const logsText = await logsResult.text();
|
|
146560
|
-
const logPath =
|
|
146780
|
+
const logPath = join6(logsDir, `job-${job.id}.log`);
|
|
146561
146781
|
writeFileSync3(logPath, logsText);
|
|
146562
146782
|
const analysis = analyzeLog(logsText, 80);
|
|
146563
146783
|
const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
|
|
@@ -146607,7 +146827,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
146607
146827
|
|
|
146608
146828
|
// mcp/commitInfo.ts
|
|
146609
146829
|
import { writeFileSync as writeFileSync4 } from "node:fs";
|
|
146610
|
-
import { join as
|
|
146830
|
+
import { join as join7 } from "node:path";
|
|
146611
146831
|
var CommitInfo = type({
|
|
146612
146832
|
sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
|
|
146613
146833
|
});
|
|
@@ -146631,7 +146851,7 @@ function CommitInfoTool(ctx) {
|
|
|
146631
146851
|
"PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
|
|
146632
146852
|
);
|
|
146633
146853
|
}
|
|
146634
|
-
const diffFile =
|
|
146854
|
+
const diffFile = join7(tempDir, `commit-${sha.slice(0, 7)}.diff`);
|
|
146635
146855
|
writeFileSync4(diffFile, formatResult.content);
|
|
146636
146856
|
log.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
|
|
146637
146857
|
return {
|
|
@@ -147089,7 +147309,7 @@ function PullRequestInfoTool(ctx) {
|
|
|
147089
147309
|
|
|
147090
147310
|
// mcp/reviewComments.ts
|
|
147091
147311
|
import { writeFileSync as writeFileSync5 } from "node:fs";
|
|
147092
|
-
import { join as
|
|
147312
|
+
import { join as join8 } from "node:path";
|
|
147093
147313
|
var REVIEW_THREADS_QUERY = `
|
|
147094
147314
|
query ($owner: String!, $name: String!, $prNumber: Int!) {
|
|
147095
147315
|
repository(owner: $owner, name: $name) {
|
|
@@ -147481,7 +147701,7 @@ function GetReviewCommentsTool(ctx) {
|
|
|
147481
147701
|
throw new Error("PULLFROG_TEMP_DIR not set");
|
|
147482
147702
|
}
|
|
147483
147703
|
const filename = `review-${params.review_id}-threads.md`;
|
|
147484
|
-
const commentsPath =
|
|
147704
|
+
const commentsPath = join8(tempDir, filename);
|
|
147485
147705
|
writeFileSync5(commentsPath, formatted.content);
|
|
147486
147706
|
log.debug(`wrote ${threadBlocks.length} threads to ${commentsPath}`);
|
|
147487
147707
|
return {
|
|
@@ -147710,7 +147930,7 @@ import { spawn as spawn4, spawnSync as spawnSync3 } from "node:child_process";
|
|
|
147710
147930
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
147711
147931
|
import { closeSync, openSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
147712
147932
|
import { userInfo } from "node:os";
|
|
147713
|
-
import { join as
|
|
147933
|
+
import { join as join9 } from "node:path";
|
|
147714
147934
|
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
147715
147935
|
var ShellParams = type({
|
|
147716
147936
|
command: "string",
|
|
@@ -147843,7 +148063,7 @@ function getTempDir() {
|
|
|
147843
148063
|
var MAX_OUTPUT_CHARS = 5e3;
|
|
147844
148064
|
function capOutput(output) {
|
|
147845
148065
|
if (output.length <= MAX_OUTPUT_CHARS) return output;
|
|
147846
|
-
const fullPath =
|
|
148066
|
+
const fullPath = join9(getTempDir(), `shell-${randomUUID2().slice(0, 8)}.log`);
|
|
147847
148067
|
writeFileSync6(fullPath, output);
|
|
147848
148068
|
const elided = output.length - MAX_OUTPUT_CHARS;
|
|
147849
148069
|
return `... [${elided} chars truncated; full output saved to ${fullPath}] ...
|
|
@@ -147858,6 +148078,7 @@ function isGitCommand(command) {
|
|
|
147858
148078
|
function ShellTool(ctx) {
|
|
147859
148079
|
return tool({
|
|
147860
148080
|
name: "shell",
|
|
148081
|
+
timeoutMs: 12e4,
|
|
147861
148082
|
description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
|
|
147862
148083
|
|
|
147863
148084
|
Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
|
|
@@ -147897,8 +148118,8 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
|
|
|
147897
148118
|
if (params.background) {
|
|
147898
148119
|
const tempDir = getTempDir();
|
|
147899
148120
|
const handle = `bg-${randomUUID2().slice(0, 8)}`;
|
|
147900
|
-
const outputPath =
|
|
147901
|
-
const pidPath =
|
|
148121
|
+
const outputPath = join9(tempDir, `${handle}.log`);
|
|
148122
|
+
const pidPath = join9(tempDir, `${handle}.pid`);
|
|
147902
148123
|
const logFd = openSync(outputPath, "a");
|
|
147903
148124
|
let proc2;
|
|
147904
148125
|
try {
|
|
@@ -148238,6 +148459,8 @@ var REVIEWER_AGENT_NAME = "reviewfrog";
|
|
|
148238
148459
|
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
148460
|
|
|
148240
148461
|
HARD CONSTRAINTS (non-negotiable, regardless of orchestrator instructions):
|
|
148462
|
+
- 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.
|
|
148463
|
+
- 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
148464
|
- 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
148465
|
- 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
148466
|
- 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 +148651,25 @@ function computeModes(agentId) {
|
|
|
148428
148651
|
|
|
148429
148652
|
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
148653
|
|
|
148431
|
-
|
|
148654
|
+
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.
|
|
148655
|
+
|
|
148656
|
+
\`\`\`
|
|
148657
|
+
## What you're reviewing
|
|
148658
|
+
This is a PRE-COMMIT Build-mode self-review. The work to review lives in the working tree (uncommitted), NOT in committed history.
|
|
148659
|
+
|
|
148660
|
+
Branch: <branch> (off <base>)
|
|
148661
|
+
Canonical diff command: git diff origin/<base>
|
|
148662
|
+
|
|
148663
|
+
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.
|
|
148664
|
+
|
|
148665
|
+
## Your task
|
|
148666
|
+
<YOUR TASK content>
|
|
148667
|
+
|
|
148668
|
+
## Build-phase failures
|
|
148669
|
+
<tight summary \u2014 what broke, root cause, the fix \u2014 or "no build-phase failures">
|
|
148670
|
+
\`\`\`
|
|
148671
|
+
|
|
148672
|
+
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
148673
|
|
|
148433
148674
|
Delegation + research discipline (distilled from \`/anneal\` canonical \u2014 these are codified learnings from many review rounds, not theoretical best practices):
|
|
148434
148675
|
- Do NOT summarize what you implemented \u2014 that biases the subagent toward validating the shape of your solution rather than questioning it.
|
|
@@ -148827,21 +149068,21 @@ function initToolState(params) {
|
|
|
148827
149068
|
}
|
|
148828
149069
|
|
|
148829
149070
|
// agents/claude.ts
|
|
148830
|
-
import { execFileSync as
|
|
149071
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
148831
149072
|
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync9 } from "node:fs";
|
|
148832
|
-
import { join as
|
|
149073
|
+
import { join as join13 } from "node:path";
|
|
148833
149074
|
import { performance as performance6 } from "node:perf_hooks";
|
|
148834
149075
|
|
|
148835
149076
|
// utils/install.ts
|
|
148836
149077
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
148837
149078
|
import { chmodSync, createWriteStream, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "node:fs";
|
|
148838
|
-
import { join as
|
|
149079
|
+
import { join as join10 } from "node:path";
|
|
148839
149080
|
import { pipeline } from "node:stream/promises";
|
|
148840
149081
|
async function installFromNpmTarball(params) {
|
|
148841
149082
|
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
148842
149083
|
if (!tempDir) throw new Error("PULLFROG_TEMP_DIR is not set");
|
|
148843
|
-
const extractedDir =
|
|
148844
|
-
const cliPath =
|
|
149084
|
+
const extractedDir = join10(tempDir, "package");
|
|
149085
|
+
const cliPath = join10(extractedDir, params.executablePath);
|
|
148845
149086
|
if (existsSync5(cliPath)) {
|
|
148846
149087
|
log.debug(`\xBB using cached binary at ${cliPath}`);
|
|
148847
149088
|
return cliPath;
|
|
@@ -148866,7 +149107,7 @@ async function installFromNpmTarball(params) {
|
|
|
148866
149107
|
}
|
|
148867
149108
|
}
|
|
148868
149109
|
log.debug(`\xBB installing ${params.packageName}@${resolvedVersion}...`);
|
|
148869
|
-
const tarballPath =
|
|
149110
|
+
const tarballPath = join10(tempDir, "package.tgz");
|
|
148870
149111
|
const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org";
|
|
148871
149112
|
let tarballUrl;
|
|
148872
149113
|
if (params.packageName.startsWith("@")) {
|
|
@@ -149005,16 +149246,16 @@ function isRouterKeylimitExhaustedError(text) {
|
|
|
149005
149246
|
// utils/skills.ts
|
|
149006
149247
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
149007
149248
|
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
|
|
149249
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
149250
|
+
import { dirname as dirname2, join as join11 } from "node:path";
|
|
149010
149251
|
import { fileURLToPath } from "node:url";
|
|
149011
149252
|
var skillsVersion = getDevDependencyVersion("skills");
|
|
149012
149253
|
var BUNDLED_SKILL_NAMES = ["git-archaeology"];
|
|
149013
149254
|
function resolveSkillPath(name) {
|
|
149014
149255
|
const here = dirname2(fileURLToPath(import.meta.url));
|
|
149015
149256
|
const candidates = [
|
|
149016
|
-
|
|
149017
|
-
|
|
149257
|
+
join11(here, "..", "skills", name, "SKILL.md"),
|
|
149258
|
+
join11(here, "skills", name, "SKILL.md")
|
|
149018
149259
|
];
|
|
149019
149260
|
for (const candidate of candidates) {
|
|
149020
149261
|
if (existsSync6(candidate)) return candidate;
|
|
@@ -149026,9 +149267,9 @@ function installBundledSkills(params) {
|
|
|
149026
149267
|
for (const name of BUNDLED_SKILL_NAMES) {
|
|
149027
149268
|
const content = readFileSync5(resolveSkillPath(name), "utf8");
|
|
149028
149269
|
for (const targetDir of SKILL_TARGET_DIRS) {
|
|
149029
|
-
const skillDir =
|
|
149270
|
+
const skillDir = join11(params.home, targetDir, name);
|
|
149030
149271
|
mkdirSync3(skillDir, { recursive: true });
|
|
149031
|
-
writeFileSync7(
|
|
149272
|
+
writeFileSync7(join11(skillDir, "SKILL.md"), content);
|
|
149032
149273
|
}
|
|
149033
149274
|
}
|
|
149034
149275
|
log.success(`installed bundled skills: ${BUNDLED_SKILL_NAMES.join(", ")}`);
|
|
@@ -149049,7 +149290,7 @@ function addSkill(params) {
|
|
|
149049
149290
|
"-y"
|
|
149050
149291
|
],
|
|
149051
149292
|
{
|
|
149052
|
-
cwd:
|
|
149293
|
+
cwd: tmpdir3(),
|
|
149053
149294
|
env: { ...process.env, ...params.env },
|
|
149054
149295
|
stdio: "pipe",
|
|
149055
149296
|
timeout: 3e4
|
|
@@ -149134,7 +149375,7 @@ var ThinkingTimer = class {
|
|
|
149134
149375
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
149135
149376
|
import { mkdirSync as mkdirSync4, rmSync as rmSync2, writeFileSync as writeFileSync8 } from "node:fs";
|
|
149136
149377
|
import { homedir } from "node:os";
|
|
149137
|
-
import { join as
|
|
149378
|
+
import { join as join12 } from "node:path";
|
|
149138
149379
|
var VERTEX_SERVICE_ACCOUNT_JSON_ENV = "VERTEX_SERVICE_ACCOUNT_JSON";
|
|
149139
149380
|
var GOOGLE_APPLICATION_CREDENTIALS_ENV = "GOOGLE_APPLICATION_CREDENTIALS";
|
|
149140
149381
|
var GOOGLE_CLOUD_PROJECT_ENV = "GOOGLE_CLOUD_PROJECT";
|
|
@@ -149163,7 +149404,7 @@ function readProjectIdFromVertexServiceAccountJson() {
|
|
|
149163
149404
|
}
|
|
149164
149405
|
function createSecretDir() {
|
|
149165
149406
|
const base = process.env.PULLFROG_SECRET_HOME || process.env.HOME || homedir();
|
|
149166
|
-
const secretDir =
|
|
149407
|
+
const secretDir = join12(base, ".pullfrog", "secrets", randomUUID3());
|
|
149167
149408
|
mkdirSync4(secretDir, { recursive: true, mode: 448 });
|
|
149168
149409
|
return secretDir;
|
|
149169
149410
|
}
|
|
@@ -149172,7 +149413,7 @@ function materializeVertexCredentials(params) {
|
|
|
149172
149413
|
const blob = process.env[VERTEX_SERVICE_ACCOUNT_JSON_ENV];
|
|
149173
149414
|
if (!blob) return void 0;
|
|
149174
149415
|
const secretDir = createSecretDir();
|
|
149175
|
-
const credentialsPath =
|
|
149416
|
+
const credentialsPath = join12(secretDir, "vertex-sa.json");
|
|
149176
149417
|
writeFileSync8(credentialsPath, blob, { mode: 384 });
|
|
149177
149418
|
process.env[GOOGLE_APPLICATION_CREDENTIALS_ENV] = credentialsPath;
|
|
149178
149419
|
const projectId = readProjectIdFromVertexServiceAccountJson();
|
|
@@ -149184,6 +149425,7 @@ function materializeVertexCredentials(params) {
|
|
|
149184
149425
|
function cleanupVertexCredentials(credentials) {
|
|
149185
149426
|
if (!credentials) return;
|
|
149186
149427
|
rmSync2(credentials.secretDir, { recursive: true, force: true });
|
|
149428
|
+
delete process.env[GOOGLE_APPLICATION_CREDENTIALS_ENV];
|
|
149187
149429
|
}
|
|
149188
149430
|
function applyClaudeVertexEnv(env2) {
|
|
149189
149431
|
env2.CLAUDE_CODE_USE_VERTEX = "1";
|
|
@@ -149497,9 +149739,9 @@ async function installClaudeCli() {
|
|
|
149497
149739
|
});
|
|
149498
149740
|
}
|
|
149499
149741
|
function writeMcpConfig(ctx) {
|
|
149500
|
-
const configDir =
|
|
149742
|
+
const configDir = join13(ctx.tmpdir, ".claude");
|
|
149501
149743
|
mkdirSync5(configDir, { recursive: true });
|
|
149502
|
-
const configPath =
|
|
149744
|
+
const configPath = join13(configDir, "mcp.json");
|
|
149503
149745
|
writeFileSync9(
|
|
149504
149746
|
configPath,
|
|
149505
149747
|
JSON.stringify({
|
|
@@ -149925,8 +150167,8 @@ function installManagedSettings(ctx) {
|
|
|
149925
150167
|
if (process.env.CI !== "true") return;
|
|
149926
150168
|
const content = JSON.stringify(buildManagedSettings(ctx), null, 2);
|
|
149927
150169
|
try {
|
|
149928
|
-
|
|
149929
|
-
|
|
150170
|
+
execFileSync5("sudo", ["mkdir", "-p", MANAGED_SETTINGS_DIR]);
|
|
150171
|
+
execFileSync5("sudo", ["tee", MANAGED_SETTINGS_PATH], {
|
|
149930
150172
|
input: content,
|
|
149931
150173
|
stdio: ["pipe", "ignore", "pipe"]
|
|
149932
150174
|
});
|
|
@@ -149948,15 +150190,15 @@ var claude = agent({
|
|
|
149948
150190
|
const model = !specifier ? void 0 : isBedrockRoute ? specifier : isVertexRoute2 ? void 0 : stripProviderPrefix(specifier);
|
|
149949
150191
|
const homeEnv = {
|
|
149950
150192
|
HOME: ctx.tmpdir,
|
|
149951
|
-
XDG_CONFIG_HOME:
|
|
150193
|
+
XDG_CONFIG_HOME: join13(ctx.tmpdir, ".config")
|
|
149952
150194
|
};
|
|
149953
|
-
mkdirSync5(
|
|
150195
|
+
mkdirSync5(join13(homeEnv.XDG_CONFIG_HOME, "claude"), { recursive: true });
|
|
149954
150196
|
const agentBrowserVersion = getDevDependencyVersion("agent-browser");
|
|
149955
150197
|
addSkill({
|
|
149956
150198
|
ref: `vercel-labs/agent-browser@v${agentBrowserVersion}`,
|
|
149957
150199
|
skill: "agent-browser",
|
|
149958
150200
|
env: homeEnv,
|
|
149959
|
-
agent: "claude"
|
|
150201
|
+
agent: "claude-code"
|
|
149960
150202
|
});
|
|
149961
150203
|
installBundledSkills({ home: homeEnv.HOME });
|
|
149962
150204
|
const mcpConfigPath = writeMcpConfig(ctx);
|
|
@@ -150035,7 +150277,7 @@ var claude = agent({
|
|
|
150035
150277
|
// agents/opencode_v2.ts
|
|
150036
150278
|
var core2 = __toESM(require_core(), 1);
|
|
150037
150279
|
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync11 } from "node:fs";
|
|
150038
|
-
import { join as
|
|
150280
|
+
import { join as join15 } from "node:path";
|
|
150039
150281
|
import { performance as performance7 } from "node:perf_hooks";
|
|
150040
150282
|
|
|
150041
150283
|
// utils/agentHangReport.ts
|
|
@@ -150136,7 +150378,7 @@ function formatBillingExhaustedBody(diagnostic) {
|
|
|
150136
150378
|
// utils/codexHome.ts
|
|
150137
150379
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync10 } from "node:fs";
|
|
150138
150380
|
import { homedir as homedir2 } from "node:os";
|
|
150139
|
-
import { join as
|
|
150381
|
+
import { join as join14 } from "node:path";
|
|
150140
150382
|
var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
|
|
150141
150383
|
function installCodexAuth() {
|
|
150142
150384
|
const raw2 = process.env[CODEX_AUTH_ENV];
|
|
@@ -150146,9 +150388,9 @@ function installCodexAuth() {
|
|
|
150146
150388
|
log.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
|
|
150147
150389
|
return null;
|
|
150148
150390
|
}
|
|
150149
|
-
const xdgDataHome =
|
|
150150
|
-
const opencodeDir =
|
|
150151
|
-
const authPath =
|
|
150391
|
+
const xdgDataHome = join14(homedir2(), ".local", "share");
|
|
150392
|
+
const opencodeDir = join14(xdgDataHome, "opencode");
|
|
150393
|
+
const authPath = join14(opencodeDir, "auth.json");
|
|
150152
150394
|
const opencodeAuth = {
|
|
150153
150395
|
openai: {
|
|
150154
150396
|
type: "oauth",
|
|
@@ -150276,7 +150518,7 @@ export default async function pullfrogEventsPlugin() {
|
|
|
150276
150518
|
`;
|
|
150277
150519
|
|
|
150278
150520
|
// agents/opencodeShared.ts
|
|
150279
|
-
import { execFileSync as
|
|
150521
|
+
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
150280
150522
|
|
|
150281
150523
|
// agents/subagentModels.ts
|
|
150282
150524
|
function deriveSubagentModels(orchestratorSpec) {
|
|
@@ -150325,7 +150567,7 @@ async function installOpencodeCli(params) {
|
|
|
150325
150567
|
var AUTO_SELECT_WARNING = "select a model explicitly in the Pullfrog console (https://pullfrog.com/console) to avoid this.";
|
|
150326
150568
|
function getOpenCodeModels(cliPath) {
|
|
150327
150569
|
try {
|
|
150328
|
-
const output =
|
|
150570
|
+
const output = execFileSync6(cliPath, ["models"], {
|
|
150329
150571
|
encoding: "utf-8",
|
|
150330
150572
|
timeout: 3e4,
|
|
150331
150573
|
env: process.env
|
|
@@ -150822,13 +151064,13 @@ var opencode = agent({
|
|
|
150822
151064
|
const model = vertexModel ?? (isBedrockRoute ? `amazon-bedrock/${rawModel}` : rawModel);
|
|
150823
151065
|
const homeEnv = {
|
|
150824
151066
|
HOME: ctx.tmpdir,
|
|
150825
|
-
XDG_CONFIG_HOME:
|
|
151067
|
+
XDG_CONFIG_HOME: join15(ctx.tmpdir, ".config")
|
|
150826
151068
|
};
|
|
150827
|
-
mkdirSync7(
|
|
150828
|
-
const opencodePluginDir =
|
|
151069
|
+
mkdirSync7(join15(homeEnv.XDG_CONFIG_HOME, "opencode"), { recursive: true });
|
|
151070
|
+
const opencodePluginDir = join15(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
|
|
150829
151071
|
mkdirSync7(opencodePluginDir, { recursive: true });
|
|
150830
151072
|
writeFileSync11(
|
|
150831
|
-
|
|
151073
|
+
join15(opencodePluginDir, PULLFROG_OPENCODE_PLUGIN_FILENAME),
|
|
150832
151074
|
PULLFROG_OPENCODE_PLUGIN_SOURCE
|
|
150833
151075
|
);
|
|
150834
151076
|
const agentBrowserVersion = getDevDependencyVersion("agent-browser");
|
|
@@ -151190,7 +151432,7 @@ async function fetchBodyHtml(ctx) {
|
|
|
151190
151432
|
}
|
|
151191
151433
|
|
|
151192
151434
|
// utils/byokFallback.ts
|
|
151193
|
-
var FREE_FALLBACK_SLUG = "opencode/
|
|
151435
|
+
var FREE_FALLBACK_SLUG = "opencode/big-pickle";
|
|
151194
151436
|
function selectFallbackModelIfNeeded(input) {
|
|
151195
151437
|
if (input.proxyModel) return { fallback: false };
|
|
151196
151438
|
if (!input.resolvedModel) return { fallback: false };
|
|
@@ -151208,7 +151450,7 @@ function selectFallbackModelIfNeeded(input) {
|
|
|
151208
151450
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
151209
151451
|
import { writeFileSync as writeFileSync12 } from "node:fs";
|
|
151210
151452
|
import { createServer as createServer2 } from "node:http";
|
|
151211
|
-
import { join as
|
|
151453
|
+
import { join as join16 } from "node:path";
|
|
151212
151454
|
var CODE_TTL_MS = 5 * 60 * 1e3;
|
|
151213
151455
|
var TAMPER_WINDOW_MS = 6e4;
|
|
151214
151456
|
function revokeGitHubToken(token) {
|
|
@@ -151280,7 +151522,7 @@ async function startGitAuthServer(tmpdir4) {
|
|
|
151280
151522
|
function writeAskpassScript(code) {
|
|
151281
151523
|
const scriptId = randomUUID4();
|
|
151282
151524
|
const scriptName = `askpass-${scriptId}.js`;
|
|
151283
|
-
const scriptPath =
|
|
151525
|
+
const scriptPath = join16(tmpdir4, scriptName);
|
|
151284
151526
|
const content = [
|
|
151285
151527
|
`#!/usr/bin/env node`,
|
|
151286
151528
|
`var a=process.argv[2]||"";`,
|
|
@@ -151319,7 +151561,7 @@ async function startGitAuthServer(tmpdir4) {
|
|
|
151319
151561
|
var core3 = __toESM(require_core(), 1);
|
|
151320
151562
|
import { createSign } from "node:crypto";
|
|
151321
151563
|
import { rename, writeFile } from "node:fs/promises";
|
|
151322
|
-
import { dirname as dirname3, join as
|
|
151564
|
+
import { dirname as dirname3, join as join17 } from "node:path";
|
|
151323
151565
|
|
|
151324
151566
|
// node_modules/.pnpm/@octokit+plugin-throttling@11.0.3_@octokit+core@7.0.5/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
|
|
151325
151567
|
var import_light = __toESM(require_light(), 1);
|
|
@@ -155177,7 +155419,7 @@ function getGitHubUsageSummary() {
|
|
|
155177
155419
|
}
|
|
155178
155420
|
async function writeGitHubUsageSummaryToFile(path3) {
|
|
155179
155421
|
const summary2 = getGitHubUsageSummary();
|
|
155180
|
-
const tmpPath =
|
|
155422
|
+
const tmpPath = join17(dirname3(path3), `.usage-summary-${process.pid}.tmp`);
|
|
155181
155423
|
await writeFile(tmpPath, JSON.stringify(summary2));
|
|
155182
155424
|
await rename(tmpPath, path3);
|
|
155183
155425
|
}
|
|
@@ -155228,7 +155470,7 @@ function createOctokit(token) {
|
|
|
155228
155470
|
}
|
|
155229
155471
|
|
|
155230
155472
|
// utils/instructions.ts
|
|
155231
|
-
import { execSync as
|
|
155473
|
+
import { execSync as execSync3 } from "node:child_process";
|
|
155232
155474
|
function buildRuntimeContext(ctx) {
|
|
155233
155475
|
const {
|
|
155234
155476
|
"~pullfrog": _2,
|
|
@@ -155240,7 +155482,7 @@ function buildRuntimeContext(ctx) {
|
|
|
155240
155482
|
} = ctx.payload;
|
|
155241
155483
|
let gitStatus;
|
|
155242
155484
|
try {
|
|
155243
|
-
gitStatus =
|
|
155485
|
+
gitStatus = execSync3("git status --short", { encoding: "utf-8", stdio: "pipe" }).trim() || "(clean)";
|
|
155244
155486
|
} catch {
|
|
155245
155487
|
}
|
|
155246
155488
|
const data = {
|
|
@@ -155577,7 +155819,7 @@ function resolveInstructions(ctx) {
|
|
|
155577
155819
|
|
|
155578
155820
|
// utils/learnings.ts
|
|
155579
155821
|
import { mkdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
155580
|
-
import { dirname as dirname4, join as
|
|
155822
|
+
import { dirname as dirname4, join as join18 } from "node:path";
|
|
155581
155823
|
|
|
155582
155824
|
// utils/learningsTruncate.ts
|
|
155583
155825
|
var MAX_LEARNINGS_LENGTH = 1e5;
|
|
@@ -155594,7 +155836,7 @@ function truncateAtLineBoundary(body, cap) {
|
|
|
155594
155836
|
// utils/learnings.ts
|
|
155595
155837
|
var LEARNINGS_FILE_NAME = "pullfrog-learnings.md";
|
|
155596
155838
|
function learningsFilePath(tmpdir4) {
|
|
155597
|
-
return
|
|
155839
|
+
return join18(tmpdir4, LEARNINGS_FILE_NAME);
|
|
155598
155840
|
}
|
|
155599
155841
|
async function seedLearningsFile(params) {
|
|
155600
155842
|
const path3 = learningsFilePath(params.tmpdir);
|
|
@@ -156274,7 +156516,7 @@ async function runProxyResolution(ctx) {
|
|
|
156274
156516
|
|
|
156275
156517
|
// utils/prSummary.ts
|
|
156276
156518
|
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
156277
|
-
import { dirname as dirname5, join as
|
|
156519
|
+
import { dirname as dirname5, join as join19 } from "node:path";
|
|
156278
156520
|
var SUMMARY_FILE_NAME = "pullfrog-summary.md";
|
|
156279
156521
|
var SUMMARY_SCAFFOLD = `# PR summary
|
|
156280
156522
|
|
|
@@ -156284,7 +156526,7 @@ var SUMMARY_SCAFFOLD = `# PR summary
|
|
|
156284
156526
|
var MIN_SNAPSHOT_LENGTH = 60;
|
|
156285
156527
|
var MAX_SNAPSHOT_LENGTH = 32768;
|
|
156286
156528
|
function summaryFilePath(tmpdir4) {
|
|
156287
|
-
return
|
|
156529
|
+
return join19(tmpdir4, SUMMARY_FILE_NAME);
|
|
156288
156530
|
}
|
|
156289
156531
|
async function seedSummaryFile(params) {
|
|
156290
156532
|
const path3 = summaryFilePath(params.tmpdir);
|
|
@@ -156478,6 +156720,16 @@ async function resolveRunContextData(params) {
|
|
|
156478
156720
|
}
|
|
156479
156721
|
|
|
156480
156722
|
// utils/runErrorRenderer.ts
|
|
156723
|
+
function isProviderModelNotFoundError(message) {
|
|
156724
|
+
return message.includes("ProviderModelNotFoundError");
|
|
156725
|
+
}
|
|
156726
|
+
function formatProviderModelNotFoundSummary(input) {
|
|
156727
|
+
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.
|
|
156728
|
+
|
|
156729
|
+
\`\`\`
|
|
156730
|
+
${input.raw}
|
|
156731
|
+
\`\`\``;
|
|
156732
|
+
}
|
|
156481
156733
|
function renderRunError(input) {
|
|
156482
156734
|
const billingError = isRouterKeylimitExhaustedError(input.errorMessage) ? new BillingError(input.errorMessage, { code: "router_keylimit_exhausted" }) : null;
|
|
156483
156735
|
if (billingError) {
|
|
@@ -156499,6 +156751,14 @@ function renderRunError(input) {
|
|
|
156499
156751
|
if (apiKeyErrorSummary) {
|
|
156500
156752
|
return { summary: apiKeyErrorSummary, comment: apiKeyErrorSummary };
|
|
156501
156753
|
}
|
|
156754
|
+
if (isProviderModelNotFoundError(input.errorMessage)) {
|
|
156755
|
+
const body = formatProviderModelNotFoundSummary({
|
|
156756
|
+
owner: input.repo.owner,
|
|
156757
|
+
name: input.repo.name,
|
|
156758
|
+
raw: input.errorMessage
|
|
156759
|
+
});
|
|
156760
|
+
return { summary: body, comment: body };
|
|
156761
|
+
}
|
|
156502
156762
|
if (hangBody) {
|
|
156503
156763
|
return {
|
|
156504
156764
|
summary: `### \u274C Pullfrog failed
|
|
@@ -156600,16 +156860,17 @@ async function persistRunArtifacts(toolContext) {
|
|
|
156600
156860
|
}
|
|
156601
156861
|
async function finalizeSuccessRun(input) {
|
|
156602
156862
|
await persistRunArtifacts(input.toolContext);
|
|
156603
|
-
|
|
156604
|
-
|
|
156605
|
-
|
|
156606
|
-
|
|
156607
|
-
|
|
156608
|
-
|
|
156609
|
-
|
|
156610
|
-
|
|
156611
|
-
|
|
156612
|
-
|
|
156863
|
+
const rendered = !input.result.success ? renderRunError({
|
|
156864
|
+
errorMessage: input.result.error || "agent run failed",
|
|
156865
|
+
repo: input.repo,
|
|
156866
|
+
agentDiagnostic: input.toolState.agentDiagnostic
|
|
156867
|
+
}) : null;
|
|
156868
|
+
if (rendered && input.toolState.progressComment) {
|
|
156869
|
+
await reportErrorToComment({ toolState: input.toolState, error: rendered.comment }).catch(
|
|
156870
|
+
(error49) => {
|
|
156871
|
+
log.debug(`failure error report failed: ${error49}`);
|
|
156872
|
+
}
|
|
156873
|
+
);
|
|
156613
156874
|
}
|
|
156614
156875
|
if (input.result.success && input.toolState.progressComment && !input.toolState.finalSummaryWritten) {
|
|
156615
156876
|
await deleteProgressComment(input.toolContext).catch((error49) => {
|
|
@@ -156619,7 +156880,7 @@ async function finalizeSuccessRun(input) {
|
|
|
156619
156880
|
try {
|
|
156620
156881
|
const usageSummary = formatUsageSummary(input.toolState.usageEntries);
|
|
156621
156882
|
const body = input.toolState.lastProgressBody || input.result.output;
|
|
156622
|
-
const parts = [body, usageSummary].filter(Boolean);
|
|
156883
|
+
const parts = [rendered?.summary, body, usageSummary].filter(Boolean);
|
|
156623
156884
|
if (parts.length > 0) {
|
|
156624
156885
|
await writeSummary(parts.join("\n\n"));
|
|
156625
156886
|
}
|
|
@@ -156704,116 +156965,6 @@ function logRunStartup(ctx) {
|
|
|
156704
156965
|
log.info(`\xBB timeout: ${resolveTimeoutForLog(ctx.payload.timeout)}`);
|
|
156705
156966
|
}
|
|
156706
156967
|
|
|
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
156968
|
// utils/todoTracking.ts
|
|
156818
156969
|
function isValidTodoStatus(value2) {
|
|
156819
156970
|
return value2 === "pending" || value2 === "in_progress" || value2 === "completed" || value2 === "cancelled";
|
|
@@ -157021,6 +157172,7 @@ async function main() {
|
|
|
157021
157172
|
toolState.beforeSha = payload.event.before_sha;
|
|
157022
157173
|
}
|
|
157023
157174
|
const tokenRef = __using(_stack2, await resolveTokens({ push: payload.push }), true);
|
|
157175
|
+
wipeRunnerLeakSurface();
|
|
157024
157176
|
const oidcCredentials = process.env.ACTIONS_ID_TOKEN_REQUEST_URL && process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN ? {
|
|
157025
157177
|
requestUrl: process.env.ACTIONS_ID_TOKEN_REQUEST_URL,
|
|
157026
157178
|
requestToken: process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN
|
|
@@ -157190,7 +157342,7 @@ ${instructions.user}` : null,
|
|
|
157190
157342
|
});
|
|
157191
157343
|
if (agentId === "opencode") {
|
|
157192
157344
|
const pluginDir = join20(process.cwd(), ".opencode", "plugin");
|
|
157193
|
-
const hasPlugins = existsSync7(pluginDir) &&
|
|
157345
|
+
const hasPlugins = existsSync7(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
|
|
157194
157346
|
if (hasPlugins && toolState.dependencyInstallation?.promise) {
|
|
157195
157347
|
log.info(
|
|
157196
157348
|
"\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"
|
|
@@ -158238,7 +158390,7 @@ async function run2() {
|
|
|
158238
158390
|
}
|
|
158239
158391
|
|
|
158240
158392
|
// cli.ts
|
|
158241
|
-
var VERSION10 = "0.1.
|
|
158393
|
+
var VERSION10 = "0.1.13";
|
|
158242
158394
|
var bin = basename2(process.argv[1] || "");
|
|
158243
158395
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
158244
158396
|
var rawArgs = process.argv.slice(2);
|