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/index.js
CHANGED
|
@@ -98924,7 +98924,7 @@ var require_fast_content_type_parse = __commonJS({
|
|
|
98924
98924
|
});
|
|
98925
98925
|
|
|
98926
98926
|
// main.ts
|
|
98927
|
-
import { existsSync as existsSync7, readdirSync } from "node:fs";
|
|
98927
|
+
import { existsSync as existsSync7, readdirSync as readdirSync2 } from "node:fs";
|
|
98928
98928
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
98929
98929
|
import { join as join19 } from "node:path";
|
|
98930
98930
|
|
|
@@ -107987,6 +107987,11 @@ var providers = {
|
|
|
107987
107987
|
resolve: "opencode/kimi-k2.6",
|
|
107988
107988
|
openRouterResolve: "openrouter/moonshotai/kimi-k2.6"
|
|
107989
107989
|
},
|
|
107990
|
+
"minimax-m2.5": {
|
|
107991
|
+
displayName: "MiniMax M2.5",
|
|
107992
|
+
resolve: "opencode/minimax-m2.5",
|
|
107993
|
+
openRouterResolve: "openrouter/minimax/minimax-m2.5"
|
|
107994
|
+
},
|
|
107990
107995
|
"gpt-5-nano": {
|
|
107991
107996
|
displayName: "GPT Nano",
|
|
107992
107997
|
resolve: "opencode/gpt-5-nano",
|
|
@@ -108003,7 +108008,9 @@ var providers = {
|
|
|
108003
108008
|
displayName: "MiniMax M2.5",
|
|
108004
108009
|
resolve: "opencode/minimax-m2.5-free",
|
|
108005
108010
|
envVars: [],
|
|
108006
|
-
isFree: true
|
|
108011
|
+
isFree: true,
|
|
108012
|
+
fallback: "opencode/big-pickle",
|
|
108013
|
+
hidden: true
|
|
108007
108014
|
}
|
|
108008
108015
|
}
|
|
108009
108016
|
}),
|
|
@@ -108141,6 +108148,11 @@ var providers = {
|
|
|
108141
108148
|
displayName: "Kimi K2",
|
|
108142
108149
|
resolve: "openrouter/moonshotai/kimi-k2.6",
|
|
108143
108150
|
openRouterResolve: "openrouter/moonshotai/kimi-k2.6"
|
|
108151
|
+
},
|
|
108152
|
+
"minimax-m2.5": {
|
|
108153
|
+
displayName: "MiniMax M2.5",
|
|
108154
|
+
resolve: "openrouter/minimax/minimax-m2.5",
|
|
108155
|
+
openRouterResolve: "openrouter/minimax/minimax-m2.5"
|
|
108144
108156
|
}
|
|
108145
108157
|
}
|
|
108146
108158
|
})
|
|
@@ -108185,6 +108197,11 @@ var modelAliases = Object.entries(providers).flatMap(
|
|
|
108185
108197
|
hidden: def.hidden ?? false
|
|
108186
108198
|
}))
|
|
108187
108199
|
);
|
|
108200
|
+
var defaultProxyAlias = modelAliases.find((a) => a.slug === "moonshotai/kimi-k2");
|
|
108201
|
+
if (!defaultProxyAlias?.openRouterResolve) {
|
|
108202
|
+
throw new Error("DEFAULT_PROXY_MODEL: moonshotai/kimi-k2 missing openRouterResolve");
|
|
108203
|
+
}
|
|
108204
|
+
var DEFAULT_PROXY_MODEL = defaultProxyAlias.openRouterResolve;
|
|
108188
108205
|
var MAX_FALLBACK_DEPTH = 10;
|
|
108189
108206
|
function resolveDisplayAlias(slug2) {
|
|
108190
108207
|
let current = slug2;
|
|
@@ -142492,7 +142509,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
142492
142509
|
// package.json
|
|
142493
142510
|
var package_default = {
|
|
142494
142511
|
name: "pullfrog",
|
|
142495
|
-
version: "0.1.
|
|
142512
|
+
version: "0.1.12",
|
|
142496
142513
|
type: "module",
|
|
142497
142514
|
bin: {
|
|
142498
142515
|
pullfrog: "dist/cli.mjs",
|
|
@@ -142690,8 +142707,8 @@ function closeBrowserDaemon(toolState) {
|
|
|
142690
142707
|
|
|
142691
142708
|
// mcp/checkout.ts
|
|
142692
142709
|
import { createHash as createHash2 } from "node:crypto";
|
|
142693
|
-
import { statSync, unlinkSync as
|
|
142694
|
-
import { join as
|
|
142710
|
+
import { statSync, unlinkSync as unlinkSync3, writeFileSync } from "node:fs";
|
|
142711
|
+
import { join as join4 } from "node:path";
|
|
142695
142712
|
|
|
142696
142713
|
// utils/diffCoverage.ts
|
|
142697
142714
|
import { isAbsolute, normalize as normalize2, resolve } from "node:path";
|
|
@@ -144203,6 +144220,183 @@ async function reportReviewNodeId(ctx, params) {
|
|
|
144203
144220
|
await patchWorkflowRunFields(ctx, { reviewNodeId: params.nodeId });
|
|
144204
144221
|
}
|
|
144205
144222
|
|
|
144223
|
+
// utils/setup.ts
|
|
144224
|
+
import { execFileSync as execFileSync3, execSync as execSync2 } from "node:child_process";
|
|
144225
|
+
import { mkdtempSync, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
|
|
144226
|
+
import { tmpdir } from "node:os";
|
|
144227
|
+
import { join as join3 } from "node:path";
|
|
144228
|
+
function createTempDirectory() {
|
|
144229
|
+
const sharedTempDir = mkdtempSync(join3(tmpdir(), "pullfrog-"));
|
|
144230
|
+
process.env.PULLFROG_TEMP_DIR = sharedTempDir;
|
|
144231
|
+
log.info(`\xBB created temp dir at ${sharedTempDir}`);
|
|
144232
|
+
return sharedTempDir;
|
|
144233
|
+
}
|
|
144234
|
+
function wipeRunnerLeakSurface() {
|
|
144235
|
+
const runnerTemp = process.env.RUNNER_TEMP;
|
|
144236
|
+
if (!runnerTemp) return;
|
|
144237
|
+
const preserve = /* @__PURE__ */ new Set();
|
|
144238
|
+
for (const envVar of [
|
|
144239
|
+
"GITHUB_OUTPUT",
|
|
144240
|
+
"GITHUB_ENV",
|
|
144241
|
+
"GITHUB_PATH",
|
|
144242
|
+
"GITHUB_STATE",
|
|
144243
|
+
"GITHUB_STEP_SUMMARY"
|
|
144244
|
+
]) {
|
|
144245
|
+
const path3 = process.env[envVar];
|
|
144246
|
+
if (!path3) continue;
|
|
144247
|
+
try {
|
|
144248
|
+
preserve.add(realpathSync2(path3));
|
|
144249
|
+
} catch {
|
|
144250
|
+
preserve.add(path3);
|
|
144251
|
+
}
|
|
144252
|
+
}
|
|
144253
|
+
const wiped = [];
|
|
144254
|
+
const tryUnlink = (path3) => {
|
|
144255
|
+
let resolved = path3;
|
|
144256
|
+
try {
|
|
144257
|
+
resolved = realpathSync2(path3);
|
|
144258
|
+
} catch {
|
|
144259
|
+
}
|
|
144260
|
+
if (preserve.has(resolved) || preserve.has(path3)) return;
|
|
144261
|
+
try {
|
|
144262
|
+
unlinkSync2(path3);
|
|
144263
|
+
wiped.push(path3);
|
|
144264
|
+
} catch {
|
|
144265
|
+
}
|
|
144266
|
+
};
|
|
144267
|
+
const listDir = (dir) => {
|
|
144268
|
+
try {
|
|
144269
|
+
return readdirSync(dir);
|
|
144270
|
+
} catch {
|
|
144271
|
+
return [];
|
|
144272
|
+
}
|
|
144273
|
+
};
|
|
144274
|
+
const fileCommandsDir = join3(runnerTemp, "_runner_file_commands");
|
|
144275
|
+
for (const entry of listDir(fileCommandsDir)) {
|
|
144276
|
+
tryUnlink(join3(fileCommandsDir, entry));
|
|
144277
|
+
}
|
|
144278
|
+
for (const entry of listDir(runnerTemp)) {
|
|
144279
|
+
if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
|
|
144280
|
+
tryUnlink(join3(runnerTemp, entry));
|
|
144281
|
+
}
|
|
144282
|
+
}
|
|
144283
|
+
if (wiped.length > 0) {
|
|
144284
|
+
log.info(`\xBB wiped ${wiped.length} leak-surface file(s) from $RUNNER_TEMP`);
|
|
144285
|
+
log.debug(`\xBB wiped paths: ${wiped.join(", ")}`);
|
|
144286
|
+
}
|
|
144287
|
+
}
|
|
144288
|
+
function envScopedToRepo() {
|
|
144289
|
+
const scoped = { ...process.env };
|
|
144290
|
+
for (const key of Object.keys(scoped)) {
|
|
144291
|
+
if (key.startsWith("GIT_")) delete scoped[key];
|
|
144292
|
+
}
|
|
144293
|
+
return scoped;
|
|
144294
|
+
}
|
|
144295
|
+
function removeIncludeIfEntries(repoDir) {
|
|
144296
|
+
const env2 = envScopedToRepo();
|
|
144297
|
+
let configOutput;
|
|
144298
|
+
try {
|
|
144299
|
+
configOutput = execSync2("git config --local --get-regexp -z ^includeif\\.", {
|
|
144300
|
+
cwd: repoDir,
|
|
144301
|
+
encoding: "utf-8",
|
|
144302
|
+
stdio: "pipe",
|
|
144303
|
+
env: env2
|
|
144304
|
+
});
|
|
144305
|
+
} catch {
|
|
144306
|
+
log.debug("\xBB no includeIf credential entries to remove");
|
|
144307
|
+
return;
|
|
144308
|
+
}
|
|
144309
|
+
const seen = /* @__PURE__ */ new Set();
|
|
144310
|
+
for (const entry of configOutput.split("\0")) {
|
|
144311
|
+
if (!entry) continue;
|
|
144312
|
+
const nl = entry.indexOf("\n");
|
|
144313
|
+
const key = nl === -1 ? entry : entry.slice(0, nl);
|
|
144314
|
+
if (!key || seen.has(key)) continue;
|
|
144315
|
+
seen.add(key);
|
|
144316
|
+
try {
|
|
144317
|
+
execFileSync3("git", ["config", "--local", "--unset-all", key], {
|
|
144318
|
+
cwd: repoDir,
|
|
144319
|
+
stdio: "pipe",
|
|
144320
|
+
env: env2
|
|
144321
|
+
});
|
|
144322
|
+
} catch (error49) {
|
|
144323
|
+
log.debug(
|
|
144324
|
+
`\xBB failed to unset ${key}: ${error49 instanceof Error ? error49.message : String(error49)}`
|
|
144325
|
+
);
|
|
144326
|
+
}
|
|
144327
|
+
}
|
|
144328
|
+
if (seen.size > 0)
|
|
144329
|
+
log.info(
|
|
144330
|
+
`\xBB removed ${seen.size} includeIf credential ${seen.size === 1 ? "entry" : "entries"}`
|
|
144331
|
+
);
|
|
144332
|
+
}
|
|
144333
|
+
async function setupGit(params) {
|
|
144334
|
+
const repoDir = process.cwd();
|
|
144335
|
+
log.info("\xBB setting up git configuration...");
|
|
144336
|
+
try {
|
|
144337
|
+
let currentEmail = "";
|
|
144338
|
+
try {
|
|
144339
|
+
currentEmail = execSync2("git config user.email", {
|
|
144340
|
+
cwd: repoDir,
|
|
144341
|
+
stdio: "pipe",
|
|
144342
|
+
encoding: "utf-8"
|
|
144343
|
+
}).trim();
|
|
144344
|
+
} catch {
|
|
144345
|
+
}
|
|
144346
|
+
const shouldSetDefaults = !currentEmail || currentEmail === "github-actions[bot]@users.noreply.github.com";
|
|
144347
|
+
if (shouldSetDefaults) {
|
|
144348
|
+
execSync2('git config --local user.email "226033991+pullfrog[bot]@users.noreply.github.com"', {
|
|
144349
|
+
cwd: repoDir,
|
|
144350
|
+
stdio: "pipe"
|
|
144351
|
+
});
|
|
144352
|
+
execSync2('git config --local user.name "pullfrog[bot]"', {
|
|
144353
|
+
cwd: repoDir,
|
|
144354
|
+
stdio: "pipe"
|
|
144355
|
+
});
|
|
144356
|
+
log.debug("\xBB git user configured (using defaults)");
|
|
144357
|
+
} else {
|
|
144358
|
+
log.debug(`\xBB git user already configured (${currentEmail}), skipping`);
|
|
144359
|
+
}
|
|
144360
|
+
if (params.shell === "disabled") {
|
|
144361
|
+
execSync2("git config --local core.hooksPath /dev/null", {
|
|
144362
|
+
cwd: repoDir,
|
|
144363
|
+
stdio: "pipe"
|
|
144364
|
+
});
|
|
144365
|
+
log.debug("\xBB git hooks disabled (shell=disabled)");
|
|
144366
|
+
}
|
|
144367
|
+
} catch (error49) {
|
|
144368
|
+
log.info(`Failed to set git config: ${error49 instanceof Error ? error49.message : String(error49)}`);
|
|
144369
|
+
}
|
|
144370
|
+
try {
|
|
144371
|
+
execSync2("git config --local --unset-all http.https://github.com/.extraheader", {
|
|
144372
|
+
cwd: repoDir,
|
|
144373
|
+
stdio: "pipe"
|
|
144374
|
+
});
|
|
144375
|
+
log.info("\xBB removed existing authentication headers");
|
|
144376
|
+
} catch {
|
|
144377
|
+
log.debug("\xBB no existing authentication headers to remove");
|
|
144378
|
+
}
|
|
144379
|
+
removeIncludeIfEntries(repoDir);
|
|
144380
|
+
const originUrl = `https://github.com/${params.owner}/${params.name}.git`;
|
|
144381
|
+
$("git", ["remote", "set-url", "origin", originUrl], { cwd: repoDir });
|
|
144382
|
+
params.toolState.pushUrl = originUrl;
|
|
144383
|
+
$("git", ["config", "--local", "credential.helper", ""], { cwd: repoDir });
|
|
144384
|
+
params.toolState.initialHead = captureInitialHead(repoDir);
|
|
144385
|
+
log.info("\xBB git authentication configured");
|
|
144386
|
+
}
|
|
144387
|
+
function captureInitialHead(repoDir) {
|
|
144388
|
+
try {
|
|
144389
|
+
const name = $("git", ["symbolic-ref", "--short", "HEAD"], {
|
|
144390
|
+
cwd: repoDir,
|
|
144391
|
+
log: false
|
|
144392
|
+
}).trim();
|
|
144393
|
+
if (name) return { kind: "branch", name };
|
|
144394
|
+
} catch {
|
|
144395
|
+
}
|
|
144396
|
+
const sha = $("git", ["rev-parse", "HEAD"], { cwd: repoDir, log: false }).trim();
|
|
144397
|
+
return { kind: "detached", sha };
|
|
144398
|
+
}
|
|
144399
|
+
|
|
144206
144400
|
// mcp/checkout.ts
|
|
144207
144401
|
function formatFilesWithLineNumbers(files) {
|
|
144208
144402
|
const output = [];
|
|
@@ -144377,7 +144571,7 @@ function cleanupStaleGitLocks() {
|
|
|
144377
144571
|
}
|
|
144378
144572
|
if (now - mtimeMs < STALE_LOCK_AGE_MS) continue;
|
|
144379
144573
|
try {
|
|
144380
|
-
|
|
144574
|
+
unlinkSync3(relPath);
|
|
144381
144575
|
log.warning(`\xBB removed stale ${relPath} from prior run`);
|
|
144382
144576
|
} catch (e) {
|
|
144383
144577
|
log.debug(
|
|
@@ -144536,6 +144730,15 @@ async function checkoutPrBranch(pr, params) {
|
|
|
144536
144730
|
return { hookWarning: postCheckoutHook.warning };
|
|
144537
144731
|
}
|
|
144538
144732
|
var inFlightCheckouts = /* @__PURE__ */ new Map();
|
|
144733
|
+
function headsEqual(a, b) {
|
|
144734
|
+
if (a.kind === "branch" && b.kind === "branch") return a.name === b.name;
|
|
144735
|
+
if (a.kind === "detached" && b.kind === "detached") return a.sha === b.sha;
|
|
144736
|
+
return false;
|
|
144737
|
+
}
|
|
144738
|
+
function describeHead(h) {
|
|
144739
|
+
if (h.kind === "branch") return `branch \`${h.name}\``;
|
|
144740
|
+
return `detached HEAD \`${h.sha}\``;
|
|
144741
|
+
}
|
|
144539
144742
|
function CheckoutPrTool(ctx) {
|
|
144540
144743
|
const runCheckout = async (pull_number) => {
|
|
144541
144744
|
const prResponse = await ctx.octokit.rest.pulls.get({
|
|
@@ -144582,7 +144785,7 @@ function CheckoutPrTool(ctx) {
|
|
|
144582
144785
|
headSha: ctx.toolState.checkoutSha
|
|
144583
144786
|
});
|
|
144584
144787
|
if (incremental) {
|
|
144585
|
-
incrementalDiffPath =
|
|
144788
|
+
incrementalDiffPath = join4(
|
|
144586
144789
|
tempDir,
|
|
144587
144790
|
`pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
|
|
144588
144791
|
);
|
|
@@ -144596,7 +144799,7 @@ function CheckoutPrTool(ctx) {
|
|
|
144596
144799
|
const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
|
|
144597
144800
|
log.debug(`formatted diff preview (first 100 lines):
|
|
144598
144801
|
${diffPreview}`);
|
|
144599
|
-
const diffPath =
|
|
144802
|
+
const diffPath = join4(tempDir, `pr-${pull_number}-${headShort}.diff`);
|
|
144600
144803
|
writeFileSync(diffPath, formatResult.content);
|
|
144601
144804
|
log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
|
|
144602
144805
|
ctx.toolState.diffCoverage = createDiffCoverageState({
|
|
@@ -144663,7 +144866,8 @@ ${diffPreview}`);
|
|
|
144663
144866
|
};
|
|
144664
144867
|
return tool({
|
|
144665
144868
|
name: "checkout_pr",
|
|
144666
|
-
|
|
144869
|
+
timeoutMs: 6e5,
|
|
144870
|
+
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.",
|
|
144667
144871
|
parameters: CheckoutPr,
|
|
144668
144872
|
execute: execute(async ({ pull_number }) => {
|
|
144669
144873
|
const inFlight = inFlightCheckouts.get(pull_number);
|
|
@@ -144671,13 +144875,23 @@ ${diffPreview}`);
|
|
|
144671
144875
|
log.info(`\xBB checkout_pr({pull_number:${pull_number}}) already in flight \u2014 sharing result`);
|
|
144672
144876
|
return inFlight;
|
|
144673
144877
|
}
|
|
144674
|
-
const
|
|
144675
|
-
if (
|
|
144676
|
-
|
|
144677
|
-
|
|
144678
|
-
throw new Error(
|
|
144679
|
-
`cannot checkout PR #${pull_number} while the working tree has uncommitted changes. commit, push, or discard them before switching. dirty paths:
|
|
144878
|
+
const dirty = $("git", ["status", "--porcelain"], { log: false }).trim();
|
|
144879
|
+
if (dirty) {
|
|
144880
|
+
throw new Error(
|
|
144881
|
+
`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:
|
|
144680
144882
|
${dirty}`
|
|
144883
|
+
);
|
|
144884
|
+
}
|
|
144885
|
+
const initialHead = ctx.toolState.initialHead;
|
|
144886
|
+
if (initialHead) {
|
|
144887
|
+
const currentHead = captureInitialHead(process.cwd());
|
|
144888
|
+
const targetBranch = `pr-${pull_number}`;
|
|
144889
|
+
const onTarget = currentHead.kind === "branch" && currentHead.name === targetBranch;
|
|
144890
|
+
const onInitial = headsEqual(currentHead, initialHead);
|
|
144891
|
+
if (!onTarget && !onInitial) {
|
|
144892
|
+
const recoverCmd = initialHead.kind === "branch" ? `git checkout ${initialHead.name}` : `git checkout ${initialHead.sha}`;
|
|
144893
|
+
throw new Error(
|
|
144894
|
+
`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.`
|
|
144681
144895
|
);
|
|
144682
144896
|
}
|
|
144683
144897
|
}
|
|
@@ -144694,7 +144908,7 @@ ${dirty}`
|
|
|
144694
144908
|
|
|
144695
144909
|
// mcp/checkSuite.ts
|
|
144696
144910
|
import { mkdirSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
144697
|
-
import { join as
|
|
144911
|
+
import { join as join5 } from "node:path";
|
|
144698
144912
|
var GetCheckSuiteLogs = type({
|
|
144699
144913
|
check_suite_id: type.number.describe("the id from check_suite.id")
|
|
144700
144914
|
});
|
|
@@ -144790,7 +145004,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
144790
145004
|
if (!tempDir) {
|
|
144791
145005
|
throw new Error("PULLFROG_TEMP_DIR not set");
|
|
144792
145006
|
}
|
|
144793
|
-
const logsDir =
|
|
145007
|
+
const logsDir = join5(tempDir, "ci-logs");
|
|
144794
145008
|
mkdirSync(logsDir, { recursive: true });
|
|
144795
145009
|
const jobResults = [];
|
|
144796
145010
|
for (const run of failedRuns) {
|
|
@@ -144817,7 +145031,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
144817
145031
|
);
|
|
144818
145032
|
}
|
|
144819
145033
|
const logsText = await logsResult.text();
|
|
144820
|
-
const logPath =
|
|
145034
|
+
const logPath = join5(logsDir, `job-${job.id}.log`);
|
|
144821
145035
|
writeFileSync2(logPath, logsText);
|
|
144822
145036
|
const analysis = analyzeLog(logsText, 80);
|
|
144823
145037
|
const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
|
|
@@ -144867,7 +145081,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
144867
145081
|
|
|
144868
145082
|
// mcp/commitInfo.ts
|
|
144869
145083
|
import { writeFileSync as writeFileSync3 } from "node:fs";
|
|
144870
|
-
import { join as
|
|
145084
|
+
import { join as join6 } from "node:path";
|
|
144871
145085
|
var CommitInfo = type({
|
|
144872
145086
|
sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
|
|
144873
145087
|
});
|
|
@@ -144891,7 +145105,7 @@ function CommitInfoTool(ctx) {
|
|
|
144891
145105
|
"PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
|
|
144892
145106
|
);
|
|
144893
145107
|
}
|
|
144894
|
-
const diffFile =
|
|
145108
|
+
const diffFile = join6(tempDir, `commit-${sha.slice(0, 7)}.diff`);
|
|
144895
145109
|
writeFileSync3(diffFile, formatResult.content);
|
|
144896
145110
|
log.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
|
|
144897
145111
|
return {
|
|
@@ -145349,7 +145563,7 @@ function PullRequestInfoTool(ctx) {
|
|
|
145349
145563
|
|
|
145350
145564
|
// mcp/reviewComments.ts
|
|
145351
145565
|
import { writeFileSync as writeFileSync4 } from "node:fs";
|
|
145352
|
-
import { join as
|
|
145566
|
+
import { join as join7 } from "node:path";
|
|
145353
145567
|
var REVIEW_THREADS_QUERY = `
|
|
145354
145568
|
query ($owner: String!, $name: String!, $prNumber: Int!) {
|
|
145355
145569
|
repository(owner: $owner, name: $name) {
|
|
@@ -145741,7 +145955,7 @@ function GetReviewCommentsTool(ctx) {
|
|
|
145741
145955
|
throw new Error("PULLFROG_TEMP_DIR not set");
|
|
145742
145956
|
}
|
|
145743
145957
|
const filename = `review-${params.review_id}-threads.md`;
|
|
145744
|
-
const commentsPath =
|
|
145958
|
+
const commentsPath = join7(tempDir, filename);
|
|
145745
145959
|
writeFileSync4(commentsPath, formatted.content);
|
|
145746
145960
|
log.debug(`wrote ${threadBlocks.length} threads to ${commentsPath}`);
|
|
145747
145961
|
return {
|
|
@@ -145970,7 +146184,7 @@ import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
|
|
|
145970
146184
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
145971
146185
|
import { closeSync, openSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
145972
146186
|
import { userInfo } from "node:os";
|
|
145973
|
-
import { join as
|
|
146187
|
+
import { join as join8 } from "node:path";
|
|
145974
146188
|
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
145975
146189
|
var ShellParams = type({
|
|
145976
146190
|
command: "string",
|
|
@@ -146103,7 +146317,7 @@ function getTempDir() {
|
|
|
146103
146317
|
var MAX_OUTPUT_CHARS = 5e3;
|
|
146104
146318
|
function capOutput(output) {
|
|
146105
146319
|
if (output.length <= MAX_OUTPUT_CHARS) return output;
|
|
146106
|
-
const fullPath =
|
|
146320
|
+
const fullPath = join8(getTempDir(), `shell-${randomUUID2().slice(0, 8)}.log`);
|
|
146107
146321
|
writeFileSync5(fullPath, output);
|
|
146108
146322
|
const elided = output.length - MAX_OUTPUT_CHARS;
|
|
146109
146323
|
return `... [${elided} chars truncated; full output saved to ${fullPath}] ...
|
|
@@ -146118,6 +146332,7 @@ function isGitCommand(command) {
|
|
|
146118
146332
|
function ShellTool(ctx) {
|
|
146119
146333
|
return tool({
|
|
146120
146334
|
name: "shell",
|
|
146335
|
+
timeoutMs: 12e4,
|
|
146121
146336
|
description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
|
|
146122
146337
|
|
|
146123
146338
|
Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
|
|
@@ -146157,8 +146372,8 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
|
|
|
146157
146372
|
if (params.background) {
|
|
146158
146373
|
const tempDir = getTempDir();
|
|
146159
146374
|
const handle = `bg-${randomUUID2().slice(0, 8)}`;
|
|
146160
|
-
const outputPath =
|
|
146161
|
-
const pidPath =
|
|
146375
|
+
const outputPath = join8(tempDir, `${handle}.log`);
|
|
146376
|
+
const pidPath = join8(tempDir, `${handle}.pid`);
|
|
146162
146377
|
const logFd = openSync(outputPath, "a");
|
|
146163
146378
|
let proc2;
|
|
146164
146379
|
try {
|
|
@@ -146498,6 +146713,8 @@ var REVIEWER_AGENT_NAME = "reviewfrog";
|
|
|
146498
146713
|
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.
|
|
146499
146714
|
|
|
146500
146715
|
HARD CONSTRAINTS (non-negotiable, regardless of orchestrator instructions):
|
|
146716
|
+
- 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.
|
|
146717
|
+
- 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.
|
|
146501
146718
|
- 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).
|
|
146502
146719
|
- 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.
|
|
146503
146720
|
- Do NOT spawn further subagents. You are a leaf reviewer; recursive dispatch pre-aggregates findings through an intermediate model and defeats the design.
|
|
@@ -146688,7 +146905,25 @@ function computeModes(agentId) {
|
|
|
146688
146905
|
|
|
146689
146906
|
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.
|
|
146690
146907
|
|
|
146691
|
-
|
|
146908
|
+
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.
|
|
146909
|
+
|
|
146910
|
+
\`\`\`
|
|
146911
|
+
## What you're reviewing
|
|
146912
|
+
This is a PRE-COMMIT Build-mode self-review. The work to review lives in the working tree (uncommitted), NOT in committed history.
|
|
146913
|
+
|
|
146914
|
+
Branch: <branch> (off <base>)
|
|
146915
|
+
Canonical diff command: git diff origin/<base>
|
|
146916
|
+
|
|
146917
|
+
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.
|
|
146918
|
+
|
|
146919
|
+
## Your task
|
|
146920
|
+
<YOUR TASK content>
|
|
146921
|
+
|
|
146922
|
+
## Build-phase failures
|
|
146923
|
+
<tight summary \u2014 what broke, root cause, the fix \u2014 or "no build-phase failures">
|
|
146924
|
+
\`\`\`
|
|
146925
|
+
|
|
146926
|
+
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.
|
|
146692
146927
|
|
|
146693
146928
|
Delegation + research discipline (distilled from \`/anneal\` canonical \u2014 these are codified learnings from many review rounds, not theoretical best practices):
|
|
146694
146929
|
- Do NOT summarize what you implemented \u2014 that biases the subagent toward validating the shape of your solution rather than questioning it.
|
|
@@ -147087,21 +147322,21 @@ function initToolState(params) {
|
|
|
147087
147322
|
}
|
|
147088
147323
|
|
|
147089
147324
|
// agents/claude.ts
|
|
147090
|
-
import { execFileSync as
|
|
147325
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
147091
147326
|
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "node:fs";
|
|
147092
|
-
import { join as
|
|
147327
|
+
import { join as join12 } from "node:path";
|
|
147093
147328
|
import { performance as performance6 } from "node:perf_hooks";
|
|
147094
147329
|
|
|
147095
147330
|
// utils/install.ts
|
|
147096
147331
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
147097
147332
|
import { chmodSync, createWriteStream, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "node:fs";
|
|
147098
|
-
import { join as
|
|
147333
|
+
import { join as join9 } from "node:path";
|
|
147099
147334
|
import { pipeline } from "node:stream/promises";
|
|
147100
147335
|
async function installFromNpmTarball(params) {
|
|
147101
147336
|
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
147102
147337
|
if (!tempDir) throw new Error("PULLFROG_TEMP_DIR is not set");
|
|
147103
|
-
const extractedDir =
|
|
147104
|
-
const cliPath =
|
|
147338
|
+
const extractedDir = join9(tempDir, "package");
|
|
147339
|
+
const cliPath = join9(extractedDir, params.executablePath);
|
|
147105
147340
|
if (existsSync5(cliPath)) {
|
|
147106
147341
|
log.debug(`\xBB using cached binary at ${cliPath}`);
|
|
147107
147342
|
return cliPath;
|
|
@@ -147126,7 +147361,7 @@ async function installFromNpmTarball(params) {
|
|
|
147126
147361
|
}
|
|
147127
147362
|
}
|
|
147128
147363
|
log.debug(`\xBB installing ${params.packageName}@${resolvedVersion}...`);
|
|
147129
|
-
const tarballPath =
|
|
147364
|
+
const tarballPath = join9(tempDir, "package.tgz");
|
|
147130
147365
|
const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org";
|
|
147131
147366
|
let tarballUrl;
|
|
147132
147367
|
if (params.packageName.startsWith("@")) {
|
|
@@ -147265,16 +147500,16 @@ function isRouterKeylimitExhaustedError(text) {
|
|
|
147265
147500
|
// utils/skills.ts
|
|
147266
147501
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
147267
147502
|
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "node:fs";
|
|
147268
|
-
import { tmpdir } from "node:os";
|
|
147269
|
-
import { dirname as dirname2, join as
|
|
147503
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
147504
|
+
import { dirname as dirname2, join as join10 } from "node:path";
|
|
147270
147505
|
import { fileURLToPath } from "node:url";
|
|
147271
147506
|
var skillsVersion = getDevDependencyVersion("skills");
|
|
147272
147507
|
var BUNDLED_SKILL_NAMES = ["git-archaeology"];
|
|
147273
147508
|
function resolveSkillPath(name) {
|
|
147274
147509
|
const here = dirname2(fileURLToPath(import.meta.url));
|
|
147275
147510
|
const candidates = [
|
|
147276
|
-
|
|
147277
|
-
|
|
147511
|
+
join10(here, "..", "skills", name, "SKILL.md"),
|
|
147512
|
+
join10(here, "skills", name, "SKILL.md")
|
|
147278
147513
|
];
|
|
147279
147514
|
for (const candidate of candidates) {
|
|
147280
147515
|
if (existsSync6(candidate)) return candidate;
|
|
@@ -147286,9 +147521,9 @@ function installBundledSkills(params) {
|
|
|
147286
147521
|
for (const name of BUNDLED_SKILL_NAMES) {
|
|
147287
147522
|
const content = readFileSync4(resolveSkillPath(name), "utf8");
|
|
147288
147523
|
for (const targetDir of SKILL_TARGET_DIRS) {
|
|
147289
|
-
const skillDir =
|
|
147524
|
+
const skillDir = join10(params.home, targetDir, name);
|
|
147290
147525
|
mkdirSync3(skillDir, { recursive: true });
|
|
147291
|
-
writeFileSync6(
|
|
147526
|
+
writeFileSync6(join10(skillDir, "SKILL.md"), content);
|
|
147292
147527
|
}
|
|
147293
147528
|
}
|
|
147294
147529
|
log.success(`installed bundled skills: ${BUNDLED_SKILL_NAMES.join(", ")}`);
|
|
@@ -147309,7 +147544,7 @@ function addSkill(params) {
|
|
|
147309
147544
|
"-y"
|
|
147310
147545
|
],
|
|
147311
147546
|
{
|
|
147312
|
-
cwd:
|
|
147547
|
+
cwd: tmpdir2(),
|
|
147313
147548
|
env: { ...process.env, ...params.env },
|
|
147314
147549
|
stdio: "pipe",
|
|
147315
147550
|
timeout: 3e4
|
|
@@ -147394,7 +147629,7 @@ var ThinkingTimer = class {
|
|
|
147394
147629
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
147395
147630
|
import { mkdirSync as mkdirSync4, rmSync, writeFileSync as writeFileSync7 } from "node:fs";
|
|
147396
147631
|
import { homedir } from "node:os";
|
|
147397
|
-
import { join as
|
|
147632
|
+
import { join as join11 } from "node:path";
|
|
147398
147633
|
var VERTEX_SERVICE_ACCOUNT_JSON_ENV = "VERTEX_SERVICE_ACCOUNT_JSON";
|
|
147399
147634
|
var GOOGLE_APPLICATION_CREDENTIALS_ENV = "GOOGLE_APPLICATION_CREDENTIALS";
|
|
147400
147635
|
var GOOGLE_CLOUD_PROJECT_ENV = "GOOGLE_CLOUD_PROJECT";
|
|
@@ -147423,7 +147658,7 @@ function readProjectIdFromVertexServiceAccountJson() {
|
|
|
147423
147658
|
}
|
|
147424
147659
|
function createSecretDir() {
|
|
147425
147660
|
const base = process.env.PULLFROG_SECRET_HOME || process.env.HOME || homedir();
|
|
147426
|
-
const secretDir =
|
|
147661
|
+
const secretDir = join11(base, ".pullfrog", "secrets", randomUUID3());
|
|
147427
147662
|
mkdirSync4(secretDir, { recursive: true, mode: 448 });
|
|
147428
147663
|
return secretDir;
|
|
147429
147664
|
}
|
|
@@ -147432,7 +147667,7 @@ function materializeVertexCredentials(params) {
|
|
|
147432
147667
|
const blob = process.env[VERTEX_SERVICE_ACCOUNT_JSON_ENV];
|
|
147433
147668
|
if (!blob) return void 0;
|
|
147434
147669
|
const secretDir = createSecretDir();
|
|
147435
|
-
const credentialsPath =
|
|
147670
|
+
const credentialsPath = join11(secretDir, "vertex-sa.json");
|
|
147436
147671
|
writeFileSync7(credentialsPath, blob, { mode: 384 });
|
|
147437
147672
|
process.env[GOOGLE_APPLICATION_CREDENTIALS_ENV] = credentialsPath;
|
|
147438
147673
|
const projectId = readProjectIdFromVertexServiceAccountJson();
|
|
@@ -147444,6 +147679,7 @@ function materializeVertexCredentials(params) {
|
|
|
147444
147679
|
function cleanupVertexCredentials(credentials) {
|
|
147445
147680
|
if (!credentials) return;
|
|
147446
147681
|
rmSync(credentials.secretDir, { recursive: true, force: true });
|
|
147682
|
+
delete process.env[GOOGLE_APPLICATION_CREDENTIALS_ENV];
|
|
147447
147683
|
}
|
|
147448
147684
|
function applyClaudeVertexEnv(env2) {
|
|
147449
147685
|
env2.CLAUDE_CODE_USE_VERTEX = "1";
|
|
@@ -147757,9 +147993,9 @@ async function installClaudeCli() {
|
|
|
147757
147993
|
});
|
|
147758
147994
|
}
|
|
147759
147995
|
function writeMcpConfig(ctx) {
|
|
147760
|
-
const configDir =
|
|
147996
|
+
const configDir = join12(ctx.tmpdir, ".claude");
|
|
147761
147997
|
mkdirSync5(configDir, { recursive: true });
|
|
147762
|
-
const configPath =
|
|
147998
|
+
const configPath = join12(configDir, "mcp.json");
|
|
147763
147999
|
writeFileSync8(
|
|
147764
148000
|
configPath,
|
|
147765
148001
|
JSON.stringify({
|
|
@@ -148185,8 +148421,8 @@ function installManagedSettings(ctx) {
|
|
|
148185
148421
|
if (process.env.CI !== "true") return;
|
|
148186
148422
|
const content = JSON.stringify(buildManagedSettings(ctx), null, 2);
|
|
148187
148423
|
try {
|
|
148188
|
-
|
|
148189
|
-
|
|
148424
|
+
execFileSync4("sudo", ["mkdir", "-p", MANAGED_SETTINGS_DIR]);
|
|
148425
|
+
execFileSync4("sudo", ["tee", MANAGED_SETTINGS_PATH], {
|
|
148190
148426
|
input: content,
|
|
148191
148427
|
stdio: ["pipe", "ignore", "pipe"]
|
|
148192
148428
|
});
|
|
@@ -148208,15 +148444,15 @@ var claude = agent({
|
|
|
148208
148444
|
const model = !specifier ? void 0 : isBedrockRoute ? specifier : isVertexRoute2 ? void 0 : stripProviderPrefix(specifier);
|
|
148209
148445
|
const homeEnv = {
|
|
148210
148446
|
HOME: ctx.tmpdir,
|
|
148211
|
-
XDG_CONFIG_HOME:
|
|
148447
|
+
XDG_CONFIG_HOME: join12(ctx.tmpdir, ".config")
|
|
148212
148448
|
};
|
|
148213
|
-
mkdirSync5(
|
|
148449
|
+
mkdirSync5(join12(homeEnv.XDG_CONFIG_HOME, "claude"), { recursive: true });
|
|
148214
148450
|
const agentBrowserVersion = getDevDependencyVersion("agent-browser");
|
|
148215
148451
|
addSkill({
|
|
148216
148452
|
ref: `vercel-labs/agent-browser@v${agentBrowserVersion}`,
|
|
148217
148453
|
skill: "agent-browser",
|
|
148218
148454
|
env: homeEnv,
|
|
148219
|
-
agent: "claude"
|
|
148455
|
+
agent: "claude-code"
|
|
148220
148456
|
});
|
|
148221
148457
|
installBundledSkills({ home: homeEnv.HOME });
|
|
148222
148458
|
const mcpConfigPath = writeMcpConfig(ctx);
|
|
@@ -148295,7 +148531,7 @@ var claude = agent({
|
|
|
148295
148531
|
// agents/opencode_v2.ts
|
|
148296
148532
|
var core2 = __toESM(require_core(), 1);
|
|
148297
148533
|
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync10 } from "node:fs";
|
|
148298
|
-
import { join as
|
|
148534
|
+
import { join as join14 } from "node:path";
|
|
148299
148535
|
import { performance as performance7 } from "node:perf_hooks";
|
|
148300
148536
|
|
|
148301
148537
|
// utils/agentHangReport.ts
|
|
@@ -148396,7 +148632,7 @@ function formatBillingExhaustedBody(diagnostic) {
|
|
|
148396
148632
|
// utils/codexHome.ts
|
|
148397
148633
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync9 } from "node:fs";
|
|
148398
148634
|
import { homedir as homedir2 } from "node:os";
|
|
148399
|
-
import { join as
|
|
148635
|
+
import { join as join13 } from "node:path";
|
|
148400
148636
|
var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
|
|
148401
148637
|
function installCodexAuth() {
|
|
148402
148638
|
const raw2 = process.env[CODEX_AUTH_ENV];
|
|
@@ -148406,9 +148642,9 @@ function installCodexAuth() {
|
|
|
148406
148642
|
log.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
|
|
148407
148643
|
return null;
|
|
148408
148644
|
}
|
|
148409
|
-
const xdgDataHome =
|
|
148410
|
-
const opencodeDir =
|
|
148411
|
-
const authPath =
|
|
148645
|
+
const xdgDataHome = join13(homedir2(), ".local", "share");
|
|
148646
|
+
const opencodeDir = join13(xdgDataHome, "opencode");
|
|
148647
|
+
const authPath = join13(opencodeDir, "auth.json");
|
|
148412
148648
|
const opencodeAuth = {
|
|
148413
148649
|
openai: {
|
|
148414
148650
|
type: "oauth",
|
|
@@ -148536,7 +148772,7 @@ export default async function pullfrogEventsPlugin() {
|
|
|
148536
148772
|
`;
|
|
148537
148773
|
|
|
148538
148774
|
// agents/opencodeShared.ts
|
|
148539
|
-
import { execFileSync as
|
|
148775
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
148540
148776
|
|
|
148541
148777
|
// agents/subagentModels.ts
|
|
148542
148778
|
function deriveSubagentModels(orchestratorSpec) {
|
|
@@ -148585,7 +148821,7 @@ async function installOpencodeCli(params) {
|
|
|
148585
148821
|
var AUTO_SELECT_WARNING = "select a model explicitly in the Pullfrog console (https://pullfrog.com/console) to avoid this.";
|
|
148586
148822
|
function getOpenCodeModels(cliPath) {
|
|
148587
148823
|
try {
|
|
148588
|
-
const output =
|
|
148824
|
+
const output = execFileSync5(cliPath, ["models"], {
|
|
148589
148825
|
encoding: "utf-8",
|
|
148590
148826
|
timeout: 3e4,
|
|
148591
148827
|
env: process.env
|
|
@@ -149082,13 +149318,13 @@ var opencode = agent({
|
|
|
149082
149318
|
const model = vertexModel ?? (isBedrockRoute ? `amazon-bedrock/${rawModel}` : rawModel);
|
|
149083
149319
|
const homeEnv = {
|
|
149084
149320
|
HOME: ctx.tmpdir,
|
|
149085
|
-
XDG_CONFIG_HOME:
|
|
149321
|
+
XDG_CONFIG_HOME: join14(ctx.tmpdir, ".config")
|
|
149086
149322
|
};
|
|
149087
|
-
mkdirSync7(
|
|
149088
|
-
const opencodePluginDir =
|
|
149323
|
+
mkdirSync7(join14(homeEnv.XDG_CONFIG_HOME, "opencode"), { recursive: true });
|
|
149324
|
+
const opencodePluginDir = join14(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
|
|
149089
149325
|
mkdirSync7(opencodePluginDir, { recursive: true });
|
|
149090
149326
|
writeFileSync10(
|
|
149091
|
-
|
|
149327
|
+
join14(opencodePluginDir, PULLFROG_OPENCODE_PLUGIN_FILENAME),
|
|
149092
149328
|
PULLFROG_OPENCODE_PLUGIN_SOURCE
|
|
149093
149329
|
);
|
|
149094
149330
|
const agentBrowserVersion = getDevDependencyVersion("agent-browser");
|
|
@@ -149450,7 +149686,7 @@ async function fetchBodyHtml(ctx) {
|
|
|
149450
149686
|
}
|
|
149451
149687
|
|
|
149452
149688
|
// utils/byokFallback.ts
|
|
149453
|
-
var FREE_FALLBACK_SLUG = "opencode/
|
|
149689
|
+
var FREE_FALLBACK_SLUG = "opencode/big-pickle";
|
|
149454
149690
|
function selectFallbackModelIfNeeded(input) {
|
|
149455
149691
|
if (input.proxyModel) return { fallback: false };
|
|
149456
149692
|
if (!input.resolvedModel) return { fallback: false };
|
|
@@ -149468,7 +149704,7 @@ function selectFallbackModelIfNeeded(input) {
|
|
|
149468
149704
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
149469
149705
|
import { writeFileSync as writeFileSync11 } from "node:fs";
|
|
149470
149706
|
import { createServer as createServer2 } from "node:http";
|
|
149471
|
-
import { join as
|
|
149707
|
+
import { join as join15 } from "node:path";
|
|
149472
149708
|
var CODE_TTL_MS = 5 * 60 * 1e3;
|
|
149473
149709
|
var TAMPER_WINDOW_MS = 6e4;
|
|
149474
149710
|
function revokeGitHubToken(token) {
|
|
@@ -149540,7 +149776,7 @@ async function startGitAuthServer(tmpdir3) {
|
|
|
149540
149776
|
function writeAskpassScript(code) {
|
|
149541
149777
|
const scriptId = randomUUID4();
|
|
149542
149778
|
const scriptName = `askpass-${scriptId}.js`;
|
|
149543
|
-
const scriptPath =
|
|
149779
|
+
const scriptPath = join15(tmpdir3, scriptName);
|
|
149544
149780
|
const content = [
|
|
149545
149781
|
`#!/usr/bin/env node`,
|
|
149546
149782
|
`var a=process.argv[2]||"";`,
|
|
@@ -149579,7 +149815,7 @@ async function startGitAuthServer(tmpdir3) {
|
|
|
149579
149815
|
var core3 = __toESM(require_core(), 1);
|
|
149580
149816
|
import { createSign } from "node:crypto";
|
|
149581
149817
|
import { rename, writeFile } from "node:fs/promises";
|
|
149582
|
-
import { dirname as dirname3, join as
|
|
149818
|
+
import { dirname as dirname3, join as join16 } from "node:path";
|
|
149583
149819
|
|
|
149584
149820
|
// node_modules/.pnpm/@octokit+plugin-throttling@11.0.3_@octokit+core@7.0.5/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
|
|
149585
149821
|
var import_light = __toESM(require_light(), 1);
|
|
@@ -153437,7 +153673,7 @@ function getGitHubUsageSummary() {
|
|
|
153437
153673
|
}
|
|
153438
153674
|
async function writeGitHubUsageSummaryToFile(path3) {
|
|
153439
153675
|
const summary2 = getGitHubUsageSummary();
|
|
153440
|
-
const tmpPath =
|
|
153676
|
+
const tmpPath = join16(dirname3(path3), `.usage-summary-${process.pid}.tmp`);
|
|
153441
153677
|
await writeFile(tmpPath, JSON.stringify(summary2));
|
|
153442
153678
|
await rename(tmpPath, path3);
|
|
153443
153679
|
}
|
|
@@ -153488,7 +153724,7 @@ function createOctokit(token) {
|
|
|
153488
153724
|
}
|
|
153489
153725
|
|
|
153490
153726
|
// utils/instructions.ts
|
|
153491
|
-
import { execSync as
|
|
153727
|
+
import { execSync as execSync3 } from "node:child_process";
|
|
153492
153728
|
function buildRuntimeContext(ctx) {
|
|
153493
153729
|
const {
|
|
153494
153730
|
"~pullfrog": _,
|
|
@@ -153500,7 +153736,7 @@ function buildRuntimeContext(ctx) {
|
|
|
153500
153736
|
} = ctx.payload;
|
|
153501
153737
|
let gitStatus;
|
|
153502
153738
|
try {
|
|
153503
|
-
gitStatus =
|
|
153739
|
+
gitStatus = execSync3("git status --short", { encoding: "utf-8", stdio: "pipe" }).trim() || "(clean)";
|
|
153504
153740
|
} catch {
|
|
153505
153741
|
}
|
|
153506
153742
|
const data = {
|
|
@@ -153837,7 +154073,7 @@ function resolveInstructions(ctx) {
|
|
|
153837
154073
|
|
|
153838
154074
|
// utils/learnings.ts
|
|
153839
154075
|
import { mkdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
153840
|
-
import { dirname as dirname4, join as
|
|
154076
|
+
import { dirname as dirname4, join as join17 } from "node:path";
|
|
153841
154077
|
|
|
153842
154078
|
// utils/learningsTruncate.ts
|
|
153843
154079
|
var MAX_LEARNINGS_LENGTH = 1e5;
|
|
@@ -153854,7 +154090,7 @@ function truncateAtLineBoundary(body, cap) {
|
|
|
153854
154090
|
// utils/learnings.ts
|
|
153855
154091
|
var LEARNINGS_FILE_NAME = "pullfrog-learnings.md";
|
|
153856
154092
|
function learningsFilePath(tmpdir3) {
|
|
153857
|
-
return
|
|
154093
|
+
return join17(tmpdir3, LEARNINGS_FILE_NAME);
|
|
153858
154094
|
}
|
|
153859
154095
|
async function seedLearningsFile(params) {
|
|
153860
154096
|
const path3 = learningsFilePath(params.tmpdir);
|
|
@@ -154534,7 +154770,7 @@ async function runProxyResolution(ctx) {
|
|
|
154534
154770
|
|
|
154535
154771
|
// utils/prSummary.ts
|
|
154536
154772
|
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
154537
|
-
import { dirname as dirname5, join as
|
|
154773
|
+
import { dirname as dirname5, join as join18 } from "node:path";
|
|
154538
154774
|
var SUMMARY_FILE_NAME = "pullfrog-summary.md";
|
|
154539
154775
|
var SUMMARY_SCAFFOLD = `# PR summary
|
|
154540
154776
|
|
|
@@ -154544,7 +154780,7 @@ var SUMMARY_SCAFFOLD = `# PR summary
|
|
|
154544
154780
|
var MIN_SNAPSHOT_LENGTH = 60;
|
|
154545
154781
|
var MAX_SNAPSHOT_LENGTH = 32768;
|
|
154546
154782
|
function summaryFilePath(tmpdir3) {
|
|
154547
|
-
return
|
|
154783
|
+
return join18(tmpdir3, SUMMARY_FILE_NAME);
|
|
154548
154784
|
}
|
|
154549
154785
|
async function seedSummaryFile(params) {
|
|
154550
154786
|
const path3 = summaryFilePath(params.tmpdir);
|
|
@@ -154738,6 +154974,16 @@ async function resolveRunContextData(params) {
|
|
|
154738
154974
|
}
|
|
154739
154975
|
|
|
154740
154976
|
// utils/runErrorRenderer.ts
|
|
154977
|
+
function isProviderModelNotFoundError(message) {
|
|
154978
|
+
return message.includes("ProviderModelNotFoundError");
|
|
154979
|
+
}
|
|
154980
|
+
function formatProviderModelNotFoundSummary(input) {
|
|
154981
|
+
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.
|
|
154982
|
+
|
|
154983
|
+
\`\`\`
|
|
154984
|
+
${input.raw}
|
|
154985
|
+
\`\`\``;
|
|
154986
|
+
}
|
|
154741
154987
|
function renderRunError(input) {
|
|
154742
154988
|
const billingError = isRouterKeylimitExhaustedError(input.errorMessage) ? new BillingError(input.errorMessage, { code: "router_keylimit_exhausted" }) : null;
|
|
154743
154989
|
if (billingError) {
|
|
@@ -154759,6 +155005,14 @@ function renderRunError(input) {
|
|
|
154759
155005
|
if (apiKeyErrorSummary) {
|
|
154760
155006
|
return { summary: apiKeyErrorSummary, comment: apiKeyErrorSummary };
|
|
154761
155007
|
}
|
|
155008
|
+
if (isProviderModelNotFoundError(input.errorMessage)) {
|
|
155009
|
+
const body = formatProviderModelNotFoundSummary({
|
|
155010
|
+
owner: input.repo.owner,
|
|
155011
|
+
name: input.repo.name,
|
|
155012
|
+
raw: input.errorMessage
|
|
155013
|
+
});
|
|
155014
|
+
return { summary: body, comment: body };
|
|
155015
|
+
}
|
|
154762
155016
|
if (hangBody) {
|
|
154763
155017
|
return {
|
|
154764
155018
|
summary: `### \u274C Pullfrog failed
|
|
@@ -154860,16 +155114,17 @@ async function persistRunArtifacts(toolContext) {
|
|
|
154860
155114
|
}
|
|
154861
155115
|
async function finalizeSuccessRun(input) {
|
|
154862
155116
|
await persistRunArtifacts(input.toolContext);
|
|
154863
|
-
|
|
154864
|
-
|
|
154865
|
-
|
|
154866
|
-
|
|
154867
|
-
|
|
154868
|
-
|
|
154869
|
-
|
|
154870
|
-
|
|
154871
|
-
|
|
154872
|
-
|
|
155117
|
+
const rendered = !input.result.success ? renderRunError({
|
|
155118
|
+
errorMessage: input.result.error || "agent run failed",
|
|
155119
|
+
repo: input.repo,
|
|
155120
|
+
agentDiagnostic: input.toolState.agentDiagnostic
|
|
155121
|
+
}) : null;
|
|
155122
|
+
if (rendered && input.toolState.progressComment) {
|
|
155123
|
+
await reportErrorToComment({ toolState: input.toolState, error: rendered.comment }).catch(
|
|
155124
|
+
(error49) => {
|
|
155125
|
+
log.debug(`failure error report failed: ${error49}`);
|
|
155126
|
+
}
|
|
155127
|
+
);
|
|
154873
155128
|
}
|
|
154874
155129
|
if (input.result.success && input.toolState.progressComment && !input.toolState.finalSummaryWritten) {
|
|
154875
155130
|
await deleteProgressComment(input.toolContext).catch((error49) => {
|
|
@@ -154879,7 +155134,7 @@ async function finalizeSuccessRun(input) {
|
|
|
154879
155134
|
try {
|
|
154880
155135
|
const usageSummary = formatUsageSummary(input.toolState.usageEntries);
|
|
154881
155136
|
const body = input.toolState.lastProgressBody || input.result.output;
|
|
154882
|
-
const parts = [body, usageSummary].filter(Boolean);
|
|
155137
|
+
const parts = [rendered?.summary, body, usageSummary].filter(Boolean);
|
|
154883
155138
|
if (parts.length > 0) {
|
|
154884
155139
|
await writeSummary(parts.join("\n\n"));
|
|
154885
155140
|
}
|
|
@@ -154964,116 +155219,6 @@ function logRunStartup(ctx) {
|
|
|
154964
155219
|
log.info(`\xBB timeout: ${resolveTimeoutForLog(ctx.payload.timeout)}`);
|
|
154965
155220
|
}
|
|
154966
155221
|
|
|
154967
|
-
// utils/setup.ts
|
|
154968
|
-
import { execFileSync as execFileSync5, execSync as execSync3 } from "node:child_process";
|
|
154969
|
-
import { mkdtempSync } from "node:fs";
|
|
154970
|
-
import { tmpdir as tmpdir2 } from "node:os";
|
|
154971
|
-
import { join as join18 } from "node:path";
|
|
154972
|
-
function createTempDirectory() {
|
|
154973
|
-
const sharedTempDir = mkdtempSync(join18(tmpdir2(), "pullfrog-"));
|
|
154974
|
-
process.env.PULLFROG_TEMP_DIR = sharedTempDir;
|
|
154975
|
-
log.info(`\xBB created temp dir at ${sharedTempDir}`);
|
|
154976
|
-
return sharedTempDir;
|
|
154977
|
-
}
|
|
154978
|
-
function envScopedToRepo() {
|
|
154979
|
-
const scoped = { ...process.env };
|
|
154980
|
-
for (const key of Object.keys(scoped)) {
|
|
154981
|
-
if (key.startsWith("GIT_")) delete scoped[key];
|
|
154982
|
-
}
|
|
154983
|
-
return scoped;
|
|
154984
|
-
}
|
|
154985
|
-
function removeIncludeIfEntries(repoDir) {
|
|
154986
|
-
const env2 = envScopedToRepo();
|
|
154987
|
-
let configOutput;
|
|
154988
|
-
try {
|
|
154989
|
-
configOutput = execSync3("git config --local --get-regexp -z ^includeif\\.", {
|
|
154990
|
-
cwd: repoDir,
|
|
154991
|
-
encoding: "utf-8",
|
|
154992
|
-
stdio: "pipe",
|
|
154993
|
-
env: env2
|
|
154994
|
-
});
|
|
154995
|
-
} catch {
|
|
154996
|
-
log.debug("\xBB no includeIf credential entries to remove");
|
|
154997
|
-
return;
|
|
154998
|
-
}
|
|
154999
|
-
const seen = /* @__PURE__ */ new Set();
|
|
155000
|
-
for (const entry of configOutput.split("\0")) {
|
|
155001
|
-
if (!entry) continue;
|
|
155002
|
-
const nl = entry.indexOf("\n");
|
|
155003
|
-
const key = nl === -1 ? entry : entry.slice(0, nl);
|
|
155004
|
-
if (!key || seen.has(key)) continue;
|
|
155005
|
-
seen.add(key);
|
|
155006
|
-
try {
|
|
155007
|
-
execFileSync5("git", ["config", "--local", "--unset-all", key], {
|
|
155008
|
-
cwd: repoDir,
|
|
155009
|
-
stdio: "pipe",
|
|
155010
|
-
env: env2
|
|
155011
|
-
});
|
|
155012
|
-
} catch (error49) {
|
|
155013
|
-
log.debug(
|
|
155014
|
-
`\xBB failed to unset ${key}: ${error49 instanceof Error ? error49.message : String(error49)}`
|
|
155015
|
-
);
|
|
155016
|
-
}
|
|
155017
|
-
}
|
|
155018
|
-
if (seen.size > 0)
|
|
155019
|
-
log.info(
|
|
155020
|
-
`\xBB removed ${seen.size} includeIf credential ${seen.size === 1 ? "entry" : "entries"}`
|
|
155021
|
-
);
|
|
155022
|
-
}
|
|
155023
|
-
async function setupGit(params) {
|
|
155024
|
-
const repoDir = process.cwd();
|
|
155025
|
-
log.info("\xBB setting up git configuration...");
|
|
155026
|
-
try {
|
|
155027
|
-
let currentEmail = "";
|
|
155028
|
-
try {
|
|
155029
|
-
currentEmail = execSync3("git config user.email", {
|
|
155030
|
-
cwd: repoDir,
|
|
155031
|
-
stdio: "pipe",
|
|
155032
|
-
encoding: "utf-8"
|
|
155033
|
-
}).trim();
|
|
155034
|
-
} catch {
|
|
155035
|
-
}
|
|
155036
|
-
const shouldSetDefaults = !currentEmail || currentEmail === "github-actions[bot]@users.noreply.github.com";
|
|
155037
|
-
if (shouldSetDefaults) {
|
|
155038
|
-
execSync3('git config --local user.email "226033991+pullfrog[bot]@users.noreply.github.com"', {
|
|
155039
|
-
cwd: repoDir,
|
|
155040
|
-
stdio: "pipe"
|
|
155041
|
-
});
|
|
155042
|
-
execSync3('git config --local user.name "pullfrog[bot]"', {
|
|
155043
|
-
cwd: repoDir,
|
|
155044
|
-
stdio: "pipe"
|
|
155045
|
-
});
|
|
155046
|
-
log.debug("\xBB git user configured (using defaults)");
|
|
155047
|
-
} else {
|
|
155048
|
-
log.debug(`\xBB git user already configured (${currentEmail}), skipping`);
|
|
155049
|
-
}
|
|
155050
|
-
if (params.shell === "disabled") {
|
|
155051
|
-
execSync3("git config --local core.hooksPath /dev/null", {
|
|
155052
|
-
cwd: repoDir,
|
|
155053
|
-
stdio: "pipe"
|
|
155054
|
-
});
|
|
155055
|
-
log.debug("\xBB git hooks disabled (shell=disabled)");
|
|
155056
|
-
}
|
|
155057
|
-
} catch (error49) {
|
|
155058
|
-
log.info(`Failed to set git config: ${error49 instanceof Error ? error49.message : String(error49)}`);
|
|
155059
|
-
}
|
|
155060
|
-
try {
|
|
155061
|
-
execSync3("git config --local --unset-all http.https://github.com/.extraheader", {
|
|
155062
|
-
cwd: repoDir,
|
|
155063
|
-
stdio: "pipe"
|
|
155064
|
-
});
|
|
155065
|
-
log.info("\xBB removed existing authentication headers");
|
|
155066
|
-
} catch {
|
|
155067
|
-
log.debug("\xBB no existing authentication headers to remove");
|
|
155068
|
-
}
|
|
155069
|
-
removeIncludeIfEntries(repoDir);
|
|
155070
|
-
const originUrl = `https://github.com/${params.owner}/${params.name}.git`;
|
|
155071
|
-
$("git", ["remote", "set-url", "origin", originUrl], { cwd: repoDir });
|
|
155072
|
-
params.toolState.pushUrl = originUrl;
|
|
155073
|
-
$("git", ["config", "--local", "credential.helper", ""], { cwd: repoDir });
|
|
155074
|
-
log.info("\xBB git authentication configured");
|
|
155075
|
-
}
|
|
155076
|
-
|
|
155077
155222
|
// utils/todoTracking.ts
|
|
155078
155223
|
function isValidTodoStatus(value2) {
|
|
155079
155224
|
return value2 === "pending" || value2 === "in_progress" || value2 === "completed" || value2 === "cancelled";
|
|
@@ -155281,6 +155426,7 @@ async function main() {
|
|
|
155281
155426
|
toolState.beforeSha = payload.event.before_sha;
|
|
155282
155427
|
}
|
|
155283
155428
|
const tokenRef = __using(_stack2, await resolveTokens({ push: payload.push }), true);
|
|
155429
|
+
wipeRunnerLeakSurface();
|
|
155284
155430
|
const oidcCredentials = process.env.ACTIONS_ID_TOKEN_REQUEST_URL && process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN ? {
|
|
155285
155431
|
requestUrl: process.env.ACTIONS_ID_TOKEN_REQUEST_URL,
|
|
155286
155432
|
requestToken: process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN
|
|
@@ -155450,7 +155596,7 @@ ${instructions.user}` : null,
|
|
|
155450
155596
|
});
|
|
155451
155597
|
if (agentId === "opencode") {
|
|
155452
155598
|
const pluginDir = join19(process.cwd(), ".opencode", "plugin");
|
|
155453
|
-
const hasPlugins = existsSync7(pluginDir) &&
|
|
155599
|
+
const hasPlugins = existsSync7(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
|
|
155454
155600
|
if (hasPlugins && toolState.dependencyInstallation?.promise) {
|
|
155455
155601
|
log.info(
|
|
155456
155602
|
"\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"
|