pullfrog 0.1.88 → 0.1.90

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
@@ -107103,7 +107103,7 @@ var import_picocolors = __toESM(require_picocolors(), 1);
107103
107103
  // package.json
107104
107104
  var package_default = {
107105
107105
  name: "pullfrog",
107106
- version: "0.1.88",
107106
+ version: "0.1.90",
107107
107107
  cliContractVersion: "1",
107108
107108
  type: "module",
107109
107109
  bin: {
@@ -111130,6 +111130,11 @@ import { spawnSync as spawnSync2 } from "node:child_process";
111130
111130
  import { chmodSync, createWriteStream, existsSync as existsSync3, mkdirSync } from "node:fs";
111131
111131
  import { join as join2 } from "node:path";
111132
111132
  import { pipeline } from "node:stream/promises";
111133
+ function resolveNpm() {
111134
+ const npmCli = process.env.npm_execpath;
111135
+ if (npmCli?.endsWith("npm-cli.js")) return { command: process.execPath, args: [npmCli] };
111136
+ return { command: "npm", args: [] };
111137
+ }
111133
111138
  async function installFromNpmTarball(params) {
111134
111139
  const tempDir = process.env.PULLFROG_TEMP_DIR;
111135
111140
  if (!tempDir) throw new Error("PULLFROG_TEMP_DIR is not set");
@@ -111192,7 +111197,8 @@ async function installFromNpmTarball(params) {
111192
111197
  }
111193
111198
  if (params.installDependencies) {
111194
111199
  log2.debug(`\xBB installing dependencies for ${params.packageName}...`);
111195
- const installResult = spawnSync2("npm", ["install", "--production"], {
111200
+ const npm2 = resolveNpm();
111201
+ const installResult = spawnSync2(npm2.command, [...npm2.args, "install", "--production"], {
111196
111202
  cwd: extractedDir,
111197
111203
  stdio: "pipe",
111198
111204
  encoding: "utf-8",
@@ -196666,8 +196672,9 @@ function ResolveReviewThreadTool(ctx) {
196666
196672
  }
196667
196673
 
196668
196674
  // mcp/searchIssues.ts
196675
+ var TERM = /^[\p{L}\p{N}_./-]+$/u;
196669
196676
  var SearchIssues = type({
196670
- terms: type.string.matching(/^[\p{L}\p{N}_./-]+$/u).atMostLength(40).array().atLeastLength(1).atMostLength(5).describe("1-5 keywords from the issue; no GitHub qualifiers or query operators")
196677
+ terms: type.string.atMostLength(40).array().atLeastLength(1).atMostLength(5).describe("1-5 keywords from the issue; no GitHub qualifiers or query operators")
196671
196678
  });
196672
196679
  function SearchIssuesTool(ctx) {
196673
196680
  return tool({
@@ -196675,6 +196682,10 @@ function SearchIssuesTool(ctx) {
196675
196682
  description: "Find open or closed issues in this repository by keyword. Inspect promising matches with get_issue before deciding whether they are duplicates or related. Results are bounded, not an exhaustive duplicate check.",
196676
196683
  parameters: SearchIssues,
196677
196684
  execute: execute(async (params) => {
196685
+ const invalid = params.terms.find((term) => !TERM.test(term));
196686
+ if (invalid) {
196687
+ throw new Error(`search term "${invalid}" may contain only letters, digits and _ . / -`);
196688
+ }
196678
196689
  const repoPath = `/repos/${ctx.repo.owner}/${ctx.repo.name}`.toLowerCase();
196679
196690
  const terms = params.terms.map((term) => `"${term}"`).join(" ");
196680
196691
  const response = await ctx.octokit.rest.search.issuesAndPullRequests({
@@ -200098,6 +200109,23 @@ ${input.raw}
200098
200109
  \`\`\``
200099
200110
  ].join("\n");
200100
200111
  }
200112
+ function isRunTimeoutError(message) {
200113
+ return message.startsWith("agent run timed out after");
200114
+ }
200115
+ function formatRunTimeoutSummary(input) {
200116
+ const settingsUrl = `${getApiUrl()}/console/${input.owner}/${input.name}`;
200117
+ return [
200118
+ "**This run hit its time limit while still working, so it stopped before finishing.**",
200119
+ "",
200120
+ "Give runs longer with `--timeout=2h` (or `--notimeout`) in this repo's instructions \u2014 or raise the workflow's `timeout` input if it sets one, since that input wins. A smaller PR or a lower effort also finishes sooner.",
200121
+ "",
200122
+ `[Repo settings \u2192](${settingsUrl}) \xB7 [Flags \u2192](https://docs.pullfrog.com/flags)`,
200123
+ "",
200124
+ `\`\`\`
200125
+ ${input.raw}
200126
+ \`\`\``
200127
+ ].join("\n");
200128
+ }
200101
200129
  function formatContextOverflowSummary(input) {
200102
200130
  const settingsUrl = `${getApiUrl()}/console/${input.owner}/${input.name}`;
200103
200131
  return [
@@ -200347,6 +200375,16 @@ ${body}`, comment: body };
200347
200375
  });
200348
200376
  return { summary: `### \u274C Pullfrog failed
200349
200377
 
200378
+ ${body}`, comment: body };
200379
+ }
200380
+ if (isRunTimeoutError(input.errorMessage)) {
200381
+ const body = formatRunTimeoutSummary({
200382
+ owner: input.repo.owner,
200383
+ name: input.repo.name,
200384
+ raw: input.errorMessage
200385
+ });
200386
+ return { summary: `### \u274C Pullfrog failed
200387
+
200350
200388
  ${body}`, comment: body };
200351
200389
  }
200352
200390
  if (isTransientUpstreamError(input.errorMessage)) {
@@ -202192,7 +202230,7 @@ function resolveTarget2(repo) {
202192
202230
  return { owner: match3[1], repo: match3[2] };
202193
202231
  }
202194
202232
  function cliVersion() {
202195
- const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.88");
202233
+ const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.90");
202196
202234
  if (!parts) return "0.0.0";
202197
202235
  return `${Number(parts[1])}.${Number(parts[2])}.${Number(parts[3])}`;
202198
202236
  }
@@ -202359,7 +202397,7 @@ async function runCli8(input) {
202359
202397
  }
202360
202398
 
202361
202399
  // cli.ts
202362
- var VERSION10 = "0.1.88";
202400
+ var VERSION10 = "0.1.90";
202363
202401
  var bin = basename2(process.argv[1] || "");
202364
202402
  var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
202365
202403
  var rawArgs = process.argv.slice(2);
package/dist/index.js CHANGED
@@ -105416,6 +105416,11 @@ import { spawnSync } from "node:child_process";
105416
105416
  import { chmodSync, createWriteStream, existsSync as existsSync2, mkdirSync } from "node:fs";
105417
105417
  import { join } from "node:path";
105418
105418
  import { pipeline } from "node:stream/promises";
105419
+ function resolveNpm() {
105420
+ const npmCli = process.env.npm_execpath;
105421
+ if (npmCli?.endsWith("npm-cli.js")) return { command: process.execPath, args: [npmCli] };
105422
+ return { command: "npm", args: [] };
105423
+ }
105419
105424
  async function installFromNpmTarball(params) {
105420
105425
  const tempDir = process.env.PULLFROG_TEMP_DIR;
105421
105426
  if (!tempDir) throw new Error("PULLFROG_TEMP_DIR is not set");
@@ -105478,7 +105483,8 @@ async function installFromNpmTarball(params) {
105478
105483
  }
105479
105484
  if (params.installDependencies) {
105480
105485
  log.debug(`\xBB installing dependencies for ${params.packageName}...`);
105481
- const installResult = spawnSync("npm", ["install", "--production"], {
105486
+ const npm2 = resolveNpm();
105487
+ const installResult = spawnSync(npm2.command, [...npm2.args, "install", "--production"], {
105482
105488
  cwd: extractedDir,
105483
105489
  stdio: "pipe",
105484
105490
  encoding: "utf-8",
@@ -105723,7 +105729,7 @@ var import_semver = __toESM(require_semver2(), 1);
105723
105729
  // package.json
105724
105730
  var package_default = {
105725
105731
  name: "pullfrog",
105726
- version: "0.1.88",
105732
+ version: "0.1.90",
105727
105733
  cliContractVersion: "1",
105728
105734
  type: "module",
105729
105735
  bin: {
@@ -193159,8 +193165,9 @@ function ResolveReviewThreadTool(ctx) {
193159
193165
  }
193160
193166
 
193161
193167
  // mcp/searchIssues.ts
193168
+ var TERM = /^[\p{L}\p{N}_./-]+$/u;
193162
193169
  var SearchIssues = type({
193163
- terms: type.string.matching(/^[\p{L}\p{N}_./-]+$/u).atMostLength(40).array().atLeastLength(1).atMostLength(5).describe("1-5 keywords from the issue; no GitHub qualifiers or query operators")
193170
+ terms: type.string.atMostLength(40).array().atLeastLength(1).atMostLength(5).describe("1-5 keywords from the issue; no GitHub qualifiers or query operators")
193164
193171
  });
193165
193172
  function SearchIssuesTool(ctx) {
193166
193173
  return tool({
@@ -193168,6 +193175,10 @@ function SearchIssuesTool(ctx) {
193168
193175
  description: "Find open or closed issues in this repository by keyword. Inspect promising matches with get_issue before deciding whether they are duplicates or related. Results are bounded, not an exhaustive duplicate check.",
193169
193176
  parameters: SearchIssues,
193170
193177
  execute: execute(async (params) => {
193178
+ const invalid = params.terms.find((term) => !TERM.test(term));
193179
+ if (invalid) {
193180
+ throw new Error(`search term "${invalid}" may contain only letters, digits and _ . / -`);
193181
+ }
193171
193182
  const repoPath = `/repos/${ctx.repo.owner}/${ctx.repo.name}`.toLowerCase();
193172
193183
  const terms = params.terms.map((term) => `"${term}"`).join(" ");
193173
193184
  const response = await ctx.octokit.rest.search.issuesAndPullRequests({
@@ -196591,6 +196602,23 @@ ${input.raw}
196591
196602
  \`\`\``
196592
196603
  ].join("\n");
196593
196604
  }
196605
+ function isRunTimeoutError(message) {
196606
+ return message.startsWith("agent run timed out after");
196607
+ }
196608
+ function formatRunTimeoutSummary(input) {
196609
+ const settingsUrl = `${getApiUrl()}/console/${input.owner}/${input.name}`;
196610
+ return [
196611
+ "**This run hit its time limit while still working, so it stopped before finishing.**",
196612
+ "",
196613
+ "Give runs longer with `--timeout=2h` (or `--notimeout`) in this repo's instructions \u2014 or raise the workflow's `timeout` input if it sets one, since that input wins. A smaller PR or a lower effort also finishes sooner.",
196614
+ "",
196615
+ `[Repo settings \u2192](${settingsUrl}) \xB7 [Flags \u2192](https://docs.pullfrog.com/flags)`,
196616
+ "",
196617
+ `\`\`\`
196618
+ ${input.raw}
196619
+ \`\`\``
196620
+ ].join("\n");
196621
+ }
196594
196622
  function formatContextOverflowSummary(input) {
196595
196623
  const settingsUrl = `${getApiUrl()}/console/${input.owner}/${input.name}`;
196596
196624
  return [
@@ -196840,6 +196868,16 @@ ${body}`, comment: body };
196840
196868
  });
196841
196869
  return { summary: `### \u274C Pullfrog failed
196842
196870
 
196871
+ ${body}`, comment: body };
196872
+ }
196873
+ if (isRunTimeoutError(input.errorMessage)) {
196874
+ const body = formatRunTimeoutSummary({
196875
+ owner: input.repo.owner,
196876
+ name: input.repo.name,
196877
+ raw: input.errorMessage
196878
+ });
196879
+ return { summary: `### \u274C Pullfrog failed
196880
+
196843
196881
  ${body}`, comment: body };
196844
196882
  }
196845
196883
  if (isTransientUpstreamError(input.errorMessage)) {
@@ -24,6 +24,7 @@ export { countOutstandingPullfrogThreads } from "../utils/outstandingThreads.ts"
24
24
  export type { CreateProgressCommentTarget, ProgressComment, ProgressCommentType, } from "../utils/progressComment.ts";
25
25
  export { createLeapingProgressComment, deleteProgressCommentApi, getProgressComment, updateProgressComment, } from "../utils/progressComment.ts";
26
26
  export { PROVIDER_DASHBOARDS, type ProviderDashboard } from "../utils/providerDashboards.ts";
27
+ export { detectProviderError } from "../utils/providerErrors.ts";
27
28
  export type { RunStatusCheckConclusion, RunStatusCheckOctokit, } from "../utils/runStatusCheck.ts";
28
29
  export { APPROVAL_CHECK_NAME, createRunStatusCheck, finalizeRunStatusCheck, RUN_STATUS_CHECK_NAME, runStatusCheckNeedsFinalizing, } from "../utils/runStatusCheck.ts";
29
30
  export * from "../utils/subscriptionCredentials.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.88",
11
+ version: "0.1.90",
12
12
  cliContractVersion: "1",
13
13
  type: "module",
14
14
  bin: {
@@ -16821,6 +16821,146 @@ var PROVIDER_DASHBOARDS = {
16821
16821
  }
16822
16822
  };
16823
16823
 
16824
+ // utils/providerErrors.ts
16825
+ var PROVIDER_BILLING_EXHAUSTED_LABEL = "provider billing exhausted";
16826
+ var PROVIDER_NO_ENDPOINTS_LABEL = "provider no routable endpoints";
16827
+ var PROVIDER_USAGE_LIMIT_LABEL = "provider usage limit reached";
16828
+ var statusKey = `\\b(?:status[_ ]?code|http[_ ]?status|status)["']?\\s*[:=]\\s*["']?`;
16829
+ var ANTHROPIC_SPEND_CAP_PATTERN = /reached your specified API usage limits\.?(?:\s*You will regain access on ([^"\n]+?)\.)?/i;
16830
+ var PROVIDER_ERROR_PATTERNS = [
16831
+ // billing-payload patterns come BEFORE bare status-code patterns. providers
16832
+ // commonly return 401 / 429 for billing/quota exhaustion (OpenCode Zen
16833
+ // `CreditsError` / `FreeUsageLimitError`, Gemini `RESOURCE_EXHAUSTED` +
16834
+ // "spending cap", Anthropic "Insufficient balance" / "credit balance is
16835
+ // too low"). these are non-retryable and require user-billing action —
16836
+ // distinct from a transient auth error or rate-limit. status-code patterns
16837
+ // would otherwise win and surface "auth error (401)" / "rate limited (429)"
16838
+ // with no billing hint. see #778, #835.
16839
+ { regex: /\bCreditsError\b/, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
16840
+ { regex: /\bFreeUsageLimitError\b/, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
16841
+ // `balance` is DeepSeek's noun, `credits` is OpenRouter's for the SAME
16842
+ // terminal state — #1164's run 7 (`Insufficient credits. Add more using
16843
+ // …/settings/credits`) matched neither this nor `requires more credits`, so
16844
+ // the one run in that episode that was a genuinely empty wallet rendered with
16845
+ // no CTA at all while runs 1-6 rendered correctly.
16846
+ { regex: /Insufficient (?:balance|credits)/i, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
16847
+ { regex: /credit balance is too low/i, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
16848
+ // "spending cap" (Gemini) and "spending limit" (xAI, #1076) are the same
16849
+ // condition in two house styles — a configured ceiling was reached.
16850
+ { regex: /spending (?:cap|limit)/i, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
16851
+ // xAI exhausts credits and the monthly ceiling in one 403 whose status code
16852
+ // sits nowhere near a `status:` key, so no status pattern below fires (#1076).
16853
+ { regex: /used all available credits/i, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
16854
+ // Gemini's daily / free-tier cap. it tells the user to "check your plan and
16855
+ // billing details" in its own words but matches none of the wallet wordings
16856
+ // above, so it fell to the catch-all `quota` label — which is not the label
16857
+ // the actionable copy is keyed on, so the run rendered a blank comment for a
16858
+ // condition the provider had spelled out (#1114).
16859
+ {
16860
+ regex: /exceeded your current quota, please check your plan and billing/i,
16861
+ label: PROVIDER_BILLING_EXHAUSTED_LABEL
16862
+ },
16863
+ { regex: /generate_content_free_tier_requests/i, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
16864
+ // Anthropic's Console spend cap. its noun is "API usage limits", which none of
16865
+ // the wallet-shaped patterns above reach, and its 400 sits nowhere near a
16866
+ // `status:` key either — so the whole message was dropped on the floor (#1208).
16867
+ { regex: ANTHROPIC_SPEND_CAP_PATTERN, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
16868
+ // OpenCode Zen / Codex-subscription cap: `AI_APICallError: The usage limit has
16869
+ // been reached`. Its noun is "usage limit" with no "rate", no status code and
16870
+ // no wallet wording, so every pattern here missed it and `lastProviderError`
16871
+ // stayed unset — which suppresses nothing, so the run was reported as "The
16872
+ // model produced no output at all … usually transient; re-running often
16873
+ // succeeds" while the same log held nine of these. Measured on a live account
16874
+ // whose ChatGPT subscription was capped.
16875
+ { regex: /\bthe usage limit has been reached\b/i, label: PROVIDER_USAGE_LIMIT_LABEL },
16876
+ // the OpenRouter shapes `isRouterKeylimitExhaustedError` matches. on a Router
16877
+ // run those mean the PULLFROG wallet is empty and `renderRunError` returns a
16878
+ // `BillingError` before ever reaching this list; on a BYOK run the very same
16879
+ // wire text means the user's OWN OpenRouter wallet is empty, and this entry is
16880
+ // what gives them the right dashboard instead of ours. see #1135.
16881
+ {
16882
+ regex: /requires more credits|Key limit exceeded \(total limit\)/i,
16883
+ label: PROVIDER_BILLING_EXHAUSTED_LABEL
16884
+ },
16885
+ // OpenRouter has no endpoint it is allowed to route to: the customer's
16886
+ // data-policy / guardrail settings exclude every provider serving the model.
16887
+ // dies ~2s into the session with nothing billed, so it is neither a billing
16888
+ // nor an auth condition — and both of those patterns would otherwise be
16889
+ // reached first for a message that mentions neither. see #1164.
16890
+ {
16891
+ regex: /No endpoints available matching your (?:guardrail restrictions|data policy)/i,
16892
+ label: PROVIDER_NO_ENDPOINTS_LABEL
16893
+ },
16894
+ // auth patterns must come BEFORE rate-limit patterns. OpenRouter 401 error
16895
+ // payloads carry `x-ratelimit-*` response headers in the dump, and the
16896
+ // free-form rate-limit regex below would otherwise win on word-boundary
16897
+ // matches inside header names. canonical 401 messages: OpenRouter returns
16898
+ // `{"error":{"message":"User not found","code":401}}` for disabled or
16899
+ // invalid keys (https://openai.luzhipeng.com/docs/api/reference/errors-and-debugging).
16900
+ { regex: new RegExp(`${statusKey}401\\b`, "i"), label: "auth error (401)" },
16901
+ { regex: new RegExp(`${statusKey}403\\b`, "i"), label: "auth error (403)" },
16902
+ { regex: /\bUser not found\b/i, label: "auth error (invalid/disabled key)" },
16903
+ { regex: /\bInvalid authentication\b/i, label: "auth error (invalid credentials)" },
16904
+ { regex: /\bNo auth credentials found\b/i, label: "auth error (missing credentials)" },
16905
+ { regex: new RegExp(`${statusKey}429\\b`, "i"), label: "rate limited (429)" },
16906
+ { regex: new RegExp(`${statusKey}500\\b`, "i"), label: "provider 500 error" },
16907
+ { regex: new RegExp(`${statusKey}503\\b`, "i"), label: "provider unavailable (503)" },
16908
+ // matches `rate limit`, `rate limited`, `rate limits exceeded`,
16909
+ // `rate_limit_error`, `rate_limit_exceeded`. the leading `\b` + `[_ ]`
16910
+ // separator rejects `x-ratelimit-*` / `anthropic-ratelimit-*` response
16911
+ // headers (no separator between "rate" and "limit") which routinely
16912
+ // appear in dumped 401 / 4xx error JSON.
16913
+ { regex: /\brate[_ ]limit/i, label: "rate limited" },
16914
+ { regex: /\bRESOURCE_EXHAUSTED\b/, label: "quota exhausted" },
16915
+ // Google gRPC `INTERNAL` status. word-boundary anchors reject
16916
+ // `INTERNAL_SERVER_ERROR` (HTTP 500 message that may appear in unrelated
16917
+ // log lines) and identifiers like `INTERNALS`.
16918
+ { regex: /\bINTERNAL\b/, label: "provider internal error" },
16919
+ { regex: /\bUNAVAILABLE\b/, label: "provider unavailable" },
16920
+ // matches `quota`, `insufficient_quota`, `quota_exceeded`, `quotaExceeded`.
16921
+ // word-character lookarounds would reject `_quota` / `quotaX`; `quota` is
16922
+ // specific enough that a plain substring match is safe.
16923
+ { regex: /quota/i, label: "quota error" },
16924
+ // explicit zero-quota response, e.g. `{"limit": 0}`. the `\b` anchor
16925
+ // around `limit` rejects keys like `time_limit` or `field_limit`.
16926
+ { regex: /["']?\blimit\b["']?\s*:\s*0\b/, label: "zero quota" }
16927
+ ];
16928
+ var EXCERPT_MAX_BYTES = 600;
16929
+ var LINES_BEFORE = 1;
16930
+ var LINES_AFTER = 2;
16931
+ function findProviderErrorMatch(text) {
16932
+ for (const entry of PROVIDER_ERROR_PATTERNS) {
16933
+ const m = entry.regex.exec(text);
16934
+ if (!m) continue;
16935
+ return { label: entry.label, excerpt: extractExcerpt(text, m.index) };
16936
+ }
16937
+ return null;
16938
+ }
16939
+ function detectProviderError(text) {
16940
+ return findProviderErrorMatch(text)?.label ?? null;
16941
+ }
16942
+ function extractExcerpt(text, matchIndex) {
16943
+ const lineStart = text.lastIndexOf("\n", matchIndex - 1) + 1;
16944
+ const lineEndRaw = text.indexOf("\n", matchIndex);
16945
+ const lineEnd = lineEndRaw === -1 ? text.length : lineEndRaw;
16946
+ let start = lineStart;
16947
+ for (let i = 0; i < LINES_BEFORE && start > 0; i++) {
16948
+ const prev = text.lastIndexOf("\n", start - 2);
16949
+ start = prev < 0 ? 0 : prev + 1;
16950
+ }
16951
+ let end = lineEnd;
16952
+ for (let i = 0; i < LINES_AFTER && end < text.length; i++) {
16953
+ const next = text.indexOf("\n", end + 1);
16954
+ end = next < 0 ? text.length : next;
16955
+ }
16956
+ let excerpt = text.slice(start, end);
16957
+ if (excerpt.length > EXCERPT_MAX_BYTES) {
16958
+ excerpt = text.slice(lineStart, lineEnd);
16959
+ if (excerpt.length > EXCERPT_MAX_BYTES) excerpt = excerpt.slice(0, EXCERPT_MAX_BYTES);
16960
+ }
16961
+ return excerpt.trim();
16962
+ }
16963
+
16824
16964
  // utils/runStatusCheck.ts
16825
16965
  var RUN_STATUS_CHECK_NAME = "pullfrog";
16826
16966
  var GITHUB_ACTIONS_APP_SLUG = "github-actions";
@@ -17214,6 +17354,7 @@ export {
17214
17354
  decodeJwtExpMs,
17215
17355
  defaultAutoTier,
17216
17356
  deleteProgressCommentApi,
17357
+ detectProviderError,
17217
17358
  finalizeRunStatusCheck,
17218
17359
  getAutoSelectHintModel,
17219
17360
  getModelEffortLevels,
@@ -40,6 +40,9 @@
40
40
  * `maximum context length is N tokens`. Actionable, so it renders on
41
41
  * both surfaces rather than collapsing to the one-line comment.
42
42
  *
43
+ * 4b'. Run time limit — the agent was still working when the run's own
44
+ * timeout fired. Actionable (raise the limit), so both surfaces.
45
+ *
43
46
  * 4c. Transient upstream failure (#1173) — Anthropic `529 Overloaded`,
44
47
  * OpenRouter `provider_unavailable` / `timeout`, Zen `Streaming response
45
48
  * failed: [5xx]`. Last of the classified branches so every more specific
@@ -63,8 +66,8 @@
63
66
  * the hang case, since the raw internal string helps nobody on the PR.
64
67
  *
65
68
  * Net: the actionable classifications (billing, API-key, model-not-found,
66
- * no-provider-available, context-overflow, transient-upstream) render identical
67
- * bodies on both surfaces; the non-actionable ones (unexplained hang,
69
+ * no-provider-available, context-overflow, run time limit, transient-upstream)
70
+ * render identical bodies on both surfaces; the non-actionable ones (unexplained hang,
68
71
  * generic) keep the forensics in the Actions job summary and show a calm
69
72
  * one-liner in the PR comment, whose footer already carries Pullfrog
70
73
  * branding + rerun links.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pullfrog",
3
- "version": "0.1.88",
3
+ "version": "0.1.90",
4
4
  "cliContractVersion": "1",
5
5
  "type": "module",
6
6
  "bin": {