pullfrog 0.1.8 → 0.1.10
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/dist/agents/opencodeShared.d.ts +40 -0
- package/dist/agents/postRun.d.ts +11 -3
- package/dist/agents/shared.d.ts +7 -0
- package/dist/cli.mjs +4275 -3256
- package/dist/external.d.ts +1 -1
- package/dist/index.js +1706 -1219
- package/dist/internal/index.d.ts +2 -1
- package/dist/internal.js +245 -85
- package/dist/models.d.ts +10 -0
- package/dist/modes.d.ts +1 -1
- package/dist/toolState.d.ts +4 -0
- package/dist/utils/activity.d.ts +31 -1
- package/dist/utils/apiKeys.d.ts +5 -1
- package/dist/utils/billingErrors.d.ts +85 -0
- package/dist/utils/buildPullfrogFooter.d.ts +7 -0
- package/dist/utils/byokFallback.d.ts +50 -0
- package/dist/utils/codexHome.d.ts +23 -0
- package/dist/utils/errorReport.d.ts +9 -0
- package/dist/utils/learnings.d.ts +20 -0
- package/dist/utils/learningsTruncate.d.ts +25 -0
- package/dist/utils/lifecycle.d.ts +23 -3
- package/dist/utils/overrides.d.ts +40 -0
- package/dist/utils/payload.d.ts +7 -0
- package/dist/utils/prSummary.d.ts +21 -0
- package/dist/utils/proxy.d.ts +47 -0
- package/dist/utils/runContext.d.ts +0 -9
- package/dist/utils/runErrorRenderer.d.ts +41 -0
- package/dist/utils/runLifecycle.d.ts +75 -0
- package/dist/utils/runStartupLog.d.ts +15 -0
- package/dist/utils/subprocess.d.ts +1 -0
- package/package.json +3 -2
- /package/dist/agents/{opencode.d.ts → opencode_v2.d.ts} +0 -0
package/dist/models.d.ts
CHANGED
|
@@ -68,6 +68,11 @@ interface ModelDef {
|
|
|
68
68
|
export interface ProviderConfig {
|
|
69
69
|
displayName: string;
|
|
70
70
|
envVars: readonly string[];
|
|
71
|
+
/** credentials authored only via `pullfrog auth <provider>` — never
|
|
72
|
+
* user-facing in `init`, never documented as a manual GHA secret. counted
|
|
73
|
+
* for hasAnyKey / log-redaction purposes but excluded from any prompt /
|
|
74
|
+
* paste flow. CLI-managed magic. see wiki/codex-auth.md. */
|
|
75
|
+
managedCredentials?: readonly string[];
|
|
71
76
|
models: Record<string, ModelDef>;
|
|
72
77
|
}
|
|
73
78
|
export declare const providers: {
|
|
@@ -89,6 +94,11 @@ export declare function parseModel(slug: string): {
|
|
|
89
94
|
export declare function getModelProvider(slug: string): string;
|
|
90
95
|
export declare function getProviderDisplayName(slug: string): string | undefined;
|
|
91
96
|
export declare function getModelEnvVars(slug: string): string[];
|
|
97
|
+
/** managed credentials are authored only via `pullfrog auth <provider>` — they
|
|
98
|
+
* count as "configured" for hasAnyKey-style UI checks but are never offered as
|
|
99
|
+
* a manual-paste option in `init` or the AgentSettings env-var button row.
|
|
100
|
+
* see `provider.managedCredentials` and wiki/codex-auth.md. */
|
|
101
|
+
export declare function getModelManagedCredentials(slug: string): string[];
|
|
92
102
|
export declare const modelAliases: ModelAlias[];
|
|
93
103
|
/** resolve a model slug to its concrete models.dev specifier (e.g. "anthropic/claude-opus-4-6") */
|
|
94
104
|
export declare function resolveModelSlug(slug: string): string | undefined;
|
package/dist/modes.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export interface Mode {
|
|
|
4
4
|
description: string;
|
|
5
5
|
prompt?: string | undefined;
|
|
6
6
|
}
|
|
7
|
-
export declare const PR_SUMMARY_FORMAT = "### Default format\n\nFollow this structure exactly:\n\n<b>TL;DR</b> \u2014 1-3 sentences on what the PR does and why. Focus on intent, not mechanics.\nNOTE: use HTML bold <b>TL;DR</b>, NOT markdown bold **TL;DR**.\n\n### Key changes\n\n- **Short human-readable title** \u2014 1 sentence per change. Write a short prose phrase (title case or sentence case); when you name a file, type, or function, put that name in backticks (e.g. **Add `TodoTracker` for live checklists**). A reviewer should understand the full PR from this list alone.\n\n<sub><b>Summary</b> \uFF5C {file_count} files \uFF5C {commit_count} commits \uFF5C base: `{base}` \u2190 `{head}`</sub>\nNOTE: the metadata line goes AFTER the bullet list, not before it.\n\nThen for each key change, a ## section with a short descriptive title that reads like a documentation heading (e.g. ## Live todo checklist tracking).\n\n<br/>\n\n## Example readable section title\n\n> **Before:** [old behavior/state]<br/>**After:** [new behavior/state]\nIMPORTANT: Before and After MUST be on a SINGLE blockquote line with an inline <br/> between them. Two separate `>` lines creates a double line break.\n\n1-2 sentences of explanation. Break up text with tables, blockquotes, or lists \u2014 NEVER 3+ plain paragraphs in a row.\n\nIf a change warrants deeper explanation, use a blockquoted details/summary framed as a question:\n> <details><summary>How does X work?</summary>\n> Extended explanation here.\n> </details>\n\nEnd each section with a file links trail (3-4 key files max):\n[`file.ts`](https://github.com/{owner}/{repo}/pull/{number}/files#diff-{sha256hex_of_filepath}) \u00B7 ...\n\nSingle-feature PRs: skip the ## sections. Fold before/after and explanation into the header after key changes.\n\nCRITICAL \u2014 GitHub markdown rendering rule:\nGitHub's markdown parser requires a blank line between ALL block-level elements. This includes transitions between: HTML tags (<br/>, <sub>, <details>, <b>, etc.) and markdown syntax (headings, lists, blockquotes, paragraphs). Without a blank line, GitHub treats the following content as a continuation of the HTML block and renders markdown syntax as literal text. ALWAYS separate block-level elements with a blank line.\n\nRules:\n- `##` titles and key-change bullet lead-ins are plain-language summaries; backtick only actual code tokens (files, types, functions) where they appear in the title\n- ALL variable names, identifiers, and file names in body text must be in backticks\n- ALL file references MUST link to the PR Files Changed view. Use the `diff-<hex>` anchor precomputed next to each filename in the `checkout_pr` TOC \u2014 do NOT run `sha256sum` or any other shell command to compute anchors. NEVER fabricate hex strings. If a file is not in the TOC, omit the `#diff-` anchor rather than guessing.\n- Add <br/> before each ## heading for visual spacing. Do NOT use horizontal rules (---)\n- Do NOT include raw diff stats like '+123 / -45' or line counts\n- Do NOT include code blocks or repeat diff contents\n- Do NOT include a changelog section \u2014 the key changes list serves this purpose\n- Focus on *intent*, not *what* \u2014 the diff already shows what changed\n- Get the file count and commit count from the checkout_pr metadata, not by counting manually";
|
|
7
|
+
export declare const PR_SUMMARY_FORMAT = "### Default format\n\nThe body has at most three parts in this exact order:\n\n1. **Reviewed changes preamble** \u2014 one bolded inline lead-in describing what was reviewed in this run, a bullet list of the substantive changes, and an HTML comment carrying review metadata for downstream agents.\n2. **Cross-cutting issue sections** (zero or more) \u2014 one `### ` heading per concern, with a human-readable problem write-up and a collapsed `<details>Technical details</details>` block underneath.\n3. **`### \u2139\uFE0F Nitpicks`** at the very bottom (only if there are nits worth surfacing in the body) \u2014 a flat bullet list, no technical-details block.\n\nInline-vs-body split: concerns that anchor to a specific line go inline (use the `comments` parameter). Body `### ` sections are reserved for concerns that **have no line to anchor to** \u2014 typically because the concern is about *absence* (something the diff should have done but didn't), *sequencing* (rollout / deletion / migration order), *design decisions only the human can make*, or *scope questions the diff implicitly raises but doesn't address*. A concern that anchors to a line but has broad implications still goes inline (use the technical-details block there to capture the implications \u2014 see Inline technical details below). If you found no non-anchorable concerns, the body has zero `### ` issue sections \u2014 just the preamble + metadata.\n\n## 1. Reviewed changes preamble\n\nOpen with a single bolded inline lead-in followed immediately by the bullet list (no `### Key changes` heading, no `<b>TL;DR</b>`):\n\n```\n**Reviewed changes** \u2014 one sentence on what was reviewed in this run. For Review (initial), this is what the PR does and why. For IncrementalReview, this is what changed since the prior pullfrog review. Focus on intent, not mechanics.\n\n- **Short human-readable title** \u2014 1 sentence per substantive change. Write a short prose phrase; when you name a file, type, or function, put that name in backticks (e.g. **Add \\`TodoTracker\\` for live checklists**). A reviewer should understand the full reviewed scope from this list alone \u2014 this IS the dispassionate \"what was reviewed and what changed\" overview, so cover the substantive changes, not just the loudest ones.\n\n<!--\nPullfrog review metadata \u2014 for any agent (or human-with-agent) reading this\nreview. Incorporate the fields below into your understanding of the context\nthis review was made in. The findings below were written against\n{head_sha_short}; if new commits have landed on {head_ref} since this review\nwas submitted, treat any specific bug, file, or line callout as POTENTIALLY\nSTALE \u2014 re-diff against {head_sha_short} (or trigger a fresh review) and\nfactor commits past {head_sha_short} into your understanding of the current\nstate before acting on findings.\n\n- Mode: Review (initial) or IncrementalReview (delta against prior pullfrog review)\n- Files reviewed: {file_count}\n- Commits reviewed: {commit_count}\n- Base: {base_ref} ({base_sha_short})\n- Head: {head_ref} ({head_sha_short})\n- Reviewed commits:\n - {sha_short} \u2014 {commit_subject}\n - ...\n- Prior pullfrog review: none or {prior_sha_short} ({prior_review_html_url})\n- Submitted at: {iso_timestamp}\n-->\n```\n\nPull every metadata field from the `checkout_pr` tool's response \u2014 file count, commit count, base/head ref + SHA, the commit list. For `IncrementalReview` runs, populate `Prior pullfrog review` with the prior review's commit_id (short SHA) and `html_url` from `list_pull_request_reviews`.\n\n## 2. Cross-cutting issue sections (zero or more)\n\nFor each cross-cutting concern, one `### ` section. Use this exact shape:\n\n```\n### {emoji} {short, descriptive title \u2014 what's wrong, not what to do}\n\n{Human-readable problem write-up. Describes the PROBLEM only \u2014 what's broken, what the symptom is, what the blast radius is. NO asks, NO suggested fixes, NO \"the right thing to do is...\". Asks and fixes live in the technical-details block below; the visible part is for the human to *understand* the problem, not to implement it.}\n\n<details><summary>Technical details</summary>\n\n\\`\\`\\`\\`markdown\n# {title repeated}\n\n## Affected sites\n- {file path:line} \u2014 {what's wrong there}\n- ...\n\n## Required outcome\n- {what the fix needs to achieve, not how to achieve it}\n- ...\n\n## Suggested approach (optional)\n{When the fix shape is non-obvious, sketch one or more reasonable directions. Skip when the outcome alone makes the fix obvious.}\n\n## Open questions for the human (optional)\n- {Any decision an implementing agent shouldn't make unilaterally \u2014 pricing thresholds, breaking-change policy, naming, scope of follow-up.}\n\\`\\`\\`\\`\n\n</details>\n```\n\nConcrete example of the visible part of a non-anchored section (technical-details block unchanged from the template above):\n\n```\n### \u2139\uFE0F Legacy `opencode.ts` has no documented deletion plan\n\nThe v2 harness lands alongside the v1 file and imports one helper from it. Worth a follow-up issue or a TODO so the next maintainer doesn't have to re-derive the cleanup plan.\n```\n\nThe example's value is its *shape*: a finding about absence (no deletion plan), not a line-anchored bug. Body sections live or die on whether the concern genuinely doesn't fit on a line.\n\n**Heading severity emoji** \u2014 every `### ` heading carries one:\n\n- \uD83D\uDEA8 critical \u2014 blocks merge (data loss, security, broken core flow)\n- \u26A0\uFE0F important \u2014 must address before merging (regression, missing validation, incorrect behavior)\n- \u2139\uFE0F informational \u2014 surfaced for awareness; mergeable as-is\n\n**Visible problem write-up rules:**\n\n- **No asks, no suggested fixes** in the visible part. The visible portion describes the problem; the technical-details block describes the fix shape and any open questions. The exception: a fix so self-evident that NOT stating it would be weird (e.g. \"the typo is missing an 'r'\") \u2014 in that case, fold it into the problem statement and skip the suggested-approach block in technical details too.\n- **Never two successive plain paragraphs.** Every transition between block-level elements must alternate prose with structure: paragraph \u2192 bullet list \u2192 paragraph; paragraph \u2192 code fence \u2192 bullet list; paragraph \u2192 table \u2192 paragraph. Two consecutive paragraphs in a row create a wall of text that's impossible to digest. If you catch yourself writing one, find a way to split it: pull a list out of it, drop a 2-3 line code fence between them, or merge them into a single tighter paragraph.\n- **Per-paragraph budget:** ~3 sentences max. Past that, you're explaining where you should be structuring.\n- **Identifier discipline still applies** in the visible part. Lead with behavior in plain English; name an identifier only when it's the subject of the concern or a public surface a reader would recognize. The technical-details block is where dense identifier references belong.\n\n**Technical-details block rules:**\n\n- Wrapped in a 4-backtick markdown fence (`\\`\\`\\`\\`markdown ... \\`\\`\\`\\``) so it's visually distinct, one-click copyable, and can contain its own 3-backtick code fences without escape gymnastics. The contents are agent-readable \u2014 a fix-agent will pull the body down and use this block as the brief.\n- File paths and `file:line` refs are encouraged (and necessary) \u2014 the next agent uses these to navigate. Identifier density is fine here.\n- Slightly more verbose than the absolute minimum is OK when it materially helps the next agent: a small code snippet showing the symptom, a short table of mismatched key/column pairs, a one-paragraph \"why CI doesn't catch it\" note. Skip massive regression-test scaffolding or full route rewrites \u2014 the implementing agent writes those.\n- Use the four standard sections (`Affected sites`, `Required outcome`, optional `Suggested approach`, optional `Open questions for the human`). Skip the optional sections when they wouldn't add anything.\n\n## Inline technical details\n\nInline comments are short (~2-3 sentences) by default. When an inline finding has broader implications worth recording for a fix-agent \u2014 e.g. a localized bug whose proper fix requires touching several files, or where the right fix depends on a design decision the human needs to make \u2014 append a collapsed `<details><summary>Technical details</summary>` block to the inline comment's body. Same shape as the body-section technical-details block (4-backtick fenced markdown, `## Affected sites` / `## Required outcome` / optional `## Suggested approach` / optional `## Open questions for the human`).\n\nGitHub renders the same markdown parser in inline comments as in the review body, so the collapsed-details affordance works the same way. The visible part of the inline comment stays scannable; the depth is one click away for any agent that needs it.\n\n## 3. `### \u2139\uFE0F Nitpicks` (optional, last section)\n\nOnly when there are nits that for some reason can't be inlined. Filepaths in nit text are fine \u2014 these are simple enough that a human or agent reads once and acts. No technical-details block.\n\n```\n### \u2139\uFE0F Nitpicks\n\n- {nit, with file path inline if useful, \u2264 ~200 chars}\n- ...\n```\n\n## Inline comment shape\n\nInline comments use the same severity framing as body `### ` sections, scaled down for line-anchored use:\n\n- **Lead with a 1-2 sentence problem statement.** The reader is looking at the line in question, so don't restate what the line says \u2014 describe what's wrong with it. Optionally prefix the visible line with a severity emoji (\uD83D\uDEA8 / \u26A0\uFE0F / \u2139\uFE0F) when severity isn't obvious from context.\n- **Optional `<details><summary>Technical details</summary>...</details>` collapsible** for findings whose technical context (longer file:line references, related-code snippets, suggested approach, regression-risk notes) would overwhelm the human-readable lead-in. Same agent-readable purpose, same 4-backtick fence shape, and same 4-section structure as the body's technical-details block \u2014 see *Inline technical details* above. Encouraged whenever the depth helps a downstream fix-agent; don't force one when the inline lead-in already says everything.\n- **Visible portion \u2264 2-3 sentences.** If you find yourself writing more, that's the cue to split the depth into the `Technical details` collapsible.\n\n## Body-wide rules\n\n- **Inline-vs-body discipline (repeated for emphasis):** anything that anchors to a specific line goes inline (with a `<details>Technical details</details>` block when the implications are broad). The body is for non-anchorable concerns only \u2014 absence, sequencing, design decisions, scope questions, architectural risk.\n- **No `### Issues found` heading** above the issue sections \u2014 each `### ` heading IS the issue.\n- **Severity emoji on every `### ` heading** (\uD83D\uDEA8 / \u26A0\uFE0F / \u2139\uFE0F). No emoji on the preamble lead-in or anywhere else.\n- **GitHub block-level rendering**: GitHub's markdown parser requires a blank line between ALL block-level elements (HTML tags like `<br/>`, `<sub>`, `<details>`, `<b>` and markdown syntax like headings, lists, blockquotes, code fences, paragraphs). Without a blank line, GitHub treats following content as a continuation of the HTML block and renders markdown syntax as literal text. ALWAYS separate block-level elements with a blank line.\n- **Backtick-wrap** every variable, identifier, or file name when you mention one (in either visible or technical-details portions).\n- **Don't repeat diff content**, don't include raw `+123 / -45` stats, don't include a changelog section, don't use horizontal rules (`---`).\n- **Pull file/commit counts from `checkout_pr` metadata** \u2014 never count manually.\n- **Legacy headings REMOVED.** Do not use `### Key changes`, `### Issues found`, `<b>TL;DR</b>`, or `<sub><b>Summary</b>`. The new structure subsumes them.";
|
|
8
8
|
export declare function computeModes(agentId: AgentId): Mode[];
|
|
9
9
|
export declare const modes: Mode[];
|
|
10
10
|
/**
|
package/dist/toolState.d.ts
CHANGED
|
@@ -64,6 +64,7 @@ export interface ToolState {
|
|
|
64
64
|
commentableLinesCheckoutSha?: string | undefined;
|
|
65
65
|
beforeSha?: string;
|
|
66
66
|
selectedMode?: string;
|
|
67
|
+
prepushFailureCount: number;
|
|
67
68
|
backgroundProcesses: Map<string, BackgroundProcess>;
|
|
68
69
|
browserDaemon?: BrowserDaemon | undefined;
|
|
69
70
|
review?: {
|
|
@@ -97,6 +98,9 @@ export interface ToolState {
|
|
|
97
98
|
output?: string;
|
|
98
99
|
usageEntries: AgentUsage[];
|
|
99
100
|
model?: string | undefined;
|
|
101
|
+
modelFallback?: {
|
|
102
|
+
from: string;
|
|
103
|
+
} | undefined;
|
|
100
104
|
todoTracker?: TodoTracker | undefined;
|
|
101
105
|
diffCoverage?: DiffCoverageState | undefined;
|
|
102
106
|
agentDiagnostic?: AgentDiagnostic | undefined;
|
package/dist/utils/activity.d.ts
CHANGED
|
@@ -12,14 +12,44 @@ export type ActivityTimeout = {
|
|
|
12
12
|
/** force the timeout to reject immediately with a custom reason */
|
|
13
13
|
forceReject: (reason: string) => void;
|
|
14
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* upper bound on how long a single tool call can suspend the activity
|
|
17
|
+
* watchdog. matched against the typical worst-case `checkout_pr`
|
|
18
|
+
* fetch+deepen on a large monorepo (issue #760: 4-5min) plus generous
|
|
19
|
+
* headroom for slower MCP tools, while still bounding the worst case if
|
|
20
|
+
* a tool genuinely hangs and `tool_result` never arrives — auto-resume
|
|
21
|
+
* fires here and the normal idle clock takes over from a fresh baseline.
|
|
22
|
+
*/
|
|
23
|
+
export declare const MAX_TOOL_CALL_SUSPENSION_MS: number;
|
|
15
24
|
/**
|
|
16
25
|
* mark activity to reset the no-output timeout.
|
|
17
26
|
* call this whenever the agent emits any event, even if it isn't logged to stdout.
|
|
18
27
|
*/
|
|
19
28
|
export declare function markActivity(): void;
|
|
20
29
|
/**
|
|
21
|
-
* get the time since last activity in milliseconds
|
|
30
|
+
* get the time since last activity in milliseconds.
|
|
31
|
+
* returns 0 while the watchdog is suspended (issue #760).
|
|
22
32
|
*/
|
|
23
33
|
export declare function getIdleMs(): number;
|
|
34
|
+
/**
|
|
35
|
+
* suspend the activity watchdog while a long-running, in-flight unit of
|
|
36
|
+
* work is happening (e.g. an MCP `tools/call` that synchronously awaits
|
|
37
|
+
* a multi-minute git fetch). bracket calls with `resumeActivity()` from
|
|
38
|
+
* the agent harness's `tool_use` / `tool_result` event handlers.
|
|
39
|
+
*
|
|
40
|
+
* - idempotent: nested suspends are no-ops; the first resume wins.
|
|
41
|
+
* - bounded: auto-resumes after `maxMs` so a buggy tool that never
|
|
42
|
+
* produces a `tool_result` can't pin the watchdog open forever.
|
|
43
|
+
* - safe: only the *agent harness* (claude.ts / opencode.ts) on explicit,
|
|
44
|
+
* paired CLI events should call this. NEVER blanket-suspend on internal
|
|
45
|
+
* noise — that would resurrect issue #12 zombie runs.
|
|
46
|
+
*/
|
|
47
|
+
export declare function suspendActivity(maxMs?: number): void;
|
|
48
|
+
/**
|
|
49
|
+
* resume the activity watchdog. resets the idle baseline so a stale
|
|
50
|
+
* idle window before the suspend can't immediately re-fire.
|
|
51
|
+
*/
|
|
52
|
+
export declare function resumeActivity(): void;
|
|
53
|
+
export declare function isActivitySuspended(): boolean;
|
|
24
54
|
export declare function createProcessOutputActivityTimeout(ctx: ActivityTimeoutContext): ActivityTimeout;
|
|
25
55
|
export {};
|
package/dist/utils/apiKeys.d.ts
CHANGED
|
@@ -10,10 +10,14 @@ export declare function validateAgentApiKey(params: {
|
|
|
10
10
|
}): void;
|
|
11
11
|
/**
|
|
12
12
|
* Detect agent-runtime auth failures that should be reformatted as an actionable
|
|
13
|
-
* key-fix CTA before being shown to the user. Covers the
|
|
13
|
+
* key-fix CTA before being shown to the user. Covers the shapes we see:
|
|
14
14
|
* - missing key (validateAgentApiKey throw): contains MISSING_KEY_MARKER
|
|
15
15
|
* - revoked / invalid key (Claude CLI 401 surfaced via api_error_status):
|
|
16
16
|
* "Invalid API key · Fix external API key" + similar provider variants
|
|
17
|
+
* - direct-Anthropic 401 (`Failed to authenticate. API Error: 401 ...
|
|
18
|
+
* {"type":"error","error":{"type":"authentication_error", ...
|
|
19
|
+
* "Invalid bearer token"}}`) emitted by the Claude CLI for revoked /
|
|
20
|
+
* mistyped / rotated `ANTHROPIC_API_KEY`. see #782.
|
|
17
21
|
*/
|
|
18
22
|
export declare function isApiKeyAuthError(text: string): boolean;
|
|
19
23
|
/**
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Billing-error classification + user-facing copy for `/api/proxy-token`
|
|
3
|
+
* failures and OpenRouter mid-run exhaustion. Two error classes (Billing vs.
|
|
4
|
+
* Transient) keep the framing honest: a card decline is *not* the same UX as
|
|
5
|
+
* a 503 from the proxy service. Both originate in `utils/proxy.ts` (mint
|
|
6
|
+
* failures) and `utils/runErrorRenderer.ts` (mid-run keylimit reclassify).
|
|
7
|
+
*
|
|
8
|
+
* Renderers return markdown bodies that are written into both the GitHub
|
|
9
|
+
* Actions job summary and the PR progress comment.
|
|
10
|
+
*
|
|
11
|
+
* Lives outside `main.ts` so adding a new error `code` branch is a one-file
|
|
12
|
+
* edit that does not retrigger the full LLM CI matrix (`action/main.ts` is
|
|
13
|
+
* in `action/test/coverage.ts::ALWAYS_RUN_ALL`).
|
|
14
|
+
*/
|
|
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
|
|
18
|
+
* summary + PR comment can use affirmative "you need to do X" copy rather than
|
|
19
|
+
* the ambiguous "billing error" label that makes transient outages look like
|
|
20
|
+
* the user's fault.
|
|
21
|
+
*
|
|
22
|
+
* `code` is a server-side discriminator: `router_requires_card` (no card + no
|
|
23
|
+
* wallet balance on Router), or null for unclassified. `declineCode` is
|
|
24
|
+
* Stripe's more specific sub-reason on `card_declined` (e.g.
|
|
25
|
+
* `insufficient_funds`, `lost_card`). `needsReauthentication` is the 3DS case
|
|
26
|
+
* broken out for convenience.
|
|
27
|
+
*/
|
|
28
|
+
export declare class BillingError extends Error {
|
|
29
|
+
code: string | null;
|
|
30
|
+
declineCode: string | null;
|
|
31
|
+
needsReauthentication: boolean;
|
|
32
|
+
constructor(message: string, opts?: {
|
|
33
|
+
code?: string | null;
|
|
34
|
+
declineCode?: string | null;
|
|
35
|
+
needsReauthentication?: boolean;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
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
|
|
41
|
+
* summary uses "temporarily unavailable" framing, and the non-zero exit lets
|
|
42
|
+
* GH Actions apply whatever retry policy the workflow has configured.
|
|
43
|
+
*/
|
|
44
|
+
export declare class TransientError extends Error {
|
|
45
|
+
constructor(message: string);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Render a BillingError as user-facing markdown (shared between GH job summary
|
|
49
|
+
* and the PR progress comment). Goals:
|
|
50
|
+
*
|
|
51
|
+
* - quiet, not alarmist — bold first line instead of an `### ❌` H3, since
|
|
52
|
+
* the comment already has Pullfrog branding in the footer
|
|
53
|
+
* - actionable — every branch ends in a single CTA deep-linked to the
|
|
54
|
+
* correct section of the owner's console
|
|
55
|
+
* - honest — say what actually went wrong (card declined vs. balance
|
|
56
|
+
* empty vs. 3DS required), don't lump them under "billing error"
|
|
57
|
+
*
|
|
58
|
+
* Branches:
|
|
59
|
+
* - `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
|
|
62
|
+
* Card flow lives.
|
|
63
|
+
* - `router_balance_exhausted`: user has a card on file but auto-reload is
|
|
64
|
+
* disabled and they've spent past their $5 overdraft buffer. Frame as
|
|
65
|
+
* "balance ran out" and surface both remediation paths (top up, or flip
|
|
66
|
+
* on auto-reload).
|
|
67
|
+
* - `router_keylimit_exhausted`: OpenRouter rejected mid-run because the
|
|
68
|
+
* per-run key budget was exhausted while the agent was working. The
|
|
69
|
+
* wallet is now negative; same remediation as `router_balance_exhausted`
|
|
70
|
+
* but framed for the after-the-fact case ("this run was cut short").
|
|
71
|
+
* - `needsReauthentication`: issuer requires 3DS on every off-session
|
|
72
|
+
* charge. Re-adding the card won't help — the only escape is a manual
|
|
73
|
+
* top-up where 3DS runs interactively in Stripe Checkout.
|
|
74
|
+
* - `declineCode` set: Stripe declined a real charge. Show the sub-code
|
|
75
|
+
* so support can act on it; tell the user we'll retry on next dispatch.
|
|
76
|
+
* - default: balance hit zero with no in-flight charge (auto-reload off
|
|
77
|
+
* or amount below threshold). Direct them to top up or enable auto-reload.
|
|
78
|
+
*/
|
|
79
|
+
export declare function formatBillingErrorSummary(error: BillingError, owner: string): string;
|
|
80
|
+
/**
|
|
81
|
+
* Render a TransientError as user-facing markdown. Distinct framing from
|
|
82
|
+
* BillingError so the user doesn't read an alarm and assume their card
|
|
83
|
+
* failed — this branch is "our fault, retry shortly", not theirs.
|
|
84
|
+
*/
|
|
85
|
+
export declare function formatTransientErrorSummary(error: TransientError, owner: string): string;
|
|
@@ -17,6 +17,13 @@ export interface BuildPullfrogFooterParams {
|
|
|
17
17
|
customParts?: string[] | undefined;
|
|
18
18
|
/** model slug from payload (e.g., "anthropic/claude-opus"). shown in footer as "Using `Model Name`" */
|
|
19
19
|
model?: string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* When the action engaged the BYOK fallback, this is the slug the user
|
|
22
|
+
* had configured (e.g. "anthropic/claude-opus") — the footer renders
|
|
23
|
+
* `Using <free model> (credentials for <configured> not configured)`
|
|
24
|
+
* so the substitution is visible in PR comments + reviews.
|
|
25
|
+
*/
|
|
26
|
+
fallbackFrom?: string | undefined;
|
|
20
27
|
}
|
|
21
28
|
/**
|
|
22
29
|
* build a pullfrog footer with configurable parts
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slug we fall back to when a BYOK-required model is configured but the
|
|
3
|
+
* runner has no provider key in env. Picked because it's free
|
|
4
|
+
* (`isFree: true`, `envVars: []` — see `action/models.ts`), stable, and
|
|
5
|
+
* currently the strongest free OpenCode model in the catalog. If a
|
|
6
|
+
* smarter free model is added later, update this single constant.
|
|
7
|
+
*
|
|
8
|
+
* The slug is intentionally hard-coded and not a config knob — the
|
|
9
|
+
* fallback is a safety net, not a user-facing preference, and adding a
|
|
10
|
+
* config surface here would just push the same "what to fall back to"
|
|
11
|
+
* decision into another setting that goes stale the same way.
|
|
12
|
+
*/
|
|
13
|
+
export declare const FREE_FALLBACK_SLUG = "opencode/minimax-m2.5-free";
|
|
14
|
+
export type FallbackDecision = {
|
|
15
|
+
fallback: false;
|
|
16
|
+
} | {
|
|
17
|
+
fallback: true;
|
|
18
|
+
from: string;
|
|
19
|
+
to: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* If the resolved model requires a BYOK key but no provider key is
|
|
23
|
+
* available in env, return `fallback: true` with a free OpenCode slug
|
|
24
|
+
* so the run can still succeed. Caller is responsible for swapping the
|
|
25
|
+
* model state and surfacing the fallback (log line + run summary).
|
|
26
|
+
*
|
|
27
|
+
* Gates on `resolvedModel` directly (not the configured slug) so the
|
|
28
|
+
* decision matches both code paths that reach this point: payload-based
|
|
29
|
+
* config (`repo.model` from DB) and `PULLFROG_MODEL` env var. Both end
|
|
30
|
+
* up in `resolvedModel` after `resolveModel()` runs upstream.
|
|
31
|
+
*
|
|
32
|
+
* Skip cases:
|
|
33
|
+
* - Router / proxy runs (`proxyModel` set): Pullfrog mints the key,
|
|
34
|
+
* no BYOK in play — never fall back.
|
|
35
|
+
* - No resolved model: keeps the existing auto-select-with-throw
|
|
36
|
+
* behavior in `validateAgentApiKey` for the "neither model nor
|
|
37
|
+
* key" case (genuine misconfig the user should see).
|
|
38
|
+
* - Resolved model is itself the free fallback: avoid suggesting we
|
|
39
|
+
* fell back to the model we're already running.
|
|
40
|
+
* - Resolved model is a Bedrock raw ID (no `/`): Bedrock has its own
|
|
41
|
+
* auth shape (`AWS_BEARER_TOKEN_BEDROCK` + region + model ID), and
|
|
42
|
+
* `validateBedrockSetup` already surfaces a tailored error. Skipping
|
|
43
|
+
* here also avoids `parseModel`'s slash requirement crashing inside
|
|
44
|
+
* `hasProviderKey`.
|
|
45
|
+
* - Resolved model has its provider key present: no fallback needed.
|
|
46
|
+
*/
|
|
47
|
+
export declare function selectFallbackModelIfNeeded(input: {
|
|
48
|
+
resolvedModel: string | undefined;
|
|
49
|
+
proxyModel: string | undefined;
|
|
50
|
+
}): FallbackDecision;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface InstalledCodexAuth {
|
|
2
|
+
/** absolute path of the auth.json we wrote — caller passes this to the
|
|
3
|
+
* post-hook via core.saveState for refresh-detection later. */
|
|
4
|
+
authPath: string;
|
|
5
|
+
/** value to set as XDG_DATA_HOME for the OpenCode subprocess. */
|
|
6
|
+
xdgDataHome: string;
|
|
7
|
+
/** refresh_token from the env at materialization time. post-hook compares
|
|
8
|
+
* against the on-disk file after the run to detect whether OpenCode
|
|
9
|
+
* refreshed during the session. */
|
|
10
|
+
originalRefresh: string;
|
|
11
|
+
}
|
|
12
|
+
/** materialize CODEX_AUTH_JSON from env into a disk path OpenCode reads from.
|
|
13
|
+
* returns null when the env var is absent, malformed, or wrong auth mode —
|
|
14
|
+
* caller treats null as "no codex auth, fall through to API key flow". */
|
|
15
|
+
export declare function installCodexAuth(): InstalledCodexAuth | null;
|
|
16
|
+
/** convert an on-disk OpenCode auth.json back to the Codex CLI shape so the
|
|
17
|
+
* post-hook can write it to the Pullfrog secret store. returns null when the
|
|
18
|
+
* file's `openai` entry is missing, has the wrong type, or hasn't actually
|
|
19
|
+
* refreshed (refresh token unchanged from `originalRefresh`). */
|
|
20
|
+
export declare function detectCodexRefresh(params: {
|
|
21
|
+
authFileContent: string;
|
|
22
|
+
originalRefresh: string;
|
|
23
|
+
}): string | null;
|
|
@@ -3,6 +3,15 @@ interface ReportErrorParams {
|
|
|
3
3
|
toolState: ToolState;
|
|
4
4
|
error: string;
|
|
5
5
|
title?: string;
|
|
6
|
+
/**
|
|
7
|
+
* When the run has no pre-existing progress comment to update (silent
|
|
8
|
+
* IncrementalReview / pull_request_synchronize, mode-less polls), create
|
|
9
|
+
* a fresh issue comment on `toolState.issueNumber` instead of returning
|
|
10
|
+
* silently. Used for terminal errors (BillingError, TransientError) where
|
|
11
|
+
* the GH job summary is the only other surface and most users never open
|
|
12
|
+
* it. see #775.
|
|
13
|
+
*/
|
|
14
|
+
createIfMissing?: boolean;
|
|
6
15
|
}
|
|
7
16
|
export declare function reportErrorToComment(ctx: ReportErrorParams): Promise<void>;
|
|
8
17
|
export {};
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { ToolContext } from "../mcp/server.ts";
|
|
2
|
+
import { MAX_LEARNINGS_LENGTH, truncateAtLineBoundary } from "./learningsTruncate.ts";
|
|
3
|
+
export { MAX_LEARNINGS_LENGTH, truncateAtLineBoundary };
|
|
1
4
|
/**
|
|
2
5
|
* Repo-level learnings — operational facts about a repo (setup steps, test
|
|
3
6
|
* commands, conventions, gotchas) that accumulate across agent runs and feed
|
|
@@ -40,3 +43,20 @@ export declare function seedLearningsFile(params: {
|
|
|
40
43
|
* missing or unreadable (treated as "no change"). caps content at the
|
|
41
44
|
* server's max length to avoid a 400 round-trip. */
|
|
42
45
|
export declare function readLearningsFile(path: string): Promise<string | null>;
|
|
46
|
+
/**
|
|
47
|
+
* Read the agent-edited repo-level learnings tmpfile and PATCH it to
|
|
48
|
+
* `Repo.learnings`.
|
|
49
|
+
*
|
|
50
|
+
* Best-effort: any failure is logged and does not affect the run's success
|
|
51
|
+
* status. Skips the PATCH when the file is byte-trim-identical to its seed —
|
|
52
|
+
* the agent didn't touch it, so writing the same content back would just
|
|
53
|
+
* burn a `LearningsRevision` row and an API round-trip.
|
|
54
|
+
*
|
|
55
|
+
* `ctx.toolState.model` is forwarded so `LearningsRevision.model` keeps
|
|
56
|
+
* populating; it powers the per-revision attribution badge in the UI
|
|
57
|
+
* history view.
|
|
58
|
+
*
|
|
59
|
+
* `learningsPersistAttempted` guards against double-execution between the
|
|
60
|
+
* normal end-of-run path and the SIGINT/SIGTERM handler.
|
|
61
|
+
*/
|
|
62
|
+
export declare function persistLearnings(ctx: ToolContext): Promise<void>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pure string helpers for capping and line-boundary-truncating the
|
|
3
|
+
* `Repo.learnings` body. lives in its own module (vs alongside
|
|
4
|
+
* `learnings.ts`) so the proprietary root app can re-export it through
|
|
5
|
+
* `action/internal/index.ts` without dragging the entire MCP type graph
|
|
6
|
+
* along — `learnings.ts` imports `ToolContext` for its runtime helpers,
|
|
7
|
+
* and pulling that into the SDK-facing `internal` barrel expands the
|
|
8
|
+
* type graph reachable from root `tsc` and `cf-worker-indexing` to every
|
|
9
|
+
* tool module under `action/mcp/`. keeping these helpers MCP-free is the
|
|
10
|
+
* cheap structural fix.
|
|
11
|
+
*
|
|
12
|
+
* see `action/utils/learnings.ts` for the full learnings-file lifecycle.
|
|
13
|
+
*/
|
|
14
|
+
/** maximum size of `Repo.learnings` body in chars. action truncates the
|
|
15
|
+
* read-back BEFORE the PATCH to avoid sending an oversized payload; the
|
|
16
|
+
* server applies the same truncation as a defense-in-depth backstop (any
|
|
17
|
+
* caller that misses the client-side step would otherwise persist a
|
|
18
|
+
* mid-line tail, breaking the next-run TOC parse).
|
|
19
|
+
*
|
|
20
|
+
* raised from 10k → 100k once the TOC affordance landed: with line-range
|
|
21
|
+
* reads via the server-parsed TOC the agent doesn't ingest the whole
|
|
22
|
+
* file, so the cap is governed by curation discipline rather than a
|
|
23
|
+
* tight byte ceiling. 100k holds ~400-500 short bullets. */
|
|
24
|
+
export declare const MAX_LEARNINGS_LENGTH = 100000;
|
|
25
|
+
export declare function truncateAtLineBoundary(body: string, cap: number): string;
|
|
@@ -2,20 +2,40 @@ export interface ExecuteLifecycleHookParams {
|
|
|
2
2
|
event: string;
|
|
3
3
|
script: string | null;
|
|
4
4
|
}
|
|
5
|
+
/** structured failure info — `output` on the `exit` variant is trimmed
|
|
6
|
+
* stderr, falling back to stdout when stderr is empty. */
|
|
7
|
+
export type LifecycleHookFailure = {
|
|
8
|
+
kind: "exit";
|
|
9
|
+
exitCode: number;
|
|
10
|
+
output: string;
|
|
11
|
+
} | {
|
|
12
|
+
kind: "timeout";
|
|
13
|
+
} | {
|
|
14
|
+
kind: "spawn";
|
|
15
|
+
spawnError: string;
|
|
16
|
+
};
|
|
5
17
|
export interface LifecycleHookResult {
|
|
6
18
|
/**
|
|
7
19
|
* human-readable warning when the hook failed. includes retry guidance:
|
|
8
20
|
* transient spawn/exit errors are worth retrying, timeouts and
|
|
9
21
|
* persistent failures are not. absent when the hook succeeded or was
|
|
10
|
-
* skipped.
|
|
22
|
+
* skipped. setup/post-checkout callers surface this verbatim; prepush
|
|
23
|
+
* builds its own message from `failure` instead.
|
|
11
24
|
*/
|
|
12
25
|
warning?: string;
|
|
26
|
+
/**
|
|
27
|
+
* structured failure info — undefined when the hook succeeded or was
|
|
28
|
+
* skipped. lets callers compose their own messaging without parsing the
|
|
29
|
+
* `warning` string.
|
|
30
|
+
*/
|
|
31
|
+
failure?: LifecycleHookFailure;
|
|
13
32
|
}
|
|
14
33
|
/**
|
|
15
34
|
* execute a lifecycle hook script if one is configured.
|
|
16
35
|
*
|
|
17
36
|
* soft-fails: instead of throwing on hook errors, returns a warning string
|
|
18
|
-
* so callers can choose whether to surface
|
|
19
|
-
* a fatal error (setup
|
|
37
|
+
* (and structured failure info) so callers can choose whether to surface
|
|
38
|
+
* it (mcp tools) or upgrade it to a fatal error (setup). timeouts are
|
|
39
|
+
* flagged as non-retryable in the warning text.
|
|
20
40
|
*/
|
|
21
41
|
export declare function executeLifecycleHook(params: ExecuteLifecycleHookParams): Promise<LifecycleHookResult>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse + apply the action's `unsafe_overrides` input — a JSON object of env
|
|
3
|
+
* var overrides that mutate `process.env` at the start of a run. Designed for
|
|
4
|
+
* e2e testing / debugging from `workflow_dispatch`; only callers with
|
|
5
|
+
* `actions:write` on the repo can supply it.
|
|
6
|
+
*
|
|
7
|
+
* The `unsafe` prefix is load-bearing: GH Actions echoes the value verbatim
|
|
8
|
+
* in the runner's step-header log, so the raw JSON (including any values
|
|
9
|
+
* passed in) is visible to anyone with `actions:read` on the calling repo.
|
|
10
|
+
* Treat the run log as compromised for any value placed in `unsafe_overrides`.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Names refused even when present in the input. Overriding these would let a
|
|
14
|
+
* caller escape pullfrog's scope (GITHUB_TOKEN), break runner internals
|
|
15
|
+
* (ACTIONS_RUNTIME_*), forge OIDC tokens (ACTIONS_ID_TOKEN_REQUEST_*), or
|
|
16
|
+
* substitute our server-side auth (PULLFROG_API_SECRET). Customer-facing
|
|
17
|
+
* provider keys (ANTHROPIC_API_KEY, OPENAI_API_KEY, CLAUDE_CODE_OAUTH_TOKEN,
|
|
18
|
+
* etc.) are intentionally NOT denied — overriding those is the use case.
|
|
19
|
+
*/
|
|
20
|
+
export declare const DENIED_OVERRIDE_NAMES: ReadonlySet<string>;
|
|
21
|
+
export interface ApplyOverridesResult {
|
|
22
|
+
applied: string[];
|
|
23
|
+
denied: string[];
|
|
24
|
+
}
|
|
25
|
+
/** Parse the JSON input. Returns `{}` for empty/whitespace. Throws on shape errors. */
|
|
26
|
+
export declare function parseOverrides(raw: string): Record<string, string>;
|
|
27
|
+
/**
|
|
28
|
+
* Mutate `params.env` in place with the supplied JSON overrides, skipping any
|
|
29
|
+
* names in `DENIED_OVERRIDE_NAMES`. Each applied value is registered with
|
|
30
|
+
* `core.setSecret` so the runner masks it in subsequent log output, and the
|
|
31
|
+
* raw `UNSAFE_OVERRIDES` env var is deleted so spawned subprocesses don't
|
|
32
|
+
* inherit the original JSON (which would defeat both the deny-list and the
|
|
33
|
+
* masking by exposing the values verbatim).
|
|
34
|
+
*
|
|
35
|
+
* Returns the applied/denied breakdown so the caller can render an audit log.
|
|
36
|
+
*/
|
|
37
|
+
export declare function applyOverrides(params: {
|
|
38
|
+
raw: string;
|
|
39
|
+
env: NodeJS.ProcessEnv;
|
|
40
|
+
}): ApplyOverridesResult;
|
package/dist/utils/payload.d.ts
CHANGED
|
@@ -49,3 +49,10 @@ export declare function resolvePayload(resolvedPromptInput: ResolvedPromptInput,
|
|
|
49
49
|
proxyModel: string | undefined;
|
|
50
50
|
};
|
|
51
51
|
export type ResolvedPayload = ReturnType<typeof resolvePayload>;
|
|
52
|
+
/**
|
|
53
|
+
* Parse and validate the optional `output_schema` action input. Returns the
|
|
54
|
+
* parsed object when present, or `undefined` when absent. Throws on invalid
|
|
55
|
+
* JSON or non-object payloads — these are workflow-author errors that should
|
|
56
|
+
* surface immediately, not silently degrade to "no schema".
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolveOutputSchema(): Record<string, unknown> | undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ToolContext } from "../mcp/server.ts";
|
|
1
2
|
/**
|
|
2
3
|
* The PR-level summary snapshot is a markdown file the agent edits in place
|
|
3
4
|
* during a Review / IncrementalReview run. The server seeds the file with
|
|
@@ -38,3 +39,23 @@ export declare function seedSummaryFile(params: {
|
|
|
38
39
|
/** read + validate the summary file written by the agent.
|
|
39
40
|
* returns null when the file is missing or fails sanity checks. */
|
|
40
41
|
export declare function readSummaryFile(path: string): Promise<string | null>;
|
|
42
|
+
/**
|
|
43
|
+
* Fetch the most recent persisted PR summary snapshot for this PR.
|
|
44
|
+
* Returns null on first-time PRs, when summary is disabled, or on any error.
|
|
45
|
+
* Best-effort: a transient API failure should not block the run.
|
|
46
|
+
*/
|
|
47
|
+
export declare function fetchPreviousSnapshot(ctx: ToolContext, prNumber: number): Promise<string | null>;
|
|
48
|
+
/**
|
|
49
|
+
* Read the agent-edited PR summary tmpfile and persist to
|
|
50
|
+
* `WorkflowRun.summarySnapshot`.
|
|
51
|
+
*
|
|
52
|
+
* Best-effort: any failure is logged and does not affect the run's success
|
|
53
|
+
* status. Skips the PATCH when the file is byte-identical to its seed —
|
|
54
|
+
* persisting the seed verbatim would either re-write what the DB already has
|
|
55
|
+
* (on incremental runs) or serialize the placeholder scaffold (on first
|
|
56
|
+
* runs), neither of which is useful.
|
|
57
|
+
*
|
|
58
|
+
* Funnels through both the success path and the SIGINT/SIGTERM handler;
|
|
59
|
+
* `summaryPersistAttempted` guards against double-execution.
|
|
60
|
+
*/
|
|
61
|
+
export declare function persistSummary(ctx: ToolContext): Promise<void>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mint an OpenRouter proxy key via `/api/proxy-token` and inject it as
|
|
3
|
+
* `OPENROUTER_API_KEY` for runs that route through Pullfrog Router (managed
|
|
4
|
+
* billing accounts) or OSS-grant paths.
|
|
5
|
+
*
|
|
6
|
+
* Authenticates one of two ways:
|
|
7
|
+
* - production: GitHub Actions OIDC token via `core.getIDToken`
|
|
8
|
+
* - local dev (`API_URL` is localhost): `x-dev-repo` header bypass
|
|
9
|
+
*
|
|
10
|
+
* `runProxyResolution` is the entrypoint `main.ts` calls. It wraps
|
|
11
|
+
* `resolveProxyModel` and renders the user-facing copy itself (job summary
|
|
12
|
+
* + PR progress comment) before rethrowing the structured error — handled
|
|
13
|
+
* here, not in the outer `main()` catch, because `toolContext` doesn't
|
|
14
|
+
* exist yet at this point in the pipeline.
|
|
15
|
+
*
|
|
16
|
+
* - 402 → `BillingError` (card declined, balance empty, 3DS, etc.)
|
|
17
|
+
* - 503 → `TransientError` (transient sync issue — retry next dispatch)
|
|
18
|
+
*/
|
|
19
|
+
import type { ToolState } from "../toolState.ts";
|
|
20
|
+
import type { ResolvedPayload } from "./payload.ts";
|
|
21
|
+
export interface OidcCredentials {
|
|
22
|
+
requestUrl: string;
|
|
23
|
+
requestToken: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Run `resolveProxyModel`; if it throws a Billing or Transient error, render
|
|
27
|
+
* the user-facing summary, mirror it to the PR progress comment, and rethrow.
|
|
28
|
+
*
|
|
29
|
+
* The rethrow is intentional: these errors are terminal for the run, and
|
|
30
|
+
* letting them surface lets `runMain` exit non-zero so GH Actions applies
|
|
31
|
+
* the workflow's retry policy. We catch them *here* (before the main try)
|
|
32
|
+
* because the outer catch needs `toolContext` (which isn't built yet) for
|
|
33
|
+
* its general-purpose rendering path — a BillingError landing in the outer
|
|
34
|
+
* catch would get rendered with `core.setFailed` only, losing the
|
|
35
|
+
* actionable copy + the PR-comment mirror.
|
|
36
|
+
*/
|
|
37
|
+
export declare function runProxyResolution(ctx: {
|
|
38
|
+
payload: ResolvedPayload;
|
|
39
|
+
oss: boolean;
|
|
40
|
+
proxyModel?: string | undefined;
|
|
41
|
+
oidcCredentials: OidcCredentials | null;
|
|
42
|
+
repo: {
|
|
43
|
+
owner: string;
|
|
44
|
+
name: string;
|
|
45
|
+
};
|
|
46
|
+
toolState: ToolState;
|
|
47
|
+
}): Promise<void>;
|
|
@@ -42,15 +42,6 @@ export interface RepoSettings {
|
|
|
42
42
|
* `"payg"` = card on file / pay-as-you-go.
|
|
43
43
|
*/
|
|
44
44
|
export type AccountPlan = "none" | "payg";
|
|
45
|
-
/**
|
|
46
|
-
* "Is Pullfrog absorbing marginal infra cost for this repo?" — composite
|
|
47
|
-
* predicate over the two orthogonal dimensions (repo-level OSS, account-level
|
|
48
|
-
* plan). Mirrors `isInfraCovered` in the server's `utils/billing.ts`.
|
|
49
|
-
*/
|
|
50
|
-
export declare function isInfraCovered(params: {
|
|
51
|
-
isOss: boolean;
|
|
52
|
-
plan: AccountPlan;
|
|
53
|
-
}): boolean;
|
|
54
45
|
export interface RunContext {
|
|
55
46
|
settings: RepoSettings;
|
|
56
47
|
apiToken: string;
|
|
@@ -0,0 +1,41 @@
|
|
|
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.
|
|
5
|
+
*
|
|
6
|
+
* Four classifications, in priority order:
|
|
7
|
+
*
|
|
8
|
+
* 1. `BillingError` — either the proxy-token mint already threw one (402
|
|
9
|
+
* handled inline) or the agent runtime surfaced an OpenRouter
|
|
10
|
+
* "key budget exhausted" string mid-run. Both render via
|
|
11
|
+
* `formatBillingErrorSummary` so the user sees actionable copy.
|
|
12
|
+
*
|
|
13
|
+
* 2. Activity-timeout hang — `errorMessage` starts with
|
|
14
|
+
* `"activity timeout"` or `"agent still pending"`. The harness keeps
|
|
15
|
+
* structured diagnostic state on `toolState.agentDiagnostic`;
|
|
16
|
+
* `formatAgentHangBody` renders that as a markdown block.
|
|
17
|
+
*
|
|
18
|
+
* 3. API-key auth error — `isApiKeyAuthError` sniffs the raw error string;
|
|
19
|
+
* `formatApiKeyErrorSummary` renders provider + console-link copy.
|
|
20
|
+
*
|
|
21
|
+
* 4. Default — a generic `❌ Pullfrog failed` block with the raw error
|
|
22
|
+
* message in a fenced code block. Same body for both surfaces.
|
|
23
|
+
*
|
|
24
|
+
* The hang body and the API-key body diverge between the two surfaces only
|
|
25
|
+
* in that the job summary wraps them in the `### ❌ Pullfrog failed` H3
|
|
26
|
+
* banner; the PR comment uses the bare body since it already has Pullfrog
|
|
27
|
+
* branding in its footer.
|
|
28
|
+
*/
|
|
29
|
+
import type { AgentDiagnostic } from "./agentHangReport.ts";
|
|
30
|
+
export type RenderedRunError = {
|
|
31
|
+
summary: string;
|
|
32
|
+
comment: string;
|
|
33
|
+
};
|
|
34
|
+
export declare function renderRunError(input: {
|
|
35
|
+
errorMessage: string;
|
|
36
|
+
repo: {
|
|
37
|
+
owner: string;
|
|
38
|
+
name: string;
|
|
39
|
+
};
|
|
40
|
+
agentDiagnostic: AgentDiagnostic | undefined;
|
|
41
|
+
}): RenderedRunError;
|