pullfrog 0.1.30 → 0.1.32

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.
Files changed (56) hide show
  1. package/README.md +17 -1
  2. package/dist/agents/claudePretoolGate.d.ts +5 -1
  3. package/dist/agents/opencodePlugin.d.ts +2 -2
  4. package/dist/agents/postRun.d.ts +8 -31
  5. package/dist/agents/shared.d.ts +6 -0
  6. package/dist/agents/subagentToolGates.d.ts +22 -40
  7. package/dist/cli.mjs +63822 -59863
  8. package/dist/external.d.ts +45 -2
  9. package/dist/index.js +62340 -58381
  10. package/dist/internal/index.d.ts +2 -2
  11. package/dist/internal.js +98 -26
  12. package/dist/mcp/checkSuite.d.ts +3 -1
  13. package/dist/mcp/checkout.d.ts +4 -2
  14. package/dist/mcp/comment.d.ts +12 -4
  15. package/dist/mcp/commitInfo.d.ts +3 -1
  16. package/dist/mcp/dependencies.d.ts +6 -2
  17. package/dist/mcp/git.d.ts +26 -7
  18. package/dist/mcp/issue.d.ts +26 -1
  19. package/dist/mcp/issueComments.d.ts +3 -1
  20. package/dist/mcp/issueEvents.d.ts +3 -1
  21. package/dist/mcp/issueInfo.d.ts +3 -1
  22. package/dist/mcp/labels.d.ts +16 -1
  23. package/dist/mcp/output.d.ts +6 -2
  24. package/dist/mcp/pr.d.ts +19 -2
  25. package/dist/mcp/prInfo.d.ts +3 -1
  26. package/dist/mcp/resolveRepoCtx.d.ts +34 -0
  27. package/dist/mcp/review.d.ts +50 -3
  28. package/dist/mcp/reviewComments.d.ts +9 -3
  29. package/dist/mcp/selectMode.d.ts +3 -1
  30. package/dist/mcp/server.d.ts +6 -1
  31. package/dist/mcp/shared.d.ts +18 -1
  32. package/dist/mcp/shell.d.ts +6 -2
  33. package/dist/mcp/upload.d.ts +3 -1
  34. package/dist/mcp/xrepo.d.ts +15 -0
  35. package/dist/models.d.ts +58 -5
  36. package/dist/toolState.d.ts +73 -16
  37. package/dist/utils/agent.d.ts +3 -1
  38. package/dist/utils/apiKeys.d.ts +4 -0
  39. package/dist/utils/buildPullfrogFooter.d.ts +34 -0
  40. package/dist/utils/errorReport.d.ts +2 -2
  41. package/dist/utils/github.d.ts +1 -1
  42. package/dist/utils/instructions.d.ts +8 -0
  43. package/dist/utils/isTransientNetworkError.d.ts +19 -0
  44. package/dist/utils/learnings.d.ts +14 -0
  45. package/dist/utils/modelAccess.d.ts +64 -0
  46. package/dist/utils/payload.d.ts +26 -1
  47. package/dist/utils/runContext.d.ts +3 -0
  48. package/dist/utils/runContextData.d.ts +10 -0
  49. package/dist/utils/setup.d.ts +18 -3
  50. package/dist/utils/statusChecks.d.ts +25 -0
  51. package/dist/utils/token.d.ts +10 -2
  52. package/dist/yes/index.d.ts +58 -0
  53. package/dist/yes/standard-schema.d.ts +117 -0
  54. package/dist/yes.js +1281 -0
  55. package/package.json +19 -10
  56. package/dist/utils/retry.d.ts +0 -13
@@ -1,5 +1,5 @@
1
1
  import type { RestEndpointMethodTypes } from "@octokit/rest";
2
- import type { CommentableLines } from "../toolState.ts";
2
+ import { type CommentableLines } from "../toolState.ts";
3
3
  import type { ToolContext } from "./server.ts";
4
4
  export type { CommentableLines };
5
5
  /**
@@ -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;
@@ -69,7 +104,7 @@ export type DuplicateReviewDecision = {
69
104
  * adds noise to the PR.
70
105
  *
71
106
  * legitimate follow-up reviews after new commits ARE allowed: the
72
- * new-commits-mid-review path advances toolState.checkoutSha past the
107
+ * new-commits-mid-review path advances the primary repo state's checkoutSha past the
73
108
  * previously reviewed sha, and a subsequent checkout_pr advances it again.
74
109
  * any call where checkoutSha has moved past the prior reviewedSha is a real
75
110
  * follow-up and goes through. anything else — same sha, or no checkoutSha
@@ -103,6 +138,7 @@ export declare function duplicateReviewDecision(params: {
103
138
  */
104
139
  export declare function reviewSkipDecision(params: {
105
140
  approved: boolean;
141
+ requestChanges?: boolean;
106
142
  body: string | null | undefined;
107
143
  hasComments: boolean;
108
144
  prApproveEnabled: boolean;
@@ -112,6 +148,7 @@ export declare const CreatePullRequestReview: import("arktype/internal/variants/
112
148
  pull_number: number;
113
149
  body?: string;
114
150
  approved?: boolean;
151
+ request_changes?: boolean;
115
152
  commit_id?: string;
116
153
  comments?: {
117
154
  path: string;
@@ -126,6 +163,7 @@ export declare function CreatePullRequestReviewTool(ctx: ToolContext): import("f
126
163
  pull_number: number;
127
164
  body?: string;
128
165
  approved?: boolean;
166
+ request_changes?: boolean;
129
167
  commit_id?: string;
130
168
  comments?: {
131
169
  path: string;
@@ -139,6 +177,7 @@ export declare function CreatePullRequestReviewTool(ctx: ToolContext): import("f
139
177
  pull_number: number;
140
178
  body?: string;
141
179
  approved?: boolean;
180
+ request_changes?: boolean;
142
181
  commit_id?: string;
143
182
  comments?: {
144
183
  path: string;
@@ -148,7 +187,14 @@ export declare function CreatePullRequestReviewTool(ctx: ToolContext): import("f
148
187
  suggestion?: string;
149
188
  start_line?: number;
150
189
  }[];
151
- }>>;
190
+ }>> & {
191
+ mutates?: boolean;
192
+ };
193
+ type FooterOpts = {
194
+ body: string;
195
+ approved: boolean;
196
+ hasComments: boolean;
197
+ };
152
198
  /**
153
199
  * clear a pending review draft stranded on the PR by a prior hard-killed run
154
200
  * (workflow timeout, OOM) so the next createReview can succeed.
@@ -190,6 +236,7 @@ export declare function clearStrandedPendingReview(ctx: ToolContext, params: {
190
236
  * comments-only) until a body-path run happened to clear the draft.
191
237
  */
192
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>>>;
193
240
  /**
194
241
  * report the review node ID so the WorkflowRun is marked as "review submitted".
195
242
  * exported for use in main.ts post-agent cleanup.
@@ -147,7 +147,9 @@ export declare function GetReviewCommentsTool(ctx: ToolContext): import("fastmcp
147
147
  }, {
148
148
  pull_number: number;
149
149
  review_id: number;
150
- }>>;
150
+ }>> & {
151
+ mutates?: boolean;
152
+ };
151
153
  export declare const ListPullRequestReviews: import("arktype/internal/variants/object.ts").ObjectType<{
152
154
  pull_number: number;
153
155
  }, {}>;
@@ -155,7 +157,9 @@ export declare function ListPullRequestReviewsTool(ctx: ToolContext): import("fa
155
157
  pull_number: number;
156
158
  }, {
157
159
  pull_number: number;
158
- }>>;
160
+ }>> & {
161
+ mutates?: boolean;
162
+ };
159
163
  export declare const ResolveReviewThread: import("arktype/internal/variants/object.ts").ObjectType<{
160
164
  thread_id: string;
161
165
  }, {}>;
@@ -163,5 +167,7 @@ export declare function ResolveReviewThreadTool(ctx: ToolContext): import("fastm
163
167
  thread_id: string;
164
168
  }, {
165
169
  thread_id: string;
166
- }>>;
170
+ }>> & {
171
+ mutates?: boolean;
172
+ };
167
173
  export {};
@@ -15,4 +15,6 @@ export declare function SelectModeTool(ctx: ToolContext): import("fastmcp").Tool
15
15
  }, {
16
16
  mode: string;
17
17
  issue_number?: number;
18
- }>>;
18
+ }>> & {
19
+ mutates?: boolean;
20
+ };
@@ -1,11 +1,12 @@
1
1
  import "./arkConfig.ts";
2
- import { type AgentId } from "../external.ts";
2
+ import { type AgentId, type XrepoConfig } from "../external.ts";
3
3
  import type { Mode } from "../modes.ts";
4
4
  import type { ToolState } from "../toolState.ts";
5
5
  import type { OctokitWithPlugins } from "../utils/github.ts";
6
6
  import type { ResolvedPayload } from "../utils/payload.ts";
7
7
  import type { AccountPlan } from "../utils/runContext.ts";
8
8
  import type { RunContextData } from "../utils/runContextData.ts";
9
+ import { type PullfrogTool } from "./shared.ts";
9
10
  export interface ToolContext {
10
11
  agentId: AgentId;
11
12
  repo: RunContextData["repo"];
@@ -13,6 +14,9 @@ export interface ToolContext {
13
14
  octokit: OctokitWithPlugins;
14
15
  githubInstallationToken: string;
15
16
  gitToken: string;
17
+ refreshGitToken: ((stale: string) => Promise<string>) | undefined;
18
+ readToken: string | undefined;
19
+ xrepo: XrepoConfig | undefined;
16
20
  apiToken: string;
17
21
  modes: Mode[];
18
22
  postCheckoutScript: string | null;
@@ -30,6 +34,7 @@ export interface ToolContext {
30
34
  resolvedModel: string | undefined;
31
35
  }
32
36
  type JsonSchema = Record<string, unknown>;
37
+ export declare function buildOrchestratorTools(ctx: ToolContext, outputSchema?: JsonSchema): PullfrogTool[];
33
38
  type McpHttpServerOptions = {
34
39
  outputSchema?: JsonSchema | undefined;
35
40
  };
@@ -1,7 +1,24 @@
1
1
  import type { StandardSchemaV1 } from "@standard-schema/spec";
2
2
  import type { FastMCP, Tool } from "fastmcp";
3
3
  import type { ToolContext } from "./server.ts";
4
- export declare const tool: <const params>(toolDef: Tool<any, StandardSchemaV1<params>>) => Tool<any, StandardSchemaV1<params>>;
4
+ /** extract the HTTP status from an unknown thrown value (octokit RequestError etc.), or undefined. */
5
+ export declare function getHttpStatus(err: unknown): number | undefined;
6
+ /**
7
+ * a Pullfrog MCP tool definition. `mutates` marks a named state-changing tool
8
+ * that must be reserved for the orchestrator and denied to subagents — it is
9
+ * the single source of truth the subagent deny list derives from (see
10
+ * action/agents/subagentToolGates.ts). general-purpose execution tools (`git`,
11
+ * `shell`) are deliberately left unmarked: they can mutate but are allowed for
12
+ * subagents and gated by command-arg validation instead.
13
+ */
14
+ export type PullfrogTool = Tool<any, any> & {
15
+ mutates?: boolean;
16
+ };
17
+ export declare const tool: <const params>(toolDef: Tool<any, StandardSchemaV1<params>> & {
18
+ mutates?: boolean;
19
+ }) => Tool<any, StandardSchemaV1<params>> & {
20
+ mutates?: boolean;
21
+ };
5
22
  export interface ToolResult {
6
23
  content: {
7
24
  type: "text";
@@ -26,7 +26,9 @@ export declare function ShellTool(ctx: ToolContext): import("fastmcp").Tool<any,
26
26
  timeout?: number;
27
27
  working_directory?: string;
28
28
  background?: boolean;
29
- }>>;
29
+ }>> & {
30
+ mutates?: boolean;
31
+ };
30
32
  export declare const KillBackgroundParams: import("arktype/internal/variants/object.ts").ObjectType<{
31
33
  handle: string;
32
34
  }, {}>;
@@ -34,4 +36,6 @@ export declare function KillBackgroundTool(ctx: ToolContext): import("fastmcp").
34
36
  handle: string;
35
37
  }, {
36
38
  handle: string;
37
- }>>;
39
+ }>> & {
40
+ mutates?: boolean;
41
+ };
@@ -3,4 +3,6 @@ export declare function UploadFileTool(ctx: ToolContext): import("fastmcp").Tool
3
3
  path: string;
4
4
  }, {
5
5
  path: string;
6
- }>>;
6
+ }>> & {
7
+ mutates?: boolean;
8
+ };
@@ -0,0 +1,15 @@
1
+ import type { ToolContext } from "./server.ts";
2
+ export declare const ListRepos: import("arktype/internal/variants/object.ts").ObjectType<object, {}>;
3
+ export declare function ListReposTool(ctx: ToolContext): import("fastmcp").Tool<any, import("@standard-schema/spec").StandardSchemaV1<object, object>> & {
4
+ mutates?: boolean;
5
+ };
6
+ export declare const CheckoutRepo: import("arktype/internal/variants/object.ts").ObjectType<{
7
+ repo: string;
8
+ }, {}>;
9
+ export declare function CheckoutRepoTool(ctx: ToolContext): import("fastmcp").Tool<any, import("@standard-schema/spec").StandardSchemaV1<{
10
+ repo: string;
11
+ }, {
12
+ repo: string;
13
+ }>> & {
14
+ mutates?: boolean;
15
+ };
package/dist/models.d.ts CHANGED
@@ -36,7 +36,11 @@ export interface ModelAlias {
36
36
  preferred: boolean;
37
37
  /** whether this alias is free and requires no API key */
38
38
  isFree: boolean;
39
- /** slug of a replacement model presence implies this model is deprecated */
39
+ /** slug of a replacement model to resolve through. presence means this alias
40
+ * never runs as-is — resolution redirects to the replacement and it's hidden
41
+ * from pickers. covers permanent deprecation AND riding out a temporarily
42
+ * unavailable model: point it at a cheaper tier (downgrade) or a working
43
+ * sibling/higher tier (upgrade), then clear it when the model returns. */
40
44
  fallback: string | undefined;
41
45
  /** dynamic-resolution discriminant — see ModelRouting docs */
42
46
  routing: ModelRouting | undefined;
@@ -57,7 +61,9 @@ interface ModelDef {
57
61
  preferred?: boolean;
58
62
  envVars?: readonly string[];
59
63
  isFree?: boolean;
60
- /** slug of a replacement model presence implies this model is deprecated */
64
+ /** slug of a replacement model to resolve through permanent deprecation or
65
+ * temporary unavailability (downgrade/upgrade until the model returns). see
66
+ * ModelAlias.fallback. */
61
67
  fallback?: string;
62
68
  /** dynamic-resolution discriminant — see ModelRouting docs */
63
69
  routing?: ModelRouting;
@@ -104,9 +110,53 @@ export declare function getModelEnvVars(slug: string): string[];
104
110
  * see `provider.managedCredentials` and wiki/codex-auth.md. */
105
111
  export declare function getModelManagedCredentials(slug: string): string[];
106
112
  export declare const modelAliases: ModelAlias[];
107
- export declare const DEFAULT_PROXY_MODEL: string;
108
- /** short label for the model auto-select picks today (console hint copy). */
109
- export declare function getAutoSelectHintModel(): string;
113
+ /**
114
+ * `repo.model` sentinels for the two managed auto tiers. stored verbatim in
115
+ * the DB (they pass the `provider/model` shape check) and resolved to a
116
+ * concrete alias by `resolveDisplayAlias` below, so every downstream consumer
117
+ * (CLI resolve, OpenRouter resolve, footer label) handles them transparently.
118
+ *
119
+ * `efficient` mirrors the OSS/default subsidy model (Kimi K2 — fast + cheap);
120
+ * `intelligent` is the frontier pick (Claude Opus). a `null` model means the
121
+ * tier hasn't been pinned: callers default by card status via `defaultAutoTier`.
122
+ */
123
+ export declare const AUTO_EFFICIENT = "auto/efficient";
124
+ export declare const AUTO_INTELLIGENT = "auto/intelligent";
125
+ export type AutoTier = typeof AUTO_EFFICIENT | typeof AUTO_INTELLIGENT;
126
+ export declare function isAutoTier(slug: string | null | undefined): slug is AutoTier;
127
+ /**
128
+ * the tier to default to when the user hasn't pinned one. card on file →
129
+ * intelligent (they've signalled willingness to pay for frontier reviews);
130
+ * otherwise → efficient (safe, cheap). server (`run-context`) and the console
131
+ * picker both call this so the displayed default and the runtime default agree.
132
+ */
133
+ export declare function defaultAutoTier(hasCard: boolean): AutoTier;
134
+ /**
135
+ * the auto tier that actually runs for an account, enforcing the card gate:
136
+ * the intelligent tier (Opus) requires a card on file, so a no-card account is
137
+ * always clamped to efficient (Kimi) — whether intelligent was the card-based
138
+ * default or an explicit pick made while a card was once present. keeps a trial
139
+ * balance from being torched on a premium model before the user has a card.
140
+ * `model` may be null, an `auto/*` sentinel, or a concrete pick being
141
+ * clamped — callers route concrete picks here only when `!hasCard` (carded
142
+ * concrete picks resolve directly), so a concrete `model` always lands on the
143
+ * efficient tier via the no-card early return.
144
+ */
145
+ export declare function resolveAutoTier(params: {
146
+ model: string | null;
147
+ hasCard: boolean;
148
+ }): AutoTier;
149
+ /**
150
+ * Router-resolvable — the set a card on file unlocks. custom (non-Auto) picks
151
+ * are card-gated wholesale on the Router: the console locks the Custom tab
152
+ * without a card and the server clamps any stored pick to the efficient tier.
153
+ * a pick with no openRouterResolve (a model OpenRouter doesn't serve yet)
154
+ * lands on the efficient default with or without a card, so "add a card to
155
+ * run this model" messaging would be false for it — hence the predicate.
156
+ * resolveOpenRouterModel walks display aliases, so auto sentinels and
157
+ * deprecated slugs are judged by the model that actually runs.
158
+ */
159
+ export declare function isCardGatedModel(slug: string): boolean;
110
160
  /** resolve a model slug to its concrete models.dev specifier (e.g. "anthropic/claude-opus-4-6") */
111
161
  export declare function resolveModelSlug(slug: string): string | undefined;
112
162
  /**
@@ -132,6 +182,9 @@ export declare function resolveCliModel(slug: string): string | undefined;
132
182
  * (e.g. free opencode models).
133
183
  */
134
184
  export declare function resolveOpenRouterModel(slug: string): string | undefined;
185
+ export declare const DEFAULT_PROXY_MODEL: string;
186
+ /** short label for the model auto-select picks today (console hint copy). */
187
+ export declare function getAutoSelectHintModel(): string;
135
188
  /** env var that supplies the Bedrock model ID for the `bedrock/byok` slug. */
136
189
  export declare const BEDROCK_MODEL_ID_ENV = "BEDROCK_MODEL_ID";
137
190
  /** env var that supplies the Vertex AI model ID for the `vertex/byok` slug. */
@@ -32,6 +32,40 @@ export type CommentableLines = {
32
32
  RIGHT: Set<number>;
33
33
  LEFT: Set<number>;
34
34
  };
35
+ /** access tier of a checked-out repo, drives token routing + push gating. */
36
+ export type RepoAccess = "primary" | "write" | "read";
37
+ /**
38
+ * per-repo working-tree + PR state. the primary repo is keyed like any other
39
+ * in `toolState.repos`; secondaries are added by `checkout_repo`. this is the
40
+ * single source of truth for all repo-scoped git/PR state — there are no
41
+ * parallel inline fields on `ToolState`. read via `primaryRepoState(toolState)`
42
+ * for primary-only call sites, or `requireRepoState(toolState, owner, name)`
43
+ * when a `repo` param routes to a specific checkout.
44
+ */
45
+ export interface RepoToolState {
46
+ owner: string;
47
+ name: string;
48
+ /** working directory: primary = `process.cwd()`; secondary = `ctx.tmpdir/xrepo/<name>` */
49
+ dir: string;
50
+ access: RepoAccess;
51
+ defaultBranch?: string;
52
+ pushUrl?: string;
53
+ pushDest?: StoredPushDest;
54
+ initialHead?: {
55
+ kind: "branch";
56
+ name: string;
57
+ } | {
58
+ kind: "detached";
59
+ sha: string;
60
+ };
61
+ issueNumber?: number;
62
+ checkoutSha?: string;
63
+ commentableLinesByFile?: Map<string, CommentableLines>;
64
+ commentableLinesPullNumber?: number;
65
+ commentableLinesCheckoutSha?: string | undefined;
66
+ beforeSha?: string;
67
+ diffCoverage?: DiffCoverageState | undefined;
68
+ }
35
69
  /**
36
70
  * mutable per-run record of facts that occurred during execution. shared
37
71
  * between the action process and the MCP server (one process — toolState is
@@ -55,21 +89,8 @@ export type CommentableLines = {
55
89
  * MCP tool already records.
56
90
  */
57
91
  export interface ToolState {
58
- pushUrl?: string;
59
- pushDest?: StoredPushDest;
60
- initialHead?: {
61
- kind: "branch";
62
- name: string;
63
- } | {
64
- kind: "detached";
65
- sha: string;
66
- };
67
- issueNumber?: number;
68
- checkoutSha?: string;
69
- commentableLinesByFile?: Map<string, CommentableLines>;
70
- commentableLinesPullNumber?: number;
71
- commentableLinesCheckoutSha?: string | undefined;
72
- beforeSha?: string;
92
+ repos: Map<string, RepoToolState>;
93
+ primaryRepoKey: string;
73
94
  selectedMode?: string;
74
95
  prepushFailureCount: number;
75
96
  backgroundProcesses: Map<string, BackgroundProcess>;
@@ -79,6 +100,10 @@ export interface ToolState {
79
100
  nodeId: string;
80
101
  reviewedSha: string | undefined;
81
102
  };
103
+ approval?: {
104
+ wouldApprove: boolean;
105
+ sha: string | undefined;
106
+ };
82
107
  reviewReplies?: Map<number, {
83
108
  commentId: number;
84
109
  url: string | undefined;
@@ -94,6 +119,7 @@ export interface ToolState {
94
119
  lastProgressBody?: string;
95
120
  wasUpdated?: boolean;
96
121
  finalSummaryWritten?: boolean;
122
+ answerCommentPosted?: boolean;
97
123
  existingPlanCommentId?: number;
98
124
  previousPlanBody?: string;
99
125
  summaryFilePath?: string;
@@ -102,12 +128,23 @@ export interface ToolState {
102
128
  learningsFilePath?: string;
103
129
  learningsSeed?: string;
104
130
  learningsPersistAttempted?: boolean;
131
+ xrepoLearningsFilePath?: string;
132
+ xrepoLearningsSeed?: string;
133
+ xrepoLearningsPersistAttempted?: boolean;
105
134
  output?: string | undefined;
106
135
  usageEntries: AgentUsage[];
107
136
  model?: string | undefined;
137
+ modelFallback?: {
138
+ from: string;
139
+ } | undefined;
140
+ unselectedProxyDefault?: boolean | undefined;
141
+ modelClamped?: {
142
+ from: string;
143
+ reason: "card" | "noRouterPath";
144
+ } | undefined;
145
+ shaPinned?: boolean | undefined;
108
146
  oss?: boolean | undefined;
109
147
  todoTracker?: TodoTracker | undefined;
110
- diffCoverage?: DiffCoverageState | undefined;
111
148
  agentDiagnostic?: AgentDiagnostic | undefined;
112
149
  }
113
150
  interface InitToolStateParams {
@@ -115,6 +152,26 @@ interface InitToolStateParams {
115
152
  id: string;
116
153
  type: ProgressCommentType;
117
154
  } | undefined;
155
+ owner: string;
156
+ name: string;
157
+ dir: string;
118
158
  }
119
159
  export declare function initToolState(params: InitToolStateParams): ToolState;
160
+ /** stable "owner/name" key for the `repos` map. lowercased — GitHub repo names are case-insensitive. */
161
+ export declare function repoKey(owner: string, name: string): string;
162
+ /** the primary (triggering) repo's state. throws if init hasn't run. */
163
+ export declare function primaryRepoState(toolState: ToolState): RepoToolState;
164
+ /**
165
+ * state for a specific checked-out repo. throws when the repo isn't a
166
+ * registered checkout — keeps the push/PR allow-set honest (the only way a
167
+ * secondary becomes registered is via `checkout_repo`).
168
+ */
169
+ export declare function requireRepoState(toolState: ToolState, owner: string, name: string): RepoToolState;
170
+ /** get-or-create a repo's state (used by checkout_repo when registering a secondary). */
171
+ export declare function ensureRepoState(toolState: ToolState, init: {
172
+ owner: string;
173
+ name: string;
174
+ dir: string;
175
+ access: RepoAccess;
176
+ }): RepoToolState;
120
177
  export {};
@@ -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
- * 3. undefined agent will auto-select.
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;
@@ -33,6 +33,10 @@ export declare function validateAgentApiKey(params: {
33
33
  * "Token refresh failed: 401". the Bedrock pattern is anchored to the
34
34
  * Claude CLI emission ("Failed to authenticate. API Error:") so generic
35
35
  * auth chatter in agent stderr can't misclassify a hang as a key error.
36
+ * - DeepSeek invalid key (#960): `Authentication Fails, Your api key:
37
+ * ****XXXX is invalid`. anchored to "Your api key: ... is invalid" so it
38
+ * can't collide with DeepSeek's already-handled `Insufficient balance`
39
+ * billing shape (which routes to formatProviderBillingExhausted).
36
40
  */
37
41
  export declare function isApiKeyAuthError(text: string): boolean;
38
42
  /**
@@ -17,6 +17,40 @@ 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;
27
+ /**
28
+ * When a Router account had a model (or the intelligent tier) selected that
29
+ * the server clamped to the efficient default — custom picks are card-gated
30
+ * wholesale. `from` is the configured slug (e.g. "anthropic/claude-opus");
31
+ * `reason` names the binding constraint — "card" (no card on file) renders
32
+ * `Using <Kimi K2> (<Claude Opus> needs a card on file)`, "noRouterPath"
33
+ * (no openRouterResolve yet and no stored provider key) renders a
34
+ * provider-key nudge — so the downgrade is visible rather than silently
35
+ * presenting Kimi as the pick.
36
+ */
37
+ clamped?: {
38
+ from: string;
39
+ reason: "card" | "noRouterPath";
40
+ } | undefined;
41
+ /**
42
+ * true when the run used the default proxy model only because no model was
43
+ * selected (Router billing + "auto"). the footer appends a note nudging the
44
+ * user to pick a model — the cost-optimized default is a weaker reviewer
45
+ * than a frontier model.
46
+ */
47
+ unselectedProxyDefault?: boolean | undefined;
48
+ /**
49
+ * true when the action is pinned to a full commit SHA — the footer leads
50
+ * with a maintenance nudge to switch to the moving `@v0` tag (a SHA pin
51
+ * freezes the post-run cleanup step, which silently fails the workflow).
52
+ */
53
+ shaPinned?: boolean | undefined;
20
54
  /**
21
55
  * true when the run's model costs are covered by the Pullfrog for OSS
22
56
  * program — the footer renders `Using <model> (free via Pullfrog for OSS)`
@@ -1,4 +1,4 @@
1
- import type { ToolState } from "../toolState.ts";
1
+ import { type ToolState } from "../toolState.ts";
2
2
  interface ReportErrorParams {
3
3
  toolState: ToolState;
4
4
  error: string;
@@ -6,7 +6,7 @@ interface ReportErrorParams {
6
6
  /**
7
7
  * When the run has no pre-existing progress comment to update (silent
8
8
  * IncrementalReview / pull_request_synchronize, mode-less polls), create
9
- * a fresh issue comment on `toolState.issueNumber` instead of returning
9
+ * a fresh issue comment on the primary repo state's `issueNumber` instead of returning
10
10
  * silently. Used for terminal errors (BillingError, TransientError) where
11
11
  * the GH job summary is the only other surface and most users never open
12
12
  * it. see #775.
@@ -40,7 +40,7 @@ type GitHubAppPermissions = {
40
40
  workflows?: WriteOnly;
41
41
  };
42
42
  type AcquireTokenOptions = {
43
- repos?: string[];
43
+ repos?: string[] | undefined;
44
44
  permissions?: GitHubAppPermissions;
45
45
  /**
46
46
  * stashed OIDC credentials for minting after restricted mode deletes
@@ -24,6 +24,14 @@ interface InstructionsContext {
24
24
  * `describeSetupFailure`), rendered as a SETUP HOOK FAILED banner. empty
25
25
  * string when the hook succeeded, was skipped, or wasn't configured. */
26
26
  setupHookFailure: string;
27
+ /** operator-authored cross-repo brief (`Account.xrepoBrief`), rendered in
28
+ * the CROSS-REPO section on --xrepo runs. null/empty otherwise. */
29
+ xrepoBrief: string | null;
30
+ /** absolute path to the seeded cross-repo learnings tmpfile (--xrepo runs
31
+ * only), or null. */
32
+ xrepoLearningsFilePath: string | null;
33
+ /** server-parsed TOC for the cross-repo learnings body. */
34
+ xrepoLearningsHeadings: LearningsHeading[];
27
35
  }
28
36
  export interface ResolvedInstructions {
29
37
  full: string;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Predicate for transient network errors that should be retried (vs. fail fast
3
+ * on explicit HTTP status / validation errors that carry their own message).
4
+ *
5
+ * Matches errors from `fetch` (`fetch failed`), TCP-level interruption
6
+ * (`ECONNRESET`, `ETIMEDOUT`), and `AbortSignal.timeout` (`AbortError`).
7
+ * Callers may pass `extraPatterns` to whitelist additional transient
8
+ * substrings on `error.message` (e.g. provider-specific framings).
9
+ */
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;
@@ -29,7 +29,9 @@ export { MAX_LEARNINGS_LENGTH, truncateAtLineBoundary };
29
29
  * agent-targetable on long-lived repos; there is no fixed taxonomy.
30
30
  */
31
31
  export declare const LEARNINGS_FILE_NAME = "pullfrog-learnings.md";
32
+ export declare const XREPO_LEARNINGS_FILE_NAME = "pullfrog-xrepo-learnings.md";
32
33
  export declare function learningsFilePath(tmpdir: string): string;
34
+ export declare function xrepoLearningsFilePath(tmpdir: string): string;
33
35
  /** seed the rolling learnings tmpfile with the verbatim DB body (or empty
34
36
  * string for fresh repos). returns the absolute path. the parsed TOC is
35
37
  * carried separately via `RepoSettings.learningsHeadings` and rendered
@@ -39,6 +41,11 @@ export declare function seedLearningsFile(params: {
39
41
  tmpdir: string;
40
42
  current: string | null;
41
43
  }): Promise<string>;
44
+ /** seed the org-level cross-repo learnings tmpfile (--xrepo runs only). */
45
+ export declare function seedXrepoLearningsFile(params: {
46
+ tmpdir: string;
47
+ current: string | null;
48
+ }): Promise<string>;
42
49
  /** read the agent-edited learnings file. returns null when the file is
43
50
  * missing or unreadable (treated as "no change"). caps content at the
44
51
  * server's max length to avoid a 400 round-trip. */
@@ -60,3 +67,10 @@ export declare function readLearningsFile(path: string): Promise<string | null>;
60
67
  * normal end-of-run path and the SIGINT/SIGTERM handler.
61
68
  */
62
69
  export declare function persistLearnings(ctx: ToolContext): Promise<void>;
70
+ /**
71
+ * Read the agent-edited cross-repo learnings tmpfile and PATCH it to
72
+ * `Account.xrepoLearnings`. Org-level analogue of `persistLearnings` —
73
+ * same best-effort + unchanged-from-seed gating. Only seeded on --xrepo runs,
74
+ * so this is a no-op when `xrepoLearningsFilePath` is unset.
75
+ */
76
+ export declare function persistXrepoLearnings(ctx: ToolContext): Promise<void>;