pullfrog 0.1.57 → 0.1.59

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
@@ -3955,7 +3955,7 @@ var require_util2 = __commonJS({
3955
3955
  "use strict";
3956
3956
  var { redirectStatusSet, referrerPolicySet: referrerPolicyTokens, badPortsSet } = require_constants2();
3957
3957
  var { getGlobalOrigin } = require_global();
3958
- var { performance: performance9 } = __require("perf_hooks");
3958
+ var { performance: performance10 } = __require("perf_hooks");
3959
3959
  var { isBlobLike, toUSVString, ReadableStreamFrom } = require_util();
3960
3960
  var assert3 = __require("assert");
3961
3961
  var { isUint8Array } = __require("util/types");
@@ -4118,7 +4118,7 @@ var require_util2 = __commonJS({
4118
4118
  }
4119
4119
  }
4120
4120
  function coarsenedSharedCurrentTime(crossOriginIsolatedCapability) {
4121
- return performance9.now();
4121
+ return performance10.now();
4122
4122
  }
4123
4123
  function createOpaqueTimingInfo(timingInfo) {
4124
4124
  return {
@@ -20099,14 +20099,14 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
20099
20099
  });
20100
20100
  }
20101
20101
  exports.group = group2;
20102
- function saveState3(name, value2) {
20102
+ function saveState4(name, value2) {
20103
20103
  const filePath = process.env["GITHUB_STATE"] || "";
20104
20104
  if (filePath) {
20105
20105
  return (0, file_command_1.issueFileCommand)("STATE", (0, file_command_1.prepareKeyValueMessage)(name, value2));
20106
20106
  }
20107
20107
  (0, command_1.issueCommand)("save-state", { name }, (0, utils_1.toCommandValue)(value2));
20108
20108
  }
20109
- exports.saveState = saveState3;
20109
+ exports.saveState = saveState4;
20110
20110
  function getState2(name) {
20111
20111
  return process.env[`STATE_${name}`] || "";
20112
20112
  }
@@ -27894,11 +27894,11 @@ var require_mode = __commonJS({
27894
27894
  var require_isexe = __commonJS({
27895
27895
  "node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js"(exports, module) {
27896
27896
  var fs5 = __require("fs");
27897
- var core11;
27897
+ var core12;
27898
27898
  if (process.platform === "win32" || global.TESTING_WINDOWS) {
27899
- core11 = require_windows();
27899
+ core12 = require_windows();
27900
27900
  } else {
27901
- core11 = require_mode();
27901
+ core12 = require_mode();
27902
27902
  }
27903
27903
  module.exports = isexe;
27904
27904
  isexe.sync = sync;
@@ -27921,7 +27921,7 @@ var require_isexe = __commonJS({
27921
27921
  });
27922
27922
  });
27923
27923
  }
27924
- core11(path4, options || {}, function(er, is) {
27924
+ core12(path4, options || {}, function(er, is) {
27925
27925
  if (er) {
27926
27926
  if (er.code === "EACCES" || options && options.ignoreErrors) {
27927
27927
  er = null;
@@ -27933,7 +27933,7 @@ var require_isexe = __commonJS({
27933
27933
  }
27934
27934
  function sync(path4, options) {
27935
27935
  try {
27936
- return core11.sync(path4, options || {});
27936
+ return core12.sync(path4, options || {});
27937
27937
  } catch (er) {
27938
27938
  if (options && options.ignoreErrors || er.code === "EACCES") {
27939
27939
  return false;
@@ -33299,7 +33299,7 @@ var require_util9 = __commonJS({
33299
33299
  var { redirectStatusSet, referrerPolicyTokens, badPortsSet } = require_constants9();
33300
33300
  var { getGlobalOrigin } = require_global3();
33301
33301
  var { collectAnHTTPQuotedString, parseMIMEType } = require_data_url();
33302
- var { performance: performance9 } = __require("node:perf_hooks");
33302
+ var { performance: performance10 } = __require("node:perf_hooks");
33303
33303
  var { ReadableStreamFrom, isValidHTTPToken, normalizedMethodRecordsBase } = require_util8();
33304
33304
  var assert3 = __require("node:assert");
33305
33305
  var { isUint8Array } = __require("node:util/types");
@@ -33454,7 +33454,7 @@ var require_util9 = __commonJS({
33454
33454
  };
33455
33455
  }
33456
33456
  function coarsenedSharedCurrentTime(crossOriginIsolatedCapability) {
33457
- return coarsenTime(performance9.now(), crossOriginIsolatedCapability);
33457
+ return coarsenTime(performance10.now(), crossOriginIsolatedCapability);
33458
33458
  }
33459
33459
  function createOpaqueTimingInfo(timingInfo) {
33460
33460
  return {
@@ -76726,7 +76726,7 @@ var require_core3 = __commonJS({
76726
76726
  Object.defineProperty(exports, "__esModule", { value: true });
76727
76727
  var id_1 = require_id();
76728
76728
  var ref_1 = require_ref();
76729
- var core11 = [
76729
+ var core12 = [
76730
76730
  "$schema",
76731
76731
  "$id",
76732
76732
  "$defs",
@@ -76736,7 +76736,7 @@ var require_core3 = __commonJS({
76736
76736
  id_1.default,
76737
76737
  ref_1.default
76738
76738
  ];
76739
- exports.default = core11;
76739
+ exports.default = core12;
76740
76740
  }
76741
76741
  });
76742
76742
 
@@ -98607,14 +98607,14 @@ var require_turndown_cjs = __commonJS({
98607
98607
  } else if (node2.nodeType === 1) {
98608
98608
  replacement = replacementForNode.call(self2, node2);
98609
98609
  }
98610
- return join28(output, replacement);
98610
+ return join29(output, replacement);
98611
98611
  }, "");
98612
98612
  }
98613
98613
  function postProcess(output) {
98614
98614
  var self2 = this;
98615
98615
  this.rules.forEach(function(rule) {
98616
98616
  if (typeof rule.append === "function") {
98617
- output = join28(output, rule.append(self2.options));
98617
+ output = join29(output, rule.append(self2.options));
98618
98618
  }
98619
98619
  });
98620
98620
  return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
@@ -98626,7 +98626,7 @@ var require_turndown_cjs = __commonJS({
98626
98626
  if (whitespace.leading || whitespace.trailing) content = content.trim();
98627
98627
  return whitespace.leading + rule.replacement(content, node2, this.options) + whitespace.trailing;
98628
98628
  }
98629
- function join28(output, replacement) {
98629
+ function join29(output, replacement) {
98630
98630
  var s1 = trimTrailingNewlines(output);
98631
98631
  var s2 = trimLeadingNewlines(replacement);
98632
98632
  var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
@@ -105306,14 +105306,14 @@ async function runClaudeAuth() {
105306
105306
  }
105307
105307
 
105308
105308
  // commands/gha.ts
105309
- var core10 = __toESM(require_core(), 1);
105309
+ var core11 = __toESM(require_core(), 1);
105310
105310
  var import_arg2 = __toESM(require_arg(), 1);
105311
105311
  import { dirname as dirname6 } from "node:path";
105312
105312
 
105313
105313
  // main.ts
105314
- import { existsSync as existsSync8, readdirSync as readdirSync2 } from "node:fs";
105314
+ import { existsSync as existsSync9, readdirSync as readdirSync2 } from "node:fs";
105315
105315
  import { readFile as readFile5 } from "node:fs/promises";
105316
- import { join as join27 } from "node:path";
105316
+ import { join as join28 } from "node:path";
105317
105317
 
105318
105318
  // node_modules/.pnpm/package-manager-detector@1.8.0/node_modules/package-manager-detector/dist/commands.mjs
105319
105319
  function splitRunArgs(args2, valueFlags = []) {
@@ -106186,6 +106186,22 @@ var providers = {
106186
106186
  }
106187
106187
  }
106188
106188
  }),
106189
+ azure: provider({
106190
+ displayName: "Azure OpenAI",
106191
+ // the resource name is half the endpoint URL and the deployment is the model
106192
+ // id, so both are as load-bearing as the key. only the key is sensitive.
106193
+ envVars: ["AZURE_API_KEY", "AZURE_RESOURCE_NAME", "AZURE_DEPLOYMENT"],
106194
+ models: {
106195
+ // single routing entry — the real model is the customer's deployment name,
106196
+ // read from AZURE_DEPLOYMENT at run time. see ModelRouting docs for why
106197
+ // Azure can't be cataloged even though models.dev lists it.
106198
+ byok: {
106199
+ displayName: "Azure OpenAI",
106200
+ resolve: "azure",
106201
+ routing: "azure"
106202
+ }
106203
+ }
106204
+ }),
106189
106205
  "openai-compatible": provider({
106190
106206
  // "Custom" is the picker group, "OpenAI-compatible" the entry under it, so the
106191
106207
  // menu reads `Custom › OpenAI-compatible` and a second custom backend (a
@@ -106411,6 +106427,11 @@ function getModelEnvVars(slug2) {
106411
106427
  }
106412
106428
  return providerConfig.envVars.slice();
106413
106429
  }
106430
+ function getModelManagedCredentials(slug2) {
106431
+ const parsed2 = parseModel(slug2);
106432
+ const providerConfig = providers[parsed2.provider];
106433
+ return providerConfig?.managedCredentials?.slice() ?? [];
106434
+ }
106414
106435
  var modelAliases = Object.entries(providers).flatMap(
106415
106436
  ([providerKey, config3]) => Object.entries(config3.models).map(([modelId, def]) => ({
106416
106437
  slug: `${providerKey}/${modelId}`,
@@ -106508,6 +106529,13 @@ function isOssAllowedModel(slug2) {
106508
106529
  }
106509
106530
  var BEDROCK_MODEL_ID_ENV = "BEDROCK_MODEL_ID";
106510
106531
  var VERTEX_MODEL_ID_ENV = "VERTEX_MODEL_ID";
106532
+ var AZURE_PROVIDER = "azure";
106533
+ var AZURE_RESOURCE_NAME_ENV = "AZURE_RESOURCE_NAME";
106534
+ var AZURE_API_KEY_ENV = "AZURE_API_KEY";
106535
+ var AZURE_DEPLOYMENT_ENV = "AZURE_DEPLOYMENT";
106536
+ var AZURE_CONTEXT_ENV = "AZURE_CONTEXT";
106537
+ var AZURE_MAX_OUTPUT_ENV = "AZURE_MAX_OUTPUT";
106538
+ var AZURE_USE_CHAT_COMPLETIONS_ENV = "AZURE_USE_CHAT_COMPLETIONS";
106511
106539
  var OPENAI_COMPATIBLE_PROVIDER = "openai-compatible";
106512
106540
  var OPENAI_COMPATIBLE_BASE_URL_ENV = "OPENAI_COMPATIBLE_BASE_URL";
106513
106541
  var OPENAI_COMPATIBLE_API_KEY_ENV = "OPENAI_COMPATIBLE_API_KEY";
@@ -106527,6 +106555,8 @@ function formatMcpToolRef(agentId, toolName) {
106527
106555
  switch (agentId) {
106528
106556
  case "claude":
106529
106557
  return `mcp__${pullfrogMcpName}__${toolName}`;
106558
+ case "codex":
106559
+ return `${pullfrogMcpName}__${toolName}`;
106530
106560
  case "opencode":
106531
106561
  return `${pullfrogMcpName}_${toolName}`;
106532
106562
  default:
@@ -107028,7 +107058,8 @@ import { pipeline } from "node:stream/promises";
107028
107058
  async function installFromNpmTarball(params) {
107029
107059
  const tempDir = process.env.PULLFROG_TEMP_DIR;
107030
107060
  if (!tempDir) throw new Error("PULLFROG_TEMP_DIR is not set");
107031
- const extractedDir = join2(tempDir, "package");
107061
+ const packageRoot = join2(tempDir, `npm-${params.packageName.replace(/[^a-z0-9]+/gi, "-")}`);
107062
+ const extractedDir = join2(packageRoot, "package");
107032
107063
  const cliPath = join2(extractedDir, params.executablePath);
107033
107064
  if (existsSync2(cliPath)) {
107034
107065
  log2.debug(`\xBB using cached binary at ${cliPath}`);
@@ -107054,7 +107085,8 @@ async function installFromNpmTarball(params) {
107054
107085
  }
107055
107086
  }
107056
107087
  log2.debug(`\xBB installing ${params.packageName}@${resolvedVersion}...`);
107057
- const tarballPath = join2(tempDir, "package.tgz");
107088
+ mkdirSync(packageRoot, { recursive: true });
107089
+ const tarballPath = join2(packageRoot, "package.tgz");
107058
107090
  const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org";
107059
107091
  let tarballUrl;
107060
107092
  if (params.packageName.startsWith("@")) {
@@ -107074,7 +107106,7 @@ async function installFromNpmTarball(params) {
107074
107106
  await pipeline(response.body, fileStream);
107075
107107
  log2.debug(`\xBB downloaded tarball to ${tarballPath}`);
107076
107108
  log2.debug(`\xBB extracting tarball...`);
107077
- const extractResult = spawnSync("tar", ["-xzf", tarballPath, "-C", tempDir], {
107109
+ const extractResult = spawnSync("tar", ["-xzf", tarballPath, "-C", packageRoot], {
107078
107110
  stdio: "pipe",
107079
107111
  encoding: "utf-8"
107080
107112
  });
@@ -107083,9 +107115,6 @@ async function installFromNpmTarball(params) {
107083
107115
  `Failed to extract tarball: ${extractResult.stderr || extractResult.stdout || "Unknown error"}`
107084
107116
  );
107085
107117
  }
107086
- if (!existsSync2(cliPath)) {
107087
- throw new Error(`Executable not found in extracted package at ${cliPath}`);
107088
- }
107089
107118
  if (params.installDependencies) {
107090
107119
  log2.debug(`\xBB installing dependencies for ${params.packageName}...`);
107091
107120
  const installResult = spawnSync("npm", ["install", "--production"], {
@@ -107106,6 +107135,9 @@ async function installFromNpmTarball(params) {
107106
107135
  }
107107
107136
  log2.debug(`\xBB dependencies installed`);
107108
107137
  }
107138
+ if (!existsSync2(cliPath)) {
107139
+ throw new Error(`Executable not found in extracted package at ${cliPath}`);
107140
+ }
107109
107141
  chmodSync(cliPath, 493);
107110
107142
  log2.debug(`\xBB ${params.packageName} installed at ${cliPath}`);
107111
107143
  return cliPath;
@@ -107279,6 +107311,7 @@ function resolveRunAlias(ctx) {
107279
107311
  }
107280
107312
  const model = ctx.resolvedModel;
107281
107313
  if (!model) return void 0;
107314
+ if (model.startsWith(`${AZURE_PROVIDER}/`)) return void 0;
107282
107315
  return modelAliases.find((a2) => !a2.fallback && a2.resolve === model) ?? matchHostedAnthropicAlias(model);
107283
107316
  }
107284
107317
  function ossEffortFloor(ctx) {
@@ -107317,7 +107350,7 @@ var import_semver = __toESM(require_semver2(), 1);
107317
107350
  // package.json
107318
107351
  var package_default = {
107319
107352
  name: "pullfrog",
107320
- version: "0.1.57",
107353
+ version: "0.1.59",
107321
107354
  type: "module",
107322
107355
  bin: {
107323
107356
  pullfrog: "dist/cli.mjs",
@@ -107349,6 +107382,8 @@ var package_default = {
107349
107382
  "@clack/prompts": "^1.2.0",
107350
107383
  "@modelcontextprotocol/sdk": "^1.26.0",
107351
107384
  "@opencode-ai/sdk": "1.18.5",
107385
+ "@openai/codex": "0.147.0",
107386
+ "@openai/codex-sdk": "0.147.0",
107352
107387
  "@octokit/plugin-throttling": "^11.0.3",
107353
107388
  "@octokit/rest": "^22.0.0",
107354
107389
  "@octokit/webhooks-types": "^7.6.1",
@@ -109470,13 +109505,735 @@ var claude = agent({
109470
109505
  }
109471
109506
  });
109472
109507
 
109473
- // agents/opencode.ts
109508
+ // agents/codex.ts
109474
109509
  var core2 = __toESM(require_core(), 1);
109475
- import { spawn as nodeSpawn2 } from "node:child_process";
109476
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "node:fs";
109510
+ import { existsSync as existsSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "node:fs";
109477
109511
  import { join as join7 } from "node:path";
109478
109512
  import { performance as performance6 } from "node:perf_hooks";
109479
109513
 
109514
+ // utils/codexHome.ts
109515
+ import { execFileSync as execFileSync4 } from "node:child_process";
109516
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
109517
+ import { homedir as homedir2, userInfo } from "node:os";
109518
+ import { join as join6 } from "node:path";
109519
+ var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
109520
+ var PULLFROG_DATA_DIR = "/var/lib/pullfrog";
109521
+ function isRejectedChain(body) {
109522
+ if (!body.refresh_rejected_at) return false;
109523
+ log2.warning(
109524
+ `\xBB ${CODEX_AUTH_ENV} was rejected by OpenAI at ${body.refresh_rejected_at} and cannot be refreshed \u2014 re-run \`npx pullfrog auth codex\`. skipping it for this run.`
109525
+ );
109526
+ return true;
109527
+ }
109528
+ function installCodexAuth() {
109529
+ const raw2 = process.env[CODEX_AUTH_ENV];
109530
+ if (!raw2) return null;
109531
+ const body = parseCodexAuthBody(raw2);
109532
+ if (!body) {
109533
+ log2.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
109534
+ return null;
109535
+ }
109536
+ if (isRejectedChain(body)) return null;
109537
+ const expiresMs = decodeJwtExpMs(body.tokens.access_token) ?? 0;
109538
+ const xdgDataHome = resolveDataHome();
109539
+ const opencodeDir = join6(xdgDataHome, "opencode");
109540
+ const authPath = join6(opencodeDir, "auth.json");
109541
+ const opencodeAuth = {
109542
+ openai: {
109543
+ type: "oauth",
109544
+ refresh: body.tokens.refresh_token,
109545
+ access: body.tokens.access_token,
109546
+ expires: expiresMs,
109547
+ ...body.tokens.account_id ? { accountId: body.tokens.account_id } : {}
109548
+ }
109549
+ };
109550
+ mkdirSync5(opencodeDir, { recursive: true });
109551
+ writeFileSync5(authPath, `${JSON.stringify(opencodeAuth, null, 2)}
109552
+ `, { mode: 384 });
109553
+ process.env.XDG_DATA_HOME = xdgDataHome;
109554
+ log2.info(`\xBB installed Codex auth at ${authPath}`);
109555
+ return {
109556
+ authPath,
109557
+ xdgDataHome,
109558
+ originalRefresh: body.tokens.refresh_token,
109559
+ originalIdToken: body.tokens.id_token
109560
+ };
109561
+ }
109562
+ function installCodexHome() {
109563
+ const raw2 = process.env[CODEX_AUTH_ENV];
109564
+ if (!raw2) return null;
109565
+ const body = parseCodexAuthBody(raw2);
109566
+ if (!body) {
109567
+ log2.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
109568
+ return null;
109569
+ }
109570
+ if (isRejectedChain(body)) return null;
109571
+ if (!body.tokens.id_token) {
109572
+ log2.warning(
109573
+ `\xBB ${CODEX_AUTH_ENV} carries no id_token \u2014 the codex CLI cannot load it. re-run \`npx pullfrog auth codex\` to mint a complete credential.`
109574
+ );
109575
+ return null;
109576
+ }
109577
+ const codexHome = join6(resolveDataHome(), "codex");
109578
+ const authPath = join6(codexHome, "auth.json");
109579
+ mkdirSync5(codexHome, { recursive: true });
109580
+ writeFileSync5(authPath, stringifyCodexAuthBody(body), { mode: 384 });
109581
+ log2.info(`\xBB installed Codex auth at ${authPath}`);
109582
+ return { codexHome, authPath, originalRefresh: body.tokens.refresh_token };
109583
+ }
109584
+ function resolveDataHome() {
109585
+ if (process.env.CI !== "true") return join6(homedir2(), ".local", "share");
109586
+ bootstrapPullfrogDataDir();
109587
+ return PULLFROG_DATA_DIR;
109588
+ }
109589
+ function bootstrapPullfrogDataDir() {
109590
+ const user = userInfo().username;
109591
+ let primaryGroup;
109592
+ try {
109593
+ primaryGroup = execFileSync4("id", ["-gn", user], { stdio: "pipe", encoding: "utf-8" }).trim();
109594
+ } catch {
109595
+ primaryGroup = user;
109596
+ }
109597
+ try {
109598
+ execFileSync4("sudo", ["-n", "mkdir", "-p", PULLFROG_DATA_DIR], { stdio: "pipe" });
109599
+ execFileSync4("sudo", ["-n", "chown", `${user}:${primaryGroup}`, PULLFROG_DATA_DIR], {
109600
+ stdio: "pipe"
109601
+ });
109602
+ execFileSync4("sudo", ["-n", "chmod", "700", PULLFROG_DATA_DIR], { stdio: "pipe" });
109603
+ } catch (err) {
109604
+ throw new Error(
109605
+ `failed to bootstrap ${PULLFROG_DATA_DIR} (required for codex auth in CI): ${err instanceof Error ? err.message : String(err)}. the MCP shell's mount-namespace sandbox cannot protect the auth file when it lives under $HOME, and silently falling back would contradict the "three independent layers" claim in wiki/codex-auth.md. passwordless sudo is required for codex auth on this runner \u2014 either configure it, or remove CODEX_AUTH_JSON from the run.`
109606
+ );
109607
+ }
109608
+ }
109609
+
109610
+ // utils/secrets.ts
109611
+ var SENSITIVE_PATTERNS = [
109612
+ /_KEY$/i,
109613
+ /_SECRET$/i,
109614
+ /_TOKEN$/i,
109615
+ /_PASSWORD$/i,
109616
+ /_CREDENTIAL$/i
109617
+ ];
109618
+ function isSensitiveEnvName(key) {
109619
+ return SENSITIVE_PATTERNS.some((p) => p.test(key));
109620
+ }
109621
+ var SAFE_ENV_PREFIXES = ["GITHUB_", "RUNNER_", "JAVA_HOME_", "GOROOT_"];
109622
+ var SAFE_ENV_NAMES = /* @__PURE__ */ new Set([
109623
+ // system
109624
+ "CI",
109625
+ "HOME",
109626
+ "LANG",
109627
+ "LOGNAME",
109628
+ "PATH",
109629
+ "SHELL",
109630
+ "SHLVL",
109631
+ "TERM",
109632
+ "TMPDIR",
109633
+ "TZ",
109634
+ "USER",
109635
+ "XDG_CONFIG_HOME",
109636
+ "XDG_RUNTIME_DIR",
109637
+ "DEBIAN_FRONTEND",
109638
+ // runner image toolchain
109639
+ "ACCEPT_EULA",
109640
+ "AGENT_TOOLSDIRECTORY",
109641
+ "ANDROID_HOME",
109642
+ "ANDROID_NDK",
109643
+ "ANDROID_NDK_HOME",
109644
+ "ANDROID_NDK_LATEST_HOME",
109645
+ "ANDROID_NDK_ROOT",
109646
+ "ANDROID_SDK_ROOT",
109647
+ "ANT_HOME",
109648
+ "AZURE_EXTENSION_DIR",
109649
+ "BOOTSTRAP_HASKELL_NONINTERACTIVE",
109650
+ "CHROME_BIN",
109651
+ "CHROMEWEBDRIVER",
109652
+ "CONDA",
109653
+ "DOTNET_MULTILEVEL_LOOKUP",
109654
+ "DOTNET_NOLOGO",
109655
+ "DOTNET_SKIP_FIRST_TIME_EXPERIENCE",
109656
+ "EDGEWEBDRIVER",
109657
+ "GECKOWEBDRIVER",
109658
+ "GHCUP_INSTALL_BASE_PREFIX",
109659
+ "GRADLE_HOME",
109660
+ "JAVA_HOME",
109661
+ "HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS",
109662
+ "HOMEBREW_NO_AUTO_UPDATE",
109663
+ "ImageOS",
109664
+ "ImageVersion",
109665
+ "NVM_DIR",
109666
+ "PIPX_BIN_DIR",
109667
+ "PIPX_HOME",
109668
+ "PSModulePath",
109669
+ "SELENIUM_JAR_PATH",
109670
+ "SGX_AESM_ADDR",
109671
+ "SWIFT_PATH",
109672
+ "VCPKG_INSTALLATION_ROOT"
109673
+ ]);
109674
+ var _userAllowlist = null;
109675
+ function setEnvAllowlist(raw2) {
109676
+ const names = raw2.split("\n").map((line) => line.trim()).filter(Boolean);
109677
+ _userAllowlist = new Set(names);
109678
+ }
109679
+ function isSafeEnvVar(key) {
109680
+ if (SAFE_ENV_NAMES.has(key)) return true;
109681
+ return SAFE_ENV_PREFIXES.some((p) => key.startsWith(p));
109682
+ }
109683
+ var WORKFLOW_COMMAND_ENV_NAMES = [
109684
+ "GITHUB_ENV",
109685
+ "GITHUB_PATH",
109686
+ "GITHUB_OUTPUT",
109687
+ "GITHUB_STATE",
109688
+ "GITHUB_STEP_SUMMARY",
109689
+ // dropping the five paths above but keeping RUNNER_TEMP would be theatre:
109690
+ // the file-command files live in `$RUNNER_TEMP/_runner_file_commands/`, so the
109691
+ // directory is one `ls` away. nothing in the install path reads it (only our
109692
+ // own leak-surface wipe in `setup.ts`, which runs in the parent), and package
109693
+ // managers use TMPDIR.
109694
+ "RUNNER_TEMP"
109695
+ ];
109696
+ function filterEnvForUntrustedCode() {
109697
+ const env2 = filterEnv();
109698
+ for (const name of WORKFLOW_COMMAND_ENV_NAMES) delete env2[name];
109699
+ return env2;
109700
+ }
109701
+ function filterEnv() {
109702
+ const filtered = {};
109703
+ for (const [key, value2] of Object.entries(process.env)) {
109704
+ if (value2 === void 0) continue;
109705
+ const userAllowed = _userAllowlist?.has(key) ?? false;
109706
+ if (isSensitiveEnvName(key) && !userAllowed) continue;
109707
+ if (isSafeEnvVar(key) || userAllowed) {
109708
+ filtered[key] = value2;
109709
+ }
109710
+ }
109711
+ return filtered;
109712
+ }
109713
+ function resolveEnv(mode) {
109714
+ if (mode === "inherit") {
109715
+ return process.env;
109716
+ }
109717
+ if (mode === "restricted" || mode === void 0) {
109718
+ return filterEnv();
109719
+ }
109720
+ return { ...filterEnv(), ...mode };
109721
+ }
109722
+
109723
+ // agents/codex.ts
109724
+ var CODEX_TARGETS = {
109725
+ "linux-x64": "x86_64-unknown-linux-musl",
109726
+ "linux-arm64": "aarch64-unknown-linux-musl",
109727
+ "darwin-x64": "x86_64-apple-darwin",
109728
+ "darwin-arm64": "aarch64-apple-darwin",
109729
+ "win32-x64": "x86_64-pc-windows-msvc",
109730
+ "win32-arm64": "aarch64-pc-windows-msvc"
109731
+ };
109732
+ async function installCodexCli() {
109733
+ const target = CODEX_TARGETS[`${process.platform}-${process.arch}`];
109734
+ if (!target) {
109735
+ throw new Error(`the codex CLI ships no binary for ${process.platform}-${process.arch}`);
109736
+ }
109737
+ const binary = process.platform === "win32" ? "codex.exe" : "codex";
109738
+ return await installFromNpmTarball({
109739
+ packageName: "@openai/codex",
109740
+ version: getDevDependencyVersion("@openai/codex"),
109741
+ executablePath: `node_modules/@openai/codex-${process.platform}-${process.arch}/vendor/${target}/bin/${binary}`,
109742
+ installDependencies: true
109743
+ });
109744
+ }
109745
+ var CODEX_DISABLED_FEATURES = [
109746
+ // browser / desktop control — network reach and arbitrary JS
109747
+ "browser_use",
109748
+ "browser_use_external",
109749
+ "browser_use_full_cdp_access",
109750
+ "computer_use",
109751
+ "in_app_browser",
109752
+ // third-party code loaded into the agent process
109753
+ "plugins",
109754
+ "remote_plugin",
109755
+ "plugin_sharing",
109756
+ "skill_mcp_dependency_install",
109757
+ // lifecycle hooks run commands sourced from repo config, which a PR can edit
109758
+ "hooks",
109759
+ // connectors
109760
+ "apps",
109761
+ // ungated subagent fan-out (see the file header)
109762
+ "multi_agent",
109763
+ "multi_agent_v2"
109764
+ ];
109765
+ var CODEX_SHELL_FEATURES = ["shell_tool", "unified_exec"];
109766
+ function tomlBool(entries, value2) {
109767
+ return entries.map((name) => `${name} = ${value2}`).join("\n");
109768
+ }
109769
+ function securityOverrideFlags(params) {
109770
+ const shellEnabled = params.ctx.payload.shell === "enabled";
109771
+ const features = [
109772
+ ...CODEX_DISABLED_FEATURES.map((name) => `${name}=false`),
109773
+ ...CODEX_SHELL_FEATURES.map((name) => `${name}=${shellEnabled}`)
109774
+ ];
109775
+ return [
109776
+ "-c",
109777
+ `approval_policy=${JSON.stringify("never")}`,
109778
+ "-c",
109779
+ `sandbox_mode=${JSON.stringify(params.sandboxMode)}`,
109780
+ // the sandbox's EXTENT has to be locked too, not just its mode: a repo
109781
+ // setting `writable_roots = ["/"]` walked straight out of the checkout with
109782
+ // `sandbox_mode` already flag-locked (measured — apply_patch wrote a file
109783
+ // under $HOME).
109784
+ "-c",
109785
+ `sandbox_workspace_write.writable_roots=[${JSON.stringify(params.repoDir)}]`,
109786
+ "-c",
109787
+ "sandbox_workspace_write.network_access=true",
109788
+ // our own MCP server, for a third time in the same class: a repo pointing
109789
+ // `mcp_servers.pullfrog.url` at its own endpoint (measured — `codex mcp get`
109790
+ // reported the repo's URL) turns every pullfrog tool call into an
109791
+ // exfiltration channel, carrying diffs, file contents and cross-repo
109792
+ // learnings to whoever it names.
109793
+ "-c",
109794
+ `mcp_servers.${pullfrogMcpName}.url=${JSON.stringify(params.mcpServerUrl)}`,
109795
+ "-c",
109796
+ `mcp_servers.${pullfrogMcpName}.${APPROVAL_MODE_KEY}=${JSON.stringify("approve")}`,
109797
+ "-c",
109798
+ `mcp_servers.${pullfrogMcpName}.required=true`,
109799
+ "-c",
109800
+ `mcp_servers.${pullfrogMcpName}.tool_timeout_sec=660`,
109801
+ // `enabled` closes the silent-DoS variant: a repo setting it false would
109802
+ // otherwise strip every GitHub tool and leave the agent narrating failure.
109803
+ "-c",
109804
+ `mcp_servers.${pullfrogMcpName}.enabled=true`,
109805
+ // not a boundary, but the same override: a repo silently re-pointing the
109806
+ // model spends the account's money on a pick it never made, and makes the
109807
+ // "Using `…`" run footer a lie about what actually ran.
109808
+ ...params.model ? ["-c", `model=${JSON.stringify(params.model)}`] : [],
109809
+ ...params.effortRung ? ["-c", `model_reasoning_effort=${JSON.stringify(params.effortRung)}`] : [],
109810
+ ...features.flatMap((entry) => ["-c", `features.${entry}`])
109811
+ ];
109812
+ }
109813
+ var CODEX_EFFORTS = ["minimal", "low", "medium", "high", "xhigh"];
109814
+ function resolveCodexModel(ctx) {
109815
+ const specifier = ctx.payload.proxyModel ?? ctx.resolvedModel;
109816
+ if (!specifier) return void 0;
109817
+ try {
109818
+ if (getModelProvider(specifier) === "openai") return stripProviderPrefix(specifier);
109819
+ } catch {
109820
+ }
109821
+ log2.warning(`\xBB ${specifier} isn't an OpenAI model \u2014 running codex on its own default instead`);
109822
+ return void 0;
109823
+ }
109824
+ var APPROVAL_MODE_KEY = "default_tools_approval_mode";
109825
+ var APPROVE_TOOLS = `${APPROVAL_MODE_KEY} = "approve"`;
109826
+ function repoMcpApprovals(repoDir) {
109827
+ const path4 = join7(repoDir, ".codex", "config.toml");
109828
+ if (!existsSync4(path4)) return [];
109829
+ const out = [];
109830
+ let section = [];
109831
+ let isServerTable = false;
109832
+ function flush() {
109833
+ if (section.length === 0) return;
109834
+ const declared = section.some((line) => line.trimStart().startsWith(APPROVAL_MODE_KEY));
109835
+ if (isServerTable && !declared) section.splice(1, 0, APPROVE_TOOLS);
109836
+ out.push(...section, "");
109837
+ section = [];
109838
+ }
109839
+ for (const line of readFileSync3(path4, "utf8").split("\n")) {
109840
+ const header = line.trim().match(/^\[(?<key>[^\]]+)\]$/);
109841
+ if (!header?.groups) {
109842
+ if (section.length > 0) section.push(line);
109843
+ continue;
109844
+ }
109845
+ flush();
109846
+ const key = header.groups.key.trim();
109847
+ if (!key.startsWith("mcp_servers.")) continue;
109848
+ const name = key.slice("mcp_servers.".length).replace(/^"([^"]*)"/, "$1");
109849
+ if (name === pullfrogMcpName || name.startsWith(`${pullfrogMcpName}.`)) continue;
109850
+ isServerTable = !name.includes(".");
109851
+ section = [line.trim()];
109852
+ }
109853
+ flush();
109854
+ return out;
109855
+ }
109856
+ function warnIfNativeEditsUnavailable(ctx) {
109857
+ if (process.platform !== "linux" || ctx.payload.shell !== "disabled") return;
109858
+ log2.warning(
109859
+ "\xBB this run cannot change files: codex's native write tool depends on bubblewrap, which Ubuntu 24.04 blocks (https://github.com/openai/codex/issues/29908), and the MCP shell is disabled for this repo. read-only work is unaffected \u2014 set `shell: restricted` if the agent needs to edit anything."
109860
+ );
109861
+ }
109862
+ function writeCodexConfig(params) {
109863
+ const shellEnabled = params.ctx.payload.shell === "enabled";
109864
+ const sandboxMode = params.ctx.payload.push === "disabled" ? "read-only" : "workspace-write";
109865
+ const repoDir = process.cwd();
109866
+ const config3 = [
109867
+ "# written by pullfrog",
109868
+ // "never" keeps the run non-interactive WITHOUT
109869
+ // --dangerously-bypass-approvals-and-sandbox, which would also switch the
109870
+ // sandbox off and let the native apply_patch tool write anywhere.
109871
+ 'approval_policy = "never"',
109872
+ `sandbox_mode = ${JSON.stringify(sandboxMode)}`,
109873
+ // matches opencode's `webfetch: "allow"` — research is part of the job and
109874
+ // neither harness gates it on a permission.
109875
+ 'web_search = "live"',
109876
+ ...params.model ? [`model = ${JSON.stringify(params.model)}`] : [],
109877
+ ...params.effortRung ? [`model_reasoning_effort = ${JSON.stringify(params.effortRung)}`] : [],
109878
+ "",
109879
+ "[features]",
109880
+ tomlBool(CODEX_DISABLED_FEATURES, false),
109881
+ tomlBool(CODEX_SHELL_FEATURES, shellEnabled),
109882
+ "",
109883
+ "[sandbox_workspace_write]",
109884
+ "network_access = true",
109885
+ // the checkout, named explicitly rather than left to `workspace-write` to
109886
+ // imply. does NOT fix the Linux native-write failure — nothing here does;
109887
+ // see wiki/codex-agent.md "The Linux native-write blocker".
109888
+ `writable_roots = [${JSON.stringify(repoDir)}]`,
109889
+ "",
109890
+ `[mcp_servers.${pullfrogMcpName}]`,
109891
+ `url = ${JSON.stringify(params.ctx.mcpServerUrl)}`,
109892
+ // codex's default MCP approval mode PROMPTS for anything it can't read as
109893
+ // read-only, and a `codex exec` run has nobody to answer — the call comes
109894
+ // back "user cancelled MCP tool call" and the agent silently accomplishes
109895
+ // nothing. our own server is the trusted one; the boundary is what we
109896
+ // register, not a second confirmation.
109897
+ APPROVE_TOOLS,
109898
+ // a run whose GitHub tools never registered can't do its job, so fail loudly
109899
+ // rather than let the agent improvise without them.
109900
+ "required = true",
109901
+ // mirrors the opencode harness's 660s: `checkout_pr` runs a multi-minute
109902
+ // fetch on large repos and enforces its own 600s deadline, so a shorter
109903
+ // client abort turns a working checkout into a spurious tool error.
109904
+ "tool_timeout_sec = 660",
109905
+ "",
109906
+ ...repoMcpApprovals(repoDir),
109907
+ // UNTRUSTED, deliberately, and this one line is the whole config-precedence
109908
+ // boundary. Codex loads `<repo>/.codex/config.toml` as a layer that
109909
+ // OUTRANKS ours (project 25 vs user 20), and its denylist strips none of
109910
+ // `sandbox_mode`, `approval_policy`, `features` or `mcp_servers` — so a
109911
+ // fork PR could ship a config re-enabling the native shell. Marking the
109912
+ // checkout untrusted makes that layer load `disabled`, closing the class
109913
+ // outright rather than key by key: `RawMcpServerConfig` alone has ~26
109914
+ // fields and enumeration can never finish.
109915
+ //
109916
+ // It costs nothing. `codex exec`'s only startup gate is a GIT-REPO check
109917
+ // (`exec/src/lib.rs:796` — `get_git_repo_root(...).is_none()`, whose error
109918
+ // text unhelpfully says "Not inside a trusted directory"), so an untrusted
109919
+ // checkout still runs. And the repo's own MCP servers survive, because
109920
+ // `repoMcpApprovals` above copies them into OUR layer — which is why that
109921
+ // copying exists at all.
109922
+ //
109923
+ // Measured both ways at 0.147.0: `trusted` → the repo's `model` won;
109924
+ // `untrusted` → it did not, and the run proceeded normally.
109925
+ `[projects.${JSON.stringify(repoDir)}]`,
109926
+ 'trust_level = "untrusted"',
109927
+ ""
109928
+ ].join("\n");
109929
+ writeFileSync6(join7(params.codexHome, "config.toml"), config3, { mode: 384 });
109930
+ log2.info(`\xBB codex config: sandbox=${sandboxMode}, nativeShell=${shellEnabled}`);
109931
+ }
109932
+ function toTodoWriteInput(item) {
109933
+ return {
109934
+ todos: item.items.map((todo, index) => ({
109935
+ id: String(index),
109936
+ content: todo.text,
109937
+ status: todo.completed ? "completed" : "pending"
109938
+ }))
109939
+ };
109940
+ }
109941
+ async function runCodex2(params) {
109942
+ const startTime = performance6.now();
109943
+ const thinkingTimer = new ThinkingTimer();
109944
+ const output = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
109945
+ const recentStderr = [];
109946
+ let eventCount = 0;
109947
+ let finalOutput = "";
109948
+ let threadId;
109949
+ let turnError = null;
109950
+ let lastProviderError = null;
109951
+ let stdoutBuffer = "";
109952
+ const tokens = { input: 0, cacheRead: 0, output: 0 };
109953
+ function buildUsage2() {
109954
+ if (tokens.input === 0 && tokens.output === 0) return void 0;
109955
+ return {
109956
+ agent: "codex",
109957
+ inputTokens: tokens.input,
109958
+ outputTokens: tokens.output,
109959
+ cacheReadTokens: tokens.cacheRead || void 0
109960
+ };
109961
+ }
109962
+ function onItem(item, phase) {
109963
+ switch (item.type) {
109964
+ case "agent_message":
109965
+ if (phase === "completed" && item.text.trim()) {
109966
+ finalOutput = item.text.trim();
109967
+ log2.box(finalOutput, { title: "Pullfrog" });
109968
+ }
109969
+ return;
109970
+ case "reasoning":
109971
+ if (phase === "completed" && item.text.trim()) {
109972
+ log2.box(item.text.trim(), { title: "Pullfrog" });
109973
+ }
109974
+ return;
109975
+ case "command_execution":
109976
+ if (phase === "started") {
109977
+ thinkingTimer.markToolCall();
109978
+ log2.toolCall({ toolName: "shell", input: { command: item.command } });
109979
+ return;
109980
+ }
109981
+ thinkingTimer.markToolResult();
109982
+ log2.startGroup("shell output");
109983
+ log2.info(item.aggregated_output || "");
109984
+ log2.endGroup();
109985
+ return;
109986
+ case "mcp_tool_call":
109987
+ if (phase === "started") {
109988
+ thinkingTimer.markToolCall();
109989
+ params.onToolUse?.({ toolName: item.tool, input: item.arguments });
109990
+ log2.toolCall({ toolName: `${item.server}__${item.tool}`, input: item.arguments });
109991
+ if (item.tool.includes("report_progress")) params.todoTracker?.cancel();
109992
+ return;
109993
+ }
109994
+ thinkingTimer.markToolResult();
109995
+ if (item.error) log2.info(`\xBB tool error: ${item.error.message}`);
109996
+ return;
109997
+ case "file_change":
109998
+ if (phase === "completed") {
109999
+ const paths = item.changes.map((c2) => `${c2.kind} ${c2.path}`).join(", ");
110000
+ log2.info(`\xBB apply_patch (${item.status}): ${paths}`);
110001
+ }
110002
+ return;
110003
+ case "web_search":
110004
+ if (phase === "started") log2.toolCall({ toolName: "web_search", input: item.query });
110005
+ return;
110006
+ case "todo_list":
110007
+ if (params.todoTracker?.enabled) params.todoTracker.update(toTodoWriteInput(item));
110008
+ return;
110009
+ case "error":
110010
+ log2.info(`\xBB ${item.message}`);
110011
+ return;
110012
+ default:
110013
+ item;
110014
+ }
110015
+ }
110016
+ function onEvent(event) {
110017
+ switch (event.type) {
110018
+ case "thread.started":
110019
+ threadId = event.thread_id;
110020
+ return;
110021
+ case "turn.completed":
110022
+ tokens.input += event.usage.input_tokens ?? 0;
110023
+ tokens.cacheRead += event.usage.cached_input_tokens ?? 0;
110024
+ tokens.output += event.usage.output_tokens ?? 0;
110025
+ return;
110026
+ case "turn.failed":
110027
+ turnError = event.error.message;
110028
+ log2.info(`\xBB turn failed: ${turnError}`);
110029
+ return;
110030
+ case "error":
110031
+ turnError = event.message;
110032
+ log2.info(`\xBB codex error: ${event.message}`);
110033
+ return;
110034
+ case "item.started":
110035
+ onItem(event.item, "started");
110036
+ return;
110037
+ case "item.completed":
110038
+ onItem(event.item, "completed");
110039
+ return;
110040
+ case "item.updated":
110041
+ if (event.item.type === "todo_list") onItem(event.item, "started");
110042
+ return;
110043
+ case "turn.started":
110044
+ return;
110045
+ default:
110046
+ event;
110047
+ }
110048
+ }
110049
+ try {
110050
+ const result = await spawn3({
110051
+ cmd: params.cliPath,
110052
+ args: params.args,
110053
+ cwd: params.cwd,
110054
+ env: params.env,
110055
+ activityTimeout: AGENT_ACTIVITY_TIMEOUT_MS,
110056
+ onActivityTimeout: params.onActivityTimeout,
110057
+ // stdin MUST be `ignore`: codex reads a piped stdin as an extra `<stdin>`
110058
+ // prompt block and blocks on EOF, so an inherited pipe hangs the run
110059
+ // before the first model call.
110060
+ stdio: ["ignore", "pipe", "pipe"],
110061
+ killGroup: true,
110062
+ retain: "none",
110063
+ onStdout: (chunk) => {
110064
+ output.append(chunk);
110065
+ markActivity();
110066
+ stdoutBuffer += chunk;
110067
+ const lines = stdoutBuffer.split("\n");
110068
+ stdoutBuffer = lines.pop() || "";
110069
+ for (const line of lines) {
110070
+ const trimmed = line.trim();
110071
+ if (!trimmed) continue;
110072
+ let event;
110073
+ try {
110074
+ event = JSON.parse(trimmed);
110075
+ } catch {
110076
+ log2.debug(`\xBB non-JSON stdout line: ${trimmed.substring(0, 200)}`);
110077
+ continue;
110078
+ }
110079
+ eventCount++;
110080
+ log2.debug(JSON.stringify(event, null, 2));
110081
+ const idleMs = getIdleMs();
110082
+ if (idleMs > 1e4) {
110083
+ log2.info(
110084
+ `\xBB no activity for ${(idleMs / 1e3).toFixed(1)}s (${eventCount} events processed so far)`
110085
+ );
110086
+ }
110087
+ markActivity();
110088
+ onEvent(event);
110089
+ }
110090
+ },
110091
+ onStderr: (chunk) => {
110092
+ const trimmed = chunk.trim();
110093
+ if (!trimmed) return;
110094
+ recentStderr.push(trimmed);
110095
+ if (recentStderr.length > MAX_STDERR_LINES) recentStderr.shift();
110096
+ const match3 = findProviderErrorMatch(trimmed);
110097
+ if (match3) {
110098
+ lastProviderError = match3.label;
110099
+ log2.info(`\xBB provider error detected (${match3.label}): ${match3.excerpt}`);
110100
+ } else {
110101
+ log2.debug(trimmed);
110102
+ }
110103
+ }
110104
+ });
110105
+ const duration4 = performance6.now() - startTime;
110106
+ log2.info(`\xBB codex completed in ${Math.round(duration4)}ms with exit code ${result.exitCode}`);
110107
+ const usage = buildUsage2();
110108
+ if (usage) {
110109
+ logTokenTable({
110110
+ input: tokens.input - tokens.cacheRead,
110111
+ cacheRead: tokens.cacheRead,
110112
+ cacheWrite: 0,
110113
+ output: tokens.output
110114
+ });
110115
+ }
110116
+ if (result.exitCode !== 0 || turnError) {
110117
+ const stderrSnapshot = recentStderr.join("\n");
110118
+ const error52 = turnError || stderrSnapshot || lastProviderError && `provider error: ${lastProviderError}` || `unknown error - no output from Codex CLI (exit ${result.exitCode})`;
110119
+ log2.error(`codex exited with code ${result.exitCode}: ${error52}`);
110120
+ return { success: false, output: finalOutput || output.toString(), error: error52, usage, threadId };
110121
+ }
110122
+ return { success: true, output: finalOutput || output.toString(), usage, threadId };
110123
+ } catch (error52) {
110124
+ const duration4 = performance6.now() - startTime;
110125
+ const message = error52 instanceof Error ? error52.message : String(error52);
110126
+ const isActivityTimeout = error52 instanceof SpawnTimeoutError && error52.code === SPAWN_ACTIVITY_TIMEOUT_CODE;
110127
+ const diagnosis = lastProviderError ? `likely cause: ${lastProviderError}` : eventCount === 0 ? "codex produced 0 stdout events - check if the API is reachable" : `${eventCount} events were processed before the hang`;
110128
+ log2.info(
110129
+ `\xBB codex ${isActivityTimeout ? "hung" : "failed"} after ${(duration4 / 1e3).toFixed(1)}s: ${message}`
110130
+ );
110131
+ log2.info(`\xBB diagnosis: ${diagnosis}`);
110132
+ if (recentStderr.length > 0) log2.info(`\xBB recent stderr:
110133
+ ${recentStderr.join("\n")}`);
110134
+ return {
110135
+ success: false,
110136
+ output: finalOutput || output.toString(),
110137
+ error: `${message} [${diagnosis}]`,
110138
+ usage: buildUsage2(),
110139
+ threadId
110140
+ };
110141
+ }
110142
+ }
110143
+ var codex = agent({
110144
+ name: "codex",
110145
+ install: installCodexCli,
110146
+ run: async (ctx) => {
110147
+ const cliPath = await installCodexCli();
110148
+ warnIfNativeEditsUnavailable(ctx);
110149
+ const codexHomeAuth = installCodexHome();
110150
+ const codexHome = codexHomeAuth?.codexHome ?? join7(ctx.tmpdir, ".codex");
110151
+ mkdirSync6(codexHome, { recursive: true });
110152
+ const effort = resolveRunEffort(ctx);
110153
+ if (effort.rung && !CODEX_EFFORTS.includes(effort.rung)) {
110154
+ log2.warning(`\xBB effort ${effort.rung} not sent \u2014 codex doesn't accept that level`);
110155
+ }
110156
+ writeCodexConfig({
110157
+ ctx,
110158
+ codexHome,
110159
+ model: resolveCodexModel(ctx),
110160
+ effortRung: effort.rung && CODEX_EFFORTS.includes(effort.rung) ? effort.rung : void 0
110161
+ });
110162
+ if (codexHomeAuth) {
110163
+ core2.saveState(
110164
+ "codex_writeback",
110165
+ JSON.stringify({
110166
+ apiToken: ctx.apiToken,
110167
+ authPath: codexHomeAuth.authPath,
110168
+ originalRefresh: codexHomeAuth.originalRefresh
110169
+ })
110170
+ );
110171
+ }
110172
+ const baseEnv = ctx.payload.shell === "enabled" ? process.env : filterEnv();
110173
+ const env2 = {
110174
+ ...baseEnv,
110175
+ CODEX_HOME: codexHome,
110176
+ PWD: process.cwd()
110177
+ };
110178
+ delete env2.CODEX_API_KEY;
110179
+ if (codexHomeAuth) {
110180
+ delete env2.OPENAI_API_KEY;
110181
+ } else if (process.env.OPENAI_API_KEY) {
110182
+ env2.CODEX_API_KEY = process.env.OPENAI_API_KEY;
110183
+ }
110184
+ const securityFlags = securityOverrideFlags({
110185
+ ctx,
110186
+ sandboxMode: ctx.payload.push === "disabled" ? "read-only" : "workspace-write",
110187
+ repoDir: process.cwd(),
110188
+ mcpServerUrl: ctx.mcpServerUrl,
110189
+ model: resolveCodexModel(ctx),
110190
+ effortRung: effort.rung && CODEX_EFFORTS.includes(effort.rung) ? effort.rung : void 0
110191
+ });
110192
+ const runnerArgs = { cliPath, cwd: process.cwd(), env: env2, todoTracker: ctx.todoTracker };
110193
+ const initial = await runCodex2({
110194
+ ...runnerArgs,
110195
+ args: [...securityFlags, "exec", "--json", ctx.instructions.full],
110196
+ onActivityTimeout: ctx.onActivityTimeout,
110197
+ onToolUse: ctx.onToolUse
110198
+ });
110199
+ const result = await runPostRunRetryLoop({
110200
+ ctx,
110201
+ initialResult: initial,
110202
+ initialUsage: initial.usage,
110203
+ reflectionPrompt: buildReflectionPrompt(ctx.toolState),
110204
+ // a resume needs the thread codex recorded. without one there is nothing
110205
+ // to continue, and `resume --last` would be worse than not resuming: it
110206
+ // picks the newest recorded session in this cwd, which on a self-hosted
110207
+ // runner with a shared CODEX_HOME is somebody else's run.
110208
+ canResume: (r2) => r2.threadId !== void 0,
110209
+ resume: async (c2) => runCodex2({
110210
+ ...runnerArgs,
110211
+ args: [
110212
+ ...securityFlags,
110213
+ "exec",
110214
+ "resume",
110215
+ "--json",
110216
+ c2.previousResult.threadId ?? "",
110217
+ c2.prompt
110218
+ ]
110219
+ })
110220
+ });
110221
+ if (result.success) {
110222
+ await ctx.todoTracker?.flush();
110223
+ } else {
110224
+ ctx.todoTracker?.cancel();
110225
+ }
110226
+ return result;
110227
+ }
110228
+ });
110229
+
110230
+ // agents/opencode.ts
110231
+ var core3 = __toESM(require_core(), 1);
110232
+ import { spawn as nodeSpawn2 } from "node:child_process";
110233
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
110234
+ import { join as join8 } from "node:path";
110235
+ import { performance as performance7 } from "node:perf_hooks";
110236
+
109480
110237
  // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/core/serverSentEvents.gen.js
109481
110238
  var createSseClient = ({ onRequest, onSseError, onSseEvent, responseTransformer, responseValidator, sseDefaultRetryDelay, sseMaxRetryAttempts, sseMaxRetryDelay, sseSleepFn, url: url4, ...options }) => {
109482
110239
  let lastEventId;
@@ -114883,71 +115640,6 @@ var import_cross_spawn = __toESM(require_cross_spawn(), 1);
114883
115640
  // agents/opencode.ts
114884
115641
  var import_undici = __toESM(require_undici2(), 1);
114885
115642
 
114886
- // utils/codexHome.ts
114887
- import { execFileSync as execFileSync4 } from "node:child_process";
114888
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
114889
- import { homedir as homedir2, userInfo } from "node:os";
114890
- import { join as join6 } from "node:path";
114891
- var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
114892
- var PULLFROG_DATA_DIR = "/var/lib/pullfrog";
114893
- function installCodexAuth() {
114894
- const raw2 = process.env[CODEX_AUTH_ENV];
114895
- if (!raw2) return null;
114896
- const body = parseCodexAuthBody(raw2);
114897
- if (!body) {
114898
- log2.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
114899
- return null;
114900
- }
114901
- const expiresMs = decodeJwtExpMs(body.tokens.access_token) ?? 0;
114902
- const xdgDataHome = resolveDataHome();
114903
- const opencodeDir = join6(xdgDataHome, "opencode");
114904
- const authPath = join6(opencodeDir, "auth.json");
114905
- const opencodeAuth = {
114906
- openai: {
114907
- type: "oauth",
114908
- refresh: body.tokens.refresh_token,
114909
- access: body.tokens.access_token,
114910
- expires: expiresMs,
114911
- ...body.tokens.account_id ? { accountId: body.tokens.account_id } : {}
114912
- }
114913
- };
114914
- mkdirSync5(opencodeDir, { recursive: true });
114915
- writeFileSync5(authPath, `${JSON.stringify(opencodeAuth, null, 2)}
114916
- `, { mode: 384 });
114917
- process.env.XDG_DATA_HOME = xdgDataHome;
114918
- log2.info(`\xBB installed Codex auth at ${authPath}`);
114919
- return {
114920
- authPath,
114921
- xdgDataHome,
114922
- originalRefresh: body.tokens.refresh_token
114923
- };
114924
- }
114925
- function resolveDataHome() {
114926
- if (process.env.CI !== "true") return join6(homedir2(), ".local", "share");
114927
- bootstrapPullfrogDataDir();
114928
- return PULLFROG_DATA_DIR;
114929
- }
114930
- function bootstrapPullfrogDataDir() {
114931
- const user = userInfo().username;
114932
- let primaryGroup;
114933
- try {
114934
- primaryGroup = execFileSync4("id", ["-gn", user], { stdio: "pipe", encoding: "utf-8" }).trim();
114935
- } catch {
114936
- primaryGroup = user;
114937
- }
114938
- try {
114939
- execFileSync4("sudo", ["-n", "mkdir", "-p", PULLFROG_DATA_DIR], { stdio: "pipe" });
114940
- execFileSync4("sudo", ["-n", "chown", `${user}:${primaryGroup}`, PULLFROG_DATA_DIR], {
114941
- stdio: "pipe"
114942
- });
114943
- execFileSync4("sudo", ["-n", "chmod", "700", PULLFROG_DATA_DIR], { stdio: "pipe" });
114944
- } catch (err) {
114945
- throw new Error(
114946
- `failed to bootstrap ${PULLFROG_DATA_DIR} (required for codex auth in CI): ${err instanceof Error ? err.message : String(err)}. the MCP shell's mount-namespace sandbox cannot protect the auth file when it lives under $HOME, and silently falling back would contradict the "three independent layers" claim in wiki/codex-auth.md. passwordless sudo is required for codex auth on this runner \u2014 either configure it, or remove CODEX_AUTH_JSON from the run.`
114947
- );
114948
- }
114949
- }
114950
-
114951
115643
  // utils/worktree.ts
114952
115644
  async function dirtyTrackedPaths(params = {}) {
114953
115645
  const result = await spawn3({
@@ -115215,6 +115907,30 @@ function openAICompatibleProvider(model) {
115215
115907
  }
115216
115908
  };
115217
115909
  }
115910
+ function azureProvider(model) {
115911
+ const prefix = `${AZURE_PROVIDER}/`;
115912
+ if (!model?.startsWith(prefix)) return {};
115913
+ const deployment = model.slice(prefix.length);
115914
+ return {
115915
+ [AZURE_PROVIDER]: {
115916
+ // always sent: opencode reads it as `Boolean(options?.useCompletionUrls)`,
115917
+ // so `false` and absent are the same, and the deep merge keeps the native
115918
+ // loader's `resourceName` either way.
115919
+ options: {
115920
+ useCompletionUrls: process.env[AZURE_USE_CHAT_COMPLETIONS_ENV]?.trim() === "true"
115921
+ },
115922
+ models: {
115923
+ [deployment]: {
115924
+ name: deployment,
115925
+ limit: {
115926
+ context: Number(process.env[AZURE_CONTEXT_ENV]),
115927
+ output: Number(process.env[AZURE_MAX_OUTPUT_ENV])
115928
+ }
115929
+ }
115930
+ }
115931
+ }
115932
+ };
115933
+ }
115218
115934
  var KIMI_ENFORCERLESS_PROVIDERS = ["siliconflow", "together"];
115219
115935
  function kimiOpenRouterProviderOverrides() {
115220
115936
  return Object.fromEntries(
@@ -115306,7 +116022,8 @@ function buildSecurityConfig(ctx, model) {
115306
116022
  // see opencodeShared.ts.
115307
116023
  provider: {
115308
116024
  openrouter: { models: kimiOpenRouterProviderOverrides() },
115309
- ...openAICompatibleProvider(model)
116025
+ ...openAICompatibleProvider(model),
116026
+ ...azureProvider(model)
115310
116027
  }
115311
116028
  };
115312
116029
  if (model) {
@@ -115474,7 +116191,7 @@ function isModelOutput(ctx, part) {
115474
116191
  }
115475
116192
  async function dispatchEvent(ctx, event) {
115476
116193
  if (event.type === "message.part.updated") {
115477
- ctx.lastEventAt = performance6.now();
116194
+ ctx.lastEventAt = performance7.now();
115478
116195
  if (isModelOutput(ctx, event.properties.part)) ctx.sawModelOutput = true;
115479
116196
  await onPartUpdated(ctx, event.properties.part);
115480
116197
  return;
@@ -115541,7 +116258,7 @@ async function onToolPart(ctx, part, label, isOrchestrator) {
115541
116258
  if (toolName === "task" && status === "running" && isOrchestrator && !ctx.taskDispatchByCallID.has(toolId)) {
115542
116259
  const input = part.state.input ?? {};
115543
116260
  const dispatched = ctx.labeler.recordTaskDispatch(input);
115544
- ctx.taskDispatchByCallID.set(toolId, { label: dispatched, startedAt: performance6.now() });
116261
+ ctx.taskDispatchByCallID.set(toolId, { label: dispatched, startedAt: performance7.now() });
115545
116262
  log2.info(
115546
116263
  `\xBB dispatching subagent: ${dispatched}` + (input.subagent_type ? ` (subagent_type=${input.subagent_type})` : "")
115547
116264
  );
@@ -115571,7 +116288,7 @@ function processTerminalToolPart(ctx, part, label, isOrchestrator) {
115571
116288
  if (toolName === "task") {
115572
116289
  const dispatch = ctx.taskDispatchByCallID.get(toolId);
115573
116290
  if (dispatch) {
115574
- const dur = ((performance6.now() - dispatch.startedAt) / 1e3).toFixed(1);
116291
+ const dur = ((performance7.now() - dispatch.startedAt) / 1e3).toFixed(1);
115575
116292
  const outputStr = state.status === "completed" ? state.output : "";
115576
116293
  const preview = typeof outputStr === "string" && outputStr.length > 120 ? `${outputStr.slice(0, 120)}\u2026` : outputStr;
115577
116294
  log2.info(
@@ -115614,7 +116331,7 @@ function withLabel(label, message) {
115614
116331
  return label === ORCHESTRATOR_LABEL ? message : formatWithLabel(label, message);
115615
116332
  }
115616
116333
  async function runPromptTurn(ctx, params) {
115617
- const start = performance6.now();
116334
+ const start = performance7.now();
115618
116335
  const turnStartMs = Date.now();
115619
116336
  ctx.currentTurn = newTurn();
115620
116337
  const turn = ctx.currentTurn;
@@ -115648,7 +116365,7 @@ async function runPromptTurn(ctx, params) {
115648
116365
  } catch (err) {
115649
116366
  networkError = err instanceof Error ? err.message : String(err);
115650
116367
  }
115651
- const durationMs = performance6.now() - start;
116368
+ const durationMs = performance7.now() - start;
115652
116369
  const aggregatedUsage = await aggregateTurnUsage(ctx, turnStartMs);
115653
116370
  const usage = aggregatedUsage ?? buildUsage(turn, assistant);
115654
116371
  const finalText = turn.finalText || extractTextFromParts(returnedParts) || "";
@@ -115802,7 +116519,7 @@ function startInnerActivityWatchdog(params) {
115802
116519
  let fired = false;
115803
116520
  const id = setInterval(() => {
115804
116521
  if (fired) return;
115805
- const idleMs = performance6.now() - params.ctx.lastEventAt;
116522
+ const idleMs = performance7.now() - params.ctx.lastEventAt;
115806
116523
  const budgetMs = params.ctx.sawModelOutput ? params.timeoutMs : AGENT_FIRST_EVENT_TIMEOUT_MS;
115807
116524
  if (idleMs <= budgetMs) return;
115808
116525
  fired = true;
@@ -115841,12 +116558,12 @@ var opencode = agent({
115841
116558
  const model = vertexModel ?? (isBedrockRoute ? `amazon-bedrock/${rawModel}` : rawModel);
115842
116559
  const homeEnv = {
115843
116560
  HOME: ctx.tmpdir,
115844
- XDG_CONFIG_HOME: join7(ctx.tmpdir, ".config")
116561
+ XDG_CONFIG_HOME: join8(ctx.tmpdir, ".config")
115845
116562
  };
115846
- const opencodePluginDir = join7(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
115847
- mkdirSync6(opencodePluginDir, { recursive: true });
115848
- writeFileSync6(
115849
- join7(opencodePluginDir, PULLFROG_OPENCODE_GATE_PLUGIN_FILENAME),
116563
+ const opencodePluginDir = join8(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
116564
+ mkdirSync7(opencodePluginDir, { recursive: true });
116565
+ writeFileSync7(
116566
+ join8(opencodePluginDir, PULLFROG_OPENCODE_GATE_PLUGIN_FILENAME),
115850
116567
  buildOpencodeSubagentGateSource(ctx.subagentDeniedTools)
115851
116568
  );
115852
116569
  const agentBrowserVersion = getDevDependencyVersion("agent-browser");
@@ -115877,12 +116594,13 @@ var opencode = agent({
115877
116594
  if (codexAuth) {
115878
116595
  env2.XDG_DATA_HOME = codexAuth.xdgDataHome;
115879
116596
  delete env2.OPENAI_API_KEY;
115880
- core2.saveState(
116597
+ core3.saveState(
115881
116598
  "codex_writeback",
115882
116599
  JSON.stringify({
115883
116600
  apiToken: ctx.apiToken,
115884
116601
  authPath: codexAuth.authPath,
115885
- originalRefresh: codexAuth.originalRefresh
116602
+ originalRefresh: codexAuth.originalRefresh,
116603
+ originalIdToken: codexAuth.originalIdToken
115886
116604
  })
115887
116605
  );
115888
116606
  }
@@ -115936,7 +116654,7 @@ var opencode = agent({
115936
116654
  onToolUse: ctx.onToolUse,
115937
116655
  currentTurn: null,
115938
116656
  eventCount: 0,
115939
- lastEventAt: performance6.now(),
116657
+ lastEventAt: performance7.now(),
115940
116658
  sawModelOutput: false,
115941
116659
  promptMessageID: void 0,
115942
116660
  taskDispatchByCallID: /* @__PURE__ */ new Map(),
@@ -116046,7 +116764,7 @@ async function runTurnGuarded(ctx, fn2) {
116046
116764
  }
116047
116765
 
116048
116766
  // agents/index.ts
116049
- var agents = { claude, opencode };
116767
+ var agents = { claude, codex, opencode };
116050
116768
 
116051
116769
  // node_modules/.pnpm/@ark+util@0.56.0/node_modules/@ark/util/out/arrays.js
116052
116770
  var liftArray = (data) => Array.isArray(data) ? data : [data];
@@ -136378,7 +137096,7 @@ var require_core$2 = /* @__PURE__ */ __commonJSMin(((exports) => {
136378
137096
  Object.defineProperty(exports, "__esModule", { value: true });
136379
137097
  const id_1 = require_id2();
136380
137098
  const ref_1 = require_ref2();
136381
- const core11 = [
137099
+ const core12 = [
136382
137100
  "$schema",
136383
137101
  "$id",
136384
137102
  "$defs",
@@ -136388,7 +137106,7 @@ var require_core$2 = /* @__PURE__ */ __commonJSMin(((exports) => {
136388
137106
  id_1.default,
136389
137107
  ref_1.default
136390
137108
  ];
136391
- exports.default = core11;
137109
+ exports.default = core12;
136392
137110
  }));
136393
137111
  var require_limitNumber2 = /* @__PURE__ */ __commonJSMin(((exports) => {
136394
137112
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -138526,7 +139244,7 @@ var require_json_schema_2019_09 = /* @__PURE__ */ __commonJSMin(((exports) => {
138526
139244
  const metaSchema = require_schema$1();
138527
139245
  const applicator = require_applicator$1();
138528
139246
  const content = require_content$1();
138529
- const core11 = require_core$1();
139247
+ const core12 = require_core$1();
138530
139248
  const format2 = require_format3();
138531
139249
  const metadata = require_meta_data$1();
138532
139250
  const validation = require_validation$1();
@@ -138536,7 +139254,7 @@ var require_json_schema_2019_09 = /* @__PURE__ */ __commonJSMin(((exports) => {
138536
139254
  metaSchema,
138537
139255
  applicator,
138538
139256
  content,
138539
- core11,
139257
+ core12,
138540
139258
  with$data(this, format2),
138541
139259
  metadata,
138542
139260
  with$data(this, validation)
@@ -138976,7 +139694,7 @@ var require_json_schema_2020_12 = /* @__PURE__ */ __commonJSMin(((exports) => {
138976
139694
  const applicator = require_applicator2();
138977
139695
  const unevaluated = require_unevaluated();
138978
139696
  const content = require_content();
138979
- const core11 = require_core4();
139697
+ const core12 = require_core4();
138980
139698
  const format2 = require_format_annotation();
138981
139699
  const metadata = require_meta_data();
138982
139700
  const validation = require_validation2();
@@ -138987,7 +139705,7 @@ var require_json_schema_2020_12 = /* @__PURE__ */ __commonJSMin(((exports) => {
138987
139705
  applicator,
138988
139706
  unevaluated,
138989
139707
  content,
138990
- core11,
139708
+ core12,
138991
139709
  with$data(this, format2),
138992
139710
  metadata,
138993
139711
  with$data(this, validation)
@@ -150429,7 +151147,7 @@ var require_core$22 = /* @__PURE__ */ __commonJSMin2(((exports) => {
150429
151147
  Object.defineProperty(exports, "__esModule", { value: true });
150430
151148
  const id_1 = require_id3();
150431
151149
  const ref_1 = require_ref3();
150432
- const core11 = [
151150
+ const core12 = [
150433
151151
  "$schema",
150434
151152
  "$id",
150435
151153
  "$defs",
@@ -150439,7 +151157,7 @@ var require_core$22 = /* @__PURE__ */ __commonJSMin2(((exports) => {
150439
151157
  id_1.default,
150440
151158
  ref_1.default
150441
151159
  ];
150442
- exports.default = core11;
151160
+ exports.default = core12;
150443
151161
  }));
150444
151162
  var require_limitNumber3 = /* @__PURE__ */ __commonJSMin2(((exports) => {
150445
151163
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -152577,7 +153295,7 @@ var require_json_schema_2019_092 = /* @__PURE__ */ __commonJSMin2(((exports) =>
152577
153295
  const metaSchema = require_schema$12();
152578
153296
  const applicator = require_applicator$12();
152579
153297
  const content = require_content$12();
152580
- const core11 = require_core$12();
153298
+ const core12 = require_core$12();
152581
153299
  const format2 = require_format4();
152582
153300
  const metadata = require_meta_data$12();
152583
153301
  const validation = require_validation$12();
@@ -152587,7 +153305,7 @@ var require_json_schema_2019_092 = /* @__PURE__ */ __commonJSMin2(((exports) =>
152587
153305
  metaSchema,
152588
153306
  applicator,
152589
153307
  content,
152590
- core11,
153308
+ core12,
152591
153309
  with$data(this, format2),
152592
153310
  metadata,
152593
153311
  with$data(this, validation)
@@ -153027,7 +153745,7 @@ var require_json_schema_2020_122 = /* @__PURE__ */ __commonJSMin2(((exports) =>
153027
153745
  const applicator = require_applicator3();
153028
153746
  const unevaluated = require_unevaluated2();
153029
153747
  const content = require_content2();
153030
- const core11 = require_core5();
153748
+ const core12 = require_core5();
153031
153749
  const format2 = require_format_annotation2();
153032
153750
  const metadata = require_meta_data2();
153033
153751
  const validation = require_validation3();
@@ -153038,7 +153756,7 @@ var require_json_schema_2020_122 = /* @__PURE__ */ __commonJSMin2(((exports) =>
153038
153756
  applicator,
153039
153757
  unevaluated,
153040
153758
  content,
153041
- core11,
153759
+ core12,
153042
153760
  with$data(this, format2),
153043
153761
  metadata,
153044
153762
  with$data(this, validation)
@@ -172999,123 +173717,8 @@ var pbkdf2Async = promisify(pbkdf2);
172999
173717
 
173000
173718
  // utils/browser.ts
173001
173719
  import { execFileSync as execFileSync5, spawnSync as spawnSync4 } from "node:child_process";
173002
- import { existsSync as existsSync4 } from "node:fs";
173720
+ import { existsSync as existsSync5 } from "node:fs";
173003
173721
  import { dirname as dirname2 } from "node:path";
173004
-
173005
- // utils/secrets.ts
173006
- var SENSITIVE_PATTERNS = [
173007
- /_KEY$/i,
173008
- /_SECRET$/i,
173009
- /_TOKEN$/i,
173010
- /_PASSWORD$/i,
173011
- /_CREDENTIAL$/i
173012
- ];
173013
- function isSensitiveEnvName(key) {
173014
- return SENSITIVE_PATTERNS.some((p) => p.test(key));
173015
- }
173016
- var SAFE_ENV_PREFIXES = ["GITHUB_", "RUNNER_", "JAVA_HOME_", "GOROOT_"];
173017
- var SAFE_ENV_NAMES = /* @__PURE__ */ new Set([
173018
- // system
173019
- "CI",
173020
- "HOME",
173021
- "LANG",
173022
- "LOGNAME",
173023
- "PATH",
173024
- "SHELL",
173025
- "SHLVL",
173026
- "TERM",
173027
- "TMPDIR",
173028
- "TZ",
173029
- "USER",
173030
- "XDG_CONFIG_HOME",
173031
- "XDG_RUNTIME_DIR",
173032
- "DEBIAN_FRONTEND",
173033
- // runner image toolchain
173034
- "ACCEPT_EULA",
173035
- "AGENT_TOOLSDIRECTORY",
173036
- "ANDROID_HOME",
173037
- "ANDROID_NDK",
173038
- "ANDROID_NDK_HOME",
173039
- "ANDROID_NDK_LATEST_HOME",
173040
- "ANDROID_NDK_ROOT",
173041
- "ANDROID_SDK_ROOT",
173042
- "ANT_HOME",
173043
- "AZURE_EXTENSION_DIR",
173044
- "BOOTSTRAP_HASKELL_NONINTERACTIVE",
173045
- "CHROME_BIN",
173046
- "CHROMEWEBDRIVER",
173047
- "CONDA",
173048
- "DOTNET_MULTILEVEL_LOOKUP",
173049
- "DOTNET_NOLOGO",
173050
- "DOTNET_SKIP_FIRST_TIME_EXPERIENCE",
173051
- "EDGEWEBDRIVER",
173052
- "GECKOWEBDRIVER",
173053
- "GHCUP_INSTALL_BASE_PREFIX",
173054
- "GRADLE_HOME",
173055
- "JAVA_HOME",
173056
- "HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS",
173057
- "HOMEBREW_NO_AUTO_UPDATE",
173058
- "ImageOS",
173059
- "ImageVersion",
173060
- "NVM_DIR",
173061
- "PIPX_BIN_DIR",
173062
- "PIPX_HOME",
173063
- "PSModulePath",
173064
- "SELENIUM_JAR_PATH",
173065
- "SGX_AESM_ADDR",
173066
- "SWIFT_PATH",
173067
- "VCPKG_INSTALLATION_ROOT"
173068
- ]);
173069
- var _userAllowlist = null;
173070
- function setEnvAllowlist(raw2) {
173071
- const names = raw2.split("\n").map((line) => line.trim()).filter(Boolean);
173072
- _userAllowlist = new Set(names);
173073
- }
173074
- function isSafeEnvVar(key) {
173075
- if (SAFE_ENV_NAMES.has(key)) return true;
173076
- return SAFE_ENV_PREFIXES.some((p) => key.startsWith(p));
173077
- }
173078
- var WORKFLOW_COMMAND_ENV_NAMES = [
173079
- "GITHUB_ENV",
173080
- "GITHUB_PATH",
173081
- "GITHUB_OUTPUT",
173082
- "GITHUB_STATE",
173083
- "GITHUB_STEP_SUMMARY",
173084
- // dropping the five paths above but keeping RUNNER_TEMP would be theatre:
173085
- // the file-command files live in `$RUNNER_TEMP/_runner_file_commands/`, so the
173086
- // directory is one `ls` away. nothing in the install path reads it (only our
173087
- // own leak-surface wipe in `setup.ts`, which runs in the parent), and package
173088
- // managers use TMPDIR.
173089
- "RUNNER_TEMP"
173090
- ];
173091
- function filterEnvForUntrustedCode() {
173092
- const env2 = filterEnv();
173093
- for (const name of WORKFLOW_COMMAND_ENV_NAMES) delete env2[name];
173094
- return env2;
173095
- }
173096
- function filterEnv() {
173097
- const filtered = {};
173098
- for (const [key, value2] of Object.entries(process.env)) {
173099
- if (value2 === void 0) continue;
173100
- const userAllowed = _userAllowlist?.has(key) ?? false;
173101
- if (isSensitiveEnvName(key) && !userAllowed) continue;
173102
- if (isSafeEnvVar(key) || userAllowed) {
173103
- filtered[key] = value2;
173104
- }
173105
- }
173106
- return filtered;
173107
- }
173108
- function resolveEnv(mode) {
173109
- if (mode === "inherit") {
173110
- return process.env;
173111
- }
173112
- if (mode === "restricted" || mode === void 0) {
173113
- return filterEnv();
173114
- }
173115
- return { ...filterEnv(), ...mode };
173116
- }
173117
-
173118
- // utils/browser.ts
173119
173722
  var CHROME_PATHS = ["/usr/bin/google-chrome-stable"];
173120
173723
  var systemChromePath;
173121
173724
  function findSystemChromePath() {
@@ -173123,7 +173726,7 @@ function findSystemChromePath() {
173123
173726
  return systemChromePath || void 0;
173124
173727
  }
173125
173728
  for (const p of CHROME_PATHS) {
173126
- if (existsSync4(p)) {
173729
+ if (existsSync5(p)) {
173127
173730
  systemChromePath = p;
173128
173731
  log2.info(`found system chrome: ${p}`);
173129
173732
  return p;
@@ -173207,8 +173810,8 @@ function closeBrowserDaemon(toolState) {
173207
173810
 
173208
173811
  // mcp/checkout.ts
173209
173812
  import { createHash as createHash2 } from "node:crypto";
173210
- import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync9 } from "node:fs";
173211
- import { join as join14 } from "node:path";
173813
+ import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync10 } from "node:fs";
173814
+ import { join as join15 } from "node:path";
173212
173815
 
173213
173816
  // node_modules/.pnpm/@ark+schema@0.56.0/node_modules/@ark/schema/out/shared/errors.js
173214
173817
  var ArkError = class _ArkError extends CastableBase {
@@ -180680,8 +181283,8 @@ function ensureRepoState(toolState, init) {
180680
181283
 
180681
181284
  // utils/changeImpact.ts
180682
181285
  import { spawn as spawn4 } from "node:child_process";
180683
- import { writeFileSync as writeFileSync7 } from "node:fs";
180684
- import { join as join8 } from "node:path";
181286
+ import { writeFileSync as writeFileSync8 } from "node:fs";
181287
+ import { join as join9 } from "node:path";
180685
181288
  var MAX_CANDIDATES = 24;
180686
181289
  var MAX_ATOM_LENGTH = 128;
180687
181290
  var MAX_ATOMS = 12;
@@ -181028,8 +181631,8 @@ async function createChangeImpactArtifact(ctx, params) {
181028
181631
  headSha: params.headSha,
181029
181632
  lookupError: lookup2.error
181030
181633
  });
181031
- const path4 = join8(ctx.tmpdir, `pr-${params.pullNumber}-${params.headSha.slice(0, 7)}-impact.md`);
181032
- writeFileSync7(path4, artifact.content);
181634
+ const path4 = join9(ctx.tmpdir, `pr-${params.pullNumber}-${params.headSha.slice(0, 7)}-impact.md`);
181635
+ writeFileSync8(path4, artifact.content);
181033
181636
  return {
181034
181637
  path: path4,
181035
181638
  candidateCount: candidates.selected.length,
@@ -181281,8 +181884,8 @@ function readNumber(params) {
181281
181884
  // utils/gitAuth.ts
181282
181885
  import { execSync } from "node:child_process";
181283
181886
  import { createHash } from "node:crypto";
181284
- import { readFileSync as readFileSync3, realpathSync, unlinkSync } from "node:fs";
181285
- import { join as join9 } from "node:path";
181887
+ import { readFileSync as readFileSync4, realpathSync, unlinkSync } from "node:fs";
181888
+ import { join as join10 } from "node:path";
181286
181889
 
181287
181890
  // utils/shell.ts
181288
181891
  import { spawnSync as spawnSync5 } from "node:child_process";
@@ -181343,7 +181946,7 @@ var TRANSIENT_AUTH_PATTERNS = [
181343
181946
  ];
181344
181947
  var gitBinary;
181345
181948
  function hashFile(path4) {
181346
- return createHash("sha256").update(readFileSync3(path4)).digest("hex");
181949
+ return createHash("sha256").update(readFileSync4(path4)).digest("hex");
181347
181950
  }
181348
181951
  function resolveGit() {
181349
181952
  const whichPath = execSync("which git", { encoding: "utf-8" }).trim();
@@ -181372,7 +181975,7 @@ function resolveHooksDir(cwd, gitPath) {
181372
181975
  cwd,
181373
181976
  log: false
181374
181977
  }).trim();
181375
- const hooksDir = join9(commonDir, "hooks");
181978
+ const hooksDir = join10(commonDir, "hooks");
181376
181979
  hooksDirCache.set(cwd, hooksDir);
181377
181980
  return hooksDir;
181378
181981
  }
@@ -181679,7 +182282,7 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
181679
182282
  }
181680
182283
 
181681
182284
  // yes/index.ts
181682
- import { performance as performance7 } from "node:perf_hooks";
182285
+ import { performance as performance8 } from "node:perf_hooks";
181683
182286
  import { setTimeout as sleep2 } from "node:timers/promises";
181684
182287
 
181685
182288
  // node_modules/.pnpm/lru-cache@11.5.2/node_modules/lru-cache/dist/esm/node/index.min.js
@@ -182394,14 +182997,14 @@ function _op(fn2, options) {
182394
182997
  `failed to create cache key${namePrefix ? ` for ${options.name}` : ""}: ${errorMessage}`
182395
182998
  );
182396
182999
  }
182397
- const startTime = performance7.now();
183000
+ const startTime = performance8.now();
182398
183001
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
182399
183002
  log3.debug(`${namePrefix}[${timestamp}] cache lookup started`, { key });
182400
183003
  if (shouldCache && lruCache) {
182401
183004
  const cached4 = lruCache.get(key);
182402
183005
  if (cached4 !== void 0) {
182403
183006
  if (keyMap?.get(key) === void 0) keyMap?.set(key, input);
182404
- const runtime = performance7.now() - startTime;
183007
+ const runtime = performance8.now() - startTime;
182405
183008
  log3.debug(`${namePrefix}[${timestamp}] cache hit (from lru)`, {
182406
183009
  key,
182407
183010
  runtime: `${Math.round(runtime)}ms`
@@ -182420,12 +183023,12 @@ function _op(fn2, options) {
182420
183023
  defaultCacheMiss(key);
182421
183024
  const retries = options.retries ?? [];
182422
183025
  let lastError;
182423
- const execStartTime = performance7.now();
183026
+ const execStartTime = performance8.now();
182424
183027
  const promise2 = (async () => {
182425
183028
  for (let attempt = 0; attempt <= retries.length; attempt++) {
182426
183029
  try {
182427
183030
  const result = acceptsCtx ? await fn2(input, ctx) : await fn2(input);
182428
- const execRuntime2 = performance7.now() - execStartTime;
183031
+ const execRuntime2 = performance8.now() - execStartTime;
182429
183032
  const skip = options.skipCache ? options.skipCache(result) : false;
182430
183033
  if (shouldCache && lruCache && result !== null && result !== void 0 && !skip) {
182431
183034
  lruCache.set(key, result);
@@ -182478,7 +183081,7 @@ function _op(fn2, options) {
182478
183081
  }
182479
183082
  }
182480
183083
  }
182481
- const execRuntime = performance7.now() - execStartTime;
183084
+ const execRuntime = performance8.now() - execStartTime;
182482
183085
  log3.debug(`${namePrefix}[${timestamp}] function failed after all retries`, {
182483
183086
  key,
182484
183087
  runtime: `${Math.round(execRuntime)}ms`,
@@ -182543,13 +183146,13 @@ function _op(fn2, options) {
182543
183146
 
182544
183147
  // mcp/git.ts
182545
183148
  import { randomUUID as randomUUID3 } from "node:crypto";
182546
- import { writeFileSync as writeFileSync8 } from "node:fs";
182547
- import { join as join12 } from "node:path";
183149
+ import { writeFileSync as writeFileSync9 } from "node:fs";
183150
+ import { join as join13 } from "node:path";
182548
183151
 
182549
183152
  // utils/apiCommit.ts
182550
183153
  import { execFileSync as execFileSync6 } from "node:child_process";
182551
183154
  import { lstat, readlink } from "node:fs/promises";
182552
- import { join as join10 } from "node:path";
183155
+ import { join as join11 } from "node:path";
182553
183156
  var GITHUB_API = "https://api.github.com";
182554
183157
  var MAX_BLOB_BYTES = 30 * 1024 * 1024;
182555
183158
  var BLOB_UPLOAD_CONCURRENCY = 8;
@@ -182614,7 +183217,7 @@ async function assertApiCommittable(files) {
182614
183217
  const root = getRepoRoot();
182615
183218
  const attrs = $(
182616
183219
  "git",
182617
- ["check-attr", "filter", "-z", "--", ...present.map((p) => join10(root, p))],
183220
+ ["check-attr", "filter", "-z", "--", ...present.map((p) => join11(root, p))],
182618
183221
  { log: false }
182619
183222
  );
182620
183223
  const parts = attrs.split("\0");
@@ -182626,7 +183229,7 @@ async function assertApiCommittable(files) {
182626
183229
  }
182627
183230
  }
182628
183231
  for (const path4 of present) {
182629
- const stat = await lstat(join10(root, path4));
183232
+ const stat = await lstat(join11(root, path4));
182630
183233
  if (stat.isDirectory()) {
182631
183234
  throw new Error(
182632
183235
  `'${path4}' is a directory (nested repository or submodule?) \u2014 signed commits only support files and symlinks.`
@@ -182635,7 +183238,7 @@ async function assertApiCommittable(files) {
182635
183238
  }
182636
183239
  }
182637
183240
  async function createBlobEntry(params) {
182638
- const absPath = join10(params.repoRoot, params.path);
183241
+ const absPath = join11(params.repoRoot, params.path);
182639
183242
  const stat = await lstat(absPath);
182640
183243
  if (stat.size > MAX_BLOB_BYTES) {
182641
183244
  throw new Error(
@@ -182812,10 +183415,10 @@ this usually means your local branch has commits that were never pushed \u2014 s
182812
183415
  }
182813
183416
 
182814
183417
  // utils/github.ts
182815
- var core3 = __toESM(require_core(), 1);
183418
+ var core4 = __toESM(require_core(), 1);
182816
183419
  import { createSign } from "node:crypto";
182817
183420
  import { rename, writeFile } from "node:fs/promises";
182818
- import { dirname as dirname3, join as join11 } from "node:path";
183421
+ import { dirname as dirname3, join as join12 } from "node:path";
182819
183422
 
182820
183423
  // node_modules/.pnpm/@octokit+plugin-throttling@11.0.5_@octokit+core@7.0.7/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
182821
183424
  var import_light = __toESM(require_light(), 1);
@@ -187013,7 +187616,7 @@ async function fetchIdTokenFromStash(creds) {
187013
187616
  throw new Error("ID token response has no value field");
187014
187617
  }
187015
187618
  if (isGitHubActions) {
187016
- core3.setSecret(body.value);
187619
+ core4.setSecret(body.value);
187017
187620
  }
187018
187621
  return body.value;
187019
187622
  }
@@ -187026,7 +187629,7 @@ function translateIdTokenError(error52) {
187026
187629
  var mintIdToken = op(
187027
187630
  async () => {
187028
187631
  try {
187029
- return await core3.getIDToken(OIDC_AUDIENCE);
187632
+ return await core4.getIDToken(OIDC_AUDIENCE);
187030
187633
  } catch (error52) {
187031
187634
  throw translateIdTokenError(error52);
187032
187635
  }
@@ -187235,7 +187838,7 @@ function getGitHubUsageSummary() {
187235
187838
  }
187236
187839
  async function writeGitHubUsageSummaryToFile(path4) {
187237
187840
  const summary2 = getGitHubUsageSummary();
187238
- const tmpPath = join11(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
187841
+ const tmpPath = join12(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
187239
187842
  await writeFile(tmpPath, JSON.stringify(summary2));
187240
187843
  await rename(tmpPath, path4);
187241
187844
  }
@@ -188387,8 +188990,8 @@ function countAhead(head, base, cwd) {
188387
188990
  function spillGitOutput(params) {
188388
188991
  const tempDir = process.env.PULLFROG_TEMP_DIR;
188389
188992
  if (!tempDir) throw new Error("PULLFROG_TEMP_DIR not set");
188390
- const outputPath = join12(tempDir, `git-${params.command}-${randomUUID3().slice(0, 8)}.txt`);
188391
- writeFileSync8(outputPath, params.output);
188993
+ const outputPath = join13(tempDir, `git-${params.command}-${randomUUID3().slice(0, 8)}.txt`);
188994
+ writeFileSync9(outputPath, params.output);
188392
188995
  const previewByLines = params.output.split("\n").slice(0, OVERFLOW_PREVIEW_LINES).join("\n");
188393
188996
  const preview = previewByLines.length <= OVERFLOW_PREVIEW_MAX_CHARS ? previewByLines : `${previewByLines.slice(0, OVERFLOW_PREVIEW_MAX_CHARS)}\u2026`;
188394
188997
  log2.info(
@@ -189902,9 +190505,9 @@ async function reportReviewNodeId(ctx, params) {
189902
190505
  import { execFileSync as execFileSync7, execSync as execSync2 } from "node:child_process";
189903
190506
  import { mkdtempSync as mkdtempSync2, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
189904
190507
  import { tmpdir as tmpdir3 } from "node:os";
189905
- import { join as join13 } from "node:path";
190508
+ import { join as join14 } from "node:path";
189906
190509
  function createTempDirectory() {
189907
- const sharedTempDir = mkdtempSync2(join13(tmpdir3(), "pullfrog-"));
190510
+ const sharedTempDir = mkdtempSync2(join14(tmpdir3(), "pullfrog-"));
189908
190511
  process.env.PULLFROG_TEMP_DIR = sharedTempDir;
189909
190512
  log2.info(`\xBB created temp dir at ${sharedTempDir}`);
189910
190513
  return sharedTempDir;
@@ -189949,13 +190552,13 @@ function wipeRunnerLeakSurface() {
189949
190552
  return [];
189950
190553
  }
189951
190554
  };
189952
- const fileCommandsDir = join13(runnerTemp, "_runner_file_commands");
190555
+ const fileCommandsDir = join14(runnerTemp, "_runner_file_commands");
189953
190556
  for (const entry of listDir(fileCommandsDir)) {
189954
- tryUnlink(join13(fileCommandsDir, entry));
190557
+ tryUnlink(join14(fileCommandsDir, entry));
189955
190558
  }
189956
190559
  for (const entry of listDir(runnerTemp)) {
189957
190560
  if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
189958
- tryUnlink(join13(runnerTemp, entry));
190561
+ tryUnlink(join14(runnerTemp, entry));
189959
190562
  }
189960
190563
  }
189961
190564
  if (wiped.length > 0) {
@@ -190505,11 +191108,11 @@ function CheckoutPrTool(ctx) {
190505
191108
  headSha: checkoutSha
190506
191109
  });
190507
191110
  if (incremental.status === "ok") {
190508
- incrementalDiffPath = join14(
191111
+ incrementalDiffPath = join15(
190509
191112
  tempDir,
190510
191113
  `pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
190511
191114
  );
190512
- writeFileSync9(incrementalDiffPath, incremental.diff);
191115
+ writeFileSync10(incrementalDiffPath, incremental.diff);
190513
191116
  log2.info(
190514
191117
  `\xBB incremental diff computed (${incremental.diff.length} bytes) \u2192 ${incrementalDiffPath}`
190515
191118
  );
@@ -190522,8 +191125,8 @@ function CheckoutPrTool(ctx) {
190522
191125
  const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
190523
191126
  log2.debug(`formatted diff preview (first 100 lines):
190524
191127
  ${diffPreview}`);
190525
- const diffPath = join14(tempDir, `pr-${pull_number}-${headShort}.diff`);
190526
- writeFileSync9(diffPath, formatResult.content);
191128
+ const diffPath = join15(tempDir, `pr-${pull_number}-${headShort}.diff`);
191129
+ writeFileSync10(diffPath, formatResult.content);
190527
191130
  log2.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
190528
191131
  primary.diffCoverage = createDiffCoverageState({
190529
191132
  diffPath,
@@ -190680,8 +191283,8 @@ ${dirty}`
190680
191283
  }
190681
191284
 
190682
191285
  // mcp/checkSuite.ts
190683
- import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync10 } from "node:fs";
190684
- import { join as join15 } from "node:path";
191286
+ import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync11 } from "node:fs";
191287
+ import { join as join16 } from "node:path";
190685
191288
  var GetCheckSuiteLogs = type({
190686
191289
  check_suite_id: type.number.describe("the id from check_suite.id")
190687
191290
  });
@@ -190777,8 +191380,8 @@ function GetCheckSuiteLogsTool(ctx) {
190777
191380
  if (!tempDir) {
190778
191381
  throw new Error("PULLFROG_TEMP_DIR not set");
190779
191382
  }
190780
- const logsDir = join15(tempDir, "ci-logs");
190781
- mkdirSync7(logsDir, { recursive: true });
191383
+ const logsDir = join16(tempDir, "ci-logs");
191384
+ mkdirSync8(logsDir, { recursive: true });
190782
191385
  const jobResults = [];
190783
191386
  for (const run4 of failedRuns) {
190784
191387
  const jobs = await ctx.octokit.paginate(ctx.octokit.rest.actions.listJobsForWorkflowRun, {
@@ -190804,8 +191407,8 @@ function GetCheckSuiteLogsTool(ctx) {
190804
191407
  );
190805
191408
  }
190806
191409
  const logsText = await logsResult.text();
190807
- const logPath = join15(logsDir, `job-${job.id}.log`);
190808
- writeFileSync10(logPath, logsText);
191410
+ const logPath = join16(logsDir, `job-${job.id}.log`);
191411
+ writeFileSync11(logPath, logsText);
190809
191412
  const analysis = analyzeLog(logsText, 80);
190810
191413
  const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
190811
191414
  jobResults.push({
@@ -190853,8 +191456,8 @@ function GetCheckSuiteLogsTool(ctx) {
190853
191456
  }
190854
191457
 
190855
191458
  // mcp/commitInfo.ts
190856
- import { writeFileSync as writeFileSync11 } from "node:fs";
190857
- import { join as join16 } from "node:path";
191459
+ import { writeFileSync as writeFileSync12 } from "node:fs";
191460
+ import { join as join17 } from "node:path";
190858
191461
  var CommitInfo = type({
190859
191462
  sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
190860
191463
  });
@@ -190878,8 +191481,8 @@ function CommitInfoTool(ctx) {
190878
191481
  "PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
190879
191482
  );
190880
191483
  }
190881
- const diffFile = join16(tempDir, `commit-${sha.slice(0, 7)}.diff`);
190882
- writeFileSync11(diffFile, formatResult.content);
191484
+ const diffFile = join17(tempDir, `commit-${sha.slice(0, 7)}.diff`);
191485
+ writeFileSync12(diffFile, formatResult.content);
190883
191486
  log2.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
190884
191487
  return {
190885
191488
  sha: data.sha,
@@ -191012,17 +191615,17 @@ function ReopenCurrentTool(ctx) {
191012
191615
  }
191013
191616
 
191014
191617
  // prep/index.ts
191015
- import { performance as performance8 } from "node:perf_hooks";
191618
+ import { performance as performance9 } from "node:perf_hooks";
191016
191619
 
191017
191620
  // prep/installNodeDependencies.ts
191018
- import { existsSync as existsSync6 } from "node:fs";
191019
- import { join as join18 } from "node:path";
191621
+ import { existsSync as existsSync7 } from "node:fs";
191622
+ import { join as join19 } from "node:path";
191020
191623
 
191021
191624
  // utils/packageManager.ts
191022
191625
  var import_semver2 = __toESM(require_semver2(), 1);
191023
- import { existsSync as existsSync5 } from "node:fs";
191626
+ import { existsSync as existsSync6 } from "node:fs";
191024
191627
  import { mkdir, readFile as readFile2 } from "node:fs/promises";
191025
- import { delimiter, join as join17 } from "node:path";
191628
+ import { delimiter, join as join18 } from "node:path";
191026
191629
  var SUPPORTED_NAMES = ["npm", "pnpm", "yarn", "bun"];
191027
191630
  var COREPACK_MANAGED = ["pnpm", "yarn"];
191028
191631
  function isSupported(name) {
@@ -191060,8 +191663,8 @@ function parseDevEnginesField(field) {
191060
191663
  };
191061
191664
  }
191062
191665
  async function resolvePackageManagerSpec(cwd) {
191063
- const pkgPath = join17(cwd, "package.json");
191064
- if (!existsSync5(pkgPath)) return null;
191666
+ const pkgPath = join18(cwd, "package.json");
191667
+ if (!existsSync6(pkgPath)) return null;
191065
191668
  let pkg;
191066
191669
  try {
191067
191670
  pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
@@ -191119,7 +191722,7 @@ async function currentVersion(name) {
191119
191722
  return result.stdout.trim();
191120
191723
  }
191121
191724
  function packageManagerBinDir(tmpdir4) {
191122
- return join17(tmpdir4, "pm-bin");
191725
+ return join18(tmpdir4, "pm-bin");
191123
191726
  }
191124
191727
  async function ensurePackageManager(params) {
191125
191728
  const spec = params.spec;
@@ -191182,7 +191785,7 @@ async function installFallback(name, installSpec) {
191182
191785
  });
191183
191786
  if (result.exitCode !== 0) return result.stderr || `failed to install ${name}`;
191184
191787
  if (name === "deno") {
191185
- const denoPath = join17(process.env.HOME || "", ".deno", "bin");
191788
+ const denoPath = join18(process.env.HOME || "", ".deno", "bin");
191186
191789
  process.env.PATH = `${denoPath}:${process.env.PATH}`;
191187
191790
  }
191188
191791
  log2.info(`\xBB installed ${name}`);
@@ -191215,8 +191818,8 @@ async function isCommandAvailable2(command) {
191215
191818
  var installNodeDependencies = {
191216
191819
  name: "installNodeDependencies",
191217
191820
  shouldRun: () => {
191218
- const packageJsonPath = join18(process.cwd(), "package.json");
191219
- return existsSync6(packageJsonPath);
191821
+ const packageJsonPath = join19(process.cwd(), "package.json");
191822
+ return existsSync7(packageJsonPath);
191220
191823
  },
191221
191824
  run: async (options) => {
191222
191825
  const declared = await resolvePackageManagerSpec(process.cwd());
@@ -191310,14 +191913,14 @@ ${errorMessage}`]
191310
191913
  };
191311
191914
 
191312
191915
  // prep/installPythonDependencies.ts
191313
- import { existsSync as existsSync7, readFileSync as readFileSync4 } from "node:fs";
191314
- import { join as join19 } from "node:path";
191916
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "node:fs";
191917
+ import { join as join20 } from "node:path";
191315
191918
  function declaresBuildSystem(path4) {
191316
- return /^\s*\[\s*build-system\s*\]/m.test(readFileSync4(path4, "utf8"));
191919
+ return /^\s*\[\s*build-system\s*\]/m.test(readFileSync5(path4, "utf8"));
191317
191920
  }
191318
191921
  function configApplies(config3, cwd) {
191319
- const path4 = join19(cwd, config3.file);
191320
- return existsSync7(path4) && (config3.applies?.(path4) ?? true);
191922
+ const path4 = join20(cwd, config3.file);
191923
+ return existsSync8(path4) && (config3.applies?.(path4) ?? true);
191321
191924
  }
191322
191925
  var PYTHON_CONFIGS = [
191323
191926
  {
@@ -191471,7 +192074,7 @@ var installPythonDependencies = {
191471
192074
  var prepSteps = [installNodeDependencies, installPythonDependencies];
191472
192075
  async function runPrepPhase(options) {
191473
192076
  log2.debug("\xBB starting prep phase...");
191474
- const startTime = performance8.now();
192077
+ const startTime = performance9.now();
191475
192078
  const results = [];
191476
192079
  const preDirty = await dirtyTrackedPaths();
191477
192080
  try {
@@ -191493,7 +192096,7 @@ async function runPrepPhase(options) {
191493
192096
  } finally {
191494
192097
  await restoreDirtiedSince({ before: preDirty, actor: "prep" });
191495
192098
  }
191496
- const totalDurationMs = performance8.now() - startTime;
192099
+ const totalDurationMs = performance9.now() - startTime;
191497
192100
  log2.debug(`\xBB prep phase completed (${Math.round(totalDurationMs)}ms)`);
191498
192101
  return results;
191499
192102
  }
@@ -191638,15 +192241,15 @@ function AwaitDependencyInstallationTool(ctx) {
191638
192241
  }
191639
192242
 
191640
192243
  // mcp/gh.ts
191641
- import { mkdirSync as mkdirSync8 } from "node:fs";
191642
- import { join as join21 } from "node:path";
192244
+ import { mkdirSync as mkdirSync9 } from "node:fs";
192245
+ import { join as join22 } from "node:path";
191643
192246
 
191644
192247
  // mcp/shell.ts
191645
192248
  import { spawn as spawn5, spawnSync as spawnSync6 } from "node:child_process";
191646
192249
  import { randomUUID as randomUUID4 } from "node:crypto";
191647
- import { closeSync, openSync, writeFileSync as writeFileSync12 } from "node:fs";
192250
+ import { closeSync, openSync, writeFileSync as writeFileSync13 } from "node:fs";
191648
192251
  import { userInfo as userInfo2 } from "node:os";
191649
- import { join as join20 } from "node:path";
192252
+ import { join as join21 } from "node:path";
191650
192253
  import { setTimeout as sleep3 } from "node:timers/promises";
191651
192254
  var ShellParams = type({
191652
192255
  command: "string",
@@ -191909,8 +192512,8 @@ ${spawnError}` : spawnError;
191909
192512
  }
191910
192513
  function capOutput(output) {
191911
192514
  if (output.length <= MAX_OUTPUT_CHARS) return output;
191912
- const fullPath = join20(getTempDir(), `shell-${randomUUID4().slice(0, 8)}.log`);
191913
- writeFileSync12(fullPath, output);
192515
+ const fullPath = join21(getTempDir(), `shell-${randomUUID4().slice(0, 8)}.log`);
192516
+ writeFileSync13(fullPath, output);
191914
192517
  const elided = output.length - MAX_OUTPUT_CHARS;
191915
192518
  return `... [${elided} chars truncated; full output saved to ${fullPath}] ...
191916
192519
  ${output.slice(-MAX_OUTPUT_CHARS)}`;
@@ -191964,8 +192567,8 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
191964
192567
  if (params.background) {
191965
192568
  const tempDir = getTempDir();
191966
192569
  const handle = `bg-${randomUUID4().slice(0, 8)}`;
191967
- const outputPath = join20(tempDir, `${handle}.log`);
191968
- const pidPath = join20(tempDir, `${handle}.pid`);
192570
+ const outputPath = join21(tempDir, `${handle}.log`);
192571
+ const pidPath = join21(tempDir, `${handle}.pid`);
191969
192572
  const logFd = openSync(outputPath, "a");
191970
192573
  let proc;
191971
192574
  try {
@@ -191982,7 +192585,7 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
191982
192585
  throw new Error("failed to start background process");
191983
192586
  }
191984
192587
  proc.unref();
191985
- writeFileSync12(pidPath, `${proc.pid}
192588
+ writeFileSync13(pidPath, `${proc.pid}
191986
192589
  `);
191987
192590
  ctx.toolState.backgroundProcesses.set(handle, { pid: proc.pid, outputPath, pidPath });
191988
192591
  return {
@@ -192062,8 +192665,8 @@ function GhTool(ctx) {
192062
192665
  description: `Run the GitHub CLI against ${ctx.repo.owner}/${ctx.repo.name}. Pass argv as an array \u2014 there is no shell, so pipes, redirects and \`&&\` do not work; use gh's own \`--json\`/\`--jq\` flags instead. \`gh api graphql\` is available. Authenticated with ${permissions ? formatPermissions(permissions) : "no permissions"} on this repo only; anything else 403s. Pushing, merging and ref writes are not available \u2014 use the git tools.`,
192063
192666
  parameters: GhParams,
192064
192667
  execute: execute(async (params) => {
192065
- const configDir = join21(ctx.tmpdir, "gh-config");
192066
- mkdirSync8(configDir, { recursive: true });
192668
+ const configDir = join22(ctx.tmpdir, "gh-config");
192669
+ mkdirSync9(configDir, { recursive: true });
192067
192670
  const command = ["gh", ...params.args.map(shellQuote)].join(" ");
192068
192671
  const result = await runSandboxed({
192069
192672
  command,
@@ -192786,8 +193389,8 @@ function PullRequestInfoTool(ctx) {
192786
193389
  }
192787
193390
 
192788
193391
  // mcp/reviewComments.ts
192789
- import { writeFileSync as writeFileSync13 } from "node:fs";
192790
- import { join as join22 } from "node:path";
193392
+ import { writeFileSync as writeFileSync14 } from "node:fs";
193393
+ import { join as join23 } from "node:path";
192791
193394
  var COMMENTS_PER_THREAD = 50;
192792
193395
  var REVIEW_THREADS_QUERY = `
192793
193396
  query ($owner: String!, $name: String!, $prNumber: Int!, $cursor: String) {
@@ -193260,8 +193863,8 @@ function GetReviewCommentsTool(ctx) {
193260
193863
  throw new Error("PULLFROG_TEMP_DIR not set");
193261
193864
  }
193262
193865
  const filename = `review-${params.review_id}-threads.md`;
193263
- const commentsPath = join22(tempDir, filename);
193264
- writeFileSync13(commentsPath, formatted.content);
193866
+ const commentsPath = join23(tempDir, filename);
193867
+ writeFileSync14(commentsPath, formatted.content);
193265
193868
  log2.debug(`wrote ${threadBlocks.length} threads to ${commentsPath}`);
193266
193869
  return {
193267
193870
  review_id: params.review_id,
@@ -193594,8 +194197,8 @@ function UploadFileTool(ctx) {
193594
194197
  }
193595
194198
 
193596
194199
  // mcp/xrepo.ts
193597
- import { mkdirSync as mkdirSync9, rmSync as rmSync3 } from "node:fs";
193598
- import { join as join23 } from "node:path";
194200
+ import { mkdirSync as mkdirSync10, rmSync as rmSync3 } from "node:fs";
194201
+ import { join as join24 } from "node:path";
193599
194202
  function assertValidRepoName(name) {
193600
194203
  if (!/^[A-Za-z0-9._-]+$/.test(name) || name.startsWith("-") || name.includes("..")) {
193601
194204
  throw new Error(
@@ -193675,8 +194278,8 @@ function CheckoutRepoTool(ctx) {
193675
194278
  return { path: existing.dir, access: existing.access, note: "already checked out." };
193676
194279
  }
193677
194280
  const access = accessFor(ctx, repo);
193678
- const dir = join23(ctx.tmpdir, "xrepo", repo);
193679
- mkdirSync9(dir, { recursive: true });
194281
+ const dir = join24(ctx.tmpdir, "xrepo", repo);
194282
+ mkdirSync10(dir, { recursive: true });
193680
194283
  const state = ensureRepoState(ctx.toolState, { owner, name: repo, dir, access });
193681
194284
  const rc = resolveRepoCtx(ctx, repo);
193682
194285
  try {
@@ -193924,6 +194527,9 @@ function hasEnvVar2(name) {
193924
194527
  function hasClaudeCodeAuth() {
193925
194528
  return hasEnvVar2("CLAUDE_CODE_OAUTH_TOKEN") || hasEnvVar2("ANTHROPIC_API_KEY");
193926
194529
  }
194530
+ function hasCodexAuth() {
194531
+ return hasEnvVar2("CODEX_AUTH_JSON") || hasEnvVar2("OPENAI_API_KEY");
194532
+ }
193927
194533
  function hasBedrockAuth() {
193928
194534
  return hasEnvVar2("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar2("AWS_ACCESS_KEY_ID") && hasEnvVar2("AWS_SECRET_ACCESS_KEY");
193929
194535
  }
@@ -193950,6 +194556,15 @@ function resolveSlug(slug2) {
193950
194556
  }
193951
194557
  return vertexId;
193952
194558
  }
194559
+ if (alias?.routing === "azure") {
194560
+ const deployment = process.env[AZURE_DEPLOYMENT_ENV]?.trim();
194561
+ if (!deployment) {
194562
+ throw new Error(
194563
+ `${AZURE_DEPLOYMENT_ENV} env var is required when the model is set to "${slug2}". set it to the name of your Azure OpenAI deployment (not the model it serves \u2014 Azure routes on the deployment name). see https://docs.pullfrog.com/azure for setup.`
194564
+ );
194565
+ }
194566
+ return `${AZURE_PROVIDER}/${deployment}`;
194567
+ }
193953
194568
  if (alias?.routing === "openai-compatible") {
193954
194569
  const modelId = process.env[OPENAI_COMPATIBLE_MODEL_ENV]?.trim();
193955
194570
  if (!modelId) {
@@ -193988,6 +194603,7 @@ function resolveAgent(ctx) {
193988
194603
  }
193989
194604
  log2.warning(`\xBB unknown PULLFROG_AGENT="${envAgent}" \u2014 falling through to auto-select`);
193990
194605
  }
194606
+ if (ctx.proxyModel) return agents.opencode;
193991
194607
  if (ctx.model && hasBedrockAuth() && process.env[BEDROCK_MODEL_ID_ENV]?.trim() === ctx.model) {
193992
194608
  return isBedrockAnthropicId(ctx.model) ? agents.claude : agents.opencode;
193993
194609
  }
@@ -193997,12 +194613,14 @@ function resolveAgent(ctx) {
193997
194613
  if (ctx.model) {
193998
194614
  try {
193999
194615
  const provider2 = getModelProvider(ctx.model);
194000
- if (provider2 === "anthropic" && hasClaudeCodeAuth()) {
194001
- return agents.claude;
194002
- }
194616
+ if (provider2 === "anthropic" && hasClaudeCodeAuth()) return agents.claude;
194617
+ if (provider2 === "openai" && ctx.codexAgent && hasCodexAuth()) return agents.codex;
194003
194618
  } catch {
194004
194619
  }
194005
194620
  }
194621
+ if (!ctx.model && ctx.codexAgent && hasCodexAuth() && !hasClaudeCodeAuth()) {
194622
+ return agents.codex;
194623
+ }
194006
194624
  return agents.opencode;
194007
194625
  }
194008
194626
 
@@ -194100,10 +194718,37 @@ auto-compaction is disabled, so long runs grow until your endpoint refuses them.
194100
194718
 
194101
194719
  for full setup instructions, see https://docs.pullfrog.com/openai-compatible`;
194102
194720
  }
194721
+ function buildAzureSetupError(params) {
194722
+ const githubSecretsUrl = `https://github.com/${params.owner}/${params.name}/settings/secrets/actions`;
194723
+ return `Azure OpenAI selected but required configuration is missing: ${params.missing.join(", ")}.
194724
+
194725
+ only the API key is sensitive \u2014 add it as a secret at ${githubSecretsUrl}. the rest is plain workflow \`env:\`:
194726
+
194727
+ ${AZURE_RESOURCE_NAME_ENV}: <name>
194728
+ ${AZURE_API_KEY_ENV}: \${{ secrets.${AZURE_API_KEY_ENV} }}
194729
+ ${AZURE_DEPLOYMENT_ENV}: <deployment-name>
194730
+ ${AZURE_CONTEXT_ENV}: "400000"
194731
+ ${AZURE_MAX_OUTPUT_ENV}: "128000"
194732
+
194733
+ ${AZURE_RESOURCE_NAME_ENV} is the \`<name>\` in your endpoint https://<name>.openai.azure.com.
194734
+ ${AZURE_DEPLOYMENT_ENV} is the name of the deployment, which is not necessarily the name of
194735
+ the model it serves \u2014 Azure routes on the deployment name.
194736
+
194737
+ set the last two to the real limits of whichever model your deployment serves. Pullfrog can't
194738
+ discover them \u2014 a deployment name carries no catalog metadata \u2014 and without them completions
194739
+ are capped at 32000 tokens (rejected outright by models with a smaller cap) and auto-compaction
194740
+ is disabled, so long runs grow until Azure refuses them.
194741
+
194742
+ for full setup instructions, see https://docs.pullfrog.com/azure`;
194743
+ }
194103
194744
  function hasEnvVar3(name) {
194104
194745
  const value2 = process.env[name];
194105
194746
  return typeof value2 === "string" && value2.length > 0;
194106
194747
  }
194748
+ function modelHasRuntimeAuth(model) {
194749
+ const authVars = [...getModelEnvVars(model), ...getModelManagedCredentials(model)];
194750
+ return authVars.length === 0 || authVars.some(hasEnvVar3);
194751
+ }
194107
194752
  function hasPositiveNumberEnvVar(name) {
194108
194753
  return Number(process.env[name]) > 0;
194109
194754
  }
@@ -194122,6 +194767,17 @@ function validateOpenAICompatibleSetup(params) {
194122
194767
  );
194123
194768
  }
194124
194769
  }
194770
+ function validateAzureSetup(params) {
194771
+ const missing = [];
194772
+ if (!hasEnvVar3(AZURE_API_KEY_ENV)) missing.push(AZURE_API_KEY_ENV);
194773
+ if (!hasEnvVar3(AZURE_RESOURCE_NAME_ENV)) missing.push(AZURE_RESOURCE_NAME_ENV);
194774
+ if (!hasEnvVar3(AZURE_DEPLOYMENT_ENV)) missing.push(AZURE_DEPLOYMENT_ENV);
194775
+ if (!hasPositiveNumberEnvVar(AZURE_CONTEXT_ENV)) missing.push(AZURE_CONTEXT_ENV);
194776
+ if (!hasPositiveNumberEnvVar(AZURE_MAX_OUTPUT_ENV)) missing.push(AZURE_MAX_OUTPUT_ENV);
194777
+ if (missing.length > 0) {
194778
+ throw new Error(buildAzureSetupError({ owner: params.owner, name: params.name, missing }));
194779
+ }
194780
+ }
194125
194781
  function validateBedrockSetup(params) {
194126
194782
  const hasAuth = hasEnvVar3("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar3("AWS_ACCESS_KEY_ID") && hasEnvVar3("AWS_SECRET_ACCESS_KEY");
194127
194783
  const missing = [];
@@ -194145,6 +194801,12 @@ function validateVertexSetup(params) {
194145
194801
  throw new Error(buildVertexSetupError({ owner: params.owner, name: params.name, missing }));
194146
194802
  }
194147
194803
  }
194804
+ function hasSingleProviderAuth(agentName) {
194805
+ if (agentName === "codex") {
194806
+ return hasEnvVar3("OPENAI_API_KEY") || hasEnvVar3("CODEX_AUTH_JSON");
194807
+ }
194808
+ return hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN");
194809
+ }
194148
194810
  function validateAgentApiKey(params) {
194149
194811
  if (params.model) {
194150
194812
  const alias = resolveDisplayAlias(params.model);
@@ -194160,6 +194822,10 @@ function validateAgentApiKey(params) {
194160
194822
  validateOpenAICompatibleSetup({ owner: params.owner, name: params.name });
194161
194823
  return;
194162
194824
  }
194825
+ if (params.model.startsWith(`${AZURE_PROVIDER}/`)) {
194826
+ validateAzureSetup({ owner: params.owner, name: params.name });
194827
+ return;
194828
+ }
194163
194829
  if (!params.model.includes("/")) {
194164
194830
  if (process.env[VERTEX_MODEL_ID_ENV]?.trim() === params.model) {
194165
194831
  validateVertexSetup({ owner: params.owner, name: params.name });
@@ -194183,7 +194849,7 @@ function validateAgentApiKey(params) {
194183
194849
  })
194184
194850
  );
194185
194851
  }
194186
- if (hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN")) return;
194852
+ if (hasSingleProviderAuth(params.agent.name)) return;
194187
194853
  throw new Error(
194188
194854
  buildKeyError({
194189
194855
  owner: params.owner,
@@ -194195,7 +194861,7 @@ function validateAgentApiKey(params) {
194195
194861
  );
194196
194862
  }
194197
194863
  if (params.agent.name === "opencode") {
194198
- if (params.authorized.size > 0) return;
194864
+ if ([...params.authorized].some(modelHasRuntimeAuth)) return;
194199
194865
  const reason = getModelsFailure();
194200
194866
  if (reason) throw new Error(reason);
194201
194867
  throw new Error(
@@ -194207,7 +194873,7 @@ function validateAgentApiKey(params) {
194207
194873
  })
194208
194874
  );
194209
194875
  }
194210
- if (hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN")) return;
194876
+ if (hasSingleProviderAuth(params.agent.name)) return;
194211
194877
  throw new Error(
194212
194878
  buildKeyError({
194213
194879
  owner: params.owner,
@@ -194580,9 +195246,9 @@ async function checkConfiguredCredentials(params) {
194580
195246
 
194581
195247
  // utils/gitAuthServer.ts
194582
195248
  import { randomUUID as randomUUID5 } from "node:crypto";
194583
- import { writeFileSync as writeFileSync14 } from "node:fs";
195249
+ import { writeFileSync as writeFileSync15 } from "node:fs";
194584
195250
  import { createServer as createServer3 } from "node:http";
194585
- import { join as join24 } from "node:path";
195251
+ import { join as join25 } from "node:path";
194586
195252
  var REVOKED_TRAP_MS = 6e4;
194587
195253
  function revokeGitHubToken(token) {
194588
195254
  fetch("https://api.github.com/installation/token", {
@@ -194651,7 +195317,7 @@ async function startGitAuthServer(tmpdir4) {
194651
195317
  function writeAskpassScript(code) {
194652
195318
  const scriptId = randomUUID5();
194653
195319
  const scriptName = `askpass-${scriptId}.js`;
194654
- const scriptPath = join24(tmpdir4, scriptName);
195320
+ const scriptPath = join25(tmpdir4, scriptName);
194655
195321
  const content = [
194656
195322
  `#!/usr/bin/env node`,
194657
195323
  `var a=process.argv[2]||"";`,
@@ -194664,7 +195330,7 @@ async function startGitAuthServer(tmpdir4) {
194664
195330
  `r.on("end",function(){process.stdout.write(d+"\\n")})`,
194665
195331
  `}).on("error",function(){process.exit(1)})}`
194666
195332
  ].join("\n");
194667
- writeFileSync14(scriptPath, content, { mode: 448 });
195333
+ writeFileSync15(scriptPath, content, { mode: 448 });
194668
195334
  return scriptPath;
194669
195335
  }
194670
195336
  async function close() {
@@ -195103,7 +195769,7 @@ function resolveInstructions(ctx) {
195103
195769
 
195104
195770
  // utils/learnings.ts
195105
195771
  import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
195106
- import { dirname as dirname4, join as join25 } from "node:path";
195772
+ import { dirname as dirname4, join as join26 } from "node:path";
195107
195773
 
195108
195774
  // utils/learningsTruncate.ts
195109
195775
  var MAX_LEARNINGS_LENGTH = 1e5;
@@ -195121,10 +195787,10 @@ function truncateAtLineBoundary(body, cap) {
195121
195787
  var LEARNINGS_FILE_NAME = "pullfrog-learnings.md";
195122
195788
  var XREPO_LEARNINGS_FILE_NAME = "pullfrog-xrepo-learnings.md";
195123
195789
  function learningsFilePath(tmpdir4) {
195124
- return join25(tmpdir4, LEARNINGS_FILE_NAME);
195790
+ return join26(tmpdir4, LEARNINGS_FILE_NAME);
195125
195791
  }
195126
195792
  function xrepoLearningsFilePath(tmpdir4) {
195127
- return join25(tmpdir4, XREPO_LEARNINGS_FILE_NAME);
195793
+ return join26(tmpdir4, XREPO_LEARNINGS_FILE_NAME);
195128
195794
  }
195129
195795
  async function seedLearningsFile(params) {
195130
195796
  const path4 = learningsFilePath(params.tmpdir);
@@ -195240,7 +195906,7 @@ async function persistXrepoLearnings(ctx) {
195240
195906
  // utils/modelAccess.ts
195241
195907
  function decideModelAccess(input) {
195242
195908
  if (!input.modelExplicit || !input.model) return { kind: "ok" };
195243
- const byokAuthorized = !!input.resolvedModel && (input.authorized.has(input.resolvedModel) || !input.resolvedModel.includes("/") || input.resolvedModel.startsWith(`${OPENAI_COMPATIBLE_PROVIDER}/`));
195909
+ const byokAuthorized = !!input.resolvedModel && (input.authorized.has(input.resolvedModel) || !input.resolvedModel.includes("/") || input.resolvedModel.startsWith(`${OPENAI_COMPATIBLE_PROVIDER}/`) || input.resolvedModel.startsWith(`${AZURE_PROVIDER}/`));
195244
195910
  if (input.proxyActive) {
195245
195911
  const target = resolveOpenRouterModel(input.model);
195246
195912
  if (input.oss) {
@@ -195285,7 +195951,7 @@ function buildModelAccessError(input) {
195285
195951
  }
195286
195952
 
195287
195953
  // utils/normalizeEnv.ts
195288
- var core4 = __toESM(require_core(), 1);
195954
+ var core5 = __toESM(require_core(), 1);
195289
195955
  function hasControlCharacter(value2) {
195290
195956
  return [...value2].some((ch) => {
195291
195957
  const code = ch.charCodeAt(0);
@@ -195311,7 +195977,7 @@ function sanitizeSecret(key, value2) {
195311
195977
  `\xBB stripped whitespace from ${key} (whitespace in secret values breaks GitHub Actions log masking)`
195312
195978
  );
195313
195979
  }
195314
- core4.setSecret(trimmed);
195980
+ core5.setSecret(trimmed);
195315
195981
  return trimmed;
195316
195982
  }
195317
195983
  function normalizeEnv() {
@@ -195355,7 +196021,7 @@ function normalizeEnv() {
195355
196021
  }
195356
196022
 
195357
196023
  // utils/overrides.ts
195358
- var core5 = __toESM(require_core(), 1);
196024
+ var core6 = __toESM(require_core(), 1);
195359
196025
  var DENIED_OVERRIDE_NAMES = /* @__PURE__ */ new Set([
195360
196026
  "GITHUB_TOKEN",
195361
196027
  "GH_TOKEN",
@@ -195401,7 +196067,7 @@ function applyOverrides(params) {
195401
196067
  denied.push(key);
195402
196068
  continue;
195403
196069
  }
195404
- if (value2.length > 0) core5.setSecret(value2);
196070
+ if (value2.length > 0) core6.setSecret(value2);
195405
196071
  params.env[key] = value2;
195406
196072
  applied.push(key);
195407
196073
  }
@@ -195410,8 +196076,8 @@ function applyOverrides(params) {
195410
196076
  }
195411
196077
 
195412
196078
  // utils/payload.ts
195413
- var core6 = __toESM(require_core(), 1);
195414
- import { readFileSync as readFileSync6 } from "node:fs";
196079
+ var core7 = __toESM(require_core(), 1);
196080
+ import { readFileSync as readFileSync7 } from "node:fs";
195415
196081
  import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
195416
196082
 
195417
196083
  // utils/versioning.ts
@@ -195498,8 +196164,8 @@ function resolveCwd(cwd) {
195498
196164
  return workspace ? resolve2(workspace, cwd) : cwd;
195499
196165
  }
195500
196166
  function resolvePromptInput() {
195501
- const promptInput = core6.getInput("prompt");
195502
- const promptFile = core6.getInput("prompt_file");
196167
+ const promptInput = core7.getInput("prompt");
196168
+ const promptFile = core7.getInput("prompt_file");
195503
196169
  if (promptInput && promptFile) {
195504
196170
  throw new Error("set exactly one of 'prompt' or 'prompt_file' inputs, not both.");
195505
196171
  }
@@ -195525,7 +196191,7 @@ function resolvePromptInput() {
195525
196191
  function resolvePromptFile(input) {
195526
196192
  const workspace = process.env.GITHUB_WORKSPACE;
195527
196193
  const path4 = isAbsolute2(input) ? input : workspace ? resolve2(workspace, input) : resolve2(input);
195528
- const content = readFileSync6(path4, "utf-8");
196194
+ const content = readFileSync7(path4, "utf-8");
195529
196195
  if (!content.trim()) {
195530
196196
  throw new Error(`prompt_file ${JSON.stringify(input)} is empty.`);
195531
196197
  }
@@ -195533,7 +196199,7 @@ function resolvePromptFile(input) {
195533
196199
  }
195534
196200
  function warnIfDeprecatedStatusChecks(value2) {
195535
196201
  if (value2 !== void 0) {
195536
- core6.warning(
196202
+ core7.warning(
195537
196203
  "`status_checks` is deprecated. Both checks are now repository settings \u2014 open the Pullfrog console for this repo (Automations \u2192 Review PRs) and set them there, then remove `status_checks` from your workflow. It keeps working until you do."
195538
196204
  );
195539
196205
  }
@@ -195541,15 +196207,15 @@ function warnIfDeprecatedStatusChecks(value2) {
195541
196207
  }
195542
196208
  function resolveNonPromptInputs() {
195543
196209
  return Inputs.omit("prompt", "prompt_file").assert({
195544
- model: core6.getInput("model") || void 0,
195545
- effort: core6.getInput("effort") || void 0,
195546
- debug: core6.getInput("debug") || void 0,
195547
- timeout: core6.getInput("timeout") || void 0,
195548
- cwd: core6.getInput("cwd") || void 0,
195549
- push: core6.getInput("push") || void 0,
195550
- shell: core6.getInput("shell") || void 0,
195551
- status_checks: warnIfDeprecatedStatusChecks(core6.getInput("status_checks") || void 0),
195552
- progress_comments: core6.getInput("progress_comments") || void 0
196210
+ model: core7.getInput("model") || void 0,
196211
+ effort: core7.getInput("effort") || void 0,
196212
+ debug: core7.getInput("debug") || void 0,
196213
+ timeout: core7.getInput("timeout") || void 0,
196214
+ cwd: core7.getInput("cwd") || void 0,
196215
+ push: core7.getInput("push") || void 0,
196216
+ shell: core7.getInput("shell") || void 0,
196217
+ status_checks: warnIfDeprecatedStatusChecks(core7.getInput("status_checks") || void 0),
196218
+ progress_comments: core7.getInput("progress_comments") || void 0
195553
196219
  });
195554
196220
  }
195555
196221
  function resolvePayload(resolvedPromptInput, repoSettings) {
@@ -195617,7 +196283,7 @@ function resolvePayload(resolvedPromptInput, repoSettings) {
195617
196283
  };
195618
196284
  }
195619
196285
  function resolveOutputSchema() {
195620
- const raw2 = core6.getInput("output_schema");
196286
+ const raw2 = core7.getInput("output_schema");
195621
196287
  if (!raw2) return void 0;
195622
196288
  let parsed2;
195623
196289
  try {
@@ -195633,10 +196299,10 @@ function resolveOutputSchema() {
195633
196299
  }
195634
196300
 
195635
196301
  // utils/proxy.ts
195636
- var core8 = __toESM(require_core(), 1);
196302
+ var core9 = __toESM(require_core(), 1);
195637
196303
 
195638
196304
  // utils/token.ts
195639
- var core7 = __toESM(require_core(), 1);
196305
+ var core8 = __toESM(require_core(), 1);
195640
196306
  import assert2 from "node:assert/strict";
195641
196307
  var mcpTokenValue;
195642
196308
  var refreshMcpTokenFn;
@@ -195644,7 +196310,7 @@ function getMcpTokenRefresh() {
195644
196310
  return refreshMcpTokenFn;
195645
196311
  }
195646
196312
  function getJobToken() {
195647
- const inputToken = core7.getInput("token");
196313
+ const inputToken = core8.getInput("token");
195648
196314
  if (inputToken) {
195649
196315
  return inputToken;
195650
196316
  }
@@ -195660,7 +196326,7 @@ async function resolveTokens(params) {
195660
196326
  if (externalToken) {
195661
196327
  mcpTokenValue = externalToken;
195662
196328
  if (isGitHubActions) {
195663
- core7.setSecret(externalToken);
196329
+ core8.setSecret(externalToken);
195664
196330
  }
195665
196331
  log2.info("\xBB using external GH_TOKEN for both git and MCP");
195666
196332
  return {
@@ -195680,7 +196346,7 @@ async function resolveTokens(params) {
195680
196346
  const gitPermissions = params.push === "disabled" ? { contents: "read" } : { contents: "write", workflows: "write" };
195681
196347
  const gitToken = await acquireNewToken({ repos: writeRepos, permissions: gitPermissions });
195682
196348
  if (isGitHubActions) {
195683
- core7.setSecret(gitToken);
196349
+ core8.setSecret(gitToken);
195684
196350
  }
195685
196351
  log2.info(
195686
196352
  `\xBB acquired git token (${Object.entries(gitPermissions).map((e) => e.join(":")).join(", ")})`
@@ -195697,7 +196363,7 @@ async function resolveTokens(params) {
195697
196363
  };
195698
196364
  const mcpToken = await acquireNewToken({ repos: writeRepos, permissions: mcpPermissions });
195699
196365
  if (isGitHubActions) {
195700
- core7.setSecret(mcpToken);
196366
+ core8.setSecret(mcpToken);
195701
196367
  }
195702
196368
  log2.info(
195703
196369
  `\xBB acquired scoped MCP token (${Object.entries(mcpPermissions).map((e) => e.join(":")).join(", ")})`
@@ -195706,7 +196372,7 @@ async function resolveTokens(params) {
195706
196372
  let ghToken;
195707
196373
  if (ghPermissions) {
195708
196374
  ghToken = await acquireNewToken({ permissions: ghPermissions });
195709
- if (isGitHubActions) core7.setSecret(ghToken);
196375
+ if (isGitHubActions) core8.setSecret(ghToken);
195710
196376
  log2.info(
195711
196377
  `\xBB acquired gh token mirroring ${params.authorPermission} (${formatPermissions(ghPermissions)})`
195712
196378
  );
@@ -195717,7 +196383,7 @@ async function resolveTokens(params) {
195717
196383
  repos: params.xrepo.read,
195718
196384
  permissions: { contents: "read" }
195719
196385
  });
195720
- if (isGitHubActions) core7.setSecret(readToken);
196386
+ if (isGitHubActions) core8.setSecret(readToken);
195721
196387
  log2.info(`\xBB acquired cross-repo read token (contents:read, ${params.xrepo.read.length} repos)`);
195722
196388
  }
195723
196389
  mcpTokenValue = mcpToken;
@@ -195736,7 +196402,7 @@ async function resolveTokens(params) {
195736
196402
  oidc: params.oidc ?? void 0
195737
196403
  }).then((fresh) => {
195738
196404
  if (isGitHubActions) {
195739
- core7.setSecret(fresh);
196405
+ core8.setSecret(fresh);
195740
196406
  }
195741
196407
  mcpTokenValue = fresh;
195742
196408
  currentMcpToken = fresh;
@@ -195757,7 +196423,7 @@ async function resolveTokens(params) {
195757
196423
  permissions: gitPermissions,
195758
196424
  oidc: params.oidc ?? void 0
195759
196425
  }).then((fresh) => {
195760
- if (isGitHubActions) core7.setSecret(fresh);
196426
+ if (isGitHubActions) core8.setSecret(fresh);
195761
196427
  void revokeGitHubInstallationToken(currentGitToken);
195762
196428
  currentGitToken = fresh;
195763
196429
  log2.warning("\xBB GitHub rejected the git token; re-acquired a fresh git token");
@@ -195774,7 +196440,7 @@ async function resolveTokens(params) {
195774
196440
  permissions: { contents: "read" },
195775
196441
  oidc: params.oidc ?? void 0
195776
196442
  }).then((fresh) => {
195777
- if (isGitHubActions) core7.setSecret(fresh);
196443
+ if (isGitHubActions) core8.setSecret(fresh);
195778
196444
  void revokeGitHubInstallationToken(read2);
195779
196445
  currentReadToken = fresh;
195780
196446
  log2.warning("\xBB GitHub rejected the read token; re-acquired a fresh read token");
@@ -195988,7 +196654,7 @@ async function resolveProxyModel(ctx) {
195988
196654
  });
195989
196655
  if (!key) return;
195990
196656
  process.env.OPENROUTER_API_KEY = key;
195991
- core8.setSecret(key);
196657
+ core9.setSecret(key);
195992
196658
  ctx.payload.proxyModel = ctx.proxyModel;
195993
196659
  ctx.toolState.model = ctx.proxyModel;
195994
196660
  const label = ctx.oss ? "oss" : "router";
@@ -196059,7 +196725,7 @@ async function runProxyResolution(ctx) {
196059
196725
 
196060
196726
  // utils/prSummary.ts
196061
196727
  import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
196062
- import { dirname as dirname5, join as join26 } from "node:path";
196728
+ import { dirname as dirname5, join as join27 } from "node:path";
196063
196729
  var SUMMARY_FILE_NAME = "pullfrog-summary.md";
196064
196730
  var SUMMARY_SCAFFOLD = `# PR summary
196065
196731
 
@@ -196069,7 +196735,7 @@ var SUMMARY_SCAFFOLD = `# PR summary
196069
196735
  var MIN_SNAPSHOT_LENGTH = 60;
196070
196736
  var MAX_SNAPSHOT_LENGTH = 32768;
196071
196737
  function summaryFilePath(tmpdir4) {
196072
- return join26(tmpdir4, SUMMARY_FILE_NAME);
196738
+ return join27(tmpdir4, SUMMARY_FILE_NAME);
196073
196739
  }
196074
196740
  async function seedSummaryFile(params) {
196075
196741
  const path4 = summaryFilePath(params.tmpdir);
@@ -196198,6 +196864,7 @@ var defaultSettings = {
196198
196864
  shell: "restricted",
196199
196865
  prApproveEnabled: false,
196200
196866
  autoMergeEnabled: false,
196867
+ codexAgent: false,
196201
196868
  signedCommits: false,
196202
196869
  repoIntelligence: false,
196203
196870
  progressComments: true,
@@ -196686,7 +197353,7 @@ ${genericBody}`,
196686
197353
  }
196687
197354
 
196688
197355
  // utils/runLifecycle.ts
196689
- var core9 = __toESM(require_core(), 1);
197356
+ var core10 = __toESM(require_core(), 1);
196690
197357
 
196691
197358
  // utils/autoMerge.ts
196692
197359
  function hasBlockingHumanReview(reviews) {
@@ -197072,7 +197739,7 @@ async function finalizeSuccessRun(input) {
197072
197739
  }
197073
197740
  if (input.toolState.output) {
197074
197741
  log2.info(`::pullfrog-output::${Buffer.from(input.toolState.output).toString("base64")}`);
197075
- core9.setOutput("result", input.toolState.output);
197742
+ core10.setOutput("result", input.toolState.output);
197076
197743
  }
197077
197744
  if (input.result.success) {
197078
197745
  await approveAfterFix(input.toolContext).catch((error52) => {
@@ -197536,7 +198203,11 @@ async function main() {
197536
198203
  }
197537
198204
  const resolvedModel = credentials.kind === "fellBack" ? credentials.replacement : configuredModel;
197538
198205
  vertexCredentials = materializeVertexCredentials({ model: resolvedModel });
197539
- const agent2 = resolveAgent({ model: resolvedModel });
198206
+ const agent2 = resolveAgent({
198207
+ model: resolvedModel,
198208
+ proxyModel: payload.proxyModel,
198209
+ codexAgent: runContext.repoSettings.codexAgent
198210
+ });
197540
198211
  const effectiveModel = payload.proxyModel ?? resolvedModel ?? payload.model;
197541
198212
  toolState.model = effectiveModel;
197542
198213
  if (!payload.proxyModel) {
@@ -197720,8 +198391,8 @@ ${instructions.user}` : null,
197720
198391
  log2.info(instructions.full);
197721
198392
  });
197722
198393
  if (agentId === "opencode") {
197723
- const pluginDir = join27(process.cwd(), ".opencode", "plugin");
197724
- const hasPlugins = existsSync8(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
198394
+ const pluginDir = join28(process.cwd(), ".opencode", "plugin");
198395
+ const hasPlugins = existsSync9(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
197725
198396
  if (hasPlugins && toolState.dependencyInstallation?.promise) {
197726
198397
  log2.info(
197727
198398
  "\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"
@@ -197913,27 +198584,27 @@ async function runMain() {
197913
198584
  }
197914
198585
  } catch (error52) {
197915
198586
  const errorMessage = error52 instanceof Error ? error52.message : "unknown error occurred";
197916
- core10.setFailed(`action failed: ${errorMessage}`);
198587
+ core11.setFailed(`action failed: ${errorMessage}`);
197917
198588
  }
197918
198589
  }
197919
198590
  async function tokenMain() {
197920
- const reposInput = core10.getInput("repos");
198591
+ const reposInput = core11.getInput("repos");
197921
198592
  const additionalRepos = reposInput ? reposInput.split(",").map((r2) => r2.trim()).filter(Boolean) : [];
197922
198593
  const token = await acquireNewToken({ repos: additionalRepos });
197923
- core10.setSecret(token);
197924
- core10.saveState(STATE_TOKEN, token);
197925
- core10.setOutput("token", token);
198594
+ core11.setSecret(token);
198595
+ core11.saveState(STATE_TOKEN, token);
198596
+ core11.setOutput("token", token);
197926
198597
  const scope2 = additionalRepos.length ? `current repo + ${additionalRepos.join(", ")}` : "current repo only";
197927
- core10.info(`\xBB installation token acquired (${scope2})`);
198598
+ core11.info(`\xBB installation token acquired (${scope2})`);
197928
198599
  }
197929
198600
  async function tokenPost() {
197930
- const token = core10.getState(STATE_TOKEN);
198601
+ const token = core11.getState(STATE_TOKEN);
197931
198602
  if (!token) {
197932
- core10.debug("no token found in state, skipping revocation");
198603
+ core11.debug("no token found in state, skipping revocation");
197933
198604
  return;
197934
198605
  }
197935
198606
  await revokeGitHubInstallationToken(token);
197936
- core10.info("\xBB installation token revoked");
198607
+ core11.info("\xBB installation token revoked");
197937
198608
  }
197938
198609
  function printGhaUsage(params) {
197939
198610
  params.stream(`usage: ${params.prog} gha [subcommand]
@@ -198049,7 +198720,7 @@ async function run(args2) {
198049
198720
  }
198050
198721
  } catch (error52) {
198051
198722
  const message = error52 instanceof Error ? error52.message : String(error52);
198052
- core10.setFailed(message);
198723
+ core11.setFailed(message);
198053
198724
  }
198054
198725
  }
198055
198726
 
@@ -198896,7 +199567,7 @@ async function runCli4(input) {
198896
199567
  }
198897
199568
 
198898
199569
  // cli.ts
198899
- var VERSION10 = "0.1.57";
199570
+ var VERSION10 = "0.1.59";
198900
199571
  var bin = basename2(process.argv[1] || "");
198901
199572
  var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
198902
199573
  var rawArgs = process.argv.slice(2);