pullfrog 0.1.5 → 0.1.6

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/index.js CHANGED
@@ -19718,10 +19718,10 @@ var require_core = __commonJS({
19718
19718
  (0, command_1.issueCommand)("set-env", { name }, convertedVal);
19719
19719
  }
19720
19720
  exports.exportVariable = exportVariable;
19721
- function setSecret3(secret) {
19721
+ function setSecret4(secret) {
19722
19722
  (0, command_1.issueCommand)("add-mask", {}, secret);
19723
19723
  }
19724
- exports.setSecret = setSecret3;
19724
+ exports.setSecret = setSecret4;
19725
19725
  function addPath(inputPath) {
19726
19726
  const filePath = process.env["GITHUB_PATH"] || "";
19727
19727
  if (filePath) {
@@ -47737,7 +47737,7 @@ var require_core3 = __commonJS({
47737
47737
  Object.defineProperty(exports, "__esModule", { value: true });
47738
47738
  var id_1 = require_id();
47739
47739
  var ref_1 = require_ref();
47740
- var core7 = [
47740
+ var core8 = [
47741
47741
  "$schema",
47742
47742
  "$id",
47743
47743
  "$defs",
@@ -47747,7 +47747,7 @@ var require_core3 = __commonJS({
47747
47747
  id_1.default,
47748
47748
  ref_1.default
47749
47749
  ];
47750
- exports.default = core7;
47750
+ exports.default = core8;
47751
47751
  }
47752
47752
  });
47753
47753
 
@@ -98924,7 +98924,7 @@ var require_fast_content_type_parse = __commonJS({
98924
98924
  });
98925
98925
 
98926
98926
  // main.ts
98927
- var core6 = __toESM(require_core(), 1);
98927
+ var core7 = __toESM(require_core(), 1);
98928
98928
  import { existsSync as existsSync7, readdirSync } from "node:fs";
98929
98929
  import { readFile as readFile4 } from "node:fs/promises";
98930
98930
  import { join as join17 } from "node:path";
@@ -109742,12 +109742,41 @@ function installSignalHandler() {
109742
109742
  killTrackedChildren();
109743
109743
  });
109744
109744
  }
109745
+ var DEFAULT_MAX_RETAINED_BYTES = 8 * 1024 * 1024;
109746
+ var TailBuffer = class {
109747
+ // explicit field declarations rather than constructor parameter properties:
109748
+ // node's strip-only TS loader (used by action/test/run.ts in CI) rejects
109749
+ // `constructor(private readonly cap: number)` with ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX.
109750
+ cap;
109751
+ buffer = "";
109752
+ truncatedBytes = 0;
109753
+ constructor(cap) {
109754
+ this.cap = cap;
109755
+ }
109756
+ append(chunk) {
109757
+ if (this.cap <= 0) return;
109758
+ this.buffer += chunk;
109759
+ if (this.buffer.length > this.cap) {
109760
+ const drop = this.buffer.length - this.cap;
109761
+ this.truncatedBytes += drop;
109762
+ this.buffer = this.buffer.slice(drop);
109763
+ }
109764
+ }
109765
+ toString() {
109766
+ if (this.truncatedBytes === 0) return this.buffer;
109767
+ const mib = (this.truncatedBytes / 1024 / 1024).toFixed(1);
109768
+ return `... [${mib} MiB truncated by retain:tail cap] ...
109769
+ ${this.buffer}`;
109770
+ }
109771
+ };
109745
109772
  async function spawn(options) {
109746
109773
  const activityTimeoutMs = options.activityTimeout ?? DEFAULT_ACTIVITY_TIMEOUT_MS;
109747
109774
  installSignalHandler();
109748
109775
  const startTime = performance3.now();
109749
- let stdoutBuffer = "";
109750
- let stderrBuffer = "";
109776
+ const retain = options.retain ?? "tail";
109777
+ const cap = options.maxRetainedBytes ?? DEFAULT_MAX_RETAINED_BYTES;
109778
+ const stdoutBuffer = retain === "none" ? null : new TailBuffer(cap);
109779
+ const stderrBuffer = retain === "none" ? null : new TailBuffer(cap);
109751
109780
  const killGroup = options.killGroup ?? false;
109752
109781
  return new Promise((resolve3, reject) => {
109753
109782
  const child = nodeSpawn(options.cmd, options.args, {
@@ -109821,17 +109850,29 @@ async function spawn(options) {
109821
109850
  }
109822
109851
  if (child.stdout) {
109823
109852
  child.stdout.on("data", (data) => {
109824
- updateActivity();
109825
- const chunk = data.toString();
109826
- stdoutBuffer += chunk;
109827
- options.onStdout?.(chunk);
109853
+ try {
109854
+ updateActivity();
109855
+ const chunk = data.toString();
109856
+ stdoutBuffer?.append(chunk);
109857
+ options.onStdout?.(chunk);
109858
+ } catch (err) {
109859
+ log.debug(
109860
+ `spawn stdout handler threw: ${err instanceof Error ? err.message : String(err)}`
109861
+ );
109862
+ }
109828
109863
  });
109829
109864
  }
109830
109865
  if (child.stderr) {
109831
109866
  child.stderr.on("data", (data) => {
109832
- const chunk = data.toString();
109833
- stderrBuffer += chunk;
109834
- options.onStderr?.(chunk);
109867
+ try {
109868
+ const chunk = data.toString();
109869
+ stderrBuffer?.append(chunk);
109870
+ options.onStderr?.(chunk);
109871
+ } catch (err) {
109872
+ log.debug(
109873
+ `spawn stderr handler threw: ${err instanceof Error ? err.message : String(err)}`
109874
+ );
109875
+ }
109835
109876
  });
109836
109877
  }
109837
109878
  child.on("close", (exitCode, signal) => {
@@ -109858,7 +109899,7 @@ async function spawn(options) {
109858
109899
  return;
109859
109900
  }
109860
109901
  let resolvedExitCode = exitCode ?? 0;
109861
- let resolvedStderr = stderrBuffer;
109902
+ let resolvedStderr = stderrBuffer?.toString() ?? "";
109862
109903
  if (exitCode === null && signal) {
109863
109904
  const killMsg = `[spawn] ${options.cmd}: killed by signal ${signal}`;
109864
109905
  resolvedStderr = resolvedStderr ? `${resolvedStderr}
@@ -109866,7 +109907,7 @@ ${killMsg}` : killMsg;
109866
109907
  resolvedExitCode = 1;
109867
109908
  }
109868
109909
  resolve3({
109869
- stdout: stdoutBuffer,
109910
+ stdout: stdoutBuffer?.toString() ?? "",
109870
109911
  stderr: resolvedStderr,
109871
109912
  exitCode: resolvedExitCode,
109872
109913
  durationMs
@@ -109880,11 +109921,12 @@ ${killMsg}` : killMsg;
109880
109921
  if (activityCheckIntervalId) clearInterval(activityCheckIntervalId);
109881
109922
  const errMsg = `[spawn] ${options.cmd}: ${error49.message}`;
109882
109923
  console.error(errMsg);
109883
- stderrBuffer = stderrBuffer ? `${stderrBuffer}
109924
+ const existingStderr = stderrBuffer?.toString() ?? "";
109925
+ const finalStderr = existingStderr ? `${existingStderr}
109884
109926
  ${errMsg}` : errMsg;
109885
109927
  resolve3({
109886
- stdout: stdoutBuffer,
109887
- stderr: stderrBuffer,
109928
+ stdout: stdoutBuffer?.toString() ?? "",
109929
+ stderr: finalStderr,
109888
109930
  exitCode: 1,
109889
109931
  durationMs
109890
109932
  });
@@ -137793,7 +137835,7 @@ var require_core4 = /* @__PURE__ */ __commonJSMin(((exports) => {
137793
137835
  Object.defineProperty(exports, "__esModule", { value: true });
137794
137836
  const id_1 = require_id2();
137795
137837
  const ref_1 = require_ref2();
137796
- const core7 = [
137838
+ const core8 = [
137797
137839
  "$schema",
137798
137840
  "$id",
137799
137841
  "$defs",
@@ -137803,7 +137845,7 @@ var require_core4 = /* @__PURE__ */ __commonJSMin(((exports) => {
137803
137845
  id_1.default,
137804
137846
  ref_1.default
137805
137847
  ];
137806
- exports.default = core7;
137848
+ exports.default = core8;
137807
137849
  }));
137808
137850
  var require_limitNumber2 = /* @__PURE__ */ __commonJSMin(((exports) => {
137809
137851
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -142313,7 +142355,7 @@ var import_semver = __toESM(require_semver2(), 1);
142313
142355
  // package.json
142314
142356
  var package_default = {
142315
142357
  name: "pullfrog",
142316
- version: "0.1.5",
142358
+ version: "0.1.6",
142317
142359
  type: "module",
142318
142360
  bin: {
142319
142361
  pullfrog: "dist/cli.mjs",
@@ -146390,6 +146432,8 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
146390
146432
  - **4\u20135 lenses (high-stakes subsystem touches)** \u2014 any billing/payments change (billing-subsystem + correctness + security + operational-readiness); new auth flow (auth-subsystem + correctness + security + test-integrity); schema migration (schema-migration-subsystem + correctness + operational-readiness + impact); cross-subsystem PR that touches billing AND auth AND schema (one subsystem lens per domain + correctness)
146391
146433
  - **6+ lenses** \u2014 almost always a smell; you're either covering overlapping ground or this PR should have been split. push back via the review body rather than expanding lens count.
146392
146434
 
146435
+ **lens-add discipline.** Each lens needs to clear a specific bar before you dispatch it: name the concrete failure mode this lens would catch *that the diff plausibly introduces*, in one sentence. "Could apply", "good to have", "for completeness" do not qualify. If you can't name what the lens is going to find, drop it. The "when unsure, treat as non-trivial" rule above is for the trivial-vs-non-trivial gate at step 3 \u2014 it does not license expanding lens count without articulated risk. Every extra lens adds wall-time, log noise, and pulls subagent attention onto speculative angles, which biases the final review toward bloat-shaped findings.
146436
+
146393
146437
  lenses come in two flavors, and you can mix them:
146394
146438
  - **themed lenses** \u2014 a perspective applied across the whole diff (correctness, security, user-journey, performance, etc.).
146395
146439
  - **subsystem lenses** \u2014 a domain-scoped frame for high-stakes subsystems the PR touches (e.g. "the auth lens", "the billing lens", "the schema-migration lens"). a subsystem lens is "review the PR specifically for what could go wrong in this subsystem" and naturally combines theme + scope. **for high-stakes domains, lead with the subsystem lens rather than the generic themed equivalent** \u2014 "billing-subsystem" outperforms "correctness on billing code" because the framing primes the subagent to remember domain-specific failure modes (double-charges, refund races, currency rounding, dispute flows) the generic lens misses.
@@ -146397,7 +146441,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
146397
146441
  starter menu (combine, omit, or invent your own):
146398
146442
  - **correctness & invariants** \u2014 bugs, races, error handling, edge cases, state-machine boundaries
146399
146443
  - **impact** \u2014 when the PR removes features, deletes exports, renames identifiers, or changes architectural patterns: stale references in code, tests, docs (\`docs/\`, \`wiki/\`), comments, configs, UI
146400
- - **research-validated assumptions** \u2014 third-party API contracts, SDK semantics, framework directives, version-gated behavior. the subagent must verify load-bearing claims via web search and quote source URLs.
146444
+ - **research-validated assumptions** \u2014 third-party API contracts, SDK semantics, framework directives, version-gated behavior. **only pick when the PR's correctness depends on the contract behaving a specific way** \u2014 not when the API is merely used. An idempotency key as a backstop, a timeout as a hint, a retry as belt-and-suspenders: not load-bearing, skip this lens. The bar is "if the third-party contract differs from what the diff assumes, the PR is incorrect." When dispatched, the subagent must verify load-bearing claims via web search and quote source URLs.
146401
146445
  - **security** \u2014 new endpoints, authZ, input validation, secrets handling, replay/CSRF/injection, cross-tenant isolation
146402
146446
  - **user-journey** \u2014 UX-touching flows: walk through happy path and failure modes as a user
146403
146447
  - **operational readiness** \u2014 observability, alerting, migrations (forward + rollback), feature flags, on-call burden
@@ -146486,7 +146530,7 @@ ${PR_SUMMARY_FORMAT}`
146486
146530
  "Looks trivial but isn't" (do NOT skip \u2014 same anti-patterns as Review mode): 1-line changes to SQL/regex/auth/billing/permissions/signature-verification code; flipping feature-flag defaults or retry/timeout constants; money/tax/HTTP-method/redirect changes; tightening or loosening a comparison operator; mixed diffs with a semantic line buried in formatting.
146487
146531
  When unsure, treat as non-trivial.
146488
146532
 
146489
- otherwise pick lenses by where the new commits concentrate risk \u2014 **there's no fixed count**, same calibration as Review mode (1 lens for pure refactor / isolated fix; 2\u20133 for typical features; 4\u20135 for high-stakes subsystem touches; 6+ is a smell). lens framing follows Review mode: themed lenses (correctness & invariants, impact when new commits remove/rename/deprecate things, research-validated assumptions, security, user-journey, operational readiness, integration & cross-cutting, test integrity, performance, holistic) and subsystem lenses (auth, billing, schema migration, etc.) \u2014 for high-stakes domains lead with the subsystem lens rather than the generic themed equivalent.
146533
+ otherwise pick lenses by where the new commits concentrate risk \u2014 **there's no fixed count**, same calibration as Review mode (1 lens for pure refactor / isolated fix; 2\u20133 for typical features; 4\u20135 for high-stakes subsystem touches; 6+ is a smell). same **lens-add discipline** as Review mode applies: each lens needs to name the concrete failure mode it would catch *that the new commits plausibly introduce* \u2014 "could apply" doesn't qualify, drop it. **research-validated assumptions** specifically: only pick when the new commits' correctness depends on a third-party contract behaving a specific way; merely using an API doesn't qualify. lens framing follows Review mode: themed lenses (correctness & invariants, impact when new commits remove/rename/deprecate things, research-validated assumptions, security, user-journey, operational readiness, integration & cross-cutting, test integrity, performance, holistic) and subsystem lenses (auth, billing, schema migration, etc.) \u2014 for high-stakes domains lead with the subsystem lens rather than the generic themed equivalent.
146490
146534
 
146491
146535
  dispatch one \`${REVIEWER_AGENT_NAME}\` subagent per lens \u2014 its baked-in system prompt enforces the non-mutative + non-recursive contract (read-only file/search/web tools and read-only MCP queries; no writes, shell side effects, state-changing MCP calls, or nested subagent dispatch). dispatch them in a **single assistant turn with multiple parallel subagent calls** (serial dispatch collapses the fan-out). if a subagent errors out, times out, or returns nothing usable, retry once with the same lens; if it still fails, proceed with partial coverage and note the missing lens in the review body \u2014 do not skip step 5 entirely on a single subagent failure. each subagent gets:
146492
146536
  - the diff scope (incremental diff path if available, full diff otherwise). do NOT tell them to skip pre-existing issues \u2014 that suppresses regressions the new commits amplified; the "issues must be NEW" filter lives at aggregation time (step 6), not in the subagent prompt
@@ -146856,20 +146900,30 @@ var ThinkingTimer = class {
146856
146900
  maximumFractionDigits: 1
146857
146901
  });
146858
146902
  lastToolResultTimestamp = null;
146903
+ formatLine;
146904
+ // node's native TS strip-only mode does not support parameter properties,
146905
+ // so the formatter is declared as a field and assigned in the body.
146906
+ constructor(formatLine = (l) => l) {
146907
+ this.formatLine = formatLine;
146908
+ }
146859
146909
  markToolResult() {
146860
146910
  this.lastToolResultTimestamp = performance5.now();
146861
- log.debug(`\xBB thinking timer: markToolResult at ${this.lastToolResultTimestamp}`);
146911
+ log.debug(
146912
+ this.formatLine(`\xBB thinking timer: markToolResult at ${this.lastToolResultTimestamp}`)
146913
+ );
146862
146914
  }
146863
146915
  markToolCall() {
146864
146916
  const now = performance5.now();
146865
146917
  log.debug(
146866
- `\xBB thinking timer: markToolCall at ${now}, lastToolResult=${this.lastToolResultTimestamp}`
146918
+ this.formatLine(
146919
+ `\xBB thinking timer: markToolCall at ${now}, lastToolResult=${this.lastToolResultTimestamp}`
146920
+ )
146867
146921
  );
146868
146922
  if (this.lastToolResultTimestamp === null) return;
146869
146923
  const elapsed = now - this.lastToolResultTimestamp;
146870
146924
  if (elapsed < THINKING_THRESHOLD) return;
146871
146925
  const seconds = elapsed / 1e3;
146872
- log.info(`\xBB thought for ${this.durationFormatter.format(seconds)}`);
146926
+ log.info(this.formatLine(`\xBB thought for ${this.durationFormatter.format(seconds)}`));
146873
146927
  }
146874
146928
  };
146875
146929
 
@@ -147118,19 +147172,39 @@ function deriveLabelFromTaskInput(input) {
147118
147172
  }
147119
147173
  var SessionLabeler = class {
147120
147174
  labels = /* @__PURE__ */ new Map();
147175
+ labelsByToolUseId = /* @__PURE__ */ new Map();
147121
147176
  pendingLabels = [];
147122
147177
  fallbackCounter = 0;
147123
- recordTaskDispatch(input) {
147178
+ /**
147179
+ * Record a Task/Agent tool dispatch.
147180
+ *
147181
+ * @param input Task tool input — used to derive the lens label.
147182
+ * @param toolUseId Optional Agent tool_use id. When provided, future events
147183
+ * carrying `parent_tool_use_id === toolUseId` resolve
147184
+ * directly to this label without consuming the FIFO queue
147185
+ * (Claude path). Always also pushed to the FIFO queue so
147186
+ * the OpenCode path still works when toolUseId is absent.
147187
+ */
147188
+ recordTaskDispatch(input, toolUseId) {
147124
147189
  const label = deriveLabelFromTaskInput(input);
147125
147190
  this.pendingLabels.push(label);
147191
+ if (toolUseId) this.labelsByToolUseId.set(toolUseId, label);
147126
147192
  return label;
147127
147193
  }
147128
147194
  /**
147129
- * Return a label for the given sessionID. Binds on first call.
147130
- * Pass undefined/empty for events that lack a session id — the caller
147131
- * gets ORCHESTRATOR_LABEL so the line is still attributable.
147195
+ * Return a label for the given event.
147196
+ *
147197
+ * @param sessionID Session id from the event (OpenCode: per-session;
147198
+ * Claude: shared across orchestrator + subagents).
147199
+ * @param parentToolUseId Claude's `parent_tool_use_id` — non-null on
147200
+ * subagent messages. When set and known, takes
147201
+ * priority over the FIFO/sessionID path.
147132
147202
  */
147133
- labelFor(sessionID) {
147203
+ labelFor(sessionID, parentToolUseId) {
147204
+ if (parentToolUseId) {
147205
+ const direct = this.labelsByToolUseId.get(parentToolUseId);
147206
+ if (direct) return direct;
147207
+ }
147134
147208
  if (!sessionID) return ORCHESTRATOR_LABEL;
147135
147209
  const existing = this.labels.get(sessionID);
147136
147210
  if (existing) return existing;
@@ -147214,7 +147288,23 @@ function tailLines(text, maxCodeUnits) {
147214
147288
  async function runClaude(params) {
147215
147289
  const startTime = performance6.now();
147216
147290
  let eventCount = 0;
147217
- const thinkingTimer = new ThinkingTimer();
147291
+ const labeler = new SessionLabeler();
147292
+ function eventLabel(event) {
147293
+ return labeler.labelFor(event.session_id ?? null, event.parent_tool_use_id ?? null);
147294
+ }
147295
+ function withLabel(label, message) {
147296
+ return label === ORCHESTRATOR_LABEL ? message : formatWithLabel(label, message);
147297
+ }
147298
+ const thinkingTimers = /* @__PURE__ */ new Map();
147299
+ function timerFor(label) {
147300
+ let t = thinkingTimers.get(label);
147301
+ if (!t) {
147302
+ const formatLine = (line) => label === ORCHESTRATOR_LABEL ? line : formatWithLabel(label, line);
147303
+ t = new ThinkingTimer(formatLine);
147304
+ thinkingTimers.set(label, t);
147305
+ }
147306
+ return t;
147307
+ }
147218
147308
  let finalOutput = "";
147219
147309
  let sessionId;
147220
147310
  let resultErrorSubtype = null;
@@ -147235,17 +147325,22 @@ async function runClaude(params) {
147235
147325
  } : void 0;
147236
147326
  }
147237
147327
  const handlers2 = {
147238
- system: (_event) => {
147239
- log.debug(`\xBB ${params.label} system event`);
147328
+ system: (event) => {
147329
+ const label = eventLabel(event);
147330
+ log.debug(withLabel(label, `\xBB ${params.label} system event`));
147240
147331
  },
147241
147332
  assistant: (event) => {
147242
147333
  const content = event.message?.content;
147243
147334
  if (!content) return;
147335
+ const label = eventLabel(event);
147336
+ const boxTitle = label === ORCHESTRATOR_LABEL ? params.label : `${params.label} [${label}]`;
147244
147337
  for (const block of content) {
147245
147338
  if (block.type === "text" && block.text?.trim()) {
147246
147339
  const message = block.text.trim();
147247
- log.box(message, { title: params.label });
147248
- finalOutput = message;
147340
+ log.box(message, { title: boxTitle });
147341
+ if (label === ORCHESTRATOR_LABEL) {
147342
+ finalOutput = message;
147343
+ }
147249
147344
  } else if (block.type === "tool_use") {
147250
147345
  const toolName = block.name || "unknown";
147251
147346
  if (params.onToolUse) {
@@ -147254,20 +147349,25 @@ async function runClaude(params) {
147254
147349
  input: block.input
147255
147350
  });
147256
147351
  }
147257
- thinkingTimer.markToolCall();
147258
- log.toolCall({ toolName, input: block.input || {} });
147259
- if (toolName === "Task" && block.input && typeof block.input === "object") {
147352
+ timerFor(label).markToolCall();
147353
+ const inputFormatted = formatJsonValue(block.input || {});
147354
+ const toolCallLine = inputFormatted !== "{}" ? `\xBB ${toolName}(${inputFormatted})` : `\xBB ${toolName}()`;
147355
+ log.info(withLabel(label, toolCallLine));
147356
+ if ((toolName === "Task" || toolName === "Agent") && block.input && typeof block.input === "object") {
147260
147357
  const taskInput = block.input;
147261
- const label = deriveLabelFromTaskInput(taskInput);
147358
+ const dispatchedLabel = labeler.recordTaskDispatch(taskInput, block.id ?? null);
147262
147359
  log.info(
147263
- `\xBB dispatching subagent: ${label}` + (taskInput.subagent_type ? ` (subagent_type=${taskInput.subagent_type})` : "")
147360
+ withLabel(
147361
+ label,
147362
+ `\xBB dispatching subagent: ${dispatchedLabel}` + (taskInput.subagent_type ? ` (subagent_type=${taskInput.subagent_type})` : "")
147363
+ )
147264
147364
  );
147265
147365
  }
147266
147366
  if (toolName.includes("report_progress") && params.todoTracker) {
147267
147367
  log.debug("\xBB report_progress detected, disabling todo tracking");
147268
147368
  params.todoTracker.cancel();
147269
147369
  }
147270
- if (toolName === "TodoWrite" && params.todoTracker?.enabled) {
147370
+ if (toolName === "TodoWrite" && params.todoTracker?.enabled && label === ORCHESTRATOR_LABEL) {
147271
147371
  params.todoTracker.update(block.input);
147272
147372
  }
147273
147373
  }
@@ -147283,17 +147383,18 @@ async function runClaude(params) {
147283
147383
  user: (event) => {
147284
147384
  const content = event.message?.content;
147285
147385
  if (!content) return;
147386
+ const label = eventLabel(event);
147286
147387
  for (const block of content) {
147287
147388
  if (typeof block === "string") continue;
147288
147389
  if (block.type === "tool_result") {
147289
- thinkingTimer.markToolResult();
147390
+ timerFor(label).markToolResult();
147290
147391
  const outputContent = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map(
147291
147392
  (entry) => typeof entry === "string" ? entry : typeof entry === "object" && entry !== null && "text" in entry ? String(entry.text) : JSON.stringify(entry)
147292
147393
  ).join("\n") : String(block.content);
147293
147394
  if (block.is_error) {
147294
- log.info(`\xBB tool error: ${outputContent}`);
147395
+ log.info(withLabel(label, `\xBB tool error: ${outputContent}`));
147295
147396
  } else {
147296
- log.debug(`\xBB tool output: ${outputContent}`);
147397
+ log.debug(withLabel(label, `\xBB tool output: ${outputContent}`));
147297
147398
  }
147298
147399
  }
147299
147400
  }
@@ -147363,7 +147464,7 @@ async function runClaude(params) {
147363
147464
  };
147364
147465
  const recentStderr = [];
147365
147466
  let lastProviderError = null;
147366
- let output = "";
147467
+ const output = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
147367
147468
  let stdoutBuffer = "";
147368
147469
  try {
147369
147470
  const result = await spawn({
@@ -147380,9 +147481,14 @@ async function runClaude(params) {
147380
147481
  // there's no shim-orphan issue like opencode-ai/bin/opencode, but
147381
147482
  // detached + killGroup is the right default for any agent runtime.
147382
147483
  killGroup: true,
147484
+ // claude already drains every chunk via onStdout (NDJSON parsing) and
147485
+ // onStderr (recentStderr ring buffer). retaining a second copy in the
147486
+ // spawn wrapper would grow unbounded for long sessions and previously
147487
+ // crashed the wrapper with RangeError. see issue #680.
147488
+ retain: "none",
147383
147489
  onStdout: async (chunk) => {
147384
147490
  const text = chunk.toString();
147385
- output += text;
147491
+ output.append(text);
147386
147492
  markActivity();
147387
147493
  stdoutBuffer += text;
147388
147494
  const lines = stdoutBuffer.split("\n");
@@ -147457,16 +147563,18 @@ ${stderrContext}`);
147457
147563
  const usage = buildUsage();
147458
147564
  if (result.exitCode !== 0) {
147459
147565
  const errorContext = lastProviderError ? ` (${lastProviderError})` : "";
147460
- const truncatedStdout = result.stdout ? tailLines(result.stdout, 2048) : "";
147461
- const errorMessage = lastResultError || result.stderr || truncatedStdout || `unknown error - no output from Claude CLI${errorContext}`;
147566
+ const stdoutSnapshot = output.toString();
147567
+ const stderrSnapshot = recentStderr.join("\n");
147568
+ const truncatedStdout = stdoutSnapshot ? tailLines(stdoutSnapshot, 2048) : "";
147569
+ const errorMessage = lastResultError || stderrSnapshot || truncatedStdout || `unknown error - no output from Claude CLI${errorContext}`;
147462
147570
  log.error(
147463
147571
  `${params.label} exited with code ${result.exitCode}${errorContext}: ${errorMessage}`
147464
147572
  );
147465
- log.debug(`stdout: ${result.stdout?.substring(0, 500)}`);
147466
- log.debug(`stderr: ${result.stderr?.substring(0, 500)}`);
147573
+ log.debug(`stdout: ${stdoutSnapshot.substring(0, 500)}`);
147574
+ log.debug(`stderr: ${stderrSnapshot.substring(0, 500)}`);
147467
147575
  return {
147468
147576
  success: false,
147469
- output: finalOutput || output,
147577
+ output: finalOutput || stdoutSnapshot,
147470
147578
  error: errorMessage,
147471
147579
  usage,
147472
147580
  sessionId
@@ -147475,7 +147583,7 @@ ${stderrContext}`);
147475
147583
  if (eventCount === 0 && lastProviderError) {
147476
147584
  return {
147477
147585
  success: false,
147478
- output: finalOutput || output,
147586
+ output: finalOutput || output.toString(),
147479
147587
  error: `provider error: ${lastProviderError}`,
147480
147588
  usage,
147481
147589
  sessionId
@@ -147484,13 +147592,13 @@ ${stderrContext}`);
147484
147592
  if (resultErrorSubtype) {
147485
147593
  return {
147486
147594
  success: false,
147487
- output: finalOutput || output,
147595
+ output: finalOutput || output.toString(),
147488
147596
  error: lastResultError || `result subtype: ${resultErrorSubtype}`,
147489
147597
  usage,
147490
147598
  sessionId
147491
147599
  };
147492
147600
  }
147493
- return { success: true, output: finalOutput || output, usage, sessionId };
147601
+ return { success: true, output: finalOutput || output.toString(), usage, sessionId };
147494
147602
  } catch (error49) {
147495
147603
  params.todoTracker?.cancel();
147496
147604
  const duration4 = performance6.now() - startTime;
@@ -147509,7 +147617,7 @@ ${stderrContext}`
147509
147617
  );
147510
147618
  return {
147511
147619
  success: false,
147512
- output: finalOutput || output,
147620
+ output: finalOutput || output.toString(),
147513
147621
  error: `${errorMessage} [${diagnosis}]`,
147514
147622
  usage: buildUsage(),
147515
147623
  sessionId
@@ -147745,6 +147853,15 @@ function buildSecurityConfig(ctx, model) {
147745
147853
  [pullfrogMcpName]: { type: "remote", url: ctx.mcpServerUrl }
147746
147854
  },
147747
147855
  agent: buildReviewerAgentConfig(),
147856
+ // opt into opencode's experimental `batch` tool (added in
147857
+ // anomalyco/opencode PR #2983, opt-in via `experimental.batch_tool`). it
147858
+ // exposes a single `batch` tool that runs 1-25 independent tool calls
147859
+ // (read/grep/glob/bash/etc.) concurrently in one assistant turn, which
147860
+ // collapses the dominant grep→20×read pattern into a single round trip.
147861
+ // edits are explicitly disallowed inside the batch upstream. paired with
147862
+ // the "Parallel tool execution" guidance in utils/instructions.ts so the
147863
+ // model actually reaches for it. see wiki/prompt.md.
147864
+ experimental: { batch_tool: true },
147748
147865
  provider: {
147749
147866
  google: {
147750
147867
  models: Object.fromEntries(
@@ -147817,7 +147934,6 @@ function autoSelectModel(cliPath) {
147817
147934
  async function runOpenCode(params) {
147818
147935
  const startTime = performance7.now();
147819
147936
  let eventCount = 0;
147820
- const thinkingTimer = new ThinkingTimer();
147821
147937
  let finalOutput = "";
147822
147938
  let accumulatedTokens = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
147823
147939
  let accumulatedCostUsd = 0;
@@ -147834,6 +147950,16 @@ async function runOpenCode(params) {
147834
147950
  function withLabel(label, message) {
147835
147951
  return label === ORCHESTRATOR_LABEL ? message : formatWithLabel(label, message);
147836
147952
  }
147953
+ const thinkingTimers = /* @__PURE__ */ new Map();
147954
+ function timerFor(label) {
147955
+ let t = thinkingTimers.get(label);
147956
+ if (!t) {
147957
+ const formatLine = (line) => label === ORCHESTRATOR_LABEL ? line : formatWithLabel(label, line);
147958
+ t = new ThinkingTimer(formatLine);
147959
+ thinkingTimers.set(label, t);
147960
+ }
147961
+ return t;
147962
+ }
147837
147963
  const taskDispatchByCallID = /* @__PURE__ */ new Map();
147838
147964
  const pendingTaskDispatches = [];
147839
147965
  const knownNonTaskCallIDs = /* @__PURE__ */ new Set();
@@ -147982,7 +148108,7 @@ async function runOpenCode(params) {
147982
148108
  input: event.part?.state?.input
147983
148109
  });
147984
148110
  }
147985
- thinkingTimer.markToolCall();
148111
+ timerFor(label).markToolCall();
147986
148112
  const inputFormatted = formatJsonValue(event.part?.state?.input || {});
147987
148113
  const toolCallLine = inputFormatted !== "{}" ? `\xBB ${toolName}(${inputFormatted})` : `\xBB ${toolName}()`;
147988
148114
  log.info(withLabel(label, toolCallLine));
@@ -148006,7 +148132,7 @@ async function runOpenCode(params) {
148006
148132
  const status = event.part?.state?.status || event.status || "unknown";
148007
148133
  const output2 = event.part?.state?.output || event.output;
148008
148134
  const label = eventLabel(event);
148009
- thinkingTimer.markToolResult();
148135
+ timerFor(label).markToolResult();
148010
148136
  if (taskDispatchByCallID.size > 0 || pendingTaskDispatches.length > 0) {
148011
148137
  if (toolId && taskDispatchByCallID.has(toolId)) {
148012
148138
  const dispatch = taskDispatchByCallID.get(toolId);
@@ -148131,7 +148257,7 @@ async function runOpenCode(params) {
148131
148257
  const recentStderr = [];
148132
148258
  let lastProviderError = null;
148133
148259
  let agentErrorEvent = null;
148134
- let output = "";
148260
+ const output = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
148135
148261
  let stdoutBuffer = "";
148136
148262
  try {
148137
148263
  const result = await spawn({
@@ -148149,6 +148275,11 @@ async function runOpenCode(params) {
148149
148275
  // never fires — producing zombie runs. detached + killGroup nukes the
148150
148276
  // whole tree.
148151
148277
  killGroup: true,
148278
+ // we already drain every chunk via onStdout/onStderr (NDJSON parsing
148279
+ // + recentStderr ring buffer). retaining a second copy in the spawn
148280
+ // wrapper would grow unbounded for multi-lens Reviews and previously
148281
+ // crashed the wrapper with RangeError at ~1 GiB. see issue #680.
148282
+ retain: "none",
148152
148283
  // NB: we used to pass `isPausedExternally: isSubagentInFlight` to suspend
148153
148284
  // the activity timer during subagent dispatches. unnecessary now that
148154
148285
  // our injected plugin (action/agents/opencodePlugin.ts) re-emits
@@ -148158,7 +148289,7 @@ async function runOpenCode(params) {
148158
148289
  // (~3.3 plugin events/sec during a typical subagent run).
148159
148290
  onStdout: async (chunk) => {
148160
148291
  const text = chunk.toString();
148161
- output += text;
148292
+ output.append(text);
148162
148293
  markActivity();
148163
148294
  stdoutBuffer += text;
148164
148295
  const lines = stdoutBuffer.split("\n");
@@ -148247,18 +148378,25 @@ ${stderrContext}`);
148247
148378
  const usage = buildUsage();
148248
148379
  if (result.exitCode !== 0) {
148249
148380
  const errorContext = lastProviderError ? ` (${lastProviderError})` : "";
148250
- const errorMessage = result.stderr || result.stdout || `unknown error - no output from OpenCode CLI${errorContext}`;
148381
+ const stdoutSnapshot = output.toString();
148382
+ const stderrSnapshot = recentStderr.join("\n");
148383
+ const errorMessage = stderrSnapshot || stdoutSnapshot || `unknown error - no output from OpenCode CLI${errorContext}`;
148251
148384
  log.error(
148252
148385
  `${params.label} exited with code ${result.exitCode}${errorContext}: ${errorMessage}`
148253
148386
  );
148254
- log.debug(`stdout: ${result.stdout?.substring(0, 500)}`);
148255
- log.debug(`stderr: ${result.stderr?.substring(0, 500)}`);
148256
- return { success: false, output: finalOutput || output, error: errorMessage, usage };
148387
+ log.debug(`stdout: ${stdoutSnapshot.substring(0, 500)}`);
148388
+ log.debug(`stderr: ${stderrSnapshot.substring(0, 500)}`);
148389
+ return {
148390
+ success: false,
148391
+ output: finalOutput || stdoutSnapshot,
148392
+ error: errorMessage,
148393
+ usage
148394
+ };
148257
148395
  }
148258
148396
  if (eventCount === 0 && lastProviderError) {
148259
148397
  return {
148260
148398
  success: false,
148261
- output: finalOutput || output,
148399
+ output: finalOutput || output.toString(),
148262
148400
  error: `provider error: ${lastProviderError}`,
148263
148401
  usage
148264
148402
  };
@@ -148269,12 +148407,12 @@ ${stderrContext}`);
148269
148407
  const errorMessage = errorEvent.error?.data?.message || errorEvent.error?.name || JSON.stringify(errorEvent);
148270
148408
  return {
148271
148409
  success: false,
148272
- output: finalOutput || output,
148410
+ output: finalOutput || output.toString(),
148273
148411
  error: `${errorName}: ${errorMessage}`,
148274
148412
  usage
148275
148413
  };
148276
148414
  }
148277
- return { success: true, output: finalOutput || output, usage };
148415
+ return { success: true, output: finalOutput || output.toString(), usage };
148278
148416
  } catch (error49) {
148279
148417
  params.todoTracker?.cancel();
148280
148418
  const duration4 = performance7.now() - startTime;
@@ -148293,7 +148431,7 @@ ${stderrContext}`
148293
148431
  );
148294
148432
  return {
148295
148433
  success: false,
148296
- output: finalOutput || output,
148434
+ output: finalOutput || output.toString(),
148297
148435
  error: `${errorMessage} [${diagnosis}]`,
148298
148436
  usage: buildUsage()
148299
148437
  };
@@ -152199,6 +152337,14 @@ function isOIDCAvailable() {
152199
152337
  process.env.ACTIONS_ID_TOKEN_REQUEST_URL && process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN
152200
152338
  );
152201
152339
  }
152340
+ var TokenExchangeError = class extends Error {
152341
+ status;
152342
+ constructor(status, message) {
152343
+ super(message);
152344
+ this.name = "TokenExchangeError";
152345
+ this.status = status;
152346
+ }
152347
+ };
152202
152348
  async function acquireTokenViaOIDC(opts) {
152203
152349
  const oidcToken = await core2.getIDToken("pullfrog-api");
152204
152350
  const repos = [...opts?.repos ?? []];
@@ -152223,7 +152369,16 @@ async function acquireTokenViaOIDC(opts) {
152223
152369
  });
152224
152370
  clearTimeout(timeoutId);
152225
152371
  if (!tokenResponse.ok) {
152226
- throw new Error(`Token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText}`);
152372
+ let serverMessage;
152373
+ try {
152374
+ const body = await tokenResponse.json();
152375
+ if (typeof body.error === "string") serverMessage = body.error;
152376
+ } catch {
152377
+ }
152378
+ throw new TokenExchangeError(
152379
+ tokenResponse.status,
152380
+ serverMessage ?? `Token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText}`
152381
+ );
152227
152382
  }
152228
152383
  const tokenData = await tokenResponse.json();
152229
152384
  return tokenData.token;
@@ -152344,7 +152499,10 @@ async function acquireNewToken(opts) {
152344
152499
  if (isOIDCAvailable()) {
152345
152500
  return await retry(() => acquireTokenViaOIDC(opts), {
152346
152501
  label: "token exchange",
152347
- shouldRetry: (error49) => error49 instanceof Error && (error49.name === "AbortError" || error49.message.includes("fetch failed") || error49.message.includes("ECONNRESET") || error49.message.includes("ETIMEDOUT") || error49.message.includes("Token exchange failed"))
152502
+ shouldRetry: (error49) => {
152503
+ if (error49 instanceof TokenExchangeError) return error49.status >= 500 || error49.status === 429;
152504
+ return error49 instanceof Error && (error49.message.includes("timed out") || error49.message.includes("fetch failed") || error49.message.includes("ECONNRESET") || error49.message.includes("ETIMEDOUT"));
152505
+ }
152348
152506
  });
152349
152507
  } else {
152350
152508
  return await acquireTokenViaGitHubApp(opts);
@@ -152891,6 +153049,21 @@ ${getStandaloneModeInstructions(ctx.payload.event.trigger, t, ctx.outputSchema)}
152891
153049
 
152892
153050
  Trust the tools \u2014 do not repeatedly verify file contents or git status after operations. If a tool reports success, proceed to the next step. Only verify if you encounter an actual error. Exception: right before \`${t("push_branch")}\`, ensure the working tree is clean \u2014 that tool rejects dirty trees, and tests you ran earlier often leave untracked output.
152893
153051
 
153052
+ ### Parallel tool execution
153053
+
153054
+ For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools simultaneously in a single assistant turn rather than sequentially. The dominant failure mode is grep \u2192 read \u2192 read \u2192 read \u2192 read across separate turns when one round trip would do. Always parallelize when calls are independent:
153055
+ - reading multiple files (especially after a grep returns candidates)
153056
+ - multiple greps with different patterns
153057
+ - glob + grep + read combos
153058
+ - listing multiple directories
153059
+ - inspecting multiple MCP tools or resources
153060
+
153061
+ Do NOT parallelize operations that depend on prior output (e.g. create a file then read it), or ordered stateful mutations. Edits are not parallelizable \u2014 sequence those normally.${ctx.agentId === "opencode" ? `
153062
+
153063
+ On OpenCode you also have a \`batch\` tool that bundles 1-25 independent calls into one wrapper call. Reach for it whenever you have >=2 independent calls. Native parallel tool_use and \`batch\` both achieve one round trip instead of N \u2014 use whichever your provider supports best.` : `
153064
+
153065
+ Emit multiple \`tool_use\` blocks in the same assistant message for independent calls \u2014 the runtime executes them concurrently. Do not wait for one tool result before issuing the next independent call.`}
153066
+
152894
153067
  ### Command execution
152895
153068
 
152896
153069
  Never use \`sleep\` to wait for commands to complete. Commands run synchronously \u2014 when the shell tool returns, the command has finished.
@@ -153017,10 +153190,22 @@ async function readLearningsFile(path3) {
153017
153190
  }
153018
153191
 
153019
153192
  // utils/normalizeEnv.ts
153020
- function maskValue(value2) {
153021
- if (value2 && typeof value2 === "string" && value2.trim().length > 0) {
153022
- console.log(`::add-mask::${value2}`);
153193
+ var core4 = __toESM(require_core(), 1);
153194
+ function sanitizeSecret(key, value2) {
153195
+ const trimmed = value2.trim();
153196
+ if (trimmed.length === 0) {
153197
+ log.warning(
153198
+ `\xBB ${key} is whitespace-only \u2014 leaving env var unchanged. check your secret value.`
153199
+ );
153200
+ return null;
153023
153201
  }
153202
+ if (trimmed !== value2) {
153203
+ log.warning(
153204
+ `\xBB stripped whitespace from ${key} (whitespace in secret values breaks GitHub Actions log masking)`
153205
+ );
153206
+ }
153207
+ core4.setSecret(trimmed);
153208
+ return trimmed;
153024
153209
  }
153025
153210
  function normalizeEnv() {
153026
153211
  const upperKeys = /* @__PURE__ */ new Map();
@@ -153031,11 +153216,6 @@ function normalizeEnv() {
153031
153216
  upperKeys.set(upper2, existing);
153032
153217
  }
153033
153218
  for (const [upperKey, keys] of upperKeys) {
153034
- if (isSensitiveEnvName(upperKey)) {
153035
- for (const key of keys) {
153036
- maskValue(process.env[key]);
153037
- }
153038
- }
153039
153219
  if (keys.length === 1) {
153040
153220
  const key = keys[0];
153041
153221
  if (key !== upperKey) {
@@ -153058,10 +153238,17 @@ function normalizeEnv() {
153058
153238
  }
153059
153239
  process.env[upperKey] = preferredValue;
153060
153240
  }
153241
+ for (const key of Object.keys(process.env)) {
153242
+ if (!isSensitiveEnvName(key)) continue;
153243
+ const value2 = process.env[key];
153244
+ if (typeof value2 !== "string" || value2.length === 0) continue;
153245
+ const sanitized = sanitizeSecret(key, value2);
153246
+ if (sanitized !== null) process.env[key] = sanitized;
153247
+ }
153061
153248
  }
153062
153249
 
153063
153250
  // utils/payload.ts
153064
- var core4 = __toESM(require_core(), 1);
153251
+ var core5 = __toESM(require_core(), 1);
153065
153252
  import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
153066
153253
 
153067
153254
  // utils/versioning.ts
@@ -153125,7 +153312,7 @@ function resolveCwd(cwd) {
153125
153312
  return workspace ? resolve2(workspace, cwd) : cwd;
153126
153313
  }
153127
153314
  function resolvePromptInput() {
153128
- const prompt = core4.getInput("prompt", { required: true });
153315
+ const prompt = core5.getInput("prompt", { required: true });
153129
153316
  let parsed2;
153130
153317
  try {
153131
153318
  parsed2 = JSON.parse(prompt);
@@ -153141,11 +153328,11 @@ function resolvePromptInput() {
153141
153328
  }
153142
153329
  function resolveNonPromptInputs() {
153143
153330
  return Inputs.omit("prompt").assert({
153144
- model: core4.getInput("model") || void 0,
153145
- timeout: core4.getInput("timeout") || void 0,
153146
- cwd: core4.getInput("cwd") || void 0,
153147
- push: core4.getInput("push") || void 0,
153148
- shell: core4.getInput("shell") || void 0
153331
+ model: core5.getInput("model") || void 0,
153332
+ timeout: core5.getInput("timeout") || void 0,
153333
+ cwd: core5.getInput("cwd") || void 0,
153334
+ push: core5.getInput("push") || void 0,
153335
+ shell: core5.getInput("shell") || void 0
153149
153336
  });
153150
153337
  }
153151
153338
  var isPullfrog = (actor) => {
@@ -153401,13 +153588,13 @@ async function fetchRunContext(params) {
153401
153588
  }
153402
153589
 
153403
153590
  // utils/runContextData.ts
153404
- var core5 = __toESM(require_core(), 1);
153591
+ var core6 = __toESM(require_core(), 1);
153405
153592
  async function resolveRunContextData(params) {
153406
153593
  log.info(`\xBB running Pullfrog v${package_default.version}...`);
153407
153594
  const repoContext = parseRepoContext();
153408
153595
  let oidcToken;
153409
153596
  try {
153410
- oidcToken = await core5.getIDToken("pullfrog-api");
153597
+ oidcToken = await core6.getIDToken("pullfrog-api");
153411
153598
  } catch {
153412
153599
  }
153413
153600
  const [repoResponse, runContext] = await Promise.all([
@@ -153710,7 +153897,7 @@ async function resolveRun(params) {
153710
153897
 
153711
153898
  // main.ts
153712
153899
  function resolveOutputSchema() {
153713
- const raw2 = core6.getInput("output_schema");
153900
+ const raw2 = core7.getInput("output_schema");
153714
153901
  if (!raw2) return void 0;
153715
153902
  let parsed2;
153716
153903
  try {
@@ -153878,7 +154065,7 @@ async function buildProxyTokenHeaders(ctx) {
153878
154065
  if (ctx.oidcCredentials) {
153879
154066
  process.env.ACTIONS_ID_TOKEN_REQUEST_URL = ctx.oidcCredentials.requestUrl;
153880
154067
  process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = ctx.oidcCredentials.requestToken;
153881
- const oidcToken = await core6.getIDToken("pullfrog-api");
154068
+ const oidcToken = await core7.getIDToken("pullfrog-api");
153882
154069
  delete process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
153883
154070
  delete process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
153884
154071
  return { Authorization: `Bearer ${oidcToken}` };
@@ -153900,7 +154087,7 @@ async function resolveProxyModel(ctx) {
153900
154087
  const key = await mintProxyKey({ oidcCredentials: ctx.oidcCredentials, repo: ctx.repo });
153901
154088
  if (!key) return;
153902
154089
  process.env.OPENROUTER_API_KEY = key;
153903
- core6.setSecret(key);
154090
+ core7.setSecret(key);
153904
154091
  ctx.payload.proxyModel = ctx.proxyModel;
153905
154092
  const label = ctx.oss ? "oss" : "router";
153906
154093
  log.info(`\xBB proxy: ${label} \u2192 ${ctx.proxyModel}`);
@@ -154012,8 +154199,8 @@ async function main() {
154012
154199
  if (runContext.dbSecrets) {
154013
154200
  for (const [key, value2] of Object.entries(runContext.dbSecrets)) {
154014
154201
  if (!process.env[key]) {
154015
- process.env[key] = value2;
154016
- core6.setSecret(value2);
154202
+ const sanitized = sanitizeSecret(key, value2);
154203
+ if (sanitized !== null) process.env[key] = sanitized;
154017
154204
  }
154018
154205
  }
154019
154206
  const count = Object.keys(runContext.dbSecrets).length;
@@ -154351,7 +154538,7 @@ ${instructions.user}` : null,
154351
154538
  }
154352
154539
  if (toolState.output) {
154353
154540
  log.info(`::pullfrog-output::${Buffer.from(toolState.output).toString("base64")}`);
154354
- core6.setOutput("result", toolState.output);
154541
+ core7.setOutput("result", toolState.output);
154355
154542
  }
154356
154543
  return await handleAgentResult({
154357
154544
  result,