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/agents/sessionLabeler.d.ts +38 -18
- package/dist/cli.mjs +294 -107
- package/dist/index.js +282 -95
- package/dist/internal.js +4 -2
- package/dist/utils/normalizeEnv.d.ts +21 -1
- package/dist/utils/subprocess.d.ts +40 -0
- package/dist/utils/timer.d.ts +11 -0
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -19935,10 +19935,10 @@ var require_core = __commonJS({
|
|
|
19935
19935
|
(0, command_1.issueCommand)("set-env", { name }, convertedVal);
|
|
19936
19936
|
}
|
|
19937
19937
|
exports.exportVariable = exportVariable;
|
|
19938
|
-
function
|
|
19938
|
+
function setSecret5(secret) {
|
|
19939
19939
|
(0, command_1.issueCommand)("add-mask", {}, secret);
|
|
19940
19940
|
}
|
|
19941
|
-
exports.setSecret =
|
|
19941
|
+
exports.setSecret = setSecret5;
|
|
19942
19942
|
function addPath(inputPath) {
|
|
19943
19943
|
const filePath = process.env["GITHUB_PATH"] || "";
|
|
19944
19944
|
if (filePath) {
|
|
@@ -47954,7 +47954,7 @@ var require_core3 = __commonJS({
|
|
|
47954
47954
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47955
47955
|
var id_1 = require_id();
|
|
47956
47956
|
var ref_1 = require_ref();
|
|
47957
|
-
var
|
|
47957
|
+
var core9 = [
|
|
47958
47958
|
"$schema",
|
|
47959
47959
|
"$id",
|
|
47960
47960
|
"$defs",
|
|
@@ -47964,7 +47964,7 @@ var require_core3 = __commonJS({
|
|
|
47964
47964
|
id_1.default,
|
|
47965
47965
|
ref_1.default
|
|
47966
47966
|
];
|
|
47967
|
-
exports.default =
|
|
47967
|
+
exports.default = core9;
|
|
47968
47968
|
}
|
|
47969
47969
|
});
|
|
47970
47970
|
|
|
@@ -99202,12 +99202,12 @@ var import_picocolors2 = __toESM(require_picocolors(), 1);
|
|
|
99202
99202
|
import { basename as basename2 } from "node:path";
|
|
99203
99203
|
|
|
99204
99204
|
// commands/gha.ts
|
|
99205
|
-
var
|
|
99205
|
+
var core8 = __toESM(require_core(), 1);
|
|
99206
99206
|
var import_arg = __toESM(require_arg(), 1);
|
|
99207
99207
|
import { dirname as dirname6 } from "node:path";
|
|
99208
99208
|
|
|
99209
99209
|
// main.ts
|
|
99210
|
-
var
|
|
99210
|
+
var core7 = __toESM(require_core(), 1);
|
|
99211
99211
|
import { existsSync as existsSync7, readdirSync } from "node:fs";
|
|
99212
99212
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
99213
99213
|
import { join as join17 } from "node:path";
|
|
@@ -110025,12 +110025,41 @@ function installSignalHandler() {
|
|
|
110025
110025
|
killTrackedChildren();
|
|
110026
110026
|
});
|
|
110027
110027
|
}
|
|
110028
|
+
var DEFAULT_MAX_RETAINED_BYTES = 8 * 1024 * 1024;
|
|
110029
|
+
var TailBuffer = class {
|
|
110030
|
+
// explicit field declarations rather than constructor parameter properties:
|
|
110031
|
+
// node's strip-only TS loader (used by action/test/run.ts in CI) rejects
|
|
110032
|
+
// `constructor(private readonly cap: number)` with ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX.
|
|
110033
|
+
cap;
|
|
110034
|
+
buffer = "";
|
|
110035
|
+
truncatedBytes = 0;
|
|
110036
|
+
constructor(cap) {
|
|
110037
|
+
this.cap = cap;
|
|
110038
|
+
}
|
|
110039
|
+
append(chunk) {
|
|
110040
|
+
if (this.cap <= 0) return;
|
|
110041
|
+
this.buffer += chunk;
|
|
110042
|
+
if (this.buffer.length > this.cap) {
|
|
110043
|
+
const drop = this.buffer.length - this.cap;
|
|
110044
|
+
this.truncatedBytes += drop;
|
|
110045
|
+
this.buffer = this.buffer.slice(drop);
|
|
110046
|
+
}
|
|
110047
|
+
}
|
|
110048
|
+
toString() {
|
|
110049
|
+
if (this.truncatedBytes === 0) return this.buffer;
|
|
110050
|
+
const mib = (this.truncatedBytes / 1024 / 1024).toFixed(1);
|
|
110051
|
+
return `... [${mib} MiB truncated by retain:tail cap] ...
|
|
110052
|
+
${this.buffer}`;
|
|
110053
|
+
}
|
|
110054
|
+
};
|
|
110028
110055
|
async function spawn(options) {
|
|
110029
110056
|
const activityTimeoutMs = options.activityTimeout ?? DEFAULT_ACTIVITY_TIMEOUT_MS;
|
|
110030
110057
|
installSignalHandler();
|
|
110031
110058
|
const startTime = performance3.now();
|
|
110032
|
-
|
|
110033
|
-
|
|
110059
|
+
const retain = options.retain ?? "tail";
|
|
110060
|
+
const cap = options.maxRetainedBytes ?? DEFAULT_MAX_RETAINED_BYTES;
|
|
110061
|
+
const stdoutBuffer = retain === "none" ? null : new TailBuffer(cap);
|
|
110062
|
+
const stderrBuffer = retain === "none" ? null : new TailBuffer(cap);
|
|
110034
110063
|
const killGroup = options.killGroup ?? false;
|
|
110035
110064
|
return new Promise((resolve3, reject) => {
|
|
110036
110065
|
const child = nodeSpawn(options.cmd, options.args, {
|
|
@@ -110104,17 +110133,29 @@ async function spawn(options) {
|
|
|
110104
110133
|
}
|
|
110105
110134
|
if (child.stdout) {
|
|
110106
110135
|
child.stdout.on("data", (data) => {
|
|
110107
|
-
|
|
110108
|
-
|
|
110109
|
-
|
|
110110
|
-
|
|
110136
|
+
try {
|
|
110137
|
+
updateActivity();
|
|
110138
|
+
const chunk = data.toString();
|
|
110139
|
+
stdoutBuffer?.append(chunk);
|
|
110140
|
+
options.onStdout?.(chunk);
|
|
110141
|
+
} catch (err) {
|
|
110142
|
+
log.debug(
|
|
110143
|
+
`spawn stdout handler threw: ${err instanceof Error ? err.message : String(err)}`
|
|
110144
|
+
);
|
|
110145
|
+
}
|
|
110111
110146
|
});
|
|
110112
110147
|
}
|
|
110113
110148
|
if (child.stderr) {
|
|
110114
110149
|
child.stderr.on("data", (data) => {
|
|
110115
|
-
|
|
110116
|
-
|
|
110117
|
-
|
|
110150
|
+
try {
|
|
110151
|
+
const chunk = data.toString();
|
|
110152
|
+
stderrBuffer?.append(chunk);
|
|
110153
|
+
options.onStderr?.(chunk);
|
|
110154
|
+
} catch (err) {
|
|
110155
|
+
log.debug(
|
|
110156
|
+
`spawn stderr handler threw: ${err instanceof Error ? err.message : String(err)}`
|
|
110157
|
+
);
|
|
110158
|
+
}
|
|
110118
110159
|
});
|
|
110119
110160
|
}
|
|
110120
110161
|
child.on("close", (exitCode, signal) => {
|
|
@@ -110141,7 +110182,7 @@ async function spawn(options) {
|
|
|
110141
110182
|
return;
|
|
110142
110183
|
}
|
|
110143
110184
|
let resolvedExitCode = exitCode ?? 0;
|
|
110144
|
-
let resolvedStderr = stderrBuffer;
|
|
110185
|
+
let resolvedStderr = stderrBuffer?.toString() ?? "";
|
|
110145
110186
|
if (exitCode === null && signal) {
|
|
110146
110187
|
const killMsg = `[spawn] ${options.cmd}: killed by signal ${signal}`;
|
|
110147
110188
|
resolvedStderr = resolvedStderr ? `${resolvedStderr}
|
|
@@ -110149,7 +110190,7 @@ ${killMsg}` : killMsg;
|
|
|
110149
110190
|
resolvedExitCode = 1;
|
|
110150
110191
|
}
|
|
110151
110192
|
resolve3({
|
|
110152
|
-
stdout: stdoutBuffer,
|
|
110193
|
+
stdout: stdoutBuffer?.toString() ?? "",
|
|
110153
110194
|
stderr: resolvedStderr,
|
|
110154
110195
|
exitCode: resolvedExitCode,
|
|
110155
110196
|
durationMs
|
|
@@ -110163,11 +110204,12 @@ ${killMsg}` : killMsg;
|
|
|
110163
110204
|
if (activityCheckIntervalId) clearInterval(activityCheckIntervalId);
|
|
110164
110205
|
const errMsg = `[spawn] ${options.cmd}: ${error49.message}`;
|
|
110165
110206
|
console.error(errMsg);
|
|
110166
|
-
|
|
110207
|
+
const existingStderr = stderrBuffer?.toString() ?? "";
|
|
110208
|
+
const finalStderr = existingStderr ? `${existingStderr}
|
|
110167
110209
|
${errMsg}` : errMsg;
|
|
110168
110210
|
resolve3({
|
|
110169
|
-
stdout: stdoutBuffer,
|
|
110170
|
-
stderr:
|
|
110211
|
+
stdout: stdoutBuffer?.toString() ?? "",
|
|
110212
|
+
stderr: finalStderr,
|
|
110171
110213
|
exitCode: 1,
|
|
110172
110214
|
durationMs
|
|
110173
110215
|
});
|
|
@@ -138076,7 +138118,7 @@ var require_core4 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
138076
138118
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
138077
138119
|
const id_1 = require_id2();
|
|
138078
138120
|
const ref_1 = require_ref2();
|
|
138079
|
-
const
|
|
138121
|
+
const core9 = [
|
|
138080
138122
|
"$schema",
|
|
138081
138123
|
"$id",
|
|
138082
138124
|
"$defs",
|
|
@@ -138086,7 +138128,7 @@ var require_core4 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
138086
138128
|
id_1.default,
|
|
138087
138129
|
ref_1.default
|
|
138088
138130
|
];
|
|
138089
|
-
exports.default =
|
|
138131
|
+
exports.default = core9;
|
|
138090
138132
|
}));
|
|
138091
138133
|
var require_limitNumber2 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
138092
138134
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -142596,7 +142638,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
142596
142638
|
// package.json
|
|
142597
142639
|
var package_default = {
|
|
142598
142640
|
name: "pullfrog",
|
|
142599
|
-
version: "0.1.
|
|
142641
|
+
version: "0.1.6",
|
|
142600
142642
|
type: "module",
|
|
142601
142643
|
bin: {
|
|
142602
142644
|
pullfrog: "dist/cli.mjs",
|
|
@@ -146673,6 +146715,8 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
146673
146715
|
- **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)
|
|
146674
146716
|
- **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.
|
|
146675
146717
|
|
|
146718
|
+
**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.
|
|
146719
|
+
|
|
146676
146720
|
lenses come in two flavors, and you can mix them:
|
|
146677
146721
|
- **themed lenses** \u2014 a perspective applied across the whole diff (correctness, security, user-journey, performance, etc.).
|
|
146678
146722
|
- **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.
|
|
@@ -146680,7 +146724,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
146680
146724
|
starter menu (combine, omit, or invent your own):
|
|
146681
146725
|
- **correctness & invariants** \u2014 bugs, races, error handling, edge cases, state-machine boundaries
|
|
146682
146726
|
- **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
|
|
146683
|
-
- **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.
|
|
146727
|
+
- **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.
|
|
146684
146728
|
- **security** \u2014 new endpoints, authZ, input validation, secrets handling, replay/CSRF/injection, cross-tenant isolation
|
|
146685
146729
|
- **user-journey** \u2014 UX-touching flows: walk through happy path and failure modes as a user
|
|
146686
146730
|
- **operational readiness** \u2014 observability, alerting, migrations (forward + rollback), feature flags, on-call burden
|
|
@@ -146769,7 +146813,7 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
146769
146813
|
"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.
|
|
146770
146814
|
When unsure, treat as non-trivial.
|
|
146771
146815
|
|
|
146772
|
-
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.
|
|
146816
|
+
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.
|
|
146773
146817
|
|
|
146774
146818
|
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:
|
|
146775
146819
|
- 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
|
|
@@ -147139,20 +147183,30 @@ var ThinkingTimer = class {
|
|
|
147139
147183
|
maximumFractionDigits: 1
|
|
147140
147184
|
});
|
|
147141
147185
|
lastToolResultTimestamp = null;
|
|
147186
|
+
formatLine;
|
|
147187
|
+
// node's native TS strip-only mode does not support parameter properties,
|
|
147188
|
+
// so the formatter is declared as a field and assigned in the body.
|
|
147189
|
+
constructor(formatLine = (l) => l) {
|
|
147190
|
+
this.formatLine = formatLine;
|
|
147191
|
+
}
|
|
147142
147192
|
markToolResult() {
|
|
147143
147193
|
this.lastToolResultTimestamp = performance5.now();
|
|
147144
|
-
log.debug(
|
|
147194
|
+
log.debug(
|
|
147195
|
+
this.formatLine(`\xBB thinking timer: markToolResult at ${this.lastToolResultTimestamp}`)
|
|
147196
|
+
);
|
|
147145
147197
|
}
|
|
147146
147198
|
markToolCall() {
|
|
147147
147199
|
const now = performance5.now();
|
|
147148
147200
|
log.debug(
|
|
147149
|
-
|
|
147201
|
+
this.formatLine(
|
|
147202
|
+
`\xBB thinking timer: markToolCall at ${now}, lastToolResult=${this.lastToolResultTimestamp}`
|
|
147203
|
+
)
|
|
147150
147204
|
);
|
|
147151
147205
|
if (this.lastToolResultTimestamp === null) return;
|
|
147152
147206
|
const elapsed = now - this.lastToolResultTimestamp;
|
|
147153
147207
|
if (elapsed < THINKING_THRESHOLD) return;
|
|
147154
147208
|
const seconds = elapsed / 1e3;
|
|
147155
|
-
log.info(`\xBB thought for ${this.durationFormatter.format(seconds)}`);
|
|
147209
|
+
log.info(this.formatLine(`\xBB thought for ${this.durationFormatter.format(seconds)}`));
|
|
147156
147210
|
}
|
|
147157
147211
|
};
|
|
147158
147212
|
|
|
@@ -147401,19 +147455,39 @@ function deriveLabelFromTaskInput(input) {
|
|
|
147401
147455
|
}
|
|
147402
147456
|
var SessionLabeler = class {
|
|
147403
147457
|
labels = /* @__PURE__ */ new Map();
|
|
147458
|
+
labelsByToolUseId = /* @__PURE__ */ new Map();
|
|
147404
147459
|
pendingLabels = [];
|
|
147405
147460
|
fallbackCounter = 0;
|
|
147406
|
-
|
|
147461
|
+
/**
|
|
147462
|
+
* Record a Task/Agent tool dispatch.
|
|
147463
|
+
*
|
|
147464
|
+
* @param input Task tool input — used to derive the lens label.
|
|
147465
|
+
* @param toolUseId Optional Agent tool_use id. When provided, future events
|
|
147466
|
+
* carrying `parent_tool_use_id === toolUseId` resolve
|
|
147467
|
+
* directly to this label without consuming the FIFO queue
|
|
147468
|
+
* (Claude path). Always also pushed to the FIFO queue so
|
|
147469
|
+
* the OpenCode path still works when toolUseId is absent.
|
|
147470
|
+
*/
|
|
147471
|
+
recordTaskDispatch(input, toolUseId) {
|
|
147407
147472
|
const label = deriveLabelFromTaskInput(input);
|
|
147408
147473
|
this.pendingLabels.push(label);
|
|
147474
|
+
if (toolUseId) this.labelsByToolUseId.set(toolUseId, label);
|
|
147409
147475
|
return label;
|
|
147410
147476
|
}
|
|
147411
147477
|
/**
|
|
147412
|
-
* Return a label for the given
|
|
147413
|
-
*
|
|
147414
|
-
*
|
|
147478
|
+
* Return a label for the given event.
|
|
147479
|
+
*
|
|
147480
|
+
* @param sessionID Session id from the event (OpenCode: per-session;
|
|
147481
|
+
* Claude: shared across orchestrator + subagents).
|
|
147482
|
+
* @param parentToolUseId Claude's `parent_tool_use_id` — non-null on
|
|
147483
|
+
* subagent messages. When set and known, takes
|
|
147484
|
+
* priority over the FIFO/sessionID path.
|
|
147415
147485
|
*/
|
|
147416
|
-
labelFor(sessionID) {
|
|
147486
|
+
labelFor(sessionID, parentToolUseId) {
|
|
147487
|
+
if (parentToolUseId) {
|
|
147488
|
+
const direct = this.labelsByToolUseId.get(parentToolUseId);
|
|
147489
|
+
if (direct) return direct;
|
|
147490
|
+
}
|
|
147417
147491
|
if (!sessionID) return ORCHESTRATOR_LABEL;
|
|
147418
147492
|
const existing = this.labels.get(sessionID);
|
|
147419
147493
|
if (existing) return existing;
|
|
@@ -147497,7 +147571,23 @@ function tailLines(text, maxCodeUnits) {
|
|
|
147497
147571
|
async function runClaude(params) {
|
|
147498
147572
|
const startTime = performance6.now();
|
|
147499
147573
|
let eventCount = 0;
|
|
147500
|
-
const
|
|
147574
|
+
const labeler = new SessionLabeler();
|
|
147575
|
+
function eventLabel(event) {
|
|
147576
|
+
return labeler.labelFor(event.session_id ?? null, event.parent_tool_use_id ?? null);
|
|
147577
|
+
}
|
|
147578
|
+
function withLabel(label, message) {
|
|
147579
|
+
return label === ORCHESTRATOR_LABEL ? message : formatWithLabel(label, message);
|
|
147580
|
+
}
|
|
147581
|
+
const thinkingTimers = /* @__PURE__ */ new Map();
|
|
147582
|
+
function timerFor(label) {
|
|
147583
|
+
let t2 = thinkingTimers.get(label);
|
|
147584
|
+
if (!t2) {
|
|
147585
|
+
const formatLine = (line) => label === ORCHESTRATOR_LABEL ? line : formatWithLabel(label, line);
|
|
147586
|
+
t2 = new ThinkingTimer(formatLine);
|
|
147587
|
+
thinkingTimers.set(label, t2);
|
|
147588
|
+
}
|
|
147589
|
+
return t2;
|
|
147590
|
+
}
|
|
147501
147591
|
let finalOutput = "";
|
|
147502
147592
|
let sessionId;
|
|
147503
147593
|
let resultErrorSubtype = null;
|
|
@@ -147518,17 +147608,22 @@ async function runClaude(params) {
|
|
|
147518
147608
|
} : void 0;
|
|
147519
147609
|
}
|
|
147520
147610
|
const handlers2 = {
|
|
147521
|
-
system: (
|
|
147522
|
-
|
|
147611
|
+
system: (event) => {
|
|
147612
|
+
const label = eventLabel(event);
|
|
147613
|
+
log.debug(withLabel(label, `\xBB ${params.label} system event`));
|
|
147523
147614
|
},
|
|
147524
147615
|
assistant: (event) => {
|
|
147525
147616
|
const content = event.message?.content;
|
|
147526
147617
|
if (!content) return;
|
|
147618
|
+
const label = eventLabel(event);
|
|
147619
|
+
const boxTitle = label === ORCHESTRATOR_LABEL ? params.label : `${params.label} [${label}]`;
|
|
147527
147620
|
for (const block of content) {
|
|
147528
147621
|
if (block.type === "text" && block.text?.trim()) {
|
|
147529
147622
|
const message = block.text.trim();
|
|
147530
|
-
log.box(message, { title:
|
|
147531
|
-
|
|
147623
|
+
log.box(message, { title: boxTitle });
|
|
147624
|
+
if (label === ORCHESTRATOR_LABEL) {
|
|
147625
|
+
finalOutput = message;
|
|
147626
|
+
}
|
|
147532
147627
|
} else if (block.type === "tool_use") {
|
|
147533
147628
|
const toolName = block.name || "unknown";
|
|
147534
147629
|
if (params.onToolUse) {
|
|
@@ -147537,20 +147632,25 @@ async function runClaude(params) {
|
|
|
147537
147632
|
input: block.input
|
|
147538
147633
|
});
|
|
147539
147634
|
}
|
|
147540
|
-
|
|
147541
|
-
|
|
147542
|
-
|
|
147635
|
+
timerFor(label).markToolCall();
|
|
147636
|
+
const inputFormatted = formatJsonValue(block.input || {});
|
|
147637
|
+
const toolCallLine = inputFormatted !== "{}" ? `\xBB ${toolName}(${inputFormatted})` : `\xBB ${toolName}()`;
|
|
147638
|
+
log.info(withLabel(label, toolCallLine));
|
|
147639
|
+
if ((toolName === "Task" || toolName === "Agent") && block.input && typeof block.input === "object") {
|
|
147543
147640
|
const taskInput = block.input;
|
|
147544
|
-
const
|
|
147641
|
+
const dispatchedLabel = labeler.recordTaskDispatch(taskInput, block.id ?? null);
|
|
147545
147642
|
log.info(
|
|
147546
|
-
|
|
147643
|
+
withLabel(
|
|
147644
|
+
label,
|
|
147645
|
+
`\xBB dispatching subagent: ${dispatchedLabel}` + (taskInput.subagent_type ? ` (subagent_type=${taskInput.subagent_type})` : "")
|
|
147646
|
+
)
|
|
147547
147647
|
);
|
|
147548
147648
|
}
|
|
147549
147649
|
if (toolName.includes("report_progress") && params.todoTracker) {
|
|
147550
147650
|
log.debug("\xBB report_progress detected, disabling todo tracking");
|
|
147551
147651
|
params.todoTracker.cancel();
|
|
147552
147652
|
}
|
|
147553
|
-
if (toolName === "TodoWrite" && params.todoTracker?.enabled) {
|
|
147653
|
+
if (toolName === "TodoWrite" && params.todoTracker?.enabled && label === ORCHESTRATOR_LABEL) {
|
|
147554
147654
|
params.todoTracker.update(block.input);
|
|
147555
147655
|
}
|
|
147556
147656
|
}
|
|
@@ -147566,17 +147666,18 @@ async function runClaude(params) {
|
|
|
147566
147666
|
user: (event) => {
|
|
147567
147667
|
const content = event.message?.content;
|
|
147568
147668
|
if (!content) return;
|
|
147669
|
+
const label = eventLabel(event);
|
|
147569
147670
|
for (const block of content) {
|
|
147570
147671
|
if (typeof block === "string") continue;
|
|
147571
147672
|
if (block.type === "tool_result") {
|
|
147572
|
-
|
|
147673
|
+
timerFor(label).markToolResult();
|
|
147573
147674
|
const outputContent = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map(
|
|
147574
147675
|
(entry) => typeof entry === "string" ? entry : typeof entry === "object" && entry !== null && "text" in entry ? String(entry.text) : JSON.stringify(entry)
|
|
147575
147676
|
).join("\n") : String(block.content);
|
|
147576
147677
|
if (block.is_error) {
|
|
147577
|
-
log.info(`\xBB tool error: ${outputContent}`);
|
|
147678
|
+
log.info(withLabel(label, `\xBB tool error: ${outputContent}`));
|
|
147578
147679
|
} else {
|
|
147579
|
-
log.debug(`\xBB tool output: ${outputContent}`);
|
|
147680
|
+
log.debug(withLabel(label, `\xBB tool output: ${outputContent}`));
|
|
147580
147681
|
}
|
|
147581
147682
|
}
|
|
147582
147683
|
}
|
|
@@ -147646,7 +147747,7 @@ async function runClaude(params) {
|
|
|
147646
147747
|
};
|
|
147647
147748
|
const recentStderr = [];
|
|
147648
147749
|
let lastProviderError = null;
|
|
147649
|
-
|
|
147750
|
+
const output = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
|
|
147650
147751
|
let stdoutBuffer = "";
|
|
147651
147752
|
try {
|
|
147652
147753
|
const result = await spawn({
|
|
@@ -147663,9 +147764,14 @@ async function runClaude(params) {
|
|
|
147663
147764
|
// there's no shim-orphan issue like opencode-ai/bin/opencode, but
|
|
147664
147765
|
// detached + killGroup is the right default for any agent runtime.
|
|
147665
147766
|
killGroup: true,
|
|
147767
|
+
// claude already drains every chunk via onStdout (NDJSON parsing) and
|
|
147768
|
+
// onStderr (recentStderr ring buffer). retaining a second copy in the
|
|
147769
|
+
// spawn wrapper would grow unbounded for long sessions and previously
|
|
147770
|
+
// crashed the wrapper with RangeError. see issue #680.
|
|
147771
|
+
retain: "none",
|
|
147666
147772
|
onStdout: async (chunk) => {
|
|
147667
147773
|
const text = chunk.toString();
|
|
147668
|
-
output
|
|
147774
|
+
output.append(text);
|
|
147669
147775
|
markActivity();
|
|
147670
147776
|
stdoutBuffer += text;
|
|
147671
147777
|
const lines = stdoutBuffer.split("\n");
|
|
@@ -147740,16 +147846,18 @@ ${stderrContext}`);
|
|
|
147740
147846
|
const usage = buildUsage();
|
|
147741
147847
|
if (result.exitCode !== 0) {
|
|
147742
147848
|
const errorContext = lastProviderError ? ` (${lastProviderError})` : "";
|
|
147743
|
-
const
|
|
147744
|
-
const
|
|
147849
|
+
const stdoutSnapshot = output.toString();
|
|
147850
|
+
const stderrSnapshot = recentStderr.join("\n");
|
|
147851
|
+
const truncatedStdout = stdoutSnapshot ? tailLines(stdoutSnapshot, 2048) : "";
|
|
147852
|
+
const errorMessage = lastResultError || stderrSnapshot || truncatedStdout || `unknown error - no output from Claude CLI${errorContext}`;
|
|
147745
147853
|
log.error(
|
|
147746
147854
|
`${params.label} exited with code ${result.exitCode}${errorContext}: ${errorMessage}`
|
|
147747
147855
|
);
|
|
147748
|
-
log.debug(`stdout: ${
|
|
147749
|
-
log.debug(`stderr: ${
|
|
147856
|
+
log.debug(`stdout: ${stdoutSnapshot.substring(0, 500)}`);
|
|
147857
|
+
log.debug(`stderr: ${stderrSnapshot.substring(0, 500)}`);
|
|
147750
147858
|
return {
|
|
147751
147859
|
success: false,
|
|
147752
|
-
output: finalOutput ||
|
|
147860
|
+
output: finalOutput || stdoutSnapshot,
|
|
147753
147861
|
error: errorMessage,
|
|
147754
147862
|
usage,
|
|
147755
147863
|
sessionId
|
|
@@ -147758,7 +147866,7 @@ ${stderrContext}`);
|
|
|
147758
147866
|
if (eventCount === 0 && lastProviderError) {
|
|
147759
147867
|
return {
|
|
147760
147868
|
success: false,
|
|
147761
|
-
output: finalOutput || output,
|
|
147869
|
+
output: finalOutput || output.toString(),
|
|
147762
147870
|
error: `provider error: ${lastProviderError}`,
|
|
147763
147871
|
usage,
|
|
147764
147872
|
sessionId
|
|
@@ -147767,13 +147875,13 @@ ${stderrContext}`);
|
|
|
147767
147875
|
if (resultErrorSubtype) {
|
|
147768
147876
|
return {
|
|
147769
147877
|
success: false,
|
|
147770
|
-
output: finalOutput || output,
|
|
147878
|
+
output: finalOutput || output.toString(),
|
|
147771
147879
|
error: lastResultError || `result subtype: ${resultErrorSubtype}`,
|
|
147772
147880
|
usage,
|
|
147773
147881
|
sessionId
|
|
147774
147882
|
};
|
|
147775
147883
|
}
|
|
147776
|
-
return { success: true, output: finalOutput || output, usage, sessionId };
|
|
147884
|
+
return { success: true, output: finalOutput || output.toString(), usage, sessionId };
|
|
147777
147885
|
} catch (error49) {
|
|
147778
147886
|
params.todoTracker?.cancel();
|
|
147779
147887
|
const duration4 = performance6.now() - startTime;
|
|
@@ -147792,7 +147900,7 @@ ${stderrContext}`
|
|
|
147792
147900
|
);
|
|
147793
147901
|
return {
|
|
147794
147902
|
success: false,
|
|
147795
|
-
output: finalOutput || output,
|
|
147903
|
+
output: finalOutput || output.toString(),
|
|
147796
147904
|
error: `${errorMessage} [${diagnosis}]`,
|
|
147797
147905
|
usage: buildUsage(),
|
|
147798
147906
|
sessionId
|
|
@@ -148028,6 +148136,15 @@ function buildSecurityConfig(ctx, model) {
|
|
|
148028
148136
|
[pullfrogMcpName]: { type: "remote", url: ctx.mcpServerUrl }
|
|
148029
148137
|
},
|
|
148030
148138
|
agent: buildReviewerAgentConfig(),
|
|
148139
|
+
// opt into opencode's experimental `batch` tool (added in
|
|
148140
|
+
// anomalyco/opencode PR #2983, opt-in via `experimental.batch_tool`). it
|
|
148141
|
+
// exposes a single `batch` tool that runs 1-25 independent tool calls
|
|
148142
|
+
// (read/grep/glob/bash/etc.) concurrently in one assistant turn, which
|
|
148143
|
+
// collapses the dominant grep→20×read pattern into a single round trip.
|
|
148144
|
+
// edits are explicitly disallowed inside the batch upstream. paired with
|
|
148145
|
+
// the "Parallel tool execution" guidance in utils/instructions.ts so the
|
|
148146
|
+
// model actually reaches for it. see wiki/prompt.md.
|
|
148147
|
+
experimental: { batch_tool: true },
|
|
148031
148148
|
provider: {
|
|
148032
148149
|
google: {
|
|
148033
148150
|
models: Object.fromEntries(
|
|
@@ -148100,7 +148217,6 @@ function autoSelectModel(cliPath) {
|
|
|
148100
148217
|
async function runOpenCode(params) {
|
|
148101
148218
|
const startTime = performance7.now();
|
|
148102
148219
|
let eventCount = 0;
|
|
148103
|
-
const thinkingTimer = new ThinkingTimer();
|
|
148104
148220
|
let finalOutput = "";
|
|
148105
148221
|
let accumulatedTokens = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
148106
148222
|
let accumulatedCostUsd = 0;
|
|
@@ -148117,6 +148233,16 @@ async function runOpenCode(params) {
|
|
|
148117
148233
|
function withLabel(label, message) {
|
|
148118
148234
|
return label === ORCHESTRATOR_LABEL ? message : formatWithLabel(label, message);
|
|
148119
148235
|
}
|
|
148236
|
+
const thinkingTimers = /* @__PURE__ */ new Map();
|
|
148237
|
+
function timerFor(label) {
|
|
148238
|
+
let t2 = thinkingTimers.get(label);
|
|
148239
|
+
if (!t2) {
|
|
148240
|
+
const formatLine = (line) => label === ORCHESTRATOR_LABEL ? line : formatWithLabel(label, line);
|
|
148241
|
+
t2 = new ThinkingTimer(formatLine);
|
|
148242
|
+
thinkingTimers.set(label, t2);
|
|
148243
|
+
}
|
|
148244
|
+
return t2;
|
|
148245
|
+
}
|
|
148120
148246
|
const taskDispatchByCallID = /* @__PURE__ */ new Map();
|
|
148121
148247
|
const pendingTaskDispatches = [];
|
|
148122
148248
|
const knownNonTaskCallIDs = /* @__PURE__ */ new Set();
|
|
@@ -148265,7 +148391,7 @@ async function runOpenCode(params) {
|
|
|
148265
148391
|
input: event.part?.state?.input
|
|
148266
148392
|
});
|
|
148267
148393
|
}
|
|
148268
|
-
|
|
148394
|
+
timerFor(label).markToolCall();
|
|
148269
148395
|
const inputFormatted = formatJsonValue(event.part?.state?.input || {});
|
|
148270
148396
|
const toolCallLine = inputFormatted !== "{}" ? `\xBB ${toolName}(${inputFormatted})` : `\xBB ${toolName}()`;
|
|
148271
148397
|
log.info(withLabel(label, toolCallLine));
|
|
@@ -148289,7 +148415,7 @@ async function runOpenCode(params) {
|
|
|
148289
148415
|
const status = event.part?.state?.status || event.status || "unknown";
|
|
148290
148416
|
const output2 = event.part?.state?.output || event.output;
|
|
148291
148417
|
const label = eventLabel(event);
|
|
148292
|
-
|
|
148418
|
+
timerFor(label).markToolResult();
|
|
148293
148419
|
if (taskDispatchByCallID.size > 0 || pendingTaskDispatches.length > 0) {
|
|
148294
148420
|
if (toolId && taskDispatchByCallID.has(toolId)) {
|
|
148295
148421
|
const dispatch = taskDispatchByCallID.get(toolId);
|
|
@@ -148414,7 +148540,7 @@ async function runOpenCode(params) {
|
|
|
148414
148540
|
const recentStderr = [];
|
|
148415
148541
|
let lastProviderError = null;
|
|
148416
148542
|
let agentErrorEvent = null;
|
|
148417
|
-
|
|
148543
|
+
const output = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
|
|
148418
148544
|
let stdoutBuffer = "";
|
|
148419
148545
|
try {
|
|
148420
148546
|
const result = await spawn({
|
|
@@ -148432,6 +148558,11 @@ async function runOpenCode(params) {
|
|
|
148432
148558
|
// never fires — producing zombie runs. detached + killGroup nukes the
|
|
148433
148559
|
// whole tree.
|
|
148434
148560
|
killGroup: true,
|
|
148561
|
+
// we already drain every chunk via onStdout/onStderr (NDJSON parsing
|
|
148562
|
+
// + recentStderr ring buffer). retaining a second copy in the spawn
|
|
148563
|
+
// wrapper would grow unbounded for multi-lens Reviews and previously
|
|
148564
|
+
// crashed the wrapper with RangeError at ~1 GiB. see issue #680.
|
|
148565
|
+
retain: "none",
|
|
148435
148566
|
// NB: we used to pass `isPausedExternally: isSubagentInFlight` to suspend
|
|
148436
148567
|
// the activity timer during subagent dispatches. unnecessary now that
|
|
148437
148568
|
// our injected plugin (action/agents/opencodePlugin.ts) re-emits
|
|
@@ -148441,7 +148572,7 @@ async function runOpenCode(params) {
|
|
|
148441
148572
|
// (~3.3 plugin events/sec during a typical subagent run).
|
|
148442
148573
|
onStdout: async (chunk) => {
|
|
148443
148574
|
const text = chunk.toString();
|
|
148444
|
-
output
|
|
148575
|
+
output.append(text);
|
|
148445
148576
|
markActivity();
|
|
148446
148577
|
stdoutBuffer += text;
|
|
148447
148578
|
const lines = stdoutBuffer.split("\n");
|
|
@@ -148530,18 +148661,25 @@ ${stderrContext}`);
|
|
|
148530
148661
|
const usage = buildUsage();
|
|
148531
148662
|
if (result.exitCode !== 0) {
|
|
148532
148663
|
const errorContext = lastProviderError ? ` (${lastProviderError})` : "";
|
|
148533
|
-
const
|
|
148664
|
+
const stdoutSnapshot = output.toString();
|
|
148665
|
+
const stderrSnapshot = recentStderr.join("\n");
|
|
148666
|
+
const errorMessage = stderrSnapshot || stdoutSnapshot || `unknown error - no output from OpenCode CLI${errorContext}`;
|
|
148534
148667
|
log.error(
|
|
148535
148668
|
`${params.label} exited with code ${result.exitCode}${errorContext}: ${errorMessage}`
|
|
148536
148669
|
);
|
|
148537
|
-
log.debug(`stdout: ${
|
|
148538
|
-
log.debug(`stderr: ${
|
|
148539
|
-
return {
|
|
148670
|
+
log.debug(`stdout: ${stdoutSnapshot.substring(0, 500)}`);
|
|
148671
|
+
log.debug(`stderr: ${stderrSnapshot.substring(0, 500)}`);
|
|
148672
|
+
return {
|
|
148673
|
+
success: false,
|
|
148674
|
+
output: finalOutput || stdoutSnapshot,
|
|
148675
|
+
error: errorMessage,
|
|
148676
|
+
usage
|
|
148677
|
+
};
|
|
148540
148678
|
}
|
|
148541
148679
|
if (eventCount === 0 && lastProviderError) {
|
|
148542
148680
|
return {
|
|
148543
148681
|
success: false,
|
|
148544
|
-
output: finalOutput || output,
|
|
148682
|
+
output: finalOutput || output.toString(),
|
|
148545
148683
|
error: `provider error: ${lastProviderError}`,
|
|
148546
148684
|
usage
|
|
148547
148685
|
};
|
|
@@ -148552,12 +148690,12 @@ ${stderrContext}`);
|
|
|
148552
148690
|
const errorMessage = errorEvent.error?.data?.message || errorEvent.error?.name || JSON.stringify(errorEvent);
|
|
148553
148691
|
return {
|
|
148554
148692
|
success: false,
|
|
148555
|
-
output: finalOutput || output,
|
|
148693
|
+
output: finalOutput || output.toString(),
|
|
148556
148694
|
error: `${errorName}: ${errorMessage}`,
|
|
148557
148695
|
usage
|
|
148558
148696
|
};
|
|
148559
148697
|
}
|
|
148560
|
-
return { success: true, output: finalOutput || output, usage };
|
|
148698
|
+
return { success: true, output: finalOutput || output.toString(), usage };
|
|
148561
148699
|
} catch (error49) {
|
|
148562
148700
|
params.todoTracker?.cancel();
|
|
148563
148701
|
const duration4 = performance7.now() - startTime;
|
|
@@ -148576,7 +148714,7 @@ ${stderrContext}`
|
|
|
148576
148714
|
);
|
|
148577
148715
|
return {
|
|
148578
148716
|
success: false,
|
|
148579
|
-
output: finalOutput || output,
|
|
148717
|
+
output: finalOutput || output.toString(),
|
|
148580
148718
|
error: `${errorMessage} [${diagnosis}]`,
|
|
148581
148719
|
usage: buildUsage()
|
|
148582
148720
|
};
|
|
@@ -152482,6 +152620,14 @@ function isOIDCAvailable() {
|
|
|
152482
152620
|
process.env.ACTIONS_ID_TOKEN_REQUEST_URL && process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN
|
|
152483
152621
|
);
|
|
152484
152622
|
}
|
|
152623
|
+
var TokenExchangeError = class extends Error {
|
|
152624
|
+
status;
|
|
152625
|
+
constructor(status, message) {
|
|
152626
|
+
super(message);
|
|
152627
|
+
this.name = "TokenExchangeError";
|
|
152628
|
+
this.status = status;
|
|
152629
|
+
}
|
|
152630
|
+
};
|
|
152485
152631
|
async function acquireTokenViaOIDC(opts) {
|
|
152486
152632
|
const oidcToken = await core2.getIDToken("pullfrog-api");
|
|
152487
152633
|
const repos = [...opts?.repos ?? []];
|
|
@@ -152506,7 +152652,16 @@ async function acquireTokenViaOIDC(opts) {
|
|
|
152506
152652
|
});
|
|
152507
152653
|
clearTimeout(timeoutId);
|
|
152508
152654
|
if (!tokenResponse.ok) {
|
|
152509
|
-
|
|
152655
|
+
let serverMessage;
|
|
152656
|
+
try {
|
|
152657
|
+
const body = await tokenResponse.json();
|
|
152658
|
+
if (typeof body.error === "string") serverMessage = body.error;
|
|
152659
|
+
} catch {
|
|
152660
|
+
}
|
|
152661
|
+
throw new TokenExchangeError(
|
|
152662
|
+
tokenResponse.status,
|
|
152663
|
+
serverMessage ?? `Token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText}`
|
|
152664
|
+
);
|
|
152510
152665
|
}
|
|
152511
152666
|
const tokenData = await tokenResponse.json();
|
|
152512
152667
|
return tokenData.token;
|
|
@@ -152627,7 +152782,10 @@ async function acquireNewToken(opts) {
|
|
|
152627
152782
|
if (isOIDCAvailable()) {
|
|
152628
152783
|
return await retry(() => acquireTokenViaOIDC(opts), {
|
|
152629
152784
|
label: "token exchange",
|
|
152630
|
-
shouldRetry: (error49) =>
|
|
152785
|
+
shouldRetry: (error49) => {
|
|
152786
|
+
if (error49 instanceof TokenExchangeError) return error49.status >= 500 || error49.status === 429;
|
|
152787
|
+
return error49 instanceof Error && (error49.message.includes("timed out") || error49.message.includes("fetch failed") || error49.message.includes("ECONNRESET") || error49.message.includes("ETIMEDOUT"));
|
|
152788
|
+
}
|
|
152631
152789
|
});
|
|
152632
152790
|
} else {
|
|
152633
152791
|
return await acquireTokenViaGitHubApp(opts);
|
|
@@ -153174,6 +153332,21 @@ ${getStandaloneModeInstructions(ctx.payload.event.trigger, t2, ctx.outputSchema)
|
|
|
153174
153332
|
|
|
153175
153333
|
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 \`${t2("push_branch")}\`, ensure the working tree is clean \u2014 that tool rejects dirty trees, and tests you ran earlier often leave untracked output.
|
|
153176
153334
|
|
|
153335
|
+
### Parallel tool execution
|
|
153336
|
+
|
|
153337
|
+
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:
|
|
153338
|
+
- reading multiple files (especially after a grep returns candidates)
|
|
153339
|
+
- multiple greps with different patterns
|
|
153340
|
+
- glob + grep + read combos
|
|
153341
|
+
- listing multiple directories
|
|
153342
|
+
- inspecting multiple MCP tools or resources
|
|
153343
|
+
|
|
153344
|
+
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" ? `
|
|
153345
|
+
|
|
153346
|
+
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.` : `
|
|
153347
|
+
|
|
153348
|
+
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.`}
|
|
153349
|
+
|
|
153177
153350
|
### Command execution
|
|
153178
153351
|
|
|
153179
153352
|
Never use \`sleep\` to wait for commands to complete. Commands run synchronously \u2014 when the shell tool returns, the command has finished.
|
|
@@ -153300,10 +153473,22 @@ async function readLearningsFile(path3) {
|
|
|
153300
153473
|
}
|
|
153301
153474
|
|
|
153302
153475
|
// utils/normalizeEnv.ts
|
|
153303
|
-
|
|
153304
|
-
|
|
153305
|
-
|
|
153476
|
+
var core4 = __toESM(require_core(), 1);
|
|
153477
|
+
function sanitizeSecret(key, value2) {
|
|
153478
|
+
const trimmed = value2.trim();
|
|
153479
|
+
if (trimmed.length === 0) {
|
|
153480
|
+
log.warning(
|
|
153481
|
+
`\xBB ${key} is whitespace-only \u2014 leaving env var unchanged. check your secret value.`
|
|
153482
|
+
);
|
|
153483
|
+
return null;
|
|
153306
153484
|
}
|
|
153485
|
+
if (trimmed !== value2) {
|
|
153486
|
+
log.warning(
|
|
153487
|
+
`\xBB stripped whitespace from ${key} (whitespace in secret values breaks GitHub Actions log masking)`
|
|
153488
|
+
);
|
|
153489
|
+
}
|
|
153490
|
+
core4.setSecret(trimmed);
|
|
153491
|
+
return trimmed;
|
|
153307
153492
|
}
|
|
153308
153493
|
function normalizeEnv() {
|
|
153309
153494
|
const upperKeys = /* @__PURE__ */ new Map();
|
|
@@ -153314,11 +153499,6 @@ function normalizeEnv() {
|
|
|
153314
153499
|
upperKeys.set(upper2, existing);
|
|
153315
153500
|
}
|
|
153316
153501
|
for (const [upperKey, keys] of upperKeys) {
|
|
153317
|
-
if (isSensitiveEnvName(upperKey)) {
|
|
153318
|
-
for (const key of keys) {
|
|
153319
|
-
maskValue(process.env[key]);
|
|
153320
|
-
}
|
|
153321
|
-
}
|
|
153322
153502
|
if (keys.length === 1) {
|
|
153323
153503
|
const key = keys[0];
|
|
153324
153504
|
if (key !== upperKey) {
|
|
@@ -153341,10 +153521,17 @@ function normalizeEnv() {
|
|
|
153341
153521
|
}
|
|
153342
153522
|
process.env[upperKey] = preferredValue;
|
|
153343
153523
|
}
|
|
153524
|
+
for (const key of Object.keys(process.env)) {
|
|
153525
|
+
if (!isSensitiveEnvName(key)) continue;
|
|
153526
|
+
const value2 = process.env[key];
|
|
153527
|
+
if (typeof value2 !== "string" || value2.length === 0) continue;
|
|
153528
|
+
const sanitized = sanitizeSecret(key, value2);
|
|
153529
|
+
if (sanitized !== null) process.env[key] = sanitized;
|
|
153530
|
+
}
|
|
153344
153531
|
}
|
|
153345
153532
|
|
|
153346
153533
|
// utils/payload.ts
|
|
153347
|
-
var
|
|
153534
|
+
var core5 = __toESM(require_core(), 1);
|
|
153348
153535
|
import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
|
|
153349
153536
|
|
|
153350
153537
|
// utils/versioning.ts
|
|
@@ -153408,7 +153595,7 @@ function resolveCwd(cwd) {
|
|
|
153408
153595
|
return workspace ? resolve2(workspace, cwd) : cwd;
|
|
153409
153596
|
}
|
|
153410
153597
|
function resolvePromptInput() {
|
|
153411
|
-
const prompt =
|
|
153598
|
+
const prompt = core5.getInput("prompt", { required: true });
|
|
153412
153599
|
let parsed2;
|
|
153413
153600
|
try {
|
|
153414
153601
|
parsed2 = JSON.parse(prompt);
|
|
@@ -153424,11 +153611,11 @@ function resolvePromptInput() {
|
|
|
153424
153611
|
}
|
|
153425
153612
|
function resolveNonPromptInputs() {
|
|
153426
153613
|
return Inputs.omit("prompt").assert({
|
|
153427
|
-
model:
|
|
153428
|
-
timeout:
|
|
153429
|
-
cwd:
|
|
153430
|
-
push:
|
|
153431
|
-
shell:
|
|
153614
|
+
model: core5.getInput("model") || void 0,
|
|
153615
|
+
timeout: core5.getInput("timeout") || void 0,
|
|
153616
|
+
cwd: core5.getInput("cwd") || void 0,
|
|
153617
|
+
push: core5.getInput("push") || void 0,
|
|
153618
|
+
shell: core5.getInput("shell") || void 0
|
|
153432
153619
|
});
|
|
153433
153620
|
}
|
|
153434
153621
|
var isPullfrog = (actor) => {
|
|
@@ -153684,13 +153871,13 @@ async function fetchRunContext(params) {
|
|
|
153684
153871
|
}
|
|
153685
153872
|
|
|
153686
153873
|
// utils/runContextData.ts
|
|
153687
|
-
var
|
|
153874
|
+
var core6 = __toESM(require_core(), 1);
|
|
153688
153875
|
async function resolveRunContextData(params) {
|
|
153689
153876
|
log.info(`\xBB running Pullfrog v${package_default.version}...`);
|
|
153690
153877
|
const repoContext = parseRepoContext();
|
|
153691
153878
|
let oidcToken;
|
|
153692
153879
|
try {
|
|
153693
|
-
oidcToken = await
|
|
153880
|
+
oidcToken = await core6.getIDToken("pullfrog-api");
|
|
153694
153881
|
} catch {
|
|
153695
153882
|
}
|
|
153696
153883
|
const [repoResponse, runContext] = await Promise.all([
|
|
@@ -153993,7 +154180,7 @@ async function resolveRun(params) {
|
|
|
153993
154180
|
|
|
153994
154181
|
// main.ts
|
|
153995
154182
|
function resolveOutputSchema() {
|
|
153996
|
-
const raw2 =
|
|
154183
|
+
const raw2 = core7.getInput("output_schema");
|
|
153997
154184
|
if (!raw2) return void 0;
|
|
153998
154185
|
let parsed2;
|
|
153999
154186
|
try {
|
|
@@ -154161,7 +154348,7 @@ async function buildProxyTokenHeaders(ctx) {
|
|
|
154161
154348
|
if (ctx.oidcCredentials) {
|
|
154162
154349
|
process.env.ACTIONS_ID_TOKEN_REQUEST_URL = ctx.oidcCredentials.requestUrl;
|
|
154163
154350
|
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = ctx.oidcCredentials.requestToken;
|
|
154164
|
-
const oidcToken = await
|
|
154351
|
+
const oidcToken = await core7.getIDToken("pullfrog-api");
|
|
154165
154352
|
delete process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
|
|
154166
154353
|
delete process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
|
154167
154354
|
return { Authorization: `Bearer ${oidcToken}` };
|
|
@@ -154183,7 +154370,7 @@ async function resolveProxyModel(ctx) {
|
|
|
154183
154370
|
const key = await mintProxyKey({ oidcCredentials: ctx.oidcCredentials, repo: ctx.repo });
|
|
154184
154371
|
if (!key) return;
|
|
154185
154372
|
process.env.OPENROUTER_API_KEY = key;
|
|
154186
|
-
|
|
154373
|
+
core7.setSecret(key);
|
|
154187
154374
|
ctx.payload.proxyModel = ctx.proxyModel;
|
|
154188
154375
|
const label = ctx.oss ? "oss" : "router";
|
|
154189
154376
|
log.info(`\xBB proxy: ${label} \u2192 ${ctx.proxyModel}`);
|
|
@@ -154295,8 +154482,8 @@ async function main() {
|
|
|
154295
154482
|
if (runContext.dbSecrets) {
|
|
154296
154483
|
for (const [key, value2] of Object.entries(runContext.dbSecrets)) {
|
|
154297
154484
|
if (!process.env[key]) {
|
|
154298
|
-
|
|
154299
|
-
|
|
154485
|
+
const sanitized = sanitizeSecret(key, value2);
|
|
154486
|
+
if (sanitized !== null) process.env[key] = sanitized;
|
|
154300
154487
|
}
|
|
154301
154488
|
}
|
|
154302
154489
|
const count = Object.keys(runContext.dbSecrets).length;
|
|
@@ -154634,7 +154821,7 @@ ${instructions.user}` : null,
|
|
|
154634
154821
|
}
|
|
154635
154822
|
if (toolState.output) {
|
|
154636
154823
|
log.info(`::pullfrog-output::${Buffer.from(toolState.output).toString("base64")}`);
|
|
154637
|
-
|
|
154824
|
+
core7.setOutput("result", toolState.output);
|
|
154638
154825
|
}
|
|
154639
154826
|
return await handleAgentResult({
|
|
154640
154827
|
result,
|
|
@@ -154723,27 +154910,27 @@ async function runMain() {
|
|
|
154723
154910
|
}
|
|
154724
154911
|
} catch (error49) {
|
|
154725
154912
|
const errorMessage = error49 instanceof Error ? error49.message : "unknown error occurred";
|
|
154726
|
-
|
|
154913
|
+
core8.setFailed(`action failed: ${errorMessage}`);
|
|
154727
154914
|
}
|
|
154728
154915
|
}
|
|
154729
154916
|
async function tokenMain() {
|
|
154730
|
-
const reposInput =
|
|
154917
|
+
const reposInput = core8.getInput("repos");
|
|
154731
154918
|
const additionalRepos = reposInput ? reposInput.split(",").map((r) => r.trim()).filter(Boolean) : [];
|
|
154732
154919
|
const token = await acquireNewToken({ repos: additionalRepos });
|
|
154733
|
-
|
|
154734
|
-
|
|
154735
|
-
|
|
154920
|
+
core8.setSecret(token);
|
|
154921
|
+
core8.saveState(STATE_TOKEN, token);
|
|
154922
|
+
core8.setOutput("token", token);
|
|
154736
154923
|
const scope2 = additionalRepos.length ? `current repo + ${additionalRepos.join(", ")}` : "current repo only";
|
|
154737
|
-
|
|
154924
|
+
core8.info(`\xBB installation token acquired (${scope2})`);
|
|
154738
154925
|
}
|
|
154739
154926
|
async function tokenPost() {
|
|
154740
|
-
const token =
|
|
154927
|
+
const token = core8.getState(STATE_TOKEN);
|
|
154741
154928
|
if (!token) {
|
|
154742
|
-
|
|
154929
|
+
core8.debug("no token found in state, skipping revocation");
|
|
154743
154930
|
return;
|
|
154744
154931
|
}
|
|
154745
154932
|
await revokeGitHubInstallationToken(token);
|
|
154746
|
-
|
|
154933
|
+
core8.info("\xBB installation token revoked");
|
|
154747
154934
|
}
|
|
154748
154935
|
function printGhaUsage(params) {
|
|
154749
154936
|
params.stream(`usage: ${params.prog} gha [subcommand]
|
|
@@ -154859,7 +155046,7 @@ async function run(args2) {
|
|
|
154859
155046
|
}
|
|
154860
155047
|
} catch (error49) {
|
|
154861
155048
|
const message = error49 instanceof Error ? error49.message : String(error49);
|
|
154862
|
-
|
|
155049
|
+
core8.setFailed(message);
|
|
154863
155050
|
}
|
|
154864
155051
|
}
|
|
154865
155052
|
|
|
@@ -156523,7 +156710,7 @@ async function run2() {
|
|
|
156523
156710
|
}
|
|
156524
156711
|
|
|
156525
156712
|
// cli.ts
|
|
156526
|
-
var VERSION10 = "0.1.
|
|
156713
|
+
var VERSION10 = "0.1.6";
|
|
156527
156714
|
var bin = basename2(process.argv[1] || "");
|
|
156528
156715
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
156529
156716
|
var rawArgs = process.argv.slice(2);
|