pullfrog 0.0.202 → 0.0.204
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/postRun.d.ts +66 -0
- package/dist/agents/reviewer.d.ts +32 -0
- package/dist/agents/sessionLabeler.d.ts +77 -0
- package/dist/agents/shared.d.ts +22 -3
- package/dist/cli.mjs +1231 -339
- package/dist/external.d.ts +6 -3
- package/dist/index.js +1146 -306
- package/dist/internal/index.d.ts +3 -1
- package/dist/internal.js +381 -70
- package/dist/mcp/comment.d.ts +6 -3
- package/dist/mcp/git.d.ts +2 -0
- package/dist/mcp/review.d.ts +35 -0
- package/dist/mcp/reviewComments.d.ts +29 -0
- package/dist/mcp/server.d.ts +9 -2
- package/dist/models.d.ts +17 -0
- package/dist/skills/git-archaeology/SKILL.md +188 -0
- package/dist/utils/payload.d.ts +8 -2
- package/dist/utils/progressComment.d.ts +146 -0
- package/dist/utils/runContext.d.ts +17 -0
- package/dist/utils/runContextData.d.ts +2 -1
- package/dist/utils/skills.d.ts +10 -0
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -62880,7 +62880,7 @@ 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
62882
|
var { writeFile: writeFile2, readFile, mkdir } = __require("node:fs/promises");
|
|
62883
|
-
var { dirname:
|
|
62883
|
+
var { dirname: dirname5, 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();
|
|
@@ -63111,7 +63111,7 @@ var require_snapshot_recorder = __commonJS({
|
|
|
63111
63111
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
63112
63112
|
}
|
|
63113
63113
|
const resolvedPath = resolve3(path3);
|
|
63114
|
-
await mkdir(
|
|
63114
|
+
await mkdir(dirname5(resolvedPath), { recursive: true });
|
|
63115
63115
|
const data = Array.from(this.#snapshots.entries()).map(([hash2, snapshot2]) => ({
|
|
63116
63116
|
hash: hash2,
|
|
63117
63117
|
snapshot: snapshot2
|
|
@@ -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 join16(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 = join16(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 join16(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,12 +99204,12 @@ 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 dirname4 } from "node:path";
|
|
99208
99208
|
|
|
99209
99209
|
// main.ts
|
|
99210
99210
|
var core6 = __toESM(require_core(), 1);
|
|
99211
|
-
import { existsSync as
|
|
99212
|
-
import { join as
|
|
99211
|
+
import { existsSync as existsSync7, readdirSync } from "node:fs";
|
|
99212
|
+
import { join as join15 } from "node:path";
|
|
99213
99213
|
|
|
99214
99214
|
// node_modules/.pnpm/@ark+util@0.56.0/node_modules/@ark/util/out/arrays.js
|
|
99215
99215
|
var liftArray = (data) => Array.isArray(data) ? data : [data];
|
|
@@ -107684,7 +107684,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
107684
107684
|
// agents/shared.ts
|
|
107685
107685
|
import { execFileSync } from "node:child_process";
|
|
107686
107686
|
var MAX_STDERR_LINES = 20;
|
|
107687
|
-
var
|
|
107687
|
+
var MAX_POST_RUN_RETRIES = 3;
|
|
107688
107688
|
function getGitStatus() {
|
|
107689
107689
|
try {
|
|
107690
107690
|
return execFileSync("git", ["status", "--porcelain"], {
|
|
@@ -107695,7 +107695,7 @@ function getGitStatus() {
|
|
|
107695
107695
|
return "";
|
|
107696
107696
|
}
|
|
107697
107697
|
}
|
|
107698
|
-
function buildCommitPrompt(
|
|
107698
|
+
function buildCommitPrompt(status) {
|
|
107699
107699
|
return [
|
|
107700
107700
|
`UNCOMMITTED CHANGES \u2014 the working tree is dirty. push all changes to a pull request (new or existing). \`git status\` must be clean before you finish.`,
|
|
107701
107701
|
"",
|
|
@@ -107704,6 +107704,9 @@ function buildCommitPrompt(_agentId, status) {
|
|
|
107704
107704
|
"```"
|
|
107705
107705
|
].join("\n");
|
|
107706
107706
|
}
|
|
107707
|
+
function hasPostRunIssues(issues) {
|
|
107708
|
+
return issues.stopHook !== void 0 || issues.dirtyTree !== void 0;
|
|
107709
|
+
}
|
|
107707
107710
|
var agent = (input) => {
|
|
107708
107711
|
return {
|
|
107709
107712
|
...input,
|
|
@@ -108015,7 +108018,7 @@ var providers = {
|
|
|
108015
108018
|
"claude-opus": {
|
|
108016
108019
|
displayName: "Claude Opus",
|
|
108017
108020
|
resolve: "anthropic/claude-opus-4-7",
|
|
108018
|
-
openRouterResolve: "openrouter/anthropic/claude-opus-4.
|
|
108021
|
+
openRouterResolve: "openrouter/anthropic/claude-opus-4.7",
|
|
108019
108022
|
preferred: true
|
|
108020
108023
|
},
|
|
108021
108024
|
"claude-sonnet": {
|
|
@@ -108034,16 +108037,38 @@ var providers = {
|
|
|
108034
108037
|
displayName: "OpenAI",
|
|
108035
108038
|
envVars: ["OPENAI_API_KEY"],
|
|
108036
108039
|
models: {
|
|
108040
|
+
gpt: {
|
|
108041
|
+
displayName: "GPT",
|
|
108042
|
+
resolve: "openai/gpt-5.5",
|
|
108043
|
+
openRouterResolve: "openrouter/openai/gpt-5.5",
|
|
108044
|
+
preferred: true
|
|
108045
|
+
},
|
|
108046
|
+
"gpt-pro": {
|
|
108047
|
+
displayName: "GPT Pro",
|
|
108048
|
+
resolve: "openai/gpt-5.5-pro",
|
|
108049
|
+
openRouterResolve: "openrouter/openai/gpt-5.5-pro"
|
|
108050
|
+
},
|
|
108051
|
+
"gpt-mini": {
|
|
108052
|
+
displayName: "GPT Mini",
|
|
108053
|
+
resolve: "openai/gpt-5.4-mini",
|
|
108054
|
+
openRouterResolve: "openrouter/openai/gpt-5.4-mini"
|
|
108055
|
+
},
|
|
108056
|
+
// legacy aliases — openai unified the codex line into the main GPT family
|
|
108057
|
+
// and is shutting down every "-codex" snapshot on 2026-07-23. transparently
|
|
108058
|
+
// upgrade existing users via the fallback chain. UI display sites resolve
|
|
108059
|
+
// to the terminal alias's label (so dropdown trigger + PR footers show
|
|
108060
|
+
// "GPT" / "GPT Mini", not the historical name).
|
|
108037
108061
|
"gpt-codex": {
|
|
108038
108062
|
displayName: "GPT Codex",
|
|
108039
108063
|
resolve: "openai/gpt-5.3-codex",
|
|
108040
108064
|
openRouterResolve: "openrouter/openai/gpt-5.3-codex",
|
|
108041
|
-
|
|
108065
|
+
fallback: "openai/gpt"
|
|
108042
108066
|
},
|
|
108043
108067
|
"gpt-codex-mini": {
|
|
108044
108068
|
displayName: "GPT Codex Mini",
|
|
108045
108069
|
resolve: "openai/gpt-5.1-codex-mini",
|
|
108046
|
-
openRouterResolve: "openrouter/openai/gpt-5.1-codex-mini"
|
|
108070
|
+
openRouterResolve: "openrouter/openai/gpt-5.1-codex-mini",
|
|
108071
|
+
fallback: "openai/gpt-mini"
|
|
108047
108072
|
},
|
|
108048
108073
|
o3: {
|
|
108049
108074
|
displayName: "O3",
|
|
@@ -108074,14 +108099,14 @@ var providers = {
|
|
|
108074
108099
|
models: {
|
|
108075
108100
|
grok: {
|
|
108076
108101
|
displayName: "Grok",
|
|
108077
|
-
resolve: "xai/grok-4",
|
|
108078
|
-
openRouterResolve: "openrouter/x-ai/grok-4",
|
|
108102
|
+
resolve: "xai/grok-4.3",
|
|
108103
|
+
openRouterResolve: "openrouter/x-ai/grok-4.3",
|
|
108079
108104
|
preferred: true
|
|
108080
108105
|
},
|
|
108081
108106
|
"grok-fast": {
|
|
108082
108107
|
displayName: "Grok Fast",
|
|
108083
|
-
resolve: "xai/grok-4-fast",
|
|
108084
|
-
openRouterResolve: "openrouter/x-ai/grok-4-fast"
|
|
108108
|
+
resolve: "xai/grok-4-1-fast",
|
|
108109
|
+
openRouterResolve: "openrouter/x-ai/grok-4.1-fast"
|
|
108085
108110
|
},
|
|
108086
108111
|
"grok-code-fast": {
|
|
108087
108112
|
displayName: "Grok Code Fast",
|
|
@@ -108094,16 +108119,30 @@ var providers = {
|
|
|
108094
108119
|
displayName: "DeepSeek",
|
|
108095
108120
|
envVars: ["DEEPSEEK_API_KEY"],
|
|
108096
108121
|
models: {
|
|
108122
|
+
"deepseek-pro": {
|
|
108123
|
+
displayName: "DeepSeek Pro",
|
|
108124
|
+
resolve: "deepseek/deepseek-v4-pro",
|
|
108125
|
+
openRouterResolve: "openrouter/deepseek/deepseek-v4-pro",
|
|
108126
|
+
preferred: true
|
|
108127
|
+
},
|
|
108128
|
+
"deepseek-flash": {
|
|
108129
|
+
displayName: "DeepSeek Flash",
|
|
108130
|
+
resolve: "deepseek/deepseek-v4-flash",
|
|
108131
|
+
openRouterResolve: "openrouter/deepseek/deepseek-v4-flash"
|
|
108132
|
+
},
|
|
108133
|
+
// legacy aliases — deepseek retires these on 2026-07-24; transparently
|
|
108134
|
+
// upgrade existing users to the v4 family via the fallback chain.
|
|
108097
108135
|
"deepseek-reasoner": {
|
|
108098
108136
|
displayName: "DeepSeek Reasoner",
|
|
108099
108137
|
resolve: "deepseek/deepseek-reasoner",
|
|
108100
108138
|
openRouterResolve: "openrouter/deepseek/deepseek-v3.2",
|
|
108101
|
-
|
|
108139
|
+
fallback: "deepseek/deepseek-pro"
|
|
108102
108140
|
},
|
|
108103
108141
|
"deepseek-chat": {
|
|
108104
108142
|
displayName: "DeepSeek Chat",
|
|
108105
108143
|
resolve: "deepseek/deepseek-chat",
|
|
108106
|
-
openRouterResolve: "openrouter/deepseek/deepseek-v3.2"
|
|
108144
|
+
openRouterResolve: "openrouter/deepseek/deepseek-v3.2",
|
|
108145
|
+
fallback: "deepseek/deepseek-flash"
|
|
108107
108146
|
}
|
|
108108
108147
|
}
|
|
108109
108148
|
}),
|
|
@@ -108113,8 +108152,8 @@ var providers = {
|
|
|
108113
108152
|
models: {
|
|
108114
108153
|
"kimi-k2": {
|
|
108115
108154
|
displayName: "Kimi K2",
|
|
108116
|
-
resolve: "moonshotai/kimi-k2.
|
|
108117
|
-
openRouterResolve: "openrouter/moonshotai/kimi-k2.
|
|
108155
|
+
resolve: "moonshotai/kimi-k2.6",
|
|
108156
|
+
openRouterResolve: "openrouter/moonshotai/kimi-k2.6",
|
|
108118
108157
|
preferred: true
|
|
108119
108158
|
}
|
|
108120
108159
|
}
|
|
@@ -108133,7 +108172,7 @@ var providers = {
|
|
|
108133
108172
|
"claude-opus": {
|
|
108134
108173
|
displayName: "Claude Opus",
|
|
108135
108174
|
resolve: "opencode/claude-opus-4-7",
|
|
108136
|
-
openRouterResolve: "openrouter/anthropic/claude-opus-4.
|
|
108175
|
+
openRouterResolve: "openrouter/anthropic/claude-opus-4.7"
|
|
108137
108176
|
},
|
|
108138
108177
|
"claude-sonnet": {
|
|
108139
108178
|
displayName: "Claude Sonnet",
|
|
@@ -108145,15 +108184,33 @@ var providers = {
|
|
|
108145
108184
|
resolve: "opencode/claude-haiku-4-5",
|
|
108146
108185
|
openRouterResolve: "openrouter/anthropic/claude-haiku-4.5"
|
|
108147
108186
|
},
|
|
108187
|
+
gpt: {
|
|
108188
|
+
displayName: "GPT",
|
|
108189
|
+
resolve: "opencode/gpt-5.5",
|
|
108190
|
+
openRouterResolve: "openrouter/openai/gpt-5.5"
|
|
108191
|
+
},
|
|
108192
|
+
"gpt-pro": {
|
|
108193
|
+
displayName: "GPT Pro",
|
|
108194
|
+
resolve: "opencode/gpt-5.5-pro",
|
|
108195
|
+
openRouterResolve: "openrouter/openai/gpt-5.5-pro"
|
|
108196
|
+
},
|
|
108197
|
+
"gpt-mini": {
|
|
108198
|
+
displayName: "GPT Mini",
|
|
108199
|
+
resolve: "opencode/gpt-5.4-mini",
|
|
108200
|
+
openRouterResolve: "openrouter/openai/gpt-5.4-mini"
|
|
108201
|
+
},
|
|
108202
|
+
// legacy aliases — see openai provider above for context.
|
|
108148
108203
|
"gpt-codex": {
|
|
108149
108204
|
displayName: "GPT Codex",
|
|
108150
108205
|
resolve: "opencode/gpt-5.3-codex",
|
|
108151
|
-
openRouterResolve: "openrouter/openai/gpt-5.3-codex"
|
|
108206
|
+
openRouterResolve: "openrouter/openai/gpt-5.3-codex",
|
|
108207
|
+
fallback: "opencode/gpt"
|
|
108152
108208
|
},
|
|
108153
108209
|
"gpt-codex-mini": {
|
|
108154
108210
|
displayName: "GPT Codex Mini",
|
|
108155
108211
|
resolve: "opencode/gpt-5.1-codex-mini",
|
|
108156
|
-
openRouterResolve: "openrouter/openai/gpt-5.1-codex-mini"
|
|
108212
|
+
openRouterResolve: "openrouter/openai/gpt-5.1-codex-mini",
|
|
108213
|
+
fallback: "opencode/gpt-mini"
|
|
108157
108214
|
},
|
|
108158
108215
|
"gemini-pro": {
|
|
108159
108216
|
displayName: "Gemini Pro",
|
|
@@ -108167,8 +108224,8 @@ var providers = {
|
|
|
108167
108224
|
},
|
|
108168
108225
|
"kimi-k2": {
|
|
108169
108226
|
displayName: "Kimi K2",
|
|
108170
|
-
resolve: "opencode/kimi-k2.
|
|
108171
|
-
openRouterResolve: "openrouter/moonshotai/kimi-k2.
|
|
108227
|
+
resolve: "opencode/kimi-k2.6",
|
|
108228
|
+
openRouterResolve: "openrouter/moonshotai/kimi-k2.6"
|
|
108172
108229
|
},
|
|
108173
108230
|
"gpt-5-nano": {
|
|
108174
108231
|
displayName: "GPT Nano",
|
|
@@ -108188,12 +108245,6 @@ var providers = {
|
|
|
108188
108245
|
resolve: "opencode/minimax-m2.5-free",
|
|
108189
108246
|
envVars: [],
|
|
108190
108247
|
isFree: true
|
|
108191
|
-
},
|
|
108192
|
-
"nemotron-3-super-free": {
|
|
108193
|
-
displayName: "Nemotron 3 Super",
|
|
108194
|
-
resolve: "opencode/nemotron-3-super-free",
|
|
108195
|
-
envVars: [],
|
|
108196
|
-
isFree: true
|
|
108197
108248
|
}
|
|
108198
108249
|
}
|
|
108199
108250
|
}),
|
|
@@ -108203,8 +108254,8 @@ var providers = {
|
|
|
108203
108254
|
models: {
|
|
108204
108255
|
"claude-opus": {
|
|
108205
108256
|
displayName: "Claude Opus",
|
|
108206
|
-
resolve: "openrouter/anthropic/claude-opus-4.
|
|
108207
|
-
openRouterResolve: "openrouter/anthropic/claude-opus-4.
|
|
108257
|
+
resolve: "openrouter/anthropic/claude-opus-4.7",
|
|
108258
|
+
openRouterResolve: "openrouter/anthropic/claude-opus-4.7",
|
|
108208
108259
|
preferred: true
|
|
108209
108260
|
},
|
|
108210
108261
|
"claude-sonnet": {
|
|
@@ -108217,15 +108268,33 @@ var providers = {
|
|
|
108217
108268
|
resolve: "openrouter/anthropic/claude-haiku-4.5",
|
|
108218
108269
|
openRouterResolve: "openrouter/anthropic/claude-haiku-4.5"
|
|
108219
108270
|
},
|
|
108271
|
+
gpt: {
|
|
108272
|
+
displayName: "GPT",
|
|
108273
|
+
resolve: "openrouter/openai/gpt-5.5",
|
|
108274
|
+
openRouterResolve: "openrouter/openai/gpt-5.5"
|
|
108275
|
+
},
|
|
108276
|
+
"gpt-pro": {
|
|
108277
|
+
displayName: "GPT Pro",
|
|
108278
|
+
resolve: "openrouter/openai/gpt-5.5-pro",
|
|
108279
|
+
openRouterResolve: "openrouter/openai/gpt-5.5-pro"
|
|
108280
|
+
},
|
|
108281
|
+
"gpt-mini": {
|
|
108282
|
+
displayName: "GPT Mini",
|
|
108283
|
+
resolve: "openrouter/openai/gpt-5.4-mini",
|
|
108284
|
+
openRouterResolve: "openrouter/openai/gpt-5.4-mini"
|
|
108285
|
+
},
|
|
108286
|
+
// legacy aliases — see openai provider for context.
|
|
108220
108287
|
"gpt-codex": {
|
|
108221
108288
|
displayName: "GPT Codex",
|
|
108222
108289
|
resolve: "openrouter/openai/gpt-5.3-codex",
|
|
108223
|
-
openRouterResolve: "openrouter/openai/gpt-5.3-codex"
|
|
108290
|
+
openRouterResolve: "openrouter/openai/gpt-5.3-codex",
|
|
108291
|
+
fallback: "openrouter/gpt"
|
|
108224
108292
|
},
|
|
108225
108293
|
"gpt-codex-mini": {
|
|
108226
108294
|
displayName: "GPT Codex Mini",
|
|
108227
108295
|
resolve: "openrouter/openai/gpt-5.1-codex-mini",
|
|
108228
|
-
openRouterResolve: "openrouter/openai/gpt-5.1-codex-mini"
|
|
108296
|
+
openRouterResolve: "openrouter/openai/gpt-5.1-codex-mini",
|
|
108297
|
+
fallback: "openrouter/gpt-mini"
|
|
108229
108298
|
},
|
|
108230
108299
|
"o4-mini": {
|
|
108231
108300
|
displayName: "O4 Mini",
|
|
@@ -108244,34 +108313,47 @@ var providers = {
|
|
|
108244
108313
|
},
|
|
108245
108314
|
grok: {
|
|
108246
108315
|
displayName: "Grok",
|
|
108247
|
-
resolve: "openrouter/x-ai/grok-4",
|
|
108248
|
-
openRouterResolve: "openrouter/x-ai/grok-4"
|
|
108316
|
+
resolve: "openrouter/x-ai/grok-4.3",
|
|
108317
|
+
openRouterResolve: "openrouter/x-ai/grok-4.3"
|
|
108318
|
+
},
|
|
108319
|
+
"deepseek-pro": {
|
|
108320
|
+
displayName: "DeepSeek Pro",
|
|
108321
|
+
resolve: "openrouter/deepseek/deepseek-v4-pro",
|
|
108322
|
+
openRouterResolve: "openrouter/deepseek/deepseek-v4-pro"
|
|
108323
|
+
},
|
|
108324
|
+
"deepseek-flash": {
|
|
108325
|
+
displayName: "DeepSeek Flash",
|
|
108326
|
+
resolve: "openrouter/deepseek/deepseek-v4-flash",
|
|
108327
|
+
openRouterResolve: "openrouter/deepseek/deepseek-v4-flash"
|
|
108249
108328
|
},
|
|
108329
|
+
// legacy alias — deepseek retires this on 2026-07-24; transparently
|
|
108330
|
+
// upgrade existing users to the v4 family via the fallback chain.
|
|
108250
108331
|
"deepseek-chat": {
|
|
108251
108332
|
displayName: "DeepSeek Chat",
|
|
108252
108333
|
resolve: "openrouter/deepseek/deepseek-v3.2",
|
|
108253
|
-
openRouterResolve: "openrouter/deepseek/deepseek-v3.2"
|
|
108334
|
+
openRouterResolve: "openrouter/deepseek/deepseek-v3.2",
|
|
108335
|
+
fallback: "openrouter/deepseek-flash"
|
|
108254
108336
|
},
|
|
108255
108337
|
"kimi-k2": {
|
|
108256
108338
|
displayName: "Kimi K2",
|
|
108257
|
-
resolve: "openrouter/moonshotai/kimi-k2.
|
|
108258
|
-
openRouterResolve: "openrouter/moonshotai/kimi-k2.
|
|
108339
|
+
resolve: "openrouter/moonshotai/kimi-k2.6",
|
|
108340
|
+
openRouterResolve: "openrouter/moonshotai/kimi-k2.6"
|
|
108259
108341
|
}
|
|
108260
108342
|
}
|
|
108261
108343
|
})
|
|
108262
108344
|
};
|
|
108263
|
-
function parseModel(
|
|
108264
|
-
const slashIdx =
|
|
108345
|
+
function parseModel(slug2) {
|
|
108346
|
+
const slashIdx = slug2.indexOf("/");
|
|
108265
108347
|
if (slashIdx === -1) {
|
|
108266
|
-
throw new Error(`invalid model slug "${
|
|
108348
|
+
throw new Error(`invalid model slug "${slug2}" \u2014 expected "provider/model"`);
|
|
108267
108349
|
}
|
|
108268
|
-
return { provider:
|
|
108350
|
+
return { provider: slug2.slice(0, slashIdx), model: slug2.slice(slashIdx + 1) };
|
|
108269
108351
|
}
|
|
108270
|
-
function getModelProvider(
|
|
108271
|
-
return parseModel(
|
|
108352
|
+
function getModelProvider(slug2) {
|
|
108353
|
+
return parseModel(slug2).provider;
|
|
108272
108354
|
}
|
|
108273
|
-
function getModelEnvVars(
|
|
108274
|
-
const parsed2 = parseModel(
|
|
108355
|
+
function getModelEnvVars(slug2) {
|
|
108356
|
+
const parsed2 = parseModel(slug2);
|
|
108275
108357
|
const providerConfig = providers[parsed2.provider];
|
|
108276
108358
|
if (!providerConfig) {
|
|
108277
108359
|
return [];
|
|
@@ -108295,26 +108377,29 @@ var modelAliases = Object.entries(providers).flatMap(
|
|
|
108295
108377
|
}))
|
|
108296
108378
|
);
|
|
108297
108379
|
var MAX_FALLBACK_DEPTH = 10;
|
|
108298
|
-
function
|
|
108299
|
-
let current =
|
|
108380
|
+
function resolveDisplayAlias(slug2) {
|
|
108381
|
+
let current = slug2;
|
|
108300
108382
|
const visited = /* @__PURE__ */ new Set();
|
|
108301
108383
|
for (let i = 0; i < MAX_FALLBACK_DEPTH; i++) {
|
|
108302
108384
|
if (visited.has(current)) return void 0;
|
|
108303
108385
|
visited.add(current);
|
|
108304
108386
|
const alias = modelAliases.find((a) => a.slug === current);
|
|
108305
108387
|
if (!alias) return void 0;
|
|
108306
|
-
if (!alias.fallback) return alias
|
|
108388
|
+
if (!alias.fallback) return alias;
|
|
108307
108389
|
current = alias.fallback;
|
|
108308
108390
|
}
|
|
108309
108391
|
return void 0;
|
|
108310
108392
|
}
|
|
108393
|
+
function resolveCliModel(slug2) {
|
|
108394
|
+
return resolveDisplayAlias(slug2)?.resolve;
|
|
108395
|
+
}
|
|
108311
108396
|
|
|
108312
108397
|
// utils/buildPullfrogFooter.ts
|
|
108313
108398
|
var PULLFROG_DIVIDER = "<!-- PULLFROG_DIVIDER_DO_NOT_REMOVE_PLZ -->";
|
|
108314
108399
|
var FROG_LOGO = `<a href="https://pullfrog.com"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pullfrog.com/logos/frog-white-full-18px.png"><img src="https://pullfrog.com/logos/frog-green-full-18px.png" width="9px" height="9px" style="vertical-align: middle; " alt="Pullfrog"></picture></a>`;
|
|
108315
|
-
function formatModelLabel(
|
|
108316
|
-
const alias =
|
|
108317
|
-
if (!alias) return `\`${
|
|
108400
|
+
function formatModelLabel(slug2) {
|
|
108401
|
+
const alias = resolveDisplayAlias(slug2);
|
|
108402
|
+
if (!alias) return `\`${slug2}\``;
|
|
108318
108403
|
return alias.isFree ? `\`${alias.displayName}\` (free)` : `\`${alias.displayName}\``;
|
|
108319
108404
|
}
|
|
108320
108405
|
function buildPullfrogFooter(params) {
|
|
@@ -108498,6 +108583,109 @@ function aggregateUsage(entries) {
|
|
|
108498
108583
|
return out;
|
|
108499
108584
|
}
|
|
108500
108585
|
|
|
108586
|
+
// utils/progressComment.ts
|
|
108587
|
+
function parseProgressComment(raw2) {
|
|
108588
|
+
if (!raw2?.id) return void 0;
|
|
108589
|
+
const id = parseInt(raw2.id, 10);
|
|
108590
|
+
if (Number.isNaN(id) || id <= 0) return void 0;
|
|
108591
|
+
return { id, type: raw2.type };
|
|
108592
|
+
}
|
|
108593
|
+
async function getProgressComment(ctx, comment) {
|
|
108594
|
+
const result = await (comment.type === "review" ? ctx.octokit.rest.pulls.getReviewComment({
|
|
108595
|
+
owner: ctx.owner,
|
|
108596
|
+
repo: ctx.repo,
|
|
108597
|
+
comment_id: comment.id
|
|
108598
|
+
}) : ctx.octokit.rest.issues.getComment({
|
|
108599
|
+
owner: ctx.owner,
|
|
108600
|
+
repo: ctx.repo,
|
|
108601
|
+
comment_id: comment.id
|
|
108602
|
+
}));
|
|
108603
|
+
return {
|
|
108604
|
+
id: result.data.id,
|
|
108605
|
+
body: result.data.body ?? void 0,
|
|
108606
|
+
html_url: result.data.html_url
|
|
108607
|
+
};
|
|
108608
|
+
}
|
|
108609
|
+
async function updateProgressComment(ctx, comment, body) {
|
|
108610
|
+
const result = await (comment.type === "review" ? ctx.octokit.rest.pulls.updateReviewComment({
|
|
108611
|
+
owner: ctx.owner,
|
|
108612
|
+
repo: ctx.repo,
|
|
108613
|
+
comment_id: comment.id,
|
|
108614
|
+
body
|
|
108615
|
+
}) : ctx.octokit.rest.issues.updateComment({
|
|
108616
|
+
owner: ctx.owner,
|
|
108617
|
+
repo: ctx.repo,
|
|
108618
|
+
comment_id: comment.id,
|
|
108619
|
+
body
|
|
108620
|
+
}));
|
|
108621
|
+
return {
|
|
108622
|
+
id: result.data.id,
|
|
108623
|
+
body: result.data.body ?? void 0,
|
|
108624
|
+
html_url: result.data.html_url,
|
|
108625
|
+
node_id: result.data.node_id
|
|
108626
|
+
};
|
|
108627
|
+
}
|
|
108628
|
+
async function deleteProgressCommentApi(ctx, comment) {
|
|
108629
|
+
if (comment.type === "review") {
|
|
108630
|
+
await ctx.octokit.rest.pulls.deleteReviewComment({
|
|
108631
|
+
owner: ctx.owner,
|
|
108632
|
+
repo: ctx.repo,
|
|
108633
|
+
comment_id: comment.id
|
|
108634
|
+
});
|
|
108635
|
+
return;
|
|
108636
|
+
}
|
|
108637
|
+
await ctx.octokit.rest.issues.deleteComment({
|
|
108638
|
+
owner: ctx.owner,
|
|
108639
|
+
repo: ctx.repo,
|
|
108640
|
+
comment_id: comment.id
|
|
108641
|
+
});
|
|
108642
|
+
}
|
|
108643
|
+
async function createLeapingProgressComment(ctx, target, body) {
|
|
108644
|
+
if (target.kind === "reviewReply") {
|
|
108645
|
+
try {
|
|
108646
|
+
const result2 = await ctx.octokit.rest.pulls.createReplyForReviewComment({
|
|
108647
|
+
owner: ctx.owner,
|
|
108648
|
+
repo: ctx.repo,
|
|
108649
|
+
pull_number: target.pullNumber,
|
|
108650
|
+
comment_id: target.replyToCommentId,
|
|
108651
|
+
body
|
|
108652
|
+
});
|
|
108653
|
+
return {
|
|
108654
|
+
comment: { id: result2.data.id, type: "review" },
|
|
108655
|
+
body: result2.data.body ?? void 0,
|
|
108656
|
+
html_url: result2.data.html_url
|
|
108657
|
+
};
|
|
108658
|
+
} catch (error49) {
|
|
108659
|
+
console.warn(
|
|
108660
|
+
`[progressComment] review reply failed (parent ${target.replyToCommentId} on PR #${target.pullNumber}), falling back to issue comment:`,
|
|
108661
|
+
error49
|
|
108662
|
+
);
|
|
108663
|
+
const fallback = await ctx.octokit.rest.issues.createComment({
|
|
108664
|
+
owner: ctx.owner,
|
|
108665
|
+
repo: ctx.repo,
|
|
108666
|
+
issue_number: target.pullNumber,
|
|
108667
|
+
body
|
|
108668
|
+
});
|
|
108669
|
+
return {
|
|
108670
|
+
comment: { id: fallback.data.id, type: "issue" },
|
|
108671
|
+
body: fallback.data.body ?? void 0,
|
|
108672
|
+
html_url: fallback.data.html_url
|
|
108673
|
+
};
|
|
108674
|
+
}
|
|
108675
|
+
}
|
|
108676
|
+
const result = await ctx.octokit.rest.issues.createComment({
|
|
108677
|
+
owner: ctx.owner,
|
|
108678
|
+
repo: ctx.repo,
|
|
108679
|
+
issue_number: target.issueNumber,
|
|
108680
|
+
body
|
|
108681
|
+
});
|
|
108682
|
+
return {
|
|
108683
|
+
comment: { id: result.data.id, type: "issue" },
|
|
108684
|
+
body: result.data.body ?? void 0,
|
|
108685
|
+
html_url: result.data.html_url
|
|
108686
|
+
};
|
|
108687
|
+
}
|
|
108688
|
+
|
|
108501
108689
|
// node_modules/.pnpm/@toon-format+toon@1.4.0/node_modules/@toon-format/toon/dist/index.mjs
|
|
108502
108690
|
var LIST_ITEM_MARKER = "-";
|
|
108503
108691
|
var LIST_ITEM_PREFIX = "- ";
|
|
@@ -109164,6 +109352,7 @@ async function reportProgress(ctx, params) {
|
|
|
109164
109352
|
}
|
|
109165
109353
|
const issueNumber = ctx.payload.event.issue_number ?? ctx.toolState.issueNumber;
|
|
109166
109354
|
const isPlanMode = ctx.toolState.selectedMode === "Plan";
|
|
109355
|
+
const apiCtx = { octokit: ctx.octokit, owner: ctx.repo.owner, repo: ctx.repo.name };
|
|
109167
109356
|
if (target_plan_comment === true && ctx.toolState.existingPlanCommentId === void 0) {
|
|
109168
109357
|
log.warning("target_plan_comment requested but no existingPlanCommentId in tool state");
|
|
109169
109358
|
}
|
|
@@ -109173,86 +109362,74 @@ async function reportProgress(ctx, params) {
|
|
|
109173
109362
|
const bodyWithoutFooter = stripExistingFooter(body);
|
|
109174
109363
|
const footer = buildCommentFooter(ctx, customParts);
|
|
109175
109364
|
const bodyWithFooter = `${bodyWithoutFooter}${footer}`;
|
|
109176
|
-
const
|
|
109177
|
-
|
|
109178
|
-
|
|
109179
|
-
|
|
109180
|
-
|
|
109181
|
-
});
|
|
109365
|
+
const result = await updateProgressComment(
|
|
109366
|
+
apiCtx,
|
|
109367
|
+
{ id: commentId, type: "issue" },
|
|
109368
|
+
bodyWithFooter
|
|
109369
|
+
);
|
|
109182
109370
|
ctx.toolState.wasUpdated = true;
|
|
109183
|
-
if (isPlanMode &&
|
|
109184
|
-
await patchWorkflowRunFields(ctx, { planCommentNodeId:
|
|
109371
|
+
if (isPlanMode && result.node_id) {
|
|
109372
|
+
await patchWorkflowRunFields(ctx, { planCommentNodeId: result.node_id });
|
|
109185
109373
|
}
|
|
109186
109374
|
return {
|
|
109187
|
-
commentId:
|
|
109188
|
-
url:
|
|
109189
|
-
body:
|
|
109375
|
+
commentId: result.id,
|
|
109376
|
+
url: result.html_url,
|
|
109377
|
+
body: result.body || "",
|
|
109190
109378
|
action: "updated"
|
|
109191
109379
|
};
|
|
109192
109380
|
}
|
|
109193
|
-
const
|
|
109194
|
-
if (
|
|
109195
|
-
const customParts = isPlanMode && issueNumber !== void 0 ? [buildImplementPlanLink(ctx, issueNumber,
|
|
109381
|
+
const existingComment = ctx.toolState.progressComment;
|
|
109382
|
+
if (existingComment) {
|
|
109383
|
+
const customParts = isPlanMode && issueNumber !== void 0 ? [buildImplementPlanLink(ctx, issueNumber, existingComment.id)] : void 0;
|
|
109196
109384
|
const bodyWithoutFooter = stripExistingFooter(body);
|
|
109197
109385
|
const footer = buildCommentFooter(ctx, customParts);
|
|
109198
109386
|
const bodyWithFooter = `${bodyWithoutFooter}${footer}`;
|
|
109199
|
-
const
|
|
109200
|
-
owner: ctx.repo.owner,
|
|
109201
|
-
repo: ctx.repo.name,
|
|
109202
|
-
comment_id: existingCommentId,
|
|
109203
|
-
body: bodyWithFooter
|
|
109204
|
-
});
|
|
109387
|
+
const result = await updateProgressComment(apiCtx, existingComment, bodyWithFooter);
|
|
109205
109388
|
ctx.toolState.wasUpdated = true;
|
|
109206
|
-
if (isPlanMode &&
|
|
109207
|
-
await patchWorkflowRunFields(ctx, { planCommentNodeId:
|
|
109389
|
+
if (isPlanMode && result.node_id) {
|
|
109390
|
+
await patchWorkflowRunFields(ctx, { planCommentNodeId: result.node_id });
|
|
109208
109391
|
}
|
|
109209
109392
|
return {
|
|
109210
|
-
commentId:
|
|
109211
|
-
url:
|
|
109212
|
-
body:
|
|
109393
|
+
commentId: result.id,
|
|
109394
|
+
url: result.html_url,
|
|
109395
|
+
body: result.body || "",
|
|
109213
109396
|
action: "updated"
|
|
109214
109397
|
};
|
|
109215
109398
|
}
|
|
109216
|
-
if (
|
|
109399
|
+
if (existingComment === null) {
|
|
109217
109400
|
return { body, action: "skipped" };
|
|
109218
109401
|
}
|
|
109219
109402
|
if (issueNumber === void 0) {
|
|
109220
109403
|
return { body, action: "skipped" };
|
|
109221
109404
|
}
|
|
109222
109405
|
const initialBody = addFooter(ctx, body);
|
|
109223
|
-
const
|
|
109224
|
-
|
|
109225
|
-
|
|
109226
|
-
|
|
109227
|
-
|
|
109228
|
-
|
|
109229
|
-
ctx.toolState.progressCommentId = result.data.id;
|
|
109406
|
+
const created = await createLeapingProgressComment(
|
|
109407
|
+
apiCtx,
|
|
109408
|
+
{ kind: "issue", issueNumber },
|
|
109409
|
+
initialBody
|
|
109410
|
+
);
|
|
109411
|
+
ctx.toolState.progressComment = created.comment;
|
|
109230
109412
|
ctx.toolState.wasUpdated = true;
|
|
109231
109413
|
if (isPlanMode) {
|
|
109232
|
-
const customParts = [buildImplementPlanLink(ctx, issueNumber,
|
|
109414
|
+
const customParts = [buildImplementPlanLink(ctx, issueNumber, created.comment.id)];
|
|
109233
109415
|
const bodyWithoutFooter = stripExistingFooter(body);
|
|
109234
109416
|
const footer = buildCommentFooter(ctx, customParts);
|
|
109235
109417
|
const bodyWithPlanLink = `${bodyWithoutFooter}${footer}`;
|
|
109236
|
-
const updateResult = await
|
|
109237
|
-
|
|
109238
|
-
|
|
109239
|
-
comment_id: result.data.id,
|
|
109240
|
-
body: bodyWithPlanLink
|
|
109241
|
-
});
|
|
109242
|
-
if (updateResult.data.node_id) {
|
|
109243
|
-
await patchWorkflowRunFields(ctx, { planCommentNodeId: updateResult.data.node_id });
|
|
109418
|
+
const updateResult = await updateProgressComment(apiCtx, created.comment, bodyWithPlanLink);
|
|
109419
|
+
if (updateResult.node_id) {
|
|
109420
|
+
await patchWorkflowRunFields(ctx, { planCommentNodeId: updateResult.node_id });
|
|
109244
109421
|
}
|
|
109245
109422
|
return {
|
|
109246
|
-
commentId: updateResult.
|
|
109247
|
-
url: updateResult.
|
|
109248
|
-
body: updateResult.
|
|
109423
|
+
commentId: updateResult.id,
|
|
109424
|
+
url: updateResult.html_url,
|
|
109425
|
+
body: updateResult.body || "",
|
|
109249
109426
|
action: "created"
|
|
109250
109427
|
};
|
|
109251
109428
|
}
|
|
109252
109429
|
return {
|
|
109253
|
-
commentId:
|
|
109254
|
-
url:
|
|
109255
|
-
body:
|
|
109430
|
+
commentId: created.comment.id,
|
|
109431
|
+
url: created.html_url,
|
|
109432
|
+
body: created.body || "",
|
|
109256
109433
|
action: "created"
|
|
109257
109434
|
};
|
|
109258
109435
|
}
|
|
@@ -109297,23 +109474,22 @@ ${collapsible}`;
|
|
|
109297
109474
|
});
|
|
109298
109475
|
}
|
|
109299
109476
|
async function deleteProgressComment(ctx) {
|
|
109300
|
-
const
|
|
109301
|
-
if (!
|
|
109477
|
+
const existing = ctx.toolState.progressComment;
|
|
109478
|
+
if (!existing) {
|
|
109302
109479
|
return false;
|
|
109303
109480
|
}
|
|
109304
109481
|
try {
|
|
109305
|
-
await
|
|
109306
|
-
owner: ctx.repo.owner,
|
|
109307
|
-
|
|
109308
|
-
|
|
109309
|
-
});
|
|
109482
|
+
await deleteProgressCommentApi(
|
|
109483
|
+
{ octokit: ctx.octokit, owner: ctx.repo.owner, repo: ctx.repo.name },
|
|
109484
|
+
existing
|
|
109485
|
+
);
|
|
109310
109486
|
} catch (error49) {
|
|
109311
109487
|
if (error49 instanceof Error && error49.message.includes("Not Found")) {
|
|
109312
109488
|
} else {
|
|
109313
109489
|
throw error49;
|
|
109314
109490
|
}
|
|
109315
109491
|
}
|
|
109316
|
-
ctx.toolState.
|
|
109492
|
+
ctx.toolState.progressComment = null;
|
|
109317
109493
|
return true;
|
|
109318
109494
|
}
|
|
109319
109495
|
var ReplyToReviewComment = type({
|
|
@@ -142394,7 +142570,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
142394
142570
|
// package.json
|
|
142395
142571
|
var package_default = {
|
|
142396
142572
|
name: "pullfrog",
|
|
142397
|
-
version: "0.0.
|
|
142573
|
+
version: "0.0.204",
|
|
142398
142574
|
type: "module",
|
|
142399
142575
|
bin: {
|
|
142400
142576
|
pullfrog: "dist/cli.mjs",
|
|
@@ -142931,8 +143107,13 @@ async function $git(subcommand, args2, options) {
|
|
|
142931
143107
|
}
|
|
142932
143108
|
if (result.exitCode !== 0) {
|
|
142933
143109
|
const stderr = result.stderr.trim();
|
|
142934
|
-
|
|
142935
|
-
|
|
143110
|
+
const stdout = result.stdout.trim();
|
|
143111
|
+
const detail = stderr && stdout ? `${stderr}
|
|
143112
|
+
--- stdout ---
|
|
143113
|
+
${stdout}` : stderr || stdout || "(no output)";
|
|
143114
|
+
const message = `git ${subcommand} failed (exit ${result.exitCode}): ${detail}`;
|
|
143115
|
+
log.info(message);
|
|
143116
|
+
throw new Error(message);
|
|
142936
143117
|
}
|
|
142937
143118
|
return {
|
|
142938
143119
|
stdout: result.stdout.trim(),
|
|
@@ -143209,6 +143390,34 @@ var PushBranch = type({
|
|
|
143209
143390
|
branchName: type.string.describe("The branch name to push (defaults to current branch)").optional(),
|
|
143210
143391
|
force: type.boolean.describe("Force push (use with caution)").default(false)
|
|
143211
143392
|
});
|
|
143393
|
+
var CONCURRENT_PUSH_PATTERNS = ["fetch first", "non-fast-forward", "cannot lock ref"];
|
|
143394
|
+
var TRANSIENT_PATTERNS = [
|
|
143395
|
+
/RPC failed/i,
|
|
143396
|
+
/early EOF/,
|
|
143397
|
+
/the remote end hung up unexpectedly/,
|
|
143398
|
+
/Connection reset/i,
|
|
143399
|
+
/Could not resolve host/i,
|
|
143400
|
+
/Operation timed out/i,
|
|
143401
|
+
/HTTP\/2 stream \d+ was not closed cleanly/i,
|
|
143402
|
+
/unexpected disconnect while reading sideband packet/i,
|
|
143403
|
+
// libcurl HTTP 5xx surfaced by git over https. matches both the
|
|
143404
|
+
// libcurl-style "The requested URL returned error: 502" and the more
|
|
143405
|
+
// recent "HTTP 502" wording. most 4xx is intentionally excluded —
|
|
143406
|
+
// 401/403/404 indicate auth/permission problems that are not
|
|
143407
|
+
// retry-safe — but 429 (rate-limited / abuse detection) IS retry-safe
|
|
143408
|
+
// and GitHub occasionally surfaces it on git push, so it's included
|
|
143409
|
+
// explicitly below.
|
|
143410
|
+
/HTTP 5\d\d/,
|
|
143411
|
+
/returned error: 5\d\d/i,
|
|
143412
|
+
/HTTP 429/,
|
|
143413
|
+
/returned error: 429/i
|
|
143414
|
+
];
|
|
143415
|
+
function classifyPushError(msg) {
|
|
143416
|
+
if (CONCURRENT_PUSH_PATTERNS.some((p2) => msg.includes(p2))) return "concurrent-push";
|
|
143417
|
+
if (TRANSIENT_PATTERNS.some((p2) => p2.test(msg))) return "transient";
|
|
143418
|
+
return "unknown";
|
|
143419
|
+
}
|
|
143420
|
+
var TRANSIENT_RETRY_DELAYS_MS = [2e3, 5e3];
|
|
143212
143421
|
function PushBranchTool(ctx) {
|
|
143213
143422
|
const defaultBranch = ctx.repo.data.default_branch || "main";
|
|
143214
143423
|
const pushPermission = ctx.payload.push;
|
|
@@ -143259,25 +143468,48 @@ ${postHookStatus}`
|
|
|
143259
143468
|
if (force) {
|
|
143260
143469
|
log.warning(`force pushing - this will overwrite remote history`);
|
|
143261
143470
|
}
|
|
143262
|
-
|
|
143263
|
-
|
|
143264
|
-
|
|
143265
|
-
|
|
143266
|
-
|
|
143267
|
-
|
|
143268
|
-
|
|
143269
|
-
|
|
143270
|
-
|
|
143271
|
-
|
|
143471
|
+
let lastErr;
|
|
143472
|
+
let pushed = false;
|
|
143473
|
+
for (let attempt = 0; attempt <= TRANSIENT_RETRY_DELAYS_MS.length; attempt++) {
|
|
143474
|
+
try {
|
|
143475
|
+
await $git("push", pushArgs, {
|
|
143476
|
+
token: ctx.gitToken
|
|
143477
|
+
});
|
|
143478
|
+
if (attempt > 0) {
|
|
143479
|
+
log.info(`push succeeded on attempt ${attempt + 1}`);
|
|
143480
|
+
}
|
|
143481
|
+
pushed = true;
|
|
143482
|
+
break;
|
|
143483
|
+
} catch (err) {
|
|
143484
|
+
lastErr = err;
|
|
143485
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
143486
|
+
const kind = classifyPushError(msg);
|
|
143487
|
+
if (kind === "concurrent-push") {
|
|
143488
|
+
const integrateStep = ctx.payload.shell === "disabled" ? `2. use the git tool to merge the remote branch into yours: git({ command: "merge", args: ["origin/${pushDest.remoteBranch}"] })` : `2. use the git tool to rebase or merge your changes on top: git({ command: "merge", args: ["origin/${pushDest.remoteBranch}"] }) (or 'rebase')`;
|
|
143489
|
+
throw new Error(
|
|
143490
|
+
`push rejected: the remote branch '${pushDest.remoteBranch}' has new commits you don't have locally (often a concurrent push to the same branch).
|
|
143272
143491
|
|
|
143273
143492
|
to resolve this:
|
|
143274
143493
|
1. use git_fetch to fetch the remote branch: git_fetch({ ref: "${pushDest.remoteBranch}" })
|
|
143275
143494
|
${integrateStep}
|
|
143276
143495
|
3. resolve any merge conflicts if needed
|
|
143277
143496
|
4. retry push_branch`
|
|
143278
|
-
|
|
143497
|
+
);
|
|
143498
|
+
}
|
|
143499
|
+
if (kind === "transient" && attempt < TRANSIENT_RETRY_DELAYS_MS.length) {
|
|
143500
|
+
const baseDelay = TRANSIENT_RETRY_DELAYS_MS[attempt] ?? 5e3;
|
|
143501
|
+
const delay2 = Math.round(baseDelay * (0.75 + Math.random() * 0.5));
|
|
143502
|
+
log.info(
|
|
143503
|
+
`push attempt ${attempt + 1} failed (transient), retrying in ${delay2}ms: ${msg.slice(0, 300)}`
|
|
143504
|
+
);
|
|
143505
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
143506
|
+
continue;
|
|
143507
|
+
}
|
|
143508
|
+
throw err;
|
|
143279
143509
|
}
|
|
143280
|
-
|
|
143510
|
+
}
|
|
143511
|
+
if (!pushed) {
|
|
143512
|
+
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
143281
143513
|
}
|
|
143282
143514
|
return {
|
|
143283
143515
|
success: true,
|
|
@@ -143535,6 +143767,18 @@ function validateInlineComments(comments, map2) {
|
|
|
143535
143767
|
return { valid, dropped };
|
|
143536
143768
|
}
|
|
143537
143769
|
var MAX_DROPPED_COMMENT_LINES = 50;
|
|
143770
|
+
function duplicateReviewDecision(params) {
|
|
143771
|
+
const existing = params.existing;
|
|
143772
|
+
if (!existing) return null;
|
|
143773
|
+
if (params.currentCheckoutSha && existing.reviewedSha && params.currentCheckoutSha !== existing.reviewedSha) {
|
|
143774
|
+
return null;
|
|
143775
|
+
}
|
|
143776
|
+
return {
|
|
143777
|
+
kind: "already-submitted",
|
|
143778
|
+
reviewId: existing.id,
|
|
143779
|
+
reason: `review ${existing.id} was already submitted in this session; ignoring duplicate call (call \`checkout_pr\` again first if new commits were pushed)`
|
|
143780
|
+
};
|
|
143781
|
+
}
|
|
143538
143782
|
function reviewSkipDecision(params) {
|
|
143539
143783
|
if (params.body || params.hasComments) return null;
|
|
143540
143784
|
if (!params.approved) {
|
|
@@ -143604,6 +143848,19 @@ function CreatePullRequestReviewTool(ctx) {
|
|
|
143604
143848
|
execute: execute(async ({ pull_number, body, approved, commit_id, comments = [] }) => {
|
|
143605
143849
|
if (body) body = fixDoubleEscapedString(body);
|
|
143606
143850
|
ctx.toolState.issueNumber = pull_number;
|
|
143851
|
+
const dup = duplicateReviewDecision({
|
|
143852
|
+
existing: ctx.toolState.review,
|
|
143853
|
+
currentCheckoutSha: ctx.toolState.checkoutSha
|
|
143854
|
+
});
|
|
143855
|
+
if (dup) {
|
|
143856
|
+
log.info(`skipping duplicate review submission: ${dup.reason}`);
|
|
143857
|
+
return {
|
|
143858
|
+
success: true,
|
|
143859
|
+
skipped: true,
|
|
143860
|
+
reason: dup.reason,
|
|
143861
|
+
reviewId: dup.reviewId
|
|
143862
|
+
};
|
|
143863
|
+
}
|
|
143607
143864
|
const skip = reviewSkipDecision({
|
|
143608
143865
|
approved: approved ?? false,
|
|
143609
143866
|
body,
|
|
@@ -143721,6 +143978,9 @@ function CreatePullRequestReviewTool(ctx) {
|
|
|
143721
143978
|
nodeId: reviewNodeId,
|
|
143722
143979
|
reviewedSha: actuallyReviewedSha
|
|
143723
143980
|
};
|
|
143981
|
+
await deleteProgressComment(ctx).catch((err) => {
|
|
143982
|
+
log.debug(`progress comment cleanup after review failed: ${err}`);
|
|
143983
|
+
});
|
|
143724
143984
|
if (ctx.toolState.checkoutSha && latestHeadSha && latestHeadSha !== ctx.toolState.checkoutSha) {
|
|
143725
143985
|
const fromSha = ctx.toolState.checkoutSha;
|
|
143726
143986
|
const toSha = latestHeadSha;
|
|
@@ -145303,35 +145563,20 @@ async function getReviewThreads(input) {
|
|
|
145303
145563
|
const username = input.approvedBy;
|
|
145304
145564
|
return threadsForReview.filter((thread) => threadHasThumbsUpFrom(thread, username));
|
|
145305
145565
|
}
|
|
145306
|
-
|
|
145307
|
-
const
|
|
145308
|
-
input.octokit.rest.pulls.getReview({
|
|
145309
|
-
owner: input.owner,
|
|
145310
|
-
repo: input.name,
|
|
145311
|
-
pull_number: input.pullNumber,
|
|
145312
|
-
review_id: input.reviewId
|
|
145313
|
-
}),
|
|
145314
|
-
getReviewThreads(input)
|
|
145315
|
-
]);
|
|
145316
|
-
const rawReviewBody = review.data.body;
|
|
145566
|
+
function formatReviewData(input) {
|
|
145567
|
+
const rawReviewBody = input.review.body;
|
|
145317
145568
|
const reviewBody = rawReviewBody ? stripExistingFooter(rawReviewBody) : "";
|
|
145318
|
-
const reviewer = review.
|
|
145319
|
-
if (threads.length === 0 && !reviewBody) return void 0;
|
|
145569
|
+
const reviewer = input.review.user?.login ?? "unknown";
|
|
145570
|
+
if (input.threads.length === 0 && !reviewBody) return void 0;
|
|
145320
145571
|
let threadBlocks = [];
|
|
145321
|
-
if (threads.length > 0) {
|
|
145322
|
-
const prFiles = await input.octokit.paginate(input.octokit.rest.pulls.listFiles, {
|
|
145323
|
-
owner: input.owner,
|
|
145324
|
-
repo: input.name,
|
|
145325
|
-
pull_number: input.pullNumber,
|
|
145326
|
-
per_page: 100
|
|
145327
|
-
});
|
|
145572
|
+
if (input.threads.length > 0) {
|
|
145328
145573
|
const filePatchMap = /* @__PURE__ */ new Map();
|
|
145329
|
-
for (const file2 of prFiles) {
|
|
145574
|
+
for (const file2 of input.prFiles) {
|
|
145330
145575
|
if (file2.patch) {
|
|
145331
145576
|
filePatchMap.set(file2.filename, parseFilePatches(file2.patch));
|
|
145332
145577
|
}
|
|
145333
145578
|
}
|
|
145334
|
-
threadBlocks = buildThreadBlocks(threads, filePatchMap, input.reviewId);
|
|
145579
|
+
threadBlocks = buildThreadBlocks(input.threads, filePatchMap, input.reviewId);
|
|
145335
145580
|
}
|
|
145336
145581
|
const formatted = formatReviewThreads(threadBlocks, {
|
|
145337
145582
|
pullNumber: input.pullNumber,
|
|
@@ -145341,6 +145586,30 @@ async function getReviewData(input) {
|
|
|
145341
145586
|
});
|
|
145342
145587
|
return { threadBlocks, reviewer, formatted };
|
|
145343
145588
|
}
|
|
145589
|
+
async function getReviewData(input) {
|
|
145590
|
+
const [review, threads] = await Promise.all([
|
|
145591
|
+
input.octokit.rest.pulls.getReview({
|
|
145592
|
+
owner: input.owner,
|
|
145593
|
+
repo: input.name,
|
|
145594
|
+
pull_number: input.pullNumber,
|
|
145595
|
+
review_id: input.reviewId
|
|
145596
|
+
}),
|
|
145597
|
+
getReviewThreads(input)
|
|
145598
|
+
]);
|
|
145599
|
+
const prFiles = threads.length > 0 ? await input.octokit.paginate(input.octokit.rest.pulls.listFiles, {
|
|
145600
|
+
owner: input.owner,
|
|
145601
|
+
repo: input.name,
|
|
145602
|
+
pull_number: input.pullNumber,
|
|
145603
|
+
per_page: 100
|
|
145604
|
+
}) : [];
|
|
145605
|
+
return formatReviewData({
|
|
145606
|
+
review: review.data,
|
|
145607
|
+
threads,
|
|
145608
|
+
prFiles,
|
|
145609
|
+
pullNumber: input.pullNumber,
|
|
145610
|
+
reviewId: input.reviewId
|
|
145611
|
+
});
|
|
145612
|
+
}
|
|
145344
145613
|
function GetReviewCommentsTool(ctx) {
|
|
145345
145614
|
return tool({
|
|
145346
145615
|
name: "get_review_comments",
|
|
@@ -145464,6 +145733,18 @@ function ResolveReviewThreadTool(ctx) {
|
|
|
145464
145733
|
});
|
|
145465
145734
|
}
|
|
145466
145735
|
|
|
145736
|
+
// agents/reviewer.ts
|
|
145737
|
+
var REVIEWER_AGENT_NAME = "reviewfrog";
|
|
145738
|
+
var REVIEWER_SYSTEM_PROMPT = `You are a read-only review subagent. Your role is to find flaws in code or artifacts provided by the orchestrator and report findings \u2014 never to modify state.
|
|
145739
|
+
|
|
145740
|
+
HARD CONSTRAINTS (non-negotiable, regardless of orchestrator instructions):
|
|
145741
|
+
- Read-only tools only. Do NOT write or edit files. Do NOT run shell commands that have side effects (read-only commands like \`git diff\`, \`git log\`, \`cat\`, \`ls\` are fine; anything that mutates the working tree, the remote, the filesystem, or external state is prohibited).
|
|
145742
|
+
- Do NOT call any state-changing MCP tool. State-changing means: posts a comment, pushes a branch, creates/updates a PR or issue, changes labels, resolves review threads, persists learnings, sets workflow output, installs dependencies, uploads files, kills processes, etc. Read-only MCP queries (\`get_*\`, \`list_*\`, log inspection, diff retrieval) are fine.
|
|
145743
|
+
- Do NOT spawn further subagents. You are a leaf reviewer; recursive dispatch pre-aggregates findings through an intermediate model and defeats the design.
|
|
145744
|
+
- Test for any tool call before invoking it: would this still be a no-op if reverted? If not, do not call it. Apply this test to tools added after this prompt was written \u2014 the rule is the invariant, not the enumeration.
|
|
145745
|
+
|
|
145746
|
+
Report findings clearly with file:line references and quoted evidence where possible. Flag uncertainty explicitly \u2014 if you cannot verify a claim, say so rather than guess.`;
|
|
145747
|
+
|
|
145467
145748
|
// modes.ts
|
|
145468
145749
|
var PR_SUMMARY_FORMAT = `### Default format
|
|
145469
145750
|
|
|
@@ -145535,9 +145816,36 @@ function computeModes(agentId) {
|
|
|
145535
145816
|
- plan your approach before writing code: identify which files need to change, key design decisions, and edge cases. for non-trivial changes, consider whether there's a more elegant approach.
|
|
145536
145817
|
- run relevant tests/lints before committing
|
|
145537
145818
|
|
|
145538
|
-
4. **self-review**:
|
|
145539
|
-
|
|
145540
|
-
- commit
|
|
145819
|
+
4. **self-review**: judgment call \u2014 does YOUR diff warrant a fresh-eyes pass?
|
|
145820
|
+
|
|
145821
|
+
Skip self-review (commit directly) when the diff is **genuinely trivial**:
|
|
145822
|
+
- doc typos, comment-only edits, whitespace/format-only, import reordering
|
|
145823
|
+
- lockfile or generated-code regeneration, mechanical rename whose only effect is import-path updates (size of diff is irrelevant \u2014 read the *shape*, not the line count)
|
|
145824
|
+
- low-risk dep patch bump from a trusted source
|
|
145825
|
+
|
|
145826
|
+
Run self-review when the diff has **any behavioral surface, however small**:
|
|
145827
|
+
- 1-line changes to SQL operators / comparison logic / regexes / redirects / HTTP methods / response codes
|
|
145828
|
+
- any change to money / tax / currency / billing / fee / refund / payout calculations or constants
|
|
145829
|
+
- any change to auth / permissions / roles / sessions / tokens / signature verification
|
|
145830
|
+
- any change to feature-flag defaults, retry counts, timeouts, rate limits, batch sizes
|
|
145831
|
+
- new endpoints, new code paths, new error branches \u2014 even small ones
|
|
145832
|
+
- mixed diffs (whitespace + a single semantic line) \u2014 the semantic line still triggers self-review
|
|
145833
|
+
- anything you're uncertain about
|
|
145834
|
+
|
|
145835
|
+
Tie-breaker: when in doubt, run self-review. One false-positive subagent dispatch costs cents; one false-negative shipped bug costs much more. There's no value in dispatching for a typo, but there's also no excuse for skipping on a 1-line change to a billing path.
|
|
145836
|
+
|
|
145837
|
+
Otherwise delegate the \`${REVIEWER_AGENT_NAME}\` subagent to review your diff with fresh eyes against YOUR TASK. The subagent's baked-in system prompt enforces a non-mutative + non-recursive contract: read-only file/search/web tools and read-only MCP queries only; no writes, shell side effects, state-changing MCP calls, or nested subagent dispatch. Enforcement is prose-only \u2014 restate the constraint in your dispatch instructions and do not relax it.
|
|
145838
|
+
|
|
145839
|
+
Provide the subagent with YOUR TASK, the output of \`git diff\`, and a tight summary (not raw output) of any lint/typecheck/test failures you fixed during build \u2014 what broke, root cause, the fix \u2014 so it can check that fixes addressed root causes rather than suppressed symptoms; say "no build-phase failures" if the build path was clean. Instruct it to flag bugs, logic errors, missing edge cases, gaps between request and diff, and unintended changes.
|
|
145840
|
+
|
|
145841
|
+
Delegation + research discipline (distilled from \`/anneal\` canonical \u2014 these are codified learnings from many review rounds, not theoretical best practices):
|
|
145842
|
+
- Do NOT summarize what you implemented \u2014 that biases the subagent toward validating the shape of your solution rather than questioning it.
|
|
145843
|
+
- Do NOT curate a reading list of files. Let the subagent discover scope from the diff and codebase.
|
|
145844
|
+
- Do NOT pre-shape output with a severity / category schema. That leaks your hypotheses; severity is your call during evaluation.
|
|
145845
|
+
- Do NOT defect-hunt the diff yourself in parallel with the subagent. Your role is dispatch + evaluation; doing the review yourself reintroduces the implementation bias the subagent is meant to mitigate.
|
|
145846
|
+
- For diffs that rely on third-party API contracts, SDK semantics, framework directives, or DB engine specifics, instruct the subagent to verify load-bearing claims via web search and quote source URLs rather than trust training data \u2014 this is the single most common review-quality failure mode.
|
|
145847
|
+
|
|
145848
|
+
Review the findings, address valid points, and discard nitpicks or false positives. The reviewer is fallible \u2014 it biases toward *recommending additions* (defensive checks for impossible cases, extra logging, new abstractions used once, comments restating code, tests asserting tautologies, "just-in-case" guards). For each finding, ask: would applying it leave the code more sound, correct, AND elegant? Two-out-of-three is not enough \u2014 a fix that improves correctness while degrading elegance still degrades the codebase. Reject bloat-shaped findings without applying them, and after applying the rest re-read your diff and be discerning about what *you just changed*: if any fix turned out to be bloat in context, revert it. The goal is code that is sound and correct *while remaining elegant*; the smallest diff that fixes the real defect almost always wins. Then verify only intended changes are present, no debug artifacts or commented-out code remain, no unrelated files were modified. Commit locally via shell (\`git add . && git commit -m "..."\`).
|
|
145541
145849
|
|
|
145542
145850
|
5. **finalize**:
|
|
145543
145851
|
- confirm a clean working tree, then push via \`${t2("push_branch")}\` (see *SYSTEM* Git rules if this fails \u2014 prepush errors are usually the repo's tests/lint, not infra timeouts)
|
|
@@ -145561,11 +145869,12 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
145561
145869
|
|
|
145562
145870
|
3. For each comment:
|
|
145563
145871
|
- understand the feedback
|
|
145564
|
-
-
|
|
145565
|
-
-
|
|
145872
|
+
- evaluate whether applying it would leave the code more **sound, correct, AND elegant**. reviewers are fallible and bias toward *recommending additions* (defensive checks for impossible cases, extra abstractions, comments restating obvious code, tests asserting tautologies, "just-in-case" guards). if a request would add bloat \u2014 ceremony without commensurate correctness benefit \u2014 push back in your reply rather than mechanically applying it. two-out-of-three is not enough; improving correctness while degrading elegance still degrades the code.
|
|
145873
|
+
- if the request stands, make the code change using your native tools; otherwise reply explaining why
|
|
145874
|
+
- record what was done (or why nothing was done)
|
|
145566
145875
|
|
|
145567
145876
|
4. Quality check:
|
|
145568
|
-
- test changes, then review the diff before committing \u2014 verify only intended changes are present, no debug artifacts remain, and the changes are clean enough that a senior engineer would approve without hesitation
|
|
145877
|
+
- test changes, then review the diff before committing \u2014 verify only intended changes are present, no debug artifacts remain, no fix turned out to be bloat in context (revert any that did), and the changes are clean enough that a senior engineer would approve without hesitation
|
|
145569
145878
|
- commit locally via shell (\`git add . && git commit -m "..."\`)
|
|
145570
145879
|
|
|
145571
145880
|
5. Finalize:
|
|
@@ -145576,28 +145885,93 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
145576
145885
|
|
|
145577
145886
|
${learningsStep(t2, 6)}`
|
|
145578
145887
|
},
|
|
145888
|
+
// Review and IncrementalReview use the multi-lens orchestrator pattern
|
|
145889
|
+
// (canonical source: .claude/commands/anneal.md). The orchestrator does
|
|
145890
|
+
// triage → parallel read-only subagent fan-out → aggregate → draft comments
|
|
145891
|
+
// → submit. For someone else's PR, parallel lenses (correctness, security,
|
|
145892
|
+
// research-validated claims, user-journey, etc.) provide breadth across
|
|
145893
|
+
// angles that a single subagent can't carry coherently. Build mode keeps
|
|
145894
|
+
// a single fresh-eyes subagent (different problem shape — orchestrator
|
|
145895
|
+
// wrote the code and bias-mitigation comes from delegating to one
|
|
145896
|
+
// subagent that doesn't share the implementation context).
|
|
145897
|
+
// Deliberate omission vs canonical /anneal: severity categorization in the
|
|
145898
|
+
// final message (the review body has its own CAUTION/IMPORTANT framing
|
|
145899
|
+
// instead of a severity table).
|
|
145579
145900
|
{
|
|
145580
145901
|
name: "Review",
|
|
145581
145902
|
description: "Review code, PRs, or implementations; provide feedback or suggestions; identify issues; or check code quality, style, and correctness",
|
|
145582
145903
|
prompt: `### Checklist
|
|
145583
145904
|
|
|
145584
|
-
1.
|
|
145585
|
-
|
|
145586
|
-
2.
|
|
145587
|
-
|
|
145588
|
-
|
|
145589
|
-
|
|
145590
|
-
|
|
145591
|
-
-
|
|
145592
|
-
-
|
|
145593
|
-
-
|
|
145594
|
-
-
|
|
145905
|
+
1. **checkout**: call \`${t2("checkout_pr")}\` \u2014 this returns PR metadata and a \`diffPath\`. read the diff TOC end-to-end and treat its file line ranges as your coverage checklist.
|
|
145906
|
+
|
|
145907
|
+
2. **triage**: orient yourself on the PR \u2014 identify *what kind of thing this is* (domain it touches, seams it crosses, external contracts it depends on, user-facing surfaces it changes). orientation only \u2014 defer specific defect-hunting to the subagents; pre-reviewing biases the lenses you pick. use \`${t2("get_pull_request")}\` and other read-only GitHub tools for additional context if needed.
|
|
145908
|
+
|
|
145909
|
+
if the PR is **genuinely trivial**, skip steps 3\u20134 entirely and submit \`Reviewed \u2014 no issues found.\` per step 5. there's no value in dispatching even one lens for a typo.
|
|
145910
|
+
|
|
145911
|
+
"Genuinely trivial" (skip):
|
|
145912
|
+
- single-word doc typo, whitespace/format-only, comment-only across any number of files
|
|
145913
|
+
- lockfile or generated-code regeneration (size of diff is irrelevant \u2014 read the *shape*)
|
|
145914
|
+
- mechanical rename whose only effect is import-path updates
|
|
145915
|
+
- low-risk dep patch bump
|
|
145916
|
+
|
|
145917
|
+
"Looks trivial but isn't" (do **NOT** skip \u2014 small diff, big blast radius):
|
|
145918
|
+
- any 1-line change to SQL / regex / auth / billing / permission / signature-verification code
|
|
145919
|
+
- flipping a feature-flag default, default config value, or retry/timeout constant
|
|
145920
|
+
- changing a money/tax/currency/fee constant by any amount
|
|
145921
|
+
- changing an HTTP method, redirect URL, response code, or status enum
|
|
145922
|
+
- tightening or loosening a comparison operator (\`<\` \u2194 \`<=\`, \`==\` \u2194 \`!=\`)
|
|
145923
|
+
- renaming a public API surface (still trivial in shape, but needs an impact lens)
|
|
145924
|
+
- adding a new direct dependency (supply-chain surface)
|
|
145925
|
+
- any "typo fix" in user-facing copy that changes meaning ("approved" \u2192 "denied")
|
|
145926
|
+
- mixed diffs where a semantic 1-liner is buried in whitespace/formatting changes
|
|
145927
|
+
|
|
145928
|
+
When unsure, treat as non-trivial. The cost of one extra subagent is cents; the cost of a missed billing/auth/data bug is much more.
|
|
145929
|
+
|
|
145930
|
+
otherwise pick lenses by where the PR concentrates risk \u2014 **there's no fixed count**. lens count is judgment, not a formula. concrete shapes to anchor against:
|
|
145931
|
+
|
|
145932
|
+
- **1 lens** \u2014 pure refactor / mechanical rename across many files (impact); new test file with no source change (test-integrity); small isolated bug fix (correctness); doc-only PR with non-trivial technical content (research-validated or holistic)
|
|
145933
|
+
- **2\u20133 lenses (most PRs land here)** \u2014 new CRUD endpoint (correctness + security + test-integrity); new UI flow (user-journey + correctness); a single bug fix in a non-critical subsystem (correctness + test-integrity); design doc covering one domain (research-validated + correctness or holistic)
|
|
145934
|
+
- **4\u20135 lenses (high-stakes subsystem touches)** \u2014 any billing/payments change (billing-subsystem + correctness + security + operational-readiness); new auth flow (auth-subsystem + correctness + security + test-integrity); schema migration (schema-migration-subsystem + correctness + operational-readiness + impact); cross-subsystem PR that touches billing AND auth AND schema (one subsystem lens per domain + correctness)
|
|
145935
|
+
- **6+ lenses** \u2014 almost always a smell; you're either covering overlapping ground or this PR should have been split. push back via the review body rather than expanding lens count.
|
|
145936
|
+
|
|
145937
|
+
lenses come in two flavors, and you can mix them:
|
|
145938
|
+
- **themed lenses** \u2014 a perspective applied across the whole diff (correctness, security, user-journey, performance, etc.).
|
|
145939
|
+
- **subsystem lenses** \u2014 a domain-scoped frame for high-stakes subsystems the PR touches (e.g. "the auth lens", "the billing lens", "the schema-migration lens"). a subsystem lens is "review the PR specifically for what could go wrong in this subsystem" and naturally combines theme + scope. **for high-stakes domains, lead with the subsystem lens rather than the generic themed equivalent** \u2014 "billing-subsystem" outperforms "correctness on billing code" because the framing primes the subagent to remember domain-specific failure modes (double-charges, refund races, currency rounding, dispute flows) the generic lens misses.
|
|
145940
|
+
|
|
145941
|
+
starter menu (combine, omit, or invent your own):
|
|
145942
|
+
- **correctness & invariants** \u2014 bugs, races, error handling, edge cases, state-machine boundaries
|
|
145943
|
+
- **impact** \u2014 when the PR removes features, deletes exports, renames identifiers, or changes architectural patterns: stale references in code, tests, docs (\`docs/\`, \`wiki/\`), comments, configs, UI
|
|
145944
|
+
- **research-validated assumptions** \u2014 third-party API contracts, SDK semantics, framework directives, version-gated behavior. the subagent must verify load-bearing claims via web search and quote source URLs.
|
|
145945
|
+
- **security** \u2014 new endpoints, authZ, input validation, secrets handling, replay/CSRF/injection, cross-tenant isolation
|
|
145946
|
+
- **user-journey** \u2014 UX-touching flows: walk through happy path and failure modes as a user
|
|
145947
|
+
- **operational readiness** \u2014 observability, alerting, migrations (forward + rollback), feature flags, on-call burden
|
|
145948
|
+
- **integration & cross-cutting** \u2014 API contracts between modules, backward-compat of public surfaces, multi-service ordering
|
|
145949
|
+
- **test integrity** \u2014 meaningful coverage for the changed behavior; deterministic; no shared-state pollution
|
|
145950
|
+
- **performance** \u2014 N+1 queries, hot-path allocation, latency budgets, index coverage
|
|
145951
|
+
- **holistic** \u2014 does the PR make sense as a whole? symmetric flows (delete for every create, rollback for every migration)?
|
|
145952
|
+
- **subsystem lenses** (invent as the PR demands) \u2014 auth, billing, payments, schema migration, webhooks, secrets, RBAC, multi-tenant isolation, cron/scheduling, etc.
|
|
145953
|
+
|
|
145954
|
+
3. **fan out**: dispatch one \`${REVIEWER_AGENT_NAME}\` subagent per lens \u2014 its baked-in system prompt enforces the non-mutative + non-recursive contract (read-only file/search/web tools and read-only MCP queries; no writes, shell side effects, state-changing MCP calls, or nested subagent dispatch). when picking 2+ lenses, dispatch them in a **single assistant turn with multiple parallel subagent calls**; issuing one and awaiting reply before the next collapses the fan-out into a serial review. if a subagent errors out, times out, or returns nothing usable, retry once with the same lens; if it still fails, proceed with partial coverage and note the missing lens in the review body \u2014 do not skip step 3 entirely on a single subagent failure. each subagent gets:
|
|
145955
|
+
- the diff path / target \u2014 reading the diff and the codebase is its job
|
|
145956
|
+
- **only one lens** \u2014 never a multi-section "review for X, Y, and Z" prompt
|
|
145957
|
+
- **a Task \`description\` set to the lens name** (e.g. \`"security"\`, \`"correctness"\`, \`"billing-subsystem"\`) \u2014 the harness reads this field to label the subagent's log lines so parallel runs can be told apart in CI output. without it, every subagent shows up as \`subagent#N\`.
|
|
145958
|
+
- the read-only contract restated in your dispatch instructions so the rule is present twice (the subagent's system prompt also enforces it). The test: would this call still be a no-op if reverted? If not (PR comments, branch pushes, issue updates, set_output, label changes, dependency installs, etc.), don't make it.
|
|
145959
|
+
- if the lens touches external contracts, instruct the subagent to verify load-bearing claims via web search rather than trust training data, and to quote source URLs in its reasoning. action runs are non-interactive \u2014 there's no human in the loop to catch "I'm pretty sure Stripe does X."
|
|
145960
|
+
- ask the subagent to report findings with file paths and NEW line numbers from the diff so you can anchor inline comments without re-reading the entire diff.
|
|
145961
|
+
|
|
145962
|
+
delegation discipline:
|
|
145963
|
+
- do NOT lens-review the diff yourself in parallel with the subagents (your job is dispatch + comment-drafting; doing the lens work yourself reintroduces the bias the fan-out avoids)
|
|
145964
|
+
- do NOT summarize the PR for them (biases toward a validation frame)
|
|
145965
|
+
- do NOT hand them a curated reading list (let them discover scope)
|
|
145966
|
+
- do NOT pre-shape their output with a finding schema
|
|
145967
|
+
- do NOT mention the other lenses (independence is the point \u2014 overlapping findings are a strong signal)
|
|
145968
|
+
|
|
145969
|
+
4. **aggregate & draft**: merge findings; de-dup overlaps (two lenses catching the same issue = higher-confidence signal); trace each finding yourself before accepting it. drop praise, style preferences, speculative/unverified claims, findings about pre-existing code unrelated to the PR (heuristic: if the finding's root cause lives in lines this PR added or modified, it's in scope; otherwise drop unless the PR plausibly introduced or amplified the regression), and anything not actionable. also drop **bloat-shaped findings** \u2014 proposed fixes that would add defensive checks for cases that can't happen, abstractions used once, comments restating obvious code, tests asserting tautologies, or "just-in-case" guards. subagents are fallible and bias toward recommending changes; the bar for an actionable inline comment is sound + correct + elegant. recommending a change that improves only one of the three (or worse, degrades elegance to nominally improve correctness) makes the codebase worse, not better.
|
|
145970
|
+
|
|
145971
|
+
for surviving findings, draft inline comments with NEW line numbers from the diff. every comment must be actionable, 2-3 sentences max. use GitHub permalink format for code references. for impact-analysis findings (stale references after rename/remove), report them in the review body ordered by severity (runtime breakage > incorrect docs > stale comments) rather than as inline comments unless they're anchored to a specific line.
|
|
145972
|
+
|
|
145973
|
+
5. **submit**: ALWAYS submit exactly one review via \`${t2("create_pull_request_review")}\`. Do NOT call \`report_progress\` \u2014 the review is the final record and the progress comment will be cleaned up automatically.
|
|
145595
145974
|
|
|
145596
|
-
3. Self-critique: review all drafted comments and drop any that are praise, style preferences, speculative/unverified claims, about pre-existing code unrelated to the PR, or not actionable.
|
|
145597
|
-
|
|
145598
|
-
4. Submit \u2014 ALWAYS submit exactly one review via \`${t2("create_pull_request_review")}\`.
|
|
145599
|
-
Do NOT call \`report_progress\` \u2014 the review is the final record and the progress
|
|
145600
|
-
comment will be cleaned up automatically.
|
|
145601
145975
|
note: the first create_pull_request_review submission may error with a one-time diff-coverage nudge listing unread TOC regions. retry the same call to proceed \u2014 optionally after reading the listed ranges. the pre-flight will not block again this session.
|
|
145602
145976
|
|
|
145603
145977
|
- **critical issues** (blocks merge \u2014 bugs, security, data loss):
|
|
@@ -145611,29 +145985,56 @@ ${learningsStep(t2, 6)}`
|
|
|
145611
145985
|
- **no actionable issues**:
|
|
145612
145986
|
\`approved: true\`, body: "Reviewed \u2014 no issues found."`
|
|
145613
145987
|
},
|
|
145988
|
+
// IncrementalReview shares Review's multi-lens orchestrator pattern but
|
|
145989
|
+
// scopes the target to the incremental diff and adds prior-review-feedback
|
|
145990
|
+
// tracking. The "issues must be NEW since the last Pullfrog review" filter
|
|
145991
|
+
// lives at aggregation time (step 5), NOT in the subagent prompt — pushing
|
|
145992
|
+
// the filter into subagents matches the canonical anneal anti-pattern of
|
|
145993
|
+
// "list known pre-existing failures — don't flag these" and suppresses
|
|
145994
|
+
// signal on regressions the new commits amplified. The body-format rules
|
|
145995
|
+
// (Reviewed changes / Prior review feedback) are unchanged from the prior
|
|
145996
|
+
// version. Same severity-table omission as Review.
|
|
145614
145997
|
{
|
|
145615
145998
|
name: "IncrementalReview",
|
|
145616
145999
|
description: "Re-review a PR after new commits are pushed; focus on new changes since the last review",
|
|
145617
146000
|
prompt: `### Checklist
|
|
145618
146001
|
|
|
145619
|
-
1.
|
|
146002
|
+
1. **checkout**: call \`${t2("checkout_pr")}\` \u2014 this returns PR metadata, \`diffPath\` (full diff), and \`incrementalDiffPath\` (changes since last reviewed version, if available). read the diff TOC first and use its line ranges as your coverage checklist.
|
|
146003
|
+
|
|
146004
|
+
2. **incremental scope**: if \`incrementalDiffPath\` is present, read it to see what changed since the last review. this is a range-diff that isolates the net changes, filtering out base branch noise. if not present, fall back to reviewing the full PR diff and determine what changed since Pullfrog's most recent review.
|
|
146005
|
+
|
|
146006
|
+
3. **prior feedback**: fetch previous reviews via \`${t2("list_pull_request_reviews")}\`. for the most recent Pullfrog review, call \`${t2("get_review_comments")}\` with the review ID to retrieve specific prior line-level feedback. you'll need this in step 6 to track which prior comments were addressed.
|
|
146007
|
+
|
|
146008
|
+
4. **triage & fan out**: orient on the *incremental* changes \u2014 domain, seams, external contracts, user-facing surfaces.
|
|
146009
|
+
|
|
146010
|
+
if the incremental changes are **genuinely trivial**, skip the fan-out entirely and jump to step 7's non-substantive path (do NOT submit a review).
|
|
146011
|
+
|
|
146012
|
+
"Genuinely trivial" (skip): formatting/comment tweaks, import reordering, lockfile regen, mechanical rename of import paths, whitespace-only.
|
|
146013
|
+
"Looks trivial but isn't" (do NOT skip \u2014 same anti-patterns as Review mode): 1-line changes to SQL/regex/auth/billing/permissions/signature-verification code; flipping feature-flag defaults or retry/timeout constants; money/tax/HTTP-method/redirect changes; tightening or loosening a comparison operator; mixed diffs with a semantic line buried in formatting.
|
|
146014
|
+
When unsure, treat as non-trivial.
|
|
145620
146015
|
|
|
145621
|
-
|
|
146016
|
+
otherwise pick lenses by where the new commits concentrate risk \u2014 **there's no fixed count**, same calibration as Review mode (1 lens for pure refactor / isolated fix; 2\u20133 for typical features; 4\u20135 for high-stakes subsystem touches; 6+ is a smell). lens framing follows Review mode: themed lenses (correctness & invariants, impact when new commits remove/rename/deprecate things, research-validated assumptions, security, user-journey, operational readiness, integration & cross-cutting, test integrity, performance, holistic) and subsystem lenses (auth, billing, schema migration, etc.) \u2014 for high-stakes domains lead with the subsystem lens rather than the generic themed equivalent.
|
|
145622
146017
|
|
|
145623
|
-
|
|
146018
|
+
dispatch one \`${REVIEWER_AGENT_NAME}\` subagent per lens \u2014 its baked-in system prompt enforces the non-mutative + non-recursive contract (read-only file/search/web tools and read-only MCP queries; no writes, shell side effects, state-changing MCP calls, or nested subagent dispatch). dispatch them in a **single assistant turn with multiple parallel subagent calls** (serial dispatch collapses the fan-out). if a subagent errors out, times out, or returns nothing usable, retry once with the same lens; if it still fails, proceed with partial coverage and note the missing lens in the review body \u2014 do not skip step 4 entirely on a single subagent failure. each subagent gets:
|
|
146019
|
+
- the diff scope (incremental diff path if available, full diff otherwise). do NOT tell them to skip pre-existing issues \u2014 that suppresses regressions the new commits amplified; the "issues must be NEW" filter lives at aggregation time (step 5), not in the subagent prompt
|
|
146020
|
+
- **only one lens** \u2014 never a multi-section "review for X, Y, and Z" prompt
|
|
146021
|
+
- **a Task \`description\` set to the lens name** (e.g. \`"security"\`, \`"correctness"\`, \`"billing-subsystem"\`) \u2014 the harness reads this field to label the subagent's log lines so parallel runs can be told apart in CI output. without it, every subagent shows up as \`subagent#N\`.
|
|
146022
|
+
- the read-only contract restated in your dispatch instructions so the rule is present twice (the subagent's system prompt also enforces it). The test: would this call still be a no-op if reverted? If not (PR comments, branch pushes, issue updates, set_output, label changes, dependency installs, etc.), don't make it.
|
|
146023
|
+
- if the lens touches external contracts, instruct the subagent to verify load-bearing claims via web search and quote source URLs. action runs are non-interactive \u2014 there's no human to catch "I'm pretty sure Stripe does X."
|
|
146024
|
+
- ask the subagent to report findings with file paths and NEW line numbers from the full PR diff so you can anchor inline comments.
|
|
145624
146025
|
|
|
145625
|
-
|
|
145626
|
-
- review the
|
|
145627
|
-
-
|
|
145628
|
-
-
|
|
145629
|
-
-
|
|
145630
|
-
-
|
|
145631
|
-
- draft inline comments with NEW line numbers from the full PR diff \u2014 every comment must be actionable (2-3 sentences max)
|
|
145632
|
-
- for large or cross-cutting PRs, consider delegating read-only subagents for parallel investigation. subagents must ONLY read files, grep, and search \u2014 no MCP tools, no writes, no shell commands, no side effects. collect their findings and use them to draft comments.
|
|
146026
|
+
delegation discipline:
|
|
146027
|
+
- do NOT lens-review the diff yourself in parallel with the subagents
|
|
146028
|
+
- do NOT summarize the changes for them (biases toward validation frame)
|
|
146029
|
+
- do NOT hand them a curated reading list (let them discover scope)
|
|
146030
|
+
- do NOT pre-shape their output with a finding schema
|
|
146031
|
+
- do NOT mention the other lenses (independence is the point)
|
|
145633
146032
|
|
|
145634
|
-
5.
|
|
146033
|
+
5. **aggregate, draft, self-critique**: merge findings; de-dup overlaps; trace each finding yourself. drop praise, style preferences, speculative/unverified claims, findings about pre-existing code unrelated to the new commits, anything not actionable, and anything that re-states prior review feedback (heuristic: if the finding's root cause lives in lines the *new commits* added or modified, it's in scope; otherwise drop). also drop **bloat-shaped findings** \u2014 proposed fixes that would add defensive checks for cases that can't happen, abstractions used once, comments restating obvious code, tests asserting tautologies, or "just-in-case" guards. subagents are fallible and bias toward recommending changes; the bar for an actionable inline comment is sound + correct + elegant. recommending a change that improves only one of the three (or degrades elegance to nominally improve correctness) makes the codebase worse, not better. To compute "lines the new commits added or modified": if \`incrementalDiffPath\` from step 1 is present, use it directly. Otherwise, take the prior Pullfrog review's \`commit_id\` (returned alongside each entry from \`${t2("list_pull_request_reviews")}\` in step 3) and run \`git diff <prior-review-sha>..HEAD\` to isolate the lines added since that review. draft inline comments with NEW line numbers from the full PR diff \u2014 every comment must be actionable, 2-3 sentences max.
|
|
145635
146034
|
|
|
145636
|
-
|
|
146035
|
+
then check: which prior review comments were addressed by the new commits? track the addressed ones for step 6b.
|
|
146036
|
+
|
|
146037
|
+
6. **build the review body** \u2014 two distinct sections:
|
|
145637
146038
|
a. **Reviewed changes**: summarize at the logical-change level, not per-file. each bullet starts with a past-tense verb (e.g. \`- Extracted shared CLI runtime into a single module\`, \`- Renamed package to pullfrog\`). avoid file paths unless they add clarity. if the changes can be described in one sentence, use one sentence \u2014 no bullets needed.
|
|
145638
146039
|
b. **Prior review feedback** (only if any were addressed): list only the prior review comments that WERE addressed by the new commits (\`- [x] safeParse instead of parse \u2014 addressed\`). omit unaddressed comments. omit this entire section if nothing was addressed. a change can appear in both sections.
|
|
145639
146040
|
- no headings, no tables, no prose paragraphs in either section \u2014 just bullets
|
|
@@ -146225,14 +146626,13 @@ function UploadFileTool(ctx) {
|
|
|
146225
146626
|
|
|
146226
146627
|
// mcp/server.ts
|
|
146227
146628
|
function initToolState(params) {
|
|
146228
|
-
const
|
|
146229
|
-
|
|
146230
|
-
|
|
146231
|
-
log.info(`\xBB using pre-created progress comment: ${resolvedId}`);
|
|
146629
|
+
const resolved = parseProgressComment(params.progressComment);
|
|
146630
|
+
if (resolved) {
|
|
146631
|
+
log.info(`\xBB using pre-created progress comment: ${resolved.id} (${resolved.type})`);
|
|
146232
146632
|
}
|
|
146233
146633
|
return {
|
|
146234
|
-
|
|
146235
|
-
hadProgressComment: !!
|
|
146634
|
+
progressComment: resolved,
|
|
146635
|
+
hadProgressComment: !!resolved,
|
|
146236
146636
|
backgroundProcesses: /* @__PURE__ */ new Map(),
|
|
146237
146637
|
usageEntries: []
|
|
146238
146638
|
};
|
|
@@ -146411,8 +146811,8 @@ async function startMcpHttpServer(ctx, options) {
|
|
|
146411
146811
|
|
|
146412
146812
|
// agents/claude.ts
|
|
146413
146813
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
146414
|
-
import { mkdirSync as
|
|
146415
|
-
import { join as
|
|
146814
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "node:fs";
|
|
146815
|
+
import { join as join10 } from "node:path";
|
|
146416
146816
|
import { performance as performance6 } from "node:perf_hooks";
|
|
146417
146817
|
|
|
146418
146818
|
// utils/install.ts
|
|
@@ -146521,8 +146921,35 @@ function detectProviderError(text) {
|
|
|
146521
146921
|
|
|
146522
146922
|
// utils/skills.ts
|
|
146523
146923
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
146924
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "node:fs";
|
|
146524
146925
|
import { tmpdir } from "node:os";
|
|
146926
|
+
import { dirname as dirname2, join as join9 } from "node:path";
|
|
146927
|
+
import { fileURLToPath } from "node:url";
|
|
146525
146928
|
var skillsVersion = getDevDependencyVersion("skills");
|
|
146929
|
+
var BUNDLED_SKILL_NAMES = ["git-archaeology"];
|
|
146930
|
+
function resolveSkillPath(name) {
|
|
146931
|
+
const here = dirname2(fileURLToPath(import.meta.url));
|
|
146932
|
+
const candidates = [
|
|
146933
|
+
join9(here, "..", "skills", name, "SKILL.md"),
|
|
146934
|
+
join9(here, "skills", name, "SKILL.md")
|
|
146935
|
+
];
|
|
146936
|
+
for (const candidate of candidates) {
|
|
146937
|
+
if (existsSync6(candidate)) return candidate;
|
|
146938
|
+
}
|
|
146939
|
+
throw new Error(`bundled skill not found: ${name} (looked in ${candidates.join(", ")})`);
|
|
146940
|
+
}
|
|
146941
|
+
var SKILL_TARGET_DIRS = [".opencode/skills", ".claude/skills", ".agents/skills"];
|
|
146942
|
+
function installBundledSkills(params) {
|
|
146943
|
+
for (const name of BUNDLED_SKILL_NAMES) {
|
|
146944
|
+
const content = readFileSync4(resolveSkillPath(name), "utf8");
|
|
146945
|
+
for (const targetDir of SKILL_TARGET_DIRS) {
|
|
146946
|
+
const skillDir = join9(params.home, targetDir, name);
|
|
146947
|
+
mkdirSync3(skillDir, { recursive: true });
|
|
146948
|
+
writeFileSync6(join9(skillDir, "SKILL.md"), content);
|
|
146949
|
+
}
|
|
146950
|
+
}
|
|
146951
|
+
log.info(`installed bundled skills: ${BUNDLED_SKILL_NAMES.join(", ")}`);
|
|
146952
|
+
}
|
|
146526
146953
|
function addSkill(params) {
|
|
146527
146954
|
const result = spawnSync5(
|
|
146528
146955
|
"npx",
|
|
@@ -146596,6 +147023,213 @@ var ThinkingTimer = class {
|
|
|
146596
147023
|
}
|
|
146597
147024
|
};
|
|
146598
147025
|
|
|
147026
|
+
// agents/postRun.ts
|
|
147027
|
+
var MAX_HOOK_OUTPUT_CHARS = 4096;
|
|
147028
|
+
function truncateHookOutput(raw2) {
|
|
147029
|
+
if (raw2.length <= MAX_HOOK_OUTPUT_CHARS) return raw2;
|
|
147030
|
+
return `...(truncated, showing last ${MAX_HOOK_OUTPUT_CHARS} chars)
|
|
147031
|
+
${raw2.slice(-MAX_HOOK_OUTPUT_CHARS)}`;
|
|
147032
|
+
}
|
|
147033
|
+
async function executeStopHook(script) {
|
|
147034
|
+
log.info("\xBB executing stop hook...");
|
|
147035
|
+
try {
|
|
147036
|
+
const result = await spawn({
|
|
147037
|
+
cmd: "bash",
|
|
147038
|
+
args: ["-c", script],
|
|
147039
|
+
env: process.env,
|
|
147040
|
+
timeout: LIFECYCLE_HOOK_TIMEOUT_MS,
|
|
147041
|
+
activityTimeout: 0,
|
|
147042
|
+
onStdout: (chunk) => process.stdout.write(chunk),
|
|
147043
|
+
onStderr: (chunk) => process.stderr.write(chunk)
|
|
147044
|
+
});
|
|
147045
|
+
if (result.exitCode === 0) {
|
|
147046
|
+
log.info("\xBB stop hook passed");
|
|
147047
|
+
return null;
|
|
147048
|
+
}
|
|
147049
|
+
const combined = [result.stderr.trim(), result.stdout.trim()].filter(Boolean).join("\n");
|
|
147050
|
+
const output = truncateHookOutput(combined);
|
|
147051
|
+
log.info(`\xBB stop hook failed with exit code ${result.exitCode}`);
|
|
147052
|
+
return { exitCode: result.exitCode, output };
|
|
147053
|
+
} catch (err) {
|
|
147054
|
+
const isTimeout = err instanceof SpawnTimeoutError && (err.code === SPAWN_TIMEOUT_CODE || err.code === SPAWN_ACTIVITY_TIMEOUT_CODE);
|
|
147055
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
147056
|
+
log.warning(
|
|
147057
|
+
`stop hook ${isTimeout ? "timed out" : "failed to spawn"}: ${msg} \u2014 skipping retry`
|
|
147058
|
+
);
|
|
147059
|
+
return null;
|
|
147060
|
+
}
|
|
147061
|
+
}
|
|
147062
|
+
function buildStopHookPrompt(failure) {
|
|
147063
|
+
return [
|
|
147064
|
+
`STOP HOOK FAILED \u2014 the repo-configured stop hook exited with code ${failure.exitCode}. your work is not done until the hook exits cleanly. address the issue below and push any resulting changes to a pull request.`,
|
|
147065
|
+
"",
|
|
147066
|
+
"```",
|
|
147067
|
+
failure.output || "(no output)",
|
|
147068
|
+
"```"
|
|
147069
|
+
].join("\n");
|
|
147070
|
+
}
|
|
147071
|
+
async function collectPostRunIssues(params) {
|
|
147072
|
+
const issues = {};
|
|
147073
|
+
if (params.stopScript) {
|
|
147074
|
+
const failure = await executeStopHook(params.stopScript);
|
|
147075
|
+
if (failure) issues.stopHook = failure;
|
|
147076
|
+
}
|
|
147077
|
+
const status = getGitStatus();
|
|
147078
|
+
if (status) issues.dirtyTree = status;
|
|
147079
|
+
return issues;
|
|
147080
|
+
}
|
|
147081
|
+
function buildPostRunPrompt(issues) {
|
|
147082
|
+
const parts = [];
|
|
147083
|
+
if (issues.stopHook) parts.push(buildStopHookPrompt(issues.stopHook));
|
|
147084
|
+
if (issues.dirtyTree) parts.push(buildCommitPrompt(issues.dirtyTree));
|
|
147085
|
+
return parts.join("\n\n---\n\n");
|
|
147086
|
+
}
|
|
147087
|
+
function buildLearningsReflectionPrompt(agentId) {
|
|
147088
|
+
const t2 = (name) => formatMcpToolRef(agentId, name);
|
|
147089
|
+
return [
|
|
147090
|
+
`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 you are confident is correct and would reliably help future runs?`,
|
|
147091
|
+
"",
|
|
147092
|
+
`if so, call \`${t2("update_learnings")}\` to persist it.`,
|
|
147093
|
+
"",
|
|
147094
|
+
`rules:`,
|
|
147095
|
+
`- only call \`${t2("update_learnings")}\` when the finding is high-confidence and broadly useful. skip if unsure, speculative, or one-off.`,
|
|
147096
|
+
`- pass the FULL merged list: existing learnings from the original prompt + your new discoveries. one fact per bullet, lines starting with \`- \`.`,
|
|
147097
|
+
`- deduplicate, and drop bullets that are clearly wrong or no longer relevant to the current codebase.`,
|
|
147098
|
+
`- if you already called \`${t2("update_learnings")}\` earlier in this run, or nothing new is worth capturing, just reply "done" and stop \u2014 do not edit the repo for this reflection.`
|
|
147099
|
+
].join("\n");
|
|
147100
|
+
}
|
|
147101
|
+
async function runPostRunRetryLoop(params) {
|
|
147102
|
+
let result = params.initialResult;
|
|
147103
|
+
let aggregatedUsage = params.initialUsage;
|
|
147104
|
+
let finalIssues = {};
|
|
147105
|
+
let gateResumeCount = 0;
|
|
147106
|
+
let pendingReflection = params.reflectionPrompt;
|
|
147107
|
+
while (gateResumeCount < MAX_POST_RUN_RETRIES) {
|
|
147108
|
+
if (!result.success) break;
|
|
147109
|
+
const issues = await collectPostRunIssues({ stopScript: params.stopScript });
|
|
147110
|
+
finalIssues = issues;
|
|
147111
|
+
if (!hasPostRunIssues(issues)) {
|
|
147112
|
+
if (!pendingReflection) break;
|
|
147113
|
+
if (params.canResume && !params.canResume(result)) break;
|
|
147114
|
+
log.info("\xBB post-run reflection: nudging agent to update learnings if relevant");
|
|
147115
|
+
const preReflection = result;
|
|
147116
|
+
const reflectionResult = await params.resume({
|
|
147117
|
+
prompt: pendingReflection,
|
|
147118
|
+
previousResult: result
|
|
147119
|
+
});
|
|
147120
|
+
aggregatedUsage = mergeAgentUsage(aggregatedUsage, reflectionResult.usage);
|
|
147121
|
+
pendingReflection = void 0;
|
|
147122
|
+
if (!reflectionResult.success) {
|
|
147123
|
+
log.warning(
|
|
147124
|
+
`\xBB reflection turn failed (${reflectionResult.error ?? "unknown error"}), preserving prior successful result`
|
|
147125
|
+
);
|
|
147126
|
+
result = preReflection;
|
|
147127
|
+
break;
|
|
147128
|
+
}
|
|
147129
|
+
result = {
|
|
147130
|
+
...reflectionResult,
|
|
147131
|
+
output: preReflection.output || reflectionResult.output
|
|
147132
|
+
};
|
|
147133
|
+
continue;
|
|
147134
|
+
}
|
|
147135
|
+
if (params.canResume && !params.canResume(result)) {
|
|
147136
|
+
log.info("\xBB post-run retry skipped: cannot resume agent session");
|
|
147137
|
+
break;
|
|
147138
|
+
}
|
|
147139
|
+
log.info(`\xBB post-run retry (attempt ${gateResumeCount + 1}/${MAX_POST_RUN_RETRIES})`);
|
|
147140
|
+
const prompt = buildPostRunPrompt(issues);
|
|
147141
|
+
result = await params.resume({ prompt, previousResult: result });
|
|
147142
|
+
aggregatedUsage = mergeAgentUsage(aggregatedUsage, result.usage);
|
|
147143
|
+
gateResumeCount++;
|
|
147144
|
+
}
|
|
147145
|
+
if (gateResumeCount > 0 && result.success && hasPostRunIssues(finalIssues)) {
|
|
147146
|
+
finalIssues = await collectPostRunIssues({ stopScript: params.stopScript });
|
|
147147
|
+
}
|
|
147148
|
+
if (result.success && finalIssues.stopHook) {
|
|
147149
|
+
const retryNote = gateResumeCount > 0 ? ` after ${gateResumeCount} retry ${gateResumeCount === 1 ? "attempt" : "attempts"}` : "";
|
|
147150
|
+
return {
|
|
147151
|
+
...result,
|
|
147152
|
+
success: false,
|
|
147153
|
+
error: `stop hook failed${retryNote} (exit code ${finalIssues.stopHook.exitCode}): ${finalIssues.stopHook.output || "(no output)"}`,
|
|
147154
|
+
usage: aggregatedUsage
|
|
147155
|
+
};
|
|
147156
|
+
}
|
|
147157
|
+
return { ...result, usage: aggregatedUsage };
|
|
147158
|
+
}
|
|
147159
|
+
|
|
147160
|
+
// agents/sessionLabeler.ts
|
|
147161
|
+
var ORCHESTRATOR_LABEL = "orchestrator";
|
|
147162
|
+
var LENS_PROMPT_PATTERN = /^\s*(?:lens|Lens|LENS)\s*[:=]\s*([A-Za-z][\w &/.-]{0,60})/m;
|
|
147163
|
+
function slug(value2) {
|
|
147164
|
+
return value2.trim().toLowerCase().replace(/[^\w-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
147165
|
+
}
|
|
147166
|
+
function deriveLabelFromTaskInput(input) {
|
|
147167
|
+
if (typeof input.prompt === "string") {
|
|
147168
|
+
const match3 = input.prompt.match(LENS_PROMPT_PATTERN);
|
|
147169
|
+
if (match3?.[1]) {
|
|
147170
|
+
const slugged = slug(match3[1]);
|
|
147171
|
+
if (slugged) return `lens:${slugged}`;
|
|
147172
|
+
}
|
|
147173
|
+
}
|
|
147174
|
+
if (input.description) {
|
|
147175
|
+
const slugged = slug(input.description);
|
|
147176
|
+
if (slugged) return `lens:${slugged}`;
|
|
147177
|
+
}
|
|
147178
|
+
if (input.subagent_type) {
|
|
147179
|
+
return input.subagent_type;
|
|
147180
|
+
}
|
|
147181
|
+
return "subagent";
|
|
147182
|
+
}
|
|
147183
|
+
var SessionLabeler = class {
|
|
147184
|
+
labels = /* @__PURE__ */ new Map();
|
|
147185
|
+
pendingLabels = [];
|
|
147186
|
+
fallbackCounter = 0;
|
|
147187
|
+
recordTaskDispatch(input) {
|
|
147188
|
+
const label = deriveLabelFromTaskInput(input);
|
|
147189
|
+
this.pendingLabels.push(label);
|
|
147190
|
+
return label;
|
|
147191
|
+
}
|
|
147192
|
+
/**
|
|
147193
|
+
* Return a label for the given sessionID. Binds on first call.
|
|
147194
|
+
* Pass undefined/empty for events that lack a session id — the caller
|
|
147195
|
+
* gets ORCHESTRATOR_LABEL so the line is still attributable.
|
|
147196
|
+
*/
|
|
147197
|
+
labelFor(sessionID) {
|
|
147198
|
+
if (!sessionID) return ORCHESTRATOR_LABEL;
|
|
147199
|
+
const existing = this.labels.get(sessionID);
|
|
147200
|
+
if (existing) return existing;
|
|
147201
|
+
let label;
|
|
147202
|
+
if (this.labels.size === 0) {
|
|
147203
|
+
label = ORCHESTRATOR_LABEL;
|
|
147204
|
+
} else if (this.pendingLabels.length > 0) {
|
|
147205
|
+
label = this.pendingLabels.shift();
|
|
147206
|
+
} else {
|
|
147207
|
+
this.fallbackCounter += 1;
|
|
147208
|
+
label = `subagent#${this.fallbackCounter}`;
|
|
147209
|
+
}
|
|
147210
|
+
this.labels.set(sessionID, label);
|
|
147211
|
+
return label;
|
|
147212
|
+
}
|
|
147213
|
+
/** number of distinct sessions seen so far (for diagnostics) */
|
|
147214
|
+
size() {
|
|
147215
|
+
return this.labels.size;
|
|
147216
|
+
}
|
|
147217
|
+
/** all (sessionID, label) pairs, oldest first */
|
|
147218
|
+
entries() {
|
|
147219
|
+
return Array.from(this.labels.entries());
|
|
147220
|
+
}
|
|
147221
|
+
/** how many pending labels are queued waiting to bind to a new session */
|
|
147222
|
+
pendingDispatchCount() {
|
|
147223
|
+
return this.pendingLabels.length;
|
|
147224
|
+
}
|
|
147225
|
+
};
|
|
147226
|
+
function formatWithLabel(label, message) {
|
|
147227
|
+
const MAGENTA2 = "\x1B[35m";
|
|
147228
|
+
const RESET2 = "\x1B[0m";
|
|
147229
|
+
const colored = `${MAGENTA2}[${label}]${RESET2} `;
|
|
147230
|
+
return message.split("\n").map((line) => `${colored}${line}`).join("\n");
|
|
147231
|
+
}
|
|
147232
|
+
|
|
146599
147233
|
// agents/claude.ts
|
|
146600
147234
|
async function installClaudeCli() {
|
|
146601
147235
|
return await installFromNpmTarball({
|
|
@@ -146606,10 +147240,10 @@ async function installClaudeCli() {
|
|
|
146606
147240
|
});
|
|
146607
147241
|
}
|
|
146608
147242
|
function writeMcpConfig(ctx) {
|
|
146609
|
-
const configDir =
|
|
146610
|
-
|
|
146611
|
-
const configPath =
|
|
146612
|
-
|
|
147243
|
+
const configDir = join10(ctx.tmpdir, ".claude");
|
|
147244
|
+
mkdirSync4(configDir, { recursive: true });
|
|
147245
|
+
const configPath = join10(configDir, "mcp.json");
|
|
147246
|
+
writeFileSync7(
|
|
146613
147247
|
configPath,
|
|
146614
147248
|
JSON.stringify({
|
|
146615
147249
|
mcpServers: {
|
|
@@ -146619,6 +147253,15 @@ function writeMcpConfig(ctx) {
|
|
|
146619
147253
|
);
|
|
146620
147254
|
return configPath;
|
|
146621
147255
|
}
|
|
147256
|
+
function buildAgentsJson() {
|
|
147257
|
+
const agents2 = {
|
|
147258
|
+
[REVIEWER_AGENT_NAME]: {
|
|
147259
|
+
description: "Read-only review subagent for self-review and lens-based code review. Reads only \u2014 no writes, no state-changing shell or MCP calls, no nested subagent dispatch.",
|
|
147260
|
+
prompt: REVIEWER_SYSTEM_PROMPT
|
|
147261
|
+
}
|
|
147262
|
+
};
|
|
147263
|
+
return JSON.stringify(agents2);
|
|
147264
|
+
}
|
|
146622
147265
|
function stripProviderPrefix(specifier) {
|
|
146623
147266
|
const slashIndex = specifier.indexOf("/");
|
|
146624
147267
|
return slashIndex > 0 ? specifier.slice(slashIndex + 1) : specifier;
|
|
@@ -146669,6 +147312,13 @@ async function runClaude(params) {
|
|
|
146669
147312
|
}
|
|
146670
147313
|
thinkingTimer.markToolCall();
|
|
146671
147314
|
log.toolCall({ toolName, input: block.input || {} });
|
|
147315
|
+
if (toolName === "Task" && block.input && typeof block.input === "object") {
|
|
147316
|
+
const taskInput = block.input;
|
|
147317
|
+
const label = deriveLabelFromTaskInput(taskInput);
|
|
147318
|
+
log.info(
|
|
147319
|
+
`\xBB dispatching subagent: ${label}` + (taskInput.subagent_type ? ` (subagent_type=${taskInput.subagent_type})` : "")
|
|
147320
|
+
);
|
|
147321
|
+
}
|
|
146672
147322
|
if (toolName.includes("report_progress") && params.todoTracker) {
|
|
146673
147323
|
log.debug("\xBB report_progress detected, disabling todo tracking");
|
|
146674
147324
|
params.todoTracker.cancel();
|
|
@@ -146934,9 +147584,9 @@ var claude = agent({
|
|
|
146934
147584
|
const model = specifier ? stripProviderPrefix(specifier) : void 0;
|
|
146935
147585
|
const homeEnv = {
|
|
146936
147586
|
HOME: ctx.tmpdir,
|
|
146937
|
-
XDG_CONFIG_HOME:
|
|
147587
|
+
XDG_CONFIG_HOME: join10(ctx.tmpdir, ".config")
|
|
146938
147588
|
};
|
|
146939
|
-
|
|
147589
|
+
mkdirSync4(join10(homeEnv.XDG_CONFIG_HOME, "claude"), { recursive: true });
|
|
146940
147590
|
const agentBrowserVersion = getDevDependencyVersion("agent-browser");
|
|
146941
147591
|
addSkill({
|
|
146942
147592
|
ref: `vercel-labs/agent-browser@v${agentBrowserVersion}`,
|
|
@@ -146944,6 +147594,7 @@ var claude = agent({
|
|
|
146944
147594
|
env: homeEnv,
|
|
146945
147595
|
agent: "claude"
|
|
146946
147596
|
});
|
|
147597
|
+
installBundledSkills({ home: homeEnv.HOME });
|
|
146947
147598
|
const mcpConfigPath = writeMcpConfig(ctx);
|
|
146948
147599
|
const effort = resolveEffort(model);
|
|
146949
147600
|
installManagedSettings();
|
|
@@ -146958,7 +147609,9 @@ var claude = agent({
|
|
|
146958
147609
|
"--effort",
|
|
146959
147610
|
effort,
|
|
146960
147611
|
"--disallowedTools",
|
|
146961
|
-
"Bash,Agent(Bash)"
|
|
147612
|
+
"Bash,Agent(Bash)",
|
|
147613
|
+
"--agents",
|
|
147614
|
+
buildAgentsJson()
|
|
146962
147615
|
];
|
|
146963
147616
|
if (model) {
|
|
146964
147617
|
baseArgs.push("--model", model);
|
|
@@ -146979,37 +147632,32 @@ var claude = agent({
|
|
|
146979
147632
|
onActivityTimeout: ctx.onActivityTimeout,
|
|
146980
147633
|
onToolUse: ctx.onToolUse
|
|
146981
147634
|
};
|
|
146982
|
-
|
|
147635
|
+
const result = await runClaude({
|
|
146983
147636
|
...runParams,
|
|
146984
147637
|
args: [...baseArgs, "-p", ctx.instructions.full]
|
|
146985
147638
|
});
|
|
146986
|
-
|
|
146987
|
-
|
|
146988
|
-
|
|
146989
|
-
|
|
146990
|
-
|
|
146991
|
-
|
|
146992
|
-
|
|
146993
|
-
|
|
146994
|
-
|
|
146995
|
-
|
|
146996
|
-
...
|
|
146997
|
-
"-p",
|
|
146998
|
-
|
|
146999
|
-
|
|
147000
|
-
|
|
147001
|
-
]
|
|
147002
|
-
});
|
|
147003
|
-
aggregatedUsage = mergeAgentUsage(aggregatedUsage, result.usage);
|
|
147004
|
-
}
|
|
147005
|
-
return { ...result, usage: aggregatedUsage };
|
|
147639
|
+
return runPostRunRetryLoop({
|
|
147640
|
+
initialResult: result,
|
|
147641
|
+
initialUsage: result.usage,
|
|
147642
|
+
stopScript: ctx.stopScript,
|
|
147643
|
+
reflectionPrompt: buildLearningsReflectionPrompt("claude"),
|
|
147644
|
+
canResume: (r) => Boolean(r.sessionId),
|
|
147645
|
+
resume: async (c2) => {
|
|
147646
|
+
const sessionId = c2.previousResult.sessionId;
|
|
147647
|
+
if (!sessionId) throw new Error("unreachable: canResume gated on sessionId");
|
|
147648
|
+
return runClaude({
|
|
147649
|
+
...runParams,
|
|
147650
|
+
args: [...baseArgs, "-p", c2.prompt, "--resume", sessionId]
|
|
147651
|
+
});
|
|
147652
|
+
}
|
|
147653
|
+
});
|
|
147006
147654
|
}
|
|
147007
147655
|
});
|
|
147008
147656
|
|
|
147009
147657
|
// agents/opencode.ts
|
|
147010
147658
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
147011
|
-
import { mkdirSync as
|
|
147012
|
-
import { join as
|
|
147659
|
+
import { mkdirSync as mkdirSync5 } from "node:fs";
|
|
147660
|
+
import { join as join11 } from "node:path";
|
|
147013
147661
|
import { performance as performance7 } from "node:perf_hooks";
|
|
147014
147662
|
async function installOpencodeCli() {
|
|
147015
147663
|
return await installFromNpmTarball({
|
|
@@ -147031,7 +147679,8 @@ function buildSecurityConfig(ctx, model) {
|
|
|
147031
147679
|
},
|
|
147032
147680
|
mcp: {
|
|
147033
147681
|
[pullfrogMcpName]: { type: "remote", url: ctx.mcpServerUrl }
|
|
147034
|
-
}
|
|
147682
|
+
},
|
|
147683
|
+
agent: buildReviewerAgentConfig()
|
|
147035
147684
|
};
|
|
147036
147685
|
if (model) {
|
|
147037
147686
|
config3.model = model;
|
|
@@ -147042,6 +147691,15 @@ function buildSecurityConfig(ctx, model) {
|
|
|
147042
147691
|
}
|
|
147043
147692
|
return JSON.stringify(config3);
|
|
147044
147693
|
}
|
|
147694
|
+
function buildReviewerAgentConfig() {
|
|
147695
|
+
return {
|
|
147696
|
+
[REVIEWER_AGENT_NAME]: {
|
|
147697
|
+
description: "Read-only review subagent for self-review and lens-based code review. Reads only \u2014 no writes, no state-changing shell or MCP calls, no nested subagent dispatch.",
|
|
147698
|
+
mode: "subagent",
|
|
147699
|
+
prompt: REVIEWER_SYSTEM_PROMPT
|
|
147700
|
+
}
|
|
147701
|
+
};
|
|
147702
|
+
}
|
|
147045
147703
|
function getOpenCodeModels(cliPath) {
|
|
147046
147704
|
try {
|
|
147047
147705
|
const output = execFileSync4(cliPath, ["models"], {
|
|
@@ -147090,6 +147748,29 @@ async function runOpenCode(params) {
|
|
|
147090
147748
|
let currentStepId = null;
|
|
147091
147749
|
let currentStepType = null;
|
|
147092
147750
|
let stepHistory = [];
|
|
147751
|
+
const labeler = new SessionLabeler();
|
|
147752
|
+
function eventLabel(event) {
|
|
147753
|
+
const sid = event.sessionID ?? event.session_id;
|
|
147754
|
+
return labeler.labelFor(typeof sid === "string" ? sid : null);
|
|
147755
|
+
}
|
|
147756
|
+
function withLabel(label, message) {
|
|
147757
|
+
return label === ORCHESTRATOR_LABEL ? message : formatWithLabel(label, message);
|
|
147758
|
+
}
|
|
147759
|
+
const taskDispatchByCallID = /* @__PURE__ */ new Map();
|
|
147760
|
+
const pendingTaskDispatches = [];
|
|
147761
|
+
const knownNonTaskCallIDs = /* @__PURE__ */ new Set();
|
|
147762
|
+
function emitSubagentFinished(dispatch, status, output2, matchKind) {
|
|
147763
|
+
const subagentDuration = performance7.now() - dispatch.startedAt;
|
|
147764
|
+
const outputStr = typeof output2 === "string" ? output2 : "";
|
|
147765
|
+
const outputPreview = outputStr.length > 120 ? `${outputStr.slice(0, 120)}\u2026` : outputStr;
|
|
147766
|
+
const matchSuffix = matchKind === "fifo" ? " [fifo-matched]" : "";
|
|
147767
|
+
log.info(
|
|
147768
|
+
`\xBB subagent finished: ${dispatch.label} (${(subagentDuration / 1e3).toFixed(1)}s, status=${status})${matchSuffix}` + (outputPreview ? ` \u2014 ${outputPreview.replace(/\n/g, " ")}` : "")
|
|
147769
|
+
);
|
|
147770
|
+
taskDispatchByCallID.delete(dispatch.toolUseCallID);
|
|
147771
|
+
const idx = pendingTaskDispatches.indexOf(dispatch);
|
|
147772
|
+
if (idx >= 0) pendingTaskDispatches.splice(idx, 1);
|
|
147773
|
+
}
|
|
147093
147774
|
function buildUsage() {
|
|
147094
147775
|
const totalInput = accumulatedTokens.input + accumulatedTokens.cacheRead + accumulatedTokens.cacheWrite;
|
|
147095
147776
|
return totalInput > 0 || accumulatedTokens.output > 0 ? {
|
|
@@ -147103,39 +147784,63 @@ async function runOpenCode(params) {
|
|
|
147103
147784
|
}
|
|
147104
147785
|
const handlers2 = {
|
|
147105
147786
|
init: (event) => {
|
|
147787
|
+
const label = labeler.labelFor(event.session_id ?? null);
|
|
147106
147788
|
log.debug(
|
|
147107
|
-
|
|
147789
|
+
withLabel(
|
|
147790
|
+
label,
|
|
147791
|
+
`\xBB ${params.label} init: session_id=${event.session_id || "unknown"}, model=${event.model || "unknown"}`
|
|
147792
|
+
)
|
|
147108
147793
|
);
|
|
147109
|
-
log.debug(`\xBB ${params.label} init event (full): ${JSON.stringify(event)}`);
|
|
147110
|
-
|
|
147111
|
-
|
|
147112
|
-
|
|
147113
|
-
|
|
147794
|
+
log.debug(withLabel(label, `\xBB ${params.label} init event (full): ${JSON.stringify(event)}`));
|
|
147795
|
+
if (label === ORCHESTRATOR_LABEL) {
|
|
147796
|
+
finalOutput = "";
|
|
147797
|
+
accumulatedTokens = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
147798
|
+
accumulatedCostUsd = 0;
|
|
147799
|
+
tokensLogged = false;
|
|
147800
|
+
} else {
|
|
147801
|
+
log.info(`\xBB ${params.label} subagent init: ${label} (session ${event.session_id || "?"})`);
|
|
147802
|
+
}
|
|
147114
147803
|
},
|
|
147115
147804
|
message: (event) => {
|
|
147805
|
+
const label = eventLabel(event);
|
|
147116
147806
|
if (event.role === "assistant" && event.content?.trim()) {
|
|
147117
147807
|
const message = event.content.trim();
|
|
147118
147808
|
if (event.delta) {
|
|
147119
147809
|
log.debug(
|
|
147120
|
-
|
|
147810
|
+
withLabel(
|
|
147811
|
+
label,
|
|
147812
|
+
`\xBB ${params.label} thinking: ${message.substring(0, 300)}${message.length > 300 ? "..." : ""}`
|
|
147813
|
+
)
|
|
147121
147814
|
);
|
|
147122
147815
|
} else {
|
|
147123
147816
|
log.debug(
|
|
147124
|
-
|
|
147817
|
+
withLabel(
|
|
147818
|
+
label,
|
|
147819
|
+
`\xBB ${params.label} message (${event.role}): ${message.substring(0, 100)}${message.length > 100 ? "..." : ""}`
|
|
147820
|
+
)
|
|
147125
147821
|
);
|
|
147126
|
-
|
|
147822
|
+
if (label === ORCHESTRATOR_LABEL) {
|
|
147823
|
+
finalOutput = message;
|
|
147824
|
+
}
|
|
147127
147825
|
}
|
|
147128
147826
|
} else if (event.role === "user") {
|
|
147129
147827
|
log.debug(
|
|
147130
|
-
|
|
147828
|
+
withLabel(
|
|
147829
|
+
label,
|
|
147830
|
+
`\xBB ${params.label} message (${event.role}): ${event.content?.substring(0, 100) || ""}${event.content && event.content.length > 100 ? "..." : ""}`
|
|
147831
|
+
)
|
|
147131
147832
|
);
|
|
147132
147833
|
}
|
|
147133
147834
|
},
|
|
147134
147835
|
text: (event) => {
|
|
147135
147836
|
if (event.part?.text?.trim()) {
|
|
147136
147837
|
const message = event.part.text.trim();
|
|
147137
|
-
|
|
147138
|
-
|
|
147838
|
+
const label = eventLabel(event);
|
|
147839
|
+
const boxTitle = label === ORCHESTRATOR_LABEL ? params.label : `${params.label} [${label}]`;
|
|
147840
|
+
log.box(message, { title: boxTitle });
|
|
147841
|
+
if (label === ORCHESTRATOR_LABEL) {
|
|
147842
|
+
finalOutput = message;
|
|
147843
|
+
}
|
|
147139
147844
|
}
|
|
147140
147845
|
},
|
|
147141
147846
|
step_start: (event) => {
|
|
@@ -147171,6 +147876,23 @@ async function runOpenCode(params) {
|
|
|
147171
147876
|
);
|
|
147172
147877
|
return;
|
|
147173
147878
|
}
|
|
147879
|
+
if (toolName === "task") {
|
|
147880
|
+
const taskInput = event.part?.state?.input ?? {};
|
|
147881
|
+
const dispatchedLabel = labeler.recordTaskDispatch(taskInput);
|
|
147882
|
+
const dispatch = {
|
|
147883
|
+
label: dispatchedLabel,
|
|
147884
|
+
startedAt: performance7.now(),
|
|
147885
|
+
toolUseCallID: toolId
|
|
147886
|
+
};
|
|
147887
|
+
taskDispatchByCallID.set(toolId, dispatch);
|
|
147888
|
+
pendingTaskDispatches.push(dispatch);
|
|
147889
|
+
log.info(
|
|
147890
|
+
`\xBB dispatching subagent: ${dispatchedLabel}` + (taskInput.subagent_type ? ` (subagent_type=${taskInput.subagent_type})` : "")
|
|
147891
|
+
);
|
|
147892
|
+
} else {
|
|
147893
|
+
knownNonTaskCallIDs.add(toolId);
|
|
147894
|
+
}
|
|
147895
|
+
const label = eventLabel(event);
|
|
147174
147896
|
if (stepHistory.length > 0) {
|
|
147175
147897
|
stepHistory[stepHistory.length - 1].toolCalls.push(toolName);
|
|
147176
147898
|
}
|
|
@@ -147181,9 +147903,11 @@ async function runOpenCode(params) {
|
|
|
147181
147903
|
});
|
|
147182
147904
|
}
|
|
147183
147905
|
thinkingTimer.markToolCall();
|
|
147184
|
-
|
|
147906
|
+
const inputFormatted = formatJsonValue(event.part?.state?.input || {});
|
|
147907
|
+
const toolCallLine = inputFormatted !== "{}" ? `\xBB ${toolName}(${inputFormatted})` : `\xBB ${toolName}()`;
|
|
147908
|
+
log.info(withLabel(label, toolCallLine));
|
|
147185
147909
|
if (event.part?.state?.status === "completed" && event.part.state.output) {
|
|
147186
|
-
log.debug(` output: ${event.part.state.output}`);
|
|
147910
|
+
log.debug(withLabel(label, ` output: ${event.part.state.output}`));
|
|
147187
147911
|
}
|
|
147188
147912
|
if (toolName.includes("report_progress") && params.todoTracker) {
|
|
147189
147913
|
log.debug("\xBB report_progress detected, disabling todo tracking");
|
|
@@ -147197,7 +147921,20 @@ async function runOpenCode(params) {
|
|
|
147197
147921
|
const toolId = event.part?.callID || event.tool_id;
|
|
147198
147922
|
const status = event.part?.state?.status || event.status || "unknown";
|
|
147199
147923
|
const output2 = event.part?.state?.output || event.output;
|
|
147924
|
+
const label = eventLabel(event);
|
|
147200
147925
|
thinkingTimer.markToolResult();
|
|
147926
|
+
if (taskDispatchByCallID.size > 0 || pendingTaskDispatches.length > 0) {
|
|
147927
|
+
if (toolId && taskDispatchByCallID.has(toolId)) {
|
|
147928
|
+
const dispatch = taskDispatchByCallID.get(toolId);
|
|
147929
|
+
if (dispatch) emitSubagentFinished(dispatch, status, output2, "exact");
|
|
147930
|
+
} else {
|
|
147931
|
+
const callIDIsKnownNonTask = toolId ? knownNonTaskCallIDs.has(toolId) : false;
|
|
147932
|
+
if (!callIDIsKnownNonTask && pendingTaskDispatches.length > 0) {
|
|
147933
|
+
const dispatch = pendingTaskDispatches[0];
|
|
147934
|
+
emitSubagentFinished(dispatch, status, output2, "fifo");
|
|
147935
|
+
}
|
|
147936
|
+
}
|
|
147937
|
+
}
|
|
147201
147938
|
if (toolId) {
|
|
147202
147939
|
const toolStartTime = toolCallTimings.get(toolId);
|
|
147203
147940
|
if (toolStartTime) {
|
|
@@ -147205,24 +147942,35 @@ async function runOpenCode(params) {
|
|
|
147205
147942
|
toolCallTimings.delete(toolId);
|
|
147206
147943
|
const stepContext = currentStepId ? ` (step=${currentStepType || "unknown"})` : "";
|
|
147207
147944
|
log.debug(
|
|
147208
|
-
|
|
147945
|
+
withLabel(
|
|
147946
|
+
label,
|
|
147947
|
+
`\xBB ${params.label} tool_result${stepContext}: id=${toolId}, status=${status}, duration=${Math.round(toolDuration)}ms`
|
|
147948
|
+
)
|
|
147209
147949
|
);
|
|
147210
147950
|
if (output2) {
|
|
147211
|
-
log.debug(
|
|
147951
|
+
log.debug(
|
|
147952
|
+
withLabel(
|
|
147953
|
+
label,
|
|
147954
|
+
` output: ${typeof output2 === "string" ? output2 : JSON.stringify(output2)}`
|
|
147955
|
+
)
|
|
147956
|
+
);
|
|
147212
147957
|
}
|
|
147213
147958
|
if (toolDuration > 5e3) {
|
|
147214
147959
|
log.info(
|
|
147215
|
-
|
|
147960
|
+
withLabel(
|
|
147961
|
+
label,
|
|
147962
|
+
`\xBB tool call took ${(toolDuration / 1e3).toFixed(1)}s - may indicate network latency`
|
|
147963
|
+
)
|
|
147216
147964
|
);
|
|
147217
147965
|
}
|
|
147218
147966
|
}
|
|
147219
147967
|
}
|
|
147220
147968
|
if (status === "error") {
|
|
147221
147969
|
const errorMsg = typeof output2 === "string" ? output2 : JSON.stringify(output2);
|
|
147222
|
-
log.info(`\xBB tool call failed: ${errorMsg}`);
|
|
147970
|
+
log.info(withLabel(label, `\xBB tool call failed: ${errorMsg}`));
|
|
147223
147971
|
} else if (output2) {
|
|
147224
147972
|
const outputStr = typeof output2 === "string" ? output2 : JSON.stringify(output2);
|
|
147225
|
-
log.debug(`tool output: ${outputStr}`);
|
|
147973
|
+
log.debug(withLabel(label, `tool output: ${outputStr}`));
|
|
147226
147974
|
}
|
|
147227
147975
|
},
|
|
147228
147976
|
result: async (event) => {
|
|
@@ -147319,6 +148067,16 @@ async function runOpenCode(params) {
|
|
|
147319
148067
|
} else {
|
|
147320
148068
|
params.todoTracker?.cancel();
|
|
147321
148069
|
}
|
|
148070
|
+
if (pendingTaskDispatches.length > 0) {
|
|
148071
|
+
for (const dispatch of [...pendingTaskDispatches]) {
|
|
148072
|
+
const elapsed = performance7.now() - dispatch.startedAt;
|
|
148073
|
+
log.info(
|
|
148074
|
+
`\xBB subagent finished (inferred at run-end): ${dispatch.label} (\u2264${(elapsed / 1e3).toFixed(1)}s) \u2014 no matching tool_result observed; subagent reply likely arrived via assistant message`
|
|
148075
|
+
);
|
|
148076
|
+
}
|
|
148077
|
+
pendingTaskDispatches.length = 0;
|
|
148078
|
+
taskDispatchByCallID.clear();
|
|
148079
|
+
}
|
|
147322
148080
|
const duration4 = performance7.now() - startTime;
|
|
147323
148081
|
log.info(
|
|
147324
148082
|
`\xBB ${params.label} completed in ${Math.round(duration4)}ms with exit code ${result.exitCode}`
|
|
@@ -147386,9 +148144,9 @@ var opencode = agent({
|
|
|
147386
148144
|
const model = ctx.payload.proxyModel ?? ctx.resolvedModel ?? autoSelectModel(cliPath);
|
|
147387
148145
|
const homeEnv = {
|
|
147388
148146
|
HOME: ctx.tmpdir,
|
|
147389
|
-
XDG_CONFIG_HOME:
|
|
148147
|
+
XDG_CONFIG_HOME: join11(ctx.tmpdir, ".config")
|
|
147390
148148
|
};
|
|
147391
|
-
|
|
148149
|
+
mkdirSync5(join11(homeEnv.XDG_CONFIG_HOME, "opencode"), { recursive: true });
|
|
147392
148150
|
const agentBrowserVersion = getDevDependencyVersion("agent-browser");
|
|
147393
148151
|
addSkill({
|
|
147394
148152
|
ref: `vercel-labs/agent-browser@v${agentBrowserVersion}`,
|
|
@@ -147396,6 +148154,7 @@ var opencode = agent({
|
|
|
147396
148154
|
env: homeEnv,
|
|
147397
148155
|
agent: "opencode"
|
|
147398
148156
|
});
|
|
148157
|
+
installBundledSkills({ home: homeEnv.HOME });
|
|
147399
148158
|
const baseArgs = ["run", "--format", "json", "--print-logs"];
|
|
147400
148159
|
const permissionOverride = JSON.stringify({
|
|
147401
148160
|
external_directory: { "*": "deny", "/tmp/*": "allow" }
|
|
@@ -147419,24 +148178,20 @@ var opencode = agent({
|
|
|
147419
148178
|
onActivityTimeout: ctx.onActivityTimeout,
|
|
147420
148179
|
onToolUse: ctx.onToolUse
|
|
147421
148180
|
};
|
|
147422
|
-
|
|
148181
|
+
const result = await runOpenCode({
|
|
147423
148182
|
...runParams,
|
|
147424
148183
|
args: [...baseArgs, ctx.instructions.full]
|
|
147425
148184
|
});
|
|
147426
|
-
|
|
147427
|
-
|
|
147428
|
-
|
|
147429
|
-
|
|
147430
|
-
|
|
147431
|
-
|
|
147432
|
-
${status}`);
|
|
147433
|
-
result = await runOpenCode({
|
|
148185
|
+
return runPostRunRetryLoop({
|
|
148186
|
+
initialResult: result,
|
|
148187
|
+
initialUsage: result.usage,
|
|
148188
|
+
stopScript: ctx.stopScript,
|
|
148189
|
+
reflectionPrompt: buildLearningsReflectionPrompt("opencode"),
|
|
148190
|
+
resume: async (c2) => runOpenCode({
|
|
147434
148191
|
...runParams,
|
|
147435
|
-
args: [...baseArgs, "--continue",
|
|
147436
|
-
})
|
|
147437
|
-
|
|
147438
|
-
}
|
|
147439
|
-
return { ...result, usage: aggregatedUsage };
|
|
148192
|
+
args: [...baseArgs, "--continue", c2.prompt]
|
|
148193
|
+
})
|
|
148194
|
+
});
|
|
147440
148195
|
}
|
|
147441
148196
|
});
|
|
147442
148197
|
|
|
@@ -147624,7 +148379,7 @@ async function fetchBodyHtml(ctx) {
|
|
|
147624
148379
|
var core2 = __toESM(require_core(), 1);
|
|
147625
148380
|
import { createSign } from "node:crypto";
|
|
147626
148381
|
import { rename, writeFile } from "node:fs/promises";
|
|
147627
|
-
import { dirname as
|
|
148382
|
+
import { dirname as dirname3, join as join12 } from "node:path";
|
|
147628
148383
|
|
|
147629
148384
|
// node_modules/.pnpm/@octokit+plugin-throttling@11.0.3_@octokit+core@7.0.5/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
|
|
147630
148385
|
var import_light = __toESM(require_light(), 1);
|
|
@@ -151456,7 +152211,7 @@ function getGitHubUsageSummary() {
|
|
|
151456
152211
|
}
|
|
151457
152212
|
async function writeGitHubUsageSummaryToFile(path3) {
|
|
151458
152213
|
const summary2 = getGitHubUsageSummary();
|
|
151459
|
-
const tmpPath =
|
|
152214
|
+
const tmpPath = join12(dirname3(path3), `.usage-summary-${process.pid}.tmp`);
|
|
151460
152215
|
await writeFile(tmpPath, JSON.stringify(summary2));
|
|
151461
152216
|
await rename(tmpPath, path3);
|
|
151462
152217
|
}
|
|
@@ -151614,8 +152369,8 @@ async function reportErrorToComment(ctx) {
|
|
|
151614
152369
|
const formattedError = ctx.title ? `${ctx.title}
|
|
151615
152370
|
|
|
151616
152371
|
${ctx.error}` : ctx.error;
|
|
151617
|
-
const
|
|
151618
|
-
if (!
|
|
152372
|
+
const comment = ctx.toolState.progressComment;
|
|
152373
|
+
if (!comment) {
|
|
151619
152374
|
return;
|
|
151620
152375
|
}
|
|
151621
152376
|
const repoContext = parseRepoContext();
|
|
@@ -151634,20 +152389,19 @@ ${ctx.error}` : ctx.error;
|
|
|
151634
152389
|
customParts,
|
|
151635
152390
|
model: ctx.toolState.model
|
|
151636
152391
|
});
|
|
151637
|
-
await
|
|
151638
|
-
owner: repoContext.owner,
|
|
151639
|
-
|
|
151640
|
-
|
|
151641
|
-
|
|
151642
|
-
});
|
|
152392
|
+
await updateProgressComment(
|
|
152393
|
+
{ octokit, owner: repoContext.owner, repo: repoContext.name },
|
|
152394
|
+
comment,
|
|
152395
|
+
`${formattedError}${footer}`
|
|
152396
|
+
);
|
|
151643
152397
|
ctx.toolState.wasUpdated = true;
|
|
151644
152398
|
}
|
|
151645
152399
|
|
|
151646
152400
|
// utils/gitAuthServer.ts
|
|
151647
152401
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
151648
|
-
import { writeFileSync as
|
|
152402
|
+
import { writeFileSync as writeFileSync8 } from "node:fs";
|
|
151649
152403
|
import { createServer as createServer2 } from "node:http";
|
|
151650
|
-
import { join as
|
|
152404
|
+
import { join as join13 } from "node:path";
|
|
151651
152405
|
var CODE_TTL_MS = 5 * 60 * 1e3;
|
|
151652
152406
|
var TAMPER_WINDOW_MS = 6e4;
|
|
151653
152407
|
function revokeGitHubToken(token) {
|
|
@@ -151719,7 +152473,7 @@ async function startGitAuthServer(tmpdir3) {
|
|
|
151719
152473
|
function writeAskpassScript(code) {
|
|
151720
152474
|
const scriptId = randomUUID3();
|
|
151721
152475
|
const scriptName = `askpass-${scriptId}.js`;
|
|
151722
|
-
const scriptPath =
|
|
152476
|
+
const scriptPath = join13(tmpdir3, scriptName);
|
|
151723
152477
|
const content = [
|
|
151724
152478
|
`#!/usr/bin/env node`,
|
|
151725
152479
|
`var a=process.argv[2]||"";`,
|
|
@@ -151734,7 +152488,7 @@ async function startGitAuthServer(tmpdir3) {
|
|
|
151734
152488
|
`try{require("fs").unlinkSync("${scriptPath.replace(/\\/g, "\\\\")}")}catch(e){}`,
|
|
151735
152489
|
`})}).on("error",function(){process.exit(1)})}`
|
|
151736
152490
|
].join("\n");
|
|
151737
|
-
|
|
152491
|
+
writeFileSync8(scriptPath, content, { mode: 448 });
|
|
151738
152492
|
return scriptPath;
|
|
151739
152493
|
}
|
|
151740
152494
|
async function close() {
|
|
@@ -152139,7 +152893,10 @@ var JsonPayload = type({
|
|
|
152139
152893
|
"eventInstructions?": "string",
|
|
152140
152894
|
"event?": "object",
|
|
152141
152895
|
"timeout?": "string | undefined",
|
|
152142
|
-
"
|
|
152896
|
+
"progressComment?": type({
|
|
152897
|
+
id: "string",
|
|
152898
|
+
type: "'issue' | 'review'"
|
|
152899
|
+
}).or("undefined")
|
|
152143
152900
|
});
|
|
152144
152901
|
var COLLABORATOR_PERMISSIONS = ["admin", "maintain", "write"];
|
|
152145
152902
|
function isCollaborator(event) {
|
|
@@ -152221,7 +152978,7 @@ function resolvePayload(resolvedPromptInput, repoSettings) {
|
|
|
152221
152978
|
event,
|
|
152222
152979
|
timeout: inputs.timeout ?? jsonPayload?.timeout,
|
|
152223
152980
|
cwd: resolveCwd(inputs.cwd),
|
|
152224
|
-
|
|
152981
|
+
progressComment: jsonPayload?.progressComment,
|
|
152225
152982
|
// permissions: inputs > repoSettings > fallbacks
|
|
152226
152983
|
push: inputs.push ?? repoSettings.push ?? "restricted",
|
|
152227
152984
|
shell: resolvedShell,
|
|
@@ -152304,7 +153061,9 @@ async function handleAgentResult(ctx) {
|
|
|
152304
153061
|
output: ctx.result.output
|
|
152305
153062
|
};
|
|
152306
153063
|
}
|
|
152307
|
-
|
|
153064
|
+
const mode = ctx.toolState.selectedMode;
|
|
153065
|
+
const isReviewMode = mode === "Review" || mode === "IncrementalReview";
|
|
153066
|
+
if (!isReviewMode && !ctx.toolState.wasUpdated && ctx.toolState.hadProgressComment && !ctx.silent) {
|
|
152308
153067
|
const error49 = ctx.result.error || "agent completed without reporting progress";
|
|
152309
153068
|
try {
|
|
152310
153069
|
await reportErrorToComment({
|
|
@@ -152327,16 +153086,17 @@ async function handleAgentResult(ctx) {
|
|
|
152327
153086
|
};
|
|
152328
153087
|
}
|
|
152329
153088
|
|
|
152330
|
-
// utils/runContextData.ts
|
|
152331
|
-
var core5 = __toESM(require_core(), 1);
|
|
152332
|
-
|
|
152333
153089
|
// utils/runContext.ts
|
|
153090
|
+
function isInfraCovered(params) {
|
|
153091
|
+
return params.isOss || params.plan === "payg";
|
|
153092
|
+
}
|
|
152334
153093
|
var defaultSettings = {
|
|
152335
153094
|
model: null,
|
|
152336
153095
|
modes: [],
|
|
152337
153096
|
setupScript: null,
|
|
152338
153097
|
postCheckoutScript: null,
|
|
152339
153098
|
prepushScript: null,
|
|
153099
|
+
stopScript: null,
|
|
152340
153100
|
push: "restricted",
|
|
152341
153101
|
shell: "restricted",
|
|
152342
153102
|
prApproveEnabled: false,
|
|
@@ -152347,7 +153107,8 @@ var defaultSettings = {
|
|
|
152347
153107
|
var defaultRunContext = {
|
|
152348
153108
|
settings: defaultSettings,
|
|
152349
153109
|
apiToken: "",
|
|
152350
|
-
oss: false
|
|
153110
|
+
oss: false,
|
|
153111
|
+
plan: "none"
|
|
152351
153112
|
};
|
|
152352
153113
|
async function fetchRunContext(params) {
|
|
152353
153114
|
const timeoutMs = 3e4;
|
|
@@ -152381,10 +153142,12 @@ async function fetchRunContext(params) {
|
|
|
152381
153142
|
modes: data.settings?.modes ?? [],
|
|
152382
153143
|
setupScript: data.settings?.setupScript ?? null,
|
|
152383
153144
|
postCheckoutScript: data.settings?.postCheckoutScript ?? null,
|
|
152384
|
-
prepushScript: data.settings?.prepushScript ?? null
|
|
153145
|
+
prepushScript: data.settings?.prepushScript ?? null,
|
|
153146
|
+
stopScript: data.settings?.stopScript ?? null
|
|
152385
153147
|
},
|
|
152386
153148
|
apiToken: data.apiToken,
|
|
152387
153149
|
oss: data.oss ?? false,
|
|
153150
|
+
plan: data.plan ?? "none",
|
|
152388
153151
|
proxyModel: data.proxyModel,
|
|
152389
153152
|
dbSecrets: data.dbSecrets
|
|
152390
153153
|
};
|
|
@@ -152395,6 +153158,7 @@ async function fetchRunContext(params) {
|
|
|
152395
153158
|
}
|
|
152396
153159
|
|
|
152397
153160
|
// utils/runContextData.ts
|
|
153161
|
+
var core5 = __toESM(require_core(), 1);
|
|
152398
153162
|
async function resolveRunContextData(params) {
|
|
152399
153163
|
log.info(`\xBB running Pullfrog v${package_default.version}...`);
|
|
152400
153164
|
const repoContext = parseRepoContext();
|
|
@@ -152416,6 +153180,7 @@ async function resolveRunContextData(params) {
|
|
|
152416
153180
|
repoSettings: runContext.settings,
|
|
152417
153181
|
apiToken: runContext.apiToken,
|
|
152418
153182
|
oss: runContext.oss,
|
|
153183
|
+
plan: runContext.plan,
|
|
152419
153184
|
proxyModel: runContext.proxyModel,
|
|
152420
153185
|
dbSecrets: runContext.dbSecrets
|
|
152421
153186
|
};
|
|
@@ -152425,9 +153190,9 @@ async function resolveRunContextData(params) {
|
|
|
152425
153190
|
import { execFileSync as execFileSync5, execSync as execSync3 } from "node:child_process";
|
|
152426
153191
|
import { mkdtempSync } from "node:fs";
|
|
152427
153192
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
152428
|
-
import { join as
|
|
153193
|
+
import { join as join14 } from "node:path";
|
|
152429
153194
|
function createTempDirectory() {
|
|
152430
|
-
const sharedTempDir = mkdtempSync(
|
|
153195
|
+
const sharedTempDir = mkdtempSync(join14(tmpdir2(), "pullfrog-"));
|
|
152431
153196
|
process.env.PULLFROG_TEMP_DIR = sharedTempDir;
|
|
152432
153197
|
log.info(`\xBB created temp dir at ${sharedTempDir}`);
|
|
152433
153198
|
return sharedTempDir;
|
|
@@ -152742,6 +153507,59 @@ function resolveAgentForLog(ctx) {
|
|
|
152742
153507
|
}
|
|
152743
153508
|
return ctx.agentName;
|
|
152744
153509
|
}
|
|
153510
|
+
var BillingError = class extends Error {
|
|
153511
|
+
code;
|
|
153512
|
+
declineCode;
|
|
153513
|
+
needsReauthentication;
|
|
153514
|
+
constructor(message, opts = {}) {
|
|
153515
|
+
super(message);
|
|
153516
|
+
this.name = "BillingError";
|
|
153517
|
+
this.code = opts.code ?? null;
|
|
153518
|
+
this.declineCode = opts.declineCode ?? null;
|
|
153519
|
+
this.needsReauthentication = opts.needsReauthentication ?? false;
|
|
153520
|
+
}
|
|
153521
|
+
};
|
|
153522
|
+
var TransientError = class extends Error {
|
|
153523
|
+
constructor(message) {
|
|
153524
|
+
super(message);
|
|
153525
|
+
this.name = "TransientError";
|
|
153526
|
+
}
|
|
153527
|
+
};
|
|
153528
|
+
function formatBillingErrorSummary(error49) {
|
|
153529
|
+
if (error49.code === "router_requires_card") {
|
|
153530
|
+
return [
|
|
153531
|
+
"### \u26D4 Pullfrog Router requires a card",
|
|
153532
|
+
"",
|
|
153533
|
+
"This run was going to use Pullfrog Router, which bills at raw OpenRouter cost and needs a card on file. Runs won't proceed until a card is added.",
|
|
153534
|
+
"",
|
|
153535
|
+
"[Add a card \u2192](https://pullfrog.com/console#model-access) \u2014 your first $20 of Router usage is free."
|
|
153536
|
+
].join("\n");
|
|
153537
|
+
}
|
|
153538
|
+
if (error49.needsReauthentication) {
|
|
153539
|
+
return [
|
|
153540
|
+
"### \u274C Pullfrog billing error \u2014 card requires 3DS on every charge",
|
|
153541
|
+
"",
|
|
153542
|
+
`Your card issuer requires a 3D Secure challenge on each off-session charge (\`${error49.declineCode ?? "authentication_required"}\`), which we can't run from the agent. Top up your Router credit balance manually \u2014 3DS runs interactively in Stripe Checkout, and subsequent runs draw from the prepaid balance without triggering another off-session charge.`,
|
|
153543
|
+
"",
|
|
153544
|
+
"[Top up your Router credit balance \u2192](https://pullfrog.com/console)"
|
|
153545
|
+
].join("\n");
|
|
153546
|
+
}
|
|
153547
|
+
const codeSuffix = error49.declineCode ? ` (\`${error49.declineCode}\`)` : "";
|
|
153548
|
+
return `### \u274C Pullfrog billing error
|
|
153549
|
+
|
|
153550
|
+
${error49.message}${codeSuffix}
|
|
153551
|
+
|
|
153552
|
+
[Manage billing \u2192](https://pullfrog.com/console)`;
|
|
153553
|
+
}
|
|
153554
|
+
function formatTransientErrorSummary(error49) {
|
|
153555
|
+
return [
|
|
153556
|
+
"### \u26A0\uFE0F Pullfrog temporarily unavailable",
|
|
153557
|
+
"",
|
|
153558
|
+
error49.message,
|
|
153559
|
+
"",
|
|
153560
|
+
"This is typically transient \u2014 the next dispatch should succeed. If it persists, check [status.pullfrog.com](https://status.pullfrog.com)."
|
|
153561
|
+
].join("\n");
|
|
153562
|
+
}
|
|
152745
153563
|
async function mintProxyKey(ctx) {
|
|
152746
153564
|
try {
|
|
152747
153565
|
process.env.ACTIONS_ID_TOKEN_REQUEST_URL = ctx.oidcCredentials.requestUrl;
|
|
@@ -152754,6 +153572,20 @@ async function mintProxyKey(ctx) {
|
|
|
152754
153572
|
method: "POST",
|
|
152755
153573
|
headers: { Authorization: `Bearer ${oidcToken}` }
|
|
152756
153574
|
});
|
|
153575
|
+
if (response.status === 402) {
|
|
153576
|
+
const body = await response.json().catch(() => null);
|
|
153577
|
+
throw new BillingError(body?.error ?? "insufficient balance", {
|
|
153578
|
+
code: body?.code ?? null,
|
|
153579
|
+
declineCode: body?.declineCode ?? null,
|
|
153580
|
+
needsReauthentication: body?.needsReauthentication ?? false
|
|
153581
|
+
});
|
|
153582
|
+
}
|
|
153583
|
+
if (response.status === 503) {
|
|
153584
|
+
const body = await response.json().catch(() => null);
|
|
153585
|
+
throw new TransientError(
|
|
153586
|
+
body?.error ?? "billing service temporarily unavailable \u2014 retry shortly"
|
|
153587
|
+
);
|
|
153588
|
+
}
|
|
152757
153589
|
if (!response.ok) {
|
|
152758
153590
|
log.warning(`proxy key mint failed (${response.status})`);
|
|
152759
153591
|
return null;
|
|
@@ -152761,6 +153593,8 @@ async function mintProxyKey(ctx) {
|
|
|
152761
153593
|
const data = await response.json();
|
|
152762
153594
|
return data.key;
|
|
152763
153595
|
} catch (error49) {
|
|
153596
|
+
if (error49 instanceof BillingError) throw error49;
|
|
153597
|
+
if (error49 instanceof TransientError) throw error49;
|
|
152764
153598
|
log.warning(`proxy key mint error: ${error49 instanceof Error ? error49.message : String(error49)}`);
|
|
152765
153599
|
return null;
|
|
152766
153600
|
} finally {
|
|
@@ -152770,19 +153604,19 @@ async function mintProxyKey(ctx) {
|
|
|
152770
153604
|
}
|
|
152771
153605
|
async function resolveProxyModel(ctx) {
|
|
152772
153606
|
if (process.env.PULLFROG_MODEL?.trim()) return;
|
|
152773
|
-
|
|
152774
|
-
|
|
152775
|
-
|
|
152776
|
-
|
|
152777
|
-
}
|
|
152778
|
-
const key = await mintProxyKey({ oidcCredentials: ctx.oidcCredentials });
|
|
152779
|
-
if (!key) return;
|
|
152780
|
-
process.env.OPENROUTER_API_KEY = key;
|
|
152781
|
-
core6.setSecret(key);
|
|
152782
|
-
ctx.payload.proxyModel = ctx.proxyModel;
|
|
152783
|
-
log.info(`\xBB proxy: oss \u2192 ${ctx.proxyModel}`);
|
|
153607
|
+
const needsProxy = isInfraCovered({ isOss: ctx.oss, plan: ctx.plan }) && ctx.proxyModel;
|
|
153608
|
+
if (!needsProxy) return;
|
|
153609
|
+
if (!ctx.oidcCredentials) {
|
|
153610
|
+
log.warning("\xBB proxy requested but no OIDC credentials available \u2014 skipping");
|
|
152784
153611
|
return;
|
|
152785
153612
|
}
|
|
153613
|
+
const key = await mintProxyKey({ oidcCredentials: ctx.oidcCredentials });
|
|
153614
|
+
if (!key) return;
|
|
153615
|
+
process.env.OPENROUTER_API_KEY = key;
|
|
153616
|
+
core6.setSecret(key);
|
|
153617
|
+
ctx.payload.proxyModel = ctx.proxyModel;
|
|
153618
|
+
const label = ctx.oss ? "oss" : "router";
|
|
153619
|
+
log.info(`\xBB proxy: ${label} \u2192 ${ctx.proxyModel}`);
|
|
152786
153620
|
}
|
|
152787
153621
|
async function writeJobSummary(toolState) {
|
|
152788
153622
|
const usageSummary = formatUsageSummary(toolState.usageEntries);
|
|
@@ -152804,7 +153638,7 @@ async function main() {
|
|
|
152804
153638
|
let safetyNetTimer;
|
|
152805
153639
|
const resolvedPromptInput = resolvePromptInput();
|
|
152806
153640
|
const toolState = initToolState({
|
|
152807
|
-
|
|
153641
|
+
progressComment: typeof resolvedPromptInput !== "string" ? resolvedPromptInput.progressComment : void 0
|
|
152808
153642
|
});
|
|
152809
153643
|
resolveGit();
|
|
152810
153644
|
const jobToken = getJobToken();
|
|
@@ -152838,12 +153672,33 @@ async function main() {
|
|
|
152838
153672
|
delete process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
|
|
152839
153673
|
delete process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
|
152840
153674
|
}
|
|
152841
|
-
|
|
152842
|
-
|
|
152843
|
-
|
|
152844
|
-
|
|
152845
|
-
|
|
152846
|
-
|
|
153675
|
+
try {
|
|
153676
|
+
await resolveProxyModel({
|
|
153677
|
+
payload,
|
|
153678
|
+
oss: runContext.oss,
|
|
153679
|
+
plan: runContext.plan,
|
|
153680
|
+
proxyModel: runContext.proxyModel,
|
|
153681
|
+
oidcCredentials
|
|
153682
|
+
});
|
|
153683
|
+
} catch (error49) {
|
|
153684
|
+
if (error49 instanceof BillingError) {
|
|
153685
|
+
const summary2 = formatBillingErrorSummary(error49);
|
|
153686
|
+
await writeSummary(summary2).catch(() => {
|
|
153687
|
+
});
|
|
153688
|
+
await reportErrorToComment({ toolState, error: summary2 }).catch(() => {
|
|
153689
|
+
});
|
|
153690
|
+
throw error49;
|
|
153691
|
+
}
|
|
153692
|
+
if (error49 instanceof TransientError) {
|
|
153693
|
+
const summary2 = formatTransientErrorSummary(error49);
|
|
153694
|
+
await writeSummary(summary2).catch(() => {
|
|
153695
|
+
});
|
|
153696
|
+
await reportErrorToComment({ toolState, error: summary2 }).catch(() => {
|
|
153697
|
+
});
|
|
153698
|
+
throw error49;
|
|
153699
|
+
}
|
|
153700
|
+
throw error49;
|
|
153701
|
+
}
|
|
152847
153702
|
const octokit = createOctokit(tokenRef.mcpToken);
|
|
152848
153703
|
const runInfo = await resolveRun({ octokit });
|
|
152849
153704
|
let toolContext;
|
|
@@ -152917,6 +153772,8 @@ async function main() {
|
|
|
152917
153772
|
jobId: runInfo.jobId,
|
|
152918
153773
|
mcpServerUrl: "",
|
|
152919
153774
|
tmpdir: tmpdir3,
|
|
153775
|
+
oss: runContext.oss,
|
|
153776
|
+
plan: runContext.plan,
|
|
152920
153777
|
resolvedModel
|
|
152921
153778
|
};
|
|
152922
153779
|
const mcpHttpServer = __using(_stack, await startMcpHttpServer(toolContext, { outputSchema }), true);
|
|
@@ -152954,8 +153811,8 @@ ${instructions.user}` : null,
|
|
|
152954
153811
|
log.info(instructions.full);
|
|
152955
153812
|
});
|
|
152956
153813
|
if (agentId === "opencode") {
|
|
152957
|
-
const pluginDir =
|
|
152958
|
-
const hasPlugins =
|
|
153814
|
+
const pluginDir = join15(process.cwd(), ".opencode", "plugin");
|
|
153815
|
+
const hasPlugins = existsSync7(pluginDir) && readdirSync(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
|
|
152959
153816
|
if (hasPlugins && toolState.dependencyInstallation?.promise) {
|
|
152960
153817
|
log.info(
|
|
152961
153818
|
"\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"
|
|
@@ -152980,6 +153837,9 @@ ${instructions.user}` : null,
|
|
|
152980
153837
|
}
|
|
152981
153838
|
});
|
|
152982
153839
|
toolState.todoTracker = todoTracker;
|
|
153840
|
+
onExitSignal(() => {
|
|
153841
|
+
todoTracker?.cancel();
|
|
153842
|
+
});
|
|
152983
153843
|
let innerTimeoutFired = false;
|
|
152984
153844
|
const onInnerActivityTimeout = () => {
|
|
152985
153845
|
if (innerTimeoutFired) return;
|
|
@@ -153009,6 +153869,7 @@ ${instructions.user}` : null,
|
|
|
153009
153869
|
tmpdir: tmpdir3,
|
|
153010
153870
|
instructions,
|
|
153011
153871
|
todoTracker,
|
|
153872
|
+
stopScript: runContext.repoSettings.stopScript,
|
|
153012
153873
|
onActivityTimeout: onInnerActivityTimeout,
|
|
153013
153874
|
onToolUse: (event) => {
|
|
153014
153875
|
const wasTracked = recordDiffReadFromToolUse({
|
|
@@ -153063,13 +153924,8 @@ ${instructions.user}` : null,
|
|
|
153063
153924
|
log.debug(`post-review cleanup failed: ${error49}`);
|
|
153064
153925
|
});
|
|
153065
153926
|
}
|
|
153066
|
-
if (toolContext && toolState.review && toolState.progressCommentId) {
|
|
153067
|
-
await deleteProgressComment(toolContext).catch((error49) => {
|
|
153068
|
-
log.debug(`review progress comment cleanup failed: ${error49}`);
|
|
153069
|
-
});
|
|
153070
|
-
}
|
|
153071
153927
|
const trackerWasLastWriter = todoTracker?.hasPublished && !toolState.finalSummaryWritten;
|
|
153072
|
-
if (toolContext && toolState.
|
|
153928
|
+
if (toolContext && toolState.progressComment && (!toolState.wasUpdated || trackerWasLastWriter)) {
|
|
153073
153929
|
await deleteProgressComment(toolContext).catch((error49) => {
|
|
153074
153930
|
log.debug(`stranded progress comment cleanup failed: ${error49}`);
|
|
153075
153931
|
});
|
|
@@ -153177,32 +154033,36 @@ The workflow encountered an error before any progress could be reported.`;
|
|
|
153177
154033
|
return `${errorMessage}${footer}`;
|
|
153178
154034
|
}
|
|
153179
154035
|
async function validateStuckProgressComment(ctx) {
|
|
153180
|
-
|
|
153181
|
-
|
|
154036
|
+
const promptComment = ctx.promptInput?.progressComment;
|
|
154037
|
+
if (!promptComment) {
|
|
154038
|
+
log.info("[post] no progressComment in prompt input, skipping cleanup");
|
|
153182
154039
|
return null;
|
|
153183
154040
|
}
|
|
153184
|
-
const
|
|
153185
|
-
|
|
154041
|
+
const comment = parseProgressComment(promptComment);
|
|
154042
|
+
if (!comment) {
|
|
154043
|
+
log.info(`[post] progressComment.id is not a positive integer: ${promptComment.id}`);
|
|
154044
|
+
return null;
|
|
154045
|
+
}
|
|
154046
|
+
log.info(`[post] validating progressComment from prompt input: ${comment.id} (${comment.type})`);
|
|
153186
154047
|
try {
|
|
153187
|
-
const
|
|
153188
|
-
owner: ctx.repoContext.owner,
|
|
153189
|
-
|
|
153190
|
-
|
|
153191
|
-
|
|
153192
|
-
const body = commentResult.data.body ?? "";
|
|
154048
|
+
const fetched = await getProgressComment(
|
|
154049
|
+
{ octokit: ctx.octokit, owner: ctx.repoContext.owner, repo: ctx.repoContext.name },
|
|
154050
|
+
comment
|
|
154051
|
+
);
|
|
154052
|
+
const body = fetched.body ?? "";
|
|
153193
154053
|
if (isLeapingIntoActionCommentBody(body)) {
|
|
153194
|
-
log.info(`[post] comment ${
|
|
153195
|
-
return
|
|
154054
|
+
log.info(`[post] comment ${comment.id} is stuck on "Leaping into action"`);
|
|
154055
|
+
return comment;
|
|
153196
154056
|
}
|
|
153197
154057
|
if (/^- \[[ x]\] |^- \*\*→\*\* |^- ~~/.test(body)) {
|
|
153198
|
-
log.info(`[post] comment ${
|
|
153199
|
-
return
|
|
154058
|
+
log.info(`[post] comment ${comment.id} is stuck on a todo checklist`);
|
|
154059
|
+
return comment;
|
|
153200
154060
|
}
|
|
153201
|
-
log.info(`[post] comment ${
|
|
154061
|
+
log.info(`[post] comment ${comment.id} is not stuck (already updated or different content)`);
|
|
153202
154062
|
return null;
|
|
153203
154063
|
} catch (error49) {
|
|
153204
154064
|
const errorMessage = error49 instanceof Error ? error49.message : String(error49);
|
|
153205
|
-
log.info(`[post] failed to get comment ${
|
|
154065
|
+
log.info(`[post] failed to get comment ${comment.id}: ${errorMessage}`);
|
|
153206
154066
|
return null;
|
|
153207
154067
|
}
|
|
153208
154068
|
}
|
|
@@ -153253,29 +154113,59 @@ async function runPostCleanup() {
|
|
|
153253
154113
|
const repoContext = parseRepoContext();
|
|
153254
154114
|
const octokit = createOctokit(token);
|
|
153255
154115
|
const ctx = { repoContext, octokit, runId, promptInput };
|
|
153256
|
-
const
|
|
153257
|
-
if (!
|
|
153258
|
-
log.info(
|
|
154116
|
+
const stuck = await validateStuckProgressComment(ctx);
|
|
154117
|
+
if (!stuck) return log.info("\xBB [post] no stuck progress comment to update, skipping cleanup");
|
|
154118
|
+
log.info(
|
|
154119
|
+
`\xBB [post] validated stuck comment: ${stuck.id} (${stuck.type}), updating with error message`
|
|
154120
|
+
);
|
|
153259
154121
|
try {
|
|
153260
154122
|
const body = buildErrorCommentBody(
|
|
153261
154123
|
ctx,
|
|
153262
154124
|
SHOULD_CHECK_REASON ? await getIsCancelled(ctx) : false
|
|
153263
154125
|
);
|
|
153264
|
-
await ctx
|
|
153265
|
-
owner: ctx.repoContext.owner,
|
|
153266
|
-
repo: ctx.repoContext.name,
|
|
153267
|
-
comment_id: commentId,
|
|
153268
|
-
body
|
|
153269
|
-
});
|
|
153270
|
-
log.info("\xBB [post] successfully updated progress comment");
|
|
154126
|
+
await writeAndVerify(ctx, stuck, body);
|
|
153271
154127
|
} catch (error49) {
|
|
153272
154128
|
const errorMessage = error49 instanceof Error ? error49.message : String(error49);
|
|
153273
154129
|
log.info(`[post] failed to update comment: ${errorMessage}`);
|
|
153274
154130
|
}
|
|
153275
154131
|
}
|
|
154132
|
+
var VERIFY_DELAY_MS = 3e3;
|
|
154133
|
+
var MAX_WRITE_ATTEMPTS = 3;
|
|
154134
|
+
async function writeAndVerify(ctx, comment, body) {
|
|
154135
|
+
const apiCtx = {
|
|
154136
|
+
octokit: ctx.octokit,
|
|
154137
|
+
owner: ctx.repoContext.owner,
|
|
154138
|
+
repo: ctx.repoContext.name
|
|
154139
|
+
};
|
|
154140
|
+
for (let attempt = 1; attempt <= MAX_WRITE_ATTEMPTS; attempt++) {
|
|
154141
|
+
await updateProgressComment(apiCtx, comment, body);
|
|
154142
|
+
await new Promise((resolve3) => setTimeout(resolve3, VERIFY_DELAY_MS));
|
|
154143
|
+
let fetched;
|
|
154144
|
+
try {
|
|
154145
|
+
fetched = await getProgressComment(apiCtx, comment);
|
|
154146
|
+
} catch (error49) {
|
|
154147
|
+
log.warning(
|
|
154148
|
+
`[post] verify GET failed after attempt ${attempt} \u2014 trusting our PUT landed: ${error49 instanceof Error ? error49.message : String(error49)}`
|
|
154149
|
+
);
|
|
154150
|
+
return;
|
|
154151
|
+
}
|
|
154152
|
+
if (fetched.body === body) {
|
|
154153
|
+
log.info(
|
|
154154
|
+
`\xBB [post] successfully updated progress comment (attempt ${attempt}/${MAX_WRITE_ATTEMPTS})`
|
|
154155
|
+
);
|
|
154156
|
+
return;
|
|
154157
|
+
}
|
|
154158
|
+
log.info(
|
|
154159
|
+
`[post] body was overwritten after our write (attempt ${attempt}/${MAX_WRITE_ATTEMPTS}), retrying`
|
|
154160
|
+
);
|
|
154161
|
+
}
|
|
154162
|
+
log.warning(
|
|
154163
|
+
`[post] gave up after ${MAX_WRITE_ATTEMPTS} attempts \u2014 comment may be stale (in-flight writes from the cancelled run kept clobbering us)`
|
|
154164
|
+
);
|
|
154165
|
+
}
|
|
153276
154166
|
|
|
153277
154167
|
// commands/gha.ts
|
|
153278
|
-
process.env.PATH = `${
|
|
154168
|
+
process.env.PATH = `${dirname4(process.execPath)}:${process.env.PATH}`;
|
|
153279
154169
|
var STATE_TOKEN = "token";
|
|
153280
154170
|
async function runMain() {
|
|
153281
154171
|
try {
|
|
@@ -154346,7 +155236,7 @@ function link(text, url4) {
|
|
|
154346
155236
|
}
|
|
154347
155237
|
function buildProviders() {
|
|
154348
155238
|
return Object.entries(providers).filter(([key]) => key !== "opencode" && key !== "openrouter").map(([key, config3]) => {
|
|
154349
|
-
const aliases = modelAliases.filter((a) => a.provider === key);
|
|
155239
|
+
const aliases = modelAliases.filter((a) => a.provider === key && !a.fallback);
|
|
154350
155240
|
const recommended = aliases.find((a) => a.preferred);
|
|
154351
155241
|
const sorted = [...aliases].sort((a, b) => {
|
|
154352
155242
|
if (a.preferred && !b.preferred) return -1;
|
|
@@ -154366,8 +155256,8 @@ function buildProviders() {
|
|
|
154366
155256
|
});
|
|
154367
155257
|
}
|
|
154368
155258
|
var CLI_PROVIDERS = buildProviders();
|
|
154369
|
-
function resolveModelProvider(
|
|
154370
|
-
const providerId =
|
|
155259
|
+
function resolveModelProvider(slug2) {
|
|
155260
|
+
const providerId = slug2.split("/")[0];
|
|
154371
155261
|
return CLI_PROVIDERS.find((p2) => p2.id === providerId) ?? null;
|
|
154372
155262
|
}
|
|
154373
155263
|
var activeSpin = null;
|
|
@@ -154918,8 +155808,10 @@ async function main2() {
|
|
|
154918
155808
|
const resolved = resolveModelProvider(secrets.model);
|
|
154919
155809
|
if (!resolved) bail(`unknown model provider: ${secrets.model}`);
|
|
154920
155810
|
provider2 = resolved;
|
|
155811
|
+
const displayAlias = resolveDisplayAlias(secrets.model);
|
|
155812
|
+
const label = displayAlias ? displayAlias.displayName : secrets.model;
|
|
154921
155813
|
spin.start("");
|
|
154922
|
-
spin.stop(`using model ${import_picocolors.default.cyan(
|
|
155814
|
+
spin.stop(`using model ${import_picocolors.default.cyan(label)}`);
|
|
154923
155815
|
} else {
|
|
154924
155816
|
const providerId = await _t({
|
|
154925
155817
|
message: "select your preferred model provider",
|
|
@@ -155057,7 +155949,7 @@ async function run2() {
|
|
|
155057
155949
|
}
|
|
155058
155950
|
|
|
155059
155951
|
// cli.ts
|
|
155060
|
-
var VERSION10 = "0.0.
|
|
155952
|
+
var VERSION10 = "0.0.204";
|
|
155061
155953
|
var bin = basename2(process.argv[1] || "");
|
|
155062
155954
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
155063
155955
|
var rawArgs = process.argv.slice(2);
|