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.
@@ -262,6 +262,14 @@ export interface WriteablePayload {
262
262
  id: string;
263
263
  type: "issue" | "review";
264
264
  } | undefined;
265
+ /**
266
+ * pre-created `pullfrog` check-run, seeded `in_progress` by the server at dispatch so
267
+ * the PR's checks list shows the run before the GHA runner boots. the action PATCHes
268
+ * it to a terminal conclusion at run end. see `action/utils/runStatusCheck.ts`.
269
+ */
270
+ checkRun?: {
271
+ id: string;
272
+ } | undefined;
265
273
  /** when true, seed the PR summary tmpfile + persist edits at run end */
266
274
  generateSummary?: boolean | undefined;
267
275
  }
package/dist/index.js CHANGED
@@ -100190,6 +100190,28 @@ var providers = {
100190
100190
  }
100191
100191
  }
100192
100192
  }),
100193
+ "openai-compatible": provider({
100194
+ // "Custom" is the picker group, "OpenAI-compatible" the entry under it, so the
100195
+ // menu reads `Custom › OpenAI-compatible` and a second custom backend (a
100196
+ // different wire format, say) slots in beside it without a rename. the
100197
+ // provider KEY stays `openai-compatible` — it's the stored slug and the
100198
+ // `OPENAI_COMPATIBLE_*` env prefix, so this is display-only.
100199
+ displayName: "Custom",
100200
+ // bring-your-own generic OpenAI-compatible endpoint — Cloudflare AI Gateway,
100201
+ // Alibaba DashScope, self-hosted vLLM, or any compatible gateway. base URL +
100202
+ // key + model ID are all supplied via env; nothing is cataloged or bumped.
100203
+ envVars: ["OPENAI_COMPATIBLE_BASE_URL", "OPENAI_COMPATIBLE_API_KEY", "OPENAI_COMPATIBLE_MODEL"],
100204
+ models: {
100205
+ // single routing entry — the actual model ID is read from
100206
+ // OPENAI_COMPATIBLE_MODEL at run time and the provider is materialized
100207
+ // via `@ai-sdk/openai-compatible`.
100208
+ byok: {
100209
+ displayName: "OpenAI-compatible",
100210
+ resolve: "openai-compatible",
100211
+ routing: "openai-compatible"
100212
+ }
100213
+ }
100214
+ }),
100193
100215
  openrouter: provider({
100194
100216
  displayName: "OpenRouter",
100195
100217
  envVars: ["OPENROUTER_API_KEY"],
@@ -100407,6 +100429,12 @@ var DEFAULT_PROXY_MODEL = defaultProxyAlias.openRouterResolve;
100407
100429
  var defaultProxyDisplayName = defaultProxyAlias.displayName;
100408
100430
  var BEDROCK_MODEL_ID_ENV = "BEDROCK_MODEL_ID";
100409
100431
  var VERTEX_MODEL_ID_ENV = "VERTEX_MODEL_ID";
100432
+ var OPENAI_COMPATIBLE_PROVIDER = "openai-compatible";
100433
+ var OPENAI_COMPATIBLE_BASE_URL_ENV = "OPENAI_COMPATIBLE_BASE_URL";
100434
+ var OPENAI_COMPATIBLE_API_KEY_ENV = "OPENAI_COMPATIBLE_API_KEY";
100435
+ var OPENAI_COMPATIBLE_MODEL_ENV = "OPENAI_COMPATIBLE_MODEL";
100436
+ var OPENAI_COMPATIBLE_CONTEXT_ENV = "OPENAI_COMPATIBLE_CONTEXT";
100437
+ var OPENAI_COMPATIBLE_MAX_OUTPUT_ENV = "OPENAI_COMPATIBLE_MAX_OUTPUT";
100410
100438
  function isBedrockAnthropicId(bedrockModelId) {
100411
100439
  return bedrockModelId.toLowerCase().split(/[./:]/).includes("anthropic");
100412
100440
  }
@@ -101093,7 +101121,7 @@ var import_semver = __toESM(require_semver2(), 1);
101093
101121
  // package.json
101094
101122
  var package_default = {
101095
101123
  name: "pullfrog",
101096
- version: "0.1.45",
101124
+ version: "0.1.47",
101097
101125
  type: "module",
101098
101126
  bin: {
101099
101127
  pullfrog: "dist/cli.mjs",
@@ -108882,8 +108910,9 @@ function readModels(cliPath) {
108882
108910
  stdio: ["ignore", "pipe", "pipe"]
108883
108911
  });
108884
108912
  if (result.status !== 0) {
108885
- failure = (result.stderr ?? "").replace(ANSI_PATTERN, "").trim() || void 0;
108886
- log.debug(`\xBB \`opencode models\` failed (${result.status}): ${failure ?? result.error}`);
108913
+ const stderr = (result.stderr ?? "").replace(ANSI_PATTERN, "").trim();
108914
+ failure = stderr || result.error?.message;
108915
+ log.debug(`\xBB \`opencode models\` failed (${result.status}): ${failure}`);
108887
108916
  return /* @__PURE__ */ new Set();
108888
108917
  }
108889
108918
  failure = void 0;
@@ -108941,6 +108970,29 @@ function geminiHighThinkingOverrides() {
108941
108970
  ])
108942
108971
  );
108943
108972
  }
108973
+ function openAICompatibleLimit() {
108974
+ return {
108975
+ context: Number(process.env[OPENAI_COMPATIBLE_CONTEXT_ENV]),
108976
+ output: Number(process.env[OPENAI_COMPATIBLE_MAX_OUTPUT_ENV])
108977
+ };
108978
+ }
108979
+ function openAICompatibleProvider(model) {
108980
+ const prefix = `${OPENAI_COMPATIBLE_PROVIDER}/`;
108981
+ if (!model?.startsWith(prefix)) return {};
108982
+ const modelId = model.slice(prefix.length);
108983
+ return {
108984
+ [OPENAI_COMPATIBLE_PROVIDER]: {
108985
+ npm: "@ai-sdk/openai-compatible",
108986
+ name: "OpenAI-compatible",
108987
+ options: {
108988
+ // trailing slash would double up against opencode's `/chat/completions` join
108989
+ baseURL: process.env[OPENAI_COMPATIBLE_BASE_URL_ENV]?.replace(/\/$/, ""),
108990
+ apiKey: process.env[OPENAI_COMPATIBLE_API_KEY_ENV]
108991
+ },
108992
+ models: { [modelId]: { name: modelId, limit: openAICompatibleLimit() } }
108993
+ }
108994
+ };
108995
+ }
108944
108996
  var KIMI_ENFORCERLESS_PROVIDERS = ["siliconflow", "together"];
108945
108997
  function kimiOpenRouterProviderOverrides() {
108946
108998
  return Object.fromEntries(
@@ -109032,7 +109084,8 @@ function buildSecurityConfig(ctx, model) {
109032
109084
  google: { models: geminiHighThinkingOverrides() },
109033
109085
  openrouter: {
109034
109086
  models: { ...deepseekHighEffortOverrides(), ...kimiOpenRouterProviderOverrides() }
109035
- }
109087
+ },
109088
+ ...openAICompatibleProvider(model)
109036
109089
  }
109037
109090
  };
109038
109091
  if (model) {
@@ -158027,6 +158080,9 @@ var JsonPayload = type({
158027
158080
  id: "string",
158028
158081
  type: "'issue' | 'review'"
158029
158082
  }).or("undefined"),
158083
+ // optional so a payload from an older server build (pre-`checkRun`) still parses
158084
+ // against a newer action across a rolling deploy.
158085
+ "checkRun?": type({ id: "string" }).or("undefined"),
158030
158086
  "generateSummary?": "boolean | undefined"
158031
158087
  });
158032
158088
  var COLLABORATOR_PERMISSIONS = ["admin", "maintain", "write"];
@@ -158140,13 +158196,19 @@ function resolvePayload(resolvedPromptInput, repoSettings) {
158140
158196
  timeout: inputs.timeout ?? jsonPayload?.timeout,
158141
158197
  cwd: resolveCwd(inputs.cwd),
158142
158198
  progressComment: jsonPayload?.progressComment,
158199
+ checkRun: jsonPayload?.checkRun,
158143
158200
  generateSummary: jsonPayload?.generateSummary,
158144
158201
  // permissions: inputs > repoSettings > fallbacks
158145
158202
  push: inputs.push ?? repoSettings.push ?? "restricted",
158146
158203
  shell: resolvedShell,
158147
- // opt-in commit-status check-runs (branch protection). workflow-level
158148
- // static input, off unless the repo's pullfrog.yml sets status_checks: enabled.
158149
- statusChecks: inputs.status_checks === "enabled",
158204
+ // the `pullfrog` run-lifecycle check. ON by default — the whole point is that a PR
158205
+ // shows whether Pullfrog is running without anyone having to opt in. the workflow
158206
+ // input is the source of truth when set (mirrors `push`); otherwise the repo
158207
+ // setting decides.
158208
+ runStatusCheck: inputs.status_checks === void 0 ? repoSettings.statusChecks : inputs.status_checks === "enabled",
158209
+ // the `pullfrog-approval` verdict check stays opt-in and workflow-only. it exists to
158210
+ // be *required* by branch protection, so it must never turn itself on.
158211
+ approvalCheck: inputs.status_checks === "enabled",
158150
158212
  // temporary progress chrome. the workflow input is the source of truth when
158151
158213
  // set (mirrors `push`); otherwise the repo setting decides. defaults to true.
158152
158214
  progressComments: inputs.progress_comments === void 0 ? repoSettings.progressComments : inputs.progress_comments === "enabled",
@@ -158674,7 +158736,11 @@ async function approveAfterFix(ctx) {
158674
158736
  approved: true,
158675
158737
  hasComments: false
158676
158738
  });
158677
- ctx.toolState.approval = { wouldApprove: true, sha: headSha };
158739
+ ctx.toolState.approval = {
158740
+ wouldApprove: true,
158741
+ sha: headSha,
158742
+ url: result.data.html_url
158743
+ };
158678
158744
  log.info(`\xBB auto-approved #${pullNumber} after fix run (review ${result.data.id})`);
158679
158745
  await deleteProgressComment(ctx).catch((err) => {
158680
158746
  log.debug(`progress comment cleanup after fix auto-approval failed: ${err}`);
@@ -159007,6 +159073,7 @@ function CreatePullRequestReviewTool(ctx) {
159007
159073
  }
159008
159074
  const reviewId = result.data.id;
159009
159075
  const reviewNodeId = result.data.node_id;
159076
+ if (ctx.toolState.approval) ctx.toolState.approval.url = result.data.html_url;
159010
159077
  log.info(`\xBB created review ${reviewId} on pull request #${pull_number}`);
159011
159078
  const actuallyReviewedSha = primary.checkoutSha ?? params.commit_id;
159012
159079
  ctx.toolState.review = {
@@ -163298,6 +163365,15 @@ function resolveSlug(slug2) {
163298
163365
  }
163299
163366
  return vertexId;
163300
163367
  }
163368
+ if (alias?.routing === "openai-compatible") {
163369
+ const modelId = process.env[OPENAI_COMPATIBLE_MODEL_ENV]?.trim();
163370
+ if (!modelId) {
163371
+ throw new Error(
163372
+ `${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.`
163373
+ );
163374
+ }
163375
+ return `${OPENAI_COMPATIBLE_PROVIDER}/${modelId}`;
163376
+ }
163301
163377
  return resolveCliModel(slug2);
163302
163378
  }
163303
163379
  function resolveModel(ctx) {
@@ -163347,6 +163423,21 @@ function resolveAgent(ctx) {
163347
163423
 
163348
163424
  // utils/apiKeys.ts
163349
163425
  var MISSING_KEY_MARKER = "no API key found";
163426
+ var SECRETS_UNAVAILABLE_MARKER = "couldn't load your Pullfrog secrets";
163427
+ function buildKeyError(params) {
163428
+ return params.secretsUnavailable ? buildSecretsUnavailableError(params) : buildMissingApiKeyError(params);
163429
+ }
163430
+ function buildSecretsUnavailableError(params) {
163431
+ const settingsUrl = `${getApiUrl()}/console/${params.owner}/${params.name}`;
163432
+ const modelClause = params.model ? ` needed for \`${params.model}\`` : "";
163433
+ return [
163434
+ `**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.`,
163435
+ "",
163436
+ "**To fix:** re-run the job. If it keeps happening, tell us in Discord.",
163437
+ "",
163438
+ `[Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/keys) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
163439
+ ].join("\n");
163440
+ }
163350
163441
  function buildMissingApiKeyError(params) {
163351
163442
  const githubSecretsUrl = `https://github.com/${params.owner}/${params.name}/settings/secrets/actions`;
163352
163443
  const settingsUrl = `${getApiUrl()}/console/${params.owner}/${params.name}`;
@@ -163389,10 +163480,47 @@ add the missing secret(s) to your GitHub repository at ${githubSecretsUrl}, then
163389
163480
 
163390
163481
  for full setup instructions, see https://docs.pullfrog.com/vertex`;
163391
163482
  }
163483
+ function buildOpenAICompatibleSetupError(params) {
163484
+ const githubSecretsUrl = `https://github.com/${params.owner}/${params.name}/settings/secrets/actions`;
163485
+ return `OpenAI-compatible model selected but required configuration is missing: ${params.missing.join(", ")}.
163486
+
163487
+ only the API key is sensitive \u2014 add it as a secret at ${githubSecretsUrl}. everything else is plain workflow \`env:\`:
163488
+
163489
+ ${OPENAI_COMPATIBLE_BASE_URL_ENV}: https://your-endpoint.example.com/v1
163490
+ ${OPENAI_COMPATIBLE_API_KEY_ENV}: \${{ secrets.${OPENAI_COMPATIBLE_API_KEY_ENV} }}
163491
+ ${OPENAI_COMPATIBLE_MODEL_ENV}: <model-id>
163492
+ ${OPENAI_COMPATIBLE_CONTEXT_ENV}: "128000"
163493
+ ${OPENAI_COMPATIBLE_MAX_OUTPUT_ENV}: "16384"
163494
+
163495
+ set the last two to the real limits of the model your endpoint serves. Pullfrog can't
163496
+ discover them \u2014 your endpoint owns the model catalog \u2014 and without them completions are
163497
+ capped at 32000 tokens (rejected outright by models with a smaller cap) and
163498
+ auto-compaction is disabled, so long runs grow until your endpoint refuses them.
163499
+
163500
+ for full setup instructions, see https://docs.pullfrog.com/openai-compatible`;
163501
+ }
163392
163502
  function hasEnvVar3(name) {
163393
163503
  const value2 = process.env[name];
163394
163504
  return typeof value2 === "string" && value2.length > 0;
163395
163505
  }
163506
+ function hasPositiveNumberEnvVar(name) {
163507
+ return Number(process.env[name]) > 0;
163508
+ }
163509
+ function validateOpenAICompatibleSetup(params) {
163510
+ const missing = [];
163511
+ if (!hasEnvVar3(OPENAI_COMPATIBLE_BASE_URL_ENV)) missing.push(OPENAI_COMPATIBLE_BASE_URL_ENV);
163512
+ if (!hasEnvVar3(OPENAI_COMPATIBLE_API_KEY_ENV)) missing.push(OPENAI_COMPATIBLE_API_KEY_ENV);
163513
+ if (!hasEnvVar3(OPENAI_COMPATIBLE_MODEL_ENV)) missing.push(OPENAI_COMPATIBLE_MODEL_ENV);
163514
+ if (!hasPositiveNumberEnvVar(OPENAI_COMPATIBLE_CONTEXT_ENV))
163515
+ missing.push(OPENAI_COMPATIBLE_CONTEXT_ENV);
163516
+ if (!hasPositiveNumberEnvVar(OPENAI_COMPATIBLE_MAX_OUTPUT_ENV))
163517
+ missing.push(OPENAI_COMPATIBLE_MAX_OUTPUT_ENV);
163518
+ if (missing.length > 0) {
163519
+ throw new Error(
163520
+ buildOpenAICompatibleSetupError({ owner: params.owner, name: params.name, missing })
163521
+ );
163522
+ }
163523
+ }
163396
163524
  function validateBedrockSetup(params) {
163397
163525
  const hasAuth = hasEnvVar3("AWS_BEARER_TOKEN_BEDROCK") || hasEnvVar3("AWS_ACCESS_KEY_ID") && hasEnvVar3("AWS_SECRET_ACCESS_KEY");
163398
163526
  const missing = [];
@@ -163427,6 +163555,10 @@ function validateAgentApiKey(params) {
163427
163555
  validateVertexSetup({ owner: params.owner, name: params.name });
163428
163556
  return;
163429
163557
  }
163558
+ if (params.model.startsWith(`${OPENAI_COMPATIBLE_PROVIDER}/`)) {
163559
+ validateOpenAICompatibleSetup({ owner: params.owner, name: params.name });
163560
+ return;
163561
+ }
163430
163562
  if (!params.model.includes("/")) {
163431
163563
  if (process.env[VERTEX_MODEL_ID_ENV]?.trim() === params.model) {
163432
163564
  validateVertexSetup({ owner: params.owner, name: params.name });
@@ -163439,23 +163571,46 @@ function validateAgentApiKey(params) {
163439
163571
  if (params.authorized.has(params.model)) return;
163440
163572
  const reason = getModelsFailure();
163441
163573
  if (reason) throw new Error(reason);
163574
+ if (getModelEnvVars(params.model).some(hasEnvVar3)) return;
163442
163575
  throw new Error(
163443
- buildMissingApiKeyError({ owner: params.owner, name: params.name, model: params.model })
163576
+ buildKeyError({
163577
+ owner: params.owner,
163578
+ name: params.name,
163579
+ model: params.model,
163580
+ secretsUnavailable: params.secretsUnavailable
163581
+ })
163444
163582
  );
163445
163583
  }
163446
163584
  if (hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN")) return;
163447
163585
  throw new Error(
163448
- buildMissingApiKeyError({ owner: params.owner, name: params.name, model: params.model })
163586
+ buildKeyError({
163587
+ owner: params.owner,
163588
+ name: params.name,
163589
+ model: params.model,
163590
+ secretsUnavailable: params.secretsUnavailable
163591
+ })
163449
163592
  );
163450
163593
  }
163451
163594
  if (params.agent.name === "opencode") {
163452
163595
  if (params.authorized.size > 0) return;
163453
163596
  const reason = getModelsFailure();
163454
163597
  if (reason) throw new Error(reason);
163455
- throw new Error(buildMissingApiKeyError({ owner: params.owner, name: params.name }));
163598
+ throw new Error(
163599
+ buildKeyError({
163600
+ owner: params.owner,
163601
+ name: params.name,
163602
+ secretsUnavailable: params.secretsUnavailable
163603
+ })
163604
+ );
163456
163605
  }
163457
163606
  if (hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN")) return;
163458
- throw new Error(buildMissingApiKeyError({ owner: params.owner, name: params.name }));
163607
+ throw new Error(
163608
+ buildKeyError({
163609
+ owner: params.owner,
163610
+ name: params.name,
163611
+ secretsUnavailable: params.secretsUnavailable
163612
+ })
163613
+ );
163459
163614
  }
163460
163615
  function isApiKeyAuthError(text) {
163461
163616
  if (!text) return false;
@@ -164140,7 +164295,7 @@ async function persistXrepoLearnings(ctx) {
164140
164295
  // utils/modelAccess.ts
164141
164296
  function decideModelAccess(input) {
164142
164297
  if (!input.modelExplicit || !input.model) return { kind: "ok" };
164143
- const byokAuthorized = !!input.resolvedModel && (input.authorized.has(input.resolvedModel) || !input.resolvedModel.includes("/"));
164298
+ const byokAuthorized = !!input.resolvedModel && (input.authorized.has(input.resolvedModel) || !input.resolvedModel.includes("/") || input.resolvedModel.startsWith(`${OPENAI_COMPATIBLE_PROVIDER}/`));
164144
164299
  if (input.proxyActive) {
164145
164300
  const target = resolveOpenRouterModel(input.model);
164146
164301
  if (input.oss) {
@@ -164935,6 +165090,7 @@ var defaultSettings = {
164935
165090
  autoMergeEnabled: false,
164936
165091
  signedCommits: false,
164937
165092
  progressComments: true,
165093
+ statusChecks: true,
164938
165094
  modeInstructions: {},
164939
165095
  learnings: null,
164940
165096
  learningsHeadings: [],
@@ -164949,6 +165105,10 @@ var defaultRunContext = {
164949
165105
  oss: false,
164950
165106
  plan: "none"
164951
165107
  };
165108
+ var unknownSecretsRunContext = {
165109
+ ...defaultRunContext,
165110
+ secretsUnavailable: true
165111
+ };
164952
165112
  async function fetchRunContext(params) {
164953
165113
  const timeoutMs = 3e4;
164954
165114
  const controller = new AbortController();
@@ -164967,7 +165127,7 @@ async function fetchRunContext(params) {
164967
165127
  });
164968
165128
  clearTimeout(timeoutId);
164969
165129
  if (!response.ok) {
164970
- return defaultRunContext;
165130
+ return response.status >= 500 ? unknownSecretsRunContext : defaultRunContext;
164971
165131
  }
164972
165132
  const data = await response.json();
164973
165133
  if (data === null) {
@@ -164991,11 +165151,12 @@ async function fetchRunContext(params) {
164991
165151
  oss: data.oss ?? false,
164992
165152
  plan: data.plan ?? "none",
164993
165153
  proxyModel: data.proxyModel,
164994
- dbSecrets: data.dbSecrets
165154
+ dbSecrets: data.dbSecrets,
165155
+ secretsUnavailable: data.secretsUnavailable
164995
165156
  };
164996
165157
  } catch {
164997
165158
  clearTimeout(timeoutId);
164998
- return defaultRunContext;
165159
+ return unknownSecretsRunContext;
164999
165160
  }
165000
165161
  }
165001
165162
 
@@ -165017,7 +165178,10 @@ async function resolveRunContextData(params) {
165017
165178
  const repoContext = parseRepoContext();
165018
165179
  let oidcToken;
165019
165180
  try {
165020
- oidcToken = await core9.getIDToken("pullfrog-api");
165181
+ oidcToken = await op(() => core9.getIDToken("pullfrog-api"), {
165182
+ name: "OIDC mint",
165183
+ retries: process.env.ACTIONS_ID_TOKEN_REQUEST_URL ? [200, 1e3] : []
165184
+ })();
165021
165185
  } catch {
165022
165186
  }
165023
165187
  const [repoResponse, runContext] = await Promise.all([
@@ -165039,7 +165203,10 @@ async function resolveRunContextData(params) {
165039
165203
  oss: runContext.oss,
165040
165204
  plan: runContext.plan,
165041
165205
  proxyModel: runContext.proxyModel,
165042
- dbSecrets: runContext.dbSecrets
165206
+ dbSecrets: runContext.dbSecrets,
165207
+ // a failed mint on a runner that should have been able to mint is the same
165208
+ // outcome as the server-side failure: the run never sees stored secrets.
165209
+ secretsUnavailable: runContext.secretsUnavailable || !!process.env.ACTIONS_ID_TOKEN_REQUEST_URL && oidcToken === void 0
165043
165210
  };
165044
165211
  }
165045
165212
 
@@ -165204,6 +165371,9 @@ function renderRunError(input) {
165204
165371
  if (input.errorMessage.includes(MODEL_ACCESS_MARKER)) {
165205
165372
  return { summary: input.errorMessage, comment: input.errorMessage };
165206
165373
  }
165374
+ if (input.errorMessage.includes(SECRETS_UNAVAILABLE_MARKER)) {
165375
+ return { summary: input.errorMessage, comment: input.errorMessage };
165376
+ }
165207
165377
  const isHang = input.errorMessage.startsWith("activity timeout") || input.errorMessage.startsWith("agent still pending");
165208
165378
  const hangBody = isHang ? formatAgentHangBody({
165209
165379
  diagnostic: input.agentDiagnostic,
@@ -165442,30 +165612,123 @@ function getCurrentWorkflowFilename() {
165442
165612
  return match3?.[1] ?? "pullfrog.yml";
165443
165613
  }
165444
165614
 
165445
- // utils/statusChecks.ts
165446
- var COMPLETION_CHECK = "pullfrog";
165447
- var APPROVAL_CHECK = "pullfrog-approval";
165448
- async function createCheckRun(ctx, params) {
165615
+ // utils/runStatusCheck.ts
165616
+ var RUN_STATUS_CHECK_NAME = "Pullfrog";
165617
+ var APPROVAL_CHECK_NAME = "Pullfrog approval";
165618
+ function parseCheckRunId(raw2) {
165619
+ if (!raw2?.id) return void 0;
165620
+ const id = parseInt(raw2.id, 10);
165621
+ if (Number.isNaN(id) || id <= 0) return void 0;
165622
+ return id;
165623
+ }
165624
+ function disableCheckLine(owner, repo) {
165625
+ const url4 = `https://pullfrog.com/console/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}#auto-review-prs`;
165626
+ return `
165627
+
165628
+ [Turn off this check \u2192](${url4}) \u2014 it reports run status only and gates nothing unless you required it in branch protection.`;
165629
+ }
165630
+ var TERMINAL_OUTPUT = {
165631
+ success: {
165632
+ title: "Pullfrog run completed",
165633
+ summary: "The Pullfrog run finished successfully."
165634
+ },
165635
+ failure: {
165636
+ title: "Pullfrog run failed",
165637
+ summary: "The Pullfrog run failed. See the run logs for details."
165638
+ },
165639
+ cancelled: {
165640
+ title: "Pullfrog run cancelled",
165641
+ summary: "The Pullfrog run was cancelled before it finished."
165642
+ },
165643
+ timed_out: {
165644
+ title: "Pullfrog run timed out",
165645
+ summary: "The Pullfrog run exceeded its timeout. See the run logs for details."
165646
+ },
165647
+ action_required: {
165648
+ title: "Pullfrog run needs attention",
165649
+ summary: "The Pullfrog run stopped and needs attention. See the run logs for details."
165650
+ },
165651
+ neutral: {
165652
+ title: "Pullfrog run finished",
165653
+ summary: "The Pullfrog run finished without a pass or fail outcome."
165654
+ },
165655
+ skipped: {
165656
+ title: "Pullfrog run skipped",
165657
+ summary: "This run was superseded by another Pullfrog run."
165658
+ },
165659
+ stale: {
165660
+ title: "Pullfrog run didn't finish",
165661
+ summary: "Pullfrog never received a completion signal for this run. See the run logs for details."
165662
+ }
165663
+ };
165664
+ function terminalOutput(params) {
165665
+ const base = TERMINAL_OUTPUT[params.conclusion];
165666
+ const review = params.reviewUrl ? `
165667
+
165668
+ [View the review Pullfrog posted \u2192](${params.reviewUrl})` : "";
165669
+ const disable = params.conclusion === "success" ? "" : disableCheckLine(params.owner, params.repo);
165670
+ return { title: base.title, summary: base.summary + review + disable };
165671
+ }
165672
+ async function finalizeRunStatusCheck(params) {
165673
+ const updateParams = {
165674
+ owner: params.owner,
165675
+ repo: params.repo,
165676
+ check_run_id: params.checkRunId,
165677
+ status: "completed",
165678
+ conclusion: params.conclusion,
165679
+ output: terminalOutput({
165680
+ conclusion: params.conclusion,
165681
+ owner: params.owner,
165682
+ repo: params.repo,
165683
+ reviewUrl: params.reviewUrl
165684
+ })
165685
+ };
165686
+ if (params.detailsUrl) updateParams.details_url = params.detailsUrl;
165687
+ await params.octokit.rest.checks.update(updateParams);
165688
+ }
165689
+ async function createTerminalRunStatusCheck(params) {
165449
165690
  const createParams = {
165450
- owner: ctx.repo.owner,
165451
- repo: ctx.repo.name,
165452
- name: params.name,
165691
+ owner: params.owner,
165692
+ repo: params.repo,
165693
+ name: RUN_STATUS_CHECK_NAME,
165453
165694
  head_sha: params.headSha,
165454
165695
  status: "completed",
165455
165696
  conclusion: params.conclusion,
165456
- output: { title: params.title, summary: params.summary }
165697
+ output: terminalOutput({
165698
+ conclusion: params.conclusion,
165699
+ owner: params.owner,
165700
+ repo: params.repo,
165701
+ reviewUrl: params.reviewUrl
165702
+ })
165457
165703
  };
165458
- if (ctx.runId) {
165459
- createParams.details_url = `https://github.com/${ctx.repo.owner}/${ctx.repo.name}/actions/runs/${ctx.runId}`;
165460
- }
165461
- await ctx.octokit.rest.checks.create(createParams);
165462
- log.info(`\xBB posted ${params.name} check (${params.conclusion}) on ${params.headSha.slice(0, 7)}`);
165704
+ if (params.detailsUrl) createParams.details_url = params.detailsUrl;
165705
+ await params.octokit.rest.checks.create(createParams);
165463
165706
  }
165707
+
165708
+ // utils/statusChecks.ts
165464
165709
  async function reportStatusChecks(ctx, params) {
165465
- if (!ctx.payload.statusChecks) return;
165466
165710
  const event = ctx.payload.event;
165467
165711
  const pullNumber = event.issue_number;
165468
165712
  if (event.is_pr !== true || typeof pullNumber !== "number") return;
165713
+ const checkRunId = parseCheckRunId(ctx.payload.checkRun);
165714
+ if (checkRunId === void 0 && !ctx.payload.runStatusCheck && !ctx.payload.approvalCheck) return;
165715
+ const conclusion = params.runSucceeded ? "success" : "failure";
165716
+ const detailsUrl = ctx.runId ? `https://github.com/${ctx.repo.owner}/${ctx.repo.name}/actions/runs/${ctx.runId}` : void 0;
165717
+ if (checkRunId !== void 0) {
165718
+ await finalizeRunStatusCheck({
165719
+ octokit: ctx.octokit,
165720
+ owner: ctx.repo.owner,
165721
+ repo: ctx.repo.name,
165722
+ checkRunId,
165723
+ conclusion,
165724
+ detailsUrl,
165725
+ reviewUrl: ctx.toolState.approval?.url
165726
+ }).then(() => log.info(`\xBB finalized ${RUN_STATUS_CHECK_NAME} check (${conclusion})`)).catch((err) => log.debug(`status checks: ${RUN_STATUS_CHECK_NAME} finalize failed: ${err}`));
165727
+ }
165728
+ const approval = ctx.toolState.approval;
165729
+ const needsApprovalCheck = ctx.payload.approvalCheck && params.runSucceeded && approval;
165730
+ const needsFallbackRunCheck = ctx.payload.runStatusCheck && checkRunId === void 0;
165731
+ if (!needsApprovalCheck && !needsFallbackRunCheck) return;
165469
165732
  let headSha;
165470
165733
  try {
165471
165734
  const pr = await ctx.octokit.rest.pulls.get({
@@ -165478,24 +165741,32 @@ async function reportStatusChecks(ctx, params) {
165478
165741
  log.debug(`status checks: failed to resolve PR #${pullNumber} head sha: ${err}`);
165479
165742
  return;
165480
165743
  }
165481
- const completionSha = primaryRepoState(ctx.toolState).checkoutSha ?? headSha;
165482
- await createCheckRun(ctx, {
165483
- name: COMPLETION_CHECK,
165484
- headSha: completionSha,
165485
- conclusion: params.runSucceeded ? "success" : "failure",
165486
- title: params.runSucceeded ? "Pullfrog run completed" : "Pullfrog run failed",
165487
- summary: params.runSucceeded ? "The Pullfrog run finished successfully." : "The Pullfrog run failed or timed out. See the run logs for details."
165488
- }).catch((err) => log.debug(`status checks: ${COMPLETION_CHECK} post failed: ${err}`));
165489
- const approval = ctx.toolState.approval;
165490
- if (params.runSucceeded && approval) {
165491
- await createCheckRun(ctx, {
165492
- name: APPROVAL_CHECK,
165493
- headSha: approval.sha ?? headSha,
165494
- conclusion: approval.wouldApprove ? "success" : "failure",
165744
+ if (needsFallbackRunCheck) {
165745
+ await createTerminalRunStatusCheck({
165746
+ octokit: ctx.octokit,
165747
+ owner: ctx.repo.owner,
165748
+ repo: ctx.repo.name,
165749
+ headSha: primaryRepoState(ctx.toolState).checkoutSha ?? headSha,
165750
+ conclusion,
165751
+ detailsUrl,
165752
+ reviewUrl: ctx.toolState.approval?.url
165753
+ }).then(() => log.info(`\xBB posted ${RUN_STATUS_CHECK_NAME} check (${conclusion})`)).catch((err) => log.debug(`status checks: ${RUN_STATUS_CHECK_NAME} post failed: ${err}`));
165754
+ }
165755
+ if (!needsApprovalCheck || !approval) return;
165756
+ const createParams = {
165757
+ owner: ctx.repo.owner,
165758
+ repo: ctx.repo.name,
165759
+ name: APPROVAL_CHECK_NAME,
165760
+ head_sha: approval.sha ?? headSha,
165761
+ status: "completed",
165762
+ conclusion: approval.wouldApprove ? "success" : "failure",
165763
+ output: {
165495
165764
  title: approval.wouldApprove ? "Pullfrog would approve" : "Pullfrog would not approve",
165496
165765
  summary: approval.wouldApprove ? "Pullfrog has no outstanding review feedback on this PR." : "Pullfrog has outstanding review feedback or requested changes on this PR."
165497
- }).catch((err) => log.debug(`status checks: ${APPROVAL_CHECK} post failed: ${err}`));
165498
- }
165766
+ }
165767
+ };
165768
+ if (detailsUrl) createParams.details_url = detailsUrl;
165769
+ 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}`));
165499
165770
  }
165500
165771
 
165501
165772
  // utils/runLifecycle.ts
@@ -165938,7 +166209,8 @@ async function main() {
165938
166209
  model: effectiveModel,
165939
166210
  authorized: getAuthorizedModels(),
165940
166211
  owner: runContext.repo.owner,
165941
- name: runContext.repo.name
166212
+ name: runContext.repo.name,
166213
+ secretsUnavailable: runContext.secretsUnavailable
165942
166214
  });
165943
166215
  }
165944
166216
  await setupGit({
@@ -15,4 +15,6 @@ export { isLeapingIntoActionCommentBody, LEAPING_INTO_ACTION_PREFIX, } from "../
15
15
  export { MAX_LEARNINGS_LENGTH, truncateAtLineBoundary } from "../utils/learningsTruncate.ts";
16
16
  export type { CreateProgressCommentTarget, ProgressComment, ProgressCommentType, } from "../utils/progressComment.ts";
17
17
  export { createLeapingProgressComment, deleteProgressCommentApi, getProgressComment, updateProgressComment, } from "../utils/progressComment.ts";
18
+ export type { RunStatusCheckConclusion, RunStatusCheckOctokit, } from "../utils/runStatusCheck.ts";
19
+ export { APPROVAL_CHECK_NAME, createRunStatusCheck, finalizeRunStatusCheck, RUN_STATUS_CHECK_NAME, runStatusCheckNeedsFinalizing, } from "../utils/runStatusCheck.ts";
18
20
  export { isValidTimeString, parseTimeString, TIMEOUT_DISABLED, } from "../utils/time.ts";