pullfrog 0.1.4 → 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 +310 -118
- package/dist/index.js +298 -106
- package/dist/internal.js +5 -4
- package/dist/mcp/geminiSanitizer.d.ts +15 -4
- 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";
|
|
@@ -107955,8 +107955,7 @@ var providers = {
|
|
|
107955
107955
|
"gpt-5-nano": {
|
|
107956
107956
|
displayName: "GPT Nano",
|
|
107957
107957
|
resolve: "opencode/gpt-5-nano",
|
|
107958
|
-
|
|
107959
|
-
isFree: true
|
|
107958
|
+
openRouterResolve: "openrouter/openai/gpt-5-nano"
|
|
107960
107959
|
},
|
|
107961
107960
|
"mimo-v2-pro-free": {
|
|
107962
107961
|
displayName: "MiMo V2 Pro",
|
|
@@ -108185,6 +108184,11 @@ async function apiFetch(options) {
|
|
|
108185
108184
|
if (bypassSecret) {
|
|
108186
108185
|
headers["x-vercel-protection-bypass"] = bypassSecret;
|
|
108187
108186
|
}
|
|
108187
|
+
if (!options.body) {
|
|
108188
|
+
for (const key of Object.keys(headers)) {
|
|
108189
|
+
if (key.toLowerCase() === "content-type") delete headers[key];
|
|
108190
|
+
}
|
|
108191
|
+
}
|
|
108188
108192
|
log.debug(`api fetch: ${options.method ?? "GET"} ${url4.pathname}`);
|
|
108189
108193
|
const init = {
|
|
108190
108194
|
method: options.method ?? "GET",
|
|
@@ -108873,8 +108877,11 @@ function sanitizeToolForGemini(tool2) {
|
|
|
108873
108877
|
}
|
|
108874
108878
|
function isGeminiRouted(ctx) {
|
|
108875
108879
|
const effective = ctx.payload.proxyModel ?? ctx.resolvedModel ?? ctx.payload.model;
|
|
108876
|
-
if (!effective) return
|
|
108877
|
-
|
|
108880
|
+
if (!effective) return true;
|
|
108881
|
+
const normalized = effective.toLowerCase();
|
|
108882
|
+
if (normalized.includes("gemini")) return true;
|
|
108883
|
+
if (!normalized.includes("/")) return true;
|
|
108884
|
+
return false;
|
|
108878
108885
|
}
|
|
108879
108886
|
|
|
108880
108887
|
// mcp/shared.ts
|
|
@@ -109735,12 +109742,41 @@ function installSignalHandler() {
|
|
|
109735
109742
|
killTrackedChildren();
|
|
109736
109743
|
});
|
|
109737
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
|
+
};
|
|
109738
109772
|
async function spawn(options) {
|
|
109739
109773
|
const activityTimeoutMs = options.activityTimeout ?? DEFAULT_ACTIVITY_TIMEOUT_MS;
|
|
109740
109774
|
installSignalHandler();
|
|
109741
109775
|
const startTime = performance3.now();
|
|
109742
|
-
|
|
109743
|
-
|
|
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);
|
|
109744
109780
|
const killGroup = options.killGroup ?? false;
|
|
109745
109781
|
return new Promise((resolve3, reject) => {
|
|
109746
109782
|
const child = nodeSpawn(options.cmd, options.args, {
|
|
@@ -109814,17 +109850,29 @@ async function spawn(options) {
|
|
|
109814
109850
|
}
|
|
109815
109851
|
if (child.stdout) {
|
|
109816
109852
|
child.stdout.on("data", (data) => {
|
|
109817
|
-
|
|
109818
|
-
|
|
109819
|
-
|
|
109820
|
-
|
|
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
|
+
}
|
|
109821
109863
|
});
|
|
109822
109864
|
}
|
|
109823
109865
|
if (child.stderr) {
|
|
109824
109866
|
child.stderr.on("data", (data) => {
|
|
109825
|
-
|
|
109826
|
-
|
|
109827
|
-
|
|
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
|
+
}
|
|
109828
109876
|
});
|
|
109829
109877
|
}
|
|
109830
109878
|
child.on("close", (exitCode, signal) => {
|
|
@@ -109851,7 +109899,7 @@ async function spawn(options) {
|
|
|
109851
109899
|
return;
|
|
109852
109900
|
}
|
|
109853
109901
|
let resolvedExitCode = exitCode ?? 0;
|
|
109854
|
-
let resolvedStderr = stderrBuffer;
|
|
109902
|
+
let resolvedStderr = stderrBuffer?.toString() ?? "";
|
|
109855
109903
|
if (exitCode === null && signal) {
|
|
109856
109904
|
const killMsg = `[spawn] ${options.cmd}: killed by signal ${signal}`;
|
|
109857
109905
|
resolvedStderr = resolvedStderr ? `${resolvedStderr}
|
|
@@ -109859,7 +109907,7 @@ ${killMsg}` : killMsg;
|
|
|
109859
109907
|
resolvedExitCode = 1;
|
|
109860
109908
|
}
|
|
109861
109909
|
resolve3({
|
|
109862
|
-
stdout: stdoutBuffer,
|
|
109910
|
+
stdout: stdoutBuffer?.toString() ?? "",
|
|
109863
109911
|
stderr: resolvedStderr,
|
|
109864
109912
|
exitCode: resolvedExitCode,
|
|
109865
109913
|
durationMs
|
|
@@ -109873,11 +109921,12 @@ ${killMsg}` : killMsg;
|
|
|
109873
109921
|
if (activityCheckIntervalId) clearInterval(activityCheckIntervalId);
|
|
109874
109922
|
const errMsg = `[spawn] ${options.cmd}: ${error49.message}`;
|
|
109875
109923
|
console.error(errMsg);
|
|
109876
|
-
|
|
109924
|
+
const existingStderr = stderrBuffer?.toString() ?? "";
|
|
109925
|
+
const finalStderr = existingStderr ? `${existingStderr}
|
|
109877
109926
|
${errMsg}` : errMsg;
|
|
109878
109927
|
resolve3({
|
|
109879
|
-
stdout: stdoutBuffer,
|
|
109880
|
-
stderr:
|
|
109928
|
+
stdout: stdoutBuffer?.toString() ?? "",
|
|
109929
|
+
stderr: finalStderr,
|
|
109881
109930
|
exitCode: 1,
|
|
109882
109931
|
durationMs
|
|
109883
109932
|
});
|
|
@@ -137786,7 +137835,7 @@ var require_core4 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
137786
137835
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
137787
137836
|
const id_1 = require_id2();
|
|
137788
137837
|
const ref_1 = require_ref2();
|
|
137789
|
-
const
|
|
137838
|
+
const core8 = [
|
|
137790
137839
|
"$schema",
|
|
137791
137840
|
"$id",
|
|
137792
137841
|
"$defs",
|
|
@@ -137796,7 +137845,7 @@ var require_core4 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
137796
137845
|
id_1.default,
|
|
137797
137846
|
ref_1.default
|
|
137798
137847
|
];
|
|
137799
|
-
exports.default =
|
|
137848
|
+
exports.default = core8;
|
|
137800
137849
|
}));
|
|
137801
137850
|
var require_limitNumber2 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
137802
137851
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -142306,7 +142355,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
142306
142355
|
// package.json
|
|
142307
142356
|
var package_default = {
|
|
142308
142357
|
name: "pullfrog",
|
|
142309
|
-
version: "0.1.
|
|
142358
|
+
version: "0.1.6",
|
|
142310
142359
|
type: "module",
|
|
142311
142360
|
bin: {
|
|
142312
142361
|
pullfrog: "dist/cli.mjs",
|
|
@@ -146383,6 +146432,8 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
146383
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)
|
|
146384
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.
|
|
146385
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
|
+
|
|
146386
146437
|
lenses come in two flavors, and you can mix them:
|
|
146387
146438
|
- **themed lenses** \u2014 a perspective applied across the whole diff (correctness, security, user-journey, performance, etc.).
|
|
146388
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.
|
|
@@ -146390,7 +146441,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
146390
146441
|
starter menu (combine, omit, or invent your own):
|
|
146391
146442
|
- **correctness & invariants** \u2014 bugs, races, error handling, edge cases, state-machine boundaries
|
|
146392
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
|
|
146393
|
-
- **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.
|
|
146394
146445
|
- **security** \u2014 new endpoints, authZ, input validation, secrets handling, replay/CSRF/injection, cross-tenant isolation
|
|
146395
146446
|
- **user-journey** \u2014 UX-touching flows: walk through happy path and failure modes as a user
|
|
146396
146447
|
- **operational readiness** \u2014 observability, alerting, migrations (forward + rollback), feature flags, on-call burden
|
|
@@ -146479,7 +146530,7 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
146479
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.
|
|
146480
146531
|
When unsure, treat as non-trivial.
|
|
146481
146532
|
|
|
146482
|
-
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.
|
|
146483
146534
|
|
|
146484
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:
|
|
146485
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
|
|
@@ -146849,20 +146900,30 @@ var ThinkingTimer = class {
|
|
|
146849
146900
|
maximumFractionDigits: 1
|
|
146850
146901
|
});
|
|
146851
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
|
+
}
|
|
146852
146909
|
markToolResult() {
|
|
146853
146910
|
this.lastToolResultTimestamp = performance5.now();
|
|
146854
|
-
log.debug(
|
|
146911
|
+
log.debug(
|
|
146912
|
+
this.formatLine(`\xBB thinking timer: markToolResult at ${this.lastToolResultTimestamp}`)
|
|
146913
|
+
);
|
|
146855
146914
|
}
|
|
146856
146915
|
markToolCall() {
|
|
146857
146916
|
const now = performance5.now();
|
|
146858
146917
|
log.debug(
|
|
146859
|
-
|
|
146918
|
+
this.formatLine(
|
|
146919
|
+
`\xBB thinking timer: markToolCall at ${now}, lastToolResult=${this.lastToolResultTimestamp}`
|
|
146920
|
+
)
|
|
146860
146921
|
);
|
|
146861
146922
|
if (this.lastToolResultTimestamp === null) return;
|
|
146862
146923
|
const elapsed = now - this.lastToolResultTimestamp;
|
|
146863
146924
|
if (elapsed < THINKING_THRESHOLD) return;
|
|
146864
146925
|
const seconds = elapsed / 1e3;
|
|
146865
|
-
log.info(`\xBB thought for ${this.durationFormatter.format(seconds)}`);
|
|
146926
|
+
log.info(this.formatLine(`\xBB thought for ${this.durationFormatter.format(seconds)}`));
|
|
146866
146927
|
}
|
|
146867
146928
|
};
|
|
146868
146929
|
|
|
@@ -147111,19 +147172,39 @@ function deriveLabelFromTaskInput(input) {
|
|
|
147111
147172
|
}
|
|
147112
147173
|
var SessionLabeler = class {
|
|
147113
147174
|
labels = /* @__PURE__ */ new Map();
|
|
147175
|
+
labelsByToolUseId = /* @__PURE__ */ new Map();
|
|
147114
147176
|
pendingLabels = [];
|
|
147115
147177
|
fallbackCounter = 0;
|
|
147116
|
-
|
|
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) {
|
|
147117
147189
|
const label = deriveLabelFromTaskInput(input);
|
|
147118
147190
|
this.pendingLabels.push(label);
|
|
147191
|
+
if (toolUseId) this.labelsByToolUseId.set(toolUseId, label);
|
|
147119
147192
|
return label;
|
|
147120
147193
|
}
|
|
147121
147194
|
/**
|
|
147122
|
-
* Return a label for the given
|
|
147123
|
-
*
|
|
147124
|
-
*
|
|
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.
|
|
147125
147202
|
*/
|
|
147126
|
-
labelFor(sessionID) {
|
|
147203
|
+
labelFor(sessionID, parentToolUseId) {
|
|
147204
|
+
if (parentToolUseId) {
|
|
147205
|
+
const direct = this.labelsByToolUseId.get(parentToolUseId);
|
|
147206
|
+
if (direct) return direct;
|
|
147207
|
+
}
|
|
147127
147208
|
if (!sessionID) return ORCHESTRATOR_LABEL;
|
|
147128
147209
|
const existing = this.labels.get(sessionID);
|
|
147129
147210
|
if (existing) return existing;
|
|
@@ -147195,8 +147276,7 @@ function stripProviderPrefix(specifier) {
|
|
|
147195
147276
|
const slashIndex = specifier.indexOf("/");
|
|
147196
147277
|
return slashIndex > 0 ? specifier.slice(slashIndex + 1) : specifier;
|
|
147197
147278
|
}
|
|
147198
|
-
function resolveEffort(
|
|
147199
|
-
if (model?.includes("opus")) return "max";
|
|
147279
|
+
function resolveEffort(_model) {
|
|
147200
147280
|
return "high";
|
|
147201
147281
|
}
|
|
147202
147282
|
function tailLines(text, maxCodeUnits) {
|
|
@@ -147208,7 +147288,23 @@ function tailLines(text, maxCodeUnits) {
|
|
|
147208
147288
|
async function runClaude(params) {
|
|
147209
147289
|
const startTime = performance6.now();
|
|
147210
147290
|
let eventCount = 0;
|
|
147211
|
-
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
|
+
}
|
|
147212
147308
|
let finalOutput = "";
|
|
147213
147309
|
let sessionId;
|
|
147214
147310
|
let resultErrorSubtype = null;
|
|
@@ -147229,17 +147325,22 @@ async function runClaude(params) {
|
|
|
147229
147325
|
} : void 0;
|
|
147230
147326
|
}
|
|
147231
147327
|
const handlers2 = {
|
|
147232
|
-
system: (
|
|
147233
|
-
|
|
147328
|
+
system: (event) => {
|
|
147329
|
+
const label = eventLabel(event);
|
|
147330
|
+
log.debug(withLabel(label, `\xBB ${params.label} system event`));
|
|
147234
147331
|
},
|
|
147235
147332
|
assistant: (event) => {
|
|
147236
147333
|
const content = event.message?.content;
|
|
147237
147334
|
if (!content) return;
|
|
147335
|
+
const label = eventLabel(event);
|
|
147336
|
+
const boxTitle = label === ORCHESTRATOR_LABEL ? params.label : `${params.label} [${label}]`;
|
|
147238
147337
|
for (const block of content) {
|
|
147239
147338
|
if (block.type === "text" && block.text?.trim()) {
|
|
147240
147339
|
const message = block.text.trim();
|
|
147241
|
-
log.box(message, { title:
|
|
147242
|
-
|
|
147340
|
+
log.box(message, { title: boxTitle });
|
|
147341
|
+
if (label === ORCHESTRATOR_LABEL) {
|
|
147342
|
+
finalOutput = message;
|
|
147343
|
+
}
|
|
147243
147344
|
} else if (block.type === "tool_use") {
|
|
147244
147345
|
const toolName = block.name || "unknown";
|
|
147245
147346
|
if (params.onToolUse) {
|
|
@@ -147248,20 +147349,25 @@ async function runClaude(params) {
|
|
|
147248
147349
|
input: block.input
|
|
147249
147350
|
});
|
|
147250
147351
|
}
|
|
147251
|
-
|
|
147252
|
-
|
|
147253
|
-
|
|
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") {
|
|
147254
147357
|
const taskInput = block.input;
|
|
147255
|
-
const
|
|
147358
|
+
const dispatchedLabel = labeler.recordTaskDispatch(taskInput, block.id ?? null);
|
|
147256
147359
|
log.info(
|
|
147257
|
-
|
|
147360
|
+
withLabel(
|
|
147361
|
+
label,
|
|
147362
|
+
`\xBB dispatching subagent: ${dispatchedLabel}` + (taskInput.subagent_type ? ` (subagent_type=${taskInput.subagent_type})` : "")
|
|
147363
|
+
)
|
|
147258
147364
|
);
|
|
147259
147365
|
}
|
|
147260
147366
|
if (toolName.includes("report_progress") && params.todoTracker) {
|
|
147261
147367
|
log.debug("\xBB report_progress detected, disabling todo tracking");
|
|
147262
147368
|
params.todoTracker.cancel();
|
|
147263
147369
|
}
|
|
147264
|
-
if (toolName === "TodoWrite" && params.todoTracker?.enabled) {
|
|
147370
|
+
if (toolName === "TodoWrite" && params.todoTracker?.enabled && label === ORCHESTRATOR_LABEL) {
|
|
147265
147371
|
params.todoTracker.update(block.input);
|
|
147266
147372
|
}
|
|
147267
147373
|
}
|
|
@@ -147277,17 +147383,18 @@ async function runClaude(params) {
|
|
|
147277
147383
|
user: (event) => {
|
|
147278
147384
|
const content = event.message?.content;
|
|
147279
147385
|
if (!content) return;
|
|
147386
|
+
const label = eventLabel(event);
|
|
147280
147387
|
for (const block of content) {
|
|
147281
147388
|
if (typeof block === "string") continue;
|
|
147282
147389
|
if (block.type === "tool_result") {
|
|
147283
|
-
|
|
147390
|
+
timerFor(label).markToolResult();
|
|
147284
147391
|
const outputContent = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map(
|
|
147285
147392
|
(entry) => typeof entry === "string" ? entry : typeof entry === "object" && entry !== null && "text" in entry ? String(entry.text) : JSON.stringify(entry)
|
|
147286
147393
|
).join("\n") : String(block.content);
|
|
147287
147394
|
if (block.is_error) {
|
|
147288
|
-
log.info(`\xBB tool error: ${outputContent}`);
|
|
147395
|
+
log.info(withLabel(label, `\xBB tool error: ${outputContent}`));
|
|
147289
147396
|
} else {
|
|
147290
|
-
log.debug(`\xBB tool output: ${outputContent}`);
|
|
147397
|
+
log.debug(withLabel(label, `\xBB tool output: ${outputContent}`));
|
|
147291
147398
|
}
|
|
147292
147399
|
}
|
|
147293
147400
|
}
|
|
@@ -147357,7 +147464,7 @@ async function runClaude(params) {
|
|
|
147357
147464
|
};
|
|
147358
147465
|
const recentStderr = [];
|
|
147359
147466
|
let lastProviderError = null;
|
|
147360
|
-
|
|
147467
|
+
const output = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
|
|
147361
147468
|
let stdoutBuffer = "";
|
|
147362
147469
|
try {
|
|
147363
147470
|
const result = await spawn({
|
|
@@ -147374,9 +147481,14 @@ async function runClaude(params) {
|
|
|
147374
147481
|
// there's no shim-orphan issue like opencode-ai/bin/opencode, but
|
|
147375
147482
|
// detached + killGroup is the right default for any agent runtime.
|
|
147376
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",
|
|
147377
147489
|
onStdout: async (chunk) => {
|
|
147378
147490
|
const text = chunk.toString();
|
|
147379
|
-
output
|
|
147491
|
+
output.append(text);
|
|
147380
147492
|
markActivity();
|
|
147381
147493
|
stdoutBuffer += text;
|
|
147382
147494
|
const lines = stdoutBuffer.split("\n");
|
|
@@ -147451,16 +147563,18 @@ ${stderrContext}`);
|
|
|
147451
147563
|
const usage = buildUsage();
|
|
147452
147564
|
if (result.exitCode !== 0) {
|
|
147453
147565
|
const errorContext = lastProviderError ? ` (${lastProviderError})` : "";
|
|
147454
|
-
const
|
|
147455
|
-
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}`;
|
|
147456
147570
|
log.error(
|
|
147457
147571
|
`${params.label} exited with code ${result.exitCode}${errorContext}: ${errorMessage}`
|
|
147458
147572
|
);
|
|
147459
|
-
log.debug(`stdout: ${
|
|
147460
|
-
log.debug(`stderr: ${
|
|
147573
|
+
log.debug(`stdout: ${stdoutSnapshot.substring(0, 500)}`);
|
|
147574
|
+
log.debug(`stderr: ${stderrSnapshot.substring(0, 500)}`);
|
|
147461
147575
|
return {
|
|
147462
147576
|
success: false,
|
|
147463
|
-
output: finalOutput ||
|
|
147577
|
+
output: finalOutput || stdoutSnapshot,
|
|
147464
147578
|
error: errorMessage,
|
|
147465
147579
|
usage,
|
|
147466
147580
|
sessionId
|
|
@@ -147469,7 +147583,7 @@ ${stderrContext}`);
|
|
|
147469
147583
|
if (eventCount === 0 && lastProviderError) {
|
|
147470
147584
|
return {
|
|
147471
147585
|
success: false,
|
|
147472
|
-
output: finalOutput || output,
|
|
147586
|
+
output: finalOutput || output.toString(),
|
|
147473
147587
|
error: `provider error: ${lastProviderError}`,
|
|
147474
147588
|
usage,
|
|
147475
147589
|
sessionId
|
|
@@ -147478,13 +147592,13 @@ ${stderrContext}`);
|
|
|
147478
147592
|
if (resultErrorSubtype) {
|
|
147479
147593
|
return {
|
|
147480
147594
|
success: false,
|
|
147481
|
-
output: finalOutput || output,
|
|
147595
|
+
output: finalOutput || output.toString(),
|
|
147482
147596
|
error: lastResultError || `result subtype: ${resultErrorSubtype}`,
|
|
147483
147597
|
usage,
|
|
147484
147598
|
sessionId
|
|
147485
147599
|
};
|
|
147486
147600
|
}
|
|
147487
|
-
return { success: true, output: finalOutput || output, usage, sessionId };
|
|
147601
|
+
return { success: true, output: finalOutput || output.toString(), usage, sessionId };
|
|
147488
147602
|
} catch (error49) {
|
|
147489
147603
|
params.todoTracker?.cancel();
|
|
147490
147604
|
const duration4 = performance6.now() - startTime;
|
|
@@ -147503,7 +147617,7 @@ ${stderrContext}`
|
|
|
147503
147617
|
);
|
|
147504
147618
|
return {
|
|
147505
147619
|
success: false,
|
|
147506
|
-
output: finalOutput || output,
|
|
147620
|
+
output: finalOutput || output.toString(),
|
|
147507
147621
|
error: `${errorMessage} [${diagnosis}]`,
|
|
147508
147622
|
usage: buildUsage(),
|
|
147509
147623
|
sessionId
|
|
@@ -147739,6 +147853,15 @@ function buildSecurityConfig(ctx, model) {
|
|
|
147739
147853
|
[pullfrogMcpName]: { type: "remote", url: ctx.mcpServerUrl }
|
|
147740
147854
|
},
|
|
147741
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 },
|
|
147742
147865
|
provider: {
|
|
147743
147866
|
google: {
|
|
147744
147867
|
models: Object.fromEntries(
|
|
@@ -147811,7 +147934,6 @@ function autoSelectModel(cliPath) {
|
|
|
147811
147934
|
async function runOpenCode(params) {
|
|
147812
147935
|
const startTime = performance7.now();
|
|
147813
147936
|
let eventCount = 0;
|
|
147814
|
-
const thinkingTimer = new ThinkingTimer();
|
|
147815
147937
|
let finalOutput = "";
|
|
147816
147938
|
let accumulatedTokens = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
147817
147939
|
let accumulatedCostUsd = 0;
|
|
@@ -147828,6 +147950,16 @@ async function runOpenCode(params) {
|
|
|
147828
147950
|
function withLabel(label, message) {
|
|
147829
147951
|
return label === ORCHESTRATOR_LABEL ? message : formatWithLabel(label, message);
|
|
147830
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
|
+
}
|
|
147831
147963
|
const taskDispatchByCallID = /* @__PURE__ */ new Map();
|
|
147832
147964
|
const pendingTaskDispatches = [];
|
|
147833
147965
|
const knownNonTaskCallIDs = /* @__PURE__ */ new Set();
|
|
@@ -147976,7 +148108,7 @@ async function runOpenCode(params) {
|
|
|
147976
148108
|
input: event.part?.state?.input
|
|
147977
148109
|
});
|
|
147978
148110
|
}
|
|
147979
|
-
|
|
148111
|
+
timerFor(label).markToolCall();
|
|
147980
148112
|
const inputFormatted = formatJsonValue(event.part?.state?.input || {});
|
|
147981
148113
|
const toolCallLine = inputFormatted !== "{}" ? `\xBB ${toolName}(${inputFormatted})` : `\xBB ${toolName}()`;
|
|
147982
148114
|
log.info(withLabel(label, toolCallLine));
|
|
@@ -148000,7 +148132,7 @@ async function runOpenCode(params) {
|
|
|
148000
148132
|
const status = event.part?.state?.status || event.status || "unknown";
|
|
148001
148133
|
const output2 = event.part?.state?.output || event.output;
|
|
148002
148134
|
const label = eventLabel(event);
|
|
148003
|
-
|
|
148135
|
+
timerFor(label).markToolResult();
|
|
148004
148136
|
if (taskDispatchByCallID.size > 0 || pendingTaskDispatches.length > 0) {
|
|
148005
148137
|
if (toolId && taskDispatchByCallID.has(toolId)) {
|
|
148006
148138
|
const dispatch = taskDispatchByCallID.get(toolId);
|
|
@@ -148125,7 +148257,7 @@ async function runOpenCode(params) {
|
|
|
148125
148257
|
const recentStderr = [];
|
|
148126
148258
|
let lastProviderError = null;
|
|
148127
148259
|
let agentErrorEvent = null;
|
|
148128
|
-
|
|
148260
|
+
const output = new TailBuffer(DEFAULT_MAX_RETAINED_BYTES);
|
|
148129
148261
|
let stdoutBuffer = "";
|
|
148130
148262
|
try {
|
|
148131
148263
|
const result = await spawn({
|
|
@@ -148143,6 +148275,11 @@ async function runOpenCode(params) {
|
|
|
148143
148275
|
// never fires — producing zombie runs. detached + killGroup nukes the
|
|
148144
148276
|
// whole tree.
|
|
148145
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",
|
|
148146
148283
|
// NB: we used to pass `isPausedExternally: isSubagentInFlight` to suspend
|
|
148147
148284
|
// the activity timer during subagent dispatches. unnecessary now that
|
|
148148
148285
|
// our injected plugin (action/agents/opencodePlugin.ts) re-emits
|
|
@@ -148152,7 +148289,7 @@ async function runOpenCode(params) {
|
|
|
148152
148289
|
// (~3.3 plugin events/sec during a typical subagent run).
|
|
148153
148290
|
onStdout: async (chunk) => {
|
|
148154
148291
|
const text = chunk.toString();
|
|
148155
|
-
output
|
|
148292
|
+
output.append(text);
|
|
148156
148293
|
markActivity();
|
|
148157
148294
|
stdoutBuffer += text;
|
|
148158
148295
|
const lines = stdoutBuffer.split("\n");
|
|
@@ -148241,18 +148378,25 @@ ${stderrContext}`);
|
|
|
148241
148378
|
const usage = buildUsage();
|
|
148242
148379
|
if (result.exitCode !== 0) {
|
|
148243
148380
|
const errorContext = lastProviderError ? ` (${lastProviderError})` : "";
|
|
148244
|
-
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}`;
|
|
148245
148384
|
log.error(
|
|
148246
148385
|
`${params.label} exited with code ${result.exitCode}${errorContext}: ${errorMessage}`
|
|
148247
148386
|
);
|
|
148248
|
-
log.debug(`stdout: ${
|
|
148249
|
-
log.debug(`stderr: ${
|
|
148250
|
-
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
|
+
};
|
|
148251
148395
|
}
|
|
148252
148396
|
if (eventCount === 0 && lastProviderError) {
|
|
148253
148397
|
return {
|
|
148254
148398
|
success: false,
|
|
148255
|
-
output: finalOutput || output,
|
|
148399
|
+
output: finalOutput || output.toString(),
|
|
148256
148400
|
error: `provider error: ${lastProviderError}`,
|
|
148257
148401
|
usage
|
|
148258
148402
|
};
|
|
@@ -148263,12 +148407,12 @@ ${stderrContext}`);
|
|
|
148263
148407
|
const errorMessage = errorEvent.error?.data?.message || errorEvent.error?.name || JSON.stringify(errorEvent);
|
|
148264
148408
|
return {
|
|
148265
148409
|
success: false,
|
|
148266
|
-
output: finalOutput || output,
|
|
148410
|
+
output: finalOutput || output.toString(),
|
|
148267
148411
|
error: `${errorName}: ${errorMessage}`,
|
|
148268
148412
|
usage
|
|
148269
148413
|
};
|
|
148270
148414
|
}
|
|
148271
|
-
return { success: true, output: finalOutput || output, usage };
|
|
148415
|
+
return { success: true, output: finalOutput || output.toString(), usage };
|
|
148272
148416
|
} catch (error49) {
|
|
148273
148417
|
params.todoTracker?.cancel();
|
|
148274
148418
|
const duration4 = performance7.now() - startTime;
|
|
@@ -148287,7 +148431,7 @@ ${stderrContext}`
|
|
|
148287
148431
|
);
|
|
148288
148432
|
return {
|
|
148289
148433
|
success: false,
|
|
148290
|
-
output: finalOutput || output,
|
|
148434
|
+
output: finalOutput || output.toString(),
|
|
148291
148435
|
error: `${errorMessage} [${diagnosis}]`,
|
|
148292
148436
|
usage: buildUsage()
|
|
148293
148437
|
};
|
|
@@ -152193,6 +152337,14 @@ function isOIDCAvailable() {
|
|
|
152193
152337
|
process.env.ACTIONS_ID_TOKEN_REQUEST_URL && process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN
|
|
152194
152338
|
);
|
|
152195
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
|
+
};
|
|
152196
152348
|
async function acquireTokenViaOIDC(opts) {
|
|
152197
152349
|
const oidcToken = await core2.getIDToken("pullfrog-api");
|
|
152198
152350
|
const repos = [...opts?.repos ?? []];
|
|
@@ -152217,7 +152369,16 @@ async function acquireTokenViaOIDC(opts) {
|
|
|
152217
152369
|
});
|
|
152218
152370
|
clearTimeout(timeoutId);
|
|
152219
152371
|
if (!tokenResponse.ok) {
|
|
152220
|
-
|
|
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
|
+
);
|
|
152221
152382
|
}
|
|
152222
152383
|
const tokenData = await tokenResponse.json();
|
|
152223
152384
|
return tokenData.token;
|
|
@@ -152338,7 +152499,10 @@ async function acquireNewToken(opts) {
|
|
|
152338
152499
|
if (isOIDCAvailable()) {
|
|
152339
152500
|
return await retry(() => acquireTokenViaOIDC(opts), {
|
|
152340
152501
|
label: "token exchange",
|
|
152341
|
-
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
|
+
}
|
|
152342
152506
|
});
|
|
152343
152507
|
} else {
|
|
152344
152508
|
return await acquireTokenViaGitHubApp(opts);
|
|
@@ -152885,6 +153049,21 @@ ${getStandaloneModeInstructions(ctx.payload.event.trigger, t, ctx.outputSchema)}
|
|
|
152885
153049
|
|
|
152886
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.
|
|
152887
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
|
+
|
|
152888
153067
|
### Command execution
|
|
152889
153068
|
|
|
152890
153069
|
Never use \`sleep\` to wait for commands to complete. Commands run synchronously \u2014 when the shell tool returns, the command has finished.
|
|
@@ -153011,10 +153190,22 @@ async function readLearningsFile(path3) {
|
|
|
153011
153190
|
}
|
|
153012
153191
|
|
|
153013
153192
|
// utils/normalizeEnv.ts
|
|
153014
|
-
|
|
153015
|
-
|
|
153016
|
-
|
|
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;
|
|
153201
|
+
}
|
|
153202
|
+
if (trimmed !== value2) {
|
|
153203
|
+
log.warning(
|
|
153204
|
+
`\xBB stripped whitespace from ${key} (whitespace in secret values breaks GitHub Actions log masking)`
|
|
153205
|
+
);
|
|
153017
153206
|
}
|
|
153207
|
+
core4.setSecret(trimmed);
|
|
153208
|
+
return trimmed;
|
|
153018
153209
|
}
|
|
153019
153210
|
function normalizeEnv() {
|
|
153020
153211
|
const upperKeys = /* @__PURE__ */ new Map();
|
|
@@ -153025,11 +153216,6 @@ function normalizeEnv() {
|
|
|
153025
153216
|
upperKeys.set(upper2, existing);
|
|
153026
153217
|
}
|
|
153027
153218
|
for (const [upperKey, keys] of upperKeys) {
|
|
153028
|
-
if (isSensitiveEnvName(upperKey)) {
|
|
153029
|
-
for (const key of keys) {
|
|
153030
|
-
maskValue(process.env[key]);
|
|
153031
|
-
}
|
|
153032
|
-
}
|
|
153033
153219
|
if (keys.length === 1) {
|
|
153034
153220
|
const key = keys[0];
|
|
153035
153221
|
if (key !== upperKey) {
|
|
@@ -153052,10 +153238,17 @@ function normalizeEnv() {
|
|
|
153052
153238
|
}
|
|
153053
153239
|
process.env[upperKey] = preferredValue;
|
|
153054
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
|
+
}
|
|
153055
153248
|
}
|
|
153056
153249
|
|
|
153057
153250
|
// utils/payload.ts
|
|
153058
|
-
var
|
|
153251
|
+
var core5 = __toESM(require_core(), 1);
|
|
153059
153252
|
import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
|
|
153060
153253
|
|
|
153061
153254
|
// utils/versioning.ts
|
|
@@ -153119,7 +153312,7 @@ function resolveCwd(cwd) {
|
|
|
153119
153312
|
return workspace ? resolve2(workspace, cwd) : cwd;
|
|
153120
153313
|
}
|
|
153121
153314
|
function resolvePromptInput() {
|
|
153122
|
-
const prompt =
|
|
153315
|
+
const prompt = core5.getInput("prompt", { required: true });
|
|
153123
153316
|
let parsed2;
|
|
153124
153317
|
try {
|
|
153125
153318
|
parsed2 = JSON.parse(prompt);
|
|
@@ -153135,11 +153328,11 @@ function resolvePromptInput() {
|
|
|
153135
153328
|
}
|
|
153136
153329
|
function resolveNonPromptInputs() {
|
|
153137
153330
|
return Inputs.omit("prompt").assert({
|
|
153138
|
-
model:
|
|
153139
|
-
timeout:
|
|
153140
|
-
cwd:
|
|
153141
|
-
push:
|
|
153142
|
-
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
|
|
153143
153336
|
});
|
|
153144
153337
|
}
|
|
153145
153338
|
var isPullfrog = (actor) => {
|
|
@@ -153354,8 +153547,7 @@ async function fetchRunContext(params) {
|
|
|
153354
153547
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
153355
153548
|
try {
|
|
153356
153549
|
const headers = {
|
|
153357
|
-
Authorization: `Bearer ${params.token}
|
|
153358
|
-
"Content-Type": "application/json"
|
|
153550
|
+
Authorization: `Bearer ${params.token}`
|
|
153359
153551
|
};
|
|
153360
153552
|
if (params.oidcToken) {
|
|
153361
153553
|
headers["X-GitHub-OIDC-Token"] = params.oidcToken;
|
|
@@ -153396,13 +153588,13 @@ async function fetchRunContext(params) {
|
|
|
153396
153588
|
}
|
|
153397
153589
|
|
|
153398
153590
|
// utils/runContextData.ts
|
|
153399
|
-
var
|
|
153591
|
+
var core6 = __toESM(require_core(), 1);
|
|
153400
153592
|
async function resolveRunContextData(params) {
|
|
153401
153593
|
log.info(`\xBB running Pullfrog v${package_default.version}...`);
|
|
153402
153594
|
const repoContext = parseRepoContext();
|
|
153403
153595
|
let oidcToken;
|
|
153404
153596
|
try {
|
|
153405
|
-
oidcToken = await
|
|
153597
|
+
oidcToken = await core6.getIDToken("pullfrog-api");
|
|
153406
153598
|
} catch {
|
|
153407
153599
|
}
|
|
153408
153600
|
const [repoResponse, runContext] = await Promise.all([
|
|
@@ -153705,7 +153897,7 @@ async function resolveRun(params) {
|
|
|
153705
153897
|
|
|
153706
153898
|
// main.ts
|
|
153707
153899
|
function resolveOutputSchema() {
|
|
153708
|
-
const raw2 =
|
|
153900
|
+
const raw2 = core7.getInput("output_schema");
|
|
153709
153901
|
if (!raw2) return void 0;
|
|
153710
153902
|
let parsed2;
|
|
153711
153903
|
try {
|
|
@@ -153771,7 +153963,7 @@ function formatBillingErrorSummary(error49, owner) {
|
|
|
153771
153963
|
return [
|
|
153772
153964
|
"**Add a card to start using Pullfrog Router.**",
|
|
153773
153965
|
"",
|
|
153774
|
-
"Router proxies OpenRouter at raw cost \u2014 no platform markup
|
|
153966
|
+
"Router proxies OpenRouter at raw cost \u2014 no platform markup. Add a card and we'll auto-reload your wallet so runs keep flowing.",
|
|
153775
153967
|
"",
|
|
153776
153968
|
`[Add a card \u2192](${billingConsoleUrl(owner, "model-access")})`
|
|
153777
153969
|
].join("\n");
|
|
@@ -153873,7 +154065,7 @@ async function buildProxyTokenHeaders(ctx) {
|
|
|
153873
154065
|
if (ctx.oidcCredentials) {
|
|
153874
154066
|
process.env.ACTIONS_ID_TOKEN_REQUEST_URL = ctx.oidcCredentials.requestUrl;
|
|
153875
154067
|
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = ctx.oidcCredentials.requestToken;
|
|
153876
|
-
const oidcToken = await
|
|
154068
|
+
const oidcToken = await core7.getIDToken("pullfrog-api");
|
|
153877
154069
|
delete process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
|
|
153878
154070
|
delete process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
|
153879
154071
|
return { Authorization: `Bearer ${oidcToken}` };
|
|
@@ -153895,7 +154087,7 @@ async function resolveProxyModel(ctx) {
|
|
|
153895
154087
|
const key = await mintProxyKey({ oidcCredentials: ctx.oidcCredentials, repo: ctx.repo });
|
|
153896
154088
|
if (!key) return;
|
|
153897
154089
|
process.env.OPENROUTER_API_KEY = key;
|
|
153898
|
-
|
|
154090
|
+
core7.setSecret(key);
|
|
153899
154091
|
ctx.payload.proxyModel = ctx.proxyModel;
|
|
153900
154092
|
const label = ctx.oss ? "oss" : "router";
|
|
153901
154093
|
log.info(`\xBB proxy: ${label} \u2192 ${ctx.proxyModel}`);
|
|
@@ -153947,12 +154139,12 @@ async function persistLearnings(ctx) {
|
|
|
153947
154139
|
});
|
|
153948
154140
|
if (!response.ok) {
|
|
153949
154141
|
const error49 = await response.text().catch(() => "(no body)");
|
|
153950
|
-
log.
|
|
154142
|
+
log.warning(`learnings persist failed (${response.status}): ${error49}`);
|
|
153951
154143
|
return;
|
|
153952
154144
|
}
|
|
153953
154145
|
log.info("\xBB learnings updated");
|
|
153954
154146
|
} catch (err) {
|
|
153955
|
-
log.
|
|
154147
|
+
log.warning(`learnings persist failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
153956
154148
|
}
|
|
153957
154149
|
}
|
|
153958
154150
|
async function persistSummary(ctx) {
|
|
@@ -154007,8 +154199,8 @@ async function main() {
|
|
|
154007
154199
|
if (runContext.dbSecrets) {
|
|
154008
154200
|
for (const [key, value2] of Object.entries(runContext.dbSecrets)) {
|
|
154009
154201
|
if (!process.env[key]) {
|
|
154010
|
-
|
|
154011
|
-
|
|
154202
|
+
const sanitized = sanitizeSecret(key, value2);
|
|
154203
|
+
if (sanitized !== null) process.env[key] = sanitized;
|
|
154012
154204
|
}
|
|
154013
154205
|
}
|
|
154014
154206
|
const count = Object.keys(runContext.dbSecrets).length;
|
|
@@ -154346,7 +154538,7 @@ ${instructions.user}` : null,
|
|
|
154346
154538
|
}
|
|
154347
154539
|
if (toolState.output) {
|
|
154348
154540
|
log.info(`::pullfrog-output::${Buffer.from(toolState.output).toString("base64")}`);
|
|
154349
|
-
|
|
154541
|
+
core7.setOutput("result", toolState.output);
|
|
154350
154542
|
}
|
|
154351
154543
|
return await handleAgentResult({
|
|
154352
154544
|
result,
|