pullfrog 0.1.29 → 0.1.30
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/subagentToolGates.d.ts +1 -1
- package/dist/cli.mjs +831 -209
- package/dist/index.js +822 -200
- package/dist/internal.js +30 -31
- package/dist/mcp/git.d.ts +7 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/modes.d.ts +1 -1
- package/dist/toolState.d.ts +0 -3
- package/dist/utils/apiCommit.d.ts +40 -0
- package/dist/utils/apiKeys.d.ts +15 -0
- package/dist/utils/buildPullfrogFooter.d.ts +0 -7
- package/dist/utils/claudeSubscription.d.ts +30 -0
- package/dist/utils/github.d.ts +27 -1
- package/dist/utils/instructions.d.ts +3 -0
- package/dist/utils/openCodeModels.d.ts +2 -2
- package/dist/utils/proxy.d.ts +3 -5
- package/dist/utils/runContext.d.ts +1 -0
- package/dist/utils/runErrorRenderer.d.ts +2 -2
- package/dist/utils/token.d.ts +12 -1
- package/package.json +1 -1
- package/dist/utils/byokFallback.d.ts +0 -44
package/dist/cli.mjs
CHANGED
|
@@ -19991,10 +19991,10 @@ var require_core = __commonJS({
|
|
|
19991
19991
|
(0, command_1.issueCommand)("set-env", { name }, convertedVal);
|
|
19992
19992
|
}
|
|
19993
19993
|
exports.exportVariable = exportVariable;
|
|
19994
|
-
function
|
|
19994
|
+
function setSecret7(secret) {
|
|
19995
19995
|
(0, command_1.issueCommand)("add-mask", {}, secret);
|
|
19996
19996
|
}
|
|
19997
|
-
exports.setSecret =
|
|
19997
|
+
exports.setSecret = setSecret7;
|
|
19998
19998
|
function addPath(inputPath) {
|
|
19999
19999
|
const filePath = process.env["GITHUB_PATH"] || "";
|
|
20000
20000
|
if (filePath) {
|
|
@@ -20111,12 +20111,12 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
|
|
|
20111
20111
|
return process.env[`STATE_${name}`] || "";
|
|
20112
20112
|
}
|
|
20113
20113
|
exports.getState = getState2;
|
|
20114
|
-
function
|
|
20114
|
+
function getIDToken3(aud) {
|
|
20115
20115
|
return __awaiter(this, void 0, void 0, function* () {
|
|
20116
20116
|
return yield oidc_utils_1.OidcClient.getIDToken(aud);
|
|
20117
20117
|
});
|
|
20118
20118
|
}
|
|
20119
|
-
exports.getIDToken =
|
|
20119
|
+
exports.getIDToken = getIDToken3;
|
|
20120
20120
|
var summary_1 = require_summary();
|
|
20121
20121
|
Object.defineProperty(exports, "summary", { enumerable: true, get: function() {
|
|
20122
20122
|
return summary_1.summary;
|
|
@@ -93765,14 +93765,14 @@ var require_turndown_cjs = __commonJS({
|
|
|
93765
93765
|
} else if (node2.nodeType === 1) {
|
|
93766
93766
|
replacement = replacementForNode.call(self2, node2);
|
|
93767
93767
|
}
|
|
93768
|
-
return
|
|
93768
|
+
return join25(output, replacement);
|
|
93769
93769
|
}, "");
|
|
93770
93770
|
}
|
|
93771
93771
|
function postProcess(output) {
|
|
93772
93772
|
var self2 = this;
|
|
93773
93773
|
this.rules.forEach(function(rule) {
|
|
93774
93774
|
if (typeof rule.append === "function") {
|
|
93775
|
-
output =
|
|
93775
|
+
output = join25(output, rule.append(self2.options));
|
|
93776
93776
|
}
|
|
93777
93777
|
});
|
|
93778
93778
|
return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
|
|
@@ -93784,7 +93784,7 @@ var require_turndown_cjs = __commonJS({
|
|
|
93784
93784
|
if (whitespace.leading || whitespace.trailing) content = content.trim();
|
|
93785
93785
|
return whitespace.leading + rule.replacement(content, node2, this.options) + whitespace.trailing;
|
|
93786
93786
|
}
|
|
93787
|
-
function
|
|
93787
|
+
function join25(output, replacement) {
|
|
93788
93788
|
var s1 = trimTrailingNewlines(output);
|
|
93789
93789
|
var s2 = trimLeadingNewlines(replacement);
|
|
93790
93790
|
var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
|
|
@@ -100726,7 +100726,7 @@ import { dirname as dirname6 } from "node:path";
|
|
|
100726
100726
|
// main.ts
|
|
100727
100727
|
import { existsSync as existsSync8, readdirSync as readdirSync2 } from "node:fs";
|
|
100728
100728
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
100729
|
-
import { join as
|
|
100729
|
+
import { join as join24 } from "node:path";
|
|
100730
100730
|
|
|
100731
100731
|
// agents/claude.ts
|
|
100732
100732
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
@@ -100743,11 +100743,20 @@ var providers = {
|
|
|
100743
100743
|
displayName: "Anthropic",
|
|
100744
100744
|
envVars: ["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"],
|
|
100745
100745
|
models: {
|
|
100746
|
+
// OpenRouter serves claude-fable-5, but models.dev's OpenRouter mirror
|
|
100747
|
+
// hasn't indexed it yet (shipped 2026-06-09), so the catalog drift gate
|
|
100748
|
+
// can't validate an openRouterResolve. omit it until the mirror catches
|
|
100749
|
+
// up; direct BYOK / Claude Code resolves anthropic/claude-fable-5 fine.
|
|
100750
|
+
"claude-fable": {
|
|
100751
|
+
displayName: "Claude Fable",
|
|
100752
|
+
resolve: "anthropic/claude-fable-5",
|
|
100753
|
+
preferred: true,
|
|
100754
|
+
subagentModel: "claude-sonnet"
|
|
100755
|
+
},
|
|
100746
100756
|
"claude-opus": {
|
|
100747
100757
|
displayName: "Claude Opus",
|
|
100748
100758
|
resolve: "anthropic/claude-opus-4-8",
|
|
100749
100759
|
openRouterResolve: "openrouter/anthropic/claude-opus-4.8",
|
|
100750
|
-
preferred: true,
|
|
100751
100760
|
subagentModel: "claude-sonnet"
|
|
100752
100761
|
},
|
|
100753
100762
|
"claude-sonnet": {
|
|
@@ -100813,7 +100822,8 @@ var providers = {
|
|
|
100813
100822
|
},
|
|
100814
100823
|
o3: {
|
|
100815
100824
|
displayName: "O3",
|
|
100816
|
-
resolve: "openai/o3"
|
|
100825
|
+
resolve: "openai/o3",
|
|
100826
|
+
openRouterResolve: "openrouter/openai/o3"
|
|
100817
100827
|
}
|
|
100818
100828
|
}
|
|
100819
100829
|
}),
|
|
@@ -101183,6 +101193,18 @@ function parseModel(slug2) {
|
|
|
101183
101193
|
function getModelProvider(slug2) {
|
|
101184
101194
|
return parseModel(slug2).provider;
|
|
101185
101195
|
}
|
|
101196
|
+
function getModelEnvVars(slug2) {
|
|
101197
|
+
const parsed2 = parseModel(slug2);
|
|
101198
|
+
const providerConfig = providers[parsed2.provider];
|
|
101199
|
+
if (!providerConfig) {
|
|
101200
|
+
return [];
|
|
101201
|
+
}
|
|
101202
|
+
const modelConfig = providerConfig.models[parsed2.model];
|
|
101203
|
+
if (modelConfig?.envVars) {
|
|
101204
|
+
return modelConfig.envVars.slice();
|
|
101205
|
+
}
|
|
101206
|
+
return providerConfig.envVars.slice();
|
|
101207
|
+
}
|
|
101186
101208
|
var modelAliases = Object.entries(providers).flatMap(
|
|
101187
101209
|
([providerKey, config3]) => Object.entries(config3.models).map(([modelId, def]) => ({
|
|
101188
101210
|
slug: `${providerKey}/${modelId}`,
|
|
@@ -101207,6 +101229,9 @@ if (!defaultProxyAlias?.openRouterResolve) {
|
|
|
101207
101229
|
}
|
|
101208
101230
|
var DEFAULT_PROXY_MODEL = defaultProxyAlias.openRouterResolve;
|
|
101209
101231
|
var defaultProxyDisplayName = defaultProxyAlias.displayName;
|
|
101232
|
+
function resolveModelSlug(slug2) {
|
|
101233
|
+
return modelAliases.find((a) => a.slug === slug2)?.resolve;
|
|
101234
|
+
}
|
|
101210
101235
|
var MAX_FALLBACK_DEPTH = 10;
|
|
101211
101236
|
function resolveDisplayAlias(slug2) {
|
|
101212
101237
|
let current = slug2;
|
|
@@ -101354,6 +101379,51 @@ function createProcessOutputActivityTimeout(ctx) {
|
|
|
101354
101379
|
};
|
|
101355
101380
|
}
|
|
101356
101381
|
|
|
101382
|
+
// utils/claudeSubscription.ts
|
|
101383
|
+
var CLAUDE_CODE_IDENTITY = "You are Claude Code, Anthropic's official CLI for Claude.";
|
|
101384
|
+
var fallbackResolve = resolveModelSlug("anthropic/claude-haiku");
|
|
101385
|
+
if (!fallbackResolve) {
|
|
101386
|
+
throw new Error("claudeSubscription preflight: anthropic/claude-haiku missing from registry");
|
|
101387
|
+
}
|
|
101388
|
+
var FALLBACK_PROBE_MODEL = fallbackResolve.slice(fallbackResolve.indexOf("/") + 1);
|
|
101389
|
+
async function preflightClaudeSubscription(params) {
|
|
101390
|
+
let res;
|
|
101391
|
+
try {
|
|
101392
|
+
res = await fetch("https://api.anthropic.com/v1/messages", {
|
|
101393
|
+
method: "POST",
|
|
101394
|
+
headers: {
|
|
101395
|
+
authorization: `Bearer ${params.token}`,
|
|
101396
|
+
"anthropic-beta": "claude-code-20250219,oauth-2025-04-20",
|
|
101397
|
+
"anthropic-version": "2023-06-01",
|
|
101398
|
+
"content-type": "application/json",
|
|
101399
|
+
"x-app": "cli"
|
|
101400
|
+
},
|
|
101401
|
+
body: JSON.stringify({
|
|
101402
|
+
model: params.model ?? FALLBACK_PROBE_MODEL,
|
|
101403
|
+
max_tokens: 1,
|
|
101404
|
+
system: CLAUDE_CODE_IDENTITY,
|
|
101405
|
+
messages: [{ role: "user", content: "ok" }]
|
|
101406
|
+
}),
|
|
101407
|
+
signal: AbortSignal.timeout(1e4)
|
|
101408
|
+
});
|
|
101409
|
+
} catch {
|
|
101410
|
+
return { usable: true };
|
|
101411
|
+
}
|
|
101412
|
+
if (res.status !== 401 && res.status !== 429) return { usable: true };
|
|
101413
|
+
const body = await res.text().catch(() => "");
|
|
101414
|
+
return { usable: false, reason: `${res.status}: ${extractApiErrorMessage(body)}` };
|
|
101415
|
+
}
|
|
101416
|
+
function extractApiErrorMessage(body) {
|
|
101417
|
+
try {
|
|
101418
|
+
const parsed2 = JSON.parse(body);
|
|
101419
|
+
if (typeof parsed2 === "object" && parsed2 !== null && "error" in parsed2 && typeof parsed2.error === "object" && parsed2.error !== null && "message" in parsed2.error && typeof parsed2.error.message === "string") {
|
|
101420
|
+
return parsed2.error.message;
|
|
101421
|
+
}
|
|
101422
|
+
} catch {
|
|
101423
|
+
}
|
|
101424
|
+
return body.slice(0, 200);
|
|
101425
|
+
}
|
|
101426
|
+
|
|
101357
101427
|
// utils/log.ts
|
|
101358
101428
|
var core = __toESM(require_core(), 1);
|
|
101359
101429
|
var import_table = __toESM(require_src2(), 1);
|
|
@@ -101867,7 +101937,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
101867
101937
|
// package.json
|
|
101868
101938
|
var package_default = {
|
|
101869
101939
|
name: "pullfrog",
|
|
101870
|
-
version: "0.1.
|
|
101940
|
+
version: "0.1.30",
|
|
101871
101941
|
type: "module",
|
|
101872
101942
|
bin: {
|
|
101873
101943
|
pullfrog: "dist/cli.mjs",
|
|
@@ -102442,10 +102512,13 @@ function resolveVertexOpenCodeModel(model) {
|
|
|
102442
102512
|
var SUBAGENT_DENIED_TOOLS = [
|
|
102443
102513
|
// working-tree mutation: switches HEAD onto pr-N and registers a push remote
|
|
102444
102514
|
"checkout_pr",
|
|
102445
|
-
// remote mutation: pushes commits / branches / tags / deletes a branch
|
|
102515
|
+
// remote mutation: pushes commits / branches / tags / deletes a branch.
|
|
102516
|
+
// commit_changes lands the orchestrator's (possibly half-finished) shared
|
|
102517
|
+
// working tree directly on the remote — strictly worse than push_branch.
|
|
102446
102518
|
"push_branch",
|
|
102447
102519
|
"push_tags",
|
|
102448
102520
|
"delete_branch",
|
|
102521
|
+
"commit_changes",
|
|
102449
102522
|
// GitHub PR state mutation
|
|
102450
102523
|
"create_pull_request",
|
|
102451
102524
|
"update_pull_request_body",
|
|
@@ -102710,8 +102783,10 @@ Inline comments use the same severity framing as body \`### \` sections, scaled
|
|
|
102710
102783
|
- **Don't repeat diff content**, don't include raw \`+123 / -45\` stats, don't include a changelog section, don't use horizontal rules (\`---\`).
|
|
102711
102784
|
- **Pull file/commit counts from \`checkout_pr\` metadata** \u2014 never count manually.
|
|
102712
102785
|
- **Legacy headings REMOVED.** Do not use \`### Key changes\`, \`### Issues found\`, \`<b>TL;DR</b>\`, or \`<sub><b>Summary</b>\`. The new structure subsumes them.`;
|
|
102713
|
-
function computeModes(agentId) {
|
|
102786
|
+
function computeModes(agentId, signedCommits = false) {
|
|
102714
102787
|
const t2 = (toolName) => formatMcpToolRef(agentId, toolName);
|
|
102788
|
+
const commitStep = signedCommits ? `commit via \`${t2("commit_changes")}\` \u2014 it lands a GitHub-signed commit directly on the remote branch (no push step)` : `commit locally via shell (\`git add . && git commit -m "..."\`)`;
|
|
102789
|
+
const finalizeStep = signedCommits ? `confirm a clean working tree (\`git status\`) \u2014 your \`${t2("commit_changes")}\` calls already landed the work on the remote` : `confirm a clean working tree, then push via \`${t2("push_branch")}\``;
|
|
102715
102790
|
return [
|
|
102716
102791
|
{
|
|
102717
102792
|
name: "Build",
|
|
@@ -102782,10 +102857,10 @@ function computeModes(agentId) {
|
|
|
102782
102857
|
- Do NOT defect-hunt the diff yourself in parallel with the subagent. Your role is dispatch + evaluation; doing the review yourself reintroduces the implementation bias the subagent is meant to mitigate.
|
|
102783
102858
|
- For diffs that rely on third-party API contracts, SDK semantics, framework directives, or DB engine specifics, instruct the subagent to verify load-bearing claims via web search and quote source URLs rather than trust training data \u2014 this is the single most common review-quality failure mode.
|
|
102784
102859
|
|
|
102785
|
-
Be **discerning** about what comes back. The reviewer is an AI subagent and is fallible \u2014 treat every finding as a hypothesis, not a directive, and **verify each one yourself** against the diff and the code before deciding whether to apply. You are searching for a solution that is **complete, minimal, and elegant** \u2014 you may need to think hard to find it. Do not over-engineer, do not be over-defensive, **do not write AI slop**. Reviewers bias toward *recommending additions*, and that bias has a recognizable slop texture: defensive checks for cases that cannot happen, extra logging, new abstractions used once, comments restating code, tests asserting tautologies, "just-in-case" guards, error handlers for cases the type system already rules out. Reject those. For each surviving finding, ask: would applying it leave the code more sound, correct, AND elegant? Two-out-of-three means look harder for a fix that gets all three before settling. After applying the fixes you accept, re-read your diff and be discerning about what *you just changed*: if any fix turned out to be bloat in context, revert it. Then verify only intended changes are present, no debug artifacts or commented-out code remain, no unrelated files were modified.
|
|
102860
|
+
Be **discerning** about what comes back. The reviewer is an AI subagent and is fallible \u2014 treat every finding as a hypothesis, not a directive, and **verify each one yourself** against the diff and the code before deciding whether to apply. You are searching for a solution that is **complete, minimal, and elegant** \u2014 you may need to think hard to find it. Do not over-engineer, do not be over-defensive, **do not write AI slop**. Reviewers bias toward *recommending additions*, and that bias has a recognizable slop texture: defensive checks for cases that cannot happen, extra logging, new abstractions used once, comments restating code, tests asserting tautologies, "just-in-case" guards, error handlers for cases the type system already rules out. Reject those. For each surviving finding, ask: would applying it leave the code more sound, correct, AND elegant? Two-out-of-three means look harder for a fix that gets all three before settling. After applying the fixes you accept, re-read your diff and be discerning about what *you just changed*: if any fix turned out to be bloat in context, revert it. Then verify only intended changes are present, no debug artifacts or commented-out code remain, no unrelated files were modified. Then ${commitStep}.
|
|
102786
102861
|
|
|
102787
102862
|
6. **finalize**:
|
|
102788
|
-
-
|
|
102863
|
+
- ${finalizeStep} (see *SYSTEM* Git rules if this fails \u2014 prepush errors are usually the repo's tests/lint, not infra timeouts)
|
|
102789
102864
|
- create a PR via \`${t2("create_pull_request")}\`
|
|
102790
102865
|
- call \`${t2("report_progress")}\` with the PR link or the exact error if push/PR failed
|
|
102791
102866
|
|
|
@@ -102813,12 +102888,12 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
102813
102888
|
|
|
102814
102889
|
5. Quality check:
|
|
102815
102890
|
- test changes, then review the diff before committing \u2014 verify only intended changes are present, no debug artifacts remain, no fix turned out to be bloat in context (revert any that did), and the changes are clean enough that a senior engineer would approve without hesitation
|
|
102816
|
-
-
|
|
102891
|
+
- ${commitStep}
|
|
102817
102892
|
|
|
102818
102893
|
6. Finalize. Reply + resolve are paired write actions: do BOTH or NEITHER for each thread.
|
|
102819
|
-
-
|
|
102820
|
-
- **if push fails**, call \`${t2("report_progress")}\` with the exact error and STOP \u2014 do NOT reply or resolve any thread until the fix is live on the remote. Resolving a thread without the fix landing misleads the reviewer.
|
|
102821
|
-
- **on
|
|
102894
|
+
- ${finalizeStep} (same push/prepush guidance as Build mode in *SYSTEM*)
|
|
102895
|
+
- **if the push/commit fails**, call \`${t2("report_progress")}\` with the exact error and STOP \u2014 do NOT reply or resolve any thread until the fix is live on the remote. Resolving a thread without the fix landing misleads the reviewer.
|
|
102896
|
+
- **once the fix is live on the remote**, for each thread you acted on:
|
|
102822
102897
|
- reply ONCE via \`${t2("reply_to_review_comment")}\`. The \`comment_id\` parameter takes the root comment's numeric \`id=\` (from the first \`comment author=...\` tag in the \`${t2("get_review_comments")}\` output) \u2014 NOT the \`thread=\` value; that's a separate GraphQL ID used by resolve. The runtime dedupes identical bodies within a session.
|
|
102823
102898
|
- **immediately** call \`${t2("resolve_review_thread")}\` with that thread's \`thread=\` value as \`thread_id\`. Resolve every thread where you (a) made the requested code change in full \u2014 partial fixes leave the thread open \u2014 OR (b) replied with a substantive answer the user explicitly asked for. Do NOT resolve threads where you pushed back on the request and the disagreement is unresolved; leave those open for the human to mediate.
|
|
102824
102899
|
- call \`${t2("report_progress")}\` with a brief summary`
|
|
@@ -103093,10 +103168,10 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
103093
103168
|
- fix the issue using your native file and shell tools
|
|
103094
103169
|
- verify the fix by re-running the exact CI command
|
|
103095
103170
|
- review the diff before committing \u2014 verify only the fix is present, no debug artifacts, no unrelated changes. the fix should be clean enough that a senior engineer would approve without hesitation.
|
|
103096
|
-
-
|
|
103171
|
+
- ${commitStep}
|
|
103097
103172
|
|
|
103098
103173
|
6. Finalize:
|
|
103099
|
-
-
|
|
103174
|
+
- ${finalizeStep} (same push/prepush guidance as Build mode in *SYSTEM*)
|
|
103100
103175
|
- call \`${t2("report_progress")}\` with the diagnosis and fix summary (or the exact push error if push failed)`
|
|
103101
103176
|
},
|
|
103102
103177
|
{
|
|
@@ -103112,8 +103187,8 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
103112
103187
|
- Call \`${t2("git_fetch")}\` to fetch the base branch.
|
|
103113
103188
|
|
|
103114
103189
|
3. **Merge Attempt**:
|
|
103115
|
-
- Run \`git merge origin/<base_branch>\` via shell.
|
|
103116
|
-
- If it succeeds automatically, confirm a clean working tree, push via \`${t2("push_branch")}\` (same push/prepush guidance as Build mode in *SYSTEM*), and call \`${t2("report_progress")}\` with a brief success note or the exact
|
|
103190
|
+
- Run \`git merge ${signedCommits ? "--no-commit " : ""}origin/<base_branch>\` via shell.
|
|
103191
|
+
- If it succeeds automatically, ${signedCommits ? `conclude it via \`${t2("commit_changes")}\` (it turns the pending merge into a signed merge commit on the remote)` : `confirm a clean working tree, push via \`${t2("push_branch")}\` (same push/prepush guidance as Build mode in *SYSTEM*)`}, and call \`${t2("report_progress")}\` with a brief success note or the exact error if it failed \u2014 **then stop; do not run steps 4\u20135.**
|
|
103117
103192
|
- If it fails (conflicts), resolve them manually (continue to steps 4\u20135).
|
|
103118
103193
|
|
|
103119
103194
|
4. **Resolve Conflicts**:
|
|
@@ -103123,8 +103198,8 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
103123
103198
|
|
|
103124
103199
|
5. **Finalize**:
|
|
103125
103200
|
- Run a final verification (build/test) to ensure the resolution works.
|
|
103126
|
-
- \`git add . && git commit -m "resolve merge conflicts"
|
|
103127
|
-
-
|
|
103201
|
+
- ${signedCommits ? `\`git add .\`, then conclude via \`${t2("commit_changes")}\` with message "resolve merge conflicts"` : `\`git add . && git commit -m "resolve merge conflicts"\``}
|
|
103202
|
+
- ${finalizeStep} (same push/prepush guidance as Build mode in *SYSTEM*)
|
|
103128
103203
|
- Call \`${t2("report_progress")}\` with a summary of what was resolved (or the exact push error if push failed)`
|
|
103129
103204
|
},
|
|
103130
103205
|
{
|
|
@@ -103143,7 +103218,7 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
103143
103218
|
- if code changes are needed: review your own diff before committing \u2014 verify only intended changes are present, no debug artifacts remain, and the changes are clean enough that a senior engineer would approve without hesitation
|
|
103144
103219
|
|
|
103145
103220
|
4. Finalize:
|
|
103146
|
-
- if code changes were made,
|
|
103221
|
+
- if code changes were made, get them onto a pull request (new or existing) using ${signedCommits ? `\`${t2("commit_changes")}\`` : `\`${t2("push_branch")}\``} and \`${t2("create_pull_request")}\` as needed. \`git status\` must be clean before you finish (see *SYSTEM* Git rules if this fails).
|
|
103147
103222
|
- call \`${t2("report_progress")}\` once with results \u2014 include exact tool errors if push or PR creation failed
|
|
103148
103223
|
- if the task involved labeling, commenting, or other GitHub operations, perform those directly`
|
|
103149
103224
|
}
|
|
@@ -104123,10 +104198,21 @@ var claude = agent({
|
|
|
104123
104198
|
env2.ANTHROPIC_MODEL = specifier;
|
|
104124
104199
|
}
|
|
104125
104200
|
if (env2.CLAUDE_CODE_OAUTH_TOKEN && !isBedrockRoute && env2.ANTHROPIC_API_KEY) {
|
|
104126
|
-
|
|
104127
|
-
|
|
104128
|
-
|
|
104129
|
-
|
|
104201
|
+
const preflight = await preflightClaudeSubscription({
|
|
104202
|
+
token: env2.CLAUDE_CODE_OAUTH_TOKEN,
|
|
104203
|
+
model
|
|
104204
|
+
});
|
|
104205
|
+
if (preflight.usable) {
|
|
104206
|
+
log.debug(
|
|
104207
|
+
"\xBB CLAUDE_CODE_OAUTH_TOKEN present \u2014 stripping ANTHROPIC_API_KEY from Claude Code env so the OAuth subscription is used"
|
|
104208
|
+
);
|
|
104209
|
+
delete env2.ANTHROPIC_API_KEY;
|
|
104210
|
+
} else {
|
|
104211
|
+
log.info(
|
|
104212
|
+
`\xBB Claude subscription unusable (${preflight.reason}) \u2014 falling back to ANTHROPIC_API_KEY`
|
|
104213
|
+
);
|
|
104214
|
+
delete env2.CLAUDE_CODE_OAUTH_TOKEN;
|
|
104215
|
+
}
|
|
104130
104216
|
}
|
|
104131
104217
|
log.info(`\xBB effort: ${effort}`);
|
|
104132
104218
|
log.debug(`\xBB starting Pullfrog (Claude Code): ${cliPath} ${baseArgs.join(" ")}`);
|
|
@@ -117144,15 +117230,6 @@ function isLocalApiUrl() {
|
|
|
117144
117230
|
// utils/buildPullfrogFooter.ts
|
|
117145
117231
|
var PULLFROG_DIVIDER = "<!-- PULLFROG_DIVIDER_DO_NOT_REMOVE_PLZ -->";
|
|
117146
117232
|
var FROG_LOGO = `<a href="https://pullfrog.com"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pullfrog.com/logos/frog-white-full-18px.png"><img src="https://pullfrog.com/logos/frog-green-full-18px.png" width="9px" height="9px" style="vertical-align: middle; " alt="Pullfrog"></picture></a>`;
|
|
117147
|
-
function providerDisplayName(slug2) {
|
|
117148
|
-
try {
|
|
117149
|
-
const key = getModelProvider(slug2);
|
|
117150
|
-
const meta3 = providers[key];
|
|
117151
|
-
return meta3?.displayName ?? key;
|
|
117152
|
-
} catch {
|
|
117153
|
-
return slug2;
|
|
117154
|
-
}
|
|
117155
|
-
}
|
|
117156
117233
|
function formatModelLabel(params) {
|
|
117157
117234
|
const alias = resolveDisplayAlias(params.model) ?? // reverse-lookup: when the caller passes an effective model (proxy or
|
|
117158
117235
|
// resolved target like "openrouter/anthropic/claude-opus-4.7") instead of
|
|
@@ -117163,9 +117240,7 @@ function formatModelLabel(params) {
|
|
|
117163
117240
|
if (params.oss) {
|
|
117164
117241
|
return `\`${displayName}\` (free via [Pullfrog for OSS](https://pullfrog.com/for-oss))`;
|
|
117165
117242
|
}
|
|
117166
|
-
|
|
117167
|
-
if (!params.fallbackFrom) return base;
|
|
117168
|
-
return `${base} (credentials for ${providerDisplayName(params.fallbackFrom)} not configured)`;
|
|
117243
|
+
return alias?.isFree ? `\`${displayName}\` (free)` : `\`${displayName}\``;
|
|
117169
117244
|
}
|
|
117170
117245
|
function buildPullfrogFooter(params) {
|
|
117171
117246
|
const parts = [];
|
|
@@ -117183,9 +117258,7 @@ function buildPullfrogFooter(params) {
|
|
|
117183
117258
|
parts.push("via [Pullfrog](https://pullfrog.com)");
|
|
117184
117259
|
}
|
|
117185
117260
|
if (params.model) {
|
|
117186
|
-
parts.push(
|
|
117187
|
-
`Using ${formatModelLabel({ model: params.model, fallbackFrom: params.fallbackFrom, oss: params.oss })}`
|
|
117188
|
-
);
|
|
117261
|
+
parts.push(`Using ${formatModelLabel({ model: params.model, oss: params.oss })}`);
|
|
117189
117262
|
}
|
|
117190
117263
|
const allParts = [...parts, "[\u{1D54F}](https://x.com/pullfrogai)"];
|
|
117191
117264
|
return `
|
|
@@ -117983,7 +118056,6 @@ function buildCommentFooter(ctx, customParts) {
|
|
|
117983
118056
|
} : void 0,
|
|
117984
118057
|
customParts,
|
|
117985
118058
|
model: ctx.toolState.model,
|
|
117986
|
-
fallbackFrom: ctx.toolState.modelFallback?.from,
|
|
117987
118059
|
oss: ctx.oss
|
|
117988
118060
|
});
|
|
117989
118061
|
}
|
|
@@ -119057,24 +119129,60 @@ var installPythonDependencies = {
|
|
|
119057
119129
|
|
|
119058
119130
|
// prep/index.ts
|
|
119059
119131
|
var prepSteps = [installNodeDependencies, installPythonDependencies];
|
|
119132
|
+
async function dirtyTrackedPaths() {
|
|
119133
|
+
const result = await spawn3({
|
|
119134
|
+
cmd: "git",
|
|
119135
|
+
args: ["diff", "--name-only", "HEAD"],
|
|
119136
|
+
env: process.env,
|
|
119137
|
+
activityTimeout: 0
|
|
119138
|
+
});
|
|
119139
|
+
if (result.exitCode !== 0) {
|
|
119140
|
+
throw new Error(
|
|
119141
|
+
`git diff --name-only HEAD failed (exit ${result.exitCode}): ${result.stderr.trim() || "(no stderr)"}`
|
|
119142
|
+
);
|
|
119143
|
+
}
|
|
119144
|
+
return new Set(result.stdout.split("\n").filter(Boolean));
|
|
119145
|
+
}
|
|
119146
|
+
async function restorePrepDirtiedFiles(preDirty) {
|
|
119147
|
+
const dirtied = [...await dirtyTrackedPaths()].filter((path4) => !preDirty.has(path4));
|
|
119148
|
+
if (dirtied.length === 0) return;
|
|
119149
|
+
const result = await spawn3({
|
|
119150
|
+
cmd: "git",
|
|
119151
|
+
args: ["restore", "--staged", "--worktree", "--", ...dirtied],
|
|
119152
|
+
env: process.env,
|
|
119153
|
+
activityTimeout: 0
|
|
119154
|
+
});
|
|
119155
|
+
if (result.exitCode !== 0) {
|
|
119156
|
+
log.warning(
|
|
119157
|
+
`\xBB failed to restore ${dirtied.length} tracked file(s) modified by prep: ${result.stderr.trim() || "(no stderr)"}`
|
|
119158
|
+
);
|
|
119159
|
+
return;
|
|
119160
|
+
}
|
|
119161
|
+
log.info(`\xBB restored ${dirtied.length} tracked file(s) modified by prep: ${dirtied.join(", ")}`);
|
|
119162
|
+
}
|
|
119060
119163
|
async function runPrepPhase(options) {
|
|
119061
119164
|
log.debug("\xBB starting prep phase...");
|
|
119062
119165
|
const startTime = performance7.now();
|
|
119063
119166
|
const results = [];
|
|
119064
|
-
|
|
119065
|
-
|
|
119066
|
-
|
|
119067
|
-
|
|
119068
|
-
|
|
119069
|
-
|
|
119070
|
-
|
|
119071
|
-
|
|
119072
|
-
|
|
119073
|
-
|
|
119074
|
-
|
|
119075
|
-
|
|
119076
|
-
|
|
119167
|
+
const preDirty = await dirtyTrackedPaths();
|
|
119168
|
+
try {
|
|
119169
|
+
for (const step of prepSteps) {
|
|
119170
|
+
const shouldRun = await step.shouldRun();
|
|
119171
|
+
if (!shouldRun) {
|
|
119172
|
+
log.debug(`\xBB skipping ${step.name} (not applicable)`);
|
|
119173
|
+
continue;
|
|
119174
|
+
}
|
|
119175
|
+
log.debug(`\xBB running ${step.name}...`);
|
|
119176
|
+
const result = await step.run(options);
|
|
119177
|
+
results.push(result);
|
|
119178
|
+
if (result.dependenciesInstalled) {
|
|
119179
|
+
log.debug(`\xBB ${step.name}: dependencies installed`);
|
|
119180
|
+
} else if (result.issues.length > 0) {
|
|
119181
|
+
log.warning(`\xBB ${step.name}: ${result.issues[0]}`);
|
|
119182
|
+
}
|
|
119077
119183
|
}
|
|
119184
|
+
} finally {
|
|
119185
|
+
await restorePrepDirtiedFiles(preDirty);
|
|
119078
119186
|
}
|
|
119079
119187
|
const totalDurationMs = performance7.now() - startTime;
|
|
119080
119188
|
log.debug(`\xBB prep phase completed (${Math.round(totalDurationMs)}ms)`);
|
|
@@ -151257,7 +151365,7 @@ function closeBrowserDaemon(toolState) {
|
|
|
151257
151365
|
// mcp/checkout.ts
|
|
151258
151366
|
import { createHash as createHash2 } from "node:crypto";
|
|
151259
151367
|
import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync8 } from "node:fs";
|
|
151260
|
-
import { join as
|
|
151368
|
+
import { join as join15 } from "node:path";
|
|
151261
151369
|
|
|
151262
151370
|
// utils/diffCoverage.ts
|
|
151263
151371
|
import { isAbsolute, normalize as normalize2, resolve } from "node:path";
|
|
@@ -151901,7 +152009,274 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
|
|
|
151901
152009
|
// mcp/git.ts
|
|
151902
152010
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
151903
152011
|
import { writeFileSync as writeFileSync7 } from "node:fs";
|
|
152012
|
+
import { join as join13 } from "node:path";
|
|
152013
|
+
|
|
152014
|
+
// utils/apiCommit.ts
|
|
152015
|
+
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
152016
|
+
import { lstat, readlink } from "node:fs/promises";
|
|
151904
152017
|
import { join as join12 } from "node:path";
|
|
152018
|
+
var GITHUB_API = "https://api.github.com";
|
|
152019
|
+
var MAX_BLOB_BYTES = 30 * 1024 * 1024;
|
|
152020
|
+
var BLOB_UPLOAD_CONCURRENCY = 8;
|
|
152021
|
+
function getRepoRoot() {
|
|
152022
|
+
return $2("git", ["rev-parse", "--show-toplevel"], { log: false }).trim();
|
|
152023
|
+
}
|
|
152024
|
+
async function gh(params) {
|
|
152025
|
+
const response = await fetch(`${GITHUB_API}${params.path}`, {
|
|
152026
|
+
method: params.method,
|
|
152027
|
+
headers: {
|
|
152028
|
+
Accept: "application/vnd.github+json",
|
|
152029
|
+
Authorization: `Bearer ${params.token}`,
|
|
152030
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
152031
|
+
"Content-Type": "application/json"
|
|
152032
|
+
},
|
|
152033
|
+
body: params.body === void 0 ? null : JSON.stringify(params.body)
|
|
152034
|
+
});
|
|
152035
|
+
const text = await response.text();
|
|
152036
|
+
let json4;
|
|
152037
|
+
try {
|
|
152038
|
+
json4 = text ? JSON.parse(text) : null;
|
|
152039
|
+
} catch {
|
|
152040
|
+
json4 = text.slice(0, 500);
|
|
152041
|
+
}
|
|
152042
|
+
return { status: response.status, json: json4 };
|
|
152043
|
+
}
|
|
152044
|
+
function ghError(method, path4, result) {
|
|
152045
|
+
return new Error(`${method} ${path4} failed (${result.status}): ${JSON.stringify(result.json)}`);
|
|
152046
|
+
}
|
|
152047
|
+
function isRetryable(status) {
|
|
152048
|
+
return status === 403 || status === 429 || status >= 500;
|
|
152049
|
+
}
|
|
152050
|
+
function detectWorkingTreeChanges() {
|
|
152051
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
152052
|
+
const diff = $2("git", ["diff", "--name-status", "--no-renames", "-z", "HEAD"], { log: false });
|
|
152053
|
+
const tokens = diff.split("\0").filter((t2) => t2.length > 0);
|
|
152054
|
+
for (let i = 0; i + 1 < tokens.length; i += 2) {
|
|
152055
|
+
const status = tokens[i];
|
|
152056
|
+
const path4 = tokens[i + 1];
|
|
152057
|
+
if (status === void 0 || path4 === void 0) break;
|
|
152058
|
+
if (status === "U") {
|
|
152059
|
+
throw new Error(
|
|
152060
|
+
`'${path4}' has unresolved merge conflicts. resolve the conflicts, stage the result with git add, then retry.`
|
|
152061
|
+
);
|
|
152062
|
+
}
|
|
152063
|
+
byPath.set(path4, { path: path4, deleted: status === "D" });
|
|
152064
|
+
}
|
|
152065
|
+
const porcelain = $2("git", ["status", "--porcelain=v1", "-z", "-uall", "--no-renames"], {
|
|
152066
|
+
log: false
|
|
152067
|
+
});
|
|
152068
|
+
for (const entry of porcelain.split("\0")) {
|
|
152069
|
+
if (entry.startsWith("?? ") && !byPath.has(entry.slice(3))) {
|
|
152070
|
+
const path4 = entry.slice(3);
|
|
152071
|
+
byPath.set(path4, { path: path4, deleted: false });
|
|
152072
|
+
}
|
|
152073
|
+
}
|
|
152074
|
+
return [...byPath.values()];
|
|
152075
|
+
}
|
|
152076
|
+
async function assertApiCommittable(files) {
|
|
152077
|
+
const present = files.filter((f) => !f.deleted).map((f) => f.path);
|
|
152078
|
+
if (present.length === 0) return;
|
|
152079
|
+
const root = getRepoRoot();
|
|
152080
|
+
const attrs = $2(
|
|
152081
|
+
"git",
|
|
152082
|
+
["check-attr", "filter", "-z", "--", ...present.map((p2) => join12(root, p2))],
|
|
152083
|
+
{ log: false }
|
|
152084
|
+
);
|
|
152085
|
+
const parts = attrs.split("\0");
|
|
152086
|
+
for (let i = 0; i + 2 < parts.length; i += 3) {
|
|
152087
|
+
if (parts[i + 2] === "lfs") {
|
|
152088
|
+
throw new Error(
|
|
152089
|
+
`'${parts[i]}' is tracked by git-lfs, which signed commits can't upload. remove it from the change set or ask the user to disable signed commits for this repo.`
|
|
152090
|
+
);
|
|
152091
|
+
}
|
|
152092
|
+
}
|
|
152093
|
+
for (const path4 of present) {
|
|
152094
|
+
const stat = await lstat(join12(root, path4));
|
|
152095
|
+
if (stat.isDirectory()) {
|
|
152096
|
+
throw new Error(
|
|
152097
|
+
`'${path4}' is a directory (nested repository or submodule?) \u2014 signed commits only support files and symlinks.`
|
|
152098
|
+
);
|
|
152099
|
+
}
|
|
152100
|
+
}
|
|
152101
|
+
}
|
|
152102
|
+
async function createBlobEntry(params) {
|
|
152103
|
+
const absPath = join12(params.repoRoot, params.path);
|
|
152104
|
+
const stat = await lstat(absPath);
|
|
152105
|
+
if (stat.size > MAX_BLOB_BYTES) {
|
|
152106
|
+
throw new Error(
|
|
152107
|
+
`'${params.path}' is ${Math.round(stat.size / 1024 / 1024)}MB \u2014 too large for signed commits (the GitHub blob API rejects large uploads). use git-lfs for large assets or ask the user to disable signed commits for this repo.`
|
|
152108
|
+
);
|
|
152109
|
+
}
|
|
152110
|
+
let mode;
|
|
152111
|
+
let content;
|
|
152112
|
+
if (stat.isSymbolicLink()) {
|
|
152113
|
+
mode = "120000";
|
|
152114
|
+
content = await readlink(absPath, { encoding: "buffer" });
|
|
152115
|
+
} else {
|
|
152116
|
+
mode = stat.mode & 64 ? "100755" : "100644";
|
|
152117
|
+
const cleanSha = $2("git", ["hash-object", "-w", "--", absPath], { log: false }).trim();
|
|
152118
|
+
content = execFileSync7("git", ["cat-file", "blob", cleanSha], {
|
|
152119
|
+
cwd: params.repoRoot,
|
|
152120
|
+
maxBuffer: 2 * MAX_BLOB_BYTES
|
|
152121
|
+
});
|
|
152122
|
+
}
|
|
152123
|
+
const path4 = `${params.repoPath}/git/blobs`;
|
|
152124
|
+
let result;
|
|
152125
|
+
for (const delayMs of [0, 1e3, 3e3]) {
|
|
152126
|
+
if (delayMs > 0) await new Promise((r) => setTimeout(r, delayMs));
|
|
152127
|
+
result = await gh({
|
|
152128
|
+
token: params.token,
|
|
152129
|
+
method: "POST",
|
|
152130
|
+
path: path4,
|
|
152131
|
+
body: { content: content.toString("base64"), encoding: "base64" }
|
|
152132
|
+
});
|
|
152133
|
+
if (result.status === 201) {
|
|
152134
|
+
const blob = result.json;
|
|
152135
|
+
return { path: params.path, mode, type: "blob", sha: blob.sha };
|
|
152136
|
+
}
|
|
152137
|
+
if (!isRetryable(result.status)) break;
|
|
152138
|
+
log.info(`blob upload for ${params.path} got ${result.status}, retrying`);
|
|
152139
|
+
}
|
|
152140
|
+
if (!result) throw new Error(`POST ${path4} failed`);
|
|
152141
|
+
throw ghError("POST", path4, result);
|
|
152142
|
+
}
|
|
152143
|
+
async function updateRefWithRetry(params) {
|
|
152144
|
+
const path4 = `${params.repoPath}/git/refs/heads/${encodeBranchPath(params.remoteBranch)}`;
|
|
152145
|
+
let lastResult;
|
|
152146
|
+
for (const delayMs of [0, 1e3, 3e3]) {
|
|
152147
|
+
if (delayMs > 0) await new Promise((r) => setTimeout(r, delayMs));
|
|
152148
|
+
const result = await gh({
|
|
152149
|
+
token: params.token,
|
|
152150
|
+
method: "PATCH",
|
|
152151
|
+
path: path4,
|
|
152152
|
+
body: { sha: params.sha, force: false }
|
|
152153
|
+
});
|
|
152154
|
+
if (result.status === 200) return;
|
|
152155
|
+
if (result.status === 422) {
|
|
152156
|
+
const detail = JSON.stringify(result.json);
|
|
152157
|
+
if (/fast.forward/i.test(detail)) {
|
|
152158
|
+
throw new Error(
|
|
152159
|
+
`the remote branch '${params.remoteBranch}' moved while committing (concurrent push). fetch it with git_fetch, integrate with git merge --no-commit origin/${params.remoteBranch}, resolve any conflicts, git add the results, then retry commit_changes.`
|
|
152160
|
+
);
|
|
152161
|
+
}
|
|
152162
|
+
throw ghError("PATCH", path4, result);
|
|
152163
|
+
}
|
|
152164
|
+
lastResult = result;
|
|
152165
|
+
if (!isRetryable(result.status)) break;
|
|
152166
|
+
log.info(`ref update got ${result.status}, retrying`);
|
|
152167
|
+
}
|
|
152168
|
+
if (lastResult) throw ghError("PATCH", path4, lastResult);
|
|
152169
|
+
throw new Error(`PATCH ${path4} failed`);
|
|
152170
|
+
}
|
|
152171
|
+
function encodeBranchPath(branch) {
|
|
152172
|
+
return branch.split("/").map(encodeURIComponent).join("/");
|
|
152173
|
+
}
|
|
152174
|
+
function validateRemoteBranch(branch) {
|
|
152175
|
+
const bad = branch.startsWith("-") || branch.startsWith("/") || branch.endsWith("/") || branch.includes("..") || branch.includes("@{") || /[\s~^:?*[\]\\]/.test(branch);
|
|
152176
|
+
if (bad) throw new Error(`invalid remote branch name '${branch}'`);
|
|
152177
|
+
}
|
|
152178
|
+
async function createSignedCommit(params) {
|
|
152179
|
+
validateRemoteBranch(params.remoteBranch);
|
|
152180
|
+
const repoPath = `/repos/${params.owner}/${params.repo}`;
|
|
152181
|
+
const branchPath = `${repoPath}/git/ref/heads/${encodeBranchPath(params.remoteBranch)}`;
|
|
152182
|
+
const refResult = await gh({ token: params.token, method: "GET", path: branchPath });
|
|
152183
|
+
const branchExists = refResult.status === 200;
|
|
152184
|
+
if (branchExists) {
|
|
152185
|
+
const ref = refResult.json;
|
|
152186
|
+
const remoteTip = ref.object.sha;
|
|
152187
|
+
if (!params.parents.includes(remoteTip)) {
|
|
152188
|
+
throw new Error(
|
|
152189
|
+
isAncestorOfHead(remoteTip) ? `your local branch has commits that were never pushed \u2014 signed-commits mode can't push local commits. run git reset --mixed ${remoteTip} (keeps every change in the working tree), then retry commit_changes.` : `the remote branch '${params.remoteBranch}' has commits you don't have locally (tip ${remoteTip.slice(0, 7)}). fetch it with git_fetch, integrate with git merge --no-commit origin/${params.remoteBranch}, resolve any conflicts, git add the results, then retry commit_changes.`
|
|
152190
|
+
);
|
|
152191
|
+
}
|
|
152192
|
+
} else if (refResult.status !== 404) {
|
|
152193
|
+
throw ghError("GET", branchPath, refResult);
|
|
152194
|
+
}
|
|
152195
|
+
const baseParent = params.parents[0];
|
|
152196
|
+
if (!baseParent) throw new Error("createSignedCommit requires at least one parent");
|
|
152197
|
+
const baseTree = $2("git", ["rev-parse", `${baseParent}^{tree}`], { log: false }).trim();
|
|
152198
|
+
let treeSha = baseTree;
|
|
152199
|
+
if (params.files.length > 0) {
|
|
152200
|
+
const repoRoot = getRepoRoot();
|
|
152201
|
+
const additions = params.files.filter((f) => !f.deleted);
|
|
152202
|
+
const blobEntries = [];
|
|
152203
|
+
for (let i = 0; i < additions.length; i += BLOB_UPLOAD_CONCURRENCY) {
|
|
152204
|
+
const chunk = additions.slice(i, i + BLOB_UPLOAD_CONCURRENCY);
|
|
152205
|
+
blobEntries.push(
|
|
152206
|
+
...await Promise.all(
|
|
152207
|
+
chunk.map(
|
|
152208
|
+
(f) => createBlobEntry({ token: params.token, repoPath, repoRoot, path: f.path })
|
|
152209
|
+
)
|
|
152210
|
+
)
|
|
152211
|
+
);
|
|
152212
|
+
}
|
|
152213
|
+
const deletionEntries = params.files.filter((f) => f.deleted).map((f) => ({ path: f.path, mode: "100644", type: "blob", sha: null }));
|
|
152214
|
+
const treeResult = await gh({
|
|
152215
|
+
token: params.token,
|
|
152216
|
+
method: "POST",
|
|
152217
|
+
path: `${repoPath}/git/trees`,
|
|
152218
|
+
body: { base_tree: baseTree, tree: [...blobEntries, ...deletionEntries] }
|
|
152219
|
+
});
|
|
152220
|
+
if (treeResult.status !== 201) {
|
|
152221
|
+
throw wrapUnknownBaseError("POST", `${repoPath}/git/trees`, treeResult, params.remoteBranch);
|
|
152222
|
+
}
|
|
152223
|
+
treeSha = treeResult.json.sha;
|
|
152224
|
+
}
|
|
152225
|
+
const commitResult = await gh({
|
|
152226
|
+
token: params.token,
|
|
152227
|
+
method: "POST",
|
|
152228
|
+
path: `${repoPath}/git/commits`,
|
|
152229
|
+
body: { message: params.message, tree: treeSha, parents: params.parents }
|
|
152230
|
+
});
|
|
152231
|
+
if (commitResult.status !== 201) {
|
|
152232
|
+
throw wrapUnknownBaseError(
|
|
152233
|
+
"POST",
|
|
152234
|
+
`${repoPath}/git/commits`,
|
|
152235
|
+
commitResult,
|
|
152236
|
+
params.remoteBranch
|
|
152237
|
+
);
|
|
152238
|
+
}
|
|
152239
|
+
const commit = commitResult.json;
|
|
152240
|
+
if (branchExists) {
|
|
152241
|
+
await updateRefWithRetry({
|
|
152242
|
+
token: params.token,
|
|
152243
|
+
repoPath,
|
|
152244
|
+
remoteBranch: params.remoteBranch,
|
|
152245
|
+
sha: commit.sha
|
|
152246
|
+
});
|
|
152247
|
+
} else {
|
|
152248
|
+
const createResult = await gh({
|
|
152249
|
+
token: params.token,
|
|
152250
|
+
method: "POST",
|
|
152251
|
+
path: `${repoPath}/git/refs`,
|
|
152252
|
+
body: { ref: `refs/heads/${params.remoteBranch}`, sha: commit.sha }
|
|
152253
|
+
});
|
|
152254
|
+
if (createResult.status !== 201) {
|
|
152255
|
+
throw ghError("POST", `${repoPath}/git/refs`, createResult);
|
|
152256
|
+
}
|
|
152257
|
+
}
|
|
152258
|
+
return { sha: commit.sha, createdBranch: !branchExists };
|
|
152259
|
+
}
|
|
152260
|
+
function isAncestorOfHead(sha) {
|
|
152261
|
+
try {
|
|
152262
|
+
$2("git", ["merge-base", "--is-ancestor", sha, "HEAD"], { log: false });
|
|
152263
|
+
return true;
|
|
152264
|
+
} catch {
|
|
152265
|
+
return false;
|
|
152266
|
+
}
|
|
152267
|
+
}
|
|
152268
|
+
function wrapUnknownBaseError(method, path4, result, remoteBranch) {
|
|
152269
|
+
if (result.status === 404 || result.status === 422) {
|
|
152270
|
+
return new Error(
|
|
152271
|
+
`${ghError(method, path4, result).message}
|
|
152272
|
+
|
|
152273
|
+
this usually means your local branch has commits that were never pushed \u2014 signed-commits mode can't push local commits. run git reset --mixed origin/${remoteBranch} (or the commit you branched from; this keeps every change in the working tree), then retry commit_changes.`
|
|
152274
|
+
);
|
|
152275
|
+
}
|
|
152276
|
+
return ghError(method, path4, result);
|
|
152277
|
+
}
|
|
152278
|
+
|
|
152279
|
+
// mcp/git.ts
|
|
151905
152280
|
function getPushDestination(branch, storedDest) {
|
|
151906
152281
|
if (storedDest && storedDest.localBranch === branch) {
|
|
151907
152282
|
log.debug(`using stored push destination: ${storedDest.remoteName}/${storedDest.remoteBranch}`);
|
|
@@ -151959,6 +152334,10 @@ function validateTagName(tag) {
|
|
|
151959
152334
|
);
|
|
151960
152335
|
}
|
|
151961
152336
|
}
|
|
152337
|
+
function pushesToBaseRepo(ctx) {
|
|
152338
|
+
const baseUrl = `https://github.com/${ctx.repo.owner}/${ctx.repo.name}.git`;
|
|
152339
|
+
return normalizeUrl(ctx.toolState.pushUrl ?? "") === normalizeUrl(baseUrl);
|
|
152340
|
+
}
|
|
151962
152341
|
function validatePushDestination(ctx, branch) {
|
|
151963
152342
|
const pushUrl = ctx.toolState.pushUrl;
|
|
151964
152343
|
if (!pushUrl) throw new Error("pushUrl not set - setupGit must run before push_branch");
|
|
@@ -151977,6 +152356,47 @@ var PushBranch = type({
|
|
|
151977
152356
|
branchName: type.string.describe("The branch name to push (defaults to current branch)").optional(),
|
|
151978
152357
|
force: type.boolean.describe("Force push (use with caution)").default(false)
|
|
151979
152358
|
});
|
|
152359
|
+
function assertPushTarget(ctx, branch, pushDest) {
|
|
152360
|
+
const prBranchMatch = branch.match(/^pr-(\d+)$/);
|
|
152361
|
+
if (prBranchMatch && pushDest.remoteBranch !== branch) {
|
|
152362
|
+
const prNumber = Number(prBranchMatch[1]);
|
|
152363
|
+
const event = ctx.payload.event;
|
|
152364
|
+
const runScoped = event.is_pr === true && event.issue_number === prNumber;
|
|
152365
|
+
if (!runScoped) {
|
|
152366
|
+
throw new Error(
|
|
152367
|
+
`push blocked: local branch '${branch}' would push to '${pushDest.remoteName}/${pushDest.remoteBranch}', but this run is not scoped to PR #${prNumber}. the 'pr-${prNumber}' branch was created by a prior checkout_pr call (likely from a subagent \u2014 subagents share the working tree and toolState with the orchestrator). you have probably landed your commit on the wrong branch. switch to your own feature branch first (e.g. 'git checkout <feature-branch>') and then push. if the push to PR #${prNumber} is intentional, this run needs to be triggered against that PR.`
|
|
152368
|
+
);
|
|
152369
|
+
}
|
|
152370
|
+
}
|
|
152371
|
+
const defaultBranch = ctx.repo.data.default_branch || "main";
|
|
152372
|
+
if (ctx.payload.push === "restricted" && pushDest.remoteBranch === defaultBranch) {
|
|
152373
|
+
throw new Error(
|
|
152374
|
+
`Push blocked: cannot push directly to default branch '${pushDest.remoteBranch}'. Create a feature branch and open a PR instead.`
|
|
152375
|
+
);
|
|
152376
|
+
}
|
|
152377
|
+
}
|
|
152378
|
+
async function runPrepushHook(ctx, retryTool) {
|
|
152379
|
+
if (ctx.toolState.prepushFailureCount > 0) {
|
|
152380
|
+
log.info(`\xBB skipping prepush hook (failed earlier this run)`);
|
|
152381
|
+
return true;
|
|
152382
|
+
}
|
|
152383
|
+
if (!ctx.prepushScript) return false;
|
|
152384
|
+
const prepushHook = await executeLifecycleHook({
|
|
152385
|
+
event: "prepush",
|
|
152386
|
+
script: ctx.prepushScript
|
|
152387
|
+
});
|
|
152388
|
+
if (prepushHook.failure) {
|
|
152389
|
+
ctx.toolState.prepushFailureCount += 1;
|
|
152390
|
+
throw new Error(
|
|
152391
|
+
buildPrepushFailureMessage({
|
|
152392
|
+
failure: prepushHook.failure,
|
|
152393
|
+
shell: ctx.payload.shell,
|
|
152394
|
+
retryTool
|
|
152395
|
+
})
|
|
152396
|
+
);
|
|
152397
|
+
}
|
|
152398
|
+
return false;
|
|
152399
|
+
}
|
|
151980
152400
|
var CONCURRENT_PUSH_PATTERNS = ["fetch first", "non-fast-forward", "cannot lock ref"];
|
|
151981
152401
|
var TRANSIENT_PATTERNS = [
|
|
151982
152402
|
/RPC failed/i,
|
|
@@ -152036,7 +152456,6 @@ async function pushWithRetry(args2, token) {
|
|
|
152036
152456
|
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
152037
152457
|
}
|
|
152038
152458
|
function PushBranchTool(ctx) {
|
|
152039
|
-
const defaultBranch = ctx.repo.data.default_branch || "main";
|
|
152040
152459
|
const pushPermission = ctx.payload.push;
|
|
152041
152460
|
return tool({
|
|
152042
152461
|
name: "push_branch",
|
|
@@ -152048,6 +152467,11 @@ function PushBranchTool(ctx) {
|
|
|
152048
152467
|
}
|
|
152049
152468
|
const branch = branchName || $2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { log: false });
|
|
152050
152469
|
rejectSpecialRef(branch, "branch");
|
|
152470
|
+
if (ctx.signedCommits && pushesToBaseRepo(ctx)) {
|
|
152471
|
+
throw new Error(
|
|
152472
|
+
"push_branch is not used in signed-commits mode \u2014 commits land on the remote via the commit_changes tool. call commit_changes to commit your working-tree changes as a GitHub-signed commit. if you already called commit_changes, your work is already on the remote \u2014 there is nothing left to push."
|
|
152473
|
+
);
|
|
152474
|
+
}
|
|
152051
152475
|
const status = $2("git", ["status", "--porcelain"], { log: false });
|
|
152052
152476
|
if (status) {
|
|
152053
152477
|
throw new Error(
|
|
@@ -152058,36 +152482,11 @@ ${status}` + (ctx.toolState.prepushFailureCount > 0 ? "\n\nnote: the prepush hoo
|
|
|
152058
152482
|
);
|
|
152059
152483
|
}
|
|
152060
152484
|
const pushDest = validatePushDestination(ctx, branch);
|
|
152061
|
-
|
|
152062
|
-
if (prBranchMatch && pushDest.remoteBranch !== branch) {
|
|
152063
|
-
const prNumber = Number(prBranchMatch[1]);
|
|
152064
|
-
const event = ctx.payload.event;
|
|
152065
|
-
const runScoped = event.is_pr === true && event.issue_number === prNumber;
|
|
152066
|
-
if (!runScoped) {
|
|
152067
|
-
throw new Error(
|
|
152068
|
-
`push blocked: local branch '${branch}' would push to '${pushDest.remoteName}/${pushDest.remoteBranch}', but this run is not scoped to PR #${prNumber}. the 'pr-${prNumber}' branch was created by a prior checkout_pr call (likely from a subagent \u2014 subagents share the working tree and toolState with the orchestrator). you have probably landed your commit on the wrong branch. switch to your own feature branch first (e.g. 'git checkout <feature-branch>') and then push. if the push to PR #${prNumber} is intentional, this run needs to be triggered against that PR.`
|
|
152069
|
-
);
|
|
152070
|
-
}
|
|
152071
|
-
}
|
|
152072
|
-
if (pushPermission === "restricted" && pushDest.remoteBranch === defaultBranch) {
|
|
152073
|
-
throw new Error(
|
|
152074
|
-
`Push blocked: cannot push directly to default branch '${pushDest.remoteBranch}'. Create a feature branch and open a PR instead.`
|
|
152075
|
-
);
|
|
152076
|
-
}
|
|
152485
|
+
assertPushTarget(ctx, branch, pushDest);
|
|
152077
152486
|
const refspec = branch === pushDest.remoteBranch ? branch : `${branch}:${pushDest.remoteBranch}`;
|
|
152078
152487
|
const pushArgs = force ? ["--force", "-u", pushDest.remoteName, refspec] : ["-u", pushDest.remoteName, refspec];
|
|
152079
|
-
const prepushSkipped = ctx
|
|
152080
|
-
if (prepushSkipped) {
|
|
152081
|
-
log.info(`\xBB skipping prepush hook (failed earlier this run)`);
|
|
152082
|
-
} else if (ctx.prepushScript) {
|
|
152083
|
-
const prepushHook = await executeLifecycleHook({
|
|
152084
|
-
event: "prepush",
|
|
152085
|
-
script: ctx.prepushScript
|
|
152086
|
-
});
|
|
152087
|
-
if (prepushHook.failure) {
|
|
152088
|
-
ctx.toolState.prepushFailureCount += 1;
|
|
152089
|
-
throw new Error(buildPrepushFailureMessage(prepushHook.failure, ctx.payload.shell));
|
|
152090
|
-
}
|
|
152488
|
+
const prepushSkipped = await runPrepushHook(ctx, "push_branch");
|
|
152489
|
+
if (!prepushSkipped && ctx.prepushScript) {
|
|
152091
152490
|
const postHookStatus = $2("git", ["status", "--porcelain"], { log: false });
|
|
152092
152491
|
if (postHookStatus) {
|
|
152093
152492
|
throw new Error(
|
|
@@ -152138,15 +152537,138 @@ ${integrateStep}
|
|
|
152138
152537
|
})
|
|
152139
152538
|
});
|
|
152140
152539
|
}
|
|
152141
|
-
function buildPrepushFailureMessage(
|
|
152540
|
+
function buildPrepushFailureMessage(params) {
|
|
152541
|
+
const failure = params.failure;
|
|
152142
152542
|
const header = failure.kind === "exit" ? `prepush hook failed with exit code ${failure.exitCode}.
|
|
152143
152543
|
|
|
152144
152544
|
script output:
|
|
152145
152545
|
${failure.output || "(empty)"}` : failure.kind === "timeout" ? `prepush hook timed out \u2014 the script is hung or doing too much work.` : `prepush hook failed to spawn: ${failure.spawnError}.`;
|
|
152146
|
-
const ifRealBug = shell === "disabled" ? `fix it before pushing again \u2014 shell access is disabled in this run, so you can't re-run the hook command yourself.` : `run the hook command yourself via the shell tool to iterate (
|
|
152546
|
+
const ifRealBug = params.shell === "disabled" ? `fix it before pushing again \u2014 shell access is disabled in this run, so you can't re-run the hook command yourself.` : `run the hook command yourself via the shell tool to iterate (${params.retryTool} will NOT re-run it).`;
|
|
152147
152547
|
return `${header}
|
|
152148
152548
|
|
|
152149
|
-
this repo's prepush hook is best-effort: the next
|
|
152549
|
+
this repo's prepush hook is best-effort: the next ${params.retryTool} call will SKIP the hook and proceed. if the failure is unrelated to your changes (pre-existing breakage, flaky check), just call ${params.retryTool} again. if it could be a real bug in your code, ${ifRealBug}`;
|
|
152550
|
+
}
|
|
152551
|
+
function buildNothingToCommitMessage(pushDest) {
|
|
152552
|
+
const base = "nothing to commit \u2014 the working tree matches HEAD.";
|
|
152553
|
+
try {
|
|
152554
|
+
const remoteTip = $2(
|
|
152555
|
+
"git",
|
|
152556
|
+
["rev-parse", `refs/remotes/${pushDest.remoteName}/${pushDest.remoteBranch}`],
|
|
152557
|
+
{ log: false }
|
|
152558
|
+
).trim();
|
|
152559
|
+
const head = $2("git", ["rev-parse", "HEAD"], { log: false }).trim();
|
|
152560
|
+
if (remoteTip === head) {
|
|
152561
|
+
return `${base} your work is already on ${pushDest.remoteName}/${pushDest.remoteBranch} \u2014 there is no push step in signed-commits mode.`;
|
|
152562
|
+
}
|
|
152563
|
+
$2("git", ["merge-base", "--is-ancestor", remoteTip, "HEAD"], { log: false });
|
|
152564
|
+
return `${base} but your local branch has commits that were never pushed \u2014 signed-commits mode can't push local commits. run git reset --mixed ${remoteTip} (keeps every change in the working tree), then retry commit_changes.`;
|
|
152565
|
+
} catch {
|
|
152566
|
+
return base;
|
|
152567
|
+
}
|
|
152568
|
+
}
|
|
152569
|
+
var CommitChanges = type({
|
|
152570
|
+
message: type.string.describe("Commit message (first line = subject)"),
|
|
152571
|
+
files: type.string.array().describe("Optional subset of changed paths to commit. Defaults to every working-tree change.").optional()
|
|
152572
|
+
});
|
|
152573
|
+
function CommitChangesTool(ctx) {
|
|
152574
|
+
const pushPermission = ctx.payload.push;
|
|
152575
|
+
return tool({
|
|
152576
|
+
name: "commit_changes",
|
|
152577
|
+
description: "Commit working-tree changes directly to the remote branch as a GitHub-signed (Verified) commit \u2014 this repository has signed commits enabled, so use this INSTEAD of git commit + push_branch. Edit files locally, then call this tool: it detects every working-tree change (new, modified, deleted files), or commits a subset via `files`. The commit lands on the remote immediately \u2014 there is no separate push step. The remote branch is created automatically on the first commit to a new local branch. A merge in progress (git merge --no-commit) is concluded as a signed merge commit \u2014 resolve conflicts and git add first. Runs the repository prepush hook (if configured) before committing \u2014 best-effort, same skip-on-failure behavior as push_branch.",
|
|
152578
|
+
parameters: CommitChanges,
|
|
152579
|
+
timeoutMs: 6e5,
|
|
152580
|
+
execute: execute(async (params) => {
|
|
152581
|
+
if (pushPermission === "disabled") {
|
|
152582
|
+
throw new Error("Push is disabled. This repository is configured for read-only access.");
|
|
152583
|
+
}
|
|
152584
|
+
const branch = $2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { log: false }).trim();
|
|
152585
|
+
if (branch === "HEAD") {
|
|
152586
|
+
throw new Error(
|
|
152587
|
+
"HEAD is detached \u2014 create or check out a branch before committing (e.g. git checkout -b pullfrog/<description>)."
|
|
152588
|
+
);
|
|
152589
|
+
}
|
|
152590
|
+
rejectSpecialRef(branch, "branch");
|
|
152591
|
+
const pushDest = validatePushDestination(ctx, branch);
|
|
152592
|
+
if (!pushesToBaseRepo(ctx)) {
|
|
152593
|
+
throw new Error(
|
|
152594
|
+
`'${branch}' pushes to the fork '${pushDest.url}', where the app can't create signed commits. commit locally via the git tool and use push_branch instead (those commits will be unsigned).`
|
|
152595
|
+
);
|
|
152596
|
+
}
|
|
152597
|
+
assertPushTarget(ctx, branch, pushDest);
|
|
152598
|
+
const prepushSkipped = await runPrepushHook(ctx, "commit_changes");
|
|
152599
|
+
const head = $2("git", ["rev-parse", "HEAD"], { log: false }).trim();
|
|
152600
|
+
let mergeHead = "";
|
|
152601
|
+
try {
|
|
152602
|
+
mergeHead = $2("git", ["rev-parse", "-q", "--verify", "MERGE_HEAD"], { log: false }).trim();
|
|
152603
|
+
} catch {
|
|
152604
|
+
}
|
|
152605
|
+
let changes = detectWorkingTreeChanges();
|
|
152606
|
+
if (params.files) {
|
|
152607
|
+
if (mergeHead) {
|
|
152608
|
+
throw new Error(
|
|
152609
|
+
"can't commit a subset of files while a merge is in progress \u2014 the merge commit must include every merged change. omit `files`."
|
|
152610
|
+
);
|
|
152611
|
+
}
|
|
152612
|
+
const requested = new Set(params.files);
|
|
152613
|
+
const known = new Set(changes.map((c2) => c2.path));
|
|
152614
|
+
const unknown4 = [...requested].filter((p2) => !known.has(p2));
|
|
152615
|
+
if (unknown4.length > 0) {
|
|
152616
|
+
throw new Error(
|
|
152617
|
+
`no detected change at: ${unknown4.join(", ")} \u2014 run git status to list changed paths.`
|
|
152618
|
+
);
|
|
152619
|
+
}
|
|
152620
|
+
changes = changes.filter((c2) => requested.has(c2.path));
|
|
152621
|
+
}
|
|
152622
|
+
if (changes.length === 0 && !mergeHead) {
|
|
152623
|
+
throw new Error(buildNothingToCommitMessage(pushDest));
|
|
152624
|
+
}
|
|
152625
|
+
await assertApiCommittable(changes);
|
|
152626
|
+
const parents = mergeHead ? [head, mergeHead] : [head];
|
|
152627
|
+
const result = await createSignedCommit({
|
|
152628
|
+
token: ctx.gitToken,
|
|
152629
|
+
owner: ctx.repo.owner,
|
|
152630
|
+
repo: ctx.repo.name,
|
|
152631
|
+
remoteBranch: pushDest.remoteBranch,
|
|
152632
|
+
message: params.message,
|
|
152633
|
+
parents,
|
|
152634
|
+
files: changes
|
|
152635
|
+
});
|
|
152636
|
+
await $git(
|
|
152637
|
+
"fetch",
|
|
152638
|
+
[
|
|
152639
|
+
"--no-tags",
|
|
152640
|
+
"origin",
|
|
152641
|
+
`+refs/heads/${pushDest.remoteBranch}:refs/remotes/origin/${pushDest.remoteBranch}`
|
|
152642
|
+
],
|
|
152643
|
+
{ token: ctx.gitToken }
|
|
152644
|
+
);
|
|
152645
|
+
$2("git", ["update-ref", `refs/heads/${branch}`, result.sha], { log: false });
|
|
152646
|
+
if (mergeHead) {
|
|
152647
|
+
$2("git", ["merge", "--quit"], { log: false });
|
|
152648
|
+
}
|
|
152649
|
+
$2("git", ["reset", "-q"], { log: false });
|
|
152650
|
+
if (result.createdBranch) {
|
|
152651
|
+
$2("git", ["config", `branch.${branch}.remote`, "origin"], { log: false });
|
|
152652
|
+
$2("git", ["config", `branch.${branch}.merge`, `refs/heads/${pushDest.remoteBranch}`], {
|
|
152653
|
+
log: false
|
|
152654
|
+
});
|
|
152655
|
+
}
|
|
152656
|
+
log.info(
|
|
152657
|
+
`\xBB created signed commit ${result.sha.slice(0, 7)} (${changes.length} file(s)) on ${pushDest.remoteName}/${pushDest.remoteBranch}`
|
|
152658
|
+
);
|
|
152659
|
+
return {
|
|
152660
|
+
success: true,
|
|
152661
|
+
sha: result.sha,
|
|
152662
|
+
branch,
|
|
152663
|
+
remoteBranch: pushDest.remoteBranch,
|
|
152664
|
+
files: changes.map((c2) => c2.deleted ? `D ${c2.path}` : c2.path),
|
|
152665
|
+
createdBranch: result.createdBranch,
|
|
152666
|
+
verified: true,
|
|
152667
|
+
prepushSkipped,
|
|
152668
|
+
message: `created signed commit ${result.sha.slice(0, 7)} on ${pushDest.remoteName}/${pushDest.remoteBranch}${result.createdBranch ? " (remote branch created)" : ""}`
|
|
152669
|
+
};
|
|
152670
|
+
})
|
|
152671
|
+
});
|
|
152150
152672
|
}
|
|
152151
152673
|
var AUTH_REQUIRED_REDIRECT = {
|
|
152152
152674
|
push: "use the push_branch tool instead \u2014 it handles authentication and permission checks.",
|
|
@@ -152219,7 +152741,7 @@ function countAhead(head, base) {
|
|
|
152219
152741
|
function spillGitOutput(params) {
|
|
152220
152742
|
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
152221
152743
|
if (!tempDir) throw new Error("PULLFROG_TEMP_DIR not set");
|
|
152222
|
-
const outputPath =
|
|
152744
|
+
const outputPath = join13(tempDir, `git-${params.command}-${randomUUID3().slice(0, 8)}.txt`);
|
|
152223
152745
|
writeFileSync7(outputPath, params.output);
|
|
152224
152746
|
const previewByLines = params.output.split("\n").slice(0, OVERFLOW_PREVIEW_LINES).join("\n");
|
|
152225
152747
|
const preview = previewByLines.length <= OVERFLOW_PREVIEW_MAX_CHARS ? previewByLines : `${previewByLines.slice(0, OVERFLOW_PREVIEW_MAX_CHARS)}\u2026`;
|
|
@@ -152253,8 +152775,30 @@ function GitTool(ctx) {
|
|
|
152253
152775
|
}
|
|
152254
152776
|
const redirect = AUTH_REQUIRED_REDIRECT[command];
|
|
152255
152777
|
if (redirect) {
|
|
152778
|
+
if (command === "push" && ctx.signedCommits) {
|
|
152779
|
+
throw new Error(
|
|
152780
|
+
"git push is not available through this tool \u2014 in signed-commits mode use commit_changes instead: it commits your working-tree changes directly to the remote as a GitHub-signed commit (push_branch only applies to fork PRs)."
|
|
152781
|
+
);
|
|
152782
|
+
}
|
|
152256
152783
|
throw new Error(`git ${command} is not available through this tool \u2014 ${redirect}`);
|
|
152257
152784
|
}
|
|
152785
|
+
if (ctx.signedCommits && (command === "commit" || command === "merge")) {
|
|
152786
|
+
if (pushesToBaseRepo(ctx)) {
|
|
152787
|
+
if (command === "commit") {
|
|
152788
|
+
throw new Error(
|
|
152789
|
+
"git commit is blocked in signed-commits mode \u2014 use the commit_changes tool instead. it commits your working-tree changes directly to the remote as a GitHub-signed (Verified) commit. if you are concluding a merge, stage the resolutions with git add and call commit_changes \u2014 no local commit is needed."
|
|
152790
|
+
);
|
|
152791
|
+
}
|
|
152792
|
+
const noLocalCommit = args2.some(
|
|
152793
|
+
(a) => a === "--no-commit" || a === "--abort" || a === "--quit"
|
|
152794
|
+
);
|
|
152795
|
+
if (!noLocalCommit) {
|
|
152796
|
+
throw new Error(
|
|
152797
|
+
"bare git merge would create a local commit, which can't be pushed in signed-commits mode. use git merge --no-commit <ref>, resolve any conflicts, git add the results, then call commit_changes \u2014 it concludes the merge as a signed merge commit."
|
|
152798
|
+
);
|
|
152799
|
+
}
|
|
152800
|
+
}
|
|
152801
|
+
}
|
|
152258
152802
|
if (ctx.payload.shell === "disabled") {
|
|
152259
152803
|
const blocked = NOSHELL_BLOCKED_SUBCOMMANDS[command];
|
|
152260
152804
|
if (blocked) {
|
|
@@ -152887,7 +153431,6 @@ async function createAndSubmitWithFooter(ctx, params, opts) {
|
|
|
152887
153431
|
workflowRun: ctx.runId ? { owner: ctx.repo.owner, repo: ctx.repo.name, runId: ctx.runId, jobId: ctx.jobId } : void 0,
|
|
152888
153432
|
customParts,
|
|
152889
153433
|
model: ctx.toolState.model,
|
|
152890
|
-
fallbackFrom: ctx.toolState.modelFallback?.from,
|
|
152891
153434
|
oss: ctx.oss
|
|
152892
153435
|
});
|
|
152893
153436
|
return await ctx.octokit.rest.pulls.submitReview({
|
|
@@ -152920,12 +153463,12 @@ async function reportReviewNodeId(ctx, params) {
|
|
|
152920
153463
|
}
|
|
152921
153464
|
|
|
152922
153465
|
// utils/setup.ts
|
|
152923
|
-
import { execFileSync as
|
|
153466
|
+
import { execFileSync as execFileSync8, execSync as execSync2 } from "node:child_process";
|
|
152924
153467
|
import { mkdtempSync as mkdtempSync2, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
|
|
152925
153468
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
152926
|
-
import { join as
|
|
153469
|
+
import { join as join14 } from "node:path";
|
|
152927
153470
|
function createTempDirectory() {
|
|
152928
|
-
const sharedTempDir = mkdtempSync2(
|
|
153471
|
+
const sharedTempDir = mkdtempSync2(join14(tmpdir3(), "pullfrog-"));
|
|
152929
153472
|
process.env.PULLFROG_TEMP_DIR = sharedTempDir;
|
|
152930
153473
|
log.info(`\xBB created temp dir at ${sharedTempDir}`);
|
|
152931
153474
|
return sharedTempDir;
|
|
@@ -152970,13 +153513,13 @@ function wipeRunnerLeakSurface() {
|
|
|
152970
153513
|
return [];
|
|
152971
153514
|
}
|
|
152972
153515
|
};
|
|
152973
|
-
const fileCommandsDir =
|
|
153516
|
+
const fileCommandsDir = join14(runnerTemp, "_runner_file_commands");
|
|
152974
153517
|
for (const entry of listDir(fileCommandsDir)) {
|
|
152975
|
-
tryUnlink(
|
|
153518
|
+
tryUnlink(join14(fileCommandsDir, entry));
|
|
152976
153519
|
}
|
|
152977
153520
|
for (const entry of listDir(runnerTemp)) {
|
|
152978
153521
|
if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
|
|
152979
|
-
tryUnlink(
|
|
153522
|
+
tryUnlink(join14(runnerTemp, entry));
|
|
152980
153523
|
}
|
|
152981
153524
|
}
|
|
152982
153525
|
if (wiped.length > 0) {
|
|
@@ -153013,7 +153556,7 @@ function removeIncludeIfEntries(repoDir) {
|
|
|
153013
153556
|
if (!key || seen.has(key)) continue;
|
|
153014
153557
|
seen.add(key);
|
|
153015
153558
|
try {
|
|
153016
|
-
|
|
153559
|
+
execFileSync8("git", ["config", "--local", "--unset-all", key], {
|
|
153017
153560
|
cwd: repoDir,
|
|
153018
153561
|
stdio: "pipe",
|
|
153019
153562
|
env: env2
|
|
@@ -153485,7 +154028,7 @@ function CheckoutPrTool(ctx) {
|
|
|
153485
154028
|
headSha: ctx.toolState.checkoutSha
|
|
153486
154029
|
});
|
|
153487
154030
|
if (incremental) {
|
|
153488
|
-
incrementalDiffPath =
|
|
154031
|
+
incrementalDiffPath = join15(
|
|
153489
154032
|
tempDir,
|
|
153490
154033
|
`pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
|
|
153491
154034
|
);
|
|
@@ -153499,7 +154042,7 @@ function CheckoutPrTool(ctx) {
|
|
|
153499
154042
|
const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
|
|
153500
154043
|
log.debug(`formatted diff preview (first 100 lines):
|
|
153501
154044
|
${diffPreview}`);
|
|
153502
|
-
const diffPath =
|
|
154045
|
+
const diffPath = join15(tempDir, `pr-${pull_number}-${headShort}.diff`);
|
|
153503
154046
|
writeFileSync8(diffPath, formatResult.content);
|
|
153504
154047
|
log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
|
|
153505
154048
|
ctx.toolState.diffCoverage = createDiffCoverageState({
|
|
@@ -153608,7 +154151,7 @@ ${dirty}`
|
|
|
153608
154151
|
|
|
153609
154152
|
// mcp/checkSuite.ts
|
|
153610
154153
|
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
153611
|
-
import { join as
|
|
154154
|
+
import { join as join16 } from "node:path";
|
|
153612
154155
|
var GetCheckSuiteLogs = type({
|
|
153613
154156
|
check_suite_id: type.number.describe("the id from check_suite.id")
|
|
153614
154157
|
});
|
|
@@ -153704,7 +154247,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
153704
154247
|
if (!tempDir) {
|
|
153705
154248
|
throw new Error("PULLFROG_TEMP_DIR not set");
|
|
153706
154249
|
}
|
|
153707
|
-
const logsDir =
|
|
154250
|
+
const logsDir = join16(tempDir, "ci-logs");
|
|
153708
154251
|
mkdirSync7(logsDir, { recursive: true });
|
|
153709
154252
|
const jobResults = [];
|
|
153710
154253
|
for (const run4 of failedRuns) {
|
|
@@ -153731,7 +154274,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
153731
154274
|
);
|
|
153732
154275
|
}
|
|
153733
154276
|
const logsText = await logsResult.text();
|
|
153734
|
-
const logPath =
|
|
154277
|
+
const logPath = join16(logsDir, `job-${job.id}.log`);
|
|
153735
154278
|
writeFileSync9(logPath, logsText);
|
|
153736
154279
|
const analysis = analyzeLog(logsText, 80);
|
|
153737
154280
|
const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
|
|
@@ -153781,7 +154324,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
153781
154324
|
|
|
153782
154325
|
// mcp/commitInfo.ts
|
|
153783
154326
|
import { writeFileSync as writeFileSync10 } from "node:fs";
|
|
153784
|
-
import { join as
|
|
154327
|
+
import { join as join17 } from "node:path";
|
|
153785
154328
|
var CommitInfo = type({
|
|
153786
154329
|
sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
|
|
153787
154330
|
});
|
|
@@ -153805,7 +154348,7 @@ function CommitInfoTool(ctx) {
|
|
|
153805
154348
|
"PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
|
|
153806
154349
|
);
|
|
153807
154350
|
}
|
|
153808
|
-
const diffFile =
|
|
154351
|
+
const diffFile = join17(tempDir, `commit-${sha.slice(0, 7)}.diff`);
|
|
153809
154352
|
writeFileSync10(diffFile, formatResult.content);
|
|
153810
154353
|
log.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
|
|
153811
154354
|
return {
|
|
@@ -154312,7 +154855,6 @@ function buildPrBodyWithFooter(ctx, body) {
|
|
|
154312
154855
|
triggeredBy: true,
|
|
154313
154856
|
workflowRun: ctx.runId ? { owner: ctx.repo.owner, repo: ctx.repo.name, runId: ctx.runId, jobId: ctx.jobId } : void 0,
|
|
154314
154857
|
model: ctx.toolState.model,
|
|
154315
|
-
fallbackFrom: ctx.toolState.modelFallback?.from,
|
|
154316
154858
|
oss: ctx.oss
|
|
154317
154859
|
});
|
|
154318
154860
|
const bodyWithoutFooter = stripExistingFooter(fixDoubleEscapedString(body));
|
|
@@ -154467,7 +155009,7 @@ function PullRequestInfoTool(ctx) {
|
|
|
154467
155009
|
|
|
154468
155010
|
// mcp/reviewComments.ts
|
|
154469
155011
|
import { writeFileSync as writeFileSync11 } from "node:fs";
|
|
154470
|
-
import { join as
|
|
155012
|
+
import { join as join18 } from "node:path";
|
|
154471
155013
|
var REVIEW_THREADS_QUERY = `
|
|
154472
155014
|
query ($owner: String!, $name: String!, $prNumber: Int!) {
|
|
154473
155015
|
repository(owner: $owner, name: $name) {
|
|
@@ -154883,7 +155425,7 @@ function GetReviewCommentsTool(ctx) {
|
|
|
154883
155425
|
throw new Error("PULLFROG_TEMP_DIR not set");
|
|
154884
155426
|
}
|
|
154885
155427
|
const filename = `review-${params.review_id}-threads.md`;
|
|
154886
|
-
const commentsPath =
|
|
155428
|
+
const commentsPath = join18(tempDir, filename);
|
|
154887
155429
|
writeFileSync11(commentsPath, formatted.content);
|
|
154888
155430
|
log.debug(`wrote ${threadBlocks.length} threads to ${commentsPath}`);
|
|
154889
155431
|
return {
|
|
@@ -155121,7 +155663,7 @@ import { spawn as spawn4, spawnSync as spawnSync5 } from "node:child_process";
|
|
|
155121
155663
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
155122
155664
|
import { closeSync, openSync, writeFileSync as writeFileSync12 } from "node:fs";
|
|
155123
155665
|
import { userInfo as userInfo2 } from "node:os";
|
|
155124
|
-
import { join as
|
|
155666
|
+
import { join as join19 } from "node:path";
|
|
155125
155667
|
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
155126
155668
|
var ShellParams = type({
|
|
155127
155669
|
command: "string",
|
|
@@ -155284,7 +155826,7 @@ function getTempDir() {
|
|
|
155284
155826
|
var MAX_OUTPUT_CHARS = 5e3;
|
|
155285
155827
|
function capOutput(output) {
|
|
155286
155828
|
if (output.length <= MAX_OUTPUT_CHARS) return output;
|
|
155287
|
-
const fullPath =
|
|
155829
|
+
const fullPath = join19(getTempDir(), `shell-${randomUUID4().slice(0, 8)}.log`);
|
|
155288
155830
|
writeFileSync12(fullPath, output);
|
|
155289
155831
|
const elided = output.length - MAX_OUTPUT_CHARS;
|
|
155290
155832
|
return `... [${elided} chars truncated; full output saved to ${fullPath}] ...
|
|
@@ -155339,8 +155881,8 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
|
|
|
155339
155881
|
if (params.background) {
|
|
155340
155882
|
const tempDir = getTempDir();
|
|
155341
155883
|
const handle = `bg-${randomUUID4().slice(0, 8)}`;
|
|
155342
|
-
const outputPath =
|
|
155343
|
-
const pidPath =
|
|
155884
|
+
const outputPath = join19(tempDir, `${handle}.log`);
|
|
155885
|
+
const pidPath = join19(tempDir, `${handle}.pid`);
|
|
155344
155886
|
const logFd = openSync(outputPath, "a");
|
|
155345
155887
|
let proc2;
|
|
155346
155888
|
try {
|
|
@@ -155571,7 +156113,7 @@ function buildCommonTools(ctx, outputSchema) {
|
|
|
155571
156113
|
return tools;
|
|
155572
156114
|
}
|
|
155573
156115
|
function buildOrchestratorTools(ctx, outputSchema) {
|
|
155574
|
-
|
|
156116
|
+
const tools = [
|
|
155575
156117
|
...buildCommonTools(ctx, outputSchema),
|
|
155576
156118
|
ReportProgressTool(ctx),
|
|
155577
156119
|
SelectModeTool(ctx),
|
|
@@ -155581,6 +156123,10 @@ function buildOrchestratorTools(ctx, outputSchema) {
|
|
|
155581
156123
|
CreatePullRequestTool(ctx),
|
|
155582
156124
|
UpdatePullRequestBodyTool(ctx)
|
|
155583
156125
|
];
|
|
156126
|
+
if (ctx.signedCommits) {
|
|
156127
|
+
tools.push(CommitChangesTool(ctx));
|
|
156128
|
+
}
|
|
156129
|
+
return tools;
|
|
155584
156130
|
}
|
|
155585
156131
|
async function tryStartMcpServer(ctx, tools, port) {
|
|
155586
156132
|
const server = new FastMCP({ name: pullfrogMcpName, version: "0.0.1" });
|
|
@@ -155771,8 +156317,14 @@ var MISSING_KEY_MARKER = "no API key found";
|
|
|
155771
156317
|
function buildMissingApiKeyError(params) {
|
|
155772
156318
|
const githubSecretsUrl = `https://github.com/${params.owner}/${params.name}/settings/secrets/actions`;
|
|
155773
156319
|
const settingsUrl = `${getApiUrl()}/console/${params.owner}/${params.name}`;
|
|
156320
|
+
const envVars = params.model?.includes("/") ? getModelEnvVars(params.model) : [];
|
|
156321
|
+
const [primary, ...alternates] = envVars;
|
|
156322
|
+
const envVarList = primary ? `\`${primary}\`${alternates.length > 0 ? ` (or ${alternates.map((v) => `\`${v}\``).join(" / ")})` : ""}` : void 0;
|
|
156323
|
+
const lead = envVarList ? `**${MISSING_KEY_MARKER}** \u2014 this repo is configured to use \`${params.model}\`, which needs ${envVarList}, but the runner has no key for it.` : `**${MISSING_KEY_MARKER}** \u2014 Pullfrog needs at least one LLM provider API key (e.g. \`ANTHROPIC_API_KEY\`, \`OPENAI_API_KEY\`, \`GEMINI_API_KEY\`) configured as a GitHub Actions secret.`;
|
|
155774
156324
|
return [
|
|
155775
|
-
|
|
156325
|
+
lead,
|
|
156326
|
+
"",
|
|
156327
|
+
"**To fix:** add the key as a GitHub Actions secret (referenced from your workflow's `env:` block) or as a Pullfrog secret in the console \u2014 or switch this repo to a different model (free models need no key).",
|
|
155776
156328
|
"",
|
|
155777
156329
|
`[Open repo secrets \u2192](${githubSecretsUrl}) \xB7 [Configure model \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/keys) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
|
|
155778
156330
|
].join("\n");
|
|
@@ -155852,10 +156404,14 @@ function validateAgentApiKey(params) {
|
|
|
155852
156404
|
}
|
|
155853
156405
|
if (params.agent.name === "opencode") {
|
|
155854
156406
|
if (params.authorized.has(params.model)) return;
|
|
155855
|
-
throw new Error(
|
|
156407
|
+
throw new Error(
|
|
156408
|
+
buildMissingApiKeyError({ owner: params.owner, name: params.name, model: params.model })
|
|
156409
|
+
);
|
|
155856
156410
|
}
|
|
155857
156411
|
if (hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN")) return;
|
|
155858
|
-
throw new Error(
|
|
156412
|
+
throw new Error(
|
|
156413
|
+
buildMissingApiKeyError({ owner: params.owner, name: params.name, model: params.model })
|
|
156414
|
+
);
|
|
155859
156415
|
}
|
|
155860
156416
|
if (params.agent.name === "opencode") {
|
|
155861
156417
|
if (params.authorized.size > 0) return;
|
|
@@ -155866,42 +156422,37 @@ function validateAgentApiKey(params) {
|
|
|
155866
156422
|
}
|
|
155867
156423
|
function isApiKeyAuthError(text) {
|
|
155868
156424
|
if (!text) return false;
|
|
155869
|
-
return text.includes(MISSING_KEY_MARKER) || /Invalid API key/i.test(text) || /\bUser not found\b/i.test(text) || /\bInvalid authentication\b/i.test(text) || /authentication_error/i.test(text) || /Invalid bearer token/i.test(text) || /api_error_status\s*=\s*401/i.test(text) || /API Error:\s*401/i.test(text);
|
|
156425
|
+
return text.includes(MISSING_KEY_MARKER) || /Invalid API key/i.test(text) || /\bUser not found\b/i.test(text) || /\bInvalid authentication\b/i.test(text) || /authentication_error/i.test(text) || /Invalid bearer token/i.test(text) || /api_error_status\s*=\s*401/i.test(text) || /API Error:\s*401/i.test(text) || /Failed to authenticate\. API Error:/i.test(text) || isOAuthCredentialExpiredError(text);
|
|
156426
|
+
}
|
|
156427
|
+
function isOAuthCredentialExpiredError(text) {
|
|
156428
|
+
return /authentication token has expired/i.test(text) || /Token refresh failed/i.test(text);
|
|
155870
156429
|
}
|
|
155871
156430
|
function formatApiKeyErrorSummary(params) {
|
|
155872
156431
|
if (params.raw.includes(MISSING_KEY_MARKER)) {
|
|
156432
|
+
if (params.raw.startsWith(`**${MISSING_KEY_MARKER}**`)) return params.raw;
|
|
155873
156433
|
return buildMissingApiKeyError({ owner: params.owner, name: params.name });
|
|
155874
156434
|
}
|
|
155875
156435
|
const githubSecretsUrl = `https://github.com/${params.owner}/${params.name}/settings/secrets/actions`;
|
|
155876
156436
|
const settingsUrl = `${getApiUrl()}/console/${params.owner}/${params.name}`;
|
|
156437
|
+
if (isOAuthCredentialExpiredError(params.raw)) {
|
|
156438
|
+
return [
|
|
156439
|
+
`**Your provider OAuth credential has expired.** Re-authenticate the provider connection (e.g. \`pullfrog auth codex\`), then re-trigger the run.`,
|
|
156440
|
+
"",
|
|
156441
|
+
`[Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/keys) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
|
|
156442
|
+
].join("\n");
|
|
156443
|
+
}
|
|
155877
156444
|
return [
|
|
155878
|
-
`**Your LLM provider API key was rejected
|
|
156445
|
+
`**Your LLM provider API key was rejected.** Rotate the key in your provider dashboard, then update the matching GitHub Actions secret.`,
|
|
155879
156446
|
"",
|
|
155880
156447
|
`[Update repo secret \u2192](${githubSecretsUrl}) \xB7 [Model settings \u2192](${settingsUrl}) \xB7 [Setup docs \u2192](https://docs.pullfrog.com/keys) \xB7 [Ask in Discord \u2192](https://discord.gg/8y96raFg8e)`
|
|
155881
156448
|
].join("\n");
|
|
155882
156449
|
}
|
|
155883
156450
|
|
|
155884
|
-
// utils/byokFallback.ts
|
|
155885
|
-
var FREE_FALLBACK_SLUG = "opencode/big-pickle";
|
|
155886
|
-
function selectFallbackModelIfNeeded(input) {
|
|
155887
|
-
if (input.proxyModel) return { fallback: false };
|
|
155888
|
-
if (!input.resolvedModel) return { fallback: false };
|
|
155889
|
-
if (input.resolvedModel === FREE_FALLBACK_SLUG) return { fallback: false };
|
|
155890
|
-
if (!input.resolvedModel.includes("/")) return { fallback: false };
|
|
155891
|
-
if (input.agentName === "claude") return { fallback: false };
|
|
155892
|
-
if (input.authorized.has(input.resolvedModel)) return { fallback: false };
|
|
155893
|
-
return {
|
|
155894
|
-
fallback: true,
|
|
155895
|
-
from: input.resolvedModel,
|
|
155896
|
-
to: FREE_FALLBACK_SLUG
|
|
155897
|
-
};
|
|
155898
|
-
}
|
|
155899
|
-
|
|
155900
156451
|
// utils/gitAuthServer.ts
|
|
155901
156452
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
155902
156453
|
import { writeFileSync as writeFileSync13 } from "node:fs";
|
|
155903
156454
|
import { createServer as createServer3 } from "node:http";
|
|
155904
|
-
import { join as
|
|
156455
|
+
import { join as join20 } from "node:path";
|
|
155905
156456
|
var REVOKED_TRAP_MS = 6e4;
|
|
155906
156457
|
function revokeGitHubToken(token) {
|
|
155907
156458
|
fetch("https://api.github.com/installation/token", {
|
|
@@ -155970,7 +156521,7 @@ async function startGitAuthServer(tmpdir4) {
|
|
|
155970
156521
|
function writeAskpassScript(code) {
|
|
155971
156522
|
const scriptId = randomUUID5();
|
|
155972
156523
|
const scriptName = `askpass-${scriptId}.js`;
|
|
155973
|
-
const scriptPath =
|
|
156524
|
+
const scriptPath = join20(tmpdir4, scriptName);
|
|
155974
156525
|
const content = [
|
|
155975
156526
|
`#!/usr/bin/env node`,
|
|
155976
156527
|
`var a=process.argv[2]||"";`,
|
|
@@ -156008,7 +156559,7 @@ async function startGitAuthServer(tmpdir4) {
|
|
|
156008
156559
|
var core3 = __toESM(require_core(), 1);
|
|
156009
156560
|
import { createSign } from "node:crypto";
|
|
156010
156561
|
import { rename, writeFile } from "node:fs/promises";
|
|
156011
|
-
import { dirname as dirname3, join as
|
|
156562
|
+
import { dirname as dirname3, join as join21 } from "node:path";
|
|
156012
156563
|
|
|
156013
156564
|
// node_modules/.pnpm/@octokit+plugin-throttling@11.0.3_@octokit+core@7.0.5/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
|
|
156014
156565
|
var import_light = __toESM(require_light(), 1);
|
|
@@ -159650,6 +160201,7 @@ var Octokit2 = Octokit.plugin(requestLog, legacyRestEndpointMethods, paginateRes
|
|
|
159650
160201
|
);
|
|
159651
160202
|
|
|
159652
160203
|
// utils/github.ts
|
|
160204
|
+
var OIDC_AUDIENCE = "pullfrog-api";
|
|
159653
160205
|
function isObject4(value2) {
|
|
159654
160206
|
return typeof value2 === "object" && value2 !== null;
|
|
159655
160207
|
}
|
|
@@ -159666,8 +160218,39 @@ var TokenExchangeError = class extends Error {
|
|
|
159666
160218
|
this.status = status;
|
|
159667
160219
|
}
|
|
159668
160220
|
};
|
|
160221
|
+
async function fetchIdTokenFromStash(creds) {
|
|
160222
|
+
const url4 = new URL(creds.requestUrl);
|
|
160223
|
+
url4.searchParams.set("audience", OIDC_AUDIENCE);
|
|
160224
|
+
const timeoutMs = 3e4;
|
|
160225
|
+
let response;
|
|
160226
|
+
try {
|
|
160227
|
+
response = await fetch(url4, {
|
|
160228
|
+
headers: { Authorization: `Bearer ${creds.requestToken}` },
|
|
160229
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
160230
|
+
});
|
|
160231
|
+
} catch (error49) {
|
|
160232
|
+
if (error49 instanceof Error && error49.name === "TimeoutError") {
|
|
160233
|
+
throw new Error(`ID token request timed out after ${timeoutMs}ms`);
|
|
160234
|
+
}
|
|
160235
|
+
throw error49;
|
|
160236
|
+
}
|
|
160237
|
+
if (!response.ok) {
|
|
160238
|
+
throw new TokenExchangeError(
|
|
160239
|
+
response.status,
|
|
160240
|
+
`Failed to get ID token: ${response.status} ${response.statusText}`
|
|
160241
|
+
);
|
|
160242
|
+
}
|
|
160243
|
+
const body = await response.json();
|
|
160244
|
+
if (!body.value) {
|
|
160245
|
+
throw new Error("ID token response has no value field");
|
|
160246
|
+
}
|
|
160247
|
+
if (isGitHubActions) {
|
|
160248
|
+
core3.setSecret(body.value);
|
|
160249
|
+
}
|
|
160250
|
+
return body.value;
|
|
160251
|
+
}
|
|
159669
160252
|
async function acquireTokenViaOIDC(opts) {
|
|
159670
|
-
const oidcToken = await core3.getIDToken(
|
|
160253
|
+
const oidcToken = opts?.oidc ? await fetchIdTokenFromStash(opts.oidc) : await core3.getIDToken(OIDC_AUDIENCE);
|
|
159671
160254
|
const repos = [...opts?.repos ?? []];
|
|
159672
160255
|
const targetRepo = process.env.GITHUB_REPOSITORY?.split("/")[1];
|
|
159673
160256
|
if (targetRepo) {
|
|
@@ -159816,14 +160399,15 @@ async function acquireTokenViaGitHubApp(opts) {
|
|
|
159816
160399
|
const installationId = await findInstallationId(jwt2, config3.repoOwner, config3.repoName);
|
|
159817
160400
|
return await createInstallationToken(jwt2, installationId, opts?.permissions);
|
|
159818
160401
|
}
|
|
160402
|
+
function isTransientTokenError(error49) {
|
|
160403
|
+
if (error49 instanceof TokenExchangeError) return error49.status >= 500 || error49.status === 429;
|
|
160404
|
+
return error49 instanceof Error && (error49.message.includes("timed out") || error49.message.includes("fetch failed") || error49.message.includes("ECONNRESET") || error49.message.includes("ETIMEDOUT"));
|
|
160405
|
+
}
|
|
159819
160406
|
async function acquireNewToken(opts) {
|
|
159820
|
-
if (isOIDCAvailable()) {
|
|
160407
|
+
if (opts?.oidc || isOIDCAvailable()) {
|
|
159821
160408
|
return await retry(() => acquireTokenViaOIDC(opts), {
|
|
159822
160409
|
label: "token exchange",
|
|
159823
|
-
shouldRetry:
|
|
159824
|
-
if (error49 instanceof TokenExchangeError) return error49.status >= 500 || error49.status === 429;
|
|
159825
|
-
return error49 instanceof Error && (error49.message.includes("timed out") || error49.message.includes("fetch failed") || error49.message.includes("ECONNRESET") || error49.message.includes("ETIMEDOUT"));
|
|
159826
|
-
}
|
|
160410
|
+
shouldRetry: isTransientTokenError
|
|
159827
160411
|
});
|
|
159828
160412
|
}
|
|
159829
160413
|
if (process.env.GITHUB_ACTIONS === "true") {
|
|
@@ -159866,14 +160450,14 @@ function getGitHubUsageSummary() {
|
|
|
159866
160450
|
}
|
|
159867
160451
|
async function writeGitHubUsageSummaryToFile(path4) {
|
|
159868
160452
|
const summary2 = getGitHubUsageSummary();
|
|
159869
|
-
const tmpPath =
|
|
160453
|
+
const tmpPath = join21(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
|
|
159870
160454
|
await writeFile(tmpPath, JSON.stringify(summary2));
|
|
159871
160455
|
await rename(tmpPath, path4);
|
|
159872
160456
|
}
|
|
159873
|
-
function createOctokit(token) {
|
|
160457
|
+
function createOctokit(token, refreshAuth) {
|
|
160458
|
+
let currentToken = token;
|
|
159874
160459
|
const OctokitWithPlugins = Octokit2.plugin(throttling);
|
|
159875
160460
|
const octokit = new OctokitWithPlugins({
|
|
159876
|
-
auth: token,
|
|
159877
160461
|
throttle: {
|
|
159878
160462
|
onRateLimit: (_retryAfter, _options, _octokit, retryCount) => {
|
|
159879
160463
|
return retryCount <= 2;
|
|
@@ -159902,6 +160486,8 @@ function createOctokit(token) {
|
|
|
159902
160486
|
return response;
|
|
159903
160487
|
};
|
|
159904
160488
|
octokit.hook.wrap("request", async (request2, options) => {
|
|
160489
|
+
const sentToken = currentToken;
|
|
160490
|
+
options.headers.authorization = `token ${sentToken}`;
|
|
159905
160491
|
try {
|
|
159906
160492
|
const response = await request2(options);
|
|
159907
160493
|
onResponse(response);
|
|
@@ -159910,6 +160496,13 @@ function createOctokit(token) {
|
|
|
159910
160496
|
if (isObject4(error49) && "response" in error49 && isObject4(error49.response) && "headers" in error49.response && isObject4(error49.response.headers)) {
|
|
159911
160497
|
onResponse(error49.response);
|
|
159912
160498
|
}
|
|
160499
|
+
if (refreshAuth && isObject4(error49) && "status" in error49 && error49.status === 401) {
|
|
160500
|
+
currentToken = await refreshAuth(sentToken);
|
|
160501
|
+
options.headers.authorization = `token ${currentToken}`;
|
|
160502
|
+
const response = await request2(options);
|
|
160503
|
+
onResponse(response);
|
|
160504
|
+
return response;
|
|
160505
|
+
}
|
|
159913
160506
|
throw error49;
|
|
159914
160507
|
}
|
|
159915
160508
|
});
|
|
@@ -160108,7 +160701,17 @@ Use \`${t2("git")}\` for local git commands (status, log, add, commit, checkout,
|
|
|
160108
160701
|
- \`${t2("checkout_pr")}\` - checkout a PR branch (fetches and configures push for forks)
|
|
160109
160702
|
- \`${t2("delete_branch")}\` - delete a remote branch (requires push: enabled)
|
|
160110
160703
|
- \`${t2("push_tags")}\` - push tags (requires push: enabled)
|
|
160111
|
-
|
|
160704
|
+
${ctx.signedCommits ? `
|
|
160705
|
+
#### Signed commits (enabled for this repository)
|
|
160706
|
+
|
|
160707
|
+
This repository requires GitHub-signed commits, which local git commits can never satisfy. This OVERRIDES any other instruction (including mode instructions) to commit via git or push via \`${t2("push_branch")}\`:
|
|
160708
|
+
- Do NOT use git commit or \`${t2("push_branch")}\` for same-repo branches \u2014 both are blocked. Instead: edit files, then call \`${t2("commit_changes")}\` with a commit message. It commits every working-tree change (or a \`files\` subset) directly to the remote branch as a GitHub-signed (Verified) commit. There is no separate push step.
|
|
160709
|
+
- New branches: create locally as usual (git checkout -b); the remote branch is created on the first \`${t2("commit_changes")}\` call.
|
|
160710
|
+
- To integrate remote changes (concurrent pushes, base branch): \`${t2("git_fetch")}\`, then git merge --no-commit <ref>, resolve conflicts, git add the results, then \`${t2("commit_changes")}\` \u2014 it concludes the merge as a signed merge commit.
|
|
160711
|
+
- \`${t2("commit_changes")}\` commits EVERY working-tree change by default \u2014 review \`git status\` first and clean up stray artifacts (or pass \`files\`).
|
|
160712
|
+
- cherry-pick/revert: use \`-n\`/\`--no-commit\` so no local commit is created, then \`${t2("commit_changes")}\`.
|
|
160713
|
+
- Fork PRs are the exception: signing is impossible there, so commit and push normally (those commits will be unsigned).
|
|
160714
|
+
` : ""}
|
|
160112
160715
|
Rules:
|
|
160113
160716
|
- All code changes must be pushed to a pull request (new or existing) before the run ends. This environment is ephemeral \u2014 unpushed work is lost permanently. \`git status\` must be clean when you finish.
|
|
160114
160717
|
- Protected branches (default branch) are blocked from direct pushes in restricted mode. Do not use \`git push\` directly \u2014 it will fail without credentials.
|
|
@@ -160284,7 +160887,7 @@ function resolveInstructions(ctx) {
|
|
|
160284
160887
|
|
|
160285
160888
|
// utils/learnings.ts
|
|
160286
160889
|
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
|
|
160287
|
-
import { dirname as dirname4, join as
|
|
160890
|
+
import { dirname as dirname4, join as join22 } from "node:path";
|
|
160288
160891
|
|
|
160289
160892
|
// utils/learningsTruncate.ts
|
|
160290
160893
|
var MAX_LEARNINGS_LENGTH = 1e5;
|
|
@@ -160301,7 +160904,7 @@ function truncateAtLineBoundary(body, cap) {
|
|
|
160301
160904
|
// utils/learnings.ts
|
|
160302
160905
|
var LEARNINGS_FILE_NAME = "pullfrog-learnings.md";
|
|
160303
160906
|
function learningsFilePath(tmpdir4) {
|
|
160304
|
-
return
|
|
160907
|
+
return join22(tmpdir4, LEARNINGS_FILE_NAME);
|
|
160305
160908
|
}
|
|
160306
160909
|
async function seedLearningsFile(params) {
|
|
160307
160910
|
const path4 = learningsFilePath(params.tmpdir);
|
|
@@ -160721,6 +161324,10 @@ function formatTransientErrorSummary(error49, owner) {
|
|
|
160721
161324
|
var core7 = __toESM(require_core(), 1);
|
|
160722
161325
|
import assert2 from "node:assert/strict";
|
|
160723
161326
|
var mcpTokenValue;
|
|
161327
|
+
var refreshMcpTokenFn;
|
|
161328
|
+
function getMcpTokenRefresh() {
|
|
161329
|
+
return refreshMcpTokenFn;
|
|
161330
|
+
}
|
|
160724
161331
|
function getJobToken() {
|
|
160725
161332
|
const inputToken = core7.getInput("token");
|
|
160726
161333
|
if (inputToken) {
|
|
@@ -160772,6 +161379,29 @@ async function resolveTokens(params) {
|
|
|
160772
161379
|
`\xBB acquired scoped MCP token (${Object.entries(mcpPermissions).map((e) => e.join(":")).join(", ")})`
|
|
160773
161380
|
);
|
|
160774
161381
|
mcpTokenValue = mcpToken;
|
|
161382
|
+
let currentMcpToken = mcpToken;
|
|
161383
|
+
let refreshPromise;
|
|
161384
|
+
refreshMcpTokenFn = (stale) => {
|
|
161385
|
+
assert2(mcpTokenValue, "tokens already disposed");
|
|
161386
|
+
if (stale !== currentMcpToken) {
|
|
161387
|
+
return Promise.resolve(currentMcpToken);
|
|
161388
|
+
}
|
|
161389
|
+
refreshPromise ??= acquireNewToken({
|
|
161390
|
+
permissions: mcpPermissions,
|
|
161391
|
+
oidc: params.oidc ?? void 0
|
|
161392
|
+
}).then((fresh) => {
|
|
161393
|
+
if (isGitHubActions) {
|
|
161394
|
+
core7.setSecret(fresh);
|
|
161395
|
+
}
|
|
161396
|
+
mcpTokenValue = fresh;
|
|
161397
|
+
currentMcpToken = fresh;
|
|
161398
|
+
log.warning("\xBB GitHub rejected the MCP token; re-acquired a fresh scoped MCP token");
|
|
161399
|
+
return fresh;
|
|
161400
|
+
}).finally(() => {
|
|
161401
|
+
refreshPromise = void 0;
|
|
161402
|
+
});
|
|
161403
|
+
return refreshPromise;
|
|
161404
|
+
};
|
|
160775
161405
|
let disposingRef;
|
|
160776
161406
|
const dispose = async () => {
|
|
160777
161407
|
if (disposingRef) {
|
|
@@ -160780,9 +161410,10 @@ async function resolveTokens(params) {
|
|
|
160780
161410
|
disposingRef = Promise.withResolvers();
|
|
160781
161411
|
try {
|
|
160782
161412
|
mcpTokenValue = void 0;
|
|
161413
|
+
refreshMcpTokenFn = void 0;
|
|
160783
161414
|
await Promise.all([
|
|
160784
161415
|
revokeGitHubInstallationToken(gitToken),
|
|
160785
|
-
revokeGitHubInstallationToken(
|
|
161416
|
+
revokeGitHubInstallationToken(currentMcpToken)
|
|
160786
161417
|
]);
|
|
160787
161418
|
} finally {
|
|
160788
161419
|
removeSignalHandler();
|
|
@@ -160826,7 +161457,7 @@ async function reportErrorToComment(ctx) {
|
|
|
160826
161457
|
|
|
160827
161458
|
${ctx.error}` : ctx.error;
|
|
160828
161459
|
const repoContext = parseRepoContext();
|
|
160829
|
-
const octokit = createOctokit(getGitHubInstallationToken());
|
|
161460
|
+
const octokit = createOctokit(getGitHubInstallationToken(), getMcpTokenRefresh());
|
|
160830
161461
|
const runId = process.env.GITHUB_RUN_ID ? Number.parseInt(process.env.GITHUB_RUN_ID, 10) : void 0;
|
|
160831
161462
|
const customParts = [];
|
|
160832
161463
|
if (runId) {
|
|
@@ -160840,7 +161471,6 @@ ${ctx.error}` : ctx.error;
|
|
|
160840
161471
|
workflowRun: runId ? { owner: repoContext.owner, repo: repoContext.name, runId } : void 0,
|
|
160841
161472
|
customParts,
|
|
160842
161473
|
model: ctx.toolState.model,
|
|
160843
|
-
fallbackFrom: ctx.toolState.modelFallback?.from,
|
|
160844
161474
|
oss: ctx.toolState.oss
|
|
160845
161475
|
});
|
|
160846
161476
|
const body = `${formattedError}${footer}`;
|
|
@@ -160907,18 +161537,15 @@ async function mintProxyKey(ctx) {
|
|
|
160907
161537
|
if (error49 instanceof TransientError) throw error49;
|
|
160908
161538
|
log.warning(`proxy key mint error: ${error49 instanceof Error ? error49.message : String(error49)}`);
|
|
160909
161539
|
return null;
|
|
160910
|
-
} finally {
|
|
160911
|
-
delete process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
|
|
160912
|
-
delete process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
|
160913
161540
|
}
|
|
160914
161541
|
}
|
|
160915
161542
|
async function buildProxyTokenHeaders(ctx) {
|
|
160916
161543
|
if (ctx.oidcCredentials) {
|
|
160917
|
-
|
|
160918
|
-
|
|
160919
|
-
|
|
160920
|
-
|
|
160921
|
-
|
|
161544
|
+
const creds = ctx.oidcCredentials;
|
|
161545
|
+
const oidcToken = await retry(() => fetchIdTokenFromStash(creds), {
|
|
161546
|
+
label: "ID token mint",
|
|
161547
|
+
shouldRetry: isTransientTokenError
|
|
161548
|
+
});
|
|
160922
161549
|
return { Authorization: `Bearer ${oidcToken}` };
|
|
160923
161550
|
}
|
|
160924
161551
|
if (isLocalApiUrl()) {
|
|
@@ -160982,7 +161609,7 @@ async function runProxyResolution(ctx) {
|
|
|
160982
161609
|
|
|
160983
161610
|
// utils/prSummary.ts
|
|
160984
161611
|
import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
|
|
160985
|
-
import { dirname as dirname5, join as
|
|
161612
|
+
import { dirname as dirname5, join as join23 } from "node:path";
|
|
160986
161613
|
var SUMMARY_FILE_NAME = "pullfrog-summary.md";
|
|
160987
161614
|
var SUMMARY_SCAFFOLD = `# PR summary
|
|
160988
161615
|
|
|
@@ -160992,7 +161619,7 @@ var SUMMARY_SCAFFOLD = `# PR summary
|
|
|
160992
161619
|
var MIN_SNAPSHOT_LENGTH = 60;
|
|
160993
161620
|
var MAX_SNAPSHOT_LENGTH = 32768;
|
|
160994
161621
|
function summaryFilePath(tmpdir4) {
|
|
160995
|
-
return
|
|
161622
|
+
return join23(tmpdir4, SUMMARY_FILE_NAME);
|
|
160996
161623
|
}
|
|
160997
161624
|
async function seedSummaryFile(params) {
|
|
160998
161625
|
const path4 = summaryFilePath(params.tmpdir);
|
|
@@ -161120,6 +161747,7 @@ var defaultSettings = {
|
|
|
161120
161747
|
push: "restricted",
|
|
161121
161748
|
shell: "restricted",
|
|
161122
161749
|
prApproveEnabled: false,
|
|
161750
|
+
signedCommits: false,
|
|
161123
161751
|
modeInstructions: {},
|
|
161124
161752
|
learnings: null,
|
|
161125
161753
|
learningsHeadings: [],
|
|
@@ -161352,7 +161980,7 @@ ${input.errorMessage}
|
|
|
161352
161980
|
].join("\n");
|
|
161353
161981
|
}
|
|
161354
161982
|
function formatProviderModelNotFoundSummary(input) {
|
|
161355
|
-
return `
|
|
161983
|
+
return `The configured model is no longer available in OpenCode's catalog. Pick a different model in the Pullfrog console for \`${input.owner}/${input.name}\`, or contact support if this persists.
|
|
161356
161984
|
|
|
161357
161985
|
\`\`\`
|
|
161358
161986
|
${input.raw}
|
|
@@ -161793,7 +162421,7 @@ async function main() {
|
|
|
161793
162421
|
const initialOctokit = createOctokit(jobToken);
|
|
161794
162422
|
const runContext = await resolveRunContextData({ octokit: initialOctokit, token: jobToken });
|
|
161795
162423
|
timer.checkpoint("runContextData");
|
|
161796
|
-
|
|
162424
|
+
createTempDirectory();
|
|
161797
162425
|
const opencodeCliPath = await agents.opencode.install();
|
|
161798
162426
|
captureBaselineModels(opencodeCliPath);
|
|
161799
162427
|
if (runContext.dbSecrets) {
|
|
@@ -161817,12 +162445,12 @@ async function main() {
|
|
|
161817
162445
|
if (payload.event.trigger === "pull_request_synchronize") {
|
|
161818
162446
|
toolState.beforeSha = payload.event.before_sha;
|
|
161819
162447
|
}
|
|
161820
|
-
const tokenRef = __using(_stack2, await resolveTokens({ push: payload.push }), true);
|
|
161821
|
-
wipeRunnerLeakSurface();
|
|
161822
162448
|
const oidcCredentials = process.env.ACTIONS_ID_TOKEN_REQUEST_URL && process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN ? {
|
|
161823
162449
|
requestUrl: process.env.ACTIONS_ID_TOKEN_REQUEST_URL,
|
|
161824
162450
|
requestToken: process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN
|
|
161825
162451
|
} : null;
|
|
162452
|
+
const tokenRef = __using(_stack2, await resolveTokens({ push: payload.push, oidc: oidcCredentials }), true);
|
|
162453
|
+
wipeRunnerLeakSurface();
|
|
161826
162454
|
if (payload.shell !== "enabled") {
|
|
161827
162455
|
delete process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
|
|
161828
162456
|
delete process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
|
@@ -161835,7 +162463,7 @@ async function main() {
|
|
|
161835
162463
|
repo: runContext.repo,
|
|
161836
162464
|
toolState
|
|
161837
162465
|
});
|
|
161838
|
-
const octokit = createOctokit(tokenRef.mcpToken);
|
|
162466
|
+
const octokit = createOctokit(tokenRef.mcpToken, getMcpTokenRefresh());
|
|
161839
162467
|
const runInfo = await resolveRun({ octokit });
|
|
161840
162468
|
let toolContext;
|
|
161841
162469
|
let progressCallbackDisabled = false;
|
|
@@ -161847,13 +162475,13 @@ async function main() {
|
|
|
161847
162475
|
if (payload.cwd && process.cwd() !== payload.cwd) {
|
|
161848
162476
|
process.chdir(payload.cwd);
|
|
161849
162477
|
}
|
|
161850
|
-
const
|
|
162478
|
+
const tmpdir4 = createTempDirectory();
|
|
161851
162479
|
const originalBody = payload.event.body;
|
|
161852
162480
|
const resolvedBody = await resolveBody({
|
|
161853
162481
|
event: payload.event,
|
|
161854
162482
|
octokit,
|
|
161855
162483
|
repo: runContext.repo,
|
|
161856
|
-
tmpdir:
|
|
162484
|
+
tmpdir: tmpdir4,
|
|
161857
162485
|
githubToken: tokenRef.mcpToken
|
|
161858
162486
|
});
|
|
161859
162487
|
if (resolvedBody !== originalBody) {
|
|
@@ -161862,33 +162490,18 @@ async function main() {
|
|
|
161862
162490
|
payload.prompt = payload.prompt.replace(originalBody, resolvedBody ?? "");
|
|
161863
162491
|
}
|
|
161864
162492
|
}
|
|
161865
|
-
const gitAuthServer = __using(_stack, await startGitAuthServer(
|
|
162493
|
+
const gitAuthServer = __using(_stack, await startGitAuthServer(tmpdir4), true);
|
|
161866
162494
|
setGitAuthServer(gitAuthServer);
|
|
161867
|
-
const
|
|
161868
|
-
const authorized2 = getAuthorizedModels();
|
|
161869
|
-
const fallback = selectFallbackModelIfNeeded({
|
|
161870
|
-
resolvedModel: initialResolvedModel,
|
|
161871
|
-
proxyModel: payload.proxyModel,
|
|
161872
|
-
authorized: authorized2,
|
|
161873
|
-
agentName: resolveAgent({ model: initialResolvedModel }).name
|
|
161874
|
-
});
|
|
161875
|
-
const effectiveSlug = fallback.fallback ? fallback.to : payload.model;
|
|
161876
|
-
const resolvedModel = fallback.fallback ? fallback.to : initialResolvedModel;
|
|
161877
|
-
if (fallback.fallback) {
|
|
161878
|
-
log.warning(
|
|
161879
|
-
`\xBB fell back from ${fallback.from} to ${fallback.to} \u2014 no BYOK key present in runner env. add a provider key in repo secrets to use ${fallback.from} instead.`
|
|
161880
|
-
);
|
|
161881
|
-
toolState.modelFallback = { from: fallback.from };
|
|
161882
|
-
}
|
|
162495
|
+
const resolvedModel = payload.proxyModel ? void 0 : resolveModel({ slug: payload.model });
|
|
161883
162496
|
vertexCredentials = materializeVertexCredentials({ model: resolvedModel });
|
|
161884
162497
|
const agent2 = resolveAgent({ model: resolvedModel });
|
|
161885
|
-
const effectiveModel = payload.proxyModel ?? resolvedModel ??
|
|
162498
|
+
const effectiveModel = payload.proxyModel ?? resolvedModel ?? payload.model;
|
|
161886
162499
|
toolState.model = effectiveModel;
|
|
161887
|
-
if (!
|
|
162500
|
+
if (!payload.proxyModel) {
|
|
161888
162501
|
validateAgentApiKey({
|
|
161889
162502
|
agent: agent2,
|
|
161890
162503
|
model: effectiveModel,
|
|
161891
|
-
authorized:
|
|
162504
|
+
authorized: getAuthorizedModels(),
|
|
161892
162505
|
owner: runContext.repo.owner,
|
|
161893
162506
|
name: runContext.repo.name
|
|
161894
162507
|
});
|
|
@@ -161905,7 +162518,7 @@ async function main() {
|
|
|
161905
162518
|
timer.checkpoint("git");
|
|
161906
162519
|
const pmSpec = await resolvePackageManagerSpec(process.cwd());
|
|
161907
162520
|
if (pmSpec) {
|
|
161908
|
-
await ensurePackageManager({ spec: pmSpec, binDir: packageManagerBinDir(
|
|
162521
|
+
await ensurePackageManager({ spec: pmSpec, binDir: packageManagerBinDir(tmpdir4) });
|
|
161909
162522
|
}
|
|
161910
162523
|
timer.checkpoint("packageManager");
|
|
161911
162524
|
const setupHook = await executeLifecycleHook({
|
|
@@ -161918,26 +162531,34 @@ async function main() {
|
|
|
161918
162531
|
}
|
|
161919
162532
|
timer.checkpoint("lifecycleHooks::setup");
|
|
161920
162533
|
const agentId = agent2.name;
|
|
161921
|
-
const modes2 = [
|
|
162534
|
+
const modes2 = [
|
|
162535
|
+
...computeModes(agentId, runContext.repoSettings.signedCommits),
|
|
162536
|
+
...runContext.repoSettings.modes
|
|
162537
|
+
];
|
|
161922
162538
|
const outputSchema = resolveOutputSchema();
|
|
161923
162539
|
toolContext = {
|
|
161924
162540
|
agentId,
|
|
161925
162541
|
repo: runContext.repo,
|
|
161926
162542
|
payload,
|
|
161927
162543
|
octokit,
|
|
161928
|
-
|
|
162544
|
+
// live getter so raw-token consumers (asset fetches, plan/summary-comment
|
|
162545
|
+
// GETs) see the refreshed MCP token after a mid-run re-acquisition (#891)
|
|
162546
|
+
get githubInstallationToken() {
|
|
162547
|
+
return getGitHubInstallationToken();
|
|
162548
|
+
},
|
|
161929
162549
|
gitToken: tokenRef.gitToken,
|
|
161930
162550
|
apiToken: runContext.apiToken,
|
|
161931
162551
|
modes: modes2,
|
|
161932
162552
|
postCheckoutScript: runContext.repoSettings.postCheckoutScript,
|
|
161933
162553
|
prepushScript: runContext.repoSettings.prepushScript,
|
|
161934
162554
|
prApproveEnabled: runContext.repoSettings.prApproveEnabled,
|
|
162555
|
+
signedCommits: runContext.repoSettings.signedCommits,
|
|
161935
162556
|
modeInstructions: runContext.repoSettings.modeInstructions,
|
|
161936
162557
|
toolState,
|
|
161937
162558
|
runId: runInfo.runId,
|
|
161938
162559
|
jobId: runInfo.jobId,
|
|
161939
162560
|
mcpServerUrl: "",
|
|
161940
|
-
tmpdir:
|
|
162561
|
+
tmpdir: tmpdir4,
|
|
161941
162562
|
oss: runContext.oss,
|
|
161942
162563
|
plan: runContext.plan,
|
|
161943
162564
|
resolvedModel
|
|
@@ -161948,7 +162569,7 @@ async function main() {
|
|
|
161948
162569
|
timer.checkpoint("mcpServer");
|
|
161949
162570
|
try {
|
|
161950
162571
|
const learningsPath = await seedLearningsFile({
|
|
161951
|
-
tmpdir:
|
|
162572
|
+
tmpdir: tmpdir4,
|
|
161952
162573
|
current: runContext.repoSettings.learnings
|
|
161953
162574
|
});
|
|
161954
162575
|
toolState.learningsFilePath = learningsPath;
|
|
@@ -161965,7 +162586,7 @@ async function main() {
|
|
|
161965
162586
|
}
|
|
161966
162587
|
if (payload.generateSummary && payload.event.is_pr && payload.event.issue_number) {
|
|
161967
162588
|
const previousSnapshot = await fetchPreviousSnapshot(toolContext, payload.event.issue_number);
|
|
161968
|
-
const filePath = await seedSummaryFile({ tmpdir:
|
|
162589
|
+
const filePath = await seedSummaryFile({ tmpdir: tmpdir4, previousSnapshot });
|
|
161969
162590
|
toolState.summaryFilePath = filePath;
|
|
161970
162591
|
try {
|
|
161971
162592
|
toolState.summarySeed = await readFile5(filePath, "utf8");
|
|
@@ -161985,6 +162606,7 @@ async function main() {
|
|
|
161985
162606
|
modes: modes2,
|
|
161986
162607
|
agentId,
|
|
161987
162608
|
outputSchema,
|
|
162609
|
+
signedCommits: runContext.repoSettings.signedCommits,
|
|
161988
162610
|
learningsFilePath: toolState.learningsFilePath ?? null,
|
|
161989
162611
|
learningsHeadings: runContext.repoSettings.learningsHeadings,
|
|
161990
162612
|
setupHookFailure: describeSetupFailure(setupHook.failure)
|
|
@@ -162003,7 +162625,7 @@ ${instructions.user}` : null,
|
|
|
162003
162625
|
log.info(instructions.full);
|
|
162004
162626
|
});
|
|
162005
162627
|
if (agentId === "opencode") {
|
|
162006
|
-
const pluginDir =
|
|
162628
|
+
const pluginDir = join24(process.cwd(), ".opencode", "plugin");
|
|
162007
162629
|
const hasPlugins = existsSync8(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
|
|
162008
162630
|
if (hasPlugins && toolState.dependencyInstallation?.promise) {
|
|
162009
162631
|
log.info(
|
|
@@ -162058,7 +162680,7 @@ ${instructions.user}` : null,
|
|
|
162058
162680
|
payload,
|
|
162059
162681
|
resolvedModel,
|
|
162060
162682
|
mcpServerUrl: mcpHttpServer.url,
|
|
162061
|
-
tmpdir:
|
|
162683
|
+
tmpdir: tmpdir4,
|
|
162062
162684
|
// PULLFROG_DATA_DIR (/var/lib/pullfrog) holds codex auth.json + any
|
|
162063
162685
|
// future pullfrog-managed on-disk secrets. bash via MCP tmpfs-overlays
|
|
162064
162686
|
// it; agent native FS tools deny it via the same secretDenyPaths plumbing
|
|
@@ -162334,7 +162956,7 @@ async function run(args2) {
|
|
|
162334
162956
|
}
|
|
162335
162957
|
|
|
162336
162958
|
// commands/init.ts
|
|
162337
|
-
import { execFileSync as
|
|
162959
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
162338
162960
|
var import_arg3 = __toESM(require_arg(), 1);
|
|
162339
162961
|
var import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
162340
162962
|
var PULLFROG_API_URL2 = (process.env.PULLFROG_API_URL || "https://pullfrog.com").replace(
|
|
@@ -162394,7 +163016,7 @@ function handleCancel2(value2) {
|
|
|
162394
163016
|
function getGhToken2() {
|
|
162395
163017
|
let token;
|
|
162396
163018
|
try {
|
|
162397
|
-
token =
|
|
163019
|
+
token = execFileSync9("gh", ["auth", "token"], { encoding: "utf-8" }).trim();
|
|
162398
163020
|
} catch {
|
|
162399
163021
|
bail2(
|
|
162400
163022
|
`gh cli not found or not authenticated.
|
|
@@ -162437,7 +163059,7 @@ async function ghApi(path4, token) {
|
|
|
162437
163059
|
function parseGitRemote2() {
|
|
162438
163060
|
let url4;
|
|
162439
163061
|
try {
|
|
162440
|
-
url4 =
|
|
163062
|
+
url4 = execFileSync9("git", ["remote", "get-url", "origin"], { encoding: "utf-8" }).trim();
|
|
162441
163063
|
} catch {
|
|
162442
163064
|
bail2("not a git repository or no 'origin' remote found.");
|
|
162443
163065
|
}
|
|
@@ -162448,10 +163070,10 @@ function parseGitRemote2() {
|
|
|
162448
163070
|
function openBrowser(url4) {
|
|
162449
163071
|
try {
|
|
162450
163072
|
const platform = process.platform;
|
|
162451
|
-
if (platform === "darwin")
|
|
163073
|
+
if (platform === "darwin") execFileSync9("open", [url4], { stdio: "ignore" });
|
|
162452
163074
|
else if (platform === "win32")
|
|
162453
|
-
|
|
162454
|
-
else
|
|
163075
|
+
execFileSync9("cmd", ["/c", "start", "", url4], { stdio: "ignore" });
|
|
163076
|
+
else execFileSync9("xdg-open", [url4], { stdio: "ignore" });
|
|
162455
163077
|
} catch {
|
|
162456
163078
|
}
|
|
162457
163079
|
}
|
|
@@ -162678,7 +163300,7 @@ function setGhSecret(ctx) {
|
|
|
162678
163300
|
let orgFailed = false;
|
|
162679
163301
|
if (ctx.org) {
|
|
162680
163302
|
try {
|
|
162681
|
-
|
|
163303
|
+
execFileSync9("gh", ["secret", "set", ctx.name, "--org", ctx.org, "--visibility", "all"], {
|
|
162682
163304
|
input: ctx.value,
|
|
162683
163305
|
stdio: ["pipe", "ignore", "pipe"],
|
|
162684
163306
|
encoding: "utf-8"
|
|
@@ -162689,7 +163311,7 @@ function setGhSecret(ctx) {
|
|
|
162689
163311
|
}
|
|
162690
163312
|
}
|
|
162691
163313
|
try {
|
|
162692
|
-
|
|
163314
|
+
execFileSync9("gh", ["secret", "set", ctx.name, "--repo", ctx.repoSlug], {
|
|
162693
163315
|
input: ctx.value,
|
|
162694
163316
|
stdio: ["pipe", "ignore", "pipe"],
|
|
162695
163317
|
encoding: "utf-8"
|
|
@@ -163061,7 +163683,7 @@ async function run2() {
|
|
|
163061
163683
|
}
|
|
163062
163684
|
|
|
163063
163685
|
// cli.ts
|
|
163064
|
-
var VERSION10 = "0.1.
|
|
163686
|
+
var VERSION10 = "0.1.30";
|
|
163065
163687
|
var bin = basename2(process.argv[1] || "");
|
|
163066
163688
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
163067
163689
|
var rawArgs = process.argv.slice(2);
|