macro-agent 0.1.8 → 0.1.11

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 (306) hide show
  1. package/CLAUDE.md +263 -33
  2. package/README.md +781 -131
  3. package/dist/acp/claude-code-replay.d.ts +11 -0
  4. package/dist/acp/claude-code-replay.d.ts.map +1 -0
  5. package/dist/acp/claude-code-replay.js +190 -0
  6. package/dist/acp/claude-code-replay.js.map +1 -0
  7. package/dist/acp/macro-agent.d.ts.map +1 -1
  8. package/dist/acp/macro-agent.js +192 -7
  9. package/dist/acp/macro-agent.js.map +1 -1
  10. package/dist/acp/types.d.ts +9 -0
  11. package/dist/acp/types.d.ts.map +1 -1
  12. package/dist/acp/types.js.map +1 -1
  13. package/dist/adapters/tasks-adapter.d.ts.map +1 -1
  14. package/dist/adapters/tasks-adapter.js +3 -0
  15. package/dist/adapters/tasks-adapter.js.map +1 -1
  16. package/dist/adapters/types.d.ts +1 -0
  17. package/dist/adapters/types.d.ts.map +1 -1
  18. package/dist/agent/agent-manager-v2.d.ts +21 -0
  19. package/dist/agent/agent-manager-v2.d.ts.map +1 -1
  20. package/dist/agent/agent-manager-v2.js +308 -54
  21. package/dist/agent/agent-manager-v2.js.map +1 -1
  22. package/dist/agent/agent-manager.d.ts +12 -0
  23. package/dist/agent/agent-manager.d.ts.map +1 -1
  24. package/dist/agent/agent-manager.js.map +1 -1
  25. package/dist/agent/agent-store.d.ts +10 -0
  26. package/dist/agent/agent-store.d.ts.map +1 -1
  27. package/dist/agent/agent-store.js +22 -0
  28. package/dist/agent/agent-store.js.map +1 -1
  29. package/dist/agent/types.d.ts +15 -2
  30. package/dist/agent/types.d.ts.map +1 -1
  31. package/dist/agent/types.js.map +1 -1
  32. package/dist/boot-v2.d.ts +129 -1
  33. package/dist/boot-v2.d.ts.map +1 -1
  34. package/dist/boot-v2.js +359 -8
  35. package/dist/boot-v2.js.map +1 -1
  36. package/dist/cli/acp.js +4 -0
  37. package/dist/cli/acp.js.map +1 -1
  38. package/dist/cli/index.js +56 -0
  39. package/dist/cli/index.js.map +1 -1
  40. package/dist/cognitive/macro-agent-backend.d.ts.map +1 -1
  41. package/dist/cognitive/macro-agent-backend.js +40 -22
  42. package/dist/cognitive/macro-agent-backend.js.map +1 -1
  43. package/dist/integrations/skilltree.d.ts.map +1 -1
  44. package/dist/integrations/skilltree.js +1 -0
  45. package/dist/integrations/skilltree.js.map +1 -1
  46. package/dist/lifecycle/cascade.d.ts +25 -2
  47. package/dist/lifecycle/cascade.d.ts.map +1 -1
  48. package/dist/lifecycle/cascade.js +70 -2
  49. package/dist/lifecycle/cascade.js.map +1 -1
  50. package/dist/lifecycle/cleanup.d.ts +33 -2
  51. package/dist/lifecycle/cleanup.d.ts.map +1 -1
  52. package/dist/lifecycle/cleanup.js +28 -6
  53. package/dist/lifecycle/cleanup.js.map +1 -1
  54. package/dist/lifecycle/handlers-v2.d.ts +7 -0
  55. package/dist/lifecycle/handlers-v2.d.ts.map +1 -1
  56. package/dist/lifecycle/handlers-v2.js +28 -2
  57. package/dist/lifecycle/handlers-v2.js.map +1 -1
  58. package/dist/lifecycle/types.d.ts +11 -0
  59. package/dist/lifecycle/types.d.ts.map +1 -1
  60. package/dist/lifecycle/types.js.map +1 -1
  61. package/dist/map/acp-bridge.d.ts +9 -0
  62. package/dist/map/acp-bridge.d.ts.map +1 -1
  63. package/dist/map/acp-bridge.js +15 -2
  64. package/dist/map/acp-bridge.js.map +1 -1
  65. package/dist/map/cascade-action-handler.d.ts +24 -0
  66. package/dist/map/cascade-action-handler.d.ts.map +1 -0
  67. package/dist/map/cascade-action-handler.js +170 -0
  68. package/dist/map/cascade-action-handler.js.map +1 -0
  69. package/dist/map/cascade-bridge.d.ts +44 -0
  70. package/dist/map/cascade-bridge.d.ts.map +1 -0
  71. package/dist/map/cascade-bridge.js +294 -0
  72. package/dist/map/cascade-bridge.js.map +1 -0
  73. package/dist/map/coordination-handler.d.ts.map +1 -1
  74. package/dist/map/coordination-handler.js +12 -1
  75. package/dist/map/coordination-handler.js.map +1 -1
  76. package/dist/map/lifecycle-bridge.d.ts +1 -1
  77. package/dist/map/lifecycle-bridge.d.ts.map +1 -1
  78. package/dist/map/lifecycle-bridge.js +58 -23
  79. package/dist/map/lifecycle-bridge.js.map +1 -1
  80. package/dist/map/server.d.ts.map +1 -1
  81. package/dist/map/server.js +219 -7
  82. package/dist/map/server.js.map +1 -1
  83. package/dist/map/sidecar.d.ts.map +1 -1
  84. package/dist/map/sidecar.js +49 -2
  85. package/dist/map/sidecar.js.map +1 -1
  86. package/dist/map/types.d.ts +22 -0
  87. package/dist/map/types.d.ts.map +1 -1
  88. package/dist/mcp/tools/done-v2.d.ts.map +1 -1
  89. package/dist/mcp/tools/done-v2.js +8 -0
  90. package/dist/mcp/tools/done-v2.js.map +1 -1
  91. package/dist/teams/team-manager-v2.d.ts.map +1 -1
  92. package/dist/teams/team-manager-v2.js +26 -0
  93. package/dist/teams/team-manager-v2.js.map +1 -1
  94. package/dist/teams/team-runtime-v2.d.ts.map +1 -1
  95. package/dist/teams/team-runtime-v2.js +16 -3
  96. package/dist/teams/team-runtime-v2.js.map +1 -1
  97. package/dist/workspace/config.d.ts +10 -10
  98. package/dist/workspace/config.d.ts.map +1 -1
  99. package/dist/workspace/config.js +4 -4
  100. package/dist/workspace/config.js.map +1 -1
  101. package/dist/workspace/git-cascade-adapter.d.ts +510 -0
  102. package/dist/workspace/git-cascade-adapter.d.ts.map +1 -0
  103. package/dist/workspace/git-cascade-adapter.js +934 -0
  104. package/dist/workspace/git-cascade-adapter.js.map +1 -0
  105. package/dist/workspace/index.d.ts +3 -3
  106. package/dist/workspace/index.d.ts.map +1 -1
  107. package/dist/workspace/index.js +4 -4
  108. package/dist/workspace/index.js.map +1 -1
  109. package/dist/workspace/landing/direct-push.d.ts +20 -0
  110. package/dist/workspace/landing/direct-push.d.ts.map +1 -0
  111. package/dist/workspace/landing/direct-push.js +74 -0
  112. package/dist/workspace/landing/direct-push.js.map +1 -0
  113. package/dist/workspace/landing/index.d.ts +29 -0
  114. package/dist/workspace/landing/index.d.ts.map +1 -0
  115. package/dist/workspace/landing/index.js +37 -0
  116. package/dist/workspace/landing/index.js.map +1 -0
  117. package/dist/workspace/landing/merge-to-parent.d.ts +41 -0
  118. package/dist/workspace/landing/merge-to-parent.d.ts.map +1 -0
  119. package/dist/workspace/landing/merge-to-parent.js +186 -0
  120. package/dist/workspace/landing/merge-to-parent.js.map +1 -0
  121. package/dist/workspace/landing/optimistic-push.d.ts +16 -0
  122. package/dist/workspace/landing/optimistic-push.d.ts.map +1 -0
  123. package/dist/workspace/landing/optimistic-push.js +27 -0
  124. package/dist/workspace/landing/optimistic-push.js.map +1 -0
  125. package/dist/workspace/landing/queue-to-branch.d.ts +24 -0
  126. package/dist/workspace/landing/queue-to-branch.d.ts.map +1 -0
  127. package/dist/workspace/landing/queue-to-branch.js +79 -0
  128. package/dist/workspace/landing/queue-to-branch.js.map +1 -0
  129. package/dist/workspace/merge-queue/merge-queue.d.ts +10 -0
  130. package/dist/workspace/merge-queue/merge-queue.d.ts.map +1 -1
  131. package/dist/workspace/merge-queue/merge-queue.js +10 -0
  132. package/dist/workspace/merge-queue/merge-queue.js.map +1 -1
  133. package/dist/workspace/merge-queue/types.d.ts +16 -2
  134. package/dist/workspace/merge-queue/types.d.ts.map +1 -1
  135. package/dist/workspace/merge-queue/types.js +9 -0
  136. package/dist/workspace/merge-queue/types.js.map +1 -1
  137. package/dist/workspace/pool/types.d.ts +1 -0
  138. package/dist/workspace/pool/types.d.ts.map +1 -1
  139. package/dist/workspace/pool/worktree-pool.d.ts.map +1 -1
  140. package/dist/workspace/pool/worktree-pool.js +1 -0
  141. package/dist/workspace/pool/worktree-pool.js.map +1 -1
  142. package/dist/workspace/recovery/abandon.d.ts +15 -0
  143. package/dist/workspace/recovery/abandon.d.ts.map +1 -0
  144. package/dist/workspace/recovery/abandon.js +45 -0
  145. package/dist/workspace/recovery/abandon.js.map +1 -0
  146. package/dist/workspace/recovery/auto-resolve.d.ts +27 -0
  147. package/dist/workspace/recovery/auto-resolve.d.ts.map +1 -0
  148. package/dist/workspace/recovery/auto-resolve.js +99 -0
  149. package/dist/workspace/recovery/auto-resolve.js.map +1 -0
  150. package/dist/workspace/recovery/defer.d.ts +15 -0
  151. package/dist/workspace/recovery/defer.d.ts.map +1 -0
  152. package/dist/workspace/recovery/defer.js +16 -0
  153. package/dist/workspace/recovery/defer.js.map +1 -0
  154. package/dist/workspace/recovery/escalate.d.ts +16 -0
  155. package/dist/workspace/recovery/escalate.d.ts.map +1 -0
  156. package/dist/workspace/recovery/escalate.js +24 -0
  157. package/dist/workspace/recovery/escalate.js.map +1 -0
  158. package/dist/workspace/recovery/index.d.ts +32 -0
  159. package/dist/workspace/recovery/index.d.ts.map +1 -0
  160. package/dist/workspace/recovery/index.js +45 -0
  161. package/dist/workspace/recovery/index.js.map +1 -0
  162. package/dist/workspace/recovery/spawn-resolver.d.ts +45 -0
  163. package/dist/workspace/recovery/spawn-resolver.d.ts.map +1 -0
  164. package/dist/workspace/recovery/spawn-resolver.js +118 -0
  165. package/dist/workspace/recovery/spawn-resolver.js.map +1 -0
  166. package/dist/workspace/recovery/types.d.ts +63 -0
  167. package/dist/workspace/recovery/types.d.ts.map +1 -0
  168. package/dist/workspace/recovery/types.js +12 -0
  169. package/dist/workspace/recovery/types.js.map +1 -0
  170. package/dist/workspace/topology/index.d.ts +9 -0
  171. package/dist/workspace/topology/index.d.ts.map +1 -0
  172. package/dist/workspace/topology/index.js +8 -0
  173. package/dist/workspace/topology/index.js.map +1 -0
  174. package/dist/workspace/topology/no-workspace.d.ts +18 -0
  175. package/dist/workspace/topology/no-workspace.d.ts.map +1 -0
  176. package/dist/workspace/topology/no-workspace.js +25 -0
  177. package/dist/workspace/topology/no-workspace.js.map +1 -0
  178. package/dist/workspace/topology/types.d.ts +97 -0
  179. package/dist/workspace/topology/types.d.ts.map +1 -0
  180. package/dist/workspace/topology/types.js +20 -0
  181. package/dist/workspace/topology/types.js.map +1 -0
  182. package/dist/workspace/topology/yaml-driven.d.ts +69 -0
  183. package/dist/workspace/topology/yaml-driven.d.ts.map +1 -0
  184. package/dist/workspace/topology/yaml-driven.js +273 -0
  185. package/dist/workspace/topology/yaml-driven.js.map +1 -0
  186. package/dist/workspace/types-v3.d.ts +117 -0
  187. package/dist/workspace/types-v3.d.ts.map +1 -0
  188. package/dist/workspace/types-v3.js +20 -0
  189. package/dist/workspace/types-v3.js.map +1 -0
  190. package/dist/workspace/types.d.ts +162 -17
  191. package/dist/workspace/types.d.ts.map +1 -1
  192. package/dist/workspace/workspace-manager.d.ts +101 -13
  193. package/dist/workspace/workspace-manager.d.ts.map +1 -1
  194. package/dist/workspace/workspace-manager.js +416 -13
  195. package/dist/workspace/workspace-manager.js.map +1 -1
  196. package/dist/workspace/yaml-schema.d.ts +254 -0
  197. package/dist/workspace/yaml-schema.d.ts.map +1 -0
  198. package/dist/workspace/yaml-schema.js +170 -0
  199. package/dist/workspace/yaml-schema.js.map +1 -0
  200. package/docs/conflict-recovery.md +472 -0
  201. package/docs/design/task-dispatcher.md +880 -0
  202. package/docs/git-cascade-integration-gaps.md +678 -0
  203. package/docs/workspace-interfaces.md +731 -0
  204. package/docs/workspace-redesign-plan.md +302 -0
  205. package/package.json +6 -5
  206. package/src/__tests__/boot-v2.test.ts +435 -0
  207. package/src/__tests__/e2e/acp-over-map.e2e.test.ts +92 -0
  208. package/src/__tests__/e2e/auto-sync.e2e.test.ts +257 -0
  209. package/src/__tests__/e2e/bootstrap.e2e.test.ts +319 -0
  210. package/src/__tests__/e2e/cascade-rebase.e2e.test.ts +254 -0
  211. package/src/__tests__/e2e/cli-run.e2e.test.ts +167 -0
  212. package/src/__tests__/e2e/dispatch-coordination.e2e.test.ts +495 -0
  213. package/src/__tests__/e2e/dispatch-live.e2e.test.ts +564 -0
  214. package/src/__tests__/e2e/dispatch-opentasks.e2e.test.ts +496 -0
  215. package/src/__tests__/e2e/dispatch-phase2-live.e2e.test.ts +456 -0
  216. package/src/__tests__/e2e/dispatch-phase2.e2e.test.ts +386 -0
  217. package/src/__tests__/e2e/dispatch.e2e.test.ts +376 -0
  218. package/src/__tests__/e2e/self-driving-v3.e2e.test.ts +197 -0
  219. package/src/__tests__/e2e/spawn-resolver.e2e.test.ts +200 -0
  220. package/src/__tests__/e2e/workspace-lifecycle.e2e.test.ts +30 -22
  221. package/src/__tests__/e2e/workspace-v3.e2e.test.ts +413 -0
  222. package/src/acp/__tests__/claude-code-replay.test.ts +225 -0
  223. package/src/acp/__tests__/macro-agent.test.ts +39 -1
  224. package/src/acp/claude-code-replay.ts +208 -0
  225. package/src/acp/macro-agent.ts +203 -10
  226. package/src/acp/types.ts +10 -0
  227. package/src/adapters/__tests__/tasks-adapter.test.ts +1 -0
  228. package/src/adapters/tasks-adapter.ts +3 -0
  229. package/src/adapters/types.ts +1 -0
  230. package/src/agent/__tests__/agent-manager-topology.test.ts +73 -0
  231. package/src/agent/__tests__/agent-manager-v2.test.ts +66 -0
  232. package/src/agent/__tests__/agent-store.test.ts +52 -0
  233. package/src/agent/__tests__/task-ref-resolution.test.ts +231 -0
  234. package/src/agent/agent-manager-v2.ts +372 -59
  235. package/src/agent/agent-manager.ts +14 -0
  236. package/src/agent/agent-store.ts +24 -0
  237. package/src/agent/types.ts +16 -2
  238. package/src/boot-v2.ts +589 -35
  239. package/src/cli/acp.ts +4 -0
  240. package/src/cli/index.ts +61 -0
  241. package/src/cognitive/macro-agent-backend.ts +45 -29
  242. package/src/integrations/skilltree.ts +1 -0
  243. package/src/lifecycle/__tests__/cascade-consolidation.test.ts +240 -0
  244. package/src/lifecycle/cascade.ts +77 -2
  245. package/src/lifecycle/cleanup.ts +52 -3
  246. package/src/lifecycle/handlers-v2.ts +40 -3
  247. package/src/lifecycle/types.ts +12 -0
  248. package/src/map/__tests__/cascade-bridge.test.ts +229 -0
  249. package/src/map/__tests__/emit-event.test.ts +71 -0
  250. package/src/map/__tests__/lifecycle-bridge.test.ts +86 -10
  251. package/src/map/acp-bridge.ts +26 -3
  252. package/src/map/cascade-action-handler.ts +205 -0
  253. package/src/map/cascade-bridge.ts +339 -0
  254. package/src/map/coordination-handler.ts +13 -1
  255. package/src/map/lifecycle-bridge.ts +52 -17
  256. package/src/map/server.ts +225 -7
  257. package/src/map/sidecar.ts +48 -1
  258. package/src/map/types.ts +23 -0
  259. package/src/mcp/tools/done-v2.ts +9 -0
  260. package/src/teams/team-manager-v2.ts +37 -0
  261. package/src/teams/team-runtime-v2.ts +23 -3
  262. package/src/workspace/__tests__/{dataplane-adapter.test.ts → git-cascade-adapter.test.ts} +209 -14
  263. package/src/workspace/__tests__/land-dispatch.test.ts +214 -0
  264. package/src/workspace/__tests__/self-driving-yaml.test.ts +114 -0
  265. package/src/workspace/__tests__/shared-worktree-refcount.test.ts +154 -0
  266. package/src/workspace/__tests__/standalone-mode.test.ts +118 -0
  267. package/src/workspace/__tests__/workspace-manager-v3.test.ts +245 -0
  268. package/src/workspace/__tests__/yaml-schema.test.ts +210 -0
  269. package/src/workspace/config.ts +11 -11
  270. package/src/workspace/git-cascade-adapter.ts +1213 -0
  271. package/src/workspace/index.ts +11 -11
  272. package/src/workspace/landing/__tests__/strategies.test.ts +184 -0
  273. package/src/workspace/landing/direct-push.ts +91 -0
  274. package/src/workspace/landing/index.ts +40 -0
  275. package/src/workspace/landing/merge-to-parent.ts +229 -0
  276. package/src/workspace/landing/optimistic-push.ts +36 -0
  277. package/src/workspace/landing/queue-to-branch.ts +108 -0
  278. package/src/workspace/merge-queue/merge-queue.ts +10 -0
  279. package/src/workspace/merge-queue/types.ts +16 -2
  280. package/src/workspace/pool/__tests__/worktree-pool.integration.test.ts +5 -5
  281. package/src/workspace/pool/types.ts +1 -0
  282. package/src/workspace/pool/worktree-pool.ts +1 -0
  283. package/src/workspace/recovery/__tests__/auto-resolve-integration.test.ts +127 -0
  284. package/src/workspace/recovery/__tests__/spawn-resolver.test.ts +139 -0
  285. package/src/workspace/recovery/__tests__/strategies.test.ts +145 -0
  286. package/src/workspace/recovery/abandon.ts +51 -0
  287. package/src/workspace/recovery/auto-resolve.ts +119 -0
  288. package/src/workspace/recovery/defer.ts +23 -0
  289. package/src/workspace/recovery/escalate.ts +30 -0
  290. package/src/workspace/recovery/index.ts +58 -0
  291. package/src/workspace/recovery/spawn-resolver.ts +152 -0
  292. package/src/workspace/recovery/types.ts +54 -0
  293. package/src/workspace/topology/__tests__/yaml-driven.test.ts +345 -0
  294. package/src/workspace/topology/index.ts +18 -0
  295. package/src/workspace/topology/no-workspace.ts +39 -0
  296. package/src/workspace/topology/types.ts +116 -0
  297. package/src/workspace/topology/yaml-driven.ts +316 -0
  298. package/src/workspace/types-v3.ts +162 -0
  299. package/src/workspace/types.ts +211 -20
  300. package/src/workspace/workspace-manager.ts +533 -19
  301. package/src/workspace/yaml-schema.ts +216 -0
  302. package/dist/workspace/dataplane-adapter.d.ts +0 -260
  303. package/dist/workspace/dataplane-adapter.d.ts.map +0 -1
  304. package/dist/workspace/dataplane-adapter.js +0 -416
  305. package/dist/workspace/dataplane-adapter.js.map +0 -1
  306. package/src/workspace/dataplane-adapter.ts +0 -546
@@ -0,0 +1,456 @@
1
+ /**
2
+ * Task Dispatch Phase 2 Live Agent E2E Tests
3
+ *
4
+ * Tests the Phase 2 dispatch orchestrator with REAL Claude Code agents.
5
+ * Constructs the orchestrator manually (like dispatch-live.e2e.test.ts)
6
+ * with Phase 2 ports to verify:
7
+ * - createOrchestrator with MessagePort + AgentRoster
8
+ * - Snapshot includes Phase 2 fields during live execution
9
+ * - Event emission with Phase 2 event types (dispatched.via)
10
+ * - Dispatch mode prefer-route falls back to spawn (no roster agents)
11
+ * - Lifecycle events tracked through Phase 2 orchestrator
12
+ *
13
+ * REQUIRES: RUN_FULL_AGENT_TESTS=true
14
+ *
15
+ * Run with:
16
+ * RUN_FULL_AGENT_TESTS=true npx vitest run --config vitest.e2e.config.ts src/__tests__/e2e/dispatch-phase2-live.e2e.test.ts
17
+ */
18
+
19
+ import {
20
+ describe,
21
+ it,
22
+ expect,
23
+ beforeEach,
24
+ afterEach,
25
+ vi,
26
+ } from "vitest";
27
+ import * as path from "path";
28
+ import * as os from "os";
29
+ import * as fs from "fs";
30
+ import { execSync } from "child_process";
31
+ import { bootV2, type MacroAgentSystemV2 } from "../../boot-v2.js";
32
+ import {
33
+ createOrchestrator,
34
+ type Orchestrator,
35
+ type DispatchAgentRuntime,
36
+ type DispatchTaskSource,
37
+ type DispatchEvent,
38
+ type Snapshot,
39
+ type AgentRoster,
40
+ type AgentRef,
41
+ } from "swarm-dispatch";
42
+ import type { TaskRecord } from "../../adapters/types.js";
43
+
44
+ // ─────────────────────────────────────────────────────────────────
45
+ // Configuration
46
+ // ─────────────────────────────────────────────────────────────────
47
+
48
+ const RUN_FULL_AGENT = !!process.env.RUN_FULL_AGENT_TESTS;
49
+ const describeFn = RUN_FULL_AGENT ? describe : describe.skip;
50
+
51
+ const TIMEOUT = {
52
+ SPAWN: 60_000,
53
+ DISPATCH: 120_000,
54
+ MULTI: 180_000,
55
+ };
56
+
57
+ // ─────────────────────────────────────────────────────────────────
58
+ // Helpers
59
+ // ─────────────────────────────────────────────────────────────────
60
+
61
+ function createTestRepo(prefix: string): { path: string; cleanup: () => void } {
62
+ const tmpDir = fs.mkdtempSync(
63
+ path.join(os.tmpdir(), `dispatch-p2-live-${prefix}-`)
64
+ );
65
+ const repoPath = path.join(tmpDir, "test-repo");
66
+ fs.mkdirSync(repoPath);
67
+ execSync("git init", { cwd: repoPath, stdio: "pipe" });
68
+ execSync('git config user.email "test@test.com"', { cwd: repoPath, stdio: "pipe" });
69
+ execSync('git config user.name "Test User"', { cwd: repoPath, stdio: "pipe" });
70
+ fs.writeFileSync(path.join(repoPath, "README.md"), "# Test Repo\n");
71
+ execSync("git add -A", { cwd: repoPath, stdio: "pipe" });
72
+ execSync('git commit -m "Initial commit"', { cwd: repoPath, stdio: "pipe" });
73
+ return {
74
+ path: repoPath,
75
+ cleanup: () => fs.rmSync(tmpDir, { recursive: true, force: true }),
76
+ };
77
+ }
78
+
79
+ function log(msg: string): void {
80
+ console.log(`[DISPATCH-P2-LIVE] ${msg}`);
81
+ }
82
+
83
+ function sleep(ms: number): Promise<void> {
84
+ return new Promise((resolve) => setTimeout(resolve, ms));
85
+ }
86
+
87
+ function createSourceAdapter(tasksAdapter: any): DispatchTaskSource {
88
+ return {
89
+ queryReady: (opts) => tasksAdapter.queryReady(opts),
90
+ claim: async (taskId, claimantId) => {
91
+ try {
92
+ await tasksAdapter.assignTask(taskId, claimantId);
93
+ return { success: true as const };
94
+ } catch { return { success: false as const }; }
95
+ },
96
+ release: async (taskId) => tasksAdapter.unclaimTask(taskId),
97
+ transition: async (taskId, action) => tasksAdapter.transitionTask(taskId, action),
98
+ getTask: async (taskId) => tasksAdapter.getTask(taskId),
99
+ isStillActive: async (taskId) => {
100
+ try {
101
+ const t = await tasksAdapter.getTask(taskId);
102
+ return t.status === "open";
103
+ } catch { return false; }
104
+ },
105
+ listInProgress: async () => tasksAdapter.listTasks({ status: "in_progress" }),
106
+ };
107
+ }
108
+
109
+ function createRuntimeAdapter(agentManager: any): DispatchAgentRuntime {
110
+ return {
111
+ spawn: async (opts) => {
112
+ const spawned = await agentManager.spawn({
113
+ task: opts.prompt, task_id: opts.taskId, role: opts.role, parent: null,
114
+ });
115
+ return { id: spawned.id };
116
+ },
117
+ terminate: async (agentId, reason) => agentManager.terminate(agentId, reason ?? "cancelled"),
118
+ onStopped: (cb) => agentManager.onLifecycleEvent((event: any) => {
119
+ if (event.type === "stopped") cb(event.agent.id, event.reason);
120
+ }),
121
+ };
122
+ }
123
+
124
+ function createEmptyRoster(): AgentRoster {
125
+ return {
126
+ async findAvailable() { return []; },
127
+ };
128
+ }
129
+
130
+ // ─────────────────────────────────────────────────────────────────
131
+ // Tests
132
+ // ─────────────────────────────────────────────────────────────────
133
+
134
+ describeFn("Task Dispatch Phase 2 Live Agent E2E", () => {
135
+ let system: MacroAgentSystemV2;
136
+ let testRepo: { path: string; cleanup: () => void };
137
+ let baseDir: string;
138
+ let orchestrator: Orchestrator;
139
+
140
+ beforeEach(async () => {
141
+ testRepo = createTestRepo("p2");
142
+ baseDir = path.join(testRepo.path, ".macro-agent");
143
+ fs.mkdirSync(baseDir, { recursive: true });
144
+
145
+ system = await bootV2({
146
+ cwd: testRepo.path,
147
+ baseDir,
148
+ defaultPermissionMode: "auto-approve",
149
+ inbox: { socketPath: path.join(baseDir, "inbox.sock") },
150
+ });
151
+ log("System booted");
152
+ });
153
+
154
+ afterEach(async () => {
155
+ if (orchestrator) await orchestrator.stop();
156
+ if (system) {
157
+ try {
158
+ const running = system.agentManager.list({ state: "running" });
159
+ for (const agent of running) {
160
+ try { await system.agentManager.terminate(agent.id, "cancelled"); } catch {}
161
+ }
162
+ await system.shutdown();
163
+ } catch {}
164
+ }
165
+ testRepo?.cleanup();
166
+ log("Cleanup complete");
167
+ });
168
+
169
+ function mockTasksAdapter(tasks: TaskRecord[]): void {
170
+ const tasksAdapter = system.tasksAdapter;
171
+ const taskMap = new Map(tasks.map((t) => [t.id, t]));
172
+ (tasksAdapter as any).queryReady = vi.fn()
173
+ .mockResolvedValueOnce(tasks)
174
+ .mockResolvedValue([]);
175
+ (tasksAdapter as any).assignTask = vi.fn().mockResolvedValue(undefined);
176
+ (tasksAdapter as any).transitionTask = vi.fn().mockResolvedValue(undefined);
177
+ (tasksAdapter as any).unclaimTask = vi.fn().mockResolvedValue(undefined);
178
+ (tasksAdapter as any).getTask = vi.fn().mockImplementation(
179
+ async (id: string) => taskMap.get(id) ?? { id, title: "Unknown", status: "open" }
180
+ );
181
+ (tasksAdapter as any).listTasks = vi.fn().mockResolvedValue([]);
182
+ }
183
+
184
+ // ── createOrchestrator with Phase 2 ports ──────────────────
185
+
186
+ it(
187
+ "dispatches via createOrchestrator with Phase 2 config (prefer-route → spawn fallback)",
188
+ async () => {
189
+ const task: TaskRecord = {
190
+ id: "p2-live-1",
191
+ title: "Create hello file",
192
+ content:
193
+ 'Create a file called hello.txt with the text "hello from phase 2". ' +
194
+ 'Then call the "done" MCP tool with status="completed" and summary="Created hello.txt".',
195
+ status: "open",
196
+ tags: ["auto"],
197
+ priority: 3,
198
+ };
199
+ mockTasksAdapter([task]);
200
+
201
+ const events: DispatchEvent[] = [];
202
+ orchestrator = createOrchestrator(
203
+ createSourceAdapter(system.tasksAdapter),
204
+ createRuntimeAdapter(system.agentManager),
205
+ {
206
+ claimantId: `test:${process.pid}:p2-live`,
207
+ pollIntervalMs: 600_000,
208
+ defaultRole: "worker",
209
+ concurrency: { global: 3 },
210
+ retry: { maxRetries: 3, baseDelayMs: 1_000, maxDelayMs: 60_000 },
211
+ reconcile: { enabled: true, intervalMs: 600_000 },
212
+ roster: createEmptyRoster(),
213
+ dispatchMode: "prefer-route",
214
+ }
215
+ );
216
+ orchestrator.onEvent((e) => events.push(e));
217
+ await orchestrator.start();
218
+
219
+ log("Triggering dispatch (prefer-route, empty roster → spawn fallback)...");
220
+ await orchestrator.dispatchNow();
221
+ await sleep(2_000);
222
+
223
+ log(`Tracker active: ${orchestrator.tracker.activeCount()}`);
224
+ expect(orchestrator.tracker.activeCount()).toBe(1);
225
+
226
+ const dispatched = events.find(
227
+ (e): e is Extract<DispatchEvent, { type: "dispatched" }> => e.type === "dispatched"
228
+ );
229
+ expect(dispatched).toBeDefined();
230
+ expect(dispatched!.via).toBe("spawn");
231
+ log(`Dispatched via: ${dispatched!.via}`);
232
+
233
+ const agentId = dispatched!.agentId;
234
+ const agentRecord = system.agentStore.getAgent(agentId);
235
+ expect(agentRecord).not.toBeNull();
236
+ expect(agentRecord!.state).toBe("running");
237
+ expect(agentRecord!.parent_id).toBeNull();
238
+
239
+ // Prompt agent to complete
240
+ log("Prompting agent to complete...");
241
+ const result = await system.agentManager.promptUntilDone(
242
+ agentId,
243
+ 'Create hello.txt with "hello from phase 2", then call done(status="completed", summary="Created hello.txt").',
244
+ { maxFollowUps: 3 }
245
+ );
246
+ log(`Done result: doneCalled=${result.doneCalled}, status=${result.doneStatus}`);
247
+ await sleep(3_000);
248
+
249
+ if (result.doneCalled && result.doneStatus === "completed") {
250
+ const helloPath = path.join(testRepo.path, "hello.txt");
251
+ if (fs.existsSync(helloPath)) {
252
+ expect(fs.readFileSync(helloPath, "utf-8")).toContain("hello from phase 2");
253
+ log("hello.txt verified");
254
+ }
255
+ }
256
+ },
257
+ TIMEOUT.MULTI
258
+ );
259
+
260
+ // ── Snapshot during live execution ─────────────────────────
261
+
262
+ it(
263
+ "snapshot includes Phase 2 fields during live execution",
264
+ async () => {
265
+ const task: TaskRecord = {
266
+ id: "snap-1",
267
+ title: "Snapshot test",
268
+ content: "Wait for instructions. Do not call done().",
269
+ status: "open",
270
+ };
271
+ mockTasksAdapter([task]);
272
+
273
+ orchestrator = createOrchestrator(
274
+ createSourceAdapter(system.tasksAdapter),
275
+ createRuntimeAdapter(system.agentManager),
276
+ {
277
+ claimantId: `test:${process.pid}:snap`,
278
+ pollIntervalMs: 600_000,
279
+ defaultRole: "worker",
280
+ concurrency: { global: 5 },
281
+ retry: { maxRetries: 3, baseDelayMs: 1_000, maxDelayMs: 60_000 },
282
+ reconcile: { enabled: true, intervalMs: 600_000 },
283
+ }
284
+ );
285
+ await orchestrator.start();
286
+ await orchestrator.dispatchNow();
287
+ await sleep(2_000);
288
+
289
+ const snap: Snapshot = orchestrator.snapshot();
290
+ log(`Snapshot counts: ${JSON.stringify(snap.counts)}`);
291
+
292
+ expect(snap.generatedAt).toBeTruthy();
293
+ expect(snap.counts.running).toBe(1);
294
+ expect(snap.running).toHaveLength(1);
295
+ expect(snap.subscriberErrorCount).toBe(0);
296
+
297
+ // Phase 2 fields
298
+ expect(snap.totals).toBeDefined();
299
+ expect(snap.totals!.tokens).toBeDefined();
300
+ expect(snap.totals!.agentSeconds).toBeGreaterThanOrEqual(0);
301
+
302
+ const entry = snap.running[0];
303
+ expect(entry.taskId).toBe("snap-1");
304
+ expect(entry.agentId).toBeTruthy();
305
+ expect(entry.state).toBe("Running");
306
+ expect(entry.origin).toBe("source");
307
+ log(`Entry: taskId=${entry.taskId}, origin=${entry.origin}, agentSeconds=${snap.totals!.agentSeconds!.toFixed(1)}`);
308
+ },
309
+ TIMEOUT.DISPATCH
310
+ );
311
+
312
+ // ── Event types ────────────────────────────────────────────
313
+
314
+ it(
315
+ "emits Phase 2 event types during dispatch + reconcile",
316
+ async () => {
317
+ orchestrator = createOrchestrator(
318
+ createSourceAdapter(system.tasksAdapter),
319
+ createRuntimeAdapter(system.agentManager),
320
+ {
321
+ claimantId: `test:${process.pid}:events`,
322
+ pollIntervalMs: 600_000,
323
+ defaultRole: "worker",
324
+ concurrency: { global: 3 },
325
+ retry: { maxRetries: 3, baseDelayMs: 1_000, maxDelayMs: 60_000 },
326
+ reconcile: { enabled: true, intervalMs: 600_000, stallTimeoutMs: 120_000 },
327
+ }
328
+ );
329
+
330
+ const events: DispatchEvent[] = [];
331
+ orchestrator.onEvent((e) => events.push(e));
332
+ await orchestrator.start();
333
+
334
+ // Empty dispatch → poll event
335
+ await orchestrator.dispatchNow();
336
+ const poll = events.find((e) => e.type === "poll");
337
+ expect(poll).toBeDefined();
338
+
339
+ // Empty reconcile → reconciled event
340
+ await orchestrator.reconcileNow();
341
+ const reconciled = events.find(
342
+ (e): e is Extract<DispatchEvent, { type: "reconciled" }> => e.type === "reconciled"
343
+ );
344
+ expect(reconciled).toBeDefined();
345
+ expect(reconciled!.stalled).toBe(0);
346
+ log(`Events: poll + reconciled verified`);
347
+ },
348
+ TIMEOUT.SPAWN
349
+ );
350
+
351
+ // ── Lifecycle tracks termination ───────────────────────────
352
+
353
+ it(
354
+ "tracks agent termination and queues retry",
355
+ async () => {
356
+ const task: TaskRecord = {
357
+ id: "term-1",
358
+ title: "Terminate test",
359
+ content: "Wait for instructions.",
360
+ status: "open",
361
+ };
362
+ mockTasksAdapter([task]);
363
+
364
+ const events: DispatchEvent[] = [];
365
+ orchestrator = createOrchestrator(
366
+ createSourceAdapter(system.tasksAdapter),
367
+ createRuntimeAdapter(system.agentManager),
368
+ {
369
+ claimantId: `test:${process.pid}:term`,
370
+ pollIntervalMs: 600_000,
371
+ defaultRole: "worker",
372
+ concurrency: { global: 3 },
373
+ retry: { maxRetries: 3, baseDelayMs: 1_000, maxDelayMs: 60_000 },
374
+ reconcile: { enabled: true, intervalMs: 600_000 },
375
+ }
376
+ );
377
+ orchestrator.onEvent((e) => events.push(e));
378
+ await orchestrator.start();
379
+ await orchestrator.dispatchNow();
380
+ await sleep(2_000);
381
+
382
+ expect(orchestrator.tracker.activeCount()).toBe(1);
383
+ const agentId = orchestrator.tracker.listActive()[0].agentId;
384
+ log(`Agent: ${agentId}`);
385
+
386
+ // Externally terminate
387
+ await system.agentManager.terminate(agentId, "cancelled");
388
+ await sleep(3_000);
389
+
390
+ log(`Active: ${orchestrator.tracker.activeCount()}, Retries: ${orchestrator.tracker.listRetries().length}`);
391
+ expect(orchestrator.tracker.isTracked("term-1")).toBe(true);
392
+
393
+ const retrying = events.find((e) => e.type === "retrying");
394
+ expect(retrying).toBeDefined();
395
+ log(`Retrying event found: attempt=${(retrying as any)?.attempt}`);
396
+ },
397
+ TIMEOUT.DISPATCH
398
+ );
399
+
400
+ // ── Reconciliation with Phase 2 orchestrator ───────────────
401
+
402
+ it(
403
+ "reconciliation detects closed task and terminates agent",
404
+ async () => {
405
+ const task: TaskRecord = {
406
+ id: "reconcile-p2",
407
+ title: "Reconcile test",
408
+ content: "Wait for instructions.",
409
+ status: "open",
410
+ };
411
+ mockTasksAdapter([task]);
412
+
413
+ const events: DispatchEvent[] = [];
414
+ orchestrator = createOrchestrator(
415
+ createSourceAdapter(system.tasksAdapter),
416
+ createRuntimeAdapter(system.agentManager),
417
+ {
418
+ claimantId: `test:${process.pid}:reconcile`,
419
+ pollIntervalMs: 600_000,
420
+ defaultRole: "worker",
421
+ concurrency: { global: 3 },
422
+ retry: { maxRetries: 3, baseDelayMs: 1_000, maxDelayMs: 60_000 },
423
+ reconcile: { enabled: true, intervalMs: 600_000 },
424
+ }
425
+ );
426
+ orchestrator.onEvent((e) => events.push(e));
427
+ await orchestrator.start();
428
+ await orchestrator.dispatchNow();
429
+ await sleep(2_000);
430
+
431
+ expect(orchestrator.tracker.activeCount()).toBe(1);
432
+ const agentId = orchestrator.tracker.listActive()[0].agentId;
433
+
434
+ // Close the task externally
435
+ (system.tasksAdapter as any).getTask = vi.fn().mockResolvedValue({
436
+ ...task,
437
+ status: "closed",
438
+ });
439
+
440
+ log("Triggering reconcile (task now closed)...");
441
+ await orchestrator.reconcileNow();
442
+ await sleep(3_000);
443
+
444
+ expect(orchestrator.tracker.isTracked("reconcile-p2")).toBe(false);
445
+ expect(orchestrator.tracker.activeCount()).toBe(0);
446
+
447
+ const cancelled = events.find((e) => e.type === "cancelled");
448
+ expect(cancelled).toBeDefined();
449
+ log("Reconciliation cancelled the agent correctly");
450
+
451
+ const finalRecord = system.agentStore.getAgent(agentId);
452
+ expect(finalRecord?.state).toBe("stopped");
453
+ },
454
+ TIMEOUT.DISPATCH
455
+ );
456
+ });