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/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.11",
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 unlinkSync2, writeFileSync } from "node:fs";
142694
- import { join as join3 } from "node:path";
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
- unlinkSync2(relPath);
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 = join3(
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 = join3(tempDir, `pr-${pull_number}-${headShort}.diff`);
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
- description: "Checkout a pull request branch locally. This fetches the PR branch and sets up push configuration for fork PRs. Returns diffPath pointing to the formatted diff file. Example: `checkout_pr({ pull_number: 1234 })`. Transient fetch timeouts are common \u2014 retry the same call up to a few times before treating the failure as terminal. If the error mentions `.git/shallow.lock: File exists` or `.git/index.lock: File exists`, that's a stale lock from a prior timed-out fetch \u2014 remove it via the shell tool (`rm -f .git/shallow.lock .git/index.lock`) and retry.",
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 currentBranch = $("git", ["rev-parse", "--abbrev-ref", "HEAD"], { log: false }).trim();
144675
- if (currentBranch !== `pr-${pull_number}`) {
144676
- const dirty = $("git", ["status", "--porcelain"], { log: false }).trim();
144677
- if (dirty) {
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 join4 } from "node:path";
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 = join4(tempDir, "ci-logs");
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 = join4(logsDir, `job-${job.id}.log`);
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 join5 } from "node:path";
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 = join5(tempDir, `commit-${sha.slice(0, 7)}.diff`);
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 join6 } from "node:path";
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 = join6(tempDir, filename);
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 join7 } from "node:path";
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 = join7(getTempDir(), `shell-${randomUUID2().slice(0, 8)}.log`);
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 = join7(tempDir, `${handle}.log`);
146161
- const pidPath = join7(tempDir, `${handle}.pid`);
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
- Provide the subagent with YOUR TASK, the output of \`git diff origin/<base-branch>\` (single-rev form, no \`HEAD\` \u2014 this compares the working tree against the remote base and captures committed + staged + unstaged work; \`main...HEAD\` and \`--cached\` both miss the uncommitted edits Build self-review runs on, since self-review happens BEFORE the commit), and a tight summary (not raw output) of any lint/typecheck/test failures you fixed during build \u2014 what broke, root cause, the fix \u2014 so it can check that fixes addressed root causes rather than suppressed symptoms; say "no build-phase failures" if the build path was clean. Instruct it to flag bugs, logic errors, missing edge cases, gaps between request and diff, and unintended changes.
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 execFileSync3 } from "node:child_process";
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 join11 } from "node:path";
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 join8 } from "node:path";
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 = join8(tempDir, "package");
147104
- const cliPath = join8(extractedDir, params.executablePath);
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 = join8(tempDir, "package.tgz");
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 join9 } from "node:path";
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
- join9(here, "..", "skills", name, "SKILL.md"),
147277
- join9(here, "skills", name, "SKILL.md")
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 = join9(params.home, targetDir, name);
147524
+ const skillDir = join10(params.home, targetDir, name);
147290
147525
  mkdirSync3(skillDir, { recursive: true });
147291
- writeFileSync6(join9(skillDir, "SKILL.md"), content);
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: tmpdir(),
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 join10 } from "node:path";
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 = join10(base, ".pullfrog", "secrets", randomUUID3());
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 = join10(secretDir, "vertex-sa.json");
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 = join11(ctx.tmpdir, ".claude");
147996
+ const configDir = join12(ctx.tmpdir, ".claude");
147761
147997
  mkdirSync5(configDir, { recursive: true });
147762
- const configPath = join11(configDir, "mcp.json");
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
- execFileSync3("sudo", ["mkdir", "-p", MANAGED_SETTINGS_DIR]);
148189
- execFileSync3("sudo", ["tee", MANAGED_SETTINGS_PATH], {
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: join11(ctx.tmpdir, ".config")
148447
+ XDG_CONFIG_HOME: join12(ctx.tmpdir, ".config")
148212
148448
  };
148213
- mkdirSync5(join11(homeEnv.XDG_CONFIG_HOME, "claude"), { recursive: true });
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 join13 } from "node:path";
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 join12 } from "node:path";
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 = join12(homedir2(), ".local", "share");
148410
- const opencodeDir = join12(xdgDataHome, "opencode");
148411
- const authPath = join12(opencodeDir, "auth.json");
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 execFileSync4 } from "node:child_process";
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 = execFileSync4(cliPath, ["models"], {
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: join13(ctx.tmpdir, ".config")
149321
+ XDG_CONFIG_HOME: join14(ctx.tmpdir, ".config")
149086
149322
  };
149087
- mkdirSync7(join13(homeEnv.XDG_CONFIG_HOME, "opencode"), { recursive: true });
149088
- const opencodePluginDir = join13(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
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
- join13(opencodePluginDir, PULLFROG_OPENCODE_PLUGIN_FILENAME),
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/minimax-m2.5-free";
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 join14 } from "node:path";
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 = join14(tmpdir3, scriptName);
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 join15 } from "node:path";
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 = join15(dirname3(path3), `.usage-summary-${process.pid}.tmp`);
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 execSync2 } from "node:child_process";
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 = execSync2("git status --short", { encoding: "utf-8", stdio: "pipe" }).trim() || "(clean)";
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 join16 } from "node:path";
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 join16(tmpdir3, LEARNINGS_FILE_NAME);
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 join17 } from "node:path";
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 join17(tmpdir3, SUMMARY_FILE_NAME);
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
- if (!input.result.success && input.toolState.progressComment) {
154864
- const rawError = input.result.error || "agent run failed";
154865
- const errorBody = isApiKeyAuthError(rawError) ? formatApiKeyErrorSummary({
154866
- owner: input.repo.owner,
154867
- name: input.repo.name,
154868
- raw: rawError
154869
- }) : rawError;
154870
- await reportErrorToComment({ toolState: input.toolState, error: errorBody }).catch((error49) => {
154871
- log.debug(`failure error report failed: ${error49}`);
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) && readdirSync(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
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"