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/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
|
|
19721
|
+
function setSecret4(secret) {
|
|
19722
19722
|
(0, command_1.issueCommand)("add-mask", {}, secret);
|
|
19723
19723
|
}
|
|
19724
|
-
exports.setSecret =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
109750
|
-
|
|
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
|
-
|
|
109825
|
-
|
|
109826
|
-
|
|
109827
|
-
|
|
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
|
-
|
|
109833
|
-
|
|
109834
|
-
|
|
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
|
-
|
|
109924
|
+
const existingStderr = stderrBuffer?.toString() ?? "";
|
|
109925
|
+
const finalStderr = existingStderr ? `${existingStderr}
|
|
109884
109926
|
${errMsg}` : errMsg;
|
|
109885
109927
|
resolve3({
|
|
109886
|
-
stdout: stdoutBuffer,
|
|
109887
|
-
stderr:
|
|
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
|
|
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 =
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
147130
|
-
*
|
|
147131
|
-
*
|
|
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
|
|
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: (
|
|
147239
|
-
|
|
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:
|
|
147248
|
-
|
|
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
|
-
|
|
147258
|
-
|
|
147259
|
-
|
|
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
|
|
147358
|
+
const dispatchedLabel = labeler.recordTaskDispatch(taskInput, block.id ?? null);
|
|
147262
147359
|
log.info(
|
|
147263
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
147461
|
-
const
|
|
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: ${
|
|
147466
|
-
log.debug(`stderr: ${
|
|
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 ||
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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: ${
|
|
148255
|
-
log.debug(`stderr: ${
|
|
148256
|
-
return {
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
153021
|
-
|
|
153022
|
-
|
|
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
|
|
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 =
|
|
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:
|
|
153145
|
-
timeout:
|
|
153146
|
-
cwd:
|
|
153147
|
-
push:
|
|
153148
|
-
shell:
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
154016
|
-
|
|
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
|
-
|
|
154541
|
+
core7.setOutput("result", toolState.output);
|
|
154355
154542
|
}
|
|
154356
154543
|
return await handleAgentResult({
|
|
154357
154544
|
result,
|