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/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.13",
|
|
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";
|
|
@@ -143363,7 +143380,13 @@ var TRANSIENT_PATTERNS = [
|
|
|
143363
143380
|
/HTTP 5\d\d/,
|
|
143364
143381
|
/returned error: 5\d\d/i,
|
|
143365
143382
|
/HTTP 429/,
|
|
143366
|
-
/returned error: 429/i
|
|
143383
|
+
/returned error: 429/i,
|
|
143384
|
+
// github installation tokens can 401 for seconds after minting while
|
|
143385
|
+
// replicating (@octokit/auth-app retries the same class). git push
|
|
143386
|
+
// surfaces it as "Invalid username or token", distinct from 403
|
|
143387
|
+
// permission denied — safe to backoff-retry with the same token.
|
|
143388
|
+
/Invalid username or token/,
|
|
143389
|
+
/Authentication failed for 'https:\/\/github\.com\//
|
|
143367
143390
|
];
|
|
143368
143391
|
function classifyPushError(msg) {
|
|
143369
143392
|
if (CONCURRENT_PUSH_PATTERNS.some((p) => msg.includes(p))) return "concurrent-push";
|
|
@@ -144203,6 +144226,183 @@ async function reportReviewNodeId(ctx, params) {
|
|
|
144203
144226
|
await patchWorkflowRunFields(ctx, { reviewNodeId: params.nodeId });
|
|
144204
144227
|
}
|
|
144205
144228
|
|
|
144229
|
+
// utils/setup.ts
|
|
144230
|
+
import { execFileSync as execFileSync3, execSync as execSync2 } from "node:child_process";
|
|
144231
|
+
import { mkdtempSync, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
|
|
144232
|
+
import { tmpdir } from "node:os";
|
|
144233
|
+
import { join as join3 } from "node:path";
|
|
144234
|
+
function createTempDirectory() {
|
|
144235
|
+
const sharedTempDir = mkdtempSync(join3(tmpdir(), "pullfrog-"));
|
|
144236
|
+
process.env.PULLFROG_TEMP_DIR = sharedTempDir;
|
|
144237
|
+
log.info(`\xBB created temp dir at ${sharedTempDir}`);
|
|
144238
|
+
return sharedTempDir;
|
|
144239
|
+
}
|
|
144240
|
+
function wipeRunnerLeakSurface() {
|
|
144241
|
+
const runnerTemp = process.env.RUNNER_TEMP;
|
|
144242
|
+
if (!runnerTemp) return;
|
|
144243
|
+
const preserve = /* @__PURE__ */ new Set();
|
|
144244
|
+
for (const envVar of [
|
|
144245
|
+
"GITHUB_OUTPUT",
|
|
144246
|
+
"GITHUB_ENV",
|
|
144247
|
+
"GITHUB_PATH",
|
|
144248
|
+
"GITHUB_STATE",
|
|
144249
|
+
"GITHUB_STEP_SUMMARY"
|
|
144250
|
+
]) {
|
|
144251
|
+
const path3 = process.env[envVar];
|
|
144252
|
+
if (!path3) continue;
|
|
144253
|
+
try {
|
|
144254
|
+
preserve.add(realpathSync2(path3));
|
|
144255
|
+
} catch {
|
|
144256
|
+
preserve.add(path3);
|
|
144257
|
+
}
|
|
144258
|
+
}
|
|
144259
|
+
const wiped = [];
|
|
144260
|
+
const tryUnlink = (path3) => {
|
|
144261
|
+
let resolved = path3;
|
|
144262
|
+
try {
|
|
144263
|
+
resolved = realpathSync2(path3);
|
|
144264
|
+
} catch {
|
|
144265
|
+
}
|
|
144266
|
+
if (preserve.has(resolved) || preserve.has(path3)) return;
|
|
144267
|
+
try {
|
|
144268
|
+
unlinkSync2(path3);
|
|
144269
|
+
wiped.push(path3);
|
|
144270
|
+
} catch {
|
|
144271
|
+
}
|
|
144272
|
+
};
|
|
144273
|
+
const listDir = (dir) => {
|
|
144274
|
+
try {
|
|
144275
|
+
return readdirSync(dir);
|
|
144276
|
+
} catch {
|
|
144277
|
+
return [];
|
|
144278
|
+
}
|
|
144279
|
+
};
|
|
144280
|
+
const fileCommandsDir = join3(runnerTemp, "_runner_file_commands");
|
|
144281
|
+
for (const entry of listDir(fileCommandsDir)) {
|
|
144282
|
+
tryUnlink(join3(fileCommandsDir, entry));
|
|
144283
|
+
}
|
|
144284
|
+
for (const entry of listDir(runnerTemp)) {
|
|
144285
|
+
if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
|
|
144286
|
+
tryUnlink(join3(runnerTemp, entry));
|
|
144287
|
+
}
|
|
144288
|
+
}
|
|
144289
|
+
if (wiped.length > 0) {
|
|
144290
|
+
log.info(`\xBB wiped ${wiped.length} leak-surface file(s) from $RUNNER_TEMP`);
|
|
144291
|
+
log.debug(`\xBB wiped paths: ${wiped.join(", ")}`);
|
|
144292
|
+
}
|
|
144293
|
+
}
|
|
144294
|
+
function envScopedToRepo() {
|
|
144295
|
+
const scoped = { ...process.env };
|
|
144296
|
+
for (const key of Object.keys(scoped)) {
|
|
144297
|
+
if (key.startsWith("GIT_")) delete scoped[key];
|
|
144298
|
+
}
|
|
144299
|
+
return scoped;
|
|
144300
|
+
}
|
|
144301
|
+
function removeIncludeIfEntries(repoDir) {
|
|
144302
|
+
const env2 = envScopedToRepo();
|
|
144303
|
+
let configOutput;
|
|
144304
|
+
try {
|
|
144305
|
+
configOutput = execSync2("git config --local --get-regexp -z ^includeif\\.", {
|
|
144306
|
+
cwd: repoDir,
|
|
144307
|
+
encoding: "utf-8",
|
|
144308
|
+
stdio: "pipe",
|
|
144309
|
+
env: env2
|
|
144310
|
+
});
|
|
144311
|
+
} catch {
|
|
144312
|
+
log.debug("\xBB no includeIf credential entries to remove");
|
|
144313
|
+
return;
|
|
144314
|
+
}
|
|
144315
|
+
const seen = /* @__PURE__ */ new Set();
|
|
144316
|
+
for (const entry of configOutput.split("\0")) {
|
|
144317
|
+
if (!entry) continue;
|
|
144318
|
+
const nl = entry.indexOf("\n");
|
|
144319
|
+
const key = nl === -1 ? entry : entry.slice(0, nl);
|
|
144320
|
+
if (!key || seen.has(key)) continue;
|
|
144321
|
+
seen.add(key);
|
|
144322
|
+
try {
|
|
144323
|
+
execFileSync3("git", ["config", "--local", "--unset-all", key], {
|
|
144324
|
+
cwd: repoDir,
|
|
144325
|
+
stdio: "pipe",
|
|
144326
|
+
env: env2
|
|
144327
|
+
});
|
|
144328
|
+
} catch (error49) {
|
|
144329
|
+
log.debug(
|
|
144330
|
+
`\xBB failed to unset ${key}: ${error49 instanceof Error ? error49.message : String(error49)}`
|
|
144331
|
+
);
|
|
144332
|
+
}
|
|
144333
|
+
}
|
|
144334
|
+
if (seen.size > 0)
|
|
144335
|
+
log.info(
|
|
144336
|
+
`\xBB removed ${seen.size} includeIf credential ${seen.size === 1 ? "entry" : "entries"}`
|
|
144337
|
+
);
|
|
144338
|
+
}
|
|
144339
|
+
async function setupGit(params) {
|
|
144340
|
+
const repoDir = process.cwd();
|
|
144341
|
+
log.info("\xBB setting up git configuration...");
|
|
144342
|
+
try {
|
|
144343
|
+
let currentEmail = "";
|
|
144344
|
+
try {
|
|
144345
|
+
currentEmail = execSync2("git config user.email", {
|
|
144346
|
+
cwd: repoDir,
|
|
144347
|
+
stdio: "pipe",
|
|
144348
|
+
encoding: "utf-8"
|
|
144349
|
+
}).trim();
|
|
144350
|
+
} catch {
|
|
144351
|
+
}
|
|
144352
|
+
const shouldSetDefaults = !currentEmail || currentEmail === "github-actions[bot]@users.noreply.github.com";
|
|
144353
|
+
if (shouldSetDefaults) {
|
|
144354
|
+
execSync2('git config --local user.email "226033991+pullfrog[bot]@users.noreply.github.com"', {
|
|
144355
|
+
cwd: repoDir,
|
|
144356
|
+
stdio: "pipe"
|
|
144357
|
+
});
|
|
144358
|
+
execSync2('git config --local user.name "pullfrog[bot]"', {
|
|
144359
|
+
cwd: repoDir,
|
|
144360
|
+
stdio: "pipe"
|
|
144361
|
+
});
|
|
144362
|
+
log.debug("\xBB git user configured (using defaults)");
|
|
144363
|
+
} else {
|
|
144364
|
+
log.debug(`\xBB git user already configured (${currentEmail}), skipping`);
|
|
144365
|
+
}
|
|
144366
|
+
if (params.shell === "disabled") {
|
|
144367
|
+
execSync2("git config --local core.hooksPath /dev/null", {
|
|
144368
|
+
cwd: repoDir,
|
|
144369
|
+
stdio: "pipe"
|
|
144370
|
+
});
|
|
144371
|
+
log.debug("\xBB git hooks disabled (shell=disabled)");
|
|
144372
|
+
}
|
|
144373
|
+
} catch (error49) {
|
|
144374
|
+
log.info(`Failed to set git config: ${error49 instanceof Error ? error49.message : String(error49)}`);
|
|
144375
|
+
}
|
|
144376
|
+
try {
|
|
144377
|
+
execSync2("git config --local --unset-all http.https://github.com/.extraheader", {
|
|
144378
|
+
cwd: repoDir,
|
|
144379
|
+
stdio: "pipe"
|
|
144380
|
+
});
|
|
144381
|
+
log.info("\xBB removed existing authentication headers");
|
|
144382
|
+
} catch {
|
|
144383
|
+
log.debug("\xBB no existing authentication headers to remove");
|
|
144384
|
+
}
|
|
144385
|
+
removeIncludeIfEntries(repoDir);
|
|
144386
|
+
const originUrl = `https://github.com/${params.owner}/${params.name}.git`;
|
|
144387
|
+
$("git", ["remote", "set-url", "origin", originUrl], { cwd: repoDir });
|
|
144388
|
+
params.toolState.pushUrl = originUrl;
|
|
144389
|
+
$("git", ["config", "--local", "credential.helper", ""], { cwd: repoDir });
|
|
144390
|
+
params.toolState.initialHead = captureInitialHead(repoDir);
|
|
144391
|
+
log.info("\xBB git authentication configured");
|
|
144392
|
+
}
|
|
144393
|
+
function captureInitialHead(repoDir) {
|
|
144394
|
+
try {
|
|
144395
|
+
const name = $("git", ["symbolic-ref", "--short", "HEAD"], {
|
|
144396
|
+
cwd: repoDir,
|
|
144397
|
+
log: false
|
|
144398
|
+
}).trim();
|
|
144399
|
+
if (name) return { kind: "branch", name };
|
|
144400
|
+
} catch {
|
|
144401
|
+
}
|
|
144402
|
+
const sha = $("git", ["rev-parse", "HEAD"], { cwd: repoDir, log: false }).trim();
|
|
144403
|
+
return { kind: "detached", sha };
|
|
144404
|
+
}
|
|
144405
|
+
|
|
144206
144406
|
// mcp/checkout.ts
|
|
144207
144407
|
function formatFilesWithLineNumbers(files) {
|
|
144208
144408
|
const output = [];
|
|
@@ -144377,7 +144577,7 @@ function cleanupStaleGitLocks() {
|
|
|
144377
144577
|
}
|
|
144378
144578
|
if (now - mtimeMs < STALE_LOCK_AGE_MS) continue;
|
|
144379
144579
|
try {
|
|
144380
|
-
|
|
144580
|
+
unlinkSync3(relPath);
|
|
144381
144581
|
log.warning(`\xBB removed stale ${relPath} from prior run`);
|
|
144382
144582
|
} catch (e) {
|
|
144383
144583
|
log.debug(
|
|
@@ -144536,6 +144736,15 @@ async function checkoutPrBranch(pr, params) {
|
|
|
144536
144736
|
return { hookWarning: postCheckoutHook.warning };
|
|
144537
144737
|
}
|
|
144538
144738
|
var inFlightCheckouts = /* @__PURE__ */ new Map();
|
|
144739
|
+
function headsEqual(a, b) {
|
|
144740
|
+
if (a.kind === "branch" && b.kind === "branch") return a.name === b.name;
|
|
144741
|
+
if (a.kind === "detached" && b.kind === "detached") return a.sha === b.sha;
|
|
144742
|
+
return false;
|
|
144743
|
+
}
|
|
144744
|
+
function describeHead(h) {
|
|
144745
|
+
if (h.kind === "branch") return `branch \`${h.name}\``;
|
|
144746
|
+
return `detached HEAD \`${h.sha}\``;
|
|
144747
|
+
}
|
|
144539
144748
|
function CheckoutPrTool(ctx) {
|
|
144540
144749
|
const runCheckout = async (pull_number) => {
|
|
144541
144750
|
const prResponse = await ctx.octokit.rest.pulls.get({
|
|
@@ -144582,7 +144791,7 @@ function CheckoutPrTool(ctx) {
|
|
|
144582
144791
|
headSha: ctx.toolState.checkoutSha
|
|
144583
144792
|
});
|
|
144584
144793
|
if (incremental) {
|
|
144585
|
-
incrementalDiffPath =
|
|
144794
|
+
incrementalDiffPath = join4(
|
|
144586
144795
|
tempDir,
|
|
144587
144796
|
`pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
|
|
144588
144797
|
);
|
|
@@ -144596,7 +144805,7 @@ function CheckoutPrTool(ctx) {
|
|
|
144596
144805
|
const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
|
|
144597
144806
|
log.debug(`formatted diff preview (first 100 lines):
|
|
144598
144807
|
${diffPreview}`);
|
|
144599
|
-
const diffPath =
|
|
144808
|
+
const diffPath = join4(tempDir, `pr-${pull_number}-${headShort}.diff`);
|
|
144600
144809
|
writeFileSync(diffPath, formatResult.content);
|
|
144601
144810
|
log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
|
|
144602
144811
|
ctx.toolState.diffCoverage = createDiffCoverageState({
|
|
@@ -144663,7 +144872,8 @@ ${diffPreview}`);
|
|
|
144663
144872
|
};
|
|
144664
144873
|
return tool({
|
|
144665
144874
|
name: "checkout_pr",
|
|
144666
|
-
|
|
144875
|
+
timeoutMs: 6e5,
|
|
144876
|
+
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
144877
|
parameters: CheckoutPr,
|
|
144668
144878
|
execute: execute(async ({ pull_number }) => {
|
|
144669
144879
|
const inFlight = inFlightCheckouts.get(pull_number);
|
|
@@ -144671,13 +144881,23 @@ ${diffPreview}`);
|
|
|
144671
144881
|
log.info(`\xBB checkout_pr({pull_number:${pull_number}}) already in flight \u2014 sharing result`);
|
|
144672
144882
|
return inFlight;
|
|
144673
144883
|
}
|
|
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:
|
|
144884
|
+
const dirty = $("git", ["status", "--porcelain"], { log: false }).trim();
|
|
144885
|
+
if (dirty) {
|
|
144886
|
+
throw new Error(
|
|
144887
|
+
`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
144888
|
${dirty}`
|
|
144889
|
+
);
|
|
144890
|
+
}
|
|
144891
|
+
const initialHead = ctx.toolState.initialHead;
|
|
144892
|
+
if (initialHead) {
|
|
144893
|
+
const currentHead = captureInitialHead(process.cwd());
|
|
144894
|
+
const targetBranch = `pr-${pull_number}`;
|
|
144895
|
+
const onTarget = currentHead.kind === "branch" && currentHead.name === targetBranch;
|
|
144896
|
+
const onInitial = headsEqual(currentHead, initialHead);
|
|
144897
|
+
if (!onTarget && !onInitial) {
|
|
144898
|
+
const recoverCmd = initialHead.kind === "branch" ? `git checkout ${initialHead.name}` : `git checkout ${initialHead.sha}`;
|
|
144899
|
+
throw new Error(
|
|
144900
|
+
`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
144901
|
);
|
|
144682
144902
|
}
|
|
144683
144903
|
}
|
|
@@ -144694,7 +144914,7 @@ ${dirty}`
|
|
|
144694
144914
|
|
|
144695
144915
|
// mcp/checkSuite.ts
|
|
144696
144916
|
import { mkdirSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
144697
|
-
import { join as
|
|
144917
|
+
import { join as join5 } from "node:path";
|
|
144698
144918
|
var GetCheckSuiteLogs = type({
|
|
144699
144919
|
check_suite_id: type.number.describe("the id from check_suite.id")
|
|
144700
144920
|
});
|
|
@@ -144790,7 +145010,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
144790
145010
|
if (!tempDir) {
|
|
144791
145011
|
throw new Error("PULLFROG_TEMP_DIR not set");
|
|
144792
145012
|
}
|
|
144793
|
-
const logsDir =
|
|
145013
|
+
const logsDir = join5(tempDir, "ci-logs");
|
|
144794
145014
|
mkdirSync(logsDir, { recursive: true });
|
|
144795
145015
|
const jobResults = [];
|
|
144796
145016
|
for (const run of failedRuns) {
|
|
@@ -144817,7 +145037,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
144817
145037
|
);
|
|
144818
145038
|
}
|
|
144819
145039
|
const logsText = await logsResult.text();
|
|
144820
|
-
const logPath =
|
|
145040
|
+
const logPath = join5(logsDir, `job-${job.id}.log`);
|
|
144821
145041
|
writeFileSync2(logPath, logsText);
|
|
144822
145042
|
const analysis = analyzeLog(logsText, 80);
|
|
144823
145043
|
const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
|
|
@@ -144867,7 +145087,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
144867
145087
|
|
|
144868
145088
|
// mcp/commitInfo.ts
|
|
144869
145089
|
import { writeFileSync as writeFileSync3 } from "node:fs";
|
|
144870
|
-
import { join as
|
|
145090
|
+
import { join as join6 } from "node:path";
|
|
144871
145091
|
var CommitInfo = type({
|
|
144872
145092
|
sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
|
|
144873
145093
|
});
|
|
@@ -144891,7 +145111,7 @@ function CommitInfoTool(ctx) {
|
|
|
144891
145111
|
"PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
|
|
144892
145112
|
);
|
|
144893
145113
|
}
|
|
144894
|
-
const diffFile =
|
|
145114
|
+
const diffFile = join6(tempDir, `commit-${sha.slice(0, 7)}.diff`);
|
|
144895
145115
|
writeFileSync3(diffFile, formatResult.content);
|
|
144896
145116
|
log.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
|
|
144897
145117
|
return {
|
|
@@ -145349,7 +145569,7 @@ function PullRequestInfoTool(ctx) {
|
|
|
145349
145569
|
|
|
145350
145570
|
// mcp/reviewComments.ts
|
|
145351
145571
|
import { writeFileSync as writeFileSync4 } from "node:fs";
|
|
145352
|
-
import { join as
|
|
145572
|
+
import { join as join7 } from "node:path";
|
|
145353
145573
|
var REVIEW_THREADS_QUERY = `
|
|
145354
145574
|
query ($owner: String!, $name: String!, $prNumber: Int!) {
|
|
145355
145575
|
repository(owner: $owner, name: $name) {
|
|
@@ -145741,7 +145961,7 @@ function GetReviewCommentsTool(ctx) {
|
|
|
145741
145961
|
throw new Error("PULLFROG_TEMP_DIR not set");
|
|
145742
145962
|
}
|
|
145743
145963
|
const filename = `review-${params.review_id}-threads.md`;
|
|
145744
|
-
const commentsPath =
|
|
145964
|
+
const commentsPath = join7(tempDir, filename);
|
|
145745
145965
|
writeFileSync4(commentsPath, formatted.content);
|
|
145746
145966
|
log.debug(`wrote ${threadBlocks.length} threads to ${commentsPath}`);
|
|
145747
145967
|
return {
|
|
@@ -145970,7 +146190,7 @@ import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
|
|
|
145970
146190
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
145971
146191
|
import { closeSync, openSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
145972
146192
|
import { userInfo } from "node:os";
|
|
145973
|
-
import { join as
|
|
146193
|
+
import { join as join8 } from "node:path";
|
|
145974
146194
|
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
145975
146195
|
var ShellParams = type({
|
|
145976
146196
|
command: "string",
|
|
@@ -146103,7 +146323,7 @@ function getTempDir() {
|
|
|
146103
146323
|
var MAX_OUTPUT_CHARS = 5e3;
|
|
146104
146324
|
function capOutput(output) {
|
|
146105
146325
|
if (output.length <= MAX_OUTPUT_CHARS) return output;
|
|
146106
|
-
const fullPath =
|
|
146326
|
+
const fullPath = join8(getTempDir(), `shell-${randomUUID2().slice(0, 8)}.log`);
|
|
146107
146327
|
writeFileSync5(fullPath, output);
|
|
146108
146328
|
const elided = output.length - MAX_OUTPUT_CHARS;
|
|
146109
146329
|
return `... [${elided} chars truncated; full output saved to ${fullPath}] ...
|
|
@@ -146118,6 +146338,7 @@ function isGitCommand(command) {
|
|
|
146118
146338
|
function ShellTool(ctx) {
|
|
146119
146339
|
return tool({
|
|
146120
146340
|
name: "shell",
|
|
146341
|
+
timeoutMs: 12e4,
|
|
146121
146342
|
description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
|
|
146122
146343
|
|
|
146123
146344
|
Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
|
|
@@ -146157,8 +146378,8 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
|
|
|
146157
146378
|
if (params.background) {
|
|
146158
146379
|
const tempDir = getTempDir();
|
|
146159
146380
|
const handle = `bg-${randomUUID2().slice(0, 8)}`;
|
|
146160
|
-
const outputPath =
|
|
146161
|
-
const pidPath =
|
|
146381
|
+
const outputPath = join8(tempDir, `${handle}.log`);
|
|
146382
|
+
const pidPath = join8(tempDir, `${handle}.pid`);
|
|
146162
146383
|
const logFd = openSync(outputPath, "a");
|
|
146163
146384
|
let proc2;
|
|
146164
146385
|
try {
|
|
@@ -146498,6 +146719,8 @@ var REVIEWER_AGENT_NAME = "reviewfrog";
|
|
|
146498
146719
|
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
146720
|
|
|
146500
146721
|
HARD CONSTRAINTS (non-negotiable, regardless of orchestrator instructions):
|
|
146722
|
+
- 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.
|
|
146723
|
+
- 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
146724
|
- 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
146725
|
- 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
146726
|
- 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 +146911,25 @@ function computeModes(agentId) {
|
|
|
146688
146911
|
|
|
146689
146912
|
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
146913
|
|
|
146691
|
-
|
|
146914
|
+
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.
|
|
146915
|
+
|
|
146916
|
+
\`\`\`
|
|
146917
|
+
## What you're reviewing
|
|
146918
|
+
This is a PRE-COMMIT Build-mode self-review. The work to review lives in the working tree (uncommitted), NOT in committed history.
|
|
146919
|
+
|
|
146920
|
+
Branch: <branch> (off <base>)
|
|
146921
|
+
Canonical diff command: git diff origin/<base>
|
|
146922
|
+
|
|
146923
|
+
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.
|
|
146924
|
+
|
|
146925
|
+
## Your task
|
|
146926
|
+
<YOUR TASK content>
|
|
146927
|
+
|
|
146928
|
+
## Build-phase failures
|
|
146929
|
+
<tight summary \u2014 what broke, root cause, the fix \u2014 or "no build-phase failures">
|
|
146930
|
+
\`\`\`
|
|
146931
|
+
|
|
146932
|
+
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
146933
|
|
|
146693
146934
|
Delegation + research discipline (distilled from \`/anneal\` canonical \u2014 these are codified learnings from many review rounds, not theoretical best practices):
|
|
146694
146935
|
- Do NOT summarize what you implemented \u2014 that biases the subagent toward validating the shape of your solution rather than questioning it.
|
|
@@ -147087,21 +147328,21 @@ function initToolState(params) {
|
|
|
147087
147328
|
}
|
|
147088
147329
|
|
|
147089
147330
|
// agents/claude.ts
|
|
147090
|
-
import { execFileSync as
|
|
147331
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
147091
147332
|
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "node:fs";
|
|
147092
|
-
import { join as
|
|
147333
|
+
import { join as join12 } from "node:path";
|
|
147093
147334
|
import { performance as performance6 } from "node:perf_hooks";
|
|
147094
147335
|
|
|
147095
147336
|
// utils/install.ts
|
|
147096
147337
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
147097
147338
|
import { chmodSync, createWriteStream, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "node:fs";
|
|
147098
|
-
import { join as
|
|
147339
|
+
import { join as join9 } from "node:path";
|
|
147099
147340
|
import { pipeline } from "node:stream/promises";
|
|
147100
147341
|
async function installFromNpmTarball(params) {
|
|
147101
147342
|
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
147102
147343
|
if (!tempDir) throw new Error("PULLFROG_TEMP_DIR is not set");
|
|
147103
|
-
const extractedDir =
|
|
147104
|
-
const cliPath =
|
|
147344
|
+
const extractedDir = join9(tempDir, "package");
|
|
147345
|
+
const cliPath = join9(extractedDir, params.executablePath);
|
|
147105
147346
|
if (existsSync5(cliPath)) {
|
|
147106
147347
|
log.debug(`\xBB using cached binary at ${cliPath}`);
|
|
147107
147348
|
return cliPath;
|
|
@@ -147126,7 +147367,7 @@ async function installFromNpmTarball(params) {
|
|
|
147126
147367
|
}
|
|
147127
147368
|
}
|
|
147128
147369
|
log.debug(`\xBB installing ${params.packageName}@${resolvedVersion}...`);
|
|
147129
|
-
const tarballPath =
|
|
147370
|
+
const tarballPath = join9(tempDir, "package.tgz");
|
|
147130
147371
|
const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org";
|
|
147131
147372
|
let tarballUrl;
|
|
147132
147373
|
if (params.packageName.startsWith("@")) {
|
|
@@ -147265,16 +147506,16 @@ function isRouterKeylimitExhaustedError(text) {
|
|
|
147265
147506
|
// utils/skills.ts
|
|
147266
147507
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
147267
147508
|
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
|
|
147509
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
147510
|
+
import { dirname as dirname2, join as join10 } from "node:path";
|
|
147270
147511
|
import { fileURLToPath } from "node:url";
|
|
147271
147512
|
var skillsVersion = getDevDependencyVersion("skills");
|
|
147272
147513
|
var BUNDLED_SKILL_NAMES = ["git-archaeology"];
|
|
147273
147514
|
function resolveSkillPath(name) {
|
|
147274
147515
|
const here = dirname2(fileURLToPath(import.meta.url));
|
|
147275
147516
|
const candidates = [
|
|
147276
|
-
|
|
147277
|
-
|
|
147517
|
+
join10(here, "..", "skills", name, "SKILL.md"),
|
|
147518
|
+
join10(here, "skills", name, "SKILL.md")
|
|
147278
147519
|
];
|
|
147279
147520
|
for (const candidate of candidates) {
|
|
147280
147521
|
if (existsSync6(candidate)) return candidate;
|
|
@@ -147286,9 +147527,9 @@ function installBundledSkills(params) {
|
|
|
147286
147527
|
for (const name of BUNDLED_SKILL_NAMES) {
|
|
147287
147528
|
const content = readFileSync4(resolveSkillPath(name), "utf8");
|
|
147288
147529
|
for (const targetDir of SKILL_TARGET_DIRS) {
|
|
147289
|
-
const skillDir =
|
|
147530
|
+
const skillDir = join10(params.home, targetDir, name);
|
|
147290
147531
|
mkdirSync3(skillDir, { recursive: true });
|
|
147291
|
-
writeFileSync6(
|
|
147532
|
+
writeFileSync6(join10(skillDir, "SKILL.md"), content);
|
|
147292
147533
|
}
|
|
147293
147534
|
}
|
|
147294
147535
|
log.success(`installed bundled skills: ${BUNDLED_SKILL_NAMES.join(", ")}`);
|
|
@@ -147309,7 +147550,7 @@ function addSkill(params) {
|
|
|
147309
147550
|
"-y"
|
|
147310
147551
|
],
|
|
147311
147552
|
{
|
|
147312
|
-
cwd:
|
|
147553
|
+
cwd: tmpdir2(),
|
|
147313
147554
|
env: { ...process.env, ...params.env },
|
|
147314
147555
|
stdio: "pipe",
|
|
147315
147556
|
timeout: 3e4
|
|
@@ -147394,7 +147635,7 @@ var ThinkingTimer = class {
|
|
|
147394
147635
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
147395
147636
|
import { mkdirSync as mkdirSync4, rmSync, writeFileSync as writeFileSync7 } from "node:fs";
|
|
147396
147637
|
import { homedir } from "node:os";
|
|
147397
|
-
import { join as
|
|
147638
|
+
import { join as join11 } from "node:path";
|
|
147398
147639
|
var VERTEX_SERVICE_ACCOUNT_JSON_ENV = "VERTEX_SERVICE_ACCOUNT_JSON";
|
|
147399
147640
|
var GOOGLE_APPLICATION_CREDENTIALS_ENV = "GOOGLE_APPLICATION_CREDENTIALS";
|
|
147400
147641
|
var GOOGLE_CLOUD_PROJECT_ENV = "GOOGLE_CLOUD_PROJECT";
|
|
@@ -147423,7 +147664,7 @@ function readProjectIdFromVertexServiceAccountJson() {
|
|
|
147423
147664
|
}
|
|
147424
147665
|
function createSecretDir() {
|
|
147425
147666
|
const base = process.env.PULLFROG_SECRET_HOME || process.env.HOME || homedir();
|
|
147426
|
-
const secretDir =
|
|
147667
|
+
const secretDir = join11(base, ".pullfrog", "secrets", randomUUID3());
|
|
147427
147668
|
mkdirSync4(secretDir, { recursive: true, mode: 448 });
|
|
147428
147669
|
return secretDir;
|
|
147429
147670
|
}
|
|
@@ -147432,7 +147673,7 @@ function materializeVertexCredentials(params) {
|
|
|
147432
147673
|
const blob = process.env[VERTEX_SERVICE_ACCOUNT_JSON_ENV];
|
|
147433
147674
|
if (!blob) return void 0;
|
|
147434
147675
|
const secretDir = createSecretDir();
|
|
147435
|
-
const credentialsPath =
|
|
147676
|
+
const credentialsPath = join11(secretDir, "vertex-sa.json");
|
|
147436
147677
|
writeFileSync7(credentialsPath, blob, { mode: 384 });
|
|
147437
147678
|
process.env[GOOGLE_APPLICATION_CREDENTIALS_ENV] = credentialsPath;
|
|
147438
147679
|
const projectId = readProjectIdFromVertexServiceAccountJson();
|
|
@@ -147444,6 +147685,7 @@ function materializeVertexCredentials(params) {
|
|
|
147444
147685
|
function cleanupVertexCredentials(credentials) {
|
|
147445
147686
|
if (!credentials) return;
|
|
147446
147687
|
rmSync(credentials.secretDir, { recursive: true, force: true });
|
|
147688
|
+
delete process.env[GOOGLE_APPLICATION_CREDENTIALS_ENV];
|
|
147447
147689
|
}
|
|
147448
147690
|
function applyClaudeVertexEnv(env2) {
|
|
147449
147691
|
env2.CLAUDE_CODE_USE_VERTEX = "1";
|
|
@@ -147757,9 +147999,9 @@ async function installClaudeCli() {
|
|
|
147757
147999
|
});
|
|
147758
148000
|
}
|
|
147759
148001
|
function writeMcpConfig(ctx) {
|
|
147760
|
-
const configDir =
|
|
148002
|
+
const configDir = join12(ctx.tmpdir, ".claude");
|
|
147761
148003
|
mkdirSync5(configDir, { recursive: true });
|
|
147762
|
-
const configPath =
|
|
148004
|
+
const configPath = join12(configDir, "mcp.json");
|
|
147763
148005
|
writeFileSync8(
|
|
147764
148006
|
configPath,
|
|
147765
148007
|
JSON.stringify({
|
|
@@ -148185,8 +148427,8 @@ function installManagedSettings(ctx) {
|
|
|
148185
148427
|
if (process.env.CI !== "true") return;
|
|
148186
148428
|
const content = JSON.stringify(buildManagedSettings(ctx), null, 2);
|
|
148187
148429
|
try {
|
|
148188
|
-
|
|
148189
|
-
|
|
148430
|
+
execFileSync4("sudo", ["mkdir", "-p", MANAGED_SETTINGS_DIR]);
|
|
148431
|
+
execFileSync4("sudo", ["tee", MANAGED_SETTINGS_PATH], {
|
|
148190
148432
|
input: content,
|
|
148191
148433
|
stdio: ["pipe", "ignore", "pipe"]
|
|
148192
148434
|
});
|
|
@@ -148208,15 +148450,15 @@ var claude = agent({
|
|
|
148208
148450
|
const model = !specifier ? void 0 : isBedrockRoute ? specifier : isVertexRoute2 ? void 0 : stripProviderPrefix(specifier);
|
|
148209
148451
|
const homeEnv = {
|
|
148210
148452
|
HOME: ctx.tmpdir,
|
|
148211
|
-
XDG_CONFIG_HOME:
|
|
148453
|
+
XDG_CONFIG_HOME: join12(ctx.tmpdir, ".config")
|
|
148212
148454
|
};
|
|
148213
|
-
mkdirSync5(
|
|
148455
|
+
mkdirSync5(join12(homeEnv.XDG_CONFIG_HOME, "claude"), { recursive: true });
|
|
148214
148456
|
const agentBrowserVersion = getDevDependencyVersion("agent-browser");
|
|
148215
148457
|
addSkill({
|
|
148216
148458
|
ref: `vercel-labs/agent-browser@v${agentBrowserVersion}`,
|
|
148217
148459
|
skill: "agent-browser",
|
|
148218
148460
|
env: homeEnv,
|
|
148219
|
-
agent: "claude"
|
|
148461
|
+
agent: "claude-code"
|
|
148220
148462
|
});
|
|
148221
148463
|
installBundledSkills({ home: homeEnv.HOME });
|
|
148222
148464
|
const mcpConfigPath = writeMcpConfig(ctx);
|
|
@@ -148295,7 +148537,7 @@ var claude = agent({
|
|
|
148295
148537
|
// agents/opencode_v2.ts
|
|
148296
148538
|
var core2 = __toESM(require_core(), 1);
|
|
148297
148539
|
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync10 } from "node:fs";
|
|
148298
|
-
import { join as
|
|
148540
|
+
import { join as join14 } from "node:path";
|
|
148299
148541
|
import { performance as performance7 } from "node:perf_hooks";
|
|
148300
148542
|
|
|
148301
148543
|
// utils/agentHangReport.ts
|
|
@@ -148396,7 +148638,7 @@ function formatBillingExhaustedBody(diagnostic) {
|
|
|
148396
148638
|
// utils/codexHome.ts
|
|
148397
148639
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync9 } from "node:fs";
|
|
148398
148640
|
import { homedir as homedir2 } from "node:os";
|
|
148399
|
-
import { join as
|
|
148641
|
+
import { join as join13 } from "node:path";
|
|
148400
148642
|
var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
|
|
148401
148643
|
function installCodexAuth() {
|
|
148402
148644
|
const raw2 = process.env[CODEX_AUTH_ENV];
|
|
@@ -148406,9 +148648,9 @@ function installCodexAuth() {
|
|
|
148406
148648
|
log.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
|
|
148407
148649
|
return null;
|
|
148408
148650
|
}
|
|
148409
|
-
const xdgDataHome =
|
|
148410
|
-
const opencodeDir =
|
|
148411
|
-
const authPath =
|
|
148651
|
+
const xdgDataHome = join13(homedir2(), ".local", "share");
|
|
148652
|
+
const opencodeDir = join13(xdgDataHome, "opencode");
|
|
148653
|
+
const authPath = join13(opencodeDir, "auth.json");
|
|
148412
148654
|
const opencodeAuth = {
|
|
148413
148655
|
openai: {
|
|
148414
148656
|
type: "oauth",
|
|
@@ -148536,7 +148778,7 @@ export default async function pullfrogEventsPlugin() {
|
|
|
148536
148778
|
`;
|
|
148537
148779
|
|
|
148538
148780
|
// agents/opencodeShared.ts
|
|
148539
|
-
import { execFileSync as
|
|
148781
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
148540
148782
|
|
|
148541
148783
|
// agents/subagentModels.ts
|
|
148542
148784
|
function deriveSubagentModels(orchestratorSpec) {
|
|
@@ -148585,7 +148827,7 @@ async function installOpencodeCli(params) {
|
|
|
148585
148827
|
var AUTO_SELECT_WARNING = "select a model explicitly in the Pullfrog console (https://pullfrog.com/console) to avoid this.";
|
|
148586
148828
|
function getOpenCodeModels(cliPath) {
|
|
148587
148829
|
try {
|
|
148588
|
-
const output =
|
|
148830
|
+
const output = execFileSync5(cliPath, ["models"], {
|
|
148589
148831
|
encoding: "utf-8",
|
|
148590
148832
|
timeout: 3e4,
|
|
148591
148833
|
env: process.env
|
|
@@ -149082,13 +149324,13 @@ var opencode = agent({
|
|
|
149082
149324
|
const model = vertexModel ?? (isBedrockRoute ? `amazon-bedrock/${rawModel}` : rawModel);
|
|
149083
149325
|
const homeEnv = {
|
|
149084
149326
|
HOME: ctx.tmpdir,
|
|
149085
|
-
XDG_CONFIG_HOME:
|
|
149327
|
+
XDG_CONFIG_HOME: join14(ctx.tmpdir, ".config")
|
|
149086
149328
|
};
|
|
149087
|
-
mkdirSync7(
|
|
149088
|
-
const opencodePluginDir =
|
|
149329
|
+
mkdirSync7(join14(homeEnv.XDG_CONFIG_HOME, "opencode"), { recursive: true });
|
|
149330
|
+
const opencodePluginDir = join14(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
|
|
149089
149331
|
mkdirSync7(opencodePluginDir, { recursive: true });
|
|
149090
149332
|
writeFileSync10(
|
|
149091
|
-
|
|
149333
|
+
join14(opencodePluginDir, PULLFROG_OPENCODE_PLUGIN_FILENAME),
|
|
149092
149334
|
PULLFROG_OPENCODE_PLUGIN_SOURCE
|
|
149093
149335
|
);
|
|
149094
149336
|
const agentBrowserVersion = getDevDependencyVersion("agent-browser");
|
|
@@ -149450,7 +149692,7 @@ async function fetchBodyHtml(ctx) {
|
|
|
149450
149692
|
}
|
|
149451
149693
|
|
|
149452
149694
|
// utils/byokFallback.ts
|
|
149453
|
-
var FREE_FALLBACK_SLUG = "opencode/
|
|
149695
|
+
var FREE_FALLBACK_SLUG = "opencode/big-pickle";
|
|
149454
149696
|
function selectFallbackModelIfNeeded(input) {
|
|
149455
149697
|
if (input.proxyModel) return { fallback: false };
|
|
149456
149698
|
if (!input.resolvedModel) return { fallback: false };
|
|
@@ -149468,7 +149710,7 @@ function selectFallbackModelIfNeeded(input) {
|
|
|
149468
149710
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
149469
149711
|
import { writeFileSync as writeFileSync11 } from "node:fs";
|
|
149470
149712
|
import { createServer as createServer2 } from "node:http";
|
|
149471
|
-
import { join as
|
|
149713
|
+
import { join as join15 } from "node:path";
|
|
149472
149714
|
var CODE_TTL_MS = 5 * 60 * 1e3;
|
|
149473
149715
|
var TAMPER_WINDOW_MS = 6e4;
|
|
149474
149716
|
function revokeGitHubToken(token) {
|
|
@@ -149540,7 +149782,7 @@ async function startGitAuthServer(tmpdir3) {
|
|
|
149540
149782
|
function writeAskpassScript(code) {
|
|
149541
149783
|
const scriptId = randomUUID4();
|
|
149542
149784
|
const scriptName = `askpass-${scriptId}.js`;
|
|
149543
|
-
const scriptPath =
|
|
149785
|
+
const scriptPath = join15(tmpdir3, scriptName);
|
|
149544
149786
|
const content = [
|
|
149545
149787
|
`#!/usr/bin/env node`,
|
|
149546
149788
|
`var a=process.argv[2]||"";`,
|
|
@@ -149579,7 +149821,7 @@ async function startGitAuthServer(tmpdir3) {
|
|
|
149579
149821
|
var core3 = __toESM(require_core(), 1);
|
|
149580
149822
|
import { createSign } from "node:crypto";
|
|
149581
149823
|
import { rename, writeFile } from "node:fs/promises";
|
|
149582
|
-
import { dirname as dirname3, join as
|
|
149824
|
+
import { dirname as dirname3, join as join16 } from "node:path";
|
|
149583
149825
|
|
|
149584
149826
|
// node_modules/.pnpm/@octokit+plugin-throttling@11.0.3_@octokit+core@7.0.5/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
|
|
149585
149827
|
var import_light = __toESM(require_light(), 1);
|
|
@@ -153437,7 +153679,7 @@ function getGitHubUsageSummary() {
|
|
|
153437
153679
|
}
|
|
153438
153680
|
async function writeGitHubUsageSummaryToFile(path3) {
|
|
153439
153681
|
const summary2 = getGitHubUsageSummary();
|
|
153440
|
-
const tmpPath =
|
|
153682
|
+
const tmpPath = join16(dirname3(path3), `.usage-summary-${process.pid}.tmp`);
|
|
153441
153683
|
await writeFile(tmpPath, JSON.stringify(summary2));
|
|
153442
153684
|
await rename(tmpPath, path3);
|
|
153443
153685
|
}
|
|
@@ -153488,7 +153730,7 @@ function createOctokit(token) {
|
|
|
153488
153730
|
}
|
|
153489
153731
|
|
|
153490
153732
|
// utils/instructions.ts
|
|
153491
|
-
import { execSync as
|
|
153733
|
+
import { execSync as execSync3 } from "node:child_process";
|
|
153492
153734
|
function buildRuntimeContext(ctx) {
|
|
153493
153735
|
const {
|
|
153494
153736
|
"~pullfrog": _,
|
|
@@ -153500,7 +153742,7 @@ function buildRuntimeContext(ctx) {
|
|
|
153500
153742
|
} = ctx.payload;
|
|
153501
153743
|
let gitStatus;
|
|
153502
153744
|
try {
|
|
153503
|
-
gitStatus =
|
|
153745
|
+
gitStatus = execSync3("git status --short", { encoding: "utf-8", stdio: "pipe" }).trim() || "(clean)";
|
|
153504
153746
|
} catch {
|
|
153505
153747
|
}
|
|
153506
153748
|
const data = {
|
|
@@ -153837,7 +154079,7 @@ function resolveInstructions(ctx) {
|
|
|
153837
154079
|
|
|
153838
154080
|
// utils/learnings.ts
|
|
153839
154081
|
import { mkdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
153840
|
-
import { dirname as dirname4, join as
|
|
154082
|
+
import { dirname as dirname4, join as join17 } from "node:path";
|
|
153841
154083
|
|
|
153842
154084
|
// utils/learningsTruncate.ts
|
|
153843
154085
|
var MAX_LEARNINGS_LENGTH = 1e5;
|
|
@@ -153854,7 +154096,7 @@ function truncateAtLineBoundary(body, cap) {
|
|
|
153854
154096
|
// utils/learnings.ts
|
|
153855
154097
|
var LEARNINGS_FILE_NAME = "pullfrog-learnings.md";
|
|
153856
154098
|
function learningsFilePath(tmpdir3) {
|
|
153857
|
-
return
|
|
154099
|
+
return join17(tmpdir3, LEARNINGS_FILE_NAME);
|
|
153858
154100
|
}
|
|
153859
154101
|
async function seedLearningsFile(params) {
|
|
153860
154102
|
const path3 = learningsFilePath(params.tmpdir);
|
|
@@ -154534,7 +154776,7 @@ async function runProxyResolution(ctx) {
|
|
|
154534
154776
|
|
|
154535
154777
|
// utils/prSummary.ts
|
|
154536
154778
|
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
154537
|
-
import { dirname as dirname5, join as
|
|
154779
|
+
import { dirname as dirname5, join as join18 } from "node:path";
|
|
154538
154780
|
var SUMMARY_FILE_NAME = "pullfrog-summary.md";
|
|
154539
154781
|
var SUMMARY_SCAFFOLD = `# PR summary
|
|
154540
154782
|
|
|
@@ -154544,7 +154786,7 @@ var SUMMARY_SCAFFOLD = `# PR summary
|
|
|
154544
154786
|
var MIN_SNAPSHOT_LENGTH = 60;
|
|
154545
154787
|
var MAX_SNAPSHOT_LENGTH = 32768;
|
|
154546
154788
|
function summaryFilePath(tmpdir3) {
|
|
154547
|
-
return
|
|
154789
|
+
return join18(tmpdir3, SUMMARY_FILE_NAME);
|
|
154548
154790
|
}
|
|
154549
154791
|
async function seedSummaryFile(params) {
|
|
154550
154792
|
const path3 = summaryFilePath(params.tmpdir);
|
|
@@ -154738,6 +154980,16 @@ async function resolveRunContextData(params) {
|
|
|
154738
154980
|
}
|
|
154739
154981
|
|
|
154740
154982
|
// utils/runErrorRenderer.ts
|
|
154983
|
+
function isProviderModelNotFoundError(message) {
|
|
154984
|
+
return message.includes("ProviderModelNotFoundError");
|
|
154985
|
+
}
|
|
154986
|
+
function formatProviderModelNotFoundSummary(input) {
|
|
154987
|
+
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.
|
|
154988
|
+
|
|
154989
|
+
\`\`\`
|
|
154990
|
+
${input.raw}
|
|
154991
|
+
\`\`\``;
|
|
154992
|
+
}
|
|
154741
154993
|
function renderRunError(input) {
|
|
154742
154994
|
const billingError = isRouterKeylimitExhaustedError(input.errorMessage) ? new BillingError(input.errorMessage, { code: "router_keylimit_exhausted" }) : null;
|
|
154743
154995
|
if (billingError) {
|
|
@@ -154759,6 +155011,14 @@ function renderRunError(input) {
|
|
|
154759
155011
|
if (apiKeyErrorSummary) {
|
|
154760
155012
|
return { summary: apiKeyErrorSummary, comment: apiKeyErrorSummary };
|
|
154761
155013
|
}
|
|
155014
|
+
if (isProviderModelNotFoundError(input.errorMessage)) {
|
|
155015
|
+
const body = formatProviderModelNotFoundSummary({
|
|
155016
|
+
owner: input.repo.owner,
|
|
155017
|
+
name: input.repo.name,
|
|
155018
|
+
raw: input.errorMessage
|
|
155019
|
+
});
|
|
155020
|
+
return { summary: body, comment: body };
|
|
155021
|
+
}
|
|
154762
155022
|
if (hangBody) {
|
|
154763
155023
|
return {
|
|
154764
155024
|
summary: `### \u274C Pullfrog failed
|
|
@@ -154860,16 +155120,17 @@ async function persistRunArtifacts(toolContext) {
|
|
|
154860
155120
|
}
|
|
154861
155121
|
async function finalizeSuccessRun(input) {
|
|
154862
155122
|
await persistRunArtifacts(input.toolContext);
|
|
154863
|
-
|
|
154864
|
-
|
|
154865
|
-
|
|
154866
|
-
|
|
154867
|
-
|
|
154868
|
-
|
|
154869
|
-
|
|
154870
|
-
|
|
154871
|
-
|
|
154872
|
-
|
|
155123
|
+
const rendered = !input.result.success ? renderRunError({
|
|
155124
|
+
errorMessage: input.result.error || "agent run failed",
|
|
155125
|
+
repo: input.repo,
|
|
155126
|
+
agentDiagnostic: input.toolState.agentDiagnostic
|
|
155127
|
+
}) : null;
|
|
155128
|
+
if (rendered && input.toolState.progressComment) {
|
|
155129
|
+
await reportErrorToComment({ toolState: input.toolState, error: rendered.comment }).catch(
|
|
155130
|
+
(error49) => {
|
|
155131
|
+
log.debug(`failure error report failed: ${error49}`);
|
|
155132
|
+
}
|
|
155133
|
+
);
|
|
154873
155134
|
}
|
|
154874
155135
|
if (input.result.success && input.toolState.progressComment && !input.toolState.finalSummaryWritten) {
|
|
154875
155136
|
await deleteProgressComment(input.toolContext).catch((error49) => {
|
|
@@ -154879,7 +155140,7 @@ async function finalizeSuccessRun(input) {
|
|
|
154879
155140
|
try {
|
|
154880
155141
|
const usageSummary = formatUsageSummary(input.toolState.usageEntries);
|
|
154881
155142
|
const body = input.toolState.lastProgressBody || input.result.output;
|
|
154882
|
-
const parts = [body, usageSummary].filter(Boolean);
|
|
155143
|
+
const parts = [rendered?.summary, body, usageSummary].filter(Boolean);
|
|
154883
155144
|
if (parts.length > 0) {
|
|
154884
155145
|
await writeSummary(parts.join("\n\n"));
|
|
154885
155146
|
}
|
|
@@ -154964,116 +155225,6 @@ function logRunStartup(ctx) {
|
|
|
154964
155225
|
log.info(`\xBB timeout: ${resolveTimeoutForLog(ctx.payload.timeout)}`);
|
|
154965
155226
|
}
|
|
154966
155227
|
|
|
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
155228
|
// utils/todoTracking.ts
|
|
155078
155229
|
function isValidTodoStatus(value2) {
|
|
155079
155230
|
return value2 === "pending" || value2 === "in_progress" || value2 === "completed" || value2 === "cancelled";
|
|
@@ -155281,6 +155432,7 @@ async function main() {
|
|
|
155281
155432
|
toolState.beforeSha = payload.event.before_sha;
|
|
155282
155433
|
}
|
|
155283
155434
|
const tokenRef = __using(_stack2, await resolveTokens({ push: payload.push }), true);
|
|
155435
|
+
wipeRunnerLeakSurface();
|
|
155284
155436
|
const oidcCredentials = process.env.ACTIONS_ID_TOKEN_REQUEST_URL && process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN ? {
|
|
155285
155437
|
requestUrl: process.env.ACTIONS_ID_TOKEN_REQUEST_URL,
|
|
155286
155438
|
requestToken: process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN
|
|
@@ -155450,7 +155602,7 @@ ${instructions.user}` : null,
|
|
|
155450
155602
|
});
|
|
155451
155603
|
if (agentId === "opencode") {
|
|
155452
155604
|
const pluginDir = join19(process.cwd(), ".opencode", "plugin");
|
|
155453
|
-
const hasPlugins = existsSync7(pluginDir) &&
|
|
155605
|
+
const hasPlugins = existsSync7(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
|
|
155454
155606
|
if (hasPlugins && toolState.dependencyInstallation?.promise) {
|
|
155455
155607
|
log.info(
|
|
155456
155608
|
"\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"
|