pullfrog 0.1.75 → 0.1.76

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.
@@ -42,6 +42,26 @@ interface AzureProviderEntry {
42
42
  * guaranteed present by `validateOpenAICompatibleSetup`.
43
43
  */
44
44
  export declare function openAICompatibleProvider(model: string | undefined): Record<string, OpenAICompatibleProviderEntry>;
45
+ /**
46
+ * Customer-gateway block for OPENCODE_CONFIG_CONTENT. opencode resolves a
47
+ * provider's endpoint as `options.baseURL ?? model.api.url` (`getSDK`), and
48
+ * models.dev declares no base-URL env var for any provider we catalog — so a
49
+ * config override is the ONLY lever that re-points one. Unlike
50
+ * `openAICompatibleProvider` this declares no `npm` and no models: the native
51
+ * provider and its catalog metadata (including token limits) stay exactly as
52
+ * they are, and only the host changes. `{}` when unset so the caller can spread
53
+ * it unconditionally.
54
+ *
55
+ * `anthropic/*` normally never reaches here — `resolveAgent` routes it to
56
+ * claude-code, which reads ANTHROPIC_BASE_URL natively — but keying off the
57
+ * resolved specifier rather than the harness means it still holds on the paths
58
+ * that do send a Claude model through opencode (`PULLFROG_AGENT=opencode`).
59
+ */
60
+ export declare function providerGatewayOverride(model: string | undefined): Record<string, {
61
+ options: {
62
+ baseURL: string;
63
+ };
64
+ }>;
45
65
  /**
46
66
  * Azure model block for OPENCODE_CONFIG_CONTENT. Unlike openai-compatible this
47
67
  * does NOT declare a provider — opencode has a native `azure` one and the env
package/dist/cli.mjs CHANGED
@@ -123000,7 +123000,7 @@ var import_picocolors = __toESM(require_picocolors(), 1);
123000
123000
  // package.json
123001
123001
  var package_default = {
123002
123002
  name: "pullfrog",
123003
- version: "0.1.75",
123003
+ version: "0.1.76",
123004
123004
  cliContractVersion: "1",
123005
123005
  type: "module",
123006
123006
  bin: {
@@ -124108,6 +124108,20 @@ var repoConfigFields = [
124108
124108
  nullable: true,
124109
124109
  description: "additional instructions for planning"
124110
124110
  },
124111
+ {
124112
+ key: "prompts.address-reviews",
124113
+ field: "AddressReviews",
124114
+ kind: "prompt",
124115
+ nullable: true,
124116
+ description: "additional instructions for addressing review feedback"
124117
+ },
124118
+ {
124119
+ key: "prompts.fix-ci",
124120
+ field: "Fix",
124121
+ kind: "prompt",
124122
+ nullable: true,
124123
+ description: "additional instructions for fixing CI failures"
124124
+ },
124111
124125
  { key: "mention.enabled", field: "mention", kind: "toggle", description: "respond to mentions" },
124112
124126
  {
124113
124127
  key: "mention.instructions",
@@ -126138,6 +126152,17 @@ var OPENAI_COMPATIBLE_API_KEY_ENV = "OPENAI_COMPATIBLE_API_KEY";
126138
126152
  var OPENAI_COMPATIBLE_MODEL_ENV = "OPENAI_COMPATIBLE_MODEL";
126139
126153
  var OPENAI_COMPATIBLE_CONTEXT_ENV = "OPENAI_COMPATIBLE_CONTEXT";
126140
126154
  var OPENAI_COMPATIBLE_MAX_OUTPUT_ENV = "OPENAI_COMPATIBLE_MAX_OUTPUT";
126155
+ var PROVIDER_GATEWAY_URL_ENV = {
126156
+ anthropic: "ANTHROPIC_BASE_URL",
126157
+ openai: "OPENAI_BASE_URL"
126158
+ };
126159
+ function getProviderGatewayUrl(specifier) {
126160
+ const slashIndex = specifier?.indexOf("/") ?? -1;
126161
+ if (!specifier || slashIndex <= 0) return void 0;
126162
+ const envVar = PROVIDER_GATEWAY_URL_ENV[specifier.slice(0, slashIndex)];
126163
+ if (!envVar) return void 0;
126164
+ return process.env[envVar]?.trim().replace(/\/$/, "") || void 0;
126165
+ }
126141
126166
  function isBedrockAnthropicId(bedrockModelId) {
126142
126167
  return bedrockModelId.toLowerCase().split(/[./:]/).includes("anthropic");
126143
126168
  }
@@ -127644,26 +127669,24 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
127644
127669
 
127645
127670
  2. Checkout the PR branch via \`${t2("checkout_pr")}\`.
127646
127671
 
127647
- 3. Fetch review comments via \`${t2("get_review_comments")}\`.
127672
+ 3. Fetch review comments via \`${t2("get_review_comments")}\` and read the full conversation in each relevant thread.
127648
127673
 
127649
- 4. For each comment:
127650
- - understand the feedback
127674
+ 4. For each thread:
127675
+ - **check intent first**: questions, explanations, and disagreement call for a reply, not a code change. Change code only when explicitly requested. Automatic review feedback on Pullfrog-authored PRs (\`address_scope: all\`) remains actionable without a separate request; later replies can narrow or withdraw that permission.
127676
+ - under \`address_scope: mentions\`, unmentioned threads are context only unless the user's review-body request explicitly includes them
127651
127677
  - **verify the finding yourself** against the actual code before deciding whether to apply \u2014 every comment (human or agent) is a hypothesis, not a directive. agent reviewers especially are fallible.
127652
- - you are searching for a solution that is **complete, minimal, and elegant** \u2014 you may need to think hard to find it. do not over-engineer, do not be over-defensive, **do not write AI slop**. reviewers bias toward *recommending additions*, and that bias has a recognizable slop texture: defensive checks for impossible cases, extra abstractions used once, comments restating obvious code, tests asserting tautologies, "just-in-case" guards, error handlers for cases the type system already rules out. reject those. evaluate whether applying the finding would leave the code more **sound, correct, AND elegant**; two-out-of-three is a signal to look harder for a fix that gets all three. if a request would add bloat \u2014 ceremony without commensurate correctness benefit \u2014 push back in your reply rather than mechanically applying it.
127653
- - if the request stands, make the code change using your native tools; otherwise reply explaining why
127678
+ - keep requested changes **sound, minimal, and elegant**. Reject defensive checks for impossible cases, single-use abstractions, redundant comments, and tautological tests rather than mechanically applying reviewer suggestions.
127679
+ - if intent permits a code change and the finding stands, implement it using your native tools; otherwise reply without editing
127654
127680
  - record what was done (or why nothing was done)
127655
127681
 
127656
127682
  5. Quality check:
127657
- - re-call \`${t2("get_review_comments")}\` with \`fresh: true\` before committing: reviewers keep commenting while you work, and any thread it now returns that you have not addressed is in scope for this run.
127658
- - test changes, then review the diff before committing \u2014 verify only intended changes are present, no debug artifacts remain, no fix turned out to be bloat in context (revert any that did), and the changes are clean enough that a senior engineer would approve without hesitation
127659
- - ${commitStep}
127683
+ - re-call \`${t2("get_review_comments")}\` with \`fresh: true\` before finishing, even when only replying. Apply the same scope and intent checks to new comments; respect requests to stop or change direction before committing.
127684
+ - if code changed, test and review the diff, remove unintended changes or bloat, then ${commitStep}
127660
127685
 
127661
- 6. Finalize. Reply + resolve are paired write actions: do BOTH or NEITHER for each thread.
127662
- - ${finalizeStep} (same push/prepush guidance as Build mode in *SYSTEM*)
127663
- - **if the push/commit fails**, call \`${t2("report_progress")}\` with the exact error and STOP \u2014 do NOT reply or resolve any thread until the fix is live on the remote. Resolving a thread without the fix landing misleads the reviewer.
127664
- - **once the fix is live on the remote**, for each thread you acted on:
127665
- - reply ONCE via \`${t2("reply_to_review_comment")}\`. The \`comment_id\` parameter takes the root comment's numeric \`id=\` (from the first \`comment author=...\` tag in the \`${t2("get_review_comments")}\` output) \u2014 NOT the \`thread=\` value; that's a separate GraphQL ID used by resolve. The runtime dedupes identical bodies within a session.
127666
- - **immediately** call \`${t2("resolve_review_thread")}\` with that thread's \`thread=\` value as \`thread_id\`. Resolve every thread where you (a) made the requested code change in full \u2014 partial fixes leave the thread open \u2014 OR (b) replied with a substantive answer the user explicitly asked for. Do NOT resolve threads where you pushed back on the request and the disagreement is unresolved; leave those open for the human to mediate.
127686
+ 6. Finalize:
127687
+ - **only if code changed**, ${finalizeStep} (same push/prepush guidance as Build mode in *SYSTEM*). If push/commit fails, report the exact error and STOP without claiming a fix or resolving its threads.
127688
+ - reply ONCE per relevant thread via \`${t2("reply_to_review_comment")}\`, including when no code changed. The \`comment_id\` is the root comment's numeric \`id=\`, NOT the \`thread=\` value from \`${t2("get_review_comments")}\`. The runtime dedupes identical bodies within a session.
127689
+ - resolve a thread via \`${t2("resolve_review_thread")}\` (using \`thread=\` as \`thread_id\`) only after its requested fix is fully on the remote, or the user explicitly asks to resolve it. Leave discussion-only replies, partial fixes, and disagreements open.
127667
127690
  - call \`${t2("report_progress")}\` with a brief summary. If your last \`${t2("get_review_comments")}\` turned up threads you did not address, name them there \u2014 a review arriving this late is suppressed as a duplicate and nothing else will pick it up.`
127668
127691
  },
127669
127692
  // Review and IncrementalReview route the minimum reviewfrog specialists
@@ -129640,6 +129663,19 @@ function warnIfNativeEditsUnavailable(ctx) {
129640
129663
  "\xBB this run cannot change files: codex's native write tool depends on bubblewrap, which Ubuntu 24.04 blocks (https://github.com/openai/codex/issues/29908), and the MCP shell is disabled for this repo. read-only work is unaffected \u2014 set `shell: restricted` if the agent needs to edit anything."
129641
129664
  );
129642
129665
  }
129666
+ var CODEX_GATEWAY_PROVIDER = "pullfrog-gateway";
129667
+ function gatewayProvider(gatewayUrl) {
129668
+ if (!gatewayUrl) return [];
129669
+ return [
129670
+ `[model_providers.${CODEX_GATEWAY_PROVIDER}]`,
129671
+ 'name = "OpenAI gateway"',
129672
+ `base_url = ${JSON.stringify(gatewayUrl)}`,
129673
+ 'wire_api = "responses"',
129674
+ // where the harness puts the run's OPENAI_API_KEY
129675
+ 'env_key = "CODEX_API_KEY"',
129676
+ ""
129677
+ ];
129678
+ }
129643
129679
  function writeCodexConfig(params) {
129644
129680
  const shellEnabled = params.ctx.payload.shell === "enabled";
129645
129681
  const sandboxMode = params.ctx.payload.push === "disabled" ? "read-only" : "workspace-write";
@@ -129656,6 +129692,7 @@ function writeCodexConfig(params) {
129656
129692
  'web_search = "live"',
129657
129693
  ...params.model ? [`model = ${JSON.stringify(params.model)}`] : [],
129658
129694
  ...params.effortRung ? [`model_reasoning_effort = ${JSON.stringify(params.effortRung)}`] : [],
129695
+ ...params.gatewayUrl ? [`model_provider = ${JSON.stringify(CODEX_GATEWAY_PROVIDER)}`] : [],
129659
129696
  "",
129660
129697
  "[features]",
129661
129698
  tomlBool(CODEX_DISABLED_FEATURES, false),
@@ -129685,6 +129722,7 @@ function writeCodexConfig(params) {
129685
129722
  "tool_timeout_sec = 660",
129686
129723
  "",
129687
129724
  ...repoMcpApprovals(repoDir),
129725
+ ...gatewayProvider(params.gatewayUrl),
129688
129726
  // UNTRUSTED, deliberately, and this one line is the whole config-precedence
129689
129727
  // boundary. Codex loads `<repo>/.codex/config.toml` as a layer that
129690
129728
  // OUTRANKS ours (project 25 vs user 20), and its denylist strips none of
@@ -129953,7 +129991,11 @@ var codex = agent({
129953
129991
  ctx,
129954
129992
  codexHome,
129955
129993
  model: resolveCodexModel(ctx),
129956
- effortRung: effort.rung && CODEX_EFFORTS.includes(effort.rung) ? effort.rung : void 0
129994
+ effortRung: effort.rung && CODEX_EFFORTS.includes(effort.rung) ? effort.rung : void 0,
129995
+ // keyed off OpenAI, not the resolved model: codex serves nothing else, an
129996
+ // auto-selected run has no model, and a ChatGPT-subscription token is
129997
+ // valid only at ChatGPT's own backend
129998
+ gatewayUrl: codexHomeAuth ? void 0 : getProviderGatewayUrl("openai/")
129957
129999
  });
129958
130000
  if (codexHomeAuth) {
129959
130001
  core3.saveState(
@@ -135720,6 +135762,14 @@ function openAICompatibleProvider(model) {
135720
135762
  }
135721
135763
  };
135722
135764
  }
135765
+ var OPENCODE_GATEWAY_PATH = { anthropic: "/v1" };
135766
+ function providerGatewayOverride(model) {
135767
+ const gatewayUrl = getProviderGatewayUrl(model);
135768
+ if (!model || !gatewayUrl) return {};
135769
+ const provider2 = model.slice(0, model.indexOf("/"));
135770
+ const baseURL = `${gatewayUrl}${OPENCODE_GATEWAY_PATH[provider2] ?? ""}`;
135771
+ return { [provider2]: { options: { baseURL } } };
135772
+ }
135723
135773
  function azureProvider(model) {
135724
135774
  const prefix = `${AZURE_PROVIDER}/`;
135725
135775
  if (!model?.startsWith(prefix)) return {};
@@ -135835,7 +135885,8 @@ function buildSecurityConfig(ctx, model) {
135835
135885
  provider: {
135836
135886
  openrouter: { models: kimiOpenRouterProviderOverrides() },
135837
135887
  ...openAICompatibleProvider(model),
135838
- ...azureProvider(model)
135888
+ ...azureProvider(model),
135889
+ ...providerGatewayOverride(model)
135839
135890
  }
135840
135891
  };
135841
135892
  if (model) {
@@ -214306,7 +214357,7 @@ function GetReviewCommentsTool(ctx) {
214306
214357
  threadCount: threadBlocks.length,
214307
214358
  commentsPath,
214308
214359
  toc: formatted.toc,
214309
- instructions: `the file at commentsPath contains ${threadBlocks.length} review threads with full conversation history. comments marked with * are from the target review (${params.review_id}); any other thread is open feedback on this PR that is also yours to address, including comments that landed after this run started. the TOC shows each thread's file:line and the line number where it appears in the file. to read a specific thread, use: grep -A 50 "^## <file:line>" ${commentsPath} (replace <file:line> with the path from the TOC, e.g. "^## action/utils/foo.ts:42"). address each thread in order, working through one file at a time.`
214360
+ instructions: `the file at commentsPath contains ${threadBlocks.length} review threads with full conversation history. comments marked with * are from the target review (${params.review_id}); any other thread is open feedback on this PR, including comments that landed after this run started. Being returned does not authorize a code change; follow the task's scope and each thread's latest request. the TOC shows each thread's file:line and the line number where it appears in the file. to read a specific thread, use: grep -A 50 "^## <file:line>" ${commentsPath} (replace <file:line> with the path from the TOC, e.g. "^## action/utils/foo.ts:42"). read each full thread before deciding whether it calls for a reply or a code change.`
214310
214361
  };
214311
214362
  })
214312
214363
  });
@@ -214429,8 +214480,7 @@ An existing plan comment was found for this issue. Update that comment with the
214429
214480
  };
214430
214481
  }
214431
214482
  var modeInstructionParent = {
214432
- IncrementalReview: "Review",
214433
- Fix: "Build"
214483
+ IncrementalReview: "Review"
214434
214484
  };
214435
214485
  function buildOrchestratorGuidance(ctx, mode, overrideGuidance) {
214436
214486
  const hardcoded = overrideGuidance ?? mode.prompt ?? "";
@@ -215642,18 +215692,13 @@ function hasEnvVar4(name) {
215642
215692
  const value2 = process.env[name];
215643
215693
  return typeof value2 === "string" && value2.length > 0;
215644
215694
  }
215645
- var HOST_OVERRIDES = {
215646
- ANTHROPIC_API_KEY: "ANTHROPIC_BASE_URL",
215647
- CLAUDE_CODE_OAUTH_TOKEN: "ANTHROPIC_BASE_URL"
215648
- };
215649
215695
  function envVarsFor(model) {
215650
215696
  return model.includes("/") ? getModelEnvVars(model) : [];
215651
215697
  }
215652
215698
  async function checkOne(params) {
215653
215699
  const value2 = process.env[params.envVar];
215654
215700
  if (!value2) return null;
215655
- const hostOverride = HOST_OVERRIDES[params.envVar];
215656
- if (hostOverride && hasEnvVar4(hostOverride)) return null;
215701
+ if (getProviderGatewayUrl(params.model)) return null;
215657
215702
  if (params.envVar === "CLAUDE_CODE_OAUTH_TOKEN") {
215658
215703
  const preflight = await preflightClaudeSubscription({
215659
215704
  token: value2,
@@ -219730,7 +219775,7 @@ function resolveTarget2(repo) {
219730
219775
  return { owner: match3[1], repo: match3[2] };
219731
219776
  }
219732
219777
  function cliVersion() {
219733
- const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.75");
219778
+ const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.76");
219734
219779
  if (!parts) return "0.0.0";
219735
219780
  return `${Number(parts[1])}.${Number(parts[2])}.${Number(parts[3])}`;
219736
219781
  }
@@ -219897,7 +219942,7 @@ async function runCli8(input2) {
219897
219942
  }
219898
219943
 
219899
219944
  // cli.ts
219900
- var VERSION10 = "0.1.75";
219945
+ var VERSION10 = "0.1.76";
219901
219946
  var bin = basename2(process.argv[1] || "");
219902
219947
  var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
219903
219948
  var rawArgs = process.argv.slice(2);
@@ -52,7 +52,7 @@ export declare function findDanglingPromptToolRefs(params: {
52
52
  export type { EffortPosition } from "./effort.ts";
53
53
  export { DEFAULT_EFFORT_POSITION, EFFORT_ALIASES, isEffortPosition, offeredRungs, parseEffortPosition, resolveRung, rungLabel, rungPosition, SUBSIDY_RUNG, } from "./effort.ts";
54
54
  export type { AutoTier, ModelAlias, ModelProvider, ProviderConfig } from "./models.ts";
55
- export { AUTO_EFFICIENT, AUTO_INTELLIGENT, DEFAULT_PROXY_MODEL, defaultAutoTier, getAutoSelectHintModel, getModelEffortLevels, getModelEnvVars, getModelManagedCredentials, getModelProvider, getProviderDisplayName, isAutoTier, isCardGatedModel, isOssAllowedModel, modelAliases, modelHasStoredAuth, OSS_MODEL_ALLOWLIST, OSS_RECOMMENDED_MODEL, ossFundedModelNames, parseModel, providers, resolveAutoTier, resolveCliModel, resolveDisplayAlias, resolveModelRung, resolveModelSlug, resolveOpenRouterModel, } from "./models.ts";
55
+ export { AUTO_EFFICIENT, AUTO_INTELLIGENT, DEFAULT_PROXY_MODEL, defaultAutoTier, getAutoSelectHintModel, getModelEffortLevels, getModelEnvVars, getModelManagedCredentials, getModelProvider, getProviderDisplayName, isAutoTier, isCardGatedModel, isOssAllowedModel, modelAliases, modelHasStoredAuth, OSS_MODEL_ALLOWLIST, OSS_RECOMMENDED_MODEL, ossFundedModelNames, PROVIDER_GATEWAY_URL_ENV, parseModel, providers, resolveAutoTier, resolveCliModel, resolveDisplayAlias, resolveModelRung, resolveModelSlug, resolveOpenRouterModel, } from "./models.ts";
56
56
  export type ToolPermission = "disabled" | "enabled";
57
57
  export type ShellPermission = "disabled" | "restricted" | "enabled";
58
58
  export type PushPermission = "disabled" | "restricted" | "enabled";
package/dist/index.js CHANGED
@@ -108971,6 +108971,17 @@ var OPENAI_COMPATIBLE_API_KEY_ENV = "OPENAI_COMPATIBLE_API_KEY";
108971
108971
  var OPENAI_COMPATIBLE_MODEL_ENV = "OPENAI_COMPATIBLE_MODEL";
108972
108972
  var OPENAI_COMPATIBLE_CONTEXT_ENV = "OPENAI_COMPATIBLE_CONTEXT";
108973
108973
  var OPENAI_COMPATIBLE_MAX_OUTPUT_ENV = "OPENAI_COMPATIBLE_MAX_OUTPUT";
108974
+ var PROVIDER_GATEWAY_URL_ENV = {
108975
+ anthropic: "ANTHROPIC_BASE_URL",
108976
+ openai: "OPENAI_BASE_URL"
108977
+ };
108978
+ function getProviderGatewayUrl(specifier) {
108979
+ const slashIndex = specifier?.indexOf("/") ?? -1;
108980
+ if (!specifier || slashIndex <= 0) return void 0;
108981
+ const envVar = PROVIDER_GATEWAY_URL_ENV[specifier.slice(0, slashIndex)];
108982
+ if (!envVar) return void 0;
108983
+ return process.env[envVar]?.trim().replace(/\/$/, "") || void 0;
108984
+ }
108974
108985
  function isBedrockAnthropicId(bedrockModelId) {
108975
108986
  return bedrockModelId.toLowerCase().split(/[./:]/).includes("anthropic");
108976
108987
  }
@@ -109795,7 +109806,7 @@ var import_semver = __toESM(require_semver2(), 1);
109795
109806
  // package.json
109796
109807
  var package_default = {
109797
109808
  name: "pullfrog",
109798
- version: "0.1.75",
109809
+ version: "0.1.76",
109799
109810
  cliContractVersion: "1",
109800
109811
  type: "module",
109801
109812
  bin: {
@@ -110594,26 +110605,24 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
110594
110605
 
110595
110606
  2. Checkout the PR branch via \`${t("checkout_pr")}\`.
110596
110607
 
110597
- 3. Fetch review comments via \`${t("get_review_comments")}\`.
110608
+ 3. Fetch review comments via \`${t("get_review_comments")}\` and read the full conversation in each relevant thread.
110598
110609
 
110599
- 4. For each comment:
110600
- - understand the feedback
110610
+ 4. For each thread:
110611
+ - **check intent first**: questions, explanations, and disagreement call for a reply, not a code change. Change code only when explicitly requested. Automatic review feedback on Pullfrog-authored PRs (\`address_scope: all\`) remains actionable without a separate request; later replies can narrow or withdraw that permission.
110612
+ - under \`address_scope: mentions\`, unmentioned threads are context only unless the user's review-body request explicitly includes them
110601
110613
  - **verify the finding yourself** against the actual code before deciding whether to apply \u2014 every comment (human or agent) is a hypothesis, not a directive. agent reviewers especially are fallible.
110602
- - you are searching for a solution that is **complete, minimal, and elegant** \u2014 you may need to think hard to find it. do not over-engineer, do not be over-defensive, **do not write AI slop**. reviewers bias toward *recommending additions*, and that bias has a recognizable slop texture: defensive checks for impossible cases, extra abstractions used once, comments restating obvious code, tests asserting tautologies, "just-in-case" guards, error handlers for cases the type system already rules out. reject those. evaluate whether applying the finding would leave the code more **sound, correct, AND elegant**; two-out-of-three is a signal to look harder for a fix that gets all three. if a request would add bloat \u2014 ceremony without commensurate correctness benefit \u2014 push back in your reply rather than mechanically applying it.
110603
- - if the request stands, make the code change using your native tools; otherwise reply explaining why
110614
+ - keep requested changes **sound, minimal, and elegant**. Reject defensive checks for impossible cases, single-use abstractions, redundant comments, and tautological tests rather than mechanically applying reviewer suggestions.
110615
+ - if intent permits a code change and the finding stands, implement it using your native tools; otherwise reply without editing
110604
110616
  - record what was done (or why nothing was done)
110605
110617
 
110606
110618
  5. Quality check:
110607
- - re-call \`${t("get_review_comments")}\` with \`fresh: true\` before committing: reviewers keep commenting while you work, and any thread it now returns that you have not addressed is in scope for this run.
110608
- - test changes, then review the diff before committing \u2014 verify only intended changes are present, no debug artifacts remain, no fix turned out to be bloat in context (revert any that did), and the changes are clean enough that a senior engineer would approve without hesitation
110609
- - ${commitStep}
110619
+ - re-call \`${t("get_review_comments")}\` with \`fresh: true\` before finishing, even when only replying. Apply the same scope and intent checks to new comments; respect requests to stop or change direction before committing.
110620
+ - if code changed, test and review the diff, remove unintended changes or bloat, then ${commitStep}
110610
110621
 
110611
- 6. Finalize. Reply + resolve are paired write actions: do BOTH or NEITHER for each thread.
110612
- - ${finalizeStep} (same push/prepush guidance as Build mode in *SYSTEM*)
110613
- - **if the push/commit fails**, call \`${t("report_progress")}\` with the exact error and STOP \u2014 do NOT reply or resolve any thread until the fix is live on the remote. Resolving a thread without the fix landing misleads the reviewer.
110614
- - **once the fix is live on the remote**, for each thread you acted on:
110615
- - reply ONCE via \`${t("reply_to_review_comment")}\`. The \`comment_id\` parameter takes the root comment's numeric \`id=\` (from the first \`comment author=...\` tag in the \`${t("get_review_comments")}\` output) \u2014 NOT the \`thread=\` value; that's a separate GraphQL ID used by resolve. The runtime dedupes identical bodies within a session.
110616
- - **immediately** call \`${t("resolve_review_thread")}\` with that thread's \`thread=\` value as \`thread_id\`. Resolve every thread where you (a) made the requested code change in full \u2014 partial fixes leave the thread open \u2014 OR (b) replied with a substantive answer the user explicitly asked for. Do NOT resolve threads where you pushed back on the request and the disagreement is unresolved; leave those open for the human to mediate.
110622
+ 6. Finalize:
110623
+ - **only if code changed**, ${finalizeStep} (same push/prepush guidance as Build mode in *SYSTEM*). If push/commit fails, report the exact error and STOP without claiming a fix or resolving its threads.
110624
+ - reply ONCE per relevant thread via \`${t("reply_to_review_comment")}\`, including when no code changed. The \`comment_id\` is the root comment's numeric \`id=\`, NOT the \`thread=\` value from \`${t("get_review_comments")}\`. The runtime dedupes identical bodies within a session.
110625
+ - resolve a thread via \`${t("resolve_review_thread")}\` (using \`thread=\` as \`thread_id\`) only after its requested fix is fully on the remote, or the user explicitly asks to resolve it. Leave discussion-only replies, partial fixes, and disagreements open.
110617
110626
  - call \`${t("report_progress")}\` with a brief summary. If your last \`${t("get_review_comments")}\` turned up threads you did not address, name them there \u2014 a review arriving this late is suppressed as a duplicate and nothing else will pick it up.`
110618
110627
  },
110619
110628
  // Review and IncrementalReview route the minimum reviewfrog specialists
@@ -112514,6 +112523,19 @@ function warnIfNativeEditsUnavailable(ctx) {
112514
112523
  "\xBB this run cannot change files: codex's native write tool depends on bubblewrap, which Ubuntu 24.04 blocks (https://github.com/openai/codex/issues/29908), and the MCP shell is disabled for this repo. read-only work is unaffected \u2014 set `shell: restricted` if the agent needs to edit anything."
112515
112524
  );
112516
112525
  }
112526
+ var CODEX_GATEWAY_PROVIDER = "pullfrog-gateway";
112527
+ function gatewayProvider(gatewayUrl) {
112528
+ if (!gatewayUrl) return [];
112529
+ return [
112530
+ `[model_providers.${CODEX_GATEWAY_PROVIDER}]`,
112531
+ 'name = "OpenAI gateway"',
112532
+ `base_url = ${JSON.stringify(gatewayUrl)}`,
112533
+ 'wire_api = "responses"',
112534
+ // where the harness puts the run's OPENAI_API_KEY
112535
+ 'env_key = "CODEX_API_KEY"',
112536
+ ""
112537
+ ];
112538
+ }
112517
112539
  function writeCodexConfig(params) {
112518
112540
  const shellEnabled = params.ctx.payload.shell === "enabled";
112519
112541
  const sandboxMode = params.ctx.payload.push === "disabled" ? "read-only" : "workspace-write";
@@ -112530,6 +112552,7 @@ function writeCodexConfig(params) {
112530
112552
  'web_search = "live"',
112531
112553
  ...params.model ? [`model = ${JSON.stringify(params.model)}`] : [],
112532
112554
  ...params.effortRung ? [`model_reasoning_effort = ${JSON.stringify(params.effortRung)}`] : [],
112555
+ ...params.gatewayUrl ? [`model_provider = ${JSON.stringify(CODEX_GATEWAY_PROVIDER)}`] : [],
112533
112556
  "",
112534
112557
  "[features]",
112535
112558
  tomlBool(CODEX_DISABLED_FEATURES, false),
@@ -112559,6 +112582,7 @@ function writeCodexConfig(params) {
112559
112582
  "tool_timeout_sec = 660",
112560
112583
  "",
112561
112584
  ...repoMcpApprovals(repoDir),
112585
+ ...gatewayProvider(params.gatewayUrl),
112562
112586
  // UNTRUSTED, deliberately, and this one line is the whole config-precedence
112563
112587
  // boundary. Codex loads `<repo>/.codex/config.toml` as a layer that
112564
112588
  // OUTRANKS ours (project 25 vs user 20), and its denylist strips none of
@@ -112827,7 +112851,11 @@ var codex = agent({
112827
112851
  ctx,
112828
112852
  codexHome,
112829
112853
  model: resolveCodexModel(ctx),
112830
- effortRung: effort.rung && CODEX_EFFORTS.includes(effort.rung) ? effort.rung : void 0
112854
+ effortRung: effort.rung && CODEX_EFFORTS.includes(effort.rung) ? effort.rung : void 0,
112855
+ // keyed off OpenAI, not the resolved model: codex serves nothing else, an
112856
+ // auto-selected run has no model, and a ChatGPT-subscription token is
112857
+ // valid only at ChatGPT's own backend
112858
+ gatewayUrl: codexHomeAuth ? void 0 : getProviderGatewayUrl("openai/")
112831
112859
  });
112832
112860
  if (codexHomeAuth) {
112833
112861
  core3.saveState(
@@ -118594,6 +118622,14 @@ function openAICompatibleProvider(model) {
118594
118622
  }
118595
118623
  };
118596
118624
  }
118625
+ var OPENCODE_GATEWAY_PATH = { anthropic: "/v1" };
118626
+ function providerGatewayOverride(model) {
118627
+ const gatewayUrl = getProviderGatewayUrl(model);
118628
+ if (!model || !gatewayUrl) return {};
118629
+ const provider2 = model.slice(0, model.indexOf("/"));
118630
+ const baseURL = `${gatewayUrl}${OPENCODE_GATEWAY_PATH[provider2] ?? ""}`;
118631
+ return { [provider2]: { options: { baseURL } } };
118632
+ }
118597
118633
  function azureProvider(model) {
118598
118634
  const prefix = `${AZURE_PROVIDER}/`;
118599
118635
  if (!model?.startsWith(prefix)) return {};
@@ -118709,7 +118745,8 @@ function buildSecurityConfig(ctx, model) {
118709
118745
  provider: {
118710
118746
  openrouter: { models: kimiOpenRouterProviderOverrides() },
118711
118747
  ...openAICompatibleProvider(model),
118712
- ...azureProvider(model)
118748
+ ...azureProvider(model),
118749
+ ...providerGatewayOverride(model)
118713
118750
  }
118714
118751
  };
118715
118752
  if (model) {
@@ -197180,7 +197217,7 @@ function GetReviewCommentsTool(ctx) {
197180
197217
  threadCount: threadBlocks.length,
197181
197218
  commentsPath,
197182
197219
  toc: formatted.toc,
197183
- instructions: `the file at commentsPath contains ${threadBlocks.length} review threads with full conversation history. comments marked with * are from the target review (${params.review_id}); any other thread is open feedback on this PR that is also yours to address, including comments that landed after this run started. the TOC shows each thread's file:line and the line number where it appears in the file. to read a specific thread, use: grep -A 50 "^## <file:line>" ${commentsPath} (replace <file:line> with the path from the TOC, e.g. "^## action/utils/foo.ts:42"). address each thread in order, working through one file at a time.`
197220
+ instructions: `the file at commentsPath contains ${threadBlocks.length} review threads with full conversation history. comments marked with * are from the target review (${params.review_id}); any other thread is open feedback on this PR, including comments that landed after this run started. Being returned does not authorize a code change; follow the task's scope and each thread's latest request. the TOC shows each thread's file:line and the line number where it appears in the file. to read a specific thread, use: grep -A 50 "^## <file:line>" ${commentsPath} (replace <file:line> with the path from the TOC, e.g. "^## action/utils/foo.ts:42"). read each full thread before deciding whether it calls for a reply or a code change.`
197184
197221
  };
197185
197222
  })
197186
197223
  });
@@ -197303,8 +197340,7 @@ An existing plan comment was found for this issue. Update that comment with the
197303
197340
  };
197304
197341
  }
197305
197342
  var modeInstructionParent = {
197306
- IncrementalReview: "Review",
197307
- Fix: "Build"
197343
+ IncrementalReview: "Review"
197308
197344
  };
197309
197345
  function buildOrchestratorGuidance(ctx, mode, overrideGuidance) {
197310
197346
  const hardcoded = overrideGuidance ?? mode.prompt ?? "";
@@ -198516,18 +198552,13 @@ function hasEnvVar4(name) {
198516
198552
  const value2 = process.env[name];
198517
198553
  return typeof value2 === "string" && value2.length > 0;
198518
198554
  }
198519
- var HOST_OVERRIDES = {
198520
- ANTHROPIC_API_KEY: "ANTHROPIC_BASE_URL",
198521
- CLAUDE_CODE_OAUTH_TOKEN: "ANTHROPIC_BASE_URL"
198522
- };
198523
198555
  function envVarsFor(model) {
198524
198556
  return model.includes("/") ? getModelEnvVars(model) : [];
198525
198557
  }
198526
198558
  async function checkOne(params) {
198527
198559
  const value2 = process.env[params.envVar];
198528
198560
  if (!value2) return null;
198529
- const hostOverride = HOST_OVERRIDES[params.envVar];
198530
- if (hostOverride && hasEnvVar4(hostOverride)) return null;
198561
+ if (getProviderGatewayUrl(params.model)) return null;
198531
198562
  if (params.envVar === "CLAUDE_CODE_OAUTH_TOKEN") {
198532
198563
  const preflight = await preflightClaudeSubscription({
198533
198564
  token: value2,
@@ -6,7 +6,7 @@ export { CLI_CONTRACT_HEADER, CLI_CONTRACT_VERSION, CLI_UPGRADE_MESSAGE } from "
6
6
  export type { ConfigField, ConfigScope, ConfigValue } from "../configuration.ts";
7
7
  export { configEntrySchema, configFields, configResponseSchema, configValueSchema, } from "../configuration.ts";
8
8
  export type { AuthorPermission, AutoTier, EffortPosition, ModelAlias, ModelProvider, Payload, PayloadEvent, ProviderConfig, PushPermission, ShellPermission, ToolPermission, WriteablePayload, XrepoConfig, } from "../external.ts";
9
- export { AUTO_EFFICIENT, AUTO_INTELLIGENT, DEFAULT_EFFORT_POSITION, DEFAULT_PROXY_MODEL, defaultAutoTier, EFFORT_ALIASES, getAutoSelectHintModel, getModelEffortLevels, getModelEnvVars, getModelManagedCredentials, getModelProvider, getProviderDisplayName, isAutoTier, isCardGatedModel, isEffortPosition, isOssAllowedModel, modelAliases, modelHasStoredAuth, OSS_MODEL_ALLOWLIST, OSS_RECOMMENDED_MODEL, offeredRungs, ossFundedModelNames, parseEffortPosition, parseModel, providers, pullfrogMcpName, resolveAutoTier, resolveCliModel, resolveDisplayAlias, resolveModelRung, resolveModelSlug, resolveOpenRouterModel, resolveRung, rungLabel, rungPosition, SUBSIDY_RUNG, } from "../external.ts";
9
+ export { AUTO_EFFICIENT, AUTO_INTELLIGENT, DEFAULT_EFFORT_POSITION, DEFAULT_PROXY_MODEL, defaultAutoTier, EFFORT_ALIASES, getAutoSelectHintModel, getModelEffortLevels, getModelEnvVars, getModelManagedCredentials, getModelProvider, getProviderDisplayName, isAutoTier, isCardGatedModel, isEffortPosition, isOssAllowedModel, modelAliases, modelHasStoredAuth, OSS_MODEL_ALLOWLIST, OSS_RECOMMENDED_MODEL, offeredRungs, ossFundedModelNames, PROVIDER_GATEWAY_URL_ENV, parseEffortPosition, parseModel, providers, pullfrogMcpName, resolveAutoTier, resolveCliModel, resolveDisplayAlias, resolveModelRung, resolveModelSlug, resolveOpenRouterModel, resolveRung, rungLabel, rungPosition, SUBSIDY_RUNG, } from "../external.ts";
10
10
  export type { Mode } from "../modes.ts";
11
11
  export { modes } from "../modes.ts";
12
12
  export { commercialPaywallBody } from "../utils/billingErrors.ts";
package/dist/internal.js CHANGED
@@ -8,7 +8,7 @@ var __export = (target, all) => {
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "pullfrog",
11
- version: "0.1.75",
11
+ version: "0.1.76",
12
12
  cliContractVersion: "1",
13
13
  type: "module",
14
14
  bin: {
@@ -628,7 +628,7 @@ __export(core_exports2, {
628
628
  parse: () => parse,
629
629
  parseAsync: () => parseAsync,
630
630
  prettifyError: () => prettifyError,
631
- process: () => process,
631
+ process: () => process2,
632
632
  regexes: () => regexes_exports,
633
633
  registry: () => registry,
634
634
  safeDecode: () => safeDecode,
@@ -11029,7 +11029,7 @@ function initializeContext(params) {
11029
11029
  external: params?.external ?? void 0
11030
11030
  };
11031
11031
  }
11032
- function process(schema, ctx, _params = { path: [], schemaPath: [] }) {
11032
+ function process2(schema, ctx, _params = { path: [], schemaPath: [] }) {
11033
11033
  var _a2;
11034
11034
  const def = schema._zod.def;
11035
11035
  const seen = ctx.seen.get(schema);
@@ -11066,7 +11066,7 @@ function process(schema, ctx, _params = { path: [], schemaPath: [] }) {
11066
11066
  if (parent) {
11067
11067
  if (!result.ref)
11068
11068
  result.ref = parent;
11069
- process(parent, ctx, params);
11069
+ process2(parent, ctx, params);
11070
11070
  ctx.seen.get(parent).isParent = true;
11071
11071
  }
11072
11072
  }
@@ -11347,14 +11347,14 @@ function isTransforming(_schema, _ctx) {
11347
11347
  }
11348
11348
  var createToJSONSchemaMethod = (schema, processors = {}) => (params) => {
11349
11349
  const ctx = initializeContext({ ...params, processors });
11350
- process(schema, ctx);
11350
+ process2(schema, ctx);
11351
11351
  extractDefs(ctx, schema);
11352
11352
  return finalize(ctx, schema);
11353
11353
  };
11354
11354
  var createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) => {
11355
11355
  const { libraryOptions, target } = params ?? {};
11356
11356
  const ctx = initializeContext({ ...libraryOptions ?? {}, target, io, processors });
11357
- process(schema, ctx);
11357
+ process2(schema, ctx);
11358
11358
  extractDefs(ctx, schema);
11359
11359
  return finalize(ctx, schema);
11360
11360
  };
@@ -11611,7 +11611,7 @@ var arrayProcessor = (schema, ctx, _json, params) => {
11611
11611
  if (typeof maximum === "number")
11612
11612
  json2.maxItems = maximum;
11613
11613
  json2.type = "array";
11614
- json2.items = process(def.element, ctx, { ...params, path: [...params.path, "items"] });
11614
+ json2.items = process2(def.element, ctx, { ...params, path: [...params.path, "items"] });
11615
11615
  };
11616
11616
  var objectProcessor = (schema, ctx, _json, params) => {
11617
11617
  const json2 = _json;
@@ -11620,7 +11620,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
11620
11620
  json2.properties = {};
11621
11621
  const shape = def.shape;
11622
11622
  for (const key in shape) {
11623
- json2.properties[key] = process(shape[key], ctx, {
11623
+ json2.properties[key] = process2(shape[key], ctx, {
11624
11624
  ...params,
11625
11625
  path: [...params.path, "properties", key]
11626
11626
  });
@@ -11643,7 +11643,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
11643
11643
  if (ctx.io === "output")
11644
11644
  json2.additionalProperties = false;
11645
11645
  } else if (def.catchall) {
11646
- json2.additionalProperties = process(def.catchall, ctx, {
11646
+ json2.additionalProperties = process2(def.catchall, ctx, {
11647
11647
  ...params,
11648
11648
  path: [...params.path, "additionalProperties"]
11649
11649
  });
@@ -11652,7 +11652,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
11652
11652
  var unionProcessor = (schema, ctx, json2, params) => {
11653
11653
  const def = schema._zod.def;
11654
11654
  const isExclusive = def.inclusive === false;
11655
- const options = def.options.map((x, i) => process(x, ctx, {
11655
+ const options = def.options.map((x, i) => process2(x, ctx, {
11656
11656
  ...params,
11657
11657
  path: [...params.path, isExclusive ? "oneOf" : "anyOf", i]
11658
11658
  }));
@@ -11664,11 +11664,11 @@ var unionProcessor = (schema, ctx, json2, params) => {
11664
11664
  };
11665
11665
  var intersectionProcessor = (schema, ctx, json2, params) => {
11666
11666
  const def = schema._zod.def;
11667
- const a = process(def.left, ctx, {
11667
+ const a = process2(def.left, ctx, {
11668
11668
  ...params,
11669
11669
  path: [...params.path, "allOf", 0]
11670
11670
  });
11671
- const b = process(def.right, ctx, {
11671
+ const b = process2(def.right, ctx, {
11672
11672
  ...params,
11673
11673
  path: [...params.path, "allOf", 1]
11674
11674
  });
@@ -11685,11 +11685,11 @@ var tupleProcessor = (schema, ctx, _json, params) => {
11685
11685
  json2.type = "array";
11686
11686
  const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
11687
11687
  const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
11688
- const prefixItems = def.items.map((x, i) => process(x, ctx, {
11688
+ const prefixItems = def.items.map((x, i) => process2(x, ctx, {
11689
11689
  ...params,
11690
11690
  path: [...params.path, prefixPath, i]
11691
11691
  }));
11692
- const rest = def.rest ? process(def.rest, ctx, {
11692
+ const rest = def.rest ? process2(def.rest, ctx, {
11693
11693
  ...params,
11694
11694
  path: [...params.path, restPath, ...ctx.target === "openapi-3.0" ? [def.items.length] : []]
11695
11695
  }) : null;
@@ -11729,7 +11729,7 @@ var recordProcessor = (schema, ctx, _json, params) => {
11729
11729
  const keyBag = keyType._zod.bag;
11730
11730
  const patterns = keyBag?.patterns;
11731
11731
  if (def.mode === "loose" && patterns && patterns.size > 0) {
11732
- const valueSchema = process(def.valueType, ctx, {
11732
+ const valueSchema = process2(def.valueType, ctx, {
11733
11733
  ...params,
11734
11734
  path: [...params.path, "patternProperties", "*"]
11735
11735
  });
@@ -11739,12 +11739,12 @@ var recordProcessor = (schema, ctx, _json, params) => {
11739
11739
  }
11740
11740
  } else {
11741
11741
  if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") {
11742
- json2.propertyNames = process(def.keyType, ctx, {
11742
+ json2.propertyNames = process2(def.keyType, ctx, {
11743
11743
  ...params,
11744
11744
  path: [...params.path, "propertyNames"]
11745
11745
  });
11746
11746
  }
11747
- json2.additionalProperties = process(def.valueType, ctx, {
11747
+ json2.additionalProperties = process2(def.valueType, ctx, {
11748
11748
  ...params,
11749
11749
  path: [...params.path, "additionalProperties"]
11750
11750
  });
@@ -11759,7 +11759,7 @@ var recordProcessor = (schema, ctx, _json, params) => {
11759
11759
  };
11760
11760
  var nullableProcessor = (schema, ctx, json2, params) => {
11761
11761
  const def = schema._zod.def;
11762
- const inner = process(def.innerType, ctx, params);
11762
+ const inner = process2(def.innerType, ctx, params);
11763
11763
  const seen = ctx.seen.get(schema);
11764
11764
  if (ctx.target === "openapi-3.0") {
11765
11765
  seen.ref = def.innerType;
@@ -11770,20 +11770,20 @@ var nullableProcessor = (schema, ctx, json2, params) => {
11770
11770
  };
11771
11771
  var nonoptionalProcessor = (schema, ctx, _json, params) => {
11772
11772
  const def = schema._zod.def;
11773
- process(def.innerType, ctx, params);
11773
+ process2(def.innerType, ctx, params);
11774
11774
  const seen = ctx.seen.get(schema);
11775
11775
  seen.ref = def.innerType;
11776
11776
  };
11777
11777
  var defaultProcessor = (schema, ctx, json2, params) => {
11778
11778
  const def = schema._zod.def;
11779
- process(def.innerType, ctx, params);
11779
+ process2(def.innerType, ctx, params);
11780
11780
  const seen = ctx.seen.get(schema);
11781
11781
  seen.ref = def.innerType;
11782
11782
  json2.default = JSON.parse(JSON.stringify(def.defaultValue));
11783
11783
  };
11784
11784
  var prefaultProcessor = (schema, ctx, json2, params) => {
11785
11785
  const def = schema._zod.def;
11786
- process(def.innerType, ctx, params);
11786
+ process2(def.innerType, ctx, params);
11787
11787
  const seen = ctx.seen.get(schema);
11788
11788
  seen.ref = def.innerType;
11789
11789
  if (ctx.io === "input")
@@ -11791,7 +11791,7 @@ var prefaultProcessor = (schema, ctx, json2, params) => {
11791
11791
  };
11792
11792
  var catchProcessor = (schema, ctx, json2, params) => {
11793
11793
  const def = schema._zod.def;
11794
- process(def.innerType, ctx, params);
11794
+ process2(def.innerType, ctx, params);
11795
11795
  const seen = ctx.seen.get(schema);
11796
11796
  seen.ref = def.innerType;
11797
11797
  let catchValue;
@@ -11805,32 +11805,32 @@ var catchProcessor = (schema, ctx, json2, params) => {
11805
11805
  var pipeProcessor = (schema, ctx, _json, params) => {
11806
11806
  const def = schema._zod.def;
11807
11807
  const innerType = ctx.io === "input" ? def.in._zod.def.type === "transform" ? def.out : def.in : def.out;
11808
- process(innerType, ctx, params);
11808
+ process2(innerType, ctx, params);
11809
11809
  const seen = ctx.seen.get(schema);
11810
11810
  seen.ref = innerType;
11811
11811
  };
11812
11812
  var readonlyProcessor = (schema, ctx, json2, params) => {
11813
11813
  const def = schema._zod.def;
11814
- process(def.innerType, ctx, params);
11814
+ process2(def.innerType, ctx, params);
11815
11815
  const seen = ctx.seen.get(schema);
11816
11816
  seen.ref = def.innerType;
11817
11817
  json2.readOnly = true;
11818
11818
  };
11819
11819
  var promiseProcessor = (schema, ctx, _json, params) => {
11820
11820
  const def = schema._zod.def;
11821
- process(def.innerType, ctx, params);
11821
+ process2(def.innerType, ctx, params);
11822
11822
  const seen = ctx.seen.get(schema);
11823
11823
  seen.ref = def.innerType;
11824
11824
  };
11825
11825
  var optionalProcessor = (schema, ctx, _json, params) => {
11826
11826
  const def = schema._zod.def;
11827
- process(def.innerType, ctx, params);
11827
+ process2(def.innerType, ctx, params);
11828
11828
  const seen = ctx.seen.get(schema);
11829
11829
  seen.ref = def.innerType;
11830
11830
  };
11831
11831
  var lazyProcessor = (schema, ctx, _json, params) => {
11832
11832
  const innerType = schema._zod.innerType;
11833
- process(innerType, ctx, params);
11833
+ process2(innerType, ctx, params);
11834
11834
  const seen = ctx.seen.get(schema);
11835
11835
  seen.ref = innerType;
11836
11836
  };
@@ -11882,7 +11882,7 @@ function toJSONSchema(input, params) {
11882
11882
  const defs = {};
11883
11883
  for (const entry of registry2._idmap.entries()) {
11884
11884
  const [_, schema] = entry;
11885
- process(schema, ctx2);
11885
+ process2(schema, ctx2);
11886
11886
  }
11887
11887
  const schemas = {};
11888
11888
  const external = {
@@ -11905,7 +11905,7 @@ function toJSONSchema(input, params) {
11905
11905
  return { schemas };
11906
11906
  }
11907
11907
  const ctx = initializeContext({ ...params, processors: allProcessors });
11908
- process(input, ctx);
11908
+ process2(input, ctx);
11909
11909
  extractDefs(ctx, input);
11910
11910
  return finalize(ctx, input);
11911
11911
  }
@@ -11963,7 +11963,7 @@ var JSONSchemaGenerator = class {
11963
11963
  * This must be called before emit().
11964
11964
  */
11965
11965
  process(schema, _params = { path: [], schemaPath: [] }) {
11966
- return process(schema, this.ctx, _params);
11966
+ return process2(schema, this.ctx, _params);
11967
11967
  }
11968
11968
  /**
11969
11969
  * Emit the final JSON Schema after processing.
@@ -14023,6 +14023,20 @@ var repoConfigFields = [
14023
14023
  nullable: true,
14024
14024
  description: "additional instructions for planning"
14025
14025
  },
14026
+ {
14027
+ key: "prompts.address-reviews",
14028
+ field: "AddressReviews",
14029
+ kind: "prompt",
14030
+ nullable: true,
14031
+ description: "additional instructions for addressing review feedback"
14032
+ },
14033
+ {
14034
+ key: "prompts.fix-ci",
14035
+ field: "Fix",
14036
+ kind: "prompt",
14037
+ nullable: true,
14038
+ description: "additional instructions for fixing CI failures"
14039
+ },
14026
14040
  { key: "mention.enabled", field: "mention", kind: "toggle", description: "respond to mentions" },
14027
14041
  {
14028
14042
  key: "mention.instructions",
@@ -15529,6 +15543,10 @@ function isOssAllowedModel(slug) {
15529
15543
  function getAutoSelectHintModel() {
15530
15544
  return defaultProxyDisplayName;
15531
15545
  }
15546
+ var PROVIDER_GATEWAY_URL_ENV = {
15547
+ anthropic: "ANTHROPIC_BASE_URL",
15548
+ openai: "OPENAI_BASE_URL"
15549
+ };
15532
15550
 
15533
15551
  // external.ts
15534
15552
  var pullfrogMcpName = "pullfrog";
@@ -15661,26 +15679,24 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
15661
15679
 
15662
15680
  2. Checkout the PR branch via \`${t("checkout_pr")}\`.
15663
15681
 
15664
- 3. Fetch review comments via \`${t("get_review_comments")}\`.
15682
+ 3. Fetch review comments via \`${t("get_review_comments")}\` and read the full conversation in each relevant thread.
15665
15683
 
15666
- 4. For each comment:
15667
- - understand the feedback
15684
+ 4. For each thread:
15685
+ - **check intent first**: questions, explanations, and disagreement call for a reply, not a code change. Change code only when explicitly requested. Automatic review feedback on Pullfrog-authored PRs (\`address_scope: all\`) remains actionable without a separate request; later replies can narrow or withdraw that permission.
15686
+ - under \`address_scope: mentions\`, unmentioned threads are context only unless the user's review-body request explicitly includes them
15668
15687
  - **verify the finding yourself** against the actual code before deciding whether to apply \u2014 every comment (human or agent) is a hypothesis, not a directive. agent reviewers especially are fallible.
15669
- - you are searching for a solution that is **complete, minimal, and elegant** \u2014 you may need to think hard to find it. do not over-engineer, do not be over-defensive, **do not write AI slop**. reviewers bias toward *recommending additions*, and that bias has a recognizable slop texture: defensive checks for impossible cases, extra abstractions used once, comments restating obvious code, tests asserting tautologies, "just-in-case" guards, error handlers for cases the type system already rules out. reject those. evaluate whether applying the finding would leave the code more **sound, correct, AND elegant**; two-out-of-three is a signal to look harder for a fix that gets all three. if a request would add bloat \u2014 ceremony without commensurate correctness benefit \u2014 push back in your reply rather than mechanically applying it.
15670
- - if the request stands, make the code change using your native tools; otherwise reply explaining why
15688
+ - keep requested changes **sound, minimal, and elegant**. Reject defensive checks for impossible cases, single-use abstractions, redundant comments, and tautological tests rather than mechanically applying reviewer suggestions.
15689
+ - if intent permits a code change and the finding stands, implement it using your native tools; otherwise reply without editing
15671
15690
  - record what was done (or why nothing was done)
15672
15691
 
15673
15692
  5. Quality check:
15674
- - re-call \`${t("get_review_comments")}\` with \`fresh: true\` before committing: reviewers keep commenting while you work, and any thread it now returns that you have not addressed is in scope for this run.
15675
- - test changes, then review the diff before committing \u2014 verify only intended changes are present, no debug artifacts remain, no fix turned out to be bloat in context (revert any that did), and the changes are clean enough that a senior engineer would approve without hesitation
15676
- - ${commitStep}
15693
+ - re-call \`${t("get_review_comments")}\` with \`fresh: true\` before finishing, even when only replying. Apply the same scope and intent checks to new comments; respect requests to stop or change direction before committing.
15694
+ - if code changed, test and review the diff, remove unintended changes or bloat, then ${commitStep}
15677
15695
 
15678
- 6. Finalize. Reply + resolve are paired write actions: do BOTH or NEITHER for each thread.
15679
- - ${finalizeStep} (same push/prepush guidance as Build mode in *SYSTEM*)
15680
- - **if the push/commit fails**, call \`${t("report_progress")}\` with the exact error and STOP \u2014 do NOT reply or resolve any thread until the fix is live on the remote. Resolving a thread without the fix landing misleads the reviewer.
15681
- - **once the fix is live on the remote**, for each thread you acted on:
15682
- - reply ONCE via \`${t("reply_to_review_comment")}\`. The \`comment_id\` parameter takes the root comment's numeric \`id=\` (from the first \`comment author=...\` tag in the \`${t("get_review_comments")}\` output) \u2014 NOT the \`thread=\` value; that's a separate GraphQL ID used by resolve. The runtime dedupes identical bodies within a session.
15683
- - **immediately** call \`${t("resolve_review_thread")}\` with that thread's \`thread=\` value as \`thread_id\`. Resolve every thread where you (a) made the requested code change in full \u2014 partial fixes leave the thread open \u2014 OR (b) replied with a substantive answer the user explicitly asked for. Do NOT resolve threads where you pushed back on the request and the disagreement is unresolved; leave those open for the human to mediate.
15696
+ 6. Finalize:
15697
+ - **only if code changed**, ${finalizeStep} (same push/prepush guidance as Build mode in *SYSTEM*). If push/commit fails, report the exact error and STOP without claiming a fix or resolving its threads.
15698
+ - reply ONCE per relevant thread via \`${t("reply_to_review_comment")}\`, including when no code changed. The \`comment_id\` is the root comment's numeric \`id=\`, NOT the \`thread=\` value from \`${t("get_review_comments")}\`. The runtime dedupes identical bodies within a session.
15699
+ - resolve a thread via \`${t("resolve_review_thread")}\` (using \`thread=\` as \`thread_id\`) only after its requested fix is fully on the remote, or the user explicitly asks to resolve it. Leave discussion-only replies, partial fixes, and disagreements open.
15684
15700
  - call \`${t("report_progress")}\` with a brief summary. If your last \`${t("get_review_comments")}\` turned up threads you did not address, name them there \u2014 a review arriving this late is suppressed as a duplicate and nothing else will pick it up.`
15685
15701
  },
15686
15702
  // Review and IncrementalReview route the minimum reviewfrog specialists
@@ -16594,6 +16610,7 @@ export {
16594
16610
  OAuthInvalidGrantError,
16595
16611
  OSS_MODEL_ALLOWLIST,
16596
16612
  OSS_RECOMMENDED_MODEL,
16613
+ PROVIDER_GATEWAY_URL_ENV,
16597
16614
  PULLFROG_DIVIDER,
16598
16615
  RUN_STATUS_CHECK_NAME,
16599
16616
  SUBSIDY_RUNG,
package/dist/models.d.ts CHANGED
@@ -376,6 +376,27 @@ export declare const OPENAI_COMPATIBLE_CONTEXT_ENV = "OPENAI_COMPATIBLE_CONTEXT"
376
376
  * `context` + `output` together, so the pair is validated and emitted as a unit.
377
377
  */
378
378
  export declare const OPENAI_COMPATIBLE_MAX_OUTPUT_ENV = "OPENAI_COMPATIBLE_MAX_OUTPUT";
379
+ /**
380
+ * Providers whose endpoint can be re-pointed at a customer gateway, and the env
381
+ * var that does it. Deliberately a short explicit map rather than a derived
382
+ * `<PROVIDER>_BASE_URL` rule: only a provider we have confirmed reachable
383
+ * through a proxy belongs here, and three of them must never be.
384
+ * `openai-compatible` already owns its endpoint via OPENAI_COMPATIBLE_BASE_URL,
385
+ * `azure` derives one from AZURE_RESOURCE_NAME, and bedrock/vertex authenticate
386
+ * against a cloud SDK rather than a URL.
387
+ *
388
+ * models.dev declares no base-URL env var for any catalogued provider (2 of 213
389
+ * do, and neither is one of these), so nothing reads these names on its own —
390
+ * `providerGatewayOverride` is what makes them mean anything to opencode and
391
+ * `gatewayProvider` to codex, while claude-code reads ANTHROPIC_BASE_URL itself.
392
+ *
393
+ * Adding a provider that ALREADY has a block in `buildSecurityConfig` needs a
394
+ * merge, not an entry here: that spread replaces the whole key, so listing
395
+ * `openrouter` would silently drop `kimiOpenRouterProviderOverrides()`.
396
+ */
397
+ export declare const PROVIDER_GATEWAY_URL_ENV: Record<string, string>;
398
+ /** the customer gateway a model's provider is re-pointed at, if any. */
399
+ export declare function getProviderGatewayUrl(specifier: string | undefined): string | undefined;
379
400
  /**
380
401
  * the Bedrock model ID passed to claude-code or opencode is whatever the
381
402
  * user set in `BEDROCK_MODEL_ID` — Pullfrog never resolves or upgrades it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pullfrog",
3
- "version": "0.1.75",
3
+ "version": "0.1.76",
4
4
  "cliContractVersion": "1",
5
5
  "type": "module",
6
6
  "bin": {