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/index.js
CHANGED
|
@@ -62663,7 +62663,7 @@ var require_snapshot_recorder = __commonJS({
|
|
|
62663
62663
|
"node_modules/.pnpm/undici@7.22.0/node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
|
|
62664
62664
|
"use strict";
|
|
62665
62665
|
var { writeFile: writeFile2, readFile, mkdir } = __require("node:fs/promises");
|
|
62666
|
-
var { dirname:
|
|
62666
|
+
var { dirname: dirname4, resolve: resolve3 } = __require("node:path");
|
|
62667
62667
|
var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("node:timers");
|
|
62668
62668
|
var { InvalidArgumentError, UndiciError } = require_errors4();
|
|
62669
62669
|
var { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require_snapshot_utils();
|
|
@@ -62894,7 +62894,7 @@ var require_snapshot_recorder = __commonJS({
|
|
|
62894
62894
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
62895
62895
|
}
|
|
62896
62896
|
const resolvedPath = resolve3(path3);
|
|
62897
|
-
await mkdir(
|
|
62897
|
+
await mkdir(dirname4(resolvedPath), { recursive: true });
|
|
62898
62898
|
const data = Array.from(this.#snapshots.entries()).map(([hash2, snapshot2]) => ({
|
|
62899
62899
|
hash: hash2,
|
|
62900
62900
|
snapshot: snapshot2
|
|
@@ -97475,14 +97475,14 @@ var require_turndown_cjs = __commonJS({
|
|
|
97475
97475
|
} else if (node2.nodeType === 1) {
|
|
97476
97476
|
replacement = replacementForNode.call(self2, node2);
|
|
97477
97477
|
}
|
|
97478
|
-
return
|
|
97478
|
+
return join16(output, replacement);
|
|
97479
97479
|
}, "");
|
|
97480
97480
|
}
|
|
97481
97481
|
function postProcess(output) {
|
|
97482
97482
|
var self2 = this;
|
|
97483
97483
|
this.rules.forEach(function(rule) {
|
|
97484
97484
|
if (typeof rule.append === "function") {
|
|
97485
|
-
output =
|
|
97485
|
+
output = join16(output, rule.append(self2.options));
|
|
97486
97486
|
}
|
|
97487
97487
|
});
|
|
97488
97488
|
return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
|
|
@@ -97494,7 +97494,7 @@ var require_turndown_cjs = __commonJS({
|
|
|
97494
97494
|
if (whitespace.leading || whitespace.trailing) content = content.trim();
|
|
97495
97495
|
return whitespace.leading + rule.replacement(content, node2, this.options) + whitespace.trailing;
|
|
97496
97496
|
}
|
|
97497
|
-
function
|
|
97497
|
+
function join16(output, replacement) {
|
|
97498
97498
|
var s1 = trimTrailingNewlines(output);
|
|
97499
97499
|
var s2 = trimLeadingNewlines(replacement);
|
|
97500
97500
|
var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
|
|
@@ -98925,8 +98925,8 @@ var require_fast_content_type_parse = __commonJS({
|
|
|
98925
98925
|
|
|
98926
98926
|
// main.ts
|
|
98927
98927
|
var core6 = __toESM(require_core(), 1);
|
|
98928
|
-
import { existsSync as
|
|
98929
|
-
import { join as
|
|
98928
|
+
import { existsSync as existsSync7, readdirSync } from "node:fs";
|
|
98929
|
+
import { join as join15 } from "node:path";
|
|
98930
98930
|
|
|
98931
98931
|
// node_modules/.pnpm/@ark+util@0.56.0/node_modules/@ark/util/out/arrays.js
|
|
98932
98932
|
var liftArray = (data) => Array.isArray(data) ? data : [data];
|
|
@@ -107401,7 +107401,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
107401
107401
|
// agents/shared.ts
|
|
107402
107402
|
import { execFileSync } from "node:child_process";
|
|
107403
107403
|
var MAX_STDERR_LINES = 20;
|
|
107404
|
-
var
|
|
107404
|
+
var MAX_POST_RUN_RETRIES = 3;
|
|
107405
107405
|
function getGitStatus() {
|
|
107406
107406
|
try {
|
|
107407
107407
|
return execFileSync("git", ["status", "--porcelain"], {
|
|
@@ -107412,7 +107412,7 @@ function getGitStatus() {
|
|
|
107412
107412
|
return "";
|
|
107413
107413
|
}
|
|
107414
107414
|
}
|
|
107415
|
-
function buildCommitPrompt(
|
|
107415
|
+
function buildCommitPrompt(status) {
|
|
107416
107416
|
return [
|
|
107417
107417
|
`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.`,
|
|
107418
107418
|
"",
|
|
@@ -107421,6 +107421,9 @@ function buildCommitPrompt(_agentId, status) {
|
|
|
107421
107421
|
"```"
|
|
107422
107422
|
].join("\n");
|
|
107423
107423
|
}
|
|
107424
|
+
function hasPostRunIssues(issues) {
|
|
107425
|
+
return issues.stopHook !== void 0 || issues.dirtyTree !== void 0;
|
|
107426
|
+
}
|
|
107424
107427
|
var agent = (input) => {
|
|
107425
107428
|
return {
|
|
107426
107429
|
...input,
|
|
@@ -107732,7 +107735,7 @@ var providers = {
|
|
|
107732
107735
|
"claude-opus": {
|
|
107733
107736
|
displayName: "Claude Opus",
|
|
107734
107737
|
resolve: "anthropic/claude-opus-4-7",
|
|
107735
|
-
openRouterResolve: "openrouter/anthropic/claude-opus-4.
|
|
107738
|
+
openRouterResolve: "openrouter/anthropic/claude-opus-4.7",
|
|
107736
107739
|
preferred: true
|
|
107737
107740
|
},
|
|
107738
107741
|
"claude-sonnet": {
|
|
@@ -107751,16 +107754,38 @@ var providers = {
|
|
|
107751
107754
|
displayName: "OpenAI",
|
|
107752
107755
|
envVars: ["OPENAI_API_KEY"],
|
|
107753
107756
|
models: {
|
|
107757
|
+
gpt: {
|
|
107758
|
+
displayName: "GPT",
|
|
107759
|
+
resolve: "openai/gpt-5.5",
|
|
107760
|
+
openRouterResolve: "openrouter/openai/gpt-5.5",
|
|
107761
|
+
preferred: true
|
|
107762
|
+
},
|
|
107763
|
+
"gpt-pro": {
|
|
107764
|
+
displayName: "GPT Pro",
|
|
107765
|
+
resolve: "openai/gpt-5.5-pro",
|
|
107766
|
+
openRouterResolve: "openrouter/openai/gpt-5.5-pro"
|
|
107767
|
+
},
|
|
107768
|
+
"gpt-mini": {
|
|
107769
|
+
displayName: "GPT Mini",
|
|
107770
|
+
resolve: "openai/gpt-5.4-mini",
|
|
107771
|
+
openRouterResolve: "openrouter/openai/gpt-5.4-mini"
|
|
107772
|
+
},
|
|
107773
|
+
// legacy aliases — openai unified the codex line into the main GPT family
|
|
107774
|
+
// and is shutting down every "-codex" snapshot on 2026-07-23. transparently
|
|
107775
|
+
// upgrade existing users via the fallback chain. UI display sites resolve
|
|
107776
|
+
// to the terminal alias's label (so dropdown trigger + PR footers show
|
|
107777
|
+
// "GPT" / "GPT Mini", not the historical name).
|
|
107754
107778
|
"gpt-codex": {
|
|
107755
107779
|
displayName: "GPT Codex",
|
|
107756
107780
|
resolve: "openai/gpt-5.3-codex",
|
|
107757
107781
|
openRouterResolve: "openrouter/openai/gpt-5.3-codex",
|
|
107758
|
-
|
|
107782
|
+
fallback: "openai/gpt"
|
|
107759
107783
|
},
|
|
107760
107784
|
"gpt-codex-mini": {
|
|
107761
107785
|
displayName: "GPT Codex Mini",
|
|
107762
107786
|
resolve: "openai/gpt-5.1-codex-mini",
|
|
107763
|
-
openRouterResolve: "openrouter/openai/gpt-5.1-codex-mini"
|
|
107787
|
+
openRouterResolve: "openrouter/openai/gpt-5.1-codex-mini",
|
|
107788
|
+
fallback: "openai/gpt-mini"
|
|
107764
107789
|
},
|
|
107765
107790
|
o3: {
|
|
107766
107791
|
displayName: "O3",
|
|
@@ -107791,14 +107816,14 @@ var providers = {
|
|
|
107791
107816
|
models: {
|
|
107792
107817
|
grok: {
|
|
107793
107818
|
displayName: "Grok",
|
|
107794
|
-
resolve: "xai/grok-4",
|
|
107795
|
-
openRouterResolve: "openrouter/x-ai/grok-4",
|
|
107819
|
+
resolve: "xai/grok-4.3",
|
|
107820
|
+
openRouterResolve: "openrouter/x-ai/grok-4.3",
|
|
107796
107821
|
preferred: true
|
|
107797
107822
|
},
|
|
107798
107823
|
"grok-fast": {
|
|
107799
107824
|
displayName: "Grok Fast",
|
|
107800
|
-
resolve: "xai/grok-4-fast",
|
|
107801
|
-
openRouterResolve: "openrouter/x-ai/grok-4-fast"
|
|
107825
|
+
resolve: "xai/grok-4-1-fast",
|
|
107826
|
+
openRouterResolve: "openrouter/x-ai/grok-4.1-fast"
|
|
107802
107827
|
},
|
|
107803
107828
|
"grok-code-fast": {
|
|
107804
107829
|
displayName: "Grok Code Fast",
|
|
@@ -107811,16 +107836,30 @@ var providers = {
|
|
|
107811
107836
|
displayName: "DeepSeek",
|
|
107812
107837
|
envVars: ["DEEPSEEK_API_KEY"],
|
|
107813
107838
|
models: {
|
|
107839
|
+
"deepseek-pro": {
|
|
107840
|
+
displayName: "DeepSeek Pro",
|
|
107841
|
+
resolve: "deepseek/deepseek-v4-pro",
|
|
107842
|
+
openRouterResolve: "openrouter/deepseek/deepseek-v4-pro",
|
|
107843
|
+
preferred: true
|
|
107844
|
+
},
|
|
107845
|
+
"deepseek-flash": {
|
|
107846
|
+
displayName: "DeepSeek Flash",
|
|
107847
|
+
resolve: "deepseek/deepseek-v4-flash",
|
|
107848
|
+
openRouterResolve: "openrouter/deepseek/deepseek-v4-flash"
|
|
107849
|
+
},
|
|
107850
|
+
// legacy aliases — deepseek retires these on 2026-07-24; transparently
|
|
107851
|
+
// upgrade existing users to the v4 family via the fallback chain.
|
|
107814
107852
|
"deepseek-reasoner": {
|
|
107815
107853
|
displayName: "DeepSeek Reasoner",
|
|
107816
107854
|
resolve: "deepseek/deepseek-reasoner",
|
|
107817
107855
|
openRouterResolve: "openrouter/deepseek/deepseek-v3.2",
|
|
107818
|
-
|
|
107856
|
+
fallback: "deepseek/deepseek-pro"
|
|
107819
107857
|
},
|
|
107820
107858
|
"deepseek-chat": {
|
|
107821
107859
|
displayName: "DeepSeek Chat",
|
|
107822
107860
|
resolve: "deepseek/deepseek-chat",
|
|
107823
|
-
openRouterResolve: "openrouter/deepseek/deepseek-v3.2"
|
|
107861
|
+
openRouterResolve: "openrouter/deepseek/deepseek-v3.2",
|
|
107862
|
+
fallback: "deepseek/deepseek-flash"
|
|
107824
107863
|
}
|
|
107825
107864
|
}
|
|
107826
107865
|
}),
|
|
@@ -107830,8 +107869,8 @@ var providers = {
|
|
|
107830
107869
|
models: {
|
|
107831
107870
|
"kimi-k2": {
|
|
107832
107871
|
displayName: "Kimi K2",
|
|
107833
|
-
resolve: "moonshotai/kimi-k2.
|
|
107834
|
-
openRouterResolve: "openrouter/moonshotai/kimi-k2.
|
|
107872
|
+
resolve: "moonshotai/kimi-k2.6",
|
|
107873
|
+
openRouterResolve: "openrouter/moonshotai/kimi-k2.6",
|
|
107835
107874
|
preferred: true
|
|
107836
107875
|
}
|
|
107837
107876
|
}
|
|
@@ -107850,7 +107889,7 @@ var providers = {
|
|
|
107850
107889
|
"claude-opus": {
|
|
107851
107890
|
displayName: "Claude Opus",
|
|
107852
107891
|
resolve: "opencode/claude-opus-4-7",
|
|
107853
|
-
openRouterResolve: "openrouter/anthropic/claude-opus-4.
|
|
107892
|
+
openRouterResolve: "openrouter/anthropic/claude-opus-4.7"
|
|
107854
107893
|
},
|
|
107855
107894
|
"claude-sonnet": {
|
|
107856
107895
|
displayName: "Claude Sonnet",
|
|
@@ -107862,15 +107901,33 @@ var providers = {
|
|
|
107862
107901
|
resolve: "opencode/claude-haiku-4-5",
|
|
107863
107902
|
openRouterResolve: "openrouter/anthropic/claude-haiku-4.5"
|
|
107864
107903
|
},
|
|
107904
|
+
gpt: {
|
|
107905
|
+
displayName: "GPT",
|
|
107906
|
+
resolve: "opencode/gpt-5.5",
|
|
107907
|
+
openRouterResolve: "openrouter/openai/gpt-5.5"
|
|
107908
|
+
},
|
|
107909
|
+
"gpt-pro": {
|
|
107910
|
+
displayName: "GPT Pro",
|
|
107911
|
+
resolve: "opencode/gpt-5.5-pro",
|
|
107912
|
+
openRouterResolve: "openrouter/openai/gpt-5.5-pro"
|
|
107913
|
+
},
|
|
107914
|
+
"gpt-mini": {
|
|
107915
|
+
displayName: "GPT Mini",
|
|
107916
|
+
resolve: "opencode/gpt-5.4-mini",
|
|
107917
|
+
openRouterResolve: "openrouter/openai/gpt-5.4-mini"
|
|
107918
|
+
},
|
|
107919
|
+
// legacy aliases — see openai provider above for context.
|
|
107865
107920
|
"gpt-codex": {
|
|
107866
107921
|
displayName: "GPT Codex",
|
|
107867
107922
|
resolve: "opencode/gpt-5.3-codex",
|
|
107868
|
-
openRouterResolve: "openrouter/openai/gpt-5.3-codex"
|
|
107923
|
+
openRouterResolve: "openrouter/openai/gpt-5.3-codex",
|
|
107924
|
+
fallback: "opencode/gpt"
|
|
107869
107925
|
},
|
|
107870
107926
|
"gpt-codex-mini": {
|
|
107871
107927
|
displayName: "GPT Codex Mini",
|
|
107872
107928
|
resolve: "opencode/gpt-5.1-codex-mini",
|
|
107873
|
-
openRouterResolve: "openrouter/openai/gpt-5.1-codex-mini"
|
|
107929
|
+
openRouterResolve: "openrouter/openai/gpt-5.1-codex-mini",
|
|
107930
|
+
fallback: "opencode/gpt-mini"
|
|
107874
107931
|
},
|
|
107875
107932
|
"gemini-pro": {
|
|
107876
107933
|
displayName: "Gemini Pro",
|
|
@@ -107884,8 +107941,8 @@ var providers = {
|
|
|
107884
107941
|
},
|
|
107885
107942
|
"kimi-k2": {
|
|
107886
107943
|
displayName: "Kimi K2",
|
|
107887
|
-
resolve: "opencode/kimi-k2.
|
|
107888
|
-
openRouterResolve: "openrouter/moonshotai/kimi-k2.
|
|
107944
|
+
resolve: "opencode/kimi-k2.6",
|
|
107945
|
+
openRouterResolve: "openrouter/moonshotai/kimi-k2.6"
|
|
107889
107946
|
},
|
|
107890
107947
|
"gpt-5-nano": {
|
|
107891
107948
|
displayName: "GPT Nano",
|
|
@@ -107905,12 +107962,6 @@ var providers = {
|
|
|
107905
107962
|
resolve: "opencode/minimax-m2.5-free",
|
|
107906
107963
|
envVars: [],
|
|
107907
107964
|
isFree: true
|
|
107908
|
-
},
|
|
107909
|
-
"nemotron-3-super-free": {
|
|
107910
|
-
displayName: "Nemotron 3 Super",
|
|
107911
|
-
resolve: "opencode/nemotron-3-super-free",
|
|
107912
|
-
envVars: [],
|
|
107913
|
-
isFree: true
|
|
107914
107965
|
}
|
|
107915
107966
|
}
|
|
107916
107967
|
}),
|
|
@@ -107920,8 +107971,8 @@ var providers = {
|
|
|
107920
107971
|
models: {
|
|
107921
107972
|
"claude-opus": {
|
|
107922
107973
|
displayName: "Claude Opus",
|
|
107923
|
-
resolve: "openrouter/anthropic/claude-opus-4.
|
|
107924
|
-
openRouterResolve: "openrouter/anthropic/claude-opus-4.
|
|
107974
|
+
resolve: "openrouter/anthropic/claude-opus-4.7",
|
|
107975
|
+
openRouterResolve: "openrouter/anthropic/claude-opus-4.7",
|
|
107925
107976
|
preferred: true
|
|
107926
107977
|
},
|
|
107927
107978
|
"claude-sonnet": {
|
|
@@ -107934,15 +107985,33 @@ var providers = {
|
|
|
107934
107985
|
resolve: "openrouter/anthropic/claude-haiku-4.5",
|
|
107935
107986
|
openRouterResolve: "openrouter/anthropic/claude-haiku-4.5"
|
|
107936
107987
|
},
|
|
107988
|
+
gpt: {
|
|
107989
|
+
displayName: "GPT",
|
|
107990
|
+
resolve: "openrouter/openai/gpt-5.5",
|
|
107991
|
+
openRouterResolve: "openrouter/openai/gpt-5.5"
|
|
107992
|
+
},
|
|
107993
|
+
"gpt-pro": {
|
|
107994
|
+
displayName: "GPT Pro",
|
|
107995
|
+
resolve: "openrouter/openai/gpt-5.5-pro",
|
|
107996
|
+
openRouterResolve: "openrouter/openai/gpt-5.5-pro"
|
|
107997
|
+
},
|
|
107998
|
+
"gpt-mini": {
|
|
107999
|
+
displayName: "GPT Mini",
|
|
108000
|
+
resolve: "openrouter/openai/gpt-5.4-mini",
|
|
108001
|
+
openRouterResolve: "openrouter/openai/gpt-5.4-mini"
|
|
108002
|
+
},
|
|
108003
|
+
// legacy aliases — see openai provider for context.
|
|
107937
108004
|
"gpt-codex": {
|
|
107938
108005
|
displayName: "GPT Codex",
|
|
107939
108006
|
resolve: "openrouter/openai/gpt-5.3-codex",
|
|
107940
|
-
openRouterResolve: "openrouter/openai/gpt-5.3-codex"
|
|
108007
|
+
openRouterResolve: "openrouter/openai/gpt-5.3-codex",
|
|
108008
|
+
fallback: "openrouter/gpt"
|
|
107941
108009
|
},
|
|
107942
108010
|
"gpt-codex-mini": {
|
|
107943
108011
|
displayName: "GPT Codex Mini",
|
|
107944
108012
|
resolve: "openrouter/openai/gpt-5.1-codex-mini",
|
|
107945
|
-
openRouterResolve: "openrouter/openai/gpt-5.1-codex-mini"
|
|
108013
|
+
openRouterResolve: "openrouter/openai/gpt-5.1-codex-mini",
|
|
108014
|
+
fallback: "openrouter/gpt-mini"
|
|
107946
108015
|
},
|
|
107947
108016
|
"o4-mini": {
|
|
107948
108017
|
displayName: "O4 Mini",
|
|
@@ -107961,34 +108030,47 @@ var providers = {
|
|
|
107961
108030
|
},
|
|
107962
108031
|
grok: {
|
|
107963
108032
|
displayName: "Grok",
|
|
107964
|
-
resolve: "openrouter/x-ai/grok-4",
|
|
107965
|
-
openRouterResolve: "openrouter/x-ai/grok-4"
|
|
108033
|
+
resolve: "openrouter/x-ai/grok-4.3",
|
|
108034
|
+
openRouterResolve: "openrouter/x-ai/grok-4.3"
|
|
107966
108035
|
},
|
|
108036
|
+
"deepseek-pro": {
|
|
108037
|
+
displayName: "DeepSeek Pro",
|
|
108038
|
+
resolve: "openrouter/deepseek/deepseek-v4-pro",
|
|
108039
|
+
openRouterResolve: "openrouter/deepseek/deepseek-v4-pro"
|
|
108040
|
+
},
|
|
108041
|
+
"deepseek-flash": {
|
|
108042
|
+
displayName: "DeepSeek Flash",
|
|
108043
|
+
resolve: "openrouter/deepseek/deepseek-v4-flash",
|
|
108044
|
+
openRouterResolve: "openrouter/deepseek/deepseek-v4-flash"
|
|
108045
|
+
},
|
|
108046
|
+
// legacy alias — deepseek retires this on 2026-07-24; transparently
|
|
108047
|
+
// upgrade existing users to the v4 family via the fallback chain.
|
|
107967
108048
|
"deepseek-chat": {
|
|
107968
108049
|
displayName: "DeepSeek Chat",
|
|
107969
108050
|
resolve: "openrouter/deepseek/deepseek-v3.2",
|
|
107970
|
-
openRouterResolve: "openrouter/deepseek/deepseek-v3.2"
|
|
108051
|
+
openRouterResolve: "openrouter/deepseek/deepseek-v3.2",
|
|
108052
|
+
fallback: "openrouter/deepseek-flash"
|
|
107971
108053
|
},
|
|
107972
108054
|
"kimi-k2": {
|
|
107973
108055
|
displayName: "Kimi K2",
|
|
107974
|
-
resolve: "openrouter/moonshotai/kimi-k2.
|
|
107975
|
-
openRouterResolve: "openrouter/moonshotai/kimi-k2.
|
|
108056
|
+
resolve: "openrouter/moonshotai/kimi-k2.6",
|
|
108057
|
+
openRouterResolve: "openrouter/moonshotai/kimi-k2.6"
|
|
107976
108058
|
}
|
|
107977
108059
|
}
|
|
107978
108060
|
})
|
|
107979
108061
|
};
|
|
107980
|
-
function parseModel(
|
|
107981
|
-
const slashIdx =
|
|
108062
|
+
function parseModel(slug2) {
|
|
108063
|
+
const slashIdx = slug2.indexOf("/");
|
|
107982
108064
|
if (slashIdx === -1) {
|
|
107983
|
-
throw new Error(`invalid model slug "${
|
|
108065
|
+
throw new Error(`invalid model slug "${slug2}" \u2014 expected "provider/model"`);
|
|
107984
108066
|
}
|
|
107985
|
-
return { provider:
|
|
108067
|
+
return { provider: slug2.slice(0, slashIdx), model: slug2.slice(slashIdx + 1) };
|
|
107986
108068
|
}
|
|
107987
|
-
function getModelProvider(
|
|
107988
|
-
return parseModel(
|
|
108069
|
+
function getModelProvider(slug2) {
|
|
108070
|
+
return parseModel(slug2).provider;
|
|
107989
108071
|
}
|
|
107990
|
-
function getModelEnvVars(
|
|
107991
|
-
const parsed2 = parseModel(
|
|
108072
|
+
function getModelEnvVars(slug2) {
|
|
108073
|
+
const parsed2 = parseModel(slug2);
|
|
107992
108074
|
const providerConfig = providers[parsed2.provider];
|
|
107993
108075
|
if (!providerConfig) {
|
|
107994
108076
|
return [];
|
|
@@ -108012,26 +108094,29 @@ var modelAliases = Object.entries(providers).flatMap(
|
|
|
108012
108094
|
}))
|
|
108013
108095
|
);
|
|
108014
108096
|
var MAX_FALLBACK_DEPTH = 10;
|
|
108015
|
-
function
|
|
108016
|
-
let current =
|
|
108097
|
+
function resolveDisplayAlias(slug2) {
|
|
108098
|
+
let current = slug2;
|
|
108017
108099
|
const visited = /* @__PURE__ */ new Set();
|
|
108018
108100
|
for (let i = 0; i < MAX_FALLBACK_DEPTH; i++) {
|
|
108019
108101
|
if (visited.has(current)) return void 0;
|
|
108020
108102
|
visited.add(current);
|
|
108021
108103
|
const alias = modelAliases.find((a) => a.slug === current);
|
|
108022
108104
|
if (!alias) return void 0;
|
|
108023
|
-
if (!alias.fallback) return alias
|
|
108105
|
+
if (!alias.fallback) return alias;
|
|
108024
108106
|
current = alias.fallback;
|
|
108025
108107
|
}
|
|
108026
108108
|
return void 0;
|
|
108027
108109
|
}
|
|
108110
|
+
function resolveCliModel(slug2) {
|
|
108111
|
+
return resolveDisplayAlias(slug2)?.resolve;
|
|
108112
|
+
}
|
|
108028
108113
|
|
|
108029
108114
|
// utils/buildPullfrogFooter.ts
|
|
108030
108115
|
var PULLFROG_DIVIDER = "<!-- PULLFROG_DIVIDER_DO_NOT_REMOVE_PLZ -->";
|
|
108031
108116
|
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>`;
|
|
108032
|
-
function formatModelLabel(
|
|
108033
|
-
const alias =
|
|
108034
|
-
if (!alias) return `\`${
|
|
108117
|
+
function formatModelLabel(slug2) {
|
|
108118
|
+
const alias = resolveDisplayAlias(slug2);
|
|
108119
|
+
if (!alias) return `\`${slug2}\``;
|
|
108035
108120
|
return alias.isFree ? `\`${alias.displayName}\` (free)` : `\`${alias.displayName}\``;
|
|
108036
108121
|
}
|
|
108037
108122
|
function buildPullfrogFooter(params) {
|
|
@@ -108215,6 +108300,93 @@ function aggregateUsage(entries) {
|
|
|
108215
108300
|
return out;
|
|
108216
108301
|
}
|
|
108217
108302
|
|
|
108303
|
+
// utils/progressComment.ts
|
|
108304
|
+
function parseProgressComment(raw2) {
|
|
108305
|
+
if (!raw2?.id) return void 0;
|
|
108306
|
+
const id = parseInt(raw2.id, 10);
|
|
108307
|
+
if (Number.isNaN(id) || id <= 0) return void 0;
|
|
108308
|
+
return { id, type: raw2.type };
|
|
108309
|
+
}
|
|
108310
|
+
async function updateProgressComment(ctx, comment, body) {
|
|
108311
|
+
const result = await (comment.type === "review" ? ctx.octokit.rest.pulls.updateReviewComment({
|
|
108312
|
+
owner: ctx.owner,
|
|
108313
|
+
repo: ctx.repo,
|
|
108314
|
+
comment_id: comment.id,
|
|
108315
|
+
body
|
|
108316
|
+
}) : ctx.octokit.rest.issues.updateComment({
|
|
108317
|
+
owner: ctx.owner,
|
|
108318
|
+
repo: ctx.repo,
|
|
108319
|
+
comment_id: comment.id,
|
|
108320
|
+
body
|
|
108321
|
+
}));
|
|
108322
|
+
return {
|
|
108323
|
+
id: result.data.id,
|
|
108324
|
+
body: result.data.body ?? void 0,
|
|
108325
|
+
html_url: result.data.html_url,
|
|
108326
|
+
node_id: result.data.node_id
|
|
108327
|
+
};
|
|
108328
|
+
}
|
|
108329
|
+
async function deleteProgressCommentApi(ctx, comment) {
|
|
108330
|
+
if (comment.type === "review") {
|
|
108331
|
+
await ctx.octokit.rest.pulls.deleteReviewComment({
|
|
108332
|
+
owner: ctx.owner,
|
|
108333
|
+
repo: ctx.repo,
|
|
108334
|
+
comment_id: comment.id
|
|
108335
|
+
});
|
|
108336
|
+
return;
|
|
108337
|
+
}
|
|
108338
|
+
await ctx.octokit.rest.issues.deleteComment({
|
|
108339
|
+
owner: ctx.owner,
|
|
108340
|
+
repo: ctx.repo,
|
|
108341
|
+
comment_id: comment.id
|
|
108342
|
+
});
|
|
108343
|
+
}
|
|
108344
|
+
async function createLeapingProgressComment(ctx, target, body) {
|
|
108345
|
+
if (target.kind === "reviewReply") {
|
|
108346
|
+
try {
|
|
108347
|
+
const result2 = await ctx.octokit.rest.pulls.createReplyForReviewComment({
|
|
108348
|
+
owner: ctx.owner,
|
|
108349
|
+
repo: ctx.repo,
|
|
108350
|
+
pull_number: target.pullNumber,
|
|
108351
|
+
comment_id: target.replyToCommentId,
|
|
108352
|
+
body
|
|
108353
|
+
});
|
|
108354
|
+
return {
|
|
108355
|
+
comment: { id: result2.data.id, type: "review" },
|
|
108356
|
+
body: result2.data.body ?? void 0,
|
|
108357
|
+
html_url: result2.data.html_url
|
|
108358
|
+
};
|
|
108359
|
+
} catch (error49) {
|
|
108360
|
+
console.warn(
|
|
108361
|
+
`[progressComment] review reply failed (parent ${target.replyToCommentId} on PR #${target.pullNumber}), falling back to issue comment:`,
|
|
108362
|
+
error49
|
|
108363
|
+
);
|
|
108364
|
+
const fallback = await ctx.octokit.rest.issues.createComment({
|
|
108365
|
+
owner: ctx.owner,
|
|
108366
|
+
repo: ctx.repo,
|
|
108367
|
+
issue_number: target.pullNumber,
|
|
108368
|
+
body
|
|
108369
|
+
});
|
|
108370
|
+
return {
|
|
108371
|
+
comment: { id: fallback.data.id, type: "issue" },
|
|
108372
|
+
body: fallback.data.body ?? void 0,
|
|
108373
|
+
html_url: fallback.data.html_url
|
|
108374
|
+
};
|
|
108375
|
+
}
|
|
108376
|
+
}
|
|
108377
|
+
const result = await ctx.octokit.rest.issues.createComment({
|
|
108378
|
+
owner: ctx.owner,
|
|
108379
|
+
repo: ctx.repo,
|
|
108380
|
+
issue_number: target.issueNumber,
|
|
108381
|
+
body
|
|
108382
|
+
});
|
|
108383
|
+
return {
|
|
108384
|
+
comment: { id: result.data.id, type: "issue" },
|
|
108385
|
+
body: result.data.body ?? void 0,
|
|
108386
|
+
html_url: result.data.html_url
|
|
108387
|
+
};
|
|
108388
|
+
}
|
|
108389
|
+
|
|
108218
108390
|
// node_modules/.pnpm/@toon-format+toon@1.4.0/node_modules/@toon-format/toon/dist/index.mjs
|
|
108219
108391
|
var LIST_ITEM_MARKER = "-";
|
|
108220
108392
|
var LIST_ITEM_PREFIX = "- ";
|
|
@@ -108875,6 +109047,7 @@ async function reportProgress(ctx, params) {
|
|
|
108875
109047
|
}
|
|
108876
109048
|
const issueNumber = ctx.payload.event.issue_number ?? ctx.toolState.issueNumber;
|
|
108877
109049
|
const isPlanMode = ctx.toolState.selectedMode === "Plan";
|
|
109050
|
+
const apiCtx = { octokit: ctx.octokit, owner: ctx.repo.owner, repo: ctx.repo.name };
|
|
108878
109051
|
if (target_plan_comment === true && ctx.toolState.existingPlanCommentId === void 0) {
|
|
108879
109052
|
log.warning("target_plan_comment requested but no existingPlanCommentId in tool state");
|
|
108880
109053
|
}
|
|
@@ -108884,86 +109057,74 @@ async function reportProgress(ctx, params) {
|
|
|
108884
109057
|
const bodyWithoutFooter = stripExistingFooter(body);
|
|
108885
109058
|
const footer = buildCommentFooter(ctx, customParts);
|
|
108886
109059
|
const bodyWithFooter = `${bodyWithoutFooter}${footer}`;
|
|
108887
|
-
const
|
|
108888
|
-
|
|
108889
|
-
|
|
108890
|
-
|
|
108891
|
-
|
|
108892
|
-
});
|
|
109060
|
+
const result = await updateProgressComment(
|
|
109061
|
+
apiCtx,
|
|
109062
|
+
{ id: commentId, type: "issue" },
|
|
109063
|
+
bodyWithFooter
|
|
109064
|
+
);
|
|
108893
109065
|
ctx.toolState.wasUpdated = true;
|
|
108894
|
-
if (isPlanMode &&
|
|
108895
|
-
await patchWorkflowRunFields(ctx, { planCommentNodeId:
|
|
109066
|
+
if (isPlanMode && result.node_id) {
|
|
109067
|
+
await patchWorkflowRunFields(ctx, { planCommentNodeId: result.node_id });
|
|
108896
109068
|
}
|
|
108897
109069
|
return {
|
|
108898
|
-
commentId:
|
|
108899
|
-
url:
|
|
108900
|
-
body:
|
|
109070
|
+
commentId: result.id,
|
|
109071
|
+
url: result.html_url,
|
|
109072
|
+
body: result.body || "",
|
|
108901
109073
|
action: "updated"
|
|
108902
109074
|
};
|
|
108903
109075
|
}
|
|
108904
|
-
const
|
|
108905
|
-
if (
|
|
108906
|
-
const customParts = isPlanMode && issueNumber !== void 0 ? [buildImplementPlanLink(ctx, issueNumber,
|
|
109076
|
+
const existingComment = ctx.toolState.progressComment;
|
|
109077
|
+
if (existingComment) {
|
|
109078
|
+
const customParts = isPlanMode && issueNumber !== void 0 ? [buildImplementPlanLink(ctx, issueNumber, existingComment.id)] : void 0;
|
|
108907
109079
|
const bodyWithoutFooter = stripExistingFooter(body);
|
|
108908
109080
|
const footer = buildCommentFooter(ctx, customParts);
|
|
108909
109081
|
const bodyWithFooter = `${bodyWithoutFooter}${footer}`;
|
|
108910
|
-
const
|
|
108911
|
-
owner: ctx.repo.owner,
|
|
108912
|
-
repo: ctx.repo.name,
|
|
108913
|
-
comment_id: existingCommentId,
|
|
108914
|
-
body: bodyWithFooter
|
|
108915
|
-
});
|
|
109082
|
+
const result = await updateProgressComment(apiCtx, existingComment, bodyWithFooter);
|
|
108916
109083
|
ctx.toolState.wasUpdated = true;
|
|
108917
|
-
if (isPlanMode &&
|
|
108918
|
-
await patchWorkflowRunFields(ctx, { planCommentNodeId:
|
|
109084
|
+
if (isPlanMode && result.node_id) {
|
|
109085
|
+
await patchWorkflowRunFields(ctx, { planCommentNodeId: result.node_id });
|
|
108919
109086
|
}
|
|
108920
109087
|
return {
|
|
108921
|
-
commentId:
|
|
108922
|
-
url:
|
|
108923
|
-
body:
|
|
109088
|
+
commentId: result.id,
|
|
109089
|
+
url: result.html_url,
|
|
109090
|
+
body: result.body || "",
|
|
108924
109091
|
action: "updated"
|
|
108925
109092
|
};
|
|
108926
109093
|
}
|
|
108927
|
-
if (
|
|
109094
|
+
if (existingComment === null) {
|
|
108928
109095
|
return { body, action: "skipped" };
|
|
108929
109096
|
}
|
|
108930
109097
|
if (issueNumber === void 0) {
|
|
108931
109098
|
return { body, action: "skipped" };
|
|
108932
109099
|
}
|
|
108933
109100
|
const initialBody = addFooter(ctx, body);
|
|
108934
|
-
const
|
|
108935
|
-
|
|
108936
|
-
|
|
108937
|
-
|
|
108938
|
-
|
|
108939
|
-
|
|
108940
|
-
ctx.toolState.progressCommentId = result.data.id;
|
|
109101
|
+
const created = await createLeapingProgressComment(
|
|
109102
|
+
apiCtx,
|
|
109103
|
+
{ kind: "issue", issueNumber },
|
|
109104
|
+
initialBody
|
|
109105
|
+
);
|
|
109106
|
+
ctx.toolState.progressComment = created.comment;
|
|
108941
109107
|
ctx.toolState.wasUpdated = true;
|
|
108942
109108
|
if (isPlanMode) {
|
|
108943
|
-
const customParts = [buildImplementPlanLink(ctx, issueNumber,
|
|
109109
|
+
const customParts = [buildImplementPlanLink(ctx, issueNumber, created.comment.id)];
|
|
108944
109110
|
const bodyWithoutFooter = stripExistingFooter(body);
|
|
108945
109111
|
const footer = buildCommentFooter(ctx, customParts);
|
|
108946
109112
|
const bodyWithPlanLink = `${bodyWithoutFooter}${footer}`;
|
|
108947
|
-
const updateResult = await
|
|
108948
|
-
|
|
108949
|
-
|
|
108950
|
-
comment_id: result.data.id,
|
|
108951
|
-
body: bodyWithPlanLink
|
|
108952
|
-
});
|
|
108953
|
-
if (updateResult.data.node_id) {
|
|
108954
|
-
await patchWorkflowRunFields(ctx, { planCommentNodeId: updateResult.data.node_id });
|
|
109113
|
+
const updateResult = await updateProgressComment(apiCtx, created.comment, bodyWithPlanLink);
|
|
109114
|
+
if (updateResult.node_id) {
|
|
109115
|
+
await patchWorkflowRunFields(ctx, { planCommentNodeId: updateResult.node_id });
|
|
108955
109116
|
}
|
|
108956
109117
|
return {
|
|
108957
|
-
commentId: updateResult.
|
|
108958
|
-
url: updateResult.
|
|
108959
|
-
body: updateResult.
|
|
109118
|
+
commentId: updateResult.id,
|
|
109119
|
+
url: updateResult.html_url,
|
|
109120
|
+
body: updateResult.body || "",
|
|
108960
109121
|
action: "created"
|
|
108961
109122
|
};
|
|
108962
109123
|
}
|
|
108963
109124
|
return {
|
|
108964
|
-
commentId:
|
|
108965
|
-
url:
|
|
108966
|
-
body:
|
|
109125
|
+
commentId: created.comment.id,
|
|
109126
|
+
url: created.html_url,
|
|
109127
|
+
body: created.body || "",
|
|
108967
109128
|
action: "created"
|
|
108968
109129
|
};
|
|
108969
109130
|
}
|
|
@@ -109008,23 +109169,22 @@ ${collapsible}`;
|
|
|
109008
109169
|
});
|
|
109009
109170
|
}
|
|
109010
109171
|
async function deleteProgressComment(ctx) {
|
|
109011
|
-
const
|
|
109012
|
-
if (!
|
|
109172
|
+
const existing = ctx.toolState.progressComment;
|
|
109173
|
+
if (!existing) {
|
|
109013
109174
|
return false;
|
|
109014
109175
|
}
|
|
109015
109176
|
try {
|
|
109016
|
-
await
|
|
109017
|
-
owner: ctx.repo.owner,
|
|
109018
|
-
|
|
109019
|
-
|
|
109020
|
-
});
|
|
109177
|
+
await deleteProgressCommentApi(
|
|
109178
|
+
{ octokit: ctx.octokit, owner: ctx.repo.owner, repo: ctx.repo.name },
|
|
109179
|
+
existing
|
|
109180
|
+
);
|
|
109021
109181
|
} catch (error49) {
|
|
109022
109182
|
if (error49 instanceof Error && error49.message.includes("Not Found")) {
|
|
109023
109183
|
} else {
|
|
109024
109184
|
throw error49;
|
|
109025
109185
|
}
|
|
109026
109186
|
}
|
|
109027
|
-
ctx.toolState.
|
|
109187
|
+
ctx.toolState.progressComment = null;
|
|
109028
109188
|
return true;
|
|
109029
109189
|
}
|
|
109030
109190
|
var ReplyToReviewComment = type({
|
|
@@ -142105,7 +142265,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
142105
142265
|
// package.json
|
|
142106
142266
|
var package_default = {
|
|
142107
142267
|
name: "pullfrog",
|
|
142108
|
-
version: "0.0.
|
|
142268
|
+
version: "0.0.204",
|
|
142109
142269
|
type: "module",
|
|
142110
142270
|
bin: {
|
|
142111
142271
|
pullfrog: "dist/cli.mjs",
|
|
@@ -142642,8 +142802,13 @@ async function $git(subcommand, args2, options) {
|
|
|
142642
142802
|
}
|
|
142643
142803
|
if (result.exitCode !== 0) {
|
|
142644
142804
|
const stderr = result.stderr.trim();
|
|
142645
|
-
|
|
142646
|
-
|
|
142805
|
+
const stdout = result.stdout.trim();
|
|
142806
|
+
const detail = stderr && stdout ? `${stderr}
|
|
142807
|
+
--- stdout ---
|
|
142808
|
+
${stdout}` : stderr || stdout || "(no output)";
|
|
142809
|
+
const message = `git ${subcommand} failed (exit ${result.exitCode}): ${detail}`;
|
|
142810
|
+
log.info(message);
|
|
142811
|
+
throw new Error(message);
|
|
142647
142812
|
}
|
|
142648
142813
|
return {
|
|
142649
142814
|
stdout: result.stdout.trim(),
|
|
@@ -142920,6 +143085,34 @@ var PushBranch = type({
|
|
|
142920
143085
|
branchName: type.string.describe("The branch name to push (defaults to current branch)").optional(),
|
|
142921
143086
|
force: type.boolean.describe("Force push (use with caution)").default(false)
|
|
142922
143087
|
});
|
|
143088
|
+
var CONCURRENT_PUSH_PATTERNS = ["fetch first", "non-fast-forward", "cannot lock ref"];
|
|
143089
|
+
var TRANSIENT_PATTERNS = [
|
|
143090
|
+
/RPC failed/i,
|
|
143091
|
+
/early EOF/,
|
|
143092
|
+
/the remote end hung up unexpectedly/,
|
|
143093
|
+
/Connection reset/i,
|
|
143094
|
+
/Could not resolve host/i,
|
|
143095
|
+
/Operation timed out/i,
|
|
143096
|
+
/HTTP\/2 stream \d+ was not closed cleanly/i,
|
|
143097
|
+
/unexpected disconnect while reading sideband packet/i,
|
|
143098
|
+
// libcurl HTTP 5xx surfaced by git over https. matches both the
|
|
143099
|
+
// libcurl-style "The requested URL returned error: 502" and the more
|
|
143100
|
+
// recent "HTTP 502" wording. most 4xx is intentionally excluded —
|
|
143101
|
+
// 401/403/404 indicate auth/permission problems that are not
|
|
143102
|
+
// retry-safe — but 429 (rate-limited / abuse detection) IS retry-safe
|
|
143103
|
+
// and GitHub occasionally surfaces it on git push, so it's included
|
|
143104
|
+
// explicitly below.
|
|
143105
|
+
/HTTP 5\d\d/,
|
|
143106
|
+
/returned error: 5\d\d/i,
|
|
143107
|
+
/HTTP 429/,
|
|
143108
|
+
/returned error: 429/i
|
|
143109
|
+
];
|
|
143110
|
+
function classifyPushError(msg) {
|
|
143111
|
+
if (CONCURRENT_PUSH_PATTERNS.some((p) => msg.includes(p))) return "concurrent-push";
|
|
143112
|
+
if (TRANSIENT_PATTERNS.some((p) => p.test(msg))) return "transient";
|
|
143113
|
+
return "unknown";
|
|
143114
|
+
}
|
|
143115
|
+
var TRANSIENT_RETRY_DELAYS_MS = [2e3, 5e3];
|
|
142923
143116
|
function PushBranchTool(ctx) {
|
|
142924
143117
|
const defaultBranch = ctx.repo.data.default_branch || "main";
|
|
142925
143118
|
const pushPermission = ctx.payload.push;
|
|
@@ -142970,25 +143163,48 @@ ${postHookStatus}`
|
|
|
142970
143163
|
if (force) {
|
|
142971
143164
|
log.warning(`force pushing - this will overwrite remote history`);
|
|
142972
143165
|
}
|
|
142973
|
-
|
|
142974
|
-
|
|
142975
|
-
|
|
142976
|
-
|
|
142977
|
-
|
|
142978
|
-
|
|
142979
|
-
|
|
142980
|
-
|
|
142981
|
-
|
|
142982
|
-
|
|
143166
|
+
let lastErr;
|
|
143167
|
+
let pushed = false;
|
|
143168
|
+
for (let attempt = 0; attempt <= TRANSIENT_RETRY_DELAYS_MS.length; attempt++) {
|
|
143169
|
+
try {
|
|
143170
|
+
await $git("push", pushArgs, {
|
|
143171
|
+
token: ctx.gitToken
|
|
143172
|
+
});
|
|
143173
|
+
if (attempt > 0) {
|
|
143174
|
+
log.info(`push succeeded on attempt ${attempt + 1}`);
|
|
143175
|
+
}
|
|
143176
|
+
pushed = true;
|
|
143177
|
+
break;
|
|
143178
|
+
} catch (err) {
|
|
143179
|
+
lastErr = err;
|
|
143180
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
143181
|
+
const kind = classifyPushError(msg);
|
|
143182
|
+
if (kind === "concurrent-push") {
|
|
143183
|
+
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')`;
|
|
143184
|
+
throw new Error(
|
|
143185
|
+
`push rejected: the remote branch '${pushDest.remoteBranch}' has new commits you don't have locally (often a concurrent push to the same branch).
|
|
142983
143186
|
|
|
142984
143187
|
to resolve this:
|
|
142985
143188
|
1. use git_fetch to fetch the remote branch: git_fetch({ ref: "${pushDest.remoteBranch}" })
|
|
142986
143189
|
${integrateStep}
|
|
142987
143190
|
3. resolve any merge conflicts if needed
|
|
142988
143191
|
4. retry push_branch`
|
|
142989
|
-
|
|
143192
|
+
);
|
|
143193
|
+
}
|
|
143194
|
+
if (kind === "transient" && attempt < TRANSIENT_RETRY_DELAYS_MS.length) {
|
|
143195
|
+
const baseDelay = TRANSIENT_RETRY_DELAYS_MS[attempt] ?? 5e3;
|
|
143196
|
+
const delay2 = Math.round(baseDelay * (0.75 + Math.random() * 0.5));
|
|
143197
|
+
log.info(
|
|
143198
|
+
`push attempt ${attempt + 1} failed (transient), retrying in ${delay2}ms: ${msg.slice(0, 300)}`
|
|
143199
|
+
);
|
|
143200
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
143201
|
+
continue;
|
|
143202
|
+
}
|
|
143203
|
+
throw err;
|
|
142990
143204
|
}
|
|
142991
|
-
|
|
143205
|
+
}
|
|
143206
|
+
if (!pushed) {
|
|
143207
|
+
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
142992
143208
|
}
|
|
142993
143209
|
return {
|
|
142994
143210
|
success: true,
|
|
@@ -143246,6 +143462,18 @@ function validateInlineComments(comments, map2) {
|
|
|
143246
143462
|
return { valid, dropped };
|
|
143247
143463
|
}
|
|
143248
143464
|
var MAX_DROPPED_COMMENT_LINES = 50;
|
|
143465
|
+
function duplicateReviewDecision(params) {
|
|
143466
|
+
const existing = params.existing;
|
|
143467
|
+
if (!existing) return null;
|
|
143468
|
+
if (params.currentCheckoutSha && existing.reviewedSha && params.currentCheckoutSha !== existing.reviewedSha) {
|
|
143469
|
+
return null;
|
|
143470
|
+
}
|
|
143471
|
+
return {
|
|
143472
|
+
kind: "already-submitted",
|
|
143473
|
+
reviewId: existing.id,
|
|
143474
|
+
reason: `review ${existing.id} was already submitted in this session; ignoring duplicate call (call \`checkout_pr\` again first if new commits were pushed)`
|
|
143475
|
+
};
|
|
143476
|
+
}
|
|
143249
143477
|
function reviewSkipDecision(params) {
|
|
143250
143478
|
if (params.body || params.hasComments) return null;
|
|
143251
143479
|
if (!params.approved) {
|
|
@@ -143315,6 +143543,19 @@ function CreatePullRequestReviewTool(ctx) {
|
|
|
143315
143543
|
execute: execute(async ({ pull_number, body, approved, commit_id, comments = [] }) => {
|
|
143316
143544
|
if (body) body = fixDoubleEscapedString(body);
|
|
143317
143545
|
ctx.toolState.issueNumber = pull_number;
|
|
143546
|
+
const dup = duplicateReviewDecision({
|
|
143547
|
+
existing: ctx.toolState.review,
|
|
143548
|
+
currentCheckoutSha: ctx.toolState.checkoutSha
|
|
143549
|
+
});
|
|
143550
|
+
if (dup) {
|
|
143551
|
+
log.info(`skipping duplicate review submission: ${dup.reason}`);
|
|
143552
|
+
return {
|
|
143553
|
+
success: true,
|
|
143554
|
+
skipped: true,
|
|
143555
|
+
reason: dup.reason,
|
|
143556
|
+
reviewId: dup.reviewId
|
|
143557
|
+
};
|
|
143558
|
+
}
|
|
143318
143559
|
const skip = reviewSkipDecision({
|
|
143319
143560
|
approved: approved ?? false,
|
|
143320
143561
|
body,
|
|
@@ -143432,6 +143673,9 @@ function CreatePullRequestReviewTool(ctx) {
|
|
|
143432
143673
|
nodeId: reviewNodeId,
|
|
143433
143674
|
reviewedSha: actuallyReviewedSha
|
|
143434
143675
|
};
|
|
143676
|
+
await deleteProgressComment(ctx).catch((err) => {
|
|
143677
|
+
log.debug(`progress comment cleanup after review failed: ${err}`);
|
|
143678
|
+
});
|
|
143435
143679
|
if (ctx.toolState.checkoutSha && latestHeadSha && latestHeadSha !== ctx.toolState.checkoutSha) {
|
|
143436
143680
|
const fromSha = ctx.toolState.checkoutSha;
|
|
143437
143681
|
const toSha = latestHeadSha;
|
|
@@ -145014,35 +145258,20 @@ async function getReviewThreads(input) {
|
|
|
145014
145258
|
const username = input.approvedBy;
|
|
145015
145259
|
return threadsForReview.filter((thread) => threadHasThumbsUpFrom(thread, username));
|
|
145016
145260
|
}
|
|
145017
|
-
|
|
145018
|
-
const
|
|
145019
|
-
input.octokit.rest.pulls.getReview({
|
|
145020
|
-
owner: input.owner,
|
|
145021
|
-
repo: input.name,
|
|
145022
|
-
pull_number: input.pullNumber,
|
|
145023
|
-
review_id: input.reviewId
|
|
145024
|
-
}),
|
|
145025
|
-
getReviewThreads(input)
|
|
145026
|
-
]);
|
|
145027
|
-
const rawReviewBody = review.data.body;
|
|
145261
|
+
function formatReviewData(input) {
|
|
145262
|
+
const rawReviewBody = input.review.body;
|
|
145028
145263
|
const reviewBody = rawReviewBody ? stripExistingFooter(rawReviewBody) : "";
|
|
145029
|
-
const reviewer = review.
|
|
145030
|
-
if (threads.length === 0 && !reviewBody) return void 0;
|
|
145264
|
+
const reviewer = input.review.user?.login ?? "unknown";
|
|
145265
|
+
if (input.threads.length === 0 && !reviewBody) return void 0;
|
|
145031
145266
|
let threadBlocks = [];
|
|
145032
|
-
if (threads.length > 0) {
|
|
145033
|
-
const prFiles = await input.octokit.paginate(input.octokit.rest.pulls.listFiles, {
|
|
145034
|
-
owner: input.owner,
|
|
145035
|
-
repo: input.name,
|
|
145036
|
-
pull_number: input.pullNumber,
|
|
145037
|
-
per_page: 100
|
|
145038
|
-
});
|
|
145267
|
+
if (input.threads.length > 0) {
|
|
145039
145268
|
const filePatchMap = /* @__PURE__ */ new Map();
|
|
145040
|
-
for (const file2 of prFiles) {
|
|
145269
|
+
for (const file2 of input.prFiles) {
|
|
145041
145270
|
if (file2.patch) {
|
|
145042
145271
|
filePatchMap.set(file2.filename, parseFilePatches(file2.patch));
|
|
145043
145272
|
}
|
|
145044
145273
|
}
|
|
145045
|
-
threadBlocks = buildThreadBlocks(threads, filePatchMap, input.reviewId);
|
|
145274
|
+
threadBlocks = buildThreadBlocks(input.threads, filePatchMap, input.reviewId);
|
|
145046
145275
|
}
|
|
145047
145276
|
const formatted = formatReviewThreads(threadBlocks, {
|
|
145048
145277
|
pullNumber: input.pullNumber,
|
|
@@ -145052,6 +145281,30 @@ async function getReviewData(input) {
|
|
|
145052
145281
|
});
|
|
145053
145282
|
return { threadBlocks, reviewer, formatted };
|
|
145054
145283
|
}
|
|
145284
|
+
async function getReviewData(input) {
|
|
145285
|
+
const [review, threads] = await Promise.all([
|
|
145286
|
+
input.octokit.rest.pulls.getReview({
|
|
145287
|
+
owner: input.owner,
|
|
145288
|
+
repo: input.name,
|
|
145289
|
+
pull_number: input.pullNumber,
|
|
145290
|
+
review_id: input.reviewId
|
|
145291
|
+
}),
|
|
145292
|
+
getReviewThreads(input)
|
|
145293
|
+
]);
|
|
145294
|
+
const prFiles = threads.length > 0 ? await input.octokit.paginate(input.octokit.rest.pulls.listFiles, {
|
|
145295
|
+
owner: input.owner,
|
|
145296
|
+
repo: input.name,
|
|
145297
|
+
pull_number: input.pullNumber,
|
|
145298
|
+
per_page: 100
|
|
145299
|
+
}) : [];
|
|
145300
|
+
return formatReviewData({
|
|
145301
|
+
review: review.data,
|
|
145302
|
+
threads,
|
|
145303
|
+
prFiles,
|
|
145304
|
+
pullNumber: input.pullNumber,
|
|
145305
|
+
reviewId: input.reviewId
|
|
145306
|
+
});
|
|
145307
|
+
}
|
|
145055
145308
|
function GetReviewCommentsTool(ctx) {
|
|
145056
145309
|
return tool({
|
|
145057
145310
|
name: "get_review_comments",
|
|
@@ -145175,6 +145428,18 @@ function ResolveReviewThreadTool(ctx) {
|
|
|
145175
145428
|
});
|
|
145176
145429
|
}
|
|
145177
145430
|
|
|
145431
|
+
// agents/reviewer.ts
|
|
145432
|
+
var REVIEWER_AGENT_NAME = "reviewfrog";
|
|
145433
|
+
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.
|
|
145434
|
+
|
|
145435
|
+
HARD CONSTRAINTS (non-negotiable, regardless of orchestrator instructions):
|
|
145436
|
+
- 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).
|
|
145437
|
+
- 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.
|
|
145438
|
+
- Do NOT spawn further subagents. You are a leaf reviewer; recursive dispatch pre-aggregates findings through an intermediate model and defeats the design.
|
|
145439
|
+
- 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.
|
|
145440
|
+
|
|
145441
|
+
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.`;
|
|
145442
|
+
|
|
145178
145443
|
// modes.ts
|
|
145179
145444
|
var PR_SUMMARY_FORMAT = `### Default format
|
|
145180
145445
|
|
|
@@ -145246,9 +145511,36 @@ function computeModes(agentId) {
|
|
|
145246
145511
|
- 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.
|
|
145247
145512
|
- run relevant tests/lints before committing
|
|
145248
145513
|
|
|
145249
|
-
4. **self-review**:
|
|
145250
|
-
|
|
145251
|
-
- commit
|
|
145514
|
+
4. **self-review**: judgment call \u2014 does YOUR diff warrant a fresh-eyes pass?
|
|
145515
|
+
|
|
145516
|
+
Skip self-review (commit directly) when the diff is **genuinely trivial**:
|
|
145517
|
+
- doc typos, comment-only edits, whitespace/format-only, import reordering
|
|
145518
|
+
- 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)
|
|
145519
|
+
- low-risk dep patch bump from a trusted source
|
|
145520
|
+
|
|
145521
|
+
Run self-review when the diff has **any behavioral surface, however small**:
|
|
145522
|
+
- 1-line changes to SQL operators / comparison logic / regexes / redirects / HTTP methods / response codes
|
|
145523
|
+
- any change to money / tax / currency / billing / fee / refund / payout calculations or constants
|
|
145524
|
+
- any change to auth / permissions / roles / sessions / tokens / signature verification
|
|
145525
|
+
- any change to feature-flag defaults, retry counts, timeouts, rate limits, batch sizes
|
|
145526
|
+
- new endpoints, new code paths, new error branches \u2014 even small ones
|
|
145527
|
+
- mixed diffs (whitespace + a single semantic line) \u2014 the semantic line still triggers self-review
|
|
145528
|
+
- anything you're uncertain about
|
|
145529
|
+
|
|
145530
|
+
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.
|
|
145531
|
+
|
|
145532
|
+
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.
|
|
145533
|
+
|
|
145534
|
+
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.
|
|
145535
|
+
|
|
145536
|
+
Delegation + research discipline (distilled from \`/anneal\` canonical \u2014 these are codified learnings from many review rounds, not theoretical best practices):
|
|
145537
|
+
- Do NOT summarize what you implemented \u2014 that biases the subagent toward validating the shape of your solution rather than questioning it.
|
|
145538
|
+
- Do NOT curate a reading list of files. Let the subagent discover scope from the diff and codebase.
|
|
145539
|
+
- Do NOT pre-shape output with a severity / category schema. That leaks your hypotheses; severity is your call during evaluation.
|
|
145540
|
+
- 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.
|
|
145541
|
+
- 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.
|
|
145542
|
+
|
|
145543
|
+
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 "..."\`).
|
|
145252
145544
|
|
|
145253
145545
|
5. **finalize**:
|
|
145254
145546
|
- confirm a clean working tree, then push via \`${t("push_branch")}\` (see *SYSTEM* Git rules if this fails \u2014 prepush errors are usually the repo's tests/lint, not infra timeouts)
|
|
@@ -145272,11 +145564,12 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
145272
145564
|
|
|
145273
145565
|
3. For each comment:
|
|
145274
145566
|
- understand the feedback
|
|
145275
|
-
-
|
|
145276
|
-
-
|
|
145567
|
+
- 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.
|
|
145568
|
+
- if the request stands, make the code change using your native tools; otherwise reply explaining why
|
|
145569
|
+
- record what was done (or why nothing was done)
|
|
145277
145570
|
|
|
145278
145571
|
4. Quality check:
|
|
145279
|
-
- 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
|
|
145572
|
+
- 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
|
|
145280
145573
|
- commit locally via shell (\`git add . && git commit -m "..."\`)
|
|
145281
145574
|
|
|
145282
145575
|
5. Finalize:
|
|
@@ -145287,28 +145580,93 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
145287
145580
|
|
|
145288
145581
|
${learningsStep(t, 6)}`
|
|
145289
145582
|
},
|
|
145583
|
+
// Review and IncrementalReview use the multi-lens orchestrator pattern
|
|
145584
|
+
// (canonical source: .claude/commands/anneal.md). The orchestrator does
|
|
145585
|
+
// triage → parallel read-only subagent fan-out → aggregate → draft comments
|
|
145586
|
+
// → submit. For someone else's PR, parallel lenses (correctness, security,
|
|
145587
|
+
// research-validated claims, user-journey, etc.) provide breadth across
|
|
145588
|
+
// angles that a single subagent can't carry coherently. Build mode keeps
|
|
145589
|
+
// a single fresh-eyes subagent (different problem shape — orchestrator
|
|
145590
|
+
// wrote the code and bias-mitigation comes from delegating to one
|
|
145591
|
+
// subagent that doesn't share the implementation context).
|
|
145592
|
+
// Deliberate omission vs canonical /anneal: severity categorization in the
|
|
145593
|
+
// final message (the review body has its own CAUTION/IMPORTANT framing
|
|
145594
|
+
// instead of a severity table).
|
|
145290
145595
|
{
|
|
145291
145596
|
name: "Review",
|
|
145292
145597
|
description: "Review code, PRs, or implementations; provide feedback or suggestions; identify issues; or check code quality, style, and correctness",
|
|
145293
145598
|
prompt: `### Checklist
|
|
145294
145599
|
|
|
145295
|
-
1.
|
|
145296
|
-
|
|
145297
|
-
2.
|
|
145298
|
-
|
|
145299
|
-
|
|
145300
|
-
|
|
145301
|
-
|
|
145302
|
-
-
|
|
145303
|
-
-
|
|
145304
|
-
-
|
|
145305
|
-
-
|
|
145306
|
-
|
|
145307
|
-
|
|
145600
|
+
1. **checkout**: call \`${t("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.
|
|
145601
|
+
|
|
145602
|
+
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 \`${t("get_pull_request")}\` and other read-only GitHub tools for additional context if needed.
|
|
145603
|
+
|
|
145604
|
+
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.
|
|
145605
|
+
|
|
145606
|
+
"Genuinely trivial" (skip):
|
|
145607
|
+
- single-word doc typo, whitespace/format-only, comment-only across any number of files
|
|
145608
|
+
- lockfile or generated-code regeneration (size of diff is irrelevant \u2014 read the *shape*)
|
|
145609
|
+
- mechanical rename whose only effect is import-path updates
|
|
145610
|
+
- low-risk dep patch bump
|
|
145611
|
+
|
|
145612
|
+
"Looks trivial but isn't" (do **NOT** skip \u2014 small diff, big blast radius):
|
|
145613
|
+
- any 1-line change to SQL / regex / auth / billing / permission / signature-verification code
|
|
145614
|
+
- flipping a feature-flag default, default config value, or retry/timeout constant
|
|
145615
|
+
- changing a money/tax/currency/fee constant by any amount
|
|
145616
|
+
- changing an HTTP method, redirect URL, response code, or status enum
|
|
145617
|
+
- tightening or loosening a comparison operator (\`<\` \u2194 \`<=\`, \`==\` \u2194 \`!=\`)
|
|
145618
|
+
- renaming a public API surface (still trivial in shape, but needs an impact lens)
|
|
145619
|
+
- adding a new direct dependency (supply-chain surface)
|
|
145620
|
+
- any "typo fix" in user-facing copy that changes meaning ("approved" \u2192 "denied")
|
|
145621
|
+
- mixed diffs where a semantic 1-liner is buried in whitespace/formatting changes
|
|
145622
|
+
|
|
145623
|
+
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.
|
|
145624
|
+
|
|
145625
|
+
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:
|
|
145626
|
+
|
|
145627
|
+
- **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)
|
|
145628
|
+
- **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)
|
|
145629
|
+
- **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)
|
|
145630
|
+
- **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.
|
|
145631
|
+
|
|
145632
|
+
lenses come in two flavors, and you can mix them:
|
|
145633
|
+
- **themed lenses** \u2014 a perspective applied across the whole diff (correctness, security, user-journey, performance, etc.).
|
|
145634
|
+
- **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.
|
|
145635
|
+
|
|
145636
|
+
starter menu (combine, omit, or invent your own):
|
|
145637
|
+
- **correctness & invariants** \u2014 bugs, races, error handling, edge cases, state-machine boundaries
|
|
145638
|
+
- **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
|
|
145639
|
+
- **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.
|
|
145640
|
+
- **security** \u2014 new endpoints, authZ, input validation, secrets handling, replay/CSRF/injection, cross-tenant isolation
|
|
145641
|
+
- **user-journey** \u2014 UX-touching flows: walk through happy path and failure modes as a user
|
|
145642
|
+
- **operational readiness** \u2014 observability, alerting, migrations (forward + rollback), feature flags, on-call burden
|
|
145643
|
+
- **integration & cross-cutting** \u2014 API contracts between modules, backward-compat of public surfaces, multi-service ordering
|
|
145644
|
+
- **test integrity** \u2014 meaningful coverage for the changed behavior; deterministic; no shared-state pollution
|
|
145645
|
+
- **performance** \u2014 N+1 queries, hot-path allocation, latency budgets, index coverage
|
|
145646
|
+
- **holistic** \u2014 does the PR make sense as a whole? symmetric flows (delete for every create, rollback for every migration)?
|
|
145647
|
+
- **subsystem lenses** (invent as the PR demands) \u2014 auth, billing, payments, schema migration, webhooks, secrets, RBAC, multi-tenant isolation, cron/scheduling, etc.
|
|
145648
|
+
|
|
145649
|
+
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:
|
|
145650
|
+
- the diff path / target \u2014 reading the diff and the codebase is its job
|
|
145651
|
+
- **only one lens** \u2014 never a multi-section "review for X, Y, and Z" prompt
|
|
145652
|
+
- **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\`.
|
|
145653
|
+
- 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.
|
|
145654
|
+
- 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."
|
|
145655
|
+
- 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.
|
|
145656
|
+
|
|
145657
|
+
delegation discipline:
|
|
145658
|
+
- 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)
|
|
145659
|
+
- do NOT summarize the PR for them (biases toward a validation frame)
|
|
145660
|
+
- do NOT hand them a curated reading list (let them discover scope)
|
|
145661
|
+
- do NOT pre-shape their output with a finding schema
|
|
145662
|
+
- do NOT mention the other lenses (independence is the point \u2014 overlapping findings are a strong signal)
|
|
145663
|
+
|
|
145664
|
+
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.
|
|
145665
|
+
|
|
145666
|
+
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.
|
|
145667
|
+
|
|
145668
|
+
5. **submit**: ALWAYS submit exactly one review via \`${t("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.
|
|
145308
145669
|
|
|
145309
|
-
4. Submit \u2014 ALWAYS submit exactly one review via \`${t("create_pull_request_review")}\`.
|
|
145310
|
-
Do NOT call \`report_progress\` \u2014 the review is the final record and the progress
|
|
145311
|
-
comment will be cleaned up automatically.
|
|
145312
145670
|
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.
|
|
145313
145671
|
|
|
145314
145672
|
- **critical issues** (blocks merge \u2014 bugs, security, data loss):
|
|
@@ -145322,29 +145680,56 @@ ${learningsStep(t, 6)}`
|
|
|
145322
145680
|
- **no actionable issues**:
|
|
145323
145681
|
\`approved: true\`, body: "Reviewed \u2014 no issues found."`
|
|
145324
145682
|
},
|
|
145683
|
+
// IncrementalReview shares Review's multi-lens orchestrator pattern but
|
|
145684
|
+
// scopes the target to the incremental diff and adds prior-review-feedback
|
|
145685
|
+
// tracking. The "issues must be NEW since the last Pullfrog review" filter
|
|
145686
|
+
// lives at aggregation time (step 5), NOT in the subagent prompt — pushing
|
|
145687
|
+
// the filter into subagents matches the canonical anneal anti-pattern of
|
|
145688
|
+
// "list known pre-existing failures — don't flag these" and suppresses
|
|
145689
|
+
// signal on regressions the new commits amplified. The body-format rules
|
|
145690
|
+
// (Reviewed changes / Prior review feedback) are unchanged from the prior
|
|
145691
|
+
// version. Same severity-table omission as Review.
|
|
145325
145692
|
{
|
|
145326
145693
|
name: "IncrementalReview",
|
|
145327
145694
|
description: "Re-review a PR after new commits are pushed; focus on new changes since the last review",
|
|
145328
145695
|
prompt: `### Checklist
|
|
145329
145696
|
|
|
145330
|
-
1.
|
|
145697
|
+
1. **checkout**: call \`${t("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.
|
|
145698
|
+
|
|
145699
|
+
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.
|
|
145700
|
+
|
|
145701
|
+
3. **prior feedback**: fetch previous reviews via \`${t("list_pull_request_reviews")}\`. for the most recent Pullfrog review, call \`${t("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.
|
|
145702
|
+
|
|
145703
|
+
4. **triage & fan out**: orient on the *incremental* changes \u2014 domain, seams, external contracts, user-facing surfaces.
|
|
145704
|
+
|
|
145705
|
+
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).
|
|
145706
|
+
|
|
145707
|
+
"Genuinely trivial" (skip): formatting/comment tweaks, import reordering, lockfile regen, mechanical rename of import paths, whitespace-only.
|
|
145708
|
+
"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.
|
|
145709
|
+
When unsure, treat as non-trivial.
|
|
145331
145710
|
|
|
145332
|
-
|
|
145711
|
+
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.
|
|
145333
145712
|
|
|
145334
|
-
|
|
145713
|
+
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:
|
|
145714
|
+
- 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
|
|
145715
|
+
- **only one lens** \u2014 never a multi-section "review for X, Y, and Z" prompt
|
|
145716
|
+
- **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\`.
|
|
145717
|
+
- 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.
|
|
145718
|
+
- 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."
|
|
145719
|
+
- ask the subagent to report findings with file paths and NEW line numbers from the full PR diff so you can anchor inline comments.
|
|
145335
145720
|
|
|
145336
|
-
|
|
145337
|
-
- review the
|
|
145338
|
-
-
|
|
145339
|
-
-
|
|
145340
|
-
-
|
|
145341
|
-
-
|
|
145342
|
-
- draft inline comments with NEW line numbers from the full PR diff \u2014 every comment must be actionable (2-3 sentences max)
|
|
145343
|
-
- 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.
|
|
145721
|
+
delegation discipline:
|
|
145722
|
+
- do NOT lens-review the diff yourself in parallel with the subagents
|
|
145723
|
+
- do NOT summarize the changes for them (biases toward validation frame)
|
|
145724
|
+
- do NOT hand them a curated reading list (let them discover scope)
|
|
145725
|
+
- do NOT pre-shape their output with a finding schema
|
|
145726
|
+
- do NOT mention the other lenses (independence is the point)
|
|
145344
145727
|
|
|
145345
|
-
5.
|
|
145728
|
+
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 \`${t("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.
|
|
145346
145729
|
|
|
145347
|
-
|
|
145730
|
+
then check: which prior review comments were addressed by the new commits? track the addressed ones for step 6b.
|
|
145731
|
+
|
|
145732
|
+
6. **build the review body** \u2014 two distinct sections:
|
|
145348
145733
|
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.
|
|
145349
145734
|
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.
|
|
145350
145735
|
- no headings, no tables, no prose paragraphs in either section \u2014 just bullets
|
|
@@ -145936,14 +146321,13 @@ function UploadFileTool(ctx) {
|
|
|
145936
146321
|
|
|
145937
146322
|
// mcp/server.ts
|
|
145938
146323
|
function initToolState(params) {
|
|
145939
|
-
const
|
|
145940
|
-
|
|
145941
|
-
|
|
145942
|
-
log.info(`\xBB using pre-created progress comment: ${resolvedId}`);
|
|
146324
|
+
const resolved = parseProgressComment(params.progressComment);
|
|
146325
|
+
if (resolved) {
|
|
146326
|
+
log.info(`\xBB using pre-created progress comment: ${resolved.id} (${resolved.type})`);
|
|
145943
146327
|
}
|
|
145944
146328
|
return {
|
|
145945
|
-
|
|
145946
|
-
hadProgressComment: !!
|
|
146329
|
+
progressComment: resolved,
|
|
146330
|
+
hadProgressComment: !!resolved,
|
|
145947
146331
|
backgroundProcesses: /* @__PURE__ */ new Map(),
|
|
145948
146332
|
usageEntries: []
|
|
145949
146333
|
};
|
|
@@ -146122,8 +146506,8 @@ async function startMcpHttpServer(ctx, options) {
|
|
|
146122
146506
|
|
|
146123
146507
|
// agents/claude.ts
|
|
146124
146508
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
146125
|
-
import { mkdirSync as
|
|
146126
|
-
import { join as
|
|
146509
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "node:fs";
|
|
146510
|
+
import { join as join10 } from "node:path";
|
|
146127
146511
|
import { performance as performance6 } from "node:perf_hooks";
|
|
146128
146512
|
|
|
146129
146513
|
// utils/install.ts
|
|
@@ -146232,8 +146616,35 @@ function detectProviderError(text) {
|
|
|
146232
146616
|
|
|
146233
146617
|
// utils/skills.ts
|
|
146234
146618
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
146619
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "node:fs";
|
|
146235
146620
|
import { tmpdir } from "node:os";
|
|
146621
|
+
import { dirname as dirname2, join as join9 } from "node:path";
|
|
146622
|
+
import { fileURLToPath } from "node:url";
|
|
146236
146623
|
var skillsVersion = getDevDependencyVersion("skills");
|
|
146624
|
+
var BUNDLED_SKILL_NAMES = ["git-archaeology"];
|
|
146625
|
+
function resolveSkillPath(name) {
|
|
146626
|
+
const here = dirname2(fileURLToPath(import.meta.url));
|
|
146627
|
+
const candidates = [
|
|
146628
|
+
join9(here, "..", "skills", name, "SKILL.md"),
|
|
146629
|
+
join9(here, "skills", name, "SKILL.md")
|
|
146630
|
+
];
|
|
146631
|
+
for (const candidate of candidates) {
|
|
146632
|
+
if (existsSync6(candidate)) return candidate;
|
|
146633
|
+
}
|
|
146634
|
+
throw new Error(`bundled skill not found: ${name} (looked in ${candidates.join(", ")})`);
|
|
146635
|
+
}
|
|
146636
|
+
var SKILL_TARGET_DIRS = [".opencode/skills", ".claude/skills", ".agents/skills"];
|
|
146637
|
+
function installBundledSkills(params) {
|
|
146638
|
+
for (const name of BUNDLED_SKILL_NAMES) {
|
|
146639
|
+
const content = readFileSync4(resolveSkillPath(name), "utf8");
|
|
146640
|
+
for (const targetDir of SKILL_TARGET_DIRS) {
|
|
146641
|
+
const skillDir = join9(params.home, targetDir, name);
|
|
146642
|
+
mkdirSync3(skillDir, { recursive: true });
|
|
146643
|
+
writeFileSync6(join9(skillDir, "SKILL.md"), content);
|
|
146644
|
+
}
|
|
146645
|
+
}
|
|
146646
|
+
log.info(`installed bundled skills: ${BUNDLED_SKILL_NAMES.join(", ")}`);
|
|
146647
|
+
}
|
|
146237
146648
|
function addSkill(params) {
|
|
146238
146649
|
const result = spawnSync5(
|
|
146239
146650
|
"npx",
|
|
@@ -146307,6 +146718,213 @@ var ThinkingTimer = class {
|
|
|
146307
146718
|
}
|
|
146308
146719
|
};
|
|
146309
146720
|
|
|
146721
|
+
// agents/postRun.ts
|
|
146722
|
+
var MAX_HOOK_OUTPUT_CHARS = 4096;
|
|
146723
|
+
function truncateHookOutput(raw2) {
|
|
146724
|
+
if (raw2.length <= MAX_HOOK_OUTPUT_CHARS) return raw2;
|
|
146725
|
+
return `...(truncated, showing last ${MAX_HOOK_OUTPUT_CHARS} chars)
|
|
146726
|
+
${raw2.slice(-MAX_HOOK_OUTPUT_CHARS)}`;
|
|
146727
|
+
}
|
|
146728
|
+
async function executeStopHook(script) {
|
|
146729
|
+
log.info("\xBB executing stop hook...");
|
|
146730
|
+
try {
|
|
146731
|
+
const result = await spawn({
|
|
146732
|
+
cmd: "bash",
|
|
146733
|
+
args: ["-c", script],
|
|
146734
|
+
env: process.env,
|
|
146735
|
+
timeout: LIFECYCLE_HOOK_TIMEOUT_MS,
|
|
146736
|
+
activityTimeout: 0,
|
|
146737
|
+
onStdout: (chunk) => process.stdout.write(chunk),
|
|
146738
|
+
onStderr: (chunk) => process.stderr.write(chunk)
|
|
146739
|
+
});
|
|
146740
|
+
if (result.exitCode === 0) {
|
|
146741
|
+
log.info("\xBB stop hook passed");
|
|
146742
|
+
return null;
|
|
146743
|
+
}
|
|
146744
|
+
const combined = [result.stderr.trim(), result.stdout.trim()].filter(Boolean).join("\n");
|
|
146745
|
+
const output = truncateHookOutput(combined);
|
|
146746
|
+
log.info(`\xBB stop hook failed with exit code ${result.exitCode}`);
|
|
146747
|
+
return { exitCode: result.exitCode, output };
|
|
146748
|
+
} catch (err) {
|
|
146749
|
+
const isTimeout = err instanceof SpawnTimeoutError && (err.code === SPAWN_TIMEOUT_CODE || err.code === SPAWN_ACTIVITY_TIMEOUT_CODE);
|
|
146750
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
146751
|
+
log.warning(
|
|
146752
|
+
`stop hook ${isTimeout ? "timed out" : "failed to spawn"}: ${msg} \u2014 skipping retry`
|
|
146753
|
+
);
|
|
146754
|
+
return null;
|
|
146755
|
+
}
|
|
146756
|
+
}
|
|
146757
|
+
function buildStopHookPrompt(failure) {
|
|
146758
|
+
return [
|
|
146759
|
+
`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.`,
|
|
146760
|
+
"",
|
|
146761
|
+
"```",
|
|
146762
|
+
failure.output || "(no output)",
|
|
146763
|
+
"```"
|
|
146764
|
+
].join("\n");
|
|
146765
|
+
}
|
|
146766
|
+
async function collectPostRunIssues(params) {
|
|
146767
|
+
const issues = {};
|
|
146768
|
+
if (params.stopScript) {
|
|
146769
|
+
const failure = await executeStopHook(params.stopScript);
|
|
146770
|
+
if (failure) issues.stopHook = failure;
|
|
146771
|
+
}
|
|
146772
|
+
const status = getGitStatus();
|
|
146773
|
+
if (status) issues.dirtyTree = status;
|
|
146774
|
+
return issues;
|
|
146775
|
+
}
|
|
146776
|
+
function buildPostRunPrompt(issues) {
|
|
146777
|
+
const parts = [];
|
|
146778
|
+
if (issues.stopHook) parts.push(buildStopHookPrompt(issues.stopHook));
|
|
146779
|
+
if (issues.dirtyTree) parts.push(buildCommitPrompt(issues.dirtyTree));
|
|
146780
|
+
return parts.join("\n\n---\n\n");
|
|
146781
|
+
}
|
|
146782
|
+
function buildLearningsReflectionPrompt(agentId) {
|
|
146783
|
+
const t = (name) => formatMcpToolRef(agentId, name);
|
|
146784
|
+
return [
|
|
146785
|
+
`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?`,
|
|
146786
|
+
"",
|
|
146787
|
+
`if so, call \`${t("update_learnings")}\` to persist it.`,
|
|
146788
|
+
"",
|
|
146789
|
+
`rules:`,
|
|
146790
|
+
`- only call \`${t("update_learnings")}\` when the finding is high-confidence and broadly useful. skip if unsure, speculative, or one-off.`,
|
|
146791
|
+
`- pass the FULL merged list: existing learnings from the original prompt + your new discoveries. one fact per bullet, lines starting with \`- \`.`,
|
|
146792
|
+
`- deduplicate, and drop bullets that are clearly wrong or no longer relevant to the current codebase.`,
|
|
146793
|
+
`- if you already called \`${t("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.`
|
|
146794
|
+
].join("\n");
|
|
146795
|
+
}
|
|
146796
|
+
async function runPostRunRetryLoop(params) {
|
|
146797
|
+
let result = params.initialResult;
|
|
146798
|
+
let aggregatedUsage = params.initialUsage;
|
|
146799
|
+
let finalIssues = {};
|
|
146800
|
+
let gateResumeCount = 0;
|
|
146801
|
+
let pendingReflection = params.reflectionPrompt;
|
|
146802
|
+
while (gateResumeCount < MAX_POST_RUN_RETRIES) {
|
|
146803
|
+
if (!result.success) break;
|
|
146804
|
+
const issues = await collectPostRunIssues({ stopScript: params.stopScript });
|
|
146805
|
+
finalIssues = issues;
|
|
146806
|
+
if (!hasPostRunIssues(issues)) {
|
|
146807
|
+
if (!pendingReflection) break;
|
|
146808
|
+
if (params.canResume && !params.canResume(result)) break;
|
|
146809
|
+
log.info("\xBB post-run reflection: nudging agent to update learnings if relevant");
|
|
146810
|
+
const preReflection = result;
|
|
146811
|
+
const reflectionResult = await params.resume({
|
|
146812
|
+
prompt: pendingReflection,
|
|
146813
|
+
previousResult: result
|
|
146814
|
+
});
|
|
146815
|
+
aggregatedUsage = mergeAgentUsage(aggregatedUsage, reflectionResult.usage);
|
|
146816
|
+
pendingReflection = void 0;
|
|
146817
|
+
if (!reflectionResult.success) {
|
|
146818
|
+
log.warning(
|
|
146819
|
+
`\xBB reflection turn failed (${reflectionResult.error ?? "unknown error"}), preserving prior successful result`
|
|
146820
|
+
);
|
|
146821
|
+
result = preReflection;
|
|
146822
|
+
break;
|
|
146823
|
+
}
|
|
146824
|
+
result = {
|
|
146825
|
+
...reflectionResult,
|
|
146826
|
+
output: preReflection.output || reflectionResult.output
|
|
146827
|
+
};
|
|
146828
|
+
continue;
|
|
146829
|
+
}
|
|
146830
|
+
if (params.canResume && !params.canResume(result)) {
|
|
146831
|
+
log.info("\xBB post-run retry skipped: cannot resume agent session");
|
|
146832
|
+
break;
|
|
146833
|
+
}
|
|
146834
|
+
log.info(`\xBB post-run retry (attempt ${gateResumeCount + 1}/${MAX_POST_RUN_RETRIES})`);
|
|
146835
|
+
const prompt = buildPostRunPrompt(issues);
|
|
146836
|
+
result = await params.resume({ prompt, previousResult: result });
|
|
146837
|
+
aggregatedUsage = mergeAgentUsage(aggregatedUsage, result.usage);
|
|
146838
|
+
gateResumeCount++;
|
|
146839
|
+
}
|
|
146840
|
+
if (gateResumeCount > 0 && result.success && hasPostRunIssues(finalIssues)) {
|
|
146841
|
+
finalIssues = await collectPostRunIssues({ stopScript: params.stopScript });
|
|
146842
|
+
}
|
|
146843
|
+
if (result.success && finalIssues.stopHook) {
|
|
146844
|
+
const retryNote = gateResumeCount > 0 ? ` after ${gateResumeCount} retry ${gateResumeCount === 1 ? "attempt" : "attempts"}` : "";
|
|
146845
|
+
return {
|
|
146846
|
+
...result,
|
|
146847
|
+
success: false,
|
|
146848
|
+
error: `stop hook failed${retryNote} (exit code ${finalIssues.stopHook.exitCode}): ${finalIssues.stopHook.output || "(no output)"}`,
|
|
146849
|
+
usage: aggregatedUsage
|
|
146850
|
+
};
|
|
146851
|
+
}
|
|
146852
|
+
return { ...result, usage: aggregatedUsage };
|
|
146853
|
+
}
|
|
146854
|
+
|
|
146855
|
+
// agents/sessionLabeler.ts
|
|
146856
|
+
var ORCHESTRATOR_LABEL = "orchestrator";
|
|
146857
|
+
var LENS_PROMPT_PATTERN = /^\s*(?:lens|Lens|LENS)\s*[:=]\s*([A-Za-z][\w &/.-]{0,60})/m;
|
|
146858
|
+
function slug(value2) {
|
|
146859
|
+
return value2.trim().toLowerCase().replace(/[^\w-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
146860
|
+
}
|
|
146861
|
+
function deriveLabelFromTaskInput(input) {
|
|
146862
|
+
if (typeof input.prompt === "string") {
|
|
146863
|
+
const match3 = input.prompt.match(LENS_PROMPT_PATTERN);
|
|
146864
|
+
if (match3?.[1]) {
|
|
146865
|
+
const slugged = slug(match3[1]);
|
|
146866
|
+
if (slugged) return `lens:${slugged}`;
|
|
146867
|
+
}
|
|
146868
|
+
}
|
|
146869
|
+
if (input.description) {
|
|
146870
|
+
const slugged = slug(input.description);
|
|
146871
|
+
if (slugged) return `lens:${slugged}`;
|
|
146872
|
+
}
|
|
146873
|
+
if (input.subagent_type) {
|
|
146874
|
+
return input.subagent_type;
|
|
146875
|
+
}
|
|
146876
|
+
return "subagent";
|
|
146877
|
+
}
|
|
146878
|
+
var SessionLabeler = class {
|
|
146879
|
+
labels = /* @__PURE__ */ new Map();
|
|
146880
|
+
pendingLabels = [];
|
|
146881
|
+
fallbackCounter = 0;
|
|
146882
|
+
recordTaskDispatch(input) {
|
|
146883
|
+
const label = deriveLabelFromTaskInput(input);
|
|
146884
|
+
this.pendingLabels.push(label);
|
|
146885
|
+
return label;
|
|
146886
|
+
}
|
|
146887
|
+
/**
|
|
146888
|
+
* Return a label for the given sessionID. Binds on first call.
|
|
146889
|
+
* Pass undefined/empty for events that lack a session id — the caller
|
|
146890
|
+
* gets ORCHESTRATOR_LABEL so the line is still attributable.
|
|
146891
|
+
*/
|
|
146892
|
+
labelFor(sessionID) {
|
|
146893
|
+
if (!sessionID) return ORCHESTRATOR_LABEL;
|
|
146894
|
+
const existing = this.labels.get(sessionID);
|
|
146895
|
+
if (existing) return existing;
|
|
146896
|
+
let label;
|
|
146897
|
+
if (this.labels.size === 0) {
|
|
146898
|
+
label = ORCHESTRATOR_LABEL;
|
|
146899
|
+
} else if (this.pendingLabels.length > 0) {
|
|
146900
|
+
label = this.pendingLabels.shift();
|
|
146901
|
+
} else {
|
|
146902
|
+
this.fallbackCounter += 1;
|
|
146903
|
+
label = `subagent#${this.fallbackCounter}`;
|
|
146904
|
+
}
|
|
146905
|
+
this.labels.set(sessionID, label);
|
|
146906
|
+
return label;
|
|
146907
|
+
}
|
|
146908
|
+
/** number of distinct sessions seen so far (for diagnostics) */
|
|
146909
|
+
size() {
|
|
146910
|
+
return this.labels.size;
|
|
146911
|
+
}
|
|
146912
|
+
/** all (sessionID, label) pairs, oldest first */
|
|
146913
|
+
entries() {
|
|
146914
|
+
return Array.from(this.labels.entries());
|
|
146915
|
+
}
|
|
146916
|
+
/** how many pending labels are queued waiting to bind to a new session */
|
|
146917
|
+
pendingDispatchCount() {
|
|
146918
|
+
return this.pendingLabels.length;
|
|
146919
|
+
}
|
|
146920
|
+
};
|
|
146921
|
+
function formatWithLabel(label, message) {
|
|
146922
|
+
const MAGENTA2 = "\x1B[35m";
|
|
146923
|
+
const RESET2 = "\x1B[0m";
|
|
146924
|
+
const colored = `${MAGENTA2}[${label}]${RESET2} `;
|
|
146925
|
+
return message.split("\n").map((line) => `${colored}${line}`).join("\n");
|
|
146926
|
+
}
|
|
146927
|
+
|
|
146310
146928
|
// agents/claude.ts
|
|
146311
146929
|
async function installClaudeCli() {
|
|
146312
146930
|
return await installFromNpmTarball({
|
|
@@ -146317,10 +146935,10 @@ async function installClaudeCli() {
|
|
|
146317
146935
|
});
|
|
146318
146936
|
}
|
|
146319
146937
|
function writeMcpConfig(ctx) {
|
|
146320
|
-
const configDir =
|
|
146321
|
-
|
|
146322
|
-
const configPath =
|
|
146323
|
-
|
|
146938
|
+
const configDir = join10(ctx.tmpdir, ".claude");
|
|
146939
|
+
mkdirSync4(configDir, { recursive: true });
|
|
146940
|
+
const configPath = join10(configDir, "mcp.json");
|
|
146941
|
+
writeFileSync7(
|
|
146324
146942
|
configPath,
|
|
146325
146943
|
JSON.stringify({
|
|
146326
146944
|
mcpServers: {
|
|
@@ -146330,6 +146948,15 @@ function writeMcpConfig(ctx) {
|
|
|
146330
146948
|
);
|
|
146331
146949
|
return configPath;
|
|
146332
146950
|
}
|
|
146951
|
+
function buildAgentsJson() {
|
|
146952
|
+
const agents2 = {
|
|
146953
|
+
[REVIEWER_AGENT_NAME]: {
|
|
146954
|
+
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.",
|
|
146955
|
+
prompt: REVIEWER_SYSTEM_PROMPT
|
|
146956
|
+
}
|
|
146957
|
+
};
|
|
146958
|
+
return JSON.stringify(agents2);
|
|
146959
|
+
}
|
|
146333
146960
|
function stripProviderPrefix(specifier) {
|
|
146334
146961
|
const slashIndex = specifier.indexOf("/");
|
|
146335
146962
|
return slashIndex > 0 ? specifier.slice(slashIndex + 1) : specifier;
|
|
@@ -146380,6 +147007,13 @@ async function runClaude(params) {
|
|
|
146380
147007
|
}
|
|
146381
147008
|
thinkingTimer.markToolCall();
|
|
146382
147009
|
log.toolCall({ toolName, input: block.input || {} });
|
|
147010
|
+
if (toolName === "Task" && block.input && typeof block.input === "object") {
|
|
147011
|
+
const taskInput = block.input;
|
|
147012
|
+
const label = deriveLabelFromTaskInput(taskInput);
|
|
147013
|
+
log.info(
|
|
147014
|
+
`\xBB dispatching subagent: ${label}` + (taskInput.subagent_type ? ` (subagent_type=${taskInput.subagent_type})` : "")
|
|
147015
|
+
);
|
|
147016
|
+
}
|
|
146383
147017
|
if (toolName.includes("report_progress") && params.todoTracker) {
|
|
146384
147018
|
log.debug("\xBB report_progress detected, disabling todo tracking");
|
|
146385
147019
|
params.todoTracker.cancel();
|
|
@@ -146645,9 +147279,9 @@ var claude = agent({
|
|
|
146645
147279
|
const model = specifier ? stripProviderPrefix(specifier) : void 0;
|
|
146646
147280
|
const homeEnv = {
|
|
146647
147281
|
HOME: ctx.tmpdir,
|
|
146648
|
-
XDG_CONFIG_HOME:
|
|
147282
|
+
XDG_CONFIG_HOME: join10(ctx.tmpdir, ".config")
|
|
146649
147283
|
};
|
|
146650
|
-
|
|
147284
|
+
mkdirSync4(join10(homeEnv.XDG_CONFIG_HOME, "claude"), { recursive: true });
|
|
146651
147285
|
const agentBrowserVersion = getDevDependencyVersion("agent-browser");
|
|
146652
147286
|
addSkill({
|
|
146653
147287
|
ref: `vercel-labs/agent-browser@v${agentBrowserVersion}`,
|
|
@@ -146655,6 +147289,7 @@ var claude = agent({
|
|
|
146655
147289
|
env: homeEnv,
|
|
146656
147290
|
agent: "claude"
|
|
146657
147291
|
});
|
|
147292
|
+
installBundledSkills({ home: homeEnv.HOME });
|
|
146658
147293
|
const mcpConfigPath = writeMcpConfig(ctx);
|
|
146659
147294
|
const effort = resolveEffort(model);
|
|
146660
147295
|
installManagedSettings();
|
|
@@ -146669,7 +147304,9 @@ var claude = agent({
|
|
|
146669
147304
|
"--effort",
|
|
146670
147305
|
effort,
|
|
146671
147306
|
"--disallowedTools",
|
|
146672
|
-
"Bash,Agent(Bash)"
|
|
147307
|
+
"Bash,Agent(Bash)",
|
|
147308
|
+
"--agents",
|
|
147309
|
+
buildAgentsJson()
|
|
146673
147310
|
];
|
|
146674
147311
|
if (model) {
|
|
146675
147312
|
baseArgs.push("--model", model);
|
|
@@ -146690,37 +147327,32 @@ var claude = agent({
|
|
|
146690
147327
|
onActivityTimeout: ctx.onActivityTimeout,
|
|
146691
147328
|
onToolUse: ctx.onToolUse
|
|
146692
147329
|
};
|
|
146693
|
-
|
|
147330
|
+
const result = await runClaude({
|
|
146694
147331
|
...runParams,
|
|
146695
147332
|
args: [...baseArgs, "-p", ctx.instructions.full]
|
|
146696
147333
|
});
|
|
146697
|
-
|
|
146698
|
-
|
|
146699
|
-
|
|
146700
|
-
|
|
146701
|
-
|
|
146702
|
-
|
|
146703
|
-
|
|
146704
|
-
|
|
146705
|
-
|
|
146706
|
-
|
|
146707
|
-
...
|
|
146708
|
-
"-p",
|
|
146709
|
-
|
|
146710
|
-
|
|
146711
|
-
|
|
146712
|
-
]
|
|
146713
|
-
});
|
|
146714
|
-
aggregatedUsage = mergeAgentUsage(aggregatedUsage, result.usage);
|
|
146715
|
-
}
|
|
146716
|
-
return { ...result, usage: aggregatedUsage };
|
|
147334
|
+
return runPostRunRetryLoop({
|
|
147335
|
+
initialResult: result,
|
|
147336
|
+
initialUsage: result.usage,
|
|
147337
|
+
stopScript: ctx.stopScript,
|
|
147338
|
+
reflectionPrompt: buildLearningsReflectionPrompt("claude"),
|
|
147339
|
+
canResume: (r) => Boolean(r.sessionId),
|
|
147340
|
+
resume: async (c) => {
|
|
147341
|
+
const sessionId = c.previousResult.sessionId;
|
|
147342
|
+
if (!sessionId) throw new Error("unreachable: canResume gated on sessionId");
|
|
147343
|
+
return runClaude({
|
|
147344
|
+
...runParams,
|
|
147345
|
+
args: [...baseArgs, "-p", c.prompt, "--resume", sessionId]
|
|
147346
|
+
});
|
|
147347
|
+
}
|
|
147348
|
+
});
|
|
146717
147349
|
}
|
|
146718
147350
|
});
|
|
146719
147351
|
|
|
146720
147352
|
// agents/opencode.ts
|
|
146721
147353
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
146722
|
-
import { mkdirSync as
|
|
146723
|
-
import { join as
|
|
147354
|
+
import { mkdirSync as mkdirSync5 } from "node:fs";
|
|
147355
|
+
import { join as join11 } from "node:path";
|
|
146724
147356
|
import { performance as performance7 } from "node:perf_hooks";
|
|
146725
147357
|
async function installOpencodeCli() {
|
|
146726
147358
|
return await installFromNpmTarball({
|
|
@@ -146742,7 +147374,8 @@ function buildSecurityConfig(ctx, model) {
|
|
|
146742
147374
|
},
|
|
146743
147375
|
mcp: {
|
|
146744
147376
|
[pullfrogMcpName]: { type: "remote", url: ctx.mcpServerUrl }
|
|
146745
|
-
}
|
|
147377
|
+
},
|
|
147378
|
+
agent: buildReviewerAgentConfig()
|
|
146746
147379
|
};
|
|
146747
147380
|
if (model) {
|
|
146748
147381
|
config3.model = model;
|
|
@@ -146753,6 +147386,15 @@ function buildSecurityConfig(ctx, model) {
|
|
|
146753
147386
|
}
|
|
146754
147387
|
return JSON.stringify(config3);
|
|
146755
147388
|
}
|
|
147389
|
+
function buildReviewerAgentConfig() {
|
|
147390
|
+
return {
|
|
147391
|
+
[REVIEWER_AGENT_NAME]: {
|
|
147392
|
+
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.",
|
|
147393
|
+
mode: "subagent",
|
|
147394
|
+
prompt: REVIEWER_SYSTEM_PROMPT
|
|
147395
|
+
}
|
|
147396
|
+
};
|
|
147397
|
+
}
|
|
146756
147398
|
function getOpenCodeModels(cliPath) {
|
|
146757
147399
|
try {
|
|
146758
147400
|
const output = execFileSync4(cliPath, ["models"], {
|
|
@@ -146801,6 +147443,29 @@ async function runOpenCode(params) {
|
|
|
146801
147443
|
let currentStepId = null;
|
|
146802
147444
|
let currentStepType = null;
|
|
146803
147445
|
let stepHistory = [];
|
|
147446
|
+
const labeler = new SessionLabeler();
|
|
147447
|
+
function eventLabel(event) {
|
|
147448
|
+
const sid = event.sessionID ?? event.session_id;
|
|
147449
|
+
return labeler.labelFor(typeof sid === "string" ? sid : null);
|
|
147450
|
+
}
|
|
147451
|
+
function withLabel(label, message) {
|
|
147452
|
+
return label === ORCHESTRATOR_LABEL ? message : formatWithLabel(label, message);
|
|
147453
|
+
}
|
|
147454
|
+
const taskDispatchByCallID = /* @__PURE__ */ new Map();
|
|
147455
|
+
const pendingTaskDispatches = [];
|
|
147456
|
+
const knownNonTaskCallIDs = /* @__PURE__ */ new Set();
|
|
147457
|
+
function emitSubagentFinished(dispatch, status, output2, matchKind) {
|
|
147458
|
+
const subagentDuration = performance7.now() - dispatch.startedAt;
|
|
147459
|
+
const outputStr = typeof output2 === "string" ? output2 : "";
|
|
147460
|
+
const outputPreview = outputStr.length > 120 ? `${outputStr.slice(0, 120)}\u2026` : outputStr;
|
|
147461
|
+
const matchSuffix = matchKind === "fifo" ? " [fifo-matched]" : "";
|
|
147462
|
+
log.info(
|
|
147463
|
+
`\xBB subagent finished: ${dispatch.label} (${(subagentDuration / 1e3).toFixed(1)}s, status=${status})${matchSuffix}` + (outputPreview ? ` \u2014 ${outputPreview.replace(/\n/g, " ")}` : "")
|
|
147464
|
+
);
|
|
147465
|
+
taskDispatchByCallID.delete(dispatch.toolUseCallID);
|
|
147466
|
+
const idx = pendingTaskDispatches.indexOf(dispatch);
|
|
147467
|
+
if (idx >= 0) pendingTaskDispatches.splice(idx, 1);
|
|
147468
|
+
}
|
|
146804
147469
|
function buildUsage() {
|
|
146805
147470
|
const totalInput = accumulatedTokens.input + accumulatedTokens.cacheRead + accumulatedTokens.cacheWrite;
|
|
146806
147471
|
return totalInput > 0 || accumulatedTokens.output > 0 ? {
|
|
@@ -146814,39 +147479,63 @@ async function runOpenCode(params) {
|
|
|
146814
147479
|
}
|
|
146815
147480
|
const handlers2 = {
|
|
146816
147481
|
init: (event) => {
|
|
147482
|
+
const label = labeler.labelFor(event.session_id ?? null);
|
|
146817
147483
|
log.debug(
|
|
146818
|
-
|
|
147484
|
+
withLabel(
|
|
147485
|
+
label,
|
|
147486
|
+
`\xBB ${params.label} init: session_id=${event.session_id || "unknown"}, model=${event.model || "unknown"}`
|
|
147487
|
+
)
|
|
146819
147488
|
);
|
|
146820
|
-
log.debug(`\xBB ${params.label} init event (full): ${JSON.stringify(event)}`);
|
|
146821
|
-
|
|
146822
|
-
|
|
146823
|
-
|
|
146824
|
-
|
|
147489
|
+
log.debug(withLabel(label, `\xBB ${params.label} init event (full): ${JSON.stringify(event)}`));
|
|
147490
|
+
if (label === ORCHESTRATOR_LABEL) {
|
|
147491
|
+
finalOutput = "";
|
|
147492
|
+
accumulatedTokens = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
147493
|
+
accumulatedCostUsd = 0;
|
|
147494
|
+
tokensLogged = false;
|
|
147495
|
+
} else {
|
|
147496
|
+
log.info(`\xBB ${params.label} subagent init: ${label} (session ${event.session_id || "?"})`);
|
|
147497
|
+
}
|
|
146825
147498
|
},
|
|
146826
147499
|
message: (event) => {
|
|
147500
|
+
const label = eventLabel(event);
|
|
146827
147501
|
if (event.role === "assistant" && event.content?.trim()) {
|
|
146828
147502
|
const message = event.content.trim();
|
|
146829
147503
|
if (event.delta) {
|
|
146830
147504
|
log.debug(
|
|
146831
|
-
|
|
147505
|
+
withLabel(
|
|
147506
|
+
label,
|
|
147507
|
+
`\xBB ${params.label} thinking: ${message.substring(0, 300)}${message.length > 300 ? "..." : ""}`
|
|
147508
|
+
)
|
|
146832
147509
|
);
|
|
146833
147510
|
} else {
|
|
146834
147511
|
log.debug(
|
|
146835
|
-
|
|
147512
|
+
withLabel(
|
|
147513
|
+
label,
|
|
147514
|
+
`\xBB ${params.label} message (${event.role}): ${message.substring(0, 100)}${message.length > 100 ? "..." : ""}`
|
|
147515
|
+
)
|
|
146836
147516
|
);
|
|
146837
|
-
|
|
147517
|
+
if (label === ORCHESTRATOR_LABEL) {
|
|
147518
|
+
finalOutput = message;
|
|
147519
|
+
}
|
|
146838
147520
|
}
|
|
146839
147521
|
} else if (event.role === "user") {
|
|
146840
147522
|
log.debug(
|
|
146841
|
-
|
|
147523
|
+
withLabel(
|
|
147524
|
+
label,
|
|
147525
|
+
`\xBB ${params.label} message (${event.role}): ${event.content?.substring(0, 100) || ""}${event.content && event.content.length > 100 ? "..." : ""}`
|
|
147526
|
+
)
|
|
146842
147527
|
);
|
|
146843
147528
|
}
|
|
146844
147529
|
},
|
|
146845
147530
|
text: (event) => {
|
|
146846
147531
|
if (event.part?.text?.trim()) {
|
|
146847
147532
|
const message = event.part.text.trim();
|
|
146848
|
-
|
|
146849
|
-
|
|
147533
|
+
const label = eventLabel(event);
|
|
147534
|
+
const boxTitle = label === ORCHESTRATOR_LABEL ? params.label : `${params.label} [${label}]`;
|
|
147535
|
+
log.box(message, { title: boxTitle });
|
|
147536
|
+
if (label === ORCHESTRATOR_LABEL) {
|
|
147537
|
+
finalOutput = message;
|
|
147538
|
+
}
|
|
146850
147539
|
}
|
|
146851
147540
|
},
|
|
146852
147541
|
step_start: (event) => {
|
|
@@ -146882,6 +147571,23 @@ async function runOpenCode(params) {
|
|
|
146882
147571
|
);
|
|
146883
147572
|
return;
|
|
146884
147573
|
}
|
|
147574
|
+
if (toolName === "task") {
|
|
147575
|
+
const taskInput = event.part?.state?.input ?? {};
|
|
147576
|
+
const dispatchedLabel = labeler.recordTaskDispatch(taskInput);
|
|
147577
|
+
const dispatch = {
|
|
147578
|
+
label: dispatchedLabel,
|
|
147579
|
+
startedAt: performance7.now(),
|
|
147580
|
+
toolUseCallID: toolId
|
|
147581
|
+
};
|
|
147582
|
+
taskDispatchByCallID.set(toolId, dispatch);
|
|
147583
|
+
pendingTaskDispatches.push(dispatch);
|
|
147584
|
+
log.info(
|
|
147585
|
+
`\xBB dispatching subagent: ${dispatchedLabel}` + (taskInput.subagent_type ? ` (subagent_type=${taskInput.subagent_type})` : "")
|
|
147586
|
+
);
|
|
147587
|
+
} else {
|
|
147588
|
+
knownNonTaskCallIDs.add(toolId);
|
|
147589
|
+
}
|
|
147590
|
+
const label = eventLabel(event);
|
|
146885
147591
|
if (stepHistory.length > 0) {
|
|
146886
147592
|
stepHistory[stepHistory.length - 1].toolCalls.push(toolName);
|
|
146887
147593
|
}
|
|
@@ -146892,9 +147598,11 @@ async function runOpenCode(params) {
|
|
|
146892
147598
|
});
|
|
146893
147599
|
}
|
|
146894
147600
|
thinkingTimer.markToolCall();
|
|
146895
|
-
|
|
147601
|
+
const inputFormatted = formatJsonValue(event.part?.state?.input || {});
|
|
147602
|
+
const toolCallLine = inputFormatted !== "{}" ? `\xBB ${toolName}(${inputFormatted})` : `\xBB ${toolName}()`;
|
|
147603
|
+
log.info(withLabel(label, toolCallLine));
|
|
146896
147604
|
if (event.part?.state?.status === "completed" && event.part.state.output) {
|
|
146897
|
-
log.debug(` output: ${event.part.state.output}`);
|
|
147605
|
+
log.debug(withLabel(label, ` output: ${event.part.state.output}`));
|
|
146898
147606
|
}
|
|
146899
147607
|
if (toolName.includes("report_progress") && params.todoTracker) {
|
|
146900
147608
|
log.debug("\xBB report_progress detected, disabling todo tracking");
|
|
@@ -146908,7 +147616,20 @@ async function runOpenCode(params) {
|
|
|
146908
147616
|
const toolId = event.part?.callID || event.tool_id;
|
|
146909
147617
|
const status = event.part?.state?.status || event.status || "unknown";
|
|
146910
147618
|
const output2 = event.part?.state?.output || event.output;
|
|
147619
|
+
const label = eventLabel(event);
|
|
146911
147620
|
thinkingTimer.markToolResult();
|
|
147621
|
+
if (taskDispatchByCallID.size > 0 || pendingTaskDispatches.length > 0) {
|
|
147622
|
+
if (toolId && taskDispatchByCallID.has(toolId)) {
|
|
147623
|
+
const dispatch = taskDispatchByCallID.get(toolId);
|
|
147624
|
+
if (dispatch) emitSubagentFinished(dispatch, status, output2, "exact");
|
|
147625
|
+
} else {
|
|
147626
|
+
const callIDIsKnownNonTask = toolId ? knownNonTaskCallIDs.has(toolId) : false;
|
|
147627
|
+
if (!callIDIsKnownNonTask && pendingTaskDispatches.length > 0) {
|
|
147628
|
+
const dispatch = pendingTaskDispatches[0];
|
|
147629
|
+
emitSubagentFinished(dispatch, status, output2, "fifo");
|
|
147630
|
+
}
|
|
147631
|
+
}
|
|
147632
|
+
}
|
|
146912
147633
|
if (toolId) {
|
|
146913
147634
|
const toolStartTime = toolCallTimings.get(toolId);
|
|
146914
147635
|
if (toolStartTime) {
|
|
@@ -146916,24 +147637,35 @@ async function runOpenCode(params) {
|
|
|
146916
147637
|
toolCallTimings.delete(toolId);
|
|
146917
147638
|
const stepContext = currentStepId ? ` (step=${currentStepType || "unknown"})` : "";
|
|
146918
147639
|
log.debug(
|
|
146919
|
-
|
|
147640
|
+
withLabel(
|
|
147641
|
+
label,
|
|
147642
|
+
`\xBB ${params.label} tool_result${stepContext}: id=${toolId}, status=${status}, duration=${Math.round(toolDuration)}ms`
|
|
147643
|
+
)
|
|
146920
147644
|
);
|
|
146921
147645
|
if (output2) {
|
|
146922
|
-
log.debug(
|
|
147646
|
+
log.debug(
|
|
147647
|
+
withLabel(
|
|
147648
|
+
label,
|
|
147649
|
+
` output: ${typeof output2 === "string" ? output2 : JSON.stringify(output2)}`
|
|
147650
|
+
)
|
|
147651
|
+
);
|
|
146923
147652
|
}
|
|
146924
147653
|
if (toolDuration > 5e3) {
|
|
146925
147654
|
log.info(
|
|
146926
|
-
|
|
147655
|
+
withLabel(
|
|
147656
|
+
label,
|
|
147657
|
+
`\xBB tool call took ${(toolDuration / 1e3).toFixed(1)}s - may indicate network latency`
|
|
147658
|
+
)
|
|
146927
147659
|
);
|
|
146928
147660
|
}
|
|
146929
147661
|
}
|
|
146930
147662
|
}
|
|
146931
147663
|
if (status === "error") {
|
|
146932
147664
|
const errorMsg = typeof output2 === "string" ? output2 : JSON.stringify(output2);
|
|
146933
|
-
log.info(`\xBB tool call failed: ${errorMsg}`);
|
|
147665
|
+
log.info(withLabel(label, `\xBB tool call failed: ${errorMsg}`));
|
|
146934
147666
|
} else if (output2) {
|
|
146935
147667
|
const outputStr = typeof output2 === "string" ? output2 : JSON.stringify(output2);
|
|
146936
|
-
log.debug(`tool output: ${outputStr}`);
|
|
147668
|
+
log.debug(withLabel(label, `tool output: ${outputStr}`));
|
|
146937
147669
|
}
|
|
146938
147670
|
},
|
|
146939
147671
|
result: async (event) => {
|
|
@@ -147030,6 +147762,16 @@ async function runOpenCode(params) {
|
|
|
147030
147762
|
} else {
|
|
147031
147763
|
params.todoTracker?.cancel();
|
|
147032
147764
|
}
|
|
147765
|
+
if (pendingTaskDispatches.length > 0) {
|
|
147766
|
+
for (const dispatch of [...pendingTaskDispatches]) {
|
|
147767
|
+
const elapsed = performance7.now() - dispatch.startedAt;
|
|
147768
|
+
log.info(
|
|
147769
|
+
`\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`
|
|
147770
|
+
);
|
|
147771
|
+
}
|
|
147772
|
+
pendingTaskDispatches.length = 0;
|
|
147773
|
+
taskDispatchByCallID.clear();
|
|
147774
|
+
}
|
|
147033
147775
|
const duration4 = performance7.now() - startTime;
|
|
147034
147776
|
log.info(
|
|
147035
147777
|
`\xBB ${params.label} completed in ${Math.round(duration4)}ms with exit code ${result.exitCode}`
|
|
@@ -147097,9 +147839,9 @@ var opencode = agent({
|
|
|
147097
147839
|
const model = ctx.payload.proxyModel ?? ctx.resolvedModel ?? autoSelectModel(cliPath);
|
|
147098
147840
|
const homeEnv = {
|
|
147099
147841
|
HOME: ctx.tmpdir,
|
|
147100
|
-
XDG_CONFIG_HOME:
|
|
147842
|
+
XDG_CONFIG_HOME: join11(ctx.tmpdir, ".config")
|
|
147101
147843
|
};
|
|
147102
|
-
|
|
147844
|
+
mkdirSync5(join11(homeEnv.XDG_CONFIG_HOME, "opencode"), { recursive: true });
|
|
147103
147845
|
const agentBrowserVersion = getDevDependencyVersion("agent-browser");
|
|
147104
147846
|
addSkill({
|
|
147105
147847
|
ref: `vercel-labs/agent-browser@v${agentBrowserVersion}`,
|
|
@@ -147107,6 +147849,7 @@ var opencode = agent({
|
|
|
147107
147849
|
env: homeEnv,
|
|
147108
147850
|
agent: "opencode"
|
|
147109
147851
|
});
|
|
147852
|
+
installBundledSkills({ home: homeEnv.HOME });
|
|
147110
147853
|
const baseArgs = ["run", "--format", "json", "--print-logs"];
|
|
147111
147854
|
const permissionOverride = JSON.stringify({
|
|
147112
147855
|
external_directory: { "*": "deny", "/tmp/*": "allow" }
|
|
@@ -147130,24 +147873,20 @@ var opencode = agent({
|
|
|
147130
147873
|
onActivityTimeout: ctx.onActivityTimeout,
|
|
147131
147874
|
onToolUse: ctx.onToolUse
|
|
147132
147875
|
};
|
|
147133
|
-
|
|
147876
|
+
const result = await runOpenCode({
|
|
147134
147877
|
...runParams,
|
|
147135
147878
|
args: [...baseArgs, ctx.instructions.full]
|
|
147136
147879
|
});
|
|
147137
|
-
|
|
147138
|
-
|
|
147139
|
-
|
|
147140
|
-
|
|
147141
|
-
|
|
147142
|
-
|
|
147143
|
-
${status}`);
|
|
147144
|
-
result = await runOpenCode({
|
|
147880
|
+
return runPostRunRetryLoop({
|
|
147881
|
+
initialResult: result,
|
|
147882
|
+
initialUsage: result.usage,
|
|
147883
|
+
stopScript: ctx.stopScript,
|
|
147884
|
+
reflectionPrompt: buildLearningsReflectionPrompt("opencode"),
|
|
147885
|
+
resume: async (c) => runOpenCode({
|
|
147145
147886
|
...runParams,
|
|
147146
|
-
args: [...baseArgs, "--continue",
|
|
147147
|
-
})
|
|
147148
|
-
|
|
147149
|
-
}
|
|
147150
|
-
return { ...result, usage: aggregatedUsage };
|
|
147887
|
+
args: [...baseArgs, "--continue", c.prompt]
|
|
147888
|
+
})
|
|
147889
|
+
});
|
|
147151
147890
|
}
|
|
147152
147891
|
});
|
|
147153
147892
|
|
|
@@ -147335,7 +148074,7 @@ async function fetchBodyHtml(ctx) {
|
|
|
147335
148074
|
var core2 = __toESM(require_core(), 1);
|
|
147336
148075
|
import { createSign } from "node:crypto";
|
|
147337
148076
|
import { rename, writeFile } from "node:fs/promises";
|
|
147338
|
-
import { dirname as
|
|
148077
|
+
import { dirname as dirname3, join as join12 } from "node:path";
|
|
147339
148078
|
|
|
147340
148079
|
// node_modules/.pnpm/@octokit+plugin-throttling@11.0.3_@octokit+core@7.0.5/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
|
|
147341
148080
|
var import_light = __toESM(require_light(), 1);
|
|
@@ -151167,7 +151906,7 @@ function getGitHubUsageSummary() {
|
|
|
151167
151906
|
}
|
|
151168
151907
|
async function writeGitHubUsageSummaryToFile(path3) {
|
|
151169
151908
|
const summary2 = getGitHubUsageSummary();
|
|
151170
|
-
const tmpPath =
|
|
151909
|
+
const tmpPath = join12(dirname3(path3), `.usage-summary-${process.pid}.tmp`);
|
|
151171
151910
|
await writeFile(tmpPath, JSON.stringify(summary2));
|
|
151172
151911
|
await rename(tmpPath, path3);
|
|
151173
151912
|
}
|
|
@@ -151325,8 +152064,8 @@ async function reportErrorToComment(ctx) {
|
|
|
151325
152064
|
const formattedError = ctx.title ? `${ctx.title}
|
|
151326
152065
|
|
|
151327
152066
|
${ctx.error}` : ctx.error;
|
|
151328
|
-
const
|
|
151329
|
-
if (!
|
|
152067
|
+
const comment = ctx.toolState.progressComment;
|
|
152068
|
+
if (!comment) {
|
|
151330
152069
|
return;
|
|
151331
152070
|
}
|
|
151332
152071
|
const repoContext = parseRepoContext();
|
|
@@ -151345,20 +152084,19 @@ ${ctx.error}` : ctx.error;
|
|
|
151345
152084
|
customParts,
|
|
151346
152085
|
model: ctx.toolState.model
|
|
151347
152086
|
});
|
|
151348
|
-
await
|
|
151349
|
-
owner: repoContext.owner,
|
|
151350
|
-
|
|
151351
|
-
|
|
151352
|
-
|
|
151353
|
-
});
|
|
152087
|
+
await updateProgressComment(
|
|
152088
|
+
{ octokit, owner: repoContext.owner, repo: repoContext.name },
|
|
152089
|
+
comment,
|
|
152090
|
+
`${formattedError}${footer}`
|
|
152091
|
+
);
|
|
151354
152092
|
ctx.toolState.wasUpdated = true;
|
|
151355
152093
|
}
|
|
151356
152094
|
|
|
151357
152095
|
// utils/gitAuthServer.ts
|
|
151358
152096
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
151359
|
-
import { writeFileSync as
|
|
152097
|
+
import { writeFileSync as writeFileSync8 } from "node:fs";
|
|
151360
152098
|
import { createServer as createServer2 } from "node:http";
|
|
151361
|
-
import { join as
|
|
152099
|
+
import { join as join13 } from "node:path";
|
|
151362
152100
|
var CODE_TTL_MS = 5 * 60 * 1e3;
|
|
151363
152101
|
var TAMPER_WINDOW_MS = 6e4;
|
|
151364
152102
|
function revokeGitHubToken(token) {
|
|
@@ -151430,7 +152168,7 @@ async function startGitAuthServer(tmpdir3) {
|
|
|
151430
152168
|
function writeAskpassScript(code) {
|
|
151431
152169
|
const scriptId = randomUUID3();
|
|
151432
152170
|
const scriptName = `askpass-${scriptId}.js`;
|
|
151433
|
-
const scriptPath =
|
|
152171
|
+
const scriptPath = join13(tmpdir3, scriptName);
|
|
151434
152172
|
const content = [
|
|
151435
152173
|
`#!/usr/bin/env node`,
|
|
151436
152174
|
`var a=process.argv[2]||"";`,
|
|
@@ -151445,7 +152183,7 @@ async function startGitAuthServer(tmpdir3) {
|
|
|
151445
152183
|
`try{require("fs").unlinkSync("${scriptPath.replace(/\\/g, "\\\\")}")}catch(e){}`,
|
|
151446
152184
|
`})}).on("error",function(){process.exit(1)})}`
|
|
151447
152185
|
].join("\n");
|
|
151448
|
-
|
|
152186
|
+
writeFileSync8(scriptPath, content, { mode: 448 });
|
|
151449
152187
|
return scriptPath;
|
|
151450
152188
|
}
|
|
151451
152189
|
async function close() {
|
|
@@ -151850,7 +152588,10 @@ var JsonPayload = type({
|
|
|
151850
152588
|
"eventInstructions?": "string",
|
|
151851
152589
|
"event?": "object",
|
|
151852
152590
|
"timeout?": "string | undefined",
|
|
151853
|
-
"
|
|
152591
|
+
"progressComment?": type({
|
|
152592
|
+
id: "string",
|
|
152593
|
+
type: "'issue' | 'review'"
|
|
152594
|
+
}).or("undefined")
|
|
151854
152595
|
});
|
|
151855
152596
|
var COLLABORATOR_PERMISSIONS = ["admin", "maintain", "write"];
|
|
151856
152597
|
function isCollaborator(event) {
|
|
@@ -151932,7 +152673,7 @@ function resolvePayload(resolvedPromptInput, repoSettings) {
|
|
|
151932
152673
|
event,
|
|
151933
152674
|
timeout: inputs.timeout ?? jsonPayload?.timeout,
|
|
151934
152675
|
cwd: resolveCwd(inputs.cwd),
|
|
151935
|
-
|
|
152676
|
+
progressComment: jsonPayload?.progressComment,
|
|
151936
152677
|
// permissions: inputs > repoSettings > fallbacks
|
|
151937
152678
|
push: inputs.push ?? repoSettings.push ?? "restricted",
|
|
151938
152679
|
shell: resolvedShell,
|
|
@@ -152015,7 +152756,9 @@ async function handleAgentResult(ctx) {
|
|
|
152015
152756
|
output: ctx.result.output
|
|
152016
152757
|
};
|
|
152017
152758
|
}
|
|
152018
|
-
|
|
152759
|
+
const mode = ctx.toolState.selectedMode;
|
|
152760
|
+
const isReviewMode = mode === "Review" || mode === "IncrementalReview";
|
|
152761
|
+
if (!isReviewMode && !ctx.toolState.wasUpdated && ctx.toolState.hadProgressComment && !ctx.silent) {
|
|
152019
152762
|
const error49 = ctx.result.error || "agent completed without reporting progress";
|
|
152020
152763
|
try {
|
|
152021
152764
|
await reportErrorToComment({
|
|
@@ -152038,16 +152781,17 @@ async function handleAgentResult(ctx) {
|
|
|
152038
152781
|
};
|
|
152039
152782
|
}
|
|
152040
152783
|
|
|
152041
|
-
// utils/runContextData.ts
|
|
152042
|
-
var core5 = __toESM(require_core(), 1);
|
|
152043
|
-
|
|
152044
152784
|
// utils/runContext.ts
|
|
152785
|
+
function isInfraCovered(params) {
|
|
152786
|
+
return params.isOss || params.plan === "payg";
|
|
152787
|
+
}
|
|
152045
152788
|
var defaultSettings = {
|
|
152046
152789
|
model: null,
|
|
152047
152790
|
modes: [],
|
|
152048
152791
|
setupScript: null,
|
|
152049
152792
|
postCheckoutScript: null,
|
|
152050
152793
|
prepushScript: null,
|
|
152794
|
+
stopScript: null,
|
|
152051
152795
|
push: "restricted",
|
|
152052
152796
|
shell: "restricted",
|
|
152053
152797
|
prApproveEnabled: false,
|
|
@@ -152058,7 +152802,8 @@ var defaultSettings = {
|
|
|
152058
152802
|
var defaultRunContext = {
|
|
152059
152803
|
settings: defaultSettings,
|
|
152060
152804
|
apiToken: "",
|
|
152061
|
-
oss: false
|
|
152805
|
+
oss: false,
|
|
152806
|
+
plan: "none"
|
|
152062
152807
|
};
|
|
152063
152808
|
async function fetchRunContext(params) {
|
|
152064
152809
|
const timeoutMs = 3e4;
|
|
@@ -152092,10 +152837,12 @@ async function fetchRunContext(params) {
|
|
|
152092
152837
|
modes: data.settings?.modes ?? [],
|
|
152093
152838
|
setupScript: data.settings?.setupScript ?? null,
|
|
152094
152839
|
postCheckoutScript: data.settings?.postCheckoutScript ?? null,
|
|
152095
|
-
prepushScript: data.settings?.prepushScript ?? null
|
|
152840
|
+
prepushScript: data.settings?.prepushScript ?? null,
|
|
152841
|
+
stopScript: data.settings?.stopScript ?? null
|
|
152096
152842
|
},
|
|
152097
152843
|
apiToken: data.apiToken,
|
|
152098
152844
|
oss: data.oss ?? false,
|
|
152845
|
+
plan: data.plan ?? "none",
|
|
152099
152846
|
proxyModel: data.proxyModel,
|
|
152100
152847
|
dbSecrets: data.dbSecrets
|
|
152101
152848
|
};
|
|
@@ -152106,6 +152853,7 @@ async function fetchRunContext(params) {
|
|
|
152106
152853
|
}
|
|
152107
152854
|
|
|
152108
152855
|
// utils/runContextData.ts
|
|
152856
|
+
var core5 = __toESM(require_core(), 1);
|
|
152109
152857
|
async function resolveRunContextData(params) {
|
|
152110
152858
|
log.info(`\xBB running Pullfrog v${package_default.version}...`);
|
|
152111
152859
|
const repoContext = parseRepoContext();
|
|
@@ -152127,6 +152875,7 @@ async function resolveRunContextData(params) {
|
|
|
152127
152875
|
repoSettings: runContext.settings,
|
|
152128
152876
|
apiToken: runContext.apiToken,
|
|
152129
152877
|
oss: runContext.oss,
|
|
152878
|
+
plan: runContext.plan,
|
|
152130
152879
|
proxyModel: runContext.proxyModel,
|
|
152131
152880
|
dbSecrets: runContext.dbSecrets
|
|
152132
152881
|
};
|
|
@@ -152136,9 +152885,9 @@ async function resolveRunContextData(params) {
|
|
|
152136
152885
|
import { execFileSync as execFileSync5, execSync as execSync3 } from "node:child_process";
|
|
152137
152886
|
import { mkdtempSync } from "node:fs";
|
|
152138
152887
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
152139
|
-
import { join as
|
|
152888
|
+
import { join as join14 } from "node:path";
|
|
152140
152889
|
function createTempDirectory() {
|
|
152141
|
-
const sharedTempDir = mkdtempSync(
|
|
152890
|
+
const sharedTempDir = mkdtempSync(join14(tmpdir2(), "pullfrog-"));
|
|
152142
152891
|
process.env.PULLFROG_TEMP_DIR = sharedTempDir;
|
|
152143
152892
|
log.info(`\xBB created temp dir at ${sharedTempDir}`);
|
|
152144
152893
|
return sharedTempDir;
|
|
@@ -152453,6 +153202,59 @@ function resolveAgentForLog(ctx) {
|
|
|
152453
153202
|
}
|
|
152454
153203
|
return ctx.agentName;
|
|
152455
153204
|
}
|
|
153205
|
+
var BillingError = class extends Error {
|
|
153206
|
+
code;
|
|
153207
|
+
declineCode;
|
|
153208
|
+
needsReauthentication;
|
|
153209
|
+
constructor(message, opts = {}) {
|
|
153210
|
+
super(message);
|
|
153211
|
+
this.name = "BillingError";
|
|
153212
|
+
this.code = opts.code ?? null;
|
|
153213
|
+
this.declineCode = opts.declineCode ?? null;
|
|
153214
|
+
this.needsReauthentication = opts.needsReauthentication ?? false;
|
|
153215
|
+
}
|
|
153216
|
+
};
|
|
153217
|
+
var TransientError = class extends Error {
|
|
153218
|
+
constructor(message) {
|
|
153219
|
+
super(message);
|
|
153220
|
+
this.name = "TransientError";
|
|
153221
|
+
}
|
|
153222
|
+
};
|
|
153223
|
+
function formatBillingErrorSummary(error49) {
|
|
153224
|
+
if (error49.code === "router_requires_card") {
|
|
153225
|
+
return [
|
|
153226
|
+
"### \u26D4 Pullfrog Router requires a card",
|
|
153227
|
+
"",
|
|
153228
|
+
"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.",
|
|
153229
|
+
"",
|
|
153230
|
+
"[Add a card \u2192](https://pullfrog.com/console#model-access) \u2014 your first $20 of Router usage is free."
|
|
153231
|
+
].join("\n");
|
|
153232
|
+
}
|
|
153233
|
+
if (error49.needsReauthentication) {
|
|
153234
|
+
return [
|
|
153235
|
+
"### \u274C Pullfrog billing error \u2014 card requires 3DS on every charge",
|
|
153236
|
+
"",
|
|
153237
|
+
`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.`,
|
|
153238
|
+
"",
|
|
153239
|
+
"[Top up your Router credit balance \u2192](https://pullfrog.com/console)"
|
|
153240
|
+
].join("\n");
|
|
153241
|
+
}
|
|
153242
|
+
const codeSuffix = error49.declineCode ? ` (\`${error49.declineCode}\`)` : "";
|
|
153243
|
+
return `### \u274C Pullfrog billing error
|
|
153244
|
+
|
|
153245
|
+
${error49.message}${codeSuffix}
|
|
153246
|
+
|
|
153247
|
+
[Manage billing \u2192](https://pullfrog.com/console)`;
|
|
153248
|
+
}
|
|
153249
|
+
function formatTransientErrorSummary(error49) {
|
|
153250
|
+
return [
|
|
153251
|
+
"### \u26A0\uFE0F Pullfrog temporarily unavailable",
|
|
153252
|
+
"",
|
|
153253
|
+
error49.message,
|
|
153254
|
+
"",
|
|
153255
|
+
"This is typically transient \u2014 the next dispatch should succeed. If it persists, check [status.pullfrog.com](https://status.pullfrog.com)."
|
|
153256
|
+
].join("\n");
|
|
153257
|
+
}
|
|
152456
153258
|
async function mintProxyKey(ctx) {
|
|
152457
153259
|
try {
|
|
152458
153260
|
process.env.ACTIONS_ID_TOKEN_REQUEST_URL = ctx.oidcCredentials.requestUrl;
|
|
@@ -152465,6 +153267,20 @@ async function mintProxyKey(ctx) {
|
|
|
152465
153267
|
method: "POST",
|
|
152466
153268
|
headers: { Authorization: `Bearer ${oidcToken}` }
|
|
152467
153269
|
});
|
|
153270
|
+
if (response.status === 402) {
|
|
153271
|
+
const body = await response.json().catch(() => null);
|
|
153272
|
+
throw new BillingError(body?.error ?? "insufficient balance", {
|
|
153273
|
+
code: body?.code ?? null,
|
|
153274
|
+
declineCode: body?.declineCode ?? null,
|
|
153275
|
+
needsReauthentication: body?.needsReauthentication ?? false
|
|
153276
|
+
});
|
|
153277
|
+
}
|
|
153278
|
+
if (response.status === 503) {
|
|
153279
|
+
const body = await response.json().catch(() => null);
|
|
153280
|
+
throw new TransientError(
|
|
153281
|
+
body?.error ?? "billing service temporarily unavailable \u2014 retry shortly"
|
|
153282
|
+
);
|
|
153283
|
+
}
|
|
152468
153284
|
if (!response.ok) {
|
|
152469
153285
|
log.warning(`proxy key mint failed (${response.status})`);
|
|
152470
153286
|
return null;
|
|
@@ -152472,6 +153288,8 @@ async function mintProxyKey(ctx) {
|
|
|
152472
153288
|
const data = await response.json();
|
|
152473
153289
|
return data.key;
|
|
152474
153290
|
} catch (error49) {
|
|
153291
|
+
if (error49 instanceof BillingError) throw error49;
|
|
153292
|
+
if (error49 instanceof TransientError) throw error49;
|
|
152475
153293
|
log.warning(`proxy key mint error: ${error49 instanceof Error ? error49.message : String(error49)}`);
|
|
152476
153294
|
return null;
|
|
152477
153295
|
} finally {
|
|
@@ -152481,19 +153299,19 @@ async function mintProxyKey(ctx) {
|
|
|
152481
153299
|
}
|
|
152482
153300
|
async function resolveProxyModel(ctx) {
|
|
152483
153301
|
if (process.env.PULLFROG_MODEL?.trim()) return;
|
|
152484
|
-
|
|
152485
|
-
|
|
152486
|
-
|
|
152487
|
-
|
|
152488
|
-
}
|
|
152489
|
-
const key = await mintProxyKey({ oidcCredentials: ctx.oidcCredentials });
|
|
152490
|
-
if (!key) return;
|
|
152491
|
-
process.env.OPENROUTER_API_KEY = key;
|
|
152492
|
-
core6.setSecret(key);
|
|
152493
|
-
ctx.payload.proxyModel = ctx.proxyModel;
|
|
152494
|
-
log.info(`\xBB proxy: oss \u2192 ${ctx.proxyModel}`);
|
|
153302
|
+
const needsProxy = isInfraCovered({ isOss: ctx.oss, plan: ctx.plan }) && ctx.proxyModel;
|
|
153303
|
+
if (!needsProxy) return;
|
|
153304
|
+
if (!ctx.oidcCredentials) {
|
|
153305
|
+
log.warning("\xBB proxy requested but no OIDC credentials available \u2014 skipping");
|
|
152495
153306
|
return;
|
|
152496
153307
|
}
|
|
153308
|
+
const key = await mintProxyKey({ oidcCredentials: ctx.oidcCredentials });
|
|
153309
|
+
if (!key) return;
|
|
153310
|
+
process.env.OPENROUTER_API_KEY = key;
|
|
153311
|
+
core6.setSecret(key);
|
|
153312
|
+
ctx.payload.proxyModel = ctx.proxyModel;
|
|
153313
|
+
const label = ctx.oss ? "oss" : "router";
|
|
153314
|
+
log.info(`\xBB proxy: ${label} \u2192 ${ctx.proxyModel}`);
|
|
152497
153315
|
}
|
|
152498
153316
|
async function writeJobSummary(toolState) {
|
|
152499
153317
|
const usageSummary = formatUsageSummary(toolState.usageEntries);
|
|
@@ -152515,7 +153333,7 @@ async function main() {
|
|
|
152515
153333
|
let safetyNetTimer;
|
|
152516
153334
|
const resolvedPromptInput = resolvePromptInput();
|
|
152517
153335
|
const toolState = initToolState({
|
|
152518
|
-
|
|
153336
|
+
progressComment: typeof resolvedPromptInput !== "string" ? resolvedPromptInput.progressComment : void 0
|
|
152519
153337
|
});
|
|
152520
153338
|
resolveGit();
|
|
152521
153339
|
const jobToken = getJobToken();
|
|
@@ -152549,12 +153367,33 @@ async function main() {
|
|
|
152549
153367
|
delete process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
|
|
152550
153368
|
delete process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
|
152551
153369
|
}
|
|
152552
|
-
|
|
152553
|
-
|
|
152554
|
-
|
|
152555
|
-
|
|
152556
|
-
|
|
152557
|
-
|
|
153370
|
+
try {
|
|
153371
|
+
await resolveProxyModel({
|
|
153372
|
+
payload,
|
|
153373
|
+
oss: runContext.oss,
|
|
153374
|
+
plan: runContext.plan,
|
|
153375
|
+
proxyModel: runContext.proxyModel,
|
|
153376
|
+
oidcCredentials
|
|
153377
|
+
});
|
|
153378
|
+
} catch (error49) {
|
|
153379
|
+
if (error49 instanceof BillingError) {
|
|
153380
|
+
const summary2 = formatBillingErrorSummary(error49);
|
|
153381
|
+
await writeSummary(summary2).catch(() => {
|
|
153382
|
+
});
|
|
153383
|
+
await reportErrorToComment({ toolState, error: summary2 }).catch(() => {
|
|
153384
|
+
});
|
|
153385
|
+
throw error49;
|
|
153386
|
+
}
|
|
153387
|
+
if (error49 instanceof TransientError) {
|
|
153388
|
+
const summary2 = formatTransientErrorSummary(error49);
|
|
153389
|
+
await writeSummary(summary2).catch(() => {
|
|
153390
|
+
});
|
|
153391
|
+
await reportErrorToComment({ toolState, error: summary2 }).catch(() => {
|
|
153392
|
+
});
|
|
153393
|
+
throw error49;
|
|
153394
|
+
}
|
|
153395
|
+
throw error49;
|
|
153396
|
+
}
|
|
152558
153397
|
const octokit = createOctokit(tokenRef.mcpToken);
|
|
152559
153398
|
const runInfo = await resolveRun({ octokit });
|
|
152560
153399
|
let toolContext;
|
|
@@ -152628,6 +153467,8 @@ async function main() {
|
|
|
152628
153467
|
jobId: runInfo.jobId,
|
|
152629
153468
|
mcpServerUrl: "",
|
|
152630
153469
|
tmpdir: tmpdir3,
|
|
153470
|
+
oss: runContext.oss,
|
|
153471
|
+
plan: runContext.plan,
|
|
152631
153472
|
resolvedModel
|
|
152632
153473
|
};
|
|
152633
153474
|
const mcpHttpServer = __using(_stack, await startMcpHttpServer(toolContext, { outputSchema }), true);
|
|
@@ -152665,8 +153506,8 @@ ${instructions.user}` : null,
|
|
|
152665
153506
|
log.info(instructions.full);
|
|
152666
153507
|
});
|
|
152667
153508
|
if (agentId === "opencode") {
|
|
152668
|
-
const pluginDir =
|
|
152669
|
-
const hasPlugins =
|
|
153509
|
+
const pluginDir = join15(process.cwd(), ".opencode", "plugin");
|
|
153510
|
+
const hasPlugins = existsSync7(pluginDir) && readdirSync(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
|
|
152670
153511
|
if (hasPlugins && toolState.dependencyInstallation?.promise) {
|
|
152671
153512
|
log.info(
|
|
152672
153513
|
"\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"
|
|
@@ -152691,6 +153532,9 @@ ${instructions.user}` : null,
|
|
|
152691
153532
|
}
|
|
152692
153533
|
});
|
|
152693
153534
|
toolState.todoTracker = todoTracker;
|
|
153535
|
+
onExitSignal(() => {
|
|
153536
|
+
todoTracker?.cancel();
|
|
153537
|
+
});
|
|
152694
153538
|
let innerTimeoutFired = false;
|
|
152695
153539
|
const onInnerActivityTimeout = () => {
|
|
152696
153540
|
if (innerTimeoutFired) return;
|
|
@@ -152720,6 +153564,7 @@ ${instructions.user}` : null,
|
|
|
152720
153564
|
tmpdir: tmpdir3,
|
|
152721
153565
|
instructions,
|
|
152722
153566
|
todoTracker,
|
|
153567
|
+
stopScript: runContext.repoSettings.stopScript,
|
|
152723
153568
|
onActivityTimeout: onInnerActivityTimeout,
|
|
152724
153569
|
onToolUse: (event) => {
|
|
152725
153570
|
const wasTracked = recordDiffReadFromToolUse({
|
|
@@ -152774,13 +153619,8 @@ ${instructions.user}` : null,
|
|
|
152774
153619
|
log.debug(`post-review cleanup failed: ${error49}`);
|
|
152775
153620
|
});
|
|
152776
153621
|
}
|
|
152777
|
-
if (toolContext && toolState.review && toolState.progressCommentId) {
|
|
152778
|
-
await deleteProgressComment(toolContext).catch((error49) => {
|
|
152779
|
-
log.debug(`review progress comment cleanup failed: ${error49}`);
|
|
152780
|
-
});
|
|
152781
|
-
}
|
|
152782
153622
|
const trackerWasLastWriter = todoTracker?.hasPublished && !toolState.finalSummaryWritten;
|
|
152783
|
-
if (toolContext && toolState.
|
|
153623
|
+
if (toolContext && toolState.progressComment && (!toolState.wasUpdated || trackerWasLastWriter)) {
|
|
152784
153624
|
await deleteProgressComment(toolContext).catch((error49) => {
|
|
152785
153625
|
log.debug(`stranded progress comment cleanup failed: ${error49}`);
|
|
152786
153626
|
});
|