pullfrog 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/opencodePlugin.d.ts +60 -0
- package/dist/agents/postRun.d.ts +12 -8
- package/dist/agents/shared.d.ts +7 -0
- package/dist/cli.mjs +365 -138
- package/dist/index.js +362 -135
- package/dist/internal.js +4 -17
- package/dist/mcp/server.d.ts +3 -0
- package/dist/utils/apiUrl.d.ts +8 -0
- package/dist/utils/instructions.d.ts +4 -1
- package/dist/utils/learnings.d.ts +31 -0
- package/dist/utils/subprocess.d.ts +0 -1
- package/package.json +1 -1
- package/dist/mcp/learnings.d.ts +0 -6
package/dist/index.js
CHANGED
|
@@ -18198,7 +18198,7 @@ var require_summary = __commonJS({
|
|
|
18198
18198
|
exports.summary = exports.markdownSummary = exports.SUMMARY_DOCS_URL = exports.SUMMARY_ENV_VAR = void 0;
|
|
18199
18199
|
var os_1 = __require("os");
|
|
18200
18200
|
var fs_1 = __require("fs");
|
|
18201
|
-
var { access, appendFile, writeFile:
|
|
18201
|
+
var { access, appendFile, writeFile: writeFile4 } = fs_1.promises;
|
|
18202
18202
|
exports.SUMMARY_ENV_VAR = "GITHUB_STEP_SUMMARY";
|
|
18203
18203
|
exports.SUMMARY_DOCS_URL = "https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary";
|
|
18204
18204
|
var Summary = class {
|
|
@@ -18256,7 +18256,7 @@ var require_summary = __commonJS({
|
|
|
18256
18256
|
return __awaiter(this, void 0, void 0, function* () {
|
|
18257
18257
|
const overwrite = !!(options === null || options === void 0 ? void 0 : options.overwrite);
|
|
18258
18258
|
const filePath = yield this.filePath();
|
|
18259
|
-
const writeFunc = overwrite ?
|
|
18259
|
+
const writeFunc = overwrite ? writeFile4 : appendFile;
|
|
18260
18260
|
yield writeFunc(filePath, this._buffer, { encoding: "utf8" });
|
|
18261
18261
|
return this.emptyBuffer();
|
|
18262
18262
|
});
|
|
@@ -62662,8 +62662,8 @@ var require_snapshot_utils = __commonJS({
|
|
|
62662
62662
|
var require_snapshot_recorder = __commonJS({
|
|
62663
62663
|
"node_modules/.pnpm/undici@7.22.0/node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
|
|
62664
62664
|
"use strict";
|
|
62665
|
-
var { writeFile:
|
|
62666
|
-
var { dirname:
|
|
62665
|
+
var { writeFile: writeFile4, readFile: readFile5, mkdir: mkdir3 } = __require("node:fs/promises");
|
|
62666
|
+
var { dirname: dirname6, resolve: resolve3 } = __require("node:path");
|
|
62667
62667
|
var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("node:timers");
|
|
62668
62668
|
var { InvalidArgumentError, UndiciError } = require_errors4();
|
|
62669
62669
|
var { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require_snapshot_utils();
|
|
@@ -62864,7 +62864,7 @@ var require_snapshot_recorder = __commonJS({
|
|
|
62864
62864
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
62865
62865
|
}
|
|
62866
62866
|
try {
|
|
62867
|
-
const data = await
|
|
62867
|
+
const data = await readFile5(resolve3(path3), "utf8");
|
|
62868
62868
|
const parsed2 = JSON.parse(data);
|
|
62869
62869
|
if (Array.isArray(parsed2)) {
|
|
62870
62870
|
this.#snapshots.clear();
|
|
@@ -62894,12 +62894,12 @@ var require_snapshot_recorder = __commonJS({
|
|
|
62894
62894
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
62895
62895
|
}
|
|
62896
62896
|
const resolvedPath = resolve3(path3);
|
|
62897
|
-
await
|
|
62897
|
+
await mkdir3(dirname6(resolvedPath), { recursive: true });
|
|
62898
62898
|
const data = Array.from(this.#snapshots.entries()).map(([hash2, snapshot2]) => ({
|
|
62899
62899
|
hash: hash2,
|
|
62900
62900
|
snapshot: snapshot2
|
|
62901
62901
|
}));
|
|
62902
|
-
await
|
|
62902
|
+
await writeFile4(resolvedPath, JSON.stringify(data, null, 2), { flush: true });
|
|
62903
62903
|
}
|
|
62904
62904
|
/**
|
|
62905
62905
|
* Clears all recorded snapshots
|
|
@@ -97475,14 +97475,14 @@ var require_turndown_cjs = __commonJS({
|
|
|
97475
97475
|
} else if (node2.nodeType === 1) {
|
|
97476
97476
|
replacement = replacementForNode.call(self2, node2);
|
|
97477
97477
|
}
|
|
97478
|
-
return
|
|
97478
|
+
return join18(output, replacement);
|
|
97479
97479
|
}, "");
|
|
97480
97480
|
}
|
|
97481
97481
|
function postProcess(output) {
|
|
97482
97482
|
var self2 = this;
|
|
97483
97483
|
this.rules.forEach(function(rule) {
|
|
97484
97484
|
if (typeof rule.append === "function") {
|
|
97485
|
-
output =
|
|
97485
|
+
output = join18(output, rule.append(self2.options));
|
|
97486
97486
|
}
|
|
97487
97487
|
});
|
|
97488
97488
|
return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
|
|
@@ -97494,7 +97494,7 @@ var require_turndown_cjs = __commonJS({
|
|
|
97494
97494
|
if (whitespace.leading || whitespace.trailing) content = content.trim();
|
|
97495
97495
|
return whitespace.leading + rule.replacement(content, node2, this.options) + whitespace.trailing;
|
|
97496
97496
|
}
|
|
97497
|
-
function
|
|
97497
|
+
function join18(output, replacement) {
|
|
97498
97498
|
var s1 = trimTrailingNewlines(output);
|
|
97499
97499
|
var s2 = trimLeadingNewlines(replacement);
|
|
97500
97500
|
var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
|
|
@@ -98926,8 +98926,8 @@ var require_fast_content_type_parse = __commonJS({
|
|
|
98926
98926
|
// main.ts
|
|
98927
98927
|
var core6 = __toESM(require_core(), 1);
|
|
98928
98928
|
import { existsSync as existsSync7, readdirSync } from "node:fs";
|
|
98929
|
-
import { readFile as
|
|
98930
|
-
import { join as
|
|
98929
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
98930
|
+
import { join as join17 } from "node:path";
|
|
98931
98931
|
|
|
98932
98932
|
// node_modules/.pnpm/@ark+util@0.56.0/node_modules/@ark/util/out/arrays.js
|
|
98933
98933
|
var liftArray = (data) => Array.isArray(data) ? data : [data];
|
|
@@ -107723,6 +107723,13 @@ function getApiUrl() {
|
|
|
107723
107723
|
log.debug(`resolved API_URL: ${raw2}`);
|
|
107724
107724
|
return raw2;
|
|
107725
107725
|
}
|
|
107726
|
+
function isLocalApiUrl() {
|
|
107727
|
+
try {
|
|
107728
|
+
return isLocalUrl(new URL(getApiUrl()));
|
|
107729
|
+
} catch {
|
|
107730
|
+
return false;
|
|
107731
|
+
}
|
|
107732
|
+
}
|
|
107726
107733
|
|
|
107727
107734
|
// models.ts
|
|
107728
107735
|
function provider(config3) {
|
|
@@ -108961,6 +108968,7 @@ function CreateCommentTool(ctx) {
|
|
|
108961
108968
|
body: bodyWithFooter
|
|
108962
108969
|
});
|
|
108963
108970
|
ctx.toolState.wasUpdated = true;
|
|
108971
|
+
log.info(`\xBB created comment ${result.data.id}`);
|
|
108964
108972
|
if (commentType === "Plan") {
|
|
108965
108973
|
if (result.data.node_id) {
|
|
108966
108974
|
await patchWorkflowRunFields(ctx, { planCommentNodeId: result.data.node_id });
|
|
@@ -108974,6 +108982,7 @@ function CreateCommentTool(ctx) {
|
|
|
108974
108982
|
comment_id: result.data.id,
|
|
108975
108983
|
body: bodyWithPlanLink
|
|
108976
108984
|
});
|
|
108985
|
+
log.info(`\xBB updated comment ${updateResult.data.id}`);
|
|
108977
108986
|
return {
|
|
108978
108987
|
success: true,
|
|
108979
108988
|
commentId: updateResult.data.id,
|
|
@@ -109007,6 +109016,7 @@ function EditCommentTool(ctx) {
|
|
|
109007
109016
|
comment_id: commentId,
|
|
109008
109017
|
body: bodyWithFooter
|
|
109009
109018
|
});
|
|
109019
|
+
log.info(`\xBB updated comment ${result.data.id}`);
|
|
109010
109020
|
return {
|
|
109011
109021
|
success: true,
|
|
109012
109022
|
commentId: result.data.id,
|
|
@@ -109142,6 +109152,9 @@ ${collapsible}`;
|
|
|
109142
109152
|
message: "progress recorded (no GitHub comment created - this may occur for workflow_dispatch events or when there is no associated issue/PR)"
|
|
109143
109153
|
};
|
|
109144
109154
|
}
|
|
109155
|
+
if (result.commentId !== void 0) {
|
|
109156
|
+
log.info(`\xBB ${result.action} comment ${result.commentId}`);
|
|
109157
|
+
}
|
|
109145
109158
|
if (!params.target_plan_comment) {
|
|
109146
109159
|
ctx.toolState.finalSummaryWritten = true;
|
|
109147
109160
|
}
|
|
@@ -109192,6 +109205,7 @@ function ReplyToReviewCommentTool(ctx) {
|
|
|
109192
109205
|
comment_id,
|
|
109193
109206
|
body: bodyWithFooter
|
|
109194
109207
|
});
|
|
109208
|
+
log.info(`\xBB created review comment ${result.data.id} (in reply to ${comment_id})`);
|
|
109195
109209
|
ctx.toolState.wasUpdated = true;
|
|
109196
109210
|
return {
|
|
109197
109211
|
success: true,
|
|
@@ -109741,11 +109755,6 @@ async function spawn(options) {
|
|
|
109741
109755
|
`spawn activity timer: pid=${child.pid} cmd=${options.cmd} timeout=${activityTimeoutMs}ms`
|
|
109742
109756
|
);
|
|
109743
109757
|
activityCheckIntervalId = setInterval(() => {
|
|
109744
|
-
if (options.isPausedExternally?.()) {
|
|
109745
|
-
lastActivityTime = performance3.now();
|
|
109746
|
-
log.debug(`spawn activity check: pid=${child.pid} paused externally`);
|
|
109747
|
-
return;
|
|
109748
|
-
}
|
|
109749
109758
|
const idleMs = performance3.now() - lastActivityTime;
|
|
109750
109759
|
log.debug(
|
|
109751
109760
|
`spawn activity check: pid=${child.pid} idle=${Math.round(idleMs)}ms / ${activityTimeoutMs}ms`
|
|
@@ -142266,7 +142275,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
142266
142275
|
// package.json
|
|
142267
142276
|
var package_default = {
|
|
142268
142277
|
name: "pullfrog",
|
|
142269
|
-
version: "0.1.
|
|
142278
|
+
version: "0.1.2",
|
|
142270
142279
|
type: "module",
|
|
142271
142280
|
bin: {
|
|
142272
142281
|
pullfrog: "dist/cli.mjs",
|
|
@@ -143210,6 +143219,10 @@ ${integrateStep}
|
|
|
143210
143219
|
if (!pushed) {
|
|
143211
143220
|
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
143212
143221
|
}
|
|
143222
|
+
const pushedSha = $("git", ["rev-parse", "HEAD"], { log: false }).trim();
|
|
143223
|
+
log.info(
|
|
143224
|
+
`\xBB pushed branch ${branch} to ${pushDest.remoteName}/${pushDest.remoteBranch} (sha ${pushedSha})`
|
|
143225
|
+
);
|
|
143213
143226
|
return {
|
|
143214
143227
|
success: true,
|
|
143215
143228
|
branch,
|
|
@@ -143358,6 +143371,7 @@ function DeleteBranchTool(ctx) {
|
|
|
143358
143371
|
await $git("push", ["origin", "--delete", `refs/heads/${params.branchName}`], {
|
|
143359
143372
|
token: ctx.gitToken
|
|
143360
143373
|
});
|
|
143374
|
+
log.info(`\xBB deleted branch ${params.branchName}`);
|
|
143361
143375
|
return { success: true, deleted: params.branchName };
|
|
143362
143376
|
})
|
|
143363
143377
|
});
|
|
@@ -143383,6 +143397,7 @@ function PushTagsTool(ctx) {
|
|
|
143383
143397
|
await $git("push", pushArgs, {
|
|
143384
143398
|
token: ctx.gitToken
|
|
143385
143399
|
});
|
|
143400
|
+
log.info(`\xBB pushed tag ${params.tag}`);
|
|
143386
143401
|
return { success: true, tag: params.tag };
|
|
143387
143402
|
})
|
|
143388
143403
|
});
|
|
@@ -143707,6 +143722,7 @@ function CreatePullRequestReviewTool(ctx) {
|
|
|
143707
143722
|
}
|
|
143708
143723
|
const reviewId = result.data.id;
|
|
143709
143724
|
const reviewNodeId = result.data.node_id;
|
|
143725
|
+
log.info(`\xBB created review ${reviewId} on pull request #${pull_number}`);
|
|
143710
143726
|
const actuallyReviewedSha = ctx.toolState.checkoutSha ?? params.commit_id;
|
|
143711
143727
|
ctx.toolState.review = {
|
|
143712
143728
|
id: reviewId,
|
|
@@ -144571,6 +144587,7 @@ function IssueTool(ctx) {
|
|
|
144571
144587
|
labels: params.labels ?? [],
|
|
144572
144588
|
assignees: params.assignees ?? []
|
|
144573
144589
|
});
|
|
144590
|
+
log.info(`\xBB created issue #${result.data.number} (id ${result.data.id})`);
|
|
144574
144591
|
const nodeId = result.data.node_id;
|
|
144575
144592
|
if (typeof nodeId === "string" && nodeId.length > 0) {
|
|
144576
144593
|
await patchWorkflowRunFields(ctx, {
|
|
@@ -144762,6 +144779,7 @@ function AddLabelsTool(ctx) {
|
|
|
144762
144779
|
issue_number,
|
|
144763
144780
|
labels
|
|
144764
144781
|
});
|
|
144782
|
+
log.info(`\xBB added labels [${labels.join(", ")}] to issue #${issue_number}`);
|
|
144765
144783
|
return {
|
|
144766
144784
|
success: true,
|
|
144767
144785
|
labels: result.data.map((label) => label.name)
|
|
@@ -144770,40 +144788,6 @@ function AddLabelsTool(ctx) {
|
|
|
144770
144788
|
});
|
|
144771
144789
|
}
|
|
144772
144790
|
|
|
144773
|
-
// mcp/learnings.ts
|
|
144774
|
-
var UpdateLearningsParams = type({
|
|
144775
|
-
learnings: type.string.describe(
|
|
144776
|
-
"the FULL merged learnings as a flat bullet list. each line starts with `- `. one discrete, actionable fact per bullet. combine existing bullets from the prompt with your new discoveries. deduplicate \u2014 if an existing bullet covers the same fact, update it in place rather than adding a new one. drop bullets that are clearly wrong or no longer relevant to the current codebase. keep the list focused and concise."
|
|
144777
|
-
)
|
|
144778
|
-
});
|
|
144779
|
-
function UpdateLearningsTool(ctx) {
|
|
144780
|
-
return tool({
|
|
144781
|
-
name: "update_learnings",
|
|
144782
|
-
description: "persist operational learnings about this repository (setup steps, test commands, key conventions, patterns). ONLY call this when you have high confidence the information is correct and broadly useful for future runs \u2014 not for one-off findings or uncertain observations. format: flat bullet list (`- ` per line, one fact per bullet). pass the FULL merged list \u2014 combine existing learnings from the prompt with new discoveries. deduplicate, and drop bullets that are clearly wrong or no longer relevant to the current codebase.",
|
|
144783
|
-
parameters: UpdateLearningsParams,
|
|
144784
|
-
execute: execute(async (params) => {
|
|
144785
|
-
const response = await apiFetch({
|
|
144786
|
-
path: `/api/repo/${ctx.repo.owner}/${ctx.repo.name}/learnings`,
|
|
144787
|
-
method: "PATCH",
|
|
144788
|
-
headers: {
|
|
144789
|
-
authorization: `Bearer ${ctx.apiToken}`,
|
|
144790
|
-
"content-type": "application/json"
|
|
144791
|
-
},
|
|
144792
|
-
body: JSON.stringify({
|
|
144793
|
-
learnings: params.learnings,
|
|
144794
|
-
model: ctx.toolState.model
|
|
144795
|
-
}),
|
|
144796
|
-
signal: AbortSignal.timeout(1e4)
|
|
144797
|
-
});
|
|
144798
|
-
if (!response.ok) {
|
|
144799
|
-
const error49 = await response.text();
|
|
144800
|
-
throw new Error(`failed to update learnings: ${error49}`);
|
|
144801
|
-
}
|
|
144802
|
-
return { success: true };
|
|
144803
|
-
})
|
|
144804
|
-
});
|
|
144805
|
-
}
|
|
144806
|
-
|
|
144807
144791
|
// mcp/output.ts
|
|
144808
144792
|
var import_ajv3 = __toESM(require_ajv(), 1);
|
|
144809
144793
|
var SetOutputParams = type({
|
|
@@ -144897,6 +144881,7 @@ function UpdatePullRequestBodyTool(ctx) {
|
|
|
144897
144881
|
pull_number: params.pull_number,
|
|
144898
144882
|
body: bodyWithFooter
|
|
144899
144883
|
});
|
|
144884
|
+
log.info(`\xBB updated pull request #${result.data.number}`);
|
|
144900
144885
|
ctx.toolState.wasUpdated = true;
|
|
144901
144886
|
return {
|
|
144902
144887
|
success: true,
|
|
@@ -144924,6 +144909,7 @@ function CreatePullRequestTool(ctx) {
|
|
|
144924
144909
|
base: params.base,
|
|
144925
144910
|
draft: params.draft ?? false
|
|
144926
144911
|
});
|
|
144912
|
+
log.info(`\xBB created pull request #${result.data.number} (id ${result.data.id})`);
|
|
144927
144913
|
const reviewer = ctx.payload.triggerer;
|
|
144928
144914
|
if (reviewer) {
|
|
144929
144915
|
try {
|
|
@@ -145475,7 +145461,7 @@ function ResolveReviewThreadTool(ctx) {
|
|
|
145475
145461
|
threadId: params.thread_id
|
|
145476
145462
|
});
|
|
145477
145463
|
const thread = response.resolveReviewThread.thread;
|
|
145478
|
-
log.
|
|
145464
|
+
log.info(`\xBB resolved review thread ${thread.id}`);
|
|
145479
145465
|
return {
|
|
145480
145466
|
thread_id: thread.id,
|
|
145481
145467
|
is_resolved: thread.isResolved,
|
|
@@ -145947,6 +145933,7 @@ function UploadFileTool(ctx) {
|
|
|
145947
145933
|
if (!uploadResponse.ok) {
|
|
145948
145934
|
throw new Error(`failed to upload file: ${uploadResponse.statusText}`);
|
|
145949
145935
|
}
|
|
145936
|
+
log.info(`\xBB uploaded file ${publicUrl}`);
|
|
145950
145937
|
return { success: true, publicUrl, filename, contentLength, contentType };
|
|
145951
145938
|
})
|
|
145952
145939
|
});
|
|
@@ -146040,8 +146027,7 @@ function buildOrchestratorTools(ctx, outputSchema) {
|
|
|
146040
146027
|
PushTagsTool(ctx),
|
|
146041
146028
|
DeleteBranchTool(ctx),
|
|
146042
146029
|
CreatePullRequestTool(ctx),
|
|
146043
|
-
UpdatePullRequestBodyTool(ctx)
|
|
146044
|
-
UpdateLearningsTool(ctx)
|
|
146030
|
+
UpdatePullRequestBodyTool(ctx)
|
|
146045
146031
|
];
|
|
146046
146032
|
}
|
|
146047
146033
|
async function tryStartMcpServer(ctx, tools, port) {
|
|
@@ -146198,9 +146184,6 @@ Rules:
|
|
|
146198
146184
|
- Do NOT include a changelog section \u2014 the key changes list serves this purpose
|
|
146199
146185
|
- Focus on *intent*, not *what* \u2014 the diff already shows what changed
|
|
146200
146186
|
- Get the file count and commit count from the checkout_pr metadata, not by counting manually`;
|
|
146201
|
-
function learningsStep(t, n) {
|
|
146202
|
-
return `${n}. **learnings** (only if high confidence): if you discovered something about repo setup, test commands, conventions, or patterns that you are confident is correct and would reliably help future runs, call \`${t("update_learnings")}\` to persist it. skip this step if you are unsure or the finding is speculative/one-off. format as a flat bullet list (\`- \` per line, one fact per bullet). merge with existing learnings from the prompt \u2014 pass the FULL merged list. deduplicate, and drop bullets that are clearly wrong or no longer relevant to the current codebase.`;
|
|
146203
|
-
}
|
|
146204
146187
|
function computeModes(agentId) {
|
|
146205
146188
|
const t = (toolName) => formatMcpToolRef(agentId, toolName);
|
|
146206
146189
|
return [
|
|
@@ -146256,8 +146239,6 @@ function computeModes(agentId) {
|
|
|
146256
146239
|
- create a PR via \`${t("create_pull_request")}\`
|
|
146257
146240
|
- call \`${t("report_progress")}\` with the PR link or the exact error if push/PR failed
|
|
146258
146241
|
|
|
146259
|
-
${learningsStep(t, 6)}
|
|
146260
|
-
|
|
146261
146242
|
### Notes
|
|
146262
146243
|
|
|
146263
146244
|
For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
@@ -146285,9 +146266,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
146285
146266
|
- confirm a clean working tree, then push via \`${t("push_branch")}\` (same push/prepush guidance as Build mode in *SYSTEM*)
|
|
146286
146267
|
- reply to each comment using \`${t("reply_to_review_comment")}\`
|
|
146287
146268
|
- resolve addressed threads via \`${t("resolve_review_thread")}\`
|
|
146288
|
-
- call \`${t("report_progress")}\` with a brief summary (or the exact push error if push failed)
|
|
146289
|
-
|
|
146290
|
-
${learningsStep(t, 6)}`
|
|
146269
|
+
- call \`${t("report_progress")}\` with a brief summary (or the exact push error if push failed)`
|
|
146291
146270
|
},
|
|
146292
146271
|
// Review and IncrementalReview use the multi-lens orchestrator pattern
|
|
146293
146272
|
// (canonical source: .claude/commands/anneal.md). The orchestrator does
|
|
@@ -146458,9 +146437,7 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
146458
146437
|
|
|
146459
146438
|
2. Produce a structured, actionable plan with clear milestones.
|
|
146460
146439
|
|
|
146461
|
-
3. Call \`${t("report_progress")}\` with the plan
|
|
146462
|
-
|
|
146463
|
-
${learningsStep(t, 4)}`
|
|
146440
|
+
3. Call \`${t("report_progress")}\` with the plan.`
|
|
146464
146441
|
},
|
|
146465
146442
|
{
|
|
146466
146443
|
name: "Fix",
|
|
@@ -146482,9 +146459,7 @@ ${learningsStep(t, 4)}`
|
|
|
146482
146459
|
|
|
146483
146460
|
5. Finalize:
|
|
146484
146461
|
- confirm a clean working tree, then push via \`${t("push_branch")}\` (same push/prepush guidance as Build mode in *SYSTEM*)
|
|
146485
|
-
- call \`${t("report_progress")}\` with the diagnosis and fix summary (or the exact push error if push failed)
|
|
146486
|
-
|
|
146487
|
-
${learningsStep(t, 6)}`
|
|
146462
|
+
- call \`${t("report_progress")}\` with the diagnosis and fix summary (or the exact push error if push failed)`
|
|
146488
146463
|
},
|
|
146489
146464
|
{
|
|
146490
146465
|
name: "ResolveConflicts",
|
|
@@ -146528,9 +146503,7 @@ ${learningsStep(t, 6)}`
|
|
|
146528
146503
|
3. Finalize:
|
|
146529
146504
|
- if code changes were made, push to a pull request (new or existing) using \`${t("push_branch")}\` and \`${t("create_pull_request")}\` as needed. \`git status\` must be clean before you finish (see *SYSTEM* Git rules if push fails).
|
|
146530
146505
|
- call \`${t("report_progress")}\` once with results \u2014 include exact tool errors if push or PR creation failed
|
|
146531
|
-
- if the task involved labeling, commenting, or other GitHub operations, perform those directly
|
|
146532
|
-
|
|
146533
|
-
${learningsStep(t, 4)}`
|
|
146506
|
+
- if the task involved labeling, commenting, or other GitHub operations, perform those directly`
|
|
146534
146507
|
}
|
|
146535
146508
|
];
|
|
146536
146509
|
}
|
|
@@ -146630,6 +146603,17 @@ async function installFromNpmTarball(params) {
|
|
|
146630
146603
|
// utils/providerErrors.ts
|
|
146631
146604
|
var statusKey = `\\b(?:status[_ ]?code|http[_ ]?status|status)["']?\\s*[:=]\\s*["']?`;
|
|
146632
146605
|
var PROVIDER_ERROR_PATTERNS = [
|
|
146606
|
+
// auth patterns must come BEFORE rate-limit patterns. OpenRouter 401 error
|
|
146607
|
+
// payloads carry `x-ratelimit-*` response headers in the dump, and the
|
|
146608
|
+
// free-form rate-limit regex below would otherwise win on word-boundary
|
|
146609
|
+
// matches inside header names. canonical 401 messages: OpenRouter returns
|
|
146610
|
+
// `{"error":{"message":"User not found","code":401}}` for disabled or
|
|
146611
|
+
// invalid keys (https://openai.luzhipeng.com/docs/api/reference/errors-and-debugging).
|
|
146612
|
+
{ regex: new RegExp(`${statusKey}401\\b`, "i"), label: "auth error (401)" },
|
|
146613
|
+
{ regex: new RegExp(`${statusKey}403\\b`, "i"), label: "auth error (403)" },
|
|
146614
|
+
{ regex: /\bUser not found\b/i, label: "auth error (invalid/disabled key)" },
|
|
146615
|
+
{ regex: /\bInvalid authentication\b/i, label: "auth error (invalid credentials)" },
|
|
146616
|
+
{ regex: /\bNo auth credentials found\b/i, label: "auth error (missing credentials)" },
|
|
146633
146617
|
{ regex: new RegExp(`${statusKey}429\\b`, "i"), label: "rate limited (429)" },
|
|
146634
146618
|
{ regex: new RegExp(`${statusKey}500\\b`, "i"), label: "provider 500 error" },
|
|
146635
146619
|
{ regex: new RegExp(`${statusKey}503\\b`, "i"), label: "provider unavailable (503)" },
|
|
@@ -146693,7 +146677,7 @@ function installBundledSkills(params) {
|
|
|
146693
146677
|
writeFileSync6(join9(skillDir, "SKILL.md"), content);
|
|
146694
146678
|
}
|
|
146695
146679
|
}
|
|
146696
|
-
log.
|
|
146680
|
+
log.success(`installed bundled skills: ${BUNDLED_SKILL_NAMES.join(", ")}`);
|
|
146697
146681
|
}
|
|
146698
146682
|
function addSkill(params) {
|
|
146699
146683
|
const result = spawnSync5(
|
|
@@ -146718,7 +146702,7 @@ function addSkill(params) {
|
|
|
146718
146702
|
}
|
|
146719
146703
|
);
|
|
146720
146704
|
if (result.status === 0) {
|
|
146721
|
-
log.
|
|
146705
|
+
log.success(`installed ${params.skill} skill (${params.agent})`);
|
|
146722
146706
|
} else {
|
|
146723
146707
|
const stderr = (result.stderr?.toString() || "").trim();
|
|
146724
146708
|
const errorMsg = result.error ? result.error.message : stderr;
|
|
@@ -146852,18 +146836,17 @@ function buildPostRunPrompt(issues) {
|
|
|
146852
146836
|
if (issues.summaryStale) parts.push(buildSummaryStalePrompt(issues.summaryStale.filePath));
|
|
146853
146837
|
return parts.join("\n\n---\n\n");
|
|
146854
146838
|
}
|
|
146855
|
-
function buildLearningsReflectionPrompt(
|
|
146856
|
-
const t = (name) => formatMcpToolRef(agentId, name);
|
|
146839
|
+
function buildLearningsReflectionPrompt(filePath) {
|
|
146857
146840
|
return [
|
|
146858
|
-
`REFLECTION \u2014 before you finish, think back over this task: did you discover anything about this repo's setup, test commands, conventions, or patterns that
|
|
146841
|
+
`REFLECTION \u2014 before you finish, think back over this task: did you discover anything about this repo's setup, test commands, conventions, or patterns that is high-confidence and would reliably help future runs?`,
|
|
146859
146842
|
"",
|
|
146860
|
-
`
|
|
146843
|
+
`the rolling learnings file is at \`${filePath}\`. read it first if you haven't already, then edit it in place using your native file tools. the server reads this file at end-of-run and persists any changes \u2014 there is no tool to call.`,
|
|
146861
146844
|
"",
|
|
146862
|
-
`
|
|
146863
|
-
`- only
|
|
146864
|
-
`-
|
|
146865
|
-
`-
|
|
146866
|
-
`- if you
|
|
146845
|
+
`keep the file healthy:`,
|
|
146846
|
+
`- only add bullets when the finding is high-confidence AND broadly useful. skip speculative, one-off, or "maybe" findings.`,
|
|
146847
|
+
`- prune bullets that are clearly wrong, no longer relevant, or low-signal (rarely useful). a focused, accurate file beats a long stale one.`,
|
|
146848
|
+
`- format: flat bullet list, one fact per line starting with \`- \`. deduplicate against existing entries \u2014 if a bullet covers the same fact, update it in place instead of adding a duplicate.`,
|
|
146849
|
+
`- leave the file alone if you have nothing substantively new to add and the existing entries still look healthy. silence is a valid outcome \u2014 just reply "done" and stop.`
|
|
146867
146850
|
].join("\n");
|
|
146868
146851
|
}
|
|
146869
146852
|
async function runPostRunRetryLoop(params) {
|
|
@@ -147446,7 +147429,7 @@ var claude = agent({
|
|
|
147446
147429
|
stopScript: ctx.stopScript,
|
|
147447
147430
|
summaryFilePath: ctx.summaryFilePath,
|
|
147448
147431
|
summarySeed: ctx.summarySeed,
|
|
147449
|
-
reflectionPrompt: buildLearningsReflectionPrompt(
|
|
147432
|
+
reflectionPrompt: ctx.learningsFilePath ? buildLearningsReflectionPrompt(ctx.learningsFilePath) : void 0,
|
|
147450
147433
|
canResume: (r) => Boolean(r.sessionId),
|
|
147451
147434
|
resume: async (c) => {
|
|
147452
147435
|
const sessionId = c.previousResult.sessionId;
|
|
@@ -147462,9 +147445,92 @@ var claude = agent({
|
|
|
147462
147445
|
|
|
147463
147446
|
// agents/opencode.ts
|
|
147464
147447
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
147465
|
-
import { mkdirSync as mkdirSync5 } from "node:fs";
|
|
147448
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "node:fs";
|
|
147466
147449
|
import { join as join11 } from "node:path";
|
|
147467
147450
|
import { performance as performance7 } from "node:perf_hooks";
|
|
147451
|
+
|
|
147452
|
+
// agents/opencodePlugin.ts
|
|
147453
|
+
var PULLFROG_BUS_EVENT_TYPE = "pullfrog_bus_event";
|
|
147454
|
+
var PULLFROG_OPENCODE_PLUGIN_FILENAME = "pullfrog-events.ts";
|
|
147455
|
+
var PULLFROG_OPENCODE_PLUGIN_SOURCE = `// AUTOGENERATED by Pullfrog. do not edit; it'll be overwritten on the next run.
|
|
147456
|
+
// surfaces opencode subagent activity that the CLI's run-loop discards. see
|
|
147457
|
+
// action/agents/opencodePlugin.ts in pullfrog/app for why this exists. lives
|
|
147458
|
+
// inside the per-run tmpdir (XDG_CONFIG_HOME/opencode/plugin/), never inside
|
|
147459
|
+
// the user's working tree.
|
|
147460
|
+
|
|
147461
|
+
const PULLFROG_BUS_EVENT_TYPE = ${JSON.stringify(PULLFROG_BUS_EVENT_TYPE)};
|
|
147462
|
+
|
|
147463
|
+
// the first sessionID we see on a message.part.updated event is the
|
|
147464
|
+
// orchestrator \u2014 opencode's run command creates exactly one top-level session
|
|
147465
|
+
// before any subagent is dispatched, and the user-prompt text part fires
|
|
147466
|
+
// before the first task tool_use. we lock that sessionID in here and use it
|
|
147467
|
+
// to filter: the orchestrator's events are already streamed by the CLI's
|
|
147468
|
+
// run-loop, so we only forward (a) all subagent events, and (b) the
|
|
147469
|
+
// orchestrator's task tool dispatches at status="running". the CLI only
|
|
147470
|
+
// emits task tool_use at status=completed (after the subagent finishes), so
|
|
147471
|
+
// without the early announce the parent's labeler binds subagent sessions
|
|
147472
|
+
// before recordTaskDispatch fires and the lens label is lost.
|
|
147473
|
+
let orchestratorSessionID: string | undefined;
|
|
147474
|
+
|
|
147475
|
+
function isOrchestratorTaskDispatch(part: {
|
|
147476
|
+
type?: string;
|
|
147477
|
+
tool?: string;
|
|
147478
|
+
state?: { status?: string };
|
|
147479
|
+
}): boolean {
|
|
147480
|
+
if (part.type !== "tool") return false;
|
|
147481
|
+
if (part.tool !== "task") return false;
|
|
147482
|
+
// only forward at status="running" (not "pending"). at pending the
|
|
147483
|
+
// state.input is still {} \u2014 the orchestrator has emitted the part shell
|
|
147484
|
+
// but the LLM hasn't filled in description/subagent_type/prompt yet. by
|
|
147485
|
+
// running, input is populated and recordTaskDispatch can derive the lens
|
|
147486
|
+
// label correctly.
|
|
147487
|
+
return part.state?.status === "running";
|
|
147488
|
+
}
|
|
147489
|
+
|
|
147490
|
+
export default async function pullfrogEventsPlugin() {
|
|
147491
|
+
return {
|
|
147492
|
+
event: async (input: {
|
|
147493
|
+
event: {
|
|
147494
|
+
type: string;
|
|
147495
|
+
properties?: {
|
|
147496
|
+
part?: {
|
|
147497
|
+
sessionID?: string;
|
|
147498
|
+
type?: string;
|
|
147499
|
+
tool?: string;
|
|
147500
|
+
state?: { status?: string };
|
|
147501
|
+
};
|
|
147502
|
+
};
|
|
147503
|
+
};
|
|
147504
|
+
}) => {
|
|
147505
|
+
const event = input.event;
|
|
147506
|
+
if (!event || typeof event !== "object") return;
|
|
147507
|
+
if (event.type !== "message.part.updated") return;
|
|
147508
|
+
const part = event.properties?.part;
|
|
147509
|
+
const sessionID = part?.sessionID;
|
|
147510
|
+
if (typeof sessionID !== "string" || sessionID.length === 0) return;
|
|
147511
|
+
if (orchestratorSessionID === undefined) orchestratorSessionID = sessionID;
|
|
147512
|
+
|
|
147513
|
+
if (sessionID === orchestratorSessionID) {
|
|
147514
|
+
// skip orchestrator events EXCEPT early task dispatches.
|
|
147515
|
+
if (!part || !isOrchestratorTaskDispatch(part)) return;
|
|
147516
|
+
}
|
|
147517
|
+
|
|
147518
|
+
try {
|
|
147519
|
+
const line = JSON.stringify({
|
|
147520
|
+
type: PULLFROG_BUS_EVENT_TYPE,
|
|
147521
|
+
bus_event: event,
|
|
147522
|
+
});
|
|
147523
|
+
process.stdout.write(line + "\\n");
|
|
147524
|
+
} catch {
|
|
147525
|
+
// a circular reference or BigInt etc. would throw; swallow rather
|
|
147526
|
+
// than letting a single bad event take down the plugin.
|
|
147527
|
+
}
|
|
147528
|
+
},
|
|
147529
|
+
};
|
|
147530
|
+
}
|
|
147531
|
+
`;
|
|
147532
|
+
|
|
147533
|
+
// agents/opencode.ts
|
|
147468
147534
|
async function installOpencodeCli() {
|
|
147469
147535
|
return await installFromNpmTarball({
|
|
147470
147536
|
packageName: "opencode-ai",
|
|
@@ -147566,9 +147632,6 @@ async function runOpenCode(params) {
|
|
|
147566
147632
|
const taskDispatchByCallID = /* @__PURE__ */ new Map();
|
|
147567
147633
|
const pendingTaskDispatches = [];
|
|
147568
147634
|
const knownNonTaskCallIDs = /* @__PURE__ */ new Set();
|
|
147569
|
-
function isSubagentInFlight() {
|
|
147570
|
-
return taskDispatchByCallID.size > 0 || pendingTaskDispatches.length > 0;
|
|
147571
|
-
}
|
|
147572
147635
|
function emitSubagentFinished(dispatch, status, output2, matchKind) {
|
|
147573
147636
|
const subagentDuration = performance7.now() - dispatch.startedAt;
|
|
147574
147637
|
const outputStr = typeof output2 === "string" ? output2 : "";
|
|
@@ -147687,18 +147750,20 @@ async function runOpenCode(params) {
|
|
|
147687
147750
|
return;
|
|
147688
147751
|
}
|
|
147689
147752
|
if (toolName === "task") {
|
|
147690
|
-
|
|
147691
|
-
|
|
147692
|
-
|
|
147693
|
-
|
|
147694
|
-
|
|
147695
|
-
|
|
147696
|
-
|
|
147697
|
-
|
|
147698
|
-
|
|
147699
|
-
|
|
147700
|
-
|
|
147701
|
-
|
|
147753
|
+
if (!taskDispatchByCallID.has(toolId)) {
|
|
147754
|
+
const taskInput = event.part?.state?.input ?? {};
|
|
147755
|
+
const dispatchedLabel = labeler.recordTaskDispatch(taskInput);
|
|
147756
|
+
const dispatch = {
|
|
147757
|
+
label: dispatchedLabel,
|
|
147758
|
+
startedAt: performance7.now(),
|
|
147759
|
+
toolUseCallID: toolId
|
|
147760
|
+
};
|
|
147761
|
+
taskDispatchByCallID.set(toolId, dispatch);
|
|
147762
|
+
pendingTaskDispatches.push(dispatch);
|
|
147763
|
+
log.info(
|
|
147764
|
+
`\xBB dispatching subagent: ${dispatchedLabel}` + (taskInput.subagent_type ? ` (subagent_type=${taskInput.subagent_type})` : "")
|
|
147765
|
+
);
|
|
147766
|
+
}
|
|
147702
147767
|
} else {
|
|
147703
147768
|
knownNonTaskCallIDs.add(toolId);
|
|
147704
147769
|
}
|
|
@@ -147719,6 +147784,10 @@ async function runOpenCode(params) {
|
|
|
147719
147784
|
if (event.part?.state?.status === "completed" && event.part.state.output) {
|
|
147720
147785
|
log.debug(withLabel(label, ` output: ${event.part.state.output}`));
|
|
147721
147786
|
}
|
|
147787
|
+
if (event.part?.state?.status === "error") {
|
|
147788
|
+
const errorMsg = event.part.state.output ?? "(no error message)";
|
|
147789
|
+
log.info(withLabel(label, `\xBB tool call failed: ${errorMsg}`));
|
|
147790
|
+
}
|
|
147722
147791
|
if (toolName.includes("report_progress") && params.todoTracker) {
|
|
147723
147792
|
log.debug("\xBB report_progress detected, disabling todo tracking");
|
|
147724
147793
|
params.todoTracker.cancel();
|
|
@@ -147805,6 +147874,53 @@ async function runOpenCode(params) {
|
|
|
147805
147874
|
tokensLogged = true;
|
|
147806
147875
|
}
|
|
147807
147876
|
}
|
|
147877
|
+
},
|
|
147878
|
+
[PULLFROG_BUS_EVENT_TYPE]: async (event) => {
|
|
147879
|
+
const busEvent = event.bus_event;
|
|
147880
|
+
if (!busEvent || busEvent.type !== "message.part.updated") return;
|
|
147881
|
+
const part = busEvent.properties?.part;
|
|
147882
|
+
if (!part || typeof part.sessionID !== "string") return;
|
|
147883
|
+
const sessionID = part.sessionID;
|
|
147884
|
+
const partType = part.type;
|
|
147885
|
+
if (partType === "tool") {
|
|
147886
|
+
const status = part.state?.status;
|
|
147887
|
+
const partWithToolFields = part;
|
|
147888
|
+
const isOrchestratorTaskDispatch = partWithToolFields.tool === "task" && status === "running";
|
|
147889
|
+
if (isOrchestratorTaskDispatch) {
|
|
147890
|
+
const callID = partWithToolFields.callID;
|
|
147891
|
+
if (typeof callID === "string" && !taskDispatchByCallID.has(callID)) {
|
|
147892
|
+
const taskInput = partWithToolFields.state?.input ?? {};
|
|
147893
|
+
const dispatchedLabel = labeler.recordTaskDispatch(taskInput);
|
|
147894
|
+
const dispatch = {
|
|
147895
|
+
label: dispatchedLabel,
|
|
147896
|
+
startedAt: performance7.now(),
|
|
147897
|
+
toolUseCallID: callID
|
|
147898
|
+
};
|
|
147899
|
+
taskDispatchByCallID.set(callID, dispatch);
|
|
147900
|
+
pendingTaskDispatches.push(dispatch);
|
|
147901
|
+
log.info(
|
|
147902
|
+
`\xBB dispatching subagent: ${dispatchedLabel}` + (taskInput.subagent_type ? ` (subagent_type=${taskInput.subagent_type})` : "")
|
|
147903
|
+
);
|
|
147904
|
+
}
|
|
147905
|
+
return;
|
|
147906
|
+
}
|
|
147907
|
+
if (status !== "completed" && status !== "error") return;
|
|
147908
|
+
await handlers2.tool_use({
|
|
147909
|
+
type: "tool_use",
|
|
147910
|
+
sessionID,
|
|
147911
|
+
part
|
|
147912
|
+
});
|
|
147913
|
+
return;
|
|
147914
|
+
}
|
|
147915
|
+
if (partType === "step-start" || partType === "step-finish") return;
|
|
147916
|
+
if (partType === "text" && part.time?.end !== void 0) {
|
|
147917
|
+
await handlers2.text({
|
|
147918
|
+
type: "text",
|
|
147919
|
+
sessionID,
|
|
147920
|
+
part
|
|
147921
|
+
});
|
|
147922
|
+
return;
|
|
147923
|
+
}
|
|
147808
147924
|
}
|
|
147809
147925
|
};
|
|
147810
147926
|
const recentStderr = [];
|
|
@@ -147828,13 +147944,13 @@ async function runOpenCode(params) {
|
|
|
147828
147944
|
// never fires — producing zombie runs. detached + killGroup nukes the
|
|
147829
147945
|
// whole tree.
|
|
147830
147946
|
killGroup: true,
|
|
147831
|
-
//
|
|
147832
|
-
//
|
|
147833
|
-
//
|
|
147834
|
-
//
|
|
147835
|
-
//
|
|
147836
|
-
//
|
|
147837
|
-
|
|
147947
|
+
// NB: we used to pass `isPausedExternally: isSubagentInFlight` to suspend
|
|
147948
|
+
// the activity timer during subagent dispatches. unnecessary now that
|
|
147949
|
+
// our injected plugin (action/agents/opencodePlugin.ts) re-emits
|
|
147950
|
+
// subagent `message.part.updated` events on opencode's stdout — those
|
|
147951
|
+
// arrive at child.stdout here, fire updateActivity(), and reset
|
|
147952
|
+
// lastActivityTime naturally. verified empirically in PR #634
|
|
147953
|
+
// (~3.3 plugin events/sec during a typical subagent run).
|
|
147838
147954
|
onStdout: async (chunk) => {
|
|
147839
147955
|
const text = chunk.toString();
|
|
147840
147956
|
output += text;
|
|
@@ -147989,6 +148105,12 @@ var opencode = agent({
|
|
|
147989
148105
|
XDG_CONFIG_HOME: join11(ctx.tmpdir, ".config")
|
|
147990
148106
|
};
|
|
147991
148107
|
mkdirSync5(join11(homeEnv.XDG_CONFIG_HOME, "opencode"), { recursive: true });
|
|
148108
|
+
const opencodePluginDir = join11(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
|
|
148109
|
+
mkdirSync5(opencodePluginDir, { recursive: true });
|
|
148110
|
+
writeFileSync8(
|
|
148111
|
+
join11(opencodePluginDir, PULLFROG_OPENCODE_PLUGIN_FILENAME),
|
|
148112
|
+
PULLFROG_OPENCODE_PLUGIN_SOURCE
|
|
148113
|
+
);
|
|
147992
148114
|
const agentBrowserVersion = getDevDependencyVersion("agent-browser");
|
|
147993
148115
|
addSkill({
|
|
147994
148116
|
ref: `vercel-labs/agent-browser@v${agentBrowserVersion}`,
|
|
@@ -148031,7 +148153,7 @@ var opencode = agent({
|
|
|
148031
148153
|
stopScript: ctx.stopScript,
|
|
148032
148154
|
summaryFilePath: ctx.summaryFilePath,
|
|
148033
148155
|
summarySeed: ctx.summarySeed,
|
|
148034
|
-
reflectionPrompt: buildLearningsReflectionPrompt(
|
|
148156
|
+
reflectionPrompt: ctx.learningsFilePath ? buildLearningsReflectionPrompt(ctx.learningsFilePath) : void 0,
|
|
148035
148157
|
resume: async (c) => runOpenCode({
|
|
148036
148158
|
...runParams,
|
|
148037
148159
|
args: [...baseArgs, "--continue", c.prompt]
|
|
@@ -152244,7 +152366,7 @@ ${ctx.error}` : ctx.error;
|
|
|
152244
152366
|
|
|
152245
152367
|
// utils/gitAuthServer.ts
|
|
152246
152368
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
152247
|
-
import { writeFileSync as
|
|
152369
|
+
import { writeFileSync as writeFileSync9 } from "node:fs";
|
|
152248
152370
|
import { createServer as createServer2 } from "node:http";
|
|
152249
152371
|
import { join as join13 } from "node:path";
|
|
152250
152372
|
var CODE_TTL_MS = 5 * 60 * 1e3;
|
|
@@ -152333,7 +152455,7 @@ async function startGitAuthServer(tmpdir3) {
|
|
|
152333
152455
|
`try{require("fs").unlinkSync("${scriptPath.replace(/\\/g, "\\\\")}")}catch(e){}`,
|
|
152334
152456
|
`})}).on("error",function(){process.exit(1)})}`
|
|
152335
152457
|
].join("\n");
|
|
152336
|
-
|
|
152458
|
+
writeFileSync9(scriptPath, content, { mode: 448 });
|
|
152337
152459
|
return scriptPath;
|
|
152338
152460
|
}
|
|
152339
152461
|
async function close() {
|
|
@@ -152607,9 +152729,9 @@ function buildPromptContext(ctx) {
|
|
|
152607
152729
|
};
|
|
152608
152730
|
}
|
|
152609
152731
|
function assembleFullPrompt(ctx) {
|
|
152610
|
-
const learningsSection = ctx.
|
|
152732
|
+
const learningsSection = ctx.learningsFilePath ? `************* LEARNINGS *************
|
|
152611
152733
|
|
|
152612
|
-
|
|
152734
|
+
Repo-level learnings accumulated by previous agent runs live at \`${ctx.learningsFilePath}\`. Read this file early and let the entries inform your approach (test commands, conventions, gotchas, etc.). The file may be empty if no learnings have been collected yet.` : "";
|
|
152613
152735
|
const runtimeSection = `************* RUNTIME *************
|
|
152614
152736
|
|
|
152615
152737
|
${ctx.runtime}`;
|
|
@@ -152636,8 +152758,8 @@ function resolveInstructions(ctx) {
|
|
|
152636
152758
|
if (eventContext)
|
|
152637
152759
|
tocEntries.push({ label: "EVENT CONTEXT", description: "related PR/issue data" });
|
|
152638
152760
|
tocEntries.push({ label: "SYSTEM", description: "persona, security, tools, workflow rules" });
|
|
152639
|
-
if (pctx.
|
|
152640
|
-
tocEntries.push({ label: "LEARNINGS", description: "repo-specific knowledge" });
|
|
152761
|
+
if (pctx.learningsFilePath)
|
|
152762
|
+
tocEntries.push({ label: "LEARNINGS", description: "repo-specific knowledge file path" });
|
|
152641
152763
|
tocEntries.push({ label: "RUNTIME", description: "environment metadata" });
|
|
152642
152764
|
const toc = buildToc(tocEntries);
|
|
152643
152765
|
const full = assembleFullPrompt({
|
|
@@ -152646,7 +152768,7 @@ function resolveInstructions(ctx) {
|
|
|
152646
152768
|
procedure,
|
|
152647
152769
|
eventContext,
|
|
152648
152770
|
system,
|
|
152649
|
-
|
|
152771
|
+
learningsFilePath: pctx.learningsFilePath,
|
|
152650
152772
|
runtime: pctx.runtime
|
|
152651
152773
|
});
|
|
152652
152774
|
const event = [pctx.eventTitle, pctx.eventMetadata].filter(Boolean).join("\n\n---\n\n");
|
|
@@ -152660,6 +152782,32 @@ function resolveInstructions(ctx) {
|
|
|
152660
152782
|
};
|
|
152661
152783
|
}
|
|
152662
152784
|
|
|
152785
|
+
// utils/learnings.ts
|
|
152786
|
+
import { mkdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
152787
|
+
import { dirname as dirname4, join as join14 } from "node:path";
|
|
152788
|
+
var LEARNINGS_FILE_NAME = "pullfrog-learnings.md";
|
|
152789
|
+
var MAX_LEARNINGS_LENGTH = 1e4;
|
|
152790
|
+
function learningsFilePath(tmpdir3) {
|
|
152791
|
+
return join14(tmpdir3, LEARNINGS_FILE_NAME);
|
|
152792
|
+
}
|
|
152793
|
+
async function seedLearningsFile(params) {
|
|
152794
|
+
const path3 = learningsFilePath(params.tmpdir);
|
|
152795
|
+
await mkdir(dirname4(path3), { recursive: true });
|
|
152796
|
+
await writeFile2(path3, params.current ?? "", "utf8");
|
|
152797
|
+
return path3;
|
|
152798
|
+
}
|
|
152799
|
+
async function readLearningsFile(path3) {
|
|
152800
|
+
let raw2;
|
|
152801
|
+
try {
|
|
152802
|
+
raw2 = await readFile2(path3, "utf8");
|
|
152803
|
+
} catch {
|
|
152804
|
+
return null;
|
|
152805
|
+
}
|
|
152806
|
+
const trimmed = raw2.trim();
|
|
152807
|
+
if (trimmed.length > MAX_LEARNINGS_LENGTH) return trimmed.slice(0, MAX_LEARNINGS_LENGTH);
|
|
152808
|
+
return trimmed;
|
|
152809
|
+
}
|
|
152810
|
+
|
|
152663
152811
|
// utils/normalizeEnv.ts
|
|
152664
152812
|
function maskValue(value2) {
|
|
152665
152813
|
if (value2 && typeof value2 === "string" && value2.trim().length > 0) {
|
|
@@ -152835,8 +152983,8 @@ function resolvePayload(resolvedPromptInput, repoSettings) {
|
|
|
152835
152983
|
}
|
|
152836
152984
|
|
|
152837
152985
|
// utils/prSummary.ts
|
|
152838
|
-
import { mkdir, readFile as
|
|
152839
|
-
import { dirname as
|
|
152986
|
+
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
152987
|
+
import { dirname as dirname5, join as join15 } from "node:path";
|
|
152840
152988
|
var SUMMARY_FILE_NAME = "pullfrog-summary.md";
|
|
152841
152989
|
var SUMMARY_SCAFFOLD = `# PR summary
|
|
152842
152990
|
|
|
@@ -152846,19 +152994,19 @@ var SUMMARY_SCAFFOLD = `# PR summary
|
|
|
152846
152994
|
var MIN_SNAPSHOT_LENGTH = 60;
|
|
152847
152995
|
var MAX_SNAPSHOT_LENGTH = 32768;
|
|
152848
152996
|
function summaryFilePath(tmpdir3) {
|
|
152849
|
-
return
|
|
152997
|
+
return join15(tmpdir3, SUMMARY_FILE_NAME);
|
|
152850
152998
|
}
|
|
152851
152999
|
async function seedSummaryFile(params) {
|
|
152852
153000
|
const path3 = summaryFilePath(params.tmpdir);
|
|
152853
|
-
await
|
|
153001
|
+
await mkdir2(dirname5(path3), { recursive: true });
|
|
152854
153002
|
const seed = params.previousSnapshot && params.previousSnapshot.trim().length >= MIN_SNAPSHOT_LENGTH ? params.previousSnapshot : SUMMARY_SCAFFOLD;
|
|
152855
|
-
await
|
|
153003
|
+
await writeFile3(path3, seed, "utf8");
|
|
152856
153004
|
return path3;
|
|
152857
153005
|
}
|
|
152858
153006
|
async function readSummaryFile(path3) {
|
|
152859
153007
|
let raw2;
|
|
152860
153008
|
try {
|
|
152861
|
-
raw2 = await
|
|
153009
|
+
raw2 = await readFile3(path3, "utf8");
|
|
152862
153010
|
} catch {
|
|
152863
153011
|
return null;
|
|
152864
153012
|
}
|
|
@@ -153076,9 +153224,9 @@ async function resolveRunContextData(params) {
|
|
|
153076
153224
|
import { execFileSync as execFileSync5, execSync as execSync3 } from "node:child_process";
|
|
153077
153225
|
import { mkdtempSync } from "node:fs";
|
|
153078
153226
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
153079
|
-
import { join as
|
|
153227
|
+
import { join as join16 } from "node:path";
|
|
153080
153228
|
function createTempDirectory() {
|
|
153081
|
-
const sharedTempDir = mkdtempSync(
|
|
153229
|
+
const sharedTempDir = mkdtempSync(join16(tmpdir2(), "pullfrog-"));
|
|
153082
153230
|
process.env.PULLFROG_TEMP_DIR = sharedTempDir;
|
|
153083
153231
|
log.info(`\xBB created temp dir at ${sharedTempDir}`);
|
|
153084
153232
|
return sharedTempDir;
|
|
@@ -153480,15 +153628,12 @@ function formatTransientErrorSummary(error49, owner) {
|
|
|
153480
153628
|
}
|
|
153481
153629
|
async function mintProxyKey(ctx) {
|
|
153482
153630
|
try {
|
|
153483
|
-
|
|
153484
|
-
|
|
153485
|
-
const oidcToken = await core6.getIDToken("pullfrog-api");
|
|
153486
|
-
delete process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
|
|
153487
|
-
delete process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
|
153631
|
+
const headers = await buildProxyTokenHeaders(ctx);
|
|
153632
|
+
if (!headers) return null;
|
|
153488
153633
|
const response = await apiFetch({
|
|
153489
153634
|
path: "/api/proxy-token",
|
|
153490
153635
|
method: "POST",
|
|
153491
|
-
headers
|
|
153636
|
+
headers
|
|
153492
153637
|
});
|
|
153493
153638
|
if (response.status === 402) {
|
|
153494
153639
|
const body = await response.json().catch(() => null);
|
|
@@ -153520,15 +153665,30 @@ async function mintProxyKey(ctx) {
|
|
|
153520
153665
|
delete process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
|
153521
153666
|
}
|
|
153522
153667
|
}
|
|
153668
|
+
async function buildProxyTokenHeaders(ctx) {
|
|
153669
|
+
if (ctx.oidcCredentials) {
|
|
153670
|
+
process.env.ACTIONS_ID_TOKEN_REQUEST_URL = ctx.oidcCredentials.requestUrl;
|
|
153671
|
+
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = ctx.oidcCredentials.requestToken;
|
|
153672
|
+
const oidcToken = await core6.getIDToken("pullfrog-api");
|
|
153673
|
+
delete process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
|
|
153674
|
+
delete process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
|
153675
|
+
return { Authorization: `Bearer ${oidcToken}` };
|
|
153676
|
+
}
|
|
153677
|
+
if (isLocalApiUrl()) {
|
|
153678
|
+
log.info(`\xBB proxy: dev bypass (x-dev-repo) for ${ctx.repo.owner}/${ctx.repo.name}`);
|
|
153679
|
+
return { "x-dev-repo": `${ctx.repo.owner}/${ctx.repo.name}` };
|
|
153680
|
+
}
|
|
153681
|
+
return null;
|
|
153682
|
+
}
|
|
153523
153683
|
async function resolveProxyModel(ctx) {
|
|
153524
153684
|
if (process.env.PULLFROG_MODEL?.trim()) return;
|
|
153525
153685
|
const needsProxy = isInfraCovered({ isOss: ctx.oss, plan: ctx.plan }) && ctx.proxyModel;
|
|
153526
153686
|
if (!needsProxy) return;
|
|
153527
|
-
if (!ctx.oidcCredentials) {
|
|
153687
|
+
if (!ctx.oidcCredentials && !isLocalApiUrl()) {
|
|
153528
153688
|
log.warning("\xBB proxy requested but no OIDC credentials available \u2014 skipping");
|
|
153529
153689
|
return;
|
|
153530
153690
|
}
|
|
153531
|
-
const key = await mintProxyKey({ oidcCredentials: ctx.oidcCredentials });
|
|
153691
|
+
const key = await mintProxyKey({ oidcCredentials: ctx.oidcCredentials, repo: ctx.repo });
|
|
153532
153692
|
if (!key) return;
|
|
153533
153693
|
process.env.OPENROUTER_API_KEY = key;
|
|
153534
153694
|
core6.setSecret(key);
|
|
@@ -153552,6 +153712,45 @@ async function fetchPreviousSnapshot(ctx, prNumber) {
|
|
|
153552
153712
|
return null;
|
|
153553
153713
|
}
|
|
153554
153714
|
}
|
|
153715
|
+
async function persistLearnings(ctx) {
|
|
153716
|
+
const filePath = ctx.toolState.learningsFilePath;
|
|
153717
|
+
if (!filePath) return;
|
|
153718
|
+
if (ctx.toolState.learningsPersistAttempted) return;
|
|
153719
|
+
ctx.toolState.learningsPersistAttempted = true;
|
|
153720
|
+
const current = await readLearningsFile(filePath);
|
|
153721
|
+
if (current === null) {
|
|
153722
|
+
log.debug(`learnings tmpfile missing or unreadable at ${filePath} \u2014 skipping persist`);
|
|
153723
|
+
return;
|
|
153724
|
+
}
|
|
153725
|
+
const seed = ctx.toolState.learningsSeed?.trim() ?? "";
|
|
153726
|
+
if (current === seed) {
|
|
153727
|
+
log.debug("learnings tmpfile unchanged from seed \u2014 skipping persist");
|
|
153728
|
+
return;
|
|
153729
|
+
}
|
|
153730
|
+
try {
|
|
153731
|
+
const response = await apiFetch({
|
|
153732
|
+
path: `/api/repo/${ctx.repo.owner}/${ctx.repo.name}/learnings`,
|
|
153733
|
+
method: "PATCH",
|
|
153734
|
+
headers: {
|
|
153735
|
+
authorization: `Bearer ${ctx.apiToken}`,
|
|
153736
|
+
"content-type": "application/json"
|
|
153737
|
+
},
|
|
153738
|
+
body: JSON.stringify({
|
|
153739
|
+
learnings: current,
|
|
153740
|
+
model: ctx.toolState.model
|
|
153741
|
+
}),
|
|
153742
|
+
signal: AbortSignal.timeout(1e4)
|
|
153743
|
+
});
|
|
153744
|
+
if (!response.ok) {
|
|
153745
|
+
const error49 = await response.text().catch(() => "(no body)");
|
|
153746
|
+
log.debug(`learnings persist failed (${response.status}): ${error49}`);
|
|
153747
|
+
return;
|
|
153748
|
+
}
|
|
153749
|
+
log.info("\xBB learnings updated");
|
|
153750
|
+
} catch (err) {
|
|
153751
|
+
log.debug(`learnings persist failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
153752
|
+
}
|
|
153753
|
+
}
|
|
153555
153754
|
async function persistSummary(ctx) {
|
|
153556
153755
|
const filePath = ctx.toolState.summaryFilePath;
|
|
153557
153756
|
if (!filePath) return;
|
|
@@ -153633,7 +153832,8 @@ async function main() {
|
|
|
153633
153832
|
oss: runContext.oss,
|
|
153634
153833
|
plan: runContext.plan,
|
|
153635
153834
|
proxyModel: runContext.proxyModel,
|
|
153636
|
-
oidcCredentials
|
|
153835
|
+
oidcCredentials,
|
|
153836
|
+
repo: runContext.repo
|
|
153637
153837
|
});
|
|
153638
153838
|
} catch (error49) {
|
|
153639
153839
|
if (error49 instanceof BillingError) {
|
|
@@ -153736,12 +153936,32 @@ async function main() {
|
|
|
153736
153936
|
toolContext.mcpServerUrl = mcpHttpServer.url;
|
|
153737
153937
|
log.info(`\xBB MCP server started at ${mcpHttpServer.url}`);
|
|
153738
153938
|
timer.checkpoint("mcpServer");
|
|
153939
|
+
try {
|
|
153940
|
+
const learningsPath = await seedLearningsFile({
|
|
153941
|
+
tmpdir: tmpdir3,
|
|
153942
|
+
current: runContext.repoSettings.learnings
|
|
153943
|
+
});
|
|
153944
|
+
toolState.learningsFilePath = learningsPath;
|
|
153945
|
+
try {
|
|
153946
|
+
toolState.learningsSeed = await readFile4(learningsPath, "utf8");
|
|
153947
|
+
} catch {
|
|
153948
|
+
}
|
|
153949
|
+
log.info(
|
|
153950
|
+
`\xBB learnings seeded at ${learningsPath} (existing=${runContext.repoSettings.learnings ? "yes" : "no"})`
|
|
153951
|
+
);
|
|
153952
|
+
const ctxForExit = toolContext;
|
|
153953
|
+
onExitSignal(() => persistLearnings(ctxForExit));
|
|
153954
|
+
} catch (err) {
|
|
153955
|
+
log.warning(
|
|
153956
|
+
`\xBB learnings seed failed: ${err instanceof Error ? err.message : String(err)} \u2014 continuing without learnings file`
|
|
153957
|
+
);
|
|
153958
|
+
}
|
|
153739
153959
|
if (payload.generateSummary && payload.event.is_pr && payload.event.issue_number) {
|
|
153740
153960
|
const previousSnapshot = await fetchPreviousSnapshot(toolContext, payload.event.issue_number);
|
|
153741
153961
|
const filePath = await seedSummaryFile({ tmpdir: tmpdir3, previousSnapshot });
|
|
153742
153962
|
toolState.summaryFilePath = filePath;
|
|
153743
153963
|
try {
|
|
153744
|
-
toolState.summarySeed = await
|
|
153964
|
+
toolState.summarySeed = await readFile4(filePath, "utf8");
|
|
153745
153965
|
} catch {
|
|
153746
153966
|
}
|
|
153747
153967
|
log.info(
|
|
@@ -153765,7 +153985,7 @@ async function main() {
|
|
|
153765
153985
|
modes: modes2,
|
|
153766
153986
|
agentId,
|
|
153767
153987
|
outputSchema,
|
|
153768
|
-
|
|
153988
|
+
learningsFilePath: toolState.learningsFilePath ?? null
|
|
153769
153989
|
});
|
|
153770
153990
|
const logParts = [
|
|
153771
153991
|
instructions.eventInstructions ? `EVENT-LEVEL INSTRUCTIONS:
|
|
@@ -153781,7 +154001,7 @@ ${instructions.user}` : null,
|
|
|
153781
154001
|
log.info(instructions.full);
|
|
153782
154002
|
});
|
|
153783
154003
|
if (agentId === "opencode") {
|
|
153784
|
-
const pluginDir =
|
|
154004
|
+
const pluginDir = join17(process.cwd(), ".opencode", "plugin");
|
|
153785
154005
|
const hasPlugins = existsSync7(pluginDir) && readdirSync(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
|
|
153786
154006
|
if (hasPlugins && toolState.dependencyInstallation?.promise) {
|
|
153787
154007
|
log.info(
|
|
@@ -153842,6 +154062,7 @@ ${instructions.user}` : null,
|
|
|
153842
154062
|
stopScript: runContext.repoSettings.stopScript,
|
|
153843
154063
|
summaryFilePath: toolState.summaryFilePath,
|
|
153844
154064
|
summarySeed: toolState.summarySeed,
|
|
154065
|
+
learningsFilePath: toolState.learningsFilePath,
|
|
153845
154066
|
onActivityTimeout: onInnerActivityTimeout,
|
|
153846
154067
|
onToolUse: (event) => {
|
|
153847
154068
|
const wasTracked = recordDiffReadFromToolUse({
|
|
@@ -153899,6 +154120,9 @@ ${instructions.user}` : null,
|
|
|
153899
154120
|
if (toolContext) {
|
|
153900
154121
|
await persistSummary(toolContext);
|
|
153901
154122
|
}
|
|
154123
|
+
if (toolContext) {
|
|
154124
|
+
await persistLearnings(toolContext);
|
|
154125
|
+
}
|
|
153902
154126
|
if (toolContext && toolState.progressComment && !toolState.finalSummaryWritten) {
|
|
153903
154127
|
await deleteProgressComment(toolContext).catch((error49) => {
|
|
153904
154128
|
log.debug(`stranded progress comment cleanup failed: ${error49}`);
|
|
@@ -153951,6 +154175,9 @@ ${errorMessage}
|
|
|
153951
154175
|
if (toolContext) {
|
|
153952
154176
|
await persistSummary(toolContext);
|
|
153953
154177
|
}
|
|
154178
|
+
if (toolContext) {
|
|
154179
|
+
await persistLearnings(toolContext);
|
|
154180
|
+
}
|
|
153954
154181
|
return {
|
|
153955
154182
|
success: false,
|
|
153956
154183
|
error: errorMessage
|