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/index.js CHANGED
@@ -3682,7 +3682,7 @@ var require_util2 = __commonJS({
3682
3682
  "use strict";
3683
3683
  var { redirectStatusSet, referrerPolicySet: referrerPolicyTokens, badPortsSet } = require_constants2();
3684
3684
  var { getGlobalOrigin } = require_global();
3685
- var { performance: performance9 } = __require("perf_hooks");
3685
+ var { performance: performance10 } = __require("perf_hooks");
3686
3686
  var { isBlobLike, toUSVString, ReadableStreamFrom } = require_util();
3687
3687
  var assert3 = __require("assert");
3688
3688
  var { isUint8Array } = __require("util/types");
@@ -3845,7 +3845,7 @@ var require_util2 = __commonJS({
3845
3845
  }
3846
3846
  }
3847
3847
  function coarsenedSharedCurrentTime(crossOriginIsolatedCapability) {
3848
- return performance9.now();
3848
+ return performance10.now();
3849
3849
  }
3850
3850
  function createOpaqueTimingInfo(timingInfo) {
3851
3851
  return {
@@ -19826,14 +19826,14 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
19826
19826
  });
19827
19827
  }
19828
19828
  exports.group = group2;
19829
- function saveState2(name, value2) {
19829
+ function saveState3(name, value2) {
19830
19830
  const filePath = process.env["GITHUB_STATE"] || "";
19831
19831
  if (filePath) {
19832
19832
  return (0, file_command_1.issueFileCommand)("STATE", (0, file_command_1.prepareKeyValueMessage)(name, value2));
19833
19833
  }
19834
19834
  (0, command_1.issueCommand)("save-state", { name }, (0, utils_1.toCommandValue)(value2));
19835
19835
  }
19836
- exports.saveState = saveState2;
19836
+ exports.saveState = saveState3;
19837
19837
  function getState(name) {
19838
19838
  return process.env[`STATE_${name}`] || "";
19839
19839
  }
@@ -27621,11 +27621,11 @@ var require_mode = __commonJS({
27621
27621
  var require_isexe = __commonJS({
27622
27622
  "node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js"(exports, module) {
27623
27623
  var fs5 = __require("fs");
27624
- var core10;
27624
+ var core11;
27625
27625
  if (process.platform === "win32" || global.TESTING_WINDOWS) {
27626
- core10 = require_windows();
27626
+ core11 = require_windows();
27627
27627
  } else {
27628
- core10 = require_mode();
27628
+ core11 = require_mode();
27629
27629
  }
27630
27630
  module.exports = isexe;
27631
27631
  isexe.sync = sync;
@@ -27648,7 +27648,7 @@ var require_isexe = __commonJS({
27648
27648
  });
27649
27649
  });
27650
27650
  }
27651
- core10(path4, options || {}, function(er, is) {
27651
+ core11(path4, options || {}, function(er, is) {
27652
27652
  if (er) {
27653
27653
  if (er.code === "EACCES" || options && options.ignoreErrors) {
27654
27654
  er = null;
@@ -27660,7 +27660,7 @@ var require_isexe = __commonJS({
27660
27660
  }
27661
27661
  function sync(path4, options) {
27662
27662
  try {
27663
- return core10.sync(path4, options || {});
27663
+ return core11.sync(path4, options || {});
27664
27664
  } catch (er) {
27665
27665
  if (options && options.ignoreErrors || er.code === "EACCES") {
27666
27666
  return false;
@@ -33026,7 +33026,7 @@ var require_util9 = __commonJS({
33026
33026
  var { redirectStatusSet, referrerPolicyTokens, badPortsSet } = require_constants9();
33027
33027
  var { getGlobalOrigin } = require_global3();
33028
33028
  var { collectAnHTTPQuotedString, parseMIMEType } = require_data_url();
33029
- var { performance: performance9 } = __require("node:perf_hooks");
33029
+ var { performance: performance10 } = __require("node:perf_hooks");
33030
33030
  var { ReadableStreamFrom, isValidHTTPToken, normalizedMethodRecordsBase } = require_util8();
33031
33031
  var assert3 = __require("node:assert");
33032
33032
  var { isUint8Array } = __require("node:util/types");
@@ -33181,7 +33181,7 @@ var require_util9 = __commonJS({
33181
33181
  };
33182
33182
  }
33183
33183
  function coarsenedSharedCurrentTime(crossOriginIsolatedCapability) {
33184
- return coarsenTime(performance9.now(), crossOriginIsolatedCapability);
33184
+ return coarsenTime(performance10.now(), crossOriginIsolatedCapability);
33185
33185
  }
33186
33186
  function createOpaqueTimingInfo(timingInfo) {
33187
33187
  return {
@@ -76453,7 +76453,7 @@ var require_core3 = __commonJS({
76453
76453
  Object.defineProperty(exports, "__esModule", { value: true });
76454
76454
  var id_1 = require_id();
76455
76455
  var ref_1 = require_ref();
76456
- var core10 = [
76456
+ var core11 = [
76457
76457
  "$schema",
76458
76458
  "$id",
76459
76459
  "$defs",
@@ -76463,7 +76463,7 @@ var require_core3 = __commonJS({
76463
76463
  id_1.default,
76464
76464
  ref_1.default
76465
76465
  ];
76466
- exports.default = core10;
76466
+ exports.default = core11;
76467
76467
  }
76468
76468
  });
76469
76469
 
@@ -98334,14 +98334,14 @@ var require_turndown_cjs = __commonJS({
98334
98334
  } else if (node2.nodeType === 1) {
98335
98335
  replacement = replacementForNode.call(self2, node2);
98336
98336
  }
98337
- return join27(output, replacement);
98337
+ return join28(output, replacement);
98338
98338
  }, "");
98339
98339
  }
98340
98340
  function postProcess(output) {
98341
98341
  var self2 = this;
98342
98342
  this.rules.forEach(function(rule) {
98343
98343
  if (typeof rule.append === "function") {
98344
- output = join27(output, rule.append(self2.options));
98344
+ output = join28(output, rule.append(self2.options));
98345
98345
  }
98346
98346
  });
98347
98347
  return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
@@ -98353,7 +98353,7 @@ var require_turndown_cjs = __commonJS({
98353
98353
  if (whitespace.leading || whitespace.trailing) content = content.trim();
98354
98354
  return whitespace.leading + rule.replacement(content, node2, this.options) + whitespace.trailing;
98355
98355
  }
98356
- function join27(output, replacement) {
98356
+ function join28(output, replacement) {
98357
98357
  var s1 = trimTrailingNewlines(output);
98358
98358
  var s2 = trimLeadingNewlines(replacement);
98359
98359
  var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
@@ -103144,9 +103144,9 @@ var init_file_type = __esm({
103144
103144
  });
103145
103145
 
103146
103146
  // main.ts
103147
- import { existsSync as existsSync8, readdirSync as readdirSync2 } from "node:fs";
103147
+ import { existsSync as existsSync9, readdirSync as readdirSync2 } from "node:fs";
103148
103148
  import { readFile as readFile5 } from "node:fs/promises";
103149
- import { join as join26 } from "node:path";
103149
+ import { join as join27 } from "node:path";
103150
103150
 
103151
103151
  // node_modules/.pnpm/package-manager-detector@1.8.0/node_modules/package-manager-detector/dist/commands.mjs
103152
103152
  function splitRunArgs(args2, valueFlags = []) {
@@ -104019,6 +104019,22 @@ var providers = {
104019
104019
  }
104020
104020
  }
104021
104021
  }),
104022
+ azure: provider({
104023
+ displayName: "Azure OpenAI",
104024
+ // the resource name is half the endpoint URL and the deployment is the model
104025
+ // id, so both are as load-bearing as the key. only the key is sensitive.
104026
+ envVars: ["AZURE_API_KEY", "AZURE_RESOURCE_NAME", "AZURE_DEPLOYMENT"],
104027
+ models: {
104028
+ // single routing entry — the real model is the customer's deployment name,
104029
+ // read from AZURE_DEPLOYMENT at run time. see ModelRouting docs for why
104030
+ // Azure can't be cataloged even though models.dev lists it.
104031
+ byok: {
104032
+ displayName: "Azure OpenAI",
104033
+ resolve: "azure",
104034
+ routing: "azure"
104035
+ }
104036
+ }
104037
+ }),
104022
104038
  "openai-compatible": provider({
104023
104039
  // "Custom" is the picker group, "OpenAI-compatible" the entry under it, so the
104024
104040
  // menu reads `Custom › OpenAI-compatible` and a second custom backend (a
@@ -104244,6 +104260,11 @@ function getModelEnvVars(slug2) {
104244
104260
  }
104245
104261
  return providerConfig.envVars.slice();
104246
104262
  }
104263
+ function getModelManagedCredentials(slug2) {
104264
+ const parsed2 = parseModel(slug2);
104265
+ const providerConfig = providers[parsed2.provider];
104266
+ return providerConfig?.managedCredentials?.slice() ?? [];
104267
+ }
104247
104268
  var modelAliases = Object.entries(providers).flatMap(
104248
104269
  ([providerKey, config3]) => Object.entries(config3.models).map(([modelId, def]) => ({
104249
104270
  slug: `${providerKey}/${modelId}`,
@@ -104341,6 +104362,13 @@ function isOssAllowedModel(slug2) {
104341
104362
  }
104342
104363
  var BEDROCK_MODEL_ID_ENV = "BEDROCK_MODEL_ID";
104343
104364
  var VERTEX_MODEL_ID_ENV = "VERTEX_MODEL_ID";
104365
+ var AZURE_PROVIDER = "azure";
104366
+ var AZURE_RESOURCE_NAME_ENV = "AZURE_RESOURCE_NAME";
104367
+ var AZURE_API_KEY_ENV = "AZURE_API_KEY";
104368
+ var AZURE_DEPLOYMENT_ENV = "AZURE_DEPLOYMENT";
104369
+ var AZURE_CONTEXT_ENV = "AZURE_CONTEXT";
104370
+ var AZURE_MAX_OUTPUT_ENV = "AZURE_MAX_OUTPUT";
104371
+ var AZURE_USE_CHAT_COMPLETIONS_ENV = "AZURE_USE_CHAT_COMPLETIONS";
104344
104372
  var OPENAI_COMPATIBLE_PROVIDER = "openai-compatible";
104345
104373
  var OPENAI_COMPATIBLE_BASE_URL_ENV = "OPENAI_COMPATIBLE_BASE_URL";
104346
104374
  var OPENAI_COMPATIBLE_API_KEY_ENV = "OPENAI_COMPATIBLE_API_KEY";
@@ -104360,6 +104388,8 @@ function formatMcpToolRef(agentId, toolName) {
104360
104388
  switch (agentId) {
104361
104389
  case "claude":
104362
104390
  return `mcp__${pullfrogMcpName}__${toolName}`;
104391
+ case "codex":
104392
+ return `${pullfrogMcpName}__${toolName}`;
104363
104393
  case "opencode":
104364
104394
  return `${pullfrogMcpName}_${toolName}`;
104365
104395
  default:
@@ -104369,15 +104399,13 @@ function formatMcpToolRef(agentId, toolName) {
104369
104399
  function extractMcpToolRefs(agentId, text) {
104370
104400
  const prefix = formatMcpToolRef(agentId, "");
104371
104401
  const escaped = prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
104372
- return [...text.matchAll(new RegExp(`${escaped}([a-z0-9_]+)`, "g"))].map((match3) => match3[1]);
104402
+ return [...text.matchAll(new RegExp(`(?<![A-Za-z0-9_])${escaped}([a-z0-9_]+)`, "g"))].map(
104403
+ (match3) => match3[1]
104404
+ );
104373
104405
  }
104374
- function assertPromptToolRefs(params) {
104406
+ function findDanglingPromptToolRefs(params) {
104375
104407
  const registered = new Set(params.toolNames);
104376
- const dangling = [...new Set(extractMcpToolRefs(params.agentId, params.prompt))].filter((name) => !registered.has(name)).sort();
104377
- if (dangling.length === 0) return;
104378
- throw new Error(
104379
- `prompt advertises ${dangling.length} tool(s) the MCP server did not register: ${dangling.join(", ")}. registered: ${[...registered].sort().join(", ")}`
104380
- );
104408
+ return [...new Set(extractMcpToolRefs(params.agentId, params.prompt))].filter((name) => !registered.has(name)).sort();
104381
104409
  }
104382
104410
  function hasSimilarIssues(params) {
104383
104411
  return params.repoIntelligence && !!params.event.issue_number && !params.event.is_pr;
@@ -104863,7 +104891,8 @@ import { pipeline } from "node:stream/promises";
104863
104891
  async function installFromNpmTarball(params) {
104864
104892
  const tempDir = process.env.PULLFROG_TEMP_DIR;
104865
104893
  if (!tempDir) throw new Error("PULLFROG_TEMP_DIR is not set");
104866
- const extractedDir = join(tempDir, "package");
104894
+ const packageRoot = join(tempDir, `npm-${params.packageName.replace(/[^a-z0-9]+/gi, "-")}`);
104895
+ const extractedDir = join(packageRoot, "package");
104867
104896
  const cliPath = join(extractedDir, params.executablePath);
104868
104897
  if (existsSync2(cliPath)) {
104869
104898
  log.debug(`\xBB using cached binary at ${cliPath}`);
@@ -104889,7 +104918,8 @@ async function installFromNpmTarball(params) {
104889
104918
  }
104890
104919
  }
104891
104920
  log.debug(`\xBB installing ${params.packageName}@${resolvedVersion}...`);
104892
- const tarballPath = join(tempDir, "package.tgz");
104921
+ mkdirSync(packageRoot, { recursive: true });
104922
+ const tarballPath = join(packageRoot, "package.tgz");
104893
104923
  const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org";
104894
104924
  let tarballUrl;
104895
104925
  if (params.packageName.startsWith("@")) {
@@ -104909,7 +104939,7 @@ async function installFromNpmTarball(params) {
104909
104939
  await pipeline(response.body, fileStream);
104910
104940
  log.debug(`\xBB downloaded tarball to ${tarballPath}`);
104911
104941
  log.debug(`\xBB extracting tarball...`);
104912
- const extractResult = spawnSync("tar", ["-xzf", tarballPath, "-C", tempDir], {
104942
+ const extractResult = spawnSync("tar", ["-xzf", tarballPath, "-C", packageRoot], {
104913
104943
  stdio: "pipe",
104914
104944
  encoding: "utf-8"
104915
104945
  });
@@ -104918,9 +104948,6 @@ async function installFromNpmTarball(params) {
104918
104948
  `Failed to extract tarball: ${extractResult.stderr || extractResult.stdout || "Unknown error"}`
104919
104949
  );
104920
104950
  }
104921
- if (!existsSync2(cliPath)) {
104922
- throw new Error(`Executable not found in extracted package at ${cliPath}`);
104923
- }
104924
104951
  if (params.installDependencies) {
104925
104952
  log.debug(`\xBB installing dependencies for ${params.packageName}...`);
104926
104953
  const installResult = spawnSync("npm", ["install", "--production"], {
@@ -104941,6 +104968,9 @@ async function installFromNpmTarball(params) {
104941
104968
  }
104942
104969
  log.debug(`\xBB dependencies installed`);
104943
104970
  }
104971
+ if (!existsSync2(cliPath)) {
104972
+ throw new Error(`Executable not found in extracted package at ${cliPath}`);
104973
+ }
104944
104974
  chmodSync(cliPath, 493);
104945
104975
  log.debug(`\xBB ${params.packageName} installed at ${cliPath}`);
104946
104976
  return cliPath;
@@ -105114,6 +105144,7 @@ function resolveRunAlias(ctx) {
105114
105144
  }
105115
105145
  const model = ctx.resolvedModel;
105116
105146
  if (!model) return void 0;
105147
+ if (model.startsWith(`${AZURE_PROVIDER}/`)) return void 0;
105117
105148
  return modelAliases.find((a) => !a.fallback && a.resolve === model) ?? matchHostedAnthropicAlias(model);
105118
105149
  }
105119
105150
  function ossEffortFloor(ctx) {
@@ -105152,7 +105183,7 @@ var import_semver = __toESM(require_semver2(), 1);
105152
105183
  // package.json
105153
105184
  var package_default = {
105154
105185
  name: "pullfrog",
105155
- version: "0.1.56",
105186
+ version: "0.1.59",
105156
105187
  type: "module",
105157
105188
  bin: {
105158
105189
  pullfrog: "dist/cli.mjs",
@@ -105184,6 +105215,8 @@ var package_default = {
105184
105215
  "@clack/prompts": "^1.2.0",
105185
105216
  "@modelcontextprotocol/sdk": "^1.26.0",
105186
105217
  "@opencode-ai/sdk": "1.18.5",
105218
+ "@openai/codex": "0.147.0",
105219
+ "@openai/codex-sdk": "0.147.0",
105187
105220
  "@octokit/plugin-throttling": "^11.0.3",
105188
105221
  "@octokit/rest": "^22.0.0",
105189
105222
  "@octokit/webhooks-types": "^7.6.1",
@@ -107305,13 +107338,782 @@ var claude = agent({
107305
107338
  }
107306
107339
  });
107307
107340
 
107308
- // agents/opencode.ts
107341
+ // agents/codex.ts
107309
107342
  var core2 = __toESM(require_core(), 1);
107310
- import { spawn as nodeSpawn2 } from "node:child_process";
107311
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "node:fs";
107343
+ import { existsSync as existsSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync2, writeFileSync as writeFileSync5 } from "node:fs";
107312
107344
  import { join as join6 } from "node:path";
107313
107345
  import { performance as performance6 } from "node:perf_hooks";
107314
107346
 
107347
+ // utils/codexHome.ts
107348
+ import { execFileSync as execFileSync3 } from "node:child_process";
107349
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
107350
+ import { homedir as homedir2, userInfo } from "node:os";
107351
+ import { join as join5 } from "node:path";
107352
+
107353
+ // utils/codexOAuth.ts
107354
+ function decodeJwtExpMs(token) {
107355
+ const parts = token.split(".");
107356
+ if (parts.length !== 3) return null;
107357
+ let payload;
107358
+ try {
107359
+ payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
107360
+ } catch {
107361
+ return null;
107362
+ }
107363
+ if (typeof payload.exp !== "number" || !Number.isFinite(payload.exp)) return null;
107364
+ return payload.exp * 1e3;
107365
+ }
107366
+ function parseCodexAuthBody(raw2) {
107367
+ let parsed2;
107368
+ try {
107369
+ parsed2 = JSON.parse(raw2);
107370
+ } catch {
107371
+ return null;
107372
+ }
107373
+ if (!parsed2 || typeof parsed2 !== "object") return null;
107374
+ const v = parsed2;
107375
+ if (v.auth_mode !== "chatgpt") return null;
107376
+ const tokens = v.tokens;
107377
+ if (!tokens || typeof tokens !== "object") return null;
107378
+ const t = tokens;
107379
+ if (typeof t.access_token !== "string" || t.access_token.length === 0) return null;
107380
+ if (typeof t.refresh_token !== "string" || t.refresh_token.length === 0) return null;
107381
+ return {
107382
+ auth_mode: "chatgpt",
107383
+ ...typeof v.refresh_rejected_at === "string" ? { refresh_rejected_at: v.refresh_rejected_at } : {},
107384
+ tokens: {
107385
+ access_token: t.access_token,
107386
+ refresh_token: t.refresh_token,
107387
+ ...typeof t.id_token === "string" ? { id_token: t.id_token } : {},
107388
+ ...typeof t.account_id === "string" ? { account_id: t.account_id } : {}
107389
+ },
107390
+ ...typeof v.last_refresh === "string" ? { last_refresh: v.last_refresh } : {}
107391
+ };
107392
+ }
107393
+ function stringifyCodexAuthBody(body) {
107394
+ return `${JSON.stringify(body, null, 2)}
107395
+ `;
107396
+ }
107397
+
107398
+ // utils/codexHome.ts
107399
+ var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
107400
+ var PULLFROG_DATA_DIR = "/var/lib/pullfrog";
107401
+ function isRejectedChain(body) {
107402
+ if (!body.refresh_rejected_at) return false;
107403
+ log.warning(
107404
+ `\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.`
107405
+ );
107406
+ return true;
107407
+ }
107408
+ function installCodexAuth() {
107409
+ const raw2 = process.env[CODEX_AUTH_ENV];
107410
+ if (!raw2) return null;
107411
+ const body = parseCodexAuthBody(raw2);
107412
+ if (!body) {
107413
+ log.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
107414
+ return null;
107415
+ }
107416
+ if (isRejectedChain(body)) return null;
107417
+ const expiresMs = decodeJwtExpMs(body.tokens.access_token) ?? 0;
107418
+ const xdgDataHome = resolveDataHome();
107419
+ const opencodeDir = join5(xdgDataHome, "opencode");
107420
+ const authPath = join5(opencodeDir, "auth.json");
107421
+ const opencodeAuth = {
107422
+ openai: {
107423
+ type: "oauth",
107424
+ refresh: body.tokens.refresh_token,
107425
+ access: body.tokens.access_token,
107426
+ expires: expiresMs,
107427
+ ...body.tokens.account_id ? { accountId: body.tokens.account_id } : {}
107428
+ }
107429
+ };
107430
+ mkdirSync5(opencodeDir, { recursive: true });
107431
+ writeFileSync4(authPath, `${JSON.stringify(opencodeAuth, null, 2)}
107432
+ `, { mode: 384 });
107433
+ process.env.XDG_DATA_HOME = xdgDataHome;
107434
+ log.info(`\xBB installed Codex auth at ${authPath}`);
107435
+ return {
107436
+ authPath,
107437
+ xdgDataHome,
107438
+ originalRefresh: body.tokens.refresh_token,
107439
+ originalIdToken: body.tokens.id_token
107440
+ };
107441
+ }
107442
+ function installCodexHome() {
107443
+ const raw2 = process.env[CODEX_AUTH_ENV];
107444
+ if (!raw2) return null;
107445
+ const body = parseCodexAuthBody(raw2);
107446
+ if (!body) {
107447
+ log.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
107448
+ return null;
107449
+ }
107450
+ if (isRejectedChain(body)) return null;
107451
+ if (!body.tokens.id_token) {
107452
+ log.warning(
107453
+ `\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.`
107454
+ );
107455
+ return null;
107456
+ }
107457
+ const codexHome = join5(resolveDataHome(), "codex");
107458
+ const authPath = join5(codexHome, "auth.json");
107459
+ mkdirSync5(codexHome, { recursive: true });
107460
+ writeFileSync4(authPath, stringifyCodexAuthBody(body), { mode: 384 });
107461
+ log.info(`\xBB installed Codex auth at ${authPath}`);
107462
+ return { codexHome, authPath, originalRefresh: body.tokens.refresh_token };
107463
+ }
107464
+ function resolveDataHome() {
107465
+ if (process.env.CI !== "true") return join5(homedir2(), ".local", "share");
107466
+ bootstrapPullfrogDataDir();
107467
+ return PULLFROG_DATA_DIR;
107468
+ }
107469
+ function bootstrapPullfrogDataDir() {
107470
+ const user = userInfo().username;
107471
+ let primaryGroup;
107472
+ try {
107473
+ primaryGroup = execFileSync3("id", ["-gn", user], { stdio: "pipe", encoding: "utf-8" }).trim();
107474
+ } catch {
107475
+ primaryGroup = user;
107476
+ }
107477
+ try {
107478
+ execFileSync3("sudo", ["-n", "mkdir", "-p", PULLFROG_DATA_DIR], { stdio: "pipe" });
107479
+ execFileSync3("sudo", ["-n", "chown", `${user}:${primaryGroup}`, PULLFROG_DATA_DIR], {
107480
+ stdio: "pipe"
107481
+ });
107482
+ execFileSync3("sudo", ["-n", "chmod", "700", PULLFROG_DATA_DIR], { stdio: "pipe" });
107483
+ } catch (err) {
107484
+ throw new Error(
107485
+ `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.`
107486
+ );
107487
+ }
107488
+ }
107489
+
107490
+ // utils/secrets.ts
107491
+ var SENSITIVE_PATTERNS = [
107492
+ /_KEY$/i,
107493
+ /_SECRET$/i,
107494
+ /_TOKEN$/i,
107495
+ /_PASSWORD$/i,
107496
+ /_CREDENTIAL$/i
107497
+ ];
107498
+ function isSensitiveEnvName(key) {
107499
+ return SENSITIVE_PATTERNS.some((p) => p.test(key));
107500
+ }
107501
+ var SAFE_ENV_PREFIXES = ["GITHUB_", "RUNNER_", "JAVA_HOME_", "GOROOT_"];
107502
+ var SAFE_ENV_NAMES = /* @__PURE__ */ new Set([
107503
+ // system
107504
+ "CI",
107505
+ "HOME",
107506
+ "LANG",
107507
+ "LOGNAME",
107508
+ "PATH",
107509
+ "SHELL",
107510
+ "SHLVL",
107511
+ "TERM",
107512
+ "TMPDIR",
107513
+ "TZ",
107514
+ "USER",
107515
+ "XDG_CONFIG_HOME",
107516
+ "XDG_RUNTIME_DIR",
107517
+ "DEBIAN_FRONTEND",
107518
+ // runner image toolchain
107519
+ "ACCEPT_EULA",
107520
+ "AGENT_TOOLSDIRECTORY",
107521
+ "ANDROID_HOME",
107522
+ "ANDROID_NDK",
107523
+ "ANDROID_NDK_HOME",
107524
+ "ANDROID_NDK_LATEST_HOME",
107525
+ "ANDROID_NDK_ROOT",
107526
+ "ANDROID_SDK_ROOT",
107527
+ "ANT_HOME",
107528
+ "AZURE_EXTENSION_DIR",
107529
+ "BOOTSTRAP_HASKELL_NONINTERACTIVE",
107530
+ "CHROME_BIN",
107531
+ "CHROMEWEBDRIVER",
107532
+ "CONDA",
107533
+ "DOTNET_MULTILEVEL_LOOKUP",
107534
+ "DOTNET_NOLOGO",
107535
+ "DOTNET_SKIP_FIRST_TIME_EXPERIENCE",
107536
+ "EDGEWEBDRIVER",
107537
+ "GECKOWEBDRIVER",
107538
+ "GHCUP_INSTALL_BASE_PREFIX",
107539
+ "GRADLE_HOME",
107540
+ "JAVA_HOME",
107541
+ "HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS",
107542
+ "HOMEBREW_NO_AUTO_UPDATE",
107543
+ "ImageOS",
107544
+ "ImageVersion",
107545
+ "NVM_DIR",
107546
+ "PIPX_BIN_DIR",
107547
+ "PIPX_HOME",
107548
+ "PSModulePath",
107549
+ "SELENIUM_JAR_PATH",
107550
+ "SGX_AESM_ADDR",
107551
+ "SWIFT_PATH",
107552
+ "VCPKG_INSTALLATION_ROOT"
107553
+ ]);
107554
+ var _userAllowlist = null;
107555
+ function setEnvAllowlist(raw2) {
107556
+ const names = raw2.split("\n").map((line) => line.trim()).filter(Boolean);
107557
+ _userAllowlist = new Set(names);
107558
+ }
107559
+ function isSafeEnvVar(key) {
107560
+ if (SAFE_ENV_NAMES.has(key)) return true;
107561
+ return SAFE_ENV_PREFIXES.some((p) => key.startsWith(p));
107562
+ }
107563
+ var WORKFLOW_COMMAND_ENV_NAMES = [
107564
+ "GITHUB_ENV",
107565
+ "GITHUB_PATH",
107566
+ "GITHUB_OUTPUT",
107567
+ "GITHUB_STATE",
107568
+ "GITHUB_STEP_SUMMARY",
107569
+ // dropping the five paths above but keeping RUNNER_TEMP would be theatre:
107570
+ // the file-command files live in `$RUNNER_TEMP/_runner_file_commands/`, so the
107571
+ // directory is one `ls` away. nothing in the install path reads it (only our
107572
+ // own leak-surface wipe in `setup.ts`, which runs in the parent), and package
107573
+ // managers use TMPDIR.
107574
+ "RUNNER_TEMP"
107575
+ ];
107576
+ function filterEnvForUntrustedCode() {
107577
+ const env2 = filterEnv();
107578
+ for (const name of WORKFLOW_COMMAND_ENV_NAMES) delete env2[name];
107579
+ return env2;
107580
+ }
107581
+ function filterEnv() {
107582
+ const filtered = {};
107583
+ for (const [key, value2] of Object.entries(process.env)) {
107584
+ if (value2 === void 0) continue;
107585
+ const userAllowed = _userAllowlist?.has(key) ?? false;
107586
+ if (isSensitiveEnvName(key) && !userAllowed) continue;
107587
+ if (isSafeEnvVar(key) || userAllowed) {
107588
+ filtered[key] = value2;
107589
+ }
107590
+ }
107591
+ return filtered;
107592
+ }
107593
+ function resolveEnv(mode) {
107594
+ if (mode === "inherit") {
107595
+ return process.env;
107596
+ }
107597
+ if (mode === "restricted" || mode === void 0) {
107598
+ return filterEnv();
107599
+ }
107600
+ return { ...filterEnv(), ...mode };
107601
+ }
107602
+
107603
+ // agents/codex.ts
107604
+ var CODEX_TARGETS = {
107605
+ "linux-x64": "x86_64-unknown-linux-musl",
107606
+ "linux-arm64": "aarch64-unknown-linux-musl",
107607
+ "darwin-x64": "x86_64-apple-darwin",
107608
+ "darwin-arm64": "aarch64-apple-darwin",
107609
+ "win32-x64": "x86_64-pc-windows-msvc",
107610
+ "win32-arm64": "aarch64-pc-windows-msvc"
107611
+ };
107612
+ async function installCodexCli() {
107613
+ const target = CODEX_TARGETS[`${process.platform}-${process.arch}`];
107614
+ if (!target) {
107615
+ throw new Error(`the codex CLI ships no binary for ${process.platform}-${process.arch}`);
107616
+ }
107617
+ const binary = process.platform === "win32" ? "codex.exe" : "codex";
107618
+ return await installFromNpmTarball({
107619
+ packageName: "@openai/codex",
107620
+ version: getDevDependencyVersion("@openai/codex"),
107621
+ executablePath: `node_modules/@openai/codex-${process.platform}-${process.arch}/vendor/${target}/bin/${binary}`,
107622
+ installDependencies: true
107623
+ });
107624
+ }
107625
+ var CODEX_DISABLED_FEATURES = [
107626
+ // browser / desktop control — network reach and arbitrary JS
107627
+ "browser_use",
107628
+ "browser_use_external",
107629
+ "browser_use_full_cdp_access",
107630
+ "computer_use",
107631
+ "in_app_browser",
107632
+ // third-party code loaded into the agent process
107633
+ "plugins",
107634
+ "remote_plugin",
107635
+ "plugin_sharing",
107636
+ "skill_mcp_dependency_install",
107637
+ // lifecycle hooks run commands sourced from repo config, which a PR can edit
107638
+ "hooks",
107639
+ // connectors
107640
+ "apps",
107641
+ // ungated subagent fan-out (see the file header)
107642
+ "multi_agent",
107643
+ "multi_agent_v2"
107644
+ ];
107645
+ var CODEX_SHELL_FEATURES = ["shell_tool", "unified_exec"];
107646
+ function tomlBool(entries, value2) {
107647
+ return entries.map((name) => `${name} = ${value2}`).join("\n");
107648
+ }
107649
+ function securityOverrideFlags(params) {
107650
+ const shellEnabled = params.ctx.payload.shell === "enabled";
107651
+ const features = [
107652
+ ...CODEX_DISABLED_FEATURES.map((name) => `${name}=false`),
107653
+ ...CODEX_SHELL_FEATURES.map((name) => `${name}=${shellEnabled}`)
107654
+ ];
107655
+ return [
107656
+ "-c",
107657
+ `approval_policy=${JSON.stringify("never")}`,
107658
+ "-c",
107659
+ `sandbox_mode=${JSON.stringify(params.sandboxMode)}`,
107660
+ // the sandbox's EXTENT has to be locked too, not just its mode: a repo
107661
+ // setting `writable_roots = ["/"]` walked straight out of the checkout with
107662
+ // `sandbox_mode` already flag-locked (measured — apply_patch wrote a file
107663
+ // under $HOME).
107664
+ "-c",
107665
+ `sandbox_workspace_write.writable_roots=[${JSON.stringify(params.repoDir)}]`,
107666
+ "-c",
107667
+ "sandbox_workspace_write.network_access=true",
107668
+ // our own MCP server, for a third time in the same class: a repo pointing
107669
+ // `mcp_servers.pullfrog.url` at its own endpoint (measured — `codex mcp get`
107670
+ // reported the repo's URL) turns every pullfrog tool call into an
107671
+ // exfiltration channel, carrying diffs, file contents and cross-repo
107672
+ // learnings to whoever it names.
107673
+ "-c",
107674
+ `mcp_servers.${pullfrogMcpName}.url=${JSON.stringify(params.mcpServerUrl)}`,
107675
+ "-c",
107676
+ `mcp_servers.${pullfrogMcpName}.${APPROVAL_MODE_KEY}=${JSON.stringify("approve")}`,
107677
+ "-c",
107678
+ `mcp_servers.${pullfrogMcpName}.required=true`,
107679
+ "-c",
107680
+ `mcp_servers.${pullfrogMcpName}.tool_timeout_sec=660`,
107681
+ // `enabled` closes the silent-DoS variant: a repo setting it false would
107682
+ // otherwise strip every GitHub tool and leave the agent narrating failure.
107683
+ "-c",
107684
+ `mcp_servers.${pullfrogMcpName}.enabled=true`,
107685
+ // not a boundary, but the same override: a repo silently re-pointing the
107686
+ // model spends the account's money on a pick it never made, and makes the
107687
+ // "Using `…`" run footer a lie about what actually ran.
107688
+ ...params.model ? ["-c", `model=${JSON.stringify(params.model)}`] : [],
107689
+ ...params.effortRung ? ["-c", `model_reasoning_effort=${JSON.stringify(params.effortRung)}`] : [],
107690
+ ...features.flatMap((entry) => ["-c", `features.${entry}`])
107691
+ ];
107692
+ }
107693
+ var CODEX_EFFORTS = ["minimal", "low", "medium", "high", "xhigh"];
107694
+ function resolveCodexModel(ctx) {
107695
+ const specifier = ctx.payload.proxyModel ?? ctx.resolvedModel;
107696
+ if (!specifier) return void 0;
107697
+ try {
107698
+ if (getModelProvider(specifier) === "openai") return stripProviderPrefix(specifier);
107699
+ } catch {
107700
+ }
107701
+ log.warning(`\xBB ${specifier} isn't an OpenAI model \u2014 running codex on its own default instead`);
107702
+ return void 0;
107703
+ }
107704
+ var APPROVAL_MODE_KEY = "default_tools_approval_mode";
107705
+ var APPROVE_TOOLS = `${APPROVAL_MODE_KEY} = "approve"`;
107706
+ function repoMcpApprovals(repoDir) {
107707
+ const path4 = join6(repoDir, ".codex", "config.toml");
107708
+ if (!existsSync4(path4)) return [];
107709
+ const out = [];
107710
+ let section = [];
107711
+ let isServerTable = false;
107712
+ function flush() {
107713
+ if (section.length === 0) return;
107714
+ const declared = section.some((line) => line.trimStart().startsWith(APPROVAL_MODE_KEY));
107715
+ if (isServerTable && !declared) section.splice(1, 0, APPROVE_TOOLS);
107716
+ out.push(...section, "");
107717
+ section = [];
107718
+ }
107719
+ for (const line of readFileSync2(path4, "utf8").split("\n")) {
107720
+ const header = line.trim().match(/^\[(?<key>[^\]]+)\]$/);
107721
+ if (!header?.groups) {
107722
+ if (section.length > 0) section.push(line);
107723
+ continue;
107724
+ }
107725
+ flush();
107726
+ const key = header.groups.key.trim();
107727
+ if (!key.startsWith("mcp_servers.")) continue;
107728
+ const name = key.slice("mcp_servers.".length).replace(/^"([^"]*)"/, "$1");
107729
+ if (name === pullfrogMcpName || name.startsWith(`${pullfrogMcpName}.`)) continue;
107730
+ isServerTable = !name.includes(".");
107731
+ section = [line.trim()];
107732
+ }
107733
+ flush();
107734
+ return out;
107735
+ }
107736
+ function warnIfNativeEditsUnavailable(ctx) {
107737
+ if (process.platform !== "linux" || ctx.payload.shell !== "disabled") return;
107738
+ log.warning(
107739
+ "\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."
107740
+ );
107741
+ }
107742
+ function writeCodexConfig(params) {
107743
+ const shellEnabled = params.ctx.payload.shell === "enabled";
107744
+ const sandboxMode = params.ctx.payload.push === "disabled" ? "read-only" : "workspace-write";
107745
+ const repoDir = process.cwd();
107746
+ const config3 = [
107747
+ "# written by pullfrog",
107748
+ // "never" keeps the run non-interactive WITHOUT
107749
+ // --dangerously-bypass-approvals-and-sandbox, which would also switch the
107750
+ // sandbox off and let the native apply_patch tool write anywhere.
107751
+ 'approval_policy = "never"',
107752
+ `sandbox_mode = ${JSON.stringify(sandboxMode)}`,
107753
+ // matches opencode's `webfetch: "allow"` — research is part of the job and
107754
+ // neither harness gates it on a permission.
107755
+ 'web_search = "live"',
107756
+ ...params.model ? [`model = ${JSON.stringify(params.model)}`] : [],
107757
+ ...params.effortRung ? [`model_reasoning_effort = ${JSON.stringify(params.effortRung)}`] : [],
107758
+ "",
107759
+ "[features]",
107760
+ tomlBool(CODEX_DISABLED_FEATURES, false),
107761
+ tomlBool(CODEX_SHELL_FEATURES, shellEnabled),
107762
+ "",
107763
+ "[sandbox_workspace_write]",
107764
+ "network_access = true",
107765
+ // the checkout, named explicitly rather than left to `workspace-write` to
107766
+ // imply. does NOT fix the Linux native-write failure — nothing here does;
107767
+ // see wiki/codex-agent.md "The Linux native-write blocker".
107768
+ `writable_roots = [${JSON.stringify(repoDir)}]`,
107769
+ "",
107770
+ `[mcp_servers.${pullfrogMcpName}]`,
107771
+ `url = ${JSON.stringify(params.ctx.mcpServerUrl)}`,
107772
+ // codex's default MCP approval mode PROMPTS for anything it can't read as
107773
+ // read-only, and a `codex exec` run has nobody to answer — the call comes
107774
+ // back "user cancelled MCP tool call" and the agent silently accomplishes
107775
+ // nothing. our own server is the trusted one; the boundary is what we
107776
+ // register, not a second confirmation.
107777
+ APPROVE_TOOLS,
107778
+ // a run whose GitHub tools never registered can't do its job, so fail loudly
107779
+ // rather than let the agent improvise without them.
107780
+ "required = true",
107781
+ // mirrors the opencode harness's 660s: `checkout_pr` runs a multi-minute
107782
+ // fetch on large repos and enforces its own 600s deadline, so a shorter
107783
+ // client abort turns a working checkout into a spurious tool error.
107784
+ "tool_timeout_sec = 660",
107785
+ "",
107786
+ ...repoMcpApprovals(repoDir),
107787
+ // UNTRUSTED, deliberately, and this one line is the whole config-precedence
107788
+ // boundary. Codex loads `<repo>/.codex/config.toml` as a layer that
107789
+ // OUTRANKS ours (project 25 vs user 20), and its denylist strips none of
107790
+ // `sandbox_mode`, `approval_policy`, `features` or `mcp_servers` — so a
107791
+ // fork PR could ship a config re-enabling the native shell. Marking the
107792
+ // checkout untrusted makes that layer load `disabled`, closing the class
107793
+ // outright rather than key by key: `RawMcpServerConfig` alone has ~26
107794
+ // fields and enumeration can never finish.
107795
+ //
107796
+ // It costs nothing. `codex exec`'s only startup gate is a GIT-REPO check
107797
+ // (`exec/src/lib.rs:796` — `get_git_repo_root(...).is_none()`, whose error
107798
+ // text unhelpfully says "Not inside a trusted directory"), so an untrusted
107799
+ // checkout still runs. And the repo's own MCP servers survive, because
107800
+ // `repoMcpApprovals` above copies them into OUR layer — which is why that
107801
+ // copying exists at all.
107802
+ //
107803
+ // Measured both ways at 0.147.0: `trusted` → the repo's `model` won;
107804
+ // `untrusted` → it did not, and the run proceeded normally.
107805
+ `[projects.${JSON.stringify(repoDir)}]`,
107806
+ 'trust_level = "untrusted"',
107807
+ ""
107808
+ ].join("\n");
107809
+ writeFileSync5(join6(params.codexHome, "config.toml"), config3, { mode: 384 });
107810
+ log.info(`\xBB codex config: sandbox=${sandboxMode}, nativeShell=${shellEnabled}`);
107811
+ }
107812
+ function toTodoWriteInput(item) {
107813
+ return {
107814
+ todos: item.items.map((todo, index) => ({
107815
+ id: String(index),
107816
+ content: todo.text,
107817
+ status: todo.completed ? "completed" : "pending"
107818
+ }))
107819
+ };
107820
+ }
107821
+ async function runCodex(params) {
107822
+ const startTime = performance6.now();
107823
+ const thinkingTimer = new ThinkingTimer();
107824
+ const output = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
107825
+ const recentStderr = [];
107826
+ let eventCount = 0;
107827
+ let finalOutput = "";
107828
+ let threadId;
107829
+ let turnError = null;
107830
+ let lastProviderError = null;
107831
+ let stdoutBuffer = "";
107832
+ const tokens = { input: 0, cacheRead: 0, output: 0 };
107833
+ function buildUsage2() {
107834
+ if (tokens.input === 0 && tokens.output === 0) return void 0;
107835
+ return {
107836
+ agent: "codex",
107837
+ inputTokens: tokens.input,
107838
+ outputTokens: tokens.output,
107839
+ cacheReadTokens: tokens.cacheRead || void 0
107840
+ };
107841
+ }
107842
+ function onItem(item, phase) {
107843
+ switch (item.type) {
107844
+ case "agent_message":
107845
+ if (phase === "completed" && item.text.trim()) {
107846
+ finalOutput = item.text.trim();
107847
+ log.box(finalOutput, { title: "Pullfrog" });
107848
+ }
107849
+ return;
107850
+ case "reasoning":
107851
+ if (phase === "completed" && item.text.trim()) {
107852
+ log.box(item.text.trim(), { title: "Pullfrog" });
107853
+ }
107854
+ return;
107855
+ case "command_execution":
107856
+ if (phase === "started") {
107857
+ thinkingTimer.markToolCall();
107858
+ log.toolCall({ toolName: "shell", input: { command: item.command } });
107859
+ return;
107860
+ }
107861
+ thinkingTimer.markToolResult();
107862
+ log.startGroup("shell output");
107863
+ log.info(item.aggregated_output || "");
107864
+ log.endGroup();
107865
+ return;
107866
+ case "mcp_tool_call":
107867
+ if (phase === "started") {
107868
+ thinkingTimer.markToolCall();
107869
+ params.onToolUse?.({ toolName: item.tool, input: item.arguments });
107870
+ log.toolCall({ toolName: `${item.server}__${item.tool}`, input: item.arguments });
107871
+ if (item.tool.includes("report_progress")) params.todoTracker?.cancel();
107872
+ return;
107873
+ }
107874
+ thinkingTimer.markToolResult();
107875
+ if (item.error) log.info(`\xBB tool error: ${item.error.message}`);
107876
+ return;
107877
+ case "file_change":
107878
+ if (phase === "completed") {
107879
+ const paths = item.changes.map((c) => `${c.kind} ${c.path}`).join(", ");
107880
+ log.info(`\xBB apply_patch (${item.status}): ${paths}`);
107881
+ }
107882
+ return;
107883
+ case "web_search":
107884
+ if (phase === "started") log.toolCall({ toolName: "web_search", input: item.query });
107885
+ return;
107886
+ case "todo_list":
107887
+ if (params.todoTracker?.enabled) params.todoTracker.update(toTodoWriteInput(item));
107888
+ return;
107889
+ case "error":
107890
+ log.info(`\xBB ${item.message}`);
107891
+ return;
107892
+ default:
107893
+ item;
107894
+ }
107895
+ }
107896
+ function onEvent(event) {
107897
+ switch (event.type) {
107898
+ case "thread.started":
107899
+ threadId = event.thread_id;
107900
+ return;
107901
+ case "turn.completed":
107902
+ tokens.input += event.usage.input_tokens ?? 0;
107903
+ tokens.cacheRead += event.usage.cached_input_tokens ?? 0;
107904
+ tokens.output += event.usage.output_tokens ?? 0;
107905
+ return;
107906
+ case "turn.failed":
107907
+ turnError = event.error.message;
107908
+ log.info(`\xBB turn failed: ${turnError}`);
107909
+ return;
107910
+ case "error":
107911
+ turnError = event.message;
107912
+ log.info(`\xBB codex error: ${event.message}`);
107913
+ return;
107914
+ case "item.started":
107915
+ onItem(event.item, "started");
107916
+ return;
107917
+ case "item.completed":
107918
+ onItem(event.item, "completed");
107919
+ return;
107920
+ case "item.updated":
107921
+ if (event.item.type === "todo_list") onItem(event.item, "started");
107922
+ return;
107923
+ case "turn.started":
107924
+ return;
107925
+ default:
107926
+ event;
107927
+ }
107928
+ }
107929
+ try {
107930
+ const result = await spawn({
107931
+ cmd: params.cliPath,
107932
+ args: params.args,
107933
+ cwd: params.cwd,
107934
+ env: params.env,
107935
+ activityTimeout: AGENT_ACTIVITY_TIMEOUT_MS,
107936
+ onActivityTimeout: params.onActivityTimeout,
107937
+ // stdin MUST be `ignore`: codex reads a piped stdin as an extra `<stdin>`
107938
+ // prompt block and blocks on EOF, so an inherited pipe hangs the run
107939
+ // before the first model call.
107940
+ stdio: ["ignore", "pipe", "pipe"],
107941
+ killGroup: true,
107942
+ retain: "none",
107943
+ onStdout: (chunk) => {
107944
+ output.append(chunk);
107945
+ markActivity();
107946
+ stdoutBuffer += chunk;
107947
+ const lines = stdoutBuffer.split("\n");
107948
+ stdoutBuffer = lines.pop() || "";
107949
+ for (const line of lines) {
107950
+ const trimmed = line.trim();
107951
+ if (!trimmed) continue;
107952
+ let event;
107953
+ try {
107954
+ event = JSON.parse(trimmed);
107955
+ } catch {
107956
+ log.debug(`\xBB non-JSON stdout line: ${trimmed.substring(0, 200)}`);
107957
+ continue;
107958
+ }
107959
+ eventCount++;
107960
+ log.debug(JSON.stringify(event, null, 2));
107961
+ const idleMs = getIdleMs();
107962
+ if (idleMs > 1e4) {
107963
+ log.info(
107964
+ `\xBB no activity for ${(idleMs / 1e3).toFixed(1)}s (${eventCount} events processed so far)`
107965
+ );
107966
+ }
107967
+ markActivity();
107968
+ onEvent(event);
107969
+ }
107970
+ },
107971
+ onStderr: (chunk) => {
107972
+ const trimmed = chunk.trim();
107973
+ if (!trimmed) return;
107974
+ recentStderr.push(trimmed);
107975
+ if (recentStderr.length > MAX_STDERR_LINES) recentStderr.shift();
107976
+ const match3 = findProviderErrorMatch(trimmed);
107977
+ if (match3) {
107978
+ lastProviderError = match3.label;
107979
+ log.info(`\xBB provider error detected (${match3.label}): ${match3.excerpt}`);
107980
+ } else {
107981
+ log.debug(trimmed);
107982
+ }
107983
+ }
107984
+ });
107985
+ const duration4 = performance6.now() - startTime;
107986
+ log.info(`\xBB codex completed in ${Math.round(duration4)}ms with exit code ${result.exitCode}`);
107987
+ const usage = buildUsage2();
107988
+ if (usage) {
107989
+ logTokenTable({
107990
+ input: tokens.input - tokens.cacheRead,
107991
+ cacheRead: tokens.cacheRead,
107992
+ cacheWrite: 0,
107993
+ output: tokens.output
107994
+ });
107995
+ }
107996
+ if (result.exitCode !== 0 || turnError) {
107997
+ const stderrSnapshot = recentStderr.join("\n");
107998
+ const error52 = turnError || stderrSnapshot || lastProviderError && `provider error: ${lastProviderError}` || `unknown error - no output from Codex CLI (exit ${result.exitCode})`;
107999
+ log.error(`codex exited with code ${result.exitCode}: ${error52}`);
108000
+ return { success: false, output: finalOutput || output.toString(), error: error52, usage, threadId };
108001
+ }
108002
+ return { success: true, output: finalOutput || output.toString(), usage, threadId };
108003
+ } catch (error52) {
108004
+ const duration4 = performance6.now() - startTime;
108005
+ const message = error52 instanceof Error ? error52.message : String(error52);
108006
+ const isActivityTimeout = error52 instanceof SpawnTimeoutError && error52.code === SPAWN_ACTIVITY_TIMEOUT_CODE;
108007
+ 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`;
108008
+ log.info(
108009
+ `\xBB codex ${isActivityTimeout ? "hung" : "failed"} after ${(duration4 / 1e3).toFixed(1)}s: ${message}`
108010
+ );
108011
+ log.info(`\xBB diagnosis: ${diagnosis}`);
108012
+ if (recentStderr.length > 0) log.info(`\xBB recent stderr:
108013
+ ${recentStderr.join("\n")}`);
108014
+ return {
108015
+ success: false,
108016
+ output: finalOutput || output.toString(),
108017
+ error: `${message} [${diagnosis}]`,
108018
+ usage: buildUsage2(),
108019
+ threadId
108020
+ };
108021
+ }
108022
+ }
108023
+ var codex = agent({
108024
+ name: "codex",
108025
+ install: installCodexCli,
108026
+ run: async (ctx) => {
108027
+ const cliPath = await installCodexCli();
108028
+ warnIfNativeEditsUnavailable(ctx);
108029
+ const codexHomeAuth = installCodexHome();
108030
+ const codexHome = codexHomeAuth?.codexHome ?? join6(ctx.tmpdir, ".codex");
108031
+ mkdirSync6(codexHome, { recursive: true });
108032
+ const effort = resolveRunEffort(ctx);
108033
+ if (effort.rung && !CODEX_EFFORTS.includes(effort.rung)) {
108034
+ log.warning(`\xBB effort ${effort.rung} not sent \u2014 codex doesn't accept that level`);
108035
+ }
108036
+ writeCodexConfig({
108037
+ ctx,
108038
+ codexHome,
108039
+ model: resolveCodexModel(ctx),
108040
+ effortRung: effort.rung && CODEX_EFFORTS.includes(effort.rung) ? effort.rung : void 0
108041
+ });
108042
+ if (codexHomeAuth) {
108043
+ core2.saveState(
108044
+ "codex_writeback",
108045
+ JSON.stringify({
108046
+ apiToken: ctx.apiToken,
108047
+ authPath: codexHomeAuth.authPath,
108048
+ originalRefresh: codexHomeAuth.originalRefresh
108049
+ })
108050
+ );
108051
+ }
108052
+ const baseEnv = ctx.payload.shell === "enabled" ? process.env : filterEnv();
108053
+ const env2 = {
108054
+ ...baseEnv,
108055
+ CODEX_HOME: codexHome,
108056
+ PWD: process.cwd()
108057
+ };
108058
+ delete env2.CODEX_API_KEY;
108059
+ if (codexHomeAuth) {
108060
+ delete env2.OPENAI_API_KEY;
108061
+ } else if (process.env.OPENAI_API_KEY) {
108062
+ env2.CODEX_API_KEY = process.env.OPENAI_API_KEY;
108063
+ }
108064
+ const securityFlags = securityOverrideFlags({
108065
+ ctx,
108066
+ sandboxMode: ctx.payload.push === "disabled" ? "read-only" : "workspace-write",
108067
+ repoDir: process.cwd(),
108068
+ mcpServerUrl: ctx.mcpServerUrl,
108069
+ model: resolveCodexModel(ctx),
108070
+ effortRung: effort.rung && CODEX_EFFORTS.includes(effort.rung) ? effort.rung : void 0
108071
+ });
108072
+ const runnerArgs = { cliPath, cwd: process.cwd(), env: env2, todoTracker: ctx.todoTracker };
108073
+ const initial = await runCodex({
108074
+ ...runnerArgs,
108075
+ args: [...securityFlags, "exec", "--json", ctx.instructions.full],
108076
+ onActivityTimeout: ctx.onActivityTimeout,
108077
+ onToolUse: ctx.onToolUse
108078
+ });
108079
+ const result = await runPostRunRetryLoop({
108080
+ ctx,
108081
+ initialResult: initial,
108082
+ initialUsage: initial.usage,
108083
+ reflectionPrompt: buildReflectionPrompt(ctx.toolState),
108084
+ // a resume needs the thread codex recorded. without one there is nothing
108085
+ // to continue, and `resume --last` would be worse than not resuming: it
108086
+ // picks the newest recorded session in this cwd, which on a self-hosted
108087
+ // runner with a shared CODEX_HOME is somebody else's run.
108088
+ canResume: (r) => r.threadId !== void 0,
108089
+ resume: async (c) => runCodex({
108090
+ ...runnerArgs,
108091
+ args: [
108092
+ ...securityFlags,
108093
+ "exec",
108094
+ "resume",
108095
+ "--json",
108096
+ c.previousResult.threadId ?? "",
108097
+ c.prompt
108098
+ ]
108099
+ })
108100
+ });
108101
+ if (result.success) {
108102
+ await ctx.todoTracker?.flush();
108103
+ } else {
108104
+ ctx.todoTracker?.cancel();
108105
+ }
108106
+ return result;
108107
+ }
108108
+ });
108109
+
108110
+ // agents/opencode.ts
108111
+ var core3 = __toESM(require_core(), 1);
108112
+ import { spawn as nodeSpawn2 } from "node:child_process";
108113
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "node:fs";
108114
+ import { join as join7 } from "node:path";
108115
+ import { performance as performance7 } from "node:perf_hooks";
108116
+
107315
108117
  // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/core/serverSentEvents.gen.js
107316
108118
  var createSseClient = ({ onRequest, onSseError, onSseEvent, responseTransformer, responseValidator, sseDefaultRetryDelay, sseMaxRetryAttempts, sseMaxRetryDelay, sseSleepFn, url: url4, ...options }) => {
107317
108119
  let lastEventId;
@@ -112718,114 +113520,6 @@ var import_cross_spawn = __toESM(require_cross_spawn(), 1);
112718
113520
  // agents/opencode.ts
112719
113521
  var import_undici = __toESM(require_undici2(), 1);
112720
113522
 
112721
- // utils/codexHome.ts
112722
- import { execFileSync as execFileSync3 } from "node:child_process";
112723
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
112724
- import { homedir as homedir2, userInfo } from "node:os";
112725
- import { join as join5 } from "node:path";
112726
-
112727
- // utils/codexOAuth.ts
112728
- function decodeJwtExpMs(token) {
112729
- const parts = token.split(".");
112730
- if (parts.length !== 3) return null;
112731
- let payload;
112732
- try {
112733
- payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
112734
- } catch {
112735
- return null;
112736
- }
112737
- if (typeof payload.exp !== "number" || !Number.isFinite(payload.exp)) return null;
112738
- return payload.exp * 1e3;
112739
- }
112740
- function parseCodexAuthBody(raw2) {
112741
- let parsed2;
112742
- try {
112743
- parsed2 = JSON.parse(raw2);
112744
- } catch {
112745
- return null;
112746
- }
112747
- if (!parsed2 || typeof parsed2 !== "object") return null;
112748
- const v = parsed2;
112749
- if (v.auth_mode !== "chatgpt") return null;
112750
- const tokens = v.tokens;
112751
- if (!tokens || typeof tokens !== "object") return null;
112752
- const t = tokens;
112753
- if (typeof t.access_token !== "string" || t.access_token.length === 0) return null;
112754
- if (typeof t.refresh_token !== "string" || t.refresh_token.length === 0) return null;
112755
- return {
112756
- auth_mode: "chatgpt",
112757
- ...typeof v.refresh_rejected_at === "string" ? { refresh_rejected_at: v.refresh_rejected_at } : {},
112758
- tokens: {
112759
- access_token: t.access_token,
112760
- refresh_token: t.refresh_token,
112761
- ...typeof t.id_token === "string" ? { id_token: t.id_token } : {},
112762
- ...typeof t.account_id === "string" ? { account_id: t.account_id } : {}
112763
- },
112764
- ...typeof v.last_refresh === "string" ? { last_refresh: v.last_refresh } : {}
112765
- };
112766
- }
112767
-
112768
- // utils/codexHome.ts
112769
- var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
112770
- var PULLFROG_DATA_DIR = "/var/lib/pullfrog";
112771
- function installCodexAuth() {
112772
- const raw2 = process.env[CODEX_AUTH_ENV];
112773
- if (!raw2) return null;
112774
- const body = parseCodexAuthBody(raw2);
112775
- if (!body) {
112776
- log.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
112777
- return null;
112778
- }
112779
- const expiresMs = decodeJwtExpMs(body.tokens.access_token) ?? 0;
112780
- const xdgDataHome = resolveDataHome();
112781
- const opencodeDir = join5(xdgDataHome, "opencode");
112782
- const authPath = join5(opencodeDir, "auth.json");
112783
- const opencodeAuth = {
112784
- openai: {
112785
- type: "oauth",
112786
- refresh: body.tokens.refresh_token,
112787
- access: body.tokens.access_token,
112788
- expires: expiresMs,
112789
- ...body.tokens.account_id ? { accountId: body.tokens.account_id } : {}
112790
- }
112791
- };
112792
- mkdirSync5(opencodeDir, { recursive: true });
112793
- writeFileSync4(authPath, `${JSON.stringify(opencodeAuth, null, 2)}
112794
- `, { mode: 384 });
112795
- process.env.XDG_DATA_HOME = xdgDataHome;
112796
- log.info(`\xBB installed Codex auth at ${authPath}`);
112797
- return {
112798
- authPath,
112799
- xdgDataHome,
112800
- originalRefresh: body.tokens.refresh_token
112801
- };
112802
- }
112803
- function resolveDataHome() {
112804
- if (process.env.CI !== "true") return join5(homedir2(), ".local", "share");
112805
- bootstrapPullfrogDataDir();
112806
- return PULLFROG_DATA_DIR;
112807
- }
112808
- function bootstrapPullfrogDataDir() {
112809
- const user = userInfo().username;
112810
- let primaryGroup;
112811
- try {
112812
- primaryGroup = execFileSync3("id", ["-gn", user], { stdio: "pipe", encoding: "utf-8" }).trim();
112813
- } catch {
112814
- primaryGroup = user;
112815
- }
112816
- try {
112817
- execFileSync3("sudo", ["-n", "mkdir", "-p", PULLFROG_DATA_DIR], { stdio: "pipe" });
112818
- execFileSync3("sudo", ["-n", "chown", `${user}:${primaryGroup}`, PULLFROG_DATA_DIR], {
112819
- stdio: "pipe"
112820
- });
112821
- execFileSync3("sudo", ["-n", "chmod", "700", PULLFROG_DATA_DIR], { stdio: "pipe" });
112822
- } catch (err) {
112823
- throw new Error(
112824
- `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.`
112825
- );
112826
- }
112827
- }
112828
-
112829
113523
  // utils/worktree.ts
112830
113524
  async function dirtyTrackedPaths(params = {}) {
112831
113525
  const result = await spawn({
@@ -113093,6 +113787,30 @@ function openAICompatibleProvider(model) {
113093
113787
  }
113094
113788
  };
113095
113789
  }
113790
+ function azureProvider(model) {
113791
+ const prefix = `${AZURE_PROVIDER}/`;
113792
+ if (!model?.startsWith(prefix)) return {};
113793
+ const deployment = model.slice(prefix.length);
113794
+ return {
113795
+ [AZURE_PROVIDER]: {
113796
+ // always sent: opencode reads it as `Boolean(options?.useCompletionUrls)`,
113797
+ // so `false` and absent are the same, and the deep merge keeps the native
113798
+ // loader's `resourceName` either way.
113799
+ options: {
113800
+ useCompletionUrls: process.env[AZURE_USE_CHAT_COMPLETIONS_ENV]?.trim() === "true"
113801
+ },
113802
+ models: {
113803
+ [deployment]: {
113804
+ name: deployment,
113805
+ limit: {
113806
+ context: Number(process.env[AZURE_CONTEXT_ENV]),
113807
+ output: Number(process.env[AZURE_MAX_OUTPUT_ENV])
113808
+ }
113809
+ }
113810
+ }
113811
+ }
113812
+ };
113813
+ }
113096
113814
  var KIMI_ENFORCERLESS_PROVIDERS = ["siliconflow", "together"];
113097
113815
  function kimiOpenRouterProviderOverrides() {
113098
113816
  return Object.fromEntries(
@@ -113184,7 +113902,8 @@ function buildSecurityConfig(ctx, model) {
113184
113902
  // see opencodeShared.ts.
113185
113903
  provider: {
113186
113904
  openrouter: { models: kimiOpenRouterProviderOverrides() },
113187
- ...openAICompatibleProvider(model)
113905
+ ...openAICompatibleProvider(model),
113906
+ ...azureProvider(model)
113188
113907
  }
113189
113908
  };
113190
113909
  if (model) {
@@ -113352,7 +114071,7 @@ function isModelOutput(ctx, part) {
113352
114071
  }
113353
114072
  async function dispatchEvent(ctx, event) {
113354
114073
  if (event.type === "message.part.updated") {
113355
- ctx.lastEventAt = performance6.now();
114074
+ ctx.lastEventAt = performance7.now();
113356
114075
  if (isModelOutput(ctx, event.properties.part)) ctx.sawModelOutput = true;
113357
114076
  await onPartUpdated(ctx, event.properties.part);
113358
114077
  return;
@@ -113419,7 +114138,7 @@ async function onToolPart(ctx, part, label, isOrchestrator) {
113419
114138
  if (toolName === "task" && status === "running" && isOrchestrator && !ctx.taskDispatchByCallID.has(toolId)) {
113420
114139
  const input = part.state.input ?? {};
113421
114140
  const dispatched = ctx.labeler.recordTaskDispatch(input);
113422
- ctx.taskDispatchByCallID.set(toolId, { label: dispatched, startedAt: performance6.now() });
114141
+ ctx.taskDispatchByCallID.set(toolId, { label: dispatched, startedAt: performance7.now() });
113423
114142
  log.info(
113424
114143
  `\xBB dispatching subagent: ${dispatched}` + (input.subagent_type ? ` (subagent_type=${input.subagent_type})` : "")
113425
114144
  );
@@ -113449,7 +114168,7 @@ function processTerminalToolPart(ctx, part, label, isOrchestrator) {
113449
114168
  if (toolName === "task") {
113450
114169
  const dispatch = ctx.taskDispatchByCallID.get(toolId);
113451
114170
  if (dispatch) {
113452
- const dur = ((performance6.now() - dispatch.startedAt) / 1e3).toFixed(1);
114171
+ const dur = ((performance7.now() - dispatch.startedAt) / 1e3).toFixed(1);
113453
114172
  const outputStr = state.status === "completed" ? state.output : "";
113454
114173
  const preview = typeof outputStr === "string" && outputStr.length > 120 ? `${outputStr.slice(0, 120)}\u2026` : outputStr;
113455
114174
  log.info(
@@ -113492,7 +114211,7 @@ function withLabel(label, message) {
113492
114211
  return label === ORCHESTRATOR_LABEL ? message : formatWithLabel(label, message);
113493
114212
  }
113494
114213
  async function runPromptTurn(ctx, params) {
113495
- const start = performance6.now();
114214
+ const start = performance7.now();
113496
114215
  const turnStartMs = Date.now();
113497
114216
  ctx.currentTurn = newTurn();
113498
114217
  const turn = ctx.currentTurn;
@@ -113526,7 +114245,7 @@ async function runPromptTurn(ctx, params) {
113526
114245
  } catch (err) {
113527
114246
  networkError = err instanceof Error ? err.message : String(err);
113528
114247
  }
113529
- const durationMs = performance6.now() - start;
114248
+ const durationMs = performance7.now() - start;
113530
114249
  const aggregatedUsage = await aggregateTurnUsage(ctx, turnStartMs);
113531
114250
  const usage = aggregatedUsage ?? buildUsage(turn, assistant);
113532
114251
  const finalText = turn.finalText || extractTextFromParts(returnedParts) || "";
@@ -113680,7 +114399,7 @@ function startInnerActivityWatchdog(params) {
113680
114399
  let fired = false;
113681
114400
  const id = setInterval(() => {
113682
114401
  if (fired) return;
113683
- const idleMs = performance6.now() - params.ctx.lastEventAt;
114402
+ const idleMs = performance7.now() - params.ctx.lastEventAt;
113684
114403
  const budgetMs = params.ctx.sawModelOutput ? params.timeoutMs : AGENT_FIRST_EVENT_TIMEOUT_MS;
113685
114404
  if (idleMs <= budgetMs) return;
113686
114405
  fired = true;
@@ -113719,12 +114438,12 @@ var opencode = agent({
113719
114438
  const model = vertexModel ?? (isBedrockRoute ? `amazon-bedrock/${rawModel}` : rawModel);
113720
114439
  const homeEnv = {
113721
114440
  HOME: ctx.tmpdir,
113722
- XDG_CONFIG_HOME: join6(ctx.tmpdir, ".config")
114441
+ XDG_CONFIG_HOME: join7(ctx.tmpdir, ".config")
113723
114442
  };
113724
- const opencodePluginDir = join6(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
113725
- mkdirSync6(opencodePluginDir, { recursive: true });
113726
- writeFileSync5(
113727
- join6(opencodePluginDir, PULLFROG_OPENCODE_GATE_PLUGIN_FILENAME),
114443
+ const opencodePluginDir = join7(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
114444
+ mkdirSync7(opencodePluginDir, { recursive: true });
114445
+ writeFileSync6(
114446
+ join7(opencodePluginDir, PULLFROG_OPENCODE_GATE_PLUGIN_FILENAME),
113728
114447
  buildOpencodeSubagentGateSource(ctx.subagentDeniedTools)
113729
114448
  );
113730
114449
  const agentBrowserVersion = getDevDependencyVersion("agent-browser");
@@ -113755,12 +114474,13 @@ var opencode = agent({
113755
114474
  if (codexAuth) {
113756
114475
  env2.XDG_DATA_HOME = codexAuth.xdgDataHome;
113757
114476
  delete env2.OPENAI_API_KEY;
113758
- core2.saveState(
114477
+ core3.saveState(
113759
114478
  "codex_writeback",
113760
114479
  JSON.stringify({
113761
114480
  apiToken: ctx.apiToken,
113762
114481
  authPath: codexAuth.authPath,
113763
- originalRefresh: codexAuth.originalRefresh
114482
+ originalRefresh: codexAuth.originalRefresh,
114483
+ originalIdToken: codexAuth.originalIdToken
113764
114484
  })
113765
114485
  );
113766
114486
  }
@@ -113814,7 +114534,7 @@ var opencode = agent({
113814
114534
  onToolUse: ctx.onToolUse,
113815
114535
  currentTurn: null,
113816
114536
  eventCount: 0,
113817
- lastEventAt: performance6.now(),
114537
+ lastEventAt: performance7.now(),
113818
114538
  sawModelOutput: false,
113819
114539
  promptMessageID: void 0,
113820
114540
  taskDispatchByCallID: /* @__PURE__ */ new Map(),
@@ -113924,7 +114644,7 @@ async function runTurnGuarded(ctx, fn2) {
113924
114644
  }
113925
114645
 
113926
114646
  // agents/index.ts
113927
- var agents = { claude, opencode };
114647
+ var agents = { claude, codex, opencode };
113928
114648
 
113929
114649
  // node_modules/.pnpm/@ark+util@0.56.0/node_modules/@ark/util/out/arrays.js
113930
114650
  var liftArray = (data) => Array.isArray(data) ? data : [data];
@@ -134256,7 +134976,7 @@ var require_core$2 = /* @__PURE__ */ __commonJSMin(((exports) => {
134256
134976
  Object.defineProperty(exports, "__esModule", { value: true });
134257
134977
  const id_1 = require_id2();
134258
134978
  const ref_1 = require_ref2();
134259
- const core10 = [
134979
+ const core11 = [
134260
134980
  "$schema",
134261
134981
  "$id",
134262
134982
  "$defs",
@@ -134266,7 +134986,7 @@ var require_core$2 = /* @__PURE__ */ __commonJSMin(((exports) => {
134266
134986
  id_1.default,
134267
134987
  ref_1.default
134268
134988
  ];
134269
- exports.default = core10;
134989
+ exports.default = core11;
134270
134990
  }));
134271
134991
  var require_limitNumber2 = /* @__PURE__ */ __commonJSMin(((exports) => {
134272
134992
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -136404,7 +137124,7 @@ var require_json_schema_2019_09 = /* @__PURE__ */ __commonJSMin(((exports) => {
136404
137124
  const metaSchema = require_schema$1();
136405
137125
  const applicator = require_applicator$1();
136406
137126
  const content = require_content$1();
136407
- const core10 = require_core$1();
137127
+ const core11 = require_core$1();
136408
137128
  const format2 = require_format3();
136409
137129
  const metadata = require_meta_data$1();
136410
137130
  const validation = require_validation$1();
@@ -136414,7 +137134,7 @@ var require_json_schema_2019_09 = /* @__PURE__ */ __commonJSMin(((exports) => {
136414
137134
  metaSchema,
136415
137135
  applicator,
136416
137136
  content,
136417
- core10,
137137
+ core11,
136418
137138
  with$data(this, format2),
136419
137139
  metadata,
136420
137140
  with$data(this, validation)
@@ -136854,7 +137574,7 @@ var require_json_schema_2020_12 = /* @__PURE__ */ __commonJSMin(((exports) => {
136854
137574
  const applicator = require_applicator2();
136855
137575
  const unevaluated = require_unevaluated();
136856
137576
  const content = require_content();
136857
- const core10 = require_core4();
137577
+ const core11 = require_core4();
136858
137578
  const format2 = require_format_annotation();
136859
137579
  const metadata = require_meta_data();
136860
137580
  const validation = require_validation2();
@@ -136865,7 +137585,7 @@ var require_json_schema_2020_12 = /* @__PURE__ */ __commonJSMin(((exports) => {
136865
137585
  applicator,
136866
137586
  unevaluated,
136867
137587
  content,
136868
- core10,
137588
+ core11,
136869
137589
  with$data(this, format2),
136870
137590
  metadata,
136871
137591
  with$data(this, validation)
@@ -148307,7 +149027,7 @@ var require_core$22 = /* @__PURE__ */ __commonJSMin2(((exports) => {
148307
149027
  Object.defineProperty(exports, "__esModule", { value: true });
148308
149028
  const id_1 = require_id3();
148309
149029
  const ref_1 = require_ref3();
148310
- const core10 = [
149030
+ const core11 = [
148311
149031
  "$schema",
148312
149032
  "$id",
148313
149033
  "$defs",
@@ -148317,7 +149037,7 @@ var require_core$22 = /* @__PURE__ */ __commonJSMin2(((exports) => {
148317
149037
  id_1.default,
148318
149038
  ref_1.default
148319
149039
  ];
148320
- exports.default = core10;
149040
+ exports.default = core11;
148321
149041
  }));
148322
149042
  var require_limitNumber3 = /* @__PURE__ */ __commonJSMin2(((exports) => {
148323
149043
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -150455,7 +151175,7 @@ var require_json_schema_2019_092 = /* @__PURE__ */ __commonJSMin2(((exports) =>
150455
151175
  const metaSchema = require_schema$12();
150456
151176
  const applicator = require_applicator$12();
150457
151177
  const content = require_content$12();
150458
- const core10 = require_core$12();
151178
+ const core11 = require_core$12();
150459
151179
  const format2 = require_format4();
150460
151180
  const metadata = require_meta_data$12();
150461
151181
  const validation = require_validation$12();
@@ -150465,7 +151185,7 @@ var require_json_schema_2019_092 = /* @__PURE__ */ __commonJSMin2(((exports) =>
150465
151185
  metaSchema,
150466
151186
  applicator,
150467
151187
  content,
150468
- core10,
151188
+ core11,
150469
151189
  with$data(this, format2),
150470
151190
  metadata,
150471
151191
  with$data(this, validation)
@@ -150905,7 +151625,7 @@ var require_json_schema_2020_122 = /* @__PURE__ */ __commonJSMin2(((exports) =>
150905
151625
  const applicator = require_applicator3();
150906
151626
  const unevaluated = require_unevaluated2();
150907
151627
  const content = require_content2();
150908
- const core10 = require_core5();
151628
+ const core11 = require_core5();
150909
151629
  const format2 = require_format_annotation2();
150910
151630
  const metadata = require_meta_data2();
150911
151631
  const validation = require_validation3();
@@ -150916,7 +151636,7 @@ var require_json_schema_2020_122 = /* @__PURE__ */ __commonJSMin2(((exports) =>
150916
151636
  applicator,
150917
151637
  unevaluated,
150918
151638
  content,
150919
- core10,
151639
+ core11,
150920
151640
  with$data(this, format2),
150921
151641
  metadata,
150922
151642
  with$data(this, validation)
@@ -170877,123 +171597,8 @@ var pbkdf2Async = promisify(pbkdf2);
170877
171597
 
170878
171598
  // utils/browser.ts
170879
171599
  import { execFileSync as execFileSync4, spawnSync as spawnSync4 } from "node:child_process";
170880
- import { existsSync as existsSync4 } from "node:fs";
171600
+ import { existsSync as existsSync5 } from "node:fs";
170881
171601
  import { dirname as dirname2 } from "node:path";
170882
-
170883
- // utils/secrets.ts
170884
- var SENSITIVE_PATTERNS = [
170885
- /_KEY$/i,
170886
- /_SECRET$/i,
170887
- /_TOKEN$/i,
170888
- /_PASSWORD$/i,
170889
- /_CREDENTIAL$/i
170890
- ];
170891
- function isSensitiveEnvName(key) {
170892
- return SENSITIVE_PATTERNS.some((p) => p.test(key));
170893
- }
170894
- var SAFE_ENV_PREFIXES = ["GITHUB_", "RUNNER_", "JAVA_HOME_", "GOROOT_"];
170895
- var SAFE_ENV_NAMES = /* @__PURE__ */ new Set([
170896
- // system
170897
- "CI",
170898
- "HOME",
170899
- "LANG",
170900
- "LOGNAME",
170901
- "PATH",
170902
- "SHELL",
170903
- "SHLVL",
170904
- "TERM",
170905
- "TMPDIR",
170906
- "TZ",
170907
- "USER",
170908
- "XDG_CONFIG_HOME",
170909
- "XDG_RUNTIME_DIR",
170910
- "DEBIAN_FRONTEND",
170911
- // runner image toolchain
170912
- "ACCEPT_EULA",
170913
- "AGENT_TOOLSDIRECTORY",
170914
- "ANDROID_HOME",
170915
- "ANDROID_NDK",
170916
- "ANDROID_NDK_HOME",
170917
- "ANDROID_NDK_LATEST_HOME",
170918
- "ANDROID_NDK_ROOT",
170919
- "ANDROID_SDK_ROOT",
170920
- "ANT_HOME",
170921
- "AZURE_EXTENSION_DIR",
170922
- "BOOTSTRAP_HASKELL_NONINTERACTIVE",
170923
- "CHROME_BIN",
170924
- "CHROMEWEBDRIVER",
170925
- "CONDA",
170926
- "DOTNET_MULTILEVEL_LOOKUP",
170927
- "DOTNET_NOLOGO",
170928
- "DOTNET_SKIP_FIRST_TIME_EXPERIENCE",
170929
- "EDGEWEBDRIVER",
170930
- "GECKOWEBDRIVER",
170931
- "GHCUP_INSTALL_BASE_PREFIX",
170932
- "GRADLE_HOME",
170933
- "JAVA_HOME",
170934
- "HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS",
170935
- "HOMEBREW_NO_AUTO_UPDATE",
170936
- "ImageOS",
170937
- "ImageVersion",
170938
- "NVM_DIR",
170939
- "PIPX_BIN_DIR",
170940
- "PIPX_HOME",
170941
- "PSModulePath",
170942
- "SELENIUM_JAR_PATH",
170943
- "SGX_AESM_ADDR",
170944
- "SWIFT_PATH",
170945
- "VCPKG_INSTALLATION_ROOT"
170946
- ]);
170947
- var _userAllowlist = null;
170948
- function setEnvAllowlist(raw2) {
170949
- const names = raw2.split("\n").map((line) => line.trim()).filter(Boolean);
170950
- _userAllowlist = new Set(names);
170951
- }
170952
- function isSafeEnvVar(key) {
170953
- if (SAFE_ENV_NAMES.has(key)) return true;
170954
- return SAFE_ENV_PREFIXES.some((p) => key.startsWith(p));
170955
- }
170956
- var WORKFLOW_COMMAND_ENV_NAMES = [
170957
- "GITHUB_ENV",
170958
- "GITHUB_PATH",
170959
- "GITHUB_OUTPUT",
170960
- "GITHUB_STATE",
170961
- "GITHUB_STEP_SUMMARY",
170962
- // dropping the five paths above but keeping RUNNER_TEMP would be theatre:
170963
- // the file-command files live in `$RUNNER_TEMP/_runner_file_commands/`, so the
170964
- // directory is one `ls` away. nothing in the install path reads it (only our
170965
- // own leak-surface wipe in `setup.ts`, which runs in the parent), and package
170966
- // managers use TMPDIR.
170967
- "RUNNER_TEMP"
170968
- ];
170969
- function filterEnvForUntrustedCode() {
170970
- const env2 = filterEnv();
170971
- for (const name of WORKFLOW_COMMAND_ENV_NAMES) delete env2[name];
170972
- return env2;
170973
- }
170974
- function filterEnv() {
170975
- const filtered = {};
170976
- for (const [key, value2] of Object.entries(process.env)) {
170977
- if (value2 === void 0) continue;
170978
- const userAllowed = _userAllowlist?.has(key) ?? false;
170979
- if (isSensitiveEnvName(key) && !userAllowed) continue;
170980
- if (isSafeEnvVar(key) || userAllowed) {
170981
- filtered[key] = value2;
170982
- }
170983
- }
170984
- return filtered;
170985
- }
170986
- function resolveEnv(mode) {
170987
- if (mode === "inherit") {
170988
- return process.env;
170989
- }
170990
- if (mode === "restricted" || mode === void 0) {
170991
- return filterEnv();
170992
- }
170993
- return { ...filterEnv(), ...mode };
170994
- }
170995
-
170996
- // utils/browser.ts
170997
171602
  var CHROME_PATHS = ["/usr/bin/google-chrome-stable"];
170998
171603
  var systemChromePath;
170999
171604
  function findSystemChromePath() {
@@ -171001,7 +171606,7 @@ function findSystemChromePath() {
171001
171606
  return systemChromePath || void 0;
171002
171607
  }
171003
171608
  for (const p of CHROME_PATHS) {
171004
- if (existsSync4(p)) {
171609
+ if (existsSync5(p)) {
171005
171610
  systemChromePath = p;
171006
171611
  log.info(`found system chrome: ${p}`);
171007
171612
  return p;
@@ -171085,8 +171690,8 @@ function closeBrowserDaemon(toolState) {
171085
171690
 
171086
171691
  // mcp/checkout.ts
171087
171692
  import { createHash as createHash2 } from "node:crypto";
171088
- import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync8 } from "node:fs";
171089
- import { join as join13 } from "node:path";
171693
+ import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync9 } from "node:fs";
171694
+ import { join as join14 } from "node:path";
171090
171695
 
171091
171696
  // node_modules/.pnpm/@ark+schema@0.56.0/node_modules/@ark/schema/out/shared/errors.js
171092
171697
  var ArkError = class _ArkError extends CastableBase {
@@ -178558,8 +179163,8 @@ function ensureRepoState(toolState, init) {
178558
179163
 
178559
179164
  // utils/changeImpact.ts
178560
179165
  import { spawn as spawn2 } from "node:child_process";
178561
- import { writeFileSync as writeFileSync6 } from "node:fs";
178562
- import { join as join7 } from "node:path";
179166
+ import { writeFileSync as writeFileSync7 } from "node:fs";
179167
+ import { join as join8 } from "node:path";
178563
179168
  var MAX_CANDIDATES = 24;
178564
179169
  var MAX_ATOM_LENGTH = 128;
178565
179170
  var MAX_ATOMS = 12;
@@ -178906,8 +179511,8 @@ async function createChangeImpactArtifact(ctx, params) {
178906
179511
  headSha: params.headSha,
178907
179512
  lookupError: lookup2.error
178908
179513
  });
178909
- const path4 = join7(ctx.tmpdir, `pr-${params.pullNumber}-${params.headSha.slice(0, 7)}-impact.md`);
178910
- writeFileSync6(path4, artifact.content);
179514
+ const path4 = join8(ctx.tmpdir, `pr-${params.pullNumber}-${params.headSha.slice(0, 7)}-impact.md`);
179515
+ writeFileSync7(path4, artifact.content);
178911
179516
  return {
178912
179517
  path: path4,
178913
179518
  candidateCount: candidates.selected.length,
@@ -179159,8 +179764,8 @@ function readNumber(params) {
179159
179764
  // utils/gitAuth.ts
179160
179765
  import { execSync } from "node:child_process";
179161
179766
  import { createHash } from "node:crypto";
179162
- import { readFileSync as readFileSync2, realpathSync, unlinkSync } from "node:fs";
179163
- import { join as join8 } from "node:path";
179767
+ import { readFileSync as readFileSync3, realpathSync, unlinkSync } from "node:fs";
179768
+ import { join as join9 } from "node:path";
179164
179769
 
179165
179770
  // utils/shell.ts
179166
179771
  import { spawnSync as spawnSync5 } from "node:child_process";
@@ -179221,7 +179826,7 @@ var TRANSIENT_AUTH_PATTERNS = [
179221
179826
  ];
179222
179827
  var gitBinary;
179223
179828
  function hashFile(path4) {
179224
- return createHash("sha256").update(readFileSync2(path4)).digest("hex");
179829
+ return createHash("sha256").update(readFileSync3(path4)).digest("hex");
179225
179830
  }
179226
179831
  function resolveGit() {
179227
179832
  const whichPath = execSync("which git", { encoding: "utf-8" }).trim();
@@ -179250,7 +179855,7 @@ function resolveHooksDir(cwd, gitPath) {
179250
179855
  cwd,
179251
179856
  log: false
179252
179857
  }).trim();
179253
- const hooksDir = join8(commonDir, "hooks");
179858
+ const hooksDir = join9(commonDir, "hooks");
179254
179859
  hooksDirCache.set(cwd, hooksDir);
179255
179860
  return hooksDir;
179256
179861
  }
@@ -179557,7 +180162,7 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
179557
180162
  }
179558
180163
 
179559
180164
  // yes/index.ts
179560
- import { performance as performance7 } from "node:perf_hooks";
180165
+ import { performance as performance8 } from "node:perf_hooks";
179561
180166
  import { setTimeout as sleep2 } from "node:timers/promises";
179562
180167
 
179563
180168
  // node_modules/.pnpm/lru-cache@11.5.2/node_modules/lru-cache/dist/esm/node/index.min.js
@@ -180272,14 +180877,14 @@ function _op(fn2, options) {
180272
180877
  `failed to create cache key${namePrefix ? ` for ${options.name}` : ""}: ${errorMessage}`
180273
180878
  );
180274
180879
  }
180275
- const startTime = performance7.now();
180880
+ const startTime = performance8.now();
180276
180881
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
180277
180882
  log2.debug(`${namePrefix}[${timestamp}] cache lookup started`, { key });
180278
180883
  if (shouldCache && lruCache) {
180279
180884
  const cached4 = lruCache.get(key);
180280
180885
  if (cached4 !== void 0) {
180281
180886
  if (keyMap?.get(key) === void 0) keyMap?.set(key, input);
180282
- const runtime = performance7.now() - startTime;
180887
+ const runtime = performance8.now() - startTime;
180283
180888
  log2.debug(`${namePrefix}[${timestamp}] cache hit (from lru)`, {
180284
180889
  key,
180285
180890
  runtime: `${Math.round(runtime)}ms`
@@ -180298,12 +180903,12 @@ function _op(fn2, options) {
180298
180903
  defaultCacheMiss(key);
180299
180904
  const retries = options.retries ?? [];
180300
180905
  let lastError;
180301
- const execStartTime = performance7.now();
180906
+ const execStartTime = performance8.now();
180302
180907
  const promise2 = (async () => {
180303
180908
  for (let attempt = 0; attempt <= retries.length; attempt++) {
180304
180909
  try {
180305
180910
  const result = acceptsCtx ? await fn2(input, ctx) : await fn2(input);
180306
- const execRuntime2 = performance7.now() - execStartTime;
180911
+ const execRuntime2 = performance8.now() - execStartTime;
180307
180912
  const skip = options.skipCache ? options.skipCache(result) : false;
180308
180913
  if (shouldCache && lruCache && result !== null && result !== void 0 && !skip) {
180309
180914
  lruCache.set(key, result);
@@ -180356,7 +180961,7 @@ function _op(fn2, options) {
180356
180961
  }
180357
180962
  }
180358
180963
  }
180359
- const execRuntime = performance7.now() - execStartTime;
180964
+ const execRuntime = performance8.now() - execStartTime;
180360
180965
  log2.debug(`${namePrefix}[${timestamp}] function failed after all retries`, {
180361
180966
  key,
180362
180967
  runtime: `${Math.round(execRuntime)}ms`,
@@ -180421,13 +181026,13 @@ function _op(fn2, options) {
180421
181026
 
180422
181027
  // mcp/git.ts
180423
181028
  import { randomUUID as randomUUID3 } from "node:crypto";
180424
- import { writeFileSync as writeFileSync7 } from "node:fs";
180425
- import { join as join11 } from "node:path";
181029
+ import { writeFileSync as writeFileSync8 } from "node:fs";
181030
+ import { join as join12 } from "node:path";
180426
181031
 
180427
181032
  // utils/apiCommit.ts
180428
181033
  import { execFileSync as execFileSync5 } from "node:child_process";
180429
181034
  import { lstat, readlink } from "node:fs/promises";
180430
- import { join as join9 } from "node:path";
181035
+ import { join as join10 } from "node:path";
180431
181036
  var GITHUB_API = "https://api.github.com";
180432
181037
  var MAX_BLOB_BYTES = 30 * 1024 * 1024;
180433
181038
  var BLOB_UPLOAD_CONCURRENCY = 8;
@@ -180492,7 +181097,7 @@ async function assertApiCommittable(files) {
180492
181097
  const root = getRepoRoot();
180493
181098
  const attrs = $(
180494
181099
  "git",
180495
- ["check-attr", "filter", "-z", "--", ...present.map((p) => join9(root, p))],
181100
+ ["check-attr", "filter", "-z", "--", ...present.map((p) => join10(root, p))],
180496
181101
  { log: false }
180497
181102
  );
180498
181103
  const parts = attrs.split("\0");
@@ -180504,7 +181109,7 @@ async function assertApiCommittable(files) {
180504
181109
  }
180505
181110
  }
180506
181111
  for (const path4 of present) {
180507
- const stat = await lstat(join9(root, path4));
181112
+ const stat = await lstat(join10(root, path4));
180508
181113
  if (stat.isDirectory()) {
180509
181114
  throw new Error(
180510
181115
  `'${path4}' is a directory (nested repository or submodule?) \u2014 signed commits only support files and symlinks.`
@@ -180513,7 +181118,7 @@ async function assertApiCommittable(files) {
180513
181118
  }
180514
181119
  }
180515
181120
  async function createBlobEntry(params) {
180516
- const absPath = join9(params.repoRoot, params.path);
181121
+ const absPath = join10(params.repoRoot, params.path);
180517
181122
  const stat = await lstat(absPath);
180518
181123
  if (stat.size > MAX_BLOB_BYTES) {
180519
181124
  throw new Error(
@@ -180690,10 +181295,10 @@ this usually means your local branch has commits that were never pushed \u2014 s
180690
181295
  }
180691
181296
 
180692
181297
  // utils/github.ts
180693
- var core3 = __toESM(require_core(), 1);
181298
+ var core4 = __toESM(require_core(), 1);
180694
181299
  import { createSign } from "node:crypto";
180695
181300
  import { rename, writeFile } from "node:fs/promises";
180696
- import { dirname as dirname3, join as join10 } from "node:path";
181301
+ import { dirname as dirname3, join as join11 } from "node:path";
180697
181302
 
180698
181303
  // node_modules/.pnpm/@octokit+plugin-throttling@11.0.5_@octokit+core@7.0.7/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
180699
181304
  var import_light = __toESM(require_light(), 1);
@@ -184891,7 +185496,7 @@ async function fetchIdTokenFromStash(creds) {
184891
185496
  throw new Error("ID token response has no value field");
184892
185497
  }
184893
185498
  if (isGitHubActions) {
184894
- core3.setSecret(body.value);
185499
+ core4.setSecret(body.value);
184895
185500
  }
184896
185501
  return body.value;
184897
185502
  }
@@ -184904,7 +185509,7 @@ function translateIdTokenError(error52) {
184904
185509
  var mintIdToken = op(
184905
185510
  async () => {
184906
185511
  try {
184907
- return await core3.getIDToken(OIDC_AUDIENCE);
185512
+ return await core4.getIDToken(OIDC_AUDIENCE);
184908
185513
  } catch (error52) {
184909
185514
  throw translateIdTokenError(error52);
184910
185515
  }
@@ -185113,7 +185718,7 @@ function getGitHubUsageSummary() {
185113
185718
  }
185114
185719
  async function writeGitHubUsageSummaryToFile(path4) {
185115
185720
  const summary2 = getGitHubUsageSummary();
185116
- const tmpPath = join10(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
185721
+ const tmpPath = join11(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
185117
185722
  await writeFile(tmpPath, JSON.stringify(summary2));
185118
185723
  await rename(tmpPath, path4);
185119
185724
  }
@@ -186265,8 +186870,8 @@ function countAhead(head, base, cwd) {
186265
186870
  function spillGitOutput(params) {
186266
186871
  const tempDir = process.env.PULLFROG_TEMP_DIR;
186267
186872
  if (!tempDir) throw new Error("PULLFROG_TEMP_DIR not set");
186268
- const outputPath = join11(tempDir, `git-${params.command}-${randomUUID3().slice(0, 8)}.txt`);
186269
- writeFileSync7(outputPath, params.output);
186873
+ const outputPath = join12(tempDir, `git-${params.command}-${randomUUID3().slice(0, 8)}.txt`);
186874
+ writeFileSync8(outputPath, params.output);
186270
186875
  const previewByLines = params.output.split("\n").slice(0, OVERFLOW_PREVIEW_LINES).join("\n");
186271
186876
  const preview = previewByLines.length <= OVERFLOW_PREVIEW_MAX_CHARS ? previewByLines : `${previewByLines.slice(0, OVERFLOW_PREVIEW_MAX_CHARS)}\u2026`;
186272
186877
  log.info(
@@ -187780,9 +188385,9 @@ async function reportReviewNodeId(ctx, params) {
187780
188385
  import { execFileSync as execFileSync6, execSync as execSync2 } from "node:child_process";
187781
188386
  import { mkdtempSync, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
187782
188387
  import { tmpdir as tmpdir2 } from "node:os";
187783
- import { join as join12 } from "node:path";
188388
+ import { join as join13 } from "node:path";
187784
188389
  function createTempDirectory() {
187785
- const sharedTempDir = mkdtempSync(join12(tmpdir2(), "pullfrog-"));
188390
+ const sharedTempDir = mkdtempSync(join13(tmpdir2(), "pullfrog-"));
187786
188391
  process.env.PULLFROG_TEMP_DIR = sharedTempDir;
187787
188392
  log.info(`\xBB created temp dir at ${sharedTempDir}`);
187788
188393
  return sharedTempDir;
@@ -187827,13 +188432,13 @@ function wipeRunnerLeakSurface() {
187827
188432
  return [];
187828
188433
  }
187829
188434
  };
187830
- const fileCommandsDir = join12(runnerTemp, "_runner_file_commands");
188435
+ const fileCommandsDir = join13(runnerTemp, "_runner_file_commands");
187831
188436
  for (const entry of listDir(fileCommandsDir)) {
187832
- tryUnlink(join12(fileCommandsDir, entry));
188437
+ tryUnlink(join13(fileCommandsDir, entry));
187833
188438
  }
187834
188439
  for (const entry of listDir(runnerTemp)) {
187835
188440
  if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
187836
- tryUnlink(join12(runnerTemp, entry));
188441
+ tryUnlink(join13(runnerTemp, entry));
187837
188442
  }
187838
188443
  }
187839
188444
  if (wiped.length > 0) {
@@ -188383,11 +188988,11 @@ function CheckoutPrTool(ctx) {
188383
188988
  headSha: checkoutSha
188384
188989
  });
188385
188990
  if (incremental.status === "ok") {
188386
- incrementalDiffPath = join13(
188991
+ incrementalDiffPath = join14(
188387
188992
  tempDir,
188388
188993
  `pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
188389
188994
  );
188390
- writeFileSync8(incrementalDiffPath, incremental.diff);
188995
+ writeFileSync9(incrementalDiffPath, incremental.diff);
188391
188996
  log.info(
188392
188997
  `\xBB incremental diff computed (${incremental.diff.length} bytes) \u2192 ${incrementalDiffPath}`
188393
188998
  );
@@ -188400,8 +189005,8 @@ function CheckoutPrTool(ctx) {
188400
189005
  const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
188401
189006
  log.debug(`formatted diff preview (first 100 lines):
188402
189007
  ${diffPreview}`);
188403
- const diffPath = join13(tempDir, `pr-${pull_number}-${headShort}.diff`);
188404
- writeFileSync8(diffPath, formatResult.content);
189008
+ const diffPath = join14(tempDir, `pr-${pull_number}-${headShort}.diff`);
189009
+ writeFileSync9(diffPath, formatResult.content);
188405
189010
  log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
188406
189011
  primary.diffCoverage = createDiffCoverageState({
188407
189012
  diffPath,
@@ -188558,8 +189163,8 @@ ${dirty}`
188558
189163
  }
188559
189164
 
188560
189165
  // mcp/checkSuite.ts
188561
- import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync9 } from "node:fs";
188562
- import { join as join14 } from "node:path";
189166
+ import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync10 } from "node:fs";
189167
+ import { join as join15 } from "node:path";
188563
189168
  var GetCheckSuiteLogs = type({
188564
189169
  check_suite_id: type.number.describe("the id from check_suite.id")
188565
189170
  });
@@ -188655,8 +189260,8 @@ function GetCheckSuiteLogsTool(ctx) {
188655
189260
  if (!tempDir) {
188656
189261
  throw new Error("PULLFROG_TEMP_DIR not set");
188657
189262
  }
188658
- const logsDir = join14(tempDir, "ci-logs");
188659
- mkdirSync7(logsDir, { recursive: true });
189263
+ const logsDir = join15(tempDir, "ci-logs");
189264
+ mkdirSync8(logsDir, { recursive: true });
188660
189265
  const jobResults = [];
188661
189266
  for (const run of failedRuns) {
188662
189267
  const jobs = await ctx.octokit.paginate(ctx.octokit.rest.actions.listJobsForWorkflowRun, {
@@ -188682,8 +189287,8 @@ function GetCheckSuiteLogsTool(ctx) {
188682
189287
  );
188683
189288
  }
188684
189289
  const logsText = await logsResult.text();
188685
- const logPath = join14(logsDir, `job-${job.id}.log`);
188686
- writeFileSync9(logPath, logsText);
189290
+ const logPath = join15(logsDir, `job-${job.id}.log`);
189291
+ writeFileSync10(logPath, logsText);
188687
189292
  const analysis = analyzeLog(logsText, 80);
188688
189293
  const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
188689
189294
  jobResults.push({
@@ -188731,8 +189336,8 @@ function GetCheckSuiteLogsTool(ctx) {
188731
189336
  }
188732
189337
 
188733
189338
  // mcp/commitInfo.ts
188734
- import { writeFileSync as writeFileSync10 } from "node:fs";
188735
- import { join as join15 } from "node:path";
189339
+ import { writeFileSync as writeFileSync11 } from "node:fs";
189340
+ import { join as join16 } from "node:path";
188736
189341
  var CommitInfo = type({
188737
189342
  sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
188738
189343
  });
@@ -188756,8 +189361,8 @@ function CommitInfoTool(ctx) {
188756
189361
  "PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
188757
189362
  );
188758
189363
  }
188759
- const diffFile = join15(tempDir, `commit-${sha.slice(0, 7)}.diff`);
188760
- writeFileSync10(diffFile, formatResult.content);
189364
+ const diffFile = join16(tempDir, `commit-${sha.slice(0, 7)}.diff`);
189365
+ writeFileSync11(diffFile, formatResult.content);
188761
189366
  log.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
188762
189367
  return {
188763
189368
  sha: data.sha,
@@ -188890,17 +189495,17 @@ function ReopenCurrentTool(ctx) {
188890
189495
  }
188891
189496
 
188892
189497
  // prep/index.ts
188893
- import { performance as performance8 } from "node:perf_hooks";
189498
+ import { performance as performance9 } from "node:perf_hooks";
188894
189499
 
188895
189500
  // prep/installNodeDependencies.ts
188896
- import { existsSync as existsSync6 } from "node:fs";
188897
- import { join as join17 } from "node:path";
189501
+ import { existsSync as existsSync7 } from "node:fs";
189502
+ import { join as join18 } from "node:path";
188898
189503
 
188899
189504
  // utils/packageManager.ts
188900
189505
  var import_semver2 = __toESM(require_semver2(), 1);
188901
- import { existsSync as existsSync5 } from "node:fs";
189506
+ import { existsSync as existsSync6 } from "node:fs";
188902
189507
  import { mkdir, readFile as readFile2 } from "node:fs/promises";
188903
- import { delimiter, join as join16 } from "node:path";
189508
+ import { delimiter, join as join17 } from "node:path";
188904
189509
  var SUPPORTED_NAMES = ["npm", "pnpm", "yarn", "bun"];
188905
189510
  var COREPACK_MANAGED = ["pnpm", "yarn"];
188906
189511
  function isSupported(name) {
@@ -188938,8 +189543,8 @@ function parseDevEnginesField(field) {
188938
189543
  };
188939
189544
  }
188940
189545
  async function resolvePackageManagerSpec(cwd) {
188941
- const pkgPath = join16(cwd, "package.json");
188942
- if (!existsSync5(pkgPath)) return null;
189546
+ const pkgPath = join17(cwd, "package.json");
189547
+ if (!existsSync6(pkgPath)) return null;
188943
189548
  let pkg;
188944
189549
  try {
188945
189550
  pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
@@ -188997,7 +189602,7 @@ async function currentVersion(name) {
188997
189602
  return result.stdout.trim();
188998
189603
  }
188999
189604
  function packageManagerBinDir(tmpdir3) {
189000
- return join16(tmpdir3, "pm-bin");
189605
+ return join17(tmpdir3, "pm-bin");
189001
189606
  }
189002
189607
  async function ensurePackageManager(params) {
189003
189608
  const spec = params.spec;
@@ -189060,7 +189665,7 @@ async function installFallback(name, installSpec) {
189060
189665
  });
189061
189666
  if (result.exitCode !== 0) return result.stderr || `failed to install ${name}`;
189062
189667
  if (name === "deno") {
189063
- const denoPath = join16(process.env.HOME || "", ".deno", "bin");
189668
+ const denoPath = join17(process.env.HOME || "", ".deno", "bin");
189064
189669
  process.env.PATH = `${denoPath}:${process.env.PATH}`;
189065
189670
  }
189066
189671
  log.info(`\xBB installed ${name}`);
@@ -189093,8 +189698,8 @@ async function isCommandAvailable2(command) {
189093
189698
  var installNodeDependencies = {
189094
189699
  name: "installNodeDependencies",
189095
189700
  shouldRun: () => {
189096
- const packageJsonPath = join17(process.cwd(), "package.json");
189097
- return existsSync6(packageJsonPath);
189701
+ const packageJsonPath = join18(process.cwd(), "package.json");
189702
+ return existsSync7(packageJsonPath);
189098
189703
  },
189099
189704
  run: async (options) => {
189100
189705
  const declared = await resolvePackageManagerSpec(process.cwd());
@@ -189188,14 +189793,14 @@ ${errorMessage}`]
189188
189793
  };
189189
189794
 
189190
189795
  // prep/installPythonDependencies.ts
189191
- import { existsSync as existsSync7, readFileSync as readFileSync3 } from "node:fs";
189192
- import { join as join18 } from "node:path";
189796
+ import { existsSync as existsSync8, readFileSync as readFileSync4 } from "node:fs";
189797
+ import { join as join19 } from "node:path";
189193
189798
  function declaresBuildSystem(path4) {
189194
- return /^\s*\[\s*build-system\s*\]/m.test(readFileSync3(path4, "utf8"));
189799
+ return /^\s*\[\s*build-system\s*\]/m.test(readFileSync4(path4, "utf8"));
189195
189800
  }
189196
189801
  function configApplies(config3, cwd) {
189197
- const path4 = join18(cwd, config3.file);
189198
- return existsSync7(path4) && (config3.applies?.(path4) ?? true);
189802
+ const path4 = join19(cwd, config3.file);
189803
+ return existsSync8(path4) && (config3.applies?.(path4) ?? true);
189199
189804
  }
189200
189805
  var PYTHON_CONFIGS = [
189201
189806
  {
@@ -189349,7 +189954,7 @@ var installPythonDependencies = {
189349
189954
  var prepSteps = [installNodeDependencies, installPythonDependencies];
189350
189955
  async function runPrepPhase(options) {
189351
189956
  log.debug("\xBB starting prep phase...");
189352
- const startTime = performance8.now();
189957
+ const startTime = performance9.now();
189353
189958
  const results = [];
189354
189959
  const preDirty = await dirtyTrackedPaths();
189355
189960
  try {
@@ -189371,7 +189976,7 @@ async function runPrepPhase(options) {
189371
189976
  } finally {
189372
189977
  await restoreDirtiedSince({ before: preDirty, actor: "prep" });
189373
189978
  }
189374
- const totalDurationMs = performance8.now() - startTime;
189979
+ const totalDurationMs = performance9.now() - startTime;
189375
189980
  log.debug(`\xBB prep phase completed (${Math.round(totalDurationMs)}ms)`);
189376
189981
  return results;
189377
189982
  }
@@ -189516,15 +190121,15 @@ function AwaitDependencyInstallationTool(ctx) {
189516
190121
  }
189517
190122
 
189518
190123
  // mcp/gh.ts
189519
- import { mkdirSync as mkdirSync8 } from "node:fs";
189520
- import { join as join20 } from "node:path";
190124
+ import { mkdirSync as mkdirSync9 } from "node:fs";
190125
+ import { join as join21 } from "node:path";
189521
190126
 
189522
190127
  // mcp/shell.ts
189523
190128
  import { spawn as spawn3, spawnSync as spawnSync6 } from "node:child_process";
189524
190129
  import { randomUUID as randomUUID4 } from "node:crypto";
189525
- import { closeSync, openSync, writeFileSync as writeFileSync11 } from "node:fs";
190130
+ import { closeSync, openSync, writeFileSync as writeFileSync12 } from "node:fs";
189526
190131
  import { userInfo as userInfo2 } from "node:os";
189527
- import { join as join19 } from "node:path";
190132
+ import { join as join20 } from "node:path";
189528
190133
  import { setTimeout as sleep3 } from "node:timers/promises";
189529
190134
  var ShellParams = type({
189530
190135
  command: "string",
@@ -189787,8 +190392,8 @@ ${spawnError}` : spawnError;
189787
190392
  }
189788
190393
  function capOutput(output) {
189789
190394
  if (output.length <= MAX_OUTPUT_CHARS) return output;
189790
- const fullPath = join19(getTempDir(), `shell-${randomUUID4().slice(0, 8)}.log`);
189791
- writeFileSync11(fullPath, output);
190395
+ const fullPath = join20(getTempDir(), `shell-${randomUUID4().slice(0, 8)}.log`);
190396
+ writeFileSync12(fullPath, output);
189792
190397
  const elided = output.length - MAX_OUTPUT_CHARS;
189793
190398
  return `... [${elided} chars truncated; full output saved to ${fullPath}] ...
189794
190399
  ${output.slice(-MAX_OUTPUT_CHARS)}`;
@@ -189842,8 +190447,8 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
189842
190447
  if (params.background) {
189843
190448
  const tempDir = getTempDir();
189844
190449
  const handle = `bg-${randomUUID4().slice(0, 8)}`;
189845
- const outputPath = join19(tempDir, `${handle}.log`);
189846
- const pidPath = join19(tempDir, `${handle}.pid`);
190450
+ const outputPath = join20(tempDir, `${handle}.log`);
190451
+ const pidPath = join20(tempDir, `${handle}.pid`);
189847
190452
  const logFd = openSync(outputPath, "a");
189848
190453
  let proc;
189849
190454
  try {
@@ -189860,7 +190465,7 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
189860
190465
  throw new Error("failed to start background process");
189861
190466
  }
189862
190467
  proc.unref();
189863
- writeFileSync11(pidPath, `${proc.pid}
190468
+ writeFileSync12(pidPath, `${proc.pid}
189864
190469
  `);
189865
190470
  ctx.toolState.backgroundProcesses.set(handle, { pid: proc.pid, outputPath, pidPath });
189866
190471
  return {
@@ -189940,8 +190545,8 @@ function GhTool(ctx) {
189940
190545
  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.`,
189941
190546
  parameters: GhParams,
189942
190547
  execute: execute(async (params) => {
189943
- const configDir = join20(ctx.tmpdir, "gh-config");
189944
- mkdirSync8(configDir, { recursive: true });
190548
+ const configDir = join21(ctx.tmpdir, "gh-config");
190549
+ mkdirSync9(configDir, { recursive: true });
189945
190550
  const command = ["gh", ...params.args.map(shellQuote)].join(" ");
189946
190551
  const result = await runSandboxed({
189947
190552
  command,
@@ -190664,8 +191269,8 @@ function PullRequestInfoTool(ctx) {
190664
191269
  }
190665
191270
 
190666
191271
  // mcp/reviewComments.ts
190667
- import { writeFileSync as writeFileSync12 } from "node:fs";
190668
- import { join as join21 } from "node:path";
191272
+ import { writeFileSync as writeFileSync13 } from "node:fs";
191273
+ import { join as join22 } from "node:path";
190669
191274
  var COMMENTS_PER_THREAD = 50;
190670
191275
  var REVIEW_THREADS_QUERY = `
190671
191276
  query ($owner: String!, $name: String!, $prNumber: Int!, $cursor: String) {
@@ -191138,8 +191743,8 @@ function GetReviewCommentsTool(ctx) {
191138
191743
  throw new Error("PULLFROG_TEMP_DIR not set");
191139
191744
  }
191140
191745
  const filename = `review-${params.review_id}-threads.md`;
191141
- const commentsPath = join21(tempDir, filename);
191142
- writeFileSync12(commentsPath, formatted.content);
191746
+ const commentsPath = join22(tempDir, filename);
191747
+ writeFileSync13(commentsPath, formatted.content);
191143
191748
  log.debug(`wrote ${threadBlocks.length} threads to ${commentsPath}`);
191144
191749
  return {
191145
191750
  review_id: params.review_id,
@@ -191472,8 +192077,8 @@ function UploadFileTool(ctx) {
191472
192077
  }
191473
192078
 
191474
192079
  // mcp/xrepo.ts
191475
- import { mkdirSync as mkdirSync9, rmSync as rmSync2 } from "node:fs";
191476
- import { join as join22 } from "node:path";
192080
+ import { mkdirSync as mkdirSync10, rmSync as rmSync2 } from "node:fs";
192081
+ import { join as join23 } from "node:path";
191477
192082
  function assertValidRepoName(name) {
191478
192083
  if (!/^[A-Za-z0-9._-]+$/.test(name) || name.startsWith("-") || name.includes("..")) {
191479
192084
  throw new Error(
@@ -191553,8 +192158,8 @@ function CheckoutRepoTool(ctx) {
191553
192158
  return { path: existing.dir, access: existing.access, note: "already checked out." };
191554
192159
  }
191555
192160
  const access = accessFor(ctx, repo);
191556
- const dir = join22(ctx.tmpdir, "xrepo", repo);
191557
- mkdirSync9(dir, { recursive: true });
192161
+ const dir = join23(ctx.tmpdir, "xrepo", repo);
192162
+ mkdirSync10(dir, { recursive: true });
191558
192163
  const state = ensureRepoState(ctx.toolState, { owner, name: repo, dir, access });
191559
192164
  const rc = resolveRepoCtx(ctx, repo);
191560
192165
  try {
@@ -191802,6 +192407,9 @@ function hasEnvVar2(name) {
191802
192407
  function hasClaudeCodeAuth() {
191803
192408
  return hasEnvVar2("CLAUDE_CODE_OAUTH_TOKEN") || hasEnvVar2("ANTHROPIC_API_KEY");
191804
192409
  }
192410
+ function hasCodexAuth() {
192411
+ return hasEnvVar2("CODEX_AUTH_JSON") || hasEnvVar2("OPENAI_API_KEY");
192412
+ }
191805
192413
  function hasBedrockAuth() {
191806
192414
  return hasEnvVar2("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar2("AWS_ACCESS_KEY_ID") && hasEnvVar2("AWS_SECRET_ACCESS_KEY");
191807
192415
  }
@@ -191828,6 +192436,15 @@ function resolveSlug(slug2) {
191828
192436
  }
191829
192437
  return vertexId;
191830
192438
  }
192439
+ if (alias?.routing === "azure") {
192440
+ const deployment = process.env[AZURE_DEPLOYMENT_ENV]?.trim();
192441
+ if (!deployment) {
192442
+ throw new Error(
192443
+ `${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.`
192444
+ );
192445
+ }
192446
+ return `${AZURE_PROVIDER}/${deployment}`;
192447
+ }
191831
192448
  if (alias?.routing === "openai-compatible") {
191832
192449
  const modelId = process.env[OPENAI_COMPATIBLE_MODEL_ENV]?.trim();
191833
192450
  if (!modelId) {
@@ -191866,6 +192483,7 @@ function resolveAgent(ctx) {
191866
192483
  }
191867
192484
  log.warning(`\xBB unknown PULLFROG_AGENT="${envAgent}" \u2014 falling through to auto-select`);
191868
192485
  }
192486
+ if (ctx.proxyModel) return agents.opencode;
191869
192487
  if (ctx.model && hasBedrockAuth() && process.env[BEDROCK_MODEL_ID_ENV]?.trim() === ctx.model) {
191870
192488
  return isBedrockAnthropicId(ctx.model) ? agents.claude : agents.opencode;
191871
192489
  }
@@ -191875,12 +192493,14 @@ function resolveAgent(ctx) {
191875
192493
  if (ctx.model) {
191876
192494
  try {
191877
192495
  const provider2 = getModelProvider(ctx.model);
191878
- if (provider2 === "anthropic" && hasClaudeCodeAuth()) {
191879
- return agents.claude;
191880
- }
192496
+ if (provider2 === "anthropic" && hasClaudeCodeAuth()) return agents.claude;
192497
+ if (provider2 === "openai" && ctx.codexAgent && hasCodexAuth()) return agents.codex;
191881
192498
  } catch {
191882
192499
  }
191883
192500
  }
192501
+ if (!ctx.model && ctx.codexAgent && hasCodexAuth() && !hasClaudeCodeAuth()) {
192502
+ return agents.codex;
192503
+ }
191884
192504
  return agents.opencode;
191885
192505
  }
191886
192506
 
@@ -191978,10 +192598,37 @@ auto-compaction is disabled, so long runs grow until your endpoint refuses them.
191978
192598
 
191979
192599
  for full setup instructions, see https://docs.pullfrog.com/openai-compatible`;
191980
192600
  }
192601
+ function buildAzureSetupError(params) {
192602
+ const githubSecretsUrl = `https://github.com/${params.owner}/${params.name}/settings/secrets/actions`;
192603
+ return `Azure OpenAI selected but required configuration is missing: ${params.missing.join(", ")}.
192604
+
192605
+ only the API key is sensitive \u2014 add it as a secret at ${githubSecretsUrl}. the rest is plain workflow \`env:\`:
192606
+
192607
+ ${AZURE_RESOURCE_NAME_ENV}: <name>
192608
+ ${AZURE_API_KEY_ENV}: \${{ secrets.${AZURE_API_KEY_ENV} }}
192609
+ ${AZURE_DEPLOYMENT_ENV}: <deployment-name>
192610
+ ${AZURE_CONTEXT_ENV}: "400000"
192611
+ ${AZURE_MAX_OUTPUT_ENV}: "128000"
192612
+
192613
+ ${AZURE_RESOURCE_NAME_ENV} is the \`<name>\` in your endpoint https://<name>.openai.azure.com.
192614
+ ${AZURE_DEPLOYMENT_ENV} is the name of the deployment, which is not necessarily the name of
192615
+ the model it serves \u2014 Azure routes on the deployment name.
192616
+
192617
+ set the last two to the real limits of whichever model your deployment serves. Pullfrog can't
192618
+ discover them \u2014 a deployment name carries no catalog metadata \u2014 and without them completions
192619
+ are capped at 32000 tokens (rejected outright by models with a smaller cap) and auto-compaction
192620
+ is disabled, so long runs grow until Azure refuses them.
192621
+
192622
+ for full setup instructions, see https://docs.pullfrog.com/azure`;
192623
+ }
191981
192624
  function hasEnvVar3(name) {
191982
192625
  const value2 = process.env[name];
191983
192626
  return typeof value2 === "string" && value2.length > 0;
191984
192627
  }
192628
+ function modelHasRuntimeAuth(model) {
192629
+ const authVars = [...getModelEnvVars(model), ...getModelManagedCredentials(model)];
192630
+ return authVars.length === 0 || authVars.some(hasEnvVar3);
192631
+ }
191985
192632
  function hasPositiveNumberEnvVar(name) {
191986
192633
  return Number(process.env[name]) > 0;
191987
192634
  }
@@ -192000,6 +192647,17 @@ function validateOpenAICompatibleSetup(params) {
192000
192647
  );
192001
192648
  }
192002
192649
  }
192650
+ function validateAzureSetup(params) {
192651
+ const missing = [];
192652
+ if (!hasEnvVar3(AZURE_API_KEY_ENV)) missing.push(AZURE_API_KEY_ENV);
192653
+ if (!hasEnvVar3(AZURE_RESOURCE_NAME_ENV)) missing.push(AZURE_RESOURCE_NAME_ENV);
192654
+ if (!hasEnvVar3(AZURE_DEPLOYMENT_ENV)) missing.push(AZURE_DEPLOYMENT_ENV);
192655
+ if (!hasPositiveNumberEnvVar(AZURE_CONTEXT_ENV)) missing.push(AZURE_CONTEXT_ENV);
192656
+ if (!hasPositiveNumberEnvVar(AZURE_MAX_OUTPUT_ENV)) missing.push(AZURE_MAX_OUTPUT_ENV);
192657
+ if (missing.length > 0) {
192658
+ throw new Error(buildAzureSetupError({ owner: params.owner, name: params.name, missing }));
192659
+ }
192660
+ }
192003
192661
  function validateBedrockSetup(params) {
192004
192662
  const hasAuth = hasEnvVar3("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar3("AWS_ACCESS_KEY_ID") && hasEnvVar3("AWS_SECRET_ACCESS_KEY");
192005
192663
  const missing = [];
@@ -192023,6 +192681,12 @@ function validateVertexSetup(params) {
192023
192681
  throw new Error(buildVertexSetupError({ owner: params.owner, name: params.name, missing }));
192024
192682
  }
192025
192683
  }
192684
+ function hasSingleProviderAuth(agentName) {
192685
+ if (agentName === "codex") {
192686
+ return hasEnvVar3("OPENAI_API_KEY") || hasEnvVar3("CODEX_AUTH_JSON");
192687
+ }
192688
+ return hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN");
192689
+ }
192026
192690
  function validateAgentApiKey(params) {
192027
192691
  if (params.model) {
192028
192692
  const alias = resolveDisplayAlias(params.model);
@@ -192038,6 +192702,10 @@ function validateAgentApiKey(params) {
192038
192702
  validateOpenAICompatibleSetup({ owner: params.owner, name: params.name });
192039
192703
  return;
192040
192704
  }
192705
+ if (params.model.startsWith(`${AZURE_PROVIDER}/`)) {
192706
+ validateAzureSetup({ owner: params.owner, name: params.name });
192707
+ return;
192708
+ }
192041
192709
  if (!params.model.includes("/")) {
192042
192710
  if (process.env[VERTEX_MODEL_ID_ENV]?.trim() === params.model) {
192043
192711
  validateVertexSetup({ owner: params.owner, name: params.name });
@@ -192061,7 +192729,7 @@ function validateAgentApiKey(params) {
192061
192729
  })
192062
192730
  );
192063
192731
  }
192064
- if (hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN")) return;
192732
+ if (hasSingleProviderAuth(params.agent.name)) return;
192065
192733
  throw new Error(
192066
192734
  buildKeyError({
192067
192735
  owner: params.owner,
@@ -192073,7 +192741,7 @@ function validateAgentApiKey(params) {
192073
192741
  );
192074
192742
  }
192075
192743
  if (params.agent.name === "opencode") {
192076
- if (params.authorized.size > 0) return;
192744
+ if ([...params.authorized].some(modelHasRuntimeAuth)) return;
192077
192745
  const reason = getModelsFailure();
192078
192746
  if (reason) throw new Error(reason);
192079
192747
  throw new Error(
@@ -192085,7 +192753,7 @@ function validateAgentApiKey(params) {
192085
192753
  })
192086
192754
  );
192087
192755
  }
192088
- if (hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN")) return;
192756
+ if (hasSingleProviderAuth(params.agent.name)) return;
192089
192757
  throw new Error(
192090
192758
  buildKeyError({
192091
192759
  owner: params.owner,
@@ -192458,9 +193126,9 @@ async function checkConfiguredCredentials(params) {
192458
193126
 
192459
193127
  // utils/gitAuthServer.ts
192460
193128
  import { randomUUID as randomUUID5 } from "node:crypto";
192461
- import { writeFileSync as writeFileSync13 } from "node:fs";
193129
+ import { writeFileSync as writeFileSync14 } from "node:fs";
192462
193130
  import { createServer as createServer3 } from "node:http";
192463
- import { join as join23 } from "node:path";
193131
+ import { join as join24 } from "node:path";
192464
193132
  var REVOKED_TRAP_MS = 6e4;
192465
193133
  function revokeGitHubToken(token) {
192466
193134
  fetch("https://api.github.com/installation/token", {
@@ -192529,7 +193197,7 @@ async function startGitAuthServer(tmpdir3) {
192529
193197
  function writeAskpassScript(code) {
192530
193198
  const scriptId = randomUUID5();
192531
193199
  const scriptName = `askpass-${scriptId}.js`;
192532
- const scriptPath = join23(tmpdir3, scriptName);
193200
+ const scriptPath = join24(tmpdir3, scriptName);
192533
193201
  const content = [
192534
193202
  `#!/usr/bin/env node`,
192535
193203
  `var a=process.argv[2]||"";`,
@@ -192542,7 +193210,7 @@ async function startGitAuthServer(tmpdir3) {
192542
193210
  `r.on("end",function(){process.stdout.write(d+"\\n")})`,
192543
193211
  `}).on("error",function(){process.exit(1)})}`
192544
193212
  ].join("\n");
192545
- writeFileSync13(scriptPath, content, { mode: 448 });
193213
+ writeFileSync14(scriptPath, content, { mode: 448 });
192546
193214
  return scriptPath;
192547
193215
  }
192548
193216
  async function close() {
@@ -192981,7 +193649,7 @@ function resolveInstructions(ctx) {
192981
193649
 
192982
193650
  // utils/learnings.ts
192983
193651
  import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
192984
- import { dirname as dirname4, join as join24 } from "node:path";
193652
+ import { dirname as dirname4, join as join25 } from "node:path";
192985
193653
 
192986
193654
  // utils/learningsTruncate.ts
192987
193655
  var MAX_LEARNINGS_LENGTH = 1e5;
@@ -192999,10 +193667,10 @@ function truncateAtLineBoundary(body, cap) {
192999
193667
  var LEARNINGS_FILE_NAME = "pullfrog-learnings.md";
193000
193668
  var XREPO_LEARNINGS_FILE_NAME = "pullfrog-xrepo-learnings.md";
193001
193669
  function learningsFilePath(tmpdir3) {
193002
- return join24(tmpdir3, LEARNINGS_FILE_NAME);
193670
+ return join25(tmpdir3, LEARNINGS_FILE_NAME);
193003
193671
  }
193004
193672
  function xrepoLearningsFilePath(tmpdir3) {
193005
- return join24(tmpdir3, XREPO_LEARNINGS_FILE_NAME);
193673
+ return join25(tmpdir3, XREPO_LEARNINGS_FILE_NAME);
193006
193674
  }
193007
193675
  async function seedLearningsFile(params) {
193008
193676
  const path4 = learningsFilePath(params.tmpdir);
@@ -193118,7 +193786,7 @@ async function persistXrepoLearnings(ctx) {
193118
193786
  // utils/modelAccess.ts
193119
193787
  function decideModelAccess(input) {
193120
193788
  if (!input.modelExplicit || !input.model) return { kind: "ok" };
193121
- const byokAuthorized = !!input.resolvedModel && (input.authorized.has(input.resolvedModel) || !input.resolvedModel.includes("/") || input.resolvedModel.startsWith(`${OPENAI_COMPATIBLE_PROVIDER}/`));
193789
+ const byokAuthorized = !!input.resolvedModel && (input.authorized.has(input.resolvedModel) || !input.resolvedModel.includes("/") || input.resolvedModel.startsWith(`${OPENAI_COMPATIBLE_PROVIDER}/`) || input.resolvedModel.startsWith(`${AZURE_PROVIDER}/`));
193122
193790
  if (input.proxyActive) {
193123
193791
  const target = resolveOpenRouterModel(input.model);
193124
193792
  if (input.oss) {
@@ -193163,7 +193831,7 @@ function buildModelAccessError(input) {
193163
193831
  }
193164
193832
 
193165
193833
  // utils/normalizeEnv.ts
193166
- var core4 = __toESM(require_core(), 1);
193834
+ var core5 = __toESM(require_core(), 1);
193167
193835
  function hasControlCharacter(value2) {
193168
193836
  return [...value2].some((ch) => {
193169
193837
  const code = ch.charCodeAt(0);
@@ -193189,7 +193857,7 @@ function sanitizeSecret(key, value2) {
193189
193857
  `\xBB stripped whitespace from ${key} (whitespace in secret values breaks GitHub Actions log masking)`
193190
193858
  );
193191
193859
  }
193192
- core4.setSecret(trimmed);
193860
+ core5.setSecret(trimmed);
193193
193861
  return trimmed;
193194
193862
  }
193195
193863
  function normalizeEnv() {
@@ -193233,7 +193901,7 @@ function normalizeEnv() {
193233
193901
  }
193234
193902
 
193235
193903
  // utils/overrides.ts
193236
- var core5 = __toESM(require_core(), 1);
193904
+ var core6 = __toESM(require_core(), 1);
193237
193905
  var DENIED_OVERRIDE_NAMES = /* @__PURE__ */ new Set([
193238
193906
  "GITHUB_TOKEN",
193239
193907
  "GH_TOKEN",
@@ -193279,7 +193947,7 @@ function applyOverrides(params) {
193279
193947
  denied.push(key);
193280
193948
  continue;
193281
193949
  }
193282
- if (value2.length > 0) core5.setSecret(value2);
193950
+ if (value2.length > 0) core6.setSecret(value2);
193283
193951
  params.env[key] = value2;
193284
193952
  applied.push(key);
193285
193953
  }
@@ -193288,8 +193956,8 @@ function applyOverrides(params) {
193288
193956
  }
193289
193957
 
193290
193958
  // utils/payload.ts
193291
- var core6 = __toESM(require_core(), 1);
193292
- import { readFileSync as readFileSync5 } from "node:fs";
193959
+ var core7 = __toESM(require_core(), 1);
193960
+ import { readFileSync as readFileSync6 } from "node:fs";
193293
193961
  import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
193294
193962
 
193295
193963
  // utils/versioning.ts
@@ -193376,8 +194044,8 @@ function resolveCwd(cwd) {
193376
194044
  return workspace ? resolve2(workspace, cwd) : cwd;
193377
194045
  }
193378
194046
  function resolvePromptInput() {
193379
- const promptInput = core6.getInput("prompt");
193380
- const promptFile = core6.getInput("prompt_file");
194047
+ const promptInput = core7.getInput("prompt");
194048
+ const promptFile = core7.getInput("prompt_file");
193381
194049
  if (promptInput && promptFile) {
193382
194050
  throw new Error("set exactly one of 'prompt' or 'prompt_file' inputs, not both.");
193383
194051
  }
@@ -193403,7 +194071,7 @@ function resolvePromptInput() {
193403
194071
  function resolvePromptFile(input) {
193404
194072
  const workspace = process.env.GITHUB_WORKSPACE;
193405
194073
  const path4 = isAbsolute2(input) ? input : workspace ? resolve2(workspace, input) : resolve2(input);
193406
- const content = readFileSync5(path4, "utf-8");
194074
+ const content = readFileSync6(path4, "utf-8");
193407
194075
  if (!content.trim()) {
193408
194076
  throw new Error(`prompt_file ${JSON.stringify(input)} is empty.`);
193409
194077
  }
@@ -193411,7 +194079,7 @@ function resolvePromptFile(input) {
193411
194079
  }
193412
194080
  function warnIfDeprecatedStatusChecks(value2) {
193413
194081
  if (value2 !== void 0) {
193414
- core6.warning(
194082
+ core7.warning(
193415
194083
  "`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."
193416
194084
  );
193417
194085
  }
@@ -193419,15 +194087,15 @@ function warnIfDeprecatedStatusChecks(value2) {
193419
194087
  }
193420
194088
  function resolveNonPromptInputs() {
193421
194089
  return Inputs.omit("prompt", "prompt_file").assert({
193422
- model: core6.getInput("model") || void 0,
193423
- effort: core6.getInput("effort") || void 0,
193424
- debug: core6.getInput("debug") || void 0,
193425
- timeout: core6.getInput("timeout") || void 0,
193426
- cwd: core6.getInput("cwd") || void 0,
193427
- push: core6.getInput("push") || void 0,
193428
- shell: core6.getInput("shell") || void 0,
193429
- status_checks: warnIfDeprecatedStatusChecks(core6.getInput("status_checks") || void 0),
193430
- progress_comments: core6.getInput("progress_comments") || void 0
194090
+ model: core7.getInput("model") || void 0,
194091
+ effort: core7.getInput("effort") || void 0,
194092
+ debug: core7.getInput("debug") || void 0,
194093
+ timeout: core7.getInput("timeout") || void 0,
194094
+ cwd: core7.getInput("cwd") || void 0,
194095
+ push: core7.getInput("push") || void 0,
194096
+ shell: core7.getInput("shell") || void 0,
194097
+ status_checks: warnIfDeprecatedStatusChecks(core7.getInput("status_checks") || void 0),
194098
+ progress_comments: core7.getInput("progress_comments") || void 0
193431
194099
  });
193432
194100
  }
193433
194101
  function resolvePayload(resolvedPromptInput, repoSettings) {
@@ -193495,7 +194163,7 @@ function resolvePayload(resolvedPromptInput, repoSettings) {
193495
194163
  };
193496
194164
  }
193497
194165
  function resolveOutputSchema() {
193498
- const raw2 = core6.getInput("output_schema");
194166
+ const raw2 = core7.getInput("output_schema");
193499
194167
  if (!raw2) return void 0;
193500
194168
  let parsed2;
193501
194169
  try {
@@ -193511,10 +194179,10 @@ function resolveOutputSchema() {
193511
194179
  }
193512
194180
 
193513
194181
  // utils/proxy.ts
193514
- var core8 = __toESM(require_core(), 1);
194182
+ var core9 = __toESM(require_core(), 1);
193515
194183
 
193516
194184
  // utils/token.ts
193517
- var core7 = __toESM(require_core(), 1);
194185
+ var core8 = __toESM(require_core(), 1);
193518
194186
  import assert2 from "node:assert/strict";
193519
194187
  var mcpTokenValue;
193520
194188
  var refreshMcpTokenFn;
@@ -193522,7 +194190,7 @@ function getMcpTokenRefresh() {
193522
194190
  return refreshMcpTokenFn;
193523
194191
  }
193524
194192
  function getJobToken() {
193525
- const inputToken = core7.getInput("token");
194193
+ const inputToken = core8.getInput("token");
193526
194194
  if (inputToken) {
193527
194195
  return inputToken;
193528
194196
  }
@@ -193538,7 +194206,7 @@ async function resolveTokens(params) {
193538
194206
  if (externalToken) {
193539
194207
  mcpTokenValue = externalToken;
193540
194208
  if (isGitHubActions) {
193541
- core7.setSecret(externalToken);
194209
+ core8.setSecret(externalToken);
193542
194210
  }
193543
194211
  log.info("\xBB using external GH_TOKEN for both git and MCP");
193544
194212
  return {
@@ -193558,7 +194226,7 @@ async function resolveTokens(params) {
193558
194226
  const gitPermissions = params.push === "disabled" ? { contents: "read" } : { contents: "write", workflows: "write" };
193559
194227
  const gitToken = await acquireNewToken({ repos: writeRepos, permissions: gitPermissions });
193560
194228
  if (isGitHubActions) {
193561
- core7.setSecret(gitToken);
194229
+ core8.setSecret(gitToken);
193562
194230
  }
193563
194231
  log.info(
193564
194232
  `\xBB acquired git token (${Object.entries(gitPermissions).map((e) => e.join(":")).join(", ")})`
@@ -193575,7 +194243,7 @@ async function resolveTokens(params) {
193575
194243
  };
193576
194244
  const mcpToken = await acquireNewToken({ repos: writeRepos, permissions: mcpPermissions });
193577
194245
  if (isGitHubActions) {
193578
- core7.setSecret(mcpToken);
194246
+ core8.setSecret(mcpToken);
193579
194247
  }
193580
194248
  log.info(
193581
194249
  `\xBB acquired scoped MCP token (${Object.entries(mcpPermissions).map((e) => e.join(":")).join(", ")})`
@@ -193584,7 +194252,7 @@ async function resolveTokens(params) {
193584
194252
  let ghToken;
193585
194253
  if (ghPermissions) {
193586
194254
  ghToken = await acquireNewToken({ permissions: ghPermissions });
193587
- if (isGitHubActions) core7.setSecret(ghToken);
194255
+ if (isGitHubActions) core8.setSecret(ghToken);
193588
194256
  log.info(
193589
194257
  `\xBB acquired gh token mirroring ${params.authorPermission} (${formatPermissions(ghPermissions)})`
193590
194258
  );
@@ -193595,7 +194263,7 @@ async function resolveTokens(params) {
193595
194263
  repos: params.xrepo.read,
193596
194264
  permissions: { contents: "read" }
193597
194265
  });
193598
- if (isGitHubActions) core7.setSecret(readToken);
194266
+ if (isGitHubActions) core8.setSecret(readToken);
193599
194267
  log.info(`\xBB acquired cross-repo read token (contents:read, ${params.xrepo.read.length} repos)`);
193600
194268
  }
193601
194269
  mcpTokenValue = mcpToken;
@@ -193614,7 +194282,7 @@ async function resolveTokens(params) {
193614
194282
  oidc: params.oidc ?? void 0
193615
194283
  }).then((fresh) => {
193616
194284
  if (isGitHubActions) {
193617
- core7.setSecret(fresh);
194285
+ core8.setSecret(fresh);
193618
194286
  }
193619
194287
  mcpTokenValue = fresh;
193620
194288
  currentMcpToken = fresh;
@@ -193635,7 +194303,7 @@ async function resolveTokens(params) {
193635
194303
  permissions: gitPermissions,
193636
194304
  oidc: params.oidc ?? void 0
193637
194305
  }).then((fresh) => {
193638
- if (isGitHubActions) core7.setSecret(fresh);
194306
+ if (isGitHubActions) core8.setSecret(fresh);
193639
194307
  void revokeGitHubInstallationToken(currentGitToken);
193640
194308
  currentGitToken = fresh;
193641
194309
  log.warning("\xBB GitHub rejected the git token; re-acquired a fresh git token");
@@ -193652,7 +194320,7 @@ async function resolveTokens(params) {
193652
194320
  permissions: { contents: "read" },
193653
194321
  oidc: params.oidc ?? void 0
193654
194322
  }).then((fresh) => {
193655
- if (isGitHubActions) core7.setSecret(fresh);
194323
+ if (isGitHubActions) core8.setSecret(fresh);
193656
194324
  void revokeGitHubInstallationToken(read2);
193657
194325
  currentReadToken = fresh;
193658
194326
  log.warning("\xBB GitHub rejected the read token; re-acquired a fresh read token");
@@ -193866,7 +194534,7 @@ async function resolveProxyModel(ctx) {
193866
194534
  });
193867
194535
  if (!key) return;
193868
194536
  process.env.OPENROUTER_API_KEY = key;
193869
- core8.setSecret(key);
194537
+ core9.setSecret(key);
193870
194538
  ctx.payload.proxyModel = ctx.proxyModel;
193871
194539
  ctx.toolState.model = ctx.proxyModel;
193872
194540
  const label = ctx.oss ? "oss" : "router";
@@ -193937,7 +194605,7 @@ async function runProxyResolution(ctx) {
193937
194605
 
193938
194606
  // utils/prSummary.ts
193939
194607
  import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
193940
- import { dirname as dirname5, join as join25 } from "node:path";
194608
+ import { dirname as dirname5, join as join26 } from "node:path";
193941
194609
  var SUMMARY_FILE_NAME = "pullfrog-summary.md";
193942
194610
  var SUMMARY_SCAFFOLD = `# PR summary
193943
194611
 
@@ -193947,7 +194615,7 @@ var SUMMARY_SCAFFOLD = `# PR summary
193947
194615
  var MIN_SNAPSHOT_LENGTH = 60;
193948
194616
  var MAX_SNAPSHOT_LENGTH = 32768;
193949
194617
  function summaryFilePath(tmpdir3) {
193950
- return join25(tmpdir3, SUMMARY_FILE_NAME);
194618
+ return join26(tmpdir3, SUMMARY_FILE_NAME);
193951
194619
  }
193952
194620
  async function seedSummaryFile(params) {
193953
194621
  const path4 = summaryFilePath(params.tmpdir);
@@ -194076,6 +194744,7 @@ var defaultSettings = {
194076
194744
  shell: "restricted",
194077
194745
  prApproveEnabled: false,
194078
194746
  autoMergeEnabled: false,
194747
+ codexAgent: false,
194079
194748
  signedCommits: false,
194080
194749
  repoIntelligence: false,
194081
194750
  progressComments: true,
@@ -194564,7 +195233,7 @@ ${genericBody}`,
194564
195233
  }
194565
195234
 
194566
195235
  // utils/runLifecycle.ts
194567
- var core9 = __toESM(require_core(), 1);
195236
+ var core10 = __toESM(require_core(), 1);
194568
195237
 
194569
195238
  // utils/autoMerge.ts
194570
195239
  function hasBlockingHumanReview(reviews) {
@@ -194950,7 +195619,7 @@ async function finalizeSuccessRun(input) {
194950
195619
  }
194951
195620
  if (input.toolState.output) {
194952
195621
  log.info(`::pullfrog-output::${Buffer.from(input.toolState.output).toString("base64")}`);
194953
- core9.setOutput("result", input.toolState.output);
195622
+ core10.setOutput("result", input.toolState.output);
194954
195623
  }
194955
195624
  if (input.result.success) {
194956
195625
  await approveAfterFix(input.toolContext).catch((error52) => {
@@ -195414,7 +196083,11 @@ async function main() {
195414
196083
  }
195415
196084
  const resolvedModel = credentials.kind === "fellBack" ? credentials.replacement : configuredModel;
195416
196085
  vertexCredentials = materializeVertexCredentials({ model: resolvedModel });
195417
- const agent2 = resolveAgent({ model: resolvedModel });
196086
+ const agent2 = resolveAgent({
196087
+ model: resolvedModel,
196088
+ proxyModel: payload.proxyModel,
196089
+ codexAgent: runContext.repoSettings.codexAgent
196090
+ });
195418
196091
  const effectiveModel = payload.proxyModel ?? resolvedModel ?? payload.model;
195419
196092
  toolState.model = effectiveModel;
195420
196093
  if (!payload.proxyModel) {
@@ -195574,11 +196247,16 @@ async function main() {
195574
196247
  xrepoLearningsFilePath: toolState.xrepoLearningsFilePath ?? null,
195575
196248
  xrepoLearningsHeadings: runContext.repoSettings.xrepoLearningsHeadings
195576
196249
  });
195577
- assertPromptToolRefs({
196250
+ const danglingToolRefs = findDanglingPromptToolRefs({
195578
196251
  agentId,
195579
196252
  prompt: instructions.full,
195580
196253
  toolNames: mcpHttpServer.toolNames
195581
196254
  });
196255
+ if (danglingToolRefs.length > 0) {
196256
+ log.warning(
196257
+ `\xBB prompt advertises ${danglingToolRefs.length} tool(s) the MCP server did not register: ${danglingToolRefs.join(", ")}`
196258
+ );
196259
+ }
195582
196260
  const logParts = [
195583
196261
  instructions.eventInstructions ? `EVENT-LEVEL INSTRUCTIONS:
195584
196262
  ${instructions.eventInstructions}` : null,
@@ -195593,8 +196271,8 @@ ${instructions.user}` : null,
195593
196271
  log.info(instructions.full);
195594
196272
  });
195595
196273
  if (agentId === "opencode") {
195596
- const pluginDir = join26(process.cwd(), ".opencode", "plugin");
195597
- const hasPlugins = existsSync8(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
196274
+ const pluginDir = join27(process.cwd(), ".opencode", "plugin");
196275
+ const hasPlugins = existsSync9(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
195598
196276
  if (hasPlugins && toolState.dependencyInstallation?.promise) {
195599
196277
  log.info(
195600
196278
  "\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"