pullfrog 0.1.68 → 0.1.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -110299,6 +110299,13 @@ var providers = {
110299
110299
  // brand-tier names the slug convention asks for — the pre-5.6 `gpt` /
110300
110300
  // `gpt-pro` / `gpt-mini` slugs are holdovers from the retired GPT / GPT Pro
110301
110301
  // / GPT Mini tiering and are carried below as deprecated aliases.
110302
+ "gpt-astra": {
110303
+ displayName: "GPT Astra",
110304
+ resolve: "openai/gpt-6-astra",
110305
+ effort: ["low", "medium", "high", "xhigh", "max"],
110306
+ openRouterResolve: "openrouter/openai/gpt-6-astra",
110307
+ subagentModel: "gpt-sol"
110308
+ },
110302
110309
  "gpt-sol": {
110303
110310
  displayName: "GPT Sol",
110304
110311
  resolve: "openai/gpt-5.6-sol",
@@ -110660,6 +110667,13 @@ var providers = {
110660
110667
  openRouterResolve: "openrouter/openai/gpt-5.6-sol",
110661
110668
  subagentModel: "gpt-terra"
110662
110669
  },
110670
+ "gpt-astra": {
110671
+ displayName: "GPT Astra",
110672
+ resolve: "opencode/gpt-6-astra",
110673
+ effort: ["low", "medium", "high", "xhigh", "max"],
110674
+ openRouterResolve: "openrouter/openai/gpt-6-astra",
110675
+ subagentModel: "gpt-sol"
110676
+ },
110663
110677
  // see openai/gpt-sol-pro — Zen has no -pro id, so direct resolves to plain Sol.
110664
110678
  "gpt-sol-pro": {
110665
110679
  displayName: "GPT Sol Pro",
@@ -110773,10 +110787,9 @@ var providers = {
110773
110787
  // was the catalog's only GLM route, and Grok had no Zen route at all.
110774
110788
  glm: {
110775
110789
  displayName: "GLM",
110776
- resolve: "opencode/glm-5.2",
110777
- effort: ["high", "max"],
110778
- openRouterEffort: ["high", "xhigh"],
110779
- openRouterResolve: "openrouter/z-ai/glm-5.2"
110790
+ resolve: "opencode/glm-5.3",
110791
+ effort: ["low", "high", "max"],
110792
+ openRouterResolve: "openrouter/z-ai/glm-5.3"
110780
110793
  },
110781
110794
  grok: {
110782
110795
  displayName: "Grok",
@@ -110823,10 +110836,11 @@ var providers = {
110823
110836
  // OpenCode Go is a separate $10/mo subscription from Zen, served on its own
110824
110837
  // base URL (`https://opencode.ai/zen/go/v1`) but authenticated with the SAME
110825
110838
  // `OPENCODE_API_KEY`. it carries the open-weight coding models plus a couple
110826
- // of frontier ones, and 14 of the ids below are served ONLY here — Zen's
110827
- // `/v1/models` does not list glm-5.3*, qwen3.7/3.8-*, mimo-*, longcat-2.0,
110828
- // hy3 or muse-spark. so for a Go subscriber this provider is not a duplicate
110829
- // route to Zen, it is the only route to most of what they pay for.
110839
+ // of frontier ones, six of which are served ONLY here — Zen's `/v1/models`
110840
+ // does not list qwen3.7/3.8-*, mimo-v2.5-pro, longcat-2.0 or hy3. (it listed
110841
+ // no glm-5.3* either until 2026-09, which is why `opencode/glm` trailed on
110842
+ // 5.2.) so for a Go subscriber this provider is not a duplicate route to Zen,
110843
+ // it is the only route to a large part of what they pay for.
110830
110844
  // like `opencode` and `openrouter` this is a ROUTER, not a vendor: slugs and
110831
110845
  // display names mirror the upstream brand tier, and the picker groups them
110832
110846
  // under the upstream vendor.
@@ -110895,7 +110909,9 @@ var providers = {
110895
110909
  // `high` the Go route does not.
110896
110910
  effort: ["low", "medium", "xhigh"],
110897
110911
  openRouterEffort: ["minimal", "low", "medium", "high", "xhigh"],
110898
- openRouterResolve: "openrouter/qwen/qwen3.8-max"
110912
+ // OpenRouter retired the rolling `qwen3.8-max` for a dated snapshot, so
110913
+ // this side has to pin one; the Go route still carries the rolling id.
110914
+ openRouterResolve: "openrouter/qwen/qwen3.8-max-0902"
110899
110915
  },
110900
110916
  "qwen-plus": {
110901
110917
  displayName: "Qwen Plus",
@@ -111075,6 +111091,13 @@ var providers = {
111075
111091
  openRouterResolve: "openrouter/openai/gpt-5.6-sol",
111076
111092
  subagentModel: "gpt-terra"
111077
111093
  },
111094
+ "gpt-astra": {
111095
+ displayName: "GPT Astra",
111096
+ resolve: "openrouter/openai/gpt-6-astra",
111097
+ effort: ["low", "medium", "high", "xhigh", "max"],
111098
+ openRouterResolve: "openrouter/openai/gpt-6-astra",
111099
+ subagentModel: "gpt-sol"
111100
+ },
111078
111101
  // see openai/gpt-sol-pro. openrouter serves sol-pro directly on both routes.
111079
111102
  "gpt-sol-pro": {
111080
111103
  displayName: "GPT Sol Pro",
@@ -111245,6 +111268,12 @@ var providers = {
111245
111268
  effort: ["none", "low", "medium", "high", "xhigh", "max"],
111246
111269
  subagentModel: "gpt-terra"
111247
111270
  },
111271
+ "gpt-astra": {
111272
+ displayName: "GPT Astra",
111273
+ resolve: "vercel/openai/gpt-6-astra",
111274
+ effort: ["low", "medium", "high", "xhigh", "max"],
111275
+ subagentModel: "gpt-sol"
111276
+ },
111248
111277
  "gpt-terra": {
111249
111278
  displayName: "GPT Terra",
111250
111279
  resolve: "vercel/openai/gpt-5.6-terra",
@@ -112252,7 +112281,7 @@ var import_semver = __toESM(require_semver2(), 1);
112252
112281
  // package.json
112253
112282
  var package_default = {
112254
112283
  name: "pullfrog",
112255
- version: "0.1.68",
112284
+ version: "0.1.70",
112256
112285
  type: "module",
112257
112286
  bin: {
112258
112287
  pullfrog: "dist/cli.mjs",
@@ -112283,12 +112312,12 @@ var package_default = {
112283
112312
  "@ark/util": "0.56.0",
112284
112313
  "@clack/prompts": "^1.2.0",
112285
112314
  "@modelcontextprotocol/sdk": "^1.26.0",
112286
- "@opencode-ai/sdk": "1.18.5",
112287
- "@openai/codex": "0.147.0",
112288
- "@openai/codex-sdk": "0.147.0",
112289
112315
  "@octokit/plugin-throttling": "^11.0.3",
112290
112316
  "@octokit/rest": "^22.0.0",
112291
112317
  "@octokit/webhooks-types": "^7.6.1",
112318
+ "@openai/codex": "0.153.4",
112319
+ "@openai/codex-sdk": "0.153.4",
112320
+ "@opencode-ai/sdk": "1.18.29",
112292
112321
  "@standard-schema/spec": "1.1.0",
112293
112322
  "@toon-format/toon": "^1.0.0",
112294
112323
  "@types/node": "^24.7.2",
@@ -112308,7 +112337,7 @@ var package_default = {
112308
112337
  husky: "^9.0.0",
112309
112338
  "lru-cache": "^11.0.0",
112310
112339
  "object-hash": "^3.0.0",
112311
- "opencode-ai": "1.18.5",
112340
+ "opencode-ai": "1.18.29",
112312
112341
  "package-manager-detector": "^1.6.0",
112313
112342
  picocolors: "^1.1.1",
112314
112343
  semver: "^7.7.3",
@@ -113059,6 +113088,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
113059
113088
  - record what was done (or why nothing was done)
113060
113089
 
113061
113090
  5. Quality check:
113091
+ - 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.
113062
113092
  - 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
113063
113093
  - ${commitStep}
113064
113094
 
@@ -113068,7 +113098,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
113068
113098
  - **once the fix is live on the remote**, for each thread you acted on:
113069
113099
  - 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.
113070
113100
  - **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.
113071
- - call \`${t2("report_progress")}\` with a brief summary`
113101
+ - 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.`
113072
113102
  },
113073
113103
  // Review and IncrementalReview route the minimum reviewfrog specialists
113074
113104
  // needed to cover unresolved, disposition-changing hypotheses. Most runs
@@ -114908,6 +114938,7 @@ function toTodoWriteInput(item) {
114908
114938
  };
114909
114939
  }
114910
114940
  var CODEX_MODEL_PRICING = {
114941
+ "gpt-6-astra": { input: 10, cacheRead: 1, cacheWrite: 12.5, output: 50 },
114911
114942
  "gpt-5.6-sol": { input: 5, cacheRead: 0.5, cacheWrite: 6.25, output: 30 },
114912
114943
  "gpt-5.6-luna": { input: 0.2, cacheRead: 0.02, cacheWrite: 0.25, output: 1.2 },
114913
114944
  "gpt-5.6-terra": { input: 2, cacheRead: 0.2, cacheWrite: 2.5, output: 12 }
@@ -115234,7 +115265,7 @@ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "node:f
115234
115265
  import { join as join8 } from "node:path";
115235
115266
  import { performance as performance7 } from "node:perf_hooks";
115236
115267
 
115237
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/core/serverSentEvents.gen.js
115268
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/core/serverSentEvents.gen.js
115238
115269
  var createSseClient = ({ onRequest, onSseError, onSseEvent, responseTransformer, responseValidator, sseDefaultRetryDelay, sseMaxRetryAttempts, sseMaxRetryDelay, sseSleepFn, url: url4, ...options }) => {
115239
115270
  let lastEventId;
115240
115271
  const sleep5 = sseSleepFn ?? ((ms) => new Promise((resolve3) => setTimeout(resolve3, ms)));
@@ -115353,7 +115384,7 @@ var createSseClient = ({ onRequest, onSseError, onSseEvent, responseTransformer,
115353
115384
  return { stream };
115354
115385
  };
115355
115386
 
115356
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/core/pathSerializer.gen.js
115387
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/core/pathSerializer.gen.js
115357
115388
  var separatorArrayExplode = (style) => {
115358
115389
  switch (style) {
115359
115390
  case "label":
@@ -115456,7 +115487,7 @@ var serializeObjectParam = ({ allowReserved, explode, name, style, value: value2
115456
115487
  return style === "label" || style === "matrix" ? separator2 + joinedValues : joinedValues;
115457
115488
  };
115458
115489
 
115459
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/core/utils.gen.js
115490
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/core/utils.gen.js
115460
115491
  var PATH_PARAM_RE = /\{[^{}]+\}/g;
115461
115492
  var defaultPathSerializer = ({ path: path4, url: _url3 }) => {
115462
115493
  let url4 = _url3;
@@ -115539,7 +115570,7 @@ function getValidRequestBody(options) {
115539
115570
  return void 0;
115540
115571
  }
115541
115572
 
115542
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/core/auth.gen.js
115573
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/core/auth.gen.js
115543
115574
  var getAuthToken = async (auth2, callback) => {
115544
115575
  const token = typeof callback === "function" ? await callback(auth2) : callback;
115545
115576
  if (!token) {
@@ -115554,12 +115585,12 @@ var getAuthToken = async (auth2, callback) => {
115554
115585
  return token;
115555
115586
  };
115556
115587
 
115557
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/core/bodySerializer.gen.js
115588
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/core/bodySerializer.gen.js
115558
115589
  var jsonBodySerializer = {
115559
115590
  bodySerializer: (body) => JSON.stringify(body, (_key, value2) => typeof value2 === "bigint" ? value2.toString() : value2)
115560
115591
  };
115561
115592
 
115562
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/client/utils.gen.js
115593
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/client/utils.gen.js
115563
115594
  var createQuerySerializer = ({ parameters = {}, ...args2 } = {}) => {
115564
115595
  const querySerializer = (queryParams) => {
115565
115596
  const search2 = [];
@@ -115769,7 +115800,7 @@ var createConfig = (override = {}) => ({
115769
115800
  ...override
115770
115801
  });
115771
115802
 
115772
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/client/client.gen.js
115803
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/client/client.gen.js
115773
115804
  var createClient = (config3 = {}) => {
115774
115805
  let _config = mergeConfigs(createConfig(), config3);
115775
115806
  const getConfig = () => ({ ..._config });
@@ -115977,7 +116008,7 @@ var createClient = (config3 = {}) => {
115977
116008
  };
115978
116009
  };
115979
116010
 
115980
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/core/params.gen.js
116011
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/core/params.gen.js
115981
116012
  var extraPrefixesMap = {
115982
116013
  $body_: "body",
115983
116014
  $headers_: "headers",
@@ -116073,10 +116104,10 @@ var buildClientParams = (args2, fields) => {
116073
116104
  return params;
116074
116105
  };
116075
116106
 
116076
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/client.gen.js
116107
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/client.gen.js
116077
116108
  var client = createClient(createConfig({ baseUrl: "http://localhost:4096" }));
116078
116109
 
116079
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/sdk.gen.js
116110
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/sdk.gen.js
116080
116111
  var HeyApiClient = class {
116081
116112
  client;
116082
116113
  constructor(args2) {
@@ -116691,7 +116722,7 @@ var Global = class extends HeyApiClient {
116691
116722
  /**
116692
116723
  * Upgrade opencode
116693
116724
  *
116694
- * Upgrade opencode to the specified version or latest if not specified.
116725
+ * Upgrade opencode to the specified version.
116695
116726
  */
116696
116727
  upgrade(parameters, options) {
116697
116728
  const params = buildClientParams([parameters], [{ args: [{ in: "body", key: "target" }] }]);
@@ -120531,7 +120562,7 @@ var OpencodeClient = class _OpencodeClient extends HeyApiClient {
120531
120562
  }
120532
120563
  };
120533
120564
 
120534
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/error-interceptor.js
120565
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/error-interceptor.js
120535
120566
  function wrapClientError(error62, response, request2, opts) {
120536
120567
  if (!opts?.throwOnError)
120537
120568
  return error62;
@@ -120558,7 +120589,7 @@ function describe(request2, response) {
120558
120589
  return `${method} ${url4}${status ? " \u2192 " + status : ""}${statusText ? " " + statusText : ""}`;
120559
120590
  }
120560
120591
 
120561
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/client.js
120592
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/client.js
120562
120593
  function pick(value2, fallback, encode5) {
120563
120594
  if (!value2)
120564
120595
  return;
@@ -120634,7 +120665,7 @@ function createOpencodeClient(config3) {
120634
120665
  return new OpencodeClient({ client: client2 });
120635
120666
  }
120636
120667
 
120637
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/server.js
120668
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/server.js
120638
120669
  var import_cross_spawn = __toESM(require_cross_spawn(), 1);
120639
120670
 
120640
120671
  // agents/opencode.ts
@@ -199174,7 +199205,10 @@ function extractFromFilePatches(hunks, startLine, endLine, side) {
199174
199205
  }
199175
199206
  var GetReviewComments = type({
199176
199207
  pull_number: type.number.describe("The pull request number"),
199177
- review_id: type.number.describe("The review ID to get comments for")
199208
+ review_id: type.number.describe("The review ID to get comments for"),
199209
+ "fresh?": type.boolean.describe(
199210
+ "Bypass the 60s thread cache. Use for the re-read before committing, so comments that landed while you worked are actually seen; leave unset otherwise."
199211
+ )
199178
199212
  });
199179
199213
  function hasThumbsUpFrom(comment, username) {
199180
199214
  if (!comment.reactionGroups) return false;
@@ -199208,7 +199242,7 @@ function formatReviewThreads(threadBlocks, header) {
199208
199242
  }
199209
199243
  const lines = [];
199210
199244
  lines.push(
199211
- `# Review Threads (${threadBlocks.length}) for PR #${header.pullNumber} - Review ${header.reviewId} by ${header.reviewer}`
199245
+ `# Open Threads (${threadBlocks.length}) on PR #${header.pullNumber} - dispatched by Review ${header.reviewId} from ${header.reviewer}`
199212
199246
  );
199213
199247
  lines.push("");
199214
199248
  if (threadBlocks.length > 0) {
@@ -199349,11 +199383,27 @@ async function getReviewThreads(input2) {
199349
199383
  if (!thread?.comments?.nodes) return false;
199350
199384
  return thread.comments.nodes.some((c2) => c2?.pullRequestReview?.databaseId === input2.reviewId);
199351
199385
  });
199352
- if (!input2.approvedBy) {
199353
- return threadsForReview;
199386
+ if (input2.approvedBy) {
199387
+ const username = input2.approvedBy;
199388
+ return threadsForReview.filter((thread) => threadHasThumbsUpFrom(thread, username));
199354
199389
  }
199355
- const username = input2.approvedBy;
199356
- return threadsForReview.filter((thread) => threadHasThumbsUpFrom(thread, username));
199390
+ if (!input2.addressScope) return threadsForReview;
199391
+ return [...threadsForReview, ...concurrentThreads(allThreads, threadsForReview, input2)];
199392
+ }
199393
+ function concurrentThreads(allThreads, threadsForReview, input2) {
199394
+ const alreadyIncluded = new Set(threadsForReview.map((thread) => thread.id));
199395
+ return allThreads.filter((thread) => {
199396
+ if (!thread?.comments?.nodes || alreadyIncluded.has(thread.id)) return false;
199397
+ if (thread.isResolved) return false;
199398
+ const comments = thread.comments.nodes.filter((c2) => c2 !== null);
199399
+ if (comments.length === 0) return false;
199400
+ if (comments.some((c2) => isPullfrog(c2.author?.login))) return false;
199401
+ return input2.addressScope === "all" || comments.some((c2) => mentionsPullfrog(c2.body));
199402
+ });
199403
+ }
199404
+ function mentionsPullfrog(body) {
199405
+ if (!body) return false;
199406
+ return body.split(/\r?\n/).filter((line) => !line.trim().startsWith(">")).join("\n").toLowerCase().includes("@pullfrog");
199357
199407
  }
199358
199408
  function formatReviewData(input2) {
199359
199409
  const rawReviewBody = input2.review.body;
@@ -199450,6 +199500,7 @@ function GetReviewCommentsTool(ctx) {
199450
199500
  parameters: GetReviewComments,
199451
199501
  execute: execute(async (params) => {
199452
199502
  const approvedBy = ctx.payload.event.trigger === "fix_review" && ctx.payload.event.approved_only ? ctx.payload.triggerer : void 0;
199503
+ if (params.fresh) invalidateReviewThreadCache(ctx.repo);
199453
199504
  const result = await getReviewData({
199454
199505
  octokit: ctx.octokit,
199455
199506
  owner: ctx.repo.owner,
@@ -199457,6 +199508,7 @@ function GetReviewCommentsTool(ctx) {
199457
199508
  pullNumber: params.pull_number,
199458
199509
  reviewId: params.review_id,
199459
199510
  approvedBy,
199511
+ addressScope: ctx.payload.event.address_scope,
199460
199512
  tmpdir: ctx.tmpdir,
199461
199513
  githubToken: ctx.githubInstallationToken
199462
199514
  });
@@ -199487,7 +199539,7 @@ function GetReviewCommentsTool(ctx) {
199487
199539
  threadCount: threadBlocks.length,
199488
199540
  commentsPath,
199489
199541
  toc: formatted.toc,
199490
- 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}). 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.`
199542
+ 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.`
199491
199543
  };
199492
199544
  })
199493
199545
  });
@@ -200658,11 +200710,11 @@ function billingConsoleUrl(owner) {
200658
200710
  function commercialPaywallBody(params) {
200659
200711
  if (params.reason === "commercial") {
200660
200712
  return [
200661
- `**Pullfrog needs a confirmed Pro plan for ${params.ownerLogin}, so this run paused.**`,
200713
+ `**Pullfrog needs Pro on ${params.ownerLogin}, so this run didn't start.**`,
200662
200714
  "",
200663
- "Open Plan and payment to confirm Pro or review the organization's billing status.",
200715
+ "Pro is $30/month per organization and covers private repositories. Public repos and personal accounts keep running free.",
200664
200716
  "",
200665
- `[Review plan and payment \u2192](${params.url})`
200717
+ `[Upgrade to Pro \u2192](${params.url})`
200666
200718
  ].join("\n");
200667
200719
  }
200668
200720
  params.reason;
@@ -204932,7 +204984,7 @@ function resolveTarget(repo) {
204932
204984
  return { owner: match3[1], repo: match3[2] };
204933
204985
  }
204934
204986
  function cliVersion() {
204935
- const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.68");
204987
+ const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.70");
204936
204988
  if (!parts) return "0.0.0";
204937
204989
  return `${Number(parts[1])}.${Number(parts[2])}.${Number(parts[3])}`;
204938
204990
  }
@@ -205099,7 +205151,7 @@ async function runCli5(input2) {
205099
205151
  }
205100
205152
 
205101
205153
  // cli.ts
205102
- var VERSION10 = "0.1.68";
205154
+ var VERSION10 = "0.1.70";
205103
205155
  var bin = basename2(process.argv[1] || "");
205104
205156
  var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
205105
205157
  var rawArgs = process.argv.slice(2);
@@ -137,6 +137,15 @@ interface PullRequestReviewSubmittedEvent extends BasePayloadEvent {
137
137
  body: string | null;
138
138
  review_state: string;
139
139
  branch: string;
140
+ /**
141
+ * How far past the dispatching review this run may read: `all` on a
142
+ * Pullfrog-authored PR, `mentions` when only `@pullfrog`-mentioning threads
143
+ * are in scope. The server already computes this to decide whether to
144
+ * dispatch; forwarding it is what lets `get_review_comments` pick up the
145
+ * comments that land while the run is working. Optional so a payload from an
146
+ * older server build still parses against a newer action.
147
+ */
148
+ address_scope?: "all" | "mentions" | undefined;
140
149
  }
141
150
  interface PullRequestReviewCommentCreatedEvent extends BasePayloadEvent {
142
151
  trigger: "pull_request_review_comment_created";
package/dist/index.js CHANGED
@@ -107809,6 +107809,13 @@ var providers = {
107809
107809
  // brand-tier names the slug convention asks for — the pre-5.6 `gpt` /
107810
107810
  // `gpt-pro` / `gpt-mini` slugs are holdovers from the retired GPT / GPT Pro
107811
107811
  // / GPT Mini tiering and are carried below as deprecated aliases.
107812
+ "gpt-astra": {
107813
+ displayName: "GPT Astra",
107814
+ resolve: "openai/gpt-6-astra",
107815
+ effort: ["low", "medium", "high", "xhigh", "max"],
107816
+ openRouterResolve: "openrouter/openai/gpt-6-astra",
107817
+ subagentModel: "gpt-sol"
107818
+ },
107812
107819
  "gpt-sol": {
107813
107820
  displayName: "GPT Sol",
107814
107821
  resolve: "openai/gpt-5.6-sol",
@@ -108170,6 +108177,13 @@ var providers = {
108170
108177
  openRouterResolve: "openrouter/openai/gpt-5.6-sol",
108171
108178
  subagentModel: "gpt-terra"
108172
108179
  },
108180
+ "gpt-astra": {
108181
+ displayName: "GPT Astra",
108182
+ resolve: "opencode/gpt-6-astra",
108183
+ effort: ["low", "medium", "high", "xhigh", "max"],
108184
+ openRouterResolve: "openrouter/openai/gpt-6-astra",
108185
+ subagentModel: "gpt-sol"
108186
+ },
108173
108187
  // see openai/gpt-sol-pro — Zen has no -pro id, so direct resolves to plain Sol.
108174
108188
  "gpt-sol-pro": {
108175
108189
  displayName: "GPT Sol Pro",
@@ -108283,10 +108297,9 @@ var providers = {
108283
108297
  // was the catalog's only GLM route, and Grok had no Zen route at all.
108284
108298
  glm: {
108285
108299
  displayName: "GLM",
108286
- resolve: "opencode/glm-5.2",
108287
- effort: ["high", "max"],
108288
- openRouterEffort: ["high", "xhigh"],
108289
- openRouterResolve: "openrouter/z-ai/glm-5.2"
108300
+ resolve: "opencode/glm-5.3",
108301
+ effort: ["low", "high", "max"],
108302
+ openRouterResolve: "openrouter/z-ai/glm-5.3"
108290
108303
  },
108291
108304
  grok: {
108292
108305
  displayName: "Grok",
@@ -108333,10 +108346,11 @@ var providers = {
108333
108346
  // OpenCode Go is a separate $10/mo subscription from Zen, served on its own
108334
108347
  // base URL (`https://opencode.ai/zen/go/v1`) but authenticated with the SAME
108335
108348
  // `OPENCODE_API_KEY`. it carries the open-weight coding models plus a couple
108336
- // of frontier ones, and 14 of the ids below are served ONLY here — Zen's
108337
- // `/v1/models` does not list glm-5.3*, qwen3.7/3.8-*, mimo-*, longcat-2.0,
108338
- // hy3 or muse-spark. so for a Go subscriber this provider is not a duplicate
108339
- // route to Zen, it is the only route to most of what they pay for.
108349
+ // of frontier ones, six of which are served ONLY here — Zen's `/v1/models`
108350
+ // does not list qwen3.7/3.8-*, mimo-v2.5-pro, longcat-2.0 or hy3. (it listed
108351
+ // no glm-5.3* either until 2026-09, which is why `opencode/glm` trailed on
108352
+ // 5.2.) so for a Go subscriber this provider is not a duplicate route to Zen,
108353
+ // it is the only route to a large part of what they pay for.
108340
108354
  // like `opencode` and `openrouter` this is a ROUTER, not a vendor: slugs and
108341
108355
  // display names mirror the upstream brand tier, and the picker groups them
108342
108356
  // under the upstream vendor.
@@ -108405,7 +108419,9 @@ var providers = {
108405
108419
  // `high` the Go route does not.
108406
108420
  effort: ["low", "medium", "xhigh"],
108407
108421
  openRouterEffort: ["minimal", "low", "medium", "high", "xhigh"],
108408
- openRouterResolve: "openrouter/qwen/qwen3.8-max"
108422
+ // OpenRouter retired the rolling `qwen3.8-max` for a dated snapshot, so
108423
+ // this side has to pin one; the Go route still carries the rolling id.
108424
+ openRouterResolve: "openrouter/qwen/qwen3.8-max-0902"
108409
108425
  },
108410
108426
  "qwen-plus": {
108411
108427
  displayName: "Qwen Plus",
@@ -108585,6 +108601,13 @@ var providers = {
108585
108601
  openRouterResolve: "openrouter/openai/gpt-5.6-sol",
108586
108602
  subagentModel: "gpt-terra"
108587
108603
  },
108604
+ "gpt-astra": {
108605
+ displayName: "GPT Astra",
108606
+ resolve: "openrouter/openai/gpt-6-astra",
108607
+ effort: ["low", "medium", "high", "xhigh", "max"],
108608
+ openRouterResolve: "openrouter/openai/gpt-6-astra",
108609
+ subagentModel: "gpt-sol"
108610
+ },
108588
108611
  // see openai/gpt-sol-pro. openrouter serves sol-pro directly on both routes.
108589
108612
  "gpt-sol-pro": {
108590
108613
  displayName: "GPT Sol Pro",
@@ -108755,6 +108778,12 @@ var providers = {
108755
108778
  effort: ["none", "low", "medium", "high", "xhigh", "max"],
108756
108779
  subagentModel: "gpt-terra"
108757
108780
  },
108781
+ "gpt-astra": {
108782
+ displayName: "GPT Astra",
108783
+ resolve: "vercel/openai/gpt-6-astra",
108784
+ effort: ["low", "medium", "high", "xhigh", "max"],
108785
+ subagentModel: "gpt-sol"
108786
+ },
108758
108787
  "gpt-terra": {
108759
108788
  displayName: "GPT Terra",
108760
108789
  resolve: "vercel/openai/gpt-5.6-terra",
@@ -109762,7 +109791,7 @@ var import_semver = __toESM(require_semver2(), 1);
109762
109791
  // package.json
109763
109792
  var package_default = {
109764
109793
  name: "pullfrog",
109765
- version: "0.1.68",
109794
+ version: "0.1.70",
109766
109795
  type: "module",
109767
109796
  bin: {
109768
109797
  pullfrog: "dist/cli.mjs",
@@ -109793,12 +109822,12 @@ var package_default = {
109793
109822
  "@ark/util": "0.56.0",
109794
109823
  "@clack/prompts": "^1.2.0",
109795
109824
  "@modelcontextprotocol/sdk": "^1.26.0",
109796
- "@opencode-ai/sdk": "1.18.5",
109797
- "@openai/codex": "0.147.0",
109798
- "@openai/codex-sdk": "0.147.0",
109799
109825
  "@octokit/plugin-throttling": "^11.0.3",
109800
109826
  "@octokit/rest": "^22.0.0",
109801
109827
  "@octokit/webhooks-types": "^7.6.1",
109828
+ "@openai/codex": "0.153.4",
109829
+ "@openai/codex-sdk": "0.153.4",
109830
+ "@opencode-ai/sdk": "1.18.29",
109802
109831
  "@standard-schema/spec": "1.1.0",
109803
109832
  "@toon-format/toon": "^1.0.0",
109804
109833
  "@types/node": "^24.7.2",
@@ -109818,7 +109847,7 @@ var package_default = {
109818
109847
  husky: "^9.0.0",
109819
109848
  "lru-cache": "^11.0.0",
109820
109849
  "object-hash": "^3.0.0",
109821
- "opencode-ai": "1.18.5",
109850
+ "opencode-ai": "1.18.29",
109822
109851
  "package-manager-detector": "^1.6.0",
109823
109852
  picocolors: "^1.1.1",
109824
109853
  semver: "^7.7.3",
@@ -110569,6 +110598,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
110569
110598
  - record what was done (or why nothing was done)
110570
110599
 
110571
110600
  5. Quality check:
110601
+ - 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.
110572
110602
  - 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
110573
110603
  - ${commitStep}
110574
110604
 
@@ -110578,7 +110608,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
110578
110608
  - **once the fix is live on the remote**, for each thread you acted on:
110579
110609
  - 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.
110580
110610
  - **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.
110581
- - call \`${t("report_progress")}\` with a brief summary`
110611
+ - 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.`
110582
110612
  },
110583
110613
  // Review and IncrementalReview route the minimum reviewfrog specialists
110584
110614
  // needed to cover unresolved, disposition-changing hypotheses. Most runs
@@ -112491,6 +112521,7 @@ function toTodoWriteInput(item) {
112491
112521
  };
112492
112522
  }
112493
112523
  var CODEX_MODEL_PRICING = {
112524
+ "gpt-6-astra": { input: 10, cacheRead: 1, cacheWrite: 12.5, output: 50 },
112494
112525
  "gpt-5.6-sol": { input: 5, cacheRead: 0.5, cacheWrite: 6.25, output: 30 },
112495
112526
  "gpt-5.6-luna": { input: 0.2, cacheRead: 0.02, cacheWrite: 0.25, output: 1.2 },
112496
112527
  "gpt-5.6-terra": { input: 2, cacheRead: 0.2, cacheWrite: 2.5, output: 12 }
@@ -112817,7 +112848,7 @@ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "node:f
112817
112848
  import { join as join7 } from "node:path";
112818
112849
  import { performance as performance7 } from "node:perf_hooks";
112819
112850
 
112820
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/core/serverSentEvents.gen.js
112851
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/core/serverSentEvents.gen.js
112821
112852
  var createSseClient = ({ onRequest, onSseError, onSseEvent, responseTransformer, responseValidator, sseDefaultRetryDelay, sseMaxRetryAttempts, sseMaxRetryDelay, sseSleepFn, url: url4, ...options }) => {
112822
112853
  let lastEventId;
112823
112854
  const sleep5 = sseSleepFn ?? ((ms) => new Promise((resolve3) => setTimeout(resolve3, ms)));
@@ -112936,7 +112967,7 @@ var createSseClient = ({ onRequest, onSseError, onSseEvent, responseTransformer,
112936
112967
  return { stream };
112937
112968
  };
112938
112969
 
112939
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/core/pathSerializer.gen.js
112970
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/core/pathSerializer.gen.js
112940
112971
  var separatorArrayExplode = (style) => {
112941
112972
  switch (style) {
112942
112973
  case "label":
@@ -113039,7 +113070,7 @@ var serializeObjectParam = ({ allowReserved, explode, name, style, value: value2
113039
113070
  return style === "label" || style === "matrix" ? separator2 + joinedValues : joinedValues;
113040
113071
  };
113041
113072
 
113042
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/core/utils.gen.js
113073
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/core/utils.gen.js
113043
113074
  var PATH_PARAM_RE = /\{[^{}]+\}/g;
113044
113075
  var defaultPathSerializer = ({ path: path4, url: _url3 }) => {
113045
113076
  let url4 = _url3;
@@ -113122,7 +113153,7 @@ function getValidRequestBody(options) {
113122
113153
  return void 0;
113123
113154
  }
113124
113155
 
113125
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/core/auth.gen.js
113156
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/core/auth.gen.js
113126
113157
  var getAuthToken = async (auth2, callback) => {
113127
113158
  const token = typeof callback === "function" ? await callback(auth2) : callback;
113128
113159
  if (!token) {
@@ -113137,12 +113168,12 @@ var getAuthToken = async (auth2, callback) => {
113137
113168
  return token;
113138
113169
  };
113139
113170
 
113140
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/core/bodySerializer.gen.js
113171
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/core/bodySerializer.gen.js
113141
113172
  var jsonBodySerializer = {
113142
113173
  bodySerializer: (body) => JSON.stringify(body, (_key, value2) => typeof value2 === "bigint" ? value2.toString() : value2)
113143
113174
  };
113144
113175
 
113145
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/client/utils.gen.js
113176
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/client/utils.gen.js
113146
113177
  var createQuerySerializer = ({ parameters = {}, ...args2 } = {}) => {
113147
113178
  const querySerializer = (queryParams) => {
113148
113179
  const search2 = [];
@@ -113352,7 +113383,7 @@ var createConfig = (override = {}) => ({
113352
113383
  ...override
113353
113384
  });
113354
113385
 
113355
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/client/client.gen.js
113386
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/client/client.gen.js
113356
113387
  var createClient = (config3 = {}) => {
113357
113388
  let _config = mergeConfigs(createConfig(), config3);
113358
113389
  const getConfig = () => ({ ..._config });
@@ -113560,7 +113591,7 @@ var createClient = (config3 = {}) => {
113560
113591
  };
113561
113592
  };
113562
113593
 
113563
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/core/params.gen.js
113594
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/core/params.gen.js
113564
113595
  var extraPrefixesMap = {
113565
113596
  $body_: "body",
113566
113597
  $headers_: "headers",
@@ -113656,10 +113687,10 @@ var buildClientParams = (args2, fields) => {
113656
113687
  return params;
113657
113688
  };
113658
113689
 
113659
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/client.gen.js
113690
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/client.gen.js
113660
113691
  var client = createClient(createConfig({ baseUrl: "http://localhost:4096" }));
113661
113692
 
113662
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/gen/sdk.gen.js
113693
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/sdk.gen.js
113663
113694
  var HeyApiClient = class {
113664
113695
  client;
113665
113696
  constructor(args2) {
@@ -114274,7 +114305,7 @@ var Global = class extends HeyApiClient {
114274
114305
  /**
114275
114306
  * Upgrade opencode
114276
114307
  *
114277
- * Upgrade opencode to the specified version or latest if not specified.
114308
+ * Upgrade opencode to the specified version.
114278
114309
  */
114279
114310
  upgrade(parameters, options) {
114280
114311
  const params = buildClientParams([parameters], [{ args: [{ in: "body", key: "target" }] }]);
@@ -118114,7 +118145,7 @@ var OpencodeClient = class _OpencodeClient extends HeyApiClient {
118114
118145
  }
118115
118146
  };
118116
118147
 
118117
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/error-interceptor.js
118148
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/error-interceptor.js
118118
118149
  function wrapClientError(error62, response, request2, opts) {
118119
118150
  if (!opts?.throwOnError)
118120
118151
  return error62;
@@ -118141,7 +118172,7 @@ function describe(request2, response) {
118141
118172
  return `${method} ${url4}${status ? " \u2192 " + status : ""}${statusText ? " " + statusText : ""}`;
118142
118173
  }
118143
118174
 
118144
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/client.js
118175
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/client.js
118145
118176
  function pick(value2, fallback, encode5) {
118146
118177
  if (!value2)
118147
118178
  return;
@@ -118217,7 +118248,7 @@ function createOpencodeClient(config3) {
118217
118248
  return new OpencodeClient({ client: client2 });
118218
118249
  }
118219
118250
 
118220
- // node_modules/.pnpm/@opencode-ai+sdk@1.18.5/node_modules/@opencode-ai/sdk/dist/v2/server.js
118251
+ // node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/server.js
118221
118252
  var import_cross_spawn = __toESM(require_cross_spawn(), 1);
118222
118253
 
118223
118254
  // agents/opencode.ts
@@ -196757,7 +196788,10 @@ function extractFromFilePatches(hunks, startLine, endLine, side) {
196757
196788
  }
196758
196789
  var GetReviewComments = type({
196759
196790
  pull_number: type.number.describe("The pull request number"),
196760
- review_id: type.number.describe("The review ID to get comments for")
196791
+ review_id: type.number.describe("The review ID to get comments for"),
196792
+ "fresh?": type.boolean.describe(
196793
+ "Bypass the 60s thread cache. Use for the re-read before committing, so comments that landed while you worked are actually seen; leave unset otherwise."
196794
+ )
196761
196795
  });
196762
196796
  function hasThumbsUpFrom(comment, username) {
196763
196797
  if (!comment.reactionGroups) return false;
@@ -196791,7 +196825,7 @@ function formatReviewThreads(threadBlocks, header) {
196791
196825
  }
196792
196826
  const lines = [];
196793
196827
  lines.push(
196794
- `# Review Threads (${threadBlocks.length}) for PR #${header.pullNumber} - Review ${header.reviewId} by ${header.reviewer}`
196828
+ `# Open Threads (${threadBlocks.length}) on PR #${header.pullNumber} - dispatched by Review ${header.reviewId} from ${header.reviewer}`
196795
196829
  );
196796
196830
  lines.push("");
196797
196831
  if (threadBlocks.length > 0) {
@@ -196932,11 +196966,27 @@ async function getReviewThreads(input2) {
196932
196966
  if (!thread?.comments?.nodes) return false;
196933
196967
  return thread.comments.nodes.some((c) => c?.pullRequestReview?.databaseId === input2.reviewId);
196934
196968
  });
196935
- if (!input2.approvedBy) {
196936
- return threadsForReview;
196969
+ if (input2.approvedBy) {
196970
+ const username = input2.approvedBy;
196971
+ return threadsForReview.filter((thread) => threadHasThumbsUpFrom(thread, username));
196937
196972
  }
196938
- const username = input2.approvedBy;
196939
- return threadsForReview.filter((thread) => threadHasThumbsUpFrom(thread, username));
196973
+ if (!input2.addressScope) return threadsForReview;
196974
+ return [...threadsForReview, ...concurrentThreads(allThreads, threadsForReview, input2)];
196975
+ }
196976
+ function concurrentThreads(allThreads, threadsForReview, input2) {
196977
+ const alreadyIncluded = new Set(threadsForReview.map((thread) => thread.id));
196978
+ return allThreads.filter((thread) => {
196979
+ if (!thread?.comments?.nodes || alreadyIncluded.has(thread.id)) return false;
196980
+ if (thread.isResolved) return false;
196981
+ const comments = thread.comments.nodes.filter((c) => c !== null);
196982
+ if (comments.length === 0) return false;
196983
+ if (comments.some((c) => isPullfrog(c.author?.login))) return false;
196984
+ return input2.addressScope === "all" || comments.some((c) => mentionsPullfrog(c.body));
196985
+ });
196986
+ }
196987
+ function mentionsPullfrog(body) {
196988
+ if (!body) return false;
196989
+ return body.split(/\r?\n/).filter((line) => !line.trim().startsWith(">")).join("\n").toLowerCase().includes("@pullfrog");
196940
196990
  }
196941
196991
  function formatReviewData(input2) {
196942
196992
  const rawReviewBody = input2.review.body;
@@ -197033,6 +197083,7 @@ function GetReviewCommentsTool(ctx) {
197033
197083
  parameters: GetReviewComments,
197034
197084
  execute: execute(async (params) => {
197035
197085
  const approvedBy = ctx.payload.event.trigger === "fix_review" && ctx.payload.event.approved_only ? ctx.payload.triggerer : void 0;
197086
+ if (params.fresh) invalidateReviewThreadCache(ctx.repo);
197036
197087
  const result = await getReviewData({
197037
197088
  octokit: ctx.octokit,
197038
197089
  owner: ctx.repo.owner,
@@ -197040,6 +197091,7 @@ function GetReviewCommentsTool(ctx) {
197040
197091
  pullNumber: params.pull_number,
197041
197092
  reviewId: params.review_id,
197042
197093
  approvedBy,
197094
+ addressScope: ctx.payload.event.address_scope,
197043
197095
  tmpdir: ctx.tmpdir,
197044
197096
  githubToken: ctx.githubInstallationToken
197045
197097
  });
@@ -197070,7 +197122,7 @@ function GetReviewCommentsTool(ctx) {
197070
197122
  threadCount: threadBlocks.length,
197071
197123
  commentsPath,
197072
197124
  toc: formatted.toc,
197073
- 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}). 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.`
197125
+ 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.`
197074
197126
  };
197075
197127
  })
197076
197128
  });
@@ -198241,11 +198293,11 @@ function billingConsoleUrl(owner) {
198241
198293
  function commercialPaywallBody(params) {
198242
198294
  if (params.reason === "commercial") {
198243
198295
  return [
198244
- `**Pullfrog needs a confirmed Pro plan for ${params.ownerLogin}, so this run paused.**`,
198296
+ `**Pullfrog needs Pro on ${params.ownerLogin}, so this run didn't start.**`,
198245
198297
  "",
198246
- "Open Plan and payment to confirm Pro or review the organization's billing status.",
198298
+ "Pro is $30/month per organization and covers private repositories. Public repos and personal accounts keep running free.",
198247
198299
  "",
198248
- `[Review plan and payment \u2192](${params.url})`
198300
+ `[Upgrade to Pro \u2192](${params.url})`
198249
198301
  ].join("\n");
198250
198302
  }
198251
198303
  params.reason;
package/dist/internal.js CHANGED
@@ -105,6 +105,13 @@ var providers = {
105
105
  // brand-tier names the slug convention asks for — the pre-5.6 `gpt` /
106
106
  // `gpt-pro` / `gpt-mini` slugs are holdovers from the retired GPT / GPT Pro
107
107
  // / GPT Mini tiering and are carried below as deprecated aliases.
108
+ "gpt-astra": {
109
+ displayName: "GPT Astra",
110
+ resolve: "openai/gpt-6-astra",
111
+ effort: ["low", "medium", "high", "xhigh", "max"],
112
+ openRouterResolve: "openrouter/openai/gpt-6-astra",
113
+ subagentModel: "gpt-sol"
114
+ },
108
115
  "gpt-sol": {
109
116
  displayName: "GPT Sol",
110
117
  resolve: "openai/gpt-5.6-sol",
@@ -466,6 +473,13 @@ var providers = {
466
473
  openRouterResolve: "openrouter/openai/gpt-5.6-sol",
467
474
  subagentModel: "gpt-terra"
468
475
  },
476
+ "gpt-astra": {
477
+ displayName: "GPT Astra",
478
+ resolve: "opencode/gpt-6-astra",
479
+ effort: ["low", "medium", "high", "xhigh", "max"],
480
+ openRouterResolve: "openrouter/openai/gpt-6-astra",
481
+ subagentModel: "gpt-sol"
482
+ },
469
483
  // see openai/gpt-sol-pro — Zen has no -pro id, so direct resolves to plain Sol.
470
484
  "gpt-sol-pro": {
471
485
  displayName: "GPT Sol Pro",
@@ -579,10 +593,9 @@ var providers = {
579
593
  // was the catalog's only GLM route, and Grok had no Zen route at all.
580
594
  glm: {
581
595
  displayName: "GLM",
582
- resolve: "opencode/glm-5.2",
583
- effort: ["high", "max"],
584
- openRouterEffort: ["high", "xhigh"],
585
- openRouterResolve: "openrouter/z-ai/glm-5.2"
596
+ resolve: "opencode/glm-5.3",
597
+ effort: ["low", "high", "max"],
598
+ openRouterResolve: "openrouter/z-ai/glm-5.3"
586
599
  },
587
600
  grok: {
588
601
  displayName: "Grok",
@@ -629,10 +642,11 @@ var providers = {
629
642
  // OpenCode Go is a separate $10/mo subscription from Zen, served on its own
630
643
  // base URL (`https://opencode.ai/zen/go/v1`) but authenticated with the SAME
631
644
  // `OPENCODE_API_KEY`. it carries the open-weight coding models plus a couple
632
- // of frontier ones, and 14 of the ids below are served ONLY here — Zen's
633
- // `/v1/models` does not list glm-5.3*, qwen3.7/3.8-*, mimo-*, longcat-2.0,
634
- // hy3 or muse-spark. so for a Go subscriber this provider is not a duplicate
635
- // route to Zen, it is the only route to most of what they pay for.
645
+ // of frontier ones, six of which are served ONLY here — Zen's `/v1/models`
646
+ // does not list qwen3.7/3.8-*, mimo-v2.5-pro, longcat-2.0 or hy3. (it listed
647
+ // no glm-5.3* either until 2026-09, which is why `opencode/glm` trailed on
648
+ // 5.2.) so for a Go subscriber this provider is not a duplicate route to Zen,
649
+ // it is the only route to a large part of what they pay for.
636
650
  // like `opencode` and `openrouter` this is a ROUTER, not a vendor: slugs and
637
651
  // display names mirror the upstream brand tier, and the picker groups them
638
652
  // under the upstream vendor.
@@ -701,7 +715,9 @@ var providers = {
701
715
  // `high` the Go route does not.
702
716
  effort: ["low", "medium", "xhigh"],
703
717
  openRouterEffort: ["minimal", "low", "medium", "high", "xhigh"],
704
- openRouterResolve: "openrouter/qwen/qwen3.8-max"
718
+ // OpenRouter retired the rolling `qwen3.8-max` for a dated snapshot, so
719
+ // this side has to pin one; the Go route still carries the rolling id.
720
+ openRouterResolve: "openrouter/qwen/qwen3.8-max-0902"
705
721
  },
706
722
  "qwen-plus": {
707
723
  displayName: "Qwen Plus",
@@ -881,6 +897,13 @@ var providers = {
881
897
  openRouterResolve: "openrouter/openai/gpt-5.6-sol",
882
898
  subagentModel: "gpt-terra"
883
899
  },
900
+ "gpt-astra": {
901
+ displayName: "GPT Astra",
902
+ resolve: "openrouter/openai/gpt-6-astra",
903
+ effort: ["low", "medium", "high", "xhigh", "max"],
904
+ openRouterResolve: "openrouter/openai/gpt-6-astra",
905
+ subagentModel: "gpt-sol"
906
+ },
884
907
  // see openai/gpt-sol-pro. openrouter serves sol-pro directly on both routes.
885
908
  "gpt-sol-pro": {
886
909
  displayName: "GPT Sol Pro",
@@ -1051,6 +1074,12 @@ var providers = {
1051
1074
  effort: ["none", "low", "medium", "high", "xhigh", "max"],
1052
1075
  subagentModel: "gpt-terra"
1053
1076
  },
1077
+ "gpt-astra": {
1078
+ displayName: "GPT Astra",
1079
+ resolve: "vercel/openai/gpt-6-astra",
1080
+ effort: ["low", "medium", "high", "xhigh", "max"],
1081
+ subagentModel: "gpt-sol"
1082
+ },
1054
1083
  "gpt-terra": {
1055
1084
  displayName: "GPT Terra",
1056
1085
  resolve: "vercel/openai/gpt-5.6-terra",
@@ -1383,6 +1412,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
1383
1412
  - record what was done (or why nothing was done)
1384
1413
 
1385
1414
  5. Quality check:
1415
+ - 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.
1386
1416
  - 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
1387
1417
  - ${commitStep}
1388
1418
 
@@ -1392,7 +1422,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
1392
1422
  - **once the fix is live on the remote**, for each thread you acted on:
1393
1423
  - 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.
1394
1424
  - **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.
1395
- - call \`${t("report_progress")}\` with a brief summary`
1425
+ - 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.`
1396
1426
  },
1397
1427
  // Review and IncrementalReview route the minimum reviewfrog specialists
1398
1428
  // needed to cover unresolved, disposition-changing hypotheses. Most runs
@@ -1653,11 +1683,11 @@ var modes = computeModes("opencode");
1653
1683
  function commercialPaywallBody(params) {
1654
1684
  if (params.reason === "commercial") {
1655
1685
  return [
1656
- `**Pullfrog needs a confirmed Pro plan for ${params.ownerLogin}, so this run paused.**`,
1686
+ `**Pullfrog needs Pro on ${params.ownerLogin}, so this run didn't start.**`,
1657
1687
  "",
1658
- "Open Plan and payment to confirm Pro or review the organization's billing status.",
1688
+ "Pro is $30/month per organization and covers private repositories. Public repos and personal accounts keep running free.",
1659
1689
  "",
1660
- `[Review plan and payment \u2192](${params.url})`
1690
+ `[Upgrade to Pro \u2192](${params.url})`
1661
1691
  ].join("\n");
1662
1692
  }
1663
1693
  params.reason;
@@ -67,6 +67,7 @@ export declare function parseFilePatches(patch: string): ParsedHunk[];
67
67
  export declare const GetReviewComments: import("arktype/internal/variants/object.ts").ObjectType<{
68
68
  pull_number: number;
69
69
  review_id: number;
70
+ fresh?: boolean;
70
71
  }, {}>;
71
72
  /**
72
73
  * formats thread blocks into markdown with TOC and line numbers.
@@ -94,6 +95,37 @@ export declare function buildThreadBlocks(threads: ReviewThread[], filePatchMap:
94
95
  lineRange: string;
95
96
  content: string[];
96
97
  }[];
98
+ /**
99
+ * Open threads on the PR that this run should also address, beyond the review
100
+ * that dispatched it.
101
+ *
102
+ * A reviewer working through a PR emits one `pull_request_review_submitted` per
103
+ * submission, so a second review lands seconds after the first — and until this
104
+ * existed, the run already working on the PR could never see it: the filter
105
+ * above keeps only threads carrying a comment from the *dispatched* `reviewId`,
106
+ * so a new comment on a different line was invisible no matter how often the
107
+ * agent re-read. That is what made the in-flight cap in `handleWebhook.ts` a
108
+ * silent loss rather than the coalescing it was documented as (#1103).
109
+ *
110
+ * `fetchAllReviewThreads` already pulls every thread on the PR and caches it for
111
+ * 60s, so this costs no extra round trip and a re-read mid-run picks up whatever
112
+ * landed behind the run.
113
+ *
114
+ * Three exclusions, each load-bearing:
115
+ * - resolved threads are done, and re-opening that conversation is noise.
116
+ * - a thread Pullfrog has already spoken in was handled by an earlier run.
117
+ * That deliberately includes the pushbacks AddressReviews leaves open for a
118
+ * human to mediate — re-addressing one re-litigates a settled disagreement —
119
+ * and it is also what stops this mode replying to Pullfrog's own reviews.
120
+ * - under `mentions` a concurrent thread is in scope only if it actually asked
121
+ * for Pullfrog. Deliberately asymmetric with `threadsForReview`, which is
122
+ * returned whole and always was: the dispatching review is the one the
123
+ * server already adjudicated, and the agent needs its unmentioned threads as
124
+ * context for the comment that did mention it. Which of those to ACT on stays
125
+ * the mode prompt's job. A concurrent review has had no such adjudication, so
126
+ * the mention is the only thing that can authorize it.
127
+ */
128
+ export declare function concurrentThreads(allThreads: (ReviewThread | null)[], threadsForReview: ReviewThread[], input: GetReviewDataInput): ReviewThread[];
97
129
  interface GetReviewDataInput {
98
130
  octokit: Octokit;
99
131
  owner: string;
@@ -101,6 +133,14 @@ interface GetReviewDataInput {
101
133
  pullNumber: number;
102
134
  reviewId: number;
103
135
  approvedBy?: string | undefined;
136
+ /**
137
+ * Widen the read past the dispatching review to the other open threads on the
138
+ * PR — `all` on a Pullfrog-authored PR, `mentions` when only `@pullfrog`-
139
+ * mentioning threads are in scope. Absent (console `fix_review`, and any
140
+ * payload from a server build that predates the field) keeps the read scoped
141
+ * to the dispatching review exactly as before.
142
+ */
143
+ addressScope?: "all" | "mentions" | undefined;
104
144
  tmpdir: string;
105
145
  githubToken: string;
106
146
  }
@@ -148,9 +188,11 @@ export declare function getReviewData(input: GetReviewDataInput): Promise<{
148
188
  export declare function GetReviewCommentsTool(ctx: ToolContext): import("fastmcp").Tool<any, import("@standard-schema/spec").StandardSchemaV1<{
149
189
  pull_number: number;
150
190
  review_id: number;
191
+ fresh?: boolean;
151
192
  }, {
152
193
  pull_number: number;
153
194
  review_id: number;
195
+ fresh?: boolean;
154
196
  }>> & {
155
197
  mutates?: boolean;
156
198
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pullfrog",
3
- "version": "0.1.68",
3
+ "version": "0.1.70",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "pullfrog": "dist/cli.mjs",
@@ -31,12 +31,12 @@
31
31
  "@ark/util": "0.56.0",
32
32
  "@clack/prompts": "^1.2.0",
33
33
  "@modelcontextprotocol/sdk": "^1.26.0",
34
- "@opencode-ai/sdk": "1.18.5",
35
- "@openai/codex": "0.147.0",
36
- "@openai/codex-sdk": "0.147.0",
37
34
  "@octokit/plugin-throttling": "^11.0.3",
38
35
  "@octokit/rest": "^22.0.0",
39
36
  "@octokit/webhooks-types": "^7.6.1",
37
+ "@openai/codex": "0.153.4",
38
+ "@openai/codex-sdk": "0.153.4",
39
+ "@opencode-ai/sdk": "1.18.29",
40
40
  "@standard-schema/spec": "1.1.0",
41
41
  "@toon-format/toon": "^1.0.0",
42
42
  "@types/node": "^24.7.2",
@@ -56,7 +56,7 @@
56
56
  "husky": "^9.0.0",
57
57
  "lru-cache": "^11.0.0",
58
58
  "object-hash": "^3.0.0",
59
- "opencode-ai": "1.18.5",
59
+ "opencode-ai": "1.18.29",
60
60
  "package-manager-detector": "^1.6.0",
61
61
  "picocolors": "^1.1.1",
62
62
  "semver": "^7.7.3",