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/index.js CHANGED
@@ -97475,14 +97475,14 @@ var require_turndown_cjs = __commonJS({
97475
97475
  } else if (node2.nodeType === 1) {
97476
97476
  replacement = replacementForNode.call(self2, node2);
97477
97477
  }
97478
- return join19(output, replacement);
97478
+ return join20(output, replacement);
97479
97479
  }, "");
97480
97480
  }
97481
97481
  function postProcess(output) {
97482
97482
  var self2 = this;
97483
97483
  this.rules.forEach(function(rule) {
97484
97484
  if (typeof rule.append === "function") {
97485
- output = join19(output, rule.append(self2.options));
97485
+ output = join20(output, rule.append(self2.options));
97486
97486
  }
97487
97487
  });
97488
97488
  return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
@@ -97494,7 +97494,7 @@ var require_turndown_cjs = __commonJS({
97494
97494
  if (whitespace.leading || whitespace.trailing) content = content.trim();
97495
97495
  return whitespace.leading + rule.replacement(content, node2, this.options) + whitespace.trailing;
97496
97496
  }
97497
- function join19(output, replacement) {
97497
+ function join20(output, replacement) {
97498
97498
  var s1 = trimTrailingNewlines(output);
97499
97499
  var s2 = trimLeadingNewlines(replacement);
97500
97500
  var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
@@ -98924,9 +98924,9 @@ var require_fast_content_type_parse = __commonJS({
98924
98924
  });
98925
98925
 
98926
98926
  // main.ts
98927
- import { existsSync as existsSync7, readdirSync } from "node:fs";
98927
+ import { existsSync as existsSync7, readdirSync as readdirSync2 } from "node:fs";
98928
98928
  import { readFile as readFile4 } from "node:fs/promises";
98929
- import { join as join18 } from "node:path";
98929
+ import { join as join19 } from "node:path";
98930
98930
 
98931
98931
  // node_modules/.pnpm/@ark+util@0.56.0/node_modules/@ark/util/out/arrays.js
98932
98932
  var liftArray = (data) => Array.isArray(data) ? data : [data];
@@ -107987,6 +107987,11 @@ var providers = {
107987
107987
  resolve: "opencode/kimi-k2.6",
107988
107988
  openRouterResolve: "openrouter/moonshotai/kimi-k2.6"
107989
107989
  },
107990
+ "minimax-m2.5": {
107991
+ displayName: "MiniMax M2.5",
107992
+ resolve: "opencode/minimax-m2.5",
107993
+ openRouterResolve: "openrouter/minimax/minimax-m2.5"
107994
+ },
107990
107995
  "gpt-5-nano": {
107991
107996
  displayName: "GPT Nano",
107992
107997
  resolve: "opencode/gpt-5-nano",
@@ -108003,7 +108008,9 @@ var providers = {
108003
108008
  displayName: "MiniMax M2.5",
108004
108009
  resolve: "opencode/minimax-m2.5-free",
108005
108010
  envVars: [],
108006
- isFree: true
108011
+ isFree: true,
108012
+ fallback: "opencode/big-pickle",
108013
+ hidden: true
108007
108014
  }
108008
108015
  }
108009
108016
  }),
@@ -108021,6 +108028,25 @@ var providers = {
108021
108028
  }
108022
108029
  }
108023
108030
  }),
108031
+ vertex: provider({
108032
+ displayName: "Google Vertex AI",
108033
+ envVars: [
108034
+ "VERTEX_SERVICE_ACCOUNT_JSON",
108035
+ "GOOGLE_CLOUD_PROJECT",
108036
+ "VERTEX_LOCATION",
108037
+ "VERTEX_MODEL_ID"
108038
+ ],
108039
+ models: {
108040
+ // single routing entry — the actual Vertex AI model ID is read from
108041
+ // VERTEX_MODEL_ID at run time. see ModelRouting docs for why we don't
108042
+ // catalog individual Vertex models.
108043
+ byok: {
108044
+ displayName: "Google Vertex AI",
108045
+ resolve: "vertex",
108046
+ routing: "vertex"
108047
+ }
108048
+ }
108049
+ }),
108024
108050
  openrouter: provider({
108025
108051
  displayName: "OpenRouter",
108026
108052
  envVars: ["OPENROUTER_API_KEY"],
@@ -108122,6 +108148,11 @@ var providers = {
108122
108148
  displayName: "Kimi K2",
108123
108149
  resolve: "openrouter/moonshotai/kimi-k2.6",
108124
108150
  openRouterResolve: "openrouter/moonshotai/kimi-k2.6"
108151
+ },
108152
+ "minimax-m2.5": {
108153
+ displayName: "MiniMax M2.5",
108154
+ resolve: "openrouter/minimax/minimax-m2.5",
108155
+ openRouterResolve: "openrouter/minimax/minimax-m2.5"
108125
108156
  }
108126
108157
  }
108127
108158
  })
@@ -108166,6 +108197,11 @@ var modelAliases = Object.entries(providers).flatMap(
108166
108197
  hidden: def.hidden ?? false
108167
108198
  }))
108168
108199
  );
108200
+ var defaultProxyAlias = modelAliases.find((a) => a.slug === "moonshotai/kimi-k2");
108201
+ if (!defaultProxyAlias?.openRouterResolve) {
108202
+ throw new Error("DEFAULT_PROXY_MODEL: moonshotai/kimi-k2 missing openRouterResolve");
108203
+ }
108204
+ var DEFAULT_PROXY_MODEL = defaultProxyAlias.openRouterResolve;
108169
108205
  var MAX_FALLBACK_DEPTH = 10;
108170
108206
  function resolveDisplayAlias(slug2) {
108171
108207
  let current = slug2;
@@ -108184,9 +108220,13 @@ function resolveCliModel(slug2) {
108184
108220
  return resolveDisplayAlias(slug2)?.resolve;
108185
108221
  }
108186
108222
  var BEDROCK_MODEL_ID_ENV = "BEDROCK_MODEL_ID";
108223
+ var VERTEX_MODEL_ID_ENV = "VERTEX_MODEL_ID";
108187
108224
  function isBedrockAnthropicId(bedrockModelId) {
108188
108225
  return bedrockModelId.toLowerCase().split(/[./:]/).includes("anthropic");
108189
108226
  }
108227
+ function isVertexAnthropicId(vertexModelId) {
108228
+ return /^claude-/i.test(vertexModelId.trim());
108229
+ }
108190
108230
 
108191
108231
  // utils/buildPullfrogFooter.ts
108192
108232
  var PULLFROG_DIVIDER = "<!-- PULLFROG_DIVIDER_DO_NOT_REMOVE_PLZ -->";
@@ -142469,7 +142509,7 @@ var import_semver = __toESM(require_semver2(), 1);
142469
142509
  // package.json
142470
142510
  var package_default = {
142471
142511
  name: "pullfrog",
142472
- version: "0.1.10",
142512
+ version: "0.1.12",
142473
142513
  type: "module",
142474
142514
  bin: {
142475
142515
  pullfrog: "dist/cli.mjs",
@@ -142667,8 +142707,8 @@ function closeBrowserDaemon(toolState) {
142667
142707
 
142668
142708
  // mcp/checkout.ts
142669
142709
  import { createHash as createHash2 } from "node:crypto";
142670
- import { statSync, unlinkSync as unlinkSync2, writeFileSync } from "node:fs";
142671
- import { join as join3 } from "node:path";
142710
+ import { statSync, unlinkSync as unlinkSync3, writeFileSync } from "node:fs";
142711
+ import { join as join4 } from "node:path";
142672
142712
 
142673
142713
  // utils/diffCoverage.ts
142674
142714
  import { isAbsolute, normalize as normalize2, resolve } from "node:path";
@@ -144180,6 +144220,183 @@ async function reportReviewNodeId(ctx, params) {
144180
144220
  await patchWorkflowRunFields(ctx, { reviewNodeId: params.nodeId });
144181
144221
  }
144182
144222
 
144223
+ // utils/setup.ts
144224
+ import { execFileSync as execFileSync3, execSync as execSync2 } from "node:child_process";
144225
+ import { mkdtempSync, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
144226
+ import { tmpdir } from "node:os";
144227
+ import { join as join3 } from "node:path";
144228
+ function createTempDirectory() {
144229
+ const sharedTempDir = mkdtempSync(join3(tmpdir(), "pullfrog-"));
144230
+ process.env.PULLFROG_TEMP_DIR = sharedTempDir;
144231
+ log.info(`\xBB created temp dir at ${sharedTempDir}`);
144232
+ return sharedTempDir;
144233
+ }
144234
+ function wipeRunnerLeakSurface() {
144235
+ const runnerTemp = process.env.RUNNER_TEMP;
144236
+ if (!runnerTemp) return;
144237
+ const preserve = /* @__PURE__ */ new Set();
144238
+ for (const envVar of [
144239
+ "GITHUB_OUTPUT",
144240
+ "GITHUB_ENV",
144241
+ "GITHUB_PATH",
144242
+ "GITHUB_STATE",
144243
+ "GITHUB_STEP_SUMMARY"
144244
+ ]) {
144245
+ const path3 = process.env[envVar];
144246
+ if (!path3) continue;
144247
+ try {
144248
+ preserve.add(realpathSync2(path3));
144249
+ } catch {
144250
+ preserve.add(path3);
144251
+ }
144252
+ }
144253
+ const wiped = [];
144254
+ const tryUnlink = (path3) => {
144255
+ let resolved = path3;
144256
+ try {
144257
+ resolved = realpathSync2(path3);
144258
+ } catch {
144259
+ }
144260
+ if (preserve.has(resolved) || preserve.has(path3)) return;
144261
+ try {
144262
+ unlinkSync2(path3);
144263
+ wiped.push(path3);
144264
+ } catch {
144265
+ }
144266
+ };
144267
+ const listDir = (dir) => {
144268
+ try {
144269
+ return readdirSync(dir);
144270
+ } catch {
144271
+ return [];
144272
+ }
144273
+ };
144274
+ const fileCommandsDir = join3(runnerTemp, "_runner_file_commands");
144275
+ for (const entry of listDir(fileCommandsDir)) {
144276
+ tryUnlink(join3(fileCommandsDir, entry));
144277
+ }
144278
+ for (const entry of listDir(runnerTemp)) {
144279
+ if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
144280
+ tryUnlink(join3(runnerTemp, entry));
144281
+ }
144282
+ }
144283
+ if (wiped.length > 0) {
144284
+ log.info(`\xBB wiped ${wiped.length} leak-surface file(s) from $RUNNER_TEMP`);
144285
+ log.debug(`\xBB wiped paths: ${wiped.join(", ")}`);
144286
+ }
144287
+ }
144288
+ function envScopedToRepo() {
144289
+ const scoped = { ...process.env };
144290
+ for (const key of Object.keys(scoped)) {
144291
+ if (key.startsWith("GIT_")) delete scoped[key];
144292
+ }
144293
+ return scoped;
144294
+ }
144295
+ function removeIncludeIfEntries(repoDir) {
144296
+ const env2 = envScopedToRepo();
144297
+ let configOutput;
144298
+ try {
144299
+ configOutput = execSync2("git config --local --get-regexp -z ^includeif\\.", {
144300
+ cwd: repoDir,
144301
+ encoding: "utf-8",
144302
+ stdio: "pipe",
144303
+ env: env2
144304
+ });
144305
+ } catch {
144306
+ log.debug("\xBB no includeIf credential entries to remove");
144307
+ return;
144308
+ }
144309
+ const seen = /* @__PURE__ */ new Set();
144310
+ for (const entry of configOutput.split("\0")) {
144311
+ if (!entry) continue;
144312
+ const nl = entry.indexOf("\n");
144313
+ const key = nl === -1 ? entry : entry.slice(0, nl);
144314
+ if (!key || seen.has(key)) continue;
144315
+ seen.add(key);
144316
+ try {
144317
+ execFileSync3("git", ["config", "--local", "--unset-all", key], {
144318
+ cwd: repoDir,
144319
+ stdio: "pipe",
144320
+ env: env2
144321
+ });
144322
+ } catch (error49) {
144323
+ log.debug(
144324
+ `\xBB failed to unset ${key}: ${error49 instanceof Error ? error49.message : String(error49)}`
144325
+ );
144326
+ }
144327
+ }
144328
+ if (seen.size > 0)
144329
+ log.info(
144330
+ `\xBB removed ${seen.size} includeIf credential ${seen.size === 1 ? "entry" : "entries"}`
144331
+ );
144332
+ }
144333
+ async function setupGit(params) {
144334
+ const repoDir = process.cwd();
144335
+ log.info("\xBB setting up git configuration...");
144336
+ try {
144337
+ let currentEmail = "";
144338
+ try {
144339
+ currentEmail = execSync2("git config user.email", {
144340
+ cwd: repoDir,
144341
+ stdio: "pipe",
144342
+ encoding: "utf-8"
144343
+ }).trim();
144344
+ } catch {
144345
+ }
144346
+ const shouldSetDefaults = !currentEmail || currentEmail === "github-actions[bot]@users.noreply.github.com";
144347
+ if (shouldSetDefaults) {
144348
+ execSync2('git config --local user.email "226033991+pullfrog[bot]@users.noreply.github.com"', {
144349
+ cwd: repoDir,
144350
+ stdio: "pipe"
144351
+ });
144352
+ execSync2('git config --local user.name "pullfrog[bot]"', {
144353
+ cwd: repoDir,
144354
+ stdio: "pipe"
144355
+ });
144356
+ log.debug("\xBB git user configured (using defaults)");
144357
+ } else {
144358
+ log.debug(`\xBB git user already configured (${currentEmail}), skipping`);
144359
+ }
144360
+ if (params.shell === "disabled") {
144361
+ execSync2("git config --local core.hooksPath /dev/null", {
144362
+ cwd: repoDir,
144363
+ stdio: "pipe"
144364
+ });
144365
+ log.debug("\xBB git hooks disabled (shell=disabled)");
144366
+ }
144367
+ } catch (error49) {
144368
+ log.info(`Failed to set git config: ${error49 instanceof Error ? error49.message : String(error49)}`);
144369
+ }
144370
+ try {
144371
+ execSync2("git config --local --unset-all http.https://github.com/.extraheader", {
144372
+ cwd: repoDir,
144373
+ stdio: "pipe"
144374
+ });
144375
+ log.info("\xBB removed existing authentication headers");
144376
+ } catch {
144377
+ log.debug("\xBB no existing authentication headers to remove");
144378
+ }
144379
+ removeIncludeIfEntries(repoDir);
144380
+ const originUrl = `https://github.com/${params.owner}/${params.name}.git`;
144381
+ $("git", ["remote", "set-url", "origin", originUrl], { cwd: repoDir });
144382
+ params.toolState.pushUrl = originUrl;
144383
+ $("git", ["config", "--local", "credential.helper", ""], { cwd: repoDir });
144384
+ params.toolState.initialHead = captureInitialHead(repoDir);
144385
+ log.info("\xBB git authentication configured");
144386
+ }
144387
+ function captureInitialHead(repoDir) {
144388
+ try {
144389
+ const name = $("git", ["symbolic-ref", "--short", "HEAD"], {
144390
+ cwd: repoDir,
144391
+ log: false
144392
+ }).trim();
144393
+ if (name) return { kind: "branch", name };
144394
+ } catch {
144395
+ }
144396
+ const sha = $("git", ["rev-parse", "HEAD"], { cwd: repoDir, log: false }).trim();
144397
+ return { kind: "detached", sha };
144398
+ }
144399
+
144183
144400
  // mcp/checkout.ts
144184
144401
  function formatFilesWithLineNumbers(files) {
144185
144402
  const output = [];
@@ -144354,7 +144571,7 @@ function cleanupStaleGitLocks() {
144354
144571
  }
144355
144572
  if (now - mtimeMs < STALE_LOCK_AGE_MS) continue;
144356
144573
  try {
144357
- unlinkSync2(relPath);
144574
+ unlinkSync3(relPath);
144358
144575
  log.warning(`\xBB removed stale ${relPath} from prior run`);
144359
144576
  } catch (e) {
144360
144577
  log.debug(
@@ -144513,6 +144730,15 @@ async function checkoutPrBranch(pr, params) {
144513
144730
  return { hookWarning: postCheckoutHook.warning };
144514
144731
  }
144515
144732
  var inFlightCheckouts = /* @__PURE__ */ new Map();
144733
+ function headsEqual(a, b) {
144734
+ if (a.kind === "branch" && b.kind === "branch") return a.name === b.name;
144735
+ if (a.kind === "detached" && b.kind === "detached") return a.sha === b.sha;
144736
+ return false;
144737
+ }
144738
+ function describeHead(h) {
144739
+ if (h.kind === "branch") return `branch \`${h.name}\``;
144740
+ return `detached HEAD \`${h.sha}\``;
144741
+ }
144516
144742
  function CheckoutPrTool(ctx) {
144517
144743
  const runCheckout = async (pull_number) => {
144518
144744
  const prResponse = await ctx.octokit.rest.pulls.get({
@@ -144559,7 +144785,7 @@ function CheckoutPrTool(ctx) {
144559
144785
  headSha: ctx.toolState.checkoutSha
144560
144786
  });
144561
144787
  if (incremental) {
144562
- incrementalDiffPath = join3(
144788
+ incrementalDiffPath = join4(
144563
144789
  tempDir,
144564
144790
  `pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
144565
144791
  );
@@ -144573,7 +144799,7 @@ function CheckoutPrTool(ctx) {
144573
144799
  const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
144574
144800
  log.debug(`formatted diff preview (first 100 lines):
144575
144801
  ${diffPreview}`);
144576
- const diffPath = join3(tempDir, `pr-${pull_number}-${headShort}.diff`);
144802
+ const diffPath = join4(tempDir, `pr-${pull_number}-${headShort}.diff`);
144577
144803
  writeFileSync(diffPath, formatResult.content);
144578
144804
  log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
144579
144805
  ctx.toolState.diffCoverage = createDiffCoverageState({
@@ -144640,7 +144866,8 @@ ${diffPreview}`);
144640
144866
  };
144641
144867
  return tool({
144642
144868
  name: "checkout_pr",
144643
- description: "Checkout a pull request branch locally. This fetches the PR branch and sets up push configuration for fork PRs. Returns diffPath pointing to the formatted diff file. Example: `checkout_pr({ pull_number: 1234 })`. Transient fetch timeouts are common \u2014 retry the same call up to a few times before treating the failure as terminal. If the error mentions `.git/shallow.lock: File exists` or `.git/index.lock: File exists`, that's a stale lock from a prior timed-out fetch \u2014 remove it via the shell tool (`rm -f .git/shallow.lock .git/index.lock`) and retry.",
144869
+ timeoutMs: 6e5,
144870
+ description: "Checkout a pull request branch locally. This fetches the PR branch and sets up push configuration for fork PRs. Returns diffPath pointing to the formatted diff file. Example: `checkout_pr({ pull_number: 1234 })`. Large repos can take several minutes \u2014 wait for the call to finish; do not treat a slow response as failure. If you see `MCP error -32001: Request timed out`, retry the same call without touching git lock files first \u2014 that error is a client-side abort. If the retry then reports `.git/shallow.lock: File exists` or `.git/index.lock: File exists`, remove those lock files via the shell tool and retry again.",
144644
144871
  parameters: CheckoutPr,
144645
144872
  execute: execute(async ({ pull_number }) => {
144646
144873
  const inFlight = inFlightCheckouts.get(pull_number);
@@ -144648,13 +144875,23 @@ ${diffPreview}`);
144648
144875
  log.info(`\xBB checkout_pr({pull_number:${pull_number}}) already in flight \u2014 sharing result`);
144649
144876
  return inFlight;
144650
144877
  }
144651
- const currentBranch = $("git", ["rev-parse", "--abbrev-ref", "HEAD"], { log: false }).trim();
144652
- if (currentBranch !== `pr-${pull_number}`) {
144653
- const dirty = $("git", ["status", "--porcelain"], { log: false }).trim();
144654
- if (dirty) {
144655
- throw new Error(
144656
- `cannot checkout PR #${pull_number} while the working tree has uncommitted changes. commit, push, or discard them before switching. dirty paths:
144878
+ const dirty = $("git", ["status", "--porcelain"], { log: false }).trim();
144879
+ if (dirty) {
144880
+ throw new Error(
144881
+ `cannot checkout PR #${pull_number} while the working tree has uncommitted changes. commit (then push if needed), or discard with \`git restore --staged --worktree .\` / \`git clean -fd\` before retrying. this refusal is unconditional \u2014 even re-checking-out the PR you're already on is refused, because shared-working-tree subagents make carry-forward edits unsafe. dirty paths:
144657
144882
  ${dirty}`
144883
+ );
144884
+ }
144885
+ const initialHead = ctx.toolState.initialHead;
144886
+ if (initialHead) {
144887
+ const currentHead = captureInitialHead(process.cwd());
144888
+ const targetBranch = `pr-${pull_number}`;
144889
+ const onTarget = currentHead.kind === "branch" && currentHead.name === targetBranch;
144890
+ const onInitial = headsEqual(currentHead, initialHead);
144891
+ if (!onTarget && !onInitial) {
144892
+ const recoverCmd = initialHead.kind === "branch" ? `git checkout ${initialHead.name}` : `git checkout ${initialHead.sha}`;
144893
+ throw new Error(
144894
+ `cannot checkout PR #${pull_number} from ${describeHead(currentHead)}. the only sanctioned HEAD positions for checkout_pr are the run-entry HEAD (${describeHead(initialHead)}) or the target PR's branch (\`${targetBranch}\`, idempotent re-checkout). recover with \`${recoverCmd}\` first \u2014 if that would carry uncommitted work along, commit or discard it (\`git restore --staged --worktree .\` / \`git clean -fd\`) before switching. routing around this via the \`git\` tool's \`checkout\`/\`switch\` subcommands is not sanctioned: this guard exists to prevent the shared-working-tree cross-PR clobber pattern from the zed-industries/cloud (2026-05-18) incident.`
144658
144895
  );
144659
144896
  }
144660
144897
  }
@@ -144671,7 +144908,7 @@ ${dirty}`
144671
144908
 
144672
144909
  // mcp/checkSuite.ts
144673
144910
  import { mkdirSync, writeFileSync as writeFileSync2 } from "node:fs";
144674
- import { join as join4 } from "node:path";
144911
+ import { join as join5 } from "node:path";
144675
144912
  var GetCheckSuiteLogs = type({
144676
144913
  check_suite_id: type.number.describe("the id from check_suite.id")
144677
144914
  });
@@ -144767,7 +145004,7 @@ function GetCheckSuiteLogsTool(ctx) {
144767
145004
  if (!tempDir) {
144768
145005
  throw new Error("PULLFROG_TEMP_DIR not set");
144769
145006
  }
144770
- const logsDir = join4(tempDir, "ci-logs");
145007
+ const logsDir = join5(tempDir, "ci-logs");
144771
145008
  mkdirSync(logsDir, { recursive: true });
144772
145009
  const jobResults = [];
144773
145010
  for (const run of failedRuns) {
@@ -144794,7 +145031,7 @@ function GetCheckSuiteLogsTool(ctx) {
144794
145031
  );
144795
145032
  }
144796
145033
  const logsText = await logsResult.text();
144797
- const logPath = join4(logsDir, `job-${job.id}.log`);
145034
+ const logPath = join5(logsDir, `job-${job.id}.log`);
144798
145035
  writeFileSync2(logPath, logsText);
144799
145036
  const analysis = analyzeLog(logsText, 80);
144800
145037
  const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
@@ -144844,7 +145081,7 @@ function GetCheckSuiteLogsTool(ctx) {
144844
145081
 
144845
145082
  // mcp/commitInfo.ts
144846
145083
  import { writeFileSync as writeFileSync3 } from "node:fs";
144847
- import { join as join5 } from "node:path";
145084
+ import { join as join6 } from "node:path";
144848
145085
  var CommitInfo = type({
144849
145086
  sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
144850
145087
  });
@@ -144868,7 +145105,7 @@ function CommitInfoTool(ctx) {
144868
145105
  "PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
144869
145106
  );
144870
145107
  }
144871
- const diffFile = join5(tempDir, `commit-${sha.slice(0, 7)}.diff`);
145108
+ const diffFile = join6(tempDir, `commit-${sha.slice(0, 7)}.diff`);
144872
145109
  writeFileSync3(diffFile, formatResult.content);
144873
145110
  log.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
144874
145111
  return {
@@ -145326,7 +145563,7 @@ function PullRequestInfoTool(ctx) {
145326
145563
 
145327
145564
  // mcp/reviewComments.ts
145328
145565
  import { writeFileSync as writeFileSync4 } from "node:fs";
145329
- import { join as join6 } from "node:path";
145566
+ import { join as join7 } from "node:path";
145330
145567
  var REVIEW_THREADS_QUERY = `
145331
145568
  query ($owner: String!, $name: String!, $prNumber: Int!) {
145332
145569
  repository(owner: $owner, name: $name) {
@@ -145718,7 +145955,7 @@ function GetReviewCommentsTool(ctx) {
145718
145955
  throw new Error("PULLFROG_TEMP_DIR not set");
145719
145956
  }
145720
145957
  const filename = `review-${params.review_id}-threads.md`;
145721
- const commentsPath = join6(tempDir, filename);
145958
+ const commentsPath = join7(tempDir, filename);
145722
145959
  writeFileSync4(commentsPath, formatted.content);
145723
145960
  log.debug(`wrote ${threadBlocks.length} threads to ${commentsPath}`);
145724
145961
  return {
@@ -145947,7 +146184,7 @@ import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
145947
146184
  import { randomUUID as randomUUID2 } from "node:crypto";
145948
146185
  import { closeSync, openSync, writeFileSync as writeFileSync5 } from "node:fs";
145949
146186
  import { userInfo } from "node:os";
145950
- import { join as join7 } from "node:path";
146187
+ import { join as join8 } from "node:path";
145951
146188
  import { setTimeout as sleep2 } from "node:timers/promises";
145952
146189
  var ShellParams = type({
145953
146190
  command: "string",
@@ -146080,7 +146317,7 @@ function getTempDir() {
146080
146317
  var MAX_OUTPUT_CHARS = 5e3;
146081
146318
  function capOutput(output) {
146082
146319
  if (output.length <= MAX_OUTPUT_CHARS) return output;
146083
- const fullPath = join7(getTempDir(), `shell-${randomUUID2().slice(0, 8)}.log`);
146320
+ const fullPath = join8(getTempDir(), `shell-${randomUUID2().slice(0, 8)}.log`);
146084
146321
  writeFileSync5(fullPath, output);
146085
146322
  const elided = output.length - MAX_OUTPUT_CHARS;
146086
146323
  return `... [${elided} chars truncated; full output saved to ${fullPath}] ...
@@ -146095,6 +146332,7 @@ function isGitCommand(command) {
146095
146332
  function ShellTool(ctx) {
146096
146333
  return tool({
146097
146334
  name: "shell",
146335
+ timeoutMs: 12e4,
146098
146336
  description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
146099
146337
 
146100
146338
  Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
@@ -146134,8 +146372,8 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
146134
146372
  if (params.background) {
146135
146373
  const tempDir = getTempDir();
146136
146374
  const handle = `bg-${randomUUID2().slice(0, 8)}`;
146137
- const outputPath = join7(tempDir, `${handle}.log`);
146138
- const pidPath = join7(tempDir, `${handle}.pid`);
146375
+ const outputPath = join8(tempDir, `${handle}.log`);
146376
+ const pidPath = join8(tempDir, `${handle}.pid`);
146139
146377
  const logFd = openSync(outputPath, "a");
146140
146378
  let proc2;
146141
146379
  try {
@@ -146475,6 +146713,8 @@ var REVIEWER_AGENT_NAME = "reviewfrog";
146475
146713
  var REVIEWER_SYSTEM_PROMPT = `You are a read-only review subagent. Your role is to find flaws in code or artifacts provided by the orchestrator and report findings \u2014 never to modify state.
146476
146714
 
146477
146715
  HARD CONSTRAINTS (non-negotiable, regardless of orchestrator instructions):
146716
+ - Your FIRST action MUST be \`git diff origin/<base>\` (single-rev form, no \`HEAD\`). This captures committed + staged + unstaged work in one command \u2014 Build-mode self-review runs BEFORE the commit, so the work to review lives in the working tree, not in committed history. Do not run any other diff command first. Do NOT call \`checkout_pr\`, do NOT fetch alternative refs, do NOT list branches or all-refs looking for the work, do NOT run \`gh pr list\`. The orchestrator's dispatch names the base branch; the diff is the source of truth for scope.
146717
+ - If \`git diff origin/<base>\` returns empty AND the orchestrator's dispatch claims there are changes to review, the most likely cause is a pre-commit Build-mode self-review: the orchestrator dispatched you before committing. Reply EXACTLY: \`no changes detected \u2014 likely pre-commit Build self-review; orchestrator should commit then re-dispatch\` and stop. Do NOT guess PR numbers (e.g. by extrapolating from \`git log\` output), do NOT check out other PRs, do NOT fetch from forks. The empty diff is the diagnosis \u2014 surface it; do not work around it.
146478
146718
  - Read-only tools only. Do NOT write or edit files. Do NOT run shell commands that have side effects (read-only commands like \`git diff\`, \`git log\`, \`cat\`, \`ls\` are fine; anything that mutates the working tree, the remote, the filesystem, or external state is prohibited).
146479
146719
  - Do NOT call any state-changing MCP tool. State-changing means: posts a comment, pushes a branch, creates/updates a PR or issue, changes labels, resolves review threads, persists learnings, sets workflow output, installs dependencies, uploads files, kills processes, etc. Read-only MCP queries (\`get_*\`, \`list_*\`, log inspection, diff retrieval) are fine.
146480
146720
  - Do NOT spawn further subagents. You are a leaf reviewer; recursive dispatch pre-aggregates findings through an intermediate model and defeats the design.
@@ -146665,7 +146905,25 @@ function computeModes(agentId) {
146665
146905
 
146666
146906
  Otherwise delegate the \`${REVIEWER_AGENT_NAME}\` subagent to review your diff with fresh eyes against YOUR TASK. The subagent's baked-in system prompt enforces a non-mutative + non-recursive contract: read-only file/search/web tools and read-only MCP queries only; no writes, shell side effects, state-changing MCP calls, or nested subagent dispatch. Enforcement is prose-only \u2014 restate the constraint in your dispatch instructions and do not relax it.
146667
146907
 
146668
- Provide the subagent with YOUR TASK, the output of \`git diff origin/<base-branch>\` (single-rev form, no \`HEAD\` \u2014 this compares the working tree against the remote base and captures committed + staged + unstaged work; \`main...HEAD\` and \`--cached\` both miss the uncommitted edits Build self-review runs on, since self-review happens BEFORE the commit), and a tight summary (not raw output) of any lint/typecheck/test failures you fixed during build \u2014 what broke, root cause, the fix \u2014 so it can check that fixes addressed root causes rather than suppressed symptoms; say "no build-phase failures" if the build path was clean. Instruct it to flag bugs, logic errors, missing edge cases, gaps between request and diff, and unintended changes.
146908
+ Compose your \`${REVIEWER_AGENT_NAME}\` dispatch prompt using this template verbatim, substituting the \`<...>\` placeholders. The preamble aligns the orchestrator side of the dispatch contract with the reviewer's baked-in system prompt \u2014 both ends say the same thing about where the work lives and what to do on an empty diff.
146909
+
146910
+ \`\`\`
146911
+ ## What you're reviewing
146912
+ This is a PRE-COMMIT Build-mode self-review. The work to review lives in the working tree (uncommitted), NOT in committed history.
146913
+
146914
+ Branch: <branch> (off <base>)
146915
+ Canonical diff command: git diff origin/<base>
146916
+
146917
+ If that command returns empty, treat it as "no changes \u2014 nothing to review" and stop per your system prompt. Do not search for the work elsewhere.
146918
+
146919
+ ## Your task
146920
+ <YOUR TASK content>
146921
+
146922
+ ## Build-phase failures
146923
+ <tight summary \u2014 what broke, root cause, the fix \u2014 or "no build-phase failures">
146924
+ \`\`\`
146925
+
146926
+ Follow the template with the diff content (\`git diff origin/<base-branch>\`, single-rev form \u2014 \`main...HEAD\` and \`--cached\` both miss the uncommitted edits self-review runs on) and your task brief. Instruct the subagent to flag bugs, logic errors, missing edge cases, gaps between request and diff, and unintended changes.
146669
146927
 
146670
146928
  Delegation + research discipline (distilled from \`/anneal\` canonical \u2014 these are codified learnings from many review rounds, not theoretical best practices):
146671
146929
  - Do NOT summarize what you implemented \u2014 that biases the subagent toward validating the shape of your solution rather than questioning it.
@@ -147064,21 +147322,21 @@ function initToolState(params) {
147064
147322
  }
147065
147323
 
147066
147324
  // agents/claude.ts
147067
- import { execFileSync as execFileSync3 } from "node:child_process";
147068
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "node:fs";
147069
- import { join as join10 } from "node:path";
147325
+ import { execFileSync as execFileSync4 } from "node:child_process";
147326
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "node:fs";
147327
+ import { join as join12 } from "node:path";
147070
147328
  import { performance as performance6 } from "node:perf_hooks";
147071
147329
 
147072
147330
  // utils/install.ts
147073
147331
  import { spawnSync as spawnSync4 } from "node:child_process";
147074
147332
  import { chmodSync, createWriteStream, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "node:fs";
147075
- import { join as join8 } from "node:path";
147333
+ import { join as join9 } from "node:path";
147076
147334
  import { pipeline } from "node:stream/promises";
147077
147335
  async function installFromNpmTarball(params) {
147078
147336
  const tempDir = process.env.PULLFROG_TEMP_DIR;
147079
147337
  if (!tempDir) throw new Error("PULLFROG_TEMP_DIR is not set");
147080
- const extractedDir = join8(tempDir, "package");
147081
- const cliPath = join8(extractedDir, params.executablePath);
147338
+ const extractedDir = join9(tempDir, "package");
147339
+ const cliPath = join9(extractedDir, params.executablePath);
147082
147340
  if (existsSync5(cliPath)) {
147083
147341
  log.debug(`\xBB using cached binary at ${cliPath}`);
147084
147342
  return cliPath;
@@ -147103,7 +147361,7 @@ async function installFromNpmTarball(params) {
147103
147361
  }
147104
147362
  }
147105
147363
  log.debug(`\xBB installing ${params.packageName}@${resolvedVersion}...`);
147106
- const tarballPath = join8(tempDir, "package.tgz");
147364
+ const tarballPath = join9(tempDir, "package.tgz");
147107
147365
  const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org";
147108
147366
  let tarballUrl;
147109
147367
  if (params.packageName.startsWith("@")) {
@@ -147242,16 +147500,16 @@ function isRouterKeylimitExhaustedError(text) {
147242
147500
  // utils/skills.ts
147243
147501
  import { spawnSync as spawnSync5 } from "node:child_process";
147244
147502
  import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "node:fs";
147245
- import { tmpdir } from "node:os";
147246
- import { dirname as dirname2, join as join9 } from "node:path";
147503
+ import { tmpdir as tmpdir2 } from "node:os";
147504
+ import { dirname as dirname2, join as join10 } from "node:path";
147247
147505
  import { fileURLToPath } from "node:url";
147248
147506
  var skillsVersion = getDevDependencyVersion("skills");
147249
147507
  var BUNDLED_SKILL_NAMES = ["git-archaeology"];
147250
147508
  function resolveSkillPath(name) {
147251
147509
  const here = dirname2(fileURLToPath(import.meta.url));
147252
147510
  const candidates = [
147253
- join9(here, "..", "skills", name, "SKILL.md"),
147254
- join9(here, "skills", name, "SKILL.md")
147511
+ join10(here, "..", "skills", name, "SKILL.md"),
147512
+ join10(here, "skills", name, "SKILL.md")
147255
147513
  ];
147256
147514
  for (const candidate of candidates) {
147257
147515
  if (existsSync6(candidate)) return candidate;
@@ -147263,9 +147521,9 @@ function installBundledSkills(params) {
147263
147521
  for (const name of BUNDLED_SKILL_NAMES) {
147264
147522
  const content = readFileSync4(resolveSkillPath(name), "utf8");
147265
147523
  for (const targetDir of SKILL_TARGET_DIRS) {
147266
- const skillDir = join9(params.home, targetDir, name);
147524
+ const skillDir = join10(params.home, targetDir, name);
147267
147525
  mkdirSync3(skillDir, { recursive: true });
147268
- writeFileSync6(join9(skillDir, "SKILL.md"), content);
147526
+ writeFileSync6(join10(skillDir, "SKILL.md"), content);
147269
147527
  }
147270
147528
  }
147271
147529
  log.success(`installed bundled skills: ${BUNDLED_SKILL_NAMES.join(", ")}`);
@@ -147286,7 +147544,7 @@ function addSkill(params) {
147286
147544
  "-y"
147287
147545
  ],
147288
147546
  {
147289
- cwd: tmpdir(),
147547
+ cwd: tmpdir2(),
147290
147548
  env: { ...process.env, ...params.env },
147291
147549
  stdio: "pipe",
147292
147550
  timeout: 3e4
@@ -147367,6 +147625,71 @@ var ThinkingTimer = class {
147367
147625
  }
147368
147626
  };
147369
147627
 
147628
+ // utils/vertex.ts
147629
+ import { randomUUID as randomUUID3 } from "node:crypto";
147630
+ import { mkdirSync as mkdirSync4, rmSync, writeFileSync as writeFileSync7 } from "node:fs";
147631
+ import { homedir } from "node:os";
147632
+ import { join as join11 } from "node:path";
147633
+ var VERTEX_SERVICE_ACCOUNT_JSON_ENV = "VERTEX_SERVICE_ACCOUNT_JSON";
147634
+ var GOOGLE_APPLICATION_CREDENTIALS_ENV = "GOOGLE_APPLICATION_CREDENTIALS";
147635
+ var GOOGLE_CLOUD_PROJECT_ENV = "GOOGLE_CLOUD_PROJECT";
147636
+ var VERTEX_LOCATION_ENV = "VERTEX_LOCATION";
147637
+ function hasEnvVar(name) {
147638
+ const value2 = process.env[name];
147639
+ return typeof value2 === "string" && value2.length > 0;
147640
+ }
147641
+ function isVertexRoute(model) {
147642
+ const vertexId = process.env[VERTEX_MODEL_ID_ENV]?.trim();
147643
+ return model !== void 0 && vertexId !== void 0 && vertexId === model;
147644
+ }
147645
+ function readProjectIdFromVertexServiceAccountJson() {
147646
+ const blob = process.env[VERTEX_SERVICE_ACCOUNT_JSON_ENV];
147647
+ if (!blob) return void 0;
147648
+ try {
147649
+ const parsed2 = JSON.parse(blob);
147650
+ if (!parsed2 || typeof parsed2 !== "object" || !("project_id" in parsed2)) {
147651
+ return void 0;
147652
+ }
147653
+ const projectId = parsed2.project_id;
147654
+ return typeof projectId === "string" && projectId.length > 0 ? projectId : void 0;
147655
+ } catch {
147656
+ return void 0;
147657
+ }
147658
+ }
147659
+ function createSecretDir() {
147660
+ const base = process.env.PULLFROG_SECRET_HOME || process.env.HOME || homedir();
147661
+ const secretDir = join11(base, ".pullfrog", "secrets", randomUUID3());
147662
+ mkdirSync4(secretDir, { recursive: true, mode: 448 });
147663
+ return secretDir;
147664
+ }
147665
+ function materializeVertexCredentials(params) {
147666
+ if (!isVertexRoute(params.model)) return void 0;
147667
+ const blob = process.env[VERTEX_SERVICE_ACCOUNT_JSON_ENV];
147668
+ if (!blob) return void 0;
147669
+ const secretDir = createSecretDir();
147670
+ const credentialsPath = join11(secretDir, "vertex-sa.json");
147671
+ writeFileSync7(credentialsPath, blob, { mode: 384 });
147672
+ process.env[GOOGLE_APPLICATION_CREDENTIALS_ENV] = credentialsPath;
147673
+ const projectId = readProjectIdFromVertexServiceAccountJson();
147674
+ if (projectId && !hasEnvVar(GOOGLE_CLOUD_PROJECT_ENV)) {
147675
+ process.env[GOOGLE_CLOUD_PROJECT_ENV] = projectId;
147676
+ }
147677
+ return { credentialsPath, secretDir };
147678
+ }
147679
+ function cleanupVertexCredentials(credentials) {
147680
+ if (!credentials) return;
147681
+ rmSync(credentials.secretDir, { recursive: true, force: true });
147682
+ delete process.env[GOOGLE_APPLICATION_CREDENTIALS_ENV];
147683
+ }
147684
+ function applyClaudeVertexEnv(env2) {
147685
+ env2.CLAUDE_CODE_USE_VERTEX = "1";
147686
+ env2.ANTHROPIC_VERTEX_PROJECT_ID ??= env2[GOOGLE_CLOUD_PROJECT_ENV];
147687
+ env2.CLOUD_ML_REGION ??= env2[VERTEX_LOCATION_ENV];
147688
+ }
147689
+ function resolveVertexOpenCodeModel(model) {
147690
+ return isVertexRoute(model) && model ? `google-vertex/${model}` : void 0;
147691
+ }
147692
+
147370
147693
  // agents/postRun.ts
147371
147694
  import { readFile } from "node:fs/promises";
147372
147695
  function getUnsubmittedReview(toolState) {
@@ -147670,10 +147993,10 @@ async function installClaudeCli() {
147670
147993
  });
147671
147994
  }
147672
147995
  function writeMcpConfig(ctx) {
147673
- const configDir = join10(ctx.tmpdir, ".claude");
147674
- mkdirSync4(configDir, { recursive: true });
147675
- const configPath = join10(configDir, "mcp.json");
147676
- writeFileSync7(
147996
+ const configDir = join12(ctx.tmpdir, ".claude");
147997
+ mkdirSync5(configDir, { recursive: true });
147998
+ const configPath = join12(configDir, "mcp.json");
147999
+ writeFileSync8(
147677
148000
  configPath,
147678
148001
  JSON.stringify({
147679
148002
  mcpServers: {
@@ -148055,37 +148378,51 @@ ${stderrContext}`
148055
148378
  var MANAGED_SETTINGS_DIR = "/etc/claude-code";
148056
148379
  var MANAGED_SETTINGS_PATH = `${MANAGED_SETTINGS_DIR}/managed-settings.json`;
148057
148380
  var CODEX_AUTH_DENY_PATH = "~/.local/share/opencode/auth.json";
148058
- var managedSettings = {
148059
- allowManagedPermissionRulesOnly: true,
148060
- allowManagedHooksOnly: true,
148061
- permissions: {
148062
- deny: [
148063
- "Read(//proc/**)",
148064
- "Read(//sys/**)",
148065
- "Grep(//proc/**)",
148066
- "Grep(//sys/**)",
148067
- "Edit(//proc/**)",
148068
- "Edit(//sys/**)",
148069
- "Glob(//proc/**)",
148070
- "Glob(//sys/**)",
148071
- `Read(${CODEX_AUTH_DENY_PATH})`,
148072
- `Grep(${CODEX_AUTH_DENY_PATH})`,
148073
- `Edit(${CODEX_AUTH_DENY_PATH})`,
148074
- `Glob(${CODEX_AUTH_DENY_PATH})`
148075
- ]
148076
- },
148077
- sandbox: {
148078
- filesystem: {
148079
- denyRead: ["/proc", "/sys", CODEX_AUTH_DENY_PATH]
148381
+ function buildManagedSettings(ctx) {
148382
+ const secretDenyPaths = ctx.secretDenyPaths ?? [];
148383
+ const toolDeny = secretDenyPaths.flatMap((path3) => [
148384
+ `Read(${path3}/**)`,
148385
+ `Read(/${path3}/**)`,
148386
+ `Grep(${path3}/**)`,
148387
+ `Grep(/${path3}/**)`,
148388
+ `Edit(${path3}/**)`,
148389
+ `Edit(/${path3}/**)`,
148390
+ `Glob(${path3}/**)`,
148391
+ `Glob(/${path3}/**)`
148392
+ ]);
148393
+ return {
148394
+ allowManagedPermissionRulesOnly: true,
148395
+ allowManagedHooksOnly: true,
148396
+ permissions: {
148397
+ deny: [
148398
+ "Read(//proc/**)",
148399
+ "Read(//sys/**)",
148400
+ "Grep(//proc/**)",
148401
+ "Grep(//sys/**)",
148402
+ "Edit(//proc/**)",
148403
+ "Edit(//sys/**)",
148404
+ "Glob(//proc/**)",
148405
+ "Glob(//sys/**)",
148406
+ `Read(${CODEX_AUTH_DENY_PATH})`,
148407
+ `Grep(${CODEX_AUTH_DENY_PATH})`,
148408
+ `Edit(${CODEX_AUTH_DENY_PATH})`,
148409
+ `Glob(${CODEX_AUTH_DENY_PATH})`,
148410
+ ...toolDeny
148411
+ ]
148412
+ },
148413
+ sandbox: {
148414
+ filesystem: {
148415
+ denyRead: ["/proc", "/sys", CODEX_AUTH_DENY_PATH, ...secretDenyPaths]
148416
+ }
148080
148417
  }
148081
- }
148082
- };
148083
- function installManagedSettings() {
148418
+ };
148419
+ }
148420
+ function installManagedSettings(ctx) {
148084
148421
  if (process.env.CI !== "true") return;
148085
- const content = JSON.stringify(managedSettings, null, 2);
148422
+ const content = JSON.stringify(buildManagedSettings(ctx), null, 2);
148086
148423
  try {
148087
- execFileSync3("sudo", ["mkdir", "-p", MANAGED_SETTINGS_DIR]);
148088
- execFileSync3("sudo", ["tee", MANAGED_SETTINGS_PATH], {
148424
+ execFileSync4("sudo", ["mkdir", "-p", MANAGED_SETTINGS_DIR]);
148425
+ execFileSync4("sudo", ["tee", MANAGED_SETTINGS_PATH], {
148089
148426
  input: content,
148090
148427
  stdio: ["pipe", "ignore", "pipe"]
148091
148428
  });
@@ -148102,23 +148439,25 @@ var claude = agent({
148102
148439
  const specifier = ctx.payload.proxyModel ?? ctx.resolvedModel;
148103
148440
  const bedrockModelId = process.env[BEDROCK_MODEL_ID_ENV]?.trim();
148104
148441
  const isBedrockRoute = specifier !== void 0 && bedrockModelId !== void 0 && bedrockModelId === specifier && isBedrockAnthropicId(specifier);
148105
- const model = !specifier ? void 0 : isBedrockRoute ? specifier : stripProviderPrefix(specifier);
148442
+ const vertexModelId = process.env[VERTEX_MODEL_ID_ENV]?.trim();
148443
+ const isVertexRoute2 = specifier !== void 0 && vertexModelId !== void 0 && vertexModelId === specifier && isVertexAnthropicId(specifier);
148444
+ const model = !specifier ? void 0 : isBedrockRoute ? specifier : isVertexRoute2 ? void 0 : stripProviderPrefix(specifier);
148106
148445
  const homeEnv = {
148107
148446
  HOME: ctx.tmpdir,
148108
- XDG_CONFIG_HOME: join10(ctx.tmpdir, ".config")
148447
+ XDG_CONFIG_HOME: join12(ctx.tmpdir, ".config")
148109
148448
  };
148110
- mkdirSync4(join10(homeEnv.XDG_CONFIG_HOME, "claude"), { recursive: true });
148449
+ mkdirSync5(join12(homeEnv.XDG_CONFIG_HOME, "claude"), { recursive: true });
148111
148450
  const agentBrowserVersion = getDevDependencyVersion("agent-browser");
148112
148451
  addSkill({
148113
148452
  ref: `vercel-labs/agent-browser@v${agentBrowserVersion}`,
148114
148453
  skill: "agent-browser",
148115
148454
  env: homeEnv,
148116
- agent: "claude"
148455
+ agent: "claude-code"
148117
148456
  });
148118
148457
  installBundledSkills({ home: homeEnv.HOME });
148119
148458
  const mcpConfigPath = writeMcpConfig(ctx);
148120
148459
  const effort = resolveEffort(model);
148121
- installManagedSettings();
148460
+ installManagedSettings(ctx);
148122
148461
  const baseArgs = [
148123
148462
  cliPath,
148124
148463
  "--output-format",
@@ -148146,6 +148485,10 @@ var claude = agent({
148146
148485
  if (isBedrockRoute) {
148147
148486
  env2.CLAUDE_CODE_USE_BEDROCK = "1";
148148
148487
  }
148488
+ if (isVertexRoute2) {
148489
+ applyClaudeVertexEnv(env2);
148490
+ env2.ANTHROPIC_MODEL = specifier;
148491
+ }
148149
148492
  if (env2.CLAUDE_CODE_OAUTH_TOKEN && !isBedrockRoute && env2.ANTHROPIC_API_KEY) {
148150
148493
  log.debug(
148151
148494
  "\xBB CLAUDE_CODE_OAUTH_TOKEN present \u2014 stripping ANTHROPIC_API_KEY from Claude Code env so the OAuth subscription is used"
@@ -148187,8 +148530,8 @@ var claude = agent({
148187
148530
 
148188
148531
  // agents/opencode_v2.ts
148189
148532
  var core2 = __toESM(require_core(), 1);
148190
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync9 } from "node:fs";
148191
- import { join as join12 } from "node:path";
148533
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync10 } from "node:fs";
148534
+ import { join as join14 } from "node:path";
148192
148535
  import { performance as performance7 } from "node:perf_hooks";
148193
148536
 
148194
148537
  // utils/agentHangReport.ts
@@ -148287,9 +148630,9 @@ function formatBillingExhaustedBody(diagnostic) {
148287
148630
  }
148288
148631
 
148289
148632
  // utils/codexHome.ts
148290
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "node:fs";
148291
- import { homedir } from "node:os";
148292
- import { join as join11 } from "node:path";
148633
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync9 } from "node:fs";
148634
+ import { homedir as homedir2 } from "node:os";
148635
+ import { join as join13 } from "node:path";
148293
148636
  var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
148294
148637
  function installCodexAuth() {
148295
148638
  const raw2 = process.env[CODEX_AUTH_ENV];
@@ -148299,9 +148642,9 @@ function installCodexAuth() {
148299
148642
  log.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
148300
148643
  return null;
148301
148644
  }
148302
- const xdgDataHome = join11(homedir(), ".local", "share");
148303
- const opencodeDir = join11(xdgDataHome, "opencode");
148304
- const authPath = join11(opencodeDir, "auth.json");
148645
+ const xdgDataHome = join13(homedir2(), ".local", "share");
148646
+ const opencodeDir = join13(xdgDataHome, "opencode");
148647
+ const authPath = join13(opencodeDir, "auth.json");
148305
148648
  const opencodeAuth = {
148306
148649
  openai: {
148307
148650
  type: "oauth",
@@ -148314,8 +148657,8 @@ function installCodexAuth() {
148314
148657
  ...blob.tokens.account_id ? { accountId: blob.tokens.account_id } : {}
148315
148658
  }
148316
148659
  };
148317
- mkdirSync5(opencodeDir, { recursive: true });
148318
- writeFileSync8(authPath, `${JSON.stringify(opencodeAuth, null, 2)}
148660
+ mkdirSync6(opencodeDir, { recursive: true });
148661
+ writeFileSync9(authPath, `${JSON.stringify(opencodeAuth, null, 2)}
148319
148662
  `, { mode: 384 });
148320
148663
  log.info(`\xBB installed Codex auth at ${authPath}`);
148321
148664
  return { authPath, xdgDataHome, originalRefresh: blob.tokens.refresh_token };
@@ -148429,7 +148772,7 @@ export default async function pullfrogEventsPlugin() {
148429
148772
  `;
148430
148773
 
148431
148774
  // agents/opencodeShared.ts
148432
- import { execFileSync as execFileSync4 } from "node:child_process";
148775
+ import { execFileSync as execFileSync5 } from "node:child_process";
148433
148776
 
148434
148777
  // agents/subagentModels.ts
148435
148778
  function deriveSubagentModels(orchestratorSpec) {
@@ -148478,7 +148821,7 @@ async function installOpencodeCli(params) {
148478
148821
  var AUTO_SELECT_WARNING = "select a model explicitly in the Pullfrog console (https://pullfrog.com/console) to avoid this.";
148479
148822
  function getOpenCodeModels(cliPath) {
148480
148823
  try {
148481
- const output = execFileSync4(cliPath, ["models"], {
148824
+ const output = execFileSync5(cliPath, ["models"], {
148482
148825
  encoding: "utf-8",
148483
148826
  timeout: 3e4,
148484
148827
  env: process.env
@@ -148971,16 +149314,17 @@ var opencode = agent({
148971
149314
  const rawModel = ctx.payload.proxyModel ?? ctx.resolvedModel ?? autoSelectModel(cliPath);
148972
149315
  const bedrockModelId = process.env[BEDROCK_MODEL_ID_ENV]?.trim();
148973
149316
  const isBedrockRoute = rawModel !== void 0 && bedrockModelId !== void 0 && bedrockModelId === rawModel;
148974
- const model = isBedrockRoute ? `amazon-bedrock/${rawModel}` : rawModel;
149317
+ const vertexModel = resolveVertexOpenCodeModel(rawModel);
149318
+ const model = vertexModel ?? (isBedrockRoute ? `amazon-bedrock/${rawModel}` : rawModel);
148975
149319
  const homeEnv = {
148976
149320
  HOME: ctx.tmpdir,
148977
- XDG_CONFIG_HOME: join12(ctx.tmpdir, ".config")
149321
+ XDG_CONFIG_HOME: join14(ctx.tmpdir, ".config")
148978
149322
  };
148979
- mkdirSync6(join12(homeEnv.XDG_CONFIG_HOME, "opencode"), { recursive: true });
148980
- const opencodePluginDir = join12(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
148981
- mkdirSync6(opencodePluginDir, { recursive: true });
148982
- writeFileSync9(
148983
- join12(opencodePluginDir, PULLFROG_OPENCODE_PLUGIN_FILENAME),
149323
+ mkdirSync7(join14(homeEnv.XDG_CONFIG_HOME, "opencode"), { recursive: true });
149324
+ const opencodePluginDir = join14(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
149325
+ mkdirSync7(opencodePluginDir, { recursive: true });
149326
+ writeFileSync10(
149327
+ join14(opencodePluginDir, PULLFROG_OPENCODE_PLUGIN_FILENAME),
148984
149328
  PULLFROG_OPENCODE_PLUGIN_SOURCE
148985
149329
  );
148986
149330
  const agentBrowserVersion = getDevDependencyVersion("agent-browser");
@@ -149050,15 +149394,18 @@ var opencode = agent({
149050
149394
  var agents = { claude, opencode };
149051
149395
 
149052
149396
  // utils/agent.ts
149053
- function hasEnvVar(name) {
149397
+ function hasEnvVar2(name) {
149054
149398
  const val = process.env[name];
149055
149399
  return typeof val === "string" && val.length > 0;
149056
149400
  }
149057
149401
  function hasClaudeCodeAuth() {
149058
- return hasEnvVar("CLAUDE_CODE_OAUTH_TOKEN") || hasEnvVar("ANTHROPIC_API_KEY");
149402
+ return hasEnvVar2("CLAUDE_CODE_OAUTH_TOKEN") || hasEnvVar2("ANTHROPIC_API_KEY");
149059
149403
  }
149060
149404
  function hasBedrockAuth() {
149061
- return hasEnvVar("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar("AWS_ACCESS_KEY_ID") && hasEnvVar("AWS_SECRET_ACCESS_KEY");
149405
+ return hasEnvVar2("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar2("AWS_ACCESS_KEY_ID") && hasEnvVar2("AWS_SECRET_ACCESS_KEY");
149406
+ }
149407
+ function hasVertexAuth() {
149408
+ return hasEnvVar2(VERTEX_SERVICE_ACCOUNT_JSON_ENV);
149062
149409
  }
149063
149410
  function resolveSlug(slug2) {
149064
149411
  const alias = resolveDisplayAlias(slug2);
@@ -149066,11 +149413,20 @@ function resolveSlug(slug2) {
149066
149413
  const bedrockId = process.env[BEDROCK_MODEL_ID_ENV]?.trim();
149067
149414
  if (!bedrockId) {
149068
149415
  throw new Error(
149069
- `${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.`
149416
+ `${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.`
149070
149417
  );
149071
149418
  }
149072
149419
  return bedrockId;
149073
149420
  }
149421
+ if (alias?.routing === "vertex") {
149422
+ const vertexId = process.env[VERTEX_MODEL_ID_ENV]?.trim();
149423
+ if (!vertexId) {
149424
+ throw new Error(
149425
+ `${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.`
149426
+ );
149427
+ }
149428
+ return vertexId;
149429
+ }
149074
149430
  return resolveCliModel(slug2);
149075
149431
  }
149076
149432
  function resolveModel(ctx) {
@@ -149098,6 +149454,9 @@ function resolveAgent(ctx) {
149098
149454
  if (ctx.model && hasBedrockAuth() && process.env[BEDROCK_MODEL_ID_ENV]?.trim() === ctx.model) {
149099
149455
  return isBedrockAnthropicId(ctx.model) ? agents.claude : agents.opencode;
149100
149456
  }
149457
+ if (ctx.model && hasVertexAuth() && process.env[VERTEX_MODEL_ID_ENV]?.trim() === ctx.model) {
149458
+ return isVertexAnthropicId(ctx.model) ? agents.claude : agents.opencode;
149459
+ }
149101
149460
  if (ctx.model) {
149102
149461
  try {
149103
149462
  const provider2 = getModelProvider(ctx.model);
@@ -149138,26 +149497,51 @@ add the missing secret(s) to your GitHub repository at ${githubSecretsUrl}, then
149138
149497
 
149139
149498
  for full setup instructions, see https://docs.pullfrog.com/bedrock`;
149140
149499
  }
149141
- function hasEnvVar2(name) {
149500
+ function buildVertexSetupError(params) {
149501
+ const githubSecretsUrl = `https://github.com/${params.owner}/${params.name}/settings/secrets/actions`;
149502
+ return `Google Vertex AI model selected but required configuration is missing: ${params.missing.join(", ")}.
149503
+
149504
+ add the missing secret(s) to your GitHub repository at ${githubSecretsUrl}, then reference them in your workflow's \`env:\` block:
149505
+
149506
+ ${VERTEX_SERVICE_ACCOUNT_JSON_ENV}: \${{ secrets.${VERTEX_SERVICE_ACCOUNT_JSON_ENV} }}
149507
+ ${GOOGLE_CLOUD_PROJECT_ENV}: my-project
149508
+ ${VERTEX_LOCATION_ENV}: global
149509
+ ${VERTEX_MODEL_ID_ENV}: <vertex-model-id>
149510
+
149511
+ for full setup instructions, see https://docs.pullfrog.com/vertex`;
149512
+ }
149513
+ function hasEnvVar3(name) {
149142
149514
  const value2 = process.env[name];
149143
149515
  return typeof value2 === "string" && value2.length > 0;
149144
149516
  }
149145
149517
  function hasProviderKey(model) {
149146
149518
  const requiredVars = getModelEnvVars(model);
149147
149519
  if (requiredVars.length === 0) return true;
149148
- return requiredVars.some((v) => hasEnvVar2(v));
149520
+ return requiredVars.some((v) => hasEnvVar3(v));
149149
149521
  }
149150
149522
  function validateBedrockSetup(params) {
149151
- const hasAuth = hasEnvVar2("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar2("AWS_ACCESS_KEY_ID") && hasEnvVar2("AWS_SECRET_ACCESS_KEY");
149523
+ const hasAuth = hasEnvVar3("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar3("AWS_ACCESS_KEY_ID") && hasEnvVar3("AWS_SECRET_ACCESS_KEY");
149152
149524
  const missing = [];
149153
149525
  if (!hasAuth)
149154
149526
  missing.push("AWS_BEARER_TOKEN_BEDROCK (or AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY)");
149155
- if (!hasEnvVar2("AWS_REGION")) missing.push("AWS_REGION");
149156
- if (!hasEnvVar2(BEDROCK_MODEL_ID_ENV)) missing.push(BEDROCK_MODEL_ID_ENV);
149527
+ if (!hasEnvVar3("AWS_REGION")) missing.push("AWS_REGION");
149528
+ if (!hasEnvVar3(BEDROCK_MODEL_ID_ENV)) missing.push(BEDROCK_MODEL_ID_ENV);
149157
149529
  if (missing.length > 0) {
149158
149530
  throw new Error(buildBedrockSetupError({ owner: params.owner, name: params.name, missing }));
149159
149531
  }
149160
149532
  }
149533
+ function validateVertexSetup(params) {
149534
+ const hasAuth = hasEnvVar3(VERTEX_SERVICE_ACCOUNT_JSON_ENV);
149535
+ const hasProject = hasEnvVar3(GOOGLE_CLOUD_PROJECT_ENV) || readProjectIdFromVertexServiceAccountJson() !== void 0;
149536
+ const missing = [];
149537
+ if (!hasAuth) missing.push(VERTEX_SERVICE_ACCOUNT_JSON_ENV);
149538
+ if (!hasProject) missing.push(GOOGLE_CLOUD_PROJECT_ENV);
149539
+ if (!hasEnvVar3(VERTEX_LOCATION_ENV)) missing.push(VERTEX_LOCATION_ENV);
149540
+ if (!hasEnvVar3(VERTEX_MODEL_ID_ENV)) missing.push(VERTEX_MODEL_ID_ENV);
149541
+ if (missing.length > 0) {
149542
+ throw new Error(buildVertexSetupError({ owner: params.owner, name: params.name, missing }));
149543
+ }
149544
+ }
149161
149545
  function validateAgentApiKey(params) {
149162
149546
  if (params.model) {
149163
149547
  const alias = resolveDisplayAlias(params.model);
@@ -149165,16 +149549,24 @@ function validateAgentApiKey(params) {
149165
149549
  validateBedrockSetup({ owner: params.owner, name: params.name });
149166
149550
  return;
149167
149551
  }
149552
+ if (alias?.routing === "vertex") {
149553
+ validateVertexSetup({ owner: params.owner, name: params.name });
149554
+ return;
149555
+ }
149168
149556
  if (!params.model.includes("/")) {
149557
+ if (process.env[VERTEX_MODEL_ID_ENV]?.trim() === params.model) {
149558
+ validateVertexSetup({ owner: params.owner, name: params.name });
149559
+ return;
149560
+ }
149169
149561
  validateBedrockSetup({ owner: params.owner, name: params.name });
149170
149562
  return;
149171
149563
  }
149172
149564
  const requiredVars = getModelEnvVars(params.model);
149173
149565
  if (requiredVars.length === 0) return;
149174
- if (requiredVars.some((v) => hasEnvVar2(v))) return;
149566
+ if (requiredVars.some((v) => hasEnvVar3(v))) return;
149175
149567
  throw new Error(buildMissingApiKeyError({ owner: params.owner, name: params.name }));
149176
149568
  }
149177
- const hasAnyKey = [...knownApiKeys].some((k) => hasEnvVar2(k));
149569
+ const hasAnyKey = [...knownApiKeys].some((k) => hasEnvVar3(k));
149178
149570
  if (!hasAnyKey) {
149179
149571
  throw new Error(buildMissingApiKeyError({ owner: params.owner, name: params.name }));
149180
149572
  }
@@ -149294,7 +149686,7 @@ async function fetchBodyHtml(ctx) {
149294
149686
  }
149295
149687
 
149296
149688
  // utils/byokFallback.ts
149297
- var FREE_FALLBACK_SLUG = "opencode/minimax-m2.5-free";
149689
+ var FREE_FALLBACK_SLUG = "opencode/big-pickle";
149298
149690
  function selectFallbackModelIfNeeded(input) {
149299
149691
  if (input.proxyModel) return { fallback: false };
149300
149692
  if (!input.resolvedModel) return { fallback: false };
@@ -149309,10 +149701,10 @@ function selectFallbackModelIfNeeded(input) {
149309
149701
  }
149310
149702
 
149311
149703
  // utils/gitAuthServer.ts
149312
- import { randomUUID as randomUUID3 } from "node:crypto";
149313
- import { writeFileSync as writeFileSync10 } from "node:fs";
149704
+ import { randomUUID as randomUUID4 } from "node:crypto";
149705
+ import { writeFileSync as writeFileSync11 } from "node:fs";
149314
149706
  import { createServer as createServer2 } from "node:http";
149315
- import { join as join13 } from "node:path";
149707
+ import { join as join15 } from "node:path";
149316
149708
  var CODE_TTL_MS = 5 * 60 * 1e3;
149317
149709
  var TAMPER_WINDOW_MS = 6e4;
149318
149710
  function revokeGitHubToken(token) {
@@ -149372,7 +149764,7 @@ async function startGitAuthServer(tmpdir3) {
149372
149764
  const port = rawAddr.port;
149373
149765
  log.debug(`git auth server listening on 127.0.0.1:${port}`);
149374
149766
  function register4(token) {
149375
- const code = randomUUID3();
149767
+ const code = randomUUID4();
149376
149768
  const timeout = setTimeout(() => {
149377
149769
  codes.delete(code);
149378
149770
  log.debug(`git auth code expired: ${code.slice(0, 8)}...`);
@@ -149382,9 +149774,9 @@ async function startGitAuthServer(tmpdir3) {
149382
149774
  return code;
149383
149775
  }
149384
149776
  function writeAskpassScript(code) {
149385
- const scriptId = randomUUID3();
149777
+ const scriptId = randomUUID4();
149386
149778
  const scriptName = `askpass-${scriptId}.js`;
149387
- const scriptPath = join13(tmpdir3, scriptName);
149779
+ const scriptPath = join15(tmpdir3, scriptName);
149388
149780
  const content = [
149389
149781
  `#!/usr/bin/env node`,
149390
149782
  `var a=process.argv[2]||"";`,
@@ -149399,7 +149791,7 @@ async function startGitAuthServer(tmpdir3) {
149399
149791
  `try{require("fs").unlinkSync("${scriptPath.replace(/\\/g, "\\\\")}")}catch(e){}`,
149400
149792
  `})}).on("error",function(){process.exit(1)})}`
149401
149793
  ].join("\n");
149402
- writeFileSync10(scriptPath, content, { mode: 448 });
149794
+ writeFileSync11(scriptPath, content, { mode: 448 });
149403
149795
  return scriptPath;
149404
149796
  }
149405
149797
  async function close() {
@@ -149423,7 +149815,7 @@ async function startGitAuthServer(tmpdir3) {
149423
149815
  var core3 = __toESM(require_core(), 1);
149424
149816
  import { createSign } from "node:crypto";
149425
149817
  import { rename, writeFile } from "node:fs/promises";
149426
- import { dirname as dirname3, join as join14 } from "node:path";
149818
+ import { dirname as dirname3, join as join16 } from "node:path";
149427
149819
 
149428
149820
  // node_modules/.pnpm/@octokit+plugin-throttling@11.0.3_@octokit+core@7.0.5/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
149429
149821
  var import_light = __toESM(require_light(), 1);
@@ -153281,7 +153673,7 @@ function getGitHubUsageSummary() {
153281
153673
  }
153282
153674
  async function writeGitHubUsageSummaryToFile(path3) {
153283
153675
  const summary2 = getGitHubUsageSummary();
153284
- const tmpPath = join14(dirname3(path3), `.usage-summary-${process.pid}.tmp`);
153676
+ const tmpPath = join16(dirname3(path3), `.usage-summary-${process.pid}.tmp`);
153285
153677
  await writeFile(tmpPath, JSON.stringify(summary2));
153286
153678
  await rename(tmpPath, path3);
153287
153679
  }
@@ -153332,7 +153724,7 @@ function createOctokit(token) {
153332
153724
  }
153333
153725
 
153334
153726
  // utils/instructions.ts
153335
- import { execSync as execSync2 } from "node:child_process";
153727
+ import { execSync as execSync3 } from "node:child_process";
153336
153728
  function buildRuntimeContext(ctx) {
153337
153729
  const {
153338
153730
  "~pullfrog": _,
@@ -153344,7 +153736,7 @@ function buildRuntimeContext(ctx) {
153344
153736
  } = ctx.payload;
153345
153737
  let gitStatus;
153346
153738
  try {
153347
- gitStatus = execSync2("git status --short", { encoding: "utf-8", stdio: "pipe" }).trim() || "(clean)";
153739
+ gitStatus = execSync3("git status --short", { encoding: "utf-8", stdio: "pipe" }).trim() || "(clean)";
153348
153740
  } catch {
153349
153741
  }
153350
153742
  const data = {
@@ -153681,7 +154073,7 @@ function resolveInstructions(ctx) {
153681
154073
 
153682
154074
  // utils/learnings.ts
153683
154075
  import { mkdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
153684
- import { dirname as dirname4, join as join15 } from "node:path";
154076
+ import { dirname as dirname4, join as join17 } from "node:path";
153685
154077
 
153686
154078
  // utils/learningsTruncate.ts
153687
154079
  var MAX_LEARNINGS_LENGTH = 1e5;
@@ -153698,7 +154090,7 @@ function truncateAtLineBoundary(body, cap) {
153698
154090
  // utils/learnings.ts
153699
154091
  var LEARNINGS_FILE_NAME = "pullfrog-learnings.md";
153700
154092
  function learningsFilePath(tmpdir3) {
153701
- return join15(tmpdir3, LEARNINGS_FILE_NAME);
154093
+ return join17(tmpdir3, LEARNINGS_FILE_NAME);
153702
154094
  }
153703
154095
  async function seedLearningsFile(params) {
153704
154096
  const path3 = learningsFilePath(params.tmpdir);
@@ -154378,7 +154770,7 @@ async function runProxyResolution(ctx) {
154378
154770
 
154379
154771
  // utils/prSummary.ts
154380
154772
  import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
154381
- import { dirname as dirname5, join as join16 } from "node:path";
154773
+ import { dirname as dirname5, join as join18 } from "node:path";
154382
154774
  var SUMMARY_FILE_NAME = "pullfrog-summary.md";
154383
154775
  var SUMMARY_SCAFFOLD = `# PR summary
154384
154776
 
@@ -154388,7 +154780,7 @@ var SUMMARY_SCAFFOLD = `# PR summary
154388
154780
  var MIN_SNAPSHOT_LENGTH = 60;
154389
154781
  var MAX_SNAPSHOT_LENGTH = 32768;
154390
154782
  function summaryFilePath(tmpdir3) {
154391
- return join16(tmpdir3, SUMMARY_FILE_NAME);
154783
+ return join18(tmpdir3, SUMMARY_FILE_NAME);
154392
154784
  }
154393
154785
  async function seedSummaryFile(params) {
154394
154786
  const path3 = summaryFilePath(params.tmpdir);
@@ -154582,6 +154974,16 @@ async function resolveRunContextData(params) {
154582
154974
  }
154583
154975
 
154584
154976
  // utils/runErrorRenderer.ts
154977
+ function isProviderModelNotFoundError(message) {
154978
+ return message.includes("ProviderModelNotFoundError");
154979
+ }
154980
+ function formatProviderModelNotFoundSummary(input) {
154981
+ return `Pullfrog's free fallback model is no longer available in OpenCode's catalog. Add an API key for your configured model in the Pullfrog console for \`${input.owner}/${input.name}\`, or contact support if this persists.
154982
+
154983
+ \`\`\`
154984
+ ${input.raw}
154985
+ \`\`\``;
154986
+ }
154585
154987
  function renderRunError(input) {
154586
154988
  const billingError = isRouterKeylimitExhaustedError(input.errorMessage) ? new BillingError(input.errorMessage, { code: "router_keylimit_exhausted" }) : null;
154587
154989
  if (billingError) {
@@ -154603,6 +155005,14 @@ function renderRunError(input) {
154603
155005
  if (apiKeyErrorSummary) {
154604
155006
  return { summary: apiKeyErrorSummary, comment: apiKeyErrorSummary };
154605
155007
  }
155008
+ if (isProviderModelNotFoundError(input.errorMessage)) {
155009
+ const body = formatProviderModelNotFoundSummary({
155010
+ owner: input.repo.owner,
155011
+ name: input.repo.name,
155012
+ raw: input.errorMessage
155013
+ });
155014
+ return { summary: body, comment: body };
155015
+ }
154606
155016
  if (hangBody) {
154607
155017
  return {
154608
155018
  summary: `### \u274C Pullfrog failed
@@ -154704,16 +155114,17 @@ async function persistRunArtifacts(toolContext) {
154704
155114
  }
154705
155115
  async function finalizeSuccessRun(input) {
154706
155116
  await persistRunArtifacts(input.toolContext);
154707
- if (!input.result.success && input.toolState.progressComment) {
154708
- const rawError = input.result.error || "agent run failed";
154709
- const errorBody = isApiKeyAuthError(rawError) ? formatApiKeyErrorSummary({
154710
- owner: input.repo.owner,
154711
- name: input.repo.name,
154712
- raw: rawError
154713
- }) : rawError;
154714
- await reportErrorToComment({ toolState: input.toolState, error: errorBody }).catch((error49) => {
154715
- log.debug(`failure error report failed: ${error49}`);
154716
- });
155117
+ const rendered = !input.result.success ? renderRunError({
155118
+ errorMessage: input.result.error || "agent run failed",
155119
+ repo: input.repo,
155120
+ agentDiagnostic: input.toolState.agentDiagnostic
155121
+ }) : null;
155122
+ if (rendered && input.toolState.progressComment) {
155123
+ await reportErrorToComment({ toolState: input.toolState, error: rendered.comment }).catch(
155124
+ (error49) => {
155125
+ log.debug(`failure error report failed: ${error49}`);
155126
+ }
155127
+ );
154717
155128
  }
154718
155129
  if (input.result.success && input.toolState.progressComment && !input.toolState.finalSummaryWritten) {
154719
155130
  await deleteProgressComment(input.toolContext).catch((error49) => {
@@ -154723,7 +155134,7 @@ async function finalizeSuccessRun(input) {
154723
155134
  try {
154724
155135
  const usageSummary = formatUsageSummary(input.toolState.usageEntries);
154725
155136
  const body = input.toolState.lastProgressBody || input.result.output;
154726
- const parts = [body, usageSummary].filter(Boolean);
155137
+ const parts = [rendered?.summary, body, usageSummary].filter(Boolean);
154727
155138
  if (parts.length > 0) {
154728
155139
  await writeSummary(parts.join("\n\n"));
154729
155140
  }
@@ -154808,116 +155219,6 @@ function logRunStartup(ctx) {
154808
155219
  log.info(`\xBB timeout: ${resolveTimeoutForLog(ctx.payload.timeout)}`);
154809
155220
  }
154810
155221
 
154811
- // utils/setup.ts
154812
- import { execFileSync as execFileSync5, execSync as execSync3 } from "node:child_process";
154813
- import { mkdtempSync } from "node:fs";
154814
- import { tmpdir as tmpdir2 } from "node:os";
154815
- import { join as join17 } from "node:path";
154816
- function createTempDirectory() {
154817
- const sharedTempDir = mkdtempSync(join17(tmpdir2(), "pullfrog-"));
154818
- process.env.PULLFROG_TEMP_DIR = sharedTempDir;
154819
- log.info(`\xBB created temp dir at ${sharedTempDir}`);
154820
- return sharedTempDir;
154821
- }
154822
- function envScopedToRepo() {
154823
- const scoped = { ...process.env };
154824
- for (const key of Object.keys(scoped)) {
154825
- if (key.startsWith("GIT_")) delete scoped[key];
154826
- }
154827
- return scoped;
154828
- }
154829
- function removeIncludeIfEntries(repoDir) {
154830
- const env2 = envScopedToRepo();
154831
- let configOutput;
154832
- try {
154833
- configOutput = execSync3("git config --local --get-regexp -z ^includeif\\.", {
154834
- cwd: repoDir,
154835
- encoding: "utf-8",
154836
- stdio: "pipe",
154837
- env: env2
154838
- });
154839
- } catch {
154840
- log.debug("\xBB no includeIf credential entries to remove");
154841
- return;
154842
- }
154843
- const seen = /* @__PURE__ */ new Set();
154844
- for (const entry of configOutput.split("\0")) {
154845
- if (!entry) continue;
154846
- const nl = entry.indexOf("\n");
154847
- const key = nl === -1 ? entry : entry.slice(0, nl);
154848
- if (!key || seen.has(key)) continue;
154849
- seen.add(key);
154850
- try {
154851
- execFileSync5("git", ["config", "--local", "--unset-all", key], {
154852
- cwd: repoDir,
154853
- stdio: "pipe",
154854
- env: env2
154855
- });
154856
- } catch (error49) {
154857
- log.debug(
154858
- `\xBB failed to unset ${key}: ${error49 instanceof Error ? error49.message : String(error49)}`
154859
- );
154860
- }
154861
- }
154862
- if (seen.size > 0)
154863
- log.info(
154864
- `\xBB removed ${seen.size} includeIf credential ${seen.size === 1 ? "entry" : "entries"}`
154865
- );
154866
- }
154867
- async function setupGit(params) {
154868
- const repoDir = process.cwd();
154869
- log.info("\xBB setting up git configuration...");
154870
- try {
154871
- let currentEmail = "";
154872
- try {
154873
- currentEmail = execSync3("git config user.email", {
154874
- cwd: repoDir,
154875
- stdio: "pipe",
154876
- encoding: "utf-8"
154877
- }).trim();
154878
- } catch {
154879
- }
154880
- const shouldSetDefaults = !currentEmail || currentEmail === "github-actions[bot]@users.noreply.github.com";
154881
- if (shouldSetDefaults) {
154882
- execSync3('git config --local user.email "226033991+pullfrog[bot]@users.noreply.github.com"', {
154883
- cwd: repoDir,
154884
- stdio: "pipe"
154885
- });
154886
- execSync3('git config --local user.name "pullfrog[bot]"', {
154887
- cwd: repoDir,
154888
- stdio: "pipe"
154889
- });
154890
- log.debug("\xBB git user configured (using defaults)");
154891
- } else {
154892
- log.debug(`\xBB git user already configured (${currentEmail}), skipping`);
154893
- }
154894
- if (params.shell === "disabled") {
154895
- execSync3("git config --local core.hooksPath /dev/null", {
154896
- cwd: repoDir,
154897
- stdio: "pipe"
154898
- });
154899
- log.debug("\xBB git hooks disabled (shell=disabled)");
154900
- }
154901
- } catch (error49) {
154902
- log.info(`Failed to set git config: ${error49 instanceof Error ? error49.message : String(error49)}`);
154903
- }
154904
- try {
154905
- execSync3("git config --local --unset-all http.https://github.com/.extraheader", {
154906
- cwd: repoDir,
154907
- stdio: "pipe"
154908
- });
154909
- log.info("\xBB removed existing authentication headers");
154910
- } catch {
154911
- log.debug("\xBB no existing authentication headers to remove");
154912
- }
154913
- removeIncludeIfEntries(repoDir);
154914
- const originUrl = `https://github.com/${params.owner}/${params.name}.git`;
154915
- $("git", ["remote", "set-url", "origin", originUrl], { cwd: repoDir });
154916
- params.toolState.pushUrl = originUrl;
154917
- $("git", ["config", "--local", "credential.helper", ""], { cwd: repoDir });
154918
- log.info("\xBB git authentication configured");
154919
- }
154920
-
154921
155222
  // utils/todoTracking.ts
154922
155223
  function isValidTodoStatus(value2) {
154923
155224
  return value2 === "pending" || value2 === "in_progress" || value2 === "completed" || value2 === "cancelled";
@@ -155125,6 +155426,7 @@ async function main() {
155125
155426
  toolState.beforeSha = payload.event.before_sha;
155126
155427
  }
155127
155428
  const tokenRef = __using(_stack2, await resolveTokens({ push: payload.push }), true);
155429
+ wipeRunnerLeakSurface();
155128
155430
  const oidcCredentials = process.env.ACTIONS_ID_TOKEN_REQUEST_URL && process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN ? {
155129
155431
  requestUrl: process.env.ACTIONS_ID_TOKEN_REQUEST_URL,
155130
155432
  requestToken: process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN
@@ -155146,6 +155448,7 @@ async function main() {
155146
155448
  let toolContext;
155147
155449
  let progressCallbackDisabled = false;
155148
155450
  let todoTracker;
155451
+ let vertexCredentials;
155149
155452
  try {
155150
155453
  var _stack = [];
155151
155454
  try {
@@ -155180,6 +155483,7 @@ async function main() {
155180
155483
  );
155181
155484
  toolState.modelFallback = { from: fallback.from };
155182
155485
  }
155486
+ vertexCredentials = materializeVertexCredentials({ model: resolvedModel });
155183
155487
  const agent2 = resolveAgent({ model: resolvedModel });
155184
155488
  toolState.model = payload.proxyModel ?? resolvedModel ?? effectiveSlug;
155185
155489
  validateAgentApiKey({
@@ -155291,8 +155595,8 @@ ${instructions.user}` : null,
155291
155595
  log.info(instructions.full);
155292
155596
  });
155293
155597
  if (agentId === "opencode") {
155294
- const pluginDir = join18(process.cwd(), ".opencode", "plugin");
155295
- const hasPlugins = existsSync7(pluginDir) && readdirSync(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
155598
+ const pluginDir = join19(process.cwd(), ".opencode", "plugin");
155599
+ const hasPlugins = existsSync7(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
155296
155600
  if (hasPlugins && toolState.dependencyInstallation?.promise) {
155297
155601
  log.info(
155298
155602
  "\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"
@@ -155347,6 +155651,7 @@ ${instructions.user}` : null,
155347
155651
  resolvedModel,
155348
155652
  mcpServerUrl: mcpHttpServer.url,
155349
155653
  tmpdir: tmpdir3,
155654
+ secretDenyPaths: vertexCredentials ? [vertexCredentials.secretDir] : [],
155350
155655
  instructions,
155351
155656
  todoTracker,
155352
155657
  stopScript: runContext.repoSettings.stopScript,
@@ -155450,6 +155755,7 @@ ${instructions.user}` : null,
155450
155755
  await patchWorkflowRunFields(toolContext, patch);
155451
155756
  }
155452
155757
  }
155758
+ cleanupVertexCredentials(vertexCredentials);
155453
155759
  }
155454
155760
  } catch (_2) {
155455
155761
  var _error2 = _2, _hasError2 = true;