pullfrog 0.1.45 → 0.1.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -101356,6 +101356,10 @@ async function setPullfrogSecret(ctx) {
101356
101356
  }
101357
101357
  return { saved: false, error: result.data.error || `api returned ${result.status}` };
101358
101358
  }
101359
+ function describeSecretTarget(ctx) {
101360
+ if (ctx.scope === "account") return `account ${import_picocolors.default.cyan(`@${ctx.owner}`)}`;
101361
+ return `repo ${import_picocolors.default.cyan(`${ctx.owner}/${ctx.repo}`)}`;
101362
+ }
101359
101363
  async function promptScope(ctx) {
101360
101364
  const scope2 = await _t({
101361
101365
  message: "secret scope",
@@ -101579,7 +101583,8 @@ async function runCodexAuth() {
101579
101583
  O2.warn(err instanceof Error ? err.message : String(err));
101580
101584
  savable = auth2;
101581
101585
  }
101582
- spin.start(`saving ${import_picocolors2.default.cyan(CODEX_AUTH_SECRET)} to Pullfrog`);
101586
+ const target = describeSecretTarget({ owner: remote.owner, repo: remote.repo, scope: scope2 });
101587
+ spin.start(`saving ${import_picocolors2.default.cyan(CODEX_AUTH_SECRET)} to ${target}`);
101583
101588
  const result = await setPullfrogSecret({
101584
101589
  token,
101585
101590
  owner: remote.owner,
@@ -101596,7 +101601,7 @@ async function runCodexAuth() {
101596
101601
  );
101597
101602
  process.exit(1);
101598
101603
  }
101599
- spin.stop(`saved ${import_picocolors2.default.cyan(CODEX_AUTH_SECRET)} to Pullfrog (${scope2})`);
101604
+ spin.stop(`saved ${import_picocolors2.default.cyan(CODEX_AUTH_SECRET)} to ${target}`);
101600
101605
  setActiveSpin(null);
101601
101606
  gt("done.");
101602
101607
  } catch (error49) {
@@ -101693,7 +101698,8 @@ async function runClaudeAuth() {
101693
101698
  `that doesn't look like a ${import_picocolors2.default.cyan("claude setup-token")} token (expected ${import_picocolors2.default.cyan(`${CLAUDE_OAUTH_TOKEN_PREFIX}\u2026`)}). saving it anyway.`
101694
101699
  );
101695
101700
  }
101696
- spin.start(`saving ${import_picocolors2.default.cyan(CLAUDE_OAUTH_SECRET)} to Pullfrog`);
101701
+ const target = describeSecretTarget({ owner: remote.owner, repo: remote.repo, scope: scope2 });
101702
+ spin.start(`saving ${import_picocolors2.default.cyan(CLAUDE_OAUTH_SECRET)} to ${target}`);
101697
101703
  const result = await setPullfrogSecret({
101698
101704
  token,
101699
101705
  owner: remote.owner,
@@ -101710,7 +101716,7 @@ async function runClaudeAuth() {
101710
101716
  );
101711
101717
  process.exit(1);
101712
101718
  }
101713
- spin.stop(`saved ${import_picocolors2.default.cyan(CLAUDE_OAUTH_SECRET)} to Pullfrog (${scope2})`);
101719
+ spin.stop(`saved ${import_picocolors2.default.cyan(CLAUDE_OAUTH_SECRET)} to ${target}`);
101714
101720
  setActiveSpin(null);
101715
101721
  gt("done.");
101716
101722
  } catch (error49) {
@@ -102119,6 +102125,28 @@ var providers = {
102119
102125
  }
102120
102126
  }
102121
102127
  }),
102128
+ "openai-compatible": provider({
102129
+ // "Custom" is the picker group, "OpenAI-compatible" the entry under it, so the
102130
+ // menu reads `Custom › OpenAI-compatible` and a second custom backend (a
102131
+ // different wire format, say) slots in beside it without a rename. the
102132
+ // provider KEY stays `openai-compatible` — it's the stored slug and the
102133
+ // `OPENAI_COMPATIBLE_*` env prefix, so this is display-only.
102134
+ displayName: "Custom",
102135
+ // bring-your-own generic OpenAI-compatible endpoint — Cloudflare AI Gateway,
102136
+ // Alibaba DashScope, self-hosted vLLM, or any compatible gateway. base URL +
102137
+ // key + model ID are all supplied via env; nothing is cataloged or bumped.
102138
+ envVars: ["OPENAI_COMPATIBLE_BASE_URL", "OPENAI_COMPATIBLE_API_KEY", "OPENAI_COMPATIBLE_MODEL"],
102139
+ models: {
102140
+ // single routing entry — the actual model ID is read from
102141
+ // OPENAI_COMPATIBLE_MODEL at run time and the provider is materialized
102142
+ // via `@ai-sdk/openai-compatible`.
102143
+ byok: {
102144
+ displayName: "OpenAI-compatible",
102145
+ resolve: "openai-compatible",
102146
+ routing: "openai-compatible"
102147
+ }
102148
+ }
102149
+ }),
102122
102150
  openrouter: provider({
102123
102151
  displayName: "OpenRouter",
102124
102152
  envVars: ["OPENROUTER_API_KEY"],
@@ -102336,6 +102364,12 @@ var DEFAULT_PROXY_MODEL = defaultProxyAlias.openRouterResolve;
102336
102364
  var defaultProxyDisplayName = defaultProxyAlias.displayName;
102337
102365
  var BEDROCK_MODEL_ID_ENV = "BEDROCK_MODEL_ID";
102338
102366
  var VERTEX_MODEL_ID_ENV = "VERTEX_MODEL_ID";
102367
+ var OPENAI_COMPATIBLE_PROVIDER = "openai-compatible";
102368
+ var OPENAI_COMPATIBLE_BASE_URL_ENV = "OPENAI_COMPATIBLE_BASE_URL";
102369
+ var OPENAI_COMPATIBLE_API_KEY_ENV = "OPENAI_COMPATIBLE_API_KEY";
102370
+ var OPENAI_COMPATIBLE_MODEL_ENV = "OPENAI_COMPATIBLE_MODEL";
102371
+ var OPENAI_COMPATIBLE_CONTEXT_ENV = "OPENAI_COMPATIBLE_CONTEXT";
102372
+ var OPENAI_COMPATIBLE_MAX_OUTPUT_ENV = "OPENAI_COMPATIBLE_MAX_OUTPUT";
102339
102373
  function isBedrockAnthropicId(bedrockModelId) {
102340
102374
  return bedrockModelId.toLowerCase().split(/[./:]/).includes("anthropic");
102341
102375
  }
@@ -103022,7 +103056,7 @@ var import_semver = __toESM(require_semver2(), 1);
103022
103056
  // package.json
103023
103057
  var package_default = {
103024
103058
  name: "pullfrog",
103025
- version: "0.1.45",
103059
+ version: "0.1.47",
103026
103060
  type: "module",
103027
103061
  bin: {
103028
103062
  pullfrog: "dist/cli.mjs",
@@ -110769,8 +110803,9 @@ function readModels(cliPath) {
110769
110803
  stdio: ["ignore", "pipe", "pipe"]
110770
110804
  });
110771
110805
  if (result.status !== 0) {
110772
- failure = (result.stderr ?? "").replace(ANSI_PATTERN, "").trim() || void 0;
110773
- log.debug(`\xBB \`opencode models\` failed (${result.status}): ${failure ?? result.error}`);
110806
+ const stderr = (result.stderr ?? "").replace(ANSI_PATTERN, "").trim();
110807
+ failure = stderr || result.error?.message;
110808
+ log.debug(`\xBB \`opencode models\` failed (${result.status}): ${failure}`);
110774
110809
  return /* @__PURE__ */ new Set();
110775
110810
  }
110776
110811
  failure = void 0;
@@ -110828,6 +110863,29 @@ function geminiHighThinkingOverrides() {
110828
110863
  ])
110829
110864
  );
110830
110865
  }
110866
+ function openAICompatibleLimit() {
110867
+ return {
110868
+ context: Number(process.env[OPENAI_COMPATIBLE_CONTEXT_ENV]),
110869
+ output: Number(process.env[OPENAI_COMPATIBLE_MAX_OUTPUT_ENV])
110870
+ };
110871
+ }
110872
+ function openAICompatibleProvider(model) {
110873
+ const prefix = `${OPENAI_COMPATIBLE_PROVIDER}/`;
110874
+ if (!model?.startsWith(prefix)) return {};
110875
+ const modelId = model.slice(prefix.length);
110876
+ return {
110877
+ [OPENAI_COMPATIBLE_PROVIDER]: {
110878
+ npm: "@ai-sdk/openai-compatible",
110879
+ name: "OpenAI-compatible",
110880
+ options: {
110881
+ // trailing slash would double up against opencode's `/chat/completions` join
110882
+ baseURL: process.env[OPENAI_COMPATIBLE_BASE_URL_ENV]?.replace(/\/$/, ""),
110883
+ apiKey: process.env[OPENAI_COMPATIBLE_API_KEY_ENV]
110884
+ },
110885
+ models: { [modelId]: { name: modelId, limit: openAICompatibleLimit() } }
110886
+ }
110887
+ };
110888
+ }
110831
110889
  var KIMI_ENFORCERLESS_PROVIDERS = ["siliconflow", "together"];
110832
110890
  function kimiOpenRouterProviderOverrides() {
110833
110891
  return Object.fromEntries(
@@ -110919,7 +110977,8 @@ function buildSecurityConfig(ctx, model) {
110919
110977
  google: { models: geminiHighThinkingOverrides() },
110920
110978
  openrouter: {
110921
110979
  models: { ...deepseekHighEffortOverrides(), ...kimiOpenRouterProviderOverrides() }
110922
- }
110980
+ },
110981
+ ...openAICompatibleProvider(model)
110923
110982
  }
110924
110983
  };
110925
110984
  if (model) {
@@ -159914,6 +159973,9 @@ var JsonPayload = type({
159914
159973
  id: "string",
159915
159974
  type: "'issue' | 'review'"
159916
159975
  }).or("undefined"),
159976
+ // optional so a payload from an older server build (pre-`checkRun`) still parses
159977
+ // against a newer action across a rolling deploy.
159978
+ "checkRun?": type({ id: "string" }).or("undefined"),
159917
159979
  "generateSummary?": "boolean | undefined"
159918
159980
  });
159919
159981
  var COLLABORATOR_PERMISSIONS = ["admin", "maintain", "write"];
@@ -160027,13 +160089,19 @@ function resolvePayload(resolvedPromptInput, repoSettings) {
160027
160089
  timeout: inputs.timeout ?? jsonPayload?.timeout,
160028
160090
  cwd: resolveCwd(inputs.cwd),
160029
160091
  progressComment: jsonPayload?.progressComment,
160092
+ checkRun: jsonPayload?.checkRun,
160030
160093
  generateSummary: jsonPayload?.generateSummary,
160031
160094
  // permissions: inputs > repoSettings > fallbacks
160032
160095
  push: inputs.push ?? repoSettings.push ?? "restricted",
160033
160096
  shell: resolvedShell,
160034
- // opt-in commit-status check-runs (branch protection). workflow-level
160035
- // static input, off unless the repo's pullfrog.yml sets status_checks: enabled.
160036
- statusChecks: inputs.status_checks === "enabled",
160097
+ // the `pullfrog` run-lifecycle check. ON by default — the whole point is that a PR
160098
+ // shows whether Pullfrog is running without anyone having to opt in. the workflow
160099
+ // input is the source of truth when set (mirrors `push`); otherwise the repo
160100
+ // setting decides.
160101
+ runStatusCheck: inputs.status_checks === void 0 ? repoSettings.statusChecks : inputs.status_checks === "enabled",
160102
+ // the `pullfrog-approval` verdict check stays opt-in and workflow-only. it exists to
160103
+ // be *required* by branch protection, so it must never turn itself on.
160104
+ approvalCheck: inputs.status_checks === "enabled",
160037
160105
  // temporary progress chrome. the workflow input is the source of truth when
160038
160106
  // set (mirrors `push`); otherwise the repo setting decides. defaults to true.
160039
160107
  progressComments: inputs.progress_comments === void 0 ? repoSettings.progressComments : inputs.progress_comments === "enabled",
@@ -160561,7 +160629,11 @@ async function approveAfterFix(ctx) {
160561
160629
  approved: true,
160562
160630
  hasComments: false
160563
160631
  });
160564
- ctx.toolState.approval = { wouldApprove: true, sha: headSha };
160632
+ ctx.toolState.approval = {
160633
+ wouldApprove: true,
160634
+ sha: headSha,
160635
+ url: result.data.html_url
160636
+ };
160565
160637
  log.info(`\xBB auto-approved #${pullNumber} after fix run (review ${result.data.id})`);
160566
160638
  await deleteProgressComment(ctx).catch((err) => {
160567
160639
  log.debug(`progress comment cleanup after fix auto-approval failed: ${err}`);
@@ -160894,6 +160966,7 @@ function CreatePullRequestReviewTool(ctx) {
160894
160966
  }
160895
160967
  const reviewId = result.data.id;
160896
160968
  const reviewNodeId = result.data.node_id;
160969
+ if (ctx.toolState.approval) ctx.toolState.approval.url = result.data.html_url;
160897
160970
  log.info(`\xBB created review ${reviewId} on pull request #${pull_number}`);
160898
160971
  const actuallyReviewedSha = primary.checkoutSha ?? params.commit_id;
160899
160972
  ctx.toolState.review = {
@@ -165185,6 +165258,15 @@ function resolveSlug(slug2) {
165185
165258
  }
165186
165259
  return vertexId;
165187
165260
  }
165261
+ if (alias?.routing === "openai-compatible") {
165262
+ const modelId = process.env[OPENAI_COMPATIBLE_MODEL_ENV]?.trim();
165263
+ if (!modelId) {
165264
+ throw new Error(
165265
+ `${OPENAI_COMPATIBLE_MODEL_ENV} env var is required when the model is set to "${slug2}". set it to the model ID served by your OpenAI-compatible endpoint (e.g. a Cloudflare AI Gateway or DashScope model). see https://docs.pullfrog.com/openai-compatible for setup.`
165266
+ );
165267
+ }
165268
+ return `${OPENAI_COMPATIBLE_PROVIDER}/${modelId}`;
165269
+ }
165188
165270
  return resolveCliModel(slug2);
165189
165271
  }
165190
165272
  function resolveModel(ctx) {
@@ -165234,6 +165316,21 @@ function resolveAgent(ctx) {
165234
165316
 
165235
165317
  // utils/apiKeys.ts
165236
165318
  var MISSING_KEY_MARKER = "no API key found";
165319
+ var SECRETS_UNAVAILABLE_MARKER = "couldn't load your Pullfrog secrets";
165320
+ function buildKeyError(params) {
165321
+ return params.secretsUnavailable ? buildSecretsUnavailableError(params) : buildMissingApiKeyError(params);
165322
+ }
165323
+ function buildSecretsUnavailableError(params) {
165324
+ const settingsUrl = `${getApiUrl()}/console/${params.owner}/${params.name}`;
165325
+ const modelClause = params.model ? ` needed for \`${params.model}\`` : "";
165326
+ return [
165327
+ `**Pullfrog ${SECRETS_UNAVAILABLE_MARKER}${modelClause} on this run.** The key is still stored \u2014 the runner just couldn't fetch it, so this is a transient failure on our side, not a missing key.`,
165328
+ "",
165329
+ "**To fix:** re-run the job. If it keeps happening, tell us in Discord.",
165330
+ "",
165331
+ `[Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/keys) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
165332
+ ].join("\n");
165333
+ }
165237
165334
  function buildMissingApiKeyError(params) {
165238
165335
  const githubSecretsUrl = `https://github.com/${params.owner}/${params.name}/settings/secrets/actions`;
165239
165336
  const settingsUrl = `${getApiUrl()}/console/${params.owner}/${params.name}`;
@@ -165276,10 +165373,47 @@ add the missing secret(s) to your GitHub repository at ${githubSecretsUrl}, then
165276
165373
 
165277
165374
  for full setup instructions, see https://docs.pullfrog.com/vertex`;
165278
165375
  }
165376
+ function buildOpenAICompatibleSetupError(params) {
165377
+ const githubSecretsUrl = `https://github.com/${params.owner}/${params.name}/settings/secrets/actions`;
165378
+ return `OpenAI-compatible model selected but required configuration is missing: ${params.missing.join(", ")}.
165379
+
165380
+ only the API key is sensitive \u2014 add it as a secret at ${githubSecretsUrl}. everything else is plain workflow \`env:\`:
165381
+
165382
+ ${OPENAI_COMPATIBLE_BASE_URL_ENV}: https://your-endpoint.example.com/v1
165383
+ ${OPENAI_COMPATIBLE_API_KEY_ENV}: \${{ secrets.${OPENAI_COMPATIBLE_API_KEY_ENV} }}
165384
+ ${OPENAI_COMPATIBLE_MODEL_ENV}: <model-id>
165385
+ ${OPENAI_COMPATIBLE_CONTEXT_ENV}: "128000"
165386
+ ${OPENAI_COMPATIBLE_MAX_OUTPUT_ENV}: "16384"
165387
+
165388
+ set the last two to the real limits of the model your endpoint serves. Pullfrog can't
165389
+ discover them \u2014 your endpoint owns the model catalog \u2014 and without them completions are
165390
+ capped at 32000 tokens (rejected outright by models with a smaller cap) and
165391
+ auto-compaction is disabled, so long runs grow until your endpoint refuses them.
165392
+
165393
+ for full setup instructions, see https://docs.pullfrog.com/openai-compatible`;
165394
+ }
165279
165395
  function hasEnvVar3(name) {
165280
165396
  const value2 = process.env[name];
165281
165397
  return typeof value2 === "string" && value2.length > 0;
165282
165398
  }
165399
+ function hasPositiveNumberEnvVar(name) {
165400
+ return Number(process.env[name]) > 0;
165401
+ }
165402
+ function validateOpenAICompatibleSetup(params) {
165403
+ const missing = [];
165404
+ if (!hasEnvVar3(OPENAI_COMPATIBLE_BASE_URL_ENV)) missing.push(OPENAI_COMPATIBLE_BASE_URL_ENV);
165405
+ if (!hasEnvVar3(OPENAI_COMPATIBLE_API_KEY_ENV)) missing.push(OPENAI_COMPATIBLE_API_KEY_ENV);
165406
+ if (!hasEnvVar3(OPENAI_COMPATIBLE_MODEL_ENV)) missing.push(OPENAI_COMPATIBLE_MODEL_ENV);
165407
+ if (!hasPositiveNumberEnvVar(OPENAI_COMPATIBLE_CONTEXT_ENV))
165408
+ missing.push(OPENAI_COMPATIBLE_CONTEXT_ENV);
165409
+ if (!hasPositiveNumberEnvVar(OPENAI_COMPATIBLE_MAX_OUTPUT_ENV))
165410
+ missing.push(OPENAI_COMPATIBLE_MAX_OUTPUT_ENV);
165411
+ if (missing.length > 0) {
165412
+ throw new Error(
165413
+ buildOpenAICompatibleSetupError({ owner: params.owner, name: params.name, missing })
165414
+ );
165415
+ }
165416
+ }
165283
165417
  function validateBedrockSetup(params) {
165284
165418
  const hasAuth = hasEnvVar3("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar3("AWS_ACCESS_KEY_ID") && hasEnvVar3("AWS_SECRET_ACCESS_KEY");
165285
165419
  const missing = [];
@@ -165314,6 +165448,10 @@ function validateAgentApiKey(params) {
165314
165448
  validateVertexSetup({ owner: params.owner, name: params.name });
165315
165449
  return;
165316
165450
  }
165451
+ if (params.model.startsWith(`${OPENAI_COMPATIBLE_PROVIDER}/`)) {
165452
+ validateOpenAICompatibleSetup({ owner: params.owner, name: params.name });
165453
+ return;
165454
+ }
165317
165455
  if (!params.model.includes("/")) {
165318
165456
  if (process.env[VERTEX_MODEL_ID_ENV]?.trim() === params.model) {
165319
165457
  validateVertexSetup({ owner: params.owner, name: params.name });
@@ -165326,23 +165464,46 @@ function validateAgentApiKey(params) {
165326
165464
  if (params.authorized.has(params.model)) return;
165327
165465
  const reason = getModelsFailure();
165328
165466
  if (reason) throw new Error(reason);
165467
+ if (getModelEnvVars(params.model).some(hasEnvVar3)) return;
165329
165468
  throw new Error(
165330
- buildMissingApiKeyError({ owner: params.owner, name: params.name, model: params.model })
165469
+ buildKeyError({
165470
+ owner: params.owner,
165471
+ name: params.name,
165472
+ model: params.model,
165473
+ secretsUnavailable: params.secretsUnavailable
165474
+ })
165331
165475
  );
165332
165476
  }
165333
165477
  if (hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN")) return;
165334
165478
  throw new Error(
165335
- buildMissingApiKeyError({ owner: params.owner, name: params.name, model: params.model })
165479
+ buildKeyError({
165480
+ owner: params.owner,
165481
+ name: params.name,
165482
+ model: params.model,
165483
+ secretsUnavailable: params.secretsUnavailable
165484
+ })
165336
165485
  );
165337
165486
  }
165338
165487
  if (params.agent.name === "opencode") {
165339
165488
  if (params.authorized.size > 0) return;
165340
165489
  const reason = getModelsFailure();
165341
165490
  if (reason) throw new Error(reason);
165342
- throw new Error(buildMissingApiKeyError({ owner: params.owner, name: params.name }));
165491
+ throw new Error(
165492
+ buildKeyError({
165493
+ owner: params.owner,
165494
+ name: params.name,
165495
+ secretsUnavailable: params.secretsUnavailable
165496
+ })
165497
+ );
165343
165498
  }
165344
165499
  if (hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN")) return;
165345
- throw new Error(buildMissingApiKeyError({ owner: params.owner, name: params.name }));
165500
+ throw new Error(
165501
+ buildKeyError({
165502
+ owner: params.owner,
165503
+ name: params.name,
165504
+ secretsUnavailable: params.secretsUnavailable
165505
+ })
165506
+ );
165346
165507
  }
165347
165508
  function isApiKeyAuthError(text) {
165348
165509
  if (!text) return false;
@@ -166027,7 +166188,7 @@ async function persistXrepoLearnings(ctx) {
166027
166188
  // utils/modelAccess.ts
166028
166189
  function decideModelAccess(input) {
166029
166190
  if (!input.modelExplicit || !input.model) return { kind: "ok" };
166030
- const byokAuthorized = !!input.resolvedModel && (input.authorized.has(input.resolvedModel) || !input.resolvedModel.includes("/"));
166191
+ const byokAuthorized = !!input.resolvedModel && (input.authorized.has(input.resolvedModel) || !input.resolvedModel.includes("/") || input.resolvedModel.startsWith(`${OPENAI_COMPATIBLE_PROVIDER}/`));
166031
166192
  if (input.proxyActive) {
166032
166193
  const target = resolveOpenRouterModel(input.model);
166033
166194
  if (input.oss) {
@@ -166822,6 +166983,7 @@ var defaultSettings = {
166822
166983
  autoMergeEnabled: false,
166823
166984
  signedCommits: false,
166824
166985
  progressComments: true,
166986
+ statusChecks: true,
166825
166987
  modeInstructions: {},
166826
166988
  learnings: null,
166827
166989
  learningsHeadings: [],
@@ -166836,6 +166998,10 @@ var defaultRunContext = {
166836
166998
  oss: false,
166837
166999
  plan: "none"
166838
167000
  };
167001
+ var unknownSecretsRunContext = {
167002
+ ...defaultRunContext,
167003
+ secretsUnavailable: true
167004
+ };
166839
167005
  async function fetchRunContext(params) {
166840
167006
  const timeoutMs = 3e4;
166841
167007
  const controller = new AbortController();
@@ -166854,7 +167020,7 @@ async function fetchRunContext(params) {
166854
167020
  });
166855
167021
  clearTimeout(timeoutId);
166856
167022
  if (!response.ok) {
166857
- return defaultRunContext;
167023
+ return response.status >= 500 ? unknownSecretsRunContext : defaultRunContext;
166858
167024
  }
166859
167025
  const data = await response.json();
166860
167026
  if (data === null) {
@@ -166878,11 +167044,12 @@ async function fetchRunContext(params) {
166878
167044
  oss: data.oss ?? false,
166879
167045
  plan: data.plan ?? "none",
166880
167046
  proxyModel: data.proxyModel,
166881
- dbSecrets: data.dbSecrets
167047
+ dbSecrets: data.dbSecrets,
167048
+ secretsUnavailable: data.secretsUnavailable
166882
167049
  };
166883
167050
  } catch {
166884
167051
  clearTimeout(timeoutId);
166885
- return defaultRunContext;
167052
+ return unknownSecretsRunContext;
166886
167053
  }
166887
167054
  }
166888
167055
 
@@ -166904,7 +167071,10 @@ async function resolveRunContextData(params) {
166904
167071
  const repoContext = parseRepoContext();
166905
167072
  let oidcToken;
166906
167073
  try {
166907
- oidcToken = await core9.getIDToken("pullfrog-api");
167074
+ oidcToken = await op(() => core9.getIDToken("pullfrog-api"), {
167075
+ name: "OIDC mint",
167076
+ retries: process.env.ACTIONS_ID_TOKEN_REQUEST_URL ? [200, 1e3] : []
167077
+ })();
166908
167078
  } catch {
166909
167079
  }
166910
167080
  const [repoResponse, runContext] = await Promise.all([
@@ -166926,7 +167096,10 @@ async function resolveRunContextData(params) {
166926
167096
  oss: runContext.oss,
166927
167097
  plan: runContext.plan,
166928
167098
  proxyModel: runContext.proxyModel,
166929
- dbSecrets: runContext.dbSecrets
167099
+ dbSecrets: runContext.dbSecrets,
167100
+ // a failed mint on a runner that should have been able to mint is the same
167101
+ // outcome as the server-side failure: the run never sees stored secrets.
167102
+ secretsUnavailable: runContext.secretsUnavailable || !!process.env.ACTIONS_ID_TOKEN_REQUEST_URL && oidcToken === void 0
166930
167103
  };
166931
167104
  }
166932
167105
 
@@ -167091,6 +167264,9 @@ function renderRunError(input) {
167091
167264
  if (input.errorMessage.includes(MODEL_ACCESS_MARKER)) {
167092
167265
  return { summary: input.errorMessage, comment: input.errorMessage };
167093
167266
  }
167267
+ if (input.errorMessage.includes(SECRETS_UNAVAILABLE_MARKER)) {
167268
+ return { summary: input.errorMessage, comment: input.errorMessage };
167269
+ }
167094
167270
  const isHang = input.errorMessage.startsWith("activity timeout") || input.errorMessage.startsWith("agent still pending");
167095
167271
  const hangBody = isHang ? formatAgentHangBody({
167096
167272
  diagnostic: input.agentDiagnostic,
@@ -167329,30 +167505,123 @@ function getCurrentWorkflowFilename() {
167329
167505
  return match3?.[1] ?? "pullfrog.yml";
167330
167506
  }
167331
167507
 
167332
- // utils/statusChecks.ts
167333
- var COMPLETION_CHECK = "pullfrog";
167334
- var APPROVAL_CHECK = "pullfrog-approval";
167335
- async function createCheckRun(ctx, params) {
167508
+ // utils/runStatusCheck.ts
167509
+ var RUN_STATUS_CHECK_NAME = "Pullfrog";
167510
+ var APPROVAL_CHECK_NAME = "Pullfrog approval";
167511
+ function parseCheckRunId(raw2) {
167512
+ if (!raw2?.id) return void 0;
167513
+ const id = parseInt(raw2.id, 10);
167514
+ if (Number.isNaN(id) || id <= 0) return void 0;
167515
+ return id;
167516
+ }
167517
+ function disableCheckLine(owner, repo) {
167518
+ const url4 = `https://pullfrog.com/console/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}#auto-review-prs`;
167519
+ return `
167520
+
167521
+ [Turn off this check \u2192](${url4}) \u2014 it reports run status only and gates nothing unless you required it in branch protection.`;
167522
+ }
167523
+ var TERMINAL_OUTPUT = {
167524
+ success: {
167525
+ title: "Pullfrog run completed",
167526
+ summary: "The Pullfrog run finished successfully."
167527
+ },
167528
+ failure: {
167529
+ title: "Pullfrog run failed",
167530
+ summary: "The Pullfrog run failed. See the run logs for details."
167531
+ },
167532
+ cancelled: {
167533
+ title: "Pullfrog run cancelled",
167534
+ summary: "The Pullfrog run was cancelled before it finished."
167535
+ },
167536
+ timed_out: {
167537
+ title: "Pullfrog run timed out",
167538
+ summary: "The Pullfrog run exceeded its timeout. See the run logs for details."
167539
+ },
167540
+ action_required: {
167541
+ title: "Pullfrog run needs attention",
167542
+ summary: "The Pullfrog run stopped and needs attention. See the run logs for details."
167543
+ },
167544
+ neutral: {
167545
+ title: "Pullfrog run finished",
167546
+ summary: "The Pullfrog run finished without a pass or fail outcome."
167547
+ },
167548
+ skipped: {
167549
+ title: "Pullfrog run skipped",
167550
+ summary: "This run was superseded by another Pullfrog run."
167551
+ },
167552
+ stale: {
167553
+ title: "Pullfrog run didn't finish",
167554
+ summary: "Pullfrog never received a completion signal for this run. See the run logs for details."
167555
+ }
167556
+ };
167557
+ function terminalOutput(params) {
167558
+ const base = TERMINAL_OUTPUT[params.conclusion];
167559
+ const review = params.reviewUrl ? `
167560
+
167561
+ [View the review Pullfrog posted \u2192](${params.reviewUrl})` : "";
167562
+ const disable = params.conclusion === "success" ? "" : disableCheckLine(params.owner, params.repo);
167563
+ return { title: base.title, summary: base.summary + review + disable };
167564
+ }
167565
+ async function finalizeRunStatusCheck(params) {
167566
+ const updateParams = {
167567
+ owner: params.owner,
167568
+ repo: params.repo,
167569
+ check_run_id: params.checkRunId,
167570
+ status: "completed",
167571
+ conclusion: params.conclusion,
167572
+ output: terminalOutput({
167573
+ conclusion: params.conclusion,
167574
+ owner: params.owner,
167575
+ repo: params.repo,
167576
+ reviewUrl: params.reviewUrl
167577
+ })
167578
+ };
167579
+ if (params.detailsUrl) updateParams.details_url = params.detailsUrl;
167580
+ await params.octokit.rest.checks.update(updateParams);
167581
+ }
167582
+ async function createTerminalRunStatusCheck(params) {
167336
167583
  const createParams = {
167337
- owner: ctx.repo.owner,
167338
- repo: ctx.repo.name,
167339
- name: params.name,
167584
+ owner: params.owner,
167585
+ repo: params.repo,
167586
+ name: RUN_STATUS_CHECK_NAME,
167340
167587
  head_sha: params.headSha,
167341
167588
  status: "completed",
167342
167589
  conclusion: params.conclusion,
167343
- output: { title: params.title, summary: params.summary }
167590
+ output: terminalOutput({
167591
+ conclusion: params.conclusion,
167592
+ owner: params.owner,
167593
+ repo: params.repo,
167594
+ reviewUrl: params.reviewUrl
167595
+ })
167344
167596
  };
167345
- if (ctx.runId) {
167346
- createParams.details_url = `https://github.com/${ctx.repo.owner}/${ctx.repo.name}/actions/runs/${ctx.runId}`;
167347
- }
167348
- await ctx.octokit.rest.checks.create(createParams);
167349
- log.info(`\xBB posted ${params.name} check (${params.conclusion}) on ${params.headSha.slice(0, 7)}`);
167597
+ if (params.detailsUrl) createParams.details_url = params.detailsUrl;
167598
+ await params.octokit.rest.checks.create(createParams);
167350
167599
  }
167600
+
167601
+ // utils/statusChecks.ts
167351
167602
  async function reportStatusChecks(ctx, params) {
167352
- if (!ctx.payload.statusChecks) return;
167353
167603
  const event = ctx.payload.event;
167354
167604
  const pullNumber = event.issue_number;
167355
167605
  if (event.is_pr !== true || typeof pullNumber !== "number") return;
167606
+ const checkRunId = parseCheckRunId(ctx.payload.checkRun);
167607
+ if (checkRunId === void 0 && !ctx.payload.runStatusCheck && !ctx.payload.approvalCheck) return;
167608
+ const conclusion = params.runSucceeded ? "success" : "failure";
167609
+ const detailsUrl = ctx.runId ? `https://github.com/${ctx.repo.owner}/${ctx.repo.name}/actions/runs/${ctx.runId}` : void 0;
167610
+ if (checkRunId !== void 0) {
167611
+ await finalizeRunStatusCheck({
167612
+ octokit: ctx.octokit,
167613
+ owner: ctx.repo.owner,
167614
+ repo: ctx.repo.name,
167615
+ checkRunId,
167616
+ conclusion,
167617
+ detailsUrl,
167618
+ reviewUrl: ctx.toolState.approval?.url
167619
+ }).then(() => log.info(`\xBB finalized ${RUN_STATUS_CHECK_NAME} check (${conclusion})`)).catch((err) => log.debug(`status checks: ${RUN_STATUS_CHECK_NAME} finalize failed: ${err}`));
167620
+ }
167621
+ const approval = ctx.toolState.approval;
167622
+ const needsApprovalCheck = ctx.payload.approvalCheck && params.runSucceeded && approval;
167623
+ const needsFallbackRunCheck = ctx.payload.runStatusCheck && checkRunId === void 0;
167624
+ if (!needsApprovalCheck && !needsFallbackRunCheck) return;
167356
167625
  let headSha;
167357
167626
  try {
167358
167627
  const pr = await ctx.octokit.rest.pulls.get({
@@ -167365,24 +167634,32 @@ async function reportStatusChecks(ctx, params) {
167365
167634
  log.debug(`status checks: failed to resolve PR #${pullNumber} head sha: ${err}`);
167366
167635
  return;
167367
167636
  }
167368
- const completionSha = primaryRepoState(ctx.toolState).checkoutSha ?? headSha;
167369
- await createCheckRun(ctx, {
167370
- name: COMPLETION_CHECK,
167371
- headSha: completionSha,
167372
- conclusion: params.runSucceeded ? "success" : "failure",
167373
- title: params.runSucceeded ? "Pullfrog run completed" : "Pullfrog run failed",
167374
- summary: params.runSucceeded ? "The Pullfrog run finished successfully." : "The Pullfrog run failed or timed out. See the run logs for details."
167375
- }).catch((err) => log.debug(`status checks: ${COMPLETION_CHECK} post failed: ${err}`));
167376
- const approval = ctx.toolState.approval;
167377
- if (params.runSucceeded && approval) {
167378
- await createCheckRun(ctx, {
167379
- name: APPROVAL_CHECK,
167380
- headSha: approval.sha ?? headSha,
167381
- conclusion: approval.wouldApprove ? "success" : "failure",
167637
+ if (needsFallbackRunCheck) {
167638
+ await createTerminalRunStatusCheck({
167639
+ octokit: ctx.octokit,
167640
+ owner: ctx.repo.owner,
167641
+ repo: ctx.repo.name,
167642
+ headSha: primaryRepoState(ctx.toolState).checkoutSha ?? headSha,
167643
+ conclusion,
167644
+ detailsUrl,
167645
+ reviewUrl: ctx.toolState.approval?.url
167646
+ }).then(() => log.info(`\xBB posted ${RUN_STATUS_CHECK_NAME} check (${conclusion})`)).catch((err) => log.debug(`status checks: ${RUN_STATUS_CHECK_NAME} post failed: ${err}`));
167647
+ }
167648
+ if (!needsApprovalCheck || !approval) return;
167649
+ const createParams = {
167650
+ owner: ctx.repo.owner,
167651
+ repo: ctx.repo.name,
167652
+ name: APPROVAL_CHECK_NAME,
167653
+ head_sha: approval.sha ?? headSha,
167654
+ status: "completed",
167655
+ conclusion: approval.wouldApprove ? "success" : "failure",
167656
+ output: {
167382
167657
  title: approval.wouldApprove ? "Pullfrog would approve" : "Pullfrog would not approve",
167383
167658
  summary: approval.wouldApprove ? "Pullfrog has no outstanding review feedback on this PR." : "Pullfrog has outstanding review feedback or requested changes on this PR."
167384
- }).catch((err) => log.debug(`status checks: ${APPROVAL_CHECK} post failed: ${err}`));
167385
- }
167659
+ }
167660
+ };
167661
+ if (detailsUrl) createParams.details_url = detailsUrl;
167662
+ await ctx.octokit.rest.checks.create(createParams).then(() => log.info(`\xBB posted ${APPROVAL_CHECK_NAME} check`)).catch((err) => log.debug(`status checks: ${APPROVAL_CHECK_NAME} post failed: ${err}`));
167386
167663
  }
167387
167664
 
167388
167665
  // utils/runLifecycle.ts
@@ -167825,7 +168102,8 @@ async function main() {
167825
168102
  model: effectiveModel,
167826
168103
  authorized: getAuthorizedModels(),
167827
168104
  owner: runContext.repo.owner,
167828
- name: runContext.repo.name
168105
+ name: runContext.repo.name,
168106
+ secretsUnavailable: runContext.secretsUnavailable
167829
168107
  });
167830
168108
  }
167831
168109
  await setupGit({
@@ -168322,7 +168600,7 @@ function link(text, url4) {
168322
168600
  return `\x1B]8;;${url4}\x07${text}\x1B]8;;\x07`;
168323
168601
  }
168324
168602
  function buildProviders() {
168325
- return Object.entries(providers).filter(([key]) => key !== "opencode" && key !== "openrouter" && key !== "bedrock").map(([key, config3]) => {
168603
+ return Object.entries(providers).filter(([key]) => key !== "opencode" && key !== "openrouter").map(([key, config3]) => {
168326
168604
  const aliases = modelAliases.filter(
168327
168605
  (a) => a.provider === key && !a.fallback && !a.routing && !a.hidden
168328
168606
  );
@@ -168342,7 +168620,7 @@ function buildProviders() {
168342
168620
  hint: a === recommended ? "recommended" : void 0
168343
168621
  }))
168344
168622
  };
168345
- });
168623
+ }).filter((p2) => p2.models.length > 0);
168346
168624
  }
168347
168625
  var CLI_PROVIDERS = buildProviders();
168348
168626
  function resolveModelProvider(slug2) {
@@ -168782,7 +169060,8 @@ async function handleSecret(ctx) {
168782
169060
  }
168783
169061
  if (method === "pullfrog") {
168784
169062
  const scope2 = ctx.secrets.isOrg ? await promptScope2(ctx) : "account";
168785
- activeSpin2.start(`saving ${envVar}`);
169063
+ const target = describeSecretTarget({ owner: ctx.owner, repo: ctx.repo, scope: scope2 });
169064
+ activeSpin2.start(`saving ${import_picocolors3.default.cyan(envVar)} to ${target}`);
168786
169065
  let saveResult;
168787
169066
  try {
168788
169067
  saveResult = await setPullfrogSecret2({
@@ -168802,7 +169081,7 @@ async function handleSecret(ctx) {
168802
169081
  return;
168803
169082
  }
168804
169083
  if (saveResult.saved) {
168805
- activeSpin2.stop(`saved ${import_picocolors3.default.cyan(envVar)} to Pullfrog`);
169084
+ activeSpin2.stop(`saved ${import_picocolors3.default.cyan(envVar)} to ${target}`);
168806
169085
  } else {
168807
169086
  activeSpin2.stop(import_picocolors3.default.red("could not save secret"));
168808
169087
  O2.warn(
@@ -169152,7 +169431,7 @@ async function runCli4(input) {
169152
169431
  }
169153
169432
 
169154
169433
  // cli.ts
169155
- var VERSION10 = "0.1.45";
169434
+ var VERSION10 = "0.1.47";
169156
169435
  var bin = basename2(process.argv[1] || "");
169157
169436
  var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
169158
169437
  var rawArgs = process.argv.slice(2);