pullfrog 0.1.57 → 0.1.60

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:
@@ -104390,6 +104420,11 @@ var DEFAULT_ACTIVITY_TIMEOUT_MS = 3e5;
104390
104420
  var AGENT_ACTIVITY_TIMEOUT_MS = 9e5;
104391
104421
  var AGENT_FIRST_EVENT_TIMEOUT_MS = 12e4;
104392
104422
  var DEFAULT_ACTIVITY_CHECK_INTERVAL_MS = 5e3;
104423
+ function watchdogBudgetMs(compiled, envVar) {
104424
+ const raw2 = Number(process.env[envVar]);
104425
+ if (!Number.isFinite(raw2) || raw2 <= 0) return compiled;
104426
+ return Math.min(compiled, raw2);
104427
+ }
104393
104428
  var DEBUG_TS_PREFIX = /^(?:\[\d{4}-\d{2}-\d{2}T[^\]]+\]\s+)?/.source;
104394
104429
  var ACTIVITY_NOISE_PATTERNS = [
104395
104430
  new RegExp(`${DEBUG_TS_PREFIX}\\[mcp-proxy\\]`),
@@ -104861,7 +104896,8 @@ import { pipeline } from "node:stream/promises";
104861
104896
  async function installFromNpmTarball(params) {
104862
104897
  const tempDir = process.env.PULLFROG_TEMP_DIR;
104863
104898
  if (!tempDir) throw new Error("PULLFROG_TEMP_DIR is not set");
104864
- const extractedDir = join(tempDir, "package");
104899
+ const packageRoot = join(tempDir, `npm-${params.packageName.replace(/[^a-z0-9]+/gi, "-")}`);
104900
+ const extractedDir = join(packageRoot, "package");
104865
104901
  const cliPath = join(extractedDir, params.executablePath);
104866
104902
  if (existsSync2(cliPath)) {
104867
104903
  log.debug(`\xBB using cached binary at ${cliPath}`);
@@ -104887,7 +104923,8 @@ async function installFromNpmTarball(params) {
104887
104923
  }
104888
104924
  }
104889
104925
  log.debug(`\xBB installing ${params.packageName}@${resolvedVersion}...`);
104890
- const tarballPath = join(tempDir, "package.tgz");
104926
+ mkdirSync(packageRoot, { recursive: true });
104927
+ const tarballPath = join(packageRoot, "package.tgz");
104891
104928
  const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org";
104892
104929
  let tarballUrl;
104893
104930
  if (params.packageName.startsWith("@")) {
@@ -104907,7 +104944,7 @@ async function installFromNpmTarball(params) {
104907
104944
  await pipeline(response.body, fileStream);
104908
104945
  log.debug(`\xBB downloaded tarball to ${tarballPath}`);
104909
104946
  log.debug(`\xBB extracting tarball...`);
104910
- const extractResult = spawnSync("tar", ["-xzf", tarballPath, "-C", tempDir], {
104947
+ const extractResult = spawnSync("tar", ["-xzf", tarballPath, "-C", packageRoot], {
104911
104948
  stdio: "pipe",
104912
104949
  encoding: "utf-8"
104913
104950
  });
@@ -104916,9 +104953,6 @@ async function installFromNpmTarball(params) {
104916
104953
  `Failed to extract tarball: ${extractResult.stderr || extractResult.stdout || "Unknown error"}`
104917
104954
  );
104918
104955
  }
104919
- if (!existsSync2(cliPath)) {
104920
- throw new Error(`Executable not found in extracted package at ${cliPath}`);
104921
- }
104922
104956
  if (params.installDependencies) {
104923
104957
  log.debug(`\xBB installing dependencies for ${params.packageName}...`);
104924
104958
  const installResult = spawnSync("npm", ["install", "--production"], {
@@ -104939,6 +104973,9 @@ async function installFromNpmTarball(params) {
104939
104973
  }
104940
104974
  log.debug(`\xBB dependencies installed`);
104941
104975
  }
104976
+ if (!existsSync2(cliPath)) {
104977
+ throw new Error(`Executable not found in extracted package at ${cliPath}`);
104978
+ }
104942
104979
  chmodSync(cliPath, 493);
104943
104980
  log.debug(`\xBB ${params.packageName} installed at ${cliPath}`);
104944
104981
  return cliPath;
@@ -105112,6 +105149,7 @@ function resolveRunAlias(ctx) {
105112
105149
  }
105113
105150
  const model = ctx.resolvedModel;
105114
105151
  if (!model) return void 0;
105152
+ if (model.startsWith(`${AZURE_PROVIDER}/`)) return void 0;
105115
105153
  return modelAliases.find((a) => !a.fallback && a.resolve === model) ?? matchHostedAnthropicAlias(model);
105116
105154
  }
105117
105155
  function ossEffortFloor(ctx) {
@@ -105150,7 +105188,7 @@ var import_semver = __toESM(require_semver2(), 1);
105150
105188
  // package.json
105151
105189
  var package_default = {
105152
105190
  name: "pullfrog",
105153
- version: "0.1.57",
105191
+ version: "0.1.60",
105154
105192
  type: "module",
105155
105193
  bin: {
105156
105194
  pullfrog: "dist/cli.mjs",
@@ -105182,6 +105220,8 @@ var package_default = {
105182
105220
  "@clack/prompts": "^1.2.0",
105183
105221
  "@modelcontextprotocol/sdk": "^1.26.0",
105184
105222
  "@opencode-ai/sdk": "1.18.5",
105223
+ "@openai/codex": "0.147.0",
105224
+ "@openai/codex-sdk": "0.147.0",
105185
105225
  "@octokit/plugin-throttling": "^11.0.3",
105186
105226
  "@octokit/rest": "^22.0.0",
105187
105227
  "@octokit/webhooks-types": "^7.6.1",
@@ -107257,7 +107297,7 @@ var claude = agent({
107257
107297
  if ((isBedrockRoute || isVertexRoute2) && specifier && effort.alias?.effort) {
107258
107298
  applyHostedEffortCapabilities({ env: env2, modelId: specifier, levels: effort.alias.effort });
107259
107299
  }
107260
- if (env2.CLAUDE_CODE_OAUTH_TOKEN && !isBedrockRoute && env2.ANTHROPIC_API_KEY) {
107300
+ if (env2.CLAUDE_CODE_OAUTH_TOKEN && !isBedrockRoute && !isVertexRoute2 && !env2.ANTHROPIC_BASE_URL && !env2.ANTHROPIC_AUTH_TOKEN && env2.ANTHROPIC_API_KEY) {
107261
107301
  const preflight = await preflightClaudeSubscription({
107262
107302
  token: env2.CLAUDE_CODE_OAUTH_TOKEN,
107263
107303
  model
@@ -107303,13 +107343,782 @@ var claude = agent({
107303
107343
  }
107304
107344
  });
107305
107345
 
107306
- // agents/opencode.ts
107346
+ // agents/codex.ts
107307
107347
  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";
107348
+ import { existsSync as existsSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync2, writeFileSync as writeFileSync5 } from "node:fs";
107310
107349
  import { join as join6 } from "node:path";
107311
107350
  import { performance as performance6 } from "node:perf_hooks";
107312
107351
 
107352
+ // utils/codexHome.ts
107353
+ import { execFileSync as execFileSync3 } from "node:child_process";
107354
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
107355
+ import { homedir as homedir2, userInfo } from "node:os";
107356
+ import { join as join5 } from "node:path";
107357
+
107358
+ // utils/codexOAuth.ts
107359
+ function decodeJwtExpMs(token) {
107360
+ const parts = token.split(".");
107361
+ if (parts.length !== 3) return null;
107362
+ let payload;
107363
+ try {
107364
+ payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
107365
+ } catch {
107366
+ return null;
107367
+ }
107368
+ if (typeof payload.exp !== "number" || !Number.isFinite(payload.exp)) return null;
107369
+ return payload.exp * 1e3;
107370
+ }
107371
+ function parseCodexAuthBody(raw2) {
107372
+ let parsed2;
107373
+ try {
107374
+ parsed2 = JSON.parse(raw2);
107375
+ } catch {
107376
+ return null;
107377
+ }
107378
+ if (!parsed2 || typeof parsed2 !== "object") return null;
107379
+ const v = parsed2;
107380
+ if (v.auth_mode !== "chatgpt") return null;
107381
+ const tokens = v.tokens;
107382
+ if (!tokens || typeof tokens !== "object") return null;
107383
+ const t = tokens;
107384
+ if (typeof t.access_token !== "string" || t.access_token.length === 0) return null;
107385
+ if (typeof t.refresh_token !== "string" || t.refresh_token.length === 0) return null;
107386
+ return {
107387
+ auth_mode: "chatgpt",
107388
+ ...typeof v.refresh_rejected_at === "string" ? { refresh_rejected_at: v.refresh_rejected_at } : {},
107389
+ tokens: {
107390
+ access_token: t.access_token,
107391
+ refresh_token: t.refresh_token,
107392
+ ...typeof t.id_token === "string" ? { id_token: t.id_token } : {},
107393
+ ...typeof t.account_id === "string" ? { account_id: t.account_id } : {}
107394
+ },
107395
+ ...typeof v.last_refresh === "string" ? { last_refresh: v.last_refresh } : {}
107396
+ };
107397
+ }
107398
+ function stringifyCodexAuthBody(body) {
107399
+ return `${JSON.stringify(body, null, 2)}
107400
+ `;
107401
+ }
107402
+
107403
+ // utils/codexHome.ts
107404
+ var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
107405
+ var PULLFROG_DATA_DIR = "/var/lib/pullfrog";
107406
+ function isRejectedChain(body) {
107407
+ if (!body.refresh_rejected_at) return false;
107408
+ log.warning(
107409
+ `\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.`
107410
+ );
107411
+ return true;
107412
+ }
107413
+ function installCodexAuth() {
107414
+ const raw2 = process.env[CODEX_AUTH_ENV];
107415
+ if (!raw2) return null;
107416
+ const body = parseCodexAuthBody(raw2);
107417
+ if (!body) {
107418
+ log.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
107419
+ return null;
107420
+ }
107421
+ if (isRejectedChain(body)) return null;
107422
+ const expiresMs = decodeJwtExpMs(body.tokens.access_token) ?? 0;
107423
+ const xdgDataHome = resolveDataHome();
107424
+ const opencodeDir = join5(xdgDataHome, "opencode");
107425
+ const authPath = join5(opencodeDir, "auth.json");
107426
+ const opencodeAuth = {
107427
+ openai: {
107428
+ type: "oauth",
107429
+ refresh: body.tokens.refresh_token,
107430
+ access: body.tokens.access_token,
107431
+ expires: expiresMs,
107432
+ ...body.tokens.account_id ? { accountId: body.tokens.account_id } : {}
107433
+ }
107434
+ };
107435
+ mkdirSync5(opencodeDir, { recursive: true });
107436
+ writeFileSync4(authPath, `${JSON.stringify(opencodeAuth, null, 2)}
107437
+ `, { mode: 384 });
107438
+ process.env.XDG_DATA_HOME = xdgDataHome;
107439
+ log.info(`\xBB installed Codex auth at ${authPath}`);
107440
+ return {
107441
+ authPath,
107442
+ xdgDataHome,
107443
+ originalRefresh: body.tokens.refresh_token,
107444
+ originalIdToken: body.tokens.id_token
107445
+ };
107446
+ }
107447
+ function installCodexHome() {
107448
+ const raw2 = process.env[CODEX_AUTH_ENV];
107449
+ if (!raw2) return null;
107450
+ const body = parseCodexAuthBody(raw2);
107451
+ if (!body) {
107452
+ log.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
107453
+ return null;
107454
+ }
107455
+ if (isRejectedChain(body)) return null;
107456
+ if (!body.tokens.id_token) {
107457
+ log.warning(
107458
+ `\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.`
107459
+ );
107460
+ return null;
107461
+ }
107462
+ const codexHome = join5(resolveDataHome(), "codex");
107463
+ const authPath = join5(codexHome, "auth.json");
107464
+ mkdirSync5(codexHome, { recursive: true });
107465
+ writeFileSync4(authPath, stringifyCodexAuthBody(body), { mode: 384 });
107466
+ log.info(`\xBB installed Codex auth at ${authPath}`);
107467
+ return { codexHome, authPath, originalRefresh: body.tokens.refresh_token };
107468
+ }
107469
+ function resolveDataHome() {
107470
+ if (process.env.CI !== "true") return join5(homedir2(), ".local", "share");
107471
+ bootstrapPullfrogDataDir();
107472
+ return PULLFROG_DATA_DIR;
107473
+ }
107474
+ function bootstrapPullfrogDataDir() {
107475
+ const user = userInfo().username;
107476
+ let primaryGroup;
107477
+ try {
107478
+ primaryGroup = execFileSync3("id", ["-gn", user], { stdio: "pipe", encoding: "utf-8" }).trim();
107479
+ } catch {
107480
+ primaryGroup = user;
107481
+ }
107482
+ try {
107483
+ execFileSync3("sudo", ["-n", "mkdir", "-p", PULLFROG_DATA_DIR], { stdio: "pipe" });
107484
+ execFileSync3("sudo", ["-n", "chown", `${user}:${primaryGroup}`, PULLFROG_DATA_DIR], {
107485
+ stdio: "pipe"
107486
+ });
107487
+ execFileSync3("sudo", ["-n", "chmod", "700", PULLFROG_DATA_DIR], { stdio: "pipe" });
107488
+ } catch (err) {
107489
+ throw new Error(
107490
+ `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.`
107491
+ );
107492
+ }
107493
+ }
107494
+
107495
+ // utils/secrets.ts
107496
+ var SENSITIVE_PATTERNS = [
107497
+ /_KEY$/i,
107498
+ /_SECRET$/i,
107499
+ /_TOKEN$/i,
107500
+ /_PASSWORD$/i,
107501
+ /_CREDENTIAL$/i
107502
+ ];
107503
+ function isSensitiveEnvName(key) {
107504
+ return SENSITIVE_PATTERNS.some((p) => p.test(key));
107505
+ }
107506
+ var SAFE_ENV_PREFIXES = ["GITHUB_", "RUNNER_", "JAVA_HOME_", "GOROOT_"];
107507
+ var SAFE_ENV_NAMES = /* @__PURE__ */ new Set([
107508
+ // system
107509
+ "CI",
107510
+ "HOME",
107511
+ "LANG",
107512
+ "LOGNAME",
107513
+ "PATH",
107514
+ "SHELL",
107515
+ "SHLVL",
107516
+ "TERM",
107517
+ "TMPDIR",
107518
+ "TZ",
107519
+ "USER",
107520
+ "XDG_CONFIG_HOME",
107521
+ "XDG_RUNTIME_DIR",
107522
+ "DEBIAN_FRONTEND",
107523
+ // runner image toolchain
107524
+ "ACCEPT_EULA",
107525
+ "AGENT_TOOLSDIRECTORY",
107526
+ "ANDROID_HOME",
107527
+ "ANDROID_NDK",
107528
+ "ANDROID_NDK_HOME",
107529
+ "ANDROID_NDK_LATEST_HOME",
107530
+ "ANDROID_NDK_ROOT",
107531
+ "ANDROID_SDK_ROOT",
107532
+ "ANT_HOME",
107533
+ "AZURE_EXTENSION_DIR",
107534
+ "BOOTSTRAP_HASKELL_NONINTERACTIVE",
107535
+ "CHROME_BIN",
107536
+ "CHROMEWEBDRIVER",
107537
+ "CONDA",
107538
+ "DOTNET_MULTILEVEL_LOOKUP",
107539
+ "DOTNET_NOLOGO",
107540
+ "DOTNET_SKIP_FIRST_TIME_EXPERIENCE",
107541
+ "EDGEWEBDRIVER",
107542
+ "GECKOWEBDRIVER",
107543
+ "GHCUP_INSTALL_BASE_PREFIX",
107544
+ "GRADLE_HOME",
107545
+ "JAVA_HOME",
107546
+ "HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS",
107547
+ "HOMEBREW_NO_AUTO_UPDATE",
107548
+ "ImageOS",
107549
+ "ImageVersion",
107550
+ "NVM_DIR",
107551
+ "PIPX_BIN_DIR",
107552
+ "PIPX_HOME",
107553
+ "PSModulePath",
107554
+ "SELENIUM_JAR_PATH",
107555
+ "SGX_AESM_ADDR",
107556
+ "SWIFT_PATH",
107557
+ "VCPKG_INSTALLATION_ROOT"
107558
+ ]);
107559
+ var _userAllowlist = null;
107560
+ function setEnvAllowlist(raw2) {
107561
+ const names = raw2.split("\n").map((line) => line.trim()).filter(Boolean);
107562
+ _userAllowlist = new Set(names);
107563
+ }
107564
+ function isSafeEnvVar(key) {
107565
+ if (SAFE_ENV_NAMES.has(key)) return true;
107566
+ return SAFE_ENV_PREFIXES.some((p) => key.startsWith(p));
107567
+ }
107568
+ var WORKFLOW_COMMAND_ENV_NAMES = [
107569
+ "GITHUB_ENV",
107570
+ "GITHUB_PATH",
107571
+ "GITHUB_OUTPUT",
107572
+ "GITHUB_STATE",
107573
+ "GITHUB_STEP_SUMMARY",
107574
+ // dropping the five paths above but keeping RUNNER_TEMP would be theatre:
107575
+ // the file-command files live in `$RUNNER_TEMP/_runner_file_commands/`, so the
107576
+ // directory is one `ls` away. nothing in the install path reads it (only our
107577
+ // own leak-surface wipe in `setup.ts`, which runs in the parent), and package
107578
+ // managers use TMPDIR.
107579
+ "RUNNER_TEMP"
107580
+ ];
107581
+ function filterEnvForUntrustedCode() {
107582
+ const env2 = filterEnv();
107583
+ for (const name of WORKFLOW_COMMAND_ENV_NAMES) delete env2[name];
107584
+ return env2;
107585
+ }
107586
+ function filterEnv() {
107587
+ const filtered = {};
107588
+ for (const [key, value2] of Object.entries(process.env)) {
107589
+ if (value2 === void 0) continue;
107590
+ const userAllowed = _userAllowlist?.has(key) ?? false;
107591
+ if (isSensitiveEnvName(key) && !userAllowed) continue;
107592
+ if (isSafeEnvVar(key) || userAllowed) {
107593
+ filtered[key] = value2;
107594
+ }
107595
+ }
107596
+ return filtered;
107597
+ }
107598
+ function resolveEnv(mode) {
107599
+ if (mode === "inherit") {
107600
+ return process.env;
107601
+ }
107602
+ if (mode === "restricted" || mode === void 0) {
107603
+ return filterEnv();
107604
+ }
107605
+ return { ...filterEnv(), ...mode };
107606
+ }
107607
+
107608
+ // agents/codex.ts
107609
+ var CODEX_TARGETS = {
107610
+ "linux-x64": "x86_64-unknown-linux-musl",
107611
+ "linux-arm64": "aarch64-unknown-linux-musl",
107612
+ "darwin-x64": "x86_64-apple-darwin",
107613
+ "darwin-arm64": "aarch64-apple-darwin",
107614
+ "win32-x64": "x86_64-pc-windows-msvc",
107615
+ "win32-arm64": "aarch64-pc-windows-msvc"
107616
+ };
107617
+ async function installCodexCli() {
107618
+ const target = CODEX_TARGETS[`${process.platform}-${process.arch}`];
107619
+ if (!target) {
107620
+ throw new Error(`the codex CLI ships no binary for ${process.platform}-${process.arch}`);
107621
+ }
107622
+ const binary = process.platform === "win32" ? "codex.exe" : "codex";
107623
+ return await installFromNpmTarball({
107624
+ packageName: "@openai/codex",
107625
+ version: getDevDependencyVersion("@openai/codex"),
107626
+ executablePath: `node_modules/@openai/codex-${process.platform}-${process.arch}/vendor/${target}/bin/${binary}`,
107627
+ installDependencies: true
107628
+ });
107629
+ }
107630
+ var CODEX_DISABLED_FEATURES = [
107631
+ // browser / desktop control — network reach and arbitrary JS
107632
+ "browser_use",
107633
+ "browser_use_external",
107634
+ "browser_use_full_cdp_access",
107635
+ "computer_use",
107636
+ "in_app_browser",
107637
+ // third-party code loaded into the agent process
107638
+ "plugins",
107639
+ "remote_plugin",
107640
+ "plugin_sharing",
107641
+ "skill_mcp_dependency_install",
107642
+ // lifecycle hooks run commands sourced from repo config, which a PR can edit
107643
+ "hooks",
107644
+ // connectors
107645
+ "apps",
107646
+ // ungated subagent fan-out (see the file header)
107647
+ "multi_agent",
107648
+ "multi_agent_v2"
107649
+ ];
107650
+ var CODEX_SHELL_FEATURES = ["shell_tool", "unified_exec"];
107651
+ function tomlBool(entries, value2) {
107652
+ return entries.map((name) => `${name} = ${value2}`).join("\n");
107653
+ }
107654
+ function securityOverrideFlags(params) {
107655
+ const shellEnabled = params.ctx.payload.shell === "enabled";
107656
+ const features = [
107657
+ ...CODEX_DISABLED_FEATURES.map((name) => `${name}=false`),
107658
+ ...CODEX_SHELL_FEATURES.map((name) => `${name}=${shellEnabled}`)
107659
+ ];
107660
+ return [
107661
+ "-c",
107662
+ `approval_policy=${JSON.stringify("never")}`,
107663
+ "-c",
107664
+ `sandbox_mode=${JSON.stringify(params.sandboxMode)}`,
107665
+ // the sandbox's EXTENT has to be locked too, not just its mode: a repo
107666
+ // setting `writable_roots = ["/"]` walked straight out of the checkout with
107667
+ // `sandbox_mode` already flag-locked (measured — apply_patch wrote a file
107668
+ // under $HOME).
107669
+ "-c",
107670
+ `sandbox_workspace_write.writable_roots=[${JSON.stringify(params.repoDir)}]`,
107671
+ "-c",
107672
+ "sandbox_workspace_write.network_access=true",
107673
+ // our own MCP server, for a third time in the same class: a repo pointing
107674
+ // `mcp_servers.pullfrog.url` at its own endpoint (measured — `codex mcp get`
107675
+ // reported the repo's URL) turns every pullfrog tool call into an
107676
+ // exfiltration channel, carrying diffs, file contents and cross-repo
107677
+ // learnings to whoever it names.
107678
+ "-c",
107679
+ `mcp_servers.${pullfrogMcpName}.url=${JSON.stringify(params.mcpServerUrl)}`,
107680
+ "-c",
107681
+ `mcp_servers.${pullfrogMcpName}.${APPROVAL_MODE_KEY}=${JSON.stringify("approve")}`,
107682
+ "-c",
107683
+ `mcp_servers.${pullfrogMcpName}.required=true`,
107684
+ "-c",
107685
+ `mcp_servers.${pullfrogMcpName}.tool_timeout_sec=660`,
107686
+ // `enabled` closes the silent-DoS variant: a repo setting it false would
107687
+ // otherwise strip every GitHub tool and leave the agent narrating failure.
107688
+ "-c",
107689
+ `mcp_servers.${pullfrogMcpName}.enabled=true`,
107690
+ // not a boundary, but the same override: a repo silently re-pointing the
107691
+ // model spends the account's money on a pick it never made, and makes the
107692
+ // "Using `…`" run footer a lie about what actually ran.
107693
+ ...params.model ? ["-c", `model=${JSON.stringify(params.model)}`] : [],
107694
+ ...params.effortRung ? ["-c", `model_reasoning_effort=${JSON.stringify(params.effortRung)}`] : [],
107695
+ ...features.flatMap((entry) => ["-c", `features.${entry}`])
107696
+ ];
107697
+ }
107698
+ var CODEX_EFFORTS = ["minimal", "low", "medium", "high", "xhigh"];
107699
+ function resolveCodexModel(ctx) {
107700
+ const specifier = ctx.payload.proxyModel ?? ctx.resolvedModel;
107701
+ if (!specifier) return void 0;
107702
+ try {
107703
+ if (getModelProvider(specifier) === "openai") return stripProviderPrefix(specifier);
107704
+ } catch {
107705
+ }
107706
+ log.warning(`\xBB ${specifier} isn't an OpenAI model \u2014 running codex on its own default instead`);
107707
+ return void 0;
107708
+ }
107709
+ var APPROVAL_MODE_KEY = "default_tools_approval_mode";
107710
+ var APPROVE_TOOLS = `${APPROVAL_MODE_KEY} = "approve"`;
107711
+ function repoMcpApprovals(repoDir) {
107712
+ const path4 = join6(repoDir, ".codex", "config.toml");
107713
+ if (!existsSync4(path4)) return [];
107714
+ const out = [];
107715
+ let section = [];
107716
+ let isServerTable = false;
107717
+ function flush() {
107718
+ if (section.length === 0) return;
107719
+ const declared = section.some((line) => line.trimStart().startsWith(APPROVAL_MODE_KEY));
107720
+ if (isServerTable && !declared) section.splice(1, 0, APPROVE_TOOLS);
107721
+ out.push(...section, "");
107722
+ section = [];
107723
+ }
107724
+ for (const line of readFileSync2(path4, "utf8").split("\n")) {
107725
+ const header = line.trim().match(/^\[(?<key>[^\]]+)\]$/);
107726
+ if (!header?.groups) {
107727
+ if (section.length > 0) section.push(line);
107728
+ continue;
107729
+ }
107730
+ flush();
107731
+ const key = header.groups.key.trim();
107732
+ if (!key.startsWith("mcp_servers.")) continue;
107733
+ const name = key.slice("mcp_servers.".length).replace(/^"([^"]*)"/, "$1");
107734
+ if (name === pullfrogMcpName || name.startsWith(`${pullfrogMcpName}.`)) continue;
107735
+ isServerTable = !name.includes(".");
107736
+ section = [line.trim()];
107737
+ }
107738
+ flush();
107739
+ return out;
107740
+ }
107741
+ function warnIfNativeEditsUnavailable(ctx) {
107742
+ if (process.platform !== "linux" || ctx.payload.shell !== "disabled") return;
107743
+ log.warning(
107744
+ "\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."
107745
+ );
107746
+ }
107747
+ function writeCodexConfig(params) {
107748
+ const shellEnabled = params.ctx.payload.shell === "enabled";
107749
+ const sandboxMode = params.ctx.payload.push === "disabled" ? "read-only" : "workspace-write";
107750
+ const repoDir = process.cwd();
107751
+ const config3 = [
107752
+ "# written by pullfrog",
107753
+ // "never" keeps the run non-interactive WITHOUT
107754
+ // --dangerously-bypass-approvals-and-sandbox, which would also switch the
107755
+ // sandbox off and let the native apply_patch tool write anywhere.
107756
+ 'approval_policy = "never"',
107757
+ `sandbox_mode = ${JSON.stringify(sandboxMode)}`,
107758
+ // matches opencode's `webfetch: "allow"` — research is part of the job and
107759
+ // neither harness gates it on a permission.
107760
+ 'web_search = "live"',
107761
+ ...params.model ? [`model = ${JSON.stringify(params.model)}`] : [],
107762
+ ...params.effortRung ? [`model_reasoning_effort = ${JSON.stringify(params.effortRung)}`] : [],
107763
+ "",
107764
+ "[features]",
107765
+ tomlBool(CODEX_DISABLED_FEATURES, false),
107766
+ tomlBool(CODEX_SHELL_FEATURES, shellEnabled),
107767
+ "",
107768
+ "[sandbox_workspace_write]",
107769
+ "network_access = true",
107770
+ // the checkout, named explicitly rather than left to `workspace-write` to
107771
+ // imply. does NOT fix the Linux native-write failure — nothing here does;
107772
+ // see wiki/codex-agent.md "The Linux native-write blocker".
107773
+ `writable_roots = [${JSON.stringify(repoDir)}]`,
107774
+ "",
107775
+ `[mcp_servers.${pullfrogMcpName}]`,
107776
+ `url = ${JSON.stringify(params.ctx.mcpServerUrl)}`,
107777
+ // codex's default MCP approval mode PROMPTS for anything it can't read as
107778
+ // read-only, and a `codex exec` run has nobody to answer — the call comes
107779
+ // back "user cancelled MCP tool call" and the agent silently accomplishes
107780
+ // nothing. our own server is the trusted one; the boundary is what we
107781
+ // register, not a second confirmation.
107782
+ APPROVE_TOOLS,
107783
+ // a run whose GitHub tools never registered can't do its job, so fail loudly
107784
+ // rather than let the agent improvise without them.
107785
+ "required = true",
107786
+ // mirrors the opencode harness's 660s: `checkout_pr` runs a multi-minute
107787
+ // fetch on large repos and enforces its own 600s deadline, so a shorter
107788
+ // client abort turns a working checkout into a spurious tool error.
107789
+ "tool_timeout_sec = 660",
107790
+ "",
107791
+ ...repoMcpApprovals(repoDir),
107792
+ // UNTRUSTED, deliberately, and this one line is the whole config-precedence
107793
+ // boundary. Codex loads `<repo>/.codex/config.toml` as a layer that
107794
+ // OUTRANKS ours (project 25 vs user 20), and its denylist strips none of
107795
+ // `sandbox_mode`, `approval_policy`, `features` or `mcp_servers` — so a
107796
+ // fork PR could ship a config re-enabling the native shell. Marking the
107797
+ // checkout untrusted makes that layer load `disabled`, closing the class
107798
+ // outright rather than key by key: `RawMcpServerConfig` alone has ~26
107799
+ // fields and enumeration can never finish.
107800
+ //
107801
+ // It costs nothing. `codex exec`'s only startup gate is a GIT-REPO check
107802
+ // (`exec/src/lib.rs:796` — `get_git_repo_root(...).is_none()`, whose error
107803
+ // text unhelpfully says "Not inside a trusted directory"), so an untrusted
107804
+ // checkout still runs. And the repo's own MCP servers survive, because
107805
+ // `repoMcpApprovals` above copies them into OUR layer — which is why that
107806
+ // copying exists at all.
107807
+ //
107808
+ // Measured both ways at 0.147.0: `trusted` → the repo's `model` won;
107809
+ // `untrusted` → it did not, and the run proceeded normally.
107810
+ `[projects.${JSON.stringify(repoDir)}]`,
107811
+ 'trust_level = "untrusted"',
107812
+ ""
107813
+ ].join("\n");
107814
+ writeFileSync5(join6(params.codexHome, "config.toml"), config3, { mode: 384 });
107815
+ log.info(`\xBB codex config: sandbox=${sandboxMode}, nativeShell=${shellEnabled}`);
107816
+ }
107817
+ function toTodoWriteInput(item) {
107818
+ return {
107819
+ todos: item.items.map((todo, index) => ({
107820
+ id: String(index),
107821
+ content: todo.text,
107822
+ status: todo.completed ? "completed" : "pending"
107823
+ }))
107824
+ };
107825
+ }
107826
+ async function runCodex(params) {
107827
+ const startTime = performance6.now();
107828
+ const thinkingTimer = new ThinkingTimer();
107829
+ const output = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
107830
+ const recentStderr = [];
107831
+ let eventCount = 0;
107832
+ let finalOutput = "";
107833
+ let threadId;
107834
+ let turnError = null;
107835
+ let lastProviderError = null;
107836
+ let stdoutBuffer = "";
107837
+ const tokens = { input: 0, cacheRead: 0, output: 0 };
107838
+ function buildUsage2() {
107839
+ if (tokens.input === 0 && tokens.output === 0) return void 0;
107840
+ return {
107841
+ agent: "codex",
107842
+ inputTokens: tokens.input,
107843
+ outputTokens: tokens.output,
107844
+ cacheReadTokens: tokens.cacheRead || void 0
107845
+ };
107846
+ }
107847
+ function onItem(item, phase) {
107848
+ switch (item.type) {
107849
+ case "agent_message":
107850
+ if (phase === "completed" && item.text.trim()) {
107851
+ finalOutput = item.text.trim();
107852
+ log.box(finalOutput, { title: "Pullfrog" });
107853
+ }
107854
+ return;
107855
+ case "reasoning":
107856
+ if (phase === "completed" && item.text.trim()) {
107857
+ log.box(item.text.trim(), { title: "Pullfrog" });
107858
+ }
107859
+ return;
107860
+ case "command_execution":
107861
+ if (phase === "started") {
107862
+ thinkingTimer.markToolCall();
107863
+ log.toolCall({ toolName: "shell", input: { command: item.command } });
107864
+ return;
107865
+ }
107866
+ thinkingTimer.markToolResult();
107867
+ log.startGroup("shell output");
107868
+ log.info(item.aggregated_output || "");
107869
+ log.endGroup();
107870
+ return;
107871
+ case "mcp_tool_call":
107872
+ if (phase === "started") {
107873
+ thinkingTimer.markToolCall();
107874
+ params.onToolUse?.({ toolName: item.tool, input: item.arguments });
107875
+ log.toolCall({ toolName: `${item.server}__${item.tool}`, input: item.arguments });
107876
+ if (item.tool.includes("report_progress")) params.todoTracker?.cancel();
107877
+ return;
107878
+ }
107879
+ thinkingTimer.markToolResult();
107880
+ if (item.error) log.info(`\xBB tool error: ${item.error.message}`);
107881
+ return;
107882
+ case "file_change":
107883
+ if (phase === "completed") {
107884
+ const paths = item.changes.map((c) => `${c.kind} ${c.path}`).join(", ");
107885
+ log.info(`\xBB apply_patch (${item.status}): ${paths}`);
107886
+ }
107887
+ return;
107888
+ case "web_search":
107889
+ if (phase === "started") log.toolCall({ toolName: "web_search", input: item.query });
107890
+ return;
107891
+ case "todo_list":
107892
+ if (params.todoTracker?.enabled) params.todoTracker.update(toTodoWriteInput(item));
107893
+ return;
107894
+ case "error":
107895
+ log.info(`\xBB ${item.message}`);
107896
+ return;
107897
+ default:
107898
+ item;
107899
+ }
107900
+ }
107901
+ function onEvent(event) {
107902
+ switch (event.type) {
107903
+ case "thread.started":
107904
+ threadId = event.thread_id;
107905
+ return;
107906
+ case "turn.completed":
107907
+ tokens.input += event.usage.input_tokens ?? 0;
107908
+ tokens.cacheRead += event.usage.cached_input_tokens ?? 0;
107909
+ tokens.output += event.usage.output_tokens ?? 0;
107910
+ return;
107911
+ case "turn.failed":
107912
+ turnError = event.error.message;
107913
+ log.info(`\xBB turn failed: ${turnError}`);
107914
+ return;
107915
+ case "error":
107916
+ turnError = event.message;
107917
+ log.info(`\xBB codex error: ${event.message}`);
107918
+ return;
107919
+ case "item.started":
107920
+ onItem(event.item, "started");
107921
+ return;
107922
+ case "item.completed":
107923
+ onItem(event.item, "completed");
107924
+ return;
107925
+ case "item.updated":
107926
+ if (event.item.type === "todo_list") onItem(event.item, "started");
107927
+ return;
107928
+ case "turn.started":
107929
+ return;
107930
+ default:
107931
+ event;
107932
+ }
107933
+ }
107934
+ try {
107935
+ const result = await spawn({
107936
+ cmd: params.cliPath,
107937
+ args: params.args,
107938
+ cwd: params.cwd,
107939
+ env: params.env,
107940
+ activityTimeout: AGENT_ACTIVITY_TIMEOUT_MS,
107941
+ onActivityTimeout: params.onActivityTimeout,
107942
+ // stdin MUST be `ignore`: codex reads a piped stdin as an extra `<stdin>`
107943
+ // prompt block and blocks on EOF, so an inherited pipe hangs the run
107944
+ // before the first model call.
107945
+ stdio: ["ignore", "pipe", "pipe"],
107946
+ killGroup: true,
107947
+ retain: "none",
107948
+ onStdout: (chunk) => {
107949
+ output.append(chunk);
107950
+ markActivity();
107951
+ stdoutBuffer += chunk;
107952
+ const lines = stdoutBuffer.split("\n");
107953
+ stdoutBuffer = lines.pop() || "";
107954
+ for (const line of lines) {
107955
+ const trimmed = line.trim();
107956
+ if (!trimmed) continue;
107957
+ let event;
107958
+ try {
107959
+ event = JSON.parse(trimmed);
107960
+ } catch {
107961
+ log.debug(`\xBB non-JSON stdout line: ${trimmed.substring(0, 200)}`);
107962
+ continue;
107963
+ }
107964
+ eventCount++;
107965
+ log.debug(JSON.stringify(event, null, 2));
107966
+ const idleMs = getIdleMs();
107967
+ if (idleMs > 1e4) {
107968
+ log.info(
107969
+ `\xBB no activity for ${(idleMs / 1e3).toFixed(1)}s (${eventCount} events processed so far)`
107970
+ );
107971
+ }
107972
+ markActivity();
107973
+ onEvent(event);
107974
+ }
107975
+ },
107976
+ onStderr: (chunk) => {
107977
+ const trimmed = chunk.trim();
107978
+ if (!trimmed) return;
107979
+ recentStderr.push(trimmed);
107980
+ if (recentStderr.length > MAX_STDERR_LINES) recentStderr.shift();
107981
+ const match3 = findProviderErrorMatch(trimmed);
107982
+ if (match3) {
107983
+ lastProviderError = match3.label;
107984
+ log.info(`\xBB provider error detected (${match3.label}): ${match3.excerpt}`);
107985
+ } else {
107986
+ log.debug(trimmed);
107987
+ }
107988
+ }
107989
+ });
107990
+ const duration4 = performance6.now() - startTime;
107991
+ log.info(`\xBB codex completed in ${Math.round(duration4)}ms with exit code ${result.exitCode}`);
107992
+ const usage = buildUsage2();
107993
+ if (usage) {
107994
+ logTokenTable({
107995
+ input: tokens.input - tokens.cacheRead,
107996
+ cacheRead: tokens.cacheRead,
107997
+ cacheWrite: 0,
107998
+ output: tokens.output
107999
+ });
108000
+ }
108001
+ if (result.exitCode !== 0 || turnError) {
108002
+ const stderrSnapshot = recentStderr.join("\n");
108003
+ const error52 = turnError || stderrSnapshot || lastProviderError && `provider error: ${lastProviderError}` || `unknown error - no output from Codex CLI (exit ${result.exitCode})`;
108004
+ log.error(`codex exited with code ${result.exitCode}: ${error52}`);
108005
+ return { success: false, output: finalOutput || output.toString(), error: error52, usage, threadId };
108006
+ }
108007
+ return { success: true, output: finalOutput || output.toString(), usage, threadId };
108008
+ } catch (error52) {
108009
+ const duration4 = performance6.now() - startTime;
108010
+ const message = error52 instanceof Error ? error52.message : String(error52);
108011
+ const isActivityTimeout = error52 instanceof SpawnTimeoutError && error52.code === SPAWN_ACTIVITY_TIMEOUT_CODE;
108012
+ 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`;
108013
+ log.info(
108014
+ `\xBB codex ${isActivityTimeout ? "hung" : "failed"} after ${(duration4 / 1e3).toFixed(1)}s: ${message}`
108015
+ );
108016
+ log.info(`\xBB diagnosis: ${diagnosis}`);
108017
+ if (recentStderr.length > 0) log.info(`\xBB recent stderr:
108018
+ ${recentStderr.join("\n")}`);
108019
+ return {
108020
+ success: false,
108021
+ output: finalOutput || output.toString(),
108022
+ error: `${message} [${diagnosis}]`,
108023
+ usage: buildUsage2(),
108024
+ threadId
108025
+ };
108026
+ }
108027
+ }
108028
+ var codex = agent({
108029
+ name: "codex",
108030
+ install: installCodexCli,
108031
+ run: async (ctx) => {
108032
+ const cliPath = await installCodexCli();
108033
+ warnIfNativeEditsUnavailable(ctx);
108034
+ const codexHomeAuth = installCodexHome();
108035
+ const codexHome = codexHomeAuth?.codexHome ?? join6(ctx.tmpdir, ".codex");
108036
+ mkdirSync6(codexHome, { recursive: true });
108037
+ const effort = resolveRunEffort(ctx);
108038
+ if (effort.rung && !CODEX_EFFORTS.includes(effort.rung)) {
108039
+ log.warning(`\xBB effort ${effort.rung} not sent \u2014 codex doesn't accept that level`);
108040
+ }
108041
+ writeCodexConfig({
108042
+ ctx,
108043
+ codexHome,
108044
+ model: resolveCodexModel(ctx),
108045
+ effortRung: effort.rung && CODEX_EFFORTS.includes(effort.rung) ? effort.rung : void 0
108046
+ });
108047
+ if (codexHomeAuth) {
108048
+ core2.saveState(
108049
+ "codex_writeback",
108050
+ JSON.stringify({
108051
+ apiToken: ctx.apiToken,
108052
+ authPath: codexHomeAuth.authPath,
108053
+ originalRefresh: codexHomeAuth.originalRefresh
108054
+ })
108055
+ );
108056
+ }
108057
+ const baseEnv = ctx.payload.shell === "enabled" ? process.env : filterEnv();
108058
+ const env2 = {
108059
+ ...baseEnv,
108060
+ CODEX_HOME: codexHome,
108061
+ PWD: process.cwd()
108062
+ };
108063
+ delete env2.CODEX_API_KEY;
108064
+ if (codexHomeAuth) {
108065
+ delete env2.OPENAI_API_KEY;
108066
+ } else if (process.env.OPENAI_API_KEY) {
108067
+ env2.CODEX_API_KEY = process.env.OPENAI_API_KEY;
108068
+ }
108069
+ const securityFlags = securityOverrideFlags({
108070
+ ctx,
108071
+ sandboxMode: ctx.payload.push === "disabled" ? "read-only" : "workspace-write",
108072
+ repoDir: process.cwd(),
108073
+ mcpServerUrl: ctx.mcpServerUrl,
108074
+ model: resolveCodexModel(ctx),
108075
+ effortRung: effort.rung && CODEX_EFFORTS.includes(effort.rung) ? effort.rung : void 0
108076
+ });
108077
+ const runnerArgs = { cliPath, cwd: process.cwd(), env: env2, todoTracker: ctx.todoTracker };
108078
+ const initial = await runCodex({
108079
+ ...runnerArgs,
108080
+ args: [...securityFlags, "exec", "--json", ctx.instructions.full],
108081
+ onActivityTimeout: ctx.onActivityTimeout,
108082
+ onToolUse: ctx.onToolUse
108083
+ });
108084
+ const result = await runPostRunRetryLoop({
108085
+ ctx,
108086
+ initialResult: initial,
108087
+ initialUsage: initial.usage,
108088
+ reflectionPrompt: buildReflectionPrompt(ctx.toolState),
108089
+ // a resume needs the thread codex recorded. without one there is nothing
108090
+ // to continue, and `resume --last` would be worse than not resuming: it
108091
+ // picks the newest recorded session in this cwd, which on a self-hosted
108092
+ // runner with a shared CODEX_HOME is somebody else's run.
108093
+ canResume: (r) => r.threadId !== void 0,
108094
+ resume: async (c) => runCodex({
108095
+ ...runnerArgs,
108096
+ args: [
108097
+ ...securityFlags,
108098
+ "exec",
108099
+ "resume",
108100
+ "--json",
108101
+ c.previousResult.threadId ?? "",
108102
+ c.prompt
108103
+ ]
108104
+ })
108105
+ });
108106
+ if (result.success) {
108107
+ await ctx.todoTracker?.flush();
108108
+ } else {
108109
+ ctx.todoTracker?.cancel();
108110
+ }
108111
+ return result;
108112
+ }
108113
+ });
108114
+
108115
+ // agents/opencode.ts
108116
+ var core3 = __toESM(require_core(), 1);
108117
+ import { spawn as nodeSpawn2 } from "node:child_process";
108118
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "node:fs";
108119
+ import { join as join7 } from "node:path";
108120
+ import { performance as performance7 } from "node:perf_hooks";
108121
+
107313
108122
  // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/core/serverSentEvents.gen.js
107314
108123
  var createSseClient = ({ onRequest, onSseError, onSseEvent, responseTransformer, responseValidator, sseDefaultRetryDelay, sseMaxRetryAttempts, sseMaxRetryDelay, sseSleepFn, url: url4, ...options }) => {
107315
108124
  let lastEventId;
@@ -112716,114 +113525,6 @@ var import_cross_spawn = __toESM(require_cross_spawn(), 1);
112716
113525
  // agents/opencode.ts
112717
113526
  var import_undici = __toESM(require_undici2(), 1);
112718
113527
 
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
113528
  // utils/worktree.ts
112828
113529
  async function dirtyTrackedPaths(params = {}) {
112829
113530
  const result = await spawn({
@@ -113091,6 +113792,30 @@ function openAICompatibleProvider(model) {
113091
113792
  }
113092
113793
  };
113093
113794
  }
113795
+ function azureProvider(model) {
113796
+ const prefix = `${AZURE_PROVIDER}/`;
113797
+ if (!model?.startsWith(prefix)) return {};
113798
+ const deployment = model.slice(prefix.length);
113799
+ return {
113800
+ [AZURE_PROVIDER]: {
113801
+ // always sent: opencode reads it as `Boolean(options?.useCompletionUrls)`,
113802
+ // so `false` and absent are the same, and the deep merge keeps the native
113803
+ // loader's `resourceName` either way.
113804
+ options: {
113805
+ useCompletionUrls: process.env[AZURE_USE_CHAT_COMPLETIONS_ENV]?.trim() === "true"
113806
+ },
113807
+ models: {
113808
+ [deployment]: {
113809
+ name: deployment,
113810
+ limit: {
113811
+ context: Number(process.env[AZURE_CONTEXT_ENV]),
113812
+ output: Number(process.env[AZURE_MAX_OUTPUT_ENV])
113813
+ }
113814
+ }
113815
+ }
113816
+ }
113817
+ };
113818
+ }
113094
113819
  var KIMI_ENFORCERLESS_PROVIDERS = ["siliconflow", "together"];
113095
113820
  function kimiOpenRouterProviderOverrides() {
113096
113821
  return Object.fromEntries(
@@ -113182,7 +113907,8 @@ function buildSecurityConfig(ctx, model) {
113182
113907
  // see opencodeShared.ts.
113183
113908
  provider: {
113184
113909
  openrouter: { models: kimiOpenRouterProviderOverrides() },
113185
- ...openAICompatibleProvider(model)
113910
+ ...openAICompatibleProvider(model),
113911
+ ...azureProvider(model)
113186
113912
  }
113187
113913
  };
113188
113914
  if (model) {
@@ -113350,7 +114076,7 @@ function isModelOutput(ctx, part) {
113350
114076
  }
113351
114077
  async function dispatchEvent(ctx, event) {
113352
114078
  if (event.type === "message.part.updated") {
113353
- ctx.lastEventAt = performance6.now();
114079
+ ctx.lastEventAt = performance7.now();
113354
114080
  if (isModelOutput(ctx, event.properties.part)) ctx.sawModelOutput = true;
113355
114081
  await onPartUpdated(ctx, event.properties.part);
113356
114082
  return;
@@ -113417,7 +114143,7 @@ async function onToolPart(ctx, part, label, isOrchestrator) {
113417
114143
  if (toolName === "task" && status === "running" && isOrchestrator && !ctx.taskDispatchByCallID.has(toolId)) {
113418
114144
  const input = part.state.input ?? {};
113419
114145
  const dispatched = ctx.labeler.recordTaskDispatch(input);
113420
- ctx.taskDispatchByCallID.set(toolId, { label: dispatched, startedAt: performance6.now() });
114146
+ ctx.taskDispatchByCallID.set(toolId, { label: dispatched, startedAt: performance7.now() });
113421
114147
  log.info(
113422
114148
  `\xBB dispatching subagent: ${dispatched}` + (input.subagent_type ? ` (subagent_type=${input.subagent_type})` : "")
113423
114149
  );
@@ -113436,6 +114162,7 @@ function processTerminalToolPart(ctx, part, label, isOrchestrator) {
113436
114162
  const callLine = inputFormatted !== "{}" ? `\xBB ${toolName}(${inputFormatted})` : `\xBB ${toolName}()`;
113437
114163
  log.info(withLabel(label, callLine));
113438
114164
  if (isOrchestrator) ctx.loggedToolCallIDs.add(toolId);
114165
+ if (isOrchestrator && toolName.startsWith("pullfrog_")) ctx.mcpToolCalls++;
113439
114166
  if (state.status === "completed") {
113440
114167
  log.debug(withLabel(label, ` output: ${state.output}`));
113441
114168
  } else {
@@ -113447,7 +114174,7 @@ function processTerminalToolPart(ctx, part, label, isOrchestrator) {
113447
114174
  if (toolName === "task") {
113448
114175
  const dispatch = ctx.taskDispatchByCallID.get(toolId);
113449
114176
  if (dispatch) {
113450
- const dur = ((performance6.now() - dispatch.startedAt) / 1e3).toFixed(1);
114177
+ const dur = ((performance7.now() - dispatch.startedAt) / 1e3).toFixed(1);
113451
114178
  const outputStr = state.status === "completed" ? state.output : "";
113452
114179
  const preview = typeof outputStr === "string" && outputStr.length > 120 ? `${outputStr.slice(0, 120)}\u2026` : outputStr;
113453
114180
  log.info(
@@ -113490,7 +114217,7 @@ function withLabel(label, message) {
113490
114217
  return label === ORCHESTRATOR_LABEL ? message : formatWithLabel(label, message);
113491
114218
  }
113492
114219
  async function runPromptTurn(ctx, params) {
113493
- const start = performance6.now();
114220
+ const start = performance7.now();
113494
114221
  const turnStartMs = Date.now();
113495
114222
  ctx.currentTurn = newTurn();
113496
114223
  const turn = ctx.currentTurn;
@@ -113524,7 +114251,7 @@ async function runPromptTurn(ctx, params) {
113524
114251
  } catch (err) {
113525
114252
  networkError = err instanceof Error ? err.message : String(err);
113526
114253
  }
113527
- const durationMs = performance6.now() - start;
114254
+ const durationMs = performance7.now() - start;
113528
114255
  const aggregatedUsage = await aggregateTurnUsage(ctx, turnStartMs);
113529
114256
  const usage = aggregatedUsage ?? buildUsage(turn, assistant);
113530
114257
  const finalText = turn.finalText || extractTextFromParts(returnedParts) || "";
@@ -113674,21 +114401,28 @@ function formatConfigError(name, data) {
113674
114401
  ).join("");
113675
114402
  return `${name}${detail}${bullets}`;
113676
114403
  }
114404
+ var WATCHDOG_SALVAGE_PROMPT = "Your previous turn was cut off mid-response by a provider stall. Nothing you had not already submitted through a tool was saved. Continue from where you stopped and submit your work now \u2014 do not restart your analysis.";
113677
114405
  function startInnerActivityWatchdog(params) {
113678
114406
  let fired = false;
114407
+ let everFired = false;
113679
114408
  const id = setInterval(() => {
113680
114409
  if (fired) return;
113681
- const idleMs = performance6.now() - params.ctx.lastEventAt;
113682
- const budgetMs = params.ctx.sawModelOutput ? params.timeoutMs : AGENT_FIRST_EVENT_TIMEOUT_MS;
114410
+ const idleMs = performance7.now() - params.ctx.lastEventAt;
114411
+ const compiledMs = params.ctx.sawModelOutput ? params.timeoutMs : AGENT_FIRST_EVENT_TIMEOUT_MS;
114412
+ const budgetMs = everFired ? compiledMs : watchdogBudgetMs(
114413
+ compiledMs,
114414
+ params.ctx.sawModelOutput ? "PULLFROG_E2E_ACTIVITY_TIMEOUT_MS" : "PULLFROG_E2E_FIRST_EVENT_TIMEOUT_MS"
114415
+ );
113683
114416
  if (idleMs <= budgetMs) return;
113684
114417
  fired = true;
114418
+ everFired = true;
113685
114419
  const idleSec = Math.round(idleMs / 1e3);
113686
114420
  params.ctx.diagnostic.idleSec = idleSec;
113687
114421
  params.ctx.diagnostic.sawModelOutput = params.ctx.sawModelOutput;
113688
114422
  log.info(
113689
114423
  params.ctx.sawModelOutput ? `\xBB no opencode events for ${idleSec}s \u2014 aborting in-flight prompt and notifying harness` : `\xBB no opencode events for ${idleSec}s \u2014 the provider never returned a first token; aborting in-flight prompt and notifying harness`
113690
114424
  );
113691
- params.abortController.abort();
114425
+ params.abortTurn();
113692
114426
  try {
113693
114427
  params.ctx.onActivityTimeout?.();
113694
114428
  } catch (err) {
@@ -113698,7 +114432,22 @@ function startInnerActivityWatchdog(params) {
113698
114432
  }
113699
114433
  }, 5e3);
113700
114434
  id.unref?.();
113701
- return { stop: () => clearInterval(id) };
114435
+ return {
114436
+ stop: () => clearInterval(id),
114437
+ /**
114438
+ * Start a turn's idle budget from now. The clock must be reset with the
114439
+ * latch: `lastEventAt` only advances on model output, so a turn following a
114440
+ * stall would inherit the full stalled interval and be aborted on the very
114441
+ * next 5s tick — killing the salvage before the provider could answer. It
114442
+ * is the right semantics for an ordinary resume too, whose budget should
114443
+ * not be pre-spent by the post-run gate checks that ran between turns.
114444
+ */
114445
+ armForTurn: () => {
114446
+ fired = false;
114447
+ params.ctx.lastEventAt = performance7.now();
114448
+ },
114449
+ firedThisTurn: () => fired
114450
+ };
113702
114451
  }
113703
114452
  var opencode = agent({
113704
114453
  name: "opencode",
@@ -113717,12 +114466,12 @@ var opencode = agent({
113717
114466
  const model = vertexModel ?? (isBedrockRoute ? `amazon-bedrock/${rawModel}` : rawModel);
113718
114467
  const homeEnv = {
113719
114468
  HOME: ctx.tmpdir,
113720
- XDG_CONFIG_HOME: join6(ctx.tmpdir, ".config")
114469
+ XDG_CONFIG_HOME: join7(ctx.tmpdir, ".config")
113721
114470
  };
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),
114471
+ const opencodePluginDir = join7(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
114472
+ mkdirSync7(opencodePluginDir, { recursive: true });
114473
+ writeFileSync6(
114474
+ join7(opencodePluginDir, PULLFROG_OPENCODE_GATE_PLUGIN_FILENAME),
113726
114475
  buildOpencodeSubagentGateSource(ctx.subagentDeniedTools)
113727
114476
  );
113728
114477
  const agentBrowserVersion = getDevDependencyVersion("agent-browser");
@@ -113753,12 +114502,13 @@ var opencode = agent({
113753
114502
  if (codexAuth) {
113754
114503
  env2.XDG_DATA_HOME = codexAuth.xdgDataHome;
113755
114504
  delete env2.OPENAI_API_KEY;
113756
- core2.saveState(
114505
+ core3.saveState(
113757
114506
  "codex_writeback",
113758
114507
  JSON.stringify({
113759
114508
  apiToken: ctx.apiToken,
113760
114509
  authPath: codexAuth.authPath,
113761
- originalRefresh: codexAuth.originalRefresh
114510
+ originalRefresh: codexAuth.originalRefresh,
114511
+ originalIdToken: codexAuth.originalIdToken
113762
114512
  })
113763
114513
  );
113764
114514
  }
@@ -113809,14 +114559,16 @@ var opencode = agent({
113809
114559
  variant: effort.rung,
113810
114560
  todoTracker: ctx.todoTracker,
113811
114561
  onActivityTimeout: ctx.onActivityTimeout,
114562
+ onTurnRecovered: ctx.onTurnRecovered,
113812
114563
  onToolUse: ctx.onToolUse,
113813
114564
  currentTurn: null,
113814
114565
  eventCount: 0,
113815
- lastEventAt: performance6.now(),
114566
+ lastEventAt: performance7.now(),
113816
114567
  sawModelOutput: false,
113817
114568
  promptMessageID: void 0,
113818
114569
  taskDispatchByCallID: /* @__PURE__ */ new Map(),
113819
114570
  loggedToolCallIDs: /* @__PURE__ */ new Set(),
114571
+ mcpToolCalls: 0,
113820
114572
  recentStderr: server.recentStderr,
113821
114573
  diagnostic: {
113822
114574
  label: "Pullfrog",
@@ -113840,9 +114592,14 @@ var opencode = agent({
113840
114592
  }
113841
114593
  }
113842
114594
  });
113843
- const abortController = new AbortController();
113844
- const eventLoopPromise = consumeEvents(runnerCtx, abortController.signal).catch((err) => {
113845
- if (!abortController.signal.aborted) {
114595
+ const runController = new AbortController();
114596
+ let turnController = new AbortController();
114597
+ const nextTurnSignal = () => {
114598
+ turnController = new AbortController();
114599
+ return AbortSignal.any([runController.signal, turnController.signal]);
114600
+ };
114601
+ const eventLoopPromise = consumeEvents(runnerCtx, runController.signal).catch((err) => {
114602
+ if (!runController.signal.aborted) {
113846
114603
  log.warning(
113847
114604
  `\xBB opencode event subscription ended: ${err instanceof Error ? err.message : String(err)}`
113848
114605
  );
@@ -113858,31 +114615,48 @@ var opencode = agent({
113858
114615
  // a long synchronous tool call (no part.updated while it runs) can't
113859
114616
  // false-positive it.
113860
114617
  timeoutMs: AGENT_ACTIVITY_TIMEOUT_MS,
113861
- abortController
114618
+ abortTurn: () => turnController.abort()
113862
114619
  });
113863
114620
  const sdkModel = parseModel2(model);
114621
+ let salvagesLeft = 1;
114622
+ const runTurn = async (text) => {
114623
+ const attempt = (prompt) => {
114624
+ watchdog.armForTurn();
114625
+ return runTurnGuarded(
114626
+ runnerCtx,
114627
+ () => runPromptTurn(runnerCtx, { text: prompt, model: sdkModel, signal: nextTurnSignal() })
114628
+ );
114629
+ };
114630
+ const standDownIfRecovered = (turn) => {
114631
+ if (watchdog.firedThisTurn() && turn.success) ctx.onTurnRecovered?.();
114632
+ return turn;
114633
+ };
114634
+ const result = await attempt(text);
114635
+ if (!watchdog.firedThisTurn()) return result;
114636
+ if (result.success) return standDownIfRecovered(result);
114637
+ if (salvagesLeft <= 0) return result;
114638
+ salvagesLeft--;
114639
+ ctx.onTurnRecovered?.();
114640
+ log.info("\xBB activity watchdog cut the turn off \u2014 re-prompting once on the same session");
114641
+ const mcpCallsBefore = runnerCtx.mcpToolCalls;
114642
+ const salvaged = await attempt(WATCHDOG_SALVAGE_PROMPT);
114643
+ const usage = mergeAgentUsage(result.usage, salvaged.usage);
114644
+ if (runnerCtx.mcpToolCalls === mcpCallsBefore) {
114645
+ log.info(
114646
+ "\xBB salvage turn reached no Pullfrog tool \u2014 keeping the activity-timeout failure"
114647
+ );
114648
+ return { ...result, usage };
114649
+ }
114650
+ return standDownIfRecovered({ ...salvaged, usage });
114651
+ };
113864
114652
  try {
113865
- const initial = await runTurnGuarded(
113866
- runnerCtx,
113867
- () => runPromptTurn(runnerCtx, {
113868
- text: ctx.instructions.full,
113869
- model: sdkModel,
113870
- signal: abortController.signal
113871
- })
113872
- );
114653
+ const initial = await runTurn(ctx.instructions.full);
113873
114654
  const result = await runPostRunRetryLoop({
113874
114655
  ctx,
113875
114656
  initialResult: initial,
113876
114657
  initialUsage: initial.usage,
113877
114658
  reflectionPrompt: buildReflectionPrompt(ctx.toolState),
113878
- resume: async (c) => runTurnGuarded(
113879
- runnerCtx,
113880
- () => runPromptTurn(runnerCtx, {
113881
- text: c.prompt,
113882
- model: sdkModel,
113883
- signal: abortController.signal
113884
- })
113885
- )
114659
+ resume: async (c) => runTurn(c.prompt)
113886
114660
  });
113887
114661
  if (result.success) {
113888
114662
  await ctx.todoTracker?.flush();
@@ -113892,7 +114666,7 @@ var opencode = agent({
113892
114666
  return result;
113893
114667
  } finally {
113894
114668
  watchdog.stop();
113895
- abortController.abort();
114669
+ runController.abort();
113896
114670
  await eventLoopPromise.catch(() => {
113897
114671
  });
113898
114672
  }
@@ -113922,7 +114696,7 @@ async function runTurnGuarded(ctx, fn2) {
113922
114696
  }
113923
114697
 
113924
114698
  // agents/index.ts
113925
- var agents = { claude, opencode };
114699
+ var agents = { claude, codex, opencode };
113926
114700
 
113927
114701
  // node_modules/.pnpm/@ark+util@0.56.0/node_modules/@ark/util/out/arrays.js
113928
114702
  var liftArray = (data) => Array.isArray(data) ? data : [data];
@@ -134254,7 +135028,7 @@ var require_core$2 = /* @__PURE__ */ __commonJSMin(((exports) => {
134254
135028
  Object.defineProperty(exports, "__esModule", { value: true });
134255
135029
  const id_1 = require_id2();
134256
135030
  const ref_1 = require_ref2();
134257
- const core10 = [
135031
+ const core11 = [
134258
135032
  "$schema",
134259
135033
  "$id",
134260
135034
  "$defs",
@@ -134264,7 +135038,7 @@ var require_core$2 = /* @__PURE__ */ __commonJSMin(((exports) => {
134264
135038
  id_1.default,
134265
135039
  ref_1.default
134266
135040
  ];
134267
- exports.default = core10;
135041
+ exports.default = core11;
134268
135042
  }));
134269
135043
  var require_limitNumber2 = /* @__PURE__ */ __commonJSMin(((exports) => {
134270
135044
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -136402,7 +137176,7 @@ var require_json_schema_2019_09 = /* @__PURE__ */ __commonJSMin(((exports) => {
136402
137176
  const metaSchema = require_schema$1();
136403
137177
  const applicator = require_applicator$1();
136404
137178
  const content = require_content$1();
136405
- const core10 = require_core$1();
137179
+ const core11 = require_core$1();
136406
137180
  const format2 = require_format3();
136407
137181
  const metadata = require_meta_data$1();
136408
137182
  const validation = require_validation$1();
@@ -136412,7 +137186,7 @@ var require_json_schema_2019_09 = /* @__PURE__ */ __commonJSMin(((exports) => {
136412
137186
  metaSchema,
136413
137187
  applicator,
136414
137188
  content,
136415
- core10,
137189
+ core11,
136416
137190
  with$data(this, format2),
136417
137191
  metadata,
136418
137192
  with$data(this, validation)
@@ -136852,7 +137626,7 @@ var require_json_schema_2020_12 = /* @__PURE__ */ __commonJSMin(((exports) => {
136852
137626
  const applicator = require_applicator2();
136853
137627
  const unevaluated = require_unevaluated();
136854
137628
  const content = require_content();
136855
- const core10 = require_core4();
137629
+ const core11 = require_core4();
136856
137630
  const format2 = require_format_annotation();
136857
137631
  const metadata = require_meta_data();
136858
137632
  const validation = require_validation2();
@@ -136863,7 +137637,7 @@ var require_json_schema_2020_12 = /* @__PURE__ */ __commonJSMin(((exports) => {
136863
137637
  applicator,
136864
137638
  unevaluated,
136865
137639
  content,
136866
- core10,
137640
+ core11,
136867
137641
  with$data(this, format2),
136868
137642
  metadata,
136869
137643
  with$data(this, validation)
@@ -148305,7 +149079,7 @@ var require_core$22 = /* @__PURE__ */ __commonJSMin2(((exports) => {
148305
149079
  Object.defineProperty(exports, "__esModule", { value: true });
148306
149080
  const id_1 = require_id3();
148307
149081
  const ref_1 = require_ref3();
148308
- const core10 = [
149082
+ const core11 = [
148309
149083
  "$schema",
148310
149084
  "$id",
148311
149085
  "$defs",
@@ -148315,7 +149089,7 @@ var require_core$22 = /* @__PURE__ */ __commonJSMin2(((exports) => {
148315
149089
  id_1.default,
148316
149090
  ref_1.default
148317
149091
  ];
148318
- exports.default = core10;
149092
+ exports.default = core11;
148319
149093
  }));
148320
149094
  var require_limitNumber3 = /* @__PURE__ */ __commonJSMin2(((exports) => {
148321
149095
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -150453,7 +151227,7 @@ var require_json_schema_2019_092 = /* @__PURE__ */ __commonJSMin2(((exports) =>
150453
151227
  const metaSchema = require_schema$12();
150454
151228
  const applicator = require_applicator$12();
150455
151229
  const content = require_content$12();
150456
- const core10 = require_core$12();
151230
+ const core11 = require_core$12();
150457
151231
  const format2 = require_format4();
150458
151232
  const metadata = require_meta_data$12();
150459
151233
  const validation = require_validation$12();
@@ -150463,7 +151237,7 @@ var require_json_schema_2019_092 = /* @__PURE__ */ __commonJSMin2(((exports) =>
150463
151237
  metaSchema,
150464
151238
  applicator,
150465
151239
  content,
150466
- core10,
151240
+ core11,
150467
151241
  with$data(this, format2),
150468
151242
  metadata,
150469
151243
  with$data(this, validation)
@@ -150903,7 +151677,7 @@ var require_json_schema_2020_122 = /* @__PURE__ */ __commonJSMin2(((exports) =>
150903
151677
  const applicator = require_applicator3();
150904
151678
  const unevaluated = require_unevaluated2();
150905
151679
  const content = require_content2();
150906
- const core10 = require_core5();
151680
+ const core11 = require_core5();
150907
151681
  const format2 = require_format_annotation2();
150908
151682
  const metadata = require_meta_data2();
150909
151683
  const validation = require_validation3();
@@ -150914,7 +151688,7 @@ var require_json_schema_2020_122 = /* @__PURE__ */ __commonJSMin2(((exports) =>
150914
151688
  applicator,
150915
151689
  unevaluated,
150916
151690
  content,
150917
- core10,
151691
+ core11,
150918
151692
  with$data(this, format2),
150919
151693
  metadata,
150920
151694
  with$data(this, validation)
@@ -170875,123 +171649,8 @@ var pbkdf2Async = promisify(pbkdf2);
170875
171649
 
170876
171650
  // utils/browser.ts
170877
171651
  import { execFileSync as execFileSync4, spawnSync as spawnSync4 } from "node:child_process";
170878
- import { existsSync as existsSync4 } from "node:fs";
171652
+ import { existsSync as existsSync5 } from "node:fs";
170879
171653
  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
171654
  var CHROME_PATHS = ["/usr/bin/google-chrome-stable"];
170996
171655
  var systemChromePath;
170997
171656
  function findSystemChromePath() {
@@ -170999,7 +171658,7 @@ function findSystemChromePath() {
170999
171658
  return systemChromePath || void 0;
171000
171659
  }
171001
171660
  for (const p of CHROME_PATHS) {
171002
- if (existsSync4(p)) {
171661
+ if (existsSync5(p)) {
171003
171662
  systemChromePath = p;
171004
171663
  log.info(`found system chrome: ${p}`);
171005
171664
  return p;
@@ -171083,8 +171742,8 @@ function closeBrowserDaemon(toolState) {
171083
171742
 
171084
171743
  // mcp/checkout.ts
171085
171744
  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";
171745
+ import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync9 } from "node:fs";
171746
+ import { join as join14 } from "node:path";
171088
171747
 
171089
171748
  // node_modules/.pnpm/@ark+schema@0.56.0/node_modules/@ark/schema/out/shared/errors.js
171090
171749
  var ArkError = class _ArkError extends CastableBase {
@@ -178556,8 +179215,8 @@ function ensureRepoState(toolState, init) {
178556
179215
 
178557
179216
  // utils/changeImpact.ts
178558
179217
  import { spawn as spawn2 } from "node:child_process";
178559
- import { writeFileSync as writeFileSync6 } from "node:fs";
178560
- import { join as join7 } from "node:path";
179218
+ import { writeFileSync as writeFileSync7 } from "node:fs";
179219
+ import { join as join8 } from "node:path";
178561
179220
  var MAX_CANDIDATES = 24;
178562
179221
  var MAX_ATOM_LENGTH = 128;
178563
179222
  var MAX_ATOMS = 12;
@@ -178904,8 +179563,8 @@ async function createChangeImpactArtifact(ctx, params) {
178904
179563
  headSha: params.headSha,
178905
179564
  lookupError: lookup2.error
178906
179565
  });
178907
- const path4 = join7(ctx.tmpdir, `pr-${params.pullNumber}-${params.headSha.slice(0, 7)}-impact.md`);
178908
- writeFileSync6(path4, artifact.content);
179566
+ const path4 = join8(ctx.tmpdir, `pr-${params.pullNumber}-${params.headSha.slice(0, 7)}-impact.md`);
179567
+ writeFileSync7(path4, artifact.content);
178909
179568
  return {
178910
179569
  path: path4,
178911
179570
  candidateCount: candidates.selected.length,
@@ -179157,8 +179816,8 @@ function readNumber(params) {
179157
179816
  // utils/gitAuth.ts
179158
179817
  import { execSync } from "node:child_process";
179159
179818
  import { createHash } from "node:crypto";
179160
- import { readFileSync as readFileSync2, realpathSync, unlinkSync } from "node:fs";
179161
- import { join as join8 } from "node:path";
179819
+ import { readFileSync as readFileSync3, realpathSync, unlinkSync } from "node:fs";
179820
+ import { join as join9 } from "node:path";
179162
179821
 
179163
179822
  // utils/shell.ts
179164
179823
  import { spawnSync as spawnSync5 } from "node:child_process";
@@ -179219,7 +179878,7 @@ var TRANSIENT_AUTH_PATTERNS = [
179219
179878
  ];
179220
179879
  var gitBinary;
179221
179880
  function hashFile(path4) {
179222
- return createHash("sha256").update(readFileSync2(path4)).digest("hex");
179881
+ return createHash("sha256").update(readFileSync3(path4)).digest("hex");
179223
179882
  }
179224
179883
  function resolveGit() {
179225
179884
  const whichPath = execSync("which git", { encoding: "utf-8" }).trim();
@@ -179248,7 +179907,7 @@ function resolveHooksDir(cwd, gitPath) {
179248
179907
  cwd,
179249
179908
  log: false
179250
179909
  }).trim();
179251
- const hooksDir = join8(commonDir, "hooks");
179910
+ const hooksDir = join9(commonDir, "hooks");
179252
179911
  hooksDirCache.set(cwd, hooksDir);
179253
179912
  return hooksDir;
179254
179913
  }
@@ -179555,7 +180214,7 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
179555
180214
  }
179556
180215
 
179557
180216
  // yes/index.ts
179558
- import { performance as performance7 } from "node:perf_hooks";
180217
+ import { performance as performance8 } from "node:perf_hooks";
179559
180218
  import { setTimeout as sleep2 } from "node:timers/promises";
179560
180219
 
179561
180220
  // node_modules/.pnpm/lru-cache@11.5.2/node_modules/lru-cache/dist/esm/node/index.min.js
@@ -180270,14 +180929,14 @@ function _op(fn2, options) {
180270
180929
  `failed to create cache key${namePrefix ? ` for ${options.name}` : ""}: ${errorMessage}`
180271
180930
  );
180272
180931
  }
180273
- const startTime = performance7.now();
180932
+ const startTime = performance8.now();
180274
180933
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
180275
180934
  log2.debug(`${namePrefix}[${timestamp}] cache lookup started`, { key });
180276
180935
  if (shouldCache && lruCache) {
180277
180936
  const cached4 = lruCache.get(key);
180278
180937
  if (cached4 !== void 0) {
180279
180938
  if (keyMap?.get(key) === void 0) keyMap?.set(key, input);
180280
- const runtime = performance7.now() - startTime;
180939
+ const runtime = performance8.now() - startTime;
180281
180940
  log2.debug(`${namePrefix}[${timestamp}] cache hit (from lru)`, {
180282
180941
  key,
180283
180942
  runtime: `${Math.round(runtime)}ms`
@@ -180296,12 +180955,12 @@ function _op(fn2, options) {
180296
180955
  defaultCacheMiss(key);
180297
180956
  const retries = options.retries ?? [];
180298
180957
  let lastError;
180299
- const execStartTime = performance7.now();
180958
+ const execStartTime = performance8.now();
180300
180959
  const promise2 = (async () => {
180301
180960
  for (let attempt = 0; attempt <= retries.length; attempt++) {
180302
180961
  try {
180303
180962
  const result = acceptsCtx ? await fn2(input, ctx) : await fn2(input);
180304
- const execRuntime2 = performance7.now() - execStartTime;
180963
+ const execRuntime2 = performance8.now() - execStartTime;
180305
180964
  const skip = options.skipCache ? options.skipCache(result) : false;
180306
180965
  if (shouldCache && lruCache && result !== null && result !== void 0 && !skip) {
180307
180966
  lruCache.set(key, result);
@@ -180354,7 +181013,7 @@ function _op(fn2, options) {
180354
181013
  }
180355
181014
  }
180356
181015
  }
180357
- const execRuntime = performance7.now() - execStartTime;
181016
+ const execRuntime = performance8.now() - execStartTime;
180358
181017
  log2.debug(`${namePrefix}[${timestamp}] function failed after all retries`, {
180359
181018
  key,
180360
181019
  runtime: `${Math.round(execRuntime)}ms`,
@@ -180419,13 +181078,13 @@ function _op(fn2, options) {
180419
181078
 
180420
181079
  // mcp/git.ts
180421
181080
  import { randomUUID as randomUUID3 } from "node:crypto";
180422
- import { writeFileSync as writeFileSync7 } from "node:fs";
180423
- import { join as join11 } from "node:path";
181081
+ import { writeFileSync as writeFileSync8 } from "node:fs";
181082
+ import { join as join12 } from "node:path";
180424
181083
 
180425
181084
  // utils/apiCommit.ts
180426
181085
  import { execFileSync as execFileSync5 } from "node:child_process";
180427
181086
  import { lstat, readlink } from "node:fs/promises";
180428
- import { join as join9 } from "node:path";
181087
+ import { join as join10 } from "node:path";
180429
181088
  var GITHUB_API = "https://api.github.com";
180430
181089
  var MAX_BLOB_BYTES = 30 * 1024 * 1024;
180431
181090
  var BLOB_UPLOAD_CONCURRENCY = 8;
@@ -180490,7 +181149,7 @@ async function assertApiCommittable(files) {
180490
181149
  const root = getRepoRoot();
180491
181150
  const attrs = $(
180492
181151
  "git",
180493
- ["check-attr", "filter", "-z", "--", ...present.map((p) => join9(root, p))],
181152
+ ["check-attr", "filter", "-z", "--", ...present.map((p) => join10(root, p))],
180494
181153
  { log: false }
180495
181154
  );
180496
181155
  const parts = attrs.split("\0");
@@ -180502,7 +181161,7 @@ async function assertApiCommittable(files) {
180502
181161
  }
180503
181162
  }
180504
181163
  for (const path4 of present) {
180505
- const stat = await lstat(join9(root, path4));
181164
+ const stat = await lstat(join10(root, path4));
180506
181165
  if (stat.isDirectory()) {
180507
181166
  throw new Error(
180508
181167
  `'${path4}' is a directory (nested repository or submodule?) \u2014 signed commits only support files and symlinks.`
@@ -180511,7 +181170,7 @@ async function assertApiCommittable(files) {
180511
181170
  }
180512
181171
  }
180513
181172
  async function createBlobEntry(params) {
180514
- const absPath = join9(params.repoRoot, params.path);
181173
+ const absPath = join10(params.repoRoot, params.path);
180515
181174
  const stat = await lstat(absPath);
180516
181175
  if (stat.size > MAX_BLOB_BYTES) {
180517
181176
  throw new Error(
@@ -180688,10 +181347,10 @@ this usually means your local branch has commits that were never pushed \u2014 s
180688
181347
  }
180689
181348
 
180690
181349
  // utils/github.ts
180691
- var core3 = __toESM(require_core(), 1);
181350
+ var core4 = __toESM(require_core(), 1);
180692
181351
  import { createSign } from "node:crypto";
180693
181352
  import { rename, writeFile } from "node:fs/promises";
180694
- import { dirname as dirname3, join as join10 } from "node:path";
181353
+ import { dirname as dirname3, join as join11 } from "node:path";
180695
181354
 
180696
181355
  // node_modules/.pnpm/@octokit+plugin-throttling@11.0.5_@octokit+core@7.0.7/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
180697
181356
  var import_light = __toESM(require_light(), 1);
@@ -184889,7 +185548,7 @@ async function fetchIdTokenFromStash(creds) {
184889
185548
  throw new Error("ID token response has no value field");
184890
185549
  }
184891
185550
  if (isGitHubActions) {
184892
- core3.setSecret(body.value);
185551
+ core4.setSecret(body.value);
184893
185552
  }
184894
185553
  return body.value;
184895
185554
  }
@@ -184902,7 +185561,7 @@ function translateIdTokenError(error52) {
184902
185561
  var mintIdToken = op(
184903
185562
  async () => {
184904
185563
  try {
184905
- return await core3.getIDToken(OIDC_AUDIENCE);
185564
+ return await core4.getIDToken(OIDC_AUDIENCE);
184906
185565
  } catch (error52) {
184907
185566
  throw translateIdTokenError(error52);
184908
185567
  }
@@ -185111,7 +185770,7 @@ function getGitHubUsageSummary() {
185111
185770
  }
185112
185771
  async function writeGitHubUsageSummaryToFile(path4) {
185113
185772
  const summary2 = getGitHubUsageSummary();
185114
- const tmpPath = join10(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
185773
+ const tmpPath = join11(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
185115
185774
  await writeFile(tmpPath, JSON.stringify(summary2));
185116
185775
  await rename(tmpPath, path4);
185117
185776
  }
@@ -186263,8 +186922,8 @@ function countAhead(head, base, cwd) {
186263
186922
  function spillGitOutput(params) {
186264
186923
  const tempDir = process.env.PULLFROG_TEMP_DIR;
186265
186924
  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);
186925
+ const outputPath = join12(tempDir, `git-${params.command}-${randomUUID3().slice(0, 8)}.txt`);
186926
+ writeFileSync8(outputPath, params.output);
186268
186927
  const previewByLines = params.output.split("\n").slice(0, OVERFLOW_PREVIEW_LINES).join("\n");
186269
186928
  const preview = previewByLines.length <= OVERFLOW_PREVIEW_MAX_CHARS ? previewByLines : `${previewByLines.slice(0, OVERFLOW_PREVIEW_MAX_CHARS)}\u2026`;
186270
186929
  log.info(
@@ -187778,9 +188437,9 @@ async function reportReviewNodeId(ctx, params) {
187778
188437
  import { execFileSync as execFileSync6, execSync as execSync2 } from "node:child_process";
187779
188438
  import { mkdtempSync, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
187780
188439
  import { tmpdir as tmpdir2 } from "node:os";
187781
- import { join as join12 } from "node:path";
188440
+ import { join as join13 } from "node:path";
187782
188441
  function createTempDirectory() {
187783
- const sharedTempDir = mkdtempSync(join12(tmpdir2(), "pullfrog-"));
188442
+ const sharedTempDir = mkdtempSync(join13(tmpdir2(), "pullfrog-"));
187784
188443
  process.env.PULLFROG_TEMP_DIR = sharedTempDir;
187785
188444
  log.info(`\xBB created temp dir at ${sharedTempDir}`);
187786
188445
  return sharedTempDir;
@@ -187825,13 +188484,13 @@ function wipeRunnerLeakSurface() {
187825
188484
  return [];
187826
188485
  }
187827
188486
  };
187828
- const fileCommandsDir = join12(runnerTemp, "_runner_file_commands");
188487
+ const fileCommandsDir = join13(runnerTemp, "_runner_file_commands");
187829
188488
  for (const entry of listDir(fileCommandsDir)) {
187830
- tryUnlink(join12(fileCommandsDir, entry));
188489
+ tryUnlink(join13(fileCommandsDir, entry));
187831
188490
  }
187832
188491
  for (const entry of listDir(runnerTemp)) {
187833
188492
  if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
187834
- tryUnlink(join12(runnerTemp, entry));
188493
+ tryUnlink(join13(runnerTemp, entry));
187835
188494
  }
187836
188495
  }
187837
188496
  if (wiped.length > 0) {
@@ -188381,11 +189040,11 @@ function CheckoutPrTool(ctx) {
188381
189040
  headSha: checkoutSha
188382
189041
  });
188383
189042
  if (incremental.status === "ok") {
188384
- incrementalDiffPath = join13(
189043
+ incrementalDiffPath = join14(
188385
189044
  tempDir,
188386
189045
  `pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
188387
189046
  );
188388
- writeFileSync8(incrementalDiffPath, incremental.diff);
189047
+ writeFileSync9(incrementalDiffPath, incremental.diff);
188389
189048
  log.info(
188390
189049
  `\xBB incremental diff computed (${incremental.diff.length} bytes) \u2192 ${incrementalDiffPath}`
188391
189050
  );
@@ -188398,8 +189057,8 @@ function CheckoutPrTool(ctx) {
188398
189057
  const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
188399
189058
  log.debug(`formatted diff preview (first 100 lines):
188400
189059
  ${diffPreview}`);
188401
- const diffPath = join13(tempDir, `pr-${pull_number}-${headShort}.diff`);
188402
- writeFileSync8(diffPath, formatResult.content);
189060
+ const diffPath = join14(tempDir, `pr-${pull_number}-${headShort}.diff`);
189061
+ writeFileSync9(diffPath, formatResult.content);
188403
189062
  log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
188404
189063
  primary.diffCoverage = createDiffCoverageState({
188405
189064
  diffPath,
@@ -188556,8 +189215,8 @@ ${dirty}`
188556
189215
  }
188557
189216
 
188558
189217
  // mcp/checkSuite.ts
188559
- import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync9 } from "node:fs";
188560
- import { join as join14 } from "node:path";
189218
+ import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync10 } from "node:fs";
189219
+ import { join as join15 } from "node:path";
188561
189220
  var GetCheckSuiteLogs = type({
188562
189221
  check_suite_id: type.number.describe("the id from check_suite.id")
188563
189222
  });
@@ -188653,8 +189312,8 @@ function GetCheckSuiteLogsTool(ctx) {
188653
189312
  if (!tempDir) {
188654
189313
  throw new Error("PULLFROG_TEMP_DIR not set");
188655
189314
  }
188656
- const logsDir = join14(tempDir, "ci-logs");
188657
- mkdirSync7(logsDir, { recursive: true });
189315
+ const logsDir = join15(tempDir, "ci-logs");
189316
+ mkdirSync8(logsDir, { recursive: true });
188658
189317
  const jobResults = [];
188659
189318
  for (const run of failedRuns) {
188660
189319
  const jobs = await ctx.octokit.paginate(ctx.octokit.rest.actions.listJobsForWorkflowRun, {
@@ -188680,8 +189339,8 @@ function GetCheckSuiteLogsTool(ctx) {
188680
189339
  );
188681
189340
  }
188682
189341
  const logsText = await logsResult.text();
188683
- const logPath = join14(logsDir, `job-${job.id}.log`);
188684
- writeFileSync9(logPath, logsText);
189342
+ const logPath = join15(logsDir, `job-${job.id}.log`);
189343
+ writeFileSync10(logPath, logsText);
188685
189344
  const analysis = analyzeLog(logsText, 80);
188686
189345
  const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
188687
189346
  jobResults.push({
@@ -188729,8 +189388,8 @@ function GetCheckSuiteLogsTool(ctx) {
188729
189388
  }
188730
189389
 
188731
189390
  // mcp/commitInfo.ts
188732
- import { writeFileSync as writeFileSync10 } from "node:fs";
188733
- import { join as join15 } from "node:path";
189391
+ import { writeFileSync as writeFileSync11 } from "node:fs";
189392
+ import { join as join16 } from "node:path";
188734
189393
  var CommitInfo = type({
188735
189394
  sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
188736
189395
  });
@@ -188754,8 +189413,8 @@ function CommitInfoTool(ctx) {
188754
189413
  "PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
188755
189414
  );
188756
189415
  }
188757
- const diffFile = join15(tempDir, `commit-${sha.slice(0, 7)}.diff`);
188758
- writeFileSync10(diffFile, formatResult.content);
189416
+ const diffFile = join16(tempDir, `commit-${sha.slice(0, 7)}.diff`);
189417
+ writeFileSync11(diffFile, formatResult.content);
188759
189418
  log.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
188760
189419
  return {
188761
189420
  sha: data.sha,
@@ -188888,17 +189547,17 @@ function ReopenCurrentTool(ctx) {
188888
189547
  }
188889
189548
 
188890
189549
  // prep/index.ts
188891
- import { performance as performance8 } from "node:perf_hooks";
189550
+ import { performance as performance9 } from "node:perf_hooks";
188892
189551
 
188893
189552
  // prep/installNodeDependencies.ts
188894
- import { existsSync as existsSync6 } from "node:fs";
188895
- import { join as join17 } from "node:path";
189553
+ import { existsSync as existsSync7 } from "node:fs";
189554
+ import { join as join18 } from "node:path";
188896
189555
 
188897
189556
  // utils/packageManager.ts
188898
189557
  var import_semver2 = __toESM(require_semver2(), 1);
188899
- import { existsSync as existsSync5 } from "node:fs";
189558
+ import { existsSync as existsSync6 } from "node:fs";
188900
189559
  import { mkdir, readFile as readFile2 } from "node:fs/promises";
188901
- import { delimiter, join as join16 } from "node:path";
189560
+ import { delimiter, join as join17 } from "node:path";
188902
189561
  var SUPPORTED_NAMES = ["npm", "pnpm", "yarn", "bun"];
188903
189562
  var COREPACK_MANAGED = ["pnpm", "yarn"];
188904
189563
  function isSupported(name) {
@@ -188936,8 +189595,8 @@ function parseDevEnginesField(field) {
188936
189595
  };
188937
189596
  }
188938
189597
  async function resolvePackageManagerSpec(cwd) {
188939
- const pkgPath = join16(cwd, "package.json");
188940
- if (!existsSync5(pkgPath)) return null;
189598
+ const pkgPath = join17(cwd, "package.json");
189599
+ if (!existsSync6(pkgPath)) return null;
188941
189600
  let pkg;
188942
189601
  try {
188943
189602
  pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
@@ -188995,7 +189654,7 @@ async function currentVersion(name) {
188995
189654
  return result.stdout.trim();
188996
189655
  }
188997
189656
  function packageManagerBinDir(tmpdir3) {
188998
- return join16(tmpdir3, "pm-bin");
189657
+ return join17(tmpdir3, "pm-bin");
188999
189658
  }
189000
189659
  async function ensurePackageManager(params) {
189001
189660
  const spec = params.spec;
@@ -189058,7 +189717,7 @@ async function installFallback(name, installSpec) {
189058
189717
  });
189059
189718
  if (result.exitCode !== 0) return result.stderr || `failed to install ${name}`;
189060
189719
  if (name === "deno") {
189061
- const denoPath = join16(process.env.HOME || "", ".deno", "bin");
189720
+ const denoPath = join17(process.env.HOME || "", ".deno", "bin");
189062
189721
  process.env.PATH = `${denoPath}:${process.env.PATH}`;
189063
189722
  }
189064
189723
  log.info(`\xBB installed ${name}`);
@@ -189091,8 +189750,8 @@ async function isCommandAvailable2(command) {
189091
189750
  var installNodeDependencies = {
189092
189751
  name: "installNodeDependencies",
189093
189752
  shouldRun: () => {
189094
- const packageJsonPath = join17(process.cwd(), "package.json");
189095
- return existsSync6(packageJsonPath);
189753
+ const packageJsonPath = join18(process.cwd(), "package.json");
189754
+ return existsSync7(packageJsonPath);
189096
189755
  },
189097
189756
  run: async (options) => {
189098
189757
  const declared = await resolvePackageManagerSpec(process.cwd());
@@ -189186,14 +189845,14 @@ ${errorMessage}`]
189186
189845
  };
189187
189846
 
189188
189847
  // prep/installPythonDependencies.ts
189189
- import { existsSync as existsSync7, readFileSync as readFileSync3 } from "node:fs";
189190
- import { join as join18 } from "node:path";
189848
+ import { existsSync as existsSync8, readFileSync as readFileSync4 } from "node:fs";
189849
+ import { join as join19 } from "node:path";
189191
189850
  function declaresBuildSystem(path4) {
189192
- return /^\s*\[\s*build-system\s*\]/m.test(readFileSync3(path4, "utf8"));
189851
+ return /^\s*\[\s*build-system\s*\]/m.test(readFileSync4(path4, "utf8"));
189193
189852
  }
189194
189853
  function configApplies(config3, cwd) {
189195
- const path4 = join18(cwd, config3.file);
189196
- return existsSync7(path4) && (config3.applies?.(path4) ?? true);
189854
+ const path4 = join19(cwd, config3.file);
189855
+ return existsSync8(path4) && (config3.applies?.(path4) ?? true);
189197
189856
  }
189198
189857
  var PYTHON_CONFIGS = [
189199
189858
  {
@@ -189347,7 +190006,7 @@ var installPythonDependencies = {
189347
190006
  var prepSteps = [installNodeDependencies, installPythonDependencies];
189348
190007
  async function runPrepPhase(options) {
189349
190008
  log.debug("\xBB starting prep phase...");
189350
- const startTime = performance8.now();
190009
+ const startTime = performance9.now();
189351
190010
  const results = [];
189352
190011
  const preDirty = await dirtyTrackedPaths();
189353
190012
  try {
@@ -189369,7 +190028,7 @@ async function runPrepPhase(options) {
189369
190028
  } finally {
189370
190029
  await restoreDirtiedSince({ before: preDirty, actor: "prep" });
189371
190030
  }
189372
- const totalDurationMs = performance8.now() - startTime;
190031
+ const totalDurationMs = performance9.now() - startTime;
189373
190032
  log.debug(`\xBB prep phase completed (${Math.round(totalDurationMs)}ms)`);
189374
190033
  return results;
189375
190034
  }
@@ -189514,15 +190173,15 @@ function AwaitDependencyInstallationTool(ctx) {
189514
190173
  }
189515
190174
 
189516
190175
  // mcp/gh.ts
189517
- import { mkdirSync as mkdirSync8 } from "node:fs";
189518
- import { join as join20 } from "node:path";
190176
+ import { mkdirSync as mkdirSync9 } from "node:fs";
190177
+ import { join as join21 } from "node:path";
189519
190178
 
189520
190179
  // mcp/shell.ts
189521
190180
  import { spawn as spawn3, spawnSync as spawnSync6 } from "node:child_process";
189522
190181
  import { randomUUID as randomUUID4 } from "node:crypto";
189523
- import { closeSync, openSync, writeFileSync as writeFileSync11 } from "node:fs";
190182
+ import { closeSync, openSync, writeFileSync as writeFileSync12 } from "node:fs";
189524
190183
  import { userInfo as userInfo2 } from "node:os";
189525
- import { join as join19 } from "node:path";
190184
+ import { join as join20 } from "node:path";
189526
190185
  import { setTimeout as sleep3 } from "node:timers/promises";
189527
190186
  var ShellParams = type({
189528
190187
  command: "string",
@@ -189785,8 +190444,8 @@ ${spawnError}` : spawnError;
189785
190444
  }
189786
190445
  function capOutput(output) {
189787
190446
  if (output.length <= MAX_OUTPUT_CHARS) return output;
189788
- const fullPath = join19(getTempDir(), `shell-${randomUUID4().slice(0, 8)}.log`);
189789
- writeFileSync11(fullPath, output);
190447
+ const fullPath = join20(getTempDir(), `shell-${randomUUID4().slice(0, 8)}.log`);
190448
+ writeFileSync12(fullPath, output);
189790
190449
  const elided = output.length - MAX_OUTPUT_CHARS;
189791
190450
  return `... [${elided} chars truncated; full output saved to ${fullPath}] ...
189792
190451
  ${output.slice(-MAX_OUTPUT_CHARS)}`;
@@ -189840,8 +190499,8 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
189840
190499
  if (params.background) {
189841
190500
  const tempDir = getTempDir();
189842
190501
  const handle = `bg-${randomUUID4().slice(0, 8)}`;
189843
- const outputPath = join19(tempDir, `${handle}.log`);
189844
- const pidPath = join19(tempDir, `${handle}.pid`);
190502
+ const outputPath = join20(tempDir, `${handle}.log`);
190503
+ const pidPath = join20(tempDir, `${handle}.pid`);
189845
190504
  const logFd = openSync(outputPath, "a");
189846
190505
  let proc;
189847
190506
  try {
@@ -189858,7 +190517,7 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
189858
190517
  throw new Error("failed to start background process");
189859
190518
  }
189860
190519
  proc.unref();
189861
- writeFileSync11(pidPath, `${proc.pid}
190520
+ writeFileSync12(pidPath, `${proc.pid}
189862
190521
  `);
189863
190522
  ctx.toolState.backgroundProcesses.set(handle, { pid: proc.pid, outputPath, pidPath });
189864
190523
  return {
@@ -189938,8 +190597,8 @@ function GhTool(ctx) {
189938
190597
  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
190598
  parameters: GhParams,
189940
190599
  execute: execute(async (params) => {
189941
- const configDir = join20(ctx.tmpdir, "gh-config");
189942
- mkdirSync8(configDir, { recursive: true });
190600
+ const configDir = join21(ctx.tmpdir, "gh-config");
190601
+ mkdirSync9(configDir, { recursive: true });
189943
190602
  const command = ["gh", ...params.args.map(shellQuote)].join(" ");
189944
190603
  const result = await runSandboxed({
189945
190604
  command,
@@ -190662,8 +191321,8 @@ function PullRequestInfoTool(ctx) {
190662
191321
  }
190663
191322
 
190664
191323
  // mcp/reviewComments.ts
190665
- import { writeFileSync as writeFileSync12 } from "node:fs";
190666
- import { join as join21 } from "node:path";
191324
+ import { writeFileSync as writeFileSync13 } from "node:fs";
191325
+ import { join as join22 } from "node:path";
190667
191326
  var COMMENTS_PER_THREAD = 50;
190668
191327
  var REVIEW_THREADS_QUERY = `
190669
191328
  query ($owner: String!, $name: String!, $prNumber: Int!, $cursor: String) {
@@ -191136,8 +191795,8 @@ function GetReviewCommentsTool(ctx) {
191136
191795
  throw new Error("PULLFROG_TEMP_DIR not set");
191137
191796
  }
191138
191797
  const filename = `review-${params.review_id}-threads.md`;
191139
- const commentsPath = join21(tempDir, filename);
191140
- writeFileSync12(commentsPath, formatted.content);
191798
+ const commentsPath = join22(tempDir, filename);
191799
+ writeFileSync13(commentsPath, formatted.content);
191141
191800
  log.debug(`wrote ${threadBlocks.length} threads to ${commentsPath}`);
191142
191801
  return {
191143
191802
  review_id: params.review_id,
@@ -191470,8 +192129,8 @@ function UploadFileTool(ctx) {
191470
192129
  }
191471
192130
 
191472
192131
  // mcp/xrepo.ts
191473
- import { mkdirSync as mkdirSync9, rmSync as rmSync2 } from "node:fs";
191474
- import { join as join22 } from "node:path";
192132
+ import { mkdirSync as mkdirSync10, rmSync as rmSync2 } from "node:fs";
192133
+ import { join as join23 } from "node:path";
191475
192134
  function assertValidRepoName(name) {
191476
192135
  if (!/^[A-Za-z0-9._-]+$/.test(name) || name.startsWith("-") || name.includes("..")) {
191477
192136
  throw new Error(
@@ -191551,8 +192210,8 @@ function CheckoutRepoTool(ctx) {
191551
192210
  return { path: existing.dir, access: existing.access, note: "already checked out." };
191552
192211
  }
191553
192212
  const access = accessFor(ctx, repo);
191554
- const dir = join22(ctx.tmpdir, "xrepo", repo);
191555
- mkdirSync9(dir, { recursive: true });
192213
+ const dir = join23(ctx.tmpdir, "xrepo", repo);
192214
+ mkdirSync10(dir, { recursive: true });
191556
192215
  const state = ensureRepoState(ctx.toolState, { owner, name: repo, dir, access });
191557
192216
  const rc = resolveRepoCtx(ctx, repo);
191558
192217
  try {
@@ -191798,7 +192457,10 @@ function hasEnvVar2(name) {
191798
192457
  return typeof val === "string" && val.length > 0;
191799
192458
  }
191800
192459
  function hasClaudeCodeAuth() {
191801
- return hasEnvVar2("CLAUDE_CODE_OAUTH_TOKEN") || hasEnvVar2("ANTHROPIC_API_KEY");
192460
+ return hasEnvVar2("CLAUDE_CODE_OAUTH_TOKEN") || hasEnvVar2("ANTHROPIC_API_KEY") || hasEnvVar2("ANTHROPIC_AUTH_TOKEN");
192461
+ }
192462
+ function hasCodexAuth() {
192463
+ return hasEnvVar2("CODEX_AUTH_JSON") || hasEnvVar2("OPENAI_API_KEY");
191802
192464
  }
191803
192465
  function hasBedrockAuth() {
191804
192466
  return hasEnvVar2("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar2("AWS_ACCESS_KEY_ID") && hasEnvVar2("AWS_SECRET_ACCESS_KEY");
@@ -191826,6 +192488,15 @@ function resolveSlug(slug2) {
191826
192488
  }
191827
192489
  return vertexId;
191828
192490
  }
192491
+ if (alias?.routing === "azure") {
192492
+ const deployment = process.env[AZURE_DEPLOYMENT_ENV]?.trim();
192493
+ if (!deployment) {
192494
+ throw new Error(
192495
+ `${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.`
192496
+ );
192497
+ }
192498
+ return `${AZURE_PROVIDER}/${deployment}`;
192499
+ }
191829
192500
  if (alias?.routing === "openai-compatible") {
191830
192501
  const modelId = process.env[OPENAI_COMPATIBLE_MODEL_ENV]?.trim();
191831
192502
  if (!modelId) {
@@ -191864,6 +192535,7 @@ function resolveAgent(ctx) {
191864
192535
  }
191865
192536
  log.warning(`\xBB unknown PULLFROG_AGENT="${envAgent}" \u2014 falling through to auto-select`);
191866
192537
  }
192538
+ if (ctx.proxyModel) return agents.opencode;
191867
192539
  if (ctx.model && hasBedrockAuth() && process.env[BEDROCK_MODEL_ID_ENV]?.trim() === ctx.model) {
191868
192540
  return isBedrockAnthropicId(ctx.model) ? agents.claude : agents.opencode;
191869
192541
  }
@@ -191873,12 +192545,17 @@ function resolveAgent(ctx) {
191873
192545
  if (ctx.model) {
191874
192546
  try {
191875
192547
  const provider2 = getModelProvider(ctx.model);
191876
- if (provider2 === "anthropic" && hasClaudeCodeAuth()) {
191877
- return agents.claude;
191878
- }
192548
+ if (provider2 === "anthropic" && hasClaudeCodeAuth()) return agents.claude;
192549
+ if (provider2 === "openai" && ctx.codexAgent && hasCodexAuth()) return agents.codex;
191879
192550
  } catch {
191880
192551
  }
191881
192552
  }
192553
+ if (!ctx.model) {
192554
+ if (hasEnvVar2("ANTHROPIC_AUTH_TOKEN") && !hasEnvVar2("ANTHROPIC_API_KEY") && !hasEnvVar2("CLAUDE_CODE_OAUTH_TOKEN")) {
192555
+ return agents.claude;
192556
+ }
192557
+ if (ctx.codexAgent && hasCodexAuth() && !hasClaudeCodeAuth()) return agents.codex;
192558
+ }
191882
192559
  return agents.opencode;
191883
192560
  }
191884
192561
 
@@ -191976,10 +192653,37 @@ auto-compaction is disabled, so long runs grow until your endpoint refuses them.
191976
192653
 
191977
192654
  for full setup instructions, see https://docs.pullfrog.com/openai-compatible`;
191978
192655
  }
192656
+ function buildAzureSetupError(params) {
192657
+ const githubSecretsUrl = `https://github.com/${params.owner}/${params.name}/settings/secrets/actions`;
192658
+ return `Azure OpenAI selected but required configuration is missing: ${params.missing.join(", ")}.
192659
+
192660
+ only the API key is sensitive \u2014 add it as a secret at ${githubSecretsUrl}. the rest is plain workflow \`env:\`:
192661
+
192662
+ ${AZURE_RESOURCE_NAME_ENV}: <name>
192663
+ ${AZURE_API_KEY_ENV}: \${{ secrets.${AZURE_API_KEY_ENV} }}
192664
+ ${AZURE_DEPLOYMENT_ENV}: <deployment-name>
192665
+ ${AZURE_CONTEXT_ENV}: "400000"
192666
+ ${AZURE_MAX_OUTPUT_ENV}: "128000"
192667
+
192668
+ ${AZURE_RESOURCE_NAME_ENV} is the \`<name>\` in your endpoint https://<name>.openai.azure.com.
192669
+ ${AZURE_DEPLOYMENT_ENV} is the name of the deployment, which is not necessarily the name of
192670
+ the model it serves \u2014 Azure routes on the deployment name.
192671
+
192672
+ set the last two to the real limits of whichever model your deployment serves. Pullfrog can't
192673
+ discover them \u2014 a deployment name carries no catalog metadata \u2014 and without them completions
192674
+ are capped at 32000 tokens (rejected outright by models with a smaller cap) and auto-compaction
192675
+ is disabled, so long runs grow until Azure refuses them.
192676
+
192677
+ for full setup instructions, see https://docs.pullfrog.com/azure`;
192678
+ }
191979
192679
  function hasEnvVar3(name) {
191980
192680
  const value2 = process.env[name];
191981
192681
  return typeof value2 === "string" && value2.length > 0;
191982
192682
  }
192683
+ function modelHasRuntimeAuth(model) {
192684
+ const authVars = [...getModelEnvVars(model), ...getModelManagedCredentials(model)];
192685
+ return authVars.length === 0 || authVars.some(hasEnvVar3);
192686
+ }
191983
192687
  function hasPositiveNumberEnvVar(name) {
191984
192688
  return Number(process.env[name]) > 0;
191985
192689
  }
@@ -191998,6 +192702,17 @@ function validateOpenAICompatibleSetup(params) {
191998
192702
  );
191999
192703
  }
192000
192704
  }
192705
+ function validateAzureSetup(params) {
192706
+ const missing = [];
192707
+ if (!hasEnvVar3(AZURE_API_KEY_ENV)) missing.push(AZURE_API_KEY_ENV);
192708
+ if (!hasEnvVar3(AZURE_RESOURCE_NAME_ENV)) missing.push(AZURE_RESOURCE_NAME_ENV);
192709
+ if (!hasEnvVar3(AZURE_DEPLOYMENT_ENV)) missing.push(AZURE_DEPLOYMENT_ENV);
192710
+ if (!hasPositiveNumberEnvVar(AZURE_CONTEXT_ENV)) missing.push(AZURE_CONTEXT_ENV);
192711
+ if (!hasPositiveNumberEnvVar(AZURE_MAX_OUTPUT_ENV)) missing.push(AZURE_MAX_OUTPUT_ENV);
192712
+ if (missing.length > 0) {
192713
+ throw new Error(buildAzureSetupError({ owner: params.owner, name: params.name, missing }));
192714
+ }
192715
+ }
192001
192716
  function validateBedrockSetup(params) {
192002
192717
  const hasAuth = hasEnvVar3("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar3("AWS_ACCESS_KEY_ID") && hasEnvVar3("AWS_SECRET_ACCESS_KEY");
192003
192718
  const missing = [];
@@ -192021,6 +192736,12 @@ function validateVertexSetup(params) {
192021
192736
  throw new Error(buildVertexSetupError({ owner: params.owner, name: params.name, missing }));
192022
192737
  }
192023
192738
  }
192739
+ function hasSingleProviderAuth(agentName) {
192740
+ if (agentName === "codex") {
192741
+ return hasEnvVar3("OPENAI_API_KEY") || hasEnvVar3("CODEX_AUTH_JSON");
192742
+ }
192743
+ return hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("ANTHROPIC_AUTH_TOKEN") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN");
192744
+ }
192024
192745
  function validateAgentApiKey(params) {
192025
192746
  if (params.model) {
192026
192747
  const alias = resolveDisplayAlias(params.model);
@@ -192036,6 +192757,10 @@ function validateAgentApiKey(params) {
192036
192757
  validateOpenAICompatibleSetup({ owner: params.owner, name: params.name });
192037
192758
  return;
192038
192759
  }
192760
+ if (params.model.startsWith(`${AZURE_PROVIDER}/`)) {
192761
+ validateAzureSetup({ owner: params.owner, name: params.name });
192762
+ return;
192763
+ }
192039
192764
  if (!params.model.includes("/")) {
192040
192765
  if (process.env[VERTEX_MODEL_ID_ENV]?.trim() === params.model) {
192041
192766
  validateVertexSetup({ owner: params.owner, name: params.name });
@@ -192059,7 +192784,7 @@ function validateAgentApiKey(params) {
192059
192784
  })
192060
192785
  );
192061
192786
  }
192062
- if (hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN")) return;
192787
+ if (hasSingleProviderAuth(params.agent.name)) return;
192063
192788
  throw new Error(
192064
192789
  buildKeyError({
192065
192790
  owner: params.owner,
@@ -192071,7 +192796,7 @@ function validateAgentApiKey(params) {
192071
192796
  );
192072
192797
  }
192073
192798
  if (params.agent.name === "opencode") {
192074
- if (params.authorized.size > 0) return;
192799
+ if ([...params.authorized].some(modelHasRuntimeAuth)) return;
192075
192800
  const reason = getModelsFailure();
192076
192801
  if (reason) throw new Error(reason);
192077
192802
  throw new Error(
@@ -192083,7 +192808,7 @@ function validateAgentApiKey(params) {
192083
192808
  })
192084
192809
  );
192085
192810
  }
192086
- if (hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN")) return;
192811
+ if (hasSingleProviderAuth(params.agent.name)) return;
192087
192812
  throw new Error(
192088
192813
  buildKeyError({
192089
192814
  owner: params.owner,
@@ -192347,7 +193072,8 @@ var PROBES = {
192347
193072
  request: (value2) => ({
192348
193073
  url: "https://api.anthropic.com/v1/models",
192349
193074
  headers: { "x-api-key": value2, "anthropic-version": "2023-06-01" }
192350
- })
193075
+ }),
193076
+ hostConfigurable: true
192351
193077
  },
192352
193078
  OPENROUTER_API_KEY: {
192353
193079
  request: (value2) => ({
@@ -192396,12 +193122,18 @@ function hasEnvVar4(name) {
192396
193122
  const value2 = process.env[name];
192397
193123
  return typeof value2 === "string" && value2.length > 0;
192398
193124
  }
193125
+ var HOST_OVERRIDES = {
193126
+ ANTHROPIC_API_KEY: "ANTHROPIC_BASE_URL",
193127
+ CLAUDE_CODE_OAUTH_TOKEN: "ANTHROPIC_BASE_URL"
193128
+ };
192399
193129
  function envVarsFor(model) {
192400
193130
  return model.includes("/") ? getModelEnvVars(model) : [];
192401
193131
  }
192402
193132
  async function checkOne(params) {
192403
193133
  const value2 = process.env[params.envVar];
192404
193134
  if (!value2) return null;
193135
+ const hostOverride = HOST_OVERRIDES[params.envVar];
193136
+ if (hostOverride && hasEnvVar4(hostOverride)) return null;
192405
193137
  if (params.envVar === "CLAUDE_CODE_OAUTH_TOKEN") {
192406
193138
  const preflight = await preflightClaudeSubscription({
192407
193139
  token: value2,
@@ -192456,9 +193188,9 @@ async function checkConfiguredCredentials(params) {
192456
193188
 
192457
193189
  // utils/gitAuthServer.ts
192458
193190
  import { randomUUID as randomUUID5 } from "node:crypto";
192459
- import { writeFileSync as writeFileSync13 } from "node:fs";
193191
+ import { writeFileSync as writeFileSync14 } from "node:fs";
192460
193192
  import { createServer as createServer3 } from "node:http";
192461
- import { join as join23 } from "node:path";
193193
+ import { join as join24 } from "node:path";
192462
193194
  var REVOKED_TRAP_MS = 6e4;
192463
193195
  function revokeGitHubToken(token) {
192464
193196
  fetch("https://api.github.com/installation/token", {
@@ -192527,7 +193259,7 @@ async function startGitAuthServer(tmpdir3) {
192527
193259
  function writeAskpassScript(code) {
192528
193260
  const scriptId = randomUUID5();
192529
193261
  const scriptName = `askpass-${scriptId}.js`;
192530
- const scriptPath = join23(tmpdir3, scriptName);
193262
+ const scriptPath = join24(tmpdir3, scriptName);
192531
193263
  const content = [
192532
193264
  `#!/usr/bin/env node`,
192533
193265
  `var a=process.argv[2]||"";`,
@@ -192540,7 +193272,7 @@ async function startGitAuthServer(tmpdir3) {
192540
193272
  `r.on("end",function(){process.stdout.write(d+"\\n")})`,
192541
193273
  `}).on("error",function(){process.exit(1)})}`
192542
193274
  ].join("\n");
192543
- writeFileSync13(scriptPath, content, { mode: 448 });
193275
+ writeFileSync14(scriptPath, content, { mode: 448 });
192544
193276
  return scriptPath;
192545
193277
  }
192546
193278
  async function close() {
@@ -192979,7 +193711,7 @@ function resolveInstructions(ctx) {
192979
193711
 
192980
193712
  // utils/learnings.ts
192981
193713
  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";
193714
+ import { dirname as dirname4, join as join25 } from "node:path";
192983
193715
 
192984
193716
  // utils/learningsTruncate.ts
192985
193717
  var MAX_LEARNINGS_LENGTH = 1e5;
@@ -192997,10 +193729,10 @@ function truncateAtLineBoundary(body, cap) {
192997
193729
  var LEARNINGS_FILE_NAME = "pullfrog-learnings.md";
192998
193730
  var XREPO_LEARNINGS_FILE_NAME = "pullfrog-xrepo-learnings.md";
192999
193731
  function learningsFilePath(tmpdir3) {
193000
- return join24(tmpdir3, LEARNINGS_FILE_NAME);
193732
+ return join25(tmpdir3, LEARNINGS_FILE_NAME);
193001
193733
  }
193002
193734
  function xrepoLearningsFilePath(tmpdir3) {
193003
- return join24(tmpdir3, XREPO_LEARNINGS_FILE_NAME);
193735
+ return join25(tmpdir3, XREPO_LEARNINGS_FILE_NAME);
193004
193736
  }
193005
193737
  async function seedLearningsFile(params) {
193006
193738
  const path4 = learningsFilePath(params.tmpdir);
@@ -193116,7 +193848,7 @@ async function persistXrepoLearnings(ctx) {
193116
193848
  // utils/modelAccess.ts
193117
193849
  function decideModelAccess(input) {
193118
193850
  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}/`));
193851
+ const byokAuthorized = !!input.resolvedModel && (input.authorized.has(input.resolvedModel) || !input.resolvedModel.includes("/") || input.resolvedModel.startsWith(`${OPENAI_COMPATIBLE_PROVIDER}/`) || input.resolvedModel.startsWith(`${AZURE_PROVIDER}/`));
193120
193852
  if (input.proxyActive) {
193121
193853
  const target = resolveOpenRouterModel(input.model);
193122
193854
  if (input.oss) {
@@ -193161,7 +193893,7 @@ function buildModelAccessError(input) {
193161
193893
  }
193162
193894
 
193163
193895
  // utils/normalizeEnv.ts
193164
- var core4 = __toESM(require_core(), 1);
193896
+ var core5 = __toESM(require_core(), 1);
193165
193897
  function hasControlCharacter(value2) {
193166
193898
  return [...value2].some((ch) => {
193167
193899
  const code = ch.charCodeAt(0);
@@ -193187,7 +193919,7 @@ function sanitizeSecret(key, value2) {
193187
193919
  `\xBB stripped whitespace from ${key} (whitespace in secret values breaks GitHub Actions log masking)`
193188
193920
  );
193189
193921
  }
193190
- core4.setSecret(trimmed);
193922
+ core5.setSecret(trimmed);
193191
193923
  return trimmed;
193192
193924
  }
193193
193925
  function normalizeEnv() {
@@ -193231,7 +193963,7 @@ function normalizeEnv() {
193231
193963
  }
193232
193964
 
193233
193965
  // utils/overrides.ts
193234
- var core5 = __toESM(require_core(), 1);
193966
+ var core6 = __toESM(require_core(), 1);
193235
193967
  var DENIED_OVERRIDE_NAMES = /* @__PURE__ */ new Set([
193236
193968
  "GITHUB_TOKEN",
193237
193969
  "GH_TOKEN",
@@ -193277,7 +194009,7 @@ function applyOverrides(params) {
193277
194009
  denied.push(key);
193278
194010
  continue;
193279
194011
  }
193280
- if (value2.length > 0) core5.setSecret(value2);
194012
+ if (value2.length > 0) core6.setSecret(value2);
193281
194013
  params.env[key] = value2;
193282
194014
  applied.push(key);
193283
194015
  }
@@ -193286,8 +194018,8 @@ function applyOverrides(params) {
193286
194018
  }
193287
194019
 
193288
194020
  // utils/payload.ts
193289
- var core6 = __toESM(require_core(), 1);
193290
- import { readFileSync as readFileSync5 } from "node:fs";
194021
+ var core7 = __toESM(require_core(), 1);
194022
+ import { readFileSync as readFileSync6 } from "node:fs";
193291
194023
  import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
193292
194024
 
193293
194025
  // utils/versioning.ts
@@ -193374,8 +194106,8 @@ function resolveCwd(cwd) {
193374
194106
  return workspace ? resolve2(workspace, cwd) : cwd;
193375
194107
  }
193376
194108
  function resolvePromptInput() {
193377
- const promptInput = core6.getInput("prompt");
193378
- const promptFile = core6.getInput("prompt_file");
194109
+ const promptInput = core7.getInput("prompt");
194110
+ const promptFile = core7.getInput("prompt_file");
193379
194111
  if (promptInput && promptFile) {
193380
194112
  throw new Error("set exactly one of 'prompt' or 'prompt_file' inputs, not both.");
193381
194113
  }
@@ -193401,7 +194133,7 @@ function resolvePromptInput() {
193401
194133
  function resolvePromptFile(input) {
193402
194134
  const workspace = process.env.GITHUB_WORKSPACE;
193403
194135
  const path4 = isAbsolute2(input) ? input : workspace ? resolve2(workspace, input) : resolve2(input);
193404
- const content = readFileSync5(path4, "utf-8");
194136
+ const content = readFileSync6(path4, "utf-8");
193405
194137
  if (!content.trim()) {
193406
194138
  throw new Error(`prompt_file ${JSON.stringify(input)} is empty.`);
193407
194139
  }
@@ -193409,7 +194141,7 @@ function resolvePromptFile(input) {
193409
194141
  }
193410
194142
  function warnIfDeprecatedStatusChecks(value2) {
193411
194143
  if (value2 !== void 0) {
193412
- core6.warning(
194144
+ core7.warning(
193413
194145
  "`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
194146
  );
193415
194147
  }
@@ -193417,15 +194149,15 @@ function warnIfDeprecatedStatusChecks(value2) {
193417
194149
  }
193418
194150
  function resolveNonPromptInputs() {
193419
194151
  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
194152
+ model: core7.getInput("model") || void 0,
194153
+ effort: core7.getInput("effort") || void 0,
194154
+ debug: core7.getInput("debug") || void 0,
194155
+ timeout: core7.getInput("timeout") || void 0,
194156
+ cwd: core7.getInput("cwd") || void 0,
194157
+ push: core7.getInput("push") || void 0,
194158
+ shell: core7.getInput("shell") || void 0,
194159
+ status_checks: warnIfDeprecatedStatusChecks(core7.getInput("status_checks") || void 0),
194160
+ progress_comments: core7.getInput("progress_comments") || void 0
193429
194161
  });
193430
194162
  }
193431
194163
  function resolvePayload(resolvedPromptInput, repoSettings) {
@@ -193493,7 +194225,7 @@ function resolvePayload(resolvedPromptInput, repoSettings) {
193493
194225
  };
193494
194226
  }
193495
194227
  function resolveOutputSchema() {
193496
- const raw2 = core6.getInput("output_schema");
194228
+ const raw2 = core7.getInput("output_schema");
193497
194229
  if (!raw2) return void 0;
193498
194230
  let parsed2;
193499
194231
  try {
@@ -193509,10 +194241,10 @@ function resolveOutputSchema() {
193509
194241
  }
193510
194242
 
193511
194243
  // utils/proxy.ts
193512
- var core8 = __toESM(require_core(), 1);
194244
+ var core9 = __toESM(require_core(), 1);
193513
194245
 
193514
194246
  // utils/token.ts
193515
- var core7 = __toESM(require_core(), 1);
194247
+ var core8 = __toESM(require_core(), 1);
193516
194248
  import assert2 from "node:assert/strict";
193517
194249
  var mcpTokenValue;
193518
194250
  var refreshMcpTokenFn;
@@ -193520,7 +194252,7 @@ function getMcpTokenRefresh() {
193520
194252
  return refreshMcpTokenFn;
193521
194253
  }
193522
194254
  function getJobToken() {
193523
- const inputToken = core7.getInput("token");
194255
+ const inputToken = core8.getInput("token");
193524
194256
  if (inputToken) {
193525
194257
  return inputToken;
193526
194258
  }
@@ -193536,7 +194268,7 @@ async function resolveTokens(params) {
193536
194268
  if (externalToken) {
193537
194269
  mcpTokenValue = externalToken;
193538
194270
  if (isGitHubActions) {
193539
- core7.setSecret(externalToken);
194271
+ core8.setSecret(externalToken);
193540
194272
  }
193541
194273
  log.info("\xBB using external GH_TOKEN for both git and MCP");
193542
194274
  return {
@@ -193556,7 +194288,7 @@ async function resolveTokens(params) {
193556
194288
  const gitPermissions = params.push === "disabled" ? { contents: "read" } : { contents: "write", workflows: "write" };
193557
194289
  const gitToken = await acquireNewToken({ repos: writeRepos, permissions: gitPermissions });
193558
194290
  if (isGitHubActions) {
193559
- core7.setSecret(gitToken);
194291
+ core8.setSecret(gitToken);
193560
194292
  }
193561
194293
  log.info(
193562
194294
  `\xBB acquired git token (${Object.entries(gitPermissions).map((e) => e.join(":")).join(", ")})`
@@ -193573,7 +194305,7 @@ async function resolveTokens(params) {
193573
194305
  };
193574
194306
  const mcpToken = await acquireNewToken({ repos: writeRepos, permissions: mcpPermissions });
193575
194307
  if (isGitHubActions) {
193576
- core7.setSecret(mcpToken);
194308
+ core8.setSecret(mcpToken);
193577
194309
  }
193578
194310
  log.info(
193579
194311
  `\xBB acquired scoped MCP token (${Object.entries(mcpPermissions).map((e) => e.join(":")).join(", ")})`
@@ -193582,7 +194314,7 @@ async function resolveTokens(params) {
193582
194314
  let ghToken;
193583
194315
  if (ghPermissions) {
193584
194316
  ghToken = await acquireNewToken({ permissions: ghPermissions });
193585
- if (isGitHubActions) core7.setSecret(ghToken);
194317
+ if (isGitHubActions) core8.setSecret(ghToken);
193586
194318
  log.info(
193587
194319
  `\xBB acquired gh token mirroring ${params.authorPermission} (${formatPermissions(ghPermissions)})`
193588
194320
  );
@@ -193593,7 +194325,7 @@ async function resolveTokens(params) {
193593
194325
  repos: params.xrepo.read,
193594
194326
  permissions: { contents: "read" }
193595
194327
  });
193596
- if (isGitHubActions) core7.setSecret(readToken);
194328
+ if (isGitHubActions) core8.setSecret(readToken);
193597
194329
  log.info(`\xBB acquired cross-repo read token (contents:read, ${params.xrepo.read.length} repos)`);
193598
194330
  }
193599
194331
  mcpTokenValue = mcpToken;
@@ -193612,7 +194344,7 @@ async function resolveTokens(params) {
193612
194344
  oidc: params.oidc ?? void 0
193613
194345
  }).then((fresh) => {
193614
194346
  if (isGitHubActions) {
193615
- core7.setSecret(fresh);
194347
+ core8.setSecret(fresh);
193616
194348
  }
193617
194349
  mcpTokenValue = fresh;
193618
194350
  currentMcpToken = fresh;
@@ -193633,7 +194365,7 @@ async function resolveTokens(params) {
193633
194365
  permissions: gitPermissions,
193634
194366
  oidc: params.oidc ?? void 0
193635
194367
  }).then((fresh) => {
193636
- if (isGitHubActions) core7.setSecret(fresh);
194368
+ if (isGitHubActions) core8.setSecret(fresh);
193637
194369
  void revokeGitHubInstallationToken(currentGitToken);
193638
194370
  currentGitToken = fresh;
193639
194371
  log.warning("\xBB GitHub rejected the git token; re-acquired a fresh git token");
@@ -193650,7 +194382,7 @@ async function resolveTokens(params) {
193650
194382
  permissions: { contents: "read" },
193651
194383
  oidc: params.oidc ?? void 0
193652
194384
  }).then((fresh) => {
193653
- if (isGitHubActions) core7.setSecret(fresh);
194385
+ if (isGitHubActions) core8.setSecret(fresh);
193654
194386
  void revokeGitHubInstallationToken(read2);
193655
194387
  currentReadToken = fresh;
193656
194388
  log.warning("\xBB GitHub rejected the read token; re-acquired a fresh read token");
@@ -193864,7 +194596,7 @@ async function resolveProxyModel(ctx) {
193864
194596
  });
193865
194597
  if (!key) return;
193866
194598
  process.env.OPENROUTER_API_KEY = key;
193867
- core8.setSecret(key);
194599
+ core9.setSecret(key);
193868
194600
  ctx.payload.proxyModel = ctx.proxyModel;
193869
194601
  ctx.toolState.model = ctx.proxyModel;
193870
194602
  const label = ctx.oss ? "oss" : "router";
@@ -193935,7 +194667,7 @@ async function runProxyResolution(ctx) {
193935
194667
 
193936
194668
  // utils/prSummary.ts
193937
194669
  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";
194670
+ import { dirname as dirname5, join as join26 } from "node:path";
193939
194671
  var SUMMARY_FILE_NAME = "pullfrog-summary.md";
193940
194672
  var SUMMARY_SCAFFOLD = `# PR summary
193941
194673
 
@@ -193945,7 +194677,7 @@ var SUMMARY_SCAFFOLD = `# PR summary
193945
194677
  var MIN_SNAPSHOT_LENGTH = 60;
193946
194678
  var MAX_SNAPSHOT_LENGTH = 32768;
193947
194679
  function summaryFilePath(tmpdir3) {
193948
- return join25(tmpdir3, SUMMARY_FILE_NAME);
194680
+ return join26(tmpdir3, SUMMARY_FILE_NAME);
193949
194681
  }
193950
194682
  async function seedSummaryFile(params) {
193951
194683
  const path4 = summaryFilePath(params.tmpdir);
@@ -194074,6 +194806,7 @@ var defaultSettings = {
194074
194806
  shell: "restricted",
194075
194807
  prApproveEnabled: false,
194076
194808
  autoMergeEnabled: false,
194809
+ codexAgent: false,
194077
194810
  signedCommits: false,
194078
194811
  repoIntelligence: false,
194079
194812
  progressComments: true,
@@ -194562,7 +195295,7 @@ ${genericBody}`,
194562
195295
  }
194563
195296
 
194564
195297
  // utils/runLifecycle.ts
194565
- var core9 = __toESM(require_core(), 1);
195298
+ var core10 = __toESM(require_core(), 1);
194566
195299
 
194567
195300
  // utils/autoMerge.ts
194568
195301
  function hasBlockingHumanReview(reviews) {
@@ -194948,7 +195681,7 @@ async function finalizeSuccessRun(input) {
194948
195681
  }
194949
195682
  if (input.toolState.output) {
194950
195683
  log.info(`::pullfrog-output::${Buffer.from(input.toolState.output).toString("base64")}`);
194951
- core9.setOutput("result", input.toolState.output);
195684
+ core10.setOutput("result", input.toolState.output);
194952
195685
  }
194953
195686
  if (input.result.success) {
194954
195687
  await approveAfterFix(input.toolContext).catch((error52) => {
@@ -195412,7 +196145,11 @@ async function main() {
195412
196145
  }
195413
196146
  const resolvedModel = credentials.kind === "fellBack" ? credentials.replacement : configuredModel;
195414
196147
  vertexCredentials = materializeVertexCredentials({ model: resolvedModel });
195415
- const agent2 = resolveAgent({ model: resolvedModel });
196148
+ const agent2 = resolveAgent({
196149
+ model: resolvedModel,
196150
+ proxyModel: payload.proxyModel,
196151
+ codexAgent: runContext.repoSettings.codexAgent
196152
+ });
195416
196153
  const effectiveModel = payload.proxyModel ?? resolvedModel ?? payload.model;
195417
196154
  toolState.model = effectiveModel;
195418
196155
  if (!payload.proxyModel) {
@@ -195596,8 +196333,8 @@ ${instructions.user}` : null,
195596
196333
  log.info(instructions.full);
195597
196334
  });
195598
196335
  if (agentId === "opencode") {
195599
- const pluginDir = join26(process.cwd(), ".opencode", "plugin");
195600
- const hasPlugins = existsSync8(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
196336
+ const pluginDir = join27(process.cwd(), ".opencode", "plugin");
196337
+ const hasPlugins = existsSync9(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
195601
196338
  if (hasPlugins && toolState.dependencyInstallation?.promise) {
195602
196339
  log.info(
195603
196340
  "\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"
@@ -195629,16 +196366,14 @@ ${instructions.user}` : null,
195629
196366
  const onInnerActivityTimeout = () => {
195630
196367
  if (innerTimeoutFired) return;
195631
196368
  innerTimeoutFired = true;
195632
- log.info(
195633
- "\xBB inner activity timeout fired \u2014 stopping MCP server and starting 5min safety-net timer"
195634
- );
195635
- mcpHttpServer[Symbol.asyncDispose]().catch((err) => {
195636
- log.debug(
195637
- `mcp server stop after inner kill failed: ${err instanceof Error ? err.message : String(err)}`
195638
- );
195639
- });
196369
+ log.info("\xBB inner activity timeout fired \u2014 starting 5min safety-net timer");
195640
196370
  safetyNetTimer = setTimeout(
195641
196371
  () => {
196372
+ mcpHttpServer[Symbol.asyncDispose]().catch((err) => {
196373
+ log.debug(
196374
+ `mcp server stop after inner kill failed: ${err instanceof Error ? err.message : String(err)}`
196375
+ );
196376
+ });
195642
196377
  activityTimeout?.forceReject(
195643
196378
  "agent still pending 5min after inner activity kill \u2014 forcing exit"
195644
196379
  );
@@ -195647,6 +196382,13 @@ ${instructions.user}` : null,
195647
196382
  );
195648
196383
  safetyNetTimer.unref?.();
195649
196384
  };
196385
+ const onTurnRecovered = () => {
196386
+ if (!innerTimeoutFired) return;
196387
+ innerTimeoutFired = false;
196388
+ if (safetyNetTimer) clearTimeout(safetyNetTimer);
196389
+ safetyNetTimer = void 0;
196390
+ log.info("\xBB inner activity safety net stood down \u2014 turn recovered");
196391
+ };
195650
196392
  const agentPromise = agent2.run({
195651
196393
  payload,
195652
196394
  resolvedModel,
@@ -195667,6 +196409,7 @@ ${instructions.user}` : null,
195667
196409
  toolState,
195668
196410
  apiToken: runContext.apiToken,
195669
196411
  onActivityTimeout: onInnerActivityTimeout,
196412
+ onTurnRecovered,
195670
196413
  onToolUse: (event) => {
195671
196414
  const wasTracked = recordDiffReadFromToolUse({
195672
196415
  state: primaryRepoState(toolState).diffCoverage,