@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.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/dist/{auth-PN7TMQHV-2W4ICG64.js → auth-FWM7MM4Q-VZC3U2XZ.js} +1 -1
  3. package/dist/{auth-BFFBUJUC.js → auth-HDK7ECJL.js} +2 -1
  4. package/dist/{chunk-RJIRWQJD.js → chunk-3BCW6ABU.js} +402 -142
  5. package/dist/{chunk-L3PWNHSA.js → chunk-3WB5CXH4.js} +180 -5
  6. package/dist/{chunk-K4OVPFY2.js → chunk-4UCJIAOU.js} +2 -2
  7. package/dist/chunk-4XTHZVDS.js +109 -0
  8. package/dist/chunk-4ZPOZULQ.js +6522 -0
  9. package/dist/{chunk-SIAQVRKG.js → chunk-5MI5GIXM.js} +48 -2
  10. package/dist/{chunk-KLEASXUR.js → chunk-6ZL6NXMV.js} +1 -1
  11. package/dist/{chunk-AV5RB3N2.js → chunk-76D3BYJD.js} +48 -0
  12. package/dist/{chunk-DOIKS6C5.js → chunk-AWOSRA5F.js} +1 -1
  13. package/dist/{chunk-UCMT5OKP.js → chunk-BFEKZZHM.js} +274 -57
  14. package/dist/chunk-C7CLUQI6.js +1286 -0
  15. package/dist/{chunk-ELTGWMDE.js → chunk-E3B5NROU.js} +7 -7
  16. package/dist/chunk-EJ7TW77N.js +1418 -0
  17. package/dist/{chunk-6OURRFP7.js → chunk-IV6MWETF.js} +383 -168
  18. package/dist/chunk-IZPJHSPX.js +1478 -0
  19. package/dist/chunk-JLHNLM3C.js +228 -0
  20. package/dist/{chunk-P2RESJRN.js → chunk-KYYI23AQ.js} +2 -2
  21. package/dist/chunk-S6ANCSYO.js +1271 -0
  22. package/dist/chunk-SEU7WWNQ.js +1251 -0
  23. package/dist/chunk-SNQ7NAIS.js +453 -0
  24. package/dist/{ulpi-RMMCUAGP-EWYUE7RU.js → chunk-TSLDGT5O.js} +73 -35
  25. package/dist/{chunk-EIWYSP3A.js → chunk-UXHCHOWQ.js} +83 -62
  26. package/dist/chunk-V2H5D6Y3.js +146 -0
  27. package/dist/{chunk-5SCG7UYM.js → chunk-VVEDXI7E.js} +1 -1
  28. package/dist/chunk-VXH5Y4FO.js +6761 -0
  29. package/dist/chunk-WED4LM5N.js +322 -0
  30. package/dist/{chunk-74WVVWJ4.js → chunk-YOKL7RB5.js} +184 -15
  31. package/dist/chunk-Z53CAR7G.js +298 -0
  32. package/dist/{ci-JQ56YIKC.js → ci-X3U2W4HC.js} +124 -26
  33. package/dist/cloud-2F3NLVHN.js +274 -0
  34. package/dist/{codemap-HMYBXJL2.js → codemap-XNGMAF3F.js} +37 -37
  35. package/dist/codex-MB5YTMRT.js +132 -0
  36. package/dist/{config-YYWEN7U2.js → config-OOELBYTH.js} +1 -1
  37. package/dist/dist-2BJYR5EI.js +59 -0
  38. package/dist/dist-3EIQTZHT.js +1380 -0
  39. package/dist/{dist-WAMAQVPK.js → dist-4U5L2X2C.js} +2 -2
  40. package/dist/{dist-4XTJ6HLM.js → dist-54KAMNLO.js} +16 -15
  41. package/dist/dist-6M4MZWZW.js +58 -0
  42. package/dist/dist-6X576SU2.js +27 -0
  43. package/dist/dist-7QOEYLFX.js +103 -0
  44. package/dist/dist-AYBGHEDY.js +2541 -0
  45. package/dist/dist-EK45QNEM.js +45 -0
  46. package/dist/{dist-U7ZIJMZD.js → dist-FKFEJRPX.js} +16 -15
  47. package/dist/dist-GTEJUBBT.js +66 -0
  48. package/dist/dist-HA74OKJZ.js +40 -0
  49. package/dist/{dist-XG2GG5SD.js → dist-HU5RZAON.js} +14 -2
  50. package/dist/dist-IYE3OBRB.js +374 -0
  51. package/dist/{dist-7WLLPWWB.js → dist-JLU26AB6.js} +12 -9
  52. package/dist/{dist-6G7JC2RA.js → dist-KUCI6JFE.js} +49 -9
  53. package/dist/dist-NUEMFZFL.js +33 -0
  54. package/dist/{dist-GWGTAHNM.js → dist-NUXMDXZ3.js} +31 -3
  55. package/dist/{dist-5R4RYNQO.js → dist-YCNWHSLN.js} +15 -5
  56. package/dist/{dist-6MFVWIFF.js → dist-YFFG2ZD6.js} +9 -16
  57. package/dist/dist-ZG4OKCSR.js +15 -0
  58. package/dist/doctor-SI4LLLDZ.js +345 -0
  59. package/dist/{export-import-4A5MWLIA.js → export-import-JFQH4KSJ.js} +1 -1
  60. package/dist/{history-RNUWO4JZ.js → history-5NE46ZAH.js} +7 -7
  61. package/dist/{hooks-installer-K2JXEBNN.js → hooks-installer-UN5JZLDQ.js} +2 -2
  62. package/dist/index.js +394 -618
  63. package/dist/{init-NQWFZPKO.js → init-5FK3VKRT.js} +76 -10
  64. package/dist/job-HIDMAFW2.js +376 -0
  65. package/dist/jobs.memory-PLMMSFHB-VBECCTHN.js +33 -0
  66. package/dist/kiro-VMUHDFGK.js +153 -0
  67. package/dist/{launchd-OYXUAVW6.js → launchd-6AWT54HR.js} +9 -17
  68. package/dist/mcp-PDUD7SGP.js +249 -0
  69. package/dist/mcp-installer-PQU3XOGO.js +259 -0
  70. package/dist/mcp-setup-OA7IB3H3.js +263 -0
  71. package/dist/{memory-D6ZFFCI2.js → memory-ZNAEAK3B.js} +17 -17
  72. package/dist/{ollama-3XCUZMZT-FYKHW4TZ.js → ollama-3XCUZMZT-4JMH6B7P.js} +1 -1
  73. package/dist/{openai-E7G2YAHU-IG33BFYF.js → openai-E7G2YAHU-T3HMBPH7.js} +2 -2
  74. package/dist/portal-JYWVHXDU.js +210 -0
  75. package/dist/prd-Q4J5NVAR.js +408 -0
  76. package/dist/repos-WWZXNN3P.js +271 -0
  77. package/dist/review-integration-5WHEJU2A.js +14 -0
  78. package/dist/{rules-3OFGWHP4.js → rules-Y4VSOY5Y.js} +3 -3
  79. package/dist/run-VPNXEIBY.js +687 -0
  80. package/dist/server-COL4AXKU-P7S7NNF6.js +11 -0
  81. package/dist/server-KKSETHDV-XSSLEENT.js +20 -0
  82. package/dist/{skills-GY2CTPWN.js → skills-QEYU2N27.js} +4 -2
  83. package/dist/start-JYOEL7AJ.js +303 -0
  84. package/dist/{status-SE43TIFJ.js → status-BHQYYGAL.js} +2 -2
  85. package/dist/{templates-O2XDKB5R.js → templates-CBRUJ66V.js} +6 -5
  86. package/dist/tui-DP7736EX.js +61 -0
  87. package/dist/ulpi-5EN6JCAS-LFE3WSL4.js +10 -0
  88. package/dist/{uninstall-KWGSGZTI.js → uninstall-ICUV6DDV.js} +3 -3
  89. package/dist/{update-QYZA4D23.js → update-7ZMAYRBH.js} +3 -3
  90. package/dist/{version-checker-MVB74DEX.js → version-checker-4ZFMZA7Y.js} +2 -2
  91. package/package.json +39 -31
  92. package/dist/chunk-26LLDX2T.js +0 -553
  93. package/dist/chunk-DDRLI6JU.js +0 -331
  94. package/dist/chunk-IFATANHR.js +0 -453
  95. package/dist/chunk-JWUUVXIV.js +0 -13694
  96. package/dist/chunk-LD52XG3X.js +0 -4273
  97. package/dist/chunk-MIAQVCFW.js +0 -39
  98. package/dist/chunk-YYZOFYS6.js +0 -415
  99. package/dist/dist-XD4YI27T.js +0 -26
  100. package/dist/mcp-installer-TOYDP77X.js +0 -124
  101. package/dist/projects-COUJP4ZC.js +0 -271
  102. package/dist/review-KMGP2S25.js +0 -152
  103. package/dist/server-USLHY6GH-F4JSXCWA.js +0 -18
  104. package/dist/server-X5P6WH2M-ULZF5WHZ.js +0 -11
  105. package/dist/skills/ulpi-generate-guardian/SKILL.md +0 -750
  106. package/dist/skills/ulpi-generate-guardian/references/framework-rules.md +0 -849
  107. package/dist/skills/ulpi-generate-guardian/references/language-rules.md +0 -591
  108. package/dist/ui-4SM2SUI6.js +0 -167
  109. 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
+ };