pullfrog 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -97748,14 +97748,14 @@ var require_turndown_cjs = __commonJS({
97748
97748
  } else if (node2.nodeType === 1) {
97749
97749
  replacement = replacementForNode.call(self2, node2);
97750
97750
  }
97751
- return join20(output, replacement);
97751
+ return join21(output, replacement);
97752
97752
  }, "");
97753
97753
  }
97754
97754
  function postProcess(output) {
97755
97755
  var self2 = this;
97756
97756
  this.rules.forEach(function(rule) {
97757
97757
  if (typeof rule.append === "function") {
97758
- output = join20(output, rule.append(self2.options));
97758
+ output = join21(output, rule.append(self2.options));
97759
97759
  }
97760
97760
  });
97761
97761
  return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
@@ -97767,7 +97767,7 @@ var require_turndown_cjs = __commonJS({
97767
97767
  if (whitespace.leading || whitespace.trailing) content = content.trim();
97768
97768
  return whitespace.leading + rule.replacement(content, node2, this.options) + whitespace.trailing;
97769
97769
  }
97770
- function join20(output, replacement) {
97770
+ function join21(output, replacement) {
97771
97771
  var s1 = trimTrailingNewlines(output);
97772
97772
  var s2 = trimLeadingNewlines(replacement);
97773
97773
  var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
@@ -100664,9 +100664,9 @@ 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
- import { join as join19 } from "node:path";
100669
+ import { join as join20 } from "node:path";
100670
100670
 
100671
100671
  // node_modules/.pnpm/@ark+util@0.56.0/node_modules/@ark/util/out/arrays.js
100672
100672
  var liftArray = (data) => Array.isArray(data) ? data : [data];
@@ -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
  }),
@@ -109761,6 +109768,25 @@ var providers = {
109761
109768
  }
109762
109769
  }
109763
109770
  }),
109771
+ vertex: provider({
109772
+ displayName: "Google Vertex AI",
109773
+ envVars: [
109774
+ "VERTEX_SERVICE_ACCOUNT_JSON",
109775
+ "GOOGLE_CLOUD_PROJECT",
109776
+ "VERTEX_LOCATION",
109777
+ "VERTEX_MODEL_ID"
109778
+ ],
109779
+ models: {
109780
+ // single routing entry — the actual Vertex AI model ID is read from
109781
+ // VERTEX_MODEL_ID at run time. see ModelRouting docs for why we don't
109782
+ // catalog individual Vertex models.
109783
+ byok: {
109784
+ displayName: "Google Vertex AI",
109785
+ resolve: "vertex",
109786
+ routing: "vertex"
109787
+ }
109788
+ }
109789
+ }),
109764
109790
  openrouter: provider({
109765
109791
  displayName: "OpenRouter",
109766
109792
  envVars: ["OPENROUTER_API_KEY"],
@@ -109862,6 +109888,11 @@ var providers = {
109862
109888
  displayName: "Kimi K2",
109863
109889
  resolve: "openrouter/moonshotai/kimi-k2.6",
109864
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"
109865
109896
  }
109866
109897
  }
109867
109898
  })
@@ -109906,6 +109937,11 @@ var modelAliases = Object.entries(providers).flatMap(
109906
109937
  hidden: def.hidden ?? false
109907
109938
  }))
109908
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;
109909
109945
  var MAX_FALLBACK_DEPTH = 10;
109910
109946
  function resolveDisplayAlias(slug2) {
109911
109947
  let current = slug2;
@@ -109924,9 +109960,13 @@ function resolveCliModel(slug2) {
109924
109960
  return resolveDisplayAlias(slug2)?.resolve;
109925
109961
  }
109926
109962
  var BEDROCK_MODEL_ID_ENV = "BEDROCK_MODEL_ID";
109963
+ var VERTEX_MODEL_ID_ENV = "VERTEX_MODEL_ID";
109927
109964
  function isBedrockAnthropicId(bedrockModelId) {
109928
109965
  return bedrockModelId.toLowerCase().split(/[./:]/).includes("anthropic");
109929
109966
  }
109967
+ function isVertexAnthropicId(vertexModelId) {
109968
+ return /^claude-/i.test(vertexModelId.trim());
109969
+ }
109930
109970
 
109931
109971
  // utils/buildPullfrogFooter.ts
109932
109972
  var PULLFROG_DIVIDER = "<!-- PULLFROG_DIVIDER_DO_NOT_REMOVE_PLZ -->";
@@ -144209,7 +144249,7 @@ var import_semver = __toESM(require_semver2(), 1);
144209
144249
  // package.json
144210
144250
  var package_default = {
144211
144251
  name: "pullfrog",
144212
- version: "0.1.10",
144252
+ version: "0.1.12",
144213
144253
  type: "module",
144214
144254
  bin: {
144215
144255
  pullfrog: "dist/cli.mjs",
@@ -144407,8 +144447,8 @@ function closeBrowserDaemon(toolState) {
144407
144447
 
144408
144448
  // mcp/checkout.ts
144409
144449
  import { createHash as createHash2 } from "node:crypto";
144410
- import { statSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
144411
- 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";
144412
144452
 
144413
144453
  // utils/diffCoverage.ts
144414
144454
  import { isAbsolute, normalize as normalize2, resolve } from "node:path";
@@ -145920,6 +145960,183 @@ async function reportReviewNodeId(ctx, params) {
145920
145960
  await patchWorkflowRunFields(ctx, { reviewNodeId: params.nodeId });
145921
145961
  }
145922
145962
 
145963
+ // utils/setup.ts
145964
+ import { execFileSync as execFileSync4, execSync as execSync2 } from "node:child_process";
145965
+ import { mkdtempSync as mkdtempSync2, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
145966
+ import { tmpdir as tmpdir2 } from "node:os";
145967
+ import { join as join4 } from "node:path";
145968
+ function createTempDirectory() {
145969
+ const sharedTempDir = mkdtempSync2(join4(tmpdir2(), "pullfrog-"));
145970
+ process.env.PULLFROG_TEMP_DIR = sharedTempDir;
145971
+ log.info(`\xBB created temp dir at ${sharedTempDir}`);
145972
+ return sharedTempDir;
145973
+ }
145974
+ function wipeRunnerLeakSurface() {
145975
+ const runnerTemp = process.env.RUNNER_TEMP;
145976
+ if (!runnerTemp) return;
145977
+ const preserve = /* @__PURE__ */ new Set();
145978
+ for (const envVar of [
145979
+ "GITHUB_OUTPUT",
145980
+ "GITHUB_ENV",
145981
+ "GITHUB_PATH",
145982
+ "GITHUB_STATE",
145983
+ "GITHUB_STEP_SUMMARY"
145984
+ ]) {
145985
+ const path3 = process.env[envVar];
145986
+ if (!path3) continue;
145987
+ try {
145988
+ preserve.add(realpathSync2(path3));
145989
+ } catch {
145990
+ preserve.add(path3);
145991
+ }
145992
+ }
145993
+ const wiped = [];
145994
+ const tryUnlink = (path3) => {
145995
+ let resolved = path3;
145996
+ try {
145997
+ resolved = realpathSync2(path3);
145998
+ } catch {
145999
+ }
146000
+ if (preserve.has(resolved) || preserve.has(path3)) return;
146001
+ try {
146002
+ unlinkSync2(path3);
146003
+ wiped.push(path3);
146004
+ } catch {
146005
+ }
146006
+ };
146007
+ const listDir = (dir) => {
146008
+ try {
146009
+ return readdirSync(dir);
146010
+ } catch {
146011
+ return [];
146012
+ }
146013
+ };
146014
+ const fileCommandsDir = join4(runnerTemp, "_runner_file_commands");
146015
+ for (const entry of listDir(fileCommandsDir)) {
146016
+ tryUnlink(join4(fileCommandsDir, entry));
146017
+ }
146018
+ for (const entry of listDir(runnerTemp)) {
146019
+ if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
146020
+ tryUnlink(join4(runnerTemp, entry));
146021
+ }
146022
+ }
146023
+ if (wiped.length > 0) {
146024
+ log.info(`\xBB wiped ${wiped.length} leak-surface file(s) from $RUNNER_TEMP`);
146025
+ log.debug(`\xBB wiped paths: ${wiped.join(", ")}`);
146026
+ }
146027
+ }
146028
+ function envScopedToRepo() {
146029
+ const scoped = { ...process.env };
146030
+ for (const key of Object.keys(scoped)) {
146031
+ if (key.startsWith("GIT_")) delete scoped[key];
146032
+ }
146033
+ return scoped;
146034
+ }
146035
+ function removeIncludeIfEntries(repoDir) {
146036
+ const env2 = envScopedToRepo();
146037
+ let configOutput;
146038
+ try {
146039
+ configOutput = execSync2("git config --local --get-regexp -z ^includeif\\.", {
146040
+ cwd: repoDir,
146041
+ encoding: "utf-8",
146042
+ stdio: "pipe",
146043
+ env: env2
146044
+ });
146045
+ } catch {
146046
+ log.debug("\xBB no includeIf credential entries to remove");
146047
+ return;
146048
+ }
146049
+ const seen = /* @__PURE__ */ new Set();
146050
+ for (const entry of configOutput.split("\0")) {
146051
+ if (!entry) continue;
146052
+ const nl = entry.indexOf("\n");
146053
+ const key = nl === -1 ? entry : entry.slice(0, nl);
146054
+ if (!key || seen.has(key)) continue;
146055
+ seen.add(key);
146056
+ try {
146057
+ execFileSync4("git", ["config", "--local", "--unset-all", key], {
146058
+ cwd: repoDir,
146059
+ stdio: "pipe",
146060
+ env: env2
146061
+ });
146062
+ } catch (error49) {
146063
+ log.debug(
146064
+ `\xBB failed to unset ${key}: ${error49 instanceof Error ? error49.message : String(error49)}`
146065
+ );
146066
+ }
146067
+ }
146068
+ if (seen.size > 0)
146069
+ log.info(
146070
+ `\xBB removed ${seen.size} includeIf credential ${seen.size === 1 ? "entry" : "entries"}`
146071
+ );
146072
+ }
146073
+ async function setupGit(params) {
146074
+ const repoDir = process.cwd();
146075
+ log.info("\xBB setting up git configuration...");
146076
+ try {
146077
+ let currentEmail = "";
146078
+ try {
146079
+ currentEmail = execSync2("git config user.email", {
146080
+ cwd: repoDir,
146081
+ stdio: "pipe",
146082
+ encoding: "utf-8"
146083
+ }).trim();
146084
+ } catch {
146085
+ }
146086
+ const shouldSetDefaults = !currentEmail || currentEmail === "github-actions[bot]@users.noreply.github.com";
146087
+ if (shouldSetDefaults) {
146088
+ execSync2('git config --local user.email "226033991+pullfrog[bot]@users.noreply.github.com"', {
146089
+ cwd: repoDir,
146090
+ stdio: "pipe"
146091
+ });
146092
+ execSync2('git config --local user.name "pullfrog[bot]"', {
146093
+ cwd: repoDir,
146094
+ stdio: "pipe"
146095
+ });
146096
+ log.debug("\xBB git user configured (using defaults)");
146097
+ } else {
146098
+ log.debug(`\xBB git user already configured (${currentEmail}), skipping`);
146099
+ }
146100
+ if (params.shell === "disabled") {
146101
+ execSync2("git config --local core.hooksPath /dev/null", {
146102
+ cwd: repoDir,
146103
+ stdio: "pipe"
146104
+ });
146105
+ log.debug("\xBB git hooks disabled (shell=disabled)");
146106
+ }
146107
+ } catch (error49) {
146108
+ log.info(`Failed to set git config: ${error49 instanceof Error ? error49.message : String(error49)}`);
146109
+ }
146110
+ try {
146111
+ execSync2("git config --local --unset-all http.https://github.com/.extraheader", {
146112
+ cwd: repoDir,
146113
+ stdio: "pipe"
146114
+ });
146115
+ log.info("\xBB removed existing authentication headers");
146116
+ } catch {
146117
+ log.debug("\xBB no existing authentication headers to remove");
146118
+ }
146119
+ removeIncludeIfEntries(repoDir);
146120
+ const originUrl = `https://github.com/${params.owner}/${params.name}.git`;
146121
+ $2("git", ["remote", "set-url", "origin", originUrl], { cwd: repoDir });
146122
+ params.toolState.pushUrl = originUrl;
146123
+ $2("git", ["config", "--local", "credential.helper", ""], { cwd: repoDir });
146124
+ params.toolState.initialHead = captureInitialHead(repoDir);
146125
+ log.info("\xBB git authentication configured");
146126
+ }
146127
+ function captureInitialHead(repoDir) {
146128
+ try {
146129
+ const name = $2("git", ["symbolic-ref", "--short", "HEAD"], {
146130
+ cwd: repoDir,
146131
+ log: false
146132
+ }).trim();
146133
+ if (name) return { kind: "branch", name };
146134
+ } catch {
146135
+ }
146136
+ const sha = $2("git", ["rev-parse", "HEAD"], { cwd: repoDir, log: false }).trim();
146137
+ return { kind: "detached", sha };
146138
+ }
146139
+
145923
146140
  // mcp/checkout.ts
145924
146141
  function formatFilesWithLineNumbers(files) {
145925
146142
  const output = [];
@@ -146094,7 +146311,7 @@ function cleanupStaleGitLocks() {
146094
146311
  }
146095
146312
  if (now - mtimeMs < STALE_LOCK_AGE_MS) continue;
146096
146313
  try {
146097
- unlinkSync2(relPath);
146314
+ unlinkSync3(relPath);
146098
146315
  log.warning(`\xBB removed stale ${relPath} from prior run`);
146099
146316
  } catch (e) {
146100
146317
  log.debug(
@@ -146253,6 +146470,15 @@ async function checkoutPrBranch(pr, params) {
146253
146470
  return { hookWarning: postCheckoutHook.warning };
146254
146471
  }
146255
146472
  var inFlightCheckouts = /* @__PURE__ */ new Map();
146473
+ function headsEqual(a, b) {
146474
+ if (a.kind === "branch" && b.kind === "branch") return a.name === b.name;
146475
+ if (a.kind === "detached" && b.kind === "detached") return a.sha === b.sha;
146476
+ return false;
146477
+ }
146478
+ function describeHead(h) {
146479
+ if (h.kind === "branch") return `branch \`${h.name}\``;
146480
+ return `detached HEAD \`${h.sha}\``;
146481
+ }
146256
146482
  function CheckoutPrTool(ctx) {
146257
146483
  const runCheckout = async (pull_number) => {
146258
146484
  const prResponse = await ctx.octokit.rest.pulls.get({
@@ -146299,7 +146525,7 @@ function CheckoutPrTool(ctx) {
146299
146525
  headSha: ctx.toolState.checkoutSha
146300
146526
  });
146301
146527
  if (incremental) {
146302
- incrementalDiffPath = join4(
146528
+ incrementalDiffPath = join5(
146303
146529
  tempDir,
146304
146530
  `pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
146305
146531
  );
@@ -146313,7 +146539,7 @@ function CheckoutPrTool(ctx) {
146313
146539
  const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
146314
146540
  log.debug(`formatted diff preview (first 100 lines):
146315
146541
  ${diffPreview}`);
146316
- const diffPath = join4(tempDir, `pr-${pull_number}-${headShort}.diff`);
146542
+ const diffPath = join5(tempDir, `pr-${pull_number}-${headShort}.diff`);
146317
146543
  writeFileSync2(diffPath, formatResult.content);
146318
146544
  log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
146319
146545
  ctx.toolState.diffCoverage = createDiffCoverageState({
@@ -146380,7 +146606,8 @@ ${diffPreview}`);
146380
146606
  };
146381
146607
  return tool({
146382
146608
  name: "checkout_pr",
146383
- 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.",
146609
+ timeoutMs: 6e5,
146610
+ 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.",
146384
146611
  parameters: CheckoutPr,
146385
146612
  execute: execute(async ({ pull_number }) => {
146386
146613
  const inFlight = inFlightCheckouts.get(pull_number);
@@ -146388,13 +146615,23 @@ ${diffPreview}`);
146388
146615
  log.info(`\xBB checkout_pr({pull_number:${pull_number}}) already in flight \u2014 sharing result`);
146389
146616
  return inFlight;
146390
146617
  }
146391
- const currentBranch = $2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { log: false }).trim();
146392
- if (currentBranch !== `pr-${pull_number}`) {
146393
- const dirty = $2("git", ["status", "--porcelain"], { log: false }).trim();
146394
- if (dirty) {
146395
- throw new Error(
146396
- `cannot checkout PR #${pull_number} while the working tree has uncommitted changes. commit, push, or discard them before switching. dirty paths:
146618
+ const dirty = $2("git", ["status", "--porcelain"], { log: false }).trim();
146619
+ if (dirty) {
146620
+ throw new Error(
146621
+ `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:
146397
146622
  ${dirty}`
146623
+ );
146624
+ }
146625
+ const initialHead = ctx.toolState.initialHead;
146626
+ if (initialHead) {
146627
+ const currentHead = captureInitialHead(process.cwd());
146628
+ const targetBranch = `pr-${pull_number}`;
146629
+ const onTarget = currentHead.kind === "branch" && currentHead.name === targetBranch;
146630
+ const onInitial = headsEqual(currentHead, initialHead);
146631
+ if (!onTarget && !onInitial) {
146632
+ const recoverCmd = initialHead.kind === "branch" ? `git checkout ${initialHead.name}` : `git checkout ${initialHead.sha}`;
146633
+ throw new Error(
146634
+ `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.`
146398
146635
  );
146399
146636
  }
146400
146637
  }
@@ -146411,7 +146648,7 @@ ${dirty}`
146411
146648
 
146412
146649
  // mcp/checkSuite.ts
146413
146650
  import { mkdirSync, writeFileSync as writeFileSync3 } from "node:fs";
146414
- import { join as join5 } from "node:path";
146651
+ import { join as join6 } from "node:path";
146415
146652
  var GetCheckSuiteLogs = type({
146416
146653
  check_suite_id: type.number.describe("the id from check_suite.id")
146417
146654
  });
@@ -146507,7 +146744,7 @@ function GetCheckSuiteLogsTool(ctx) {
146507
146744
  if (!tempDir) {
146508
146745
  throw new Error("PULLFROG_TEMP_DIR not set");
146509
146746
  }
146510
- const logsDir = join5(tempDir, "ci-logs");
146747
+ const logsDir = join6(tempDir, "ci-logs");
146511
146748
  mkdirSync(logsDir, { recursive: true });
146512
146749
  const jobResults = [];
146513
146750
  for (const run4 of failedRuns) {
@@ -146534,7 +146771,7 @@ function GetCheckSuiteLogsTool(ctx) {
146534
146771
  );
146535
146772
  }
146536
146773
  const logsText = await logsResult.text();
146537
- const logPath = join5(logsDir, `job-${job.id}.log`);
146774
+ const logPath = join6(logsDir, `job-${job.id}.log`);
146538
146775
  writeFileSync3(logPath, logsText);
146539
146776
  const analysis = analyzeLog(logsText, 80);
146540
146777
  const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
@@ -146584,7 +146821,7 @@ function GetCheckSuiteLogsTool(ctx) {
146584
146821
 
146585
146822
  // mcp/commitInfo.ts
146586
146823
  import { writeFileSync as writeFileSync4 } from "node:fs";
146587
- import { join as join6 } from "node:path";
146824
+ import { join as join7 } from "node:path";
146588
146825
  var CommitInfo = type({
146589
146826
  sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
146590
146827
  });
@@ -146608,7 +146845,7 @@ function CommitInfoTool(ctx) {
146608
146845
  "PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
146609
146846
  );
146610
146847
  }
146611
- const diffFile = join6(tempDir, `commit-${sha.slice(0, 7)}.diff`);
146848
+ const diffFile = join7(tempDir, `commit-${sha.slice(0, 7)}.diff`);
146612
146849
  writeFileSync4(diffFile, formatResult.content);
146613
146850
  log.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
146614
146851
  return {
@@ -147066,7 +147303,7 @@ function PullRequestInfoTool(ctx) {
147066
147303
 
147067
147304
  // mcp/reviewComments.ts
147068
147305
  import { writeFileSync as writeFileSync5 } from "node:fs";
147069
- import { join as join7 } from "node:path";
147306
+ import { join as join8 } from "node:path";
147070
147307
  var REVIEW_THREADS_QUERY = `
147071
147308
  query ($owner: String!, $name: String!, $prNumber: Int!) {
147072
147309
  repository(owner: $owner, name: $name) {
@@ -147458,7 +147695,7 @@ function GetReviewCommentsTool(ctx) {
147458
147695
  throw new Error("PULLFROG_TEMP_DIR not set");
147459
147696
  }
147460
147697
  const filename = `review-${params.review_id}-threads.md`;
147461
- const commentsPath = join7(tempDir, filename);
147698
+ const commentsPath = join8(tempDir, filename);
147462
147699
  writeFileSync5(commentsPath, formatted.content);
147463
147700
  log.debug(`wrote ${threadBlocks.length} threads to ${commentsPath}`);
147464
147701
  return {
@@ -147687,7 +147924,7 @@ import { spawn as spawn4, spawnSync as spawnSync3 } from "node:child_process";
147687
147924
  import { randomUUID as randomUUID2 } from "node:crypto";
147688
147925
  import { closeSync, openSync, writeFileSync as writeFileSync6 } from "node:fs";
147689
147926
  import { userInfo } from "node:os";
147690
- import { join as join8 } from "node:path";
147927
+ import { join as join9 } from "node:path";
147691
147928
  import { setTimeout as sleep2 } from "node:timers/promises";
147692
147929
  var ShellParams = type({
147693
147930
  command: "string",
@@ -147820,7 +148057,7 @@ function getTempDir() {
147820
148057
  var MAX_OUTPUT_CHARS = 5e3;
147821
148058
  function capOutput(output) {
147822
148059
  if (output.length <= MAX_OUTPUT_CHARS) return output;
147823
- const fullPath = join8(getTempDir(), `shell-${randomUUID2().slice(0, 8)}.log`);
148060
+ const fullPath = join9(getTempDir(), `shell-${randomUUID2().slice(0, 8)}.log`);
147824
148061
  writeFileSync6(fullPath, output);
147825
148062
  const elided = output.length - MAX_OUTPUT_CHARS;
147826
148063
  return `... [${elided} chars truncated; full output saved to ${fullPath}] ...
@@ -147835,6 +148072,7 @@ function isGitCommand(command) {
147835
148072
  function ShellTool(ctx) {
147836
148073
  return tool({
147837
148074
  name: "shell",
148075
+ timeoutMs: 12e4,
147838
148076
  description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
147839
148077
 
147840
148078
  Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
@@ -147874,8 +148112,8 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
147874
148112
  if (params.background) {
147875
148113
  const tempDir = getTempDir();
147876
148114
  const handle = `bg-${randomUUID2().slice(0, 8)}`;
147877
- const outputPath = join8(tempDir, `${handle}.log`);
147878
- const pidPath = join8(tempDir, `${handle}.pid`);
148115
+ const outputPath = join9(tempDir, `${handle}.log`);
148116
+ const pidPath = join9(tempDir, `${handle}.pid`);
147879
148117
  const logFd = openSync(outputPath, "a");
147880
148118
  let proc2;
147881
148119
  try {
@@ -148215,6 +148453,8 @@ var REVIEWER_AGENT_NAME = "reviewfrog";
148215
148453
  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.
148216
148454
 
148217
148455
  HARD CONSTRAINTS (non-negotiable, regardless of orchestrator instructions):
148456
+ - 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.
148457
+ - 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.
148218
148458
  - 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).
148219
148459
  - 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.
148220
148460
  - Do NOT spawn further subagents. You are a leaf reviewer; recursive dispatch pre-aggregates findings through an intermediate model and defeats the design.
@@ -148405,7 +148645,25 @@ function computeModes(agentId) {
148405
148645
 
148406
148646
  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.
148407
148647
 
148408
- 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.
148648
+ 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.
148649
+
148650
+ \`\`\`
148651
+ ## What you're reviewing
148652
+ This is a PRE-COMMIT Build-mode self-review. The work to review lives in the working tree (uncommitted), NOT in committed history.
148653
+
148654
+ Branch: <branch> (off <base>)
148655
+ Canonical diff command: git diff origin/<base>
148656
+
148657
+ 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.
148658
+
148659
+ ## Your task
148660
+ <YOUR TASK content>
148661
+
148662
+ ## Build-phase failures
148663
+ <tight summary \u2014 what broke, root cause, the fix \u2014 or "no build-phase failures">
148664
+ \`\`\`
148665
+
148666
+ 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.
148409
148667
 
148410
148668
  Delegation + research discipline (distilled from \`/anneal\` canonical \u2014 these are codified learnings from many review rounds, not theoretical best practices):
148411
148669
  - Do NOT summarize what you implemented \u2014 that biases the subagent toward validating the shape of your solution rather than questioning it.
@@ -148804,21 +149062,21 @@ function initToolState(params) {
148804
149062
  }
148805
149063
 
148806
149064
  // agents/claude.ts
148807
- import { execFileSync as execFileSync4 } from "node:child_process";
148808
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync8 } from "node:fs";
148809
- import { join as join11 } from "node:path";
149065
+ import { execFileSync as execFileSync5 } from "node:child_process";
149066
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync9 } from "node:fs";
149067
+ import { join as join13 } from "node:path";
148810
149068
  import { performance as performance6 } from "node:perf_hooks";
148811
149069
 
148812
149070
  // utils/install.ts
148813
149071
  import { spawnSync as spawnSync4 } from "node:child_process";
148814
149072
  import { chmodSync, createWriteStream, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "node:fs";
148815
- import { join as join9 } from "node:path";
149073
+ import { join as join10 } from "node:path";
148816
149074
  import { pipeline } from "node:stream/promises";
148817
149075
  async function installFromNpmTarball(params) {
148818
149076
  const tempDir = process.env.PULLFROG_TEMP_DIR;
148819
149077
  if (!tempDir) throw new Error("PULLFROG_TEMP_DIR is not set");
148820
- const extractedDir = join9(tempDir, "package");
148821
- const cliPath = join9(extractedDir, params.executablePath);
149078
+ const extractedDir = join10(tempDir, "package");
149079
+ const cliPath = join10(extractedDir, params.executablePath);
148822
149080
  if (existsSync5(cliPath)) {
148823
149081
  log.debug(`\xBB using cached binary at ${cliPath}`);
148824
149082
  return cliPath;
@@ -148843,7 +149101,7 @@ async function installFromNpmTarball(params) {
148843
149101
  }
148844
149102
  }
148845
149103
  log.debug(`\xBB installing ${params.packageName}@${resolvedVersion}...`);
148846
- const tarballPath = join9(tempDir, "package.tgz");
149104
+ const tarballPath = join10(tempDir, "package.tgz");
148847
149105
  const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org";
148848
149106
  let tarballUrl;
148849
149107
  if (params.packageName.startsWith("@")) {
@@ -148982,16 +149240,16 @@ function isRouterKeylimitExhaustedError(text) {
148982
149240
  // utils/skills.ts
148983
149241
  import { spawnSync as spawnSync5 } from "node:child_process";
148984
149242
  import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync7 } from "node:fs";
148985
- import { tmpdir as tmpdir2 } from "node:os";
148986
- import { dirname as dirname2, join as join10 } from "node:path";
149243
+ import { tmpdir as tmpdir3 } from "node:os";
149244
+ import { dirname as dirname2, join as join11 } from "node:path";
148987
149245
  import { fileURLToPath } from "node:url";
148988
149246
  var skillsVersion = getDevDependencyVersion("skills");
148989
149247
  var BUNDLED_SKILL_NAMES = ["git-archaeology"];
148990
149248
  function resolveSkillPath(name) {
148991
149249
  const here = dirname2(fileURLToPath(import.meta.url));
148992
149250
  const candidates = [
148993
- join10(here, "..", "skills", name, "SKILL.md"),
148994
- join10(here, "skills", name, "SKILL.md")
149251
+ join11(here, "..", "skills", name, "SKILL.md"),
149252
+ join11(here, "skills", name, "SKILL.md")
148995
149253
  ];
148996
149254
  for (const candidate of candidates) {
148997
149255
  if (existsSync6(candidate)) return candidate;
@@ -149003,9 +149261,9 @@ function installBundledSkills(params) {
149003
149261
  for (const name of BUNDLED_SKILL_NAMES) {
149004
149262
  const content = readFileSync5(resolveSkillPath(name), "utf8");
149005
149263
  for (const targetDir of SKILL_TARGET_DIRS) {
149006
- const skillDir = join10(params.home, targetDir, name);
149264
+ const skillDir = join11(params.home, targetDir, name);
149007
149265
  mkdirSync3(skillDir, { recursive: true });
149008
- writeFileSync7(join10(skillDir, "SKILL.md"), content);
149266
+ writeFileSync7(join11(skillDir, "SKILL.md"), content);
149009
149267
  }
149010
149268
  }
149011
149269
  log.success(`installed bundled skills: ${BUNDLED_SKILL_NAMES.join(", ")}`);
@@ -149026,7 +149284,7 @@ function addSkill(params) {
149026
149284
  "-y"
149027
149285
  ],
149028
149286
  {
149029
- cwd: tmpdir2(),
149287
+ cwd: tmpdir3(),
149030
149288
  env: { ...process.env, ...params.env },
149031
149289
  stdio: "pipe",
149032
149290
  timeout: 3e4
@@ -149107,6 +149365,71 @@ var ThinkingTimer = class {
149107
149365
  }
149108
149366
  };
149109
149367
 
149368
+ // utils/vertex.ts
149369
+ import { randomUUID as randomUUID3 } from "node:crypto";
149370
+ import { mkdirSync as mkdirSync4, rmSync as rmSync2, writeFileSync as writeFileSync8 } from "node:fs";
149371
+ import { homedir } from "node:os";
149372
+ import { join as join12 } from "node:path";
149373
+ var VERTEX_SERVICE_ACCOUNT_JSON_ENV = "VERTEX_SERVICE_ACCOUNT_JSON";
149374
+ var GOOGLE_APPLICATION_CREDENTIALS_ENV = "GOOGLE_APPLICATION_CREDENTIALS";
149375
+ var GOOGLE_CLOUD_PROJECT_ENV = "GOOGLE_CLOUD_PROJECT";
149376
+ var VERTEX_LOCATION_ENV = "VERTEX_LOCATION";
149377
+ function hasEnvVar(name) {
149378
+ const value2 = process.env[name];
149379
+ return typeof value2 === "string" && value2.length > 0;
149380
+ }
149381
+ function isVertexRoute(model) {
149382
+ const vertexId = process.env[VERTEX_MODEL_ID_ENV]?.trim();
149383
+ return model !== void 0 && vertexId !== void 0 && vertexId === model;
149384
+ }
149385
+ function readProjectIdFromVertexServiceAccountJson() {
149386
+ const blob = process.env[VERTEX_SERVICE_ACCOUNT_JSON_ENV];
149387
+ if (!blob) return void 0;
149388
+ try {
149389
+ const parsed2 = JSON.parse(blob);
149390
+ if (!parsed2 || typeof parsed2 !== "object" || !("project_id" in parsed2)) {
149391
+ return void 0;
149392
+ }
149393
+ const projectId = parsed2.project_id;
149394
+ return typeof projectId === "string" && projectId.length > 0 ? projectId : void 0;
149395
+ } catch {
149396
+ return void 0;
149397
+ }
149398
+ }
149399
+ function createSecretDir() {
149400
+ const base = process.env.PULLFROG_SECRET_HOME || process.env.HOME || homedir();
149401
+ const secretDir = join12(base, ".pullfrog", "secrets", randomUUID3());
149402
+ mkdirSync4(secretDir, { recursive: true, mode: 448 });
149403
+ return secretDir;
149404
+ }
149405
+ function materializeVertexCredentials(params) {
149406
+ if (!isVertexRoute(params.model)) return void 0;
149407
+ const blob = process.env[VERTEX_SERVICE_ACCOUNT_JSON_ENV];
149408
+ if (!blob) return void 0;
149409
+ const secretDir = createSecretDir();
149410
+ const credentialsPath = join12(secretDir, "vertex-sa.json");
149411
+ writeFileSync8(credentialsPath, blob, { mode: 384 });
149412
+ process.env[GOOGLE_APPLICATION_CREDENTIALS_ENV] = credentialsPath;
149413
+ const projectId = readProjectIdFromVertexServiceAccountJson();
149414
+ if (projectId && !hasEnvVar(GOOGLE_CLOUD_PROJECT_ENV)) {
149415
+ process.env[GOOGLE_CLOUD_PROJECT_ENV] = projectId;
149416
+ }
149417
+ return { credentialsPath, secretDir };
149418
+ }
149419
+ function cleanupVertexCredentials(credentials) {
149420
+ if (!credentials) return;
149421
+ rmSync2(credentials.secretDir, { recursive: true, force: true });
149422
+ delete process.env[GOOGLE_APPLICATION_CREDENTIALS_ENV];
149423
+ }
149424
+ function applyClaudeVertexEnv(env2) {
149425
+ env2.CLAUDE_CODE_USE_VERTEX = "1";
149426
+ env2.ANTHROPIC_VERTEX_PROJECT_ID ??= env2[GOOGLE_CLOUD_PROJECT_ENV];
149427
+ env2.CLOUD_ML_REGION ??= env2[VERTEX_LOCATION_ENV];
149428
+ }
149429
+ function resolveVertexOpenCodeModel(model) {
149430
+ return isVertexRoute(model) && model ? `google-vertex/${model}` : void 0;
149431
+ }
149432
+
149110
149433
  // agents/postRun.ts
149111
149434
  import { readFile } from "node:fs/promises";
149112
149435
  function getUnsubmittedReview(toolState) {
@@ -149410,10 +149733,10 @@ async function installClaudeCli() {
149410
149733
  });
149411
149734
  }
149412
149735
  function writeMcpConfig(ctx) {
149413
- const configDir = join11(ctx.tmpdir, ".claude");
149414
- mkdirSync4(configDir, { recursive: true });
149415
- const configPath = join11(configDir, "mcp.json");
149416
- writeFileSync8(
149736
+ const configDir = join13(ctx.tmpdir, ".claude");
149737
+ mkdirSync5(configDir, { recursive: true });
149738
+ const configPath = join13(configDir, "mcp.json");
149739
+ writeFileSync9(
149417
149740
  configPath,
149418
149741
  JSON.stringify({
149419
149742
  mcpServers: {
@@ -149795,37 +150118,51 @@ ${stderrContext}`
149795
150118
  var MANAGED_SETTINGS_DIR = "/etc/claude-code";
149796
150119
  var MANAGED_SETTINGS_PATH = `${MANAGED_SETTINGS_DIR}/managed-settings.json`;
149797
150120
  var CODEX_AUTH_DENY_PATH = "~/.local/share/opencode/auth.json";
149798
- var managedSettings = {
149799
- allowManagedPermissionRulesOnly: true,
149800
- allowManagedHooksOnly: true,
149801
- permissions: {
149802
- deny: [
149803
- "Read(//proc/**)",
149804
- "Read(//sys/**)",
149805
- "Grep(//proc/**)",
149806
- "Grep(//sys/**)",
149807
- "Edit(//proc/**)",
149808
- "Edit(//sys/**)",
149809
- "Glob(//proc/**)",
149810
- "Glob(//sys/**)",
149811
- `Read(${CODEX_AUTH_DENY_PATH})`,
149812
- `Grep(${CODEX_AUTH_DENY_PATH})`,
149813
- `Edit(${CODEX_AUTH_DENY_PATH})`,
149814
- `Glob(${CODEX_AUTH_DENY_PATH})`
149815
- ]
149816
- },
149817
- sandbox: {
149818
- filesystem: {
149819
- denyRead: ["/proc", "/sys", CODEX_AUTH_DENY_PATH]
150121
+ function buildManagedSettings(ctx) {
150122
+ const secretDenyPaths = ctx.secretDenyPaths ?? [];
150123
+ const toolDeny = secretDenyPaths.flatMap((path3) => [
150124
+ `Read(${path3}/**)`,
150125
+ `Read(/${path3}/**)`,
150126
+ `Grep(${path3}/**)`,
150127
+ `Grep(/${path3}/**)`,
150128
+ `Edit(${path3}/**)`,
150129
+ `Edit(/${path3}/**)`,
150130
+ `Glob(${path3}/**)`,
150131
+ `Glob(/${path3}/**)`
150132
+ ]);
150133
+ return {
150134
+ allowManagedPermissionRulesOnly: true,
150135
+ allowManagedHooksOnly: true,
150136
+ permissions: {
150137
+ deny: [
150138
+ "Read(//proc/**)",
150139
+ "Read(//sys/**)",
150140
+ "Grep(//proc/**)",
150141
+ "Grep(//sys/**)",
150142
+ "Edit(//proc/**)",
150143
+ "Edit(//sys/**)",
150144
+ "Glob(//proc/**)",
150145
+ "Glob(//sys/**)",
150146
+ `Read(${CODEX_AUTH_DENY_PATH})`,
150147
+ `Grep(${CODEX_AUTH_DENY_PATH})`,
150148
+ `Edit(${CODEX_AUTH_DENY_PATH})`,
150149
+ `Glob(${CODEX_AUTH_DENY_PATH})`,
150150
+ ...toolDeny
150151
+ ]
150152
+ },
150153
+ sandbox: {
150154
+ filesystem: {
150155
+ denyRead: ["/proc", "/sys", CODEX_AUTH_DENY_PATH, ...secretDenyPaths]
150156
+ }
149820
150157
  }
149821
- }
149822
- };
149823
- function installManagedSettings() {
150158
+ };
150159
+ }
150160
+ function installManagedSettings(ctx) {
149824
150161
  if (process.env.CI !== "true") return;
149825
- const content = JSON.stringify(managedSettings, null, 2);
150162
+ const content = JSON.stringify(buildManagedSettings(ctx), null, 2);
149826
150163
  try {
149827
- execFileSync4("sudo", ["mkdir", "-p", MANAGED_SETTINGS_DIR]);
149828
- execFileSync4("sudo", ["tee", MANAGED_SETTINGS_PATH], {
150164
+ execFileSync5("sudo", ["mkdir", "-p", MANAGED_SETTINGS_DIR]);
150165
+ execFileSync5("sudo", ["tee", MANAGED_SETTINGS_PATH], {
149829
150166
  input: content,
149830
150167
  stdio: ["pipe", "ignore", "pipe"]
149831
150168
  });
@@ -149842,23 +150179,25 @@ var claude = agent({
149842
150179
  const specifier = ctx.payload.proxyModel ?? ctx.resolvedModel;
149843
150180
  const bedrockModelId = process.env[BEDROCK_MODEL_ID_ENV]?.trim();
149844
150181
  const isBedrockRoute = specifier !== void 0 && bedrockModelId !== void 0 && bedrockModelId === specifier && isBedrockAnthropicId(specifier);
149845
- const model = !specifier ? void 0 : isBedrockRoute ? specifier : stripProviderPrefix(specifier);
150182
+ const vertexModelId = process.env[VERTEX_MODEL_ID_ENV]?.trim();
150183
+ const isVertexRoute2 = specifier !== void 0 && vertexModelId !== void 0 && vertexModelId === specifier && isVertexAnthropicId(specifier);
150184
+ const model = !specifier ? void 0 : isBedrockRoute ? specifier : isVertexRoute2 ? void 0 : stripProviderPrefix(specifier);
149846
150185
  const homeEnv = {
149847
150186
  HOME: ctx.tmpdir,
149848
- XDG_CONFIG_HOME: join11(ctx.tmpdir, ".config")
150187
+ XDG_CONFIG_HOME: join13(ctx.tmpdir, ".config")
149849
150188
  };
149850
- mkdirSync4(join11(homeEnv.XDG_CONFIG_HOME, "claude"), { recursive: true });
150189
+ mkdirSync5(join13(homeEnv.XDG_CONFIG_HOME, "claude"), { recursive: true });
149851
150190
  const agentBrowserVersion = getDevDependencyVersion("agent-browser");
149852
150191
  addSkill({
149853
150192
  ref: `vercel-labs/agent-browser@v${agentBrowserVersion}`,
149854
150193
  skill: "agent-browser",
149855
150194
  env: homeEnv,
149856
- agent: "claude"
150195
+ agent: "claude-code"
149857
150196
  });
149858
150197
  installBundledSkills({ home: homeEnv.HOME });
149859
150198
  const mcpConfigPath = writeMcpConfig(ctx);
149860
150199
  const effort = resolveEffort(model);
149861
- installManagedSettings();
150200
+ installManagedSettings(ctx);
149862
150201
  const baseArgs = [
149863
150202
  cliPath,
149864
150203
  "--output-format",
@@ -149886,6 +150225,10 @@ var claude = agent({
149886
150225
  if (isBedrockRoute) {
149887
150226
  env2.CLAUDE_CODE_USE_BEDROCK = "1";
149888
150227
  }
150228
+ if (isVertexRoute2) {
150229
+ applyClaudeVertexEnv(env2);
150230
+ env2.ANTHROPIC_MODEL = specifier;
150231
+ }
149889
150232
  if (env2.CLAUDE_CODE_OAUTH_TOKEN && !isBedrockRoute && env2.ANTHROPIC_API_KEY) {
149890
150233
  log.debug(
149891
150234
  "\xBB CLAUDE_CODE_OAUTH_TOKEN present \u2014 stripping ANTHROPIC_API_KEY from Claude Code env so the OAuth subscription is used"
@@ -149927,8 +150270,8 @@ var claude = agent({
149927
150270
 
149928
150271
  // agents/opencode_v2.ts
149929
150272
  var core2 = __toESM(require_core(), 1);
149930
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync10 } from "node:fs";
149931
- import { join as join13 } from "node:path";
150273
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync11 } from "node:fs";
150274
+ import { join as join15 } from "node:path";
149932
150275
  import { performance as performance7 } from "node:perf_hooks";
149933
150276
 
149934
150277
  // utils/agentHangReport.ts
@@ -150027,9 +150370,9 @@ function formatBillingExhaustedBody(diagnostic) {
150027
150370
  }
150028
150371
 
150029
150372
  // utils/codexHome.ts
150030
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync9 } from "node:fs";
150031
- import { homedir } from "node:os";
150032
- import { join as join12 } from "node:path";
150373
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync10 } from "node:fs";
150374
+ import { homedir as homedir2 } from "node:os";
150375
+ import { join as join14 } from "node:path";
150033
150376
  var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
150034
150377
  function installCodexAuth() {
150035
150378
  const raw2 = process.env[CODEX_AUTH_ENV];
@@ -150039,9 +150382,9 @@ function installCodexAuth() {
150039
150382
  log.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
150040
150383
  return null;
150041
150384
  }
150042
- const xdgDataHome = join12(homedir(), ".local", "share");
150043
- const opencodeDir = join12(xdgDataHome, "opencode");
150044
- const authPath = join12(opencodeDir, "auth.json");
150385
+ const xdgDataHome = join14(homedir2(), ".local", "share");
150386
+ const opencodeDir = join14(xdgDataHome, "opencode");
150387
+ const authPath = join14(opencodeDir, "auth.json");
150045
150388
  const opencodeAuth = {
150046
150389
  openai: {
150047
150390
  type: "oauth",
@@ -150054,8 +150397,8 @@ function installCodexAuth() {
150054
150397
  ...blob.tokens.account_id ? { accountId: blob.tokens.account_id } : {}
150055
150398
  }
150056
150399
  };
150057
- mkdirSync5(opencodeDir, { recursive: true });
150058
- writeFileSync9(authPath, `${JSON.stringify(opencodeAuth, null, 2)}
150400
+ mkdirSync6(opencodeDir, { recursive: true });
150401
+ writeFileSync10(authPath, `${JSON.stringify(opencodeAuth, null, 2)}
150059
150402
  `, { mode: 384 });
150060
150403
  log.info(`\xBB installed Codex auth at ${authPath}`);
150061
150404
  return { authPath, xdgDataHome, originalRefresh: blob.tokens.refresh_token };
@@ -150169,7 +150512,7 @@ export default async function pullfrogEventsPlugin() {
150169
150512
  `;
150170
150513
 
150171
150514
  // agents/opencodeShared.ts
150172
- import { execFileSync as execFileSync5 } from "node:child_process";
150515
+ import { execFileSync as execFileSync6 } from "node:child_process";
150173
150516
 
150174
150517
  // agents/subagentModels.ts
150175
150518
  function deriveSubagentModels(orchestratorSpec) {
@@ -150218,7 +150561,7 @@ async function installOpencodeCli(params) {
150218
150561
  var AUTO_SELECT_WARNING = "select a model explicitly in the Pullfrog console (https://pullfrog.com/console) to avoid this.";
150219
150562
  function getOpenCodeModels(cliPath) {
150220
150563
  try {
150221
- const output = execFileSync5(cliPath, ["models"], {
150564
+ const output = execFileSync6(cliPath, ["models"], {
150222
150565
  encoding: "utf-8",
150223
150566
  timeout: 3e4,
150224
150567
  env: process.env
@@ -150711,16 +151054,17 @@ var opencode = agent({
150711
151054
  const rawModel = ctx.payload.proxyModel ?? ctx.resolvedModel ?? autoSelectModel(cliPath);
150712
151055
  const bedrockModelId = process.env[BEDROCK_MODEL_ID_ENV]?.trim();
150713
151056
  const isBedrockRoute = rawModel !== void 0 && bedrockModelId !== void 0 && bedrockModelId === rawModel;
150714
- const model = isBedrockRoute ? `amazon-bedrock/${rawModel}` : rawModel;
151057
+ const vertexModel = resolveVertexOpenCodeModel(rawModel);
151058
+ const model = vertexModel ?? (isBedrockRoute ? `amazon-bedrock/${rawModel}` : rawModel);
150715
151059
  const homeEnv = {
150716
151060
  HOME: ctx.tmpdir,
150717
- XDG_CONFIG_HOME: join13(ctx.tmpdir, ".config")
151061
+ XDG_CONFIG_HOME: join15(ctx.tmpdir, ".config")
150718
151062
  };
150719
- mkdirSync6(join13(homeEnv.XDG_CONFIG_HOME, "opencode"), { recursive: true });
150720
- const opencodePluginDir = join13(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
150721
- mkdirSync6(opencodePluginDir, { recursive: true });
150722
- writeFileSync10(
150723
- join13(opencodePluginDir, PULLFROG_OPENCODE_PLUGIN_FILENAME),
151063
+ mkdirSync7(join15(homeEnv.XDG_CONFIG_HOME, "opencode"), { recursive: true });
151064
+ const opencodePluginDir = join15(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
151065
+ mkdirSync7(opencodePluginDir, { recursive: true });
151066
+ writeFileSync11(
151067
+ join15(opencodePluginDir, PULLFROG_OPENCODE_PLUGIN_FILENAME),
150724
151068
  PULLFROG_OPENCODE_PLUGIN_SOURCE
150725
151069
  );
150726
151070
  const agentBrowserVersion = getDevDependencyVersion("agent-browser");
@@ -150790,15 +151134,18 @@ var opencode = agent({
150790
151134
  var agents = { claude, opencode };
150791
151135
 
150792
151136
  // utils/agent.ts
150793
- function hasEnvVar(name) {
151137
+ function hasEnvVar2(name) {
150794
151138
  const val = process.env[name];
150795
151139
  return typeof val === "string" && val.length > 0;
150796
151140
  }
150797
151141
  function hasClaudeCodeAuth() {
150798
- return hasEnvVar("CLAUDE_CODE_OAUTH_TOKEN") || hasEnvVar("ANTHROPIC_API_KEY");
151142
+ return hasEnvVar2("CLAUDE_CODE_OAUTH_TOKEN") || hasEnvVar2("ANTHROPIC_API_KEY");
150799
151143
  }
150800
151144
  function hasBedrockAuth() {
150801
- return hasEnvVar("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar("AWS_ACCESS_KEY_ID") && hasEnvVar("AWS_SECRET_ACCESS_KEY");
151145
+ return hasEnvVar2("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar2("AWS_ACCESS_KEY_ID") && hasEnvVar2("AWS_SECRET_ACCESS_KEY");
151146
+ }
151147
+ function hasVertexAuth() {
151148
+ return hasEnvVar2(VERTEX_SERVICE_ACCOUNT_JSON_ENV);
150802
151149
  }
150803
151150
  function resolveSlug(slug2) {
150804
151151
  const alias = resolveDisplayAlias(slug2);
@@ -150806,11 +151153,20 @@ function resolveSlug(slug2) {
150806
151153
  const bedrockId = process.env[BEDROCK_MODEL_ID_ENV]?.trim();
150807
151154
  if (!bedrockId) {
150808
151155
  throw new Error(
150809
- `${BEDROCK_MODEL_ID_ENV} env var is required when the model is set to "${slug2}". set it to an AWS Bedrock model ID (e.g. "us.anthropic.claude-opus-4-7", "amazon.nova-pro-v1:0"). see https://docs.pullfrog.com/bedrock for setup.`
151156
+ `${BEDROCK_MODEL_ID_ENV} env var is required when the model is set to "${slug2}". set it to an AWS Bedrock model ID from the Bedrock console. see https://docs.pullfrog.com/bedrock for setup.`
150810
151157
  );
150811
151158
  }
150812
151159
  return bedrockId;
150813
151160
  }
151161
+ if (alias?.routing === "vertex") {
151162
+ const vertexId = process.env[VERTEX_MODEL_ID_ENV]?.trim();
151163
+ if (!vertexId) {
151164
+ throw new Error(
151165
+ `${VERTEX_MODEL_ID_ENV} env var is required when the model is set to "${slug2}". set it to a Google Vertex AI model ID from Model Garden. see https://docs.pullfrog.com/vertex for setup.`
151166
+ );
151167
+ }
151168
+ return vertexId;
151169
+ }
150814
151170
  return resolveCliModel(slug2);
150815
151171
  }
150816
151172
  function resolveModel(ctx) {
@@ -150838,6 +151194,9 @@ function resolveAgent(ctx) {
150838
151194
  if (ctx.model && hasBedrockAuth() && process.env[BEDROCK_MODEL_ID_ENV]?.trim() === ctx.model) {
150839
151195
  return isBedrockAnthropicId(ctx.model) ? agents.claude : agents.opencode;
150840
151196
  }
151197
+ if (ctx.model && hasVertexAuth() && process.env[VERTEX_MODEL_ID_ENV]?.trim() === ctx.model) {
151198
+ return isVertexAnthropicId(ctx.model) ? agents.claude : agents.opencode;
151199
+ }
150841
151200
  if (ctx.model) {
150842
151201
  try {
150843
151202
  const provider2 = getModelProvider(ctx.model);
@@ -150878,26 +151237,51 @@ add the missing secret(s) to your GitHub repository at ${githubSecretsUrl}, then
150878
151237
 
150879
151238
  for full setup instructions, see https://docs.pullfrog.com/bedrock`;
150880
151239
  }
150881
- function hasEnvVar2(name) {
151240
+ function buildVertexSetupError(params) {
151241
+ const githubSecretsUrl = `https://github.com/${params.owner}/${params.name}/settings/secrets/actions`;
151242
+ return `Google Vertex AI model selected but required configuration is missing: ${params.missing.join(", ")}.
151243
+
151244
+ add the missing secret(s) to your GitHub repository at ${githubSecretsUrl}, then reference them in your workflow's \`env:\` block:
151245
+
151246
+ ${VERTEX_SERVICE_ACCOUNT_JSON_ENV}: \${{ secrets.${VERTEX_SERVICE_ACCOUNT_JSON_ENV} }}
151247
+ ${GOOGLE_CLOUD_PROJECT_ENV}: my-project
151248
+ ${VERTEX_LOCATION_ENV}: global
151249
+ ${VERTEX_MODEL_ID_ENV}: <vertex-model-id>
151250
+
151251
+ for full setup instructions, see https://docs.pullfrog.com/vertex`;
151252
+ }
151253
+ function hasEnvVar3(name) {
150882
151254
  const value2 = process.env[name];
150883
151255
  return typeof value2 === "string" && value2.length > 0;
150884
151256
  }
150885
151257
  function hasProviderKey(model) {
150886
151258
  const requiredVars = getModelEnvVars(model);
150887
151259
  if (requiredVars.length === 0) return true;
150888
- return requiredVars.some((v) => hasEnvVar2(v));
151260
+ return requiredVars.some((v) => hasEnvVar3(v));
150889
151261
  }
150890
151262
  function validateBedrockSetup(params) {
150891
- const hasAuth = hasEnvVar2("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar2("AWS_ACCESS_KEY_ID") && hasEnvVar2("AWS_SECRET_ACCESS_KEY");
151263
+ const hasAuth = hasEnvVar3("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar3("AWS_ACCESS_KEY_ID") && hasEnvVar3("AWS_SECRET_ACCESS_KEY");
150892
151264
  const missing = [];
150893
151265
  if (!hasAuth)
150894
151266
  missing.push("AWS_BEARER_TOKEN_BEDROCK (or AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY)");
150895
- if (!hasEnvVar2("AWS_REGION")) missing.push("AWS_REGION");
150896
- if (!hasEnvVar2(BEDROCK_MODEL_ID_ENV)) missing.push(BEDROCK_MODEL_ID_ENV);
151267
+ if (!hasEnvVar3("AWS_REGION")) missing.push("AWS_REGION");
151268
+ if (!hasEnvVar3(BEDROCK_MODEL_ID_ENV)) missing.push(BEDROCK_MODEL_ID_ENV);
150897
151269
  if (missing.length > 0) {
150898
151270
  throw new Error(buildBedrockSetupError({ owner: params.owner, name: params.name, missing }));
150899
151271
  }
150900
151272
  }
151273
+ function validateVertexSetup(params) {
151274
+ const hasAuth = hasEnvVar3(VERTEX_SERVICE_ACCOUNT_JSON_ENV);
151275
+ const hasProject = hasEnvVar3(GOOGLE_CLOUD_PROJECT_ENV) || readProjectIdFromVertexServiceAccountJson() !== void 0;
151276
+ const missing = [];
151277
+ if (!hasAuth) missing.push(VERTEX_SERVICE_ACCOUNT_JSON_ENV);
151278
+ if (!hasProject) missing.push(GOOGLE_CLOUD_PROJECT_ENV);
151279
+ if (!hasEnvVar3(VERTEX_LOCATION_ENV)) missing.push(VERTEX_LOCATION_ENV);
151280
+ if (!hasEnvVar3(VERTEX_MODEL_ID_ENV)) missing.push(VERTEX_MODEL_ID_ENV);
151281
+ if (missing.length > 0) {
151282
+ throw new Error(buildVertexSetupError({ owner: params.owner, name: params.name, missing }));
151283
+ }
151284
+ }
150901
151285
  function validateAgentApiKey(params) {
150902
151286
  if (params.model) {
150903
151287
  const alias = resolveDisplayAlias(params.model);
@@ -150905,16 +151289,24 @@ function validateAgentApiKey(params) {
150905
151289
  validateBedrockSetup({ owner: params.owner, name: params.name });
150906
151290
  return;
150907
151291
  }
151292
+ if (alias?.routing === "vertex") {
151293
+ validateVertexSetup({ owner: params.owner, name: params.name });
151294
+ return;
151295
+ }
150908
151296
  if (!params.model.includes("/")) {
151297
+ if (process.env[VERTEX_MODEL_ID_ENV]?.trim() === params.model) {
151298
+ validateVertexSetup({ owner: params.owner, name: params.name });
151299
+ return;
151300
+ }
150909
151301
  validateBedrockSetup({ owner: params.owner, name: params.name });
150910
151302
  return;
150911
151303
  }
150912
151304
  const requiredVars = getModelEnvVars(params.model);
150913
151305
  if (requiredVars.length === 0) return;
150914
- if (requiredVars.some((v) => hasEnvVar2(v))) return;
151306
+ if (requiredVars.some((v) => hasEnvVar3(v))) return;
150915
151307
  throw new Error(buildMissingApiKeyError({ owner: params.owner, name: params.name }));
150916
151308
  }
150917
- const hasAnyKey = [...knownApiKeys].some((k) => hasEnvVar2(k));
151309
+ const hasAnyKey = [...knownApiKeys].some((k) => hasEnvVar3(k));
150918
151310
  if (!hasAnyKey) {
150919
151311
  throw new Error(buildMissingApiKeyError({ owner: params.owner, name: params.name }));
150920
151312
  }
@@ -151034,7 +151426,7 @@ async function fetchBodyHtml(ctx) {
151034
151426
  }
151035
151427
 
151036
151428
  // utils/byokFallback.ts
151037
- var FREE_FALLBACK_SLUG = "opencode/minimax-m2.5-free";
151429
+ var FREE_FALLBACK_SLUG = "opencode/big-pickle";
151038
151430
  function selectFallbackModelIfNeeded(input) {
151039
151431
  if (input.proxyModel) return { fallback: false };
151040
151432
  if (!input.resolvedModel) return { fallback: false };
@@ -151049,10 +151441,10 @@ function selectFallbackModelIfNeeded(input) {
151049
151441
  }
151050
151442
 
151051
151443
  // utils/gitAuthServer.ts
151052
- import { randomUUID as randomUUID3 } from "node:crypto";
151053
- import { writeFileSync as writeFileSync11 } from "node:fs";
151444
+ import { randomUUID as randomUUID4 } from "node:crypto";
151445
+ import { writeFileSync as writeFileSync12 } from "node:fs";
151054
151446
  import { createServer as createServer2 } from "node:http";
151055
- import { join as join14 } from "node:path";
151447
+ import { join as join16 } from "node:path";
151056
151448
  var CODE_TTL_MS = 5 * 60 * 1e3;
151057
151449
  var TAMPER_WINDOW_MS = 6e4;
151058
151450
  function revokeGitHubToken(token) {
@@ -151112,7 +151504,7 @@ async function startGitAuthServer(tmpdir4) {
151112
151504
  const port = rawAddr.port;
151113
151505
  log.debug(`git auth server listening on 127.0.0.1:${port}`);
151114
151506
  function register4(token) {
151115
- const code = randomUUID3();
151507
+ const code = randomUUID4();
151116
151508
  const timeout = setTimeout(() => {
151117
151509
  codes.delete(code);
151118
151510
  log.debug(`git auth code expired: ${code.slice(0, 8)}...`);
@@ -151122,9 +151514,9 @@ async function startGitAuthServer(tmpdir4) {
151122
151514
  return code;
151123
151515
  }
151124
151516
  function writeAskpassScript(code) {
151125
- const scriptId = randomUUID3();
151517
+ const scriptId = randomUUID4();
151126
151518
  const scriptName = `askpass-${scriptId}.js`;
151127
- const scriptPath = join14(tmpdir4, scriptName);
151519
+ const scriptPath = join16(tmpdir4, scriptName);
151128
151520
  const content = [
151129
151521
  `#!/usr/bin/env node`,
151130
151522
  `var a=process.argv[2]||"";`,
@@ -151139,7 +151531,7 @@ async function startGitAuthServer(tmpdir4) {
151139
151531
  `try{require("fs").unlinkSync("${scriptPath.replace(/\\/g, "\\\\")}")}catch(e){}`,
151140
151532
  `})}).on("error",function(){process.exit(1)})}`
151141
151533
  ].join("\n");
151142
- writeFileSync11(scriptPath, content, { mode: 448 });
151534
+ writeFileSync12(scriptPath, content, { mode: 448 });
151143
151535
  return scriptPath;
151144
151536
  }
151145
151537
  async function close() {
@@ -151163,7 +151555,7 @@ async function startGitAuthServer(tmpdir4) {
151163
151555
  var core3 = __toESM(require_core(), 1);
151164
151556
  import { createSign } from "node:crypto";
151165
151557
  import { rename, writeFile } from "node:fs/promises";
151166
- import { dirname as dirname3, join as join15 } from "node:path";
151558
+ import { dirname as dirname3, join as join17 } from "node:path";
151167
151559
 
151168
151560
  // node_modules/.pnpm/@octokit+plugin-throttling@11.0.3_@octokit+core@7.0.5/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
151169
151561
  var import_light = __toESM(require_light(), 1);
@@ -155021,7 +155413,7 @@ function getGitHubUsageSummary() {
155021
155413
  }
155022
155414
  async function writeGitHubUsageSummaryToFile(path3) {
155023
155415
  const summary2 = getGitHubUsageSummary();
155024
- const tmpPath = join15(dirname3(path3), `.usage-summary-${process.pid}.tmp`);
155416
+ const tmpPath = join17(dirname3(path3), `.usage-summary-${process.pid}.tmp`);
155025
155417
  await writeFile(tmpPath, JSON.stringify(summary2));
155026
155418
  await rename(tmpPath, path3);
155027
155419
  }
@@ -155072,7 +155464,7 @@ function createOctokit(token) {
155072
155464
  }
155073
155465
 
155074
155466
  // utils/instructions.ts
155075
- import { execSync as execSync2 } from "node:child_process";
155467
+ import { execSync as execSync3 } from "node:child_process";
155076
155468
  function buildRuntimeContext(ctx) {
155077
155469
  const {
155078
155470
  "~pullfrog": _2,
@@ -155084,7 +155476,7 @@ function buildRuntimeContext(ctx) {
155084
155476
  } = ctx.payload;
155085
155477
  let gitStatus;
155086
155478
  try {
155087
- gitStatus = execSync2("git status --short", { encoding: "utf-8", stdio: "pipe" }).trim() || "(clean)";
155479
+ gitStatus = execSync3("git status --short", { encoding: "utf-8", stdio: "pipe" }).trim() || "(clean)";
155088
155480
  } catch {
155089
155481
  }
155090
155482
  const data = {
@@ -155421,7 +155813,7 @@ function resolveInstructions(ctx) {
155421
155813
 
155422
155814
  // utils/learnings.ts
155423
155815
  import { mkdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
155424
- import { dirname as dirname4, join as join16 } from "node:path";
155816
+ import { dirname as dirname4, join as join18 } from "node:path";
155425
155817
 
155426
155818
  // utils/learningsTruncate.ts
155427
155819
  var MAX_LEARNINGS_LENGTH = 1e5;
@@ -155438,7 +155830,7 @@ function truncateAtLineBoundary(body, cap) {
155438
155830
  // utils/learnings.ts
155439
155831
  var LEARNINGS_FILE_NAME = "pullfrog-learnings.md";
155440
155832
  function learningsFilePath(tmpdir4) {
155441
- return join16(tmpdir4, LEARNINGS_FILE_NAME);
155833
+ return join18(tmpdir4, LEARNINGS_FILE_NAME);
155442
155834
  }
155443
155835
  async function seedLearningsFile(params) {
155444
155836
  const path3 = learningsFilePath(params.tmpdir);
@@ -156118,7 +156510,7 @@ async function runProxyResolution(ctx) {
156118
156510
 
156119
156511
  // utils/prSummary.ts
156120
156512
  import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
156121
- import { dirname as dirname5, join as join17 } from "node:path";
156513
+ import { dirname as dirname5, join as join19 } from "node:path";
156122
156514
  var SUMMARY_FILE_NAME = "pullfrog-summary.md";
156123
156515
  var SUMMARY_SCAFFOLD = `# PR summary
156124
156516
 
@@ -156128,7 +156520,7 @@ var SUMMARY_SCAFFOLD = `# PR summary
156128
156520
  var MIN_SNAPSHOT_LENGTH = 60;
156129
156521
  var MAX_SNAPSHOT_LENGTH = 32768;
156130
156522
  function summaryFilePath(tmpdir4) {
156131
- return join17(tmpdir4, SUMMARY_FILE_NAME);
156523
+ return join19(tmpdir4, SUMMARY_FILE_NAME);
156132
156524
  }
156133
156525
  async function seedSummaryFile(params) {
156134
156526
  const path3 = summaryFilePath(params.tmpdir);
@@ -156322,6 +156714,16 @@ async function resolveRunContextData(params) {
156322
156714
  }
156323
156715
 
156324
156716
  // utils/runErrorRenderer.ts
156717
+ function isProviderModelNotFoundError(message) {
156718
+ return message.includes("ProviderModelNotFoundError");
156719
+ }
156720
+ function formatProviderModelNotFoundSummary(input) {
156721
+ 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.
156722
+
156723
+ \`\`\`
156724
+ ${input.raw}
156725
+ \`\`\``;
156726
+ }
156325
156727
  function renderRunError(input) {
156326
156728
  const billingError = isRouterKeylimitExhaustedError(input.errorMessage) ? new BillingError(input.errorMessage, { code: "router_keylimit_exhausted" }) : null;
156327
156729
  if (billingError) {
@@ -156343,6 +156745,14 @@ function renderRunError(input) {
156343
156745
  if (apiKeyErrorSummary) {
156344
156746
  return { summary: apiKeyErrorSummary, comment: apiKeyErrorSummary };
156345
156747
  }
156748
+ if (isProviderModelNotFoundError(input.errorMessage)) {
156749
+ const body = formatProviderModelNotFoundSummary({
156750
+ owner: input.repo.owner,
156751
+ name: input.repo.name,
156752
+ raw: input.errorMessage
156753
+ });
156754
+ return { summary: body, comment: body };
156755
+ }
156346
156756
  if (hangBody) {
156347
156757
  return {
156348
156758
  summary: `### \u274C Pullfrog failed
@@ -156444,16 +156854,17 @@ async function persistRunArtifacts(toolContext) {
156444
156854
  }
156445
156855
  async function finalizeSuccessRun(input) {
156446
156856
  await persistRunArtifacts(input.toolContext);
156447
- if (!input.result.success && input.toolState.progressComment) {
156448
- const rawError = input.result.error || "agent run failed";
156449
- const errorBody = isApiKeyAuthError(rawError) ? formatApiKeyErrorSummary({
156450
- owner: input.repo.owner,
156451
- name: input.repo.name,
156452
- raw: rawError
156453
- }) : rawError;
156454
- await reportErrorToComment({ toolState: input.toolState, error: errorBody }).catch((error49) => {
156455
- log.debug(`failure error report failed: ${error49}`);
156456
- });
156857
+ const rendered = !input.result.success ? renderRunError({
156858
+ errorMessage: input.result.error || "agent run failed",
156859
+ repo: input.repo,
156860
+ agentDiagnostic: input.toolState.agentDiagnostic
156861
+ }) : null;
156862
+ if (rendered && input.toolState.progressComment) {
156863
+ await reportErrorToComment({ toolState: input.toolState, error: rendered.comment }).catch(
156864
+ (error49) => {
156865
+ log.debug(`failure error report failed: ${error49}`);
156866
+ }
156867
+ );
156457
156868
  }
156458
156869
  if (input.result.success && input.toolState.progressComment && !input.toolState.finalSummaryWritten) {
156459
156870
  await deleteProgressComment(input.toolContext).catch((error49) => {
@@ -156463,7 +156874,7 @@ async function finalizeSuccessRun(input) {
156463
156874
  try {
156464
156875
  const usageSummary = formatUsageSummary(input.toolState.usageEntries);
156465
156876
  const body = input.toolState.lastProgressBody || input.result.output;
156466
- const parts = [body, usageSummary].filter(Boolean);
156877
+ const parts = [rendered?.summary, body, usageSummary].filter(Boolean);
156467
156878
  if (parts.length > 0) {
156468
156879
  await writeSummary(parts.join("\n\n"));
156469
156880
  }
@@ -156548,116 +156959,6 @@ function logRunStartup(ctx) {
156548
156959
  log.info(`\xBB timeout: ${resolveTimeoutForLog(ctx.payload.timeout)}`);
156549
156960
  }
156550
156961
 
156551
- // utils/setup.ts
156552
- import { execFileSync as execFileSync6, execSync as execSync3 } from "node:child_process";
156553
- import { mkdtempSync as mkdtempSync2 } from "node:fs";
156554
- import { tmpdir as tmpdir3 } from "node:os";
156555
- import { join as join18 } from "node:path";
156556
- function createTempDirectory() {
156557
- const sharedTempDir = mkdtempSync2(join18(tmpdir3(), "pullfrog-"));
156558
- process.env.PULLFROG_TEMP_DIR = sharedTempDir;
156559
- log.info(`\xBB created temp dir at ${sharedTempDir}`);
156560
- return sharedTempDir;
156561
- }
156562
- function envScopedToRepo() {
156563
- const scoped = { ...process.env };
156564
- for (const key of Object.keys(scoped)) {
156565
- if (key.startsWith("GIT_")) delete scoped[key];
156566
- }
156567
- return scoped;
156568
- }
156569
- function removeIncludeIfEntries(repoDir) {
156570
- const env2 = envScopedToRepo();
156571
- let configOutput;
156572
- try {
156573
- configOutput = execSync3("git config --local --get-regexp -z ^includeif\\.", {
156574
- cwd: repoDir,
156575
- encoding: "utf-8",
156576
- stdio: "pipe",
156577
- env: env2
156578
- });
156579
- } catch {
156580
- log.debug("\xBB no includeIf credential entries to remove");
156581
- return;
156582
- }
156583
- const seen = /* @__PURE__ */ new Set();
156584
- for (const entry of configOutput.split("\0")) {
156585
- if (!entry) continue;
156586
- const nl = entry.indexOf("\n");
156587
- const key = nl === -1 ? entry : entry.slice(0, nl);
156588
- if (!key || seen.has(key)) continue;
156589
- seen.add(key);
156590
- try {
156591
- execFileSync6("git", ["config", "--local", "--unset-all", key], {
156592
- cwd: repoDir,
156593
- stdio: "pipe",
156594
- env: env2
156595
- });
156596
- } catch (error49) {
156597
- log.debug(
156598
- `\xBB failed to unset ${key}: ${error49 instanceof Error ? error49.message : String(error49)}`
156599
- );
156600
- }
156601
- }
156602
- if (seen.size > 0)
156603
- log.info(
156604
- `\xBB removed ${seen.size} includeIf credential ${seen.size === 1 ? "entry" : "entries"}`
156605
- );
156606
- }
156607
- async function setupGit(params) {
156608
- const repoDir = process.cwd();
156609
- log.info("\xBB setting up git configuration...");
156610
- try {
156611
- let currentEmail = "";
156612
- try {
156613
- currentEmail = execSync3("git config user.email", {
156614
- cwd: repoDir,
156615
- stdio: "pipe",
156616
- encoding: "utf-8"
156617
- }).trim();
156618
- } catch {
156619
- }
156620
- const shouldSetDefaults = !currentEmail || currentEmail === "github-actions[bot]@users.noreply.github.com";
156621
- if (shouldSetDefaults) {
156622
- execSync3('git config --local user.email "226033991+pullfrog[bot]@users.noreply.github.com"', {
156623
- cwd: repoDir,
156624
- stdio: "pipe"
156625
- });
156626
- execSync3('git config --local user.name "pullfrog[bot]"', {
156627
- cwd: repoDir,
156628
- stdio: "pipe"
156629
- });
156630
- log.debug("\xBB git user configured (using defaults)");
156631
- } else {
156632
- log.debug(`\xBB git user already configured (${currentEmail}), skipping`);
156633
- }
156634
- if (params.shell === "disabled") {
156635
- execSync3("git config --local core.hooksPath /dev/null", {
156636
- cwd: repoDir,
156637
- stdio: "pipe"
156638
- });
156639
- log.debug("\xBB git hooks disabled (shell=disabled)");
156640
- }
156641
- } catch (error49) {
156642
- log.info(`Failed to set git config: ${error49 instanceof Error ? error49.message : String(error49)}`);
156643
- }
156644
- try {
156645
- execSync3("git config --local --unset-all http.https://github.com/.extraheader", {
156646
- cwd: repoDir,
156647
- stdio: "pipe"
156648
- });
156649
- log.info("\xBB removed existing authentication headers");
156650
- } catch {
156651
- log.debug("\xBB no existing authentication headers to remove");
156652
- }
156653
- removeIncludeIfEntries(repoDir);
156654
- const originUrl = `https://github.com/${params.owner}/${params.name}.git`;
156655
- $2("git", ["remote", "set-url", "origin", originUrl], { cwd: repoDir });
156656
- params.toolState.pushUrl = originUrl;
156657
- $2("git", ["config", "--local", "credential.helper", ""], { cwd: repoDir });
156658
- log.info("\xBB git authentication configured");
156659
- }
156660
-
156661
156962
  // utils/todoTracking.ts
156662
156963
  function isValidTodoStatus(value2) {
156663
156964
  return value2 === "pending" || value2 === "in_progress" || value2 === "completed" || value2 === "cancelled";
@@ -156865,6 +157166,7 @@ async function main() {
156865
157166
  toolState.beforeSha = payload.event.before_sha;
156866
157167
  }
156867
157168
  const tokenRef = __using(_stack2, await resolveTokens({ push: payload.push }), true);
157169
+ wipeRunnerLeakSurface();
156868
157170
  const oidcCredentials = process.env.ACTIONS_ID_TOKEN_REQUEST_URL && process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN ? {
156869
157171
  requestUrl: process.env.ACTIONS_ID_TOKEN_REQUEST_URL,
156870
157172
  requestToken: process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN
@@ -156886,6 +157188,7 @@ async function main() {
156886
157188
  let toolContext;
156887
157189
  let progressCallbackDisabled = false;
156888
157190
  let todoTracker;
157191
+ let vertexCredentials;
156889
157192
  try {
156890
157193
  var _stack = [];
156891
157194
  try {
@@ -156920,6 +157223,7 @@ async function main() {
156920
157223
  );
156921
157224
  toolState.modelFallback = { from: fallback.from };
156922
157225
  }
157226
+ vertexCredentials = materializeVertexCredentials({ model: resolvedModel });
156923
157227
  const agent2 = resolveAgent({ model: resolvedModel });
156924
157228
  toolState.model = payload.proxyModel ?? resolvedModel ?? effectiveSlug;
156925
157229
  validateAgentApiKey({
@@ -157031,8 +157335,8 @@ ${instructions.user}` : null,
157031
157335
  log.info(instructions.full);
157032
157336
  });
157033
157337
  if (agentId === "opencode") {
157034
- const pluginDir = join19(process.cwd(), ".opencode", "plugin");
157035
- const hasPlugins = existsSync7(pluginDir) && readdirSync(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
157338
+ const pluginDir = join20(process.cwd(), ".opencode", "plugin");
157339
+ const hasPlugins = existsSync7(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
157036
157340
  if (hasPlugins && toolState.dependencyInstallation?.promise) {
157037
157341
  log.info(
157038
157342
  "\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"
@@ -157087,6 +157391,7 @@ ${instructions.user}` : null,
157087
157391
  resolvedModel,
157088
157392
  mcpServerUrl: mcpHttpServer.url,
157089
157393
  tmpdir: tmpdir4,
157394
+ secretDenyPaths: vertexCredentials ? [vertexCredentials.secretDir] : [],
157090
157395
  instructions,
157091
157396
  todoTracker,
157092
157397
  stopScript: runContext.repoSettings.stopScript,
@@ -157190,6 +157495,7 @@ ${instructions.user}` : null,
157190
157495
  await patchWorkflowRunFields(toolContext, patch);
157191
157496
  }
157192
157497
  }
157498
+ cleanupVertexCredentials(vertexCredentials);
157193
157499
  }
157194
157500
  } catch (_3) {
157195
157501
  var _error2 = _3, _hasError2 = true;
@@ -158078,7 +158384,7 @@ async function run2() {
158078
158384
  }
158079
158385
 
158080
158386
  // cli.ts
158081
- var VERSION10 = "0.1.10";
158387
+ var VERSION10 = "0.1.12";
158082
158388
  var bin = basename2(process.argv[1] || "");
158083
158389
  var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
158084
158390
  var rawArgs = process.argv.slice(2);