pullfrog 0.1.0 → 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 +396 -129
- package/dist/index.js +393 -126
- 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 +1 -0
- 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,
|
|
@@ -109696,6 +109710,7 @@ async function spawn(options) {
|
|
|
109696
109710
|
const startTime = performance3.now();
|
|
109697
109711
|
let stdoutBuffer = "";
|
|
109698
109712
|
let stderrBuffer = "";
|
|
109713
|
+
const killGroup = options.killGroup ?? false;
|
|
109699
109714
|
return new Promise((resolve3, reject) => {
|
|
109700
109715
|
const child = nodeSpawn(options.cmd, options.args, {
|
|
109701
109716
|
env: options.env || {
|
|
@@ -109703,9 +109718,20 @@ async function spawn(options) {
|
|
|
109703
109718
|
HOME: process.env.HOME || ""
|
|
109704
109719
|
},
|
|
109705
109720
|
stdio: options.stdio || ["pipe", "pipe", "pipe"],
|
|
109706
|
-
cwd: options.cwd || process.cwd()
|
|
109721
|
+
cwd: options.cwd || process.cwd(),
|
|
109722
|
+
detached: killGroup
|
|
109707
109723
|
});
|
|
109708
|
-
|
|
109724
|
+
const killSelf = (signal) => {
|
|
109725
|
+
if (killGroup && child.pid) {
|
|
109726
|
+
try {
|
|
109727
|
+
process.kill(-child.pid, signal);
|
|
109728
|
+
return;
|
|
109729
|
+
} catch {
|
|
109730
|
+
}
|
|
109731
|
+
}
|
|
109732
|
+
child.kill(signal);
|
|
109733
|
+
};
|
|
109734
|
+
trackChild({ child, killGroup });
|
|
109709
109735
|
let timeoutId;
|
|
109710
109736
|
let sigkillEscalatorId;
|
|
109711
109737
|
let activityCheckIntervalId;
|
|
@@ -109716,10 +109742,10 @@ async function spawn(options) {
|
|
|
109716
109742
|
if (options.timeout) {
|
|
109717
109743
|
timeoutId = setTimeout(() => {
|
|
109718
109744
|
isTimedOut = true;
|
|
109719
|
-
|
|
109745
|
+
killSelf("SIGTERM");
|
|
109720
109746
|
sigkillEscalatorId = setTimeout(() => {
|
|
109721
109747
|
if (!child.killed) {
|
|
109722
|
-
|
|
109748
|
+
killSelf("SIGKILL");
|
|
109723
109749
|
}
|
|
109724
109750
|
}, 5e3);
|
|
109725
109751
|
}, options.timeout);
|
|
@@ -109738,9 +109764,9 @@ async function spawn(options) {
|
|
|
109738
109764
|
killedAtIdleMs = idleMs;
|
|
109739
109765
|
const idleSec = Math.round(idleMs / 1e3);
|
|
109740
109766
|
log.info(
|
|
109741
|
-
`no output for ${idleSec}s from pid=${child.pid} (${options.cmd}), killing process`
|
|
109767
|
+
`no output for ${idleSec}s from pid=${child.pid} (${options.cmd}), killing process${killGroup ? " group" : ""}`
|
|
109742
109768
|
);
|
|
109743
|
-
|
|
109769
|
+
killSelf("SIGKILL");
|
|
109744
109770
|
clearInterval(activityCheckIntervalId);
|
|
109745
109771
|
try {
|
|
109746
109772
|
options.onActivityTimeout?.();
|
|
@@ -142249,7 +142275,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
142249
142275
|
// package.json
|
|
142250
142276
|
var package_default = {
|
|
142251
142277
|
name: "pullfrog",
|
|
142252
|
-
version: "0.1.
|
|
142278
|
+
version: "0.1.2",
|
|
142253
142279
|
type: "module",
|
|
142254
142280
|
bin: {
|
|
142255
142281
|
pullfrog: "dist/cli.mjs",
|
|
@@ -143193,6 +143219,10 @@ ${integrateStep}
|
|
|
143193
143219
|
if (!pushed) {
|
|
143194
143220
|
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
143195
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
|
+
);
|
|
143196
143226
|
return {
|
|
143197
143227
|
success: true,
|
|
143198
143228
|
branch,
|
|
@@ -143341,6 +143371,7 @@ function DeleteBranchTool(ctx) {
|
|
|
143341
143371
|
await $git("push", ["origin", "--delete", `refs/heads/${params.branchName}`], {
|
|
143342
143372
|
token: ctx.gitToken
|
|
143343
143373
|
});
|
|
143374
|
+
log.info(`\xBB deleted branch ${params.branchName}`);
|
|
143344
143375
|
return { success: true, deleted: params.branchName };
|
|
143345
143376
|
})
|
|
143346
143377
|
});
|
|
@@ -143366,6 +143397,7 @@ function PushTagsTool(ctx) {
|
|
|
143366
143397
|
await $git("push", pushArgs, {
|
|
143367
143398
|
token: ctx.gitToken
|
|
143368
143399
|
});
|
|
143400
|
+
log.info(`\xBB pushed tag ${params.tag}`);
|
|
143369
143401
|
return { success: true, tag: params.tag };
|
|
143370
143402
|
})
|
|
143371
143403
|
});
|
|
@@ -143690,6 +143722,7 @@ function CreatePullRequestReviewTool(ctx) {
|
|
|
143690
143722
|
}
|
|
143691
143723
|
const reviewId = result.data.id;
|
|
143692
143724
|
const reviewNodeId = result.data.node_id;
|
|
143725
|
+
log.info(`\xBB created review ${reviewId} on pull request #${pull_number}`);
|
|
143693
143726
|
const actuallyReviewedSha = ctx.toolState.checkoutSha ?? params.commit_id;
|
|
143694
143727
|
ctx.toolState.review = {
|
|
143695
143728
|
id: reviewId,
|
|
@@ -144554,6 +144587,7 @@ function IssueTool(ctx) {
|
|
|
144554
144587
|
labels: params.labels ?? [],
|
|
144555
144588
|
assignees: params.assignees ?? []
|
|
144556
144589
|
});
|
|
144590
|
+
log.info(`\xBB created issue #${result.data.number} (id ${result.data.id})`);
|
|
144557
144591
|
const nodeId = result.data.node_id;
|
|
144558
144592
|
if (typeof nodeId === "string" && nodeId.length > 0) {
|
|
144559
144593
|
await patchWorkflowRunFields(ctx, {
|
|
@@ -144745,6 +144779,7 @@ function AddLabelsTool(ctx) {
|
|
|
144745
144779
|
issue_number,
|
|
144746
144780
|
labels
|
|
144747
144781
|
});
|
|
144782
|
+
log.info(`\xBB added labels [${labels.join(", ")}] to issue #${issue_number}`);
|
|
144748
144783
|
return {
|
|
144749
144784
|
success: true,
|
|
144750
144785
|
labels: result.data.map((label) => label.name)
|
|
@@ -144753,40 +144788,6 @@ function AddLabelsTool(ctx) {
|
|
|
144753
144788
|
});
|
|
144754
144789
|
}
|
|
144755
144790
|
|
|
144756
|
-
// mcp/learnings.ts
|
|
144757
|
-
var UpdateLearningsParams = type({
|
|
144758
|
-
learnings: type.string.describe(
|
|
144759
|
-
"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."
|
|
144760
|
-
)
|
|
144761
|
-
});
|
|
144762
|
-
function UpdateLearningsTool(ctx) {
|
|
144763
|
-
return tool({
|
|
144764
|
-
name: "update_learnings",
|
|
144765
|
-
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.",
|
|
144766
|
-
parameters: UpdateLearningsParams,
|
|
144767
|
-
execute: execute(async (params) => {
|
|
144768
|
-
const response = await apiFetch({
|
|
144769
|
-
path: `/api/repo/${ctx.repo.owner}/${ctx.repo.name}/learnings`,
|
|
144770
|
-
method: "PATCH",
|
|
144771
|
-
headers: {
|
|
144772
|
-
authorization: `Bearer ${ctx.apiToken}`,
|
|
144773
|
-
"content-type": "application/json"
|
|
144774
|
-
},
|
|
144775
|
-
body: JSON.stringify({
|
|
144776
|
-
learnings: params.learnings,
|
|
144777
|
-
model: ctx.toolState.model
|
|
144778
|
-
}),
|
|
144779
|
-
signal: AbortSignal.timeout(1e4)
|
|
144780
|
-
});
|
|
144781
|
-
if (!response.ok) {
|
|
144782
|
-
const error49 = await response.text();
|
|
144783
|
-
throw new Error(`failed to update learnings: ${error49}`);
|
|
144784
|
-
}
|
|
144785
|
-
return { success: true };
|
|
144786
|
-
})
|
|
144787
|
-
});
|
|
144788
|
-
}
|
|
144789
|
-
|
|
144790
144791
|
// mcp/output.ts
|
|
144791
144792
|
var import_ajv3 = __toESM(require_ajv(), 1);
|
|
144792
144793
|
var SetOutputParams = type({
|
|
@@ -144880,6 +144881,7 @@ function UpdatePullRequestBodyTool(ctx) {
|
|
|
144880
144881
|
pull_number: params.pull_number,
|
|
144881
144882
|
body: bodyWithFooter
|
|
144882
144883
|
});
|
|
144884
|
+
log.info(`\xBB updated pull request #${result.data.number}`);
|
|
144883
144885
|
ctx.toolState.wasUpdated = true;
|
|
144884
144886
|
return {
|
|
144885
144887
|
success: true,
|
|
@@ -144907,6 +144909,7 @@ function CreatePullRequestTool(ctx) {
|
|
|
144907
144909
|
base: params.base,
|
|
144908
144910
|
draft: params.draft ?? false
|
|
144909
144911
|
});
|
|
144912
|
+
log.info(`\xBB created pull request #${result.data.number} (id ${result.data.id})`);
|
|
144910
144913
|
const reviewer = ctx.payload.triggerer;
|
|
144911
144914
|
if (reviewer) {
|
|
144912
144915
|
try {
|
|
@@ -145458,7 +145461,7 @@ function ResolveReviewThreadTool(ctx) {
|
|
|
145458
145461
|
threadId: params.thread_id
|
|
145459
145462
|
});
|
|
145460
145463
|
const thread = response.resolveReviewThread.thread;
|
|
145461
|
-
log.
|
|
145464
|
+
log.info(`\xBB resolved review thread ${thread.id}`);
|
|
145462
145465
|
return {
|
|
145463
145466
|
thread_id: thread.id,
|
|
145464
145467
|
is_resolved: thread.isResolved,
|
|
@@ -145930,6 +145933,7 @@ function UploadFileTool(ctx) {
|
|
|
145930
145933
|
if (!uploadResponse.ok) {
|
|
145931
145934
|
throw new Error(`failed to upload file: ${uploadResponse.statusText}`);
|
|
145932
145935
|
}
|
|
145936
|
+
log.info(`\xBB uploaded file ${publicUrl}`);
|
|
145933
145937
|
return { success: true, publicUrl, filename, contentLength, contentType };
|
|
145934
145938
|
})
|
|
145935
145939
|
});
|
|
@@ -146023,8 +146027,7 @@ function buildOrchestratorTools(ctx, outputSchema) {
|
|
|
146023
146027
|
PushTagsTool(ctx),
|
|
146024
146028
|
DeleteBranchTool(ctx),
|
|
146025
146029
|
CreatePullRequestTool(ctx),
|
|
146026
|
-
UpdatePullRequestBodyTool(ctx)
|
|
146027
|
-
UpdateLearningsTool(ctx)
|
|
146030
|
+
UpdatePullRequestBodyTool(ctx)
|
|
146028
146031
|
];
|
|
146029
146032
|
}
|
|
146030
146033
|
async function tryStartMcpServer(ctx, tools, port) {
|
|
@@ -146181,9 +146184,6 @@ Rules:
|
|
|
146181
146184
|
- Do NOT include a changelog section \u2014 the key changes list serves this purpose
|
|
146182
146185
|
- Focus on *intent*, not *what* \u2014 the diff already shows what changed
|
|
146183
146186
|
- Get the file count and commit count from the checkout_pr metadata, not by counting manually`;
|
|
146184
|
-
function learningsStep(t, n) {
|
|
146185
|
-
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.`;
|
|
146186
|
-
}
|
|
146187
146187
|
function computeModes(agentId) {
|
|
146188
146188
|
const t = (toolName) => formatMcpToolRef(agentId, toolName);
|
|
146189
146189
|
return [
|
|
@@ -146239,8 +146239,6 @@ function computeModes(agentId) {
|
|
|
146239
146239
|
- create a PR via \`${t("create_pull_request")}\`
|
|
146240
146240
|
- call \`${t("report_progress")}\` with the PR link or the exact error if push/PR failed
|
|
146241
146241
|
|
|
146242
|
-
${learningsStep(t, 6)}
|
|
146243
|
-
|
|
146244
146242
|
### Notes
|
|
146245
146243
|
|
|
146246
146244
|
For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
@@ -146268,9 +146266,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
146268
146266
|
- confirm a clean working tree, then push via \`${t("push_branch")}\` (same push/prepush guidance as Build mode in *SYSTEM*)
|
|
146269
146267
|
- reply to each comment using \`${t("reply_to_review_comment")}\`
|
|
146270
146268
|
- resolve addressed threads via \`${t("resolve_review_thread")}\`
|
|
146271
|
-
- call \`${t("report_progress")}\` with a brief summary (or the exact push error if push failed)
|
|
146272
|
-
|
|
146273
|
-
${learningsStep(t, 6)}`
|
|
146269
|
+
- call \`${t("report_progress")}\` with a brief summary (or the exact push error if push failed)`
|
|
146274
146270
|
},
|
|
146275
146271
|
// Review and IncrementalReview use the multi-lens orchestrator pattern
|
|
146276
146272
|
// (canonical source: .claude/commands/anneal.md). The orchestrator does
|
|
@@ -146441,9 +146437,7 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
146441
146437
|
|
|
146442
146438
|
2. Produce a structured, actionable plan with clear milestones.
|
|
146443
146439
|
|
|
146444
|
-
3. Call \`${t("report_progress")}\` with the plan
|
|
146445
|
-
|
|
146446
|
-
${learningsStep(t, 4)}`
|
|
146440
|
+
3. Call \`${t("report_progress")}\` with the plan.`
|
|
146447
146441
|
},
|
|
146448
146442
|
{
|
|
146449
146443
|
name: "Fix",
|
|
@@ -146465,9 +146459,7 @@ ${learningsStep(t, 4)}`
|
|
|
146465
146459
|
|
|
146466
146460
|
5. Finalize:
|
|
146467
146461
|
- confirm a clean working tree, then push via \`${t("push_branch")}\` (same push/prepush guidance as Build mode in *SYSTEM*)
|
|
146468
|
-
- call \`${t("report_progress")}\` with the diagnosis and fix summary (or the exact push error if push failed)
|
|
146469
|
-
|
|
146470
|
-
${learningsStep(t, 6)}`
|
|
146462
|
+
- call \`${t("report_progress")}\` with the diagnosis and fix summary (or the exact push error if push failed)`
|
|
146471
146463
|
},
|
|
146472
146464
|
{
|
|
146473
146465
|
name: "ResolveConflicts",
|
|
@@ -146511,9 +146503,7 @@ ${learningsStep(t, 6)}`
|
|
|
146511
146503
|
3. Finalize:
|
|
146512
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).
|
|
146513
146505
|
- call \`${t("report_progress")}\` once with results \u2014 include exact tool errors if push or PR creation failed
|
|
146514
|
-
- if the task involved labeling, commenting, or other GitHub operations, perform those directly
|
|
146515
|
-
|
|
146516
|
-
${learningsStep(t, 4)}`
|
|
146506
|
+
- if the task involved labeling, commenting, or other GitHub operations, perform those directly`
|
|
146517
146507
|
}
|
|
146518
146508
|
];
|
|
146519
146509
|
}
|
|
@@ -146613,6 +146603,17 @@ async function installFromNpmTarball(params) {
|
|
|
146613
146603
|
// utils/providerErrors.ts
|
|
146614
146604
|
var statusKey = `\\b(?:status[_ ]?code|http[_ ]?status|status)["']?\\s*[:=]\\s*["']?`;
|
|
146615
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)" },
|
|
146616
146617
|
{ regex: new RegExp(`${statusKey}429\\b`, "i"), label: "rate limited (429)" },
|
|
146617
146618
|
{ regex: new RegExp(`${statusKey}500\\b`, "i"), label: "provider 500 error" },
|
|
146618
146619
|
{ regex: new RegExp(`${statusKey}503\\b`, "i"), label: "provider unavailable (503)" },
|
|
@@ -146676,7 +146677,7 @@ function installBundledSkills(params) {
|
|
|
146676
146677
|
writeFileSync6(join9(skillDir, "SKILL.md"), content);
|
|
146677
146678
|
}
|
|
146678
146679
|
}
|
|
146679
|
-
log.
|
|
146680
|
+
log.success(`installed bundled skills: ${BUNDLED_SKILL_NAMES.join(", ")}`);
|
|
146680
146681
|
}
|
|
146681
146682
|
function addSkill(params) {
|
|
146682
146683
|
const result = spawnSync5(
|
|
@@ -146701,7 +146702,7 @@ function addSkill(params) {
|
|
|
146701
146702
|
}
|
|
146702
146703
|
);
|
|
146703
146704
|
if (result.status === 0) {
|
|
146704
|
-
log.
|
|
146705
|
+
log.success(`installed ${params.skill} skill (${params.agent})`);
|
|
146705
146706
|
} else {
|
|
146706
146707
|
const stderr = (result.stderr?.toString() || "").trim();
|
|
146707
146708
|
const errorMsg = result.error ? result.error.message : stderr;
|
|
@@ -146835,18 +146836,17 @@ function buildPostRunPrompt(issues) {
|
|
|
146835
146836
|
if (issues.summaryStale) parts.push(buildSummaryStalePrompt(issues.summaryStale.filePath));
|
|
146836
146837
|
return parts.join("\n\n---\n\n");
|
|
146837
146838
|
}
|
|
146838
|
-
function buildLearningsReflectionPrompt(
|
|
146839
|
-
const t = (name) => formatMcpToolRef(agentId, name);
|
|
146839
|
+
function buildLearningsReflectionPrompt(filePath) {
|
|
146840
146840
|
return [
|
|
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
|
|
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?`,
|
|
146842
146842
|
"",
|
|
146843
|
-
`
|
|
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.`,
|
|
146844
146844
|
"",
|
|
146845
|
-
`
|
|
146846
|
-
`- only
|
|
146847
|
-
`-
|
|
146848
|
-
`-
|
|
146849
|
-
`- 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.`
|
|
146850
146850
|
].join("\n");
|
|
146851
146851
|
}
|
|
146852
146852
|
async function runPostRunRetryLoop(params) {
|
|
@@ -147184,6 +147184,12 @@ async function runClaude(params) {
|
|
|
147184
147184
|
activityTimeout: 3e5,
|
|
147185
147185
|
onActivityTimeout: params.onActivityTimeout,
|
|
147186
147186
|
stdio: ["ignore", "pipe", "pipe"],
|
|
147187
|
+
// run claude in its own process group so SIGKILL on activity timeout /
|
|
147188
|
+
// outer cancellation reaches any subprocesses it spawns (rg, file
|
|
147189
|
+
// watchers, mcp transports, etc). claude itself is a node bundle so
|
|
147190
|
+
// there's no shim-orphan issue like opencode-ai/bin/opencode, but
|
|
147191
|
+
// detached + killGroup is the right default for any agent runtime.
|
|
147192
|
+
killGroup: true,
|
|
147187
147193
|
onStdout: async (chunk) => {
|
|
147188
147194
|
const text = chunk.toString();
|
|
147189
147195
|
output += text;
|
|
@@ -147423,7 +147429,7 @@ var claude = agent({
|
|
|
147423
147429
|
stopScript: ctx.stopScript,
|
|
147424
147430
|
summaryFilePath: ctx.summaryFilePath,
|
|
147425
147431
|
summarySeed: ctx.summarySeed,
|
|
147426
|
-
reflectionPrompt: buildLearningsReflectionPrompt(
|
|
147432
|
+
reflectionPrompt: ctx.learningsFilePath ? buildLearningsReflectionPrompt(ctx.learningsFilePath) : void 0,
|
|
147427
147433
|
canResume: (r) => Boolean(r.sessionId),
|
|
147428
147434
|
resume: async (c) => {
|
|
147429
147435
|
const sessionId = c.previousResult.sessionId;
|
|
@@ -147439,9 +147445,92 @@ var claude = agent({
|
|
|
147439
147445
|
|
|
147440
147446
|
// agents/opencode.ts
|
|
147441
147447
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
147442
|
-
import { mkdirSync as mkdirSync5 } from "node:fs";
|
|
147448
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "node:fs";
|
|
147443
147449
|
import { join as join11 } from "node:path";
|
|
147444
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
|
|
147445
147534
|
async function installOpencodeCli() {
|
|
147446
147535
|
return await installFromNpmTarball({
|
|
147447
147536
|
packageName: "opencode-ai",
|
|
@@ -147661,18 +147750,20 @@ async function runOpenCode(params) {
|
|
|
147661
147750
|
return;
|
|
147662
147751
|
}
|
|
147663
147752
|
if (toolName === "task") {
|
|
147664
|
-
|
|
147665
|
-
|
|
147666
|
-
|
|
147667
|
-
|
|
147668
|
-
|
|
147669
|
-
|
|
147670
|
-
|
|
147671
|
-
|
|
147672
|
-
|
|
147673
|
-
|
|
147674
|
-
|
|
147675
|
-
|
|
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
|
+
}
|
|
147676
147767
|
} else {
|
|
147677
147768
|
knownNonTaskCallIDs.add(toolId);
|
|
147678
147769
|
}
|
|
@@ -147693,6 +147784,10 @@ async function runOpenCode(params) {
|
|
|
147693
147784
|
if (event.part?.state?.status === "completed" && event.part.state.output) {
|
|
147694
147785
|
log.debug(withLabel(label, ` output: ${event.part.state.output}`));
|
|
147695
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
|
+
}
|
|
147696
147791
|
if (toolName.includes("report_progress") && params.todoTracker) {
|
|
147697
147792
|
log.debug("\xBB report_progress detected, disabling todo tracking");
|
|
147698
147793
|
params.todoTracker.cancel();
|
|
@@ -147779,6 +147874,53 @@ async function runOpenCode(params) {
|
|
|
147779
147874
|
tokensLogged = true;
|
|
147780
147875
|
}
|
|
147781
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
|
+
}
|
|
147782
147924
|
}
|
|
147783
147925
|
};
|
|
147784
147926
|
const recentStderr = [];
|
|
@@ -147795,6 +147937,20 @@ async function runOpenCode(params) {
|
|
|
147795
147937
|
activityTimeout: 3e5,
|
|
147796
147938
|
onActivityTimeout: params.onActivityTimeout,
|
|
147797
147939
|
stdio: ["ignore", "pipe", "pipe"],
|
|
147940
|
+
// node_modules/opencode-ai/bin/opencode is a Node shim that spawnSyncs
|
|
147941
|
+
// the native opencode-<plat>-<arch> binary with stdio:"inherit". without
|
|
147942
|
+
// a process-group kill, SIGKILL hits only the shim, the native binary
|
|
147943
|
+
// is reparented to PID 1, holds our stdout pipe open, and `child.close`
|
|
147944
|
+
// never fires — producing zombie runs. detached + killGroup nukes the
|
|
147945
|
+
// whole tree.
|
|
147946
|
+
killGroup: true,
|
|
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).
|
|
147798
147954
|
onStdout: async (chunk) => {
|
|
147799
147955
|
const text = chunk.toString();
|
|
147800
147956
|
output += text;
|
|
@@ -147949,6 +148105,12 @@ var opencode = agent({
|
|
|
147949
148105
|
XDG_CONFIG_HOME: join11(ctx.tmpdir, ".config")
|
|
147950
148106
|
};
|
|
147951
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
|
+
);
|
|
147952
148114
|
const agentBrowserVersion = getDevDependencyVersion("agent-browser");
|
|
147953
148115
|
addSkill({
|
|
147954
148116
|
ref: `vercel-labs/agent-browser@v${agentBrowserVersion}`,
|
|
@@ -147991,7 +148153,7 @@ var opencode = agent({
|
|
|
147991
148153
|
stopScript: ctx.stopScript,
|
|
147992
148154
|
summaryFilePath: ctx.summaryFilePath,
|
|
147993
148155
|
summarySeed: ctx.summarySeed,
|
|
147994
|
-
reflectionPrompt: buildLearningsReflectionPrompt(
|
|
148156
|
+
reflectionPrompt: ctx.learningsFilePath ? buildLearningsReflectionPrompt(ctx.learningsFilePath) : void 0,
|
|
147995
148157
|
resume: async (c) => runOpenCode({
|
|
147996
148158
|
...runParams,
|
|
147997
148159
|
args: [...baseArgs, "--continue", c.prompt]
|
|
@@ -152204,7 +152366,7 @@ ${ctx.error}` : ctx.error;
|
|
|
152204
152366
|
|
|
152205
152367
|
// utils/gitAuthServer.ts
|
|
152206
152368
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
152207
|
-
import { writeFileSync as
|
|
152369
|
+
import { writeFileSync as writeFileSync9 } from "node:fs";
|
|
152208
152370
|
import { createServer as createServer2 } from "node:http";
|
|
152209
152371
|
import { join as join13 } from "node:path";
|
|
152210
152372
|
var CODE_TTL_MS = 5 * 60 * 1e3;
|
|
@@ -152293,7 +152455,7 @@ async function startGitAuthServer(tmpdir3) {
|
|
|
152293
152455
|
`try{require("fs").unlinkSync("${scriptPath.replace(/\\/g, "\\\\")}")}catch(e){}`,
|
|
152294
152456
|
`})}).on("error",function(){process.exit(1)})}`
|
|
152295
152457
|
].join("\n");
|
|
152296
|
-
|
|
152458
|
+
writeFileSync9(scriptPath, content, { mode: 448 });
|
|
152297
152459
|
return scriptPath;
|
|
152298
152460
|
}
|
|
152299
152461
|
async function close() {
|
|
@@ -152567,9 +152729,9 @@ function buildPromptContext(ctx) {
|
|
|
152567
152729
|
};
|
|
152568
152730
|
}
|
|
152569
152731
|
function assembleFullPrompt(ctx) {
|
|
152570
|
-
const learningsSection = ctx.
|
|
152732
|
+
const learningsSection = ctx.learningsFilePath ? `************* LEARNINGS *************
|
|
152571
152733
|
|
|
152572
|
-
|
|
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.` : "";
|
|
152573
152735
|
const runtimeSection = `************* RUNTIME *************
|
|
152574
152736
|
|
|
152575
152737
|
${ctx.runtime}`;
|
|
@@ -152596,8 +152758,8 @@ function resolveInstructions(ctx) {
|
|
|
152596
152758
|
if (eventContext)
|
|
152597
152759
|
tocEntries.push({ label: "EVENT CONTEXT", description: "related PR/issue data" });
|
|
152598
152760
|
tocEntries.push({ label: "SYSTEM", description: "persona, security, tools, workflow rules" });
|
|
152599
|
-
if (pctx.
|
|
152600
|
-
tocEntries.push({ label: "LEARNINGS", description: "repo-specific knowledge" });
|
|
152761
|
+
if (pctx.learningsFilePath)
|
|
152762
|
+
tocEntries.push({ label: "LEARNINGS", description: "repo-specific knowledge file path" });
|
|
152601
152763
|
tocEntries.push({ label: "RUNTIME", description: "environment metadata" });
|
|
152602
152764
|
const toc = buildToc(tocEntries);
|
|
152603
152765
|
const full = assembleFullPrompt({
|
|
@@ -152606,7 +152768,7 @@ function resolveInstructions(ctx) {
|
|
|
152606
152768
|
procedure,
|
|
152607
152769
|
eventContext,
|
|
152608
152770
|
system,
|
|
152609
|
-
|
|
152771
|
+
learningsFilePath: pctx.learningsFilePath,
|
|
152610
152772
|
runtime: pctx.runtime
|
|
152611
152773
|
});
|
|
152612
152774
|
const event = [pctx.eventTitle, pctx.eventMetadata].filter(Boolean).join("\n\n---\n\n");
|
|
@@ -152620,6 +152782,32 @@ function resolveInstructions(ctx) {
|
|
|
152620
152782
|
};
|
|
152621
152783
|
}
|
|
152622
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
|
+
|
|
152623
152811
|
// utils/normalizeEnv.ts
|
|
152624
152812
|
function maskValue(value2) {
|
|
152625
152813
|
if (value2 && typeof value2 === "string" && value2.trim().length > 0) {
|
|
@@ -152795,8 +152983,8 @@ function resolvePayload(resolvedPromptInput, repoSettings) {
|
|
|
152795
152983
|
}
|
|
152796
152984
|
|
|
152797
152985
|
// utils/prSummary.ts
|
|
152798
|
-
import { mkdir, readFile as
|
|
152799
|
-
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";
|
|
152800
152988
|
var SUMMARY_FILE_NAME = "pullfrog-summary.md";
|
|
152801
152989
|
var SUMMARY_SCAFFOLD = `# PR summary
|
|
152802
152990
|
|
|
@@ -152806,19 +152994,19 @@ var SUMMARY_SCAFFOLD = `# PR summary
|
|
|
152806
152994
|
var MIN_SNAPSHOT_LENGTH = 60;
|
|
152807
152995
|
var MAX_SNAPSHOT_LENGTH = 32768;
|
|
152808
152996
|
function summaryFilePath(tmpdir3) {
|
|
152809
|
-
return
|
|
152997
|
+
return join15(tmpdir3, SUMMARY_FILE_NAME);
|
|
152810
152998
|
}
|
|
152811
152999
|
async function seedSummaryFile(params) {
|
|
152812
153000
|
const path3 = summaryFilePath(params.tmpdir);
|
|
152813
|
-
await
|
|
153001
|
+
await mkdir2(dirname5(path3), { recursive: true });
|
|
152814
153002
|
const seed = params.previousSnapshot && params.previousSnapshot.trim().length >= MIN_SNAPSHOT_LENGTH ? params.previousSnapshot : SUMMARY_SCAFFOLD;
|
|
152815
|
-
await
|
|
153003
|
+
await writeFile3(path3, seed, "utf8");
|
|
152816
153004
|
return path3;
|
|
152817
153005
|
}
|
|
152818
153006
|
async function readSummaryFile(path3) {
|
|
152819
153007
|
let raw2;
|
|
152820
153008
|
try {
|
|
152821
|
-
raw2 = await
|
|
153009
|
+
raw2 = await readFile3(path3, "utf8");
|
|
152822
153010
|
} catch {
|
|
152823
153011
|
return null;
|
|
152824
153012
|
}
|
|
@@ -153036,9 +153224,9 @@ async function resolveRunContextData(params) {
|
|
|
153036
153224
|
import { execFileSync as execFileSync5, execSync as execSync3 } from "node:child_process";
|
|
153037
153225
|
import { mkdtempSync } from "node:fs";
|
|
153038
153226
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
153039
|
-
import { join as
|
|
153227
|
+
import { join as join16 } from "node:path";
|
|
153040
153228
|
function createTempDirectory() {
|
|
153041
|
-
const sharedTempDir = mkdtempSync(
|
|
153229
|
+
const sharedTempDir = mkdtempSync(join16(tmpdir2(), "pullfrog-"));
|
|
153042
153230
|
process.env.PULLFROG_TEMP_DIR = sharedTempDir;
|
|
153043
153231
|
log.info(`\xBB created temp dir at ${sharedTempDir}`);
|
|
153044
153232
|
return sharedTempDir;
|
|
@@ -153440,15 +153628,12 @@ function formatTransientErrorSummary(error49, owner) {
|
|
|
153440
153628
|
}
|
|
153441
153629
|
async function mintProxyKey(ctx) {
|
|
153442
153630
|
try {
|
|
153443
|
-
|
|
153444
|
-
|
|
153445
|
-
const oidcToken = await core6.getIDToken("pullfrog-api");
|
|
153446
|
-
delete process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
|
|
153447
|
-
delete process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
|
153631
|
+
const headers = await buildProxyTokenHeaders(ctx);
|
|
153632
|
+
if (!headers) return null;
|
|
153448
153633
|
const response = await apiFetch({
|
|
153449
153634
|
path: "/api/proxy-token",
|
|
153450
153635
|
method: "POST",
|
|
153451
|
-
headers
|
|
153636
|
+
headers
|
|
153452
153637
|
});
|
|
153453
153638
|
if (response.status === 402) {
|
|
153454
153639
|
const body = await response.json().catch(() => null);
|
|
@@ -153480,15 +153665,30 @@ async function mintProxyKey(ctx) {
|
|
|
153480
153665
|
delete process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
|
153481
153666
|
}
|
|
153482
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
|
+
}
|
|
153483
153683
|
async function resolveProxyModel(ctx) {
|
|
153484
153684
|
if (process.env.PULLFROG_MODEL?.trim()) return;
|
|
153485
153685
|
const needsProxy = isInfraCovered({ isOss: ctx.oss, plan: ctx.plan }) && ctx.proxyModel;
|
|
153486
153686
|
if (!needsProxy) return;
|
|
153487
|
-
if (!ctx.oidcCredentials) {
|
|
153687
|
+
if (!ctx.oidcCredentials && !isLocalApiUrl()) {
|
|
153488
153688
|
log.warning("\xBB proxy requested but no OIDC credentials available \u2014 skipping");
|
|
153489
153689
|
return;
|
|
153490
153690
|
}
|
|
153491
|
-
const key = await mintProxyKey({ oidcCredentials: ctx.oidcCredentials });
|
|
153691
|
+
const key = await mintProxyKey({ oidcCredentials: ctx.oidcCredentials, repo: ctx.repo });
|
|
153492
153692
|
if (!key) return;
|
|
153493
153693
|
process.env.OPENROUTER_API_KEY = key;
|
|
153494
153694
|
core6.setSecret(key);
|
|
@@ -153512,6 +153712,45 @@ async function fetchPreviousSnapshot(ctx, prNumber) {
|
|
|
153512
153712
|
return null;
|
|
153513
153713
|
}
|
|
153514
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
|
+
}
|
|
153515
153754
|
async function persistSummary(ctx) {
|
|
153516
153755
|
const filePath = ctx.toolState.summaryFilePath;
|
|
153517
153756
|
if (!filePath) return;
|
|
@@ -153593,7 +153832,8 @@ async function main() {
|
|
|
153593
153832
|
oss: runContext.oss,
|
|
153594
153833
|
plan: runContext.plan,
|
|
153595
153834
|
proxyModel: runContext.proxyModel,
|
|
153596
|
-
oidcCredentials
|
|
153835
|
+
oidcCredentials,
|
|
153836
|
+
repo: runContext.repo
|
|
153597
153837
|
});
|
|
153598
153838
|
} catch (error49) {
|
|
153599
153839
|
if (error49 instanceof BillingError) {
|
|
@@ -153696,12 +153936,32 @@ async function main() {
|
|
|
153696
153936
|
toolContext.mcpServerUrl = mcpHttpServer.url;
|
|
153697
153937
|
log.info(`\xBB MCP server started at ${mcpHttpServer.url}`);
|
|
153698
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
|
+
}
|
|
153699
153959
|
if (payload.generateSummary && payload.event.is_pr && payload.event.issue_number) {
|
|
153700
153960
|
const previousSnapshot = await fetchPreviousSnapshot(toolContext, payload.event.issue_number);
|
|
153701
153961
|
const filePath = await seedSummaryFile({ tmpdir: tmpdir3, previousSnapshot });
|
|
153702
153962
|
toolState.summaryFilePath = filePath;
|
|
153703
153963
|
try {
|
|
153704
|
-
toolState.summarySeed = await
|
|
153964
|
+
toolState.summarySeed = await readFile4(filePath, "utf8");
|
|
153705
153965
|
} catch {
|
|
153706
153966
|
}
|
|
153707
153967
|
log.info(
|
|
@@ -153725,7 +153985,7 @@ async function main() {
|
|
|
153725
153985
|
modes: modes2,
|
|
153726
153986
|
agentId,
|
|
153727
153987
|
outputSchema,
|
|
153728
|
-
|
|
153988
|
+
learningsFilePath: toolState.learningsFilePath ?? null
|
|
153729
153989
|
});
|
|
153730
153990
|
const logParts = [
|
|
153731
153991
|
instructions.eventInstructions ? `EVENT-LEVEL INSTRUCTIONS:
|
|
@@ -153741,7 +154001,7 @@ ${instructions.user}` : null,
|
|
|
153741
154001
|
log.info(instructions.full);
|
|
153742
154002
|
});
|
|
153743
154003
|
if (agentId === "opencode") {
|
|
153744
|
-
const pluginDir =
|
|
154004
|
+
const pluginDir = join17(process.cwd(), ".opencode", "plugin");
|
|
153745
154005
|
const hasPlugins = existsSync7(pluginDir) && readdirSync(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
|
|
153746
154006
|
if (hasPlugins && toolState.dependencyInstallation?.promise) {
|
|
153747
154007
|
log.info(
|
|
@@ -153802,6 +154062,7 @@ ${instructions.user}` : null,
|
|
|
153802
154062
|
stopScript: runContext.repoSettings.stopScript,
|
|
153803
154063
|
summaryFilePath: toolState.summaryFilePath,
|
|
153804
154064
|
summarySeed: toolState.summarySeed,
|
|
154065
|
+
learningsFilePath: toolState.learningsFilePath,
|
|
153805
154066
|
onActivityTimeout: onInnerActivityTimeout,
|
|
153806
154067
|
onToolUse: (event) => {
|
|
153807
154068
|
const wasTracked = recordDiffReadFromToolUse({
|
|
@@ -153859,6 +154120,9 @@ ${instructions.user}` : null,
|
|
|
153859
154120
|
if (toolContext) {
|
|
153860
154121
|
await persistSummary(toolContext);
|
|
153861
154122
|
}
|
|
154123
|
+
if (toolContext) {
|
|
154124
|
+
await persistLearnings(toolContext);
|
|
154125
|
+
}
|
|
153862
154126
|
if (toolContext && toolState.progressComment && !toolState.finalSummaryWritten) {
|
|
153863
154127
|
await deleteProgressComment(toolContext).catch((error49) => {
|
|
153864
154128
|
log.debug(`stranded progress comment cleanup failed: ${error49}`);
|
|
@@ -153911,6 +154175,9 @@ ${errorMessage}
|
|
|
153911
154175
|
if (toolContext) {
|
|
153912
154176
|
await persistSummary(toolContext);
|
|
153913
154177
|
}
|
|
154178
|
+
if (toolContext) {
|
|
154179
|
+
await persistLearnings(toolContext);
|
|
154180
|
+
}
|
|
153914
154181
|
return {
|
|
153915
154182
|
success: false,
|
|
153916
154183
|
error: errorMessage
|