pullfrog 0.1.57 → 0.1.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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:
@@ -104861,7 +104891,8 @@ import { pipeline } from "node:stream/promises";
104861
104891
  async function installFromNpmTarball(params) {
104862
104892
  const tempDir = process.env.PULLFROG_TEMP_DIR;
104863
104893
  if (!tempDir) throw new Error("PULLFROG_TEMP_DIR is not set");
104864
- const extractedDir = join(tempDir, "package");
104894
+ const packageRoot = join(tempDir, `npm-${params.packageName.replace(/[^a-z0-9]+/gi, "-")}`);
104895
+ const extractedDir = join(packageRoot, "package");
104865
104896
  const cliPath = join(extractedDir, params.executablePath);
104866
104897
  if (existsSync2(cliPath)) {
104867
104898
  log.debug(`\xBB using cached binary at ${cliPath}`);
@@ -104887,7 +104918,8 @@ async function installFromNpmTarball(params) {
104887
104918
  }
104888
104919
  }
104889
104920
  log.debug(`\xBB installing ${params.packageName}@${resolvedVersion}...`);
104890
- const tarballPath = join(tempDir, "package.tgz");
104921
+ mkdirSync(packageRoot, { recursive: true });
104922
+ const tarballPath = join(packageRoot, "package.tgz");
104891
104923
  const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org";
104892
104924
  let tarballUrl;
104893
104925
  if (params.packageName.startsWith("@")) {
@@ -104907,7 +104939,7 @@ async function installFromNpmTarball(params) {
104907
104939
  await pipeline(response.body, fileStream);
104908
104940
  log.debug(`\xBB downloaded tarball to ${tarballPath}`);
104909
104941
  log.debug(`\xBB extracting tarball...`);
104910
- const extractResult = spawnSync("tar", ["-xzf", tarballPath, "-C", tempDir], {
104942
+ const extractResult = spawnSync("tar", ["-xzf", tarballPath, "-C", packageRoot], {
104911
104943
  stdio: "pipe",
104912
104944
  encoding: "utf-8"
104913
104945
  });
@@ -104916,9 +104948,6 @@ async function installFromNpmTarball(params) {
104916
104948
  `Failed to extract tarball: ${extractResult.stderr || extractResult.stdout || "Unknown error"}`
104917
104949
  );
104918
104950
  }
104919
- if (!existsSync2(cliPath)) {
104920
- throw new Error(`Executable not found in extracted package at ${cliPath}`);
104921
- }
104922
104951
  if (params.installDependencies) {
104923
104952
  log.debug(`\xBB installing dependencies for ${params.packageName}...`);
104924
104953
  const installResult = spawnSync("npm", ["install", "--production"], {
@@ -104939,6 +104968,9 @@ async function installFromNpmTarball(params) {
104939
104968
  }
104940
104969
  log.debug(`\xBB dependencies installed`);
104941
104970
  }
104971
+ if (!existsSync2(cliPath)) {
104972
+ throw new Error(`Executable not found in extracted package at ${cliPath}`);
104973
+ }
104942
104974
  chmodSync(cliPath, 493);
104943
104975
  log.debug(`\xBB ${params.packageName} installed at ${cliPath}`);
104944
104976
  return cliPath;
@@ -105112,6 +105144,7 @@ function resolveRunAlias(ctx) {
105112
105144
  }
105113
105145
  const model = ctx.resolvedModel;
105114
105146
  if (!model) return void 0;
105147
+ if (model.startsWith(`${AZURE_PROVIDER}/`)) return void 0;
105115
105148
  return modelAliases.find((a) => !a.fallback && a.resolve === model) ?? matchHostedAnthropicAlias(model);
105116
105149
  }
105117
105150
  function ossEffortFloor(ctx) {
@@ -105150,7 +105183,7 @@ var import_semver = __toESM(require_semver2(), 1);
105150
105183
  // package.json
105151
105184
  var package_default = {
105152
105185
  name: "pullfrog",
105153
- version: "0.1.57",
105186
+ version: "0.1.59",
105154
105187
  type: "module",
105155
105188
  bin: {
105156
105189
  pullfrog: "dist/cli.mjs",
@@ -105182,6 +105215,8 @@ var package_default = {
105182
105215
  "@clack/prompts": "^1.2.0",
105183
105216
  "@modelcontextprotocol/sdk": "^1.26.0",
105184
105217
  "@opencode-ai/sdk": "1.18.5",
105218
+ "@openai/codex": "0.147.0",
105219
+ "@openai/codex-sdk": "0.147.0",
105185
105220
  "@octokit/plugin-throttling": "^11.0.3",
105186
105221
  "@octokit/rest": "^22.0.0",
105187
105222
  "@octokit/webhooks-types": "^7.6.1",
@@ -107303,13 +107338,782 @@ var claude = agent({
107303
107338
  }
107304
107339
  });
107305
107340
 
107306
- // agents/opencode.ts
107341
+ // agents/codex.ts
107307
107342
  var core2 = __toESM(require_core(), 1);
107308
- import { spawn as nodeSpawn2 } from "node:child_process";
107309
- 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";
107310
107344
  import { join as join6 } from "node:path";
107311
107345
  import { performance as performance6 } from "node:perf_hooks";
107312
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
+
107313
108117
  // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/core/serverSentEvents.gen.js
107314
108118
  var createSseClient = ({ onRequest, onSseError, onSseEvent, responseTransformer, responseValidator, sseDefaultRetryDelay, sseMaxRetryAttempts, sseMaxRetryDelay, sseSleepFn, url: url4, ...options }) => {
107315
108119
  let lastEventId;
@@ -112716,114 +113520,6 @@ var import_cross_spawn = __toESM(require_cross_spawn(), 1);
112716
113520
  // agents/opencode.ts
112717
113521
  var import_undici = __toESM(require_undici2(), 1);
112718
113522
 
112719
- // utils/codexHome.ts
112720
- import { execFileSync as execFileSync3 } from "node:child_process";
112721
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
112722
- import { homedir as homedir2, userInfo } from "node:os";
112723
- import { join as join5 } from "node:path";
112724
-
112725
- // utils/codexOAuth.ts
112726
- function decodeJwtExpMs(token) {
112727
- const parts = token.split(".");
112728
- if (parts.length !== 3) return null;
112729
- let payload;
112730
- try {
112731
- payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
112732
- } catch {
112733
- return null;
112734
- }
112735
- if (typeof payload.exp !== "number" || !Number.isFinite(payload.exp)) return null;
112736
- return payload.exp * 1e3;
112737
- }
112738
- function parseCodexAuthBody(raw2) {
112739
- let parsed2;
112740
- try {
112741
- parsed2 = JSON.parse(raw2);
112742
- } catch {
112743
- return null;
112744
- }
112745
- if (!parsed2 || typeof parsed2 !== "object") return null;
112746
- const v = parsed2;
112747
- if (v.auth_mode !== "chatgpt") return null;
112748
- const tokens = v.tokens;
112749
- if (!tokens || typeof tokens !== "object") return null;
112750
- const t = tokens;
112751
- if (typeof t.access_token !== "string" || t.access_token.length === 0) return null;
112752
- if (typeof t.refresh_token !== "string" || t.refresh_token.length === 0) return null;
112753
- return {
112754
- auth_mode: "chatgpt",
112755
- ...typeof v.refresh_rejected_at === "string" ? { refresh_rejected_at: v.refresh_rejected_at } : {},
112756
- tokens: {
112757
- access_token: t.access_token,
112758
- refresh_token: t.refresh_token,
112759
- ...typeof t.id_token === "string" ? { id_token: t.id_token } : {},
112760
- ...typeof t.account_id === "string" ? { account_id: t.account_id } : {}
112761
- },
112762
- ...typeof v.last_refresh === "string" ? { last_refresh: v.last_refresh } : {}
112763
- };
112764
- }
112765
-
112766
- // utils/codexHome.ts
112767
- var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
112768
- var PULLFROG_DATA_DIR = "/var/lib/pullfrog";
112769
- function installCodexAuth() {
112770
- const raw2 = process.env[CODEX_AUTH_ENV];
112771
- if (!raw2) return null;
112772
- const body = parseCodexAuthBody(raw2);
112773
- if (!body) {
112774
- log.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
112775
- return null;
112776
- }
112777
- const expiresMs = decodeJwtExpMs(body.tokens.access_token) ?? 0;
112778
- const xdgDataHome = resolveDataHome();
112779
- const opencodeDir = join5(xdgDataHome, "opencode");
112780
- const authPath = join5(opencodeDir, "auth.json");
112781
- const opencodeAuth = {
112782
- openai: {
112783
- type: "oauth",
112784
- refresh: body.tokens.refresh_token,
112785
- access: body.tokens.access_token,
112786
- expires: expiresMs,
112787
- ...body.tokens.account_id ? { accountId: body.tokens.account_id } : {}
112788
- }
112789
- };
112790
- mkdirSync5(opencodeDir, { recursive: true });
112791
- writeFileSync4(authPath, `${JSON.stringify(opencodeAuth, null, 2)}
112792
- `, { mode: 384 });
112793
- process.env.XDG_DATA_HOME = xdgDataHome;
112794
- log.info(`\xBB installed Codex auth at ${authPath}`);
112795
- return {
112796
- authPath,
112797
- xdgDataHome,
112798
- originalRefresh: body.tokens.refresh_token
112799
- };
112800
- }
112801
- function resolveDataHome() {
112802
- if (process.env.CI !== "true") return join5(homedir2(), ".local", "share");
112803
- bootstrapPullfrogDataDir();
112804
- return PULLFROG_DATA_DIR;
112805
- }
112806
- function bootstrapPullfrogDataDir() {
112807
- const user = userInfo().username;
112808
- let primaryGroup;
112809
- try {
112810
- primaryGroup = execFileSync3("id", ["-gn", user], { stdio: "pipe", encoding: "utf-8" }).trim();
112811
- } catch {
112812
- primaryGroup = user;
112813
- }
112814
- try {
112815
- execFileSync3("sudo", ["-n", "mkdir", "-p", PULLFROG_DATA_DIR], { stdio: "pipe" });
112816
- execFileSync3("sudo", ["-n", "chown", `${user}:${primaryGroup}`, PULLFROG_DATA_DIR], {
112817
- stdio: "pipe"
112818
- });
112819
- execFileSync3("sudo", ["-n", "chmod", "700", PULLFROG_DATA_DIR], { stdio: "pipe" });
112820
- } catch (err) {
112821
- throw new Error(
112822
- `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.`
112823
- );
112824
- }
112825
- }
112826
-
112827
113523
  // utils/worktree.ts
112828
113524
  async function dirtyTrackedPaths(params = {}) {
112829
113525
  const result = await spawn({
@@ -113091,6 +113787,30 @@ function openAICompatibleProvider(model) {
113091
113787
  }
113092
113788
  };
113093
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
+ }
113094
113814
  var KIMI_ENFORCERLESS_PROVIDERS = ["siliconflow", "together"];
113095
113815
  function kimiOpenRouterProviderOverrides() {
113096
113816
  return Object.fromEntries(
@@ -113182,7 +113902,8 @@ function buildSecurityConfig(ctx, model) {
113182
113902
  // see opencodeShared.ts.
113183
113903
  provider: {
113184
113904
  openrouter: { models: kimiOpenRouterProviderOverrides() },
113185
- ...openAICompatibleProvider(model)
113905
+ ...openAICompatibleProvider(model),
113906
+ ...azureProvider(model)
113186
113907
  }
113187
113908
  };
113188
113909
  if (model) {
@@ -113350,7 +114071,7 @@ function isModelOutput(ctx, part) {
113350
114071
  }
113351
114072
  async function dispatchEvent(ctx, event) {
113352
114073
  if (event.type === "message.part.updated") {
113353
- ctx.lastEventAt = performance6.now();
114074
+ ctx.lastEventAt = performance7.now();
113354
114075
  if (isModelOutput(ctx, event.properties.part)) ctx.sawModelOutput = true;
113355
114076
  await onPartUpdated(ctx, event.properties.part);
113356
114077
  return;
@@ -113417,7 +114138,7 @@ async function onToolPart(ctx, part, label, isOrchestrator) {
113417
114138
  if (toolName === "task" && status === "running" && isOrchestrator && !ctx.taskDispatchByCallID.has(toolId)) {
113418
114139
  const input = part.state.input ?? {};
113419
114140
  const dispatched = ctx.labeler.recordTaskDispatch(input);
113420
- ctx.taskDispatchByCallID.set(toolId, { label: dispatched, startedAt: performance6.now() });
114141
+ ctx.taskDispatchByCallID.set(toolId, { label: dispatched, startedAt: performance7.now() });
113421
114142
  log.info(
113422
114143
  `\xBB dispatching subagent: ${dispatched}` + (input.subagent_type ? ` (subagent_type=${input.subagent_type})` : "")
113423
114144
  );
@@ -113447,7 +114168,7 @@ function processTerminalToolPart(ctx, part, label, isOrchestrator) {
113447
114168
  if (toolName === "task") {
113448
114169
  const dispatch = ctx.taskDispatchByCallID.get(toolId);
113449
114170
  if (dispatch) {
113450
- const dur = ((performance6.now() - dispatch.startedAt) / 1e3).toFixed(1);
114171
+ const dur = ((performance7.now() - dispatch.startedAt) / 1e3).toFixed(1);
113451
114172
  const outputStr = state.status === "completed" ? state.output : "";
113452
114173
  const preview = typeof outputStr === "string" && outputStr.length > 120 ? `${outputStr.slice(0, 120)}\u2026` : outputStr;
113453
114174
  log.info(
@@ -113490,7 +114211,7 @@ function withLabel(label, message) {
113490
114211
  return label === ORCHESTRATOR_LABEL ? message : formatWithLabel(label, message);
113491
114212
  }
113492
114213
  async function runPromptTurn(ctx, params) {
113493
- const start = performance6.now();
114214
+ const start = performance7.now();
113494
114215
  const turnStartMs = Date.now();
113495
114216
  ctx.currentTurn = newTurn();
113496
114217
  const turn = ctx.currentTurn;
@@ -113524,7 +114245,7 @@ async function runPromptTurn(ctx, params) {
113524
114245
  } catch (err) {
113525
114246
  networkError = err instanceof Error ? err.message : String(err);
113526
114247
  }
113527
- const durationMs = performance6.now() - start;
114248
+ const durationMs = performance7.now() - start;
113528
114249
  const aggregatedUsage = await aggregateTurnUsage(ctx, turnStartMs);
113529
114250
  const usage = aggregatedUsage ?? buildUsage(turn, assistant);
113530
114251
  const finalText = turn.finalText || extractTextFromParts(returnedParts) || "";
@@ -113678,7 +114399,7 @@ function startInnerActivityWatchdog(params) {
113678
114399
  let fired = false;
113679
114400
  const id = setInterval(() => {
113680
114401
  if (fired) return;
113681
- const idleMs = performance6.now() - params.ctx.lastEventAt;
114402
+ const idleMs = performance7.now() - params.ctx.lastEventAt;
113682
114403
  const budgetMs = params.ctx.sawModelOutput ? params.timeoutMs : AGENT_FIRST_EVENT_TIMEOUT_MS;
113683
114404
  if (idleMs <= budgetMs) return;
113684
114405
  fired = true;
@@ -113717,12 +114438,12 @@ var opencode = agent({
113717
114438
  const model = vertexModel ?? (isBedrockRoute ? `amazon-bedrock/${rawModel}` : rawModel);
113718
114439
  const homeEnv = {
113719
114440
  HOME: ctx.tmpdir,
113720
- XDG_CONFIG_HOME: join6(ctx.tmpdir, ".config")
114441
+ XDG_CONFIG_HOME: join7(ctx.tmpdir, ".config")
113721
114442
  };
113722
- const opencodePluginDir = join6(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
113723
- mkdirSync6(opencodePluginDir, { recursive: true });
113724
- writeFileSync5(
113725
- 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),
113726
114447
  buildOpencodeSubagentGateSource(ctx.subagentDeniedTools)
113727
114448
  );
113728
114449
  const agentBrowserVersion = getDevDependencyVersion("agent-browser");
@@ -113753,12 +114474,13 @@ var opencode = agent({
113753
114474
  if (codexAuth) {
113754
114475
  env2.XDG_DATA_HOME = codexAuth.xdgDataHome;
113755
114476
  delete env2.OPENAI_API_KEY;
113756
- core2.saveState(
114477
+ core3.saveState(
113757
114478
  "codex_writeback",
113758
114479
  JSON.stringify({
113759
114480
  apiToken: ctx.apiToken,
113760
114481
  authPath: codexAuth.authPath,
113761
- originalRefresh: codexAuth.originalRefresh
114482
+ originalRefresh: codexAuth.originalRefresh,
114483
+ originalIdToken: codexAuth.originalIdToken
113762
114484
  })
113763
114485
  );
113764
114486
  }
@@ -113812,7 +114534,7 @@ var opencode = agent({
113812
114534
  onToolUse: ctx.onToolUse,
113813
114535
  currentTurn: null,
113814
114536
  eventCount: 0,
113815
- lastEventAt: performance6.now(),
114537
+ lastEventAt: performance7.now(),
113816
114538
  sawModelOutput: false,
113817
114539
  promptMessageID: void 0,
113818
114540
  taskDispatchByCallID: /* @__PURE__ */ new Map(),
@@ -113922,7 +114644,7 @@ async function runTurnGuarded(ctx, fn2) {
113922
114644
  }
113923
114645
 
113924
114646
  // agents/index.ts
113925
- var agents = { claude, opencode };
114647
+ var agents = { claude, codex, opencode };
113926
114648
 
113927
114649
  // node_modules/.pnpm/@ark+util@0.56.0/node_modules/@ark/util/out/arrays.js
113928
114650
  var liftArray = (data) => Array.isArray(data) ? data : [data];
@@ -134254,7 +134976,7 @@ var require_core$2 = /* @__PURE__ */ __commonJSMin(((exports) => {
134254
134976
  Object.defineProperty(exports, "__esModule", { value: true });
134255
134977
  const id_1 = require_id2();
134256
134978
  const ref_1 = require_ref2();
134257
- const core10 = [
134979
+ const core11 = [
134258
134980
  "$schema",
134259
134981
  "$id",
134260
134982
  "$defs",
@@ -134264,7 +134986,7 @@ var require_core$2 = /* @__PURE__ */ __commonJSMin(((exports) => {
134264
134986
  id_1.default,
134265
134987
  ref_1.default
134266
134988
  ];
134267
- exports.default = core10;
134989
+ exports.default = core11;
134268
134990
  }));
134269
134991
  var require_limitNumber2 = /* @__PURE__ */ __commonJSMin(((exports) => {
134270
134992
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -136402,7 +137124,7 @@ var require_json_schema_2019_09 = /* @__PURE__ */ __commonJSMin(((exports) => {
136402
137124
  const metaSchema = require_schema$1();
136403
137125
  const applicator = require_applicator$1();
136404
137126
  const content = require_content$1();
136405
- const core10 = require_core$1();
137127
+ const core11 = require_core$1();
136406
137128
  const format2 = require_format3();
136407
137129
  const metadata = require_meta_data$1();
136408
137130
  const validation = require_validation$1();
@@ -136412,7 +137134,7 @@ var require_json_schema_2019_09 = /* @__PURE__ */ __commonJSMin(((exports) => {
136412
137134
  metaSchema,
136413
137135
  applicator,
136414
137136
  content,
136415
- core10,
137137
+ core11,
136416
137138
  with$data(this, format2),
136417
137139
  metadata,
136418
137140
  with$data(this, validation)
@@ -136852,7 +137574,7 @@ var require_json_schema_2020_12 = /* @__PURE__ */ __commonJSMin(((exports) => {
136852
137574
  const applicator = require_applicator2();
136853
137575
  const unevaluated = require_unevaluated();
136854
137576
  const content = require_content();
136855
- const core10 = require_core4();
137577
+ const core11 = require_core4();
136856
137578
  const format2 = require_format_annotation();
136857
137579
  const metadata = require_meta_data();
136858
137580
  const validation = require_validation2();
@@ -136863,7 +137585,7 @@ var require_json_schema_2020_12 = /* @__PURE__ */ __commonJSMin(((exports) => {
136863
137585
  applicator,
136864
137586
  unevaluated,
136865
137587
  content,
136866
- core10,
137588
+ core11,
136867
137589
  with$data(this, format2),
136868
137590
  metadata,
136869
137591
  with$data(this, validation)
@@ -148305,7 +149027,7 @@ var require_core$22 = /* @__PURE__ */ __commonJSMin2(((exports) => {
148305
149027
  Object.defineProperty(exports, "__esModule", { value: true });
148306
149028
  const id_1 = require_id3();
148307
149029
  const ref_1 = require_ref3();
148308
- const core10 = [
149030
+ const core11 = [
148309
149031
  "$schema",
148310
149032
  "$id",
148311
149033
  "$defs",
@@ -148315,7 +149037,7 @@ var require_core$22 = /* @__PURE__ */ __commonJSMin2(((exports) => {
148315
149037
  id_1.default,
148316
149038
  ref_1.default
148317
149039
  ];
148318
- exports.default = core10;
149040
+ exports.default = core11;
148319
149041
  }));
148320
149042
  var require_limitNumber3 = /* @__PURE__ */ __commonJSMin2(((exports) => {
148321
149043
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -150453,7 +151175,7 @@ var require_json_schema_2019_092 = /* @__PURE__ */ __commonJSMin2(((exports) =>
150453
151175
  const metaSchema = require_schema$12();
150454
151176
  const applicator = require_applicator$12();
150455
151177
  const content = require_content$12();
150456
- const core10 = require_core$12();
151178
+ const core11 = require_core$12();
150457
151179
  const format2 = require_format4();
150458
151180
  const metadata = require_meta_data$12();
150459
151181
  const validation = require_validation$12();
@@ -150463,7 +151185,7 @@ var require_json_schema_2019_092 = /* @__PURE__ */ __commonJSMin2(((exports) =>
150463
151185
  metaSchema,
150464
151186
  applicator,
150465
151187
  content,
150466
- core10,
151188
+ core11,
150467
151189
  with$data(this, format2),
150468
151190
  metadata,
150469
151191
  with$data(this, validation)
@@ -150903,7 +151625,7 @@ var require_json_schema_2020_122 = /* @__PURE__ */ __commonJSMin2(((exports) =>
150903
151625
  const applicator = require_applicator3();
150904
151626
  const unevaluated = require_unevaluated2();
150905
151627
  const content = require_content2();
150906
- const core10 = require_core5();
151628
+ const core11 = require_core5();
150907
151629
  const format2 = require_format_annotation2();
150908
151630
  const metadata = require_meta_data2();
150909
151631
  const validation = require_validation3();
@@ -150914,7 +151636,7 @@ var require_json_schema_2020_122 = /* @__PURE__ */ __commonJSMin2(((exports) =>
150914
151636
  applicator,
150915
151637
  unevaluated,
150916
151638
  content,
150917
- core10,
151639
+ core11,
150918
151640
  with$data(this, format2),
150919
151641
  metadata,
150920
151642
  with$data(this, validation)
@@ -170875,123 +171597,8 @@ var pbkdf2Async = promisify(pbkdf2);
170875
171597
 
170876
171598
  // utils/browser.ts
170877
171599
  import { execFileSync as execFileSync4, spawnSync as spawnSync4 } from "node:child_process";
170878
- import { existsSync as existsSync4 } from "node:fs";
171600
+ import { existsSync as existsSync5 } from "node:fs";
170879
171601
  import { dirname as dirname2 } from "node:path";
170880
-
170881
- // utils/secrets.ts
170882
- var SENSITIVE_PATTERNS = [
170883
- /_KEY$/i,
170884
- /_SECRET$/i,
170885
- /_TOKEN$/i,
170886
- /_PASSWORD$/i,
170887
- /_CREDENTIAL$/i
170888
- ];
170889
- function isSensitiveEnvName(key) {
170890
- return SENSITIVE_PATTERNS.some((p) => p.test(key));
170891
- }
170892
- var SAFE_ENV_PREFIXES = ["GITHUB_", "RUNNER_", "JAVA_HOME_", "GOROOT_"];
170893
- var SAFE_ENV_NAMES = /* @__PURE__ */ new Set([
170894
- // system
170895
- "CI",
170896
- "HOME",
170897
- "LANG",
170898
- "LOGNAME",
170899
- "PATH",
170900
- "SHELL",
170901
- "SHLVL",
170902
- "TERM",
170903
- "TMPDIR",
170904
- "TZ",
170905
- "USER",
170906
- "XDG_CONFIG_HOME",
170907
- "XDG_RUNTIME_DIR",
170908
- "DEBIAN_FRONTEND",
170909
- // runner image toolchain
170910
- "ACCEPT_EULA",
170911
- "AGENT_TOOLSDIRECTORY",
170912
- "ANDROID_HOME",
170913
- "ANDROID_NDK",
170914
- "ANDROID_NDK_HOME",
170915
- "ANDROID_NDK_LATEST_HOME",
170916
- "ANDROID_NDK_ROOT",
170917
- "ANDROID_SDK_ROOT",
170918
- "ANT_HOME",
170919
- "AZURE_EXTENSION_DIR",
170920
- "BOOTSTRAP_HASKELL_NONINTERACTIVE",
170921
- "CHROME_BIN",
170922
- "CHROMEWEBDRIVER",
170923
- "CONDA",
170924
- "DOTNET_MULTILEVEL_LOOKUP",
170925
- "DOTNET_NOLOGO",
170926
- "DOTNET_SKIP_FIRST_TIME_EXPERIENCE",
170927
- "EDGEWEBDRIVER",
170928
- "GECKOWEBDRIVER",
170929
- "GHCUP_INSTALL_BASE_PREFIX",
170930
- "GRADLE_HOME",
170931
- "JAVA_HOME",
170932
- "HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS",
170933
- "HOMEBREW_NO_AUTO_UPDATE",
170934
- "ImageOS",
170935
- "ImageVersion",
170936
- "NVM_DIR",
170937
- "PIPX_BIN_DIR",
170938
- "PIPX_HOME",
170939
- "PSModulePath",
170940
- "SELENIUM_JAR_PATH",
170941
- "SGX_AESM_ADDR",
170942
- "SWIFT_PATH",
170943
- "VCPKG_INSTALLATION_ROOT"
170944
- ]);
170945
- var _userAllowlist = null;
170946
- function setEnvAllowlist(raw2) {
170947
- const names = raw2.split("\n").map((line) => line.trim()).filter(Boolean);
170948
- _userAllowlist = new Set(names);
170949
- }
170950
- function isSafeEnvVar(key) {
170951
- if (SAFE_ENV_NAMES.has(key)) return true;
170952
- return SAFE_ENV_PREFIXES.some((p) => key.startsWith(p));
170953
- }
170954
- var WORKFLOW_COMMAND_ENV_NAMES = [
170955
- "GITHUB_ENV",
170956
- "GITHUB_PATH",
170957
- "GITHUB_OUTPUT",
170958
- "GITHUB_STATE",
170959
- "GITHUB_STEP_SUMMARY",
170960
- // dropping the five paths above but keeping RUNNER_TEMP would be theatre:
170961
- // the file-command files live in `$RUNNER_TEMP/_runner_file_commands/`, so the
170962
- // directory is one `ls` away. nothing in the install path reads it (only our
170963
- // own leak-surface wipe in `setup.ts`, which runs in the parent), and package
170964
- // managers use TMPDIR.
170965
- "RUNNER_TEMP"
170966
- ];
170967
- function filterEnvForUntrustedCode() {
170968
- const env2 = filterEnv();
170969
- for (const name of WORKFLOW_COMMAND_ENV_NAMES) delete env2[name];
170970
- return env2;
170971
- }
170972
- function filterEnv() {
170973
- const filtered = {};
170974
- for (const [key, value2] of Object.entries(process.env)) {
170975
- if (value2 === void 0) continue;
170976
- const userAllowed = _userAllowlist?.has(key) ?? false;
170977
- if (isSensitiveEnvName(key) && !userAllowed) continue;
170978
- if (isSafeEnvVar(key) || userAllowed) {
170979
- filtered[key] = value2;
170980
- }
170981
- }
170982
- return filtered;
170983
- }
170984
- function resolveEnv(mode) {
170985
- if (mode === "inherit") {
170986
- return process.env;
170987
- }
170988
- if (mode === "restricted" || mode === void 0) {
170989
- return filterEnv();
170990
- }
170991
- return { ...filterEnv(), ...mode };
170992
- }
170993
-
170994
- // utils/browser.ts
170995
171602
  var CHROME_PATHS = ["/usr/bin/google-chrome-stable"];
170996
171603
  var systemChromePath;
170997
171604
  function findSystemChromePath() {
@@ -170999,7 +171606,7 @@ function findSystemChromePath() {
170999
171606
  return systemChromePath || void 0;
171000
171607
  }
171001
171608
  for (const p of CHROME_PATHS) {
171002
- if (existsSync4(p)) {
171609
+ if (existsSync5(p)) {
171003
171610
  systemChromePath = p;
171004
171611
  log.info(`found system chrome: ${p}`);
171005
171612
  return p;
@@ -171083,8 +171690,8 @@ function closeBrowserDaemon(toolState) {
171083
171690
 
171084
171691
  // mcp/checkout.ts
171085
171692
  import { createHash as createHash2 } from "node:crypto";
171086
- import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync8 } from "node:fs";
171087
- 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";
171088
171695
 
171089
171696
  // node_modules/.pnpm/@ark+schema@0.56.0/node_modules/@ark/schema/out/shared/errors.js
171090
171697
  var ArkError = class _ArkError extends CastableBase {
@@ -178556,8 +179163,8 @@ function ensureRepoState(toolState, init) {
178556
179163
 
178557
179164
  // utils/changeImpact.ts
178558
179165
  import { spawn as spawn2 } from "node:child_process";
178559
- import { writeFileSync as writeFileSync6 } from "node:fs";
178560
- import { join as join7 } from "node:path";
179166
+ import { writeFileSync as writeFileSync7 } from "node:fs";
179167
+ import { join as join8 } from "node:path";
178561
179168
  var MAX_CANDIDATES = 24;
178562
179169
  var MAX_ATOM_LENGTH = 128;
178563
179170
  var MAX_ATOMS = 12;
@@ -178904,8 +179511,8 @@ async function createChangeImpactArtifact(ctx, params) {
178904
179511
  headSha: params.headSha,
178905
179512
  lookupError: lookup2.error
178906
179513
  });
178907
- const path4 = join7(ctx.tmpdir, `pr-${params.pullNumber}-${params.headSha.slice(0, 7)}-impact.md`);
178908
- 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);
178909
179516
  return {
178910
179517
  path: path4,
178911
179518
  candidateCount: candidates.selected.length,
@@ -179157,8 +179764,8 @@ function readNumber(params) {
179157
179764
  // utils/gitAuth.ts
179158
179765
  import { execSync } from "node:child_process";
179159
179766
  import { createHash } from "node:crypto";
179160
- import { readFileSync as readFileSync2, realpathSync, unlinkSync } from "node:fs";
179161
- 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";
179162
179769
 
179163
179770
  // utils/shell.ts
179164
179771
  import { spawnSync as spawnSync5 } from "node:child_process";
@@ -179219,7 +179826,7 @@ var TRANSIENT_AUTH_PATTERNS = [
179219
179826
  ];
179220
179827
  var gitBinary;
179221
179828
  function hashFile(path4) {
179222
- return createHash("sha256").update(readFileSync2(path4)).digest("hex");
179829
+ return createHash("sha256").update(readFileSync3(path4)).digest("hex");
179223
179830
  }
179224
179831
  function resolveGit() {
179225
179832
  const whichPath = execSync("which git", { encoding: "utf-8" }).trim();
@@ -179248,7 +179855,7 @@ function resolveHooksDir(cwd, gitPath) {
179248
179855
  cwd,
179249
179856
  log: false
179250
179857
  }).trim();
179251
- const hooksDir = join8(commonDir, "hooks");
179858
+ const hooksDir = join9(commonDir, "hooks");
179252
179859
  hooksDirCache.set(cwd, hooksDir);
179253
179860
  return hooksDir;
179254
179861
  }
@@ -179555,7 +180162,7 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
179555
180162
  }
179556
180163
 
179557
180164
  // yes/index.ts
179558
- import { performance as performance7 } from "node:perf_hooks";
180165
+ import { performance as performance8 } from "node:perf_hooks";
179559
180166
  import { setTimeout as sleep2 } from "node:timers/promises";
179560
180167
 
179561
180168
  // node_modules/.pnpm/lru-cache@11.5.2/node_modules/lru-cache/dist/esm/node/index.min.js
@@ -180270,14 +180877,14 @@ function _op(fn2, options) {
180270
180877
  `failed to create cache key${namePrefix ? ` for ${options.name}` : ""}: ${errorMessage}`
180271
180878
  );
180272
180879
  }
180273
- const startTime = performance7.now();
180880
+ const startTime = performance8.now();
180274
180881
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
180275
180882
  log2.debug(`${namePrefix}[${timestamp}] cache lookup started`, { key });
180276
180883
  if (shouldCache && lruCache) {
180277
180884
  const cached4 = lruCache.get(key);
180278
180885
  if (cached4 !== void 0) {
180279
180886
  if (keyMap?.get(key) === void 0) keyMap?.set(key, input);
180280
- const runtime = performance7.now() - startTime;
180887
+ const runtime = performance8.now() - startTime;
180281
180888
  log2.debug(`${namePrefix}[${timestamp}] cache hit (from lru)`, {
180282
180889
  key,
180283
180890
  runtime: `${Math.round(runtime)}ms`
@@ -180296,12 +180903,12 @@ function _op(fn2, options) {
180296
180903
  defaultCacheMiss(key);
180297
180904
  const retries = options.retries ?? [];
180298
180905
  let lastError;
180299
- const execStartTime = performance7.now();
180906
+ const execStartTime = performance8.now();
180300
180907
  const promise2 = (async () => {
180301
180908
  for (let attempt = 0; attempt <= retries.length; attempt++) {
180302
180909
  try {
180303
180910
  const result = acceptsCtx ? await fn2(input, ctx) : await fn2(input);
180304
- const execRuntime2 = performance7.now() - execStartTime;
180911
+ const execRuntime2 = performance8.now() - execStartTime;
180305
180912
  const skip = options.skipCache ? options.skipCache(result) : false;
180306
180913
  if (shouldCache && lruCache && result !== null && result !== void 0 && !skip) {
180307
180914
  lruCache.set(key, result);
@@ -180354,7 +180961,7 @@ function _op(fn2, options) {
180354
180961
  }
180355
180962
  }
180356
180963
  }
180357
- const execRuntime = performance7.now() - execStartTime;
180964
+ const execRuntime = performance8.now() - execStartTime;
180358
180965
  log2.debug(`${namePrefix}[${timestamp}] function failed after all retries`, {
180359
180966
  key,
180360
180967
  runtime: `${Math.round(execRuntime)}ms`,
@@ -180419,13 +181026,13 @@ function _op(fn2, options) {
180419
181026
 
180420
181027
  // mcp/git.ts
180421
181028
  import { randomUUID as randomUUID3 } from "node:crypto";
180422
- import { writeFileSync as writeFileSync7 } from "node:fs";
180423
- import { join as join11 } from "node:path";
181029
+ import { writeFileSync as writeFileSync8 } from "node:fs";
181030
+ import { join as join12 } from "node:path";
180424
181031
 
180425
181032
  // utils/apiCommit.ts
180426
181033
  import { execFileSync as execFileSync5 } from "node:child_process";
180427
181034
  import { lstat, readlink } from "node:fs/promises";
180428
- import { join as join9 } from "node:path";
181035
+ import { join as join10 } from "node:path";
180429
181036
  var GITHUB_API = "https://api.github.com";
180430
181037
  var MAX_BLOB_BYTES = 30 * 1024 * 1024;
180431
181038
  var BLOB_UPLOAD_CONCURRENCY = 8;
@@ -180490,7 +181097,7 @@ async function assertApiCommittable(files) {
180490
181097
  const root = getRepoRoot();
180491
181098
  const attrs = $(
180492
181099
  "git",
180493
- ["check-attr", "filter", "-z", "--", ...present.map((p) => join9(root, p))],
181100
+ ["check-attr", "filter", "-z", "--", ...present.map((p) => join10(root, p))],
180494
181101
  { log: false }
180495
181102
  );
180496
181103
  const parts = attrs.split("\0");
@@ -180502,7 +181109,7 @@ async function assertApiCommittable(files) {
180502
181109
  }
180503
181110
  }
180504
181111
  for (const path4 of present) {
180505
- const stat = await lstat(join9(root, path4));
181112
+ const stat = await lstat(join10(root, path4));
180506
181113
  if (stat.isDirectory()) {
180507
181114
  throw new Error(
180508
181115
  `'${path4}' is a directory (nested repository or submodule?) \u2014 signed commits only support files and symlinks.`
@@ -180511,7 +181118,7 @@ async function assertApiCommittable(files) {
180511
181118
  }
180512
181119
  }
180513
181120
  async function createBlobEntry(params) {
180514
- const absPath = join9(params.repoRoot, params.path);
181121
+ const absPath = join10(params.repoRoot, params.path);
180515
181122
  const stat = await lstat(absPath);
180516
181123
  if (stat.size > MAX_BLOB_BYTES) {
180517
181124
  throw new Error(
@@ -180688,10 +181295,10 @@ this usually means your local branch has commits that were never pushed \u2014 s
180688
181295
  }
180689
181296
 
180690
181297
  // utils/github.ts
180691
- var core3 = __toESM(require_core(), 1);
181298
+ var core4 = __toESM(require_core(), 1);
180692
181299
  import { createSign } from "node:crypto";
180693
181300
  import { rename, writeFile } from "node:fs/promises";
180694
- import { dirname as dirname3, join as join10 } from "node:path";
181301
+ import { dirname as dirname3, join as join11 } from "node:path";
180695
181302
 
180696
181303
  // node_modules/.pnpm/@octokit+plugin-throttling@11.0.5_@octokit+core@7.0.7/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
180697
181304
  var import_light = __toESM(require_light(), 1);
@@ -184889,7 +185496,7 @@ async function fetchIdTokenFromStash(creds) {
184889
185496
  throw new Error("ID token response has no value field");
184890
185497
  }
184891
185498
  if (isGitHubActions) {
184892
- core3.setSecret(body.value);
185499
+ core4.setSecret(body.value);
184893
185500
  }
184894
185501
  return body.value;
184895
185502
  }
@@ -184902,7 +185509,7 @@ function translateIdTokenError(error52) {
184902
185509
  var mintIdToken = op(
184903
185510
  async () => {
184904
185511
  try {
184905
- return await core3.getIDToken(OIDC_AUDIENCE);
185512
+ return await core4.getIDToken(OIDC_AUDIENCE);
184906
185513
  } catch (error52) {
184907
185514
  throw translateIdTokenError(error52);
184908
185515
  }
@@ -185111,7 +185718,7 @@ function getGitHubUsageSummary() {
185111
185718
  }
185112
185719
  async function writeGitHubUsageSummaryToFile(path4) {
185113
185720
  const summary2 = getGitHubUsageSummary();
185114
- const tmpPath = join10(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
185721
+ const tmpPath = join11(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
185115
185722
  await writeFile(tmpPath, JSON.stringify(summary2));
185116
185723
  await rename(tmpPath, path4);
185117
185724
  }
@@ -186263,8 +186870,8 @@ function countAhead(head, base, cwd) {
186263
186870
  function spillGitOutput(params) {
186264
186871
  const tempDir = process.env.PULLFROG_TEMP_DIR;
186265
186872
  if (!tempDir) throw new Error("PULLFROG_TEMP_DIR not set");
186266
- const outputPath = join11(tempDir, `git-${params.command}-${randomUUID3().slice(0, 8)}.txt`);
186267
- writeFileSync7(outputPath, params.output);
186873
+ const outputPath = join12(tempDir, `git-${params.command}-${randomUUID3().slice(0, 8)}.txt`);
186874
+ writeFileSync8(outputPath, params.output);
186268
186875
  const previewByLines = params.output.split("\n").slice(0, OVERFLOW_PREVIEW_LINES).join("\n");
186269
186876
  const preview = previewByLines.length <= OVERFLOW_PREVIEW_MAX_CHARS ? previewByLines : `${previewByLines.slice(0, OVERFLOW_PREVIEW_MAX_CHARS)}\u2026`;
186270
186877
  log.info(
@@ -187778,9 +188385,9 @@ async function reportReviewNodeId(ctx, params) {
187778
188385
  import { execFileSync as execFileSync6, execSync as execSync2 } from "node:child_process";
187779
188386
  import { mkdtempSync, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
187780
188387
  import { tmpdir as tmpdir2 } from "node:os";
187781
- import { join as join12 } from "node:path";
188388
+ import { join as join13 } from "node:path";
187782
188389
  function createTempDirectory() {
187783
- const sharedTempDir = mkdtempSync(join12(tmpdir2(), "pullfrog-"));
188390
+ const sharedTempDir = mkdtempSync(join13(tmpdir2(), "pullfrog-"));
187784
188391
  process.env.PULLFROG_TEMP_DIR = sharedTempDir;
187785
188392
  log.info(`\xBB created temp dir at ${sharedTempDir}`);
187786
188393
  return sharedTempDir;
@@ -187825,13 +188432,13 @@ function wipeRunnerLeakSurface() {
187825
188432
  return [];
187826
188433
  }
187827
188434
  };
187828
- const fileCommandsDir = join12(runnerTemp, "_runner_file_commands");
188435
+ const fileCommandsDir = join13(runnerTemp, "_runner_file_commands");
187829
188436
  for (const entry of listDir(fileCommandsDir)) {
187830
- tryUnlink(join12(fileCommandsDir, entry));
188437
+ tryUnlink(join13(fileCommandsDir, entry));
187831
188438
  }
187832
188439
  for (const entry of listDir(runnerTemp)) {
187833
188440
  if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
187834
- tryUnlink(join12(runnerTemp, entry));
188441
+ tryUnlink(join13(runnerTemp, entry));
187835
188442
  }
187836
188443
  }
187837
188444
  if (wiped.length > 0) {
@@ -188381,11 +188988,11 @@ function CheckoutPrTool(ctx) {
188381
188988
  headSha: checkoutSha
188382
188989
  });
188383
188990
  if (incremental.status === "ok") {
188384
- incrementalDiffPath = join13(
188991
+ incrementalDiffPath = join14(
188385
188992
  tempDir,
188386
188993
  `pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
188387
188994
  );
188388
- writeFileSync8(incrementalDiffPath, incremental.diff);
188995
+ writeFileSync9(incrementalDiffPath, incremental.diff);
188389
188996
  log.info(
188390
188997
  `\xBB incremental diff computed (${incremental.diff.length} bytes) \u2192 ${incrementalDiffPath}`
188391
188998
  );
@@ -188398,8 +189005,8 @@ function CheckoutPrTool(ctx) {
188398
189005
  const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
188399
189006
  log.debug(`formatted diff preview (first 100 lines):
188400
189007
  ${diffPreview}`);
188401
- const diffPath = join13(tempDir, `pr-${pull_number}-${headShort}.diff`);
188402
- writeFileSync8(diffPath, formatResult.content);
189008
+ const diffPath = join14(tempDir, `pr-${pull_number}-${headShort}.diff`);
189009
+ writeFileSync9(diffPath, formatResult.content);
188403
189010
  log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
188404
189011
  primary.diffCoverage = createDiffCoverageState({
188405
189012
  diffPath,
@@ -188556,8 +189163,8 @@ ${dirty}`
188556
189163
  }
188557
189164
 
188558
189165
  // mcp/checkSuite.ts
188559
- import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync9 } from "node:fs";
188560
- 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";
188561
189168
  var GetCheckSuiteLogs = type({
188562
189169
  check_suite_id: type.number.describe("the id from check_suite.id")
188563
189170
  });
@@ -188653,8 +189260,8 @@ function GetCheckSuiteLogsTool(ctx) {
188653
189260
  if (!tempDir) {
188654
189261
  throw new Error("PULLFROG_TEMP_DIR not set");
188655
189262
  }
188656
- const logsDir = join14(tempDir, "ci-logs");
188657
- mkdirSync7(logsDir, { recursive: true });
189263
+ const logsDir = join15(tempDir, "ci-logs");
189264
+ mkdirSync8(logsDir, { recursive: true });
188658
189265
  const jobResults = [];
188659
189266
  for (const run of failedRuns) {
188660
189267
  const jobs = await ctx.octokit.paginate(ctx.octokit.rest.actions.listJobsForWorkflowRun, {
@@ -188680,8 +189287,8 @@ function GetCheckSuiteLogsTool(ctx) {
188680
189287
  );
188681
189288
  }
188682
189289
  const logsText = await logsResult.text();
188683
- const logPath = join14(logsDir, `job-${job.id}.log`);
188684
- writeFileSync9(logPath, logsText);
189290
+ const logPath = join15(logsDir, `job-${job.id}.log`);
189291
+ writeFileSync10(logPath, logsText);
188685
189292
  const analysis = analyzeLog(logsText, 80);
188686
189293
  const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
188687
189294
  jobResults.push({
@@ -188729,8 +189336,8 @@ function GetCheckSuiteLogsTool(ctx) {
188729
189336
  }
188730
189337
 
188731
189338
  // mcp/commitInfo.ts
188732
- import { writeFileSync as writeFileSync10 } from "node:fs";
188733
- import { join as join15 } from "node:path";
189339
+ import { writeFileSync as writeFileSync11 } from "node:fs";
189340
+ import { join as join16 } from "node:path";
188734
189341
  var CommitInfo = type({
188735
189342
  sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
188736
189343
  });
@@ -188754,8 +189361,8 @@ function CommitInfoTool(ctx) {
188754
189361
  "PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
188755
189362
  );
188756
189363
  }
188757
- const diffFile = join15(tempDir, `commit-${sha.slice(0, 7)}.diff`);
188758
- writeFileSync10(diffFile, formatResult.content);
189364
+ const diffFile = join16(tempDir, `commit-${sha.slice(0, 7)}.diff`);
189365
+ writeFileSync11(diffFile, formatResult.content);
188759
189366
  log.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
188760
189367
  return {
188761
189368
  sha: data.sha,
@@ -188888,17 +189495,17 @@ function ReopenCurrentTool(ctx) {
188888
189495
  }
188889
189496
 
188890
189497
  // prep/index.ts
188891
- import { performance as performance8 } from "node:perf_hooks";
189498
+ import { performance as performance9 } from "node:perf_hooks";
188892
189499
 
188893
189500
  // prep/installNodeDependencies.ts
188894
- import { existsSync as existsSync6 } from "node:fs";
188895
- import { join as join17 } from "node:path";
189501
+ import { existsSync as existsSync7 } from "node:fs";
189502
+ import { join as join18 } from "node:path";
188896
189503
 
188897
189504
  // utils/packageManager.ts
188898
189505
  var import_semver2 = __toESM(require_semver2(), 1);
188899
- import { existsSync as existsSync5 } from "node:fs";
189506
+ import { existsSync as existsSync6 } from "node:fs";
188900
189507
  import { mkdir, readFile as readFile2 } from "node:fs/promises";
188901
- import { delimiter, join as join16 } from "node:path";
189508
+ import { delimiter, join as join17 } from "node:path";
188902
189509
  var SUPPORTED_NAMES = ["npm", "pnpm", "yarn", "bun"];
188903
189510
  var COREPACK_MANAGED = ["pnpm", "yarn"];
188904
189511
  function isSupported(name) {
@@ -188936,8 +189543,8 @@ function parseDevEnginesField(field) {
188936
189543
  };
188937
189544
  }
188938
189545
  async function resolvePackageManagerSpec(cwd) {
188939
- const pkgPath = join16(cwd, "package.json");
188940
- if (!existsSync5(pkgPath)) return null;
189546
+ const pkgPath = join17(cwd, "package.json");
189547
+ if (!existsSync6(pkgPath)) return null;
188941
189548
  let pkg;
188942
189549
  try {
188943
189550
  pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
@@ -188995,7 +189602,7 @@ async function currentVersion(name) {
188995
189602
  return result.stdout.trim();
188996
189603
  }
188997
189604
  function packageManagerBinDir(tmpdir3) {
188998
- return join16(tmpdir3, "pm-bin");
189605
+ return join17(tmpdir3, "pm-bin");
188999
189606
  }
189000
189607
  async function ensurePackageManager(params) {
189001
189608
  const spec = params.spec;
@@ -189058,7 +189665,7 @@ async function installFallback(name, installSpec) {
189058
189665
  });
189059
189666
  if (result.exitCode !== 0) return result.stderr || `failed to install ${name}`;
189060
189667
  if (name === "deno") {
189061
- const denoPath = join16(process.env.HOME || "", ".deno", "bin");
189668
+ const denoPath = join17(process.env.HOME || "", ".deno", "bin");
189062
189669
  process.env.PATH = `${denoPath}:${process.env.PATH}`;
189063
189670
  }
189064
189671
  log.info(`\xBB installed ${name}`);
@@ -189091,8 +189698,8 @@ async function isCommandAvailable2(command) {
189091
189698
  var installNodeDependencies = {
189092
189699
  name: "installNodeDependencies",
189093
189700
  shouldRun: () => {
189094
- const packageJsonPath = join17(process.cwd(), "package.json");
189095
- return existsSync6(packageJsonPath);
189701
+ const packageJsonPath = join18(process.cwd(), "package.json");
189702
+ return existsSync7(packageJsonPath);
189096
189703
  },
189097
189704
  run: async (options) => {
189098
189705
  const declared = await resolvePackageManagerSpec(process.cwd());
@@ -189186,14 +189793,14 @@ ${errorMessage}`]
189186
189793
  };
189187
189794
 
189188
189795
  // prep/installPythonDependencies.ts
189189
- import { existsSync as existsSync7, readFileSync as readFileSync3 } from "node:fs";
189190
- 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";
189191
189798
  function declaresBuildSystem(path4) {
189192
- return /^\s*\[\s*build-system\s*\]/m.test(readFileSync3(path4, "utf8"));
189799
+ return /^\s*\[\s*build-system\s*\]/m.test(readFileSync4(path4, "utf8"));
189193
189800
  }
189194
189801
  function configApplies(config3, cwd) {
189195
- const path4 = join18(cwd, config3.file);
189196
- return existsSync7(path4) && (config3.applies?.(path4) ?? true);
189802
+ const path4 = join19(cwd, config3.file);
189803
+ return existsSync8(path4) && (config3.applies?.(path4) ?? true);
189197
189804
  }
189198
189805
  var PYTHON_CONFIGS = [
189199
189806
  {
@@ -189347,7 +189954,7 @@ var installPythonDependencies = {
189347
189954
  var prepSteps = [installNodeDependencies, installPythonDependencies];
189348
189955
  async function runPrepPhase(options) {
189349
189956
  log.debug("\xBB starting prep phase...");
189350
- const startTime = performance8.now();
189957
+ const startTime = performance9.now();
189351
189958
  const results = [];
189352
189959
  const preDirty = await dirtyTrackedPaths();
189353
189960
  try {
@@ -189369,7 +189976,7 @@ async function runPrepPhase(options) {
189369
189976
  } finally {
189370
189977
  await restoreDirtiedSince({ before: preDirty, actor: "prep" });
189371
189978
  }
189372
- const totalDurationMs = performance8.now() - startTime;
189979
+ const totalDurationMs = performance9.now() - startTime;
189373
189980
  log.debug(`\xBB prep phase completed (${Math.round(totalDurationMs)}ms)`);
189374
189981
  return results;
189375
189982
  }
@@ -189514,15 +190121,15 @@ function AwaitDependencyInstallationTool(ctx) {
189514
190121
  }
189515
190122
 
189516
190123
  // mcp/gh.ts
189517
- import { mkdirSync as mkdirSync8 } from "node:fs";
189518
- import { join as join20 } from "node:path";
190124
+ import { mkdirSync as mkdirSync9 } from "node:fs";
190125
+ import { join as join21 } from "node:path";
189519
190126
 
189520
190127
  // mcp/shell.ts
189521
190128
  import { spawn as spawn3, spawnSync as spawnSync6 } from "node:child_process";
189522
190129
  import { randomUUID as randomUUID4 } from "node:crypto";
189523
- import { closeSync, openSync, writeFileSync as writeFileSync11 } from "node:fs";
190130
+ import { closeSync, openSync, writeFileSync as writeFileSync12 } from "node:fs";
189524
190131
  import { userInfo as userInfo2 } from "node:os";
189525
- import { join as join19 } from "node:path";
190132
+ import { join as join20 } from "node:path";
189526
190133
  import { setTimeout as sleep3 } from "node:timers/promises";
189527
190134
  var ShellParams = type({
189528
190135
  command: "string",
@@ -189785,8 +190392,8 @@ ${spawnError}` : spawnError;
189785
190392
  }
189786
190393
  function capOutput(output) {
189787
190394
  if (output.length <= MAX_OUTPUT_CHARS) return output;
189788
- const fullPath = join19(getTempDir(), `shell-${randomUUID4().slice(0, 8)}.log`);
189789
- writeFileSync11(fullPath, output);
190395
+ const fullPath = join20(getTempDir(), `shell-${randomUUID4().slice(0, 8)}.log`);
190396
+ writeFileSync12(fullPath, output);
189790
190397
  const elided = output.length - MAX_OUTPUT_CHARS;
189791
190398
  return `... [${elided} chars truncated; full output saved to ${fullPath}] ...
189792
190399
  ${output.slice(-MAX_OUTPUT_CHARS)}`;
@@ -189840,8 +190447,8 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
189840
190447
  if (params.background) {
189841
190448
  const tempDir = getTempDir();
189842
190449
  const handle = `bg-${randomUUID4().slice(0, 8)}`;
189843
- const outputPath = join19(tempDir, `${handle}.log`);
189844
- const pidPath = join19(tempDir, `${handle}.pid`);
190450
+ const outputPath = join20(tempDir, `${handle}.log`);
190451
+ const pidPath = join20(tempDir, `${handle}.pid`);
189845
190452
  const logFd = openSync(outputPath, "a");
189846
190453
  let proc;
189847
190454
  try {
@@ -189858,7 +190465,7 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
189858
190465
  throw new Error("failed to start background process");
189859
190466
  }
189860
190467
  proc.unref();
189861
- writeFileSync11(pidPath, `${proc.pid}
190468
+ writeFileSync12(pidPath, `${proc.pid}
189862
190469
  `);
189863
190470
  ctx.toolState.backgroundProcesses.set(handle, { pid: proc.pid, outputPath, pidPath });
189864
190471
  return {
@@ -189938,8 +190545,8 @@ function GhTool(ctx) {
189938
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.`,
189939
190546
  parameters: GhParams,
189940
190547
  execute: execute(async (params) => {
189941
- const configDir = join20(ctx.tmpdir, "gh-config");
189942
- mkdirSync8(configDir, { recursive: true });
190548
+ const configDir = join21(ctx.tmpdir, "gh-config");
190549
+ mkdirSync9(configDir, { recursive: true });
189943
190550
  const command = ["gh", ...params.args.map(shellQuote)].join(" ");
189944
190551
  const result = await runSandboxed({
189945
190552
  command,
@@ -190662,8 +191269,8 @@ function PullRequestInfoTool(ctx) {
190662
191269
  }
190663
191270
 
190664
191271
  // mcp/reviewComments.ts
190665
- import { writeFileSync as writeFileSync12 } from "node:fs";
190666
- import { join as join21 } from "node:path";
191272
+ import { writeFileSync as writeFileSync13 } from "node:fs";
191273
+ import { join as join22 } from "node:path";
190667
191274
  var COMMENTS_PER_THREAD = 50;
190668
191275
  var REVIEW_THREADS_QUERY = `
190669
191276
  query ($owner: String!, $name: String!, $prNumber: Int!, $cursor: String) {
@@ -191136,8 +191743,8 @@ function GetReviewCommentsTool(ctx) {
191136
191743
  throw new Error("PULLFROG_TEMP_DIR not set");
191137
191744
  }
191138
191745
  const filename = `review-${params.review_id}-threads.md`;
191139
- const commentsPath = join21(tempDir, filename);
191140
- writeFileSync12(commentsPath, formatted.content);
191746
+ const commentsPath = join22(tempDir, filename);
191747
+ writeFileSync13(commentsPath, formatted.content);
191141
191748
  log.debug(`wrote ${threadBlocks.length} threads to ${commentsPath}`);
191142
191749
  return {
191143
191750
  review_id: params.review_id,
@@ -191470,8 +192077,8 @@ function UploadFileTool(ctx) {
191470
192077
  }
191471
192078
 
191472
192079
  // mcp/xrepo.ts
191473
- import { mkdirSync as mkdirSync9, rmSync as rmSync2 } from "node:fs";
191474
- 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";
191475
192082
  function assertValidRepoName(name) {
191476
192083
  if (!/^[A-Za-z0-9._-]+$/.test(name) || name.startsWith("-") || name.includes("..")) {
191477
192084
  throw new Error(
@@ -191551,8 +192158,8 @@ function CheckoutRepoTool(ctx) {
191551
192158
  return { path: existing.dir, access: existing.access, note: "already checked out." };
191552
192159
  }
191553
192160
  const access = accessFor(ctx, repo);
191554
- const dir = join22(ctx.tmpdir, "xrepo", repo);
191555
- mkdirSync9(dir, { recursive: true });
192161
+ const dir = join23(ctx.tmpdir, "xrepo", repo);
192162
+ mkdirSync10(dir, { recursive: true });
191556
192163
  const state = ensureRepoState(ctx.toolState, { owner, name: repo, dir, access });
191557
192164
  const rc = resolveRepoCtx(ctx, repo);
191558
192165
  try {
@@ -191800,6 +192407,9 @@ function hasEnvVar2(name) {
191800
192407
  function hasClaudeCodeAuth() {
191801
192408
  return hasEnvVar2("CLAUDE_CODE_OAUTH_TOKEN") || hasEnvVar2("ANTHROPIC_API_KEY");
191802
192409
  }
192410
+ function hasCodexAuth() {
192411
+ return hasEnvVar2("CODEX_AUTH_JSON") || hasEnvVar2("OPENAI_API_KEY");
192412
+ }
191803
192413
  function hasBedrockAuth() {
191804
192414
  return hasEnvVar2("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar2("AWS_ACCESS_KEY_ID") && hasEnvVar2("AWS_SECRET_ACCESS_KEY");
191805
192415
  }
@@ -191826,6 +192436,15 @@ function resolveSlug(slug2) {
191826
192436
  }
191827
192437
  return vertexId;
191828
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
+ }
191829
192448
  if (alias?.routing === "openai-compatible") {
191830
192449
  const modelId = process.env[OPENAI_COMPATIBLE_MODEL_ENV]?.trim();
191831
192450
  if (!modelId) {
@@ -191864,6 +192483,7 @@ function resolveAgent(ctx) {
191864
192483
  }
191865
192484
  log.warning(`\xBB unknown PULLFROG_AGENT="${envAgent}" \u2014 falling through to auto-select`);
191866
192485
  }
192486
+ if (ctx.proxyModel) return agents.opencode;
191867
192487
  if (ctx.model && hasBedrockAuth() && process.env[BEDROCK_MODEL_ID_ENV]?.trim() === ctx.model) {
191868
192488
  return isBedrockAnthropicId(ctx.model) ? agents.claude : agents.opencode;
191869
192489
  }
@@ -191873,12 +192493,14 @@ function resolveAgent(ctx) {
191873
192493
  if (ctx.model) {
191874
192494
  try {
191875
192495
  const provider2 = getModelProvider(ctx.model);
191876
- if (provider2 === "anthropic" && hasClaudeCodeAuth()) {
191877
- return agents.claude;
191878
- }
192496
+ if (provider2 === "anthropic" && hasClaudeCodeAuth()) return agents.claude;
192497
+ if (provider2 === "openai" && ctx.codexAgent && hasCodexAuth()) return agents.codex;
191879
192498
  } catch {
191880
192499
  }
191881
192500
  }
192501
+ if (!ctx.model && ctx.codexAgent && hasCodexAuth() && !hasClaudeCodeAuth()) {
192502
+ return agents.codex;
192503
+ }
191882
192504
  return agents.opencode;
191883
192505
  }
191884
192506
 
@@ -191976,10 +192598,37 @@ auto-compaction is disabled, so long runs grow until your endpoint refuses them.
191976
192598
 
191977
192599
  for full setup instructions, see https://docs.pullfrog.com/openai-compatible`;
191978
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
+ }
191979
192624
  function hasEnvVar3(name) {
191980
192625
  const value2 = process.env[name];
191981
192626
  return typeof value2 === "string" && value2.length > 0;
191982
192627
  }
192628
+ function modelHasRuntimeAuth(model) {
192629
+ const authVars = [...getModelEnvVars(model), ...getModelManagedCredentials(model)];
192630
+ return authVars.length === 0 || authVars.some(hasEnvVar3);
192631
+ }
191983
192632
  function hasPositiveNumberEnvVar(name) {
191984
192633
  return Number(process.env[name]) > 0;
191985
192634
  }
@@ -191998,6 +192647,17 @@ function validateOpenAICompatibleSetup(params) {
191998
192647
  );
191999
192648
  }
192000
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
+ }
192001
192661
  function validateBedrockSetup(params) {
192002
192662
  const hasAuth = hasEnvVar3("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar3("AWS_ACCESS_KEY_ID") && hasEnvVar3("AWS_SECRET_ACCESS_KEY");
192003
192663
  const missing = [];
@@ -192021,6 +192681,12 @@ function validateVertexSetup(params) {
192021
192681
  throw new Error(buildVertexSetupError({ owner: params.owner, name: params.name, missing }));
192022
192682
  }
192023
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
+ }
192024
192690
  function validateAgentApiKey(params) {
192025
192691
  if (params.model) {
192026
192692
  const alias = resolveDisplayAlias(params.model);
@@ -192036,6 +192702,10 @@ function validateAgentApiKey(params) {
192036
192702
  validateOpenAICompatibleSetup({ owner: params.owner, name: params.name });
192037
192703
  return;
192038
192704
  }
192705
+ if (params.model.startsWith(`${AZURE_PROVIDER}/`)) {
192706
+ validateAzureSetup({ owner: params.owner, name: params.name });
192707
+ return;
192708
+ }
192039
192709
  if (!params.model.includes("/")) {
192040
192710
  if (process.env[VERTEX_MODEL_ID_ENV]?.trim() === params.model) {
192041
192711
  validateVertexSetup({ owner: params.owner, name: params.name });
@@ -192059,7 +192729,7 @@ function validateAgentApiKey(params) {
192059
192729
  })
192060
192730
  );
192061
192731
  }
192062
- if (hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN")) return;
192732
+ if (hasSingleProviderAuth(params.agent.name)) return;
192063
192733
  throw new Error(
192064
192734
  buildKeyError({
192065
192735
  owner: params.owner,
@@ -192071,7 +192741,7 @@ function validateAgentApiKey(params) {
192071
192741
  );
192072
192742
  }
192073
192743
  if (params.agent.name === "opencode") {
192074
- if (params.authorized.size > 0) return;
192744
+ if ([...params.authorized].some(modelHasRuntimeAuth)) return;
192075
192745
  const reason = getModelsFailure();
192076
192746
  if (reason) throw new Error(reason);
192077
192747
  throw new Error(
@@ -192083,7 +192753,7 @@ function validateAgentApiKey(params) {
192083
192753
  })
192084
192754
  );
192085
192755
  }
192086
- if (hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN")) return;
192756
+ if (hasSingleProviderAuth(params.agent.name)) return;
192087
192757
  throw new Error(
192088
192758
  buildKeyError({
192089
192759
  owner: params.owner,
@@ -192456,9 +193126,9 @@ async function checkConfiguredCredentials(params) {
192456
193126
 
192457
193127
  // utils/gitAuthServer.ts
192458
193128
  import { randomUUID as randomUUID5 } from "node:crypto";
192459
- import { writeFileSync as writeFileSync13 } from "node:fs";
193129
+ import { writeFileSync as writeFileSync14 } from "node:fs";
192460
193130
  import { createServer as createServer3 } from "node:http";
192461
- import { join as join23 } from "node:path";
193131
+ import { join as join24 } from "node:path";
192462
193132
  var REVOKED_TRAP_MS = 6e4;
192463
193133
  function revokeGitHubToken(token) {
192464
193134
  fetch("https://api.github.com/installation/token", {
@@ -192527,7 +193197,7 @@ async function startGitAuthServer(tmpdir3) {
192527
193197
  function writeAskpassScript(code) {
192528
193198
  const scriptId = randomUUID5();
192529
193199
  const scriptName = `askpass-${scriptId}.js`;
192530
- const scriptPath = join23(tmpdir3, scriptName);
193200
+ const scriptPath = join24(tmpdir3, scriptName);
192531
193201
  const content = [
192532
193202
  `#!/usr/bin/env node`,
192533
193203
  `var a=process.argv[2]||"";`,
@@ -192540,7 +193210,7 @@ async function startGitAuthServer(tmpdir3) {
192540
193210
  `r.on("end",function(){process.stdout.write(d+"\\n")})`,
192541
193211
  `}).on("error",function(){process.exit(1)})}`
192542
193212
  ].join("\n");
192543
- writeFileSync13(scriptPath, content, { mode: 448 });
193213
+ writeFileSync14(scriptPath, content, { mode: 448 });
192544
193214
  return scriptPath;
192545
193215
  }
192546
193216
  async function close() {
@@ -192979,7 +193649,7 @@ function resolveInstructions(ctx) {
192979
193649
 
192980
193650
  // utils/learnings.ts
192981
193651
  import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
192982
- import { dirname as dirname4, join as join24 } from "node:path";
193652
+ import { dirname as dirname4, join as join25 } from "node:path";
192983
193653
 
192984
193654
  // utils/learningsTruncate.ts
192985
193655
  var MAX_LEARNINGS_LENGTH = 1e5;
@@ -192997,10 +193667,10 @@ function truncateAtLineBoundary(body, cap) {
192997
193667
  var LEARNINGS_FILE_NAME = "pullfrog-learnings.md";
192998
193668
  var XREPO_LEARNINGS_FILE_NAME = "pullfrog-xrepo-learnings.md";
192999
193669
  function learningsFilePath(tmpdir3) {
193000
- return join24(tmpdir3, LEARNINGS_FILE_NAME);
193670
+ return join25(tmpdir3, LEARNINGS_FILE_NAME);
193001
193671
  }
193002
193672
  function xrepoLearningsFilePath(tmpdir3) {
193003
- return join24(tmpdir3, XREPO_LEARNINGS_FILE_NAME);
193673
+ return join25(tmpdir3, XREPO_LEARNINGS_FILE_NAME);
193004
193674
  }
193005
193675
  async function seedLearningsFile(params) {
193006
193676
  const path4 = learningsFilePath(params.tmpdir);
@@ -193116,7 +193786,7 @@ async function persistXrepoLearnings(ctx) {
193116
193786
  // utils/modelAccess.ts
193117
193787
  function decideModelAccess(input) {
193118
193788
  if (!input.modelExplicit || !input.model) return { kind: "ok" };
193119
- 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}/`));
193120
193790
  if (input.proxyActive) {
193121
193791
  const target = resolveOpenRouterModel(input.model);
193122
193792
  if (input.oss) {
@@ -193161,7 +193831,7 @@ function buildModelAccessError(input) {
193161
193831
  }
193162
193832
 
193163
193833
  // utils/normalizeEnv.ts
193164
- var core4 = __toESM(require_core(), 1);
193834
+ var core5 = __toESM(require_core(), 1);
193165
193835
  function hasControlCharacter(value2) {
193166
193836
  return [...value2].some((ch) => {
193167
193837
  const code = ch.charCodeAt(0);
@@ -193187,7 +193857,7 @@ function sanitizeSecret(key, value2) {
193187
193857
  `\xBB stripped whitespace from ${key} (whitespace in secret values breaks GitHub Actions log masking)`
193188
193858
  );
193189
193859
  }
193190
- core4.setSecret(trimmed);
193860
+ core5.setSecret(trimmed);
193191
193861
  return trimmed;
193192
193862
  }
193193
193863
  function normalizeEnv() {
@@ -193231,7 +193901,7 @@ function normalizeEnv() {
193231
193901
  }
193232
193902
 
193233
193903
  // utils/overrides.ts
193234
- var core5 = __toESM(require_core(), 1);
193904
+ var core6 = __toESM(require_core(), 1);
193235
193905
  var DENIED_OVERRIDE_NAMES = /* @__PURE__ */ new Set([
193236
193906
  "GITHUB_TOKEN",
193237
193907
  "GH_TOKEN",
@@ -193277,7 +193947,7 @@ function applyOverrides(params) {
193277
193947
  denied.push(key);
193278
193948
  continue;
193279
193949
  }
193280
- if (value2.length > 0) core5.setSecret(value2);
193950
+ if (value2.length > 0) core6.setSecret(value2);
193281
193951
  params.env[key] = value2;
193282
193952
  applied.push(key);
193283
193953
  }
@@ -193286,8 +193956,8 @@ function applyOverrides(params) {
193286
193956
  }
193287
193957
 
193288
193958
  // utils/payload.ts
193289
- var core6 = __toESM(require_core(), 1);
193290
- import { readFileSync as readFileSync5 } from "node:fs";
193959
+ var core7 = __toESM(require_core(), 1);
193960
+ import { readFileSync as readFileSync6 } from "node:fs";
193291
193961
  import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
193292
193962
 
193293
193963
  // utils/versioning.ts
@@ -193374,8 +194044,8 @@ function resolveCwd(cwd) {
193374
194044
  return workspace ? resolve2(workspace, cwd) : cwd;
193375
194045
  }
193376
194046
  function resolvePromptInput() {
193377
- const promptInput = core6.getInput("prompt");
193378
- const promptFile = core6.getInput("prompt_file");
194047
+ const promptInput = core7.getInput("prompt");
194048
+ const promptFile = core7.getInput("prompt_file");
193379
194049
  if (promptInput && promptFile) {
193380
194050
  throw new Error("set exactly one of 'prompt' or 'prompt_file' inputs, not both.");
193381
194051
  }
@@ -193401,7 +194071,7 @@ function resolvePromptInput() {
193401
194071
  function resolvePromptFile(input) {
193402
194072
  const workspace = process.env.GITHUB_WORKSPACE;
193403
194073
  const path4 = isAbsolute2(input) ? input : workspace ? resolve2(workspace, input) : resolve2(input);
193404
- const content = readFileSync5(path4, "utf-8");
194074
+ const content = readFileSync6(path4, "utf-8");
193405
194075
  if (!content.trim()) {
193406
194076
  throw new Error(`prompt_file ${JSON.stringify(input)} is empty.`);
193407
194077
  }
@@ -193409,7 +194079,7 @@ function resolvePromptFile(input) {
193409
194079
  }
193410
194080
  function warnIfDeprecatedStatusChecks(value2) {
193411
194081
  if (value2 !== void 0) {
193412
- core6.warning(
194082
+ core7.warning(
193413
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."
193414
194084
  );
193415
194085
  }
@@ -193417,15 +194087,15 @@ function warnIfDeprecatedStatusChecks(value2) {
193417
194087
  }
193418
194088
  function resolveNonPromptInputs() {
193419
194089
  return Inputs.omit("prompt", "prompt_file").assert({
193420
- model: core6.getInput("model") || void 0,
193421
- effort: core6.getInput("effort") || void 0,
193422
- debug: core6.getInput("debug") || void 0,
193423
- timeout: core6.getInput("timeout") || void 0,
193424
- cwd: core6.getInput("cwd") || void 0,
193425
- push: core6.getInput("push") || void 0,
193426
- shell: core6.getInput("shell") || void 0,
193427
- status_checks: warnIfDeprecatedStatusChecks(core6.getInput("status_checks") || void 0),
193428
- 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
193429
194099
  });
193430
194100
  }
193431
194101
  function resolvePayload(resolvedPromptInput, repoSettings) {
@@ -193493,7 +194163,7 @@ function resolvePayload(resolvedPromptInput, repoSettings) {
193493
194163
  };
193494
194164
  }
193495
194165
  function resolveOutputSchema() {
193496
- const raw2 = core6.getInput("output_schema");
194166
+ const raw2 = core7.getInput("output_schema");
193497
194167
  if (!raw2) return void 0;
193498
194168
  let parsed2;
193499
194169
  try {
@@ -193509,10 +194179,10 @@ function resolveOutputSchema() {
193509
194179
  }
193510
194180
 
193511
194181
  // utils/proxy.ts
193512
- var core8 = __toESM(require_core(), 1);
194182
+ var core9 = __toESM(require_core(), 1);
193513
194183
 
193514
194184
  // utils/token.ts
193515
- var core7 = __toESM(require_core(), 1);
194185
+ var core8 = __toESM(require_core(), 1);
193516
194186
  import assert2 from "node:assert/strict";
193517
194187
  var mcpTokenValue;
193518
194188
  var refreshMcpTokenFn;
@@ -193520,7 +194190,7 @@ function getMcpTokenRefresh() {
193520
194190
  return refreshMcpTokenFn;
193521
194191
  }
193522
194192
  function getJobToken() {
193523
- const inputToken = core7.getInput("token");
194193
+ const inputToken = core8.getInput("token");
193524
194194
  if (inputToken) {
193525
194195
  return inputToken;
193526
194196
  }
@@ -193536,7 +194206,7 @@ async function resolveTokens(params) {
193536
194206
  if (externalToken) {
193537
194207
  mcpTokenValue = externalToken;
193538
194208
  if (isGitHubActions) {
193539
- core7.setSecret(externalToken);
194209
+ core8.setSecret(externalToken);
193540
194210
  }
193541
194211
  log.info("\xBB using external GH_TOKEN for both git and MCP");
193542
194212
  return {
@@ -193556,7 +194226,7 @@ async function resolveTokens(params) {
193556
194226
  const gitPermissions = params.push === "disabled" ? { contents: "read" } : { contents: "write", workflows: "write" };
193557
194227
  const gitToken = await acquireNewToken({ repos: writeRepos, permissions: gitPermissions });
193558
194228
  if (isGitHubActions) {
193559
- core7.setSecret(gitToken);
194229
+ core8.setSecret(gitToken);
193560
194230
  }
193561
194231
  log.info(
193562
194232
  `\xBB acquired git token (${Object.entries(gitPermissions).map((e) => e.join(":")).join(", ")})`
@@ -193573,7 +194243,7 @@ async function resolveTokens(params) {
193573
194243
  };
193574
194244
  const mcpToken = await acquireNewToken({ repos: writeRepos, permissions: mcpPermissions });
193575
194245
  if (isGitHubActions) {
193576
- core7.setSecret(mcpToken);
194246
+ core8.setSecret(mcpToken);
193577
194247
  }
193578
194248
  log.info(
193579
194249
  `\xBB acquired scoped MCP token (${Object.entries(mcpPermissions).map((e) => e.join(":")).join(", ")})`
@@ -193582,7 +194252,7 @@ async function resolveTokens(params) {
193582
194252
  let ghToken;
193583
194253
  if (ghPermissions) {
193584
194254
  ghToken = await acquireNewToken({ permissions: ghPermissions });
193585
- if (isGitHubActions) core7.setSecret(ghToken);
194255
+ if (isGitHubActions) core8.setSecret(ghToken);
193586
194256
  log.info(
193587
194257
  `\xBB acquired gh token mirroring ${params.authorPermission} (${formatPermissions(ghPermissions)})`
193588
194258
  );
@@ -193593,7 +194263,7 @@ async function resolveTokens(params) {
193593
194263
  repos: params.xrepo.read,
193594
194264
  permissions: { contents: "read" }
193595
194265
  });
193596
- if (isGitHubActions) core7.setSecret(readToken);
194266
+ if (isGitHubActions) core8.setSecret(readToken);
193597
194267
  log.info(`\xBB acquired cross-repo read token (contents:read, ${params.xrepo.read.length} repos)`);
193598
194268
  }
193599
194269
  mcpTokenValue = mcpToken;
@@ -193612,7 +194282,7 @@ async function resolveTokens(params) {
193612
194282
  oidc: params.oidc ?? void 0
193613
194283
  }).then((fresh) => {
193614
194284
  if (isGitHubActions) {
193615
- core7.setSecret(fresh);
194285
+ core8.setSecret(fresh);
193616
194286
  }
193617
194287
  mcpTokenValue = fresh;
193618
194288
  currentMcpToken = fresh;
@@ -193633,7 +194303,7 @@ async function resolveTokens(params) {
193633
194303
  permissions: gitPermissions,
193634
194304
  oidc: params.oidc ?? void 0
193635
194305
  }).then((fresh) => {
193636
- if (isGitHubActions) core7.setSecret(fresh);
194306
+ if (isGitHubActions) core8.setSecret(fresh);
193637
194307
  void revokeGitHubInstallationToken(currentGitToken);
193638
194308
  currentGitToken = fresh;
193639
194309
  log.warning("\xBB GitHub rejected the git token; re-acquired a fresh git token");
@@ -193650,7 +194320,7 @@ async function resolveTokens(params) {
193650
194320
  permissions: { contents: "read" },
193651
194321
  oidc: params.oidc ?? void 0
193652
194322
  }).then((fresh) => {
193653
- if (isGitHubActions) core7.setSecret(fresh);
194323
+ if (isGitHubActions) core8.setSecret(fresh);
193654
194324
  void revokeGitHubInstallationToken(read2);
193655
194325
  currentReadToken = fresh;
193656
194326
  log.warning("\xBB GitHub rejected the read token; re-acquired a fresh read token");
@@ -193864,7 +194534,7 @@ async function resolveProxyModel(ctx) {
193864
194534
  });
193865
194535
  if (!key) return;
193866
194536
  process.env.OPENROUTER_API_KEY = key;
193867
- core8.setSecret(key);
194537
+ core9.setSecret(key);
193868
194538
  ctx.payload.proxyModel = ctx.proxyModel;
193869
194539
  ctx.toolState.model = ctx.proxyModel;
193870
194540
  const label = ctx.oss ? "oss" : "router";
@@ -193935,7 +194605,7 @@ async function runProxyResolution(ctx) {
193935
194605
 
193936
194606
  // utils/prSummary.ts
193937
194607
  import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
193938
- import { dirname as dirname5, join as join25 } from "node:path";
194608
+ import { dirname as dirname5, join as join26 } from "node:path";
193939
194609
  var SUMMARY_FILE_NAME = "pullfrog-summary.md";
193940
194610
  var SUMMARY_SCAFFOLD = `# PR summary
193941
194611
 
@@ -193945,7 +194615,7 @@ var SUMMARY_SCAFFOLD = `# PR summary
193945
194615
  var MIN_SNAPSHOT_LENGTH = 60;
193946
194616
  var MAX_SNAPSHOT_LENGTH = 32768;
193947
194617
  function summaryFilePath(tmpdir3) {
193948
- return join25(tmpdir3, SUMMARY_FILE_NAME);
194618
+ return join26(tmpdir3, SUMMARY_FILE_NAME);
193949
194619
  }
193950
194620
  async function seedSummaryFile(params) {
193951
194621
  const path4 = summaryFilePath(params.tmpdir);
@@ -194074,6 +194744,7 @@ var defaultSettings = {
194074
194744
  shell: "restricted",
194075
194745
  prApproveEnabled: false,
194076
194746
  autoMergeEnabled: false,
194747
+ codexAgent: false,
194077
194748
  signedCommits: false,
194078
194749
  repoIntelligence: false,
194079
194750
  progressComments: true,
@@ -194562,7 +195233,7 @@ ${genericBody}`,
194562
195233
  }
194563
195234
 
194564
195235
  // utils/runLifecycle.ts
194565
- var core9 = __toESM(require_core(), 1);
195236
+ var core10 = __toESM(require_core(), 1);
194566
195237
 
194567
195238
  // utils/autoMerge.ts
194568
195239
  function hasBlockingHumanReview(reviews) {
@@ -194948,7 +195619,7 @@ async function finalizeSuccessRun(input) {
194948
195619
  }
194949
195620
  if (input.toolState.output) {
194950
195621
  log.info(`::pullfrog-output::${Buffer.from(input.toolState.output).toString("base64")}`);
194951
- core9.setOutput("result", input.toolState.output);
195622
+ core10.setOutput("result", input.toolState.output);
194952
195623
  }
194953
195624
  if (input.result.success) {
194954
195625
  await approveAfterFix(input.toolContext).catch((error52) => {
@@ -195412,7 +196083,11 @@ async function main() {
195412
196083
  }
195413
196084
  const resolvedModel = credentials.kind === "fellBack" ? credentials.replacement : configuredModel;
195414
196085
  vertexCredentials = materializeVertexCredentials({ model: resolvedModel });
195415
- const agent2 = resolveAgent({ model: resolvedModel });
196086
+ const agent2 = resolveAgent({
196087
+ model: resolvedModel,
196088
+ proxyModel: payload.proxyModel,
196089
+ codexAgent: runContext.repoSettings.codexAgent
196090
+ });
195416
196091
  const effectiveModel = payload.proxyModel ?? resolvedModel ?? payload.model;
195417
196092
  toolState.model = effectiveModel;
195418
196093
  if (!payload.proxyModel) {
@@ -195596,8 +196271,8 @@ ${instructions.user}` : null,
195596
196271
  log.info(instructions.full);
195597
196272
  });
195598
196273
  if (agentId === "opencode") {
195599
- const pluginDir = join26(process.cwd(), ".opencode", "plugin");
195600
- 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));
195601
196276
  if (hasPlugins && toolState.dependencyInstallation?.promise) {
195602
196277
  log.info(
195603
196278
  "\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"