pullfrog 0.1.96 → 0.1.98
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/opencodePlugin.d.ts +4 -2
- package/dist/cli.mjs +656 -452
- package/dist/index.js +740 -545
- package/dist/internal.js +82 -29
- package/dist/mcp/checkout.d.ts +2 -0
- package/dist/utils/apiKeys.d.ts +26 -0
- package/dist/utils/changeImpact.d.ts +1 -0
- package/dist/utils/codexHome.d.ts +4 -6
- package/dist/utils/gitAuth.d.ts +1 -0
- package/dist/utils/oauthWriteback.d.ts +5 -0
- package/dist/utils/openCodeModels.d.ts +19 -2
- package/dist/utils/providerErrors.d.ts +4 -1
- package/dist/utils/runErrorRenderer.d.ts +25 -9
- package/dist/utils/subprocess.d.ts +1 -0
- package/package.json +2 -2
package/dist/cli.mjs
CHANGED
|
@@ -98361,14 +98361,14 @@ var require_turndown_cjs = __commonJS({
|
|
|
98361
98361
|
} else if (node2.nodeType === 1) {
|
|
98362
98362
|
replacement = replacementForNode.call(self2, node2);
|
|
98363
98363
|
}
|
|
98364
|
-
return
|
|
98364
|
+
return join31(output, replacement);
|
|
98365
98365
|
}, "");
|
|
98366
98366
|
}
|
|
98367
98367
|
function postProcess(output) {
|
|
98368
98368
|
var self2 = this;
|
|
98369
98369
|
this.rules.forEach(function(rule) {
|
|
98370
98370
|
if (typeof rule.append === "function") {
|
|
98371
|
-
output =
|
|
98371
|
+
output = join31(output, rule.append(self2.options));
|
|
98372
98372
|
}
|
|
98373
98373
|
});
|
|
98374
98374
|
return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
|
|
@@ -98380,7 +98380,7 @@ var require_turndown_cjs = __commonJS({
|
|
|
98380
98380
|
if (whitespace.leading || whitespace.trailing) content = content.trim();
|
|
98381
98381
|
return whitespace.leading + rule.replacement(content, node2, this.options) + whitespace.trailing;
|
|
98382
98382
|
}
|
|
98383
|
-
function
|
|
98383
|
+
function join31(output, replacement) {
|
|
98384
98384
|
var s1 = trimTrailingNewlines(output);
|
|
98385
98385
|
var s2 = trimLeadingNewlines(replacement);
|
|
98386
98386
|
var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
|
|
@@ -106963,7 +106963,7 @@ var import_picocolors = __toESM(require_picocolors(), 1);
|
|
|
106963
106963
|
// package.json
|
|
106964
106964
|
var package_default = {
|
|
106965
106965
|
name: "pullfrog",
|
|
106966
|
-
version: "0.1.
|
|
106966
|
+
version: "0.1.98",
|
|
106967
106967
|
cliContractVersion: "1",
|
|
106968
106968
|
type: "module",
|
|
106969
106969
|
bin: {
|
|
@@ -106991,7 +106991,7 @@ var package_default = {
|
|
|
106991
106991
|
},
|
|
106992
106992
|
devDependencies: {
|
|
106993
106993
|
"@actions/core": "^1.11.1",
|
|
106994
|
-
"@anthropic-ai/claude-code": "2.1.
|
|
106994
|
+
"@anthropic-ai/claude-code": "2.1.293",
|
|
106995
106995
|
"@ark/fs": "0.56.0",
|
|
106996
106996
|
"@ark/util": "0.56.0",
|
|
106997
106997
|
"@clack/prompts": "^1.2.0",
|
|
@@ -108607,9 +108607,9 @@ var import_arg6 = __toESM(require_arg(), 1);
|
|
|
108607
108607
|
import { dirname as dirname6 } from "node:path";
|
|
108608
108608
|
|
|
108609
108609
|
// main.ts
|
|
108610
|
-
import { existsSync as
|
|
108610
|
+
import { existsSync as existsSync13, readdirSync as readdirSync2 } from "node:fs";
|
|
108611
108611
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
108612
|
-
import { join as
|
|
108612
|
+
import { join as join29 } from "node:path";
|
|
108613
108613
|
|
|
108614
108614
|
// node_modules/.pnpm/package-manager-detector@1.8.0/node_modules/package-manager-detector/dist/commands.mjs
|
|
108615
108615
|
function splitRunArgs(args2, valueFlags = []) {
|
|
@@ -109042,8 +109042,9 @@ var providers = {
|
|
|
109042
109042
|
},
|
|
109043
109043
|
"claude-haiku": {
|
|
109044
109044
|
displayName: "Claude Haiku",
|
|
109045
|
-
resolve: "anthropic/claude-haiku-
|
|
109046
|
-
|
|
109045
|
+
resolve: "anthropic/claude-haiku-5-5",
|
|
109046
|
+
effort: ["low", "medium", "high", "xhigh", "max"],
|
|
109047
|
+
openRouterResolve: "openrouter/anthropic/claude-haiku-5.5"
|
|
109047
109048
|
}
|
|
109048
109049
|
}
|
|
109049
109050
|
}),
|
|
@@ -109461,8 +109462,9 @@ var providers = {
|
|
|
109461
109462
|
},
|
|
109462
109463
|
"claude-haiku": {
|
|
109463
109464
|
displayName: "Claude Haiku",
|
|
109464
|
-
resolve: "opencode/claude-haiku-
|
|
109465
|
-
|
|
109465
|
+
resolve: "opencode/claude-haiku-5-5",
|
|
109466
|
+
effort: ["low", "medium", "high", "xhigh", "max"],
|
|
109467
|
+
openRouterResolve: "openrouter/anthropic/claude-haiku-5.5"
|
|
109466
109468
|
},
|
|
109467
109469
|
"gpt-sol": {
|
|
109468
109470
|
displayName: "GPT Sol",
|
|
@@ -109618,6 +109620,14 @@ var providers = {
|
|
|
109618
109620
|
openRouterEffort: [],
|
|
109619
109621
|
openRouterResolve: "openrouter/qwen/qwen3.8-flash"
|
|
109620
109622
|
},
|
|
109623
|
+
// Zen lists Mistral Large 4 at half its list price for launch, level with
|
|
109624
|
+
// the routers.
|
|
109625
|
+
"mistral-large": {
|
|
109626
|
+
displayName: "Mistral Large",
|
|
109627
|
+
resolve: "opencode/mistral-large-4",
|
|
109628
|
+
effort: ["none", "high"],
|
|
109629
|
+
openRouterResolve: "openrouter/mistralai/mistral-large-4-0"
|
|
109630
|
+
},
|
|
109621
109631
|
"gpt-5-nano": {
|
|
109622
109632
|
displayName: "GPT Nano",
|
|
109623
109633
|
resolve: "opencode/gpt-5.4-nano",
|
|
@@ -109666,12 +109676,12 @@ var providers = {
|
|
|
109666
109676
|
fallback: "opencode/big-pickle"
|
|
109667
109677
|
},
|
|
109668
109678
|
// Zen's FREE stealth preview (listed 2026-09-23), the third after
|
|
109669
|
-
// big-pickle and union-alpha.
|
|
109670
|
-
//
|
|
109671
|
-
//
|
|
109679
|
+
// big-pickle and union-alpha. Zen's docs: zero retention, no training.
|
|
109680
|
+
// unlike the first two it publishes an effort ladder, so the gate
|
|
109681
|
+
// mirrors one here.
|
|
109672
109682
|
"space-bunny": {
|
|
109673
109683
|
displayName: "Space Bunny",
|
|
109674
|
-
description: "Stealth preview
|
|
109684
|
+
description: "Stealth preview, free for a limited time",
|
|
109675
109685
|
resolve: "opencode/space-bunny-free",
|
|
109676
109686
|
effort: ["low", "medium", "high", "xhigh", "max"],
|
|
109677
109687
|
// free to run, still gated on the provider's own OPENCODE_API_KEY —
|
|
@@ -109689,17 +109699,46 @@ var providers = {
|
|
|
109689
109699
|
// see the big-pickle note above (#1077).
|
|
109690
109700
|
isFree: true
|
|
109691
109701
|
},
|
|
109692
|
-
// Zen's FREE stealth preview (listed 2026-10-01), the fifth promo. Zen
|
|
109693
|
-
//
|
|
109702
|
+
// Zen's FREE stealth preview (listed 2026-10-01), the fifth promo. Zen's
|
|
109703
|
+
// docs: data may be used to improve the model during the free period.
|
|
109694
109704
|
"fledge-alpha": {
|
|
109695
109705
|
displayName: "Fledge Alpha",
|
|
109696
|
-
description: "Stealth preview
|
|
109706
|
+
description: "Stealth preview; data may be used to improve it",
|
|
109697
109707
|
resolve: "opencode/fledge-alpha-free",
|
|
109698
109708
|
effort: ["low", "high", "max"],
|
|
109699
109709
|
// free to run, still gated on the provider's own OPENCODE_API_KEY —
|
|
109700
109710
|
// see the big-pickle note above (#1077).
|
|
109711
|
+
isFree: true,
|
|
109712
|
+
// Zen dropped the id from `/v1/models` and its docs on 2026-10-08 and
|
|
109713
|
+
// answers a 500 for it; stored picks land on the other free stealth
|
|
109714
|
+
// model, as `union-alpha` does.
|
|
109715
|
+
fallback: "opencode/big-pickle"
|
|
109716
|
+
},
|
|
109717
|
+
// Zen's FREE preview (listed 2026-10-06), the sixth promo, on Fledge
|
|
109718
|
+
// Alpha's terms.
|
|
109719
|
+
exo: {
|
|
109720
|
+
displayName: "Exo",
|
|
109721
|
+
description: "Preview; data may be used to improve it",
|
|
109722
|
+
resolve: "opencode/exo-free",
|
|
109723
|
+
// Zen publishes a single rung.
|
|
109724
|
+
effort: ["high"],
|
|
109725
|
+
// free to run, still gated on the provider's own OPENCODE_API_KEY —
|
|
109726
|
+
// see the big-pickle note above (#1077).
|
|
109701
109727
|
isFree: true
|
|
109702
109728
|
},
|
|
109729
|
+
// Zen's FREE StepFun preview (listed 2026-10-08), the seventh promo. Zen's
|
|
109730
|
+
// docs: zero retention, no training. Go lists the same free id; one key
|
|
109731
|
+
// reaches both. hidden until a published `pullfrog` resolves it.
|
|
109732
|
+
"step-5-preview": {
|
|
109733
|
+
displayName: "Step 5 Preview",
|
|
109734
|
+
description: "StepFun preview, free for a limited time",
|
|
109735
|
+
resolve: "opencode/step-5-preview-free",
|
|
109736
|
+
effort: ["low", "medium", "high"],
|
|
109737
|
+
// free to run, still gated on the provider's own OPENCODE_API_KEY —
|
|
109738
|
+
// see the big-pickle note above (#1077).
|
|
109739
|
+
isFree: true,
|
|
109740
|
+
hidden: true
|
|
109741
|
+
},
|
|
109703
109742
|
// Zen's live free MiMo, and the second free row in a menu that big-pickle
|
|
109704
109743
|
// was alone in since `mimo-v2-pro-free` lost its model.
|
|
109705
109744
|
mimo: {
|
|
@@ -109984,6 +110023,9 @@ var providers = {
|
|
|
109984
110023
|
"claude-haiku": {
|
|
109985
110024
|
displayName: "Claude Haiku",
|
|
109986
110025
|
resolve: "openrouter/~anthropic/claude-haiku-latest",
|
|
110026
|
+
// the rolling alias moved to Haiku 5.5 on 2026-10-07, which publishes
|
|
110027
|
+
// the ladder Haiku 4.5 did not.
|
|
110028
|
+
effort: ["low", "medium", "high", "xhigh", "max"],
|
|
109987
110029
|
openRouterResolve: "openrouter/~anthropic/claude-haiku-latest"
|
|
109988
110030
|
},
|
|
109989
110031
|
// pinned to the explicit Sol/Terra/Luna tiers (not the ~openai/gpt-latest
|
|
@@ -110155,6 +110197,14 @@ var providers = {
|
|
|
110155
110197
|
resolve: "openrouter/meta/muse-spark-1.3-contributor",
|
|
110156
110198
|
effort: ["minimal", "low", "medium", "high", "xhigh", "max"],
|
|
110157
110199
|
openRouterResolve: "openrouter/meta/muse-spark-1.3-contributor"
|
|
110200
|
+
},
|
|
110201
|
+
// Mistral's flagship (public preview, 2026-10-06), on the routers and
|
|
110202
|
+
// Zen only: the catalog carries no direct Mistral provider.
|
|
110203
|
+
"mistral-large": {
|
|
110204
|
+
displayName: "Mistral Large",
|
|
110205
|
+
resolve: "openrouter/mistralai/mistral-large-4-0",
|
|
110206
|
+
effort: ["none", "high"],
|
|
110207
|
+
openRouterResolve: "openrouter/mistralai/mistral-large-4-0"
|
|
110158
110208
|
}
|
|
110159
110209
|
}
|
|
110160
110210
|
}),
|
|
@@ -110185,7 +110235,8 @@ var providers = {
|
|
|
110185
110235
|
},
|
|
110186
110236
|
"claude-haiku": {
|
|
110187
110237
|
displayName: "Claude Haiku",
|
|
110188
|
-
resolve: "vercel/anthropic/claude-haiku-
|
|
110238
|
+
resolve: "vercel/anthropic/claude-haiku-5.5",
|
|
110239
|
+
effort: ["none", "low", "medium", "high", "xhigh", "max"]
|
|
110189
110240
|
},
|
|
110190
110241
|
"gpt-sol": {
|
|
110191
110242
|
displayName: "GPT Sol",
|
|
@@ -110246,6 +110297,11 @@ var providers = {
|
|
|
110246
110297
|
displayName: "Muse Spark",
|
|
110247
110298
|
resolve: "vercel/meta/muse-spark-1.3",
|
|
110248
110299
|
effort: ["minimal", "low", "medium", "high", "xhigh"]
|
|
110300
|
+
},
|
|
110301
|
+
"mistral-large": {
|
|
110302
|
+
displayName: "Mistral Large",
|
|
110303
|
+
resolve: "vercel/mistral/mistral-large-4",
|
|
110304
|
+
effort: ["none", "low", "medium", "high"]
|
|
110249
110305
|
}
|
|
110250
110306
|
}
|
|
110251
110307
|
})
|
|
@@ -111126,7 +111182,13 @@ var PROVIDER_ERROR_PATTERNS = [
|
|
|
111126
111182
|
// …/settings/credits`) matched neither this nor `requires more credits`, so
|
|
111127
111183
|
// the one run in that episode that was a genuinely empty wallet rendered with
|
|
111128
111184
|
// no CTA at all while runs 1-6 rendered correctly.
|
|
111129
|
-
|
|
111185
|
+
// `account funds` is Zen's (#1474).
|
|
111186
|
+
{
|
|
111187
|
+
regex: /Insufficient (?:balance|credits|account funds)/i,
|
|
111188
|
+
label: PROVIDER_BILLING_EXHAUSTED_LABEL
|
|
111189
|
+
},
|
|
111190
|
+
// OpenAI's `You have no credits remaining` (#1474).
|
|
111191
|
+
{ regex: /no credits remaining/i, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
|
|
111130
111192
|
{ regex: /credit balance is too low/i, label: PROVIDER_BILLING_EXHAUSTED_LABEL },
|
|
111131
111193
|
// "spending cap" (Gemini) and "spending limit" (xAI, #1076) are the same
|
|
111132
111194
|
// condition in two house styles — a configured ceiling was reached.
|
|
@@ -111247,6 +111309,9 @@ function isRouterKeylimitExhaustedError(text) {
|
|
|
111247
111309
|
function isProviderBillingExhausted(text) {
|
|
111248
111310
|
return findProviderErrorMatch(text)?.label === PROVIDER_BILLING_EXHAUSTED_LABEL;
|
|
111249
111311
|
}
|
|
111312
|
+
function isProviderUsageLimit(text) {
|
|
111313
|
+
return findProviderErrorMatch(text)?.label === PROVIDER_USAGE_LIMIT_LABEL;
|
|
111314
|
+
}
|
|
111250
111315
|
function isProviderNoRoutableEndpoints(text) {
|
|
111251
111316
|
return findProviderErrorMatch(text)?.label === PROVIDER_NO_ENDPOINTS_LABEL;
|
|
111252
111317
|
}
|
|
@@ -111505,6 +111570,7 @@ ${this.buffer}`;
|
|
|
111505
111570
|
};
|
|
111506
111571
|
async function spawn3(options) {
|
|
111507
111572
|
const activityTimeoutMs = options.activityTimeout ?? DEFAULT_ACTIVITY_TIMEOUT_MS;
|
|
111573
|
+
options.signal?.throwIfAborted();
|
|
111508
111574
|
installSignalHandler();
|
|
111509
111575
|
const startTime = performance3.now();
|
|
111510
111576
|
const retain = options.retain ?? "tail";
|
|
@@ -111540,17 +111606,18 @@ async function spawn3(options) {
|
|
|
111540
111606
|
let isActivityTimedOut = false;
|
|
111541
111607
|
let lastActivityTime = performance3.now();
|
|
111542
111608
|
let killedAtIdleMs;
|
|
111609
|
+
const terminate = () => {
|
|
111610
|
+
if (sigkillEscalatorId) return;
|
|
111611
|
+
killSelf("SIGTERM");
|
|
111612
|
+
sigkillEscalatorId = setTimeout(() => killSelf("SIGKILL"), 5e3);
|
|
111613
|
+
};
|
|
111543
111614
|
if (options.timeout) {
|
|
111544
111615
|
timeoutId = setTimeout(() => {
|
|
111545
111616
|
isTimedOut = true;
|
|
111546
|
-
|
|
111547
|
-
sigkillEscalatorId = setTimeout(() => {
|
|
111548
|
-
if (!child.killed) {
|
|
111549
|
-
killSelf("SIGKILL");
|
|
111550
|
-
}
|
|
111551
|
-
}, 5e3);
|
|
111617
|
+
terminate();
|
|
111552
111618
|
}, options.timeout);
|
|
111553
111619
|
}
|
|
111620
|
+
options.signal?.addEventListener("abort", terminate, { once: true });
|
|
111554
111621
|
if (activityTimeoutMs > 0) {
|
|
111555
111622
|
log2.debug(
|
|
111556
111623
|
`spawn activity timer: pid=${child.pid} cmd=${options.cmd} timeout=${activityTimeoutMs}ms`
|
|
@@ -111615,6 +111682,11 @@ async function spawn3(options) {
|
|
|
111615
111682
|
if (timeoutId) clearTimeout(timeoutId);
|
|
111616
111683
|
if (sigkillEscalatorId) clearTimeout(sigkillEscalatorId);
|
|
111617
111684
|
if (activityCheckIntervalId) clearInterval(activityCheckIntervalId);
|
|
111685
|
+
options.signal?.removeEventListener("abort", terminate);
|
|
111686
|
+
if (options.signal?.aborted) {
|
|
111687
|
+
reject(options.signal.reason);
|
|
111688
|
+
return;
|
|
111689
|
+
}
|
|
111618
111690
|
if (isTimedOut) {
|
|
111619
111691
|
reject(
|
|
111620
111692
|
new SpawnTimeoutError(`process timed out after ${options.timeout}ms`, SPAWN_TIMEOUT_CODE)
|
|
@@ -111653,6 +111725,7 @@ ${killMsg}` : killMsg;
|
|
|
111653
111725
|
if (timeoutId) clearTimeout(timeoutId);
|
|
111654
111726
|
if (sigkillEscalatorId) clearTimeout(sigkillEscalatorId);
|
|
111655
111727
|
if (activityCheckIntervalId) clearInterval(activityCheckIntervalId);
|
|
111728
|
+
options.signal?.removeEventListener("abort", terminate);
|
|
111656
111729
|
const errMsg = `[spawn] ${options.cmd}: ${error49.message}`;
|
|
111657
111730
|
console.error(errMsg);
|
|
111658
111731
|
const existingStderr = stderrBuffer?.toString() ?? "";
|
|
@@ -111894,17 +111967,16 @@ Report findings clearly with file:line references and quoted evidence where poss
|
|
|
111894
111967
|
// modes.ts
|
|
111895
111968
|
var PR_SUMMARY_FORMAT = `### Default format
|
|
111896
111969
|
|
|
111897
|
-
The body has at most
|
|
111970
|
+
The body has at most two parts, in this order:
|
|
111898
111971
|
|
|
111899
111972
|
1. **Reviewed changes preamble** \u2014 a bolded \`**Reviewed changes**\` lead-in with one sentence on what was reviewed in this run (for \`IncrementalReview\`: what changed since the prior pullfrog review), then a bullet list of the substantive changes \u2014 short bolded title, one sentence each. A reviewer should understand the full reviewed scope from this list alone. Close the preamble with the metadata comment below.
|
|
111900
111973
|
2. **Cross-cutting issue sections** (zero or more) \u2014 one \`### {emoji} {what's wrong, not what to do}\` heading per concern.
|
|
111901
|
-
3. **\`### \u2139\uFE0F Nitpicks\`** at the very bottom, if any \u2014 a flat bullet list, no technical-details block. Only for what has no runtime effect: a stale or wrong comment or doc, a misleading name, a typo, dead code, a missing test. Never formatting or whitespace.
|
|
111902
111974
|
|
|
111903
111975
|
**Inline vs. body.** Concerns that anchor to a specific line go inline (the \`comments\` parameter), even when their implications are broad. Body \`### \` sections are reserved for concerns that have **no line to anchor to** \u2014 *absence* (something the diff should have done but didn't), *sequencing* (rollout / deletion / migration order), *design decisions only the human can make*, or *scope questions the diff raises but doesn't address*. With no non-anchorable concerns, the body is just the preamble + metadata.
|
|
111904
111976
|
|
|
111905
111977
|
**Severity emoji** on every \`### \` heading, and nowhere else: \u{1F6A8} critical (blocks merge \u2014 data loss, security, broken core flow) \xB7 \u26A0\uFE0F important (must address before merging) \xB7 \u2139\uFE0F informational (mergeable as-is).
|
|
111906
111978
|
|
|
111907
|
-
**Behavior decides placement, not fix size.** A finding that changes what the code does \u2014 a wrong value or message, a crash, a race, a leak, a missed cleanup, a slow path \u2014 gets its own inline comment (or a \`### \` section if no line anchors it) at its real severity, however small the fix.
|
|
111979
|
+
**Behavior decides placement, not fix size.** A finding that changes what the code does \u2014 a wrong value or message, a crash, a race, a leak, a missed cleanup, a slow path \u2014 gets its own inline comment (or a \`### \` section if no line anchors it) at its real severity, however small the fix.
|
|
111908
111980
|
|
|
111909
111981
|
**Blank line between every block-level element.** GitHub's markdown parser requires one before and after HTML tags (\`<details>\`, \`<summary>\`, \`<sub>\`, \`<br/>\`) \u2014 without it GitHub treats what follows as a continuation of the HTML block and renders your markdown as literal text. This is a parser quirk, not a style preference, and it permanently breaks the posted review.
|
|
111910
111982
|
|
|
@@ -112053,8 +112125,6 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
112053
112125
|
|
|
112054
112126
|
3. **triage**: orient yourself on the PR \u2014 identify *what kind of thing this is* (domain it touches, seams it crosses, external contracts it depends on, user-facing surfaces it changes). pull as much context as you need to render a confident, well-grounded review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths, fetch related GitHub state. **you are the synthesizer** \u2014 never delegate understanding to subagents.
|
|
112055
112127
|
|
|
112056
|
-
when the diff adds or changes a test, check that it can actually fail: a test that would still pass with the bug present is theatre, not coverage. the usual tell is a loose assertion standing where an exact one belongs \u2014 \`>=\` or a truthiness check over an expected value, or a snapshot that absorbs whatever it is handed. read the assertion against the behavior it claims to pin, not against whether it currently passes.
|
|
112057
|
-
|
|
112058
112128
|
skip the deeper pass and submit a \`No new issues found.\` review per step 7 only when the diff has **no behavioral surface at all** \u2014 doc typos, whitespace/formatting, lockfile or generated-code regeneration, a mechanical rename whose only effect is import-path updates. line count is not the signal: a one-line change to auth, money, SQL, a comparison operator, a redirect, or a config default is not trivial.
|
|
112059
112129
|
|
|
112060
112130
|
4. **specialist decision**: after reading the complete diff, name the questions you still cannot answer confidently yourself, and dispatch one \`${REVIEWER_AGENT_NAME}\` specialist per question. a question qualifies only when a specialist could return evidence that **changes your disposition** on the PR \u2014 generic requests for another look, extra confidence, or polish do not. most reviews need zero or one; some need several.
|
|
@@ -112078,8 +112148,6 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
112078
112148
|
|
|
112079
112149
|
6. **aggregate & draft**: when specialist results land, merge findings; de-dup overlaps (two specialists catching the same issue = higher-confidence signal); trace each finding yourself before accepting it. drop praise, style preferences, speculative/unverified claims, findings about pre-existing code unrelated to the PR (heuristic: if the finding's root cause lives in lines this PR added or modified, it's in scope; otherwise drop unless the PR plausibly introduced or amplified the regression), and anything not actionable. also drop **bloat-shaped findings** \u2014 proposed fixes that would add defensive checks for cases that can't happen, abstractions used once, comments restating obvious code, tests asserting tautologies, or "just-in-case" guards. subagents are fallible and bias toward recommending changes; the bar for an actionable inline comment is sound + correct + elegant. recommending a change that improves only one of the three (or worse, degrades elegance to nominally improve correctness) makes the codebase worse, not better.
|
|
112080
112150
|
|
|
112081
|
-
**Hunt for non-anchored concerns before drafting.** After collecting your anchored findings, deliberately scan for concerns that have no specific line to point at \u2014 typically: deletion / cleanup plans for code the diff replaces or shadows; rollout sequencing (what happens to in-flight state during deploy / revert?); scope questions that only the human can answer (e.g. is the legacy path going away or is this a long-term dual track?); architectural risks the diff opens up that aren't a single-line bug.
|
|
112082
|
-
|
|
112083
112151
|
for surviving findings, draft inline comments with NEW line numbers from the diff \u2014 attach a \`<details>Technical details</details>\` block to any inline comment whose fix is non-trivial or has cross-file implications (see Inline technical details in the format below). every comment must be actionable, 2-3 sentences max in the visible part, one per root cause \u2014 name its other sites in that comment rather than repeating it. use GitHub permalink format for code references. for impact-analysis findings (stale references after rename/remove), report them in the review body ordered by severity (runtime breakage > incorrect docs > stale comments) rather than as inline comments unless they're anchored to a specific line.
|
|
112084
112152
|
|
|
112085
112153
|
7. **submit**: ALWAYS submit exactly one review via \`${t2("create_pull_request_review")}\`. Do NOT call \`report_progress\` \u2014 the review is the final record and the progress comment will be cleaned up automatically.
|
|
@@ -112101,7 +112169,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
112101
112169
|
\`approved: false\`. Body opens with \`> [!CAUTION]\\n> This PR introduces ...\`, followed by the PR summary. Include all inline comments via \`comments\`.
|
|
112102
112170
|
- **must-address non-critical findings** (real consequences if shipped \u2014 incorrect behavior in non-critical paths, missing validation on user input, regressions the author should fix before merge):
|
|
112103
112171
|
\`approved: false\`. Body opens with \`> [!IMPORTANT]\\n> ...\`, followed by the PR summary. Reserve this tier for findings with concrete fallout \u2014 do NOT use \`[!IMPORTANT]\` for nits, style preferences, or "consider also" suggestions. Include all inline comments via \`comments\`.
|
|
112104
|
-
- **minor suggestions only** (low-stakes findings
|
|
112172
|
+
- **minor suggestions only** (low-stakes findings):
|
|
112105
112173
|
\`approved: false\`. Body opens with \`> \u2139\uFE0F No critical issues \u2014 minor suggestions inline.\\n\\n\` followed by the PR summary. Include all inline comments via \`comments\`. Vary the wording after the emoji to fit the review (e.g. "Minor suggestions only.", "Two rough edges worth a look."), but always keep the \u2139\uFE0F prefix and keep it short.
|
|
112106
112174
|
- **informational observations** (mergeable as-is, nothing actionable \u2014 e.g. prior feedback addressed cleanly, calling out something noteworthy without recommending a change):
|
|
112107
112175
|
\`approved: true\`. Body opens with \`> \u2705 No new issues found.\\n\\n\` followed by the PR summary. Do NOT include inline \`comments\` \u2014 the \u2705 signals "no action needed", which contradicts an actionable anchor; if a point is concrete enough to anchor to a line, downgrade the whole review to "minor suggestions only" (\`approved: false\`) instead.
|
|
@@ -112144,8 +112212,6 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
112144
112212
|
|
|
112145
112213
|
5. **triage**: orient on the *incremental* changes \u2014 domain, seams, external contracts, user-facing surfaces. pull as much context as you need to render a confident review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths. **you are the synthesizer.**
|
|
112146
112214
|
|
|
112147
|
-
a test added or changed in this delta must be able to fail \u2014 one that would still pass with the bug present is theatre, not coverage. the tell is a loose assertion where an exact one belongs (\`>=\` or a truthiness check over an expected value, a snapshot that absorbs whatever it is handed).
|
|
112148
|
-
|
|
112149
112215
|
skip the deeper pass and jump to step 10's non-substantive path (do NOT submit a review) only when the incremental changes have **no behavioral surface at all** \u2014 formatting, comment tweaks, import reordering, lockfile regen, a mechanical rename of import paths. line count is not the signal: a one-line change to auth, money, SQL, a comparison operator, a redirect, or a config default is not trivial.
|
|
112150
112216
|
|
|
112151
112217
|
6. **specialist decision**: after covering the incremental and full diffs, name the questions about the new changes that you still cannot answer confidently yourself, and dispatch one \`${REVIEWER_AGENT_NAME}\` specialist per question. a question qualifies only when a specialist could return evidence that **changes your disposition** on the PR \u2014 generic requests for another look, extra confidence, or polish do not. most incremental reviews need zero or one, especially thread-reply re-reviews; some need several.
|
|
@@ -112169,11 +112235,9 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
112169
112235
|
|
|
112170
112236
|
8. **aggregate, draft, self-critique**: merge findings (yours + output from every specialist you dispatched); de-dup overlaps; trace each finding yourself. drop praise, style preferences, speculative/unverified claims, findings about pre-existing code unrelated to the new commits, anything not actionable, and anything that re-states prior review feedback (heuristic: if the finding's root cause lives in lines the *new commits* added or modified, it's in scope; otherwise drop). also drop **bloat-shaped findings** \u2014 proposed fixes that would add defensive checks for cases that can't happen, abstractions used once, comments restating obvious code, tests asserting tautologies, or "just-in-case" guards. subagents are fallible and bias toward recommending changes; the bar for an actionable inline comment is sound + correct + elegant. recommending a change that improves only one of the three (or degrades elegance to nominally improve correctness) makes the codebase worse, not better. To compute "lines the new commits added or modified": if \`incrementalDiffPath\` from step 2 is present, use it directly. Otherwise, take the prior Pullfrog review's \`commit_id\` (returned alongside each entry from \`${t2("list_pull_request_reviews")}\` in step 4) and run \`git diff <prior-review-sha>..HEAD\` to isolate the lines added since that review.
|
|
112171
112237
|
|
|
112172
|
-
**Hunt for non-anchored concerns before drafting.** After collecting your anchored findings, deliberately scan for concerns that have no specific line to point at \u2014 typically: deletion / cleanup plans for code the new commits replace or shadow; rollout sequencing (what happens to in-flight state during deploy / revert?); scope questions that only the human can answer (e.g. is the legacy path going away or is this a long-term dual track?); architectural risks the new commits open up that aren't a single-line bug.
|
|
112173
|
-
|
|
112174
112238
|
draft inline comments with NEW line numbers from the full PR diff \u2014 attach a \`<details>Technical details</details>\` block to any inline comment whose fix is non-trivial or has cross-file implications (see Inline technical details in the format below). every comment must be actionable, 2-3 sentences max in the visible part, one per root cause \u2014 name its other sites in that comment rather than repeating it.
|
|
112175
112239
|
|
|
112176
|
-
9. **build the review body**: use the same default format as Review mode (preamble + optional cross-cutting \`### \` sections
|
|
112240
|
+
9. **build the review body**: use the same default format as Review mode (preamble + optional cross-cutting \`### \` sections) \u2014 scoped to the **incremental delta**, not the full PR. The "Reviewed changes" bullets describe what changed since the prior pullfrog review (each bullet starts with a past-tense verb, e.g. \`- Extracted shared CLI runtime into a single module\`). Do NOT include a separate "Prior review feedback" checklist \u2014 that's tracked in the rolling PR summary snapshot for the next agent run, and surfacing it in the user-facing body is noise (changes that addressed prior feedback are already covered by the Reviewed-changes bullets). In some cases you may receive a complete diff for the whole PR instead of an incremental one; when this happens, determine what changed since Pullfrog's most recent review yourself before drafting bullets.
|
|
112177
112241
|
|
|
112178
112242
|
10. Submit \u2014 every run must end with EXACTLY ONE of \`${t2("create_pull_request_review")}\` (substantive review) or \`${t2("report_progress")}\` (no-review acknowledgement). do NOT call \`create_issue_comment\` for review output.
|
|
112179
112243
|
|
|
@@ -112184,7 +112248,7 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
112184
112248
|
- IF NO NEW ISSUES, NON-SUBSTANTIVE CHANGES ONLY (trivial formatting, import reordering, comment tweaks): do NOT submit a review. Instead call \`${t2("report_progress")}\` with \`no_review_needed: true\` and a 1-2 sentence note explaining no review was warranted (e.g. "No new issues. Changes since last review are formatting-only."). this leaves a visible signal that the run completed.
|
|
112185
112249
|
- ELSE IF NEW CRITICAL ISSUES (blocks merge \u2014 bugs, security, data loss, broken core flows): call \`${t2("create_pull_request_review")}\` with \`approved: false\`, all comments, and the review body. body opens with \`> [!CAUTION]\\n> This PR introduces ...\`, followed by the PR summary using the default format below.
|
|
112186
112250
|
- ELSE IF NEW MUST-ADDRESS NON-CRITICAL FINDINGS (real consequences if shipped \u2014 incorrect behavior, missing validation, regressions the author should fix before merge): call \`${t2("create_pull_request_review")}\` with \`approved: false\`, all comments, and the review body. body opens with \`> [!IMPORTANT]\\n> ...\`, followed by the PR summary using the default format below. Do NOT use this tier for nits, style preferences, or "consider also" suggestions.
|
|
112187
|
-
- ELSE IF NEW MINOR SUGGESTIONS ONLY (low-stakes findings
|
|
112251
|
+
- ELSE IF NEW MINOR SUGGESTIONS ONLY (low-stakes findings): call \`${t2("create_pull_request_review")}\` with \`approved: false\`, all comments, and the review body. body opens with \`> \u2139\uFE0F No critical issues \u2014 minor suggestions inline.\\n\\n\` (vary the wording after \u2139\uFE0F to fit the review), followed by the PR summary using the default format below.
|
|
112188
112252
|
- ELSE IF INFORMATIONAL OBSERVATIONS (mergeable as-is, but worth surfacing \u2014 e.g. prior feedback addressed cleanly, or a noteworthy positive observation): call \`${t2("create_pull_request_review")}\` with \`approved: true\`, NO inline comments, and the review body. body opens with \`> \u2705 No new issues found.\\n\\n\` (or similar friendly green opener), followed by the PR summary using the default format below. If a point is concrete enough to anchor to a line, downgrade the whole review to "minor suggestions only" (\`approved: false\`) instead \u2014 the \u2705 signals "no action needed", which contradicts an actionable anchor.
|
|
112189
112253
|
- ELSE IF NO NEW ISSUES, SUBSTANTIVE CHANGES (new functionality, behavior changes, or fixes to prior review feedback): call \`${t2("create_pull_request_review")}\` to create a PR review. If all previous reviews have been properly addressed and no new issues were discovered, set \`approved: true\`. body opens with \`> \u2705 No new issues found.\\n\\n\`, followed by the PR summary using the default format below.
|
|
112190
112254
|
|
|
@@ -113401,16 +113465,253 @@ import { performance as performance6 } from "node:perf_hooks";
|
|
|
113401
113465
|
|
|
113402
113466
|
// utils/codexHome.ts
|
|
113403
113467
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
113404
|
-
import { existsSync as
|
|
113468
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, mkdtempSync as mkdtempSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
113405
113469
|
import { homedir as homedir2, userInfo } from "node:os";
|
|
113406
113470
|
import { join as join6 } from "node:path";
|
|
113471
|
+
|
|
113472
|
+
// utils/oauthWriteback.ts
|
|
113473
|
+
var core2 = __toESM(require_core(), 1);
|
|
113474
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, rmSync as rmSync3 } from "node:fs";
|
|
113475
|
+
|
|
113476
|
+
// utils/apiUrl.ts
|
|
113477
|
+
function isLocalUrl(url4) {
|
|
113478
|
+
return url4.hostname === "localhost" || url4.hostname === "127.0.0.1";
|
|
113479
|
+
}
|
|
113480
|
+
function getApiUrl() {
|
|
113481
|
+
const raw2 = process.env.API_URL || "https://pullfrog.com";
|
|
113482
|
+
const parsed2 = new URL(raw2);
|
|
113483
|
+
if (parsed2.protocol !== "https:" && !isLocalUrl(parsed2)) {
|
|
113484
|
+
throw new Error(
|
|
113485
|
+
`API_URL must use https:// (got ${parsed2.protocol}). only localhost is exempt.`
|
|
113486
|
+
);
|
|
113487
|
+
}
|
|
113488
|
+
log2.debug(`resolved API_URL: ${raw2}`);
|
|
113489
|
+
return raw2;
|
|
113490
|
+
}
|
|
113491
|
+
function isLocalApiUrl() {
|
|
113492
|
+
try {
|
|
113493
|
+
return isLocalUrl(new URL(getApiUrl()));
|
|
113494
|
+
} catch {
|
|
113495
|
+
return false;
|
|
113496
|
+
}
|
|
113497
|
+
}
|
|
113498
|
+
|
|
113499
|
+
// utils/apiFetch.ts
|
|
113500
|
+
async function apiFetch(options) {
|
|
113501
|
+
const apiUrl = getApiUrl();
|
|
113502
|
+
const url4 = new URL(options.path, apiUrl);
|
|
113503
|
+
const bypassSecret = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
|
|
113504
|
+
if (bypassSecret) {
|
|
113505
|
+
url4.searchParams.set("x-vercel-protection-bypass", bypassSecret);
|
|
113506
|
+
}
|
|
113507
|
+
const headers = {
|
|
113508
|
+
...options.headers
|
|
113509
|
+
};
|
|
113510
|
+
if (bypassSecret) {
|
|
113511
|
+
headers["x-vercel-protection-bypass"] = bypassSecret;
|
|
113512
|
+
}
|
|
113513
|
+
if (!options.body) {
|
|
113514
|
+
for (const key of Object.keys(headers)) {
|
|
113515
|
+
if (key.toLowerCase() === "content-type") delete headers[key];
|
|
113516
|
+
}
|
|
113517
|
+
}
|
|
113518
|
+
log2.debug(`api fetch: ${options.method ?? "GET"} ${url4.pathname}`);
|
|
113519
|
+
const init = {
|
|
113520
|
+
method: options.method ?? "GET",
|
|
113521
|
+
headers
|
|
113522
|
+
};
|
|
113523
|
+
if (options.body) init.body = options.body;
|
|
113524
|
+
if (options.signal) init.signal = options.signal;
|
|
113525
|
+
return fetch(url4.toString(), init);
|
|
113526
|
+
}
|
|
113527
|
+
|
|
113528
|
+
// utils/codexRefreshDetect.ts
|
|
113529
|
+
function detectCodexRefresh(params) {
|
|
113530
|
+
let parsed2;
|
|
113531
|
+
try {
|
|
113532
|
+
parsed2 = JSON.parse(params.authFileContent);
|
|
113533
|
+
} catch {
|
|
113534
|
+
return null;
|
|
113535
|
+
}
|
|
113536
|
+
if (!parsed2 || typeof parsed2 !== "object") return null;
|
|
113537
|
+
const root = parsed2;
|
|
113538
|
+
const tokens = readCodexTokens(root) ?? readOpenCodeTokens(root);
|
|
113539
|
+
if (!tokens) return null;
|
|
113540
|
+
if (tokens.refresh_token === params.originalRefresh) return null;
|
|
113541
|
+
const idToken = tokens.id_token ?? params.originalIdToken;
|
|
113542
|
+
const codexShape = {
|
|
113543
|
+
auth_mode: "chatgpt",
|
|
113544
|
+
tokens: {
|
|
113545
|
+
...idToken ? { id_token: idToken } : {},
|
|
113546
|
+
access_token: tokens.access_token,
|
|
113547
|
+
refresh_token: tokens.refresh_token,
|
|
113548
|
+
...tokens.account_id ? { account_id: tokens.account_id } : {}
|
|
113549
|
+
},
|
|
113550
|
+
last_refresh: (/* @__PURE__ */ new Date()).toISOString()
|
|
113551
|
+
};
|
|
113552
|
+
return `${JSON.stringify(codexShape, null, 2)}
|
|
113553
|
+
`;
|
|
113554
|
+
}
|
|
113555
|
+
function readCodexTokens(root) {
|
|
113556
|
+
if (root.auth_mode !== "chatgpt") return null;
|
|
113557
|
+
const tokens = root.tokens;
|
|
113558
|
+
if (!tokens || typeof tokens !== "object") return null;
|
|
113559
|
+
const t2 = tokens;
|
|
113560
|
+
if (typeof t2.access_token !== "string" || typeof t2.refresh_token !== "string") return null;
|
|
113561
|
+
return {
|
|
113562
|
+
access_token: t2.access_token,
|
|
113563
|
+
refresh_token: t2.refresh_token,
|
|
113564
|
+
id_token: typeof t2.id_token === "string" ? t2.id_token : void 0,
|
|
113565
|
+
account_id: typeof t2.account_id === "string" ? t2.account_id : void 0
|
|
113566
|
+
};
|
|
113567
|
+
}
|
|
113568
|
+
function readOpenCodeTokens(root) {
|
|
113569
|
+
const oauth = root.openai;
|
|
113570
|
+
if (!oauth || typeof oauth !== "object") return null;
|
|
113571
|
+
const o = oauth;
|
|
113572
|
+
if (o.type !== "oauth") return null;
|
|
113573
|
+
if (typeof o.refresh !== "string" || typeof o.access !== "string") return null;
|
|
113574
|
+
return {
|
|
113575
|
+
access_token: o.access,
|
|
113576
|
+
refresh_token: o.refresh,
|
|
113577
|
+
id_token: void 0,
|
|
113578
|
+
account_id: typeof o.accountId === "string" ? o.accountId : void 0
|
|
113579
|
+
};
|
|
113580
|
+
}
|
|
113581
|
+
function detectXaiRefresh(params) {
|
|
113582
|
+
let parsed2;
|
|
113583
|
+
try {
|
|
113584
|
+
parsed2 = JSON.parse(params.authFileContent);
|
|
113585
|
+
} catch {
|
|
113586
|
+
return null;
|
|
113587
|
+
}
|
|
113588
|
+
if (!parsed2 || typeof parsed2 !== "object") return null;
|
|
113589
|
+
const oauth = parsed2.xai;
|
|
113590
|
+
if (!oauth || typeof oauth !== "object") return null;
|
|
113591
|
+
const o = oauth;
|
|
113592
|
+
if (o.type !== "oauth") return null;
|
|
113593
|
+
if (typeof o.access !== "string" || typeof o.refresh !== "string") return null;
|
|
113594
|
+
if (o.refresh === params.originalRefresh) return null;
|
|
113595
|
+
const grokShape = {
|
|
113596
|
+
auth_mode: "grok",
|
|
113597
|
+
tokens: { access_token: o.access, refresh_token: o.refresh },
|
|
113598
|
+
last_refresh: (/* @__PURE__ */ new Date()).toISOString()
|
|
113599
|
+
};
|
|
113600
|
+
return `${JSON.stringify(grokShape, null, 2)}
|
|
113601
|
+
`;
|
|
113602
|
+
}
|
|
113603
|
+
|
|
113604
|
+
// utils/oauthWriteback.ts
|
|
113605
|
+
var OAUTH_WRITEBACK_STATE = "oauth_writeback";
|
|
113606
|
+
var SUBSCRIPTION_DATA_DIR_STATE = "subscription_data_dir";
|
|
113607
|
+
async function runOAuthWriteback() {
|
|
113608
|
+
await writeBackEntries();
|
|
113609
|
+
const dataDir = core2.getState(SUBSCRIPTION_DATA_DIR_STATE);
|
|
113610
|
+
if (!dataDir) return;
|
|
113611
|
+
rmSync3(dataDir, { recursive: true, force: true });
|
|
113612
|
+
core2.info(`oauth post-hook: removed ${dataDir}`);
|
|
113613
|
+
}
|
|
113614
|
+
async function writeBackEntries() {
|
|
113615
|
+
const raw2 = core2.getState(OAUTH_WRITEBACK_STATE);
|
|
113616
|
+
if (!raw2) {
|
|
113617
|
+
core2.info("oauth post-hook: no writeback state \u2014 skipping");
|
|
113618
|
+
return;
|
|
113619
|
+
}
|
|
113620
|
+
let state;
|
|
113621
|
+
try {
|
|
113622
|
+
state = JSON.parse(raw2);
|
|
113623
|
+
} catch (err) {
|
|
113624
|
+
core2.warning(`oauth post-hook: malformed writeback state \u2014 ${err}`);
|
|
113625
|
+
return;
|
|
113626
|
+
}
|
|
113627
|
+
if (!state.apiToken || !Array.isArray(state.entries)) {
|
|
113628
|
+
core2.warning("oauth post-hook: incomplete writeback state \u2014 skipping");
|
|
113629
|
+
return;
|
|
113630
|
+
}
|
|
113631
|
+
for (const entry of state.entries) {
|
|
113632
|
+
await writeBackEntry(state.apiToken, entry);
|
|
113633
|
+
}
|
|
113634
|
+
}
|
|
113635
|
+
async function writeBackEntry(apiToken, entry) {
|
|
113636
|
+
if (!entry.secretName || !entry.authPath || !entry.originalRefresh) {
|
|
113637
|
+
core2.warning("oauth post-hook: incomplete writeback entry \u2014 skipping");
|
|
113638
|
+
return;
|
|
113639
|
+
}
|
|
113640
|
+
if (!existsSync5(entry.authPath)) {
|
|
113641
|
+
core2.info(`oauth post-hook: ${entry.authPath} not found \u2014 nothing to write back`);
|
|
113642
|
+
return;
|
|
113643
|
+
}
|
|
113644
|
+
let authFileContent;
|
|
113645
|
+
try {
|
|
113646
|
+
authFileContent = readFileSync3(entry.authPath, "utf8");
|
|
113647
|
+
} catch (err) {
|
|
113648
|
+
core2.warning(`oauth post-hook: cannot read ${entry.authPath} \u2014 ${err}`);
|
|
113649
|
+
return;
|
|
113650
|
+
}
|
|
113651
|
+
const refreshed = entry.provider === "xai" ? detectXaiRefresh({
|
|
113652
|
+
authFileContent,
|
|
113653
|
+
originalRefresh: entry.originalRefresh
|
|
113654
|
+
}) : detectCodexRefresh({
|
|
113655
|
+
authFileContent,
|
|
113656
|
+
originalRefresh: entry.originalRefresh,
|
|
113657
|
+
originalIdToken: entry.originalIdToken
|
|
113658
|
+
});
|
|
113659
|
+
if (!refreshed) {
|
|
113660
|
+
core2.info(`oauth post-hook: ${entry.secretName} chain unchanged \u2014 no writeback needed`);
|
|
113661
|
+
return;
|
|
113662
|
+
}
|
|
113663
|
+
try {
|
|
113664
|
+
const poolState = core2.getState("credential_receipts");
|
|
113665
|
+
const receipts2 = external_exports.record(external_exports.string(), external_exports.string()).parse(JSON.parse(poolState || "{}"));
|
|
113666
|
+
const receipt = receipts2[entry.secretName];
|
|
113667
|
+
if (poolState && !receipt) return;
|
|
113668
|
+
const response = await apiFetch({
|
|
113669
|
+
path: receipt ? "/api/runtime/credentials" : "/api/runtime/secret",
|
|
113670
|
+
method: "PUT",
|
|
113671
|
+
headers: {
|
|
113672
|
+
authorization: `Bearer ${apiToken}`,
|
|
113673
|
+
"content-type": "application/json"
|
|
113674
|
+
},
|
|
113675
|
+
body: JSON.stringify({ name: entry.secretName, value: refreshed, receipt }),
|
|
113676
|
+
// the workflow is already finished; a hung write-back would hold the
|
|
113677
|
+
// runner open for nothing.
|
|
113678
|
+
signal: AbortSignal.timeout(3e4)
|
|
113679
|
+
});
|
|
113680
|
+
if (!response.ok) {
|
|
113681
|
+
const body = await response.text().catch(() => "");
|
|
113682
|
+
core2.warning(`oauth post-hook: writeback returned ${response.status}: ${body}`);
|
|
113683
|
+
return;
|
|
113684
|
+
}
|
|
113685
|
+
core2.info(`oauth post-hook: refreshed ${entry.secretName} persisted to Pullfrog`);
|
|
113686
|
+
} catch (err) {
|
|
113687
|
+
core2.warning(`oauth post-hook: writeback failed \u2014 ${err}`);
|
|
113688
|
+
}
|
|
113689
|
+
}
|
|
113690
|
+
|
|
113691
|
+
// utils/secretCommands.ts
|
|
113692
|
+
var core3 = __toESM(require_core(), 1);
|
|
113693
|
+
function maskSecret(value2) {
|
|
113694
|
+
if (isGitHubActions) core3.setSecret(value2);
|
|
113695
|
+
}
|
|
113696
|
+
function saveSecretState(name, value2) {
|
|
113697
|
+
if (isGitHubActions) core3.saveState(name, value2);
|
|
113698
|
+
}
|
|
113699
|
+
function setSecretOutput(name, value2) {
|
|
113700
|
+
if (isGitHubActions) core3.setOutput(name, value2);
|
|
113701
|
+
}
|
|
113702
|
+
|
|
113703
|
+
// utils/codexHome.ts
|
|
113407
113704
|
var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
|
|
113408
113705
|
var XAI_AUTH_ENV = "GROK_AUTH_JSON";
|
|
113409
113706
|
var installedSubscriptionPaths = /* @__PURE__ */ new Map();
|
|
113707
|
+
var installedDataHome;
|
|
113708
|
+
function getSubscriptionDataHome() {
|
|
113709
|
+
return installedDataHome;
|
|
113710
|
+
}
|
|
113410
113711
|
function clearInstalledSubscription(name) {
|
|
113411
113712
|
const path4 = installedSubscriptionPaths.get(name);
|
|
113412
|
-
if (!path4 || !
|
|
113413
|
-
const value2 = JSON.parse(
|
|
113713
|
+
if (!path4 || !existsSync6(path4)) return;
|
|
113714
|
+
const value2 = JSON.parse(readFileSync4(path4, "utf8"));
|
|
113414
113715
|
if (!value2 || typeof value2 !== "object") return;
|
|
113415
113716
|
if (name === "CODEX_AUTH_JSON" && "openai" in value2) delete value2.openai;
|
|
113416
113717
|
if (name === "GROK_AUTH_JSON" && "xai" in value2) delete value2.xai;
|
|
@@ -113452,21 +113753,20 @@ function installCodexAuth() {
|
|
|
113452
113753
|
...body.tokens.account_id ? { accountId: body.tokens.account_id } : {}
|
|
113453
113754
|
}
|
|
113454
113755
|
});
|
|
113455
|
-
|
|
113756
|
+
installedDataHome = xdgDataHome;
|
|
113456
113757
|
log2.info(`\xBB installed Codex auth at ${authPath}`);
|
|
113457
113758
|
installedSubscriptionPaths.set(CODEX_AUTH_ENV, authPath);
|
|
113458
113759
|
return {
|
|
113459
113760
|
authPath,
|
|
113460
|
-
xdgDataHome,
|
|
113461
113761
|
originalRefresh: body.tokens.refresh_token,
|
|
113462
113762
|
originalIdToken: body.tokens.id_token
|
|
113463
113763
|
};
|
|
113464
113764
|
}
|
|
113465
113765
|
function writeOpenCodeAuthEntry(params) {
|
|
113466
113766
|
let existing = {};
|
|
113467
|
-
if (
|
|
113767
|
+
if (existsSync6(params.authPath)) {
|
|
113468
113768
|
try {
|
|
113469
|
-
const parsed2 = JSON.parse(
|
|
113769
|
+
const parsed2 = JSON.parse(readFileSync4(params.authPath, "utf8"));
|
|
113470
113770
|
if (parsed2 && typeof parsed2 === "object") existing = parsed2;
|
|
113471
113771
|
} catch {
|
|
113472
113772
|
log2.warning(`\xBB ${params.authPath} was unreadable; rewriting it`);
|
|
@@ -113505,10 +113805,10 @@ function installXaiAuth() {
|
|
|
113505
113805
|
expires: decodeJwtExpMs(body.tokens.access_token) ?? 0
|
|
113506
113806
|
}
|
|
113507
113807
|
});
|
|
113508
|
-
|
|
113808
|
+
installedDataHome = xdgDataHome;
|
|
113509
113809
|
log2.info(`\xBB installed Grok auth at ${authPath}`);
|
|
113510
113810
|
installedSubscriptionPaths.set(XAI_AUTH_ENV, authPath);
|
|
113511
|
-
return { authPath,
|
|
113811
|
+
return { authPath, originalRefresh: body.tokens.refresh_token };
|
|
113512
113812
|
}
|
|
113513
113813
|
function isXaiRejectedChain(body) {
|
|
113514
113814
|
if (!body.refresh_rejected_at) return false;
|
|
@@ -113575,230 +113875,11 @@ function bootstrapPullfrogDataDir() {
|
|
|
113575
113875
|
}
|
|
113576
113876
|
return null;
|
|
113577
113877
|
}
|
|
113578
|
-
|
|
113579
|
-
|
|
113580
|
-
|
|
113581
|
-
// utils/oauthWriteback.ts
|
|
113582
|
-
var core2 = __toESM(require_core(), 1);
|
|
113583
|
-
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:fs";
|
|
113584
|
-
|
|
113585
|
-
// utils/apiUrl.ts
|
|
113586
|
-
function isLocalUrl(url4) {
|
|
113587
|
-
return url4.hostname === "localhost" || url4.hostname === "127.0.0.1";
|
|
113588
|
-
}
|
|
113589
|
-
function getApiUrl() {
|
|
113590
|
-
const raw2 = process.env.API_URL || "https://pullfrog.com";
|
|
113591
|
-
const parsed2 = new URL(raw2);
|
|
113592
|
-
if (parsed2.protocol !== "https:" && !isLocalUrl(parsed2)) {
|
|
113593
|
-
throw new Error(
|
|
113594
|
-
`API_URL must use https:// (got ${parsed2.protocol}). only localhost is exempt.`
|
|
113595
|
-
);
|
|
113596
|
-
}
|
|
113597
|
-
log2.debug(`resolved API_URL: ${raw2}`);
|
|
113598
|
-
return raw2;
|
|
113599
|
-
}
|
|
113600
|
-
function isLocalApiUrl() {
|
|
113601
|
-
try {
|
|
113602
|
-
return isLocalUrl(new URL(getApiUrl()));
|
|
113603
|
-
} catch {
|
|
113604
|
-
return false;
|
|
113878
|
+
const runDir = mkdtempSync2(join6(PULLFROG_DATA_DIR, "run-"));
|
|
113879
|
+
if (process.env.RUNNER_ENVIRONMENT !== "github-hosted") {
|
|
113880
|
+
saveSecretState(SUBSCRIPTION_DATA_DIR_STATE, runDir);
|
|
113605
113881
|
}
|
|
113606
|
-
|
|
113607
|
-
|
|
113608
|
-
// utils/apiFetch.ts
|
|
113609
|
-
async function apiFetch(options) {
|
|
113610
|
-
const apiUrl = getApiUrl();
|
|
113611
|
-
const url4 = new URL(options.path, apiUrl);
|
|
113612
|
-
const bypassSecret = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
|
|
113613
|
-
if (bypassSecret) {
|
|
113614
|
-
url4.searchParams.set("x-vercel-protection-bypass", bypassSecret);
|
|
113615
|
-
}
|
|
113616
|
-
const headers = {
|
|
113617
|
-
...options.headers
|
|
113618
|
-
};
|
|
113619
|
-
if (bypassSecret) {
|
|
113620
|
-
headers["x-vercel-protection-bypass"] = bypassSecret;
|
|
113621
|
-
}
|
|
113622
|
-
if (!options.body) {
|
|
113623
|
-
for (const key of Object.keys(headers)) {
|
|
113624
|
-
if (key.toLowerCase() === "content-type") delete headers[key];
|
|
113625
|
-
}
|
|
113626
|
-
}
|
|
113627
|
-
log2.debug(`api fetch: ${options.method ?? "GET"} ${url4.pathname}`);
|
|
113628
|
-
const init = {
|
|
113629
|
-
method: options.method ?? "GET",
|
|
113630
|
-
headers
|
|
113631
|
-
};
|
|
113632
|
-
if (options.body) init.body = options.body;
|
|
113633
|
-
if (options.signal) init.signal = options.signal;
|
|
113634
|
-
return fetch(url4.toString(), init);
|
|
113635
|
-
}
|
|
113636
|
-
|
|
113637
|
-
// utils/codexRefreshDetect.ts
|
|
113638
|
-
function detectCodexRefresh(params) {
|
|
113639
|
-
let parsed2;
|
|
113640
|
-
try {
|
|
113641
|
-
parsed2 = JSON.parse(params.authFileContent);
|
|
113642
|
-
} catch {
|
|
113643
|
-
return null;
|
|
113644
|
-
}
|
|
113645
|
-
if (!parsed2 || typeof parsed2 !== "object") return null;
|
|
113646
|
-
const root = parsed2;
|
|
113647
|
-
const tokens = readCodexTokens(root) ?? readOpenCodeTokens(root);
|
|
113648
|
-
if (!tokens) return null;
|
|
113649
|
-
if (tokens.refresh_token === params.originalRefresh) return null;
|
|
113650
|
-
const idToken = tokens.id_token ?? params.originalIdToken;
|
|
113651
|
-
const codexShape = {
|
|
113652
|
-
auth_mode: "chatgpt",
|
|
113653
|
-
tokens: {
|
|
113654
|
-
...idToken ? { id_token: idToken } : {},
|
|
113655
|
-
access_token: tokens.access_token,
|
|
113656
|
-
refresh_token: tokens.refresh_token,
|
|
113657
|
-
...tokens.account_id ? { account_id: tokens.account_id } : {}
|
|
113658
|
-
},
|
|
113659
|
-
last_refresh: (/* @__PURE__ */ new Date()).toISOString()
|
|
113660
|
-
};
|
|
113661
|
-
return `${JSON.stringify(codexShape, null, 2)}
|
|
113662
|
-
`;
|
|
113663
|
-
}
|
|
113664
|
-
function readCodexTokens(root) {
|
|
113665
|
-
if (root.auth_mode !== "chatgpt") return null;
|
|
113666
|
-
const tokens = root.tokens;
|
|
113667
|
-
if (!tokens || typeof tokens !== "object") return null;
|
|
113668
|
-
const t2 = tokens;
|
|
113669
|
-
if (typeof t2.access_token !== "string" || typeof t2.refresh_token !== "string") return null;
|
|
113670
|
-
return {
|
|
113671
|
-
access_token: t2.access_token,
|
|
113672
|
-
refresh_token: t2.refresh_token,
|
|
113673
|
-
id_token: typeof t2.id_token === "string" ? t2.id_token : void 0,
|
|
113674
|
-
account_id: typeof t2.account_id === "string" ? t2.account_id : void 0
|
|
113675
|
-
};
|
|
113676
|
-
}
|
|
113677
|
-
function readOpenCodeTokens(root) {
|
|
113678
|
-
const oauth = root.openai;
|
|
113679
|
-
if (!oauth || typeof oauth !== "object") return null;
|
|
113680
|
-
const o = oauth;
|
|
113681
|
-
if (o.type !== "oauth") return null;
|
|
113682
|
-
if (typeof o.refresh !== "string" || typeof o.access !== "string") return null;
|
|
113683
|
-
return {
|
|
113684
|
-
access_token: o.access,
|
|
113685
|
-
refresh_token: o.refresh,
|
|
113686
|
-
id_token: void 0,
|
|
113687
|
-
account_id: typeof o.accountId === "string" ? o.accountId : void 0
|
|
113688
|
-
};
|
|
113689
|
-
}
|
|
113690
|
-
function detectXaiRefresh(params) {
|
|
113691
|
-
let parsed2;
|
|
113692
|
-
try {
|
|
113693
|
-
parsed2 = JSON.parse(params.authFileContent);
|
|
113694
|
-
} catch {
|
|
113695
|
-
return null;
|
|
113696
|
-
}
|
|
113697
|
-
if (!parsed2 || typeof parsed2 !== "object") return null;
|
|
113698
|
-
const oauth = parsed2.xai;
|
|
113699
|
-
if (!oauth || typeof oauth !== "object") return null;
|
|
113700
|
-
const o = oauth;
|
|
113701
|
-
if (o.type !== "oauth") return null;
|
|
113702
|
-
if (typeof o.access !== "string" || typeof o.refresh !== "string") return null;
|
|
113703
|
-
if (o.refresh === params.originalRefresh) return null;
|
|
113704
|
-
const grokShape = {
|
|
113705
|
-
auth_mode: "grok",
|
|
113706
|
-
tokens: { access_token: o.access, refresh_token: o.refresh },
|
|
113707
|
-
last_refresh: (/* @__PURE__ */ new Date()).toISOString()
|
|
113708
|
-
};
|
|
113709
|
-
return `${JSON.stringify(grokShape, null, 2)}
|
|
113710
|
-
`;
|
|
113711
|
-
}
|
|
113712
|
-
|
|
113713
|
-
// utils/oauthWriteback.ts
|
|
113714
|
-
var OAUTH_WRITEBACK_STATE = "oauth_writeback";
|
|
113715
|
-
async function runOAuthWriteback() {
|
|
113716
|
-
const raw2 = core2.getState(OAUTH_WRITEBACK_STATE);
|
|
113717
|
-
if (!raw2) {
|
|
113718
|
-
core2.info("oauth post-hook: no writeback state \u2014 skipping");
|
|
113719
|
-
return;
|
|
113720
|
-
}
|
|
113721
|
-
let state;
|
|
113722
|
-
try {
|
|
113723
|
-
state = JSON.parse(raw2);
|
|
113724
|
-
} catch (err) {
|
|
113725
|
-
core2.warning(`oauth post-hook: malformed writeback state \u2014 ${err}`);
|
|
113726
|
-
return;
|
|
113727
|
-
}
|
|
113728
|
-
if (!state.apiToken || !Array.isArray(state.entries)) {
|
|
113729
|
-
core2.warning("oauth post-hook: incomplete writeback state \u2014 skipping");
|
|
113730
|
-
return;
|
|
113731
|
-
}
|
|
113732
|
-
for (const entry of state.entries) {
|
|
113733
|
-
await writeBackEntry(state.apiToken, entry);
|
|
113734
|
-
}
|
|
113735
|
-
}
|
|
113736
|
-
async function writeBackEntry(apiToken, entry) {
|
|
113737
|
-
if (!entry.secretName || !entry.authPath || !entry.originalRefresh) {
|
|
113738
|
-
core2.warning("oauth post-hook: incomplete writeback entry \u2014 skipping");
|
|
113739
|
-
return;
|
|
113740
|
-
}
|
|
113741
|
-
if (!existsSync6(entry.authPath)) {
|
|
113742
|
-
core2.info(`oauth post-hook: ${entry.authPath} not found \u2014 nothing to write back`);
|
|
113743
|
-
return;
|
|
113744
|
-
}
|
|
113745
|
-
let authFileContent;
|
|
113746
|
-
try {
|
|
113747
|
-
authFileContent = readFileSync4(entry.authPath, "utf8");
|
|
113748
|
-
} catch (err) {
|
|
113749
|
-
core2.warning(`oauth post-hook: cannot read ${entry.authPath} \u2014 ${err}`);
|
|
113750
|
-
return;
|
|
113751
|
-
}
|
|
113752
|
-
const refreshed = entry.provider === "xai" ? detectXaiRefresh({
|
|
113753
|
-
authFileContent,
|
|
113754
|
-
originalRefresh: entry.originalRefresh
|
|
113755
|
-
}) : detectCodexRefresh({
|
|
113756
|
-
authFileContent,
|
|
113757
|
-
originalRefresh: entry.originalRefresh,
|
|
113758
|
-
originalIdToken: entry.originalIdToken
|
|
113759
|
-
});
|
|
113760
|
-
if (!refreshed) {
|
|
113761
|
-
core2.info(`oauth post-hook: ${entry.secretName} chain unchanged \u2014 no writeback needed`);
|
|
113762
|
-
return;
|
|
113763
|
-
}
|
|
113764
|
-
try {
|
|
113765
|
-
const poolState = core2.getState("credential_receipts");
|
|
113766
|
-
const receipts2 = external_exports.record(external_exports.string(), external_exports.string()).parse(JSON.parse(poolState || "{}"));
|
|
113767
|
-
const receipt = receipts2[entry.secretName];
|
|
113768
|
-
if (poolState && !receipt) return;
|
|
113769
|
-
const response = await apiFetch({
|
|
113770
|
-
path: receipt ? "/api/runtime/credentials" : "/api/runtime/secret",
|
|
113771
|
-
method: "PUT",
|
|
113772
|
-
headers: {
|
|
113773
|
-
authorization: `Bearer ${apiToken}`,
|
|
113774
|
-
"content-type": "application/json"
|
|
113775
|
-
},
|
|
113776
|
-
body: JSON.stringify({ name: entry.secretName, value: refreshed, receipt }),
|
|
113777
|
-
// the workflow is already finished; a hung write-back would hold the
|
|
113778
|
-
// runner open for nothing.
|
|
113779
|
-
signal: AbortSignal.timeout(3e4)
|
|
113780
|
-
});
|
|
113781
|
-
if (!response.ok) {
|
|
113782
|
-
const body = await response.text().catch(() => "");
|
|
113783
|
-
core2.warning(`oauth post-hook: writeback returned ${response.status}: ${body}`);
|
|
113784
|
-
return;
|
|
113785
|
-
}
|
|
113786
|
-
core2.info(`oauth post-hook: refreshed ${entry.secretName} persisted to Pullfrog`);
|
|
113787
|
-
} catch (err) {
|
|
113788
|
-
core2.warning(`oauth post-hook: writeback failed \u2014 ${err}`);
|
|
113789
|
-
}
|
|
113790
|
-
}
|
|
113791
|
-
|
|
113792
|
-
// utils/secretCommands.ts
|
|
113793
|
-
var core3 = __toESM(require_core(), 1);
|
|
113794
|
-
function maskSecret(value2) {
|
|
113795
|
-
if (isGitHubActions) core3.setSecret(value2);
|
|
113796
|
-
}
|
|
113797
|
-
function saveSecretState(name, value2) {
|
|
113798
|
-
if (isGitHubActions) core3.saveState(name, value2);
|
|
113799
|
-
}
|
|
113800
|
-
function setSecretOutput(name, value2) {
|
|
113801
|
-
if (isGitHubActions) core3.setOutput(name, value2);
|
|
113882
|
+
return runDir;
|
|
113802
113883
|
}
|
|
113803
113884
|
|
|
113804
113885
|
// utils/secrets.ts
|
|
@@ -114447,9 +114528,8 @@ var codex = agent({
|
|
|
114447
114528
|
initialUsage: initial.usage,
|
|
114448
114529
|
reflectionPrompt: buildReflectionPrompt(ctx.toolState),
|
|
114449
114530
|
// a resume needs the thread codex recorded. without one there is nothing
|
|
114450
|
-
// to continue, and `resume --last`
|
|
114451
|
-
//
|
|
114452
|
-
// runner with a shared CODEX_HOME is somebody else's run.
|
|
114531
|
+
// to continue, and `resume --last` could pick another run's session off CI,
|
|
114532
|
+
// where CODEX_HOME is the shared ~/.local/share/codex (in CI it is per run).
|
|
114453
114533
|
canResume: (r2) => r2.threadId !== void 0,
|
|
114454
114534
|
resume: async (c2) => runCodex2({
|
|
114455
114535
|
...runnerArgs,
|
|
@@ -114476,7 +114556,7 @@ var codex = agent({
|
|
|
114476
114556
|
import { spawn as nodeSpawn2 } from "node:child_process";
|
|
114477
114557
|
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
|
|
114478
114558
|
import { totalmem } from "node:os";
|
|
114479
|
-
import { join as
|
|
114559
|
+
import { join as join9 } from "node:path";
|
|
114480
114560
|
import { performance as performance7 } from "node:perf_hooks";
|
|
114481
114561
|
|
|
114482
114562
|
// node_modules/.pnpm/@opencode-ai+sdk@1.18.29/node_modules/@opencode-ai/sdk/dist/v2/gen/core/serverSentEvents.gen.js
|
|
@@ -119885,6 +119965,68 @@ var import_cross_spawn = __toESM(require_cross_spawn(), 1);
|
|
|
119885
119965
|
// agents/opencode.ts
|
|
119886
119966
|
var import_undici = __toESM(require_undici2(), 1);
|
|
119887
119967
|
|
|
119968
|
+
// utils/openCodeModels.ts
|
|
119969
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
119970
|
+
import { join as join8 } from "node:path";
|
|
119971
|
+
var baseline;
|
|
119972
|
+
var authorized;
|
|
119973
|
+
var failure;
|
|
119974
|
+
var ANSI_PATTERN = /\[[0-9;]*m/g;
|
|
119975
|
+
function openCodeHomeEnv(tmpdir4) {
|
|
119976
|
+
return {
|
|
119977
|
+
HOME: tmpdir4,
|
|
119978
|
+
XDG_CONFIG_HOME: join8(tmpdir4, ".config"),
|
|
119979
|
+
XDG_DATA_HOME: getSubscriptionDataHome() ?? join8(tmpdir4, ".local", "share"),
|
|
119980
|
+
XDG_CACHE_HOME: join8(tmpdir4, ".cache"),
|
|
119981
|
+
XDG_STATE_HOME: join8(tmpdir4, ".local", "state")
|
|
119982
|
+
};
|
|
119983
|
+
}
|
|
119984
|
+
function readModels(params) {
|
|
119985
|
+
const result = spawnSync4(params.cliPath, ["models"], {
|
|
119986
|
+
encoding: "utf-8",
|
|
119987
|
+
timeout: 3e4,
|
|
119988
|
+
env: { ...process.env, ...openCodeHomeEnv(params.tmpdir) },
|
|
119989
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
119990
|
+
});
|
|
119991
|
+
if (result.status !== 0) {
|
|
119992
|
+
const stderr = (result.stderr ?? "").replace(ANSI_PATTERN, "").trim();
|
|
119993
|
+
failure = stderr || result.error?.message;
|
|
119994
|
+
log2.debug(`\xBB \`opencode models\` failed (${result.status}): ${failure}`);
|
|
119995
|
+
return /* @__PURE__ */ new Set();
|
|
119996
|
+
}
|
|
119997
|
+
failure = void 0;
|
|
119998
|
+
return new Set(
|
|
119999
|
+
result.stdout.split("\n").map((line) => line.trim()).filter(Boolean)
|
|
120000
|
+
);
|
|
120001
|
+
}
|
|
120002
|
+
function getModelsFailure() {
|
|
120003
|
+
return failure;
|
|
120004
|
+
}
|
|
120005
|
+
function captureBaselineModels(params) {
|
|
120006
|
+
baseline = readModels(params);
|
|
120007
|
+
log2.debug(`\xBB opencode baseline: ${baseline.size} models`);
|
|
120008
|
+
}
|
|
120009
|
+
function captureAuthorizedModels(params) {
|
|
120010
|
+
authorized = readModels(params);
|
|
120011
|
+
const base = baseline;
|
|
120012
|
+
if (base) {
|
|
120013
|
+
const diff = [...authorized].filter((m) => !base.has(m));
|
|
120014
|
+
if (diff.length > 0) {
|
|
120015
|
+
log2.info(`\xBB BYOK auth enabled ${diff.length} model(s): ${diff.join(", ")}`);
|
|
120016
|
+
}
|
|
120017
|
+
}
|
|
120018
|
+
log2.debug(`\xBB opencode authorized: ${authorized.size} models`);
|
|
120019
|
+
}
|
|
120020
|
+
function authorizeModel(model) {
|
|
120021
|
+
getAuthorizedModels().add(model);
|
|
120022
|
+
}
|
|
120023
|
+
function getAuthorizedModels() {
|
|
120024
|
+
if (!authorized) {
|
|
120025
|
+
throw new Error("getAuthorizedModels called before captureAuthorizedModels");
|
|
120026
|
+
}
|
|
120027
|
+
return authorized;
|
|
120028
|
+
}
|
|
120029
|
+
|
|
119888
120030
|
// utils/worktree.ts
|
|
119889
120031
|
async function dirtyTrackedPaths(params = {}) {
|
|
119890
120032
|
const result = await spawn3({
|
|
@@ -120001,7 +120143,8 @@ export default async function pullfrogEventsPlugin() {
|
|
|
120001
120143
|
};
|
|
120002
120144
|
}
|
|
120003
120145
|
`;
|
|
120004
|
-
function buildOpencodeSubagentGateSource(
|
|
120146
|
+
function buildOpencodeSubagentGateSource(ctx) {
|
|
120147
|
+
const bashRefusal = ctx.payload.shell === "disabled" ? "shell execution is disabled for this run and no shell tool exists \u2014 use read, grep and glob instead." : `native bash is disabled in Pullfrog runs \u2014 call ${formatMcpToolRef("opencode", "shell")} with the same command instead.`;
|
|
120005
120148
|
return `// AUTOGENERATED by Pullfrog. do not edit; it'll be overwritten on the next run.
|
|
120006
120149
|
// hard-blocks state-mutating MCP tool calls from opencode subagents (PR
|
|
120007
120150
|
// following pullfrog/app#796 \u2014 see action/agents/subagentToolGates.ts). see
|
|
@@ -120012,7 +120155,7 @@ function buildOpencodeSubagentGateSource(deniedTools) {
|
|
|
120012
120155
|
// embedded at template-render time so the plugin source has no imports. the
|
|
120013
120156
|
// denied set is derived from each tool's \`mutates\` flag \u2014 see
|
|
120014
120157
|
// action/agents/subagentToolGates.ts.
|
|
120015
|
-
const SUBAGENT_DENIED_TOOLS = new Set(${JSON.stringify(
|
|
120158
|
+
const SUBAGENT_DENIED_TOOLS = new Set(${JSON.stringify(ctx.subagentDeniedTools)});
|
|
120016
120159
|
|
|
120017
120160
|
// opencode presents MCP tools as \`pullfrog_<bare>\`; strip that to compare
|
|
120018
120161
|
// against the canonical bare names in SUBAGENT_DENIED_TOOLS. native tools
|
|
@@ -120048,7 +120191,7 @@ export default async function pullfrogSubagentGatePlugin() {
|
|
|
120048
120191
|
// above the sessionID check: the bash block must never depend on a
|
|
120049
120192
|
// session id being present.
|
|
120050
120193
|
if (tool === "bash") {
|
|
120051
|
-
throw new Error(
|
|
120194
|
+
throw new Error(${JSON.stringify(bashRefusal)});
|
|
120052
120195
|
}
|
|
120053
120196
|
if (!sessionID || !tool) return;
|
|
120054
120197
|
if (orchestratorSessionID === undefined) orchestratorSessionID = sessionID;
|
|
@@ -120068,58 +120211,6 @@ export default async function pullfrogSubagentGatePlugin() {
|
|
|
120068
120211
|
`;
|
|
120069
120212
|
}
|
|
120070
120213
|
|
|
120071
|
-
// utils/openCodeModels.ts
|
|
120072
|
-
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
120073
|
-
var baseline;
|
|
120074
|
-
var authorized;
|
|
120075
|
-
var failure;
|
|
120076
|
-
var ANSI_PATTERN = /\[[0-9;]*m/g;
|
|
120077
|
-
function readModels(cliPath) {
|
|
120078
|
-
const result = spawnSync4(cliPath, ["models"], {
|
|
120079
|
-
encoding: "utf-8",
|
|
120080
|
-
timeout: 3e4,
|
|
120081
|
-
env: process.env,
|
|
120082
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
120083
|
-
});
|
|
120084
|
-
if (result.status !== 0) {
|
|
120085
|
-
const stderr = (result.stderr ?? "").replace(ANSI_PATTERN, "").trim();
|
|
120086
|
-
failure = stderr || result.error?.message;
|
|
120087
|
-
log2.debug(`\xBB \`opencode models\` failed (${result.status}): ${failure}`);
|
|
120088
|
-
return /* @__PURE__ */ new Set();
|
|
120089
|
-
}
|
|
120090
|
-
failure = void 0;
|
|
120091
|
-
return new Set(
|
|
120092
|
-
result.stdout.split("\n").map((line) => line.trim()).filter(Boolean)
|
|
120093
|
-
);
|
|
120094
|
-
}
|
|
120095
|
-
function getModelsFailure() {
|
|
120096
|
-
return failure;
|
|
120097
|
-
}
|
|
120098
|
-
function captureBaselineModels(cliPath) {
|
|
120099
|
-
baseline = readModels(cliPath);
|
|
120100
|
-
log2.debug(`\xBB opencode baseline: ${baseline.size} models`);
|
|
120101
|
-
}
|
|
120102
|
-
function captureAuthorizedModels(cliPath) {
|
|
120103
|
-
authorized = readModels(cliPath);
|
|
120104
|
-
const base = baseline;
|
|
120105
|
-
if (base) {
|
|
120106
|
-
const diff = [...authorized].filter((m) => !base.has(m));
|
|
120107
|
-
if (diff.length > 0) {
|
|
120108
|
-
log2.info(`\xBB BYOK auth enabled ${diff.length} model(s): ${diff.join(", ")}`);
|
|
120109
|
-
}
|
|
120110
|
-
}
|
|
120111
|
-
log2.debug(`\xBB opencode authorized: ${authorized.size} models`);
|
|
120112
|
-
}
|
|
120113
|
-
function authorizeModel(model) {
|
|
120114
|
-
getAuthorizedModels().add(model);
|
|
120115
|
-
}
|
|
120116
|
-
function getAuthorizedModels() {
|
|
120117
|
-
if (!authorized) {
|
|
120118
|
-
throw new Error("getAuthorizedModels called before captureAuthorizedModels");
|
|
120119
|
-
}
|
|
120120
|
-
return authorized;
|
|
120121
|
-
}
|
|
120122
|
-
|
|
120123
120214
|
// agents/subagentModels.ts
|
|
120124
120215
|
function deriveSubagentModels(orchestratorSpec) {
|
|
120125
120216
|
if (!orchestratorSpec) return { reviewer: void 0 };
|
|
@@ -120494,6 +120585,7 @@ async function dispatchEvent(ctx, event) {
|
|
|
120494
120585
|
const err = event.properties.error;
|
|
120495
120586
|
const message = err ? extractErrorMessage(err) : "(no error payload)";
|
|
120496
120587
|
log2.info(`\xBB ${ctx.label} session error: ${message}`);
|
|
120588
|
+
if (err) ctx.lastSessionError = message;
|
|
120497
120589
|
return;
|
|
120498
120590
|
}
|
|
120499
120591
|
}
|
|
@@ -120632,6 +120724,7 @@ async function runPromptTurn(ctx, params) {
|
|
|
120632
120724
|
ctx.promptMessageID = void 0;
|
|
120633
120725
|
ctx.diagnostic.sawModelOutput = false;
|
|
120634
120726
|
ctx.diagnostic.idleSec = void 0;
|
|
120727
|
+
ctx.lastSessionError = void 0;
|
|
120635
120728
|
let assistant;
|
|
120636
120729
|
let returnedParts;
|
|
120637
120730
|
let networkError = null;
|
|
@@ -120676,11 +120769,12 @@ async function runPromptTurn(ctx, params) {
|
|
|
120676
120769
|
}
|
|
120677
120770
|
const cause = ctx.serverExitCause() ?? ctx.diagnostic.lastProviderError;
|
|
120678
120771
|
const diagnosis = cause ? ` \u2014 likely cause: ${cause}` : "";
|
|
120772
|
+
const promptCause = ctx.serverExitCause() ?? (ctx.diagnostic.sawModelOutput ? void 0 : ctx.lastSessionError) ?? ctx.diagnostic.lastProviderError;
|
|
120679
120773
|
if (networkError) {
|
|
120680
120774
|
return {
|
|
120681
120775
|
success: false,
|
|
120682
120776
|
output: finalText,
|
|
120683
|
-
error: params.signal.aborted ? `activity timeout: the model went silent and the turn was aborted by the activity watchdog (${networkError})${diagnosis}` : `opencode prompt failed: ${networkError}${
|
|
120777
|
+
error: params.signal.aborted ? `activity timeout: the model went silent and the turn was aborted by the activity watchdog (${networkError})${diagnosis}` : `opencode prompt failed: ${networkError}${promptCause ? ` \u2014 likely cause: ${promptCause}` : ""}`,
|
|
120684
120778
|
usage
|
|
120685
120779
|
};
|
|
120686
120780
|
}
|
|
@@ -120872,15 +120966,14 @@ var opencode = agent({
|
|
|
120872
120966
|
const isBedrockRoute = rawModel !== void 0 && bedrockModelId !== void 0 && bedrockModelId === rawModel;
|
|
120873
120967
|
const vertexModel = resolveVertexOpenCodeModel(rawModel);
|
|
120874
120968
|
const model = vertexModel ?? (isBedrockRoute ? `amazon-bedrock/${rawModel}` : rawModel);
|
|
120875
|
-
const
|
|
120876
|
-
|
|
120877
|
-
|
|
120878
|
-
|
|
120879
|
-
const opencodePluginDir = join8(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
|
|
120969
|
+
const codexAuth = installCodexAuth();
|
|
120970
|
+
const xaiAuth = installXaiAuth();
|
|
120971
|
+
const homeEnv = openCodeHomeEnv(ctx.tmpdir);
|
|
120972
|
+
const opencodePluginDir = join9(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
|
|
120880
120973
|
mkdirSync7(opencodePluginDir, { recursive: true });
|
|
120881
120974
|
writeFileSync7(
|
|
120882
|
-
|
|
120883
|
-
buildOpencodeSubagentGateSource(ctx
|
|
120975
|
+
join9(opencodePluginDir, PULLFROG_OPENCODE_GATE_PLUGIN_FILENAME),
|
|
120976
|
+
buildOpencodeSubagentGateSource(ctx)
|
|
120884
120977
|
);
|
|
120885
120978
|
const agentBrowserVersion = getDevDependencyVersion("agent-browser");
|
|
120886
120979
|
addSkill({
|
|
@@ -120890,8 +120983,6 @@ var opencode = agent({
|
|
|
120890
120983
|
agent: "opencode"
|
|
120891
120984
|
});
|
|
120892
120985
|
installBundledSkills({ home: homeEnv.HOME });
|
|
120893
|
-
const codexAuth = installCodexAuth();
|
|
120894
|
-
const xaiAuth = installXaiAuth();
|
|
120895
120986
|
const permissionOverride = JSON.stringify({
|
|
120896
120987
|
external_directory: { "*": "deny", "/tmp/*": "allow" },
|
|
120897
120988
|
read: { "*": "allow", ...GIT_NATIVE_READ_DENY_OPENCODE },
|
|
@@ -120910,7 +121001,6 @@ var opencode = agent({
|
|
|
120910
121001
|
delete env2.OPENCODE_EXPERIMENTAL_CODE_MODE;
|
|
120911
121002
|
const writebacks = [];
|
|
120912
121003
|
if (codexAuth) {
|
|
120913
|
-
env2.XDG_DATA_HOME = codexAuth.xdgDataHome;
|
|
120914
121004
|
delete env2.OPENAI_API_KEY;
|
|
120915
121005
|
writebacks.push({
|
|
120916
121006
|
secretName: "CODEX_AUTH_JSON",
|
|
@@ -120921,7 +121011,6 @@ var opencode = agent({
|
|
|
120921
121011
|
});
|
|
120922
121012
|
}
|
|
120923
121013
|
if (xaiAuth) {
|
|
120924
|
-
env2.XDG_DATA_HOME = xaiAuth.xdgDataHome;
|
|
120925
121014
|
delete env2.XAI_API_KEY;
|
|
120926
121015
|
writebacks.push({
|
|
120927
121016
|
secretName: "GROK_AUTH_JSON",
|
|
@@ -120994,6 +121083,7 @@ var opencode = agent({
|
|
|
120994
121083
|
mcpToolCalls: 0,
|
|
120995
121084
|
recentStderr: server.recentStderr,
|
|
120996
121085
|
serverExitCause: server.exitCause,
|
|
121086
|
+
lastSessionError: void 0,
|
|
120997
121087
|
diagnostic: {
|
|
120998
121088
|
label: "Pullfrog",
|
|
120999
121089
|
recentStderr: server.recentStderr,
|
|
@@ -175912,7 +176002,7 @@ function closeBrowserDaemon(toolState) {
|
|
|
175912
176002
|
// mcp/checkout.ts
|
|
175913
176003
|
import { createHash as createHash2 } from "node:crypto";
|
|
175914
176004
|
import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync11 } from "node:fs";
|
|
175915
|
-
import { join as
|
|
176005
|
+
import { join as join17 } from "node:path";
|
|
175916
176006
|
|
|
175917
176007
|
// node_modules/.pnpm/@ark+schema@0.56.0/node_modules/@ark/schema/out/shared/errors.js
|
|
175918
176008
|
var ArkError = class _ArkError extends CastableBase {
|
|
@@ -184283,13 +184373,14 @@ function ensureRepoState(toolState, init) {
|
|
|
184283
184373
|
// utils/changeImpact.ts
|
|
184284
184374
|
import { spawn as spawn4 } from "node:child_process";
|
|
184285
184375
|
import { writeFileSync as writeFileSync8 } from "node:fs";
|
|
184286
|
-
import { join as
|
|
184376
|
+
import { join as join10 } from "node:path";
|
|
184287
184377
|
var MAX_CANDIDATES = 24;
|
|
184288
184378
|
var MAX_ATOM_LENGTH = 128;
|
|
184289
184379
|
var MAX_ATOMS = 12;
|
|
184290
184380
|
var MAX_REFERENCES = 6;
|
|
184291
184381
|
var MAX_EXCERPT = 160;
|
|
184292
184382
|
var MAX_BYTES = 16e3;
|
|
184383
|
+
var LOOKUP_BUDGET_MS = 6e4;
|
|
184293
184384
|
function isSymbolShaped(atom) {
|
|
184294
184385
|
return atom.length >= 4 && atom.length <= MAX_ATOM_LENGTH && /[A-Z_]/.test(atom);
|
|
184295
184386
|
}
|
|
@@ -184492,7 +184583,7 @@ function consumeGrepChunk(params) {
|
|
|
184492
184583
|
async function lookupReferences(params) {
|
|
184493
184584
|
const buckets = emptyBuckets(params.candidates);
|
|
184494
184585
|
if (params.candidates.length === 0) return { buckets, error: void 0 };
|
|
184495
|
-
const
|
|
184586
|
+
const pattern = params.candidates.map((candidate) => candidate.atom).join("|");
|
|
184496
184587
|
const child = spawn4(
|
|
184497
184588
|
"git",
|
|
184498
184589
|
[
|
|
@@ -184503,13 +184594,14 @@ async function lookupReferences(params) {
|
|
|
184503
184594
|
"-z",
|
|
184504
184595
|
"-I",
|
|
184505
184596
|
"-w",
|
|
184506
|
-
"-
|
|
184507
|
-
|
|
184597
|
+
"-E",
|
|
184598
|
+
"-e",
|
|
184599
|
+
pattern,
|
|
184508
184600
|
params.treeish,
|
|
184509
184601
|
"--",
|
|
184510
184602
|
":(top)"
|
|
184511
184603
|
],
|
|
184512
|
-
{ env: resolveEnv(void 0), stdio: ["ignore", "pipe", "pipe"] }
|
|
184604
|
+
{ env: resolveEnv(void 0), stdio: ["ignore", "pipe", "pipe"], signal: params.signal }
|
|
184513
184605
|
);
|
|
184514
184606
|
const state = createGrepRecordState();
|
|
184515
184607
|
const maxAtomLength = Math.max(...params.candidates.map((candidate) => candidate.atom.length));
|
|
@@ -184529,7 +184621,7 @@ async function lookupReferences(params) {
|
|
|
184529
184621
|
child.stderr.resume();
|
|
184530
184622
|
const status = await new Promise((resolve3) => {
|
|
184531
184623
|
child.once("error", (error49) => {
|
|
184532
|
-
spawnError = error49.message;
|
|
184624
|
+
spawnError = error49.cause instanceof Error ? error49.cause.message : error49.message;
|
|
184533
184625
|
resolve3(null);
|
|
184534
184626
|
});
|
|
184535
184627
|
child.once("close", resolve3);
|
|
@@ -184617,8 +184709,10 @@ async function createChangeImpactArtifact(ctx, params) {
|
|
|
184617
184709
|
const lookup2 = await lookupReferences({
|
|
184618
184710
|
candidates: candidates.selected,
|
|
184619
184711
|
addedLines: candidates.addedLines,
|
|
184620
|
-
treeish: params.headSha
|
|
184712
|
+
treeish: params.headSha,
|
|
184713
|
+
signal: AbortSignal.any([params.signal, AbortSignal.timeout(LOOKUP_BUDGET_MS)])
|
|
184621
184714
|
});
|
|
184715
|
+
params.signal.throwIfAborted();
|
|
184622
184716
|
const artifact = formatArtifact({
|
|
184623
184717
|
entries: candidates.selected.map((change) => {
|
|
184624
184718
|
return { change, bucket: lookup2.buckets.get(change.atom) ?? { count: 0, samples: [] } };
|
|
@@ -184630,7 +184724,7 @@ async function createChangeImpactArtifact(ctx, params) {
|
|
|
184630
184724
|
headSha: params.headSha,
|
|
184631
184725
|
lookupError: lookup2.error
|
|
184632
184726
|
});
|
|
184633
|
-
const path4 =
|
|
184727
|
+
const path4 = join10(ctx.tmpdir, `pr-${params.pullNumber}-${params.headSha.slice(0, 7)}-impact.md`);
|
|
184634
184728
|
writeFileSync8(path4, artifact.content);
|
|
184635
184729
|
return {
|
|
184636
184730
|
path: path4,
|
|
@@ -184884,7 +184978,7 @@ function readNumber(params) {
|
|
|
184884
184978
|
import { execSync } from "node:child_process";
|
|
184885
184979
|
import { createHash } from "node:crypto";
|
|
184886
184980
|
import { readFileSync as readFileSync6, realpathSync, unlinkSync } from "node:fs";
|
|
184887
|
-
import { join as
|
|
184981
|
+
import { join as join11 } from "node:path";
|
|
184888
184982
|
|
|
184889
184983
|
// utils/shell.ts
|
|
184890
184984
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
@@ -184974,7 +185068,7 @@ function resolveHooksDir(cwd, gitPath) {
|
|
|
184974
185068
|
cwd,
|
|
184975
185069
|
log: false
|
|
184976
185070
|
}).trim();
|
|
184977
|
-
const hooksDir =
|
|
185071
|
+
const hooksDir = join11(commonDir, "hooks");
|
|
184978
185072
|
hooksDirCache.set(cwd, hooksDir);
|
|
184979
185073
|
return hooksDir;
|
|
184980
185074
|
}
|
|
@@ -185021,7 +185115,8 @@ async function $git(subcommand, args2, options) {
|
|
|
185021
185115
|
GIT_CONFIG_COUNT: "0",
|
|
185022
185116
|
GIT_CONFIG_PARAMETERS: ""
|
|
185023
185117
|
},
|
|
185024
|
-
activityTimeout: 0
|
|
185118
|
+
activityTimeout: 0,
|
|
185119
|
+
signal: options.signal
|
|
185025
185120
|
});
|
|
185026
185121
|
if (result.stderr.includes("askpass-compromised")) {
|
|
185027
185122
|
log2.info("askpass code was replayed after revoke \u2014 token has been revoked");
|
|
@@ -185088,9 +185183,10 @@ import { spawnSync as spawnSync8 } from "node:child_process";
|
|
|
185088
185183
|
// mcp/shell.ts
|
|
185089
185184
|
import { spawn as spawn5, spawnSync as spawnSync7 } from "node:child_process";
|
|
185090
185185
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
185091
|
-
import {
|
|
185186
|
+
import { once } from "node:events";
|
|
185187
|
+
import { closeSync, existsSync as existsSync9, openSync, writeFileSync as writeFileSync9 } from "node:fs";
|
|
185092
185188
|
import { userInfo as userInfo2 } from "node:os";
|
|
185093
|
-
import { join as
|
|
185189
|
+
import { join as join12 } from "node:path";
|
|
185094
185190
|
import { setTimeout as sleep3 } from "node:timers/promises";
|
|
185095
185191
|
|
|
185096
185192
|
// node_modules/.pnpm/@toon-format+toon@1.4.0/node_modules/@toon-format/toon/dist/index.mjs
|
|
@@ -185734,7 +185830,7 @@ function buildFsMounts(repoDir) {
|
|
|
185734
185830
|
function shimMount() {
|
|
185735
185831
|
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
185736
185832
|
if (!tempDir) return "";
|
|
185737
|
-
const dir =
|
|
185833
|
+
const dir = join12(tempDir, "pm-bin").replace(/'/g, "'\\''");
|
|
185738
185834
|
return `[ -d '${dir}' ] && mount --bind '${dir}' '${dir}' 2>/dev/null && mount -o remount,bind,ro '${dir}' 2>/dev/null;`;
|
|
185739
185835
|
}
|
|
185740
185836
|
var _repoRoot;
|
|
@@ -185905,7 +186001,7 @@ ${spawnError}` : spawnError;
|
|
|
185905
186001
|
}
|
|
185906
186002
|
function capOutput(output) {
|
|
185907
186003
|
if (output.length <= MAX_OUTPUT_CHARS) return output;
|
|
185908
|
-
const fullPath =
|
|
186004
|
+
const fullPath = join12(getTempDir(), `shell-${randomUUID3().slice(0, 8)}.log`);
|
|
185909
186005
|
writeFileSync9(fullPath, output);
|
|
185910
186006
|
const elided = output.length - MAX_OUTPUT_CHARS;
|
|
185911
186007
|
return `... [${elided} chars truncated; output saved to ${fullPath}] ...
|
|
@@ -185942,6 +186038,7 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
|
|
|
185942
186038
|
}
|
|
185943
186039
|
const timeout = Math.min(params.timeout ?? 3e4, 12e4);
|
|
185944
186040
|
const cwd = params.working_directory ?? process.cwd();
|
|
186041
|
+
if (!existsSync9(cwd)) throw new Error(`working_directory does not exist: ${cwd}`);
|
|
185945
186042
|
const env2 = resolveEnv(ctx.payload.shell === "enabled" ? "inherit" : "restricted");
|
|
185946
186043
|
if (params.command.includes("agent-browser")) {
|
|
185947
186044
|
const daemonError = ensureBrowserDaemon(ctx.toolState);
|
|
@@ -185960,8 +186057,8 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
|
|
|
185960
186057
|
if (params.background) {
|
|
185961
186058
|
const tempDir = getTempDir();
|
|
185962
186059
|
const handle = `bg-${randomUUID3().slice(0, 8)}`;
|
|
185963
|
-
const outputPath =
|
|
185964
|
-
const pidPath =
|
|
186060
|
+
const outputPath = join12(tempDir, `${handle}.log`);
|
|
186061
|
+
const pidPath = join12(tempDir, `${handle}.pid`);
|
|
185965
186062
|
const logFd = openSync(outputPath, "a");
|
|
185966
186063
|
let proc;
|
|
185967
186064
|
try {
|
|
@@ -185975,7 +186072,8 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
|
|
|
185975
186072
|
closeSync(logFd);
|
|
185976
186073
|
}
|
|
185977
186074
|
if (!proc.pid) {
|
|
185978
|
-
|
|
186075
|
+
const [error49] = await once(proc, "error");
|
|
186076
|
+
throw new Error(`failed to start background process: ${error49.message}`);
|
|
185979
186077
|
}
|
|
185980
186078
|
proc.unref();
|
|
185981
186079
|
writeFileSync9(pidPath, `${proc.pid}
|
|
@@ -186252,12 +186350,12 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
|
|
|
186252
186350
|
// mcp/git.ts
|
|
186253
186351
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
186254
186352
|
import { writeFileSync as writeFileSync10 } from "node:fs";
|
|
186255
|
-
import { join as
|
|
186353
|
+
import { join as join15 } from "node:path";
|
|
186256
186354
|
|
|
186257
186355
|
// utils/apiCommit.ts
|
|
186258
186356
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
186259
186357
|
import { lstat, readlink } from "node:fs/promises";
|
|
186260
|
-
import { join as
|
|
186358
|
+
import { join as join13 } from "node:path";
|
|
186261
186359
|
var GITHUB_API = "https://api.github.com";
|
|
186262
186360
|
var MAX_BLOB_BYTES = 30 * 1024 * 1024;
|
|
186263
186361
|
var BLOB_UPLOAD_CONCURRENCY = 8;
|
|
@@ -186322,7 +186420,7 @@ async function assertApiCommittable(files) {
|
|
|
186322
186420
|
const root = getRepoRoot();
|
|
186323
186421
|
const attrs = $(
|
|
186324
186422
|
"git",
|
|
186325
|
-
["check-attr", "filter", "-z", "--", ...present.map((p) =>
|
|
186423
|
+
["check-attr", "filter", "-z", "--", ...present.map((p) => join13(root, p))],
|
|
186326
186424
|
{ log: false }
|
|
186327
186425
|
);
|
|
186328
186426
|
const parts = attrs.split("\0");
|
|
@@ -186334,7 +186432,7 @@ async function assertApiCommittable(files) {
|
|
|
186334
186432
|
}
|
|
186335
186433
|
}
|
|
186336
186434
|
for (const path4 of present) {
|
|
186337
|
-
const stat = await lstat(
|
|
186435
|
+
const stat = await lstat(join13(root, path4));
|
|
186338
186436
|
if (stat.isDirectory()) {
|
|
186339
186437
|
throw new Error(
|
|
186340
186438
|
`'${path4}' is a directory (nested repository or submodule?) \u2014 signed commits only support files and symlinks.`
|
|
@@ -186343,7 +186441,7 @@ async function assertApiCommittable(files) {
|
|
|
186343
186441
|
}
|
|
186344
186442
|
}
|
|
186345
186443
|
async function createBlobEntry(params) {
|
|
186346
|
-
const absPath =
|
|
186444
|
+
const absPath = join13(params.repoRoot, params.path);
|
|
186347
186445
|
const stat = await lstat(absPath);
|
|
186348
186446
|
if (stat.size > MAX_BLOB_BYTES) {
|
|
186349
186447
|
throw new Error(
|
|
@@ -186523,7 +186621,7 @@ this usually means your local branch has commits that were never pushed \u2014 s
|
|
|
186523
186621
|
var core4 = __toESM(require_core(), 1);
|
|
186524
186622
|
import { createSign } from "node:crypto";
|
|
186525
186623
|
import { rename, writeFile } from "node:fs/promises";
|
|
186526
|
-
import { dirname as dirname3, join as
|
|
186624
|
+
import { dirname as dirname3, join as join14 } from "node:path";
|
|
186527
186625
|
|
|
186528
186626
|
// node_modules/.pnpm/@octokit+plugin-throttling@11.0.5_@octokit+core@7.0.8/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
|
|
186529
186627
|
var import_light = __toESM(require_light(), 1);
|
|
@@ -191072,7 +191170,7 @@ function getGitHubUsageSummary() {
|
|
|
191072
191170
|
}
|
|
191073
191171
|
async function writeGitHubUsageSummaryToFile(path4) {
|
|
191074
191172
|
const summary2 = getGitHubUsageSummary();
|
|
191075
|
-
const tmpPath =
|
|
191173
|
+
const tmpPath = join14(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
|
|
191076
191174
|
await writeFile(tmpPath, JSON.stringify(summary2));
|
|
191077
191175
|
await rename(tmpPath, path4);
|
|
191078
191176
|
}
|
|
@@ -191700,7 +191798,7 @@ function countAhead(head, base, cwd) {
|
|
|
191700
191798
|
function spillGitOutput(params) {
|
|
191701
191799
|
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
191702
191800
|
if (!tempDir) throw new Error("PULLFROG_TEMP_DIR not set");
|
|
191703
|
-
const outputPath =
|
|
191801
|
+
const outputPath = join15(tempDir, `git-${params.command}-${randomUUID4().slice(0, 8)}.txt`);
|
|
191704
191802
|
writeFileSync10(outputPath, params.output);
|
|
191705
191803
|
const previewByLines = params.output.split("\n").slice(0, OVERFLOW_PREVIEW_LINES).join("\n");
|
|
191706
191804
|
const preview = previewByLines.length <= OVERFLOW_PREVIEW_MAX_CHARS ? previewByLines : `${previewByLines.slice(0, OVERFLOW_PREVIEW_MAX_CHARS)}\u2026`;
|
|
@@ -193355,11 +193453,11 @@ async function reportReviewNodeId(ctx, params) {
|
|
|
193355
193453
|
|
|
193356
193454
|
// utils/setup.ts
|
|
193357
193455
|
import { execFileSync as execFileSync7, execSync as execSync2 } from "node:child_process";
|
|
193358
|
-
import { mkdtempSync as
|
|
193456
|
+
import { mkdtempSync as mkdtempSync3, readdirSync, realpathSync as realpathSync2, rmSync as rmSync4, unlinkSync as unlinkSync2 } from "node:fs";
|
|
193359
193457
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
193360
|
-
import { join as
|
|
193458
|
+
import { join as join16 } from "node:path";
|
|
193361
193459
|
function createTempDirectory() {
|
|
193362
|
-
const path4 =
|
|
193460
|
+
const path4 = mkdtempSync3(join16(tmpdir3(), "pullfrog-"));
|
|
193363
193461
|
process.env.PULLFROG_TEMP_DIR = path4;
|
|
193364
193462
|
log2.info(`\xBB created temp dir at ${path4}`);
|
|
193365
193463
|
return { path: path4, [Symbol.dispose]: () => removeTempDirectory(path4) };
|
|
@@ -193367,10 +193465,10 @@ function createTempDirectory() {
|
|
|
193367
193465
|
function removeTempDirectory(path4) {
|
|
193368
193466
|
try {
|
|
193369
193467
|
try {
|
|
193370
|
-
|
|
193468
|
+
rmSync4(path4, { recursive: true, force: true });
|
|
193371
193469
|
} catch {
|
|
193372
193470
|
execFileSync7("chmod", ["-R", "u+w", path4]);
|
|
193373
|
-
|
|
193471
|
+
rmSync4(path4, { recursive: true, force: true });
|
|
193374
193472
|
}
|
|
193375
193473
|
log2.debug(`\xBB removed temp dir at ${path4}`);
|
|
193376
193474
|
} catch (err) {
|
|
@@ -193419,13 +193517,13 @@ function wipeRunnerLeakSurface() {
|
|
|
193419
193517
|
return [];
|
|
193420
193518
|
}
|
|
193421
193519
|
};
|
|
193422
|
-
const fileCommandsDir =
|
|
193520
|
+
const fileCommandsDir = join16(runnerTemp, "_runner_file_commands");
|
|
193423
193521
|
for (const entry of listDir(fileCommandsDir)) {
|
|
193424
|
-
tryUnlink(
|
|
193522
|
+
tryUnlink(join16(fileCommandsDir, entry));
|
|
193425
193523
|
}
|
|
193426
193524
|
for (const entry of listDir(runnerTemp)) {
|
|
193427
193525
|
if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
|
|
193428
|
-
tryUnlink(
|
|
193526
|
+
tryUnlink(join16(runnerTemp, entry));
|
|
193429
193527
|
}
|
|
193430
193528
|
}
|
|
193431
193529
|
if (wiped.length > 0) {
|
|
@@ -193697,7 +193795,7 @@ async function ensureBeforeShaReachable(params) {
|
|
|
193697
193795
|
"origin",
|
|
193698
193796
|
tempBranch
|
|
193699
193797
|
],
|
|
193700
|
-
{ token: params.gitToken, refreshGitToken: params.refreshGitToken },
|
|
193798
|
+
{ token: params.gitToken, refreshGitToken: params.refreshGitToken, signal: params.signal },
|
|
193701
193799
|
`before_sha temp branch ${tempBranch}`
|
|
193702
193800
|
);
|
|
193703
193801
|
log2.debug(`\xBB fetched before_sha via temp branch ${tempBranch}`);
|
|
@@ -193738,7 +193836,7 @@ function cleanupStaleGitLocks() {
|
|
|
193738
193836
|
} catch {
|
|
193739
193837
|
continue;
|
|
193740
193838
|
}
|
|
193741
|
-
if (now - mtimeMs < STALE_LOCK_AGE_MS) continue;
|
|
193839
|
+
if (now - mtimeMs < STALE_LOCK_AGE_MS || mtimeMs >= performance.timeOrigin) continue;
|
|
193742
193840
|
try {
|
|
193743
193841
|
unlinkSync3(relPath);
|
|
193744
193842
|
log2.warning(`\xBB removed stale ${relPath} from prior run`);
|
|
@@ -193771,7 +193869,7 @@ async function abortIfPullRequestMoved(args2) {
|
|
|
193771
193869
|
);
|
|
193772
193870
|
}
|
|
193773
193871
|
async function checkoutPrBranch(pr, params) {
|
|
193774
|
-
const { octokit, owner, name, gitToken, refreshGitToken, toolState, beforeSha } = params;
|
|
193872
|
+
const { octokit, owner, name, gitToken, refreshGitToken, toolState, beforeSha, signal } = params;
|
|
193775
193873
|
rejectIfLeadingDash(pr.baseRef, "PR base ref");
|
|
193776
193874
|
rejectIfLeadingDash(pr.headRef, "PR head ref");
|
|
193777
193875
|
const repoState = requireRepoState(toolState, owner, name);
|
|
@@ -193785,7 +193883,7 @@ async function checkoutPrBranch(pr, params) {
|
|
|
193785
193883
|
log2.debug(`\xBB fetching base branch (${pr.baseRef})...`);
|
|
193786
193884
|
await $gitFetchWithDeepen(
|
|
193787
193885
|
["--no-tags", "origin", pr.baseRef],
|
|
193788
|
-
{ token: gitToken, refreshGitToken },
|
|
193886
|
+
{ token: gitToken, refreshGitToken, signal },
|
|
193789
193887
|
`base branch ${pr.baseRef}`
|
|
193790
193888
|
);
|
|
193791
193889
|
if (!alreadyOnBranch) {
|
|
@@ -193796,7 +193894,7 @@ async function checkoutPrBranch(pr, params) {
|
|
|
193796
193894
|
try {
|
|
193797
193895
|
await $gitFetchWithDeepen(
|
|
193798
193896
|
["--no-tags", "origin", `+pull/${pr.number}/head:${localBranch}`],
|
|
193799
|
-
{ token: gitToken, refreshGitToken },
|
|
193897
|
+
{ token: gitToken, refreshGitToken, signal },
|
|
193800
193898
|
`PR #${pr.number}`
|
|
193801
193899
|
);
|
|
193802
193900
|
} catch (e) {
|
|
@@ -193821,7 +193919,8 @@ async function checkoutPrBranch(pr, params) {
|
|
|
193821
193919
|
repo: name,
|
|
193822
193920
|
gitToken,
|
|
193823
193921
|
refreshGitToken,
|
|
193824
|
-
isShallow
|
|
193922
|
+
isShallow,
|
|
193923
|
+
signal
|
|
193825
193924
|
}) : false;
|
|
193826
193925
|
if (isShallow) {
|
|
193827
193926
|
let deepenDepth = 0;
|
|
@@ -193856,7 +193955,8 @@ async function checkoutPrBranch(pr, params) {
|
|
|
193856
193955
|
if (deepenDepth) {
|
|
193857
193956
|
log2.debug(`\xBB deepening by ${deepenDepth} to reach merge base...`);
|
|
193858
193957
|
await $git("fetch", [`--deepen=${deepenDepth}`, "--no-tags", "origin"], {
|
|
193859
|
-
token: gitToken
|
|
193958
|
+
token: gitToken,
|
|
193959
|
+
signal
|
|
193860
193960
|
});
|
|
193861
193961
|
}
|
|
193862
193962
|
}
|
|
@@ -193894,6 +193994,7 @@ async function checkoutPrBranch(pr, params) {
|
|
|
193894
193994
|
remoteBranch: pr.headRef,
|
|
193895
193995
|
localBranch
|
|
193896
193996
|
};
|
|
193997
|
+
signal.throwIfAborted();
|
|
193897
193998
|
const postCheckoutHook = await executeLifecycleHook({
|
|
193898
193999
|
event: "post-checkout",
|
|
193899
194000
|
script: params.postCheckoutScript,
|
|
@@ -193904,19 +194005,30 @@ async function checkoutPrBranch(pr, params) {
|
|
|
193904
194005
|
}
|
|
193905
194006
|
var inFlightCheckouts = /* @__PURE__ */ new Map();
|
|
193906
194007
|
var CHECKOUT_DEADLINE_MS = 57e4;
|
|
193907
|
-
|
|
193908
|
-
|
|
193909
|
-
const
|
|
193910
|
-
|
|
194008
|
+
var CHECKOUT_ABORT_GRACE_MS = 15e3;
|
|
194009
|
+
function withCheckoutDeadline(run4, pullNumber) {
|
|
194010
|
+
const exceeded = `checkout_pr #${pullNumber} exceeded ${CHECKOUT_DEADLINE_MS / 1e3}s`;
|
|
194011
|
+
const retry = "retry the call to start a fresh attempt.";
|
|
194012
|
+
const controller = new AbortController();
|
|
194013
|
+
const abortTimer = setTimeout(
|
|
194014
|
+
() => controller.abort(new Error(`${exceeded} and was cancelled; ${retry}`)),
|
|
194015
|
+
CHECKOUT_DEADLINE_MS
|
|
194016
|
+
);
|
|
194017
|
+
let backstopTimer;
|
|
194018
|
+
const backstop = new Promise((_, reject) => {
|
|
194019
|
+
backstopTimer = setTimeout(
|
|
193911
194020
|
() => reject(
|
|
193912
194021
|
new Error(
|
|
193913
|
-
|
|
194022
|
+
`${exceeded} and was asked to cancel, but a step that cannot be interrupted (the post-checkout hook or a GitHub API call) was still running ${CHECKOUT_ABORT_GRACE_MS / 1e3}s later. ${retry}`
|
|
193914
194023
|
)
|
|
193915
194024
|
),
|
|
193916
|
-
CHECKOUT_DEADLINE_MS
|
|
194025
|
+
CHECKOUT_DEADLINE_MS + CHECKOUT_ABORT_GRACE_MS
|
|
193917
194026
|
);
|
|
193918
194027
|
});
|
|
193919
|
-
return Promise.race([
|
|
194028
|
+
return Promise.race([run4(controller.signal), backstop]).finally(() => {
|
|
194029
|
+
clearTimeout(abortTimer);
|
|
194030
|
+
clearTimeout(backstopTimer);
|
|
194031
|
+
});
|
|
193920
194032
|
}
|
|
193921
194033
|
function headsEqual(a2, b) {
|
|
193922
194034
|
if (a2.kind === "branch" && b.kind === "branch") return a2.name === b.name;
|
|
@@ -193928,7 +194040,7 @@ function describeHead(h2) {
|
|
|
193928
194040
|
return `detached HEAD \`${h2.sha}\``;
|
|
193929
194041
|
}
|
|
193930
194042
|
function CheckoutPrTool(ctx) {
|
|
193931
|
-
const runCheckout = async (pull_number) => {
|
|
194043
|
+
const runCheckout = async (pull_number, signal) => {
|
|
193932
194044
|
const prResponse = await ctx.octokit.rest.pulls.get({
|
|
193933
194045
|
owner: ctx.repo.owner,
|
|
193934
194046
|
repo: ctx.repo.name,
|
|
@@ -193957,7 +194069,8 @@ function CheckoutPrTool(ctx) {
|
|
|
193957
194069
|
toolState: ctx.toolState,
|
|
193958
194070
|
shell: ctx.payload.shell,
|
|
193959
194071
|
postCheckoutScript: ctx.postCheckoutScript,
|
|
193960
|
-
beforeSha: primary.beforeSha
|
|
194072
|
+
beforeSha: primary.beforeSha,
|
|
194073
|
+
signal
|
|
193961
194074
|
});
|
|
193962
194075
|
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
193963
194076
|
if (!tempDir) {
|
|
@@ -193979,7 +194092,7 @@ function CheckoutPrTool(ctx) {
|
|
|
193979
194092
|
headSha: checkoutSha
|
|
193980
194093
|
});
|
|
193981
194094
|
if (incremental.status === "ok") {
|
|
193982
|
-
incrementalDiffPath =
|
|
194095
|
+
incrementalDiffPath = join17(
|
|
193983
194096
|
tempDir,
|
|
193984
194097
|
`pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
|
|
193985
194098
|
);
|
|
@@ -193996,7 +194109,7 @@ function CheckoutPrTool(ctx) {
|
|
|
193996
194109
|
const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
|
|
193997
194110
|
log2.debug(`formatted diff preview (first 100 lines):
|
|
193998
194111
|
${diffPreview}`);
|
|
193999
|
-
const diffPath =
|
|
194112
|
+
const diffPath = join17(tempDir, `pr-${pull_number}-${headShort}.diff`);
|
|
194000
194113
|
writeFileSync11(diffPath, formatResult.content);
|
|
194001
194114
|
log2.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
|
|
194002
194115
|
primary.diffCoverage = createDiffCoverageState({
|
|
@@ -194036,7 +194149,8 @@ ${diffPreview}`);
|
|
|
194036
194149
|
files: formatResult.files,
|
|
194037
194150
|
pullNumber: pull_number,
|
|
194038
194151
|
baseSha: prResponse.data.base.sha,
|
|
194039
|
-
headSha: checkoutSha
|
|
194152
|
+
headSha: checkoutSha,
|
|
194153
|
+
signal
|
|
194040
194154
|
});
|
|
194041
194155
|
impactPath = impact.path;
|
|
194042
194156
|
log2.info(
|
|
@@ -194142,7 +194256,10 @@ ${dirty}`
|
|
|
194142
194256
|
);
|
|
194143
194257
|
}
|
|
194144
194258
|
}
|
|
194145
|
-
const promise2 = withCheckoutDeadline(
|
|
194259
|
+
const promise2 = withCheckoutDeadline(
|
|
194260
|
+
(signal) => runCheckout(pull_number, signal),
|
|
194261
|
+
pull_number
|
|
194262
|
+
);
|
|
194146
194263
|
inFlightCheckouts.set(pull_number, promise2);
|
|
194147
194264
|
try {
|
|
194148
194265
|
return await promise2;
|
|
@@ -194155,7 +194272,7 @@ ${dirty}`
|
|
|
194155
194272
|
|
|
194156
194273
|
// mcp/checkSuite.ts
|
|
194157
194274
|
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync12 } from "node:fs";
|
|
194158
|
-
import { join as
|
|
194275
|
+
import { join as join18 } from "node:path";
|
|
194159
194276
|
var GetCheckSuiteLogs = type({
|
|
194160
194277
|
check_suite_id: type.number.describe("the id from check_suite.id")
|
|
194161
194278
|
});
|
|
@@ -194251,7 +194368,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
194251
194368
|
if (!tempDir) {
|
|
194252
194369
|
throw new Error("PULLFROG_TEMP_DIR not set");
|
|
194253
194370
|
}
|
|
194254
|
-
const logsDir =
|
|
194371
|
+
const logsDir = join18(tempDir, "ci-logs");
|
|
194255
194372
|
mkdirSync8(logsDir, { recursive: true });
|
|
194256
194373
|
const jobResults = [];
|
|
194257
194374
|
for (const run4 of failedRuns) {
|
|
@@ -194278,7 +194395,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
194278
194395
|
);
|
|
194279
194396
|
}
|
|
194280
194397
|
const logsText = await logsResult.text();
|
|
194281
|
-
const logPath =
|
|
194398
|
+
const logPath = join18(logsDir, `job-${job.id}.log`);
|
|
194282
194399
|
writeFileSync12(logPath, logsText);
|
|
194283
194400
|
const analysis = analyzeLog(logsText, 80);
|
|
194284
194401
|
const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
|
|
@@ -194328,7 +194445,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
194328
194445
|
|
|
194329
194446
|
// mcp/commitInfo.ts
|
|
194330
194447
|
import { writeFileSync as writeFileSync13 } from "node:fs";
|
|
194331
|
-
import { join as
|
|
194448
|
+
import { join as join19 } from "node:path";
|
|
194332
194449
|
var CommitInfo = type({
|
|
194333
194450
|
sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
|
|
194334
194451
|
});
|
|
@@ -194352,7 +194469,7 @@ function CommitInfoTool(ctx) {
|
|
|
194352
194469
|
"PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
|
|
194353
194470
|
);
|
|
194354
194471
|
}
|
|
194355
|
-
const diffFile =
|
|
194472
|
+
const diffFile = join19(tempDir, `commit-${sha.slice(0, 7)}.diff`);
|
|
194356
194473
|
writeFileSync13(diffFile, formatResult.content);
|
|
194357
194474
|
log2.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
|
|
194358
194475
|
return {
|
|
@@ -194489,14 +194606,14 @@ function ReopenCurrentTool(ctx) {
|
|
|
194489
194606
|
import { performance as performance8 } from "node:perf_hooks";
|
|
194490
194607
|
|
|
194491
194608
|
// prep/installNodeDependencies.ts
|
|
194492
|
-
import { existsSync as
|
|
194493
|
-
import { join as
|
|
194609
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
194610
|
+
import { join as join21 } from "node:path";
|
|
194494
194611
|
|
|
194495
194612
|
// utils/packageManager.ts
|
|
194496
194613
|
var import_semver2 = __toESM(require_semver2(), 1);
|
|
194497
|
-
import { existsSync as
|
|
194614
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
194498
194615
|
import { mkdir, readFile as readFile3 } from "node:fs/promises";
|
|
194499
|
-
import { delimiter as delimiter2, join as
|
|
194616
|
+
import { delimiter as delimiter2, join as join20 } from "node:path";
|
|
194500
194617
|
var SUPPORTED_NAMES = ["npm", "pnpm", "yarn", "bun"];
|
|
194501
194618
|
var COREPACK_MANAGED = ["pnpm", "yarn"];
|
|
194502
194619
|
function isSupported(name) {
|
|
@@ -194534,8 +194651,8 @@ function parseDevEnginesField(field) {
|
|
|
194534
194651
|
};
|
|
194535
194652
|
}
|
|
194536
194653
|
async function resolvePackageManagerSpec(cwd) {
|
|
194537
|
-
const pkgPath =
|
|
194538
|
-
if (!
|
|
194654
|
+
const pkgPath = join20(cwd, "package.json");
|
|
194655
|
+
if (!existsSync10(pkgPath)) return null;
|
|
194539
194656
|
let pkg;
|
|
194540
194657
|
try {
|
|
194541
194658
|
pkg = JSON.parse(await readFile3(pkgPath, "utf-8"));
|
|
@@ -194593,7 +194710,7 @@ async function currentVersion(name) {
|
|
|
194593
194710
|
return result.stdout.trim();
|
|
194594
194711
|
}
|
|
194595
194712
|
function packageManagerBinDir(tmpdir4) {
|
|
194596
|
-
return
|
|
194713
|
+
return join20(tmpdir4, "pm-bin");
|
|
194597
194714
|
}
|
|
194598
194715
|
async function ensurePackageManager(params) {
|
|
194599
194716
|
const spec = params.spec;
|
|
@@ -194656,7 +194773,7 @@ async function installFallback(name, installSpec) {
|
|
|
194656
194773
|
});
|
|
194657
194774
|
if (result.exitCode !== 0) return result.stderr || `failed to install ${name}`;
|
|
194658
194775
|
if (name === "deno") {
|
|
194659
|
-
const denoPath =
|
|
194776
|
+
const denoPath = join20(process.env.HOME || "", ".deno", "bin");
|
|
194660
194777
|
process.env.PATH = `${denoPath}:${process.env.PATH}`;
|
|
194661
194778
|
}
|
|
194662
194779
|
log2.info(`\xBB installed ${name}`);
|
|
@@ -194689,8 +194806,8 @@ async function isCommandAvailable2(command) {
|
|
|
194689
194806
|
var installNodeDependencies = {
|
|
194690
194807
|
name: "installNodeDependencies",
|
|
194691
194808
|
shouldRun: () => {
|
|
194692
|
-
const packageJsonPath =
|
|
194693
|
-
return
|
|
194809
|
+
const packageJsonPath = join21(process.cwd(), "package.json");
|
|
194810
|
+
return existsSync11(packageJsonPath);
|
|
194694
194811
|
},
|
|
194695
194812
|
run: async (options) => {
|
|
194696
194813
|
const declared = await resolvePackageManagerSpec(process.cwd());
|
|
@@ -194784,14 +194901,14 @@ ${errorMessage}`]
|
|
|
194784
194901
|
};
|
|
194785
194902
|
|
|
194786
194903
|
// prep/installPythonDependencies.ts
|
|
194787
|
-
import { existsSync as
|
|
194788
|
-
import { join as
|
|
194904
|
+
import { existsSync as existsSync12, readFileSync as readFileSync7 } from "node:fs";
|
|
194905
|
+
import { join as join22 } from "node:path";
|
|
194789
194906
|
function declaresBuildSystem(path4) {
|
|
194790
194907
|
return /^\s*\[\s*build-system\s*\]/m.test(readFileSync7(path4, "utf8"));
|
|
194791
194908
|
}
|
|
194792
194909
|
function configApplies(config3, cwd) {
|
|
194793
|
-
const path4 =
|
|
194794
|
-
return
|
|
194910
|
+
const path4 = join22(cwd, config3.file);
|
|
194911
|
+
return existsSync12(path4) && (config3.applies?.(path4) ?? true);
|
|
194795
194912
|
}
|
|
194796
194913
|
var PYTHON_CONFIGS = [
|
|
194797
194914
|
{
|
|
@@ -195113,7 +195230,7 @@ function AwaitDependencyInstallationTool(ctx) {
|
|
|
195113
195230
|
|
|
195114
195231
|
// mcp/gh.ts
|
|
195115
195232
|
import { mkdirSync as mkdirSync9 } from "node:fs";
|
|
195116
|
-
import { join as
|
|
195233
|
+
import { join as join23 } from "node:path";
|
|
195117
195234
|
var GH_TIMEOUT_MS = 12e4;
|
|
195118
195235
|
var GhParams = type({
|
|
195119
195236
|
args: type.string.array().describe(
|
|
@@ -195135,7 +195252,7 @@ function GhTool(ctx) {
|
|
|
195135
195252
|
description: `Run the GitHub CLI against ${ctx.repo.owner}/${ctx.repo.name}. Pass argv as an array \u2014 there is no shell, so pipes, redirects and \`&&\` do not work; use gh's own \`--json\`/\`--jq\` flags instead. \`gh api graphql\` is available. Authenticated with ${permissions ? formatPermissions(permissions) : "no permissions"} on this repo only; anything else 403s. Pushing, merging and ref writes are not available \u2014 use the git tools.`,
|
|
195136
195253
|
parameters: GhParams,
|
|
195137
195254
|
execute: execute(async (params) => {
|
|
195138
|
-
const configDir =
|
|
195255
|
+
const configDir = join23(ctx.tmpdir, "gh-config");
|
|
195139
195256
|
mkdirSync9(configDir, { recursive: true });
|
|
195140
195257
|
const command = ["gh", ...params.args.map(shellQuote)].join(" ");
|
|
195141
195258
|
const result = await runSandboxed({
|
|
@@ -195859,7 +195976,7 @@ function PullRequestInfoTool(ctx) {
|
|
|
195859
195976
|
|
|
195860
195977
|
// mcp/reviewComments.ts
|
|
195861
195978
|
import { writeFileSync as writeFileSync14 } from "node:fs";
|
|
195862
|
-
import { join as
|
|
195979
|
+
import { join as join24 } from "node:path";
|
|
195863
195980
|
var COMMENTS_PER_THREAD = 50;
|
|
195864
195981
|
var REVIEW_THREADS_QUERY = `
|
|
195865
195982
|
query ($owner: String!, $name: String!, $prNumber: Int!, $cursor: String) {
|
|
@@ -196353,7 +196470,7 @@ function GetReviewCommentsTool(ctx) {
|
|
|
196353
196470
|
throw new Error("PULLFROG_TEMP_DIR not set");
|
|
196354
196471
|
}
|
|
196355
196472
|
const filename = `review-${params.review_id}-threads.md`;
|
|
196356
|
-
const commentsPath =
|
|
196473
|
+
const commentsPath = join24(tempDir, filename);
|
|
196357
196474
|
writeFileSync14(commentsPath, formatted.content);
|
|
196358
196475
|
log2.debug(`wrote ${threadBlocks.length} threads to ${commentsPath}`);
|
|
196359
196476
|
return {
|
|
@@ -196722,8 +196839,8 @@ function UploadFileTool(ctx) {
|
|
|
196722
196839
|
}
|
|
196723
196840
|
|
|
196724
196841
|
// mcp/xrepo.ts
|
|
196725
|
-
import { mkdirSync as mkdirSync10, rmSync as
|
|
196726
|
-
import { join as
|
|
196842
|
+
import { mkdirSync as mkdirSync10, rmSync as rmSync5 } from "node:fs";
|
|
196843
|
+
import { join as join25 } from "node:path";
|
|
196727
196844
|
function assertValidRepoName(name) {
|
|
196728
196845
|
if (!/^[A-Za-z0-9._-]+$/.test(name) || name.startsWith("-") || name.includes("..")) {
|
|
196729
196846
|
throw new Error(
|
|
@@ -196803,7 +196920,7 @@ function CheckoutRepoTool(ctx) {
|
|
|
196803
196920
|
return { path: existing.dir, access: existing.access, note: "already checked out." };
|
|
196804
196921
|
}
|
|
196805
196922
|
const access = accessFor(ctx, repo);
|
|
196806
|
-
const dir =
|
|
196923
|
+
const dir = join25(ctx.tmpdir, "xrepo", repo);
|
|
196807
196924
|
mkdirSync10(dir, { recursive: true });
|
|
196808
196925
|
const state = ensureRepoState(ctx.toolState, { owner, name: repo, dir, access });
|
|
196809
196926
|
const rc = resolveRepoCtx(ctx, repo);
|
|
@@ -196834,7 +196951,7 @@ function CheckoutRepoTool(ctx) {
|
|
|
196834
196951
|
return { path: dir, access, defaultBranch };
|
|
196835
196952
|
} catch (err) {
|
|
196836
196953
|
ctx.toolState.repos.delete(repoKey(owner, repo));
|
|
196837
|
-
|
|
196954
|
+
rmSync5(dir, { recursive: true, force: true });
|
|
196838
196955
|
throw err;
|
|
196839
196956
|
}
|
|
196840
196957
|
})
|
|
@@ -197169,6 +197286,7 @@ function isByokSetupError(text) {
|
|
|
197169
197286
|
var SECRETS_UNAVAILABLE_MARKER = "couldn't load your Pullfrog secrets";
|
|
197170
197287
|
var ROUTER_UNFUNDED_MARKER = "your Pullfrog Router balance is empty";
|
|
197171
197288
|
var CREDENTIAL_REJECTED_MARKER = "was rejected by its provider";
|
|
197289
|
+
var CREDENTIAL_POOL_REFUSED_MARKER = "No configured credential can serve this run";
|
|
197172
197290
|
var NoUsableCredentialError = class extends Error {
|
|
197173
197291
|
};
|
|
197174
197292
|
function throwKeyError(params) {
|
|
@@ -197435,7 +197553,8 @@ function isOAuthCredentialExpiredError(text) {
|
|
|
197435
197553
|
text
|
|
197436
197554
|
) || // the provider no longer recognises the token at all (#1086) — same dead
|
|
197437
197555
|
// credential, phrased as a lookup miss rather than a state transition.
|
|
197438
|
-
/Could not find the appropriate key in your authentication token/i.test(text) ||
|
|
197556
|
+
/Could not find the appropriate key in your authentication token/i.test(text) || // the reversed word order: `Encountered invalidated oauth token for user` (#1474).
|
|
197557
|
+
/(?:expired|invalidated|revoked) (?:OAuth|authentication) token/i.test(text) || /Token refresh failed/i.test(text)
|
|
197439
197558
|
);
|
|
197440
197559
|
}
|
|
197441
197560
|
function isMalformedKeyError(text) {
|
|
@@ -197482,7 +197601,7 @@ function buildRejectedCredentialError(params) {
|
|
|
197482
197601
|
].join("\n");
|
|
197483
197602
|
}
|
|
197484
197603
|
const where = params.inPullfrogStore ? `[Update it in Pullfrog \u2192](${settingsUrl})` : `[Update the GitHub Actions secret \u2192](https://github.com/${params.owner}/${params.name}/settings/secrets/actions)`;
|
|
197485
|
-
const keysUrl =
|
|
197604
|
+
const keysUrl = providerDashboard(params.credential)?.keys;
|
|
197486
197605
|
const issue3 = keysUrl ? `[Issue a new key \u2192](${keysUrl}) \xB7 ` : "";
|
|
197487
197606
|
return [
|
|
197488
197607
|
`**Your \`${params.credential}\` ${CREDENTIAL_REJECTED_MARKER}**${detail}, so the agent never ran.`,
|
|
@@ -197492,12 +197611,43 @@ function buildRejectedCredentialError(params) {
|
|
|
197492
197611
|
`${issue3}${where} \xB7 [Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/keys) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
|
|
197493
197612
|
].join("\n");
|
|
197494
197613
|
}
|
|
197495
|
-
function
|
|
197614
|
+
function providerDashboard(credential) {
|
|
197496
197615
|
for (const [id, provider2] of Object.entries(providers)) {
|
|
197497
|
-
if (provider2.envVars.includes(credential)) return PROVIDER_DASHBOARDS[id]
|
|
197616
|
+
if (provider2.envVars.includes(credential)) return PROVIDER_DASHBOARDS[id];
|
|
197498
197617
|
}
|
|
197499
197618
|
return void 0;
|
|
197500
197619
|
}
|
|
197620
|
+
function buildCredentialPoolRefusedError(params) {
|
|
197621
|
+
const settingsUrl = `${getApiUrl()}/console/${params.owner}/${params.name}`;
|
|
197622
|
+
return [
|
|
197623
|
+
`**${CREDENTIAL_POOL_REFUSED_MARKER}.** Every credential configured for \`${params.model}\` was rejected or is out of quota, so the agent never ran.`,
|
|
197624
|
+
"",
|
|
197625
|
+
...params.refused.map(
|
|
197626
|
+
(credential) => `- ${describeRefusal({ ...params, credential, settingsUrl })}`
|
|
197627
|
+
),
|
|
197628
|
+
"",
|
|
197629
|
+
`[Model settings \u2192](${settingsUrl}) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
|
|
197630
|
+
].join("\n");
|
|
197631
|
+
}
|
|
197632
|
+
function describeRefusal(params) {
|
|
197633
|
+
const credential = params.credential;
|
|
197634
|
+
const verdict = credential.verdict;
|
|
197635
|
+
const workflow = credential.source === "workflow";
|
|
197636
|
+
const head = `\`${credential.name}\` from ${workflow ? "the workflow" : `${credential.source} scope`}: \`${verdict.detail}\` \u2014`;
|
|
197637
|
+
const subscription = SUBSCRIPTION_CREDENTIALS[credential.name];
|
|
197638
|
+
const dashboard = providerDashboard(credential.name);
|
|
197639
|
+
if (verdict.status === "exhausted" && subscription) {
|
|
197640
|
+
const wait = verdict.resetAt ? `it resets at **${verdict.resetAt.toISOString().slice(0, 16).replace("T", " ")} UTC**; re-trigger after that` : "re-trigger after the limit resets";
|
|
197641
|
+
return `${head} ${wait}, or add an API key for this provider, which Pullfrog falls back to automatically.`;
|
|
197642
|
+
}
|
|
197643
|
+
if (verdict.status === "exhausted")
|
|
197644
|
+
return `${head} add credit or raise its quota with the provider.${dashboard ? ` [Top up \u2192](${dashboard.billing})` : ""}`;
|
|
197645
|
+
if (subscription)
|
|
197646
|
+
return `${head} re-authenticate with \`${subscription.command}\`, or switch this repo to a model you hold an API key for. [Re-authenticate \u2192](${subscription.docs})`;
|
|
197647
|
+
const where = workflow ? `[Update the GitHub Actions secret \u2192](https://github.com/${params.owner}/${params.name}/settings/secrets/actions)` : `[Update it in Pullfrog \u2192](${params.settingsUrl})`;
|
|
197648
|
+
const issue3 = dashboard?.keys ? `[Issue a new key \u2192](${dashboard.keys}) \xB7 ` : "";
|
|
197649
|
+
return `${head} issue a new key and update the copy Pullfrog uses. ${issue3}${where}`;
|
|
197650
|
+
}
|
|
197501
197651
|
function formatApiKeyErrorSummary(params) {
|
|
197502
197652
|
if (params.raw.includes(MISSING_KEY_MARKER)) {
|
|
197503
197653
|
if (params.raw.startsWith(`**${MISSING_KEY_MARKER}**`)) return params.raw;
|
|
@@ -198227,11 +198377,16 @@ async function selectConfiguredCredential(input) {
|
|
|
198227
198377
|
if (option.candidate) log2.info(`\xBB selected ${option.name} from ${from}`);
|
|
198228
198378
|
return true;
|
|
198229
198379
|
}
|
|
198230
|
-
refused.push(
|
|
198380
|
+
refused.push({ name: option.name, source: option.candidate?.source ?? "workflow", verdict });
|
|
198231
198381
|
log2.info(`\xBB ${option.name} from ${from} unavailable; trying the next credential`);
|
|
198232
198382
|
}
|
|
198233
198383
|
throw new Error(
|
|
198234
|
-
|
|
198384
|
+
buildCredentialPoolRefusedError({
|
|
198385
|
+
model,
|
|
198386
|
+
refused,
|
|
198387
|
+
owner: input.ctx.repo.owner,
|
|
198388
|
+
name: input.ctx.repo.name
|
|
198389
|
+
})
|
|
198235
198390
|
);
|
|
198236
198391
|
}
|
|
198237
198392
|
async function probe(input) {
|
|
@@ -198272,7 +198427,7 @@ function activate(input) {
|
|
|
198272
198427
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
198273
198428
|
import { writeFileSync as writeFileSync15 } from "node:fs";
|
|
198274
198429
|
import { createServer as createServer3 } from "node:http";
|
|
198275
|
-
import { join as
|
|
198430
|
+
import { join as join26 } from "node:path";
|
|
198276
198431
|
var REVOKED_TRAP_MS = 6e4;
|
|
198277
198432
|
function revokeGitHubToken(token) {
|
|
198278
198433
|
fetch("https://api.github.com/installation/token", {
|
|
@@ -198341,7 +198496,7 @@ async function startGitAuthServer(tmpdir4) {
|
|
|
198341
198496
|
function writeAskpassScript(code) {
|
|
198342
198497
|
const scriptId = randomUUID6();
|
|
198343
198498
|
const scriptName = `askpass-${scriptId}.js`;
|
|
198344
|
-
const scriptPath =
|
|
198499
|
+
const scriptPath = join26(tmpdir4, scriptName);
|
|
198345
198500
|
const content = [
|
|
198346
198501
|
`#!/usr/bin/env node`,
|
|
198347
198502
|
`var a=process.argv[2]||"";`,
|
|
@@ -198790,7 +198945,7 @@ function resolveInstructions(ctx) {
|
|
|
198790
198945
|
|
|
198791
198946
|
// utils/learnings.ts
|
|
198792
198947
|
import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
|
|
198793
|
-
import { dirname as dirname4, join as
|
|
198948
|
+
import { dirname as dirname4, join as join27 } from "node:path";
|
|
198794
198949
|
|
|
198795
198950
|
// utils/learningsTruncate.ts
|
|
198796
198951
|
var MAX_LEARNINGS_LENGTH = 1e5;
|
|
@@ -198808,10 +198963,10 @@ function truncateAtLineBoundary(body, cap) {
|
|
|
198808
198963
|
var LEARNINGS_FILE_NAME = "pullfrog-learnings.md";
|
|
198809
198964
|
var XREPO_LEARNINGS_FILE_NAME = "pullfrog-xrepo-learnings.md";
|
|
198810
198965
|
function learningsFilePath(tmpdir4) {
|
|
198811
|
-
return
|
|
198966
|
+
return join27(tmpdir4, LEARNINGS_FILE_NAME);
|
|
198812
198967
|
}
|
|
198813
198968
|
function xrepoLearningsFilePath(tmpdir4) {
|
|
198814
|
-
return
|
|
198969
|
+
return join27(tmpdir4, XREPO_LEARNINGS_FILE_NAME);
|
|
198815
198970
|
}
|
|
198816
198971
|
async function seedLearningsFile(params) {
|
|
198817
198972
|
const path4 = learningsFilePath(params.tmpdir);
|
|
@@ -199362,7 +199517,9 @@ async function resolveTokens(params) {
|
|
|
199362
199517
|
// commit-status check-runs for branch protection. the app already grants
|
|
199363
199518
|
// checks:write; this scopes the MCP token up to use it.
|
|
199364
199519
|
checks: "write",
|
|
199365
|
-
|
|
199520
|
+
// write (not read) so postReviewCleanup can dispatch the follow-up re-review
|
|
199521
|
+
// for commits pushed while a review was in flight; read 403s it.
|
|
199522
|
+
actions: "write"
|
|
199366
199523
|
};
|
|
199367
199524
|
const mcpToken = await acquireNewToken({
|
|
199368
199525
|
repos: writeRepos,
|
|
@@ -199748,7 +199905,7 @@ async function resolveTrialFallback(ctx) {
|
|
|
199748
199905
|
|
|
199749
199906
|
// utils/prSummary.ts
|
|
199750
199907
|
import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from "node:fs/promises";
|
|
199751
|
-
import { dirname as dirname5, join as
|
|
199908
|
+
import { dirname as dirname5, join as join28 } from "node:path";
|
|
199752
199909
|
var SUMMARY_FILE_NAME = "pullfrog-summary.md";
|
|
199753
199910
|
var SUMMARY_SCAFFOLD = `# PR summary
|
|
199754
199911
|
|
|
@@ -199758,7 +199915,7 @@ var SUMMARY_SCAFFOLD = `# PR summary
|
|
|
199758
199915
|
var MIN_SNAPSHOT_LENGTH = 60;
|
|
199759
199916
|
var MAX_SNAPSHOT_LENGTH = 32768;
|
|
199760
199917
|
function summaryFilePath(tmpdir4) {
|
|
199761
|
-
return
|
|
199918
|
+
return join28(tmpdir4, SUMMARY_FILE_NAME);
|
|
199762
199919
|
}
|
|
199763
199920
|
async function seedSummaryFile(params) {
|
|
199764
199921
|
const path4 = summaryFilePath(params.tmpdir);
|
|
@@ -200027,7 +200184,7 @@ function formatBillingExhaustedBody(diagnostic) {
|
|
|
200027
200184
|
|
|
200028
200185
|
// utils/runErrorRenderer.ts
|
|
200029
200186
|
function isProviderModelNotFoundError(message) {
|
|
200030
|
-
return message
|
|
200187
|
+
return /ProviderModelNotFoundError|\bModel not found: /.test(message);
|
|
200031
200188
|
}
|
|
200032
200189
|
function isContextOverflowError(message) {
|
|
200033
200190
|
return /Prompt is too long/i.test(message) || /Input exceeds context window/i.test(message) || /maximum context length is \d+ tokens/i.test(message) || /Session too large to compact/i.test(message);
|
|
@@ -200124,6 +200281,7 @@ function detectProviderId(message) {
|
|
|
200124
200281
|
if (/requires more credits|Key limit exceeded \(total limit\)|openrouter\.ai/i.test(message)) {
|
|
200125
200282
|
return "openrouter";
|
|
200126
200283
|
}
|
|
200284
|
+
if (/platform\.openai\.com/i.test(message)) return "openai";
|
|
200127
200285
|
return null;
|
|
200128
200286
|
}
|
|
200129
200287
|
var OPENROUTER_PRIVACY_URL = "https://openrouter.ai/settings/privacy";
|
|
@@ -200221,12 +200379,38 @@ function formatByokSetupSummary(input) {
|
|
|
200221
200379
|
].join("\n");
|
|
200222
200380
|
}
|
|
200223
200381
|
function formatProviderModelNotFoundSummary(input) {
|
|
200224
|
-
return `The configured model is
|
|
200382
|
+
return `The configured model is not in OpenCode's catalog. Pick a different model for \`${input.owner}/${input.name}\` where it is set \u2014 the Pullfrog console, the workflow's \`model\` input, or the \`PULLFROG_MODEL\` variable \u2014 or contact support if this persists.
|
|
200225
200383
|
|
|
200226
200384
|
\`\`\`
|
|
200227
200385
|
${input.raw}
|
|
200228
200386
|
\`\`\``;
|
|
200229
200387
|
}
|
|
200388
|
+
function formatProviderUsageLimit(input) {
|
|
200389
|
+
return [
|
|
200390
|
+
"**The provider's usage limit for this account has been reached.**",
|
|
200391
|
+
"",
|
|
200392
|
+
"A subscription window resets on its own, so re-trigger after it does; a limit set on a pay-as-you-go workspace has to be raised. A model on another provider can run in the meantime.",
|
|
200393
|
+
"",
|
|
200394
|
+
`[Model settings \u2192](${getApiUrl()}/console/${input.owner}/${input.name}) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`,
|
|
200395
|
+
"",
|
|
200396
|
+
`\`\`\`
|
|
200397
|
+
${input.raw}
|
|
200398
|
+
\`\`\``
|
|
200399
|
+
].join("\n");
|
|
200400
|
+
}
|
|
200401
|
+
function formatProviderErrorBody(input) {
|
|
200402
|
+
return [
|
|
200403
|
+
"**The model provider returned an error, so this run stopped.**",
|
|
200404
|
+
"",
|
|
200405
|
+
"The error is below. If it names your key, plan or balance, fix that with the provider or pick another model before re-triggering.",
|
|
200406
|
+
"",
|
|
200407
|
+
`[Model settings \u2192](${getApiUrl()}/console/${input.owner}/${input.name}) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`,
|
|
200408
|
+
"",
|
|
200409
|
+
`\`\`\`
|
|
200410
|
+
${input.raw}
|
|
200411
|
+
\`\`\``
|
|
200412
|
+
].join("\n");
|
|
200413
|
+
}
|
|
200230
200414
|
function renderRunError(input) {
|
|
200231
200415
|
const billingError = input.routerActive && isRouterKeylimitExhaustedError(input.errorMessage) ? new BillingError(input.errorMessage, { code: "router_keylimit_exhausted" }) : null;
|
|
200232
200416
|
if (billingError) {
|
|
@@ -200242,7 +200426,7 @@ function renderRunError(input) {
|
|
|
200242
200426
|
if (input.errorMessage.includes(ROUTER_UNFUNDED_MARKER)) {
|
|
200243
200427
|
return { summary: input.errorMessage, comment: input.errorMessage };
|
|
200244
200428
|
}
|
|
200245
|
-
if (input.errorMessage.includes(CREDENTIAL_REJECTED_MARKER)) {
|
|
200429
|
+
if (input.errorMessage.includes(CREDENTIAL_REJECTED_MARKER) || input.errorMessage.includes(CREDENTIAL_POOL_REFUSED_MARKER)) {
|
|
200246
200430
|
return { summary: input.errorMessage, comment: input.errorMessage };
|
|
200247
200431
|
}
|
|
200248
200432
|
const isHang = input.errorMessage.startsWith("activity timeout") || input.errorMessage.startsWith("agent still pending");
|
|
@@ -200265,6 +200449,16 @@ ${body}`, comment: body };
|
|
|
200265
200449
|
});
|
|
200266
200450
|
return { summary: `### \u274C Pullfrog failed
|
|
200267
200451
|
|
|
200452
|
+
${body}`, comment: body };
|
|
200453
|
+
}
|
|
200454
|
+
if (isProviderUsageLimit(input.errorMessage)) {
|
|
200455
|
+
const body = formatProviderUsageLimit({
|
|
200456
|
+
owner: input.repo.owner,
|
|
200457
|
+
name: input.repo.name,
|
|
200458
|
+
raw: input.errorMessage
|
|
200459
|
+
});
|
|
200460
|
+
return { summary: `### \u274C Pullfrog failed
|
|
200461
|
+
|
|
200268
200462
|
${body}`, comment: body };
|
|
200269
200463
|
}
|
|
200270
200464
|
if (isProviderMissingCredential(input.errorMessage)) {
|
|
@@ -200359,6 +200553,16 @@ ${hangBody}`,
|
|
|
200359
200553
|
comment: explained ? hangBody : formatMinimalFailureComment(input.repo)
|
|
200360
200554
|
};
|
|
200361
200555
|
}
|
|
200556
|
+
if (input.errorMessage.startsWith("provider error: ")) {
|
|
200557
|
+
const body = formatProviderErrorBody({
|
|
200558
|
+
owner: input.repo.owner,
|
|
200559
|
+
name: input.repo.name,
|
|
200560
|
+
raw: input.errorMessage
|
|
200561
|
+
});
|
|
200562
|
+
return { summary: `### \u274C Pullfrog failed
|
|
200563
|
+
|
|
200564
|
+
${body}`, comment: body };
|
|
200565
|
+
}
|
|
200362
200566
|
const genericBody = formatGenericFailure(input.errorMessage);
|
|
200363
200567
|
return {
|
|
200364
200568
|
summary: `### \u274C Pullfrog failed
|
|
@@ -201169,7 +201373,7 @@ async function main() {
|
|
|
201169
201373
|
const repoDir = payload.cwd;
|
|
201170
201374
|
const preIntrospectionDirty = repoDir ? await dirtyTrackedPaths({ cwd: repoDir }) : null;
|
|
201171
201375
|
const opencodeCliPath = await agents.opencode.install();
|
|
201172
|
-
captureBaselineModels(opencodeCliPath);
|
|
201376
|
+
captureBaselineModels({ cliPath: opencodeCliPath, tmpdir: tmpdir4 });
|
|
201173
201377
|
const injected = [];
|
|
201174
201378
|
if (runContext.dbSecrets) {
|
|
201175
201379
|
for (const [key, value2] of Object.entries(runContext.dbSecrets)) {
|
|
@@ -201186,7 +201390,7 @@ async function main() {
|
|
|
201186
201390
|
await initializeCredentialPool({ ctx: runContext, slug: payload.model, injected });
|
|
201187
201391
|
installCodexAuth();
|
|
201188
201392
|
installXaiAuth();
|
|
201189
|
-
captureAuthorizedModels(opencodeCliPath);
|
|
201393
|
+
captureAuthorizedModels({ cliPath: opencodeCliPath, tmpdir: tmpdir4 });
|
|
201190
201394
|
if (preIntrospectionDirty && repoDir) {
|
|
201191
201395
|
await restoreDirtiedSince({
|
|
201192
201396
|
before: preIntrospectionDirty,
|
|
@@ -201524,8 +201728,8 @@ ${instructions.user}` : null,
|
|
|
201524
201728
|
log2.info(instructions.full);
|
|
201525
201729
|
});
|
|
201526
201730
|
if (agentId === "opencode") {
|
|
201527
|
-
const pluginDir =
|
|
201528
|
-
const hasPlugins =
|
|
201731
|
+
const pluginDir = join29(process.cwd(), ".opencode", "plugin");
|
|
201732
|
+
const hasPlugins = existsSync13(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
|
|
201529
201733
|
if (hasPlugins && toolState.dependencyInstallation?.promise) {
|
|
201530
201734
|
log2.info(
|
|
201531
201735
|
"\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"
|
|
@@ -202134,7 +202338,7 @@ async function run2() {
|
|
|
202134
202338
|
// commands/_prEvents.ts
|
|
202135
202339
|
import { mkdirSync as mkdirSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync16 } from "node:fs";
|
|
202136
202340
|
import { homedir as homedir3 } from "node:os";
|
|
202137
|
-
import { join as
|
|
202341
|
+
import { join as join30 } from "node:path";
|
|
202138
202342
|
var REQUEST_TIMEOUT_MS = 35e3;
|
|
202139
202343
|
var SERVER_POLL_WINDOW_MS = 2e4;
|
|
202140
202344
|
var PrEventsAuthError = class extends Error {
|
|
@@ -202183,11 +202387,11 @@ async function pollPrEvents(ctx) {
|
|
|
202183
202387
|
}
|
|
202184
202388
|
}
|
|
202185
202389
|
function stateDir() {
|
|
202186
|
-
return process.env.PULLFROG_STATE_DIR ||
|
|
202390
|
+
return process.env.PULLFROG_STATE_DIR || join30(homedir3(), ".pullfrog");
|
|
202187
202391
|
}
|
|
202188
202392
|
function cursorPath(target) {
|
|
202189
202393
|
const key = [target.owner, target.repo, String(target.pr)].map(encodeURIComponent).join("__");
|
|
202190
|
-
return
|
|
202394
|
+
return join30(stateDir(), "watch", `${key}.cursor`);
|
|
202191
202395
|
}
|
|
202192
202396
|
function readCursor(target) {
|
|
202193
202397
|
try {
|
|
@@ -202200,7 +202404,7 @@ function readCursor(target) {
|
|
|
202200
202404
|
function writeCursor(target, cursor3) {
|
|
202201
202405
|
try {
|
|
202202
202406
|
const path4 = cursorPath(target);
|
|
202203
|
-
mkdirSync11(
|
|
202407
|
+
mkdirSync11(join30(stateDir(), "watch"), { recursive: true });
|
|
202204
202408
|
writeFileSync16(path4, cursor3, "utf-8");
|
|
202205
202409
|
} catch {
|
|
202206
202410
|
}
|
|
@@ -202255,7 +202459,7 @@ function resolveTarget2(repo) {
|
|
|
202255
202459
|
return { owner: match3[1], repo: match3[2] };
|
|
202256
202460
|
}
|
|
202257
202461
|
function cliVersion() {
|
|
202258
|
-
const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.
|
|
202462
|
+
const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.98");
|
|
202259
202463
|
if (!parts) return "0.0.0";
|
|
202260
202464
|
return `${Number(parts[1])}.${Number(parts[2])}.${Number(parts[3])}`;
|
|
202261
202465
|
}
|
|
@@ -202422,7 +202626,7 @@ async function runCli8(input) {
|
|
|
202422
202626
|
}
|
|
202423
202627
|
|
|
202424
202628
|
// cli.ts
|
|
202425
|
-
var VERSION10 = "0.1.
|
|
202629
|
+
var VERSION10 = "0.1.98";
|
|
202426
202630
|
var bin = basename2(process.argv[1] || "");
|
|
202427
202631
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
202428
202632
|
var rawArgs = process.argv.slice(2);
|