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/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.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 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";
@@ -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
- unlinkSync2(relPath);
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 = join3(
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 = join3(tempDir, `pr-${pull_number}-${headShort}.diff`);
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
- 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.",
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 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:
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 join4 } from "node:path";
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 = join4(tempDir, "ci-logs");
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 = join4(logsDir, `job-${job.id}.log`);
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 join5 } from "node:path";
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 = join5(tempDir, `commit-${sha.slice(0, 7)}.diff`);
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 join6 } from "node:path";
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 = join6(tempDir, filename);
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 join7 } from "node:path";
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 = join7(getTempDir(), `shell-${randomUUID2().slice(0, 8)}.log`);
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 = join7(tempDir, `${handle}.log`);
146161
- const pidPath = join7(tempDir, `${handle}.pid`);
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
- 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.
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 execFileSync3 } from "node:child_process";
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 join11 } from "node:path";
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 join8 } from "node:path";
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 = join8(tempDir, "package");
147104
- const cliPath = join8(extractedDir, params.executablePath);
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 = join8(tempDir, "package.tgz");
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 join9 } from "node:path";
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
- join9(here, "..", "skills", name, "SKILL.md"),
147277
- join9(here, "skills", name, "SKILL.md")
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 = join9(params.home, targetDir, name);
147530
+ const skillDir = join10(params.home, targetDir, name);
147290
147531
  mkdirSync3(skillDir, { recursive: true });
147291
- writeFileSync6(join9(skillDir, "SKILL.md"), content);
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: tmpdir(),
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 join10 } from "node:path";
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 = join10(base, ".pullfrog", "secrets", randomUUID3());
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 = join10(secretDir, "vertex-sa.json");
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 = join11(ctx.tmpdir, ".claude");
148002
+ const configDir = join12(ctx.tmpdir, ".claude");
147761
148003
  mkdirSync5(configDir, { recursive: true });
147762
- const configPath = join11(configDir, "mcp.json");
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
- execFileSync3("sudo", ["mkdir", "-p", MANAGED_SETTINGS_DIR]);
148189
- execFileSync3("sudo", ["tee", MANAGED_SETTINGS_PATH], {
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: join11(ctx.tmpdir, ".config")
148453
+ XDG_CONFIG_HOME: join12(ctx.tmpdir, ".config")
148212
148454
  };
148213
- mkdirSync5(join11(homeEnv.XDG_CONFIG_HOME, "claude"), { recursive: true });
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 join13 } from "node:path";
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 join12 } from "node:path";
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 = join12(homedir2(), ".local", "share");
148410
- const opencodeDir = join12(xdgDataHome, "opencode");
148411
- const authPath = join12(opencodeDir, "auth.json");
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 execFileSync4 } from "node:child_process";
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 = execFileSync4(cliPath, ["models"], {
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: join13(ctx.tmpdir, ".config")
149327
+ XDG_CONFIG_HOME: join14(ctx.tmpdir, ".config")
149086
149328
  };
149087
- mkdirSync7(join13(homeEnv.XDG_CONFIG_HOME, "opencode"), { recursive: true });
149088
- const opencodePluginDir = join13(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
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
- join13(opencodePluginDir, PULLFROG_OPENCODE_PLUGIN_FILENAME),
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/minimax-m2.5-free";
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 join14 } from "node:path";
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 = join14(tmpdir3, scriptName);
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 join15 } from "node:path";
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 = join15(dirname3(path3), `.usage-summary-${process.pid}.tmp`);
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 execSync2 } from "node:child_process";
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 = execSync2("git status --short", { encoding: "utf-8", stdio: "pipe" }).trim() || "(clean)";
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 join16 } from "node:path";
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 join16(tmpdir3, LEARNINGS_FILE_NAME);
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 join17 } from "node:path";
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 join17(tmpdir3, SUMMARY_FILE_NAME);
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
- 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
- });
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) && readdirSync(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
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"