pullfrog 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -100664,7 +100664,7 @@ var import_arg2 = __toESM(require_arg(), 1);
100664
100664
  import { dirname as dirname6 } from "node:path";
100665
100665
 
100666
100666
  // main.ts
100667
- import { existsSync as existsSync7, readdirSync } from "node:fs";
100667
+ import { existsSync as existsSync7, readdirSync as readdirSync2 } from "node:fs";
100668
100668
  import { readFile as readFile4 } from "node:fs/promises";
100669
100669
  import { join as join20 } from "node:path";
100670
100670
 
@@ -109727,6 +109727,11 @@ var providers = {
109727
109727
  resolve: "opencode/kimi-k2.6",
109728
109728
  openRouterResolve: "openrouter/moonshotai/kimi-k2.6"
109729
109729
  },
109730
+ "minimax-m2.5": {
109731
+ displayName: "MiniMax M2.5",
109732
+ resolve: "opencode/minimax-m2.5",
109733
+ openRouterResolve: "openrouter/minimax/minimax-m2.5"
109734
+ },
109730
109735
  "gpt-5-nano": {
109731
109736
  displayName: "GPT Nano",
109732
109737
  resolve: "opencode/gpt-5-nano",
@@ -109743,7 +109748,9 @@ var providers = {
109743
109748
  displayName: "MiniMax M2.5",
109744
109749
  resolve: "opencode/minimax-m2.5-free",
109745
109750
  envVars: [],
109746
- isFree: true
109751
+ isFree: true,
109752
+ fallback: "opencode/big-pickle",
109753
+ hidden: true
109747
109754
  }
109748
109755
  }
109749
109756
  }),
@@ -109881,6 +109888,11 @@ var providers = {
109881
109888
  displayName: "Kimi K2",
109882
109889
  resolve: "openrouter/moonshotai/kimi-k2.6",
109883
109890
  openRouterResolve: "openrouter/moonshotai/kimi-k2.6"
109891
+ },
109892
+ "minimax-m2.5": {
109893
+ displayName: "MiniMax M2.5",
109894
+ resolve: "openrouter/minimax/minimax-m2.5",
109895
+ openRouterResolve: "openrouter/minimax/minimax-m2.5"
109884
109896
  }
109885
109897
  }
109886
109898
  })
@@ -109925,6 +109937,11 @@ var modelAliases = Object.entries(providers).flatMap(
109925
109937
  hidden: def.hidden ?? false
109926
109938
  }))
109927
109939
  );
109940
+ var defaultProxyAlias = modelAliases.find((a) => a.slug === "moonshotai/kimi-k2");
109941
+ if (!defaultProxyAlias?.openRouterResolve) {
109942
+ throw new Error("DEFAULT_PROXY_MODEL: moonshotai/kimi-k2 missing openRouterResolve");
109943
+ }
109944
+ var DEFAULT_PROXY_MODEL = defaultProxyAlias.openRouterResolve;
109928
109945
  var MAX_FALLBACK_DEPTH = 10;
109929
109946
  function resolveDisplayAlias(slug2) {
109930
109947
  let current = slug2;
@@ -144232,7 +144249,7 @@ var import_semver = __toESM(require_semver2(), 1);
144232
144249
  // package.json
144233
144250
  var package_default = {
144234
144251
  name: "pullfrog",
144235
- version: "0.1.11",
144252
+ version: "0.1.13",
144236
144253
  type: "module",
144237
144254
  bin: {
144238
144255
  pullfrog: "dist/cli.mjs",
@@ -144430,8 +144447,8 @@ function closeBrowserDaemon(toolState) {
144430
144447
 
144431
144448
  // mcp/checkout.ts
144432
144449
  import { createHash as createHash2 } from "node:crypto";
144433
- import { statSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
144434
- import { join as join4 } from "node:path";
144450
+ import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
144451
+ import { join as join5 } from "node:path";
144435
144452
 
144436
144453
  // utils/diffCoverage.ts
144437
144454
  import { isAbsolute, normalize as normalize2, resolve } from "node:path";
@@ -145103,7 +145120,13 @@ var TRANSIENT_PATTERNS = [
145103
145120
  /HTTP 5\d\d/,
145104
145121
  /returned error: 5\d\d/i,
145105
145122
  /HTTP 429/,
145106
- /returned error: 429/i
145123
+ /returned error: 429/i,
145124
+ // github installation tokens can 401 for seconds after minting while
145125
+ // replicating (@octokit/auth-app retries the same class). git push
145126
+ // surfaces it as "Invalid username or token", distinct from 403
145127
+ // permission denied — safe to backoff-retry with the same token.
145128
+ /Invalid username or token/,
145129
+ /Authentication failed for 'https:\/\/github\.com\//
145107
145130
  ];
145108
145131
  function classifyPushError(msg) {
145109
145132
  if (CONCURRENT_PUSH_PATTERNS.some((p2) => msg.includes(p2))) return "concurrent-push";
@@ -145943,6 +145966,183 @@ async function reportReviewNodeId(ctx, params) {
145943
145966
  await patchWorkflowRunFields(ctx, { reviewNodeId: params.nodeId });
145944
145967
  }
145945
145968
 
145969
+ // utils/setup.ts
145970
+ import { execFileSync as execFileSync4, execSync as execSync2 } from "node:child_process";
145971
+ import { mkdtempSync as mkdtempSync2, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
145972
+ import { tmpdir as tmpdir2 } from "node:os";
145973
+ import { join as join4 } from "node:path";
145974
+ function createTempDirectory() {
145975
+ const sharedTempDir = mkdtempSync2(join4(tmpdir2(), "pullfrog-"));
145976
+ process.env.PULLFROG_TEMP_DIR = sharedTempDir;
145977
+ log.info(`\xBB created temp dir at ${sharedTempDir}`);
145978
+ return sharedTempDir;
145979
+ }
145980
+ function wipeRunnerLeakSurface() {
145981
+ const runnerTemp = process.env.RUNNER_TEMP;
145982
+ if (!runnerTemp) return;
145983
+ const preserve = /* @__PURE__ */ new Set();
145984
+ for (const envVar of [
145985
+ "GITHUB_OUTPUT",
145986
+ "GITHUB_ENV",
145987
+ "GITHUB_PATH",
145988
+ "GITHUB_STATE",
145989
+ "GITHUB_STEP_SUMMARY"
145990
+ ]) {
145991
+ const path3 = process.env[envVar];
145992
+ if (!path3) continue;
145993
+ try {
145994
+ preserve.add(realpathSync2(path3));
145995
+ } catch {
145996
+ preserve.add(path3);
145997
+ }
145998
+ }
145999
+ const wiped = [];
146000
+ const tryUnlink = (path3) => {
146001
+ let resolved = path3;
146002
+ try {
146003
+ resolved = realpathSync2(path3);
146004
+ } catch {
146005
+ }
146006
+ if (preserve.has(resolved) || preserve.has(path3)) return;
146007
+ try {
146008
+ unlinkSync2(path3);
146009
+ wiped.push(path3);
146010
+ } catch {
146011
+ }
146012
+ };
146013
+ const listDir = (dir) => {
146014
+ try {
146015
+ return readdirSync(dir);
146016
+ } catch {
146017
+ return [];
146018
+ }
146019
+ };
146020
+ const fileCommandsDir = join4(runnerTemp, "_runner_file_commands");
146021
+ for (const entry of listDir(fileCommandsDir)) {
146022
+ tryUnlink(join4(fileCommandsDir, entry));
146023
+ }
146024
+ for (const entry of listDir(runnerTemp)) {
146025
+ if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
146026
+ tryUnlink(join4(runnerTemp, entry));
146027
+ }
146028
+ }
146029
+ if (wiped.length > 0) {
146030
+ log.info(`\xBB wiped ${wiped.length} leak-surface file(s) from $RUNNER_TEMP`);
146031
+ log.debug(`\xBB wiped paths: ${wiped.join(", ")}`);
146032
+ }
146033
+ }
146034
+ function envScopedToRepo() {
146035
+ const scoped = { ...process.env };
146036
+ for (const key of Object.keys(scoped)) {
146037
+ if (key.startsWith("GIT_")) delete scoped[key];
146038
+ }
146039
+ return scoped;
146040
+ }
146041
+ function removeIncludeIfEntries(repoDir) {
146042
+ const env2 = envScopedToRepo();
146043
+ let configOutput;
146044
+ try {
146045
+ configOutput = execSync2("git config --local --get-regexp -z ^includeif\\.", {
146046
+ cwd: repoDir,
146047
+ encoding: "utf-8",
146048
+ stdio: "pipe",
146049
+ env: env2
146050
+ });
146051
+ } catch {
146052
+ log.debug("\xBB no includeIf credential entries to remove");
146053
+ return;
146054
+ }
146055
+ const seen = /* @__PURE__ */ new Set();
146056
+ for (const entry of configOutput.split("\0")) {
146057
+ if (!entry) continue;
146058
+ const nl = entry.indexOf("\n");
146059
+ const key = nl === -1 ? entry : entry.slice(0, nl);
146060
+ if (!key || seen.has(key)) continue;
146061
+ seen.add(key);
146062
+ try {
146063
+ execFileSync4("git", ["config", "--local", "--unset-all", key], {
146064
+ cwd: repoDir,
146065
+ stdio: "pipe",
146066
+ env: env2
146067
+ });
146068
+ } catch (error49) {
146069
+ log.debug(
146070
+ `\xBB failed to unset ${key}: ${error49 instanceof Error ? error49.message : String(error49)}`
146071
+ );
146072
+ }
146073
+ }
146074
+ if (seen.size > 0)
146075
+ log.info(
146076
+ `\xBB removed ${seen.size} includeIf credential ${seen.size === 1 ? "entry" : "entries"}`
146077
+ );
146078
+ }
146079
+ async function setupGit(params) {
146080
+ const repoDir = process.cwd();
146081
+ log.info("\xBB setting up git configuration...");
146082
+ try {
146083
+ let currentEmail = "";
146084
+ try {
146085
+ currentEmail = execSync2("git config user.email", {
146086
+ cwd: repoDir,
146087
+ stdio: "pipe",
146088
+ encoding: "utf-8"
146089
+ }).trim();
146090
+ } catch {
146091
+ }
146092
+ const shouldSetDefaults = !currentEmail || currentEmail === "github-actions[bot]@users.noreply.github.com";
146093
+ if (shouldSetDefaults) {
146094
+ execSync2('git config --local user.email "226033991+pullfrog[bot]@users.noreply.github.com"', {
146095
+ cwd: repoDir,
146096
+ stdio: "pipe"
146097
+ });
146098
+ execSync2('git config --local user.name "pullfrog[bot]"', {
146099
+ cwd: repoDir,
146100
+ stdio: "pipe"
146101
+ });
146102
+ log.debug("\xBB git user configured (using defaults)");
146103
+ } else {
146104
+ log.debug(`\xBB git user already configured (${currentEmail}), skipping`);
146105
+ }
146106
+ if (params.shell === "disabled") {
146107
+ execSync2("git config --local core.hooksPath /dev/null", {
146108
+ cwd: repoDir,
146109
+ stdio: "pipe"
146110
+ });
146111
+ log.debug("\xBB git hooks disabled (shell=disabled)");
146112
+ }
146113
+ } catch (error49) {
146114
+ log.info(`Failed to set git config: ${error49 instanceof Error ? error49.message : String(error49)}`);
146115
+ }
146116
+ try {
146117
+ execSync2("git config --local --unset-all http.https://github.com/.extraheader", {
146118
+ cwd: repoDir,
146119
+ stdio: "pipe"
146120
+ });
146121
+ log.info("\xBB removed existing authentication headers");
146122
+ } catch {
146123
+ log.debug("\xBB no existing authentication headers to remove");
146124
+ }
146125
+ removeIncludeIfEntries(repoDir);
146126
+ const originUrl = `https://github.com/${params.owner}/${params.name}.git`;
146127
+ $2("git", ["remote", "set-url", "origin", originUrl], { cwd: repoDir });
146128
+ params.toolState.pushUrl = originUrl;
146129
+ $2("git", ["config", "--local", "credential.helper", ""], { cwd: repoDir });
146130
+ params.toolState.initialHead = captureInitialHead(repoDir);
146131
+ log.info("\xBB git authentication configured");
146132
+ }
146133
+ function captureInitialHead(repoDir) {
146134
+ try {
146135
+ const name = $2("git", ["symbolic-ref", "--short", "HEAD"], {
146136
+ cwd: repoDir,
146137
+ log: false
146138
+ }).trim();
146139
+ if (name) return { kind: "branch", name };
146140
+ } catch {
146141
+ }
146142
+ const sha = $2("git", ["rev-parse", "HEAD"], { cwd: repoDir, log: false }).trim();
146143
+ return { kind: "detached", sha };
146144
+ }
146145
+
145946
146146
  // mcp/checkout.ts
145947
146147
  function formatFilesWithLineNumbers(files) {
145948
146148
  const output = [];
@@ -146117,7 +146317,7 @@ function cleanupStaleGitLocks() {
146117
146317
  }
146118
146318
  if (now - mtimeMs < STALE_LOCK_AGE_MS) continue;
146119
146319
  try {
146120
- unlinkSync2(relPath);
146320
+ unlinkSync3(relPath);
146121
146321
  log.warning(`\xBB removed stale ${relPath} from prior run`);
146122
146322
  } catch (e) {
146123
146323
  log.debug(
@@ -146276,6 +146476,15 @@ async function checkoutPrBranch(pr, params) {
146276
146476
  return { hookWarning: postCheckoutHook.warning };
146277
146477
  }
146278
146478
  var inFlightCheckouts = /* @__PURE__ */ new Map();
146479
+ function headsEqual(a, b) {
146480
+ if (a.kind === "branch" && b.kind === "branch") return a.name === b.name;
146481
+ if (a.kind === "detached" && b.kind === "detached") return a.sha === b.sha;
146482
+ return false;
146483
+ }
146484
+ function describeHead(h) {
146485
+ if (h.kind === "branch") return `branch \`${h.name}\``;
146486
+ return `detached HEAD \`${h.sha}\``;
146487
+ }
146279
146488
  function CheckoutPrTool(ctx) {
146280
146489
  const runCheckout = async (pull_number) => {
146281
146490
  const prResponse = await ctx.octokit.rest.pulls.get({
@@ -146322,7 +146531,7 @@ function CheckoutPrTool(ctx) {
146322
146531
  headSha: ctx.toolState.checkoutSha
146323
146532
  });
146324
146533
  if (incremental) {
146325
- incrementalDiffPath = join4(
146534
+ incrementalDiffPath = join5(
146326
146535
  tempDir,
146327
146536
  `pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
146328
146537
  );
@@ -146336,7 +146545,7 @@ function CheckoutPrTool(ctx) {
146336
146545
  const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
146337
146546
  log.debug(`formatted diff preview (first 100 lines):
146338
146547
  ${diffPreview}`);
146339
- const diffPath = join4(tempDir, `pr-${pull_number}-${headShort}.diff`);
146548
+ const diffPath = join5(tempDir, `pr-${pull_number}-${headShort}.diff`);
146340
146549
  writeFileSync2(diffPath, formatResult.content);
146341
146550
  log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
146342
146551
  ctx.toolState.diffCoverage = createDiffCoverageState({
@@ -146403,7 +146612,8 @@ ${diffPreview}`);
146403
146612
  };
146404
146613
  return tool({
146405
146614
  name: "checkout_pr",
146406
- 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.",
146615
+ timeoutMs: 6e5,
146616
+ description: "Checkout a pull request branch locally. This fetches the PR branch and sets up push configuration for fork PRs. Returns diffPath pointing to the formatted diff file. Example: `checkout_pr({ pull_number: 1234 })`. Large repos can take several minutes \u2014 wait for the call to finish; do not treat a slow response as failure. If you see `MCP error -32001: Request timed out`, retry the same call without touching git lock files first \u2014 that error is a client-side abort. If the retry then reports `.git/shallow.lock: File exists` or `.git/index.lock: File exists`, remove those lock files via the shell tool and retry again.",
146407
146617
  parameters: CheckoutPr,
146408
146618
  execute: execute(async ({ pull_number }) => {
146409
146619
  const inFlight = inFlightCheckouts.get(pull_number);
@@ -146411,13 +146621,23 @@ ${diffPreview}`);
146411
146621
  log.info(`\xBB checkout_pr({pull_number:${pull_number}}) already in flight \u2014 sharing result`);
146412
146622
  return inFlight;
146413
146623
  }
146414
- const currentBranch = $2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { log: false }).trim();
146415
- if (currentBranch !== `pr-${pull_number}`) {
146416
- const dirty = $2("git", ["status", "--porcelain"], { log: false }).trim();
146417
- if (dirty) {
146418
- throw new Error(
146419
- `cannot checkout PR #${pull_number} while the working tree has uncommitted changes. commit, push, or discard them before switching. dirty paths:
146624
+ const dirty = $2("git", ["status", "--porcelain"], { log: false }).trim();
146625
+ if (dirty) {
146626
+ throw new Error(
146627
+ `cannot checkout PR #${pull_number} while the working tree has uncommitted changes. commit (then push if needed), or discard with \`git restore --staged --worktree .\` / \`git clean -fd\` before retrying. this refusal is unconditional \u2014 even re-checking-out the PR you're already on is refused, because shared-working-tree subagents make carry-forward edits unsafe. dirty paths:
146420
146628
  ${dirty}`
146629
+ );
146630
+ }
146631
+ const initialHead = ctx.toolState.initialHead;
146632
+ if (initialHead) {
146633
+ const currentHead = captureInitialHead(process.cwd());
146634
+ const targetBranch = `pr-${pull_number}`;
146635
+ const onTarget = currentHead.kind === "branch" && currentHead.name === targetBranch;
146636
+ const onInitial = headsEqual(currentHead, initialHead);
146637
+ if (!onTarget && !onInitial) {
146638
+ const recoverCmd = initialHead.kind === "branch" ? `git checkout ${initialHead.name}` : `git checkout ${initialHead.sha}`;
146639
+ throw new Error(
146640
+ `cannot checkout PR #${pull_number} from ${describeHead(currentHead)}. the only sanctioned HEAD positions for checkout_pr are the run-entry HEAD (${describeHead(initialHead)}) or the target PR's branch (\`${targetBranch}\`, idempotent re-checkout). recover with \`${recoverCmd}\` first \u2014 if that would carry uncommitted work along, commit or discard it (\`git restore --staged --worktree .\` / \`git clean -fd\`) before switching. routing around this via the \`git\` tool's \`checkout\`/\`switch\` subcommands is not sanctioned: this guard exists to prevent the shared-working-tree cross-PR clobber pattern from the zed-industries/cloud (2026-05-18) incident.`
146421
146641
  );
146422
146642
  }
146423
146643
  }
@@ -146434,7 +146654,7 @@ ${dirty}`
146434
146654
 
146435
146655
  // mcp/checkSuite.ts
146436
146656
  import { mkdirSync, writeFileSync as writeFileSync3 } from "node:fs";
146437
- import { join as join5 } from "node:path";
146657
+ import { join as join6 } from "node:path";
146438
146658
  var GetCheckSuiteLogs = type({
146439
146659
  check_suite_id: type.number.describe("the id from check_suite.id")
146440
146660
  });
@@ -146530,7 +146750,7 @@ function GetCheckSuiteLogsTool(ctx) {
146530
146750
  if (!tempDir) {
146531
146751
  throw new Error("PULLFROG_TEMP_DIR not set");
146532
146752
  }
146533
- const logsDir = join5(tempDir, "ci-logs");
146753
+ const logsDir = join6(tempDir, "ci-logs");
146534
146754
  mkdirSync(logsDir, { recursive: true });
146535
146755
  const jobResults = [];
146536
146756
  for (const run4 of failedRuns) {
@@ -146557,7 +146777,7 @@ function GetCheckSuiteLogsTool(ctx) {
146557
146777
  );
146558
146778
  }
146559
146779
  const logsText = await logsResult.text();
146560
- const logPath = join5(logsDir, `job-${job.id}.log`);
146780
+ const logPath = join6(logsDir, `job-${job.id}.log`);
146561
146781
  writeFileSync3(logPath, logsText);
146562
146782
  const analysis = analyzeLog(logsText, 80);
146563
146783
  const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
@@ -146607,7 +146827,7 @@ function GetCheckSuiteLogsTool(ctx) {
146607
146827
 
146608
146828
  // mcp/commitInfo.ts
146609
146829
  import { writeFileSync as writeFileSync4 } from "node:fs";
146610
- import { join as join6 } from "node:path";
146830
+ import { join as join7 } from "node:path";
146611
146831
  var CommitInfo = type({
146612
146832
  sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
146613
146833
  });
@@ -146631,7 +146851,7 @@ function CommitInfoTool(ctx) {
146631
146851
  "PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
146632
146852
  );
146633
146853
  }
146634
- const diffFile = join6(tempDir, `commit-${sha.slice(0, 7)}.diff`);
146854
+ const diffFile = join7(tempDir, `commit-${sha.slice(0, 7)}.diff`);
146635
146855
  writeFileSync4(diffFile, formatResult.content);
146636
146856
  log.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
146637
146857
  return {
@@ -147089,7 +147309,7 @@ function PullRequestInfoTool(ctx) {
147089
147309
 
147090
147310
  // mcp/reviewComments.ts
147091
147311
  import { writeFileSync as writeFileSync5 } from "node:fs";
147092
- import { join as join7 } from "node:path";
147312
+ import { join as join8 } from "node:path";
147093
147313
  var REVIEW_THREADS_QUERY = `
147094
147314
  query ($owner: String!, $name: String!, $prNumber: Int!) {
147095
147315
  repository(owner: $owner, name: $name) {
@@ -147481,7 +147701,7 @@ function GetReviewCommentsTool(ctx) {
147481
147701
  throw new Error("PULLFROG_TEMP_DIR not set");
147482
147702
  }
147483
147703
  const filename = `review-${params.review_id}-threads.md`;
147484
- const commentsPath = join7(tempDir, filename);
147704
+ const commentsPath = join8(tempDir, filename);
147485
147705
  writeFileSync5(commentsPath, formatted.content);
147486
147706
  log.debug(`wrote ${threadBlocks.length} threads to ${commentsPath}`);
147487
147707
  return {
@@ -147710,7 +147930,7 @@ import { spawn as spawn4, spawnSync as spawnSync3 } from "node:child_process";
147710
147930
  import { randomUUID as randomUUID2 } from "node:crypto";
147711
147931
  import { closeSync, openSync, writeFileSync as writeFileSync6 } from "node:fs";
147712
147932
  import { userInfo } from "node:os";
147713
- import { join as join8 } from "node:path";
147933
+ import { join as join9 } from "node:path";
147714
147934
  import { setTimeout as sleep2 } from "node:timers/promises";
147715
147935
  var ShellParams = type({
147716
147936
  command: "string",
@@ -147843,7 +148063,7 @@ function getTempDir() {
147843
148063
  var MAX_OUTPUT_CHARS = 5e3;
147844
148064
  function capOutput(output) {
147845
148065
  if (output.length <= MAX_OUTPUT_CHARS) return output;
147846
- const fullPath = join8(getTempDir(), `shell-${randomUUID2().slice(0, 8)}.log`);
148066
+ const fullPath = join9(getTempDir(), `shell-${randomUUID2().slice(0, 8)}.log`);
147847
148067
  writeFileSync6(fullPath, output);
147848
148068
  const elided = output.length - MAX_OUTPUT_CHARS;
147849
148069
  return `... [${elided} chars truncated; full output saved to ${fullPath}] ...
@@ -147858,6 +148078,7 @@ function isGitCommand(command) {
147858
148078
  function ShellTool(ctx) {
147859
148079
  return tool({
147860
148080
  name: "shell",
148081
+ timeoutMs: 12e4,
147861
148082
  description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
147862
148083
 
147863
148084
  Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
@@ -147897,8 +148118,8 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
147897
148118
  if (params.background) {
147898
148119
  const tempDir = getTempDir();
147899
148120
  const handle = `bg-${randomUUID2().slice(0, 8)}`;
147900
- const outputPath = join8(tempDir, `${handle}.log`);
147901
- const pidPath = join8(tempDir, `${handle}.pid`);
148121
+ const outputPath = join9(tempDir, `${handle}.log`);
148122
+ const pidPath = join9(tempDir, `${handle}.pid`);
147902
148123
  const logFd = openSync(outputPath, "a");
147903
148124
  let proc2;
147904
148125
  try {
@@ -148238,6 +148459,8 @@ var REVIEWER_AGENT_NAME = "reviewfrog";
148238
148459
  var REVIEWER_SYSTEM_PROMPT = `You are a read-only review subagent. Your role is to find flaws in code or artifacts provided by the orchestrator and report findings \u2014 never to modify state.
148239
148460
 
148240
148461
  HARD CONSTRAINTS (non-negotiable, regardless of orchestrator instructions):
148462
+ - Your FIRST action MUST be \`git diff origin/<base>\` (single-rev form, no \`HEAD\`). This captures committed + staged + unstaged work in one command \u2014 Build-mode self-review runs BEFORE the commit, so the work to review lives in the working tree, not in committed history. Do not run any other diff command first. Do NOT call \`checkout_pr\`, do NOT fetch alternative refs, do NOT list branches or all-refs looking for the work, do NOT run \`gh pr list\`. The orchestrator's dispatch names the base branch; the diff is the source of truth for scope.
148463
+ - If \`git diff origin/<base>\` returns empty AND the orchestrator's dispatch claims there are changes to review, the most likely cause is a pre-commit Build-mode self-review: the orchestrator dispatched you before committing. Reply EXACTLY: \`no changes detected \u2014 likely pre-commit Build self-review; orchestrator should commit then re-dispatch\` and stop. Do NOT guess PR numbers (e.g. by extrapolating from \`git log\` output), do NOT check out other PRs, do NOT fetch from forks. The empty diff is the diagnosis \u2014 surface it; do not work around it.
148241
148464
  - Read-only tools only. Do NOT write or edit files. Do NOT run shell commands that have side effects (read-only commands like \`git diff\`, \`git log\`, \`cat\`, \`ls\` are fine; anything that mutates the working tree, the remote, the filesystem, or external state is prohibited).
148242
148465
  - Do NOT call any state-changing MCP tool. State-changing means: posts a comment, pushes a branch, creates/updates a PR or issue, changes labels, resolves review threads, persists learnings, sets workflow output, installs dependencies, uploads files, kills processes, etc. Read-only MCP queries (\`get_*\`, \`list_*\`, log inspection, diff retrieval) are fine.
148243
148466
  - Do NOT spawn further subagents. You are a leaf reviewer; recursive dispatch pre-aggregates findings through an intermediate model and defeats the design.
@@ -148428,7 +148651,25 @@ function computeModes(agentId) {
148428
148651
 
148429
148652
  Otherwise delegate the \`${REVIEWER_AGENT_NAME}\` subagent to review your diff with fresh eyes against YOUR TASK. The subagent's baked-in system prompt enforces a non-mutative + non-recursive contract: read-only file/search/web tools and read-only MCP queries only; no writes, shell side effects, state-changing MCP calls, or nested subagent dispatch. Enforcement is prose-only \u2014 restate the constraint in your dispatch instructions and do not relax it.
148430
148653
 
148431
- 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.
148654
+ Compose your \`${REVIEWER_AGENT_NAME}\` dispatch prompt using this template verbatim, substituting the \`<...>\` placeholders. The preamble aligns the orchestrator side of the dispatch contract with the reviewer's baked-in system prompt \u2014 both ends say the same thing about where the work lives and what to do on an empty diff.
148655
+
148656
+ \`\`\`
148657
+ ## What you're reviewing
148658
+ This is a PRE-COMMIT Build-mode self-review. The work to review lives in the working tree (uncommitted), NOT in committed history.
148659
+
148660
+ Branch: <branch> (off <base>)
148661
+ Canonical diff command: git diff origin/<base>
148662
+
148663
+ If that command returns empty, treat it as "no changes \u2014 nothing to review" and stop per your system prompt. Do not search for the work elsewhere.
148664
+
148665
+ ## Your task
148666
+ <YOUR TASK content>
148667
+
148668
+ ## Build-phase failures
148669
+ <tight summary \u2014 what broke, root cause, the fix \u2014 or "no build-phase failures">
148670
+ \`\`\`
148671
+
148672
+ Follow the template with the diff content (\`git diff origin/<base-branch>\`, single-rev form \u2014 \`main...HEAD\` and \`--cached\` both miss the uncommitted edits self-review runs on) and your task brief. Instruct the subagent to flag bugs, logic errors, missing edge cases, gaps between request and diff, and unintended changes.
148432
148673
 
148433
148674
  Delegation + research discipline (distilled from \`/anneal\` canonical \u2014 these are codified learnings from many review rounds, not theoretical best practices):
148434
148675
  - Do NOT summarize what you implemented \u2014 that biases the subagent toward validating the shape of your solution rather than questioning it.
@@ -148827,21 +149068,21 @@ function initToolState(params) {
148827
149068
  }
148828
149069
 
148829
149070
  // agents/claude.ts
148830
- import { execFileSync as execFileSync4 } from "node:child_process";
149071
+ import { execFileSync as execFileSync5 } from "node:child_process";
148831
149072
  import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync9 } from "node:fs";
148832
- import { join as join12 } from "node:path";
149073
+ import { join as join13 } from "node:path";
148833
149074
  import { performance as performance6 } from "node:perf_hooks";
148834
149075
 
148835
149076
  // utils/install.ts
148836
149077
  import { spawnSync as spawnSync4 } from "node:child_process";
148837
149078
  import { chmodSync, createWriteStream, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "node:fs";
148838
- import { join as join9 } from "node:path";
149079
+ import { join as join10 } from "node:path";
148839
149080
  import { pipeline } from "node:stream/promises";
148840
149081
  async function installFromNpmTarball(params) {
148841
149082
  const tempDir = process.env.PULLFROG_TEMP_DIR;
148842
149083
  if (!tempDir) throw new Error("PULLFROG_TEMP_DIR is not set");
148843
- const extractedDir = join9(tempDir, "package");
148844
- const cliPath = join9(extractedDir, params.executablePath);
149084
+ const extractedDir = join10(tempDir, "package");
149085
+ const cliPath = join10(extractedDir, params.executablePath);
148845
149086
  if (existsSync5(cliPath)) {
148846
149087
  log.debug(`\xBB using cached binary at ${cliPath}`);
148847
149088
  return cliPath;
@@ -148866,7 +149107,7 @@ async function installFromNpmTarball(params) {
148866
149107
  }
148867
149108
  }
148868
149109
  log.debug(`\xBB installing ${params.packageName}@${resolvedVersion}...`);
148869
- const tarballPath = join9(tempDir, "package.tgz");
149110
+ const tarballPath = join10(tempDir, "package.tgz");
148870
149111
  const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org";
148871
149112
  let tarballUrl;
148872
149113
  if (params.packageName.startsWith("@")) {
@@ -149005,16 +149246,16 @@ function isRouterKeylimitExhaustedError(text) {
149005
149246
  // utils/skills.ts
149006
149247
  import { spawnSync as spawnSync5 } from "node:child_process";
149007
149248
  import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync7 } from "node:fs";
149008
- import { tmpdir as tmpdir2 } from "node:os";
149009
- import { dirname as dirname2, join as join10 } from "node:path";
149249
+ import { tmpdir as tmpdir3 } from "node:os";
149250
+ import { dirname as dirname2, join as join11 } from "node:path";
149010
149251
  import { fileURLToPath } from "node:url";
149011
149252
  var skillsVersion = getDevDependencyVersion("skills");
149012
149253
  var BUNDLED_SKILL_NAMES = ["git-archaeology"];
149013
149254
  function resolveSkillPath(name) {
149014
149255
  const here = dirname2(fileURLToPath(import.meta.url));
149015
149256
  const candidates = [
149016
- join10(here, "..", "skills", name, "SKILL.md"),
149017
- join10(here, "skills", name, "SKILL.md")
149257
+ join11(here, "..", "skills", name, "SKILL.md"),
149258
+ join11(here, "skills", name, "SKILL.md")
149018
149259
  ];
149019
149260
  for (const candidate of candidates) {
149020
149261
  if (existsSync6(candidate)) return candidate;
@@ -149026,9 +149267,9 @@ function installBundledSkills(params) {
149026
149267
  for (const name of BUNDLED_SKILL_NAMES) {
149027
149268
  const content = readFileSync5(resolveSkillPath(name), "utf8");
149028
149269
  for (const targetDir of SKILL_TARGET_DIRS) {
149029
- const skillDir = join10(params.home, targetDir, name);
149270
+ const skillDir = join11(params.home, targetDir, name);
149030
149271
  mkdirSync3(skillDir, { recursive: true });
149031
- writeFileSync7(join10(skillDir, "SKILL.md"), content);
149272
+ writeFileSync7(join11(skillDir, "SKILL.md"), content);
149032
149273
  }
149033
149274
  }
149034
149275
  log.success(`installed bundled skills: ${BUNDLED_SKILL_NAMES.join(", ")}`);
@@ -149049,7 +149290,7 @@ function addSkill(params) {
149049
149290
  "-y"
149050
149291
  ],
149051
149292
  {
149052
- cwd: tmpdir2(),
149293
+ cwd: tmpdir3(),
149053
149294
  env: { ...process.env, ...params.env },
149054
149295
  stdio: "pipe",
149055
149296
  timeout: 3e4
@@ -149134,7 +149375,7 @@ var ThinkingTimer = class {
149134
149375
  import { randomUUID as randomUUID3 } from "node:crypto";
149135
149376
  import { mkdirSync as mkdirSync4, rmSync as rmSync2, writeFileSync as writeFileSync8 } from "node:fs";
149136
149377
  import { homedir } from "node:os";
149137
- import { join as join11 } from "node:path";
149378
+ import { join as join12 } from "node:path";
149138
149379
  var VERTEX_SERVICE_ACCOUNT_JSON_ENV = "VERTEX_SERVICE_ACCOUNT_JSON";
149139
149380
  var GOOGLE_APPLICATION_CREDENTIALS_ENV = "GOOGLE_APPLICATION_CREDENTIALS";
149140
149381
  var GOOGLE_CLOUD_PROJECT_ENV = "GOOGLE_CLOUD_PROJECT";
@@ -149163,7 +149404,7 @@ function readProjectIdFromVertexServiceAccountJson() {
149163
149404
  }
149164
149405
  function createSecretDir() {
149165
149406
  const base = process.env.PULLFROG_SECRET_HOME || process.env.HOME || homedir();
149166
- const secretDir = join11(base, ".pullfrog", "secrets", randomUUID3());
149407
+ const secretDir = join12(base, ".pullfrog", "secrets", randomUUID3());
149167
149408
  mkdirSync4(secretDir, { recursive: true, mode: 448 });
149168
149409
  return secretDir;
149169
149410
  }
@@ -149172,7 +149413,7 @@ function materializeVertexCredentials(params) {
149172
149413
  const blob = process.env[VERTEX_SERVICE_ACCOUNT_JSON_ENV];
149173
149414
  if (!blob) return void 0;
149174
149415
  const secretDir = createSecretDir();
149175
- const credentialsPath = join11(secretDir, "vertex-sa.json");
149416
+ const credentialsPath = join12(secretDir, "vertex-sa.json");
149176
149417
  writeFileSync8(credentialsPath, blob, { mode: 384 });
149177
149418
  process.env[GOOGLE_APPLICATION_CREDENTIALS_ENV] = credentialsPath;
149178
149419
  const projectId = readProjectIdFromVertexServiceAccountJson();
@@ -149184,6 +149425,7 @@ function materializeVertexCredentials(params) {
149184
149425
  function cleanupVertexCredentials(credentials) {
149185
149426
  if (!credentials) return;
149186
149427
  rmSync2(credentials.secretDir, { recursive: true, force: true });
149428
+ delete process.env[GOOGLE_APPLICATION_CREDENTIALS_ENV];
149187
149429
  }
149188
149430
  function applyClaudeVertexEnv(env2) {
149189
149431
  env2.CLAUDE_CODE_USE_VERTEX = "1";
@@ -149497,9 +149739,9 @@ async function installClaudeCli() {
149497
149739
  });
149498
149740
  }
149499
149741
  function writeMcpConfig(ctx) {
149500
- const configDir = join12(ctx.tmpdir, ".claude");
149742
+ const configDir = join13(ctx.tmpdir, ".claude");
149501
149743
  mkdirSync5(configDir, { recursive: true });
149502
- const configPath = join12(configDir, "mcp.json");
149744
+ const configPath = join13(configDir, "mcp.json");
149503
149745
  writeFileSync9(
149504
149746
  configPath,
149505
149747
  JSON.stringify({
@@ -149925,8 +150167,8 @@ function installManagedSettings(ctx) {
149925
150167
  if (process.env.CI !== "true") return;
149926
150168
  const content = JSON.stringify(buildManagedSettings(ctx), null, 2);
149927
150169
  try {
149928
- execFileSync4("sudo", ["mkdir", "-p", MANAGED_SETTINGS_DIR]);
149929
- execFileSync4("sudo", ["tee", MANAGED_SETTINGS_PATH], {
150170
+ execFileSync5("sudo", ["mkdir", "-p", MANAGED_SETTINGS_DIR]);
150171
+ execFileSync5("sudo", ["tee", MANAGED_SETTINGS_PATH], {
149930
150172
  input: content,
149931
150173
  stdio: ["pipe", "ignore", "pipe"]
149932
150174
  });
@@ -149948,15 +150190,15 @@ var claude = agent({
149948
150190
  const model = !specifier ? void 0 : isBedrockRoute ? specifier : isVertexRoute2 ? void 0 : stripProviderPrefix(specifier);
149949
150191
  const homeEnv = {
149950
150192
  HOME: ctx.tmpdir,
149951
- XDG_CONFIG_HOME: join12(ctx.tmpdir, ".config")
150193
+ XDG_CONFIG_HOME: join13(ctx.tmpdir, ".config")
149952
150194
  };
149953
- mkdirSync5(join12(homeEnv.XDG_CONFIG_HOME, "claude"), { recursive: true });
150195
+ mkdirSync5(join13(homeEnv.XDG_CONFIG_HOME, "claude"), { recursive: true });
149954
150196
  const agentBrowserVersion = getDevDependencyVersion("agent-browser");
149955
150197
  addSkill({
149956
150198
  ref: `vercel-labs/agent-browser@v${agentBrowserVersion}`,
149957
150199
  skill: "agent-browser",
149958
150200
  env: homeEnv,
149959
- agent: "claude"
150201
+ agent: "claude-code"
149960
150202
  });
149961
150203
  installBundledSkills({ home: homeEnv.HOME });
149962
150204
  const mcpConfigPath = writeMcpConfig(ctx);
@@ -150035,7 +150277,7 @@ var claude = agent({
150035
150277
  // agents/opencode_v2.ts
150036
150278
  var core2 = __toESM(require_core(), 1);
150037
150279
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync11 } from "node:fs";
150038
- import { join as join14 } from "node:path";
150280
+ import { join as join15 } from "node:path";
150039
150281
  import { performance as performance7 } from "node:perf_hooks";
150040
150282
 
150041
150283
  // utils/agentHangReport.ts
@@ -150136,7 +150378,7 @@ function formatBillingExhaustedBody(diagnostic) {
150136
150378
  // utils/codexHome.ts
150137
150379
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync10 } from "node:fs";
150138
150380
  import { homedir as homedir2 } from "node:os";
150139
- import { join as join13 } from "node:path";
150381
+ import { join as join14 } from "node:path";
150140
150382
  var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
150141
150383
  function installCodexAuth() {
150142
150384
  const raw2 = process.env[CODEX_AUTH_ENV];
@@ -150146,9 +150388,9 @@ function installCodexAuth() {
150146
150388
  log.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
150147
150389
  return null;
150148
150390
  }
150149
- const xdgDataHome = join13(homedir2(), ".local", "share");
150150
- const opencodeDir = join13(xdgDataHome, "opencode");
150151
- const authPath = join13(opencodeDir, "auth.json");
150391
+ const xdgDataHome = join14(homedir2(), ".local", "share");
150392
+ const opencodeDir = join14(xdgDataHome, "opencode");
150393
+ const authPath = join14(opencodeDir, "auth.json");
150152
150394
  const opencodeAuth = {
150153
150395
  openai: {
150154
150396
  type: "oauth",
@@ -150276,7 +150518,7 @@ export default async function pullfrogEventsPlugin() {
150276
150518
  `;
150277
150519
 
150278
150520
  // agents/opencodeShared.ts
150279
- import { execFileSync as execFileSync5 } from "node:child_process";
150521
+ import { execFileSync as execFileSync6 } from "node:child_process";
150280
150522
 
150281
150523
  // agents/subagentModels.ts
150282
150524
  function deriveSubagentModels(orchestratorSpec) {
@@ -150325,7 +150567,7 @@ async function installOpencodeCli(params) {
150325
150567
  var AUTO_SELECT_WARNING = "select a model explicitly in the Pullfrog console (https://pullfrog.com/console) to avoid this.";
150326
150568
  function getOpenCodeModels(cliPath) {
150327
150569
  try {
150328
- const output = execFileSync5(cliPath, ["models"], {
150570
+ const output = execFileSync6(cliPath, ["models"], {
150329
150571
  encoding: "utf-8",
150330
150572
  timeout: 3e4,
150331
150573
  env: process.env
@@ -150822,13 +151064,13 @@ var opencode = agent({
150822
151064
  const model = vertexModel ?? (isBedrockRoute ? `amazon-bedrock/${rawModel}` : rawModel);
150823
151065
  const homeEnv = {
150824
151066
  HOME: ctx.tmpdir,
150825
- XDG_CONFIG_HOME: join14(ctx.tmpdir, ".config")
151067
+ XDG_CONFIG_HOME: join15(ctx.tmpdir, ".config")
150826
151068
  };
150827
- mkdirSync7(join14(homeEnv.XDG_CONFIG_HOME, "opencode"), { recursive: true });
150828
- const opencodePluginDir = join14(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
151069
+ mkdirSync7(join15(homeEnv.XDG_CONFIG_HOME, "opencode"), { recursive: true });
151070
+ const opencodePluginDir = join15(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
150829
151071
  mkdirSync7(opencodePluginDir, { recursive: true });
150830
151072
  writeFileSync11(
150831
- join14(opencodePluginDir, PULLFROG_OPENCODE_PLUGIN_FILENAME),
151073
+ join15(opencodePluginDir, PULLFROG_OPENCODE_PLUGIN_FILENAME),
150832
151074
  PULLFROG_OPENCODE_PLUGIN_SOURCE
150833
151075
  );
150834
151076
  const agentBrowserVersion = getDevDependencyVersion("agent-browser");
@@ -151190,7 +151432,7 @@ async function fetchBodyHtml(ctx) {
151190
151432
  }
151191
151433
 
151192
151434
  // utils/byokFallback.ts
151193
- var FREE_FALLBACK_SLUG = "opencode/minimax-m2.5-free";
151435
+ var FREE_FALLBACK_SLUG = "opencode/big-pickle";
151194
151436
  function selectFallbackModelIfNeeded(input) {
151195
151437
  if (input.proxyModel) return { fallback: false };
151196
151438
  if (!input.resolvedModel) return { fallback: false };
@@ -151208,7 +151450,7 @@ function selectFallbackModelIfNeeded(input) {
151208
151450
  import { randomUUID as randomUUID4 } from "node:crypto";
151209
151451
  import { writeFileSync as writeFileSync12 } from "node:fs";
151210
151452
  import { createServer as createServer2 } from "node:http";
151211
- import { join as join15 } from "node:path";
151453
+ import { join as join16 } from "node:path";
151212
151454
  var CODE_TTL_MS = 5 * 60 * 1e3;
151213
151455
  var TAMPER_WINDOW_MS = 6e4;
151214
151456
  function revokeGitHubToken(token) {
@@ -151280,7 +151522,7 @@ async function startGitAuthServer(tmpdir4) {
151280
151522
  function writeAskpassScript(code) {
151281
151523
  const scriptId = randomUUID4();
151282
151524
  const scriptName = `askpass-${scriptId}.js`;
151283
- const scriptPath = join15(tmpdir4, scriptName);
151525
+ const scriptPath = join16(tmpdir4, scriptName);
151284
151526
  const content = [
151285
151527
  `#!/usr/bin/env node`,
151286
151528
  `var a=process.argv[2]||"";`,
@@ -151319,7 +151561,7 @@ async function startGitAuthServer(tmpdir4) {
151319
151561
  var core3 = __toESM(require_core(), 1);
151320
151562
  import { createSign } from "node:crypto";
151321
151563
  import { rename, writeFile } from "node:fs/promises";
151322
- import { dirname as dirname3, join as join16 } from "node:path";
151564
+ import { dirname as dirname3, join as join17 } from "node:path";
151323
151565
 
151324
151566
  // node_modules/.pnpm/@octokit+plugin-throttling@11.0.3_@octokit+core@7.0.5/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
151325
151567
  var import_light = __toESM(require_light(), 1);
@@ -155177,7 +155419,7 @@ function getGitHubUsageSummary() {
155177
155419
  }
155178
155420
  async function writeGitHubUsageSummaryToFile(path3) {
155179
155421
  const summary2 = getGitHubUsageSummary();
155180
- const tmpPath = join16(dirname3(path3), `.usage-summary-${process.pid}.tmp`);
155422
+ const tmpPath = join17(dirname3(path3), `.usage-summary-${process.pid}.tmp`);
155181
155423
  await writeFile(tmpPath, JSON.stringify(summary2));
155182
155424
  await rename(tmpPath, path3);
155183
155425
  }
@@ -155228,7 +155470,7 @@ function createOctokit(token) {
155228
155470
  }
155229
155471
 
155230
155472
  // utils/instructions.ts
155231
- import { execSync as execSync2 } from "node:child_process";
155473
+ import { execSync as execSync3 } from "node:child_process";
155232
155474
  function buildRuntimeContext(ctx) {
155233
155475
  const {
155234
155476
  "~pullfrog": _2,
@@ -155240,7 +155482,7 @@ function buildRuntimeContext(ctx) {
155240
155482
  } = ctx.payload;
155241
155483
  let gitStatus;
155242
155484
  try {
155243
- gitStatus = execSync2("git status --short", { encoding: "utf-8", stdio: "pipe" }).trim() || "(clean)";
155485
+ gitStatus = execSync3("git status --short", { encoding: "utf-8", stdio: "pipe" }).trim() || "(clean)";
155244
155486
  } catch {
155245
155487
  }
155246
155488
  const data = {
@@ -155577,7 +155819,7 @@ function resolveInstructions(ctx) {
155577
155819
 
155578
155820
  // utils/learnings.ts
155579
155821
  import { mkdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
155580
- import { dirname as dirname4, join as join17 } from "node:path";
155822
+ import { dirname as dirname4, join as join18 } from "node:path";
155581
155823
 
155582
155824
  // utils/learningsTruncate.ts
155583
155825
  var MAX_LEARNINGS_LENGTH = 1e5;
@@ -155594,7 +155836,7 @@ function truncateAtLineBoundary(body, cap) {
155594
155836
  // utils/learnings.ts
155595
155837
  var LEARNINGS_FILE_NAME = "pullfrog-learnings.md";
155596
155838
  function learningsFilePath(tmpdir4) {
155597
- return join17(tmpdir4, LEARNINGS_FILE_NAME);
155839
+ return join18(tmpdir4, LEARNINGS_FILE_NAME);
155598
155840
  }
155599
155841
  async function seedLearningsFile(params) {
155600
155842
  const path3 = learningsFilePath(params.tmpdir);
@@ -156274,7 +156516,7 @@ async function runProxyResolution(ctx) {
156274
156516
 
156275
156517
  // utils/prSummary.ts
156276
156518
  import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
156277
- import { dirname as dirname5, join as join18 } from "node:path";
156519
+ import { dirname as dirname5, join as join19 } from "node:path";
156278
156520
  var SUMMARY_FILE_NAME = "pullfrog-summary.md";
156279
156521
  var SUMMARY_SCAFFOLD = `# PR summary
156280
156522
 
@@ -156284,7 +156526,7 @@ var SUMMARY_SCAFFOLD = `# PR summary
156284
156526
  var MIN_SNAPSHOT_LENGTH = 60;
156285
156527
  var MAX_SNAPSHOT_LENGTH = 32768;
156286
156528
  function summaryFilePath(tmpdir4) {
156287
- return join18(tmpdir4, SUMMARY_FILE_NAME);
156529
+ return join19(tmpdir4, SUMMARY_FILE_NAME);
156288
156530
  }
156289
156531
  async function seedSummaryFile(params) {
156290
156532
  const path3 = summaryFilePath(params.tmpdir);
@@ -156478,6 +156720,16 @@ async function resolveRunContextData(params) {
156478
156720
  }
156479
156721
 
156480
156722
  // utils/runErrorRenderer.ts
156723
+ function isProviderModelNotFoundError(message) {
156724
+ return message.includes("ProviderModelNotFoundError");
156725
+ }
156726
+ function formatProviderModelNotFoundSummary(input) {
156727
+ return `Pullfrog's free fallback model is no longer available in OpenCode's catalog. Add an API key for your configured model in the Pullfrog console for \`${input.owner}/${input.name}\`, or contact support if this persists.
156728
+
156729
+ \`\`\`
156730
+ ${input.raw}
156731
+ \`\`\``;
156732
+ }
156481
156733
  function renderRunError(input) {
156482
156734
  const billingError = isRouterKeylimitExhaustedError(input.errorMessage) ? new BillingError(input.errorMessage, { code: "router_keylimit_exhausted" }) : null;
156483
156735
  if (billingError) {
@@ -156499,6 +156751,14 @@ function renderRunError(input) {
156499
156751
  if (apiKeyErrorSummary) {
156500
156752
  return { summary: apiKeyErrorSummary, comment: apiKeyErrorSummary };
156501
156753
  }
156754
+ if (isProviderModelNotFoundError(input.errorMessage)) {
156755
+ const body = formatProviderModelNotFoundSummary({
156756
+ owner: input.repo.owner,
156757
+ name: input.repo.name,
156758
+ raw: input.errorMessage
156759
+ });
156760
+ return { summary: body, comment: body };
156761
+ }
156502
156762
  if (hangBody) {
156503
156763
  return {
156504
156764
  summary: `### \u274C Pullfrog failed
@@ -156600,16 +156860,17 @@ async function persistRunArtifacts(toolContext) {
156600
156860
  }
156601
156861
  async function finalizeSuccessRun(input) {
156602
156862
  await persistRunArtifacts(input.toolContext);
156603
- if (!input.result.success && input.toolState.progressComment) {
156604
- const rawError = input.result.error || "agent run failed";
156605
- const errorBody = isApiKeyAuthError(rawError) ? formatApiKeyErrorSummary({
156606
- owner: input.repo.owner,
156607
- name: input.repo.name,
156608
- raw: rawError
156609
- }) : rawError;
156610
- await reportErrorToComment({ toolState: input.toolState, error: errorBody }).catch((error49) => {
156611
- log.debug(`failure error report failed: ${error49}`);
156612
- });
156863
+ const rendered = !input.result.success ? renderRunError({
156864
+ errorMessage: input.result.error || "agent run failed",
156865
+ repo: input.repo,
156866
+ agentDiagnostic: input.toolState.agentDiagnostic
156867
+ }) : null;
156868
+ if (rendered && input.toolState.progressComment) {
156869
+ await reportErrorToComment({ toolState: input.toolState, error: rendered.comment }).catch(
156870
+ (error49) => {
156871
+ log.debug(`failure error report failed: ${error49}`);
156872
+ }
156873
+ );
156613
156874
  }
156614
156875
  if (input.result.success && input.toolState.progressComment && !input.toolState.finalSummaryWritten) {
156615
156876
  await deleteProgressComment(input.toolContext).catch((error49) => {
@@ -156619,7 +156880,7 @@ async function finalizeSuccessRun(input) {
156619
156880
  try {
156620
156881
  const usageSummary = formatUsageSummary(input.toolState.usageEntries);
156621
156882
  const body = input.toolState.lastProgressBody || input.result.output;
156622
- const parts = [body, usageSummary].filter(Boolean);
156883
+ const parts = [rendered?.summary, body, usageSummary].filter(Boolean);
156623
156884
  if (parts.length > 0) {
156624
156885
  await writeSummary(parts.join("\n\n"));
156625
156886
  }
@@ -156704,116 +156965,6 @@ function logRunStartup(ctx) {
156704
156965
  log.info(`\xBB timeout: ${resolveTimeoutForLog(ctx.payload.timeout)}`);
156705
156966
  }
156706
156967
 
156707
- // utils/setup.ts
156708
- import { execFileSync as execFileSync6, execSync as execSync3 } from "node:child_process";
156709
- import { mkdtempSync as mkdtempSync2 } from "node:fs";
156710
- import { tmpdir as tmpdir3 } from "node:os";
156711
- import { join as join19 } from "node:path";
156712
- function createTempDirectory() {
156713
- const sharedTempDir = mkdtempSync2(join19(tmpdir3(), "pullfrog-"));
156714
- process.env.PULLFROG_TEMP_DIR = sharedTempDir;
156715
- log.info(`\xBB created temp dir at ${sharedTempDir}`);
156716
- return sharedTempDir;
156717
- }
156718
- function envScopedToRepo() {
156719
- const scoped = { ...process.env };
156720
- for (const key of Object.keys(scoped)) {
156721
- if (key.startsWith("GIT_")) delete scoped[key];
156722
- }
156723
- return scoped;
156724
- }
156725
- function removeIncludeIfEntries(repoDir) {
156726
- const env2 = envScopedToRepo();
156727
- let configOutput;
156728
- try {
156729
- configOutput = execSync3("git config --local --get-regexp -z ^includeif\\.", {
156730
- cwd: repoDir,
156731
- encoding: "utf-8",
156732
- stdio: "pipe",
156733
- env: env2
156734
- });
156735
- } catch {
156736
- log.debug("\xBB no includeIf credential entries to remove");
156737
- return;
156738
- }
156739
- const seen = /* @__PURE__ */ new Set();
156740
- for (const entry of configOutput.split("\0")) {
156741
- if (!entry) continue;
156742
- const nl = entry.indexOf("\n");
156743
- const key = nl === -1 ? entry : entry.slice(0, nl);
156744
- if (!key || seen.has(key)) continue;
156745
- seen.add(key);
156746
- try {
156747
- execFileSync6("git", ["config", "--local", "--unset-all", key], {
156748
- cwd: repoDir,
156749
- stdio: "pipe",
156750
- env: env2
156751
- });
156752
- } catch (error49) {
156753
- log.debug(
156754
- `\xBB failed to unset ${key}: ${error49 instanceof Error ? error49.message : String(error49)}`
156755
- );
156756
- }
156757
- }
156758
- if (seen.size > 0)
156759
- log.info(
156760
- `\xBB removed ${seen.size} includeIf credential ${seen.size === 1 ? "entry" : "entries"}`
156761
- );
156762
- }
156763
- async function setupGit(params) {
156764
- const repoDir = process.cwd();
156765
- log.info("\xBB setting up git configuration...");
156766
- try {
156767
- let currentEmail = "";
156768
- try {
156769
- currentEmail = execSync3("git config user.email", {
156770
- cwd: repoDir,
156771
- stdio: "pipe",
156772
- encoding: "utf-8"
156773
- }).trim();
156774
- } catch {
156775
- }
156776
- const shouldSetDefaults = !currentEmail || currentEmail === "github-actions[bot]@users.noreply.github.com";
156777
- if (shouldSetDefaults) {
156778
- execSync3('git config --local user.email "226033991+pullfrog[bot]@users.noreply.github.com"', {
156779
- cwd: repoDir,
156780
- stdio: "pipe"
156781
- });
156782
- execSync3('git config --local user.name "pullfrog[bot]"', {
156783
- cwd: repoDir,
156784
- stdio: "pipe"
156785
- });
156786
- log.debug("\xBB git user configured (using defaults)");
156787
- } else {
156788
- log.debug(`\xBB git user already configured (${currentEmail}), skipping`);
156789
- }
156790
- if (params.shell === "disabled") {
156791
- execSync3("git config --local core.hooksPath /dev/null", {
156792
- cwd: repoDir,
156793
- stdio: "pipe"
156794
- });
156795
- log.debug("\xBB git hooks disabled (shell=disabled)");
156796
- }
156797
- } catch (error49) {
156798
- log.info(`Failed to set git config: ${error49 instanceof Error ? error49.message : String(error49)}`);
156799
- }
156800
- try {
156801
- execSync3("git config --local --unset-all http.https://github.com/.extraheader", {
156802
- cwd: repoDir,
156803
- stdio: "pipe"
156804
- });
156805
- log.info("\xBB removed existing authentication headers");
156806
- } catch {
156807
- log.debug("\xBB no existing authentication headers to remove");
156808
- }
156809
- removeIncludeIfEntries(repoDir);
156810
- const originUrl = `https://github.com/${params.owner}/${params.name}.git`;
156811
- $2("git", ["remote", "set-url", "origin", originUrl], { cwd: repoDir });
156812
- params.toolState.pushUrl = originUrl;
156813
- $2("git", ["config", "--local", "credential.helper", ""], { cwd: repoDir });
156814
- log.info("\xBB git authentication configured");
156815
- }
156816
-
156817
156968
  // utils/todoTracking.ts
156818
156969
  function isValidTodoStatus(value2) {
156819
156970
  return value2 === "pending" || value2 === "in_progress" || value2 === "completed" || value2 === "cancelled";
@@ -157021,6 +157172,7 @@ async function main() {
157021
157172
  toolState.beforeSha = payload.event.before_sha;
157022
157173
  }
157023
157174
  const tokenRef = __using(_stack2, await resolveTokens({ push: payload.push }), true);
157175
+ wipeRunnerLeakSurface();
157024
157176
  const oidcCredentials = process.env.ACTIONS_ID_TOKEN_REQUEST_URL && process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN ? {
157025
157177
  requestUrl: process.env.ACTIONS_ID_TOKEN_REQUEST_URL,
157026
157178
  requestToken: process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN
@@ -157190,7 +157342,7 @@ ${instructions.user}` : null,
157190
157342
  });
157191
157343
  if (agentId === "opencode") {
157192
157344
  const pluginDir = join20(process.cwd(), ".opencode", "plugin");
157193
- const hasPlugins = existsSync7(pluginDir) && readdirSync(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
157345
+ const hasPlugins = existsSync7(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
157194
157346
  if (hasPlugins && toolState.dependencyInstallation?.promise) {
157195
157347
  log.info(
157196
157348
  "\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"
@@ -158238,7 +158390,7 @@ async function run2() {
158238
158390
  }
158239
158391
 
158240
158392
  // cli.ts
158241
- var VERSION10 = "0.1.11";
158393
+ var VERSION10 = "0.1.13";
158242
158394
  var bin = basename2(process.argv[1] || "");
158243
158395
  var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
158244
158396
  var rawArgs = process.argv.slice(2);