pullfrog 0.1.100 → 0.1.102

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.
@@ -1,6 +1,6 @@
1
1
  import type { Octokit } from "@octokit/rest";
2
2
  import type { ToolContext } from "./server.ts";
3
- export declare const REVIEW_THREADS_QUERY = "\nquery ($owner: String!, $name: String!, $prNumber: Int!, $cursor: String) {\n repository(owner: $owner, name: $name) {\n pullRequest(number: $prNumber) {\n reviewThreads(first: 100, after: $cursor) {\n pageInfo { hasNextPage endCursor }\n nodes {\n id\n path\n line\n startLine\n diffSide\n isResolved\n isOutdated\n comments(first: 50) {\n nodes {\n fullDatabaseId\n body\n bodyHTML\n createdAt\n diffHunk\n line\n startLine\n originalLine\n originalStartLine\n author { login }\n pullRequestReview {\n databaseId\n author { login }\n }\n reactionGroups {\n content\n reactors(first: 10) {\n nodes {\n ... on Actor { login }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n}\n";
3
+ export declare const REVIEW_THREADS_QUERY = "\nquery ($owner: String!, $name: String!, $prNumber: Int!, $cursor: String) {\n repository(owner: $owner, name: $name) {\n pullRequest(number: $prNumber) {\n reviewThreads(first: 100, after: $cursor) {\n pageInfo { hasNextPage endCursor }\n nodes {\n id\n path\n line\n startLine\n diffSide\n isResolved\n isOutdated\n comments(first: 50) {\n nodes {\n fullDatabaseId\n body\n bodyHTML\n createdAt\n diffHunk\n line\n startLine\n originalLine\n originalStartLine\n author { login __typename }\n pullRequestReview {\n databaseId\n author { login }\n }\n reactionGroups {\n content\n reactors(first: 10) {\n nodes {\n ... on Actor { login }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n}\n";
4
4
  export type ReviewThreadComment = {
5
5
  fullDatabaseId: string | null;
6
6
  body: string;
@@ -13,6 +13,7 @@ export type ReviewThreadComment = {
13
13
  originalStartLine: number | null;
14
14
  author: {
15
15
  login: string;
16
+ __typename: string;
16
17
  } | null;
17
18
  pullRequestReview: {
18
19
  databaseId: number | null;
@@ -141,6 +142,8 @@ interface GetReviewDataInput {
141
142
  * to the dispatching review exactly as before.
142
143
  */
143
144
  addressScope?: "all" | "mentions" | undefined;
145
+ /** whether this run can push, which is what makes a comment's author matter */
146
+ canPush: boolean;
144
147
  tmpdir: string;
145
148
  githubToken: string;
146
149
  }
@@ -9,6 +9,7 @@ export declare const ShellParams: import("arktype/internal/variants/object.ts").
9
9
  export type SandboxMethod = "unshare" | "sudo-unshare" | "userns-unshare" | "none";
10
10
  /** get the current sandbox method (for testing/diagnostics) */
11
11
  export declare function getSandboxMethod(): SandboxMethod;
12
+ export declare function resolveRepoRoot(): string;
12
13
  /** chars of shell output kept inline in the agent reply. anything past this
13
14
  * blows the agent's context budget on commands that dump big logs (test
14
15
  * runners, build tools, grep on large trees), so the overflow is spilled
package/dist/models.d.ts CHANGED
@@ -354,6 +354,10 @@ export declare function resolveModelRung(params: {
354
354
  useOpenRouter: boolean;
355
355
  }): string | undefined;
356
356
  export declare const DEFAULT_PROXY_MODEL: string;
357
+ export declare const TRIAL_PROXY_MODEL: string;
358
+ export declare const TRIAL_PROXY_DISPLAY_NAME: string;
359
+ /** lifetime cap, in USD, on the model usage Pullfrog funds for an account's trial runs. */
360
+ export declare const TRIAL_FUNDED_USD_LIMIT = 10;
357
361
  /**
358
362
  * the models Pullfrog is willing to fund on the OSS program. an accepted OSS
359
363
  * repo may pick any of these and run-context honors the pick; anything else
@@ -1,6 +1,7 @@
1
1
  import type { AgentCredential, AgentUsage } from "./agents/shared.ts";
2
2
  import type { PrepResult } from "./prep/types.ts";
3
3
  import type { AgentDiagnostic } from "./utils/agentHangReport.ts";
4
+ import type { ModelClamp } from "./utils/buildPullfrogFooter.ts";
4
5
  import type { DiffCoverageState } from "./utils/diffCoverage.ts";
5
6
  import { type ProgressComment, type ProgressCommentType } from "./utils/progressComment.ts";
6
7
  import type { TodoTracker } from "./utils/todoTracking.ts";
@@ -134,16 +135,14 @@ export interface ToolState {
134
135
  xrepoLearningsFilePath?: string;
135
136
  xrepoLearningsSeed?: string;
136
137
  xrepoLearningsPersistAttempted?: boolean;
138
+ learningsLocked?: boolean;
137
139
  output?: string | undefined;
138
140
  usageEntries: AgentUsage[];
139
141
  model?: string | undefined;
140
142
  agent?: string | undefined;
141
143
  credential?: AgentCredential | undefined;
142
144
  unselectedProxyDefault?: boolean | undefined;
143
- modelClamped?: {
144
- from: string;
145
- reason: "card" | "noRouterPath" | "oss" | "trial";
146
- } | undefined;
145
+ modelClamped?: ModelClamp | undefined;
147
146
  oss?: boolean | undefined;
148
147
  todoTracker?: TodoTracker | undefined;
149
148
  agentDiagnostic?: AgentDiagnostic | undefined;
@@ -33,19 +33,15 @@ export type AgentDiagnostic = {
33
33
  sawModelOutput: boolean;
34
34
  };
35
35
  /**
36
- * Build a user-facing markdown body for an agent hang or failure.
36
+ * The body for a run the activity watchdog ended. Rendered into both the PR
37
+ * progress comment and the job summary; `null` when the harness left no
38
+ * diagnostic, so the caller falls back to its bare-error rendering.
37
39
  *
38
- * Rendered into both the PR progress comment and the GitHub Actions job
39
- * summary. Returns `null` when no diagnostic is available, which signals to
40
- * the caller to fall back to its bare-error rendering.
41
- *
42
- * `errorMessage` is the underlying timer / spawn reject string (e.g.
43
- * `activity timeout: no output for 301s`). The idle seconds are parsed out
44
- * of it for the hang explanation — total runtime would overstate the stall
45
- * for runs that streamed for a long time before going quiet.
40
+ * The headline says whose side went quiet, because a silent run is nearly
41
+ * always the provider's and "Pullfrog stalled" read as ours. The agent's last
42
+ * output goes in a collapsed section for whoever wants it.
46
43
  */
47
44
  export declare function formatAgentHangBody(input: {
48
45
  diagnostic: AgentDiagnostic | undefined;
49
- isHang: boolean;
50
46
  errorMessage: string;
51
47
  }): string | null;
@@ -1,9 +1,10 @@
1
+ import { type FailureLink } from "./failureBody.ts";
1
2
  import type { CredentialCandidate } from "./subscriptionCredentials.ts";
2
3
  import type { ProbeVerdict } from "./subscriptionProbe.ts";
3
4
  /** shared sentence in the four `resolveSlug` routing-slug throws. */
4
- export declare const BYOK_SLUG_MARKER = "env var is required when the model is set to";
5
+ export declare const BYOK_SLUG_MARKER = "must be set when the model is";
5
6
  /** shared sentence in the four `validate*Setup` throws. */
6
- export declare const BYOK_CONFIG_MARKER = "selected but required configuration is missing:";
7
+ export declare const BYOK_CONFIG_MARKER = "is missing required settings:";
7
8
  /**
8
9
  * A BYOK provider setup is incomplete, at either of the two points that catch it:
9
10
  * `resolveSlug` refusing a routing slug whose backing env var is unset, and the
@@ -28,14 +29,35 @@ export declare function isByokSetupError(text: string): boolean;
28
29
  * `MODEL_ACCESS_MARKER`) because blaming the user for a transient fetch failure
29
30
  * on our side is the wrong CTA — their key is configured and still stored.
30
31
  */
31
- export declare const SECRETS_UNAVAILABLE_MARKER = "couldn't load your Pullfrog secrets";
32
+ export declare const SECRETS_UNAVAILABLE_MARKER = "couldn't load your stored secrets";
32
33
  /**
33
34
  * marker for the "your Router wallet is empty AND you have no provider key"
34
35
  * body. its own marker rather than the missing-key one because
35
36
  * `formatApiKeyErrorSummary` rebuilds any body carrying `MISSING_KEY_MARKER`
36
37
  * that doesn't start with it, which would silently discard this copy.
37
38
  */
38
- export declare const ROUTER_UNFUNDED_MARKER = "your Pullfrog Router balance is empty";
39
+ export declare const ROUTER_UNFUNDED_MARKER = "Your Pullfrog Router balance is empty";
40
+ /**
41
+ * marker for the body a trial account gets once its funded credits are spent:
42
+ * at the late fallback, or when the funded key runs out mid-run. Without it the
43
+ * first is told to add an API key and the second that a Router wallet it never
44
+ * had went negative, and neither mentions the trial.
45
+ */
46
+ export declare const TRIAL_SPENT_MARKER = "reached the limit of your free trial credits";
47
+ export declare function buildTrialSpentError(params: {
48
+ owner: string;
49
+ }): string;
50
+ /** the repo's console page: its model, instructions and scripts. */
51
+ export declare function repoConsoleUrl(repo: {
52
+ owner: string;
53
+ name: string;
54
+ }): string;
55
+ export declare function pickModelLink(repo: {
56
+ owner: string;
57
+ name: string;
58
+ }): FailureLink;
59
+ /** the account console's billing card: provider keys, subscriptions and Router credit. */
60
+ export declare function providerLink(owner: string): FailureLink;
39
61
  /**
40
62
  * marker for a credential the provider explicitly REJECTED before the agent
41
63
  * started. needs a verbatim guard for the same reason as its three siblings,
@@ -76,6 +98,7 @@ export declare const CREDENTIAL_POOL_REFUSED_MARKER = "No configured credential
76
98
  */
77
99
  export declare class NoUsableCredentialError extends Error {
78
100
  }
101
+ export declare function buildSecretsUnavailableError(): string;
79
102
  /**
80
103
  * Validate that the resolved model can actually be served by the chosen
81
104
  * agent. For routing slugs (Bedrock / Vertex) the auth shape is multi-var
@@ -160,6 +183,8 @@ export declare function isMalformedKeyError(text: string): boolean;
160
183
  * Anthropic entitlement denial for Claude Pro/Max subscription auth (#1072):
161
184
  * an org admin turned off Claude Code subscription access. no credential the
162
185
  * user controls fixes it, so it gets its own copy instead of the re-auth CTA.
186
+ * The probe sees the same denial as a 403, `OAuth authentication is currently
187
+ * not allowed for this organization`, which used to be answered "sign in again".
163
188
  */
164
189
  export declare function isClaudeSubscriptionDisabledError(text: string): boolean;
165
190
  /**
@@ -178,6 +203,15 @@ export declare function isClaudeSubscriptionDisabledError(text: string): boolean
178
203
  */
179
204
  export declare function isKimiPlanTierError(text: string): boolean;
180
205
  export declare function isClaudeSessionLimitError(text: string): boolean;
206
+ /** the subscription that serves `model`'s provider, named by the env var that carries it. */
207
+ export declare function subscriptionFor(model: string | undefined): {
208
+ label: string;
209
+ command: string;
210
+ provider: string;
211
+ env: string;
212
+ } | undefined;
213
+ /** a provider id's display name, e.g. `openai` → OpenAI. */
214
+ export declare function providerDisplayName(id: string | null | undefined): string | undefined;
181
215
  /**
182
216
  * The provider rejected a credential we checked before the agent started, and
183
217
  * nothing else on the account can serve this run. Names the credential and the
@@ -201,8 +235,8 @@ export type RefusedCredential = {
201
235
  };
202
236
  /**
203
237
  * The credential pool probed every credential configured for the model and
204
- * none can serve it. One bullet per credential: the probe's own verdict, quoted,
205
- * plus the remedy its kind and status call for — never inferred from the prose.
238
+ * none can serve it. One sentence per credential, from its kind and status —
239
+ * never inferred from the prose — with the probe's own verdicts collapsed below.
206
240
  */
207
241
  export declare function buildCredentialPoolRefusedError(params: {
208
242
  model: string;
@@ -213,10 +247,16 @@ export declare function buildCredentialPoolRefusedError(params: {
213
247
  /**
214
248
  * Friendly Markdown summary for both the missing-key and invalid-key cases.
215
249
  * Used in the catch / result-failure paths in `main.ts` to overwrite the raw
216
- * agent error before it's posted to the PR progress comment.
250
+ * agent error before it's posted to the PR progress comment. `model` names the
251
+ * subscription in play — guessing it from the env once told a GPT run its Claude
252
+ * subscription was rejected — and `output`, the provider's own words, is
253
+ * collapsed under the copy.
217
254
  */
218
255
  export declare function formatApiKeyErrorSummary(params: {
219
256
  owner: string;
220
257
  name: string;
221
258
  raw: string;
259
+ model?: string | undefined;
260
+ credential?: string | undefined;
261
+ output?: string | undefined;
222
262
  }): string;
@@ -41,7 +41,9 @@ export declare class BillingError extends Error {
41
41
  * GH Actions apply whatever retry policy the workflow has configured.
42
42
  */
43
43
  export declare class TransientError extends Error {
44
- constructor(message: string);
44
+ /** the customer-facing follow-up to `message`, which is the headline. */
45
+ detail: string;
46
+ constructor(message: string, detail?: string);
45
47
  }
46
48
  /**
47
49
  * Why the org commercial gate refused a run (billing model v2), resolved by
@@ -68,7 +70,7 @@ export declare function commercialPaywallBody(params: {
68
70
  /**
69
71
  * commercial-gate copy for action runs, linked to the account billing card.
70
72
  *
71
- * if this fires on a `pullfrog/preview-*` repo it is an infrastructure bug, not
73
+ * if this fires on a `PullfrogDev/preview-*` repo it is an infrastructure bug, not
72
74
  * a billing state — those repos have no real billing state to refuse. the
73
75
  * original cause was the gate comparing the subscription's originating host
74
76
  * with strict equality, which no preview could satisfy against a cloned
@@ -114,6 +116,7 @@ export declare function formatBillingErrorSummary(error: BillingError, owner: st
114
116
  /**
115
117
  * Render a TransientError as user-facing markdown. Distinct framing from
116
118
  * BillingError so the user doesn't read an alarm and assume their card
117
- * failed, this branch is "our fault, retry shortly", not theirs.
119
+ * failed, this branch is "our fault, retry shortly", not theirs. Both parts are
120
+ * ours and customer-ready; the server's own wording goes to the log.
118
121
  */
119
- export declare function formatTransientErrorSummary(error: TransientError, owner: string): string;
122
+ export declare function formatTransientErrorSummary(error: TransientError): string;
@@ -1,4 +1,18 @@
1
1
  export declare const PULLFROG_DIVIDER = "<!-- PULLFROG_DIVIDER_DO_NOT_REMOVE_PLZ -->";
2
+ /**
3
+ * A run whose configured model was swapped for one Pullfrog could fund. `trial`
4
+ * carries the account's remaining trial credits, as run-context counted them
5
+ * when the run started, so the disclosure can say how much is left; undefined
6
+ * when the server predates the figure.
7
+ */
8
+ export type ModelClamp = {
9
+ from: string;
10
+ reason: "card" | "noRouterPath" | "oss";
11
+ } | {
12
+ from: string;
13
+ reason: "trial";
14
+ creditsLeftUsd: number | undefined;
15
+ };
2
16
  export interface WorkflowRunFooterInfo {
3
17
  owner: string;
4
18
  repo: string;
@@ -27,10 +41,7 @@ export interface BuildPullfrogFooterParams {
27
41
  * provider-key nudge — so the downgrade is visible rather than silently
28
42
  * presenting Kimi as the pick.
29
43
  */
30
- clamped?: {
31
- from: string;
32
- reason: "card" | "noRouterPath" | "oss" | "trial";
33
- } | undefined;
44
+ clamped?: ModelClamp | undefined;
34
45
  /**
35
46
  * true when the run used the default proxy model only because no model was
36
47
  * selected (Router billing + "auto"). the footer appends a note nudging the
@@ -47,6 +58,8 @@ export interface BuildPullfrogFooterParams {
47
58
  /** repo owner, used to deep-link the trial disclosure at that account's own
48
59
  * console rather than the generic landing page. */
49
60
  owner?: string | undefined;
61
+ /** a failure comment: no 𝕏 link, model nudge or trial disclosure under an error. */
62
+ failed?: boolean | undefined;
50
63
  }
51
64
  /**
52
65
  * build a pullfrog footer with configurable parts
@@ -2,7 +2,6 @@ import { type ToolState } from "../toolState.ts";
2
2
  interface ReportErrorParams {
3
3
  toolState: ToolState;
4
4
  error: string;
5
- title?: string;
6
5
  /**
7
6
  * When the run has no pre-existing progress comment to update (silent
8
7
  * IncrementalReview / pull_request_synchronize, mode-less polls), create
@@ -0,0 +1,27 @@
1
+ export type FailureLink = {
2
+ label: string;
3
+ url: string;
4
+ };
5
+ /**
6
+ * The one shape every failure comment takes: a bold, sentence-case headline that
7
+ * names the cause, then at most a short paragraph or a list of two or more, then
8
+ * the error or the tool's own output, then at most two links. Built here rather
9
+ * than per message so those rules hold for every message at once.
10
+ *
11
+ * `error` is shown open, for when the error IS the explanation (an unrecognized
12
+ * failure). `details` is collapsed, for output that backs up a headline we
13
+ * already wrote. Free of imports: `billingErrors.ts` reaches client bundles
14
+ * through `action/internal`.
15
+ */
16
+ export declare function renderFailure(params: {
17
+ headline: string;
18
+ detail?: string | undefined;
19
+ list?: [string, string, ...string[]] | undefined;
20
+ error?: string | undefined;
21
+ details?: {
22
+ summary: string;
23
+ text: string;
24
+ } | undefined;
25
+ links?: [] | [FailureLink] | [FailureLink, FailureLink] | undefined;
26
+ }): string;
27
+ export declare const DISCORD_LINK: FailureLink;
@@ -75,7 +75,7 @@ export declare function fetchIdTokenFromStash(creds: OidcCredentials): Promise<s
75
75
  * own retry still re-mints. Mid-run refreshes take the stashed-credentials path
76
76
  * (`opts.oidc`) and deliberately bypass this.
77
77
  */
78
- export declare const mintIdToken: (() => Promise<string>) & yes.AttemptMethod<[], string> & yes.CacheMethods<undefined, undefined, () => Promise<string>>;
78
+ export declare const mintIdToken: yes.Cached<() => Promise<string>>;
79
79
  /**
80
80
  * ensure a GitHub token is available in the environment.
81
81
  *
@@ -9,3 +9,12 @@
9
9
  */
10
10
  export declare const LEAPING_INTO_ACTION_PREFIX = "Leaping into action";
11
11
  export declare function isLeapingIntoActionCommentBody(body: string): boolean;
12
+ /** the spinner on a to-do list's in-progress item. */
13
+ export declare const TODO_SPINNER_URL = "https://uploads.pullfrog.com/Progress%20Indicator.gif";
14
+ /**
15
+ * A live to-do list: one item still carries the spinner. Every finished body — a
16
+ * final summary, a salvaged answer, an error report — renders with in-progress
17
+ * items completed, so the spinner is only ever on a list whose run is still going
18
+ * or died without writing again.
19
+ */
20
+ export declare function isLiveChecklistCommentBody(body: string): boolean;
@@ -39,6 +39,11 @@ export declare const JsonPayload: import("arktype/internal/variants/object.ts").
39
39
  generateSummary?: boolean | undefined;
40
40
  codexArm?: boolean | undefined;
41
41
  }, {}>;
42
+ /**
43
+ * a run steered by someone resolved as unable to push here. an absent permission
44
+ * is a run no outside author steers (CI fix, a review bot on Pullfrog's own PR).
45
+ */
46
+ export declare function isOutsideAuthor(event: PayloadEvent): boolean;
42
47
  export declare const Inputs: import("arktype/internal/variants/object.ts").ObjectType<{
43
48
  prompt?: string | undefined;
44
49
  prompt_file?: string | undefined;
@@ -61,6 +61,7 @@ export declare function runProxyResolution(ctx: {
61
61
  export declare function resolveTrialFallback(ctx: {
62
62
  payload: ResolvedPayload;
63
63
  configuredModel: string | undefined;
64
+ creditsLeftUsd: number | undefined;
64
65
  oidcCredentials: OidcCredentials | null;
65
66
  repo: {
66
67
  owner: string;
@@ -42,8 +42,9 @@ import type { GitHubAppPermissions } from "./github.ts";
42
42
  * wiki/autonomous-maintenance.md's "no injected instruction can cause a
43
43
  * merge" still true. Legitimate pushes go through `push_branch`, which
44
44
  * enforces the branch rules.
45
- * - `checks` is absent for the same class of reason: no human role grants it,
46
- * and it would let the agent post its own passing `pullfrog-approval` status
45
+ * - `checks` is `read`, never `write`, for the same class of reason: every role
46
+ * can read a PR's check runs, but no human role can write one, and write would
47
+ * let the agent post its own passing `pullfrog-approval` status
47
48
  * (see wiki/review-approval.md).
48
49
  * - Every qualifying role therefore receives the SAME set. The role decides
49
50
  * whether a token is minted at all, not how wide it is — `admin`, `maintain`
@@ -100,6 +100,9 @@ export interface RunContext {
100
100
  * run — the runner may mint a subsidized efficient-tier key if, and only if,
101
101
  * its own key search comes up dry. see the run-context route. */
102
102
  trialFallback?: boolean | undefined;
103
+ /** the trial account's remaining funded credits, in USD, when it is inside its
104
+ * trial at all. zero or below means the credits are used up. */
105
+ trialCreditsLeftUsd?: number | undefined;
103
106
  }
104
107
  /**
105
108
  * fetch run context from Pullfrog API
@@ -29,6 +29,8 @@ export interface RunContextData {
29
29
  * own key search comes up dry. a permission, not a routing decision — the
30
30
  * server cannot see workflow `env:` keys. see `RunContext.trialFallback`. */
31
31
  trialFallback?: boolean | undefined;
32
+ /** see `RunContext.trialCreditsLeftUsd`. */
33
+ trialCreditsLeftUsd?: number | undefined;
32
34
  }
33
35
  interface ResolveRunContextDataParams {
34
36
  octokit: OctokitWithPlugins;
@@ -1,99 +1,67 @@
1
1
  /**
2
- * Classify + render the error thrown out of the main run try-block into a
3
- * pair of user-facing markdown bodies — one for the GitHub Actions job
4
- * summary tab, one for the PR progress comment.
2
+ * Classify + render the error thrown out of the main run try-block into the
3
+ * markdown the user reads — the PR progress comment, and the same body under a
4
+ * `### ❌ Pullfrog failed` banner in the GitHub Actions job summary.
5
5
  *
6
- * Classifications, in dispatch order (first match wins; the api-key
7
- * branch additionally folds in the activity-timeout hang body as a
8
- * sub-source so a hang masking an api-key error still surfaces the api-key
9
- * CTA):
6
+ * Every body names its cause, so none collapses to a bare "Run failed.": an
7
+ * error nothing below recognizes is shown as-is under "An unrecognized error
8
+ * occurred." The shape is `renderFailure`'s, so the copy rules hold for all of
9
+ * them — see `failureBody.ts`.
10
10
  *
11
- * 1. `BillingError` — either the proxy-token mint already threw one (402
12
- * handled inline) or the agent runtime surfaced an OpenRouter
13
- * "key budget exhausted" string mid-run. Both render via
14
- * `formatBillingErrorSummary` so the user sees actionable copy.
11
+ * Classifications, in dispatch order (first match wins):
15
12
  *
16
- * 1a. Pre-rendered bodies — a throw site that already built the markdown
13
+ * 1. Trial credits — a trial key that ran out mid-run is the account's free
14
+ * credits running out, never the Router wallet the same wire text means
15
+ * on a Router run.
16
+ *
17
+ * 1a. `BillingError` — the agent runtime surfaced an OpenRouter "key budget
18
+ * exhausted" string mid-run on a Router run. Same copy as a proxy-token 402.
19
+ *
20
+ * 1b. Pre-rendered bodies — a throw site that already built the markdown
17
21
  * (`MODEL_ACCESS_MARKER`, `SECRETS_UNAVAILABLE_MARKER`,
18
- * `ROUTER_UNFUNDED_MARKER`, `CREDENTIAL_REJECTED_MARKER`,
19
- * `CREDENTIAL_POOL_REFUSED_MARKER`) passes through verbatim on both surfaces.
22
+ * `ROUTER_UNFUNDED_MARKER`, `TRIAL_SPENT_MARKER`,
23
+ * `CREDENTIAL_REJECTED_MARKER`, `CREDENTIAL_POOL_REFUSED_MARKER`) passes
24
+ * through verbatim.
20
25
  *
21
26
  * 2. BYOK provider billing-exhausted (#835) — DeepSeek "Insufficient
22
27
  * Balance", Anthropic "credit balance is too low", OpenCode Zen
23
- * `CreditsError`, Gemini "spending cap". Checked before api-key auth
24
- * because billing-exhausted responses often carry 401 status codes
25
- * that `isApiKeyAuthError` would otherwise mis-classify.
28
+ * `CreditsError`, Gemini "spending cap", a ChatGPT workspace out of
29
+ * credits. Checked before api-key auth because billing-exhausted responses
30
+ * often carry 401 status codes that `isApiKeyAuthError` would otherwise
31
+ * mis-classify.
26
32
  *
27
33
  * 2a. Provider usage limit (#1474) — a capped subscription window or a
28
34
  * configured workspace limit; "wait or raise it", never "top up".
29
35
  *
30
36
  * 3. API-key auth error — `isApiKeyAuthError` sniffs the raw error string
31
37
  * (or the activity-timeout hang body when present, since that's where
32
- * the underlying provider error often lands); `formatApiKeyErrorSummary`
33
- * renders provider + console-link copy.
34
- *
35
- * 4. ProviderModelNotFoundError — configured model id not in the OpenCode
36
- * catalog (opencode ≥1.18 words it `Model not found: <id>`, #1470);
37
- * renders a nudge to pick a different model.
38
- *
39
- * 4a. No-provider-available (#1077) — the model IS in the catalog but the
40
- * provider declines to route it on this account's plan (OpenCode Zen's
41
- * own refusal string). Same "pick another model" CTA, different reason.
38
+ * the underlying provider error often lands).
42
39
  *
43
- * 4a'. Zen free-tier gate (#1377) — Zen 403s a free-tier request whose tool
44
- * list is missing a built-in it expects. The harness keeps `bash` listed,
45
- * but opencode's auto-compaction request carries no tools, so a long
46
- * free-Zen run can still die mid-flight on the raw upstream string.
47
- *
48
- * 4b. Context-window overflow (#1116) — terminal `Prompt is too long` /
49
- * `maximum context length is N tokens`. Actionable, so it renders on
50
- * both surfaces rather than collapsing to the one-line comment.
51
- *
52
- * 4b'. Run time limit — the agent was still working when the run's own
53
- * timeout fired. Actionable (raise the limit), so both surfaces.
40
+ * 4. Model not in OpenCode's catalog (opencode ≥1.18 words it `Model not
41
+ * found: <id>`, #1470), a provider declining the model (#1077), Zen's
42
+ * free-tier gate (#1377), context overflow (#1116), the run's time limit,
43
+ * the repo's stop script, and an agent that never posted its review.
54
44
  *
55
45
  * 4c. Transient upstream failure (#1173) — Anthropic `529 Overloaded`,
56
46
  * OpenRouter `provider_unavailable` / `timeout`, Zen `Streaming response
57
- * failed: [5xx]`. Last of the classified branches so every more specific
58
- * one wins first. Renders on both surfaces: the remedy is re-triggering,
59
- * which the user can only know if we say so.
47
+ * failed: [5xx]`, Codex's model at capacity. Last of the classified
48
+ * branches so every more specific one wins first.
60
49
  *
61
- * 5. Activity-timeout hang — `errorMessage` starts with
62
- * `"activity timeout"` or `"agent still pending"` AND none of the
63
- * above matched. The harness keeps structured diagnostic state on
64
- * `toolState.agentDiagnostic`; `formatAgentHangBody` renders that into
65
- * the job summary. The PR comment keeps that same body whenever the hang
66
- * is EXPLAINED — a classified provider error (#778, #1183) or a provider
67
- * that never returned a first token — and otherwise collapses to a
68
- * one-line `**Run failed.** [View the logs →]`, because watchdog jargon,
69
- * event counts and a benign stderr tail are operator-grade detail that
70
- * only alarm the average user.
50
+ * 5. Activity-timeout hang — `formatAgentHangBody` says whose side went
51
+ * quiet, with the agent's last output collapsed under it.
71
52
  *
72
53
  * 6. Unclassified provider error (#1474) — `errorMessage` starts with
73
- * `provider error: `. opencode prefixes every provider-side turn failure
74
- * with the provider's text; claude-code (a zero-event exit) and codex (no
75
- * turn error, no stderr) prefix only our classifier label. Either names
76
- * something the user can act on, so both surfaces get a lead line plus the
77
- * raw text in a fenced block.
54
+ * `provider error: `: the provider's own text, shown under a lead line.
78
55
  *
79
- * 7. Default — the job summary gets a plain-English lead sentence plus the
80
- * raw error in a fenced code block under the `### ❌ Pullfrog failed`
81
- * banner; the PR comment collapses to the same one-line logs link as
82
- * the hang case, since the raw internal string helps nobody on the PR.
83
- *
84
- * Net: the actionable classifications (billing, usage limit, API-key,
85
- * model-not-found, no-provider-available, context-overflow, run time limit,
86
- * transient-upstream, unclassified provider error) render identical bodies on
87
- * both surfaces; the non-actionable ones (unexplained hang, generic) keep the
88
- * forensics in the Actions job summary and show a calm one-liner in the PR
89
- * comment, whose footer already carries Pullfrog branding + rerun links.
56
+ * 7. Default — "An unrecognized error occurred." over the error itself.
90
57
  */
58
+ import type { AgentCredential } from "../agents/shared.ts";
91
59
  import type { AgentDiagnostic } from "./agentHangReport.ts";
92
60
  export type RenderedRunError = {
93
61
  summary: string;
94
62
  comment: string;
95
63
  };
96
- export declare function renderRunError(input: {
64
+ type RenderRunErrorInput = {
97
65
  errorMessage: string;
98
66
  repo: {
99
67
  owner: string;
@@ -104,4 +72,11 @@ export declare function renderRunError(input: {
104
72
  * the user's own provider credential. `payload.proxyModel` at both call
105
73
  * sites. */
106
74
  routerActive: boolean;
107
- }): RenderedRunError;
75
+ /** the model that ran: it names the provider, and the subscription when one is in play. */
76
+ model?: string | undefined;
77
+ credential?: AgentCredential | undefined;
78
+ /** the run was served by the account's free trial credits. */
79
+ trial?: boolean | undefined;
80
+ };
81
+ export declare function renderRunError(input: RenderRunErrorInput): RenderedRunError;
82
+ export {};
@@ -1,19 +1,10 @@
1
+ export declare function maskSecret(value: string): void;
1
2
  /**
2
- * `@actions/core` workflow commands that carry a credential, gated on actually
3
- * running under a runner.
4
- *
5
- * Off a runner nothing consumes these commands and `@actions/core` writes the
6
- * raw value to stdout instead — `setSecret` always, `saveState` / `setOutput`
7
- * whenever `GITHUB_STATE` / `GITHUB_OUTPUT` are unset. That is a plaintext
8
- * credential on whatever terminal is running the action, which is how an OpenAI
9
- * key reached local Codex transcripts and was disabled (2026-09).
10
- *
11
- * Route every credential-bearing call through here instead of calling `core`
12
- * directly — `nub run check:secret-commands` enforces it. Non-credential output
13
- * (the agent's own `result` text) stays ungated, because the test harness reads
14
- * it back through `GITHUB_OUTPUT` on a host with no runner.
3
+ * Every value `maskSecret` registered, replaced in `text` as the runner would in
4
+ * a log: whole, then line by line. Parts under 8 characters are skipped, or the
5
+ * `{` and `}` lines of a JSON key would blank every brace in the comment.
15
6
  */
16
- export declare function maskSecret(value: string): void;
7
+ export declare function redactSecrets(text: string): string;
17
8
  /** @see maskSecret — same gate, for state the `post:` hook reads back. */
18
9
  export declare function saveSecretState(name: string, value: string): void;
19
10
  /** @see maskSecret — same gate, for a step output carrying a credential. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pullfrog",
3
- "version": "0.1.100",
3
+ "version": "0.1.102",
4
4
  "cliContractVersion": "1",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,12 +27,12 @@
27
27
  "prepare": "cd .. && husky"
28
28
  },
29
29
  "devDependencies": {
30
- "@actions/core": "^1.11.1",
30
+ "@actions/core": "^3.0.1",
31
31
  "@anthropic-ai/claude-code": "2.1.293",
32
32
  "@ark/fs": "0.56.0",
33
33
  "@ark/util": "0.56.0",
34
34
  "@clack/prompts": "^1.2.0",
35
- "@modelcontextprotocol/sdk": "^1.26.0",
35
+ "@modelcontextprotocol/sdk": "^1.32.0",
36
36
  "@octokit/plugin-throttling": "^11.0.3",
37
37
  "@octokit/rest": "^22.0.0",
38
38
  "@octokit/webhooks-types": "^7.6.1",
@@ -40,7 +40,7 @@
40
40
  "@openai/codex-sdk": "0.159.2",
41
41
  "@opencode-ai/sdk": "1.18.29",
42
42
  "@standard-schema/spec": "1.1.0",
43
- "@toon-format/toon": "^1.0.0",
43
+ "@toon-format/toon": "^2.3.1",
44
44
  "@types/node": "^24.7.2",
45
45
  "@types/semver": "^7.7.1",
46
46
  "@types/turndown": "^5.0.5",
@@ -53,7 +53,7 @@
53
53
  "esbuild": "^0.25.9",
54
54
  "execa": "^9.6.0",
55
55
  "fastmcp": "^3.34.0",
56
- "file-type": "^21.3.0",
56
+ "file-type": "^21.3.2",
57
57
  "husky": "^9.0.0",
58
58
  "opencode-ai": "1.18.29",
59
59
  "package-manager-detector": "^1.6.0",
@@ -64,10 +64,10 @@
64
64
  "turndown": "^7.2.0",
65
65
  "typescript": "^5.9.3",
66
66
  "undici": "^7.22.0",
67
- "vitest": "^4.0.17",
68
- "yaml": "^2.8.2",
69
- "yes": "0.2.4",
70
- "zod": "4.3.6"
67
+ "vitest": "^4.1.12",
68
+ "yaml": "^2.8.3",
69
+ "yes": "0.2.7",
70
+ "zod": "4.6.6"
71
71
  },
72
72
  "repository": {
73
73
  "type": "git",