pullfrog 0.1.62 → 0.1.64

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
@@ -106004,7 +106004,16 @@ var providers = {
106004
106004
  resolve: "opencode/claude-opus-5",
106005
106005
  effort: ["low", "medium", "high", "xhigh", "max"],
106006
106006
  openRouterResolve: "openrouter/anthropic/claude-opus-5",
106007
- subagentModel: "claude-sonnet"
106007
+ subagentModel: "claude-sonnet",
106008
+ // TEMPORARY — clear this when Zen serves opus again. Zen still LISTS
106009
+ // claude-opus-5 in /zen/v1/models, so the catalog test passes, but the
106010
+ // endpoint answers 503 `Upstream request failed: Endpoint is
106011
+ // unavailable.` (measured 5/5; claude-sonnet-5, claude-opus-4-8,
106012
+ // claude-haiku-4-5 and claude-fable-5 all 200 on the same key). opencode
106013
+ // retries the 503 above the AI SDK emitting no part.updated, so a run
106014
+ // just produces nothing until it is killed — metaideas/init logged six
106015
+ // zero-output failures from 2026-08-23. see wiki/opencode-silent-stall.md
106016
+ fallback: "opencode/claude-sonnet"
106008
106017
  },
106009
106018
  "claude-sonnet": {
106010
106019
  displayName: "Claude Sonnet",
@@ -107358,7 +107367,7 @@ var import_semver = __toESM(require_semver2(), 1);
107358
107367
  // package.json
107359
107368
  var package_default = {
107360
107369
  name: "pullfrog",
107361
- version: "0.1.62",
107370
+ version: "0.1.64",
107362
107371
  type: "module",
107363
107372
  bin: {
107364
107373
  pullfrog: "dist/cli.mjs",
@@ -116237,7 +116246,7 @@ function isModelOutput(ctx, part) {
116237
116246
  async function dispatchEvent(ctx, event) {
116238
116247
  if (event.type === "message.part.updated") {
116239
116248
  ctx.lastEventAt = performance7.now();
116240
- if (isModelOutput(ctx, event.properties.part)) ctx.sawModelOutput = true;
116249
+ if (isModelOutput(ctx, event.properties.part)) ctx.diagnostic.sawModelOutput = true;
116241
116250
  await onPartUpdated(ctx, event.properties.part);
116242
116251
  return;
116243
116252
  }
@@ -116383,7 +116392,8 @@ async function runPromptTurn(ctx, params) {
116383
116392
  const turn = ctx.currentTurn;
116384
116393
  const part = { type: "text", text: params.text };
116385
116394
  ctx.promptMessageID = void 0;
116386
- ctx.sawModelOutput = false;
116395
+ ctx.diagnostic.sawModelOutput = false;
116396
+ ctx.diagnostic.idleSec = void 0;
116387
116397
  let assistant;
116388
116398
  let returnedParts;
116389
116399
  let networkError = null;
@@ -116443,7 +116453,7 @@ async function runPromptTurn(ctx, params) {
116443
116453
  usage
116444
116454
  };
116445
116455
  }
116446
- if (!ctx.sawModelOutput && finalText.trim().length === 0 && !usage) {
116456
+ if (!ctx.diagnostic.sawModelOutput && finalText.trim().length === 0 && !usage) {
116447
116457
  return {
116448
116458
  success: false,
116449
116459
  output: finalText,
@@ -116568,19 +116578,18 @@ function startInnerActivityWatchdog(params) {
116568
116578
  const id = setInterval(() => {
116569
116579
  if (fired) return;
116570
116580
  const idleMs = performance7.now() - params.ctx.lastEventAt;
116571
- const compiledMs = params.ctx.sawModelOutput ? params.timeoutMs : AGENT_FIRST_EVENT_TIMEOUT_MS;
116581
+ const compiledMs = params.ctx.diagnostic.sawModelOutput ? params.timeoutMs : AGENT_FIRST_EVENT_TIMEOUT_MS;
116572
116582
  const budgetMs = everFired ? compiledMs : watchdogBudgetMs(
116573
116583
  compiledMs,
116574
- params.ctx.sawModelOutput ? "PULLFROG_E2E_ACTIVITY_TIMEOUT_MS" : "PULLFROG_E2E_FIRST_EVENT_TIMEOUT_MS"
116584
+ params.ctx.diagnostic.sawModelOutput ? "PULLFROG_E2E_ACTIVITY_TIMEOUT_MS" : "PULLFROG_E2E_FIRST_EVENT_TIMEOUT_MS"
116575
116585
  );
116576
116586
  if (idleMs <= budgetMs) return;
116577
116587
  fired = true;
116578
116588
  everFired = true;
116579
116589
  const idleSec = Math.round(idleMs / 1e3);
116580
116590
  params.ctx.diagnostic.idleSec = idleSec;
116581
- params.ctx.diagnostic.sawModelOutput = params.ctx.sawModelOutput;
116582
116591
  log2.info(
116583
- params.ctx.sawModelOutput ? `\xBB no opencode events for ${idleSec}s \u2014 aborting in-flight prompt and notifying harness` : `\xBB no opencode events for ${idleSec}s \u2014 the provider never returned a first token; aborting in-flight prompt and notifying harness`
116592
+ params.ctx.diagnostic.sawModelOutput ? `\xBB no opencode events for ${idleSec}s \u2014 aborting in-flight prompt and notifying harness` : `\xBB no opencode events for ${idleSec}s \u2014 the provider never returned a first token; aborting in-flight prompt and notifying harness`
116584
116593
  );
116585
116594
  params.abortTurn();
116586
116595
  try {
@@ -116724,7 +116733,6 @@ var opencode = agent({
116724
116733
  currentTurn: null,
116725
116734
  eventCount: 0,
116726
116735
  lastEventAt: performance7.now(),
116727
- sawModelOutput: false,
116728
116736
  promptMessageID: void 0,
116729
116737
  taskDispatchByCallID: /* @__PURE__ */ new Map(),
116730
116738
  loggedToolCallIDs: /* @__PURE__ */ new Set(),
@@ -116788,7 +116796,9 @@ var opencode = agent({
116788
116796
  );
116789
116797
  };
116790
116798
  const standDownIfRecovered = (turn) => {
116791
- if (watchdog.firedThisTurn() && turn.success) ctx.onTurnRecovered?.();
116799
+ if (!watchdog.firedThisTurn() || !turn.success) return turn;
116800
+ runnerCtx.diagnostic.idleSec = void 0;
116801
+ ctx.onTurnRecovered?.();
116792
116802
  return turn;
116793
116803
  };
116794
116804
  const result = await attempt(text);
@@ -196276,6 +196286,43 @@ function resolveCwd(cwd) {
196276
196286
  if (isAbsolute2(cwd)) return cwd;
196277
196287
  return workspace ? resolve2(workspace, cwd) : cwd;
196278
196288
  }
196289
+ var DISPATCH_PAYLOAD_KEY = '"~pullfrog"';
196290
+ function sliceBalancedObject(text, open) {
196291
+ let depth = 0;
196292
+ let inString = false;
196293
+ for (let i2 = open; i2 < text.length; i2++) {
196294
+ const char = text[i2];
196295
+ if (inString) {
196296
+ if (char === "\\") i2++;
196297
+ else if (char === '"') inString = false;
196298
+ continue;
196299
+ }
196300
+ if (char === '"') inString = true;
196301
+ else if (char === "{") depth++;
196302
+ else if (char === "}" && --depth === 0) return text.slice(open, i2 + 1);
196303
+ }
196304
+ return void 0;
196305
+ }
196306
+ function findEmbeddedDispatchPayload(text) {
196307
+ const limit = text.lastIndexOf(DISPATCH_PAYLOAD_KEY);
196308
+ if (limit === -1) return void 0;
196309
+ for (let open = text.indexOf("{"); open !== -1 && open < limit; open = text.indexOf("{", open + 1)) {
196310
+ const candidate = sliceBalancedObject(text, open);
196311
+ if (!candidate) continue;
196312
+ try {
196313
+ const parsed2 = JSON.parse(candidate);
196314
+ if (JsonPayload.allows(parsed2)) return parsed2;
196315
+ } catch {
196316
+ }
196317
+ }
196318
+ return void 0;
196319
+ }
196320
+ function assertDispatchPayloadIntact(promptInput) {
196321
+ if (!findEmbeddedDispatchPayload(promptInput)) return;
196322
+ throw new Error(
196323
+ "this workflow wraps `${{ inputs.prompt }}` in other text, so Pullfrog's dispatch payload can't be read and the run would lose its pull request context, its progress comment, and its standing instructions. in `.github/workflows/pullfrog.yml`, set `prompt:` to exactly `${{ inputs.prompt }}`, and move your own preamble into Standing instructions in the Pullfrog console."
196324
+ );
196325
+ }
196279
196326
  function resolvePromptInput() {
196280
196327
  const promptInput = core7.getInput("prompt");
196281
196328
  const promptFile = core7.getInput("prompt_file");
@@ -196292,9 +196339,9 @@ function resolvePromptInput() {
196292
196339
  try {
196293
196340
  parsed2 = JSON.parse(promptInput);
196294
196341
  } catch {
196295
- return promptInput;
196296
196342
  }
196297
196343
  if (!parsed2 || typeof parsed2 !== "object" || !("~pullfrog" in parsed2)) {
196344
+ assertDispatchPayloadIntact(promptInput);
196298
196345
  return promptInput;
196299
196346
  }
196300
196347
  const jsonPayload = JsonPayload.assert(parsed2);
@@ -197472,7 +197519,7 @@ ${body}`, comment: body };
197472
197519
  ${body}`, comment: body };
197473
197520
  }
197474
197521
  if (hangBody) {
197475
- const explained = input.agentDiagnostic?.lastProviderError !== void 0;
197522
+ const explained = input.agentDiagnostic?.lastProviderError !== void 0 || input.agentDiagnostic?.sawModelOutput === false;
197476
197523
  return {
197477
197524
  summary: `### \u274C Pullfrog failed
197478
197525
 
@@ -199265,7 +199312,7 @@ async function runCli4(input) {
199265
199312
  }
199266
199313
 
199267
199314
  // cli.ts
199268
- var VERSION10 = "0.1.62";
199315
+ var VERSION10 = "0.1.64";
199269
199316
  var bin = basename2(process.argv[1] || "");
199270
199317
  var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
199271
199318
  var rawArgs = process.argv.slice(2);
package/dist/index.js CHANGED
@@ -103837,7 +103837,16 @@ var providers = {
103837
103837
  resolve: "opencode/claude-opus-5",
103838
103838
  effort: ["low", "medium", "high", "xhigh", "max"],
103839
103839
  openRouterResolve: "openrouter/anthropic/claude-opus-5",
103840
- subagentModel: "claude-sonnet"
103840
+ subagentModel: "claude-sonnet",
103841
+ // TEMPORARY — clear this when Zen serves opus again. Zen still LISTS
103842
+ // claude-opus-5 in /zen/v1/models, so the catalog test passes, but the
103843
+ // endpoint answers 503 `Upstream request failed: Endpoint is
103844
+ // unavailable.` (measured 5/5; claude-sonnet-5, claude-opus-4-8,
103845
+ // claude-haiku-4-5 and claude-fable-5 all 200 on the same key). opencode
103846
+ // retries the 503 above the AI SDK emitting no part.updated, so a run
103847
+ // just produces nothing until it is killed — metaideas/init logged six
103848
+ // zero-output failures from 2026-08-23. see wiki/opencode-silent-stall.md
103849
+ fallback: "opencode/claude-sonnet"
103841
103850
  },
103842
103851
  "claude-sonnet": {
103843
103852
  displayName: "Claude Sonnet",
@@ -105191,7 +105200,7 @@ var import_semver = __toESM(require_semver2(), 1);
105191
105200
  // package.json
105192
105201
  var package_default = {
105193
105202
  name: "pullfrog",
105194
- version: "0.1.62",
105203
+ version: "0.1.64",
105195
105204
  type: "module",
105196
105205
  bin: {
105197
105206
  pullfrog: "dist/cli.mjs",
@@ -114117,7 +114126,7 @@ function isModelOutput(ctx, part) {
114117
114126
  async function dispatchEvent(ctx, event) {
114118
114127
  if (event.type === "message.part.updated") {
114119
114128
  ctx.lastEventAt = performance7.now();
114120
- if (isModelOutput(ctx, event.properties.part)) ctx.sawModelOutput = true;
114129
+ if (isModelOutput(ctx, event.properties.part)) ctx.diagnostic.sawModelOutput = true;
114121
114130
  await onPartUpdated(ctx, event.properties.part);
114122
114131
  return;
114123
114132
  }
@@ -114263,7 +114272,8 @@ async function runPromptTurn(ctx, params) {
114263
114272
  const turn = ctx.currentTurn;
114264
114273
  const part = { type: "text", text: params.text };
114265
114274
  ctx.promptMessageID = void 0;
114266
- ctx.sawModelOutput = false;
114275
+ ctx.diagnostic.sawModelOutput = false;
114276
+ ctx.diagnostic.idleSec = void 0;
114267
114277
  let assistant;
114268
114278
  let returnedParts;
114269
114279
  let networkError = null;
@@ -114323,7 +114333,7 @@ async function runPromptTurn(ctx, params) {
114323
114333
  usage
114324
114334
  };
114325
114335
  }
114326
- if (!ctx.sawModelOutput && finalText.trim().length === 0 && !usage) {
114336
+ if (!ctx.diagnostic.sawModelOutput && finalText.trim().length === 0 && !usage) {
114327
114337
  return {
114328
114338
  success: false,
114329
114339
  output: finalText,
@@ -114448,19 +114458,18 @@ function startInnerActivityWatchdog(params) {
114448
114458
  const id = setInterval(() => {
114449
114459
  if (fired) return;
114450
114460
  const idleMs = performance7.now() - params.ctx.lastEventAt;
114451
- const compiledMs = params.ctx.sawModelOutput ? params.timeoutMs : AGENT_FIRST_EVENT_TIMEOUT_MS;
114461
+ const compiledMs = params.ctx.diagnostic.sawModelOutput ? params.timeoutMs : AGENT_FIRST_EVENT_TIMEOUT_MS;
114452
114462
  const budgetMs = everFired ? compiledMs : watchdogBudgetMs(
114453
114463
  compiledMs,
114454
- params.ctx.sawModelOutput ? "PULLFROG_E2E_ACTIVITY_TIMEOUT_MS" : "PULLFROG_E2E_FIRST_EVENT_TIMEOUT_MS"
114464
+ params.ctx.diagnostic.sawModelOutput ? "PULLFROG_E2E_ACTIVITY_TIMEOUT_MS" : "PULLFROG_E2E_FIRST_EVENT_TIMEOUT_MS"
114455
114465
  );
114456
114466
  if (idleMs <= budgetMs) return;
114457
114467
  fired = true;
114458
114468
  everFired = true;
114459
114469
  const idleSec = Math.round(idleMs / 1e3);
114460
114470
  params.ctx.diagnostic.idleSec = idleSec;
114461
- params.ctx.diagnostic.sawModelOutput = params.ctx.sawModelOutput;
114462
114471
  log.info(
114463
- params.ctx.sawModelOutput ? `\xBB no opencode events for ${idleSec}s \u2014 aborting in-flight prompt and notifying harness` : `\xBB no opencode events for ${idleSec}s \u2014 the provider never returned a first token; aborting in-flight prompt and notifying harness`
114472
+ params.ctx.diagnostic.sawModelOutput ? `\xBB no opencode events for ${idleSec}s \u2014 aborting in-flight prompt and notifying harness` : `\xBB no opencode events for ${idleSec}s \u2014 the provider never returned a first token; aborting in-flight prompt and notifying harness`
114464
114473
  );
114465
114474
  params.abortTurn();
114466
114475
  try {
@@ -114604,7 +114613,6 @@ var opencode = agent({
114604
114613
  currentTurn: null,
114605
114614
  eventCount: 0,
114606
114615
  lastEventAt: performance7.now(),
114607
- sawModelOutput: false,
114608
114616
  promptMessageID: void 0,
114609
114617
  taskDispatchByCallID: /* @__PURE__ */ new Map(),
114610
114618
  loggedToolCallIDs: /* @__PURE__ */ new Set(),
@@ -114668,7 +114676,9 @@ var opencode = agent({
114668
114676
  );
114669
114677
  };
114670
114678
  const standDownIfRecovered = (turn) => {
114671
- if (watchdog.firedThisTurn() && turn.success) ctx.onTurnRecovered?.();
114679
+ if (!watchdog.firedThisTurn() || !turn.success) return turn;
114680
+ runnerCtx.diagnostic.idleSec = void 0;
114681
+ ctx.onTurnRecovered?.();
114672
114682
  return turn;
114673
114683
  };
114674
114684
  const result = await attempt(text);
@@ -194156,6 +194166,43 @@ function resolveCwd(cwd) {
194156
194166
  if (isAbsolute2(cwd)) return cwd;
194157
194167
  return workspace ? resolve2(workspace, cwd) : cwd;
194158
194168
  }
194169
+ var DISPATCH_PAYLOAD_KEY = '"~pullfrog"';
194170
+ function sliceBalancedObject(text, open) {
194171
+ let depth = 0;
194172
+ let inString = false;
194173
+ for (let i = open; i < text.length; i++) {
194174
+ const char = text[i];
194175
+ if (inString) {
194176
+ if (char === "\\") i++;
194177
+ else if (char === '"') inString = false;
194178
+ continue;
194179
+ }
194180
+ if (char === '"') inString = true;
194181
+ else if (char === "{") depth++;
194182
+ else if (char === "}" && --depth === 0) return text.slice(open, i + 1);
194183
+ }
194184
+ return void 0;
194185
+ }
194186
+ function findEmbeddedDispatchPayload(text) {
194187
+ const limit = text.lastIndexOf(DISPATCH_PAYLOAD_KEY);
194188
+ if (limit === -1) return void 0;
194189
+ for (let open = text.indexOf("{"); open !== -1 && open < limit; open = text.indexOf("{", open + 1)) {
194190
+ const candidate = sliceBalancedObject(text, open);
194191
+ if (!candidate) continue;
194192
+ try {
194193
+ const parsed2 = JSON.parse(candidate);
194194
+ if (JsonPayload.allows(parsed2)) return parsed2;
194195
+ } catch {
194196
+ }
194197
+ }
194198
+ return void 0;
194199
+ }
194200
+ function assertDispatchPayloadIntact(promptInput) {
194201
+ if (!findEmbeddedDispatchPayload(promptInput)) return;
194202
+ throw new Error(
194203
+ "this workflow wraps `${{ inputs.prompt }}` in other text, so Pullfrog's dispatch payload can't be read and the run would lose its pull request context, its progress comment, and its standing instructions. in `.github/workflows/pullfrog.yml`, set `prompt:` to exactly `${{ inputs.prompt }}`, and move your own preamble into Standing instructions in the Pullfrog console."
194204
+ );
194205
+ }
194159
194206
  function resolvePromptInput() {
194160
194207
  const promptInput = core7.getInput("prompt");
194161
194208
  const promptFile = core7.getInput("prompt_file");
@@ -194172,9 +194219,9 @@ function resolvePromptInput() {
194172
194219
  try {
194173
194220
  parsed2 = JSON.parse(promptInput);
194174
194221
  } catch {
194175
- return promptInput;
194176
194222
  }
194177
194223
  if (!parsed2 || typeof parsed2 !== "object" || !("~pullfrog" in parsed2)) {
194224
+ assertDispatchPayloadIntact(promptInput);
194178
194225
  return promptInput;
194179
194226
  }
194180
194227
  const jsonPayload = JsonPayload.assert(parsed2);
@@ -195352,7 +195399,7 @@ ${body}`, comment: body };
195352
195399
  ${body}`, comment: body };
195353
195400
  }
195354
195401
  if (hangBody) {
195355
- const explained = input.agentDiagnostic?.lastProviderError !== void 0;
195402
+ const explained = input.agentDiagnostic?.lastProviderError !== void 0 || input.agentDiagnostic?.sawModelOutput === false;
195356
195403
  return {
195357
195404
  summary: `### \u274C Pullfrog failed
195358
195405
 
package/dist/internal.js CHANGED
@@ -349,7 +349,16 @@ var providers = {
349
349
  resolve: "opencode/claude-opus-5",
350
350
  effort: ["low", "medium", "high", "xhigh", "max"],
351
351
  openRouterResolve: "openrouter/anthropic/claude-opus-5",
352
- subagentModel: "claude-sonnet"
352
+ subagentModel: "claude-sonnet",
353
+ // TEMPORARY — clear this when Zen serves opus again. Zen still LISTS
354
+ // claude-opus-5 in /zen/v1/models, so the catalog test passes, but the
355
+ // endpoint answers 503 `Upstream request failed: Endpoint is
356
+ // unavailable.` (measured 5/5; claude-sonnet-5, claude-opus-4-8,
357
+ // claude-haiku-4-5 and claude-fable-5 all 200 on the same key). opencode
358
+ // retries the 503 above the AI SDK emitting no part.updated, so a run
359
+ // just produces nothing until it is killed — metaideas/init logged six
360
+ // zero-output failures from 2026-08-23. see wiki/opencode-silent-stall.md
361
+ fallback: "opencode/claude-sonnet"
353
362
  },
354
363
  "claude-sonnet": {
355
364
  displayName: "Claude Sonnet",
@@ -45,12 +45,12 @@
45
45
  * `"activity timeout"` or `"agent still pending"` AND none of the
46
46
  * above matched. The harness keeps structured diagnostic state on
47
47
  * `toolState.agentDiagnostic`; `formatAgentHangBody` renders that into
48
- * the job summary. The PR comment instead collapses to a one-line
49
- * `**Run failed.** [View the logs →]` the watchdog jargon, event
50
- * counts, and benign stderr tail are operator-grade detail that only
51
- * alarm the average user. The one exception is a hang masking billing
52
- * exhaustion (#778), where `formatAgentHangBody` emits an actionable
53
- * top-up CTA that the comment keeps verbatim.
48
+ * the job summary. The PR comment keeps that same body whenever the hang
49
+ * is EXPLAINED a classified provider error (#778, #1183) or a provider
50
+ * that never returned a first token and otherwise collapses to a
51
+ * one-line `**Run failed.** [View the logs →]`, because watchdog jargon,
52
+ * event counts and a benign stderr tail are operator-grade detail that
53
+ * only alarm the average user.
54
54
  *
55
55
  * 6. Default — the job summary gets a plain-English lead sentence plus the
56
56
  * raw error in a fenced code block under the `### ❌ Pullfrog failed`
@@ -59,7 +59,7 @@
59
59
  *
60
60
  * Net: the actionable classifications (billing, API-key, model-not-found,
61
61
  * no-provider-available, context-overflow, transient-upstream) render identical
62
- * bodies on both surfaces; the non-actionable ones (hang,
62
+ * bodies on both surfaces; the non-actionable ones (unexplained hang,
63
63
  * generic) keep the forensics in the Actions job summary and show a calm
64
64
  * one-liner in the PR comment, whose footer already carries Pullfrog
65
65
  * branding + rerun links.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pullfrog",
3
- "version": "0.1.62",
3
+ "version": "0.1.64",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "pullfrog": "dist/cli.mjs",