@virtengine/openfleet 0.25.0

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 (120) hide show
  1. package/.env.example +914 -0
  2. package/LICENSE +190 -0
  3. package/README.md +500 -0
  4. package/agent-endpoint.mjs +918 -0
  5. package/agent-hook-bridge.mjs +230 -0
  6. package/agent-hooks.mjs +1188 -0
  7. package/agent-pool.mjs +2403 -0
  8. package/agent-prompts.mjs +689 -0
  9. package/agent-sdk.mjs +141 -0
  10. package/anomaly-detector.mjs +1195 -0
  11. package/autofix.mjs +1294 -0
  12. package/claude-shell.mjs +708 -0
  13. package/cli.mjs +906 -0
  14. package/codex-config.mjs +1274 -0
  15. package/codex-model-profiles.mjs +135 -0
  16. package/codex-shell.mjs +762 -0
  17. package/config-doctor.mjs +613 -0
  18. package/config.mjs +1720 -0
  19. package/conflict-resolver.mjs +248 -0
  20. package/container-runner.mjs +450 -0
  21. package/copilot-shell.mjs +827 -0
  22. package/daemon-restart-policy.mjs +56 -0
  23. package/diff-stats.mjs +282 -0
  24. package/error-detector.mjs +829 -0
  25. package/fetch-runtime.mjs +34 -0
  26. package/fleet-coordinator.mjs +838 -0
  27. package/get-telegram-chat-id.mjs +71 -0
  28. package/git-safety.mjs +170 -0
  29. package/github-reconciler.mjs +403 -0
  30. package/hook-profiles.mjs +651 -0
  31. package/kanban-adapter.mjs +4491 -0
  32. package/lib/logger.mjs +645 -0
  33. package/maintenance.mjs +828 -0
  34. package/merge-strategy.mjs +1171 -0
  35. package/monitor.mjs +12207 -0
  36. package/openfleet.config.example.json +115 -0
  37. package/openfleet.schema.json +465 -0
  38. package/package.json +203 -0
  39. package/postinstall.mjs +187 -0
  40. package/pr-cleanup-daemon.mjs +978 -0
  41. package/preflight.mjs +408 -0
  42. package/prepublish-check.mjs +90 -0
  43. package/presence.mjs +328 -0
  44. package/primary-agent.mjs +282 -0
  45. package/publish.mjs +151 -0
  46. package/repo-root.mjs +29 -0
  47. package/restart-controller.mjs +100 -0
  48. package/review-agent.mjs +557 -0
  49. package/rotate-agent-logs.sh +133 -0
  50. package/sdk-conflict-resolver.mjs +973 -0
  51. package/session-tracker.mjs +880 -0
  52. package/setup.mjs +3937 -0
  53. package/shared-knowledge.mjs +410 -0
  54. package/shared-state-manager.mjs +841 -0
  55. package/shared-workspace-cli.mjs +199 -0
  56. package/shared-workspace-registry.mjs +537 -0
  57. package/shared-workspaces.json +18 -0
  58. package/startup-service.mjs +1070 -0
  59. package/sync-engine.mjs +1063 -0
  60. package/task-archiver.mjs +801 -0
  61. package/task-assessment.mjs +550 -0
  62. package/task-claims.mjs +924 -0
  63. package/task-complexity.mjs +581 -0
  64. package/task-executor.mjs +5111 -0
  65. package/task-store.mjs +753 -0
  66. package/telegram-bot.mjs +9281 -0
  67. package/telegram-sentinel.mjs +2010 -0
  68. package/ui/app.js +867 -0
  69. package/ui/app.legacy.js +1464 -0
  70. package/ui/app.monolith.js +2488 -0
  71. package/ui/components/charts.js +226 -0
  72. package/ui/components/chat-view.js +567 -0
  73. package/ui/components/command-palette.js +587 -0
  74. package/ui/components/diff-viewer.js +190 -0
  75. package/ui/components/forms.js +327 -0
  76. package/ui/components/kanban-board.js +451 -0
  77. package/ui/components/session-list.js +305 -0
  78. package/ui/components/shared.js +473 -0
  79. package/ui/index.html +70 -0
  80. package/ui/modules/api.js +297 -0
  81. package/ui/modules/icons.js +461 -0
  82. package/ui/modules/router.js +81 -0
  83. package/ui/modules/settings-schema.js +261 -0
  84. package/ui/modules/state.js +679 -0
  85. package/ui/modules/telegram.js +331 -0
  86. package/ui/modules/utils.js +270 -0
  87. package/ui/styles/animations.css +140 -0
  88. package/ui/styles/base.css +98 -0
  89. package/ui/styles/components.css +1915 -0
  90. package/ui/styles/kanban.css +286 -0
  91. package/ui/styles/layout.css +809 -0
  92. package/ui/styles/sessions.css +827 -0
  93. package/ui/styles/variables.css +188 -0
  94. package/ui/styles.css +141 -0
  95. package/ui/styles.monolith.css +1046 -0
  96. package/ui/tabs/agents.js +1417 -0
  97. package/ui/tabs/chat.js +74 -0
  98. package/ui/tabs/control.js +887 -0
  99. package/ui/tabs/dashboard.js +515 -0
  100. package/ui/tabs/infra.js +537 -0
  101. package/ui/tabs/logs.js +783 -0
  102. package/ui/tabs/settings.js +1487 -0
  103. package/ui/tabs/tasks.js +1385 -0
  104. package/ui-server.mjs +4073 -0
  105. package/update-check.mjs +465 -0
  106. package/utils.mjs +172 -0
  107. package/ve-kanban.mjs +654 -0
  108. package/ve-kanban.ps1 +1365 -0
  109. package/ve-kanban.sh +18 -0
  110. package/ve-orchestrator.mjs +340 -0
  111. package/ve-orchestrator.ps1 +6546 -0
  112. package/ve-orchestrator.sh +18 -0
  113. package/vibe-kanban-wrapper.mjs +41 -0
  114. package/vk-error-resolver.mjs +470 -0
  115. package/vk-log-stream.mjs +914 -0
  116. package/whatsapp-channel.mjs +520 -0
  117. package/workspace-monitor.mjs +581 -0
  118. package/workspace-reaper.mjs +405 -0
  119. package/workspace-registry.mjs +238 -0
  120. package/worktree-manager.mjs +1266 -0
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
5
+ NODE_SCRIPT="${SCRIPT_DIR}/ve-orchestrator.mjs"
6
+
7
+ # Native Linux/macOS path only.
8
+ if [[ -f "${NODE_SCRIPT}" ]] && command -v node >/dev/null 2>&1; then
9
+ NODE_SCRIPT_PATH="${NODE_SCRIPT}"
10
+ NODE_PLATFORM="$(node -p 'process.platform' 2>/dev/null || true)"
11
+ if [[ "${NODE_PLATFORM}" == "win32" ]] && command -v wslpath >/dev/null 2>&1; then
12
+ NODE_SCRIPT_PATH="$(wslpath -w "${NODE_SCRIPT}")"
13
+ fi
14
+ exec node "${NODE_SCRIPT_PATH}" "$@"
15
+ fi
16
+
17
+ echo "[ve-orchestrator.sh] Native runtime unavailable (need node + ve-orchestrator.mjs)." >&2
18
+ exit 1
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * vibe-kanban-wrapper.mjs - Wrapper to expose bundled vibe-kanban CLI
4
+ *
5
+ * This wrapper ensures the bundled vibe-kanban CLI is available when
6
+ * @virtengine/openfleet is installed globally. npm doesn't expose
7
+ * bins from transitive dependencies, so we need this proxy.
8
+ */
9
+
10
+ import { spawn } from "node:child_process";
11
+ import { fileURLToPath } from "node:url";
12
+ import { dirname, resolve } from "node:path";
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+
17
+ // Path to the bundled vibe-kanban CLI
18
+ const vkBin = resolve(
19
+ __dirname,
20
+ "node_modules",
21
+ "vibe-kanban",
22
+ "bin",
23
+ "cli.js",
24
+ );
25
+
26
+ // Forward all args to the bundled vibe-kanban
27
+ const args = process.argv.slice(2);
28
+ const child = spawn("node", [vkBin, ...args], {
29
+ stdio: "inherit",
30
+ shell: false,
31
+ env: {
32
+ ...process.env,
33
+ // Prevent git from opening interactive editors (blocks agents if AFK)
34
+ GIT_EDITOR: "true",
35
+ GIT_SEQUENCE_EDITOR: "true",
36
+ },
37
+ });
38
+
39
+ child.on("exit", (code) => {
40
+ process.exit(code || 0);
41
+ });
@@ -0,0 +1,470 @@
1
+ /**
2
+ * vk-error-resolver.mjs — Background agent for auto-resolving VK log errors
3
+ *
4
+ * Monitors VK log stream for error patterns and spawns resolution agents
5
+ * in isolated worktrees. Each resolution operates independently to avoid
6
+ * blocking the main orchestration loop.
7
+ *
8
+ * Supported error patterns:
9
+ * 1. Uncommitted changes: `has uncommitted changes: <files>`
10
+ * 2. Push failures: `Failed to push branch to remote`
11
+ * 3. CI re-trigger failures: `Failed to re-trigger CI on PR`
12
+ *
13
+ * Resolution strategy:
14
+ * - Each resolution runs in a fresh worktree
15
+ * - Maximum 3 attempts per error signature
16
+ * - 5-minute cooldown between attempts
17
+ * - Only resolves for successfully completed tasks
18
+ */
19
+
20
+ import { spawn, execSync } from "node:child_process";
21
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
22
+ import { resolve } from "node:path";
23
+ import { fileURLToPath } from "url";
24
+ import { getWorktreeManager } from "./worktree-manager.mjs";
25
+
26
+ const __dirname = resolve(fileURLToPath(new URL(".", import.meta.url)));
27
+
28
+ // ── Configuration ────────────────────────────────────────────────────────────
29
+ const CONFIG = {
30
+ maxAttempts: 3,
31
+ cooldownMinutes: 5,
32
+ worktreePrefix: "openfleet-resolver",
33
+ stateFile: resolve(__dirname, "logs", "vk-error-resolver-state.json"),
34
+ };
35
+
36
+ // ── Error Pattern Detection ─────────────────────────────────────────────────
37
+
38
+ const ERROR_PATTERNS = [
39
+ {
40
+ name: "uncommitted-changes",
41
+ pattern: /has uncommitted changes: (.+)$/,
42
+ extract: (match, logLine) => ({
43
+ branch: extractBranch(logLine),
44
+ files: match[1].split(",").map((f) => f.trim()),
45
+ }),
46
+ },
47
+ {
48
+ name: "push-failure",
49
+ pattern: /Failed to push branch to remote: (\S+)/,
50
+ extract: (match, logLine) => ({
51
+ branch: match[1],
52
+ }),
53
+ },
54
+ {
55
+ name: "ci-retrigger-failure",
56
+ pattern: /Failed to re-trigger CI on PR #(\d+)/,
57
+ extract: (match, logLine) => ({
58
+ prNumber: match[1],
59
+ }),
60
+ },
61
+ ];
62
+
63
+ function extractBranch(logLine) {
64
+ const branchMatch = logLine.match(/ve\/[\w-]+/);
65
+ return branchMatch ? branchMatch[0] : null;
66
+ }
67
+
68
+ // ── State Management ─────────────────────────────────────────────────────────
69
+
70
+ class ResolutionState {
71
+ constructor(stateFile) {
72
+ this.stateFile = stateFile;
73
+ this.state = this.load();
74
+ }
75
+
76
+ load() {
77
+ if (!existsSync(this.stateFile)) {
78
+ return { attempts: {}, cooldowns: {} };
79
+ }
80
+ try {
81
+ return JSON.parse(readFileSync(this.stateFile, "utf8"));
82
+ } catch {
83
+ return { attempts: {}, cooldowns: {} };
84
+ }
85
+ }
86
+
87
+ save() {
88
+ const dir = resolve(this.stateFile, "..");
89
+ if (!existsSync(dir)) {
90
+ mkdirSync(dir, { recursive: true });
91
+ }
92
+ writeFileSync(this.stateFile, JSON.stringify(this.state, null, 2));
93
+ }
94
+
95
+ getSignature(errorType, context) {
96
+ return `${errorType}:${context.branch || context.prNumber}`;
97
+ }
98
+
99
+ canAttempt(signature) {
100
+ const attempts = this.state.attempts[signature] || 0;
101
+ if (attempts >= CONFIG.maxAttempts) {
102
+ return { allowed: false, reason: "max-attempts-reached" };
103
+ }
104
+
105
+ const cooldownUntil = this.state.cooldowns[signature];
106
+ if (cooldownUntil && new Date() < new Date(cooldownUntil)) {
107
+ return { allowed: false, reason: "cooldown-active" };
108
+ }
109
+
110
+ return { allowed: true };
111
+ }
112
+
113
+ recordAttempt(signature) {
114
+ this.state.attempts[signature] = (this.state.attempts[signature] || 0) + 1;
115
+ const cooldownUntil = new Date(
116
+ Date.now() + CONFIG.cooldownMinutes * 60 * 1000,
117
+ );
118
+ this.state.cooldowns[signature] = cooldownUntil.toISOString();
119
+ this.save();
120
+ }
121
+
122
+ clearSignature(signature) {
123
+ delete this.state.attempts[signature];
124
+ delete this.state.cooldowns[signature];
125
+ this.save();
126
+ }
127
+ }
128
+
129
+ // ── Task Status Verification ─────────────────────────────────────────────────
130
+
131
+ /**
132
+ * Check if a task attempt completed successfully
133
+ * Only auto-resolve errors for successful completions
134
+ */
135
+ async function isTaskSuccessful(branch, vkBaseUrl) {
136
+ try {
137
+ // Extract task ID from branch name (ve/<id>-<slug>)
138
+ const taskIdMatch = branch.match(/ve\/([a-f0-9]+)-/);
139
+ if (!taskIdMatch) return false;
140
+
141
+ const taskId = taskIdMatch[1];
142
+
143
+ // Query VK API for task status
144
+ const response = await fetch(
145
+ `${vkBaseUrl}/api/task-attempts?task_id=${taskId}`,
146
+ {
147
+ headers: { Accept: "application/json" },
148
+ },
149
+ );
150
+
151
+ if (!response.ok) return false;
152
+
153
+ const attempts = await response.json();
154
+ const latest = attempts.sort(
155
+ (a, b) => new Date(b.created_at) - new Date(a.created_at),
156
+ )[0];
157
+
158
+ return latest?.status === "COMPLETED" && latest?.result === "SUCCESS";
159
+ } catch (err) {
160
+ console.error(
161
+ `[vk-error-resolver] Failed to check task status: ${err.message}`,
162
+ );
163
+ return false;
164
+ }
165
+ }
166
+
167
+ // ── Resolution Handlers ──────────────────────────────────────────────────────
168
+
169
+ class UncommittedChangesResolver {
170
+ constructor(repoPath, stateManager) {
171
+ this.repoPath = repoPath;
172
+ this.stateManager = stateManager;
173
+ }
174
+
175
+ async resolve(context) {
176
+ const { branch, files } = context;
177
+ const signature = this.stateManager.getSignature(
178
+ "uncommitted-changes",
179
+ context,
180
+ );
181
+
182
+ console.log(
183
+ `[vk-error-resolver] Resolving uncommitted changes on ${branch}`,
184
+ );
185
+
186
+ // Acquire worktree via centralized manager
187
+ const wm = getWorktreeManager(this.repoPath);
188
+ const acquired = await wm.acquireWorktree(
189
+ branch,
190
+ `err-uncommitted-${branch}`,
191
+ { owner: "error-resolver" },
192
+ );
193
+ const worktreePath = acquired?.path || null;
194
+ if (!worktreePath) {
195
+ return { success: false, reason: "worktree-creation-failed" };
196
+ }
197
+
198
+ try {
199
+ // Add uncommitted files
200
+ execSync("git add .", {
201
+ cwd: worktreePath,
202
+ stdio: "pipe",
203
+ env: { ...process.env, GIT_EDITOR: ":", GIT_MERGE_AUTOEDIT: "no" },
204
+ });
205
+
206
+ // Commit changes
207
+ const commitMsg = "chore(openfleet): add uncommitted changes";
208
+ execSync(`git commit -m "${commitMsg}" --no-edit`, {
209
+ cwd: worktreePath,
210
+ stdio: "pipe",
211
+ env: { ...process.env, GIT_EDITOR: ":", GIT_MERGE_AUTOEDIT: "no" },
212
+ });
213
+
214
+ // Push to remote
215
+ execSync(`git push origin ${branch}`, {
216
+ cwd: worktreePath,
217
+ stdio: "pipe",
218
+ });
219
+
220
+ console.log(
221
+ `[vk-error-resolver] ✓ Resolved uncommitted changes on ${branch}`,
222
+ );
223
+ this.stateManager.clearSignature(signature);
224
+ return { success: true };
225
+ } catch (err) {
226
+ console.error(`[vk-error-resolver] Resolution failed: ${err.message}`);
227
+ return { success: false, reason: err.message };
228
+ } finally {
229
+ wm.releaseWorktreeByPath(worktreePath);
230
+ }
231
+ }
232
+ }
233
+
234
+ class PushFailureResolver {
235
+ constructor(repoPath, stateManager) {
236
+ this.repoPath = repoPath;
237
+ this.stateManager = stateManager;
238
+ }
239
+
240
+ async resolve(context) {
241
+ const { branch } = context;
242
+ const signature = this.stateManager.getSignature("push-failure", context);
243
+
244
+ console.log(`[vk-error-resolver] Resolving push failure on ${branch}`);
245
+
246
+ // Acquire worktree via centralized manager
247
+ const wm = getWorktreeManager(this.repoPath);
248
+ const acquired = await wm.acquireWorktree(branch, `err-push-${branch}`, {
249
+ owner: "error-resolver",
250
+ });
251
+ const worktreePath = acquired?.path || null;
252
+ if (!worktreePath) {
253
+ return { success: false, reason: "worktree-creation-failed" };
254
+ }
255
+
256
+ try {
257
+ // Fetch latest from remote
258
+ execSync(`git fetch origin ${branch}`, {
259
+ cwd: worktreePath,
260
+ stdio: "pipe",
261
+ });
262
+
263
+ // Check if behind
264
+ const behind = execSync(`git rev-list --count HEAD..origin/${branch}`, {
265
+ cwd: worktreePath,
266
+ encoding: "utf8",
267
+ }).trim();
268
+
269
+ if (parseInt(behind) > 0) {
270
+ // Rebase and retry push
271
+ execSync(`git rebase origin/${branch}`, {
272
+ cwd: worktreePath,
273
+ stdio: "pipe",
274
+ env: { ...process.env, GIT_EDITOR: ":", GIT_MERGE_AUTOEDIT: "no" },
275
+ });
276
+ execSync(`git push origin ${branch} --force-with-lease`, {
277
+ cwd: worktreePath,
278
+ stdio: "pipe",
279
+ });
280
+
281
+ console.log(
282
+ `[vk-error-resolver] ✓ Resolved push failure on ${branch} (rebased)`,
283
+ );
284
+ this.stateManager.clearSignature(signature);
285
+ return { success: true };
286
+ }
287
+
288
+ // Try force push as last resort
289
+ execSync(`git push origin ${branch} --force-with-lease`, {
290
+ cwd: worktreePath,
291
+ stdio: "pipe",
292
+ });
293
+
294
+ console.log(
295
+ `[vk-error-resolver] ✓ Resolved push failure on ${branch} (force-pushed)`,
296
+ );
297
+ this.stateManager.clearSignature(signature);
298
+ return { success: true };
299
+ } catch (err) {
300
+ console.error(`[vk-error-resolver] Resolution failed: ${err.message}`);
301
+ return { success: false, reason: err.message };
302
+ } finally {
303
+ wm.releaseWorktreeByPath(worktreePath);
304
+ }
305
+ }
306
+ }
307
+
308
+ class CIRetriggerResolver {
309
+ constructor(repoPath, stateManager) {
310
+ this.repoPath = repoPath;
311
+ this.stateManager = stateManager;
312
+ }
313
+
314
+ async resolve(context) {
315
+ const { prNumber } = context;
316
+ const signature = this.stateManager.getSignature("ci-retrigger", context);
317
+
318
+ console.log(
319
+ `[vk-error-resolver] Resolving CI re-trigger for PR #${prNumber}`,
320
+ );
321
+
322
+ try {
323
+ // Check PR status
324
+ const status = execSync(
325
+ `gh pr view ${prNumber} --json mergeable,mergeStateStatus`,
326
+ { cwd: this.repoPath, encoding: "utf8" },
327
+ );
328
+ const prStatus = JSON.parse(status);
329
+
330
+ if (prStatus.mergeable === "MERGEABLE") {
331
+ // Create empty commit to trigger CI
332
+ const branch = execSync(
333
+ `gh pr view ${prNumber} --json headRefName --jq .headRefName`,
334
+ { cwd: this.repoPath, encoding: "utf8" },
335
+ ).trim();
336
+
337
+ // Acquire worktree via centralized manager
338
+ const wm = getWorktreeManager(this.repoPath);
339
+ const acquired = await wm.acquireWorktree(branch, `err-ci-${branch}`, {
340
+ owner: "error-resolver",
341
+ });
342
+ const worktreePath = acquired?.path || null;
343
+ if (!worktreePath) {
344
+ return { success: false, reason: "worktree-creation-failed" };
345
+ }
346
+
347
+ try {
348
+ execSync(
349
+ 'git commit --allow-empty -m "chore: trigger CI" --no-edit',
350
+ {
351
+ cwd: worktreePath,
352
+ stdio: "pipe",
353
+ env: {
354
+ ...process.env,
355
+ GIT_EDITOR: ":",
356
+ GIT_MERGE_AUTOEDIT: "no",
357
+ },
358
+ },
359
+ );
360
+ execSync(`git push origin ${branch}`, {
361
+ cwd: worktreePath,
362
+ stdio: "pipe",
363
+ });
364
+
365
+ console.log(`[vk-error-resolver] ✓ Triggered CI for PR #${prNumber}`);
366
+ this.stateManager.clearSignature(signature);
367
+ return { success: true };
368
+ } finally {
369
+ wm.releaseWorktreeByPath(worktreePath);
370
+ }
371
+ } else {
372
+ console.log(
373
+ `[vk-error-resolver] PR #${prNumber} not mergeable, escalating`,
374
+ );
375
+ return { success: false, reason: "pr-not-mergeable", escalate: true };
376
+ }
377
+ } catch (err) {
378
+ console.error(`[vk-error-resolver] Resolution failed: ${err.message}`);
379
+ return { success: false, reason: err.message };
380
+ }
381
+ }
382
+ }
383
+
384
+ // ── Main Error Resolver ──────────────────────────────────────────────────────
385
+
386
+ export class VKErrorResolver {
387
+ constructor(repoPath, vkBaseUrl, options = {}) {
388
+ this.repoPath = repoPath;
389
+ this.vkBaseUrl = vkBaseUrl;
390
+ this.stateManager = new ResolutionState(CONFIG.stateFile);
391
+ this.enabled = options.enabled ?? true;
392
+ this.onResolve = options.onResolve || null;
393
+
394
+ this.resolvers = {
395
+ "uncommitted-changes": new UncommittedChangesResolver(
396
+ repoPath,
397
+ this.stateManager,
398
+ ),
399
+ "push-failure": new PushFailureResolver(repoPath, this.stateManager),
400
+ "ci-retrigger": new CIRetriggerResolver(repoPath, this.stateManager),
401
+ };
402
+ }
403
+
404
+ async handleLogLine(line) {
405
+ if (!this.enabled) return;
406
+
407
+ for (const pattern of ERROR_PATTERNS) {
408
+ const match = line.match(pattern.pattern);
409
+ if (!match) continue;
410
+
411
+ const context = pattern.extract(match, line);
412
+ const signature = this.stateManager.getSignature(pattern.name, context);
413
+
414
+ // Check if can attempt resolution
415
+ const canAttempt = this.stateManager.canAttempt(signature);
416
+ if (!canAttempt.allowed) {
417
+ console.log(
418
+ `[vk-error-resolver] Skipping ${pattern.name} on ${context.branch || context.prNumber}: ${canAttempt.reason}`,
419
+ );
420
+ continue;
421
+ }
422
+
423
+ // Verify task completed successfully
424
+ if (
425
+ context.branch &&
426
+ !(await isTaskSuccessful(context.branch, this.vkBaseUrl))
427
+ ) {
428
+ console.log(
429
+ `[vk-error-resolver] Skipping ${pattern.name}: task not successful`,
430
+ );
431
+ continue;
432
+ }
433
+
434
+ // Attempt resolution
435
+ console.log(
436
+ `[vk-error-resolver] Attempting resolution for ${pattern.name}`,
437
+ );
438
+ this.stateManager.recordAttempt(signature);
439
+
440
+ const resolver = this.resolvers[pattern.name];
441
+ const result = await resolver.resolve(context);
442
+
443
+ if (this.onResolve) {
444
+ this.onResolve({
445
+ errorType: pattern.name,
446
+ context,
447
+ result,
448
+ signature,
449
+ });
450
+ }
451
+
452
+ if (result.escalate) {
453
+ console.warn(
454
+ `[vk-error-resolver] Escalation required for ${pattern.name}`,
455
+ );
456
+ }
457
+ }
458
+ }
459
+
460
+ getStats() {
461
+ return {
462
+ attempts: Object.keys(this.stateManager.state.attempts).length,
463
+ activeCooldowns: Object.keys(this.stateManager.state.cooldowns).filter(
464
+ (sig) => new Date() < new Date(this.stateManager.state.cooldowns[sig]),
465
+ ).length,
466
+ };
467
+ }
468
+ }
469
+
470
+ export default VKErrorResolver;