pullfrog 0.1.83 → 0.1.85

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.
@@ -20,11 +20,14 @@ export type { ResourceUsage, UsageSummary } from "../utils/github.ts";
20
20
  export { isPullfrog } from "../utils/isPullfrog.ts";
21
21
  export { isLeapingIntoActionCommentBody, LEAPING_INTO_ACTION_PREFIX, } from "../utils/leapingComment.ts";
22
22
  export { MAX_LEARNINGS_LENGTH, truncateAtLineBoundary } from "../utils/learningsTruncate.ts";
23
+ export { countOutstandingPullfrogThreads } from "../utils/outstandingThreads.ts";
23
24
  export type { CreateProgressCommentTarget, ProgressComment, ProgressCommentType, } from "../utils/progressComment.ts";
24
25
  export { createLeapingProgressComment, deleteProgressCommentApi, getProgressComment, updateProgressComment, } from "../utils/progressComment.ts";
25
26
  export { PROVIDER_DASHBOARDS, type ProviderDashboard } from "../utils/providerDashboards.ts";
26
27
  export type { RunStatusCheckConclusion, RunStatusCheckOctokit, } from "../utils/runStatusCheck.ts";
27
28
  export { APPROVAL_CHECK_NAME, createRunStatusCheck, finalizeRunStatusCheck, RUN_STATUS_CHECK_NAME, runStatusCheckNeedsFinalizing, } from "../utils/runStatusCheck.ts";
29
+ export * from "../utils/subscriptionCredentials.ts";
30
+ export { probeSubscription, SubscriptionCredentialError, subscriptionIdentity, } from "../utils/subscriptionProbe.ts";
28
31
  export { isValidTimeString, parseTimeString, TIMEOUT_DISABLED, } from "../utils/time.ts";
29
32
  export type { XaiAuthBody } from "../utils/xaiOAuth.ts";
30
33
  export { parseXaiAuthBody, refreshXaiAuthBody, stringifyXaiAuthBody, } from "../utils/xaiOAuth.ts";
package/dist/internal.js CHANGED
@@ -8,7 +8,7 @@ var __export = (target, all) => {
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "pullfrog",
11
- version: "0.1.83",
11
+ version: "0.1.85",
12
12
  cliContractVersion: "1",
13
13
  type: "module",
14
14
  bin: {
@@ -44,8 +44,8 @@ var package_default = {
44
44
  "@octokit/plugin-throttling": "^11.0.3",
45
45
  "@octokit/rest": "^22.0.0",
46
46
  "@octokit/webhooks-types": "^7.6.1",
47
- "@openai/codex": "0.153.4",
48
- "@openai/codex-sdk": "0.153.4",
47
+ "@openai/codex": "0.157.0",
48
+ "@openai/codex-sdk": "0.157.0",
49
49
  "@opencode-ai/sdk": "1.18.29",
50
50
  "@standard-schema/spec": "1.1.0",
51
51
  "@toon-format/toon": "^1.0.0",
@@ -14931,22 +14931,14 @@ var providers = {
14931
14931
  displayName: "Qwen Max",
14932
14932
  resolve: "opencode/qwen3.8-max",
14933
14933
  openRouterEffort: ["minimal", "low", "medium", "high", "xhigh"],
14934
- openRouterResolve: "openrouter/qwen/qwen3.8-max-0902",
14935
- // unlisted until a published `pullfrog` carries this alias (the
14936
- // merge-vs-publish rule in wiki/models-catalog.md). unhide in the release
14937
- // that ships it.
14938
- hidden: true
14934
+ openRouterResolve: "openrouter/qwen/qwen3.8-max-0902"
14939
14935
  },
14940
14936
  "qwen-flash": {
14941
14937
  displayName: "Qwen Flash",
14942
14938
  resolve: "opencode/qwen3.8-flash",
14943
14939
  effort: ["low", "medium", "xhigh"],
14944
14940
  openRouterEffort: [],
14945
- openRouterResolve: "openrouter/qwen/qwen3.8-flash",
14946
- // unlisted until a published `pullfrog` carries this alias (the
14947
- // merge-vs-publish rule in wiki/models-catalog.md). unhide in the release
14948
- // that ships it.
14949
- hidden: true
14941
+ openRouterResolve: "openrouter/qwen/qwen3.8-flash"
14950
14942
  },
14951
14943
  "gpt-5-nano": {
14952
14944
  displayName: "GPT Nano",
@@ -15006,11 +14998,18 @@ var providers = {
15006
14998
  effort: ["low", "medium", "high", "xhigh", "max"],
15007
14999
  // free to run, still gated on the provider's own OPENCODE_API_KEY —
15008
15000
  // see the big-pickle note above (#1077).
15009
- isFree: true,
15010
- // unlisted until a published `pullfrog` carries this alias: 0.1.82
15011
- // predates it, so a run passes `opencode/space-bunny` through raw and
15012
- // Zen answers a 500. unhide in the release that ships it.
15013
- hidden: true
15001
+ isFree: true
15002
+ },
15003
+ // Zen's FREE Meituan preview (listed 2026-09-25). Zen's docs: zero
15004
+ // retention, no training. Go lists the same free id, but one key reaches
15005
+ // both, so the promo gets one row, here with the other free ones.
15006
+ "longcat-preview": {
15007
+ displayName: "LongCat Preview",
15008
+ description: "Meituan preview, free for a limited time",
15009
+ resolve: "opencode/longcat-2.5-preview-free",
15010
+ // free to run, still gated on the provider's own OPENCODE_API_KEY —
15011
+ // see the big-pickle note above (#1077).
15012
+ isFree: true
15014
15013
  },
15015
15014
  // Zen's live free MiMo, and the second free row in a menu that big-pickle
15016
15015
  // was alone in since `mimo-v2-pro-free` lost its model.
@@ -15645,7 +15644,7 @@ function isRouterTier(value) {
15645
15644
  }
15646
15645
  var PROVIDER_LADDERS = {
15647
15646
  anthropic: {
15648
- minimal: "anthropic/claude-haiku",
15647
+ minimal: "anthropic/claude-sonnet",
15649
15648
  light: "anthropic/claude-sonnet",
15650
15649
  standard: "anthropic/claude-opus",
15651
15650
  deep: "anthropic/claude-opus"
@@ -15963,7 +15962,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
15963
15962
  6. Finalize:
15964
15963
  - **only if code changed**, ${finalizeStep} (same push/prepush guidance as Build mode in *SYSTEM*). If push/commit fails, report the exact error and STOP without claiming a fix or resolving its threads.
15965
15964
  - reply ONCE per relevant thread via \`${t("reply_to_review_comment")}\`, including when no code changed. The \`comment_id\` is the root comment's numeric \`id=\`, NOT the \`thread=\` value from \`${t("get_review_comments")}\`. The runtime dedupes identical bodies within a session.
15966
- - resolve a thread via \`${t("resolve_review_thread")}\` (using \`thread=\` as \`thread_id\`) only after its requested fix is fully on the remote, or the user explicitly asks to resolve it. Leave discussion-only replies, partial fixes, and disagreements open.
15965
+ - resolve a thread via \`${t("resolve_review_thread")}\` (using \`thread=\` as \`thread_id\`) only after its requested fix is fully on the remote, the user explicitly asks to resolve it, or it is a Pullfrog finding you agree to withdraw. Leave other discussion-only replies, partial fixes, and disagreements open.
15967
15966
  - call \`${t("report_progress")}\` with a brief summary. If your last \`${t("get_review_comments")}\` turned up threads you did not address, name them there \u2014 a review arriving this late is suppressed as a duplicate and nothing else will pick it up.`
15968
15967
  },
15969
15968
  // Review and IncrementalReview route the minimum reviewfrog specialists
@@ -16223,35 +16222,25 @@ var modes = computeModes("opencode");
16223
16222
 
16224
16223
  // utils/billingErrors.ts
16225
16224
  function commercialPaywallBody(params) {
16226
- const pitch = "Pro is required on private organization repos like this one: a flat $30/month per organization for unlimited runs, members, and repos.";
16225
+ let action;
16227
16226
  switch (params.reason) {
16228
16227
  case "commercial":
16229
- return [
16230
- `**${params.ownerLogin}'s Pullfrog trial has expired.**`,
16231
- "",
16232
- pitch,
16233
- "",
16234
- `[Upgrade now \u2192](${params.url})`
16235
- ].join("\n");
16228
+ action = "upgrade";
16229
+ break;
16236
16230
  case "subscription_ended":
16237
- return [
16238
- `**${params.ownerLogin}'s Pullfrog Pro subscription has ended.**`,
16239
- "",
16240
- pitch,
16241
- "",
16242
- `[Resubscribe \u2192](${params.url})`
16243
- ].join("\n");
16231
+ action = "resubscribe";
16232
+ break;
16244
16233
  case "subscription_unpaid":
16245
- return [
16246
- `**Pullfrog paused runs on ${params.ownerLogin}: the Pro renewal failed.**`,
16247
- "",
16248
- "Update the card on file to resume runs.",
16249
- "",
16250
- `[Update billing \u2192](${params.url})`
16251
- ].join("\n");
16234
+ action = "update billing";
16235
+ break;
16252
16236
  default:
16253
16237
  return params.reason;
16254
16238
  }
16239
+ return [
16240
+ `**Pullfrog is ${params.freshReply ? "still paused" : "paused"} for ${params.ownerLogin}.**`,
16241
+ "",
16242
+ `An org owner can [${action}](${params.url}) to resume private-repo runs.`
16243
+ ].join("\n");
16255
16244
  }
16256
16245
 
16257
16246
  // utils/buildPullfrogFooter.ts
@@ -16590,6 +16579,42 @@ function truncateAtLineBoundary(body, cap) {
16590
16579
  return head.slice(0, lastNewline);
16591
16580
  }
16592
16581
 
16582
+ // utils/outstandingThreads.ts
16583
+ var OUTSTANDING_THREADS_QUERY = `
16584
+ query ($owner: String!, $name: String!, $prNumber: Int!, $cursor: String) {
16585
+ repository(owner: $owner, name: $name) {
16586
+ pullRequest(number: $prNumber) {
16587
+ reviewThreads(first: 100, after: $cursor) {
16588
+ pageInfo { hasNextPage endCursor }
16589
+ nodes {
16590
+ isResolved
16591
+ comments(first: 1) { nodes { author { login } } }
16592
+ }
16593
+ }
16594
+ }
16595
+ }
16596
+ }
16597
+ `;
16598
+ async function countOutstandingPullfrogThreads(ctx, pullNumber) {
16599
+ let count = 0;
16600
+ let cursor = null;
16601
+ for (let page = 0; page < 50; page += 1) {
16602
+ const response = await ctx.octokit.graphql(
16603
+ OUTSTANDING_THREADS_QUERY,
16604
+ { owner: ctx.repo.owner, name: ctx.repo.name, prNumber: pullNumber, cursor }
16605
+ );
16606
+ const conn = response.repository?.pullRequest?.reviewThreads;
16607
+ for (const thread of conn?.nodes ?? []) {
16608
+ if (!thread || thread.isResolved) continue;
16609
+ const root = thread.comments?.nodes?.find((c) => c != null);
16610
+ if (root && isPullfrog(root.author?.login)) count += 1;
16611
+ }
16612
+ if (!conn?.pageInfo.hasNextPage) break;
16613
+ cursor = conn.pageInfo.endCursor;
16614
+ }
16615
+ return count;
16616
+ }
16617
+
16593
16618
  // utils/progressComment.ts
16594
16619
  async function getProgressComment(ctx, comment) {
16595
16620
  const result = await (comment.type === "review" ? ctx.octokit.rest.pulls.getReviewComment({
@@ -16911,19 +16936,47 @@ async function runStatusCheckNeedsFinalizing(params) {
16911
16936
  }
16912
16937
  }
16913
16938
 
16914
- // utils/time.ts
16915
- var TIMEOUT_DISABLED = "none";
16916
- var TIME_STRING_REGEX = /^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/;
16917
- function parseTimeString(input) {
16918
- const match = input.match(TIME_STRING_REGEX);
16919
- if (!match || !match[1] && !match[2] && !match[3]) return null;
16920
- const hours = parseInt(match[1] || "0", 10);
16921
- const minutes = parseInt(match[2] || "0", 10);
16922
- const seconds = parseInt(match[3] || "0", 10);
16923
- return (hours * 3600 + minutes * 60 + seconds) * 1e3;
16924
- }
16925
- function isValidTimeString(input) {
16926
- return parseTimeString(input) !== null;
16939
+ // utils/subscriptionCredentials.ts
16940
+ var subscriptionNameSchema = external_exports.enum([
16941
+ "CLAUDE_CODE_OAUTH_TOKEN",
16942
+ "CODEX_AUTH_JSON",
16943
+ "GROK_AUTH_JSON"
16944
+ ]);
16945
+ var subscriptionSchema = external_exports.object({
16946
+ id: external_exports.string(),
16947
+ credentialId: external_exports.string(),
16948
+ name: subscriptionNameSchema,
16949
+ label: external_exports.string(),
16950
+ owner: external_exports.string(),
16951
+ inherited: external_exports.boolean(),
16952
+ shared: external_exports.boolean(),
16953
+ registeredAt: external_exports.string()
16954
+ });
16955
+ var subscriptionListSchema = external_exports.object({
16956
+ subscriptions: external_exports.array(subscriptionSchema),
16957
+ writable: external_exports.boolean()
16958
+ });
16959
+ var subscriptionInputSchema = external_exports.object({
16960
+ name: subscriptionNameSchema,
16961
+ value: external_exports.string().trim().min(1).refine((value) => Buffer.byteLength(value, "utf8") <= 49152),
16962
+ replaceId: external_exports.string().optional()
16963
+ });
16964
+ var credentialCandidateSchema = external_exports.object({
16965
+ id: external_exports.string(),
16966
+ name: external_exports.string(),
16967
+ source: external_exports.enum(["repo", "account", "personal"])
16968
+ });
16969
+ var credentialAccessSchema = external_exports.object({
16970
+ token: external_exports.string(),
16971
+ candidates: external_exports.array(credentialCandidateSchema)
16972
+ });
16973
+ var selectedCredentialSchema = external_exports.object({
16974
+ name: external_exports.string(),
16975
+ value: external_exports.string(),
16976
+ receipt: external_exports.string()
16977
+ });
16978
+ function subscriptionLabel(input) {
16979
+ return input.email ?? `\u2022\u2022\u2022\u2022 ${input.suffix}`;
16927
16980
  }
16928
16981
 
16929
16982
  // utils/xaiOAuth.ts
@@ -16988,6 +17041,137 @@ function stringifyXaiAuthBody(body) {
16988
17041
  return `${JSON.stringify(body, null, 2)}
16989
17042
  `;
16990
17043
  }
17044
+
17045
+ // utils/subscriptionProbe.ts
17046
+ var identitySchema = external_exports.object({
17047
+ email: external_exports.string().email().optional(),
17048
+ sub: external_exports.string().optional(),
17049
+ user_id: external_exports.string().optional(),
17050
+ account_id: external_exports.string().optional(),
17051
+ email_verified: external_exports.boolean().optional()
17052
+ });
17053
+ var codexUsageSchema = external_exports.object({
17054
+ rate_limit: external_exports.object({ allowed: external_exports.boolean(), limit_reached: external_exports.boolean() }).optional()
17055
+ });
17056
+ var SubscriptionCredentialError = class extends Error {
17057
+ };
17058
+ async function subscriptionIdentity(input) {
17059
+ const empty = { email: null, subject: null };
17060
+ if (input.name === "CLAUDE_CODE_OAUTH_TOKEN") return empty;
17061
+ const codex = input.name === "CODEX_AUTH_JSON" ? parseCodexAuthBody(input.value) : null;
17062
+ const grok = input.name === "GROK_AUTH_JSON" ? parseXaiAuthBody(input.value) : null;
17063
+ const token = codex?.tokens.access_token ?? grok?.tokens.access_token;
17064
+ if (!token) throw new SubscriptionCredentialError("invalid subscription credential");
17065
+ const response = await readSubscriptionEndpoint({
17066
+ url: codex ? "https://chatgpt.com/backend-api/wham/usage" : "https://auth.x.ai/oauth2/userinfo",
17067
+ token,
17068
+ accountId: codex?.tokens.account_id
17069
+ });
17070
+ if (response?.status === 401)
17071
+ throw new SubscriptionCredentialError("subscription credential was rejected");
17072
+ if (!response?.ok) return empty;
17073
+ const parsed = identitySchema.safeParse(await response.json().catch(() => null));
17074
+ if (!parsed.success) return empty;
17075
+ const subject = parsed.data.sub ?? parsed.data.user_id;
17076
+ return {
17077
+ email: parsed.data.email_verified === false ? null : parsed.data.email ?? null,
17078
+ subject: subject ? `${subject}:${parsed.data.account_id ?? ""}` : null
17079
+ };
17080
+ }
17081
+ async function readSubscriptionEndpoint(input) {
17082
+ const headers = new Headers({ authorization: `Bearer ${input.token}` });
17083
+ if (input.accountId) headers.set("ChatGPT-Account-Id", input.accountId);
17084
+ if (input.grokBilling) headers.set("x-xai-token-auth", "xai-grok-cli");
17085
+ return fetch(input.url, {
17086
+ headers,
17087
+ redirect: "error",
17088
+ signal: AbortSignal.timeout(1e4)
17089
+ }).catch(() => null);
17090
+ }
17091
+ async function probeSubscription(input) {
17092
+ if (input.name === "CLAUDE_CODE_OAUTH_TOKEN") {
17093
+ const result = await preflightClaudeSubscription({ token: input.value, model: input.model });
17094
+ return result.usable ? "usable" : "rejected";
17095
+ }
17096
+ const codex = input.name === "CODEX_AUTH_JSON" ? parseCodexAuthBody(input.value) : null;
17097
+ const grok = input.name === "GROK_AUTH_JSON" ? parseXaiAuthBody(input.value) : null;
17098
+ const auth = codex ?? grok;
17099
+ if (!auth || auth.refresh_rejected_at) return "rejected";
17100
+ if (grok && input.model)
17101
+ return probeInference({
17102
+ name: "XAI_API_KEY",
17103
+ value: grok.tokens.access_token,
17104
+ model: input.model
17105
+ });
17106
+ const response = await readSubscriptionEndpoint({
17107
+ url: codex ? "https://chatgpt.com/backend-api/wham/usage" : "https://cli-chat-proxy.grok.com/v1/billing?format=credits",
17108
+ token: auth.tokens.access_token,
17109
+ accountId: codex?.tokens.account_id,
17110
+ grokBilling: !!grok
17111
+ });
17112
+ if (response?.status === 401) return "rejected";
17113
+ if (!response?.ok) return "unknown";
17114
+ const body = await response.json().catch(() => null);
17115
+ if (codex) {
17116
+ const parsed = codexUsageSchema.safeParse(body);
17117
+ const limit = parsed.success ? parsed.data.rate_limit : void 0;
17118
+ if (!limit) return "unknown";
17119
+ return limit.allowed && !limit.limit_reached ? "usable" : "rejected";
17120
+ }
17121
+ return "unknown";
17122
+ }
17123
+ async function probeInference(input) {
17124
+ const anthropic = input.name === "ANTHROPIC_API_KEY";
17125
+ const openai = input.name === "OPENAI_API_KEY";
17126
+ if (!anthropic && !openai && input.name !== "XAI_API_KEY") {
17127
+ const result = await verifyCredential({ envVar: input.name, value: input.value });
17128
+ return result === "dead" ? "rejected" : result === "alive" ? "usable" : "unknown";
17129
+ }
17130
+ const headers = new Headers({ "content-type": "application/json" });
17131
+ if (anthropic) {
17132
+ headers.set("x-api-key", input.value);
17133
+ headers.set("anthropic-version", "2023-06-01");
17134
+ } else headers.set("authorization", `Bearer ${input.value}`);
17135
+ const response = await fetch(
17136
+ anthropic ? "https://api.anthropic.com/v1/messages" : openai ? "https://api.openai.com/v1/responses" : "https://api.x.ai/v1/chat/completions",
17137
+ {
17138
+ method: "POST",
17139
+ headers,
17140
+ body: JSON.stringify(
17141
+ openai ? { model: input.model, input: "Reply OK.", max_output_tokens: 16, store: false } : {
17142
+ model: input.model,
17143
+ messages: [{ role: "user", content: "Reply OK." }],
17144
+ max_tokens: 1
17145
+ }
17146
+ ),
17147
+ redirect: "error",
17148
+ signal: AbortSignal.timeout(1e4)
17149
+ }
17150
+ ).catch(() => null);
17151
+ if (response?.status === 400 && input.name === "XAI_API_KEY") {
17152
+ const error48 = external_exports.object({ error: external_exports.string() }).safeParse(await response.json().catch(() => null));
17153
+ return error48.success && error48.data.error.startsWith("Incorrect API key provided.") ? "rejected" : "unknown";
17154
+ }
17155
+ await response?.body?.cancel();
17156
+ if (!response) return "unknown";
17157
+ if (response.ok) return "usable";
17158
+ return [401, 402, 403, 429].includes(response.status) ? "rejected" : "unknown";
17159
+ }
17160
+
17161
+ // utils/time.ts
17162
+ var TIMEOUT_DISABLED = "none";
17163
+ var TIME_STRING_REGEX = /^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/;
17164
+ function parseTimeString(input) {
17165
+ const match = input.match(TIME_STRING_REGEX);
17166
+ if (!match || !match[1] && !match[2] && !match[3]) return null;
17167
+ const hours = parseInt(match[1] || "0", 10);
17168
+ const minutes = parseInt(match[2] || "0", 10);
17169
+ const seconds = parseInt(match[3] || "0", 10);
17170
+ return (hours * 3600 + minutes * 60 + seconds) * 1e3;
17171
+ }
17172
+ function isValidTimeString(input) {
17173
+ return parseTimeString(input) !== null;
17174
+ }
16991
17175
  export {
16992
17176
  APPROVAL_CHECK_NAME,
16993
17177
  AUTO_EFFICIENT,
@@ -17012,6 +17196,7 @@ export {
17012
17196
  ROUTER_TIERS,
17013
17197
  RUN_STATUS_CHECK_NAME,
17014
17198
  SUBSIDY_RUNG,
17199
+ SubscriptionCredentialError,
17015
17200
  TIMEOUT_DISABLED,
17016
17201
  autoLadder,
17017
17202
  autoRoutedSlug,
@@ -17021,8 +17206,11 @@ export {
17021
17206
  configFields,
17022
17207
  configResponseSchema,
17023
17208
  configValueSchema,
17209
+ countOutstandingPullfrogThreads,
17024
17210
  createLeapingProgressComment,
17025
17211
  createRunStatusCheck,
17212
+ credentialAccessSchema,
17213
+ credentialCandidateSchema,
17026
17214
  decodeJwtExpMs,
17027
17215
  defaultAutoTier,
17028
17216
  deleteProgressCommentApi,
@@ -17056,6 +17244,7 @@ export {
17056
17244
  parseModel,
17057
17245
  parseTimeString,
17058
17246
  parseXaiAuthBody,
17247
+ probeSubscription,
17059
17248
  providers,
17060
17249
  pullfrogMcpName,
17061
17250
  refreshCodexAuthBody,
@@ -17071,9 +17260,16 @@ export {
17071
17260
  runStatusCheckNeedsFinalizing,
17072
17261
  rungLabel,
17073
17262
  rungPosition,
17263
+ selectedCredentialSchema,
17074
17264
  stringifyCodexAuthBody,
17075
17265
  stringifyXaiAuthBody,
17076
17266
  stripExistingFooter,
17267
+ subscriptionIdentity,
17268
+ subscriptionInputSchema,
17269
+ subscriptionLabel,
17270
+ subscriptionListSchema,
17271
+ subscriptionNameSchema,
17272
+ subscriptionSchema,
17077
17273
  truncateAtLineBoundary,
17078
17274
  updateProgressComment,
17079
17275
  verifyCredential
@@ -23,20 +23,6 @@ export declare const TRANSIENT_REVIEW_RETRY_DELAYS_MS: number[];
23
23
  */
24
24
  export declare function commentableLinesForFile(patch: string | undefined): CommentableLines;
25
25
  export declare function buildCommentableMap(ctx: ToolContext, pullNumber: number): Promise<Map<string, CommentableLines>>;
26
- /**
27
- * count unresolved review threads on a PR whose root comment was authored by
28
- * Pullfrog's bot. this is the full set of open Pullfrog findings on the PR —
29
- * the basis for both the never-approve-with-outstanding-issues invariant (the
30
- * approval gate below) and the `pullfrog-approval` status verdict. it is a
31
- * function of all open findings, NOT just the latest commit's delta.
32
- *
33
- * outdated-but-unresolved threads still count — GitHub marks a thread
34
- * `[OUTDATED]` when the anchor line moved (reformat, rename, force-push), which
35
- * is not the same as the concern being addressed. human-reviewer threads are
36
- * excluded: they belong to those reviewers to resolve. walks every page so a
37
- * long-lived PR with >100 threads can't hide an outstanding finding.
38
- */
39
- export declare function countOutstandingPullfrogThreads(ctx: ToolContext, pullNumber: number): Promise<number>;
40
26
  /**
41
27
  * proactive approve-when-clean for the Fix-all / Fix-👍s flow (`fix_review`
42
28
  * trigger). when such a run completes successfully and every Pullfrog-originated
@@ -63,6 +63,7 @@ export declare function commercialPaywallBody(params: {
63
63
  reason: CommercialRefusal;
64
64
  ownerLogin: string;
65
65
  url: string;
66
+ freshReply?: boolean | undefined;
66
67
  }): string;
67
68
  /**
68
69
  * commercial-gate copy for action runs, linked to the account billing card.
@@ -1,3 +1,4 @@
1
+ export declare function clearInstalledSubscription(name: "CODEX_AUTH_JSON" | "GROK_AUTH_JSON"): void;
1
2
  /** sandbox-hidden home for pullfrog-managed on-disk secrets in CI. bash via
2
3
  * MCP shell tmpfs-overlays this path; opencode's internal auth module
3
4
  * bypasses external_directory and reaches the real file. mirrors the
@@ -0,0 +1,10 @@
1
+ import type { RunContextData } from "./runContextData.ts";
2
+ /** load one subscription per provider for model discovery; keep the pool unflattened. */
3
+ export declare function initializeCredentialPool(ctx: RunContextData, model?: string): Promise<void>;
4
+ export declare function resolvePoolModel(input: {
5
+ codexAgent: boolean;
6
+ }): string | undefined;
7
+ export declare function selectConfiguredCredential(input: {
8
+ ctx: RunContextData;
9
+ model: string | undefined;
10
+ }): Promise<boolean>;
@@ -0,0 +1,25 @@
1
+ /** the slice of a run or webhook context the query needs — shared by the action and the server. */
2
+ export type OutstandingThreadsCtx = {
3
+ octokit: {
4
+ graphql: <T>(query: string, variables: Record<string, unknown>) => Promise<T>;
5
+ };
6
+ repo: {
7
+ owner: string;
8
+ name: string;
9
+ };
10
+ };
11
+ /**
12
+ * count unresolved review threads on a PR whose root comment was authored by
13
+ * Pullfrog's bot. this is the full set of open Pullfrog findings on the PR —
14
+ * the basis for both the never-approve-with-outstanding-issues invariant (the
15
+ * approval gate in create_pull_request_review) and the `pullfrog-approval`
16
+ * status verdict. it is a function of all open findings, NOT just the latest
17
+ * commit's delta.
18
+ *
19
+ * outdated-but-unresolved threads still count — GitHub marks a thread
20
+ * `[OUTDATED]` when the anchor line moved (reformat, rename, force-push), which
21
+ * is not the same as the concern being addressed. human-reviewer threads are
22
+ * excluded: they belong to those reviewers to resolve. walks every page so a
23
+ * long-lived PR with >100 threads can't hide an outstanding finding.
24
+ */
25
+ export declare function countOutstandingPullfrogThreads(ctx: OutstandingThreadsCtx, pullNumber: number): Promise<number>;
@@ -11,7 +11,7 @@ export declare const JsonPayload: import("arktype/internal/variants/object.ts").
11
11
  tier: "minimal" | "light" | "standard" | "deep";
12
12
  stakes: number;
13
13
  workload: number;
14
- source: "scorer" | "heuristic" | "fixed";
14
+ source: "jev" | "scorer" | "heuristic" | "fixed";
15
15
  rationale: string;
16
16
  } | undefined;
17
17
  effort?: string | number | undefined;
@@ -65,7 +65,7 @@ export declare function resolvePayload(resolvedPromptInput: ResolvedPromptInput,
65
65
  tier: "minimal" | "light" | "standard" | "deep";
66
66
  stakes: number;
67
67
  workload: number;
68
- source: "scorer" | "heuristic" | "fixed";
68
+ source: "jev" | "scorer" | "heuristic" | "fixed";
69
69
  rationale: string;
70
70
  } | undefined;
71
71
  effort: number | undefined;
@@ -2,6 +2,7 @@ import type { PushPermission, ShellPermission } from "../external.ts";
2
2
  import type { RouterTier } from "../models.ts";
3
3
  import type { CommercialRefusal } from "./billingErrors.ts";
4
4
  import type { RepoContext } from "./github.ts";
5
+ import type { CredentialAccess } from "./subscriptionCredentials.ts";
5
6
  export interface Mode {
6
7
  id: string;
7
8
  name: string;
@@ -57,6 +58,7 @@ export interface RepoSettings {
57
58
  */
58
59
  export type AccountPlan = "none" | "payg";
59
60
  export interface RunContext {
61
+ credentialAccess?: CredentialAccess | undefined;
60
62
  settings: RepoSettings;
61
63
  apiToken: string;
62
64
  oss: boolean;
@@ -3,7 +3,9 @@ import type { RouterTier } from "../models.ts";
3
3
  import type { CommercialRefusal } from "./billingErrors.ts";
4
4
  import { type OctokitWithPlugins } from "./github.ts";
5
5
  import { type AccountPlan, type RepoSettings } from "./runContext.ts";
6
+ import type { CredentialAccess } from "./subscriptionCredentials.ts";
6
7
  export interface RunContextData {
8
+ credentialAccess?: CredentialAccess | undefined;
7
9
  repo: {
8
10
  owner: string;
9
11
  name: string;
@@ -5,9 +5,15 @@ export interface SetupOptions {
5
5
  tempDir: string;
6
6
  }
7
7
  /**
8
- * Create a shared temp directory for the action
8
+ * Create a shared temp directory for the action, removed when the run's scope
9
+ * exits. a persistent self-hosted runner never wipes `/tmp`, so every run used
10
+ * to leave its tree (the OpenCode install alone is hundreds of MB) until the
11
+ * disk filled and every job on the runner died.
9
12
  */
10
- export declare function createTempDirectory(): string;
13
+ export declare function createTempDirectory(): {
14
+ path: string;
15
+ [Symbol.dispose]: () => void;
16
+ };
11
17
  /**
12
18
  * snapshot-and-delete the GHA runner's known credential leak surfaces inside
13
19
  * `$RUNNER_TEMP` before the agent spawns. without this, a shell-capable agent
@@ -0,0 +1,80 @@
1
+ import { z } from "zod";
2
+ export declare const subscriptionNameSchema: z.ZodEnum<{
3
+ CLAUDE_CODE_OAUTH_TOKEN: "CLAUDE_CODE_OAUTH_TOKEN";
4
+ CODEX_AUTH_JSON: "CODEX_AUTH_JSON";
5
+ GROK_AUTH_JSON: "GROK_AUTH_JSON";
6
+ }>;
7
+ export type SubscriptionName = z.infer<typeof subscriptionNameSchema>;
8
+ export declare const subscriptionSchema: z.ZodObject<{
9
+ id: z.ZodString;
10
+ credentialId: z.ZodString;
11
+ name: z.ZodEnum<{
12
+ CLAUDE_CODE_OAUTH_TOKEN: "CLAUDE_CODE_OAUTH_TOKEN";
13
+ CODEX_AUTH_JSON: "CODEX_AUTH_JSON";
14
+ GROK_AUTH_JSON: "GROK_AUTH_JSON";
15
+ }>;
16
+ label: z.ZodString;
17
+ owner: z.ZodString;
18
+ inherited: z.ZodBoolean;
19
+ shared: z.ZodBoolean;
20
+ registeredAt: z.ZodString;
21
+ }, z.core.$strip>;
22
+ export type Subscription = z.infer<typeof subscriptionSchema>;
23
+ export declare const subscriptionListSchema: z.ZodObject<{
24
+ subscriptions: z.ZodArray<z.ZodObject<{
25
+ id: z.ZodString;
26
+ credentialId: z.ZodString;
27
+ name: z.ZodEnum<{
28
+ CLAUDE_CODE_OAUTH_TOKEN: "CLAUDE_CODE_OAUTH_TOKEN";
29
+ CODEX_AUTH_JSON: "CODEX_AUTH_JSON";
30
+ GROK_AUTH_JSON: "GROK_AUTH_JSON";
31
+ }>;
32
+ label: z.ZodString;
33
+ owner: z.ZodString;
34
+ inherited: z.ZodBoolean;
35
+ shared: z.ZodBoolean;
36
+ registeredAt: z.ZodString;
37
+ }, z.core.$strip>>;
38
+ writable: z.ZodBoolean;
39
+ }, z.core.$strip>;
40
+ export declare const subscriptionInputSchema: z.ZodObject<{
41
+ name: z.ZodEnum<{
42
+ CLAUDE_CODE_OAUTH_TOKEN: "CLAUDE_CODE_OAUTH_TOKEN";
43
+ CODEX_AUTH_JSON: "CODEX_AUTH_JSON";
44
+ GROK_AUTH_JSON: "GROK_AUTH_JSON";
45
+ }>;
46
+ value: z.ZodString;
47
+ replaceId: z.ZodOptional<z.ZodString>;
48
+ }, z.core.$strip>;
49
+ export declare const credentialCandidateSchema: z.ZodObject<{
50
+ id: z.ZodString;
51
+ name: z.ZodString;
52
+ source: z.ZodEnum<{
53
+ repo: "repo";
54
+ account: "account";
55
+ personal: "personal";
56
+ }>;
57
+ }, z.core.$strip>;
58
+ export type CredentialCandidate = z.infer<typeof credentialCandidateSchema>;
59
+ export declare const credentialAccessSchema: z.ZodObject<{
60
+ token: z.ZodString;
61
+ candidates: z.ZodArray<z.ZodObject<{
62
+ id: z.ZodString;
63
+ name: z.ZodString;
64
+ source: z.ZodEnum<{
65
+ repo: "repo";
66
+ account: "account";
67
+ personal: "personal";
68
+ }>;
69
+ }, z.core.$strip>>;
70
+ }, z.core.$strip>;
71
+ export type CredentialAccess = z.infer<typeof credentialAccessSchema>;
72
+ export declare const selectedCredentialSchema: z.ZodObject<{
73
+ name: z.ZodString;
74
+ value: z.ZodString;
75
+ receipt: z.ZodString;
76
+ }, z.core.$strip>;
77
+ export declare function subscriptionLabel(input: {
78
+ email: string | null;
79
+ suffix: string;
80
+ }): string;