pullfrog 0.1.30 → 0.1.31

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 (53) hide show
  1. package/dist/agents/claudePretoolGate.d.ts +5 -1
  2. package/dist/agents/opencodePlugin.d.ts +2 -2
  3. package/dist/agents/postRun.d.ts +8 -31
  4. package/dist/agents/shared.d.ts +6 -0
  5. package/dist/agents/subagentToolGates.d.ts +22 -40
  6. package/dist/cli.mjs +63441 -59740
  7. package/dist/external.d.ts +45 -2
  8. package/dist/index.js +61939 -58238
  9. package/dist/internal/index.d.ts +2 -2
  10. package/dist/internal.js +89 -22
  11. package/dist/mcp/checkSuite.d.ts +3 -1
  12. package/dist/mcp/checkout.d.ts +4 -2
  13. package/dist/mcp/comment.d.ts +12 -4
  14. package/dist/mcp/commitInfo.d.ts +3 -1
  15. package/dist/mcp/dependencies.d.ts +6 -2
  16. package/dist/mcp/git.d.ts +26 -7
  17. package/dist/mcp/issue.d.ts +26 -1
  18. package/dist/mcp/issueComments.d.ts +3 -1
  19. package/dist/mcp/issueEvents.d.ts +3 -1
  20. package/dist/mcp/issueInfo.d.ts +3 -1
  21. package/dist/mcp/labels.d.ts +16 -1
  22. package/dist/mcp/output.d.ts +6 -2
  23. package/dist/mcp/pr.d.ts +19 -2
  24. package/dist/mcp/prInfo.d.ts +3 -1
  25. package/dist/mcp/resolveRepoCtx.d.ts +34 -0
  26. package/dist/mcp/review.d.ts +9 -3
  27. package/dist/mcp/reviewComments.d.ts +9 -3
  28. package/dist/mcp/selectMode.d.ts +3 -1
  29. package/dist/mcp/server.d.ts +6 -1
  30. package/dist/mcp/shared.d.ts +18 -1
  31. package/dist/mcp/shell.d.ts +6 -2
  32. package/dist/mcp/upload.d.ts +3 -1
  33. package/dist/mcp/xrepo.d.ts +15 -0
  34. package/dist/models.d.ts +58 -5
  35. package/dist/toolState.d.ts +69 -16
  36. package/dist/utils/apiKeys.d.ts +4 -0
  37. package/dist/utils/buildPullfrogFooter.d.ts +34 -0
  38. package/dist/utils/errorReport.d.ts +2 -2
  39. package/dist/utils/github.d.ts +1 -1
  40. package/dist/utils/instructions.d.ts +8 -0
  41. package/dist/utils/isTransientNetworkError.d.ts +10 -0
  42. package/dist/utils/learnings.d.ts +14 -0
  43. package/dist/utils/modelAccess.d.ts +64 -0
  44. package/dist/utils/payload.d.ts +16 -0
  45. package/dist/utils/runContext.d.ts +3 -0
  46. package/dist/utils/runContextData.d.ts +10 -0
  47. package/dist/utils/setup.d.ts +18 -3
  48. package/dist/utils/token.d.ts +10 -2
  49. package/dist/yes/index.d.ts +32 -0
  50. package/dist/yes/standard-schema.d.ts +117 -0
  51. package/dist/yes.js +1244 -0
  52. package/package.json +19 -10
  53. package/dist/utils/retry.d.ts +0 -13
@@ -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>;
@@ -94,6 +115,7 @@ export interface ToolState {
94
115
  lastProgressBody?: string;
95
116
  wasUpdated?: boolean;
96
117
  finalSummaryWritten?: boolean;
118
+ answerCommentPosted?: boolean;
97
119
  existingPlanCommentId?: number;
98
120
  previousPlanBody?: string;
99
121
  summaryFilePath?: string;
@@ -102,12 +124,23 @@ export interface ToolState {
102
124
  learningsFilePath?: string;
103
125
  learningsSeed?: string;
104
126
  learningsPersistAttempted?: boolean;
127
+ xrepoLearningsFilePath?: string;
128
+ xrepoLearningsSeed?: string;
129
+ xrepoLearningsPersistAttempted?: boolean;
105
130
  output?: string | undefined;
106
131
  usageEntries: AgentUsage[];
107
132
  model?: string | undefined;
133
+ modelFallback?: {
134
+ from: string;
135
+ } | undefined;
136
+ unselectedProxyDefault?: boolean | undefined;
137
+ modelClamped?: {
138
+ from: string;
139
+ reason: "card" | "noRouterPath";
140
+ } | undefined;
141
+ shaPinned?: boolean | undefined;
108
142
  oss?: boolean | undefined;
109
143
  todoTracker?: TodoTracker | undefined;
110
- diffCoverage?: DiffCoverageState | undefined;
111
144
  agentDiagnostic?: AgentDiagnostic | undefined;
112
145
  }
113
146
  interface InitToolStateParams {
@@ -115,6 +148,26 @@ interface InitToolStateParams {
115
148
  id: string;
116
149
  type: ProgressCommentType;
117
150
  } | undefined;
151
+ owner: string;
152
+ name: string;
153
+ dir: string;
118
154
  }
119
155
  export declare function initToolState(params: InitToolStateParams): ToolState;
156
+ /** stable "owner/name" key for the `repos` map. lowercased — GitHub repo names are case-insensitive. */
157
+ export declare function repoKey(owner: string, name: string): string;
158
+ /** the primary (triggering) repo's state. throws if init hasn't run. */
159
+ export declare function primaryRepoState(toolState: ToolState): RepoToolState;
160
+ /**
161
+ * state for a specific checked-out repo. throws when the repo isn't a
162
+ * registered checkout — keeps the push/PR allow-set honest (the only way a
163
+ * secondary becomes registered is via `checkout_repo`).
164
+ */
165
+ export declare function requireRepoState(toolState: ToolState, owner: string, name: string): RepoToolState;
166
+ /** get-or-create a repo's state (used by checkout_repo when registering a secondary). */
167
+ export declare function ensureRepoState(toolState: ToolState, init: {
168
+ owner: string;
169
+ name: string;
170
+ dir: string;
171
+ access: RepoAccess;
172
+ }): RepoToolState;
120
173
  export {};
@@ -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,10 @@
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;
@@ -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>;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Model-access gate for explicitly-requested per-run models (`--opus`,
3
+ * `--model=<slug>`). An explicit model the run can't serve hard-fails *before*
4
+ * the agent starts with one consistent, reason-tailored error.
5
+ *
6
+ * The provenance split lives upstream: `payload.modelExplicit` is true only for
7
+ * a flag in the triggerer's own @pullfrog prompt. A standing default (repo
8
+ * setting, a model flag in org/repo `baseInstructions`, or a trigger's fallback
9
+ * instructions) keeps `modelExplicit = false` and never reaches the error
10
+ * branches here — a missing key for a standing default surfaces through
11
+ * `validateAgentApiKey`'s missing-key error instead (#938).
12
+ *
13
+ * `decideModelAccess` is pure (no env / IO) so the branching is unit-testable;
14
+ * `buildModelAccessError` renders the user-facing markdown that `main.ts`
15
+ * throws and `runErrorRenderer.ts` re-surfaces on both run surfaces.
16
+ */
17
+ export type ModelAccessReason = "oss" | "byok_no_key" | "router";
18
+ export type ModelAccessDecision = {
19
+ kind: "ok";
20
+ }
21
+ /** route through the Router/OSS proxy, re-targeted to the requested model. */
22
+ | {
23
+ kind: "proxy";
24
+ target: string;
25
+ }
26
+ /** run BYOK with the requested model — clear the minted proxy target. */
27
+ | {
28
+ kind: "byok";
29
+ } | {
30
+ kind: "error";
31
+ reason: ModelAccessReason;
32
+ };
33
+ /**
34
+ * decide whether an (already-resolved) requested model can run, and how.
35
+ *
36
+ * - non-explicit / no model → `ok` (caller's missing-key validation applies).
37
+ * - proxy active + OSS → only the funded subsidy target routes; a different
38
+ * model needs the repo's own key (`byok`) else `error("oss")`.
39
+ * - proxy active + Router → honor any Router-servable model (`proxy`); fall to
40
+ * BYOK when locally authorized, else `error("router")`.
41
+ * - no proxy → the resolved model must be locally authorized, else
42
+ * `error("byok_no_key")`.
43
+ */
44
+ export declare function decideModelAccess(input: {
45
+ modelExplicit: boolean;
46
+ model: string | undefined;
47
+ oss: boolean;
48
+ proxyActive: boolean;
49
+ subsidyTarget: string | undefined;
50
+ resolvedModel: string | undefined;
51
+ authorized: Set<string>;
52
+ }): ModelAccessDecision;
53
+ /** marker on the throw message so `runErrorRenderer` can reclassify it. */
54
+ export declare const MODEL_ACCESS_MARKER = "requested model is not available";
55
+ /**
56
+ * render the model-access failure body (used for both the thrown error and the
57
+ * PR comment / job summary). one shape, reason-branched why → CTA.
58
+ */
59
+ export declare function buildModelAccessError(input: {
60
+ reason: ModelAccessReason;
61
+ model: string;
62
+ owner: string;
63
+ name: string;
64
+ }): string;
@@ -5,10 +5,18 @@ export declare const JsonPayload: import("arktype/internal/variants/object.ts").
5
5
  version: string;
6
6
  prompt: string;
7
7
  model?: string | undefined;
8
+ modelExplicit?: boolean | undefined;
8
9
  triggerer?: string | undefined;
10
+ baseInstructions?: string | undefined;
9
11
  eventInstructions?: string;
10
12
  previousRunsNote?: string;
11
13
  event?: object;
14
+ xrepo?: {
15
+ mode: "all" | "explicit";
16
+ read: string[];
17
+ write: string[];
18
+ unavailable?: string[];
19
+ } | undefined;
12
20
  timeout?: string | undefined;
13
21
  progressComment?: {
14
22
  id: string;
@@ -32,11 +40,19 @@ export declare function resolvePayload(resolvedPromptInput: ResolvedPromptInput,
32
40
  "~pullfrog": true;
33
41
  version: string;
34
42
  model: string | undefined;
43
+ modelExplicit: boolean;
35
44
  prompt: string;
36
45
  triggerer: string | undefined;
46
+ baseInstructions: string | undefined;
37
47
  eventInstructions: string | undefined;
38
48
  previousRunsNote: string | undefined;
39
49
  event: PayloadEvent;
50
+ xrepo: {
51
+ mode: "all" | "explicit";
52
+ read: string[];
53
+ write: string[];
54
+ unavailable?: string[];
55
+ } | undefined;
40
56
  timeout: string | undefined;
41
57
  cwd: string | undefined;
42
58
  progressComment: {
@@ -36,6 +36,9 @@ export interface RepoSettings {
36
36
  learnings: string | null;
37
37
  learningsHeadings: LearningsHeading[];
38
38
  envAllowlist: string | null;
39
+ xrepoBrief: string | null;
40
+ xrepoLearnings: string | null;
41
+ xrepoLearningsHeadings: LearningsHeading[];
39
42
  }
40
43
  /**
41
44
  * Account-level billing plan. Orthogonal to repo-level OSS status. Mirrors
@@ -18,6 +18,16 @@ interface ResolveRunContextDataParams {
18
18
  octokit: OctokitWithPlugins;
19
19
  token: string;
20
20
  }
21
+ /**
22
+ * true when the action is pinned to a full commit SHA (vs the moving `@v0`
23
+ * tag or a branch). GitHub runs the action's `post:` hook (and reads
24
+ * `action.yml`) straight from the checked-out action ref, so a SHA pin freezes
25
+ * that checkout forever: the main agent still floats to the latest `^semver`
26
+ * via the npm bootstrap (this code is proof — it ran), but the post-run
27
+ * cleanup step keeps executing the pinned commit's source and never receives
28
+ * fixes. surfaced in the log and PR footers to nudge repos back to `@v0`.
29
+ */
30
+ export declare function isActionPinnedToSha(): boolean;
21
31
  /**
22
32
  * initialize run context data: parse context, fetch repo info and settings
23
33
  */