pullfrog 0.1.56 → 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:
@@ -106536,15 +106566,13 @@ function formatMcpToolRef(agentId, toolName) {
106536
106566
  function extractMcpToolRefs(agentId, text) {
106537
106567
  const prefix = formatMcpToolRef(agentId, "");
106538
106568
  const escaped = prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
106539
- return [...text.matchAll(new RegExp(`${escaped}([a-z0-9_]+)`, "g"))].map((match3) => match3[1]);
106569
+ return [...text.matchAll(new RegExp(`(?<![A-Za-z0-9_])${escaped}([a-z0-9_]+)`, "g"))].map(
106570
+ (match3) => match3[1]
106571
+ );
106540
106572
  }
106541
- function assertPromptToolRefs(params) {
106573
+ function findDanglingPromptToolRefs(params) {
106542
106574
  const registered = new Set(params.toolNames);
106543
- const dangling = [...new Set(extractMcpToolRefs(params.agentId, params.prompt))].filter((name) => !registered.has(name)).sort();
106544
- if (dangling.length === 0) return;
106545
- throw new Error(
106546
- `prompt advertises ${dangling.length} tool(s) the MCP server did not register: ${dangling.join(", ")}. registered: ${[...registered].sort().join(", ")}`
106547
- );
106575
+ return [...new Set(extractMcpToolRefs(params.agentId, params.prompt))].filter((name) => !registered.has(name)).sort();
106548
106576
  }
106549
106577
  function hasSimilarIssues(params) {
106550
106578
  return params.repoIntelligence && !!params.event.issue_number && !params.event.is_pr;
@@ -107030,7 +107058,8 @@ import { pipeline } from "node:stream/promises";
107030
107058
  async function installFromNpmTarball(params) {
107031
107059
  const tempDir = process.env.PULLFROG_TEMP_DIR;
107032
107060
  if (!tempDir) throw new Error("PULLFROG_TEMP_DIR is not set");
107033
- const extractedDir = join2(tempDir, "package");
107061
+ const packageRoot = join2(tempDir, `npm-${params.packageName.replace(/[^a-z0-9]+/gi, "-")}`);
107062
+ const extractedDir = join2(packageRoot, "package");
107034
107063
  const cliPath = join2(extractedDir, params.executablePath);
107035
107064
  if (existsSync2(cliPath)) {
107036
107065
  log2.debug(`\xBB using cached binary at ${cliPath}`);
@@ -107056,7 +107085,8 @@ async function installFromNpmTarball(params) {
107056
107085
  }
107057
107086
  }
107058
107087
  log2.debug(`\xBB installing ${params.packageName}@${resolvedVersion}...`);
107059
- const tarballPath = join2(tempDir, "package.tgz");
107088
+ mkdirSync(packageRoot, { recursive: true });
107089
+ const tarballPath = join2(packageRoot, "package.tgz");
107060
107090
  const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org";
107061
107091
  let tarballUrl;
107062
107092
  if (params.packageName.startsWith("@")) {
@@ -107076,7 +107106,7 @@ async function installFromNpmTarball(params) {
107076
107106
  await pipeline(response.body, fileStream);
107077
107107
  log2.debug(`\xBB downloaded tarball to ${tarballPath}`);
107078
107108
  log2.debug(`\xBB extracting tarball...`);
107079
- const extractResult = spawnSync("tar", ["-xzf", tarballPath, "-C", tempDir], {
107109
+ const extractResult = spawnSync("tar", ["-xzf", tarballPath, "-C", packageRoot], {
107080
107110
  stdio: "pipe",
107081
107111
  encoding: "utf-8"
107082
107112
  });
@@ -107085,9 +107115,6 @@ async function installFromNpmTarball(params) {
107085
107115
  `Failed to extract tarball: ${extractResult.stderr || extractResult.stdout || "Unknown error"}`
107086
107116
  );
107087
107117
  }
107088
- if (!existsSync2(cliPath)) {
107089
- throw new Error(`Executable not found in extracted package at ${cliPath}`);
107090
- }
107091
107118
  if (params.installDependencies) {
107092
107119
  log2.debug(`\xBB installing dependencies for ${params.packageName}...`);
107093
107120
  const installResult = spawnSync("npm", ["install", "--production"], {
@@ -107108,6 +107135,9 @@ async function installFromNpmTarball(params) {
107108
107135
  }
107109
107136
  log2.debug(`\xBB dependencies installed`);
107110
107137
  }
107138
+ if (!existsSync2(cliPath)) {
107139
+ throw new Error(`Executable not found in extracted package at ${cliPath}`);
107140
+ }
107111
107141
  chmodSync(cliPath, 493);
107112
107142
  log2.debug(`\xBB ${params.packageName} installed at ${cliPath}`);
107113
107143
  return cliPath;
@@ -107281,6 +107311,7 @@ function resolveRunAlias(ctx) {
107281
107311
  }
107282
107312
  const model = ctx.resolvedModel;
107283
107313
  if (!model) return void 0;
107314
+ if (model.startsWith(`${AZURE_PROVIDER}/`)) return void 0;
107284
107315
  return modelAliases.find((a2) => !a2.fallback && a2.resolve === model) ?? matchHostedAnthropicAlias(model);
107285
107316
  }
107286
107317
  function ossEffortFloor(ctx) {
@@ -107319,7 +107350,7 @@ var import_semver = __toESM(require_semver2(), 1);
107319
107350
  // package.json
107320
107351
  var package_default = {
107321
107352
  name: "pullfrog",
107322
- version: "0.1.56",
107353
+ version: "0.1.59",
107323
107354
  type: "module",
107324
107355
  bin: {
107325
107356
  pullfrog: "dist/cli.mjs",
@@ -107351,6 +107382,8 @@ var package_default = {
107351
107382
  "@clack/prompts": "^1.2.0",
107352
107383
  "@modelcontextprotocol/sdk": "^1.26.0",
107353
107384
  "@opencode-ai/sdk": "1.18.5",
107385
+ "@openai/codex": "0.147.0",
107386
+ "@openai/codex-sdk": "0.147.0",
107354
107387
  "@octokit/plugin-throttling": "^11.0.3",
107355
107388
  "@octokit/rest": "^22.0.0",
107356
107389
  "@octokit/webhooks-types": "^7.6.1",
@@ -109472,13 +109505,735 @@ var claude = agent({
109472
109505
  }
109473
109506
  });
109474
109507
 
109475
- // agents/opencode.ts
109508
+ // agents/codex.ts
109476
109509
  var core2 = __toESM(require_core(), 1);
109477
- import { spawn as nodeSpawn2 } from "node:child_process";
109478
- 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";
109479
109511
  import { join as join7 } from "node:path";
109480
109512
  import { performance as performance6 } from "node:perf_hooks";
109481
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
+
109482
110237
  // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/core/serverSentEvents.gen.js
109483
110238
  var createSseClient = ({ onRequest, onSseError, onSseEvent, responseTransformer, responseValidator, sseDefaultRetryDelay, sseMaxRetryAttempts, sseMaxRetryDelay, sseSleepFn, url: url4, ...options }) => {
109484
110239
  let lastEventId;
@@ -114885,71 +115640,6 @@ var import_cross_spawn = __toESM(require_cross_spawn(), 1);
114885
115640
  // agents/opencode.ts
114886
115641
  var import_undici = __toESM(require_undici2(), 1);
114887
115642
 
114888
- // utils/codexHome.ts
114889
- import { execFileSync as execFileSync4 } from "node:child_process";
114890
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
114891
- import { homedir as homedir2, userInfo } from "node:os";
114892
- import { join as join6 } from "node:path";
114893
- var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
114894
- var PULLFROG_DATA_DIR = "/var/lib/pullfrog";
114895
- function installCodexAuth() {
114896
- const raw2 = process.env[CODEX_AUTH_ENV];
114897
- if (!raw2) return null;
114898
- const body = parseCodexAuthBody(raw2);
114899
- if (!body) {
114900
- log2.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
114901
- return null;
114902
- }
114903
- const expiresMs = decodeJwtExpMs(body.tokens.access_token) ?? 0;
114904
- const xdgDataHome = resolveDataHome();
114905
- const opencodeDir = join6(xdgDataHome, "opencode");
114906
- const authPath = join6(opencodeDir, "auth.json");
114907
- const opencodeAuth = {
114908
- openai: {
114909
- type: "oauth",
114910
- refresh: body.tokens.refresh_token,
114911
- access: body.tokens.access_token,
114912
- expires: expiresMs,
114913
- ...body.tokens.account_id ? { accountId: body.tokens.account_id } : {}
114914
- }
114915
- };
114916
- mkdirSync5(opencodeDir, { recursive: true });
114917
- writeFileSync5(authPath, `${JSON.stringify(opencodeAuth, null, 2)}
114918
- `, { mode: 384 });
114919
- process.env.XDG_DATA_HOME = xdgDataHome;
114920
- log2.info(`\xBB installed Codex auth at ${authPath}`);
114921
- return {
114922
- authPath,
114923
- xdgDataHome,
114924
- originalRefresh: body.tokens.refresh_token
114925
- };
114926
- }
114927
- function resolveDataHome() {
114928
- if (process.env.CI !== "true") return join6(homedir2(), ".local", "share");
114929
- bootstrapPullfrogDataDir();
114930
- return PULLFROG_DATA_DIR;
114931
- }
114932
- function bootstrapPullfrogDataDir() {
114933
- const user = userInfo().username;
114934
- let primaryGroup;
114935
- try {
114936
- primaryGroup = execFileSync4("id", ["-gn", user], { stdio: "pipe", encoding: "utf-8" }).trim();
114937
- } catch {
114938
- primaryGroup = user;
114939
- }
114940
- try {
114941
- execFileSync4("sudo", ["-n", "mkdir", "-p", PULLFROG_DATA_DIR], { stdio: "pipe" });
114942
- execFileSync4("sudo", ["-n", "chown", `${user}:${primaryGroup}`, PULLFROG_DATA_DIR], {
114943
- stdio: "pipe"
114944
- });
114945
- execFileSync4("sudo", ["-n", "chmod", "700", PULLFROG_DATA_DIR], { stdio: "pipe" });
114946
- } catch (err) {
114947
- throw new Error(
114948
- `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.`
114949
- );
114950
- }
114951
- }
114952
-
114953
115643
  // utils/worktree.ts
114954
115644
  async function dirtyTrackedPaths(params = {}) {
114955
115645
  const result = await spawn3({
@@ -115217,6 +115907,30 @@ function openAICompatibleProvider(model) {
115217
115907
  }
115218
115908
  };
115219
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
+ }
115220
115934
  var KIMI_ENFORCERLESS_PROVIDERS = ["siliconflow", "together"];
115221
115935
  function kimiOpenRouterProviderOverrides() {
115222
115936
  return Object.fromEntries(
@@ -115308,7 +116022,8 @@ function buildSecurityConfig(ctx, model) {
115308
116022
  // see opencodeShared.ts.
115309
116023
  provider: {
115310
116024
  openrouter: { models: kimiOpenRouterProviderOverrides() },
115311
- ...openAICompatibleProvider(model)
116025
+ ...openAICompatibleProvider(model),
116026
+ ...azureProvider(model)
115312
116027
  }
115313
116028
  };
115314
116029
  if (model) {
@@ -115476,7 +116191,7 @@ function isModelOutput(ctx, part) {
115476
116191
  }
115477
116192
  async function dispatchEvent(ctx, event) {
115478
116193
  if (event.type === "message.part.updated") {
115479
- ctx.lastEventAt = performance6.now();
116194
+ ctx.lastEventAt = performance7.now();
115480
116195
  if (isModelOutput(ctx, event.properties.part)) ctx.sawModelOutput = true;
115481
116196
  await onPartUpdated(ctx, event.properties.part);
115482
116197
  return;
@@ -115543,7 +116258,7 @@ async function onToolPart(ctx, part, label, isOrchestrator) {
115543
116258
  if (toolName === "task" && status === "running" && isOrchestrator && !ctx.taskDispatchByCallID.has(toolId)) {
115544
116259
  const input = part.state.input ?? {};
115545
116260
  const dispatched = ctx.labeler.recordTaskDispatch(input);
115546
- ctx.taskDispatchByCallID.set(toolId, { label: dispatched, startedAt: performance6.now() });
116261
+ ctx.taskDispatchByCallID.set(toolId, { label: dispatched, startedAt: performance7.now() });
115547
116262
  log2.info(
115548
116263
  `\xBB dispatching subagent: ${dispatched}` + (input.subagent_type ? ` (subagent_type=${input.subagent_type})` : "")
115549
116264
  );
@@ -115573,7 +116288,7 @@ function processTerminalToolPart(ctx, part, label, isOrchestrator) {
115573
116288
  if (toolName === "task") {
115574
116289
  const dispatch = ctx.taskDispatchByCallID.get(toolId);
115575
116290
  if (dispatch) {
115576
- const dur = ((performance6.now() - dispatch.startedAt) / 1e3).toFixed(1);
116291
+ const dur = ((performance7.now() - dispatch.startedAt) / 1e3).toFixed(1);
115577
116292
  const outputStr = state.status === "completed" ? state.output : "";
115578
116293
  const preview = typeof outputStr === "string" && outputStr.length > 120 ? `${outputStr.slice(0, 120)}\u2026` : outputStr;
115579
116294
  log2.info(
@@ -115616,7 +116331,7 @@ function withLabel(label, message) {
115616
116331
  return label === ORCHESTRATOR_LABEL ? message : formatWithLabel(label, message);
115617
116332
  }
115618
116333
  async function runPromptTurn(ctx, params) {
115619
- const start = performance6.now();
116334
+ const start = performance7.now();
115620
116335
  const turnStartMs = Date.now();
115621
116336
  ctx.currentTurn = newTurn();
115622
116337
  const turn = ctx.currentTurn;
@@ -115650,7 +116365,7 @@ async function runPromptTurn(ctx, params) {
115650
116365
  } catch (err) {
115651
116366
  networkError = err instanceof Error ? err.message : String(err);
115652
116367
  }
115653
- const durationMs = performance6.now() - start;
116368
+ const durationMs = performance7.now() - start;
115654
116369
  const aggregatedUsage = await aggregateTurnUsage(ctx, turnStartMs);
115655
116370
  const usage = aggregatedUsage ?? buildUsage(turn, assistant);
115656
116371
  const finalText = turn.finalText || extractTextFromParts(returnedParts) || "";
@@ -115804,7 +116519,7 @@ function startInnerActivityWatchdog(params) {
115804
116519
  let fired = false;
115805
116520
  const id = setInterval(() => {
115806
116521
  if (fired) return;
115807
- const idleMs = performance6.now() - params.ctx.lastEventAt;
116522
+ const idleMs = performance7.now() - params.ctx.lastEventAt;
115808
116523
  const budgetMs = params.ctx.sawModelOutput ? params.timeoutMs : AGENT_FIRST_EVENT_TIMEOUT_MS;
115809
116524
  if (idleMs <= budgetMs) return;
115810
116525
  fired = true;
@@ -115843,12 +116558,12 @@ var opencode = agent({
115843
116558
  const model = vertexModel ?? (isBedrockRoute ? `amazon-bedrock/${rawModel}` : rawModel);
115844
116559
  const homeEnv = {
115845
116560
  HOME: ctx.tmpdir,
115846
- XDG_CONFIG_HOME: join7(ctx.tmpdir, ".config")
116561
+ XDG_CONFIG_HOME: join8(ctx.tmpdir, ".config")
115847
116562
  };
115848
- const opencodePluginDir = join7(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
115849
- mkdirSync6(opencodePluginDir, { recursive: true });
115850
- writeFileSync6(
115851
- 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),
115852
116567
  buildOpencodeSubagentGateSource(ctx.subagentDeniedTools)
115853
116568
  );
115854
116569
  const agentBrowserVersion = getDevDependencyVersion("agent-browser");
@@ -115879,12 +116594,13 @@ var opencode = agent({
115879
116594
  if (codexAuth) {
115880
116595
  env2.XDG_DATA_HOME = codexAuth.xdgDataHome;
115881
116596
  delete env2.OPENAI_API_KEY;
115882
- core2.saveState(
116597
+ core3.saveState(
115883
116598
  "codex_writeback",
115884
116599
  JSON.stringify({
115885
116600
  apiToken: ctx.apiToken,
115886
116601
  authPath: codexAuth.authPath,
115887
- originalRefresh: codexAuth.originalRefresh
116602
+ originalRefresh: codexAuth.originalRefresh,
116603
+ originalIdToken: codexAuth.originalIdToken
115888
116604
  })
115889
116605
  );
115890
116606
  }
@@ -115938,7 +116654,7 @@ var opencode = agent({
115938
116654
  onToolUse: ctx.onToolUse,
115939
116655
  currentTurn: null,
115940
116656
  eventCount: 0,
115941
- lastEventAt: performance6.now(),
116657
+ lastEventAt: performance7.now(),
115942
116658
  sawModelOutput: false,
115943
116659
  promptMessageID: void 0,
115944
116660
  taskDispatchByCallID: /* @__PURE__ */ new Map(),
@@ -116048,7 +116764,7 @@ async function runTurnGuarded(ctx, fn2) {
116048
116764
  }
116049
116765
 
116050
116766
  // agents/index.ts
116051
- var agents = { claude, opencode };
116767
+ var agents = { claude, codex, opencode };
116052
116768
 
116053
116769
  // node_modules/.pnpm/@ark+util@0.56.0/node_modules/@ark/util/out/arrays.js
116054
116770
  var liftArray = (data) => Array.isArray(data) ? data : [data];
@@ -136380,7 +137096,7 @@ var require_core$2 = /* @__PURE__ */ __commonJSMin(((exports) => {
136380
137096
  Object.defineProperty(exports, "__esModule", { value: true });
136381
137097
  const id_1 = require_id2();
136382
137098
  const ref_1 = require_ref2();
136383
- const core11 = [
137099
+ const core12 = [
136384
137100
  "$schema",
136385
137101
  "$id",
136386
137102
  "$defs",
@@ -136390,7 +137106,7 @@ var require_core$2 = /* @__PURE__ */ __commonJSMin(((exports) => {
136390
137106
  id_1.default,
136391
137107
  ref_1.default
136392
137108
  ];
136393
- exports.default = core11;
137109
+ exports.default = core12;
136394
137110
  }));
136395
137111
  var require_limitNumber2 = /* @__PURE__ */ __commonJSMin(((exports) => {
136396
137112
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -138528,7 +139244,7 @@ var require_json_schema_2019_09 = /* @__PURE__ */ __commonJSMin(((exports) => {
138528
139244
  const metaSchema = require_schema$1();
138529
139245
  const applicator = require_applicator$1();
138530
139246
  const content = require_content$1();
138531
- const core11 = require_core$1();
139247
+ const core12 = require_core$1();
138532
139248
  const format2 = require_format3();
138533
139249
  const metadata = require_meta_data$1();
138534
139250
  const validation = require_validation$1();
@@ -138538,7 +139254,7 @@ var require_json_schema_2019_09 = /* @__PURE__ */ __commonJSMin(((exports) => {
138538
139254
  metaSchema,
138539
139255
  applicator,
138540
139256
  content,
138541
- core11,
139257
+ core12,
138542
139258
  with$data(this, format2),
138543
139259
  metadata,
138544
139260
  with$data(this, validation)
@@ -138978,7 +139694,7 @@ var require_json_schema_2020_12 = /* @__PURE__ */ __commonJSMin(((exports) => {
138978
139694
  const applicator = require_applicator2();
138979
139695
  const unevaluated = require_unevaluated();
138980
139696
  const content = require_content();
138981
- const core11 = require_core4();
139697
+ const core12 = require_core4();
138982
139698
  const format2 = require_format_annotation();
138983
139699
  const metadata = require_meta_data();
138984
139700
  const validation = require_validation2();
@@ -138989,7 +139705,7 @@ var require_json_schema_2020_12 = /* @__PURE__ */ __commonJSMin(((exports) => {
138989
139705
  applicator,
138990
139706
  unevaluated,
138991
139707
  content,
138992
- core11,
139708
+ core12,
138993
139709
  with$data(this, format2),
138994
139710
  metadata,
138995
139711
  with$data(this, validation)
@@ -150431,7 +151147,7 @@ var require_core$22 = /* @__PURE__ */ __commonJSMin2(((exports) => {
150431
151147
  Object.defineProperty(exports, "__esModule", { value: true });
150432
151148
  const id_1 = require_id3();
150433
151149
  const ref_1 = require_ref3();
150434
- const core11 = [
151150
+ const core12 = [
150435
151151
  "$schema",
150436
151152
  "$id",
150437
151153
  "$defs",
@@ -150441,7 +151157,7 @@ var require_core$22 = /* @__PURE__ */ __commonJSMin2(((exports) => {
150441
151157
  id_1.default,
150442
151158
  ref_1.default
150443
151159
  ];
150444
- exports.default = core11;
151160
+ exports.default = core12;
150445
151161
  }));
150446
151162
  var require_limitNumber3 = /* @__PURE__ */ __commonJSMin2(((exports) => {
150447
151163
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -152579,7 +153295,7 @@ var require_json_schema_2019_092 = /* @__PURE__ */ __commonJSMin2(((exports) =>
152579
153295
  const metaSchema = require_schema$12();
152580
153296
  const applicator = require_applicator$12();
152581
153297
  const content = require_content$12();
152582
- const core11 = require_core$12();
153298
+ const core12 = require_core$12();
152583
153299
  const format2 = require_format4();
152584
153300
  const metadata = require_meta_data$12();
152585
153301
  const validation = require_validation$12();
@@ -152589,7 +153305,7 @@ var require_json_schema_2019_092 = /* @__PURE__ */ __commonJSMin2(((exports) =>
152589
153305
  metaSchema,
152590
153306
  applicator,
152591
153307
  content,
152592
- core11,
153308
+ core12,
152593
153309
  with$data(this, format2),
152594
153310
  metadata,
152595
153311
  with$data(this, validation)
@@ -153029,7 +153745,7 @@ var require_json_schema_2020_122 = /* @__PURE__ */ __commonJSMin2(((exports) =>
153029
153745
  const applicator = require_applicator3();
153030
153746
  const unevaluated = require_unevaluated2();
153031
153747
  const content = require_content2();
153032
- const core11 = require_core5();
153748
+ const core12 = require_core5();
153033
153749
  const format2 = require_format_annotation2();
153034
153750
  const metadata = require_meta_data2();
153035
153751
  const validation = require_validation3();
@@ -153040,7 +153756,7 @@ var require_json_schema_2020_122 = /* @__PURE__ */ __commonJSMin2(((exports) =>
153040
153756
  applicator,
153041
153757
  unevaluated,
153042
153758
  content,
153043
- core11,
153759
+ core12,
153044
153760
  with$data(this, format2),
153045
153761
  metadata,
153046
153762
  with$data(this, validation)
@@ -173001,123 +173717,8 @@ var pbkdf2Async = promisify(pbkdf2);
173001
173717
 
173002
173718
  // utils/browser.ts
173003
173719
  import { execFileSync as execFileSync5, spawnSync as spawnSync4 } from "node:child_process";
173004
- import { existsSync as existsSync4 } from "node:fs";
173720
+ import { existsSync as existsSync5 } from "node:fs";
173005
173721
  import { dirname as dirname2 } from "node:path";
173006
-
173007
- // utils/secrets.ts
173008
- var SENSITIVE_PATTERNS = [
173009
- /_KEY$/i,
173010
- /_SECRET$/i,
173011
- /_TOKEN$/i,
173012
- /_PASSWORD$/i,
173013
- /_CREDENTIAL$/i
173014
- ];
173015
- function isSensitiveEnvName(key) {
173016
- return SENSITIVE_PATTERNS.some((p) => p.test(key));
173017
- }
173018
- var SAFE_ENV_PREFIXES = ["GITHUB_", "RUNNER_", "JAVA_HOME_", "GOROOT_"];
173019
- var SAFE_ENV_NAMES = /* @__PURE__ */ new Set([
173020
- // system
173021
- "CI",
173022
- "HOME",
173023
- "LANG",
173024
- "LOGNAME",
173025
- "PATH",
173026
- "SHELL",
173027
- "SHLVL",
173028
- "TERM",
173029
- "TMPDIR",
173030
- "TZ",
173031
- "USER",
173032
- "XDG_CONFIG_HOME",
173033
- "XDG_RUNTIME_DIR",
173034
- "DEBIAN_FRONTEND",
173035
- // runner image toolchain
173036
- "ACCEPT_EULA",
173037
- "AGENT_TOOLSDIRECTORY",
173038
- "ANDROID_HOME",
173039
- "ANDROID_NDK",
173040
- "ANDROID_NDK_HOME",
173041
- "ANDROID_NDK_LATEST_HOME",
173042
- "ANDROID_NDK_ROOT",
173043
- "ANDROID_SDK_ROOT",
173044
- "ANT_HOME",
173045
- "AZURE_EXTENSION_DIR",
173046
- "BOOTSTRAP_HASKELL_NONINTERACTIVE",
173047
- "CHROME_BIN",
173048
- "CHROMEWEBDRIVER",
173049
- "CONDA",
173050
- "DOTNET_MULTILEVEL_LOOKUP",
173051
- "DOTNET_NOLOGO",
173052
- "DOTNET_SKIP_FIRST_TIME_EXPERIENCE",
173053
- "EDGEWEBDRIVER",
173054
- "GECKOWEBDRIVER",
173055
- "GHCUP_INSTALL_BASE_PREFIX",
173056
- "GRADLE_HOME",
173057
- "JAVA_HOME",
173058
- "HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS",
173059
- "HOMEBREW_NO_AUTO_UPDATE",
173060
- "ImageOS",
173061
- "ImageVersion",
173062
- "NVM_DIR",
173063
- "PIPX_BIN_DIR",
173064
- "PIPX_HOME",
173065
- "PSModulePath",
173066
- "SELENIUM_JAR_PATH",
173067
- "SGX_AESM_ADDR",
173068
- "SWIFT_PATH",
173069
- "VCPKG_INSTALLATION_ROOT"
173070
- ]);
173071
- var _userAllowlist = null;
173072
- function setEnvAllowlist(raw2) {
173073
- const names = raw2.split("\n").map((line) => line.trim()).filter(Boolean);
173074
- _userAllowlist = new Set(names);
173075
- }
173076
- function isSafeEnvVar(key) {
173077
- if (SAFE_ENV_NAMES.has(key)) return true;
173078
- return SAFE_ENV_PREFIXES.some((p) => key.startsWith(p));
173079
- }
173080
- var WORKFLOW_COMMAND_ENV_NAMES = [
173081
- "GITHUB_ENV",
173082
- "GITHUB_PATH",
173083
- "GITHUB_OUTPUT",
173084
- "GITHUB_STATE",
173085
- "GITHUB_STEP_SUMMARY",
173086
- // dropping the five paths above but keeping RUNNER_TEMP would be theatre:
173087
- // the file-command files live in `$RUNNER_TEMP/_runner_file_commands/`, so the
173088
- // directory is one `ls` away. nothing in the install path reads it (only our
173089
- // own leak-surface wipe in `setup.ts`, which runs in the parent), and package
173090
- // managers use TMPDIR.
173091
- "RUNNER_TEMP"
173092
- ];
173093
- function filterEnvForUntrustedCode() {
173094
- const env2 = filterEnv();
173095
- for (const name of WORKFLOW_COMMAND_ENV_NAMES) delete env2[name];
173096
- return env2;
173097
- }
173098
- function filterEnv() {
173099
- const filtered = {};
173100
- for (const [key, value2] of Object.entries(process.env)) {
173101
- if (value2 === void 0) continue;
173102
- const userAllowed = _userAllowlist?.has(key) ?? false;
173103
- if (isSensitiveEnvName(key) && !userAllowed) continue;
173104
- if (isSafeEnvVar(key) || userAllowed) {
173105
- filtered[key] = value2;
173106
- }
173107
- }
173108
- return filtered;
173109
- }
173110
- function resolveEnv(mode) {
173111
- if (mode === "inherit") {
173112
- return process.env;
173113
- }
173114
- if (mode === "restricted" || mode === void 0) {
173115
- return filterEnv();
173116
- }
173117
- return { ...filterEnv(), ...mode };
173118
- }
173119
-
173120
- // utils/browser.ts
173121
173722
  var CHROME_PATHS = ["/usr/bin/google-chrome-stable"];
173122
173723
  var systemChromePath;
173123
173724
  function findSystemChromePath() {
@@ -173125,7 +173726,7 @@ function findSystemChromePath() {
173125
173726
  return systemChromePath || void 0;
173126
173727
  }
173127
173728
  for (const p of CHROME_PATHS) {
173128
- if (existsSync4(p)) {
173729
+ if (existsSync5(p)) {
173129
173730
  systemChromePath = p;
173130
173731
  log2.info(`found system chrome: ${p}`);
173131
173732
  return p;
@@ -173209,8 +173810,8 @@ function closeBrowserDaemon(toolState) {
173209
173810
 
173210
173811
  // mcp/checkout.ts
173211
173812
  import { createHash as createHash2 } from "node:crypto";
173212
- import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync9 } from "node:fs";
173213
- 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";
173214
173815
 
173215
173816
  // node_modules/.pnpm/@ark+schema@0.56.0/node_modules/@ark/schema/out/shared/errors.js
173216
173817
  var ArkError = class _ArkError extends CastableBase {
@@ -180682,8 +181283,8 @@ function ensureRepoState(toolState, init) {
180682
181283
 
180683
181284
  // utils/changeImpact.ts
180684
181285
  import { spawn as spawn4 } from "node:child_process";
180685
- import { writeFileSync as writeFileSync7 } from "node:fs";
180686
- import { join as join8 } from "node:path";
181286
+ import { writeFileSync as writeFileSync8 } from "node:fs";
181287
+ import { join as join9 } from "node:path";
180687
181288
  var MAX_CANDIDATES = 24;
180688
181289
  var MAX_ATOM_LENGTH = 128;
180689
181290
  var MAX_ATOMS = 12;
@@ -181030,8 +181631,8 @@ async function createChangeImpactArtifact(ctx, params) {
181030
181631
  headSha: params.headSha,
181031
181632
  lookupError: lookup2.error
181032
181633
  });
181033
- const path4 = join8(ctx.tmpdir, `pr-${params.pullNumber}-${params.headSha.slice(0, 7)}-impact.md`);
181034
- 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);
181035
181636
  return {
181036
181637
  path: path4,
181037
181638
  candidateCount: candidates.selected.length,
@@ -181283,8 +181884,8 @@ function readNumber(params) {
181283
181884
  // utils/gitAuth.ts
181284
181885
  import { execSync } from "node:child_process";
181285
181886
  import { createHash } from "node:crypto";
181286
- import { readFileSync as readFileSync3, realpathSync, unlinkSync } from "node:fs";
181287
- 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";
181288
181889
 
181289
181890
  // utils/shell.ts
181290
181891
  import { spawnSync as spawnSync5 } from "node:child_process";
@@ -181345,7 +181946,7 @@ var TRANSIENT_AUTH_PATTERNS = [
181345
181946
  ];
181346
181947
  var gitBinary;
181347
181948
  function hashFile(path4) {
181348
- return createHash("sha256").update(readFileSync3(path4)).digest("hex");
181949
+ return createHash("sha256").update(readFileSync4(path4)).digest("hex");
181349
181950
  }
181350
181951
  function resolveGit() {
181351
181952
  const whichPath = execSync("which git", { encoding: "utf-8" }).trim();
@@ -181374,7 +181975,7 @@ function resolveHooksDir(cwd, gitPath) {
181374
181975
  cwd,
181375
181976
  log: false
181376
181977
  }).trim();
181377
- const hooksDir = join9(commonDir, "hooks");
181978
+ const hooksDir = join10(commonDir, "hooks");
181378
181979
  hooksDirCache.set(cwd, hooksDir);
181379
181980
  return hooksDir;
181380
181981
  }
@@ -181681,7 +182282,7 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
181681
182282
  }
181682
182283
 
181683
182284
  // yes/index.ts
181684
- import { performance as performance7 } from "node:perf_hooks";
182285
+ import { performance as performance8 } from "node:perf_hooks";
181685
182286
  import { setTimeout as sleep2 } from "node:timers/promises";
181686
182287
 
181687
182288
  // node_modules/.pnpm/lru-cache@11.5.2/node_modules/lru-cache/dist/esm/node/index.min.js
@@ -182396,14 +182997,14 @@ function _op(fn2, options) {
182396
182997
  `failed to create cache key${namePrefix ? ` for ${options.name}` : ""}: ${errorMessage}`
182397
182998
  );
182398
182999
  }
182399
- const startTime = performance7.now();
183000
+ const startTime = performance8.now();
182400
183001
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
182401
183002
  log3.debug(`${namePrefix}[${timestamp}] cache lookup started`, { key });
182402
183003
  if (shouldCache && lruCache) {
182403
183004
  const cached4 = lruCache.get(key);
182404
183005
  if (cached4 !== void 0) {
182405
183006
  if (keyMap?.get(key) === void 0) keyMap?.set(key, input);
182406
- const runtime = performance7.now() - startTime;
183007
+ const runtime = performance8.now() - startTime;
182407
183008
  log3.debug(`${namePrefix}[${timestamp}] cache hit (from lru)`, {
182408
183009
  key,
182409
183010
  runtime: `${Math.round(runtime)}ms`
@@ -182422,12 +183023,12 @@ function _op(fn2, options) {
182422
183023
  defaultCacheMiss(key);
182423
183024
  const retries = options.retries ?? [];
182424
183025
  let lastError;
182425
- const execStartTime = performance7.now();
183026
+ const execStartTime = performance8.now();
182426
183027
  const promise2 = (async () => {
182427
183028
  for (let attempt = 0; attempt <= retries.length; attempt++) {
182428
183029
  try {
182429
183030
  const result = acceptsCtx ? await fn2(input, ctx) : await fn2(input);
182430
- const execRuntime2 = performance7.now() - execStartTime;
183031
+ const execRuntime2 = performance8.now() - execStartTime;
182431
183032
  const skip = options.skipCache ? options.skipCache(result) : false;
182432
183033
  if (shouldCache && lruCache && result !== null && result !== void 0 && !skip) {
182433
183034
  lruCache.set(key, result);
@@ -182480,7 +183081,7 @@ function _op(fn2, options) {
182480
183081
  }
182481
183082
  }
182482
183083
  }
182483
- const execRuntime = performance7.now() - execStartTime;
183084
+ const execRuntime = performance8.now() - execStartTime;
182484
183085
  log3.debug(`${namePrefix}[${timestamp}] function failed after all retries`, {
182485
183086
  key,
182486
183087
  runtime: `${Math.round(execRuntime)}ms`,
@@ -182545,13 +183146,13 @@ function _op(fn2, options) {
182545
183146
 
182546
183147
  // mcp/git.ts
182547
183148
  import { randomUUID as randomUUID3 } from "node:crypto";
182548
- import { writeFileSync as writeFileSync8 } from "node:fs";
182549
- import { join as join12 } from "node:path";
183149
+ import { writeFileSync as writeFileSync9 } from "node:fs";
183150
+ import { join as join13 } from "node:path";
182550
183151
 
182551
183152
  // utils/apiCommit.ts
182552
183153
  import { execFileSync as execFileSync6 } from "node:child_process";
182553
183154
  import { lstat, readlink } from "node:fs/promises";
182554
- import { join as join10 } from "node:path";
183155
+ import { join as join11 } from "node:path";
182555
183156
  var GITHUB_API = "https://api.github.com";
182556
183157
  var MAX_BLOB_BYTES = 30 * 1024 * 1024;
182557
183158
  var BLOB_UPLOAD_CONCURRENCY = 8;
@@ -182616,7 +183217,7 @@ async function assertApiCommittable(files) {
182616
183217
  const root = getRepoRoot();
182617
183218
  const attrs = $(
182618
183219
  "git",
182619
- ["check-attr", "filter", "-z", "--", ...present.map((p) => join10(root, p))],
183220
+ ["check-attr", "filter", "-z", "--", ...present.map((p) => join11(root, p))],
182620
183221
  { log: false }
182621
183222
  );
182622
183223
  const parts = attrs.split("\0");
@@ -182628,7 +183229,7 @@ async function assertApiCommittable(files) {
182628
183229
  }
182629
183230
  }
182630
183231
  for (const path4 of present) {
182631
- const stat = await lstat(join10(root, path4));
183232
+ const stat = await lstat(join11(root, path4));
182632
183233
  if (stat.isDirectory()) {
182633
183234
  throw new Error(
182634
183235
  `'${path4}' is a directory (nested repository or submodule?) \u2014 signed commits only support files and symlinks.`
@@ -182637,7 +183238,7 @@ async function assertApiCommittable(files) {
182637
183238
  }
182638
183239
  }
182639
183240
  async function createBlobEntry(params) {
182640
- const absPath = join10(params.repoRoot, params.path);
183241
+ const absPath = join11(params.repoRoot, params.path);
182641
183242
  const stat = await lstat(absPath);
182642
183243
  if (stat.size > MAX_BLOB_BYTES) {
182643
183244
  throw new Error(
@@ -182814,10 +183415,10 @@ this usually means your local branch has commits that were never pushed \u2014 s
182814
183415
  }
182815
183416
 
182816
183417
  // utils/github.ts
182817
- var core3 = __toESM(require_core(), 1);
183418
+ var core4 = __toESM(require_core(), 1);
182818
183419
  import { createSign } from "node:crypto";
182819
183420
  import { rename, writeFile } from "node:fs/promises";
182820
- import { dirname as dirname3, join as join11 } from "node:path";
183421
+ import { dirname as dirname3, join as join12 } from "node:path";
182821
183422
 
182822
183423
  // node_modules/.pnpm/@octokit+plugin-throttling@11.0.5_@octokit+core@7.0.7/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
182823
183424
  var import_light = __toESM(require_light(), 1);
@@ -187015,7 +187616,7 @@ async function fetchIdTokenFromStash(creds) {
187015
187616
  throw new Error("ID token response has no value field");
187016
187617
  }
187017
187618
  if (isGitHubActions) {
187018
- core3.setSecret(body.value);
187619
+ core4.setSecret(body.value);
187019
187620
  }
187020
187621
  return body.value;
187021
187622
  }
@@ -187028,7 +187629,7 @@ function translateIdTokenError(error52) {
187028
187629
  var mintIdToken = op(
187029
187630
  async () => {
187030
187631
  try {
187031
- return await core3.getIDToken(OIDC_AUDIENCE);
187632
+ return await core4.getIDToken(OIDC_AUDIENCE);
187032
187633
  } catch (error52) {
187033
187634
  throw translateIdTokenError(error52);
187034
187635
  }
@@ -187237,7 +187838,7 @@ function getGitHubUsageSummary() {
187237
187838
  }
187238
187839
  async function writeGitHubUsageSummaryToFile(path4) {
187239
187840
  const summary2 = getGitHubUsageSummary();
187240
- const tmpPath = join11(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
187841
+ const tmpPath = join12(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
187241
187842
  await writeFile(tmpPath, JSON.stringify(summary2));
187242
187843
  await rename(tmpPath, path4);
187243
187844
  }
@@ -188389,8 +188990,8 @@ function countAhead(head, base, cwd) {
188389
188990
  function spillGitOutput(params) {
188390
188991
  const tempDir = process.env.PULLFROG_TEMP_DIR;
188391
188992
  if (!tempDir) throw new Error("PULLFROG_TEMP_DIR not set");
188392
- const outputPath = join12(tempDir, `git-${params.command}-${randomUUID3().slice(0, 8)}.txt`);
188393
- writeFileSync8(outputPath, params.output);
188993
+ const outputPath = join13(tempDir, `git-${params.command}-${randomUUID3().slice(0, 8)}.txt`);
188994
+ writeFileSync9(outputPath, params.output);
188394
188995
  const previewByLines = params.output.split("\n").slice(0, OVERFLOW_PREVIEW_LINES).join("\n");
188395
188996
  const preview = previewByLines.length <= OVERFLOW_PREVIEW_MAX_CHARS ? previewByLines : `${previewByLines.slice(0, OVERFLOW_PREVIEW_MAX_CHARS)}\u2026`;
188396
188997
  log2.info(
@@ -189904,9 +190505,9 @@ async function reportReviewNodeId(ctx, params) {
189904
190505
  import { execFileSync as execFileSync7, execSync as execSync2 } from "node:child_process";
189905
190506
  import { mkdtempSync as mkdtempSync2, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
189906
190507
  import { tmpdir as tmpdir3 } from "node:os";
189907
- import { join as join13 } from "node:path";
190508
+ import { join as join14 } from "node:path";
189908
190509
  function createTempDirectory() {
189909
- const sharedTempDir = mkdtempSync2(join13(tmpdir3(), "pullfrog-"));
190510
+ const sharedTempDir = mkdtempSync2(join14(tmpdir3(), "pullfrog-"));
189910
190511
  process.env.PULLFROG_TEMP_DIR = sharedTempDir;
189911
190512
  log2.info(`\xBB created temp dir at ${sharedTempDir}`);
189912
190513
  return sharedTempDir;
@@ -189951,13 +190552,13 @@ function wipeRunnerLeakSurface() {
189951
190552
  return [];
189952
190553
  }
189953
190554
  };
189954
- const fileCommandsDir = join13(runnerTemp, "_runner_file_commands");
190555
+ const fileCommandsDir = join14(runnerTemp, "_runner_file_commands");
189955
190556
  for (const entry of listDir(fileCommandsDir)) {
189956
- tryUnlink(join13(fileCommandsDir, entry));
190557
+ tryUnlink(join14(fileCommandsDir, entry));
189957
190558
  }
189958
190559
  for (const entry of listDir(runnerTemp)) {
189959
190560
  if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
189960
- tryUnlink(join13(runnerTemp, entry));
190561
+ tryUnlink(join14(runnerTemp, entry));
189961
190562
  }
189962
190563
  }
189963
190564
  if (wiped.length > 0) {
@@ -190507,11 +191108,11 @@ function CheckoutPrTool(ctx) {
190507
191108
  headSha: checkoutSha
190508
191109
  });
190509
191110
  if (incremental.status === "ok") {
190510
- incrementalDiffPath = join14(
191111
+ incrementalDiffPath = join15(
190511
191112
  tempDir,
190512
191113
  `pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
190513
191114
  );
190514
- writeFileSync9(incrementalDiffPath, incremental.diff);
191115
+ writeFileSync10(incrementalDiffPath, incremental.diff);
190515
191116
  log2.info(
190516
191117
  `\xBB incremental diff computed (${incremental.diff.length} bytes) \u2192 ${incrementalDiffPath}`
190517
191118
  );
@@ -190524,8 +191125,8 @@ function CheckoutPrTool(ctx) {
190524
191125
  const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
190525
191126
  log2.debug(`formatted diff preview (first 100 lines):
190526
191127
  ${diffPreview}`);
190527
- const diffPath = join14(tempDir, `pr-${pull_number}-${headShort}.diff`);
190528
- writeFileSync9(diffPath, formatResult.content);
191128
+ const diffPath = join15(tempDir, `pr-${pull_number}-${headShort}.diff`);
191129
+ writeFileSync10(diffPath, formatResult.content);
190529
191130
  log2.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
190530
191131
  primary.diffCoverage = createDiffCoverageState({
190531
191132
  diffPath,
@@ -190682,8 +191283,8 @@ ${dirty}`
190682
191283
  }
190683
191284
 
190684
191285
  // mcp/checkSuite.ts
190685
- import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync10 } from "node:fs";
190686
- 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";
190687
191288
  var GetCheckSuiteLogs = type({
190688
191289
  check_suite_id: type.number.describe("the id from check_suite.id")
190689
191290
  });
@@ -190779,8 +191380,8 @@ function GetCheckSuiteLogsTool(ctx) {
190779
191380
  if (!tempDir) {
190780
191381
  throw new Error("PULLFROG_TEMP_DIR not set");
190781
191382
  }
190782
- const logsDir = join15(tempDir, "ci-logs");
190783
- mkdirSync7(logsDir, { recursive: true });
191383
+ const logsDir = join16(tempDir, "ci-logs");
191384
+ mkdirSync8(logsDir, { recursive: true });
190784
191385
  const jobResults = [];
190785
191386
  for (const run4 of failedRuns) {
190786
191387
  const jobs = await ctx.octokit.paginate(ctx.octokit.rest.actions.listJobsForWorkflowRun, {
@@ -190806,8 +191407,8 @@ function GetCheckSuiteLogsTool(ctx) {
190806
191407
  );
190807
191408
  }
190808
191409
  const logsText = await logsResult.text();
190809
- const logPath = join15(logsDir, `job-${job.id}.log`);
190810
- writeFileSync10(logPath, logsText);
191410
+ const logPath = join16(logsDir, `job-${job.id}.log`);
191411
+ writeFileSync11(logPath, logsText);
190811
191412
  const analysis = analyzeLog(logsText, 80);
190812
191413
  const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
190813
191414
  jobResults.push({
@@ -190855,8 +191456,8 @@ function GetCheckSuiteLogsTool(ctx) {
190855
191456
  }
190856
191457
 
190857
191458
  // mcp/commitInfo.ts
190858
- import { writeFileSync as writeFileSync11 } from "node:fs";
190859
- import { join as join16 } from "node:path";
191459
+ import { writeFileSync as writeFileSync12 } from "node:fs";
191460
+ import { join as join17 } from "node:path";
190860
191461
  var CommitInfo = type({
190861
191462
  sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
190862
191463
  });
@@ -190880,8 +191481,8 @@ function CommitInfoTool(ctx) {
190880
191481
  "PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
190881
191482
  );
190882
191483
  }
190883
- const diffFile = join16(tempDir, `commit-${sha.slice(0, 7)}.diff`);
190884
- writeFileSync11(diffFile, formatResult.content);
191484
+ const diffFile = join17(tempDir, `commit-${sha.slice(0, 7)}.diff`);
191485
+ writeFileSync12(diffFile, formatResult.content);
190885
191486
  log2.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
190886
191487
  return {
190887
191488
  sha: data.sha,
@@ -191014,17 +191615,17 @@ function ReopenCurrentTool(ctx) {
191014
191615
  }
191015
191616
 
191016
191617
  // prep/index.ts
191017
- import { performance as performance8 } from "node:perf_hooks";
191618
+ import { performance as performance9 } from "node:perf_hooks";
191018
191619
 
191019
191620
  // prep/installNodeDependencies.ts
191020
- import { existsSync as existsSync6 } from "node:fs";
191021
- import { join as join18 } from "node:path";
191621
+ import { existsSync as existsSync7 } from "node:fs";
191622
+ import { join as join19 } from "node:path";
191022
191623
 
191023
191624
  // utils/packageManager.ts
191024
191625
  var import_semver2 = __toESM(require_semver2(), 1);
191025
- import { existsSync as existsSync5 } from "node:fs";
191626
+ import { existsSync as existsSync6 } from "node:fs";
191026
191627
  import { mkdir, readFile as readFile2 } from "node:fs/promises";
191027
- import { delimiter, join as join17 } from "node:path";
191628
+ import { delimiter, join as join18 } from "node:path";
191028
191629
  var SUPPORTED_NAMES = ["npm", "pnpm", "yarn", "bun"];
191029
191630
  var COREPACK_MANAGED = ["pnpm", "yarn"];
191030
191631
  function isSupported(name) {
@@ -191062,8 +191663,8 @@ function parseDevEnginesField(field) {
191062
191663
  };
191063
191664
  }
191064
191665
  async function resolvePackageManagerSpec(cwd) {
191065
- const pkgPath = join17(cwd, "package.json");
191066
- if (!existsSync5(pkgPath)) return null;
191666
+ const pkgPath = join18(cwd, "package.json");
191667
+ if (!existsSync6(pkgPath)) return null;
191067
191668
  let pkg;
191068
191669
  try {
191069
191670
  pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
@@ -191121,7 +191722,7 @@ async function currentVersion(name) {
191121
191722
  return result.stdout.trim();
191122
191723
  }
191123
191724
  function packageManagerBinDir(tmpdir4) {
191124
- return join17(tmpdir4, "pm-bin");
191725
+ return join18(tmpdir4, "pm-bin");
191125
191726
  }
191126
191727
  async function ensurePackageManager(params) {
191127
191728
  const spec = params.spec;
@@ -191184,7 +191785,7 @@ async function installFallback(name, installSpec) {
191184
191785
  });
191185
191786
  if (result.exitCode !== 0) return result.stderr || `failed to install ${name}`;
191186
191787
  if (name === "deno") {
191187
- const denoPath = join17(process.env.HOME || "", ".deno", "bin");
191788
+ const denoPath = join18(process.env.HOME || "", ".deno", "bin");
191188
191789
  process.env.PATH = `${denoPath}:${process.env.PATH}`;
191189
191790
  }
191190
191791
  log2.info(`\xBB installed ${name}`);
@@ -191217,8 +191818,8 @@ async function isCommandAvailable2(command) {
191217
191818
  var installNodeDependencies = {
191218
191819
  name: "installNodeDependencies",
191219
191820
  shouldRun: () => {
191220
- const packageJsonPath = join18(process.cwd(), "package.json");
191221
- return existsSync6(packageJsonPath);
191821
+ const packageJsonPath = join19(process.cwd(), "package.json");
191822
+ return existsSync7(packageJsonPath);
191222
191823
  },
191223
191824
  run: async (options) => {
191224
191825
  const declared = await resolvePackageManagerSpec(process.cwd());
@@ -191312,14 +191913,14 @@ ${errorMessage}`]
191312
191913
  };
191313
191914
 
191314
191915
  // prep/installPythonDependencies.ts
191315
- import { existsSync as existsSync7, readFileSync as readFileSync4 } from "node:fs";
191316
- 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";
191317
191918
  function declaresBuildSystem(path4) {
191318
- return /^\s*\[\s*build-system\s*\]/m.test(readFileSync4(path4, "utf8"));
191919
+ return /^\s*\[\s*build-system\s*\]/m.test(readFileSync5(path4, "utf8"));
191319
191920
  }
191320
191921
  function configApplies(config3, cwd) {
191321
- const path4 = join19(cwd, config3.file);
191322
- return existsSync7(path4) && (config3.applies?.(path4) ?? true);
191922
+ const path4 = join20(cwd, config3.file);
191923
+ return existsSync8(path4) && (config3.applies?.(path4) ?? true);
191323
191924
  }
191324
191925
  var PYTHON_CONFIGS = [
191325
191926
  {
@@ -191473,7 +192074,7 @@ var installPythonDependencies = {
191473
192074
  var prepSteps = [installNodeDependencies, installPythonDependencies];
191474
192075
  async function runPrepPhase(options) {
191475
192076
  log2.debug("\xBB starting prep phase...");
191476
- const startTime = performance8.now();
192077
+ const startTime = performance9.now();
191477
192078
  const results = [];
191478
192079
  const preDirty = await dirtyTrackedPaths();
191479
192080
  try {
@@ -191495,7 +192096,7 @@ async function runPrepPhase(options) {
191495
192096
  } finally {
191496
192097
  await restoreDirtiedSince({ before: preDirty, actor: "prep" });
191497
192098
  }
191498
- const totalDurationMs = performance8.now() - startTime;
192099
+ const totalDurationMs = performance9.now() - startTime;
191499
192100
  log2.debug(`\xBB prep phase completed (${Math.round(totalDurationMs)}ms)`);
191500
192101
  return results;
191501
192102
  }
@@ -191640,15 +192241,15 @@ function AwaitDependencyInstallationTool(ctx) {
191640
192241
  }
191641
192242
 
191642
192243
  // mcp/gh.ts
191643
- import { mkdirSync as mkdirSync8 } from "node:fs";
191644
- import { join as join21 } from "node:path";
192244
+ import { mkdirSync as mkdirSync9 } from "node:fs";
192245
+ import { join as join22 } from "node:path";
191645
192246
 
191646
192247
  // mcp/shell.ts
191647
192248
  import { spawn as spawn5, spawnSync as spawnSync6 } from "node:child_process";
191648
192249
  import { randomUUID as randomUUID4 } from "node:crypto";
191649
- import { closeSync, openSync, writeFileSync as writeFileSync12 } from "node:fs";
192250
+ import { closeSync, openSync, writeFileSync as writeFileSync13 } from "node:fs";
191650
192251
  import { userInfo as userInfo2 } from "node:os";
191651
- import { join as join20 } from "node:path";
192252
+ import { join as join21 } from "node:path";
191652
192253
  import { setTimeout as sleep3 } from "node:timers/promises";
191653
192254
  var ShellParams = type({
191654
192255
  command: "string",
@@ -191911,8 +192512,8 @@ ${spawnError}` : spawnError;
191911
192512
  }
191912
192513
  function capOutput(output) {
191913
192514
  if (output.length <= MAX_OUTPUT_CHARS) return output;
191914
- const fullPath = join20(getTempDir(), `shell-${randomUUID4().slice(0, 8)}.log`);
191915
- writeFileSync12(fullPath, output);
192515
+ const fullPath = join21(getTempDir(), `shell-${randomUUID4().slice(0, 8)}.log`);
192516
+ writeFileSync13(fullPath, output);
191916
192517
  const elided = output.length - MAX_OUTPUT_CHARS;
191917
192518
  return `... [${elided} chars truncated; full output saved to ${fullPath}] ...
191918
192519
  ${output.slice(-MAX_OUTPUT_CHARS)}`;
@@ -191966,8 +192567,8 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
191966
192567
  if (params.background) {
191967
192568
  const tempDir = getTempDir();
191968
192569
  const handle = `bg-${randomUUID4().slice(0, 8)}`;
191969
- const outputPath = join20(tempDir, `${handle}.log`);
191970
- const pidPath = join20(tempDir, `${handle}.pid`);
192570
+ const outputPath = join21(tempDir, `${handle}.log`);
192571
+ const pidPath = join21(tempDir, `${handle}.pid`);
191971
192572
  const logFd = openSync(outputPath, "a");
191972
192573
  let proc;
191973
192574
  try {
@@ -191984,7 +192585,7 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
191984
192585
  throw new Error("failed to start background process");
191985
192586
  }
191986
192587
  proc.unref();
191987
- writeFileSync12(pidPath, `${proc.pid}
192588
+ writeFileSync13(pidPath, `${proc.pid}
191988
192589
  `);
191989
192590
  ctx.toolState.backgroundProcesses.set(handle, { pid: proc.pid, outputPath, pidPath });
191990
192591
  return {
@@ -192064,8 +192665,8 @@ function GhTool(ctx) {
192064
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.`,
192065
192666
  parameters: GhParams,
192066
192667
  execute: execute(async (params) => {
192067
- const configDir = join21(ctx.tmpdir, "gh-config");
192068
- mkdirSync8(configDir, { recursive: true });
192668
+ const configDir = join22(ctx.tmpdir, "gh-config");
192669
+ mkdirSync9(configDir, { recursive: true });
192069
192670
  const command = ["gh", ...params.args.map(shellQuote)].join(" ");
192070
192671
  const result = await runSandboxed({
192071
192672
  command,
@@ -192788,8 +193389,8 @@ function PullRequestInfoTool(ctx) {
192788
193389
  }
192789
193390
 
192790
193391
  // mcp/reviewComments.ts
192791
- import { writeFileSync as writeFileSync13 } from "node:fs";
192792
- import { join as join22 } from "node:path";
193392
+ import { writeFileSync as writeFileSync14 } from "node:fs";
193393
+ import { join as join23 } from "node:path";
192793
193394
  var COMMENTS_PER_THREAD = 50;
192794
193395
  var REVIEW_THREADS_QUERY = `
192795
193396
  query ($owner: String!, $name: String!, $prNumber: Int!, $cursor: String) {
@@ -193262,8 +193863,8 @@ function GetReviewCommentsTool(ctx) {
193262
193863
  throw new Error("PULLFROG_TEMP_DIR not set");
193263
193864
  }
193264
193865
  const filename = `review-${params.review_id}-threads.md`;
193265
- const commentsPath = join22(tempDir, filename);
193266
- writeFileSync13(commentsPath, formatted.content);
193866
+ const commentsPath = join23(tempDir, filename);
193867
+ writeFileSync14(commentsPath, formatted.content);
193267
193868
  log2.debug(`wrote ${threadBlocks.length} threads to ${commentsPath}`);
193268
193869
  return {
193269
193870
  review_id: params.review_id,
@@ -193596,8 +194197,8 @@ function UploadFileTool(ctx) {
193596
194197
  }
193597
194198
 
193598
194199
  // mcp/xrepo.ts
193599
- import { mkdirSync as mkdirSync9, rmSync as rmSync3 } from "node:fs";
193600
- 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";
193601
194202
  function assertValidRepoName(name) {
193602
194203
  if (!/^[A-Za-z0-9._-]+$/.test(name) || name.startsWith("-") || name.includes("..")) {
193603
194204
  throw new Error(
@@ -193677,8 +194278,8 @@ function CheckoutRepoTool(ctx) {
193677
194278
  return { path: existing.dir, access: existing.access, note: "already checked out." };
193678
194279
  }
193679
194280
  const access = accessFor(ctx, repo);
193680
- const dir = join23(ctx.tmpdir, "xrepo", repo);
193681
- mkdirSync9(dir, { recursive: true });
194281
+ const dir = join24(ctx.tmpdir, "xrepo", repo);
194282
+ mkdirSync10(dir, { recursive: true });
193682
194283
  const state = ensureRepoState(ctx.toolState, { owner, name: repo, dir, access });
193683
194284
  const rc = resolveRepoCtx(ctx, repo);
193684
194285
  try {
@@ -193926,6 +194527,9 @@ function hasEnvVar2(name) {
193926
194527
  function hasClaudeCodeAuth() {
193927
194528
  return hasEnvVar2("CLAUDE_CODE_OAUTH_TOKEN") || hasEnvVar2("ANTHROPIC_API_KEY");
193928
194529
  }
194530
+ function hasCodexAuth() {
194531
+ return hasEnvVar2("CODEX_AUTH_JSON") || hasEnvVar2("OPENAI_API_KEY");
194532
+ }
193929
194533
  function hasBedrockAuth() {
193930
194534
  return hasEnvVar2("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar2("AWS_ACCESS_KEY_ID") && hasEnvVar2("AWS_SECRET_ACCESS_KEY");
193931
194535
  }
@@ -193952,6 +194556,15 @@ function resolveSlug(slug2) {
193952
194556
  }
193953
194557
  return vertexId;
193954
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
+ }
193955
194568
  if (alias?.routing === "openai-compatible") {
193956
194569
  const modelId = process.env[OPENAI_COMPATIBLE_MODEL_ENV]?.trim();
193957
194570
  if (!modelId) {
@@ -193990,6 +194603,7 @@ function resolveAgent(ctx) {
193990
194603
  }
193991
194604
  log2.warning(`\xBB unknown PULLFROG_AGENT="${envAgent}" \u2014 falling through to auto-select`);
193992
194605
  }
194606
+ if (ctx.proxyModel) return agents.opencode;
193993
194607
  if (ctx.model && hasBedrockAuth() && process.env[BEDROCK_MODEL_ID_ENV]?.trim() === ctx.model) {
193994
194608
  return isBedrockAnthropicId(ctx.model) ? agents.claude : agents.opencode;
193995
194609
  }
@@ -193999,12 +194613,14 @@ function resolveAgent(ctx) {
193999
194613
  if (ctx.model) {
194000
194614
  try {
194001
194615
  const provider2 = getModelProvider(ctx.model);
194002
- if (provider2 === "anthropic" && hasClaudeCodeAuth()) {
194003
- return agents.claude;
194004
- }
194616
+ if (provider2 === "anthropic" && hasClaudeCodeAuth()) return agents.claude;
194617
+ if (provider2 === "openai" && ctx.codexAgent && hasCodexAuth()) return agents.codex;
194005
194618
  } catch {
194006
194619
  }
194007
194620
  }
194621
+ if (!ctx.model && ctx.codexAgent && hasCodexAuth() && !hasClaudeCodeAuth()) {
194622
+ return agents.codex;
194623
+ }
194008
194624
  return agents.opencode;
194009
194625
  }
194010
194626
 
@@ -194102,10 +194718,37 @@ auto-compaction is disabled, so long runs grow until your endpoint refuses them.
194102
194718
 
194103
194719
  for full setup instructions, see https://docs.pullfrog.com/openai-compatible`;
194104
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
+ }
194105
194744
  function hasEnvVar3(name) {
194106
194745
  const value2 = process.env[name];
194107
194746
  return typeof value2 === "string" && value2.length > 0;
194108
194747
  }
194748
+ function modelHasRuntimeAuth(model) {
194749
+ const authVars = [...getModelEnvVars(model), ...getModelManagedCredentials(model)];
194750
+ return authVars.length === 0 || authVars.some(hasEnvVar3);
194751
+ }
194109
194752
  function hasPositiveNumberEnvVar(name) {
194110
194753
  return Number(process.env[name]) > 0;
194111
194754
  }
@@ -194124,6 +194767,17 @@ function validateOpenAICompatibleSetup(params) {
194124
194767
  );
194125
194768
  }
194126
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
+ }
194127
194781
  function validateBedrockSetup(params) {
194128
194782
  const hasAuth = hasEnvVar3("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar3("AWS_ACCESS_KEY_ID") && hasEnvVar3("AWS_SECRET_ACCESS_KEY");
194129
194783
  const missing = [];
@@ -194147,6 +194801,12 @@ function validateVertexSetup(params) {
194147
194801
  throw new Error(buildVertexSetupError({ owner: params.owner, name: params.name, missing }));
194148
194802
  }
194149
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
+ }
194150
194810
  function validateAgentApiKey(params) {
194151
194811
  if (params.model) {
194152
194812
  const alias = resolveDisplayAlias(params.model);
@@ -194162,6 +194822,10 @@ function validateAgentApiKey(params) {
194162
194822
  validateOpenAICompatibleSetup({ owner: params.owner, name: params.name });
194163
194823
  return;
194164
194824
  }
194825
+ if (params.model.startsWith(`${AZURE_PROVIDER}/`)) {
194826
+ validateAzureSetup({ owner: params.owner, name: params.name });
194827
+ return;
194828
+ }
194165
194829
  if (!params.model.includes("/")) {
194166
194830
  if (process.env[VERTEX_MODEL_ID_ENV]?.trim() === params.model) {
194167
194831
  validateVertexSetup({ owner: params.owner, name: params.name });
@@ -194185,7 +194849,7 @@ function validateAgentApiKey(params) {
194185
194849
  })
194186
194850
  );
194187
194851
  }
194188
- if (hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN")) return;
194852
+ if (hasSingleProviderAuth(params.agent.name)) return;
194189
194853
  throw new Error(
194190
194854
  buildKeyError({
194191
194855
  owner: params.owner,
@@ -194197,7 +194861,7 @@ function validateAgentApiKey(params) {
194197
194861
  );
194198
194862
  }
194199
194863
  if (params.agent.name === "opencode") {
194200
- if (params.authorized.size > 0) return;
194864
+ if ([...params.authorized].some(modelHasRuntimeAuth)) return;
194201
194865
  const reason = getModelsFailure();
194202
194866
  if (reason) throw new Error(reason);
194203
194867
  throw new Error(
@@ -194209,7 +194873,7 @@ function validateAgentApiKey(params) {
194209
194873
  })
194210
194874
  );
194211
194875
  }
194212
- if (hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN")) return;
194876
+ if (hasSingleProviderAuth(params.agent.name)) return;
194213
194877
  throw new Error(
194214
194878
  buildKeyError({
194215
194879
  owner: params.owner,
@@ -194582,9 +195246,9 @@ async function checkConfiguredCredentials(params) {
194582
195246
 
194583
195247
  // utils/gitAuthServer.ts
194584
195248
  import { randomUUID as randomUUID5 } from "node:crypto";
194585
- import { writeFileSync as writeFileSync14 } from "node:fs";
195249
+ import { writeFileSync as writeFileSync15 } from "node:fs";
194586
195250
  import { createServer as createServer3 } from "node:http";
194587
- import { join as join24 } from "node:path";
195251
+ import { join as join25 } from "node:path";
194588
195252
  var REVOKED_TRAP_MS = 6e4;
194589
195253
  function revokeGitHubToken(token) {
194590
195254
  fetch("https://api.github.com/installation/token", {
@@ -194653,7 +195317,7 @@ async function startGitAuthServer(tmpdir4) {
194653
195317
  function writeAskpassScript(code) {
194654
195318
  const scriptId = randomUUID5();
194655
195319
  const scriptName = `askpass-${scriptId}.js`;
194656
- const scriptPath = join24(tmpdir4, scriptName);
195320
+ const scriptPath = join25(tmpdir4, scriptName);
194657
195321
  const content = [
194658
195322
  `#!/usr/bin/env node`,
194659
195323
  `var a=process.argv[2]||"";`,
@@ -194666,7 +195330,7 @@ async function startGitAuthServer(tmpdir4) {
194666
195330
  `r.on("end",function(){process.stdout.write(d+"\\n")})`,
194667
195331
  `}).on("error",function(){process.exit(1)})}`
194668
195332
  ].join("\n");
194669
- writeFileSync14(scriptPath, content, { mode: 448 });
195333
+ writeFileSync15(scriptPath, content, { mode: 448 });
194670
195334
  return scriptPath;
194671
195335
  }
194672
195336
  async function close() {
@@ -195105,7 +195769,7 @@ function resolveInstructions(ctx) {
195105
195769
 
195106
195770
  // utils/learnings.ts
195107
195771
  import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
195108
- import { dirname as dirname4, join as join25 } from "node:path";
195772
+ import { dirname as dirname4, join as join26 } from "node:path";
195109
195773
 
195110
195774
  // utils/learningsTruncate.ts
195111
195775
  var MAX_LEARNINGS_LENGTH = 1e5;
@@ -195123,10 +195787,10 @@ function truncateAtLineBoundary(body, cap) {
195123
195787
  var LEARNINGS_FILE_NAME = "pullfrog-learnings.md";
195124
195788
  var XREPO_LEARNINGS_FILE_NAME = "pullfrog-xrepo-learnings.md";
195125
195789
  function learningsFilePath(tmpdir4) {
195126
- return join25(tmpdir4, LEARNINGS_FILE_NAME);
195790
+ return join26(tmpdir4, LEARNINGS_FILE_NAME);
195127
195791
  }
195128
195792
  function xrepoLearningsFilePath(tmpdir4) {
195129
- return join25(tmpdir4, XREPO_LEARNINGS_FILE_NAME);
195793
+ return join26(tmpdir4, XREPO_LEARNINGS_FILE_NAME);
195130
195794
  }
195131
195795
  async function seedLearningsFile(params) {
195132
195796
  const path4 = learningsFilePath(params.tmpdir);
@@ -195242,7 +195906,7 @@ async function persistXrepoLearnings(ctx) {
195242
195906
  // utils/modelAccess.ts
195243
195907
  function decideModelAccess(input) {
195244
195908
  if (!input.modelExplicit || !input.model) return { kind: "ok" };
195245
- 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}/`));
195246
195910
  if (input.proxyActive) {
195247
195911
  const target = resolveOpenRouterModel(input.model);
195248
195912
  if (input.oss) {
@@ -195287,7 +195951,7 @@ function buildModelAccessError(input) {
195287
195951
  }
195288
195952
 
195289
195953
  // utils/normalizeEnv.ts
195290
- var core4 = __toESM(require_core(), 1);
195954
+ var core5 = __toESM(require_core(), 1);
195291
195955
  function hasControlCharacter(value2) {
195292
195956
  return [...value2].some((ch) => {
195293
195957
  const code = ch.charCodeAt(0);
@@ -195313,7 +195977,7 @@ function sanitizeSecret(key, value2) {
195313
195977
  `\xBB stripped whitespace from ${key} (whitespace in secret values breaks GitHub Actions log masking)`
195314
195978
  );
195315
195979
  }
195316
- core4.setSecret(trimmed);
195980
+ core5.setSecret(trimmed);
195317
195981
  return trimmed;
195318
195982
  }
195319
195983
  function normalizeEnv() {
@@ -195357,7 +196021,7 @@ function normalizeEnv() {
195357
196021
  }
195358
196022
 
195359
196023
  // utils/overrides.ts
195360
- var core5 = __toESM(require_core(), 1);
196024
+ var core6 = __toESM(require_core(), 1);
195361
196025
  var DENIED_OVERRIDE_NAMES = /* @__PURE__ */ new Set([
195362
196026
  "GITHUB_TOKEN",
195363
196027
  "GH_TOKEN",
@@ -195403,7 +196067,7 @@ function applyOverrides(params) {
195403
196067
  denied.push(key);
195404
196068
  continue;
195405
196069
  }
195406
- if (value2.length > 0) core5.setSecret(value2);
196070
+ if (value2.length > 0) core6.setSecret(value2);
195407
196071
  params.env[key] = value2;
195408
196072
  applied.push(key);
195409
196073
  }
@@ -195412,8 +196076,8 @@ function applyOverrides(params) {
195412
196076
  }
195413
196077
 
195414
196078
  // utils/payload.ts
195415
- var core6 = __toESM(require_core(), 1);
195416
- import { readFileSync as readFileSync6 } from "node:fs";
196079
+ var core7 = __toESM(require_core(), 1);
196080
+ import { readFileSync as readFileSync7 } from "node:fs";
195417
196081
  import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
195418
196082
 
195419
196083
  // utils/versioning.ts
@@ -195500,8 +196164,8 @@ function resolveCwd(cwd) {
195500
196164
  return workspace ? resolve2(workspace, cwd) : cwd;
195501
196165
  }
195502
196166
  function resolvePromptInput() {
195503
- const promptInput = core6.getInput("prompt");
195504
- const promptFile = core6.getInput("prompt_file");
196167
+ const promptInput = core7.getInput("prompt");
196168
+ const promptFile = core7.getInput("prompt_file");
195505
196169
  if (promptInput && promptFile) {
195506
196170
  throw new Error("set exactly one of 'prompt' or 'prompt_file' inputs, not both.");
195507
196171
  }
@@ -195527,7 +196191,7 @@ function resolvePromptInput() {
195527
196191
  function resolvePromptFile(input) {
195528
196192
  const workspace = process.env.GITHUB_WORKSPACE;
195529
196193
  const path4 = isAbsolute2(input) ? input : workspace ? resolve2(workspace, input) : resolve2(input);
195530
- const content = readFileSync6(path4, "utf-8");
196194
+ const content = readFileSync7(path4, "utf-8");
195531
196195
  if (!content.trim()) {
195532
196196
  throw new Error(`prompt_file ${JSON.stringify(input)} is empty.`);
195533
196197
  }
@@ -195535,7 +196199,7 @@ function resolvePromptFile(input) {
195535
196199
  }
195536
196200
  function warnIfDeprecatedStatusChecks(value2) {
195537
196201
  if (value2 !== void 0) {
195538
- core6.warning(
196202
+ core7.warning(
195539
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."
195540
196204
  );
195541
196205
  }
@@ -195543,15 +196207,15 @@ function warnIfDeprecatedStatusChecks(value2) {
195543
196207
  }
195544
196208
  function resolveNonPromptInputs() {
195545
196209
  return Inputs.omit("prompt", "prompt_file").assert({
195546
- model: core6.getInput("model") || void 0,
195547
- effort: core6.getInput("effort") || void 0,
195548
- debug: core6.getInput("debug") || void 0,
195549
- timeout: core6.getInput("timeout") || void 0,
195550
- cwd: core6.getInput("cwd") || void 0,
195551
- push: core6.getInput("push") || void 0,
195552
- shell: core6.getInput("shell") || void 0,
195553
- status_checks: warnIfDeprecatedStatusChecks(core6.getInput("status_checks") || void 0),
195554
- 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
195555
196219
  });
195556
196220
  }
195557
196221
  function resolvePayload(resolvedPromptInput, repoSettings) {
@@ -195619,7 +196283,7 @@ function resolvePayload(resolvedPromptInput, repoSettings) {
195619
196283
  };
195620
196284
  }
195621
196285
  function resolveOutputSchema() {
195622
- const raw2 = core6.getInput("output_schema");
196286
+ const raw2 = core7.getInput("output_schema");
195623
196287
  if (!raw2) return void 0;
195624
196288
  let parsed2;
195625
196289
  try {
@@ -195635,10 +196299,10 @@ function resolveOutputSchema() {
195635
196299
  }
195636
196300
 
195637
196301
  // utils/proxy.ts
195638
- var core8 = __toESM(require_core(), 1);
196302
+ var core9 = __toESM(require_core(), 1);
195639
196303
 
195640
196304
  // utils/token.ts
195641
- var core7 = __toESM(require_core(), 1);
196305
+ var core8 = __toESM(require_core(), 1);
195642
196306
  import assert2 from "node:assert/strict";
195643
196307
  var mcpTokenValue;
195644
196308
  var refreshMcpTokenFn;
@@ -195646,7 +196310,7 @@ function getMcpTokenRefresh() {
195646
196310
  return refreshMcpTokenFn;
195647
196311
  }
195648
196312
  function getJobToken() {
195649
- const inputToken = core7.getInput("token");
196313
+ const inputToken = core8.getInput("token");
195650
196314
  if (inputToken) {
195651
196315
  return inputToken;
195652
196316
  }
@@ -195662,7 +196326,7 @@ async function resolveTokens(params) {
195662
196326
  if (externalToken) {
195663
196327
  mcpTokenValue = externalToken;
195664
196328
  if (isGitHubActions) {
195665
- core7.setSecret(externalToken);
196329
+ core8.setSecret(externalToken);
195666
196330
  }
195667
196331
  log2.info("\xBB using external GH_TOKEN for both git and MCP");
195668
196332
  return {
@@ -195682,7 +196346,7 @@ async function resolveTokens(params) {
195682
196346
  const gitPermissions = params.push === "disabled" ? { contents: "read" } : { contents: "write", workflows: "write" };
195683
196347
  const gitToken = await acquireNewToken({ repos: writeRepos, permissions: gitPermissions });
195684
196348
  if (isGitHubActions) {
195685
- core7.setSecret(gitToken);
196349
+ core8.setSecret(gitToken);
195686
196350
  }
195687
196351
  log2.info(
195688
196352
  `\xBB acquired git token (${Object.entries(gitPermissions).map((e) => e.join(":")).join(", ")})`
@@ -195699,7 +196363,7 @@ async function resolveTokens(params) {
195699
196363
  };
195700
196364
  const mcpToken = await acquireNewToken({ repos: writeRepos, permissions: mcpPermissions });
195701
196365
  if (isGitHubActions) {
195702
- core7.setSecret(mcpToken);
196366
+ core8.setSecret(mcpToken);
195703
196367
  }
195704
196368
  log2.info(
195705
196369
  `\xBB acquired scoped MCP token (${Object.entries(mcpPermissions).map((e) => e.join(":")).join(", ")})`
@@ -195708,7 +196372,7 @@ async function resolveTokens(params) {
195708
196372
  let ghToken;
195709
196373
  if (ghPermissions) {
195710
196374
  ghToken = await acquireNewToken({ permissions: ghPermissions });
195711
- if (isGitHubActions) core7.setSecret(ghToken);
196375
+ if (isGitHubActions) core8.setSecret(ghToken);
195712
196376
  log2.info(
195713
196377
  `\xBB acquired gh token mirroring ${params.authorPermission} (${formatPermissions(ghPermissions)})`
195714
196378
  );
@@ -195719,7 +196383,7 @@ async function resolveTokens(params) {
195719
196383
  repos: params.xrepo.read,
195720
196384
  permissions: { contents: "read" }
195721
196385
  });
195722
- if (isGitHubActions) core7.setSecret(readToken);
196386
+ if (isGitHubActions) core8.setSecret(readToken);
195723
196387
  log2.info(`\xBB acquired cross-repo read token (contents:read, ${params.xrepo.read.length} repos)`);
195724
196388
  }
195725
196389
  mcpTokenValue = mcpToken;
@@ -195738,7 +196402,7 @@ async function resolveTokens(params) {
195738
196402
  oidc: params.oidc ?? void 0
195739
196403
  }).then((fresh) => {
195740
196404
  if (isGitHubActions) {
195741
- core7.setSecret(fresh);
196405
+ core8.setSecret(fresh);
195742
196406
  }
195743
196407
  mcpTokenValue = fresh;
195744
196408
  currentMcpToken = fresh;
@@ -195759,7 +196423,7 @@ async function resolveTokens(params) {
195759
196423
  permissions: gitPermissions,
195760
196424
  oidc: params.oidc ?? void 0
195761
196425
  }).then((fresh) => {
195762
- if (isGitHubActions) core7.setSecret(fresh);
196426
+ if (isGitHubActions) core8.setSecret(fresh);
195763
196427
  void revokeGitHubInstallationToken(currentGitToken);
195764
196428
  currentGitToken = fresh;
195765
196429
  log2.warning("\xBB GitHub rejected the git token; re-acquired a fresh git token");
@@ -195776,7 +196440,7 @@ async function resolveTokens(params) {
195776
196440
  permissions: { contents: "read" },
195777
196441
  oidc: params.oidc ?? void 0
195778
196442
  }).then((fresh) => {
195779
- if (isGitHubActions) core7.setSecret(fresh);
196443
+ if (isGitHubActions) core8.setSecret(fresh);
195780
196444
  void revokeGitHubInstallationToken(read2);
195781
196445
  currentReadToken = fresh;
195782
196446
  log2.warning("\xBB GitHub rejected the read token; re-acquired a fresh read token");
@@ -195990,7 +196654,7 @@ async function resolveProxyModel(ctx) {
195990
196654
  });
195991
196655
  if (!key) return;
195992
196656
  process.env.OPENROUTER_API_KEY = key;
195993
- core8.setSecret(key);
196657
+ core9.setSecret(key);
195994
196658
  ctx.payload.proxyModel = ctx.proxyModel;
195995
196659
  ctx.toolState.model = ctx.proxyModel;
195996
196660
  const label = ctx.oss ? "oss" : "router";
@@ -196061,7 +196725,7 @@ async function runProxyResolution(ctx) {
196061
196725
 
196062
196726
  // utils/prSummary.ts
196063
196727
  import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
196064
- import { dirname as dirname5, join as join26 } from "node:path";
196728
+ import { dirname as dirname5, join as join27 } from "node:path";
196065
196729
  var SUMMARY_FILE_NAME = "pullfrog-summary.md";
196066
196730
  var SUMMARY_SCAFFOLD = `# PR summary
196067
196731
 
@@ -196071,7 +196735,7 @@ var SUMMARY_SCAFFOLD = `# PR summary
196071
196735
  var MIN_SNAPSHOT_LENGTH = 60;
196072
196736
  var MAX_SNAPSHOT_LENGTH = 32768;
196073
196737
  function summaryFilePath(tmpdir4) {
196074
- return join26(tmpdir4, SUMMARY_FILE_NAME);
196738
+ return join27(tmpdir4, SUMMARY_FILE_NAME);
196075
196739
  }
196076
196740
  async function seedSummaryFile(params) {
196077
196741
  const path4 = summaryFilePath(params.tmpdir);
@@ -196200,6 +196864,7 @@ var defaultSettings = {
196200
196864
  shell: "restricted",
196201
196865
  prApproveEnabled: false,
196202
196866
  autoMergeEnabled: false,
196867
+ codexAgent: false,
196203
196868
  signedCommits: false,
196204
196869
  repoIntelligence: false,
196205
196870
  progressComments: true,
@@ -196688,7 +197353,7 @@ ${genericBody}`,
196688
197353
  }
196689
197354
 
196690
197355
  // utils/runLifecycle.ts
196691
- var core9 = __toESM(require_core(), 1);
197356
+ var core10 = __toESM(require_core(), 1);
196692
197357
 
196693
197358
  // utils/autoMerge.ts
196694
197359
  function hasBlockingHumanReview(reviews) {
@@ -197074,7 +197739,7 @@ async function finalizeSuccessRun(input) {
197074
197739
  }
197075
197740
  if (input.toolState.output) {
197076
197741
  log2.info(`::pullfrog-output::${Buffer.from(input.toolState.output).toString("base64")}`);
197077
- core9.setOutput("result", input.toolState.output);
197742
+ core10.setOutput("result", input.toolState.output);
197078
197743
  }
197079
197744
  if (input.result.success) {
197080
197745
  await approveAfterFix(input.toolContext).catch((error52) => {
@@ -197538,7 +198203,11 @@ async function main() {
197538
198203
  }
197539
198204
  const resolvedModel = credentials.kind === "fellBack" ? credentials.replacement : configuredModel;
197540
198205
  vertexCredentials = materializeVertexCredentials({ model: resolvedModel });
197541
- const agent2 = resolveAgent({ model: resolvedModel });
198206
+ const agent2 = resolveAgent({
198207
+ model: resolvedModel,
198208
+ proxyModel: payload.proxyModel,
198209
+ codexAgent: runContext.repoSettings.codexAgent
198210
+ });
197542
198211
  const effectiveModel = payload.proxyModel ?? resolvedModel ?? payload.model;
197543
198212
  toolState.model = effectiveModel;
197544
198213
  if (!payload.proxyModel) {
@@ -197698,11 +198367,16 @@ async function main() {
197698
198367
  xrepoLearningsFilePath: toolState.xrepoLearningsFilePath ?? null,
197699
198368
  xrepoLearningsHeadings: runContext.repoSettings.xrepoLearningsHeadings
197700
198369
  });
197701
- assertPromptToolRefs({
198370
+ const danglingToolRefs = findDanglingPromptToolRefs({
197702
198371
  agentId,
197703
198372
  prompt: instructions.full,
197704
198373
  toolNames: mcpHttpServer.toolNames
197705
198374
  });
198375
+ if (danglingToolRefs.length > 0) {
198376
+ log2.warning(
198377
+ `\xBB prompt advertises ${danglingToolRefs.length} tool(s) the MCP server did not register: ${danglingToolRefs.join(", ")}`
198378
+ );
198379
+ }
197706
198380
  const logParts = [
197707
198381
  instructions.eventInstructions ? `EVENT-LEVEL INSTRUCTIONS:
197708
198382
  ${instructions.eventInstructions}` : null,
@@ -197717,8 +198391,8 @@ ${instructions.user}` : null,
197717
198391
  log2.info(instructions.full);
197718
198392
  });
197719
198393
  if (agentId === "opencode") {
197720
- const pluginDir = join27(process.cwd(), ".opencode", "plugin");
197721
- 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));
197722
198396
  if (hasPlugins && toolState.dependencyInstallation?.promise) {
197723
198397
  log2.info(
197724
198398
  "\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"
@@ -197910,27 +198584,27 @@ async function runMain() {
197910
198584
  }
197911
198585
  } catch (error52) {
197912
198586
  const errorMessage = error52 instanceof Error ? error52.message : "unknown error occurred";
197913
- core10.setFailed(`action failed: ${errorMessage}`);
198587
+ core11.setFailed(`action failed: ${errorMessage}`);
197914
198588
  }
197915
198589
  }
197916
198590
  async function tokenMain() {
197917
- const reposInput = core10.getInput("repos");
198591
+ const reposInput = core11.getInput("repos");
197918
198592
  const additionalRepos = reposInput ? reposInput.split(",").map((r2) => r2.trim()).filter(Boolean) : [];
197919
198593
  const token = await acquireNewToken({ repos: additionalRepos });
197920
- core10.setSecret(token);
197921
- core10.saveState(STATE_TOKEN, token);
197922
- core10.setOutput("token", token);
198594
+ core11.setSecret(token);
198595
+ core11.saveState(STATE_TOKEN, token);
198596
+ core11.setOutput("token", token);
197923
198597
  const scope2 = additionalRepos.length ? `current repo + ${additionalRepos.join(", ")}` : "current repo only";
197924
- core10.info(`\xBB installation token acquired (${scope2})`);
198598
+ core11.info(`\xBB installation token acquired (${scope2})`);
197925
198599
  }
197926
198600
  async function tokenPost() {
197927
- const token = core10.getState(STATE_TOKEN);
198601
+ const token = core11.getState(STATE_TOKEN);
197928
198602
  if (!token) {
197929
- core10.debug("no token found in state, skipping revocation");
198603
+ core11.debug("no token found in state, skipping revocation");
197930
198604
  return;
197931
198605
  }
197932
198606
  await revokeGitHubInstallationToken(token);
197933
- core10.info("\xBB installation token revoked");
198607
+ core11.info("\xBB installation token revoked");
197934
198608
  }
197935
198609
  function printGhaUsage(params) {
197936
198610
  params.stream(`usage: ${params.prog} gha [subcommand]
@@ -198046,7 +198720,7 @@ async function run(args2) {
198046
198720
  }
198047
198721
  } catch (error52) {
198048
198722
  const message = error52 instanceof Error ? error52.message : String(error52);
198049
- core10.setFailed(message);
198723
+ core11.setFailed(message);
198050
198724
  }
198051
198725
  }
198052
198726
 
@@ -198893,7 +199567,7 @@ async function runCli4(input) {
198893
199567
  }
198894
199568
 
198895
199569
  // cli.ts
198896
- var VERSION10 = "0.1.56";
199570
+ var VERSION10 = "0.1.59";
198897
199571
  var bin = basename2(process.argv[1] || "");
198898
199572
  var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
198899
199573
  var rawArgs = process.argv.slice(2);