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/cli.mjs
CHANGED
|
@@ -18415,7 +18415,7 @@ var require_summary = __commonJS({
|
|
|
18415
18415
|
exports.summary = exports.markdownSummary = exports.SUMMARY_DOCS_URL = exports.SUMMARY_ENV_VAR = void 0;
|
|
18416
18416
|
var os_1 = __require("os");
|
|
18417
18417
|
var fs_1 = __require("fs");
|
|
18418
|
-
var { access, appendFile, writeFile:
|
|
18418
|
+
var { access, appendFile, writeFile: writeFile4 } = fs_1.promises;
|
|
18419
18419
|
exports.SUMMARY_ENV_VAR = "GITHUB_STEP_SUMMARY";
|
|
18420
18420
|
exports.SUMMARY_DOCS_URL = "https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary";
|
|
18421
18421
|
var Summary = class {
|
|
@@ -18473,7 +18473,7 @@ var require_summary = __commonJS({
|
|
|
18473
18473
|
return __awaiter(this, void 0, void 0, function* () {
|
|
18474
18474
|
const overwrite = !!(options === null || options === void 0 ? void 0 : options.overwrite);
|
|
18475
18475
|
const filePath = yield this.filePath();
|
|
18476
|
-
const writeFunc = overwrite ?
|
|
18476
|
+
const writeFunc = overwrite ? writeFile4 : appendFile;
|
|
18477
18477
|
yield writeFunc(filePath, this._buffer, { encoding: "utf8" });
|
|
18478
18478
|
return this.emptyBuffer();
|
|
18479
18479
|
});
|
|
@@ -62879,8 +62879,8 @@ var require_snapshot_utils = __commonJS({
|
|
|
62879
62879
|
var require_snapshot_recorder = __commonJS({
|
|
62880
62880
|
"node_modules/.pnpm/undici@7.22.0/node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
|
|
62881
62881
|
"use strict";
|
|
62882
|
-
var { writeFile:
|
|
62883
|
-
var { dirname:
|
|
62882
|
+
var { writeFile: writeFile4, readFile: readFile5, mkdir: mkdir3 } = __require("node:fs/promises");
|
|
62883
|
+
var { dirname: dirname7, resolve: resolve3 } = __require("node:path");
|
|
62884
62884
|
var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("node:timers");
|
|
62885
62885
|
var { InvalidArgumentError, UndiciError } = require_errors4();
|
|
62886
62886
|
var { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require_snapshot_utils();
|
|
@@ -63081,7 +63081,7 @@ var require_snapshot_recorder = __commonJS({
|
|
|
63081
63081
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
63082
63082
|
}
|
|
63083
63083
|
try {
|
|
63084
|
-
const data = await
|
|
63084
|
+
const data = await readFile5(resolve3(path3), "utf8");
|
|
63085
63085
|
const parsed2 = JSON.parse(data);
|
|
63086
63086
|
if (Array.isArray(parsed2)) {
|
|
63087
63087
|
this.#snapshots.clear();
|
|
@@ -63111,12 +63111,12 @@ var require_snapshot_recorder = __commonJS({
|
|
|
63111
63111
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
63112
63112
|
}
|
|
63113
63113
|
const resolvedPath = resolve3(path3);
|
|
63114
|
-
await
|
|
63114
|
+
await mkdir3(dirname7(resolvedPath), { recursive: true });
|
|
63115
63115
|
const data = Array.from(this.#snapshots.entries()).map(([hash2, snapshot2]) => ({
|
|
63116
63116
|
hash: hash2,
|
|
63117
63117
|
snapshot: snapshot2
|
|
63118
63118
|
}));
|
|
63119
|
-
await
|
|
63119
|
+
await writeFile4(resolvedPath, JSON.stringify(data, null, 2), { flush: true });
|
|
63120
63120
|
}
|
|
63121
63121
|
/**
|
|
63122
63122
|
* Clears all recorded snapshots
|
|
@@ -97692,14 +97692,14 @@ var require_turndown_cjs = __commonJS({
|
|
|
97692
97692
|
} else if (node2.nodeType === 1) {
|
|
97693
97693
|
replacement = replacementForNode.call(self2, node2);
|
|
97694
97694
|
}
|
|
97695
|
-
return
|
|
97695
|
+
return join18(output, replacement);
|
|
97696
97696
|
}, "");
|
|
97697
97697
|
}
|
|
97698
97698
|
function postProcess(output) {
|
|
97699
97699
|
var self2 = this;
|
|
97700
97700
|
this.rules.forEach(function(rule) {
|
|
97701
97701
|
if (typeof rule.append === "function") {
|
|
97702
|
-
output =
|
|
97702
|
+
output = join18(output, rule.append(self2.options));
|
|
97703
97703
|
}
|
|
97704
97704
|
});
|
|
97705
97705
|
return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
|
|
@@ -97711,7 +97711,7 @@ var require_turndown_cjs = __commonJS({
|
|
|
97711
97711
|
if (whitespace.leading || whitespace.trailing) content = content.trim();
|
|
97712
97712
|
return whitespace.leading + rule.replacement(content, node2, this.options) + whitespace.trailing;
|
|
97713
97713
|
}
|
|
97714
|
-
function
|
|
97714
|
+
function join18(output, replacement) {
|
|
97715
97715
|
var s1 = trimTrailingNewlines(output);
|
|
97716
97716
|
var s2 = trimLeadingNewlines(replacement);
|
|
97717
97717
|
var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
|
|
@@ -99204,13 +99204,13 @@ import { basename as basename2 } from "node:path";
|
|
|
99204
99204
|
// commands/gha.ts
|
|
99205
99205
|
var core7 = __toESM(require_core(), 1);
|
|
99206
99206
|
var import_arg = __toESM(require_arg(), 1);
|
|
99207
|
-
import { dirname as
|
|
99207
|
+
import { dirname as dirname6 } from "node:path";
|
|
99208
99208
|
|
|
99209
99209
|
// main.ts
|
|
99210
99210
|
var core6 = __toESM(require_core(), 1);
|
|
99211
99211
|
import { existsSync as existsSync7, readdirSync } from "node:fs";
|
|
99212
|
-
import { readFile as
|
|
99213
|
-
import { join as
|
|
99212
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
99213
|
+
import { join as join17 } from "node:path";
|
|
99214
99214
|
|
|
99215
99215
|
// node_modules/.pnpm/@ark+util@0.56.0/node_modules/@ark/util/out/arrays.js
|
|
99216
99216
|
var liftArray = (data) => Array.isArray(data) ? data : [data];
|
|
@@ -108006,6 +108006,13 @@ function getApiUrl() {
|
|
|
108006
108006
|
log.debug(`resolved API_URL: ${raw2}`);
|
|
108007
108007
|
return raw2;
|
|
108008
108008
|
}
|
|
108009
|
+
function isLocalApiUrl() {
|
|
108010
|
+
try {
|
|
108011
|
+
return isLocalUrl(new URL(getApiUrl()));
|
|
108012
|
+
} catch {
|
|
108013
|
+
return false;
|
|
108014
|
+
}
|
|
108015
|
+
}
|
|
108009
108016
|
|
|
108010
108017
|
// models.ts
|
|
108011
108018
|
function provider(config3) {
|
|
@@ -109244,6 +109251,7 @@ function CreateCommentTool(ctx) {
|
|
|
109244
109251
|
body: bodyWithFooter
|
|
109245
109252
|
});
|
|
109246
109253
|
ctx.toolState.wasUpdated = true;
|
|
109254
|
+
log.info(`\xBB created comment ${result.data.id}`);
|
|
109247
109255
|
if (commentType === "Plan") {
|
|
109248
109256
|
if (result.data.node_id) {
|
|
109249
109257
|
await patchWorkflowRunFields(ctx, { planCommentNodeId: result.data.node_id });
|
|
@@ -109257,6 +109265,7 @@ function CreateCommentTool(ctx) {
|
|
|
109257
109265
|
comment_id: result.data.id,
|
|
109258
109266
|
body: bodyWithPlanLink
|
|
109259
109267
|
});
|
|
109268
|
+
log.info(`\xBB updated comment ${updateResult.data.id}`);
|
|
109260
109269
|
return {
|
|
109261
109270
|
success: true,
|
|
109262
109271
|
commentId: updateResult.data.id,
|
|
@@ -109290,6 +109299,7 @@ function EditCommentTool(ctx) {
|
|
|
109290
109299
|
comment_id: commentId,
|
|
109291
109300
|
body: bodyWithFooter
|
|
109292
109301
|
});
|
|
109302
|
+
log.info(`\xBB updated comment ${result.data.id}`);
|
|
109293
109303
|
return {
|
|
109294
109304
|
success: true,
|
|
109295
109305
|
commentId: result.data.id,
|
|
@@ -109425,6 +109435,9 @@ ${collapsible}`;
|
|
|
109425
109435
|
message: "progress recorded (no GitHub comment created - this may occur for workflow_dispatch events or when there is no associated issue/PR)"
|
|
109426
109436
|
};
|
|
109427
109437
|
}
|
|
109438
|
+
if (result.commentId !== void 0) {
|
|
109439
|
+
log.info(`\xBB ${result.action} comment ${result.commentId}`);
|
|
109440
|
+
}
|
|
109428
109441
|
if (!params.target_plan_comment) {
|
|
109429
109442
|
ctx.toolState.finalSummaryWritten = true;
|
|
109430
109443
|
}
|
|
@@ -109475,6 +109488,7 @@ function ReplyToReviewCommentTool(ctx) {
|
|
|
109475
109488
|
comment_id,
|
|
109476
109489
|
body: bodyWithFooter
|
|
109477
109490
|
});
|
|
109491
|
+
log.info(`\xBB created review comment ${result.data.id} (in reply to ${comment_id})`);
|
|
109478
109492
|
ctx.toolState.wasUpdated = true;
|
|
109479
109493
|
return {
|
|
109480
109494
|
success: true,
|
|
@@ -110024,11 +110038,6 @@ async function spawn(options) {
|
|
|
110024
110038
|
`spawn activity timer: pid=${child.pid} cmd=${options.cmd} timeout=${activityTimeoutMs}ms`
|
|
110025
110039
|
);
|
|
110026
110040
|
activityCheckIntervalId = setInterval(() => {
|
|
110027
|
-
if (options.isPausedExternally?.()) {
|
|
110028
|
-
lastActivityTime = performance3.now();
|
|
110029
|
-
log.debug(`spawn activity check: pid=${child.pid} paused externally`);
|
|
110030
|
-
return;
|
|
110031
|
-
}
|
|
110032
110041
|
const idleMs = performance3.now() - lastActivityTime;
|
|
110033
110042
|
log.debug(
|
|
110034
110043
|
`spawn activity check: pid=${child.pid} idle=${Math.round(idleMs)}ms / ${activityTimeoutMs}ms`
|
|
@@ -142549,7 +142558,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
142549
142558
|
// package.json
|
|
142550
142559
|
var package_default = {
|
|
142551
142560
|
name: "pullfrog",
|
|
142552
|
-
version: "0.1.
|
|
142561
|
+
version: "0.1.2",
|
|
142553
142562
|
type: "module",
|
|
142554
142563
|
bin: {
|
|
142555
142564
|
pullfrog: "dist/cli.mjs",
|
|
@@ -143493,6 +143502,10 @@ ${integrateStep}
|
|
|
143493
143502
|
if (!pushed) {
|
|
143494
143503
|
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
143495
143504
|
}
|
|
143505
|
+
const pushedSha = $("git", ["rev-parse", "HEAD"], { log: false }).trim();
|
|
143506
|
+
log.info(
|
|
143507
|
+
`\xBB pushed branch ${branch} to ${pushDest.remoteName}/${pushDest.remoteBranch} (sha ${pushedSha})`
|
|
143508
|
+
);
|
|
143496
143509
|
return {
|
|
143497
143510
|
success: true,
|
|
143498
143511
|
branch,
|
|
@@ -143641,6 +143654,7 @@ function DeleteBranchTool(ctx) {
|
|
|
143641
143654
|
await $git("push", ["origin", "--delete", `refs/heads/${params.branchName}`], {
|
|
143642
143655
|
token: ctx.gitToken
|
|
143643
143656
|
});
|
|
143657
|
+
log.info(`\xBB deleted branch ${params.branchName}`);
|
|
143644
143658
|
return { success: true, deleted: params.branchName };
|
|
143645
143659
|
})
|
|
143646
143660
|
});
|
|
@@ -143666,6 +143680,7 @@ function PushTagsTool(ctx) {
|
|
|
143666
143680
|
await $git("push", pushArgs, {
|
|
143667
143681
|
token: ctx.gitToken
|
|
143668
143682
|
});
|
|
143683
|
+
log.info(`\xBB pushed tag ${params.tag}`);
|
|
143669
143684
|
return { success: true, tag: params.tag };
|
|
143670
143685
|
})
|
|
143671
143686
|
});
|
|
@@ -143990,6 +144005,7 @@ function CreatePullRequestReviewTool(ctx) {
|
|
|
143990
144005
|
}
|
|
143991
144006
|
const reviewId = result.data.id;
|
|
143992
144007
|
const reviewNodeId = result.data.node_id;
|
|
144008
|
+
log.info(`\xBB created review ${reviewId} on pull request #${pull_number}`);
|
|
143993
144009
|
const actuallyReviewedSha = ctx.toolState.checkoutSha ?? params.commit_id;
|
|
143994
144010
|
ctx.toolState.review = {
|
|
143995
144011
|
id: reviewId,
|
|
@@ -144854,6 +144870,7 @@ function IssueTool(ctx) {
|
|
|
144854
144870
|
labels: params.labels ?? [],
|
|
144855
144871
|
assignees: params.assignees ?? []
|
|
144856
144872
|
});
|
|
144873
|
+
log.info(`\xBB created issue #${result.data.number} (id ${result.data.id})`);
|
|
144857
144874
|
const nodeId = result.data.node_id;
|
|
144858
144875
|
if (typeof nodeId === "string" && nodeId.length > 0) {
|
|
144859
144876
|
await patchWorkflowRunFields(ctx, {
|
|
@@ -145045,6 +145062,7 @@ function AddLabelsTool(ctx) {
|
|
|
145045
145062
|
issue_number,
|
|
145046
145063
|
labels
|
|
145047
145064
|
});
|
|
145065
|
+
log.info(`\xBB added labels [${labels.join(", ")}] to issue #${issue_number}`);
|
|
145048
145066
|
return {
|
|
145049
145067
|
success: true,
|
|
145050
145068
|
labels: result.data.map((label) => label.name)
|
|
@@ -145053,40 +145071,6 @@ function AddLabelsTool(ctx) {
|
|
|
145053
145071
|
});
|
|
145054
145072
|
}
|
|
145055
145073
|
|
|
145056
|
-
// mcp/learnings.ts
|
|
145057
|
-
var UpdateLearningsParams = type({
|
|
145058
|
-
learnings: type.string.describe(
|
|
145059
|
-
"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."
|
|
145060
|
-
)
|
|
145061
|
-
});
|
|
145062
|
-
function UpdateLearningsTool(ctx) {
|
|
145063
|
-
return tool({
|
|
145064
|
-
name: "update_learnings",
|
|
145065
|
-
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.",
|
|
145066
|
-
parameters: UpdateLearningsParams,
|
|
145067
|
-
execute: execute(async (params) => {
|
|
145068
|
-
const response = await apiFetch({
|
|
145069
|
-
path: `/api/repo/${ctx.repo.owner}/${ctx.repo.name}/learnings`,
|
|
145070
|
-
method: "PATCH",
|
|
145071
|
-
headers: {
|
|
145072
|
-
authorization: `Bearer ${ctx.apiToken}`,
|
|
145073
|
-
"content-type": "application/json"
|
|
145074
|
-
},
|
|
145075
|
-
body: JSON.stringify({
|
|
145076
|
-
learnings: params.learnings,
|
|
145077
|
-
model: ctx.toolState.model
|
|
145078
|
-
}),
|
|
145079
|
-
signal: AbortSignal.timeout(1e4)
|
|
145080
|
-
});
|
|
145081
|
-
if (!response.ok) {
|
|
145082
|
-
const error49 = await response.text();
|
|
145083
|
-
throw new Error(`failed to update learnings: ${error49}`);
|
|
145084
|
-
}
|
|
145085
|
-
return { success: true };
|
|
145086
|
-
})
|
|
145087
|
-
});
|
|
145088
|
-
}
|
|
145089
|
-
|
|
145090
145074
|
// mcp/output.ts
|
|
145091
145075
|
var import_ajv3 = __toESM(require_ajv(), 1);
|
|
145092
145076
|
var SetOutputParams = type({
|
|
@@ -145180,6 +145164,7 @@ function UpdatePullRequestBodyTool(ctx) {
|
|
|
145180
145164
|
pull_number: params.pull_number,
|
|
145181
145165
|
body: bodyWithFooter
|
|
145182
145166
|
});
|
|
145167
|
+
log.info(`\xBB updated pull request #${result.data.number}`);
|
|
145183
145168
|
ctx.toolState.wasUpdated = true;
|
|
145184
145169
|
return {
|
|
145185
145170
|
success: true,
|
|
@@ -145207,6 +145192,7 @@ function CreatePullRequestTool(ctx) {
|
|
|
145207
145192
|
base: params.base,
|
|
145208
145193
|
draft: params.draft ?? false
|
|
145209
145194
|
});
|
|
145195
|
+
log.info(`\xBB created pull request #${result.data.number} (id ${result.data.id})`);
|
|
145210
145196
|
const reviewer = ctx.payload.triggerer;
|
|
145211
145197
|
if (reviewer) {
|
|
145212
145198
|
try {
|
|
@@ -145758,7 +145744,7 @@ function ResolveReviewThreadTool(ctx) {
|
|
|
145758
145744
|
threadId: params.thread_id
|
|
145759
145745
|
});
|
|
145760
145746
|
const thread = response.resolveReviewThread.thread;
|
|
145761
|
-
log.
|
|
145747
|
+
log.info(`\xBB resolved review thread ${thread.id}`);
|
|
145762
145748
|
return {
|
|
145763
145749
|
thread_id: thread.id,
|
|
145764
145750
|
is_resolved: thread.isResolved,
|
|
@@ -146230,6 +146216,7 @@ function UploadFileTool(ctx) {
|
|
|
146230
146216
|
if (!uploadResponse.ok) {
|
|
146231
146217
|
throw new Error(`failed to upload file: ${uploadResponse.statusText}`);
|
|
146232
146218
|
}
|
|
146219
|
+
log.info(`\xBB uploaded file ${publicUrl}`);
|
|
146233
146220
|
return { success: true, publicUrl, filename, contentLength, contentType };
|
|
146234
146221
|
})
|
|
146235
146222
|
});
|
|
@@ -146323,8 +146310,7 @@ function buildOrchestratorTools(ctx, outputSchema) {
|
|
|
146323
146310
|
PushTagsTool(ctx),
|
|
146324
146311
|
DeleteBranchTool(ctx),
|
|
146325
146312
|
CreatePullRequestTool(ctx),
|
|
146326
|
-
UpdatePullRequestBodyTool(ctx)
|
|
146327
|
-
UpdateLearningsTool(ctx)
|
|
146313
|
+
UpdatePullRequestBodyTool(ctx)
|
|
146328
146314
|
];
|
|
146329
146315
|
}
|
|
146330
146316
|
async function tryStartMcpServer(ctx, tools, port) {
|
|
@@ -146481,9 +146467,6 @@ Rules:
|
|
|
146481
146467
|
- Do NOT include a changelog section \u2014 the key changes list serves this purpose
|
|
146482
146468
|
- Focus on *intent*, not *what* \u2014 the diff already shows what changed
|
|
146483
146469
|
- Get the file count and commit count from the checkout_pr metadata, not by counting manually`;
|
|
146484
|
-
function learningsStep(t2, n) {
|
|
146485
|
-
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 \`${t2("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.`;
|
|
146486
|
-
}
|
|
146487
146470
|
function computeModes(agentId) {
|
|
146488
146471
|
const t2 = (toolName) => formatMcpToolRef(agentId, toolName);
|
|
146489
146472
|
return [
|
|
@@ -146539,8 +146522,6 @@ function computeModes(agentId) {
|
|
|
146539
146522
|
- create a PR via \`${t2("create_pull_request")}\`
|
|
146540
146523
|
- call \`${t2("report_progress")}\` with the PR link or the exact error if push/PR failed
|
|
146541
146524
|
|
|
146542
|
-
${learningsStep(t2, 6)}
|
|
146543
|
-
|
|
146544
146525
|
### Notes
|
|
146545
146526
|
|
|
146546
146527
|
For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
@@ -146568,9 +146549,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
146568
146549
|
- confirm a clean working tree, then push via \`${t2("push_branch")}\` (same push/prepush guidance as Build mode in *SYSTEM*)
|
|
146569
146550
|
- reply to each comment using \`${t2("reply_to_review_comment")}\`
|
|
146570
146551
|
- resolve addressed threads via \`${t2("resolve_review_thread")}\`
|
|
146571
|
-
- call \`${t2("report_progress")}\` with a brief summary (or the exact push error if push failed)
|
|
146572
|
-
|
|
146573
|
-
${learningsStep(t2, 6)}`
|
|
146552
|
+
- call \`${t2("report_progress")}\` with a brief summary (or the exact push error if push failed)`
|
|
146574
146553
|
},
|
|
146575
146554
|
// Review and IncrementalReview use the multi-lens orchestrator pattern
|
|
146576
146555
|
// (canonical source: .claude/commands/anneal.md). The orchestrator does
|
|
@@ -146741,9 +146720,7 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
146741
146720
|
|
|
146742
146721
|
2. Produce a structured, actionable plan with clear milestones.
|
|
146743
146722
|
|
|
146744
|
-
3. Call \`${t2("report_progress")}\` with the plan
|
|
146745
|
-
|
|
146746
|
-
${learningsStep(t2, 4)}`
|
|
146723
|
+
3. Call \`${t2("report_progress")}\` with the plan.`
|
|
146747
146724
|
},
|
|
146748
146725
|
{
|
|
146749
146726
|
name: "Fix",
|
|
@@ -146765,9 +146742,7 @@ ${learningsStep(t2, 4)}`
|
|
|
146765
146742
|
|
|
146766
146743
|
5. Finalize:
|
|
146767
146744
|
- confirm a clean working tree, then push via \`${t2("push_branch")}\` (same push/prepush guidance as Build mode in *SYSTEM*)
|
|
146768
|
-
- call \`${t2("report_progress")}\` with the diagnosis and fix summary (or the exact push error if push failed)
|
|
146769
|
-
|
|
146770
|
-
${learningsStep(t2, 6)}`
|
|
146745
|
+
- call \`${t2("report_progress")}\` with the diagnosis and fix summary (or the exact push error if push failed)`
|
|
146771
146746
|
},
|
|
146772
146747
|
{
|
|
146773
146748
|
name: "ResolveConflicts",
|
|
@@ -146811,9 +146786,7 @@ ${learningsStep(t2, 6)}`
|
|
|
146811
146786
|
3. Finalize:
|
|
146812
146787
|
- if code changes were made, push to a pull request (new or existing) using \`${t2("push_branch")}\` and \`${t2("create_pull_request")}\` as needed. \`git status\` must be clean before you finish (see *SYSTEM* Git rules if push fails).
|
|
146813
146788
|
- call \`${t2("report_progress")}\` once with results \u2014 include exact tool errors if push or PR creation failed
|
|
146814
|
-
- if the task involved labeling, commenting, or other GitHub operations, perform those directly
|
|
146815
|
-
|
|
146816
|
-
${learningsStep(t2, 4)}`
|
|
146789
|
+
- if the task involved labeling, commenting, or other GitHub operations, perform those directly`
|
|
146817
146790
|
}
|
|
146818
146791
|
];
|
|
146819
146792
|
}
|
|
@@ -146913,6 +146886,17 @@ async function installFromNpmTarball(params) {
|
|
|
146913
146886
|
// utils/providerErrors.ts
|
|
146914
146887
|
var statusKey = `\\b(?:status[_ ]?code|http[_ ]?status|status)["']?\\s*[:=]\\s*["']?`;
|
|
146915
146888
|
var PROVIDER_ERROR_PATTERNS = [
|
|
146889
|
+
// auth patterns must come BEFORE rate-limit patterns. OpenRouter 401 error
|
|
146890
|
+
// payloads carry `x-ratelimit-*` response headers in the dump, and the
|
|
146891
|
+
// free-form rate-limit regex below would otherwise win on word-boundary
|
|
146892
|
+
// matches inside header names. canonical 401 messages: OpenRouter returns
|
|
146893
|
+
// `{"error":{"message":"User not found","code":401}}` for disabled or
|
|
146894
|
+
// invalid keys (https://openai.luzhipeng.com/docs/api/reference/errors-and-debugging).
|
|
146895
|
+
{ regex: new RegExp(`${statusKey}401\\b`, "i"), label: "auth error (401)" },
|
|
146896
|
+
{ regex: new RegExp(`${statusKey}403\\b`, "i"), label: "auth error (403)" },
|
|
146897
|
+
{ regex: /\bUser not found\b/i, label: "auth error (invalid/disabled key)" },
|
|
146898
|
+
{ regex: /\bInvalid authentication\b/i, label: "auth error (invalid credentials)" },
|
|
146899
|
+
{ regex: /\bNo auth credentials found\b/i, label: "auth error (missing credentials)" },
|
|
146916
146900
|
{ regex: new RegExp(`${statusKey}429\\b`, "i"), label: "rate limited (429)" },
|
|
146917
146901
|
{ regex: new RegExp(`${statusKey}500\\b`, "i"), label: "provider 500 error" },
|
|
146918
146902
|
{ regex: new RegExp(`${statusKey}503\\b`, "i"), label: "provider unavailable (503)" },
|
|
@@ -146976,7 +146960,7 @@ function installBundledSkills(params) {
|
|
|
146976
146960
|
writeFileSync6(join9(skillDir, "SKILL.md"), content);
|
|
146977
146961
|
}
|
|
146978
146962
|
}
|
|
146979
|
-
log.
|
|
146963
|
+
log.success(`installed bundled skills: ${BUNDLED_SKILL_NAMES.join(", ")}`);
|
|
146980
146964
|
}
|
|
146981
146965
|
function addSkill(params) {
|
|
146982
146966
|
const result = spawnSync5(
|
|
@@ -147001,7 +146985,7 @@ function addSkill(params) {
|
|
|
147001
146985
|
}
|
|
147002
146986
|
);
|
|
147003
146987
|
if (result.status === 0) {
|
|
147004
|
-
log.
|
|
146988
|
+
log.success(`installed ${params.skill} skill (${params.agent})`);
|
|
147005
146989
|
} else {
|
|
147006
146990
|
const stderr = (result.stderr?.toString() || "").trim();
|
|
147007
146991
|
const errorMsg = result.error ? result.error.message : stderr;
|
|
@@ -147135,18 +147119,17 @@ function buildPostRunPrompt(issues) {
|
|
|
147135
147119
|
if (issues.summaryStale) parts.push(buildSummaryStalePrompt(issues.summaryStale.filePath));
|
|
147136
147120
|
return parts.join("\n\n---\n\n");
|
|
147137
147121
|
}
|
|
147138
|
-
function buildLearningsReflectionPrompt(
|
|
147139
|
-
const t2 = (name) => formatMcpToolRef(agentId, name);
|
|
147122
|
+
function buildLearningsReflectionPrompt(filePath) {
|
|
147140
147123
|
return [
|
|
147141
|
-
`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
|
|
147124
|
+
`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?`,
|
|
147142
147125
|
"",
|
|
147143
|
-
`
|
|
147126
|
+
`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.`,
|
|
147144
147127
|
"",
|
|
147145
|
-
`
|
|
147146
|
-
`- only
|
|
147147
|
-
`-
|
|
147148
|
-
`-
|
|
147149
|
-
`- if you
|
|
147128
|
+
`keep the file healthy:`,
|
|
147129
|
+
`- only add bullets when the finding is high-confidence AND broadly useful. skip speculative, one-off, or "maybe" findings.`,
|
|
147130
|
+
`- prune bullets that are clearly wrong, no longer relevant, or low-signal (rarely useful). a focused, accurate file beats a long stale one.`,
|
|
147131
|
+
`- 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.`,
|
|
147132
|
+
`- 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.`
|
|
147150
147133
|
].join("\n");
|
|
147151
147134
|
}
|
|
147152
147135
|
async function runPostRunRetryLoop(params) {
|
|
@@ -147729,7 +147712,7 @@ var claude = agent({
|
|
|
147729
147712
|
stopScript: ctx.stopScript,
|
|
147730
147713
|
summaryFilePath: ctx.summaryFilePath,
|
|
147731
147714
|
summarySeed: ctx.summarySeed,
|
|
147732
|
-
reflectionPrompt: buildLearningsReflectionPrompt(
|
|
147715
|
+
reflectionPrompt: ctx.learningsFilePath ? buildLearningsReflectionPrompt(ctx.learningsFilePath) : void 0,
|
|
147733
147716
|
canResume: (r) => Boolean(r.sessionId),
|
|
147734
147717
|
resume: async (c2) => {
|
|
147735
147718
|
const sessionId = c2.previousResult.sessionId;
|
|
@@ -147745,9 +147728,92 @@ var claude = agent({
|
|
|
147745
147728
|
|
|
147746
147729
|
// agents/opencode.ts
|
|
147747
147730
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
147748
|
-
import { mkdirSync as mkdirSync5 } from "node:fs";
|
|
147731
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "node:fs";
|
|
147749
147732
|
import { join as join11 } from "node:path";
|
|
147750
147733
|
import { performance as performance7 } from "node:perf_hooks";
|
|
147734
|
+
|
|
147735
|
+
// agents/opencodePlugin.ts
|
|
147736
|
+
var PULLFROG_BUS_EVENT_TYPE = "pullfrog_bus_event";
|
|
147737
|
+
var PULLFROG_OPENCODE_PLUGIN_FILENAME = "pullfrog-events.ts";
|
|
147738
|
+
var PULLFROG_OPENCODE_PLUGIN_SOURCE = `// AUTOGENERATED by Pullfrog. do not edit; it'll be overwritten on the next run.
|
|
147739
|
+
// surfaces opencode subagent activity that the CLI's run-loop discards. see
|
|
147740
|
+
// action/agents/opencodePlugin.ts in pullfrog/app for why this exists. lives
|
|
147741
|
+
// inside the per-run tmpdir (XDG_CONFIG_HOME/opencode/plugin/), never inside
|
|
147742
|
+
// the user's working tree.
|
|
147743
|
+
|
|
147744
|
+
const PULLFROG_BUS_EVENT_TYPE = ${JSON.stringify(PULLFROG_BUS_EVENT_TYPE)};
|
|
147745
|
+
|
|
147746
|
+
// the first sessionID we see on a message.part.updated event is the
|
|
147747
|
+
// orchestrator \u2014 opencode's run command creates exactly one top-level session
|
|
147748
|
+
// before any subagent is dispatched, and the user-prompt text part fires
|
|
147749
|
+
// before the first task tool_use. we lock that sessionID in here and use it
|
|
147750
|
+
// to filter: the orchestrator's events are already streamed by the CLI's
|
|
147751
|
+
// run-loop, so we only forward (a) all subagent events, and (b) the
|
|
147752
|
+
// orchestrator's task tool dispatches at status="running". the CLI only
|
|
147753
|
+
// emits task tool_use at status=completed (after the subagent finishes), so
|
|
147754
|
+
// without the early announce the parent's labeler binds subagent sessions
|
|
147755
|
+
// before recordTaskDispatch fires and the lens label is lost.
|
|
147756
|
+
let orchestratorSessionID: string | undefined;
|
|
147757
|
+
|
|
147758
|
+
function isOrchestratorTaskDispatch(part: {
|
|
147759
|
+
type?: string;
|
|
147760
|
+
tool?: string;
|
|
147761
|
+
state?: { status?: string };
|
|
147762
|
+
}): boolean {
|
|
147763
|
+
if (part.type !== "tool") return false;
|
|
147764
|
+
if (part.tool !== "task") return false;
|
|
147765
|
+
// only forward at status="running" (not "pending"). at pending the
|
|
147766
|
+
// state.input is still {} \u2014 the orchestrator has emitted the part shell
|
|
147767
|
+
// but the LLM hasn't filled in description/subagent_type/prompt yet. by
|
|
147768
|
+
// running, input is populated and recordTaskDispatch can derive the lens
|
|
147769
|
+
// label correctly.
|
|
147770
|
+
return part.state?.status === "running";
|
|
147771
|
+
}
|
|
147772
|
+
|
|
147773
|
+
export default async function pullfrogEventsPlugin() {
|
|
147774
|
+
return {
|
|
147775
|
+
event: async (input: {
|
|
147776
|
+
event: {
|
|
147777
|
+
type: string;
|
|
147778
|
+
properties?: {
|
|
147779
|
+
part?: {
|
|
147780
|
+
sessionID?: string;
|
|
147781
|
+
type?: string;
|
|
147782
|
+
tool?: string;
|
|
147783
|
+
state?: { status?: string };
|
|
147784
|
+
};
|
|
147785
|
+
};
|
|
147786
|
+
};
|
|
147787
|
+
}) => {
|
|
147788
|
+
const event = input.event;
|
|
147789
|
+
if (!event || typeof event !== "object") return;
|
|
147790
|
+
if (event.type !== "message.part.updated") return;
|
|
147791
|
+
const part = event.properties?.part;
|
|
147792
|
+
const sessionID = part?.sessionID;
|
|
147793
|
+
if (typeof sessionID !== "string" || sessionID.length === 0) return;
|
|
147794
|
+
if (orchestratorSessionID === undefined) orchestratorSessionID = sessionID;
|
|
147795
|
+
|
|
147796
|
+
if (sessionID === orchestratorSessionID) {
|
|
147797
|
+
// skip orchestrator events EXCEPT early task dispatches.
|
|
147798
|
+
if (!part || !isOrchestratorTaskDispatch(part)) return;
|
|
147799
|
+
}
|
|
147800
|
+
|
|
147801
|
+
try {
|
|
147802
|
+
const line = JSON.stringify({
|
|
147803
|
+
type: PULLFROG_BUS_EVENT_TYPE,
|
|
147804
|
+
bus_event: event,
|
|
147805
|
+
});
|
|
147806
|
+
process.stdout.write(line + "\\n");
|
|
147807
|
+
} catch {
|
|
147808
|
+
// a circular reference or BigInt etc. would throw; swallow rather
|
|
147809
|
+
// than letting a single bad event take down the plugin.
|
|
147810
|
+
}
|
|
147811
|
+
},
|
|
147812
|
+
};
|
|
147813
|
+
}
|
|
147814
|
+
`;
|
|
147815
|
+
|
|
147816
|
+
// agents/opencode.ts
|
|
147751
147817
|
async function installOpencodeCli() {
|
|
147752
147818
|
return await installFromNpmTarball({
|
|
147753
147819
|
packageName: "opencode-ai",
|
|
@@ -147849,9 +147915,6 @@ async function runOpenCode(params) {
|
|
|
147849
147915
|
const taskDispatchByCallID = /* @__PURE__ */ new Map();
|
|
147850
147916
|
const pendingTaskDispatches = [];
|
|
147851
147917
|
const knownNonTaskCallIDs = /* @__PURE__ */ new Set();
|
|
147852
|
-
function isSubagentInFlight() {
|
|
147853
|
-
return taskDispatchByCallID.size > 0 || pendingTaskDispatches.length > 0;
|
|
147854
|
-
}
|
|
147855
147918
|
function emitSubagentFinished(dispatch, status, output2, matchKind) {
|
|
147856
147919
|
const subagentDuration = performance7.now() - dispatch.startedAt;
|
|
147857
147920
|
const outputStr = typeof output2 === "string" ? output2 : "";
|
|
@@ -147970,18 +148033,20 @@ async function runOpenCode(params) {
|
|
|
147970
148033
|
return;
|
|
147971
148034
|
}
|
|
147972
148035
|
if (toolName === "task") {
|
|
147973
|
-
|
|
147974
|
-
|
|
147975
|
-
|
|
147976
|
-
|
|
147977
|
-
|
|
147978
|
-
|
|
147979
|
-
|
|
147980
|
-
|
|
147981
|
-
|
|
147982
|
-
|
|
147983
|
-
|
|
147984
|
-
|
|
148036
|
+
if (!taskDispatchByCallID.has(toolId)) {
|
|
148037
|
+
const taskInput = event.part?.state?.input ?? {};
|
|
148038
|
+
const dispatchedLabel = labeler.recordTaskDispatch(taskInput);
|
|
148039
|
+
const dispatch = {
|
|
148040
|
+
label: dispatchedLabel,
|
|
148041
|
+
startedAt: performance7.now(),
|
|
148042
|
+
toolUseCallID: toolId
|
|
148043
|
+
};
|
|
148044
|
+
taskDispatchByCallID.set(toolId, dispatch);
|
|
148045
|
+
pendingTaskDispatches.push(dispatch);
|
|
148046
|
+
log.info(
|
|
148047
|
+
`\xBB dispatching subagent: ${dispatchedLabel}` + (taskInput.subagent_type ? ` (subagent_type=${taskInput.subagent_type})` : "")
|
|
148048
|
+
);
|
|
148049
|
+
}
|
|
147985
148050
|
} else {
|
|
147986
148051
|
knownNonTaskCallIDs.add(toolId);
|
|
147987
148052
|
}
|
|
@@ -148002,6 +148067,10 @@ async function runOpenCode(params) {
|
|
|
148002
148067
|
if (event.part?.state?.status === "completed" && event.part.state.output) {
|
|
148003
148068
|
log.debug(withLabel(label, ` output: ${event.part.state.output}`));
|
|
148004
148069
|
}
|
|
148070
|
+
if (event.part?.state?.status === "error") {
|
|
148071
|
+
const errorMsg = event.part.state.output ?? "(no error message)";
|
|
148072
|
+
log.info(withLabel(label, `\xBB tool call failed: ${errorMsg}`));
|
|
148073
|
+
}
|
|
148005
148074
|
if (toolName.includes("report_progress") && params.todoTracker) {
|
|
148006
148075
|
log.debug("\xBB report_progress detected, disabling todo tracking");
|
|
148007
148076
|
params.todoTracker.cancel();
|
|
@@ -148088,6 +148157,53 @@ async function runOpenCode(params) {
|
|
|
148088
148157
|
tokensLogged = true;
|
|
148089
148158
|
}
|
|
148090
148159
|
}
|
|
148160
|
+
},
|
|
148161
|
+
[PULLFROG_BUS_EVENT_TYPE]: async (event) => {
|
|
148162
|
+
const busEvent = event.bus_event;
|
|
148163
|
+
if (!busEvent || busEvent.type !== "message.part.updated") return;
|
|
148164
|
+
const part = busEvent.properties?.part;
|
|
148165
|
+
if (!part || typeof part.sessionID !== "string") return;
|
|
148166
|
+
const sessionID = part.sessionID;
|
|
148167
|
+
const partType = part.type;
|
|
148168
|
+
if (partType === "tool") {
|
|
148169
|
+
const status = part.state?.status;
|
|
148170
|
+
const partWithToolFields = part;
|
|
148171
|
+
const isOrchestratorTaskDispatch = partWithToolFields.tool === "task" && status === "running";
|
|
148172
|
+
if (isOrchestratorTaskDispatch) {
|
|
148173
|
+
const callID = partWithToolFields.callID;
|
|
148174
|
+
if (typeof callID === "string" && !taskDispatchByCallID.has(callID)) {
|
|
148175
|
+
const taskInput = partWithToolFields.state?.input ?? {};
|
|
148176
|
+
const dispatchedLabel = labeler.recordTaskDispatch(taskInput);
|
|
148177
|
+
const dispatch = {
|
|
148178
|
+
label: dispatchedLabel,
|
|
148179
|
+
startedAt: performance7.now(),
|
|
148180
|
+
toolUseCallID: callID
|
|
148181
|
+
};
|
|
148182
|
+
taskDispatchByCallID.set(callID, dispatch);
|
|
148183
|
+
pendingTaskDispatches.push(dispatch);
|
|
148184
|
+
log.info(
|
|
148185
|
+
`\xBB dispatching subagent: ${dispatchedLabel}` + (taskInput.subagent_type ? ` (subagent_type=${taskInput.subagent_type})` : "")
|
|
148186
|
+
);
|
|
148187
|
+
}
|
|
148188
|
+
return;
|
|
148189
|
+
}
|
|
148190
|
+
if (status !== "completed" && status !== "error") return;
|
|
148191
|
+
await handlers2.tool_use({
|
|
148192
|
+
type: "tool_use",
|
|
148193
|
+
sessionID,
|
|
148194
|
+
part
|
|
148195
|
+
});
|
|
148196
|
+
return;
|
|
148197
|
+
}
|
|
148198
|
+
if (partType === "step-start" || partType === "step-finish") return;
|
|
148199
|
+
if (partType === "text" && part.time?.end !== void 0) {
|
|
148200
|
+
await handlers2.text({
|
|
148201
|
+
type: "text",
|
|
148202
|
+
sessionID,
|
|
148203
|
+
part
|
|
148204
|
+
});
|
|
148205
|
+
return;
|
|
148206
|
+
}
|
|
148091
148207
|
}
|
|
148092
148208
|
};
|
|
148093
148209
|
const recentStderr = [];
|
|
@@ -148111,13 +148227,13 @@ async function runOpenCode(params) {
|
|
|
148111
148227
|
// never fires — producing zombie runs. detached + killGroup nukes the
|
|
148112
148228
|
// whole tree.
|
|
148113
148229
|
killGroup: true,
|
|
148114
|
-
//
|
|
148115
|
-
//
|
|
148116
|
-
//
|
|
148117
|
-
//
|
|
148118
|
-
//
|
|
148119
|
-
//
|
|
148120
|
-
|
|
148230
|
+
// NB: we used to pass `isPausedExternally: isSubagentInFlight` to suspend
|
|
148231
|
+
// the activity timer during subagent dispatches. unnecessary now that
|
|
148232
|
+
// our injected plugin (action/agents/opencodePlugin.ts) re-emits
|
|
148233
|
+
// subagent `message.part.updated` events on opencode's stdout — those
|
|
148234
|
+
// arrive at child.stdout here, fire updateActivity(), and reset
|
|
148235
|
+
// lastActivityTime naturally. verified empirically in PR #634
|
|
148236
|
+
// (~3.3 plugin events/sec during a typical subagent run).
|
|
148121
148237
|
onStdout: async (chunk) => {
|
|
148122
148238
|
const text = chunk.toString();
|
|
148123
148239
|
output += text;
|
|
@@ -148272,6 +148388,12 @@ var opencode = agent({
|
|
|
148272
148388
|
XDG_CONFIG_HOME: join11(ctx.tmpdir, ".config")
|
|
148273
148389
|
};
|
|
148274
148390
|
mkdirSync5(join11(homeEnv.XDG_CONFIG_HOME, "opencode"), { recursive: true });
|
|
148391
|
+
const opencodePluginDir = join11(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
|
|
148392
|
+
mkdirSync5(opencodePluginDir, { recursive: true });
|
|
148393
|
+
writeFileSync8(
|
|
148394
|
+
join11(opencodePluginDir, PULLFROG_OPENCODE_PLUGIN_FILENAME),
|
|
148395
|
+
PULLFROG_OPENCODE_PLUGIN_SOURCE
|
|
148396
|
+
);
|
|
148275
148397
|
const agentBrowserVersion = getDevDependencyVersion("agent-browser");
|
|
148276
148398
|
addSkill({
|
|
148277
148399
|
ref: `vercel-labs/agent-browser@v${agentBrowserVersion}`,
|
|
@@ -148314,7 +148436,7 @@ var opencode = agent({
|
|
|
148314
148436
|
stopScript: ctx.stopScript,
|
|
148315
148437
|
summaryFilePath: ctx.summaryFilePath,
|
|
148316
148438
|
summarySeed: ctx.summarySeed,
|
|
148317
|
-
reflectionPrompt: buildLearningsReflectionPrompt(
|
|
148439
|
+
reflectionPrompt: ctx.learningsFilePath ? buildLearningsReflectionPrompt(ctx.learningsFilePath) : void 0,
|
|
148318
148440
|
resume: async (c2) => runOpenCode({
|
|
148319
148441
|
...runParams,
|
|
148320
148442
|
args: [...baseArgs, "--continue", c2.prompt]
|
|
@@ -152527,7 +152649,7 @@ ${ctx.error}` : ctx.error;
|
|
|
152527
152649
|
|
|
152528
152650
|
// utils/gitAuthServer.ts
|
|
152529
152651
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
152530
|
-
import { writeFileSync as
|
|
152652
|
+
import { writeFileSync as writeFileSync9 } from "node:fs";
|
|
152531
152653
|
import { createServer as createServer2 } from "node:http";
|
|
152532
152654
|
import { join as join13 } from "node:path";
|
|
152533
152655
|
var CODE_TTL_MS = 5 * 60 * 1e3;
|
|
@@ -152616,7 +152738,7 @@ async function startGitAuthServer(tmpdir3) {
|
|
|
152616
152738
|
`try{require("fs").unlinkSync("${scriptPath.replace(/\\/g, "\\\\")}")}catch(e){}`,
|
|
152617
152739
|
`})}).on("error",function(){process.exit(1)})}`
|
|
152618
152740
|
].join("\n");
|
|
152619
|
-
|
|
152741
|
+
writeFileSync9(scriptPath, content, { mode: 448 });
|
|
152620
152742
|
return scriptPath;
|
|
152621
152743
|
}
|
|
152622
152744
|
async function close() {
|
|
@@ -152890,9 +153012,9 @@ function buildPromptContext(ctx) {
|
|
|
152890
153012
|
};
|
|
152891
153013
|
}
|
|
152892
153014
|
function assembleFullPrompt(ctx) {
|
|
152893
|
-
const learningsSection = ctx.
|
|
153015
|
+
const learningsSection = ctx.learningsFilePath ? `************* LEARNINGS *************
|
|
152894
153016
|
|
|
152895
|
-
|
|
153017
|
+
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.` : "";
|
|
152896
153018
|
const runtimeSection = `************* RUNTIME *************
|
|
152897
153019
|
|
|
152898
153020
|
${ctx.runtime}`;
|
|
@@ -152919,8 +153041,8 @@ function resolveInstructions(ctx) {
|
|
|
152919
153041
|
if (eventContext)
|
|
152920
153042
|
tocEntries.push({ label: "EVENT CONTEXT", description: "related PR/issue data" });
|
|
152921
153043
|
tocEntries.push({ label: "SYSTEM", description: "persona, security, tools, workflow rules" });
|
|
152922
|
-
if (pctx.
|
|
152923
|
-
tocEntries.push({ label: "LEARNINGS", description: "repo-specific knowledge" });
|
|
153044
|
+
if (pctx.learningsFilePath)
|
|
153045
|
+
tocEntries.push({ label: "LEARNINGS", description: "repo-specific knowledge file path" });
|
|
152924
153046
|
tocEntries.push({ label: "RUNTIME", description: "environment metadata" });
|
|
152925
153047
|
const toc = buildToc(tocEntries);
|
|
152926
153048
|
const full = assembleFullPrompt({
|
|
@@ -152929,7 +153051,7 @@ function resolveInstructions(ctx) {
|
|
|
152929
153051
|
procedure,
|
|
152930
153052
|
eventContext,
|
|
152931
153053
|
system,
|
|
152932
|
-
|
|
153054
|
+
learningsFilePath: pctx.learningsFilePath,
|
|
152933
153055
|
runtime: pctx.runtime
|
|
152934
153056
|
});
|
|
152935
153057
|
const event = [pctx.eventTitle, pctx.eventMetadata].filter(Boolean).join("\n\n---\n\n");
|
|
@@ -152943,6 +153065,32 @@ function resolveInstructions(ctx) {
|
|
|
152943
153065
|
};
|
|
152944
153066
|
}
|
|
152945
153067
|
|
|
153068
|
+
// utils/learnings.ts
|
|
153069
|
+
import { mkdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
153070
|
+
import { dirname as dirname4, join as join14 } from "node:path";
|
|
153071
|
+
var LEARNINGS_FILE_NAME = "pullfrog-learnings.md";
|
|
153072
|
+
var MAX_LEARNINGS_LENGTH = 1e4;
|
|
153073
|
+
function learningsFilePath(tmpdir3) {
|
|
153074
|
+
return join14(tmpdir3, LEARNINGS_FILE_NAME);
|
|
153075
|
+
}
|
|
153076
|
+
async function seedLearningsFile(params) {
|
|
153077
|
+
const path3 = learningsFilePath(params.tmpdir);
|
|
153078
|
+
await mkdir(dirname4(path3), { recursive: true });
|
|
153079
|
+
await writeFile2(path3, params.current ?? "", "utf8");
|
|
153080
|
+
return path3;
|
|
153081
|
+
}
|
|
153082
|
+
async function readLearningsFile(path3) {
|
|
153083
|
+
let raw2;
|
|
153084
|
+
try {
|
|
153085
|
+
raw2 = await readFile2(path3, "utf8");
|
|
153086
|
+
} catch {
|
|
153087
|
+
return null;
|
|
153088
|
+
}
|
|
153089
|
+
const trimmed = raw2.trim();
|
|
153090
|
+
if (trimmed.length > MAX_LEARNINGS_LENGTH) return trimmed.slice(0, MAX_LEARNINGS_LENGTH);
|
|
153091
|
+
return trimmed;
|
|
153092
|
+
}
|
|
153093
|
+
|
|
152946
153094
|
// utils/normalizeEnv.ts
|
|
152947
153095
|
function maskValue(value2) {
|
|
152948
153096
|
if (value2 && typeof value2 === "string" && value2.trim().length > 0) {
|
|
@@ -153118,8 +153266,8 @@ function resolvePayload(resolvedPromptInput, repoSettings) {
|
|
|
153118
153266
|
}
|
|
153119
153267
|
|
|
153120
153268
|
// utils/prSummary.ts
|
|
153121
|
-
import { mkdir, readFile as
|
|
153122
|
-
import { dirname as
|
|
153269
|
+
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
153270
|
+
import { dirname as dirname5, join as join15 } from "node:path";
|
|
153123
153271
|
var SUMMARY_FILE_NAME = "pullfrog-summary.md";
|
|
153124
153272
|
var SUMMARY_SCAFFOLD = `# PR summary
|
|
153125
153273
|
|
|
@@ -153129,19 +153277,19 @@ var SUMMARY_SCAFFOLD = `# PR summary
|
|
|
153129
153277
|
var MIN_SNAPSHOT_LENGTH = 60;
|
|
153130
153278
|
var MAX_SNAPSHOT_LENGTH = 32768;
|
|
153131
153279
|
function summaryFilePath(tmpdir3) {
|
|
153132
|
-
return
|
|
153280
|
+
return join15(tmpdir3, SUMMARY_FILE_NAME);
|
|
153133
153281
|
}
|
|
153134
153282
|
async function seedSummaryFile(params) {
|
|
153135
153283
|
const path3 = summaryFilePath(params.tmpdir);
|
|
153136
|
-
await
|
|
153284
|
+
await mkdir2(dirname5(path3), { recursive: true });
|
|
153137
153285
|
const seed = params.previousSnapshot && params.previousSnapshot.trim().length >= MIN_SNAPSHOT_LENGTH ? params.previousSnapshot : SUMMARY_SCAFFOLD;
|
|
153138
|
-
await
|
|
153286
|
+
await writeFile3(path3, seed, "utf8");
|
|
153139
153287
|
return path3;
|
|
153140
153288
|
}
|
|
153141
153289
|
async function readSummaryFile(path3) {
|
|
153142
153290
|
let raw2;
|
|
153143
153291
|
try {
|
|
153144
|
-
raw2 = await
|
|
153292
|
+
raw2 = await readFile3(path3, "utf8");
|
|
153145
153293
|
} catch {
|
|
153146
153294
|
return null;
|
|
153147
153295
|
}
|
|
@@ -153359,9 +153507,9 @@ async function resolveRunContextData(params) {
|
|
|
153359
153507
|
import { execFileSync as execFileSync5, execSync as execSync3 } from "node:child_process";
|
|
153360
153508
|
import { mkdtempSync } from "node:fs";
|
|
153361
153509
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
153362
|
-
import { join as
|
|
153510
|
+
import { join as join16 } from "node:path";
|
|
153363
153511
|
function createTempDirectory() {
|
|
153364
|
-
const sharedTempDir = mkdtempSync(
|
|
153512
|
+
const sharedTempDir = mkdtempSync(join16(tmpdir2(), "pullfrog-"));
|
|
153365
153513
|
process.env.PULLFROG_TEMP_DIR = sharedTempDir;
|
|
153366
153514
|
log.info(`\xBB created temp dir at ${sharedTempDir}`);
|
|
153367
153515
|
return sharedTempDir;
|
|
@@ -153763,15 +153911,12 @@ function formatTransientErrorSummary(error49, owner) {
|
|
|
153763
153911
|
}
|
|
153764
153912
|
async function mintProxyKey(ctx) {
|
|
153765
153913
|
try {
|
|
153766
|
-
|
|
153767
|
-
|
|
153768
|
-
const oidcToken = await core6.getIDToken("pullfrog-api");
|
|
153769
|
-
delete process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
|
|
153770
|
-
delete process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
|
153914
|
+
const headers = await buildProxyTokenHeaders(ctx);
|
|
153915
|
+
if (!headers) return null;
|
|
153771
153916
|
const response = await apiFetch({
|
|
153772
153917
|
path: "/api/proxy-token",
|
|
153773
153918
|
method: "POST",
|
|
153774
|
-
headers
|
|
153919
|
+
headers
|
|
153775
153920
|
});
|
|
153776
153921
|
if (response.status === 402) {
|
|
153777
153922
|
const body = await response.json().catch(() => null);
|
|
@@ -153803,15 +153948,30 @@ async function mintProxyKey(ctx) {
|
|
|
153803
153948
|
delete process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
|
153804
153949
|
}
|
|
153805
153950
|
}
|
|
153951
|
+
async function buildProxyTokenHeaders(ctx) {
|
|
153952
|
+
if (ctx.oidcCredentials) {
|
|
153953
|
+
process.env.ACTIONS_ID_TOKEN_REQUEST_URL = ctx.oidcCredentials.requestUrl;
|
|
153954
|
+
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = ctx.oidcCredentials.requestToken;
|
|
153955
|
+
const oidcToken = await core6.getIDToken("pullfrog-api");
|
|
153956
|
+
delete process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
|
|
153957
|
+
delete process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
|
153958
|
+
return { Authorization: `Bearer ${oidcToken}` };
|
|
153959
|
+
}
|
|
153960
|
+
if (isLocalApiUrl()) {
|
|
153961
|
+
log.info(`\xBB proxy: dev bypass (x-dev-repo) for ${ctx.repo.owner}/${ctx.repo.name}`);
|
|
153962
|
+
return { "x-dev-repo": `${ctx.repo.owner}/${ctx.repo.name}` };
|
|
153963
|
+
}
|
|
153964
|
+
return null;
|
|
153965
|
+
}
|
|
153806
153966
|
async function resolveProxyModel(ctx) {
|
|
153807
153967
|
if (process.env.PULLFROG_MODEL?.trim()) return;
|
|
153808
153968
|
const needsProxy = isInfraCovered({ isOss: ctx.oss, plan: ctx.plan }) && ctx.proxyModel;
|
|
153809
153969
|
if (!needsProxy) return;
|
|
153810
|
-
if (!ctx.oidcCredentials) {
|
|
153970
|
+
if (!ctx.oidcCredentials && !isLocalApiUrl()) {
|
|
153811
153971
|
log.warning("\xBB proxy requested but no OIDC credentials available \u2014 skipping");
|
|
153812
153972
|
return;
|
|
153813
153973
|
}
|
|
153814
|
-
const key = await mintProxyKey({ oidcCredentials: ctx.oidcCredentials });
|
|
153974
|
+
const key = await mintProxyKey({ oidcCredentials: ctx.oidcCredentials, repo: ctx.repo });
|
|
153815
153975
|
if (!key) return;
|
|
153816
153976
|
process.env.OPENROUTER_API_KEY = key;
|
|
153817
153977
|
core6.setSecret(key);
|
|
@@ -153835,6 +153995,45 @@ async function fetchPreviousSnapshot(ctx, prNumber) {
|
|
|
153835
153995
|
return null;
|
|
153836
153996
|
}
|
|
153837
153997
|
}
|
|
153998
|
+
async function persistLearnings(ctx) {
|
|
153999
|
+
const filePath = ctx.toolState.learningsFilePath;
|
|
154000
|
+
if (!filePath) return;
|
|
154001
|
+
if (ctx.toolState.learningsPersistAttempted) return;
|
|
154002
|
+
ctx.toolState.learningsPersistAttempted = true;
|
|
154003
|
+
const current = await readLearningsFile(filePath);
|
|
154004
|
+
if (current === null) {
|
|
154005
|
+
log.debug(`learnings tmpfile missing or unreadable at ${filePath} \u2014 skipping persist`);
|
|
154006
|
+
return;
|
|
154007
|
+
}
|
|
154008
|
+
const seed = ctx.toolState.learningsSeed?.trim() ?? "";
|
|
154009
|
+
if (current === seed) {
|
|
154010
|
+
log.debug("learnings tmpfile unchanged from seed \u2014 skipping persist");
|
|
154011
|
+
return;
|
|
154012
|
+
}
|
|
154013
|
+
try {
|
|
154014
|
+
const response = await apiFetch({
|
|
154015
|
+
path: `/api/repo/${ctx.repo.owner}/${ctx.repo.name}/learnings`,
|
|
154016
|
+
method: "PATCH",
|
|
154017
|
+
headers: {
|
|
154018
|
+
authorization: `Bearer ${ctx.apiToken}`,
|
|
154019
|
+
"content-type": "application/json"
|
|
154020
|
+
},
|
|
154021
|
+
body: JSON.stringify({
|
|
154022
|
+
learnings: current,
|
|
154023
|
+
model: ctx.toolState.model
|
|
154024
|
+
}),
|
|
154025
|
+
signal: AbortSignal.timeout(1e4)
|
|
154026
|
+
});
|
|
154027
|
+
if (!response.ok) {
|
|
154028
|
+
const error49 = await response.text().catch(() => "(no body)");
|
|
154029
|
+
log.debug(`learnings persist failed (${response.status}): ${error49}`);
|
|
154030
|
+
return;
|
|
154031
|
+
}
|
|
154032
|
+
log.info("\xBB learnings updated");
|
|
154033
|
+
} catch (err) {
|
|
154034
|
+
log.debug(`learnings persist failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
154035
|
+
}
|
|
154036
|
+
}
|
|
153838
154037
|
async function persistSummary(ctx) {
|
|
153839
154038
|
const filePath = ctx.toolState.summaryFilePath;
|
|
153840
154039
|
if (!filePath) return;
|
|
@@ -153916,7 +154115,8 @@ async function main() {
|
|
|
153916
154115
|
oss: runContext.oss,
|
|
153917
154116
|
plan: runContext.plan,
|
|
153918
154117
|
proxyModel: runContext.proxyModel,
|
|
153919
|
-
oidcCredentials
|
|
154118
|
+
oidcCredentials,
|
|
154119
|
+
repo: runContext.repo
|
|
153920
154120
|
});
|
|
153921
154121
|
} catch (error49) {
|
|
153922
154122
|
if (error49 instanceof BillingError) {
|
|
@@ -154019,12 +154219,32 @@ async function main() {
|
|
|
154019
154219
|
toolContext.mcpServerUrl = mcpHttpServer.url;
|
|
154020
154220
|
log.info(`\xBB MCP server started at ${mcpHttpServer.url}`);
|
|
154021
154221
|
timer.checkpoint("mcpServer");
|
|
154222
|
+
try {
|
|
154223
|
+
const learningsPath = await seedLearningsFile({
|
|
154224
|
+
tmpdir: tmpdir3,
|
|
154225
|
+
current: runContext.repoSettings.learnings
|
|
154226
|
+
});
|
|
154227
|
+
toolState.learningsFilePath = learningsPath;
|
|
154228
|
+
try {
|
|
154229
|
+
toolState.learningsSeed = await readFile4(learningsPath, "utf8");
|
|
154230
|
+
} catch {
|
|
154231
|
+
}
|
|
154232
|
+
log.info(
|
|
154233
|
+
`\xBB learnings seeded at ${learningsPath} (existing=${runContext.repoSettings.learnings ? "yes" : "no"})`
|
|
154234
|
+
);
|
|
154235
|
+
const ctxForExit = toolContext;
|
|
154236
|
+
onExitSignal(() => persistLearnings(ctxForExit));
|
|
154237
|
+
} catch (err) {
|
|
154238
|
+
log.warning(
|
|
154239
|
+
`\xBB learnings seed failed: ${err instanceof Error ? err.message : String(err)} \u2014 continuing without learnings file`
|
|
154240
|
+
);
|
|
154241
|
+
}
|
|
154022
154242
|
if (payload.generateSummary && payload.event.is_pr && payload.event.issue_number) {
|
|
154023
154243
|
const previousSnapshot = await fetchPreviousSnapshot(toolContext, payload.event.issue_number);
|
|
154024
154244
|
const filePath = await seedSummaryFile({ tmpdir: tmpdir3, previousSnapshot });
|
|
154025
154245
|
toolState.summaryFilePath = filePath;
|
|
154026
154246
|
try {
|
|
154027
|
-
toolState.summarySeed = await
|
|
154247
|
+
toolState.summarySeed = await readFile4(filePath, "utf8");
|
|
154028
154248
|
} catch {
|
|
154029
154249
|
}
|
|
154030
154250
|
log.info(
|
|
@@ -154048,7 +154268,7 @@ async function main() {
|
|
|
154048
154268
|
modes: modes2,
|
|
154049
154269
|
agentId,
|
|
154050
154270
|
outputSchema,
|
|
154051
|
-
|
|
154271
|
+
learningsFilePath: toolState.learningsFilePath ?? null
|
|
154052
154272
|
});
|
|
154053
154273
|
const logParts = [
|
|
154054
154274
|
instructions.eventInstructions ? `EVENT-LEVEL INSTRUCTIONS:
|
|
@@ -154064,7 +154284,7 @@ ${instructions.user}` : null,
|
|
|
154064
154284
|
log.info(instructions.full);
|
|
154065
154285
|
});
|
|
154066
154286
|
if (agentId === "opencode") {
|
|
154067
|
-
const pluginDir =
|
|
154287
|
+
const pluginDir = join17(process.cwd(), ".opencode", "plugin");
|
|
154068
154288
|
const hasPlugins = existsSync7(pluginDir) && readdirSync(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
|
|
154069
154289
|
if (hasPlugins && toolState.dependencyInstallation?.promise) {
|
|
154070
154290
|
log.info(
|
|
@@ -154125,6 +154345,7 @@ ${instructions.user}` : null,
|
|
|
154125
154345
|
stopScript: runContext.repoSettings.stopScript,
|
|
154126
154346
|
summaryFilePath: toolState.summaryFilePath,
|
|
154127
154347
|
summarySeed: toolState.summarySeed,
|
|
154348
|
+
learningsFilePath: toolState.learningsFilePath,
|
|
154128
154349
|
onActivityTimeout: onInnerActivityTimeout,
|
|
154129
154350
|
onToolUse: (event) => {
|
|
154130
154351
|
const wasTracked = recordDiffReadFromToolUse({
|
|
@@ -154182,6 +154403,9 @@ ${instructions.user}` : null,
|
|
|
154182
154403
|
if (toolContext) {
|
|
154183
154404
|
await persistSummary(toolContext);
|
|
154184
154405
|
}
|
|
154406
|
+
if (toolContext) {
|
|
154407
|
+
await persistLearnings(toolContext);
|
|
154408
|
+
}
|
|
154185
154409
|
if (toolContext && toolState.progressComment && !toolState.finalSummaryWritten) {
|
|
154186
154410
|
await deleteProgressComment(toolContext).catch((error49) => {
|
|
154187
154411
|
log.debug(`stranded progress comment cleanup failed: ${error49}`);
|
|
@@ -154234,6 +154458,9 @@ ${errorMessage}
|
|
|
154234
154458
|
if (toolContext) {
|
|
154235
154459
|
await persistSummary(toolContext);
|
|
154236
154460
|
}
|
|
154461
|
+
if (toolContext) {
|
|
154462
|
+
await persistLearnings(toolContext);
|
|
154463
|
+
}
|
|
154237
154464
|
return {
|
|
154238
154465
|
success: false,
|
|
154239
154466
|
error: errorMessage
|
|
@@ -154266,7 +154493,7 @@ ${errorMessage}
|
|
|
154266
154493
|
}
|
|
154267
154494
|
|
|
154268
154495
|
// commands/gha.ts
|
|
154269
|
-
process.env.PATH = `${
|
|
154496
|
+
process.env.PATH = `${dirname6(process.execPath)}:${process.env.PATH}`;
|
|
154270
154497
|
var STATE_TOKEN = "token";
|
|
154271
154498
|
async function runMain() {
|
|
154272
154499
|
try {
|
|
@@ -156076,7 +156303,7 @@ async function run2() {
|
|
|
156076
156303
|
}
|
|
156077
156304
|
|
|
156078
156305
|
// cli.ts
|
|
156079
|
-
var VERSION10 = "0.1.
|
|
156306
|
+
var VERSION10 = "0.1.2";
|
|
156080
156307
|
var bin = basename2(process.argv[1] || "");
|
|
156081
156308
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
156082
156309
|
var rawArgs = process.argv.slice(2);
|