@ulpi/cli 0.1.5 → 0.1.6
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/LICENSE +21 -0
- package/dist/{auth-PN7TMQHV-2W4ICG64.js → auth-FWM7MM4Q-VZC3U2XZ.js} +1 -1
- package/dist/{auth-BFFBUJUC.js → auth-HDK7ECJL.js} +2 -1
- package/dist/{chunk-RJIRWQJD.js → chunk-3BCW6ABU.js} +402 -142
- package/dist/{chunk-L3PWNHSA.js → chunk-3WB5CXH4.js} +180 -5
- package/dist/{chunk-K4OVPFY2.js → chunk-4UCJIAOU.js} +2 -2
- package/dist/chunk-4XTHZVDS.js +109 -0
- package/dist/chunk-4ZPOZULQ.js +6522 -0
- package/dist/{chunk-SIAQVRKG.js → chunk-5MI5GIXM.js} +48 -2
- package/dist/{chunk-KLEASXUR.js → chunk-6ZL6NXMV.js} +1 -1
- package/dist/{chunk-AV5RB3N2.js → chunk-76D3BYJD.js} +48 -0
- package/dist/{chunk-DOIKS6C5.js → chunk-AWOSRA5F.js} +1 -1
- package/dist/{chunk-UCMT5OKP.js → chunk-BFEKZZHM.js} +274 -57
- package/dist/chunk-C7CLUQI6.js +1286 -0
- package/dist/{chunk-ELTGWMDE.js → chunk-E3B5NROU.js} +7 -7
- package/dist/chunk-EJ7TW77N.js +1418 -0
- package/dist/{chunk-6OURRFP7.js → chunk-IV6MWETF.js} +383 -168
- package/dist/chunk-IZPJHSPX.js +1478 -0
- package/dist/chunk-JLHNLM3C.js +228 -0
- package/dist/{chunk-P2RESJRN.js → chunk-KYYI23AQ.js} +2 -2
- package/dist/chunk-S6ANCSYO.js +1271 -0
- package/dist/chunk-SEU7WWNQ.js +1251 -0
- package/dist/chunk-SNQ7NAIS.js +453 -0
- package/dist/{ulpi-RMMCUAGP-EWYUE7RU.js → chunk-TSLDGT5O.js} +73 -35
- package/dist/{chunk-EIWYSP3A.js → chunk-UXHCHOWQ.js} +83 -62
- package/dist/chunk-V2H5D6Y3.js +146 -0
- package/dist/{chunk-5SCG7UYM.js → chunk-VVEDXI7E.js} +1 -1
- package/dist/chunk-VXH5Y4FO.js +6761 -0
- package/dist/chunk-WED4LM5N.js +322 -0
- package/dist/{chunk-74WVVWJ4.js → chunk-YOKL7RB5.js} +184 -15
- package/dist/chunk-Z53CAR7G.js +298 -0
- package/dist/{ci-JQ56YIKC.js → ci-X3U2W4HC.js} +124 -26
- package/dist/cloud-2F3NLVHN.js +274 -0
- package/dist/{codemap-HMYBXJL2.js → codemap-XNGMAF3F.js} +37 -37
- package/dist/codex-MB5YTMRT.js +132 -0
- package/dist/{config-YYWEN7U2.js → config-OOELBYTH.js} +1 -1
- package/dist/dist-2BJYR5EI.js +59 -0
- package/dist/dist-3EIQTZHT.js +1380 -0
- package/dist/{dist-WAMAQVPK.js → dist-4U5L2X2C.js} +2 -2
- package/dist/{dist-4XTJ6HLM.js → dist-54KAMNLO.js} +16 -15
- package/dist/dist-6M4MZWZW.js +58 -0
- package/dist/dist-6X576SU2.js +27 -0
- package/dist/dist-7QOEYLFX.js +103 -0
- package/dist/dist-AYBGHEDY.js +2541 -0
- package/dist/dist-EK45QNEM.js +45 -0
- package/dist/{dist-U7ZIJMZD.js → dist-FKFEJRPX.js} +16 -15
- package/dist/dist-GTEJUBBT.js +66 -0
- package/dist/dist-HA74OKJZ.js +40 -0
- package/dist/{dist-XG2GG5SD.js → dist-HU5RZAON.js} +14 -2
- package/dist/dist-IYE3OBRB.js +374 -0
- package/dist/{dist-7WLLPWWB.js → dist-JLU26AB6.js} +12 -9
- package/dist/{dist-6G7JC2RA.js → dist-KUCI6JFE.js} +49 -9
- package/dist/dist-NUEMFZFL.js +33 -0
- package/dist/{dist-GWGTAHNM.js → dist-NUXMDXZ3.js} +31 -3
- package/dist/{dist-5R4RYNQO.js → dist-YCNWHSLN.js} +15 -5
- package/dist/{dist-6MFVWIFF.js → dist-YFFG2ZD6.js} +9 -16
- package/dist/dist-ZG4OKCSR.js +15 -0
- package/dist/doctor-SI4LLLDZ.js +345 -0
- package/dist/{export-import-4A5MWLIA.js → export-import-JFQH4KSJ.js} +1 -1
- package/dist/{history-RNUWO4JZ.js → history-5NE46ZAH.js} +7 -7
- package/dist/{hooks-installer-K2JXEBNN.js → hooks-installer-UN5JZLDQ.js} +2 -2
- package/dist/index.js +394 -618
- package/dist/{init-NQWFZPKO.js → init-5FK3VKRT.js} +76 -10
- package/dist/job-HIDMAFW2.js +376 -0
- package/dist/jobs.memory-PLMMSFHB-VBECCTHN.js +33 -0
- package/dist/kiro-VMUHDFGK.js +153 -0
- package/dist/{launchd-OYXUAVW6.js → launchd-6AWT54HR.js} +9 -17
- package/dist/mcp-PDUD7SGP.js +249 -0
- package/dist/mcp-installer-PQU3XOGO.js +259 -0
- package/dist/mcp-setup-OA7IB3H3.js +263 -0
- package/dist/{memory-D6ZFFCI2.js → memory-ZNAEAK3B.js} +17 -17
- package/dist/{ollama-3XCUZMZT-FYKHW4TZ.js → ollama-3XCUZMZT-4JMH6B7P.js} +1 -1
- package/dist/{openai-E7G2YAHU-IG33BFYF.js → openai-E7G2YAHU-T3HMBPH7.js} +2 -2
- package/dist/portal-JYWVHXDU.js +210 -0
- package/dist/prd-Q4J5NVAR.js +408 -0
- package/dist/repos-WWZXNN3P.js +271 -0
- package/dist/review-integration-5WHEJU2A.js +14 -0
- package/dist/{rules-3OFGWHP4.js → rules-Y4VSOY5Y.js} +3 -3
- package/dist/run-VPNXEIBY.js +687 -0
- package/dist/server-COL4AXKU-P7S7NNF6.js +11 -0
- package/dist/server-KKSETHDV-XSSLEENT.js +20 -0
- package/dist/{skills-GY2CTPWN.js → skills-QEYU2N27.js} +4 -2
- package/dist/start-JYOEL7AJ.js +303 -0
- package/dist/{status-SE43TIFJ.js → status-BHQYYGAL.js} +2 -2
- package/dist/{templates-O2XDKB5R.js → templates-CBRUJ66V.js} +6 -5
- package/dist/tui-DP7736EX.js +61 -0
- package/dist/ulpi-5EN6JCAS-LFE3WSL4.js +10 -0
- package/dist/{uninstall-KWGSGZTI.js → uninstall-ICUV6DDV.js} +3 -3
- package/dist/{update-QYZA4D23.js → update-7ZMAYRBH.js} +3 -3
- package/dist/{version-checker-MVB74DEX.js → version-checker-4ZFMZA7Y.js} +2 -2
- package/package.json +39 -31
- package/dist/chunk-26LLDX2T.js +0 -553
- package/dist/chunk-DDRLI6JU.js +0 -331
- package/dist/chunk-IFATANHR.js +0 -453
- package/dist/chunk-JWUUVXIV.js +0 -13694
- package/dist/chunk-LD52XG3X.js +0 -4273
- package/dist/chunk-MIAQVCFW.js +0 -39
- package/dist/chunk-YYZOFYS6.js +0 -415
- package/dist/dist-XD4YI27T.js +0 -26
- package/dist/mcp-installer-TOYDP77X.js +0 -124
- package/dist/projects-COUJP4ZC.js +0 -271
- package/dist/review-KMGP2S25.js +0 -152
- package/dist/server-USLHY6GH-F4JSXCWA.js +0 -18
- package/dist/server-X5P6WH2M-ULZF5WHZ.js +0 -11
- package/dist/skills/ulpi-generate-guardian/SKILL.md +0 -750
- package/dist/skills/ulpi-generate-guardian/references/framework-rules.md +0 -849
- package/dist/skills/ulpi-generate-guardian/references/language-rules.md +0 -591
- package/dist/ui-4SM2SUI6.js +0 -167
- package/dist/ui.html +0 -698
|
@@ -0,0 +1,1418 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getTrackerRegistry
|
|
3
|
+
} from "./chunk-S6ANCSYO.js";
|
|
4
|
+
import {
|
|
5
|
+
getAgentRegistry
|
|
6
|
+
} from "./chunk-IZPJHSPX.js";
|
|
7
|
+
import {
|
|
8
|
+
OutputBuffer
|
|
9
|
+
} from "./chunk-SEU7WWNQ.js";
|
|
10
|
+
|
|
11
|
+
// ../../packages/loop-engine/dist/index.js
|
|
12
|
+
import { execFileSync } from "child_process";
|
|
13
|
+
var DEFAULT_ENGINE_CONFIG = {
|
|
14
|
+
maxRetries: 3,
|
|
15
|
+
retryDelayMs: 5e3,
|
|
16
|
+
timeout: 0,
|
|
17
|
+
maxIterations: 0,
|
|
18
|
+
iterationDelayMs: 0,
|
|
19
|
+
errorStrategy: "retry",
|
|
20
|
+
rateLimit: {
|
|
21
|
+
enabled: true,
|
|
22
|
+
maxRetries: 3,
|
|
23
|
+
baseBackoffMs: 5e3,
|
|
24
|
+
recoverPrimaryBetweenIterations: true
|
|
25
|
+
},
|
|
26
|
+
agentConfig: {}
|
|
27
|
+
};
|
|
28
|
+
var COMMON_PATTERNS = [
|
|
29
|
+
// HTTP 429 — must appear in error/HTTP context, not just any "429"
|
|
30
|
+
{
|
|
31
|
+
pattern: /(?:HTTP|status|error|code|response)[\s:]*429|429\s*(?:too many|rate limit|error)/i,
|
|
32
|
+
retryAfterPattern: /retry[- ]?after[:\s]+(\d+)\s*s/i
|
|
33
|
+
},
|
|
34
|
+
// Generic rate limit phrases
|
|
35
|
+
{
|
|
36
|
+
pattern: /rate[- ]limit/i,
|
|
37
|
+
retryAfterPattern: /retry[- ]?after[:\s]+(\d+)\s*s/i
|
|
38
|
+
},
|
|
39
|
+
// Too many requests
|
|
40
|
+
{
|
|
41
|
+
pattern: /too many requests/i,
|
|
42
|
+
retryAfterPattern: /(\d+)\s*seconds?/i
|
|
43
|
+
},
|
|
44
|
+
// Quota exceeded
|
|
45
|
+
{
|
|
46
|
+
pattern: /quota[- ]?exceeded/i,
|
|
47
|
+
retryAfterPattern: /(\d+)\s*seconds?/i
|
|
48
|
+
},
|
|
49
|
+
// Overloaded
|
|
50
|
+
{
|
|
51
|
+
pattern: /\boverloaded\b/i,
|
|
52
|
+
retryAfterPattern: /(\d+)\s*seconds?/i
|
|
53
|
+
}
|
|
54
|
+
];
|
|
55
|
+
var AGENT_SPECIFIC_PATTERNS = {
|
|
56
|
+
claude: [
|
|
57
|
+
{
|
|
58
|
+
pattern: /anthropic.*rate[- ]?limit/i,
|
|
59
|
+
retryAfterPattern: /retry[- ]?after[:\s]+(\d+)\s*s/i
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
pattern: /API rate limit exceeded/i,
|
|
63
|
+
retryAfterPattern: /wait[:\s]+(\d+)\s*s/i
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
pattern: /claude.*is currently overloaded/i,
|
|
67
|
+
retryAfterPattern: /(\d+)\s*seconds?/i
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
pattern: /api[- ]?error.*429/i,
|
|
71
|
+
retryAfterPattern: /retry[- ]?after[:\s]+(\d+)/i
|
|
72
|
+
}
|
|
73
|
+
],
|
|
74
|
+
opencode: [
|
|
75
|
+
{
|
|
76
|
+
pattern: /openai.*rate[- ]?limit/i,
|
|
77
|
+
retryAfterPattern: /retry[- ]?after[:\s]+(\d+)\s*s/i
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
pattern: /tokens per minute/i,
|
|
81
|
+
retryAfterPattern: /(\d+)\s*seconds?/i
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
pattern: /requests per minute/i,
|
|
85
|
+
retryAfterPattern: /(\d+)\s*seconds?/i
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
gemini: [
|
|
89
|
+
{
|
|
90
|
+
pattern: /google.*rate[- ]?limit/i,
|
|
91
|
+
retryAfterPattern: /(\d+)\s*seconds?/i
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
pattern: /resource[- ]?exhausted/i,
|
|
95
|
+
retryAfterPattern: /(\d+)\s*seconds?/i
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
};
|
|
99
|
+
var RATE_LIMIT_EXIT_CODES = /* @__PURE__ */ new Set([1, 2, 429]);
|
|
100
|
+
function detectRateLimit(input) {
|
|
101
|
+
const { stderr, exitCode, agentId } = input;
|
|
102
|
+
if (!stderr.trim() && exitCode === 0) {
|
|
103
|
+
return { isRateLimit: false };
|
|
104
|
+
}
|
|
105
|
+
const patterns = [...COMMON_PATTERNS];
|
|
106
|
+
if (agentId && AGENT_SPECIFIC_PATTERNS[agentId]) {
|
|
107
|
+
patterns.push(...AGENT_SPECIFIC_PATTERNS[agentId]);
|
|
108
|
+
}
|
|
109
|
+
for (const { pattern, retryAfterPattern } of patterns) {
|
|
110
|
+
if (pattern.test(stderr)) {
|
|
111
|
+
const message = extractMessage(stderr, pattern);
|
|
112
|
+
const retryAfter = retryAfterPattern ? extractRetryAfter(stderr, retryAfterPattern) : void 0;
|
|
113
|
+
return { isRateLimit: true, message, retryAfter };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (exitCode !== void 0 && exitCode !== 0 && RATE_LIMIT_EXIT_CODES.has(exitCode)) {
|
|
117
|
+
const looseMessage = looseRateLimitCheck(stderr);
|
|
118
|
+
if (looseMessage) {
|
|
119
|
+
return {
|
|
120
|
+
isRateLimit: true,
|
|
121
|
+
message: looseMessage,
|
|
122
|
+
retryAfter: extractAnyRetryAfter(stderr)
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return { isRateLimit: false };
|
|
127
|
+
}
|
|
128
|
+
function calculateBackoff(attempt, baseBackoffMs, retryAfter) {
|
|
129
|
+
if (retryAfter !== void 0 && retryAfter > 0) {
|
|
130
|
+
return { delayMs: retryAfter * 1e3, usedRetryAfter: true };
|
|
131
|
+
}
|
|
132
|
+
const delayMs = baseBackoffMs * Math.pow(3, attempt);
|
|
133
|
+
return { delayMs, usedRetryAfter: false };
|
|
134
|
+
}
|
|
135
|
+
function shouldFallback(limitedAgents, fallbackAgents) {
|
|
136
|
+
for (const agent of fallbackAgents) {
|
|
137
|
+
if (!limitedAgents.has(agent)) {
|
|
138
|
+
return agent;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
function extractMessage(output, pattern) {
|
|
144
|
+
const match = output.match(pattern);
|
|
145
|
+
if (!match) return "Rate limit detected";
|
|
146
|
+
const matchIndex = match.index ?? 0;
|
|
147
|
+
const start = Math.max(0, matchIndex - 50);
|
|
148
|
+
const end = Math.min(output.length, matchIndex + match[0].length + 100);
|
|
149
|
+
let message = output.slice(start, end).trim().replace(/\s+/g, " ");
|
|
150
|
+
if (message.length > 200) {
|
|
151
|
+
message = message.slice(0, 200) + "...";
|
|
152
|
+
}
|
|
153
|
+
return message;
|
|
154
|
+
}
|
|
155
|
+
function extractRetryAfter(output, pattern) {
|
|
156
|
+
const match = output.match(pattern);
|
|
157
|
+
if (match?.[1]) {
|
|
158
|
+
const seconds = parseInt(match[1], 10);
|
|
159
|
+
if (!isNaN(seconds) && seconds > 0 && seconds < 3600) {
|
|
160
|
+
return seconds;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return void 0;
|
|
164
|
+
}
|
|
165
|
+
function extractAnyRetryAfter(output) {
|
|
166
|
+
const patterns = [
|
|
167
|
+
/retry[- ]?after[:\s]+(\d+)\s*s/i,
|
|
168
|
+
/wait[:\s]+(\d+)\s*s/i,
|
|
169
|
+
/try again in[:\s]+(\d+)\s*s/i,
|
|
170
|
+
/(\d+)\s*seconds?(?:\s*(?:before|until|wait))/i
|
|
171
|
+
];
|
|
172
|
+
for (const pattern of patterns) {
|
|
173
|
+
const match = output.match(pattern);
|
|
174
|
+
if (match?.[1]) {
|
|
175
|
+
const seconds = parseInt(match[1], 10);
|
|
176
|
+
if (!isNaN(seconds) && seconds > 0 && seconds < 3600) {
|
|
177
|
+
return seconds;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return void 0;
|
|
182
|
+
}
|
|
183
|
+
function looseRateLimitCheck(output) {
|
|
184
|
+
const loosePatterns = [
|
|
185
|
+
/throttl/i,
|
|
186
|
+
/limit.*exceeded/i,
|
|
187
|
+
/exceeded.*limit/i,
|
|
188
|
+
/capacity/i,
|
|
189
|
+
/backoff/i
|
|
190
|
+
];
|
|
191
|
+
for (const pattern of loosePatterns) {
|
|
192
|
+
if (pattern.test(output)) {
|
|
193
|
+
return extractMessage(output, pattern);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
function hasUncommittedChanges(cwd) {
|
|
199
|
+
try {
|
|
200
|
+
const output = execFileSync("git", ["status", "--porcelain"], {
|
|
201
|
+
cwd,
|
|
202
|
+
encoding: "utf-8",
|
|
203
|
+
timeout: 1e4
|
|
204
|
+
});
|
|
205
|
+
return output.trim().length > 0;
|
|
206
|
+
} catch (err) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
`git status failed: ${err instanceof Error ? err.message : String(err)}`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async function autoCommitChanges(options) {
|
|
213
|
+
const { workingDir, taskId, taskTitle } = options;
|
|
214
|
+
let hasChanges;
|
|
215
|
+
try {
|
|
216
|
+
hasChanges = hasUncommittedChanges(workingDir);
|
|
217
|
+
} catch (err) {
|
|
218
|
+
return {
|
|
219
|
+
committed: false,
|
|
220
|
+
error: err instanceof Error ? err.message : String(err)
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (!hasChanges) {
|
|
224
|
+
return {
|
|
225
|
+
committed: false,
|
|
226
|
+
skipReason: "no uncommitted changes"
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
execFileSync("git", ["add", "-A"], {
|
|
231
|
+
cwd: workingDir,
|
|
232
|
+
encoding: "utf-8",
|
|
233
|
+
timeout: 3e4
|
|
234
|
+
});
|
|
235
|
+
} catch (err) {
|
|
236
|
+
return {
|
|
237
|
+
committed: false,
|
|
238
|
+
error: `git add failed: ${err instanceof Error ? err.message : String(err)}`
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
const commitMessage = `feat: ${taskId} - ${taskTitle}`;
|
|
242
|
+
try {
|
|
243
|
+
execFileSync("git", ["commit", "-m", commitMessage], {
|
|
244
|
+
cwd: workingDir,
|
|
245
|
+
encoding: "utf-8",
|
|
246
|
+
timeout: 3e4
|
|
247
|
+
});
|
|
248
|
+
} catch (err) {
|
|
249
|
+
return {
|
|
250
|
+
committed: false,
|
|
251
|
+
error: `git commit failed: ${err instanceof Error ? err.message : String(err)}`
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
let commitSha;
|
|
255
|
+
try {
|
|
256
|
+
commitSha = execFileSync("git", ["rev-parse", "--short", "HEAD"], {
|
|
257
|
+
cwd: workingDir,
|
|
258
|
+
encoding: "utf-8",
|
|
259
|
+
timeout: 5e3
|
|
260
|
+
}).trim();
|
|
261
|
+
} catch {
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
committed: true,
|
|
265
|
+
commitMessage,
|
|
266
|
+
commitSha
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
async function injectMemories(taskDescription, projectDir) {
|
|
270
|
+
try {
|
|
271
|
+
const { searchMemory, formatMemoriesForAgent } = await import("./dist-JLU26AB6.js");
|
|
272
|
+
const result = await searchMemory(projectDir, {
|
|
273
|
+
query: taskDescription,
|
|
274
|
+
limit: 5
|
|
275
|
+
});
|
|
276
|
+
if (result.results.length === 0) return "";
|
|
277
|
+
const entries = result.results.map((r) => r.entry);
|
|
278
|
+
const formatted = formatMemoriesForAgent(entries);
|
|
279
|
+
return formatted;
|
|
280
|
+
} catch {
|
|
281
|
+
return "";
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async function captureIterationMemory(iterationResult, projectDir) {
|
|
285
|
+
if (iterationResult.status !== "completed" || !iterationResult.taskCompleted) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
const { rememberMemory, generateMemoryId, isMemoryEnabled } = await import("./dist-JLU26AB6.js");
|
|
290
|
+
if (!isMemoryEnabled(projectDir)) return;
|
|
291
|
+
const { MemoryEntrySchema } = await import("./dist-KUCI6JFE.js");
|
|
292
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
293
|
+
const taskTitle = iterationResult.task.title;
|
|
294
|
+
const taskId = iterationResult.task.id;
|
|
295
|
+
const summary = `Task "${taskTitle}" (${taskId}) completed in ${iterationResult.durationMs}ms${iterationResult.commitSha ? ` with commit ${iterationResult.commitSha}` : ""}`;
|
|
296
|
+
const detail = [
|
|
297
|
+
`Task: ${taskTitle}`,
|
|
298
|
+
`Description: ${iterationResult.task.description ?? "N/A"}`,
|
|
299
|
+
`Duration: ${iterationResult.durationMs}ms`,
|
|
300
|
+
`Iteration: ${iterationResult.iteration}`,
|
|
301
|
+
iterationResult.commitSha ? `Commit: ${iterationResult.commitSha}` : null
|
|
302
|
+
].filter(Boolean).join("\n");
|
|
303
|
+
const entry = MemoryEntrySchema.parse({
|
|
304
|
+
id: generateMemoryId("LESSON", summary),
|
|
305
|
+
type: "LESSON",
|
|
306
|
+
summary,
|
|
307
|
+
detail,
|
|
308
|
+
importance: "low",
|
|
309
|
+
tags: ["loop-engine", `task:${taskId}`],
|
|
310
|
+
relatedFiles: [],
|
|
311
|
+
source: {
|
|
312
|
+
type: "explicit",
|
|
313
|
+
sessionId: `loop-${taskId}`
|
|
314
|
+
},
|
|
315
|
+
createdAt: now,
|
|
316
|
+
updatedAt: now,
|
|
317
|
+
lastAccessedAt: now,
|
|
318
|
+
accessCount: 0
|
|
319
|
+
});
|
|
320
|
+
await rememberMemory(projectDir, entry);
|
|
321
|
+
} catch {
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async function getSemanticContext(taskDescription, projectDir) {
|
|
325
|
+
try {
|
|
326
|
+
const { searchCode } = await import("./dist-YFFG2ZD6.js");
|
|
327
|
+
const result = await searchCode(projectDir, taskDescription, {
|
|
328
|
+
limit: 8,
|
|
329
|
+
includeTests: false,
|
|
330
|
+
includeDocs: false
|
|
331
|
+
});
|
|
332
|
+
if (result.results.length === 0) return "";
|
|
333
|
+
const lines = [];
|
|
334
|
+
for (const item of result.results) {
|
|
335
|
+
const lineRange = item.startLine && item.endLine ? ` (L${item.startLine}-${item.endLine})` : "";
|
|
336
|
+
const scoreStr = item.score ? ` [relevance: ${item.score.toFixed(2)}]` : "";
|
|
337
|
+
lines.push(`### ${item.filePath}${lineRange}${scoreStr}`);
|
|
338
|
+
if (item.snippet) {
|
|
339
|
+
const snippet = item.snippet.length > 500 ? item.snippet.slice(0, 500) + "\n..." : item.snippet;
|
|
340
|
+
lines.push("```");
|
|
341
|
+
lines.push(snippet);
|
|
342
|
+
lines.push("```");
|
|
343
|
+
}
|
|
344
|
+
lines.push("");
|
|
345
|
+
}
|
|
346
|
+
return lines.join("\n");
|
|
347
|
+
} catch {
|
|
348
|
+
return "";
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
async function getCodemapAvailability(projectDir) {
|
|
352
|
+
try {
|
|
353
|
+
const { getCodemapStatus } = await import("./dist-YFFG2ZD6.js");
|
|
354
|
+
const status = getCodemapStatus(projectDir);
|
|
355
|
+
return status.initialized && status.totalChunks > 0;
|
|
356
|
+
} catch {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
var COMPLETION_SIGNAL = "<completion>DONE</completion>";
|
|
361
|
+
var COMPLETION_SIGNAL_PATTERN = /<completion>\s*DONE\s*<\/completion>/i;
|
|
362
|
+
function buildPrompt(options) {
|
|
363
|
+
const { task, additionalContext } = options;
|
|
364
|
+
const lines = [];
|
|
365
|
+
lines.push("## Task");
|
|
366
|
+
lines.push(`**ID**: ${task.id}`);
|
|
367
|
+
lines.push(`**Title**: ${task.title}`);
|
|
368
|
+
if (task.type) {
|
|
369
|
+
lines.push(`**Type**: ${task.type}`);
|
|
370
|
+
}
|
|
371
|
+
if (task.priority !== void 0) {
|
|
372
|
+
const priorityLabel = formatPriority(task.priority);
|
|
373
|
+
lines.push(`**Priority**: ${priorityLabel}`);
|
|
374
|
+
}
|
|
375
|
+
if (task.labels && task.labels.length > 0) {
|
|
376
|
+
lines.push(`**Labels**: ${task.labels.join(", ")}`);
|
|
377
|
+
}
|
|
378
|
+
if (task.dependsOn && task.dependsOn.length > 0) {
|
|
379
|
+
lines.push(`**Depends on**: ${task.dependsOn.join(", ")}`);
|
|
380
|
+
}
|
|
381
|
+
if (task.description) {
|
|
382
|
+
lines.push("");
|
|
383
|
+
lines.push("## Description");
|
|
384
|
+
lines.push(task.description);
|
|
385
|
+
}
|
|
386
|
+
if (additionalContext) {
|
|
387
|
+
lines.push("");
|
|
388
|
+
lines.push("## Context");
|
|
389
|
+
lines.push(additionalContext);
|
|
390
|
+
}
|
|
391
|
+
lines.push("");
|
|
392
|
+
lines.push("## Completion");
|
|
393
|
+
lines.push(
|
|
394
|
+
"When you have completed this task, signal completion by outputting the following on its own line:"
|
|
395
|
+
);
|
|
396
|
+
lines.push("");
|
|
397
|
+
lines.push("```");
|
|
398
|
+
lines.push(COMPLETION_SIGNAL);
|
|
399
|
+
lines.push("```");
|
|
400
|
+
lines.push("");
|
|
401
|
+
lines.push(
|
|
402
|
+
"Only emit this signal when the task is fully complete. Do not emit it if you encounter a blocker or need clarification."
|
|
403
|
+
);
|
|
404
|
+
return lines.join("\n");
|
|
405
|
+
}
|
|
406
|
+
async function enrichPromptContext(taskDescription, workingDir) {
|
|
407
|
+
const sections = [];
|
|
408
|
+
const [memoryContext, codemapContext] = await Promise.allSettled([
|
|
409
|
+
injectMemories(taskDescription, workingDir),
|
|
410
|
+
getSemanticContext(taskDescription, workingDir)
|
|
411
|
+
]);
|
|
412
|
+
const memoryStr = memoryContext.status === "fulfilled" ? memoryContext.value : "";
|
|
413
|
+
const codemapStr = codemapContext.status === "fulfilled" ? codemapContext.value : "";
|
|
414
|
+
if (memoryStr) {
|
|
415
|
+
sections.push("## Relevant Learnings from Previous Sessions");
|
|
416
|
+
sections.push(memoryStr);
|
|
417
|
+
}
|
|
418
|
+
if (codemapStr) {
|
|
419
|
+
sections.push("## Relevant Code Context");
|
|
420
|
+
sections.push(codemapStr);
|
|
421
|
+
}
|
|
422
|
+
return sections.join("\n\n");
|
|
423
|
+
}
|
|
424
|
+
function formatPriority(priority) {
|
|
425
|
+
switch (priority) {
|
|
426
|
+
case 0:
|
|
427
|
+
return "Critical (P0)";
|
|
428
|
+
case 1:
|
|
429
|
+
return "High (P1)";
|
|
430
|
+
case 2:
|
|
431
|
+
return "Medium (P2)";
|
|
432
|
+
case 3:
|
|
433
|
+
return "Low (P3)";
|
|
434
|
+
case 4:
|
|
435
|
+
return "Minimal (P4)";
|
|
436
|
+
default:
|
|
437
|
+
return `P${priority}`;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
var COMPLETION_SIGNAL_JSON_SAFE = /<completion>\s*DONE\s*<\\?\/completion>/i;
|
|
441
|
+
function detectCompletionSignal(stdout) {
|
|
442
|
+
if (COMPLETION_SIGNAL_PATTERN.test(stdout)) return true;
|
|
443
|
+
if (COMPLETION_SIGNAL_JSON_SAFE.test(stdout)) return true;
|
|
444
|
+
const extracted = extractTextFromStreamJson(stdout);
|
|
445
|
+
if (extracted && COMPLETION_SIGNAL_PATTERN.test(extracted)) return true;
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
function extractTextFromStreamJson(stdout) {
|
|
449
|
+
const lines = stdout.split("\n");
|
|
450
|
+
const texts = [];
|
|
451
|
+
for (const line of lines) {
|
|
452
|
+
const trimmed = line.trim();
|
|
453
|
+
if (!trimmed || trimmed[0] !== "{") continue;
|
|
454
|
+
try {
|
|
455
|
+
const obj = JSON.parse(trimmed);
|
|
456
|
+
if (obj.type === "result") {
|
|
457
|
+
if (typeof obj.result === "string") {
|
|
458
|
+
texts.push(obj.result);
|
|
459
|
+
} else if (obj.result?.content) {
|
|
460
|
+
for (const block of obj.result.content) {
|
|
461
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
462
|
+
texts.push(block.text);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (obj.type === "assistant" && obj.message?.content) {
|
|
468
|
+
for (const block of obj.message.content) {
|
|
469
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
470
|
+
texts.push(block.text);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (obj.type === "content_block_delta" && typeof obj.delta?.text === "string") {
|
|
475
|
+
texts.push(obj.delta.text);
|
|
476
|
+
}
|
|
477
|
+
} catch {
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return texts.join("\n");
|
|
481
|
+
}
|
|
482
|
+
function detectStreamJsonSuccess(stdout) {
|
|
483
|
+
if (!stdout.includes('"type":"result"')) return false;
|
|
484
|
+
const lines = stdout.split("\n");
|
|
485
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
486
|
+
const line = lines[i].trim();
|
|
487
|
+
if (!line || line[0] !== "{") continue;
|
|
488
|
+
try {
|
|
489
|
+
const obj = JSON.parse(line);
|
|
490
|
+
if (obj.type === "result" && obj.subtype === "success" && !obj.is_error) {
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
} catch {
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
function isTaskComplete(stdout) {
|
|
500
|
+
if (detectCompletionSignal(stdout)) return true;
|
|
501
|
+
if (detectStreamJsonSuccess(stdout)) return true;
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
async function evaluateBeforeExecution(taskId, agentType, projectDir) {
|
|
505
|
+
try {
|
|
506
|
+
const { loadRulesSync, evaluateRules } = await import("./dist-GTEJUBBT.js");
|
|
507
|
+
const { projectGuardsFile } = await import("./dist-NUXMDXZ3.js");
|
|
508
|
+
const rulesPath = projectGuardsFile(projectDir);
|
|
509
|
+
const rules = loadRulesSync(rulesPath);
|
|
510
|
+
if (!rules) {
|
|
511
|
+
return { allowed: true };
|
|
512
|
+
}
|
|
513
|
+
const syntheticInput = {
|
|
514
|
+
session_id: `loop-${taskId}`,
|
|
515
|
+
transcript_path: "",
|
|
516
|
+
cwd: projectDir,
|
|
517
|
+
permission_mode: "default",
|
|
518
|
+
hook_event_name: "PreToolUse",
|
|
519
|
+
tool_name: "Bash",
|
|
520
|
+
tool_input: {
|
|
521
|
+
command: `ulpi-loop-agent:${agentType}:${taskId}`
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
const syntheticState = {
|
|
525
|
+
sessionId: `loop-${taskId}`,
|
|
526
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
527
|
+
projectDir,
|
|
528
|
+
phase: "active",
|
|
529
|
+
filesRead: [],
|
|
530
|
+
filesWritten: [],
|
|
531
|
+
filesDeleted: [],
|
|
532
|
+
commandsRun: [],
|
|
533
|
+
testsRun: false,
|
|
534
|
+
testsPassed: null,
|
|
535
|
+
lintRun: false,
|
|
536
|
+
lintPassed: null,
|
|
537
|
+
buildRun: false,
|
|
538
|
+
buildPassed: null,
|
|
539
|
+
formatterRun: false,
|
|
540
|
+
branchCreated: false,
|
|
541
|
+
planApproved: false,
|
|
542
|
+
rulesEnforced: 0,
|
|
543
|
+
actionsBlocked: 0,
|
|
544
|
+
autoActionsRun: 0,
|
|
545
|
+
consecutiveBlocks: 0,
|
|
546
|
+
lastNotifications: {}
|
|
547
|
+
};
|
|
548
|
+
const result = evaluateRules(
|
|
549
|
+
syntheticInput,
|
|
550
|
+
rules,
|
|
551
|
+
syntheticState,
|
|
552
|
+
projectDir
|
|
553
|
+
);
|
|
554
|
+
if (result.action === "block") {
|
|
555
|
+
return {
|
|
556
|
+
allowed: false,
|
|
557
|
+
reason: result.stderrMessage ?? "Blocked by guards rule"
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
return { allowed: true };
|
|
561
|
+
} catch {
|
|
562
|
+
return { allowed: true };
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
async function getGovernanceConfig(projectDir) {
|
|
566
|
+
try {
|
|
567
|
+
const { loadRulesSync } = await import("./dist-GTEJUBBT.js");
|
|
568
|
+
const { projectGuardsFile } = await import("./dist-NUXMDXZ3.js");
|
|
569
|
+
const rulesPath = projectGuardsFile(projectDir);
|
|
570
|
+
const rules = loadRulesSync(rulesPath);
|
|
571
|
+
if (!rules) {
|
|
572
|
+
return {
|
|
573
|
+
available: false,
|
|
574
|
+
autoCommitAllowed: true,
|
|
575
|
+
permissionDefaults: "allow",
|
|
576
|
+
dangerousCommandBlocking: true
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
const hasDenyOnGit = Object.values(rules.permissions).some(
|
|
580
|
+
(r) => r.enabled && r.decision === "deny" && r.command_pattern && /git\s+(commit|push)/.test(r.command_pattern)
|
|
581
|
+
);
|
|
582
|
+
const denyCount = Object.values(rules.permissions).filter(
|
|
583
|
+
(r) => r.enabled && r.decision === "deny"
|
|
584
|
+
).length;
|
|
585
|
+
const allowCount = Object.values(rules.permissions).filter(
|
|
586
|
+
(r) => r.enabled && r.decision === "allow"
|
|
587
|
+
).length;
|
|
588
|
+
let permissionDefaults = "allow";
|
|
589
|
+
if (denyCount > allowCount) permissionDefaults = "deny";
|
|
590
|
+
return {
|
|
591
|
+
available: true,
|
|
592
|
+
autoCommitAllowed: !hasDenyOnGit,
|
|
593
|
+
permissionDefaults,
|
|
594
|
+
dangerousCommandBlocking: true
|
|
595
|
+
};
|
|
596
|
+
} catch {
|
|
597
|
+
return {
|
|
598
|
+
available: false,
|
|
599
|
+
autoCommitAllowed: true,
|
|
600
|
+
permissionDefaults: "allow",
|
|
601
|
+
dangerousCommandBlocking: true
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
var PRIMARY_RECOVERY_TEST_TIMEOUT_MS = 5e3;
|
|
606
|
+
var PRIMARY_RECOVERY_TEST_PROMPT = 'Reply with just the word "ok".';
|
|
607
|
+
var ExecutionEngine = class {
|
|
608
|
+
config;
|
|
609
|
+
agent = null;
|
|
610
|
+
tracker = null;
|
|
611
|
+
listeners = [];
|
|
612
|
+
state;
|
|
613
|
+
currentExecution = null;
|
|
614
|
+
shouldStop = false;
|
|
615
|
+
/** Retry attempts per task (for error handling). */
|
|
616
|
+
retryCountMap = /* @__PURE__ */ new Map();
|
|
617
|
+
/** Rate-limit retry attempts per task (separate from generic retries). */
|
|
618
|
+
rateLimitRetryMap = /* @__PURE__ */ new Map();
|
|
619
|
+
/** Tasks that were skipped (to avoid retrying). */
|
|
620
|
+
skippedTasks = /* @__PURE__ */ new Set();
|
|
621
|
+
/** Agents currently rate-limited for the active task batch. */
|
|
622
|
+
rateLimitedAgents = /* @__PURE__ */ new Set();
|
|
623
|
+
/** Primary agent instance — preserved for recovery attempts. */
|
|
624
|
+
primaryAgentInstance = null;
|
|
625
|
+
/** Forced task for worker mode — engine only works on this single task. */
|
|
626
|
+
forcedTask = null;
|
|
627
|
+
/** Whether the forced task has been processed. */
|
|
628
|
+
forcedTaskProcessed = false;
|
|
629
|
+
/** Output buffers for current iteration. */
|
|
630
|
+
stdoutBuffer = new OutputBuffer();
|
|
631
|
+
stderrBuffer = new OutputBuffer();
|
|
632
|
+
constructor(config) {
|
|
633
|
+
this.config = {
|
|
634
|
+
...DEFAULT_ENGINE_CONFIG,
|
|
635
|
+
...config
|
|
636
|
+
};
|
|
637
|
+
this.state = this.createInitialState();
|
|
638
|
+
}
|
|
639
|
+
// ─── Public API ───
|
|
640
|
+
/**
|
|
641
|
+
* Initialize the engine: resolve agent and tracker plugins, sync tracker.
|
|
642
|
+
*/
|
|
643
|
+
async initialize(options) {
|
|
644
|
+
const agentRegistry = getAgentRegistry();
|
|
645
|
+
await agentRegistry.initialize();
|
|
646
|
+
this.agent = await agentRegistry.getInstance({
|
|
647
|
+
name: this.config.agentName,
|
|
648
|
+
plugin: this.config.agentName,
|
|
649
|
+
options: this.config.agentConfig
|
|
650
|
+
});
|
|
651
|
+
const detectResult = await this.agent.detect();
|
|
652
|
+
if (!detectResult.available) {
|
|
653
|
+
throw new Error(
|
|
654
|
+
`Agent '${this.config.agentName}' not available: ${detectResult.error}`
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
this.primaryAgentInstance = this.agent;
|
|
658
|
+
this.state.activeAgent = this.config.agentName;
|
|
659
|
+
this.state.rateLimitState.currentAgent = this.config.agentName;
|
|
660
|
+
if (options?.tracker && options?.forcedTask) {
|
|
661
|
+
this.tracker = options.tracker;
|
|
662
|
+
this.forcedTask = options.forcedTask;
|
|
663
|
+
this.state.totalTasks = 1;
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
const trackerRegistry = getTrackerRegistry();
|
|
667
|
+
await trackerRegistry.initialize();
|
|
668
|
+
this.tracker = await trackerRegistry.getInstance({
|
|
669
|
+
name: this.config.trackerName,
|
|
670
|
+
plugin: this.config.trackerName,
|
|
671
|
+
options: this.config.trackerConfig
|
|
672
|
+
});
|
|
673
|
+
await this.tracker.sync();
|
|
674
|
+
const tasks = await this.tracker.getTasks({
|
|
675
|
+
status: ["open", "in_progress"]
|
|
676
|
+
});
|
|
677
|
+
this.state.totalTasks = tasks.length;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Subscribe to engine events.
|
|
681
|
+
* Returns an unsubscribe function.
|
|
682
|
+
*/
|
|
683
|
+
on(listener) {
|
|
684
|
+
this.listeners.push(listener);
|
|
685
|
+
return () => {
|
|
686
|
+
const idx = this.listeners.indexOf(listener);
|
|
687
|
+
if (idx !== -1) this.listeners.splice(idx, 1);
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Get a read-only snapshot of the current engine state.
|
|
692
|
+
*/
|
|
693
|
+
getState() {
|
|
694
|
+
return {
|
|
695
|
+
...this.state,
|
|
696
|
+
elapsedMs: this.state.startedAt ? Date.now() - new Date(this.state.startedAt).getTime() : 0
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Get the current engine status.
|
|
701
|
+
*/
|
|
702
|
+
getStatus() {
|
|
703
|
+
return this.state.status;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Get the tracker instance for external operations.
|
|
707
|
+
*/
|
|
708
|
+
getTracker() {
|
|
709
|
+
return this.tracker;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Start the execution loop.
|
|
713
|
+
*/
|
|
714
|
+
async start() {
|
|
715
|
+
if (this.state.status !== "idle") {
|
|
716
|
+
throw new Error(`Cannot start engine in '${this.state.status}' state`);
|
|
717
|
+
}
|
|
718
|
+
if (!this.agent || !this.tracker) {
|
|
719
|
+
throw new Error("Engine not initialized. Call initialize() first.");
|
|
720
|
+
}
|
|
721
|
+
this.state.status = "running";
|
|
722
|
+
this.state.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
723
|
+
this.shouldStop = false;
|
|
724
|
+
const allTasks = await this.tracker.getTasks({
|
|
725
|
+
status: ["open", "in_progress", "done"]
|
|
726
|
+
});
|
|
727
|
+
this.emit({
|
|
728
|
+
type: "engine:started",
|
|
729
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
730
|
+
totalTasks: this.state.totalTasks,
|
|
731
|
+
tasks: allTasks
|
|
732
|
+
});
|
|
733
|
+
try {
|
|
734
|
+
await this.runLoop();
|
|
735
|
+
} finally {
|
|
736
|
+
const s = this.state.status;
|
|
737
|
+
if (s !== "stopped" && s !== "error") {
|
|
738
|
+
this.state.status = "idle";
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Request the engine to stop after the current iteration.
|
|
744
|
+
*/
|
|
745
|
+
async stop() {
|
|
746
|
+
this.shouldStop = true;
|
|
747
|
+
this.state.status = "stopping";
|
|
748
|
+
if (this.currentExecution) {
|
|
749
|
+
this.currentExecution.interrupt();
|
|
750
|
+
}
|
|
751
|
+
this.emit({
|
|
752
|
+
type: "engine:stopped",
|
|
753
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
754
|
+
reason: "interrupted",
|
|
755
|
+
totalIterations: this.state.currentIteration,
|
|
756
|
+
tasksCompleted: this.state.completedTasks.length
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Request to pause the execution loop after the current iteration completes.
|
|
761
|
+
*/
|
|
762
|
+
pause() {
|
|
763
|
+
if (this.state.status !== "running") return;
|
|
764
|
+
this.state.status = "pausing";
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Resume the execution loop from a paused state.
|
|
768
|
+
*/
|
|
769
|
+
resume() {
|
|
770
|
+
if (this.state.status === "pausing") {
|
|
771
|
+
this.state.status = "running";
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
if (this.state.status !== "paused") return;
|
|
775
|
+
this.state.status = "running";
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Check if the engine is paused.
|
|
779
|
+
*/
|
|
780
|
+
isPaused() {
|
|
781
|
+
return this.state.status === "paused";
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Reset tasks that were marked in_progress back to open.
|
|
785
|
+
* Used during graceful shutdown.
|
|
786
|
+
*/
|
|
787
|
+
async resetTasksToOpen(taskIds) {
|
|
788
|
+
if (!this.tracker || taskIds.length === 0) return 0;
|
|
789
|
+
let resetCount = 0;
|
|
790
|
+
for (const taskId of taskIds) {
|
|
791
|
+
try {
|
|
792
|
+
await this.tracker.updateTaskStatus(taskId, "open");
|
|
793
|
+
resetCount++;
|
|
794
|
+
} catch {
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
return resetCount;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Dispose of engine resources.
|
|
801
|
+
*/
|
|
802
|
+
async dispose() {
|
|
803
|
+
await this.stop();
|
|
804
|
+
this.listeners = [];
|
|
805
|
+
}
|
|
806
|
+
// ─── Main Loop ───
|
|
807
|
+
async runLoop() {
|
|
808
|
+
while (!this.shouldStop) {
|
|
809
|
+
if (this.state.status === "pausing") {
|
|
810
|
+
this.state.status = "paused";
|
|
811
|
+
this.emit({
|
|
812
|
+
type: "engine:paused",
|
|
813
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
814
|
+
currentIteration: this.state.currentIteration
|
|
815
|
+
});
|
|
816
|
+
while (this.state.status === "paused" && !this.shouldStop) {
|
|
817
|
+
await this.delay(100);
|
|
818
|
+
}
|
|
819
|
+
if (this.shouldStop) break;
|
|
820
|
+
this.emit({
|
|
821
|
+
type: "engine:resumed",
|
|
822
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
823
|
+
fromIteration: this.state.currentIteration
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
if (this.shouldRecoverPrimaryAgent()) {
|
|
827
|
+
await this.attemptPrimaryAgentRecovery();
|
|
828
|
+
}
|
|
829
|
+
if (this.config.maxIterations > 0 && this.state.currentIteration >= this.config.maxIterations) {
|
|
830
|
+
this.emit({
|
|
831
|
+
type: "engine:stopped",
|
|
832
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
833
|
+
reason: "max_iterations",
|
|
834
|
+
totalIterations: this.state.currentIteration,
|
|
835
|
+
tasksCompleted: this.state.completedTasks.length
|
|
836
|
+
});
|
|
837
|
+
break;
|
|
838
|
+
}
|
|
839
|
+
const isComplete = this.forcedTask ? this.state.completedTasks.length >= 1 : await this.tracker.isComplete();
|
|
840
|
+
if (isComplete) {
|
|
841
|
+
this.emit({
|
|
842
|
+
type: "all:complete",
|
|
843
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
844
|
+
totalCompleted: this.state.completedTasks.length,
|
|
845
|
+
totalIterations: this.state.currentIteration
|
|
846
|
+
});
|
|
847
|
+
this.emit({
|
|
848
|
+
type: "engine:stopped",
|
|
849
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
850
|
+
reason: "completed",
|
|
851
|
+
totalIterations: this.state.currentIteration,
|
|
852
|
+
tasksCompleted: this.state.completedTasks.length
|
|
853
|
+
});
|
|
854
|
+
break;
|
|
855
|
+
}
|
|
856
|
+
const task = await this.getNextAvailableTask();
|
|
857
|
+
if (!task) {
|
|
858
|
+
this.emit({
|
|
859
|
+
type: "engine:stopped",
|
|
860
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
861
|
+
reason: "no_tasks",
|
|
862
|
+
totalIterations: this.state.currentIteration,
|
|
863
|
+
tasksCompleted: this.state.completedTasks.length
|
|
864
|
+
});
|
|
865
|
+
break;
|
|
866
|
+
}
|
|
867
|
+
const result = await this.runIterationWithErrorHandling(task);
|
|
868
|
+
if (result.status === "failed" && this.config.errorStrategy === "abort") {
|
|
869
|
+
this.emit({
|
|
870
|
+
type: "engine:stopped",
|
|
871
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
872
|
+
reason: "error",
|
|
873
|
+
totalIterations: this.state.currentIteration,
|
|
874
|
+
tasksCompleted: this.state.completedTasks.length
|
|
875
|
+
});
|
|
876
|
+
break;
|
|
877
|
+
}
|
|
878
|
+
if (this.config.iterationDelayMs > 0 && !this.shouldStop) {
|
|
879
|
+
await this.delay(this.config.iterationDelayMs);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
// ─── Task Selection ───
|
|
884
|
+
async getNextAvailableTask() {
|
|
885
|
+
if (this.forcedTask) {
|
|
886
|
+
if (this.state.completedTasks.length >= 1 || this.forcedTaskProcessed) {
|
|
887
|
+
return null;
|
|
888
|
+
}
|
|
889
|
+
return this.forcedTask;
|
|
890
|
+
}
|
|
891
|
+
const task = await this.tracker.getNextTask({
|
|
892
|
+
status: ["open", "in_progress"],
|
|
893
|
+
excludeIds: this.skippedTasks.size > 0 ? Array.from(this.skippedTasks) : void 0
|
|
894
|
+
});
|
|
895
|
+
return task ?? null;
|
|
896
|
+
}
|
|
897
|
+
// ─── Iteration with Error Handling ───
|
|
898
|
+
async runIterationWithErrorHandling(task) {
|
|
899
|
+
let result = await this.runIteration(task);
|
|
900
|
+
this.state.iterations.push(result);
|
|
901
|
+
if (result.status !== "failed") {
|
|
902
|
+
if (result.taskCompleted) {
|
|
903
|
+
this.state.completedTasks.push(task.id);
|
|
904
|
+
this.retryCountMap.delete(task.id);
|
|
905
|
+
}
|
|
906
|
+
return result;
|
|
907
|
+
}
|
|
908
|
+
const errorMessage = result.error ?? "Unknown error";
|
|
909
|
+
switch (this.config.errorStrategy) {
|
|
910
|
+
case "retry": {
|
|
911
|
+
const currentRetries = this.retryCountMap.get(task.id) ?? 0;
|
|
912
|
+
if (currentRetries < this.config.maxRetries) {
|
|
913
|
+
this.emit({
|
|
914
|
+
type: "iteration:failed",
|
|
915
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
916
|
+
iteration: this.state.currentIteration,
|
|
917
|
+
error: errorMessage,
|
|
918
|
+
task,
|
|
919
|
+
action: "retry"
|
|
920
|
+
});
|
|
921
|
+
this.emit({
|
|
922
|
+
type: "iteration:retrying",
|
|
923
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
924
|
+
iteration: this.state.currentIteration,
|
|
925
|
+
retryAttempt: currentRetries + 1,
|
|
926
|
+
maxRetries: this.config.maxRetries,
|
|
927
|
+
task,
|
|
928
|
+
previousError: errorMessage,
|
|
929
|
+
delayMs: this.config.retryDelayMs
|
|
930
|
+
});
|
|
931
|
+
this.retryCountMap.set(task.id, currentRetries + 1);
|
|
932
|
+
if (this.config.retryDelayMs > 0 && !this.shouldStop) {
|
|
933
|
+
await this.delay(this.config.retryDelayMs);
|
|
934
|
+
}
|
|
935
|
+
if (!this.shouldStop) {
|
|
936
|
+
return this.runIterationWithErrorHandling(task);
|
|
937
|
+
}
|
|
938
|
+
} else {
|
|
939
|
+
const skipReason = `Max retries (${this.config.maxRetries}) exceeded: ${errorMessage}`;
|
|
940
|
+
this.emit({
|
|
941
|
+
type: "iteration:failed",
|
|
942
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
943
|
+
iteration: this.state.currentIteration,
|
|
944
|
+
error: skipReason,
|
|
945
|
+
task,
|
|
946
|
+
action: "skip"
|
|
947
|
+
});
|
|
948
|
+
this.emitSkipEvent(task, skipReason);
|
|
949
|
+
this.skippedTasks.add(task.id);
|
|
950
|
+
this.state.skippedTasks.push(task.id);
|
|
951
|
+
this.retryCountMap.delete(task.id);
|
|
952
|
+
if (this.forcedTask?.id === task.id) {
|
|
953
|
+
this.forcedTaskProcessed = true;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
break;
|
|
957
|
+
}
|
|
958
|
+
case "skip": {
|
|
959
|
+
this.emit({
|
|
960
|
+
type: "iteration:failed",
|
|
961
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
962
|
+
iteration: this.state.currentIteration,
|
|
963
|
+
error: errorMessage,
|
|
964
|
+
task,
|
|
965
|
+
action: "skip"
|
|
966
|
+
});
|
|
967
|
+
this.emitSkipEvent(task, errorMessage);
|
|
968
|
+
this.skippedTasks.add(task.id);
|
|
969
|
+
this.state.skippedTasks.push(task.id);
|
|
970
|
+
if (this.forcedTask?.id === task.id) {
|
|
971
|
+
this.forcedTaskProcessed = true;
|
|
972
|
+
}
|
|
973
|
+
break;
|
|
974
|
+
}
|
|
975
|
+
case "abort": {
|
|
976
|
+
this.emit({
|
|
977
|
+
type: "iteration:failed",
|
|
978
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
979
|
+
iteration: this.state.currentIteration,
|
|
980
|
+
error: errorMessage,
|
|
981
|
+
task,
|
|
982
|
+
action: "abort"
|
|
983
|
+
});
|
|
984
|
+
this.state.failedTasks.push(task.id);
|
|
985
|
+
if (this.forcedTask?.id === task.id) {
|
|
986
|
+
this.forcedTaskProcessed = true;
|
|
987
|
+
}
|
|
988
|
+
break;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return result;
|
|
992
|
+
}
|
|
993
|
+
// ─── Single Iteration ───
|
|
994
|
+
async runIteration(task) {
|
|
995
|
+
this.state.currentIteration++;
|
|
996
|
+
this.state.currentTaskId = task.id;
|
|
997
|
+
this.stdoutBuffer.clear();
|
|
998
|
+
this.stderrBuffer.clear();
|
|
999
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
1000
|
+
const iteration = this.state.currentIteration;
|
|
1001
|
+
this.emit({
|
|
1002
|
+
type: "iteration:started",
|
|
1003
|
+
timestamp: startedAt.toISOString(),
|
|
1004
|
+
iteration,
|
|
1005
|
+
task
|
|
1006
|
+
});
|
|
1007
|
+
this.emit({
|
|
1008
|
+
type: "task:selected",
|
|
1009
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1010
|
+
task,
|
|
1011
|
+
iteration
|
|
1012
|
+
});
|
|
1013
|
+
try {
|
|
1014
|
+
const governanceResult = await evaluateBeforeExecution(
|
|
1015
|
+
task.id,
|
|
1016
|
+
this.config.agentName,
|
|
1017
|
+
this.config.workingDir
|
|
1018
|
+
);
|
|
1019
|
+
if (!governanceResult.allowed) {
|
|
1020
|
+
this.emit({
|
|
1021
|
+
type: "iteration:failed",
|
|
1022
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1023
|
+
iteration,
|
|
1024
|
+
error: governanceResult.reason ?? "Blocked by governance rules",
|
|
1025
|
+
task,
|
|
1026
|
+
action: "skip"
|
|
1027
|
+
});
|
|
1028
|
+
return {
|
|
1029
|
+
iteration,
|
|
1030
|
+
status: "skipped",
|
|
1031
|
+
task,
|
|
1032
|
+
taskCompleted: false,
|
|
1033
|
+
completionSignalDetected: false,
|
|
1034
|
+
durationMs: (/* @__PURE__ */ new Date()).getTime() - startedAt.getTime(),
|
|
1035
|
+
error: `Governance blocked: ${governanceResult.reason ?? "unknown"}`,
|
|
1036
|
+
startedAt: startedAt.toISOString(),
|
|
1037
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
} catch {
|
|
1041
|
+
}
|
|
1042
|
+
await this.tracker.updateTaskStatus(task.id, "in_progress");
|
|
1043
|
+
let additionalContext;
|
|
1044
|
+
try {
|
|
1045
|
+
const taskDesc = task.description ?? task.title;
|
|
1046
|
+
additionalContext = await enrichPromptContext(
|
|
1047
|
+
taskDesc,
|
|
1048
|
+
this.config.sourceProjectDir ?? this.config.workingDir
|
|
1049
|
+
);
|
|
1050
|
+
} catch {
|
|
1051
|
+
}
|
|
1052
|
+
const prompt = buildPrompt({
|
|
1053
|
+
task,
|
|
1054
|
+
workingDir: this.config.workingDir,
|
|
1055
|
+
templateName: this.config.promptTemplate,
|
|
1056
|
+
additionalContext: additionalContext || void 0
|
|
1057
|
+
});
|
|
1058
|
+
try {
|
|
1059
|
+
const handle = this.agent.execute(prompt, {
|
|
1060
|
+
cwd: this.config.workingDir,
|
|
1061
|
+
timeout: this.config.timeout > 0 ? this.config.timeout : void 0,
|
|
1062
|
+
env: { ULPI_RUN_MODE: this.config.workerMode ? "parallel" : "loop" },
|
|
1063
|
+
onStdout: (data) => {
|
|
1064
|
+
this.stdoutBuffer.append(data);
|
|
1065
|
+
this.emit({
|
|
1066
|
+
type: "agent:output",
|
|
1067
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1068
|
+
stream: "stdout",
|
|
1069
|
+
data,
|
|
1070
|
+
iteration
|
|
1071
|
+
});
|
|
1072
|
+
},
|
|
1073
|
+
onStderr: (data) => {
|
|
1074
|
+
this.stderrBuffer.append(data);
|
|
1075
|
+
this.emit({
|
|
1076
|
+
type: "agent:output",
|
|
1077
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1078
|
+
stream: "stderr",
|
|
1079
|
+
data,
|
|
1080
|
+
iteration
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
this.currentExecution = handle;
|
|
1085
|
+
const agentResult = await handle.promise;
|
|
1086
|
+
this.currentExecution = null;
|
|
1087
|
+
const rateLimitResult = detectRateLimit({
|
|
1088
|
+
stderr: agentResult.stderr,
|
|
1089
|
+
exitCode: agentResult.exitCode,
|
|
1090
|
+
agentId: this.config.agentName
|
|
1091
|
+
});
|
|
1092
|
+
if (rateLimitResult.isRateLimit) {
|
|
1093
|
+
return this.handleRateLimitInIteration(
|
|
1094
|
+
task,
|
|
1095
|
+
rateLimitResult,
|
|
1096
|
+
iteration,
|
|
1097
|
+
startedAt
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
this.rateLimitRetryMap.delete(task.id);
|
|
1101
|
+
const endedAt = /* @__PURE__ */ new Date();
|
|
1102
|
+
const durationMs = endedAt.getTime() - startedAt.getTime();
|
|
1103
|
+
const completionSignalDetected = isTaskComplete(agentResult.stdout);
|
|
1104
|
+
const taskCompleted = completionSignalDetected;
|
|
1105
|
+
if (taskCompleted) {
|
|
1106
|
+
await this.tracker.completeTask(task.id, {
|
|
1107
|
+
taskId: task.id,
|
|
1108
|
+
status: "done",
|
|
1109
|
+
summary: "Completed by agent"
|
|
1110
|
+
});
|
|
1111
|
+
this.emit({
|
|
1112
|
+
type: "task:completed",
|
|
1113
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1114
|
+
task,
|
|
1115
|
+
iteration
|
|
1116
|
+
});
|
|
1117
|
+
this.rateLimitedAgents.clear();
|
|
1118
|
+
}
|
|
1119
|
+
let commitSha;
|
|
1120
|
+
if (taskCompleted && this.config.autoCommit) {
|
|
1121
|
+
commitSha = await this.handleAutoCommit(task, iteration);
|
|
1122
|
+
}
|
|
1123
|
+
let status;
|
|
1124
|
+
if (agentResult.interrupted) {
|
|
1125
|
+
status = "interrupted";
|
|
1126
|
+
} else if (agentResult.status === "failed" || agentResult.status === "timeout") {
|
|
1127
|
+
status = "failed";
|
|
1128
|
+
} else {
|
|
1129
|
+
status = "completed";
|
|
1130
|
+
}
|
|
1131
|
+
const result = {
|
|
1132
|
+
iteration,
|
|
1133
|
+
status,
|
|
1134
|
+
task,
|
|
1135
|
+
taskCompleted,
|
|
1136
|
+
completionSignalDetected,
|
|
1137
|
+
durationMs,
|
|
1138
|
+
exitCode: agentResult.exitCode,
|
|
1139
|
+
startedAt: startedAt.toISOString(),
|
|
1140
|
+
endedAt: endedAt.toISOString(),
|
|
1141
|
+
commitSha
|
|
1142
|
+
};
|
|
1143
|
+
this.emit({
|
|
1144
|
+
type: "iteration:completed",
|
|
1145
|
+
timestamp: endedAt.toISOString(),
|
|
1146
|
+
result
|
|
1147
|
+
});
|
|
1148
|
+
return result;
|
|
1149
|
+
} catch (error) {
|
|
1150
|
+
const endedAt = /* @__PURE__ */ new Date();
|
|
1151
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1152
|
+
return {
|
|
1153
|
+
iteration,
|
|
1154
|
+
status: "failed",
|
|
1155
|
+
task,
|
|
1156
|
+
taskCompleted: false,
|
|
1157
|
+
completionSignalDetected: false,
|
|
1158
|
+
durationMs: endedAt.getTime() - startedAt.getTime(),
|
|
1159
|
+
error: errorMessage,
|
|
1160
|
+
startedAt: startedAt.toISOString(),
|
|
1161
|
+
endedAt: endedAt.toISOString()
|
|
1162
|
+
};
|
|
1163
|
+
} finally {
|
|
1164
|
+
this.state.currentTaskId = null;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
// ─── Rate Limit Handling ───
|
|
1168
|
+
async handleRateLimitInIteration(task, rateLimitResult, iteration, startedAt) {
|
|
1169
|
+
const currentRetries = this.rateLimitRetryMap.get(task.id) ?? 0;
|
|
1170
|
+
const maxRetries = this.config.rateLimit.maxRetries;
|
|
1171
|
+
if (currentRetries < maxRetries) {
|
|
1172
|
+
const { delayMs, usedRetryAfter } = calculateBackoff(
|
|
1173
|
+
currentRetries,
|
|
1174
|
+
this.config.rateLimit.baseBackoffMs,
|
|
1175
|
+
rateLimitResult.retryAfter
|
|
1176
|
+
);
|
|
1177
|
+
this.rateLimitRetryMap.set(task.id, currentRetries + 1);
|
|
1178
|
+
this.emit({
|
|
1179
|
+
type: "iteration:rate-limited",
|
|
1180
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1181
|
+
iteration,
|
|
1182
|
+
task,
|
|
1183
|
+
retryAttempt: currentRetries + 1,
|
|
1184
|
+
maxRetries,
|
|
1185
|
+
delayMs,
|
|
1186
|
+
rateLimitMessage: rateLimitResult.message,
|
|
1187
|
+
usedRetryAfter
|
|
1188
|
+
});
|
|
1189
|
+
if (!this.shouldStop) {
|
|
1190
|
+
await this.delay(delayMs);
|
|
1191
|
+
}
|
|
1192
|
+
if (!this.shouldStop) {
|
|
1193
|
+
this.state.currentIteration--;
|
|
1194
|
+
return this.runIteration(task);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
this.rateLimitRetryMap.delete(task.id);
|
|
1198
|
+
this.rateLimitedAgents.add(this.state.activeAgent);
|
|
1199
|
+
const fallbackAgents = this.config.fallbackAgents ?? [];
|
|
1200
|
+
const nextFallback = shouldFallback(this.rateLimitedAgents, fallbackAgents);
|
|
1201
|
+
if (nextFallback) {
|
|
1202
|
+
await this.switchToFallbackAgent(nextFallback);
|
|
1203
|
+
this.state.currentIteration--;
|
|
1204
|
+
return this.runIteration(task);
|
|
1205
|
+
}
|
|
1206
|
+
if (fallbackAgents.length > 0) {
|
|
1207
|
+
this.emit({
|
|
1208
|
+
type: "agent:all-limited",
|
|
1209
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1210
|
+
task,
|
|
1211
|
+
triedAgents: Array.from(this.rateLimitedAgents)
|
|
1212
|
+
});
|
|
1213
|
+
this.pause();
|
|
1214
|
+
this.rateLimitedAgents.clear();
|
|
1215
|
+
}
|
|
1216
|
+
const endedAt = /* @__PURE__ */ new Date();
|
|
1217
|
+
return {
|
|
1218
|
+
iteration,
|
|
1219
|
+
status: "failed",
|
|
1220
|
+
task,
|
|
1221
|
+
taskCompleted: false,
|
|
1222
|
+
completionSignalDetected: false,
|
|
1223
|
+
durationMs: endedAt.getTime() - startedAt.getTime(),
|
|
1224
|
+
error: `Rate limit exceeded after ${maxRetries} retries: ${rateLimitResult.message}`,
|
|
1225
|
+
startedAt: startedAt.toISOString(),
|
|
1226
|
+
endedAt: endedAt.toISOString()
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
// ─── Agent Switching ───
|
|
1230
|
+
async switchToFallbackAgent(agentPlugin) {
|
|
1231
|
+
const previousAgent = this.state.activeAgent;
|
|
1232
|
+
const agentRegistry = getAgentRegistry();
|
|
1233
|
+
const fallbackInstance = await agentRegistry.getInstance({
|
|
1234
|
+
name: agentPlugin,
|
|
1235
|
+
plugin: agentPlugin,
|
|
1236
|
+
options: this.config.agentConfig
|
|
1237
|
+
});
|
|
1238
|
+
const detectResult = await fallbackInstance.detect();
|
|
1239
|
+
if (!detectResult.available) {
|
|
1240
|
+
this.rateLimitedAgents.add(agentPlugin);
|
|
1241
|
+
throw new Error(`Fallback agent '${agentPlugin}' not available: ${detectResult.error}`);
|
|
1242
|
+
}
|
|
1243
|
+
this.agent = fallbackInstance;
|
|
1244
|
+
this.state.activeAgent = agentPlugin;
|
|
1245
|
+
this.state.rateLimitState.currentAgent = agentPlugin;
|
|
1246
|
+
this.state.rateLimitState.isRateLimited = true;
|
|
1247
|
+
this.state.rateLimitState.limitedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1248
|
+
this.emit({
|
|
1249
|
+
type: "agent:switched",
|
|
1250
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1251
|
+
previousAgent,
|
|
1252
|
+
newAgent: agentPlugin,
|
|
1253
|
+
reason: "fallback"
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
shouldRecoverPrimaryAgent() {
|
|
1257
|
+
if (this.state.activeAgent === this.config.agentName) return false;
|
|
1258
|
+
return this.config.rateLimit.recoverPrimaryBetweenIterations;
|
|
1259
|
+
}
|
|
1260
|
+
async attemptPrimaryAgentRecovery() {
|
|
1261
|
+
if (!this.primaryAgentInstance) return false;
|
|
1262
|
+
const primaryAgent = this.config.agentName;
|
|
1263
|
+
const fallbackAgent = this.state.activeAgent;
|
|
1264
|
+
const startTime = Date.now();
|
|
1265
|
+
try {
|
|
1266
|
+
const handle = this.primaryAgentInstance.execute(
|
|
1267
|
+
PRIMARY_RECOVERY_TEST_PROMPT,
|
|
1268
|
+
{ cwd: this.config.workingDir, timeout: PRIMARY_RECOVERY_TEST_TIMEOUT_MS }
|
|
1269
|
+
);
|
|
1270
|
+
const result = await handle.promise;
|
|
1271
|
+
const testDurationMs = Date.now() - startTime;
|
|
1272
|
+
const rateLimitResult = detectRateLimit({
|
|
1273
|
+
stderr: result.stderr,
|
|
1274
|
+
exitCode: result.exitCode,
|
|
1275
|
+
agentId: primaryAgent
|
|
1276
|
+
});
|
|
1277
|
+
const success = !rateLimitResult.isRateLimit && result.status === "completed";
|
|
1278
|
+
this.emit({
|
|
1279
|
+
type: "agent:recovery-attempted",
|
|
1280
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1281
|
+
primaryAgent,
|
|
1282
|
+
fallbackAgent,
|
|
1283
|
+
success,
|
|
1284
|
+
testDurationMs,
|
|
1285
|
+
rateLimitMessage: rateLimitResult.message
|
|
1286
|
+
});
|
|
1287
|
+
if (success) {
|
|
1288
|
+
this.agent = this.primaryAgentInstance;
|
|
1289
|
+
this.state.activeAgent = primaryAgent;
|
|
1290
|
+
this.state.rateLimitState.currentAgent = primaryAgent;
|
|
1291
|
+
this.state.rateLimitState.isRateLimited = false;
|
|
1292
|
+
this.state.rateLimitState.limitedAt = void 0;
|
|
1293
|
+
this.rateLimitedAgents.clear();
|
|
1294
|
+
this.emit({
|
|
1295
|
+
type: "agent:switched",
|
|
1296
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1297
|
+
previousAgent: fallbackAgent,
|
|
1298
|
+
newAgent: primaryAgent,
|
|
1299
|
+
reason: "primary"
|
|
1300
|
+
});
|
|
1301
|
+
return true;
|
|
1302
|
+
}
|
|
1303
|
+
return false;
|
|
1304
|
+
} catch {
|
|
1305
|
+
return false;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
// ─── Auto-Commit ───
|
|
1309
|
+
async handleAutoCommit(task, iteration) {
|
|
1310
|
+
try {
|
|
1311
|
+
const result = await autoCommitChanges({
|
|
1312
|
+
workingDir: this.config.workingDir,
|
|
1313
|
+
taskId: task.id,
|
|
1314
|
+
taskTitle: task.title
|
|
1315
|
+
});
|
|
1316
|
+
if (result.committed) {
|
|
1317
|
+
this.emit({
|
|
1318
|
+
type: "task:auto-committed",
|
|
1319
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1320
|
+
task,
|
|
1321
|
+
iteration,
|
|
1322
|
+
commitMessage: result.commitMessage,
|
|
1323
|
+
commitSha: result.commitSha
|
|
1324
|
+
});
|
|
1325
|
+
return result.commitSha;
|
|
1326
|
+
} else if (result.error) {
|
|
1327
|
+
this.emit({
|
|
1328
|
+
type: "task:auto-commit-failed",
|
|
1329
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1330
|
+
task,
|
|
1331
|
+
iteration,
|
|
1332
|
+
error: result.error
|
|
1333
|
+
});
|
|
1334
|
+
} else if (result.skipReason) {
|
|
1335
|
+
this.emit({
|
|
1336
|
+
type: "task:auto-commit-skipped",
|
|
1337
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1338
|
+
task,
|
|
1339
|
+
iteration,
|
|
1340
|
+
reason: result.skipReason
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
} catch (err) {
|
|
1344
|
+
this.emit({
|
|
1345
|
+
type: "task:auto-commit-failed",
|
|
1346
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1347
|
+
task,
|
|
1348
|
+
iteration,
|
|
1349
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
return void 0;
|
|
1353
|
+
}
|
|
1354
|
+
// ─── Helpers ───
|
|
1355
|
+
emit(event) {
|
|
1356
|
+
for (const listener of this.listeners) {
|
|
1357
|
+
try {
|
|
1358
|
+
listener(event);
|
|
1359
|
+
} catch {
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
emitSkipEvent(task, reason) {
|
|
1364
|
+
this.emit({
|
|
1365
|
+
type: "iteration:skipped",
|
|
1366
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1367
|
+
iteration: this.state.currentIteration,
|
|
1368
|
+
task,
|
|
1369
|
+
reason
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
delay(ms) {
|
|
1373
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1374
|
+
}
|
|
1375
|
+
createInitialState() {
|
|
1376
|
+
return {
|
|
1377
|
+
status: "idle",
|
|
1378
|
+
currentTaskId: null,
|
|
1379
|
+
currentIteration: 0,
|
|
1380
|
+
completedTasks: [],
|
|
1381
|
+
failedTasks: [],
|
|
1382
|
+
skippedTasks: [],
|
|
1383
|
+
totalTasks: 0,
|
|
1384
|
+
startedAt: null,
|
|
1385
|
+
elapsedMs: 0,
|
|
1386
|
+
activeAgent: this.config.agentName,
|
|
1387
|
+
rateLimitState: {
|
|
1388
|
+
isRateLimited: false,
|
|
1389
|
+
currentAgent: this.config.agentName,
|
|
1390
|
+
limitedAgents: /* @__PURE__ */ new Set(),
|
|
1391
|
+
backoffMs: 0,
|
|
1392
|
+
attempts: 0
|
|
1393
|
+
},
|
|
1394
|
+
iterations: []
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
};
|
|
1398
|
+
|
|
1399
|
+
export {
|
|
1400
|
+
DEFAULT_ENGINE_CONFIG,
|
|
1401
|
+
detectRateLimit,
|
|
1402
|
+
calculateBackoff,
|
|
1403
|
+
shouldFallback,
|
|
1404
|
+
autoCommitChanges,
|
|
1405
|
+
injectMemories,
|
|
1406
|
+
captureIterationMemory,
|
|
1407
|
+
getSemanticContext,
|
|
1408
|
+
getCodemapAvailability,
|
|
1409
|
+
COMPLETION_SIGNAL,
|
|
1410
|
+
COMPLETION_SIGNAL_PATTERN,
|
|
1411
|
+
buildPrompt,
|
|
1412
|
+
enrichPromptContext,
|
|
1413
|
+
detectCompletionSignal,
|
|
1414
|
+
isTaskComplete,
|
|
1415
|
+
evaluateBeforeExecution,
|
|
1416
|
+
getGovernanceConfig,
|
|
1417
|
+
ExecutionEngine
|
|
1418
|
+
};
|