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,376 @@
1
+ /**
2
+ * Task Dispatch E2E Tests (mocked agents)
3
+ *
4
+ * Tests dispatch boot wiring, configuration, and basic dispatch flow
5
+ * using mocked acp-factory and opentasks (no real agents or daemon).
6
+ *
7
+ * REQUIRES: RUN_E2E_TESTS=true
8
+ *
9
+ * Run with:
10
+ * RUN_E2E_TESTS=true npx vitest run --config vitest.e2e.config.ts src/__tests__/e2e/dispatch.e2e.test.ts
11
+ */
12
+
13
+ import {
14
+ describe,
15
+ it,
16
+ expect,
17
+ beforeEach,
18
+ afterEach,
19
+ vi,
20
+ } from "vitest";
21
+ import * as path from "path";
22
+ import * as os from "os";
23
+ import * as fs from "fs";
24
+ import { bootV2, type MacroAgentSystemV2 } from "../../boot-v2.js";
25
+
26
+ // ─────────────────────────────────────────────────────────────────
27
+ // Configuration
28
+ // ─────────────────────────────────────────────────────────────────
29
+
30
+ const RUN_E2E = !!process.env.RUN_E2E_TESTS;
31
+ const describeFn = RUN_E2E ? describe : describe.skip;
32
+
33
+ // ─────────────────────────────────────────────────────────────────
34
+ // Mocks
35
+ // ─────────────────────────────────────────────────────────────────
36
+
37
+ vi.mock("acp-factory", () => ({
38
+ AgentFactory: {
39
+ spawn: vi.fn().mockResolvedValue({
40
+ createSession: vi.fn().mockResolvedValue({
41
+ id: `session-${Date.now()}`,
42
+ prompt: vi.fn().mockReturnValue({
43
+ [Symbol.asyncIterator]: () => ({
44
+ next: () => Promise.resolve({ done: true, value: undefined }),
45
+ }),
46
+ }),
47
+ forkWithFlush: vi.fn().mockResolvedValue({ id: `forked-${Date.now()}` }),
48
+ }),
49
+ loadSession: vi.fn().mockResolvedValue({ id: `loaded-${Date.now()}` }),
50
+ close: vi.fn().mockResolvedValue(undefined),
51
+ isRunning: vi.fn().mockReturnValue(true),
52
+ }),
53
+ },
54
+ }));
55
+
56
+ vi.mock("opentasks", () => ({
57
+ OpenTasksClient: vi.fn().mockImplementation(() => ({
58
+ connect: vi.fn().mockRejectedValue(new Error("No daemon")),
59
+ disconnect: vi.fn(),
60
+ query: vi.fn().mockResolvedValue({ items: [] }),
61
+ link: vi.fn().mockResolvedValue({ success: true }),
62
+ task: vi.fn().mockResolvedValue({ id: "t-1" }),
63
+ })),
64
+ }));
65
+
66
+ // ─────────────────────────────────────────────────────────────────
67
+ // Helpers
68
+ // ─────────────────────────────────────────────────────────────────
69
+
70
+ function createTestDir(): string {
71
+ const dir = path.join(
72
+ os.tmpdir(),
73
+ `dispatch-e2e-${Date.now()}-${Math.random().toString(36).slice(2)}`
74
+ );
75
+ fs.mkdirSync(dir, { recursive: true });
76
+ return dir;
77
+ }
78
+
79
+ // ─────────────────────────────────────────────────────────────────
80
+ // Tests
81
+ // ─────────────────────────────────────────────────────────────────
82
+
83
+ describeFn("Task Dispatch E2E", () => {
84
+ let system: MacroAgentSystemV2;
85
+ let testDir: string;
86
+
87
+ beforeEach(async () => {
88
+ testDir = createTestDir();
89
+ });
90
+
91
+ afterEach(async () => {
92
+ if (system) {
93
+ try {
94
+ const running = system.agentManager.list({ state: "running" } as any);
95
+ for (const agent of running) {
96
+ await system.agentManager.terminate(agent.id, "cancelled");
97
+ }
98
+ } catch { /* best effort */ }
99
+ await system.shutdown();
100
+ }
101
+ if (fs.existsSync(testDir)) {
102
+ fs.rmSync(testDir, { recursive: true, force: true });
103
+ }
104
+ });
105
+
106
+ // ── Boot with dispatch enabled ─────────────────────────────
107
+
108
+ describe("Boot", () => {
109
+ it("boots successfully with dispatch enabled", async () => {
110
+ system = await bootV2({
111
+ cwd: testDir,
112
+ baseDir: testDir,
113
+ inbox: { socketPath: path.join(testDir, "inbox.sock") },
114
+ dispatch: {
115
+ enabled: true,
116
+ pollIntervalMs: 60_000,
117
+ maxConcurrent: 3,
118
+ defaultRole: "worker",
119
+ },
120
+ });
121
+
122
+ expect(system).toBeDefined();
123
+ expect(system.taskDispatcher).toBeDefined();
124
+ expect(system.taskDispatcher!.running).toBe(true);
125
+ });
126
+
127
+ it("boots successfully with dispatch and reconcile enabled", async () => {
128
+ system = await bootV2({
129
+ cwd: testDir,
130
+ baseDir: testDir,
131
+ inbox: { socketPath: path.join(testDir, "inbox.sock") },
132
+ dispatch: {
133
+ enabled: true,
134
+ pollIntervalMs: 60_000,
135
+ maxConcurrent: 3,
136
+ reconcile: { enabled: true, intervalMs: 120_000 },
137
+ },
138
+ });
139
+
140
+ expect(system.taskDispatcher).toBeDefined();
141
+ expect(system.taskDispatcher!.running).toBe(true);
142
+ });
143
+
144
+ it("boots without dispatch when not enabled", async () => {
145
+ system = await bootV2({
146
+ cwd: testDir,
147
+ baseDir: testDir,
148
+ inbox: { socketPath: path.join(testDir, "inbox.sock") },
149
+ });
150
+
151
+ expect(system.taskDispatcher).toBeUndefined();
152
+ });
153
+
154
+ it("exposes tracker for observability", async () => {
155
+ system = await bootV2({
156
+ cwd: testDir,
157
+ baseDir: testDir,
158
+ inbox: { socketPath: path.join(testDir, "inbox.sock") },
159
+ dispatch: {
160
+ enabled: true,
161
+ pollIntervalMs: 60_000,
162
+ maxConcurrent: 3,
163
+ },
164
+ });
165
+
166
+ expect(system.taskDispatcher!.tracker).toBeDefined();
167
+ expect(system.taskDispatcher!.tracker.activeCount()).toBe(0);
168
+ expect(system.taskDispatcher!.tracker.listRetries()).toHaveLength(0);
169
+ });
170
+ });
171
+
172
+ // ── Dispatch via dispatchNow ───────────────────────────────
173
+
174
+ describe("Dispatch", () => {
175
+ it("dispatchNow triggers a dispatch cycle", async () => {
176
+ system = await bootV2({
177
+ cwd: testDir,
178
+ baseDir: testDir,
179
+ inbox: { socketPath: path.join(testDir, "inbox.sock") },
180
+ dispatch: {
181
+ enabled: true,
182
+ pollIntervalMs: 600_000,
183
+ maxConcurrent: 5,
184
+ },
185
+ });
186
+
187
+ // dispatchNow should not throw even with no ready tasks
188
+ await system.taskDispatcher!.dispatchNow();
189
+ expect(system.taskDispatcher!.tracker.activeCount()).toBe(0);
190
+ });
191
+
192
+ it("reconcileNow triggers a reconciliation cycle", async () => {
193
+ system = await bootV2({
194
+ cwd: testDir,
195
+ baseDir: testDir,
196
+ inbox: { socketPath: path.join(testDir, "inbox.sock") },
197
+ dispatch: {
198
+ enabled: true,
199
+ pollIntervalMs: 600_000,
200
+ maxConcurrent: 3,
201
+ reconcile: { enabled: true, intervalMs: 600_000 },
202
+ },
203
+ });
204
+
205
+ // reconcileNow should not throw with no active dispatches
206
+ await system.taskDispatcher!.reconcileNow();
207
+ });
208
+ });
209
+
210
+ // ── Event subscription ─────────────────────────────────────
211
+
212
+ describe("Events", () => {
213
+ it("emits poll events via onEvent", async () => {
214
+ system = await bootV2({
215
+ cwd: testDir,
216
+ baseDir: testDir,
217
+ inbox: { socketPath: path.join(testDir, "inbox.sock") },
218
+ dispatch: {
219
+ enabled: true,
220
+ pollIntervalMs: 600_000,
221
+ },
222
+ });
223
+
224
+ const events: any[] = [];
225
+ system.taskDispatcher!.onEvent((e) => events.push(e));
226
+
227
+ await system.taskDispatcher!.dispatchNow();
228
+
229
+ const poll = events.find((e) => e.type === "poll");
230
+ expect(poll).toBeDefined();
231
+ expect(poll.dispatched).toBe(0);
232
+ expect(poll.active).toBe(0);
233
+ });
234
+ });
235
+
236
+ // ── Shutdown ───────────────────────────────────────────────
237
+
238
+ describe("Shutdown", () => {
239
+ it("shuts down cleanly with dispatch enabled", async () => {
240
+ system = await bootV2({
241
+ cwd: testDir,
242
+ baseDir: testDir,
243
+ inbox: { socketPath: path.join(testDir, "inbox.sock") },
244
+ dispatch: {
245
+ enabled: true,
246
+ pollIntervalMs: 60_000,
247
+ maxConcurrent: 3,
248
+ reconcile: { enabled: true, intervalMs: 120_000 },
249
+ },
250
+ });
251
+
252
+ await system.shutdown();
253
+ system = undefined as any;
254
+ });
255
+ });
256
+
257
+ // ── Config Variations ──────────────────────────────────────
258
+
259
+ describe("Configuration", () => {
260
+ it("applies custom config values", async () => {
261
+ system = await bootV2({
262
+ cwd: testDir,
263
+ baseDir: testDir,
264
+ inbox: { socketPath: path.join(testDir, "inbox.sock") },
265
+ dispatch: {
266
+ enabled: true,
267
+ pollIntervalMs: 5_000,
268
+ maxConcurrent: 10,
269
+ defaultRole: "security-auditor",
270
+ tags: ["security", "audit"],
271
+ maxRetries: 5,
272
+ retryBaseDelayMs: 5_000,
273
+ retryMaxDelayMs: 120_000,
274
+ reconcile: { enabled: true, intervalMs: 30_000 },
275
+ eligibility: {
276
+ minPriority: 3,
277
+ excludeTags: ["wip"],
278
+ minScore: 0.5,
279
+ },
280
+ },
281
+ });
282
+
283
+ expect(system.taskDispatcher).toBeDefined();
284
+ expect(system.taskDispatcher!.running).toBe(true);
285
+ });
286
+
287
+ it("boots without reconcile when reconcile.enabled is false", async () => {
288
+ system = await bootV2({
289
+ cwd: testDir,
290
+ baseDir: testDir,
291
+ inbox: { socketPath: path.join(testDir, "inbox.sock") },
292
+ dispatch: {
293
+ enabled: true,
294
+ pollIntervalMs: 60_000,
295
+ reconcile: { enabled: false },
296
+ },
297
+ });
298
+
299
+ // Dispatcher should still work — just no reconcile timer
300
+ expect(system.taskDispatcher).toBeDefined();
301
+ expect(system.taskDispatcher!.running).toBe(true);
302
+ });
303
+ });
304
+
305
+ // ── MAP Event Bridge ───────────────────────────────────────
306
+
307
+ describe("MAP Event Bridge", () => {
308
+ it("dispatcher works without MAP sidecar", async () => {
309
+ // No MAP config → no sidecar → dispatch should still work
310
+ system = await bootV2({
311
+ cwd: testDir,
312
+ baseDir: testDir,
313
+ inbox: { socketPath: path.join(testDir, "inbox.sock") },
314
+ dispatch: {
315
+ enabled: true,
316
+ pollIntervalMs: 60_000,
317
+ },
318
+ // No map config
319
+ });
320
+
321
+ expect(system.taskDispatcher).toBeDefined();
322
+ expect(system.taskDispatcher!.running).toBe(true);
323
+
324
+ // dispatchNow should work without MAP bridge
325
+ await system.taskDispatcher!.dispatchNow();
326
+ });
327
+
328
+ it("onEvent subscription works independently of MAP", async () => {
329
+ system = await bootV2({
330
+ cwd: testDir,
331
+ baseDir: testDir,
332
+ inbox: { socketPath: path.join(testDir, "inbox.sock") },
333
+ dispatch: {
334
+ enabled: true,
335
+ pollIntervalMs: 60_000,
336
+ },
337
+ });
338
+
339
+ const events: any[] = [];
340
+ const unsub = system.taskDispatcher!.onEvent((e) => events.push(e));
341
+
342
+ await system.taskDispatcher!.dispatchNow();
343
+
344
+ expect(events.some((e) => e.type === "poll")).toBe(true);
345
+
346
+ // Unsubscribe stops events
347
+ const countBefore = events.length;
348
+ unsub();
349
+ await system.taskDispatcher!.dispatchNow();
350
+ expect(events.length).toBe(countBefore);
351
+ });
352
+
353
+ it("MAP sidecar with unreachable server does not break dispatch", async () => {
354
+ system = await bootV2({
355
+ cwd: testDir,
356
+ baseDir: testDir,
357
+ inbox: { socketPath: path.join(testDir, "inbox.sock") },
358
+ dispatch: {
359
+ enabled: true,
360
+ pollIntervalMs: 60_000,
361
+ },
362
+ map: {
363
+ enabled: true,
364
+ server: "ws://127.0.0.1:1", // Unreachable
365
+ scope: "swarm:test",
366
+ },
367
+ });
368
+
369
+ // Dispatch should work even though MAP sidecar failed to connect
370
+ expect(system.taskDispatcher).toBeDefined();
371
+ expect(system.taskDispatcher!.running).toBe(true);
372
+
373
+ await system.taskDispatcher!.dispatchNow();
374
+ });
375
+ });
376
+ });
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Self-driving team V3 end-to-end test.
3
+ *
4
+ * Exercises the complete path from a real team YAML on disk:
5
+ * .multiagent/teams/self-driving/team.yaml
6
+ * → TeamManagerV2.startTeam("self-driving")
7
+ * → extracts macro_agent.workspace block
8
+ * → constructs YamlDrivenTopology, installs on AgentManager
9
+ * → onTeamStart creates the team root stream
10
+ * → agents spawn with the correct V3 WorkspaceDecision per role
11
+ *
12
+ * Uses mocked acp-factory to avoid spawning real Claude Code — the purpose
13
+ * here is verifying the wiring, not agent behavior.
14
+ *
15
+ * REQUIRES: RUN_E2E_TESTS=true
16
+ */
17
+
18
+ import {
19
+ describe,
20
+ it,
21
+ expect,
22
+ beforeEach,
23
+ afterEach,
24
+ vi,
25
+ } from 'vitest';
26
+ import * as fs from 'fs';
27
+ import * as os from 'os';
28
+ import * as path from 'path';
29
+ import { execSync } from 'child_process';
30
+ import { bootV2, type MacroAgentSystemV2 } from '../../boot-v2.js';
31
+ import { GitCascadeAdapter } from '../../workspace/git-cascade-adapter.js';
32
+ import {
33
+ DefaultWorkspaceManager,
34
+ createWorkspaceManagerWithAdapter,
35
+ } from '../../workspace/workspace-manager.js';
36
+ import { TeamManagerV2 } from '../../teams/team-manager-v2.js';
37
+
38
+ const RUN_E2E = !!process.env.RUN_E2E_TESTS;
39
+ const describeFn = RUN_E2E ? describe : describe.skip;
40
+
41
+ // Mock acp-factory so we don't spawn real Claude Code
42
+ vi.mock('acp-factory', () => ({
43
+ AgentFactory: {
44
+ spawn: vi.fn().mockResolvedValue({
45
+ createSession: vi.fn().mockResolvedValue({
46
+ id: `session-${Date.now()}`,
47
+ prompt: vi.fn().mockReturnValue({
48
+ [Symbol.asyncIterator]: () => ({
49
+ next: () => Promise.resolve({ done: true, value: undefined }),
50
+ }),
51
+ }),
52
+ forkWithFlush: vi.fn().mockResolvedValue({ id: `forked-${Date.now()}` }),
53
+ }),
54
+ loadSession: vi.fn().mockResolvedValue({ id: `loaded-${Date.now()}` }),
55
+ close: vi.fn().mockResolvedValue(undefined),
56
+ isRunning: vi.fn().mockReturnValue(true),
57
+ }),
58
+ },
59
+ }));
60
+
61
+ vi.mock('opentasks', () => ({
62
+ OpenTasksClient: vi.fn().mockImplementation(() => ({
63
+ connect: vi.fn().mockRejectedValue(new Error('No daemon')),
64
+ disconnect: vi.fn(),
65
+ query: vi.fn().mockResolvedValue({ items: [] }),
66
+ link: vi.fn().mockResolvedValue({ success: true }),
67
+ task: vi.fn().mockResolvedValue({ id: 't-1' }),
68
+ })),
69
+ }));
70
+
71
+ describeFn('Self-driving team V3 auto-wire', () => {
72
+ let system: MacroAgentSystemV2;
73
+ let testDir: string;
74
+ let repoPath: string;
75
+ let adapter: GitCascadeAdapter;
76
+ let workspaceManager: DefaultWorkspaceManager;
77
+ let teamManager: TeamManagerV2;
78
+ const projectRoot = process.cwd();
79
+
80
+ beforeEach(async () => {
81
+ testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'self-driving-e2e-'));
82
+ repoPath = path.join(testDir, 'repo');
83
+ fs.mkdirSync(repoPath);
84
+
85
+ execSync('git init -b main', { cwd: repoPath, stdio: 'pipe' });
86
+ execSync('git config user.email "t@t.com"', { cwd: repoPath, stdio: 'pipe' });
87
+ execSync('git config user.name "T"', { cwd: repoPath, stdio: 'pipe' });
88
+ fs.writeFileSync(path.join(repoPath, 'README.md'), '# test');
89
+ execSync('git add .', { cwd: repoPath, stdio: 'pipe' });
90
+ execSync('git commit -m "init"', { cwd: repoPath, stdio: 'pipe' });
91
+
92
+ adapter = new GitCascadeAdapter({
93
+ enabled: true,
94
+ repoPath,
95
+ dbPath: path.join(testDir, 'gc.db'),
96
+ skipRecovery: true,
97
+ });
98
+ workspaceManager = createWorkspaceManagerWithAdapter(adapter, {
99
+ worktreeBaseDir: path.join(repoPath, '.worktrees'),
100
+ }) as DefaultWorkspaceManager;
101
+
102
+ system = await bootV2({
103
+ cwd: repoPath,
104
+ baseDir: testDir,
105
+ inbox: { socketPath: path.join(testDir, 'inbox.sock') },
106
+ workspaceManager,
107
+ });
108
+
109
+ teamManager = new TeamManagerV2({
110
+ agentManager: system.agentManager,
111
+ inboxAdapter: system.inboxAdapter,
112
+ tasksAdapter: system.tasksAdapter,
113
+ workspaceManager,
114
+ });
115
+ teamManager.install();
116
+ });
117
+
118
+ afterEach(async () => {
119
+ if (system) await system.shutdown();
120
+ if (workspaceManager) workspaceManager.close();
121
+ if (adapter) adapter.close();
122
+ if (fs.existsSync(testDir)) fs.rmSync(testDir, { recursive: true, force: true });
123
+ });
124
+
125
+ it('startTeam auto-wires YamlDrivenTopology from macro_agent.workspace', async () => {
126
+ // Start the real self-driving team template from .multiagent/teams/
127
+ await teamManager.startTeam('self-driving', projectRoot);
128
+
129
+ // Team root stream should have been created by onTeamStart
130
+ const streams = workspaceManager.listStreams();
131
+ const teamRoot = streams.find((s) => s.agentId === 'team:self-driving');
132
+ expect(teamRoot).toBeDefined();
133
+ expect(teamRoot!.name).toBe('self-driving');
134
+ });
135
+
136
+ it('spawning a grinder via the V3 path forks a stream off team root', async () => {
137
+ await teamManager.startTeam('self-driving', projectRoot);
138
+
139
+ const teamStreams = workspaceManager.listStreams();
140
+ const teamRoot = teamStreams.find((s) => s.agentId === 'team:self-driving');
141
+ expect(teamRoot).toBeDefined();
142
+
143
+ const grinder = await system.agentManager.spawn({
144
+ role: 'grinder',
145
+ task: 'do a thing',
146
+ });
147
+
148
+ const record = system.agentStore.getAgent(grinder.id);
149
+ expect(record?.workspace_path).toBeDefined();
150
+ expect(fs.existsSync(record!.workspace_path!)).toBe(true);
151
+
152
+ const grinderStream = workspaceManager
153
+ .listStreams()
154
+ .find((s) => s.agentId === grinder.id);
155
+ expect(grinderStream).toBeDefined();
156
+ expect(grinderStream?.parentStream).toBe(teamRoot!.id);
157
+ });
158
+
159
+ it('judge spawn returns workspace: none (no worktree allocated)', async () => {
160
+ await teamManager.startTeam('self-driving', projectRoot);
161
+
162
+ const judge = await system.agentManager.spawn({
163
+ role: 'judge',
164
+ task: 'review',
165
+ });
166
+
167
+ const record = system.agentStore.getAgent(judge.id);
168
+ // workspace: none → no workspace_path assigned
169
+ expect(record?.workspace_path).toBeFalsy();
170
+ });
171
+
172
+ it('planner attaches to team root stream (not a new fork)', async () => {
173
+ // Note: self-driving's topology already bootstraps the planner as the
174
+ // root agent; we spawn explicitly to verify the decision path.
175
+ await teamManager.startTeam('self-driving', projectRoot);
176
+
177
+ const plannerBeforeBootstrap = system.agentStore.listAgents({
178
+ state: 'running',
179
+ });
180
+
181
+ // The bootstrap planner is the "root" — already spawned.
182
+ const rootPlanner = plannerBeforeBootstrap.find((a) => a.role === 'planner');
183
+ expect(rootPlanner).toBeDefined();
184
+
185
+ // Verify its workspace_path (if allocated) is on the team root branch
186
+ if (rootPlanner?.workspace_path) {
187
+ expect(fs.existsSync(rootPlanner.workspace_path)).toBe(true);
188
+ }
189
+
190
+ // The team_root stream should still be the only top-level stream
191
+ // (planner did not fork a new stream — it attached)
192
+ const topLevelStreams = workspaceManager
193
+ .listStreams()
194
+ .filter((s) => !s.parentStream);
195
+ expect(topLevelStreams.length).toBe(1);
196
+ });
197
+ });