pullfrog 0.1.31 → 0.1.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -1
- package/dist/cli.mjs +594 -213
- package/dist/index.js +593 -212
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal.js +37 -10
- package/dist/mcp/review.d.ts +41 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/toolState.d.ts +4 -0
- package/dist/utils/agent.d.ts +3 -1
- package/dist/utils/autoMerge.d.ts +37 -0
- package/dist/utils/checksGate.d.ts +45 -0
- package/dist/utils/isTransientNetworkError.d.ts +9 -0
- package/dist/utils/payload.d.ts +10 -1
- package/dist/utils/runContext.d.ts +1 -0
- package/dist/utils/statusChecks.d.ts +25 -0
- package/dist/yes/index.d.ts +26 -0
- package/dist/yes.js +38 -1
- package/package.json +1 -1
package/dist/internal/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export type { Mode } from "../modes.ts";
|
|
|
8
8
|
export { modes } from "../modes.ts";
|
|
9
9
|
export type { BuildPullfrogFooterParams, WorkflowRunFooterInfo, } from "../utils/buildPullfrogFooter.ts";
|
|
10
10
|
export { buildPullfrogFooter, PULLFROG_DIVIDER, stripExistingFooter, } from "../utils/buildPullfrogFooter.ts";
|
|
11
|
+
export { checkRunsGate, hasVerifiedCheck } from "../utils/checksGate.ts";
|
|
11
12
|
export type { CodexAuthBody } from "../utils/codexOAuth.ts";
|
|
12
13
|
export { decodeJwtExpMs, OAuthInvalidGrantError, parseCodexAuthBody, refreshCodexAuthBody, stringifyCodexAuthBody, } from "../utils/codexOAuth.ts";
|
|
13
14
|
export type { ResourceUsage, UsageSummary } from "../utils/github.ts";
|
package/dist/internal.js
CHANGED
|
@@ -9,15 +9,20 @@ var providers = {
|
|
|
9
9
|
displayName: "Anthropic",
|
|
10
10
|
envVars: ["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"],
|
|
11
11
|
models: {
|
|
12
|
-
// claude-fable-5 is
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
12
|
+
// claude-fable-5 is selectable but not recommended/auto-selected — it's
|
|
13
|
+
// moving to usage-credits-only billing and isn't broadly available yet, so
|
|
14
|
+
// opus stays the universally-available flagship, the AUTO_INTELLIGENT tier
|
|
15
|
+
// target, and the recommended pick. an explicit fable pick hits the real
|
|
16
|
+
// API and is access-gated: it errors for accounts without access today and
|
|
17
|
+
// just works once usage credits are live — no silent opus fallback, the
|
|
18
|
+
// API is the source of truth (#959).
|
|
17
19
|
"claude-fable": {
|
|
18
20
|
displayName: "Claude Fable",
|
|
19
21
|
resolve: "anthropic/claude-fable-5",
|
|
20
|
-
|
|
22
|
+
// rolling alias: models.dev's OpenRouter mirror lags brand-new pinned
|
|
23
|
+
// versions (claude-fable-5 isn't indexed yet), so track ~…-latest to
|
|
24
|
+
// stay catalog-valid and auto-follow version bumps.
|
|
25
|
+
openRouterResolve: "openrouter/~anthropic/claude-fable-latest",
|
|
21
26
|
subagentModel: "claude-sonnet"
|
|
22
27
|
},
|
|
23
28
|
"claude-opus": {
|
|
@@ -29,8 +34,8 @@ var providers = {
|
|
|
29
34
|
},
|
|
30
35
|
"claude-sonnet": {
|
|
31
36
|
displayName: "Claude Sonnet",
|
|
32
|
-
resolve: "anthropic/claude-sonnet-
|
|
33
|
-
openRouterResolve: "openrouter/anthropic/claude-sonnet-
|
|
37
|
+
resolve: "anthropic/claude-sonnet-5",
|
|
38
|
+
openRouterResolve: "openrouter/anthropic/claude-sonnet-5"
|
|
34
39
|
},
|
|
35
40
|
"claude-haiku": {
|
|
36
41
|
displayName: "Claude Haiku",
|
|
@@ -208,8 +213,8 @@ var providers = {
|
|
|
208
213
|
},
|
|
209
214
|
"claude-sonnet": {
|
|
210
215
|
displayName: "Claude Sonnet",
|
|
211
|
-
resolve: "opencode/claude-sonnet-
|
|
212
|
-
openRouterResolve: "openrouter/anthropic/claude-sonnet-
|
|
216
|
+
resolve: "opencode/claude-sonnet-5",
|
|
217
|
+
openRouterResolve: "openrouter/anthropic/claude-sonnet-5"
|
|
213
218
|
},
|
|
214
219
|
"claude-haiku": {
|
|
215
220
|
displayName: "Claude Haiku",
|
|
@@ -1240,6 +1245,26 @@ function stripExistingFooter(body) {
|
|
|
1240
1245
|
return body.substring(0, dividerIndex).trimEnd();
|
|
1241
1246
|
}
|
|
1242
1247
|
|
|
1248
|
+
// utils/checksGate.ts
|
|
1249
|
+
var OK_CONCLUSIONS = /* @__PURE__ */ new Set(["success", "neutral", "skipped"]);
|
|
1250
|
+
var PULLFROG_CHECKS = /* @__PURE__ */ new Set(["pullfrog", "pullfrog-approval"]);
|
|
1251
|
+
function checkRunsGate(params) {
|
|
1252
|
+
for (const run of params.checkRuns) {
|
|
1253
|
+
if (PULLFROG_CHECKS.has(run.name.toLowerCase())) continue;
|
|
1254
|
+
if (run.status !== "completed")
|
|
1255
|
+
return { ok: false, reason: `check "${run.name}" ${run.status}` };
|
|
1256
|
+
if (!OK_CONCLUSIONS.has(run.conclusion ?? "")) {
|
|
1257
|
+
return { ok: false, reason: `check "${run.name}" concluded ${run.conclusion}` };
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
return { ok: true, reason: "all checks passed" };
|
|
1261
|
+
}
|
|
1262
|
+
function hasVerifiedCheck(params) {
|
|
1263
|
+
return params.checkRuns.some(
|
|
1264
|
+
(r) => !PULLFROG_CHECKS.has(r.name.toLowerCase()) && r.status === "completed"
|
|
1265
|
+
);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1243
1268
|
// utils/codexOAuth.ts
|
|
1244
1269
|
var CODEX_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
1245
1270
|
var CODEX_OAUTH_TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
@@ -1467,6 +1492,7 @@ export {
|
|
|
1467
1492
|
PULLFROG_DIVIDER,
|
|
1468
1493
|
TIMEOUT_DISABLED,
|
|
1469
1494
|
buildPullfrogFooter,
|
|
1495
|
+
checkRunsGate,
|
|
1470
1496
|
createLeapingProgressComment,
|
|
1471
1497
|
decodeJwtExpMs,
|
|
1472
1498
|
defaultAutoTier,
|
|
@@ -1477,6 +1503,7 @@ export {
|
|
|
1477
1503
|
getModelProvider,
|
|
1478
1504
|
getProgressComment,
|
|
1479
1505
|
getProviderDisplayName,
|
|
1506
|
+
hasVerifiedCheck,
|
|
1480
1507
|
isAutoTier,
|
|
1481
1508
|
isCardGatedModel,
|
|
1482
1509
|
isLeapingIntoActionCommentBody,
|
package/dist/mcp/review.d.ts
CHANGED
|
@@ -23,6 +23,41 @@ 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
|
+
/**
|
|
41
|
+
* proactive approve-when-clean for the Fix-all / Fix-👍s flow (`fix_review`
|
|
42
|
+
* trigger). when such a run completes successfully and every Pullfrog-originated
|
|
43
|
+
* review thread it raised is resolved, post a NEW approving review summarizing
|
|
44
|
+
* the run's work.
|
|
45
|
+
*
|
|
46
|
+
* the verification step shares `countOutstandingPullfrogThreads` with the
|
|
47
|
+
* approval gate in create_pull_request_review: that gate REACTIVELY blocks a
|
|
48
|
+
* bad approval; this is the PROACTIVE approve side. approval is contingent on
|
|
49
|
+
* Pullfrog's own findings actually being addressed — never a blind post-fix
|
|
50
|
+
* approval. a Fix-👍s run that only resolved a subset naturally fails the
|
|
51
|
+
* outstanding-thread check and is left unapproved.
|
|
52
|
+
*
|
|
53
|
+
* respects `prApproveEnabled` (the repo's "Allow Pullfrog to approve PRs"
|
|
54
|
+
* opt-in): a repo that hasn't enabled binding bot approvals never gets one
|
|
55
|
+
* here. skips when the agent already rendered an explicit review verdict this
|
|
56
|
+
* run (`toolState.approval`), so an agent that found a real issue and submitted
|
|
57
|
+
* a non-approving review is never overridden. best-effort — a failure must not
|
|
58
|
+
* flip the run's outcome.
|
|
59
|
+
*/
|
|
60
|
+
export declare function approveAfterFix(ctx: ToolContext): Promise<void>;
|
|
26
61
|
export type ReviewCommentInput = NonNullable<RestEndpointMethodTypes["pulls"]["createReview"]["parameters"]["comments"]>[number];
|
|
27
62
|
export interface DroppedComment {
|
|
28
63
|
path: string;
|
|
@@ -155,6 +190,11 @@ export declare function CreatePullRequestReviewTool(ctx: ToolContext): import("f
|
|
|
155
190
|
}>> & {
|
|
156
191
|
mutates?: boolean;
|
|
157
192
|
};
|
|
193
|
+
type FooterOpts = {
|
|
194
|
+
body: string;
|
|
195
|
+
approved: boolean;
|
|
196
|
+
hasComments: boolean;
|
|
197
|
+
};
|
|
158
198
|
/**
|
|
159
199
|
* clear a pending review draft stranded on the PR by a prior hard-killed run
|
|
160
200
|
* (workflow timeout, OOM) so the next createReview can succeed.
|
|
@@ -196,6 +236,7 @@ export declare function clearStrandedPendingReview(ctx: ToolContext, params: {
|
|
|
196
236
|
* comments-only) until a body-path run happened to clear the draft.
|
|
197
237
|
*/
|
|
198
238
|
export declare function createReviewWithStrandedRecovery(ctx: ToolContext, params: RestEndpointMethodTypes["pulls"]["createReview"]["parameters"]): Promise<Awaited<ReturnType<typeof ctx.octokit.rest.pulls.createReview>>>;
|
|
239
|
+
export declare function createAndSubmitWithFooter(ctx: ToolContext, params: RestEndpointMethodTypes["pulls"]["createReview"]["parameters"], opts: FooterOpts): Promise<Awaited<ReturnType<typeof ctx.octokit.rest.pulls.submitReview>>>;
|
|
199
240
|
/**
|
|
200
241
|
* report the review node ID so the WorkflowRun is marked as "review submitted".
|
|
201
242
|
* exported for use in main.ts post-agent cleanup.
|
package/dist/mcp/server.d.ts
CHANGED
package/dist/toolState.d.ts
CHANGED
|
@@ -100,6 +100,10 @@ export interface ToolState {
|
|
|
100
100
|
nodeId: string;
|
|
101
101
|
reviewedSha: string | undefined;
|
|
102
102
|
};
|
|
103
|
+
approval?: {
|
|
104
|
+
wouldApprove: boolean;
|
|
105
|
+
sha: string | undefined;
|
|
106
|
+
};
|
|
103
107
|
reviewReplies?: Map<number, {
|
|
104
108
|
commentId: number;
|
|
105
109
|
url: string | undefined;
|
package/dist/utils/agent.d.ts
CHANGED
|
@@ -10,7 +10,9 @@ import type { Agent } from "../agents/index.ts";
|
|
|
10
10
|
* Bedrock model, change `BEDROCK_MODEL_ID`, not `PULLFROG_MODEL`.
|
|
11
11
|
* 2. slug from repo config / payload → alias registry. routing slugs
|
|
12
12
|
* (e.g. `bedrock/byok`) defer to a separate env var (`BEDROCK_MODEL_ID`).
|
|
13
|
-
*
|
|
13
|
+
* a non-curated value with a slash passes through unchanged as a raw
|
|
14
|
+
* models.dev specifier (same as `PULLFROG_MODEL`), not auto-selected away.
|
|
15
|
+
* 3. undefined (no slug, or a bare-word non-specifier) — agent auto-selects.
|
|
14
16
|
*/
|
|
15
17
|
export declare function resolveModel(ctx: {
|
|
16
18
|
slug?: string | undefined;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ToolContext } from "../mcp/server.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Run-end lifecycle action: merge ANY open PR that this run mechanically
|
|
4
|
+
* approved on its current head — Pullfrog acting as a full repo maintainer,
|
|
5
|
+
* contributor PRs included, not just merging its own work. There is deliberately
|
|
6
|
+
* NO agent-callable merge tool — the merge is a pure, deterministic consequence
|
|
7
|
+
* of the runtime observing a clean approved state, so a prompt-injected agent has
|
|
8
|
+
* no merge surface to reach for. Every clause is fail-closed and re-verified
|
|
9
|
+
* against GitHub's own state at merge time; any failure logs why and returns
|
|
10
|
+
* without merging. Best-effort — the caller wraps this in a `.catch`, and a
|
|
11
|
+
* merge/comment failure can never flip the run's outcome.
|
|
12
|
+
*
|
|
13
|
+
* There is NO self-authorship restriction: the approval verdict IS the trust
|
|
14
|
+
* decision (Pullfrog reviewed it and would approve), exactly like a human
|
|
15
|
+
* maintainer merging a contributor PR. The population of mergeable PRs is bounded
|
|
16
|
+
* upstream by the review triggers (`prCreated` / `prCreatedAllowNonCollaborator`)
|
|
17
|
+
* — Pullfrog can only merge what it was configured to review + approve — and the
|
|
18
|
+
* whole capability is opt-in per repo (clause 1). The remaining clauses are the
|
|
19
|
+
* maintainer's controls.
|
|
20
|
+
*
|
|
21
|
+
* The invariant (all must hold):
|
|
22
|
+
* 1. `ctx.autoMergeEnabled` — the per-repo toggle ANDed with the global
|
|
23
|
+
* `isAutonomousMaintenanceEnabled()` kill switch, server-side (run-context).
|
|
24
|
+
* 2. `ctx.prApproveEnabled` — cannot auto-merge what it may not approve.
|
|
25
|
+
* 3. this run recorded an APPROVE verdict (`toolState.approval.wouldApprove`) —
|
|
26
|
+
* Pullfrog's review is the merge decision.
|
|
27
|
+
* 4. the PR is open and not a draft.
|
|
28
|
+
* 5. `toolState.approval.sha === <current head sha>` — approved THIS head, so a
|
|
29
|
+
* commit pushed after the review can't ride the approval in (409-safe below).
|
|
30
|
+
* 6. `countOutstandingPullfrogThreads === 0` — re-queried at merge time.
|
|
31
|
+
* 7. no un-dismissed human CHANGES_REQUESTED — the human veto always wins.
|
|
32
|
+
* 8. GitHub reports the PR mergeable and not blocked/dirty/behind.
|
|
33
|
+
* 9. every external check-run/commit-status on the head is complete and
|
|
34
|
+
* non-failing (`checkRunsGate`) — red or in-flight CI never auto-merges,
|
|
35
|
+
* even on a repo without branch protection.
|
|
36
|
+
*/
|
|
37
|
+
export declare function autoMergeAfterApprove(ctx: ToolContext): Promise<void>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared, dependency-free CI green/red predicate — the single source of truth for
|
|
3
|
+
* "given these check-runs, is this PR head safe to merge?". Consumed by the
|
|
4
|
+
* auto-merge path: `autoMerge.ts` gates the PR head at merge time, and the
|
|
5
|
+
* `pr-merge-completion` webhook re-checks it before dispatching a slow-CI re-wake.
|
|
6
|
+
* A red or in-flight sha never merges, and a sha with no confirmable green
|
|
7
|
+
* check-run is treated as unverified (refused, not merged blind).
|
|
8
|
+
*
|
|
9
|
+
* Gates on the Checks API only. The Pullfrog App deliberately holds no `statuses`
|
|
10
|
+
* permission (requesting it would force re-approval on every install — see
|
|
11
|
+
* wiki/app-permissions.md), so the legacy Commit Statuses API is unreadable
|
|
12
|
+
* (`listCommitStatusesForRef` 403s). Modern CI (GitHub Actions, etc.) reports via
|
|
13
|
+
* check-runs; a repo relying on the legacy Statuses API is covered by branch
|
|
14
|
+
* protection instead — a failing required status makes `mergeable_state` `blocked`,
|
|
15
|
+
* which the merge invariant already refuses (`NON_MERGEABLE_STATES`).
|
|
16
|
+
*/
|
|
17
|
+
export type CheckRun = {
|
|
18
|
+
name: string;
|
|
19
|
+
status: string;
|
|
20
|
+
conclusion: string | null;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Refuse if any external check-run is incomplete (queued/in_progress) or concluded
|
|
24
|
+
* anything other than success/neutral/skipped. Fail-closed on unknown/absent
|
|
25
|
+
* conclusions. Pure so the pass/fail logic is inspectable directly.
|
|
26
|
+
*/
|
|
27
|
+
export declare function checkRunsGate(params: {
|
|
28
|
+
checkRuns: CheckRun[];
|
|
29
|
+
}): {
|
|
30
|
+
ok: boolean;
|
|
31
|
+
reason: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* true when the head has ≥1 CONCLUDED non-failing EXTERNAL check-run — CI
|
|
35
|
+
* positively ran, not merely "no red was seen". paired with `checkRunsGate.ok`
|
|
36
|
+
* (any completed check is non-failing), this == "≥1 concluded non-failing check".
|
|
37
|
+
* the merge requires it: a head with ZERO check-runs passes
|
|
38
|
+
* `checkRunsGate` (nothing red) but has no verification, and since the app can't
|
|
39
|
+
* read legacy commit statuses, "zero check-runs" is indistinguishable from "green
|
|
40
|
+
* Actions but a red CircleCI status" — so we only act on a head we can positively
|
|
41
|
+
* confirm is green. Pullfrog's own verdict checks don't count as external CI.
|
|
42
|
+
*/
|
|
43
|
+
export declare function hasVerifiedCheck(params: {
|
|
44
|
+
checkRuns: CheckRun[];
|
|
45
|
+
}): boolean;
|
|
@@ -8,3 +8,12 @@
|
|
|
8
8
|
* substrings on `error.message` (e.g. provider-specific framings).
|
|
9
9
|
*/
|
|
10
10
|
export declare function isTransientNetworkError(error: unknown, extraPatterns?: string[]): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* broader transient predicate for octokit calls: an HTTP 5xx (GitHub upstream
|
|
13
|
+
* blip) OR a network-class error. distinct from `isTransientNetworkError` — an
|
|
14
|
+
* HTTP 4xx (404/401/403/422) is deterministic and fails fast. the action
|
|
15
|
+
* octokit carries only the throttling plugin + a 401 re-mint (no 5xx retry),
|
|
16
|
+
* so callers on critical paths retry idempotent GitHub reads/dispatches with
|
|
17
|
+
* this. mirrors the server's `isTransientUpstreamError`. see #999.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isTransientOctokitError(error: unknown): boolean;
|
package/dist/utils/payload.d.ts
CHANGED
|
@@ -25,17 +25,25 @@ export declare const JsonPayload: import("arktype/internal/variants/object.ts").
|
|
|
25
25
|
generateSummary?: boolean | undefined;
|
|
26
26
|
}, {}>;
|
|
27
27
|
export declare const Inputs: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
28
|
-
prompt
|
|
28
|
+
prompt?: string | undefined;
|
|
29
|
+
prompt_file?: string | undefined;
|
|
29
30
|
model?: string | undefined;
|
|
30
31
|
timeout?: string | undefined;
|
|
31
32
|
push?: "disabled" | "enabled" | "restricted" | undefined;
|
|
32
33
|
shell?: "disabled" | "enabled" | "restricted" | undefined;
|
|
34
|
+
status_checks?: "disabled" | "enabled" | undefined;
|
|
33
35
|
cwd?: string | undefined;
|
|
34
36
|
output_schema?: string | undefined;
|
|
35
37
|
}, {}>;
|
|
36
38
|
export type Inputs = typeof Inputs.infer;
|
|
37
39
|
export type ResolvedPromptInput = string | typeof JsonPayload.infer;
|
|
38
40
|
export declare function resolvePromptInput(): ResolvedPromptInput;
|
|
41
|
+
/**
|
|
42
|
+
* true when `actor` is Pullfrog's own GitHub identity — the bot login
|
|
43
|
+
* (`pullfrog[bot]` / `pullfrogdev[bot]`) or the bare account. used to skip
|
|
44
|
+
* self-triggered events and to recognize Pullfrog-originated review threads.
|
|
45
|
+
*/
|
|
46
|
+
export declare const isPullfrog: (actor: string | null | undefined) => boolean;
|
|
39
47
|
export declare function resolvePayload(resolvedPromptInput: ResolvedPromptInput, repoSettings: RepoSettings): {
|
|
40
48
|
"~pullfrog": true;
|
|
41
49
|
version: string;
|
|
@@ -62,6 +70,7 @@ export declare function resolvePayload(resolvedPromptInput: ResolvedPromptInput,
|
|
|
62
70
|
generateSummary: boolean | undefined;
|
|
63
71
|
push: import("../external.ts").PushPermission;
|
|
64
72
|
shell: import("../external.ts").ShellPermission;
|
|
73
|
+
statusChecks: boolean;
|
|
65
74
|
proxyModel: string | undefined;
|
|
66
75
|
};
|
|
67
76
|
export type ResolvedPayload = ReturnType<typeof resolvePayload>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ToolContext } from "../mcp/server.ts";
|
|
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.
|
|
7
|
+
*
|
|
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.
|
|
12
|
+
*
|
|
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.
|
|
22
|
+
*/
|
|
23
|
+
export declare function reportStatusChecks(ctx: ToolContext, params: {
|
|
24
|
+
runSucceeded: boolean;
|
|
25
|
+
}): Promise<void>;
|
package/dist/yes/index.d.ts
CHANGED
|
@@ -4,6 +4,21 @@ export declare function range(n: number): number[];
|
|
|
4
4
|
export declare function range(start: number, end: number): number[];
|
|
5
5
|
export declare function range(n: number, fn: (i: number) => number): number[];
|
|
6
6
|
export declare function schedule(fn: (i: number) => number, count: number): number[];
|
|
7
|
+
/**
|
|
8
|
+
* read a retry delay (ms) from a thrown HTTP error's response headers, honoring
|
|
9
|
+
* GitHub's rate-limit backoff hints on octokit errors (`error.response.headers`):
|
|
10
|
+
* - `retry-after` (RFC 9110 delta-seconds or an HTTP-date) — an explicit
|
|
11
|
+
* backoff directive whenever present (covers secondary rate limits, which
|
|
12
|
+
* carry it with `x-ratelimit-remaining > 0`).
|
|
13
|
+
* - else `x-ratelimit-reset` (epoch seconds) but ONLY when
|
|
14
|
+
* `x-ratelimit-remaining` is `"0"` — GitHub sends the reset header on ~every
|
|
15
|
+
* response (~1h ahead), so it's a backoff hint only under actual primary
|
|
16
|
+
* rate-limit exhaustion (matching octokit's throttling plugin). without this
|
|
17
|
+
* gate a non-rate-limit retried error (e.g. a permissions 403) would be
|
|
18
|
+
* wrongly delayed to the reset time.
|
|
19
|
+
* returns `undefined` when no applicable hint is present/parseable.
|
|
20
|
+
*/
|
|
21
|
+
export declare function retryAfterMs(error: unknown): number | undefined;
|
|
7
22
|
type InferInput<S> = S extends StandardSchemaV1<infer I, any> ? I : never;
|
|
8
23
|
type InferOutput<S> = S extends StandardSchemaV1<any, infer O> ? O : never;
|
|
9
24
|
export interface OpOptions<TReturn = any> {
|
|
@@ -11,6 +26,17 @@ export interface OpOptions<TReturn = any> {
|
|
|
11
26
|
ttl?: number;
|
|
12
27
|
maxItems?: number;
|
|
13
28
|
retries?: number[];
|
|
29
|
+
/**
|
|
30
|
+
* honor a server backoff hint on retries: when a retried error carries a
|
|
31
|
+
* `retry-after` / `x-ratelimit-reset` header, schedule the next retry from
|
|
32
|
+
* that value (clamped to `[fixed delay, retryAfterCap]`) instead of the fixed
|
|
33
|
+
* `retries[attempt]` backoff. `true` uses the built-in `retryAfterMs` reader;
|
|
34
|
+
* a function extracts a delay-ms from a custom error shape. errors without the
|
|
35
|
+
* header fall back to the fixed backoff.
|
|
36
|
+
*/
|
|
37
|
+
retryAfter?: boolean | ((error: unknown) => number | undefined);
|
|
38
|
+
/** cap (ms) on a honored retry-after hint. default 20000. */
|
|
39
|
+
retryAfterCap?: number;
|
|
14
40
|
cacheHit?: ((key: string) => void) | null;
|
|
15
41
|
cacheMiss?: ((key: string) => void) | null;
|
|
16
42
|
skipCache?: (result: TReturn) => boolean;
|
package/dist/yes.js
CHANGED
|
@@ -1022,6 +1022,34 @@ function range(a, b) {
|
|
|
1022
1022
|
function schedule(fn, count) {
|
|
1023
1023
|
return Array.from({ length: count }, (_, i) => fn(i));
|
|
1024
1024
|
}
|
|
1025
|
+
var DEFAULT_RETRY_AFTER_CAP_MS = 2e4;
|
|
1026
|
+
function getErrorResponseHeaders(error) {
|
|
1027
|
+
if (error === null || typeof error !== "object") return void 0;
|
|
1028
|
+
const response = error.response;
|
|
1029
|
+
if (response === null || typeof response !== "object") return void 0;
|
|
1030
|
+
const headers = response.headers;
|
|
1031
|
+
if (headers === null || typeof headers !== "object") return void 0;
|
|
1032
|
+
return headers;
|
|
1033
|
+
}
|
|
1034
|
+
function retryAfterMs(error) {
|
|
1035
|
+
const headers = getErrorResponseHeaders(error);
|
|
1036
|
+
if (!headers) return void 0;
|
|
1037
|
+
const retryAfter = headers["retry-after"];
|
|
1038
|
+
if (typeof retryAfter === "string" && retryAfter.trim() !== "") {
|
|
1039
|
+
const seconds = Number(retryAfter);
|
|
1040
|
+
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
|
|
1041
|
+
const dateMs = Date.parse(retryAfter);
|
|
1042
|
+
if (!Number.isNaN(dateMs)) return Math.max(0, dateMs - Date.now());
|
|
1043
|
+
}
|
|
1044
|
+
if (headers["x-ratelimit-remaining"] === "0") {
|
|
1045
|
+
const reset = headers["x-ratelimit-reset"];
|
|
1046
|
+
if (typeof reset === "string" && reset.trim() !== "") {
|
|
1047
|
+
const resetEpoch = Number(reset);
|
|
1048
|
+
if (Number.isFinite(resetEpoch)) return Math.max(0, resetEpoch * 1e3 - Date.now());
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
return void 0;
|
|
1052
|
+
}
|
|
1025
1053
|
function validateSchema(schema, value) {
|
|
1026
1054
|
const result = schema["~standard"].validate(value);
|
|
1027
1055
|
if (result instanceof Promise) {
|
|
@@ -1159,7 +1187,15 @@ function _op(fn, options) {
|
|
|
1159
1187
|
log.info({ key, error, attempt });
|
|
1160
1188
|
}
|
|
1161
1189
|
} else {
|
|
1162
|
-
|
|
1190
|
+
let delay = retries[attempt];
|
|
1191
|
+
if (options.retryAfter) {
|
|
1192
|
+
const extract = options.retryAfter === true ? retryAfterMs : options.retryAfter;
|
|
1193
|
+
const hint = extract(error);
|
|
1194
|
+
if (hint !== void 0) {
|
|
1195
|
+
const cap = options.retryAfterCap ?? DEFAULT_RETRY_AFTER_CAP_MS;
|
|
1196
|
+
delay = Math.max(delay, Math.min(cap, hint));
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1163
1199
|
if (namePrefix) {
|
|
1164
1200
|
log.info(
|
|
1165
1201
|
`${namePrefix}attempt ${attempt + 1}/${retries.length + 1} failed, retrying in ${delay}ms`
|
|
@@ -1240,5 +1276,6 @@ function _op(fn, options) {
|
|
|
1240
1276
|
export {
|
|
1241
1277
|
op,
|
|
1242
1278
|
range,
|
|
1279
|
+
retryAfterMs,
|
|
1243
1280
|
schedule
|
|
1244
1281
|
};
|