pullfrog 0.1.46 → 0.1.48

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.
@@ -13,8 +13,8 @@
13
13
  * in `action/test/coverage.ts::ALWAYS_RUN_ALL`).
14
14
  */
15
15
  /**
16
- * Billing-layer error surfaced from `/api/proxy-token` as a 402. User-actionable
17
- * distinct from TransientError (503 / transient sync issue) so the job
16
+ * Billing-layer error surfaced from `/api/proxy-token` as a 402. User-actionable,
17
+ * distinct from TransientError (503 / transient sync issue) so the job
18
18
  * summary + PR comment can use affirmative "you need to do X" copy rather than
19
19
  * the ambiguous "billing error" label that makes transient outages look like
20
20
  * the user's fault.
@@ -37,28 +37,45 @@ export declare class BillingError extends Error {
37
37
  }
38
38
  /**
39
39
  * Transient service failures from `/api/proxy-token` (503: partial OpenRouter
40
- * usage sync, DB flake, in-flight payment intent). Not the user's fault the
40
+ * usage sync, DB flake, in-flight payment intent). Not the user's fault, the
41
41
  * summary uses "temporarily unavailable" framing, and the non-zero exit lets
42
42
  * GH Actions apply whatever retry policy the workflow has configured.
43
43
  */
44
44
  export declare class TransientError extends Error {
45
45
  constructor(message: string);
46
46
  }
47
+ /**
48
+ * Canonical paused-run paywall body (billing model v2). Single source of truth so
49
+ * the two surfaces that show it can never drift: the trigger-time comment (a run
50
+ * refused before it starts, `triggerWorkflow.postPaywallComment`) and the mid-run
51
+ * 402 render (`formatBillingErrorSummary` via `renderRunError`). `url` is the
52
+ * caller's billing surface (each passes its own, internal plan page vs console).
53
+ */
54
+ export declare function commercialPaywallBody(params: {
55
+ reason: "commercial" | "subscription_unpaid";
56
+ ownerLogin: string;
57
+ url: string;
58
+ }): string;
59
+ /** commercial-gate copy for action runs, linked to the account billing card. */
60
+ export declare function formatCommercialGateSummary(params: {
61
+ reason: "commercial" | "subscription_unpaid";
62
+ ownerLogin: string;
63
+ }): string;
47
64
  /**
48
65
  * Render a BillingError as user-facing markdown (shared between GH job summary
49
66
  * and the PR progress comment). Goals:
50
67
  *
51
- * - quiet, not alarmist bold first line instead of an `### ❌` H3, since
68
+ * - quiet, not alarmist, bold first line instead of an `### ❌` H3, since
52
69
  * the comment already has Pullfrog branding in the footer
53
- * - actionable every branch ends in a single CTA deep-linked to the
70
+ * - actionable, every branch ends in a single CTA deep-linked to the
54
71
  * correct section of the owner's console
55
- * - honest say what actually went wrong (card declined vs. balance
72
+ * - honest, say what actually went wrong (card declined vs. balance
56
73
  * empty vs. 3DS required), don't lump them under "billing error"
57
74
  *
58
75
  * Branches:
59
76
  * - `router_requires_card`: user is on Router mode with no card AND no
60
- * wallet balance (signup credit exhausted or not granted). Frame as
61
- * "add a card to continue", link to `#model-access` where the Add
77
+ * wallet balance. Frame as
78
+ * "add a card to continue", link to `#billing` where the Add
62
79
  * Card flow lives.
63
80
  * - `router_balance_exhausted`: user has a card on file but auto-reload is
64
81
  * disabled and they've spent past their $5 overdraft buffer. Frame as
@@ -69,7 +86,7 @@ export declare class TransientError extends Error {
69
86
  * wallet is now negative; same remediation as `router_balance_exhausted`
70
87
  * but framed for the after-the-fact case ("this run was cut short").
71
88
  * - `needsReauthentication`: issuer requires 3DS on every off-session
72
- * charge. Re-adding the card won't help the only escape is a manual
89
+ * charge. Re-adding the card won't help, the only escape is a manual
73
90
  * top-up where 3DS runs interactively in Stripe Checkout.
74
91
  * - `declineCode` set: Stripe declined a real charge. Show the sub-code
75
92
  * so support can act on it; tell the user we'll retry on next dispatch.
@@ -80,6 +97,6 @@ export declare function formatBillingErrorSummary(error: BillingError, owner: st
80
97
  /**
81
98
  * Render a TransientError as user-facing markdown. Distinct framing from
82
99
  * BillingError so the user doesn't read an alarm and assume their card
83
- * failed this branch is "our fault, retry shortly", not theirs.
100
+ * failed, this branch is "our fault, retry shortly", not theirs.
84
101
  */
85
102
  export declare function formatTransientErrorSummary(error: TransientError, owner: string): string;
@@ -1,4 +1,4 @@
1
- import type { PayloadEvent } from "../external.ts";
1
+ import { type PayloadEvent } from "../external.ts";
2
2
  import type { RepoSettings } from "./runContext.ts";
3
3
  export declare const JsonPayload: import("arktype/internal/variants/object.ts").ObjectType<{
4
4
  "~pullfrog": true;
@@ -6,6 +6,7 @@ export declare const JsonPayload: import("arktype/internal/variants/object.ts").
6
6
  prompt: string;
7
7
  model?: string | undefined;
8
8
  modelExplicit?: boolean | undefined;
9
+ effort?: string | number | undefined;
9
10
  triggerer?: string | undefined;
10
11
  baseInstructions?: string | undefined;
11
12
  eventInstructions?: string;
@@ -22,12 +23,16 @@ export declare const JsonPayload: import("arktype/internal/variants/object.ts").
22
23
  id: string;
23
24
  type: "issue" | "review";
24
25
  } | undefined;
26
+ checkRun?: {
27
+ id: string;
28
+ } | undefined;
25
29
  generateSummary?: boolean | undefined;
26
30
  }, {}>;
27
31
  export declare const Inputs: import("arktype/internal/variants/object.ts").ObjectType<{
28
32
  prompt?: string | undefined;
29
33
  prompt_file?: string | undefined;
30
34
  model?: string | undefined;
35
+ effort?: string | undefined;
31
36
  timeout?: string | undefined;
32
37
  push?: "disabled" | "enabled" | "restricted" | undefined;
33
38
  shell?: "disabled" | "enabled" | "restricted" | undefined;
@@ -50,6 +55,7 @@ export declare function resolvePayload(resolvedPromptInput: ResolvedPromptInput,
50
55
  version: string;
51
56
  model: string | undefined;
52
57
  modelExplicit: boolean;
58
+ effort: number | undefined;
53
59
  prompt: string;
54
60
  triggerer: string | undefined;
55
61
  baseInstructions: string | undefined;
@@ -68,10 +74,14 @@ export declare function resolvePayload(resolvedPromptInput: ResolvedPromptInput,
68
74
  id: string;
69
75
  type: "issue" | "review";
70
76
  } | undefined;
77
+ checkRun: {
78
+ id: string;
79
+ } | undefined;
71
80
  generateSummary: boolean | undefined;
72
81
  push: import("../external.ts").PushPermission;
73
82
  shell: import("../external.ts").ShellPermission;
74
- statusChecks: boolean;
83
+ runStatusCheck: boolean;
84
+ approvalCheck: boolean;
75
85
  progressComments: boolean;
76
86
  proxyModel: string | undefined;
77
87
  };
@@ -23,6 +23,7 @@ export interface LearningsHeading {
23
23
  }
24
24
  export interface RepoSettings {
25
25
  model: string | null;
26
+ effort: number | null;
26
27
  modes: Mode[];
27
28
  setupScript: string | null;
28
29
  postCheckoutScript: string | null;
@@ -34,6 +35,7 @@ export interface RepoSettings {
34
35
  autoMergeEnabled: boolean;
35
36
  signedCommits: boolean;
36
37
  progressComments: boolean;
38
+ statusChecks: boolean;
37
39
  modeInstructions: Record<string, string>;
38
40
  learnings: string | null;
39
41
  learningsHeadings: LearningsHeading[];
@@ -43,11 +45,20 @@ export interface RepoSettings {
43
45
  xrepoLearningsHeadings: LearningsHeading[];
44
46
  }
45
47
  /**
46
- * Account-level billing plan. Orthogonal to repo-level OSS status. Mirrors
47
- * the server's `AccountPlan` in `utils/billing.ts`. `"none"` = free tier,
48
- * `"payg"` = card on file / pay-as-you-go.
48
+ * Account-level card signal. Orthogonal to repo-level OSS and Pro status.
49
+ * Mirrors the server's legacy-named `AccountPlan` in `utils/billing.ts`.
50
+ * `"none"` = no card; `"payg"` = card on file.
49
51
  */
50
52
  export type AccountPlan = "none" | "payg";
53
+ /**
54
+ * The org commercial gate's refusal (billing model v2). Set when run-context
55
+ * returns 402 for a `paused`/`unpaid` org — the backstop for manual re-runs and
56
+ * self-configured triggers that never hit reserveRun. main.ts stops before
57
+ * installing the agent or loading account secrets and writes actionable copy.
58
+ * A forked action can bypass this response, so proxy-token enforces the same
59
+ * verdict before issuing a Pullfrog Router key.
60
+ */
61
+ export type CommercialRefusal = "commercial" | "subscription_unpaid";
51
62
  export interface RunContext {
52
63
  settings: RepoSettings;
53
64
  apiToken: string;
@@ -55,6 +66,7 @@ export interface RunContext {
55
66
  plan: AccountPlan;
56
67
  proxyModel?: string | undefined;
57
68
  dbSecrets?: Record<string, string> | undefined;
69
+ commercialRefused?: CommercialRefusal | undefined;
58
70
  /**
59
71
  * the server tried and failed to materialize Pullfrog-stored secrets (or we
60
72
  * never got a usable response at all). distinct from an absent `dbSecrets`,
@@ -1,6 +1,6 @@
1
1
  import type { Octokit } from "@octokit/rest";
2
2
  import { type OctokitWithPlugins } from "./github.ts";
3
- import { type AccountPlan, type RepoSettings } from "./runContext.ts";
3
+ import { type AccountPlan, type CommercialRefusal, type RepoSettings } from "./runContext.ts";
4
4
  export interface RunContextData {
5
5
  repo: {
6
6
  owner: string;
@@ -13,6 +13,7 @@ export interface RunContextData {
13
13
  plan: AccountPlan;
14
14
  proxyModel?: string | undefined;
15
15
  dbSecrets?: Record<string, string> | undefined;
16
+ commercialRefused?: CommercialRefusal | undefined;
16
17
  /** stored secrets couldn't be materialized for this run — not the same as
17
18
  * the user having none. see `RunContext.secretsUnavailable`. */
18
19
  secretsUnavailable?: boolean | undefined;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * reasoning effort for a run: a stored POSITION landed on the ladder of the
3
+ * model that actually runs. shared by both harnesses and the startup log so
4
+ * they can't disagree — the log must never claim a level the agent didn't send.
5
+ * see wiki/effort.md.
6
+ */
7
+ import { type EffortPosition } from "../effort.ts";
8
+ import { type ModelAlias } from "../models.ts";
9
+ import type { ResolvedPayload } from "./payload.ts";
10
+ export interface RunEffort {
11
+ /** the position this run resolves at — the stored one, or the default. */
12
+ position: EffortPosition;
13
+ /** false when nothing was configured and `position` is the default. */
14
+ configured: boolean;
15
+ /**
16
+ * the rung the harness sends — always one the model published. undefined when
17
+ * the model publishes no rungs or we can't place it at all; either way the
18
+ * flag is omitted.
19
+ */
20
+ rung: string | undefined;
21
+ /** the catalog alias the ladder came from, if we recognized one. */
22
+ alias: ModelAlias | undefined;
23
+ }
24
+ export declare function resolveRunEffort(ctx: {
25
+ payload: ResolvedPayload;
26
+ resolvedModel?: string | undefined;
27
+ }): RunEffort;
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Startup log formatting for the resolver pipeline. Computes the
3
- * "model / agent / push / shell / timeout" block that main.ts prints
3
+ * "model / effort / agent / push / shell / timeout" block that main.ts prints
4
4
  * after resolving the agent + model + payload.
5
5
  */
6
6
  import type { ResolvedPayload } from "./payload.ts";
7
7
  /**
8
- * Emit the startup block ("» model / agent / push / shell / timeout") after
9
- * the agent and model are resolved. Single side-effect; no return.
8
+ * Emit the startup block ("» model / effort / agent / push / shell / timeout")
9
+ * after the agent and model are resolved. Single side-effect; no return.
10
10
  */
11
11
  export declare function logRunStartup(ctx: {
12
12
  payload: ResolvedPayload;
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Single source of truth for the `pullfrog` commit-status check-run — the row a PR's
3
+ * checks list shows while a run is in flight and after it ends.
4
+ *
5
+ * Pullfrog dispatches its workflow with `ref: <default branch>`, so the GitHub Actions
6
+ * run attaches its check suite to the default branch head and never to the PR head.
7
+ * Nothing about a run is therefore visible from the PR itself unless we post this
8
+ * check-run against the PR head sha ourselves.
9
+ *
10
+ * Created `in_progress` by the server at dispatch — before the runner boots, and
11
+ * upstream of every action-side failure that would otherwise leave the PR silent
12
+ * (billing 402, token resolution, bad model slug, missing provider key). Moved to a
13
+ * terminal conclusion by whichever of three actors reaches it first, all calling
14
+ * `finalizeRunStatusCheck`:
15
+ *
16
+ * 1. the action at run end (`reportStatusChecks`)
17
+ * 2. the `workflow_run.completed` webhook (covers cancel / SIGKILL / early throw)
18
+ * 3. the hourly stuck-run reaper (covers a dropped webhook)
19
+ *
20
+ * That three-layer close-out is what makes an `in_progress` check safe here: a check
21
+ * stranded `in_progress` on a required branch-protection rule would block merges
22
+ * forever, which is why this file did not exist before.
23
+ */
24
+ /**
25
+ * the run-lifecycle check. verdict-agnostic — it reports that a run happened, not what it
26
+ * found. this string IS the branch-protection identifier, so changing it silently breaks
27
+ * any repo that required the old name.
28
+ */
29
+ export declare const RUN_STATUS_CHECK_NAME = "Pullfrog";
30
+ /** the review-verdict check. opt-in, terminal-only, and deliberately separate from the above. */
31
+ export declare const APPROVAL_CHECK_NAME = "Pullfrog approval";
32
+ /**
33
+ * The terminal states we report. Exactly GitHub's check-run `conclusion` enum, which
34
+ * happens to be `WorkflowRunStatus` minus `running` — so the server can map a finished
35
+ * run's status straight across (`checkConclusionFromStatus` in utils/workflowRunStatus.ts).
36
+ */
37
+ export type RunStatusCheckConclusion = "success" | "failure" | "cancelled" | "timed_out" | "action_required" | "neutral" | "skipped" | "stale";
38
+ /**
39
+ * Parse the on-the-wire `{ id: string }` shape (the form carried in `JsonPayload`) into
40
+ * a check-run id. Mirrors `parseProgressComment` — returns undefined when the id isn't a
41
+ * positive integer so callers can short-circuit cleanly.
42
+ */
43
+ export declare function parseCheckRunId(raw: {
44
+ id: string;
45
+ } | null | undefined): number | undefined;
46
+ interface CheckRunResponse {
47
+ data: {
48
+ id: number;
49
+ };
50
+ }
51
+ type CheckRunStatus = "queued" | "in_progress" | "completed";
52
+ type CheckRunOutput = {
53
+ title: string;
54
+ summary: string;
55
+ };
56
+ type CreateCheckRunParams = {
57
+ owner: string;
58
+ repo: string;
59
+ name: string;
60
+ head_sha: string;
61
+ status?: CheckRunStatus;
62
+ conclusion?: RunStatusCheckConclusion;
63
+ details_url?: string;
64
+ output?: CheckRunOutput;
65
+ };
66
+ type UpdateCheckRunParams = {
67
+ owner: string;
68
+ repo: string;
69
+ check_run_id: number;
70
+ status?: CheckRunStatus;
71
+ conclusion?: RunStatusCheckConclusion;
72
+ details_url?: string;
73
+ output?: CheckRunOutput;
74
+ };
75
+ export interface RunStatusCheckOctokit {
76
+ rest: {
77
+ checks: {
78
+ create: (params: CreateCheckRunParams) => Promise<CheckRunResponse>;
79
+ update: (params: UpdateCheckRunParams) => Promise<CheckRunResponse>;
80
+ get: (params: {
81
+ owner: string;
82
+ repo: string;
83
+ check_run_id: number;
84
+ }) => Promise<{
85
+ data: {
86
+ status: string;
87
+ };
88
+ }>;
89
+ listForRef: (params: {
90
+ owner: string;
91
+ repo: string;
92
+ ref: string;
93
+ check_name?: string;
94
+ status?: "queued" | "in_progress" | "completed";
95
+ }) => Promise<{
96
+ data: {
97
+ check_runs: {
98
+ id: number;
99
+ }[];
100
+ };
101
+ }>;
102
+ };
103
+ };
104
+ }
105
+ /**
106
+ * Post the `in_progress` check-run on `headSha` and return its id for the finalizers.
107
+ *
108
+ * Best-effort: a transient 5xx, a repo that revoked `checks: write`, or a PR whose head
109
+ * has already been deleted must never stop a run from dispatching. Returns undefined on
110
+ * failure, which every caller treats as "no check to finalize".
111
+ */
112
+ export declare function createRunStatusCheck(params: {
113
+ octokit: RunStatusCheckOctokit;
114
+ owner: string;
115
+ repo: string;
116
+ headSha: string;
117
+ detailsUrl: string | undefined;
118
+ }): Promise<number | undefined>;
119
+ /**
120
+ * Move an existing check-run to its terminal conclusion.
121
+ *
122
+ * A PATCH, not a second create: `POST /check-runs` with the same name + sha creates a
123
+ * SECOND row rather than replacing the first, so creating twice would show the PR two
124
+ * contradictory `pullfrog` rows. (That is a real bug today — `finalizeSuccessRun` posts
125
+ * success and a later throw posts failure, both as fresh rows.)
126
+ */
127
+ export declare function finalizeRunStatusCheck(params: {
128
+ octokit: RunStatusCheckOctokit;
129
+ owner: string;
130
+ repo: string;
131
+ checkRunId: number;
132
+ conclusion: RunStatusCheckConclusion;
133
+ detailsUrl: string | undefined;
134
+ reviewUrl?: string | undefined;
135
+ }): Promise<void>;
136
+ /**
137
+ * Post an already-terminal check-run in one call.
138
+ *
139
+ * The fallback for runs that never got a server-created check to update: a rolling
140
+ * deploy where the dispatch payload predates `checkRun`, or a workflow invoked outside
141
+ * Pullfrog's own dispatch path (`prompt_file`, a hand-written step).
142
+ */
143
+ export declare function createTerminalRunStatusCheck(params: {
144
+ octokit: RunStatusCheckOctokit;
145
+ owner: string;
146
+ repo: string;
147
+ headSha: string;
148
+ conclusion: RunStatusCheckConclusion;
149
+ detailsUrl: string | undefined;
150
+ reviewUrl?: string | undefined;
151
+ }): Promise<void>;
152
+ /**
153
+ * Whether this check still needs finalizing.
154
+ *
155
+ * The server-side close-outs are BACKSTOPS: they exist for runs the action could not
156
+ * finalize itself. Firing them unconditionally is actively harmful, because the action
157
+ * writes a richer summary than they can — it knows the review URL, they do not — so a
158
+ * blind re-PATCH silently strips that link seconds after it appears. It also resets
159
+ * `completed_at`, discarding the duration GitHub renders as "Successful in 2m".
160
+ *
161
+ * Returns false when GitHub cannot be reached: a backstop that cannot confirm the check
162
+ * is unfinished must not overwrite a good one.
163
+ */
164
+ export declare function runStatusCheckNeedsFinalizing(params: {
165
+ octokit: RunStatusCheckOctokit;
166
+ owner: string;
167
+ repo: string;
168
+ checkRunId: number;
169
+ }): Promise<boolean>;
170
+ export {};
@@ -1,24 +1,21 @@
1
1
  import type { ToolContext } from "../mcp/server.ts";
2
2
  /**
3
- * post the opt-in `pullfrog` (run completion) and `pullfrog-approval` (review
4
- * verdict) commit-status check-runs so they can be required by branch
5
- * protection. no-op unless the `status_checks` input is enabled and the run is
6
- * on a pull request.
3
+ * post the `Pullfrog` (run lifecycle) and `Pullfrog approval` (review verdict)
4
+ * commit-status check-runs.
7
5
  *
8
- * terminal-only by design: we never create an `in_progress` check. a
9
- * hard-cancelled run (SIGKILL) would strand a required check `in_progress` and
10
- * block merges forever; an absent required check already blocks merge the same
11
- * way while the run is in flight, with no stuck-check failure mode.
6
+ * - `Pullfrog` is on by default (`Repo.statusChecks`). the server already created it
7
+ * `in_progress` at dispatch, so the work here is a PATCH to its terminal conclusion —
8
+ * see `runStatusCheck.ts` for why a second create would leave two contradictory rows.
9
+ * the terminal-create fallback covers a payload with no `checkRun` (older server
10
+ * build mid-rolling-deploy, or a workflow driven outside Pullfrog's dispatch path).
11
+ * - `Pullfrog approval` stays opt-in (`status_checks: enabled`) and terminal-only:
12
+ * it asserts a review verdict, which only exists once a run produces one. anchored
13
+ * to the exact reviewed sha so a mid-run push leaves the new head unapproved until
14
+ * a follow-up re-review reports.
12
15
  *
13
- * - `pullfrog` is posted on every PR run: success iff the run finished
14
- * successfully, failure on error/timeout. review-verdict-agnostic.
15
- * - `pullfrog-approval` is posted only when this run produced an approval
16
- * verdict (`toolState.approval`, set by create_pull_request_review),
17
- * anchored to the reviewed sha so a mid-run push leaves the new head
18
- * unapproved until the follow-up re-review reports.
19
- *
20
- * best-effort throughout: a check-post failure (fork PR head not in the base
21
- * repo, transient 5xx, closed PR) must never flip the run's own outcome.
16
+ * best-effort throughout: a check-post failure (transient 5xx, closed PR, revoked
17
+ * permission) must never flip the run's own outcome. the `workflow_run.completed` webhook
18
+ * and both stuck-run reaper sweeps close out a check this function fails to finalize.
22
19
  */
23
20
  export declare function reportStatusChecks(ctx: ToolContext, params: {
24
21
  runSucceeded: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pullfrog",
3
- "version": "0.1.46",
3
+ "version": "0.1.48",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "pullfrog": "dist/cli.mjs",
File without changes