macro-agent 0.0.11 → 0.0.12

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 (408) hide show
  1. package/.macro-agent/teams/self-driving/prompts/grinder.md +27 -0
  2. package/.macro-agent/teams/self-driving/prompts/judge.md +27 -0
  3. package/.macro-agent/teams/self-driving/prompts/planner.md +33 -0
  4. package/.macro-agent/teams/self-driving/roles/grinder.yaml +17 -0
  5. package/.macro-agent/teams/self-driving/roles/judge.yaml +24 -0
  6. package/.macro-agent/teams/self-driving/roles/planner.yaml +18 -0
  7. package/.macro-agent/teams/self-driving/team.yaml +103 -0
  8. package/.macro-agent/teams/structured/prompts/developer.md +26 -0
  9. package/.macro-agent/teams/structured/prompts/lead.md +25 -0
  10. package/.macro-agent/teams/structured/prompts/reviewer.md +24 -0
  11. package/.macro-agent/teams/structured/roles/developer.yaml +12 -0
  12. package/.macro-agent/teams/structured/roles/lead.yaml +11 -0
  13. package/.macro-agent/teams/structured/roles/reviewer.yaml +19 -0
  14. package/.macro-agent/teams/structured/team.yaml +89 -0
  15. package/.sudocode/issues.jsonl +6 -0
  16. package/.sudocode/specs.jsonl +7 -0
  17. package/CLAUDE.md +110 -30
  18. package/README.md +60 -3
  19. package/dist/acp/macro-agent.d.ts +4 -0
  20. package/dist/acp/macro-agent.d.ts.map +1 -1
  21. package/dist/acp/macro-agent.js +50 -4
  22. package/dist/acp/macro-agent.js.map +1 -1
  23. package/dist/acp/session-mapper.d.ts +20 -1
  24. package/dist/acp/session-mapper.d.ts.map +1 -1
  25. package/dist/acp/session-mapper.js +90 -1
  26. package/dist/acp/session-mapper.js.map +1 -1
  27. package/dist/acp/types.d.ts +24 -1
  28. package/dist/acp/types.d.ts.map +1 -1
  29. package/dist/acp/types.js.map +1 -1
  30. package/dist/agent/agent-manager.d.ts +25 -1
  31. package/dist/agent/agent-manager.d.ts.map +1 -1
  32. package/dist/agent/agent-manager.js +93 -7
  33. package/dist/agent/agent-manager.js.map +1 -1
  34. package/dist/agent/types.d.ts +22 -0
  35. package/dist/agent/types.d.ts.map +1 -1
  36. package/dist/agent/types.js.map +1 -1
  37. package/dist/agent-detection/command-builder.d.ts +30 -0
  38. package/dist/agent-detection/command-builder.d.ts.map +1 -0
  39. package/dist/agent-detection/command-builder.js +71 -0
  40. package/dist/agent-detection/command-builder.js.map +1 -0
  41. package/dist/agent-detection/detector.d.ts +84 -0
  42. package/dist/agent-detection/detector.d.ts.map +1 -0
  43. package/dist/agent-detection/detector.js +240 -0
  44. package/dist/agent-detection/detector.js.map +1 -0
  45. package/dist/agent-detection/index.d.ts +12 -0
  46. package/dist/agent-detection/index.d.ts.map +1 -0
  47. package/dist/agent-detection/index.js +14 -0
  48. package/dist/agent-detection/index.js.map +1 -0
  49. package/dist/agent-detection/registry.d.ts +53 -0
  50. package/dist/agent-detection/registry.d.ts.map +1 -0
  51. package/dist/agent-detection/registry.js +177 -0
  52. package/dist/agent-detection/registry.js.map +1 -0
  53. package/dist/agent-detection/types.d.ts +121 -0
  54. package/dist/agent-detection/types.d.ts.map +1 -0
  55. package/dist/agent-detection/types.js +20 -0
  56. package/dist/agent-detection/types.js.map +1 -0
  57. package/dist/api/server.d.ts.map +1 -1
  58. package/dist/api/server.js +95 -0
  59. package/dist/api/server.js.map +1 -1
  60. package/dist/cli/index.js +29 -0
  61. package/dist/cli/index.js.map +1 -1
  62. package/dist/cli/mcp.js +38 -0
  63. package/dist/cli/mcp.js.map +1 -1
  64. package/dist/config/index.d.ts +2 -0
  65. package/dist/config/index.d.ts.map +1 -0
  66. package/dist/config/index.js +2 -0
  67. package/dist/config/index.js.map +1 -0
  68. package/dist/config/project-config.d.ts +46 -0
  69. package/dist/config/project-config.d.ts.map +1 -0
  70. package/dist/config/project-config.js +68 -0
  71. package/dist/config/project-config.js.map +1 -0
  72. package/dist/lifecycle/cascade.d.ts +1 -1
  73. package/dist/lifecycle/cascade.d.ts.map +1 -1
  74. package/dist/lifecycle/handlers/index.d.ts +4 -0
  75. package/dist/lifecycle/handlers/index.d.ts.map +1 -1
  76. package/dist/lifecycle/handlers/index.js +2 -0
  77. package/dist/lifecycle/handlers/index.js.map +1 -1
  78. package/dist/lifecycle/handlers/worker.d.ts +4 -0
  79. package/dist/lifecycle/handlers/worker.d.ts.map +1 -1
  80. package/dist/lifecycle/handlers/worker.js +35 -3
  81. package/dist/lifecycle/handlers/worker.js.map +1 -1
  82. package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
  83. package/dist/map/adapter/acp-over-map.js +32 -2
  84. package/dist/map/adapter/acp-over-map.js.map +1 -1
  85. package/dist/map/adapter/event-translator.d.ts.map +1 -1
  86. package/dist/map/adapter/event-translator.js +1 -0
  87. package/dist/map/adapter/event-translator.js.map +1 -1
  88. package/dist/map/adapter/extensions/agent-detection.d.ts +49 -0
  89. package/dist/map/adapter/extensions/agent-detection.d.ts.map +1 -0
  90. package/dist/map/adapter/extensions/agent-detection.js +91 -0
  91. package/dist/map/adapter/extensions/agent-detection.js.map +1 -0
  92. package/dist/map/adapter/extensions/index.d.ts +10 -1
  93. package/dist/map/adapter/extensions/index.d.ts.map +1 -1
  94. package/dist/map/adapter/extensions/index.js +39 -0
  95. package/dist/map/adapter/extensions/index.js.map +1 -1
  96. package/dist/map/adapter/extensions/resume.d.ts +47 -0
  97. package/dist/map/adapter/extensions/resume.d.ts.map +1 -0
  98. package/dist/map/adapter/extensions/resume.js +59 -0
  99. package/dist/map/adapter/extensions/resume.js.map +1 -0
  100. package/dist/map/adapter/extensions/workspace-files.d.ts +42 -0
  101. package/dist/map/adapter/extensions/workspace-files.d.ts.map +1 -0
  102. package/dist/map/adapter/extensions/workspace-files.js +338 -0
  103. package/dist/map/adapter/extensions/workspace-files.js.map +1 -0
  104. package/dist/mcp/mcp-server.d.ts +6 -0
  105. package/dist/mcp/mcp-server.d.ts.map +1 -1
  106. package/dist/mcp/mcp-server.js +45 -0
  107. package/dist/mcp/mcp-server.js.map +1 -1
  108. package/dist/mcp/tools/claim_task.d.ts +35 -0
  109. package/dist/mcp/tools/claim_task.d.ts.map +1 -0
  110. package/dist/mcp/tools/claim_task.js +58 -0
  111. package/dist/mcp/tools/claim_task.js.map +1 -0
  112. package/dist/mcp/tools/done.d.ts +11 -2
  113. package/dist/mcp/tools/done.d.ts.map +1 -1
  114. package/dist/mcp/tools/done.js +15 -10
  115. package/dist/mcp/tools/done.js.map +1 -1
  116. package/dist/mcp/tools/list_claimable_tasks.d.ts +38 -0
  117. package/dist/mcp/tools/list_claimable_tasks.d.ts.map +1 -0
  118. package/dist/mcp/tools/list_claimable_tasks.js +63 -0
  119. package/dist/mcp/tools/list_claimable_tasks.js.map +1 -0
  120. package/dist/mcp/tools/unclaim_task.d.ts +31 -0
  121. package/dist/mcp/tools/unclaim_task.d.ts.map +1 -0
  122. package/dist/mcp/tools/unclaim_task.js +47 -0
  123. package/dist/mcp/tools/unclaim_task.js.map +1 -0
  124. package/dist/metrics/index.d.ts +2 -0
  125. package/dist/metrics/index.d.ts.map +1 -0
  126. package/dist/metrics/index.js +2 -0
  127. package/dist/metrics/index.js.map +1 -0
  128. package/dist/metrics/metrics.d.ts +79 -0
  129. package/dist/metrics/metrics.d.ts.map +1 -0
  130. package/dist/metrics/metrics.js +166 -0
  131. package/dist/metrics/metrics.js.map +1 -0
  132. package/dist/roles/capabilities.d.ts +1 -0
  133. package/dist/roles/capabilities.d.ts.map +1 -1
  134. package/dist/roles/capabilities.js +3 -0
  135. package/dist/roles/capabilities.js.map +1 -1
  136. package/dist/roles/types.d.ts +1 -1
  137. package/dist/roles/types.d.ts.map +1 -1
  138. package/dist/router/message-router.d.ts +41 -0
  139. package/dist/router/message-router.d.ts.map +1 -1
  140. package/dist/router/message-router.js +136 -5
  141. package/dist/router/message-router.js.map +1 -1
  142. package/dist/store/event-store.d.ts +8 -1
  143. package/dist/store/event-store.d.ts.map +1 -1
  144. package/dist/store/event-store.js +120 -4
  145. package/dist/store/event-store.js.map +1 -1
  146. package/dist/store/types/agents.d.ts +1 -1
  147. package/dist/store/types/agents.d.ts.map +1 -1
  148. package/dist/store/types/events.d.ts +1 -1
  149. package/dist/store/types/events.d.ts.map +1 -1
  150. package/dist/store/types/events.js.map +1 -1
  151. package/dist/store/types/index.d.ts +1 -0
  152. package/dist/store/types/index.d.ts.map +1 -1
  153. package/dist/store/types/index.js +1 -0
  154. package/dist/store/types/index.js.map +1 -1
  155. package/dist/store/types/sessions.d.ts +44 -0
  156. package/dist/store/types/sessions.d.ts.map +1 -0
  157. package/dist/store/types/sessions.js +9 -0
  158. package/dist/store/types/sessions.js.map +1 -0
  159. package/dist/store/types/tasks.d.ts +2 -0
  160. package/dist/store/types/tasks.d.ts.map +1 -1
  161. package/dist/task/backend/memory.d.ts +4 -1
  162. package/dist/task/backend/memory.d.ts.map +1 -1
  163. package/dist/task/backend/memory.js +81 -0
  164. package/dist/task/backend/memory.js.map +1 -1
  165. package/dist/task/backend/types.d.ts +30 -0
  166. package/dist/task/backend/types.d.ts.map +1 -1
  167. package/dist/task/backend/types.js.map +1 -1
  168. package/dist/teams/index.d.ts +4 -0
  169. package/dist/teams/index.d.ts.map +1 -0
  170. package/dist/teams/index.js +4 -0
  171. package/dist/teams/index.js.map +1 -0
  172. package/dist/teams/team-loader.d.ts +20 -0
  173. package/dist/teams/team-loader.d.ts.map +1 -0
  174. package/dist/teams/team-loader.js +293 -0
  175. package/dist/teams/team-loader.js.map +1 -0
  176. package/dist/teams/team-runtime.d.ts +139 -0
  177. package/dist/teams/team-runtime.d.ts.map +1 -0
  178. package/dist/teams/team-runtime.js +613 -0
  179. package/dist/teams/team-runtime.js.map +1 -0
  180. package/dist/teams/types.d.ts +266 -0
  181. package/dist/teams/types.d.ts.map +1 -0
  182. package/dist/teams/types.js +20 -0
  183. package/dist/teams/types.js.map +1 -0
  184. package/dist/workspace/dataplane-adapter.d.ts +1 -1
  185. package/dist/workspace/dataplane-adapter.d.ts.map +1 -1
  186. package/dist/workspace/dataplane-adapter.js +1 -1
  187. package/dist/workspace/dataplane-adapter.js.map +1 -1
  188. package/dist/workspace/index.d.ts +1 -1
  189. package/dist/workspace/index.d.ts.map +1 -1
  190. package/dist/workspace/strategies/index.d.ts +6 -0
  191. package/dist/workspace/strategies/index.d.ts.map +1 -0
  192. package/dist/workspace/strategies/index.js +5 -0
  193. package/dist/workspace/strategies/index.js.map +1 -0
  194. package/dist/workspace/strategies/optimistic.d.ts +26 -0
  195. package/dist/workspace/strategies/optimistic.d.ts.map +1 -0
  196. package/dist/workspace/strategies/optimistic.js +121 -0
  197. package/dist/workspace/strategies/optimistic.js.map +1 -0
  198. package/dist/workspace/strategies/queue.d.ts +26 -0
  199. package/dist/workspace/strategies/queue.d.ts.map +1 -0
  200. package/dist/workspace/strategies/queue.js +67 -0
  201. package/dist/workspace/strategies/queue.js.map +1 -0
  202. package/dist/workspace/strategies/registry.d.ts +37 -0
  203. package/dist/workspace/strategies/registry.d.ts.map +1 -0
  204. package/dist/workspace/strategies/registry.js +63 -0
  205. package/dist/workspace/strategies/registry.js.map +1 -0
  206. package/dist/workspace/strategies/trunk.d.ts +20 -0
  207. package/dist/workspace/strategies/trunk.d.ts.map +1 -0
  208. package/dist/workspace/strategies/trunk.js +108 -0
  209. package/dist/workspace/strategies/trunk.js.map +1 -0
  210. package/dist/workspace/strategies/types.d.ts +104 -0
  211. package/dist/workspace/strategies/types.d.ts.map +1 -0
  212. package/dist/workspace/strategies/types.js +11 -0
  213. package/dist/workspace/strategies/types.js.map +1 -0
  214. package/dist/workspace/types.d.ts +1 -1
  215. package/dist/workspace/types.d.ts.map +1 -1
  216. package/dist/workspace/workspace-manager.d.ts +1 -1
  217. package/dist/workspace/workspace-manager.d.ts.map +1 -1
  218. package/docs/implementation-details.md +1127 -0
  219. package/docs/implementation-summary.md +448 -0
  220. package/docs/plan-self-driving-support.md +433 -0
  221. package/docs/spec-self-driving-support.md +462 -0
  222. package/docs/team-templates.md +860 -0
  223. package/docs/teams.md +233 -0
  224. package/package.json +4 -2
  225. package/src/acp/__tests__/integration.test.ts +161 -1
  226. package/src/acp/__tests__/macro-agent.test.ts +95 -0
  227. package/src/acp/__tests__/session-persistence.test.ts +276 -0
  228. package/src/acp/macro-agent.ts +79 -7
  229. package/src/acp/session-mapper.ts +108 -1
  230. package/src/acp/types.ts +33 -1
  231. package/src/agent/agent-manager.ts +158 -6
  232. package/src/agent/types.ts +27 -0
  233. package/src/agent-detection/__tests__/command-builder.test.ts +336 -0
  234. package/src/agent-detection/__tests__/detector.test.ts +768 -0
  235. package/src/agent-detection/__tests__/registry.test.ts +254 -0
  236. package/src/agent-detection/command-builder.ts +90 -0
  237. package/src/agent-detection/detector.ts +307 -0
  238. package/src/agent-detection/index.ts +36 -0
  239. package/src/agent-detection/registry.ts +200 -0
  240. package/src/agent-detection/types.ts +184 -0
  241. package/src/api/server.ts +110 -0
  242. package/src/cli/index.ts +44 -0
  243. package/src/cli/mcp.ts +47 -0
  244. package/src/config/index.ts +9 -0
  245. package/src/config/project-config.ts +107 -0
  246. package/src/lifecycle/cascade.ts +1 -1
  247. package/src/lifecycle/handlers/index.ts +8 -0
  248. package/src/lifecycle/handlers/worker.ts +48 -3
  249. package/src/map/adapter/__tests__/extensions.test.ts +359 -0
  250. package/src/map/adapter/__tests__/workspace-files.test.ts +673 -0
  251. package/src/map/adapter/acp-over-map.ts +45 -2
  252. package/src/map/adapter/event-translator.ts +1 -0
  253. package/src/map/adapter/extensions/agent-detection.ts +201 -0
  254. package/src/map/adapter/extensions/index.ts +63 -0
  255. package/src/map/adapter/extensions/resume.ts +114 -0
  256. package/src/map/adapter/extensions/workspace-files.ts +449 -0
  257. package/src/mcp/mcp-server.ts +67 -0
  258. package/src/mcp/tools/claim_task.ts +86 -0
  259. package/src/mcp/tools/done.ts +24 -10
  260. package/src/mcp/tools/list_claimable_tasks.ts +93 -0
  261. package/src/mcp/tools/unclaim_task.ts +71 -0
  262. package/src/metrics/index.ts +9 -0
  263. package/src/metrics/metrics.ts +280 -0
  264. package/src/roles/capabilities.ts +3 -0
  265. package/src/roles/types.ts +2 -1
  266. package/src/router/__tests__/message-router.test.ts +561 -0
  267. package/src/router/message-router.ts +223 -6
  268. package/src/store/event-store.ts +151 -3
  269. package/src/store/types/agents.ts +1 -1
  270. package/src/store/types/events.ts +2 -1
  271. package/src/store/types/index.ts +1 -0
  272. package/src/store/types/sessions.ts +53 -0
  273. package/src/store/types/tasks.ts +3 -0
  274. package/src/task/backend/memory.ts +116 -0
  275. package/src/task/backend/types.ts +43 -0
  276. package/src/teams/__tests__/cross-subsystem.integration.test.ts +983 -0
  277. package/src/teams/__tests__/e2e/team-runtime.e2e.test.ts +553 -0
  278. package/src/teams/__tests__/team-system.test.ts +1280 -0
  279. package/src/teams/index.ts +13 -0
  280. package/src/teams/team-loader.ts +434 -0
  281. package/src/teams/team-runtime.ts +727 -0
  282. package/src/teams/types.ts +377 -0
  283. package/src/workspace/dataplane-adapter.ts +1 -1
  284. package/src/workspace/index.ts +1 -1
  285. package/src/workspace/strategies/index.ts +18 -0
  286. package/src/workspace/strategies/optimistic.ts +136 -0
  287. package/src/workspace/strategies/queue.ts +81 -0
  288. package/src/workspace/strategies/registry.ts +89 -0
  289. package/src/workspace/strategies/trunk.ts +123 -0
  290. package/src/workspace/strategies/types.ts +145 -0
  291. package/src/workspace/types.ts +1 -1
  292. package/src/workspace/workspace-manager.ts +1 -1
  293. package/.claude/settings.local.json +0 -59
  294. package/dist/map/utils/address-translation.d.ts +0 -99
  295. package/dist/map/utils/address-translation.d.ts.map +0 -1
  296. package/dist/map/utils/address-translation.js +0 -285
  297. package/dist/map/utils/address-translation.js.map +0 -1
  298. package/dist/map/utils/index.d.ts +0 -7
  299. package/dist/map/utils/index.d.ts.map +0 -1
  300. package/dist/map/utils/index.js +0 -7
  301. package/dist/map/utils/index.js.map +0 -1
  302. package/references/acp-factory-ref/CHANGELOG.md +0 -33
  303. package/references/acp-factory-ref/LICENSE +0 -21
  304. package/references/acp-factory-ref/README.md +0 -341
  305. package/references/acp-factory-ref/package-lock.json +0 -3102
  306. package/references/acp-factory-ref/package.json +0 -96
  307. package/references/acp-factory-ref/python/CHANGELOG.md +0 -33
  308. package/references/acp-factory-ref/python/LICENSE +0 -21
  309. package/references/acp-factory-ref/python/Makefile +0 -57
  310. package/references/acp-factory-ref/python/README.md +0 -253
  311. package/references/acp-factory-ref/python/pyproject.toml +0 -73
  312. package/references/acp-factory-ref/python/tests/__init__.py +0 -0
  313. package/references/acp-factory-ref/python/tests/e2e/__init__.py +0 -1
  314. package/references/acp-factory-ref/python/tests/e2e/test_codex_e2e.py +0 -349
  315. package/references/acp-factory-ref/python/tests/e2e/test_gemini_e2e.py +0 -165
  316. package/references/acp-factory-ref/python/tests/e2e/test_opencode_e2e.py +0 -296
  317. package/references/acp-factory-ref/python/tests/test_client_handler.py +0 -543
  318. package/references/acp-factory-ref/python/tests/test_pushable.py +0 -199
  319. package/references/claude-code-acp/.github/workflows/ci.yml +0 -45
  320. package/references/claude-code-acp/.github/workflows/publish.yml +0 -34
  321. package/references/claude-code-acp/.prettierrc.json +0 -4
  322. package/references/claude-code-acp/CHANGELOG.md +0 -249
  323. package/references/claude-code-acp/LICENSE +0 -222
  324. package/references/claude-code-acp/README.md +0 -53
  325. package/references/claude-code-acp/docs/RELEASES.md +0 -24
  326. package/references/claude-code-acp/eslint.config.js +0 -48
  327. package/references/claude-code-acp/package-lock.json +0 -4570
  328. package/references/claude-code-acp/package.json +0 -88
  329. package/references/claude-code-acp/scripts/release.sh +0 -119
  330. package/references/claude-code-acp/src/acp-agent.ts +0 -2065
  331. package/references/claude-code-acp/src/index.ts +0 -26
  332. package/references/claude-code-acp/src/lib.ts +0 -38
  333. package/references/claude-code-acp/src/mcp-server.ts +0 -911
  334. package/references/claude-code-acp/src/settings.ts +0 -522
  335. package/references/claude-code-acp/src/tests/.claude/commands/quick-math.md +0 -5
  336. package/references/claude-code-acp/src/tests/.claude/commands/say-hello.md +0 -6
  337. package/references/claude-code-acp/src/tests/acp-agent-fork.test.ts +0 -479
  338. package/references/claude-code-acp/src/tests/acp-agent.test.ts +0 -1502
  339. package/references/claude-code-acp/src/tests/extract-lines.test.ts +0 -103
  340. package/references/claude-code-acp/src/tests/fork-session.test.ts +0 -335
  341. package/references/claude-code-acp/src/tests/replace-and-calculate-location.test.ts +0 -334
  342. package/references/claude-code-acp/src/tests/settings.test.ts +0 -617
  343. package/references/claude-code-acp/src/tests/skills-options.test.ts +0 -187
  344. package/references/claude-code-acp/src/tests/tools.test.ts +0 -318
  345. package/references/claude-code-acp/src/tests/typescript-declarations.test.ts +0 -558
  346. package/references/claude-code-acp/src/tools.ts +0 -819
  347. package/references/claude-code-acp/src/utils.ts +0 -171
  348. package/references/claude-code-acp/tsconfig.json +0 -18
  349. package/references/claude-code-acp/vitest.config.ts +0 -19
  350. package/references/multi-agent-protocol/.sudocode/issues.jsonl +0 -111
  351. package/references/multi-agent-protocol/.sudocode/specs.jsonl +0 -13
  352. package/references/multi-agent-protocol/LICENSE +0 -21
  353. package/references/multi-agent-protocol/README.md +0 -113
  354. package/references/multi-agent-protocol/docs/00-design-specification.md +0 -496
  355. package/references/multi-agent-protocol/docs/01-open-questions.md +0 -1050
  356. package/references/multi-agent-protocol/docs/02-wire-protocol.md +0 -296
  357. package/references/multi-agent-protocol/docs/03-streaming-semantics.md +0 -252
  358. package/references/multi-agent-protocol/docs/04-error-handling.md +0 -231
  359. package/references/multi-agent-protocol/docs/05-connection-model.md +0 -244
  360. package/references/multi-agent-protocol/docs/06-visibility-permissions.md +0 -243
  361. package/references/multi-agent-protocol/docs/07-federation.md +0 -259
  362. package/references/multi-agent-protocol/docs/08-macro-agent-migration.md +0 -253
  363. package/references/multi-agent-protocol/docs/09-authentication.md +0 -680
  364. package/references/multi-agent-protocol/docs/10-mail-protocol.md +0 -553
  365. package/references/multi-agent-protocol/docs/agent-iam-integration.md +0 -877
  366. package/references/multi-agent-protocol/docs/agentic-mesh-integration-draft.md +0 -459
  367. package/references/multi-agent-protocol/docs/git-transport-draft.md +0 -251
  368. package/references/multi-agent-protocol/docs-site/Gemfile +0 -22
  369. package/references/multi-agent-protocol/docs-site/README.md +0 -82
  370. package/references/multi-agent-protocol/docs-site/_config.yml +0 -91
  371. package/references/multi-agent-protocol/docs-site/_includes/head_custom.html +0 -20
  372. package/references/multi-agent-protocol/docs-site/_sass/color_schemes/map.scss +0 -42
  373. package/references/multi-agent-protocol/docs-site/_sass/custom/custom.scss +0 -34
  374. package/references/multi-agent-protocol/docs-site/examples/full-integration.md +0 -510
  375. package/references/multi-agent-protocol/docs-site/examples/index.md +0 -138
  376. package/references/multi-agent-protocol/docs-site/examples/simple-chat.md +0 -282
  377. package/references/multi-agent-protocol/docs-site/examples/task-queue.md +0 -399
  378. package/references/multi-agent-protocol/docs-site/getting-started/index.md +0 -98
  379. package/references/multi-agent-protocol/docs-site/getting-started/installation.md +0 -219
  380. package/references/multi-agent-protocol/docs-site/getting-started/overview.md +0 -172
  381. package/references/multi-agent-protocol/docs-site/getting-started/quickstart.md +0 -237
  382. package/references/multi-agent-protocol/docs-site/index.md +0 -136
  383. package/references/multi-agent-protocol/docs-site/protocol/authentication.md +0 -391
  384. package/references/multi-agent-protocol/docs-site/protocol/connection-model.md +0 -376
  385. package/references/multi-agent-protocol/docs-site/protocol/design.md +0 -284
  386. package/references/multi-agent-protocol/docs-site/protocol/error-handling.md +0 -312
  387. package/references/multi-agent-protocol/docs-site/protocol/federation.md +0 -449
  388. package/references/multi-agent-protocol/docs-site/protocol/index.md +0 -129
  389. package/references/multi-agent-protocol/docs-site/protocol/permissions.md +0 -398
  390. package/references/multi-agent-protocol/docs-site/protocol/streaming.md +0 -353
  391. package/references/multi-agent-protocol/docs-site/protocol/wire-protocol.md +0 -369
  392. package/references/multi-agent-protocol/docs-site/sdk/api/agent.md +0 -357
  393. package/references/multi-agent-protocol/docs-site/sdk/api/client.md +0 -380
  394. package/references/multi-agent-protocol/docs-site/sdk/api/index.md +0 -62
  395. package/references/multi-agent-protocol/docs-site/sdk/api/server.md +0 -453
  396. package/references/multi-agent-protocol/docs-site/sdk/api/types.md +0 -468
  397. package/references/multi-agent-protocol/docs-site/sdk/guides/agent.md +0 -375
  398. package/references/multi-agent-protocol/docs-site/sdk/guides/authentication.md +0 -405
  399. package/references/multi-agent-protocol/docs-site/sdk/guides/client.md +0 -352
  400. package/references/multi-agent-protocol/docs-site/sdk/guides/index.md +0 -89
  401. package/references/multi-agent-protocol/docs-site/sdk/guides/server.md +0 -360
  402. package/references/multi-agent-protocol/docs-site/sdk/guides/testing.md +0 -446
  403. package/references/multi-agent-protocol/docs-site/sdk/guides/transports.md +0 -363
  404. package/references/multi-agent-protocol/docs-site/sdk/index.md +0 -206
  405. package/references/multi-agent-protocol/package-lock.json +0 -3886
  406. package/references/multi-agent-protocol/package.json +0 -56
  407. package/references/multi-agent-protocol/schema/meta.json +0 -467
  408. package/references/multi-agent-protocol/schema/schema.json +0 -2558
@@ -0,0 +1,1280 @@
1
+ /**
2
+ * Team System Tests
3
+ *
4
+ * Tests loading team templates, runtime initialization, bootstrap,
5
+ * and integration between team subsystems (roles, communication,
6
+ * strategies, task modes).
7
+ */
8
+
9
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
10
+ import * as path from "path";
11
+ import { loadTeam } from "../team-loader.js";
12
+ import { TeamRuntime, type TeamServices } from "../team-runtime.js";
13
+ import { DefaultRoleRegistry } from "../../roles/registry.js";
14
+ import type { RoleDefinition } from "../../roles/types.js";
15
+ import type { AgentManager, SpawnInterceptor } from "../../agent/agent-manager.js";
16
+ import type { MessageRouter } from "../../router/message-router.js";
17
+ import type { EventStore } from "../../store/event-store.js";
18
+ import type { SpawnAgentOptions } from "../../agent/types.js";
19
+ import type { AgentId, Event } from "../../store/types/index.js";
20
+
21
+ // =============================================================================
22
+ // Helpers
23
+ // =============================================================================
24
+
25
+ const PROJECT_ROOT = path.resolve(import.meta.dirname, "../../..");
26
+
27
+ function createMockEventStore(): EventStore {
28
+ const events: Event[] = [];
29
+ return {
30
+ emit: vi.fn((input: Record<string, unknown>) => {
31
+ const event = {
32
+ id: `evt_${events.length}`,
33
+ type: input.type,
34
+ timestamp: Date.now(),
35
+ source: input.source,
36
+ target: input.target,
37
+ payload: input.payload,
38
+ } as unknown as Event;
39
+ events.push(event);
40
+ return event;
41
+ }),
42
+ persist: vi.fn().mockResolvedValue(undefined),
43
+ close: vi.fn().mockResolvedValue(undefined),
44
+ query: vi.fn().mockReturnValue([]),
45
+ getAgent: vi.fn().mockReturnValue(null),
46
+ getTask: vi.fn().mockReturnValue(null),
47
+ listAgents: vi.fn().mockReturnValue([]),
48
+ onAgentChange: vi.fn(),
49
+ onTaskChange: vi.fn(),
50
+ instanceId: "test-instance",
51
+ _events: events,
52
+ } as unknown as EventStore & { _events: Event[] };
53
+ }
54
+
55
+ function createMockMessageRouter(): MessageRouter {
56
+ return {
57
+ sendToAddress: vi.fn().mockResolvedValue({ delivered: true }),
58
+ emitStatus: vi.fn(),
59
+ getMessages: vi.fn().mockReturnValue([]),
60
+ subscribe: vi.fn(),
61
+ unsubscribe: vi.fn(),
62
+ getSubscriptions: vi.fn().mockReturnValue([]),
63
+ setupDefaultSubscriptions: vi.fn(),
64
+ setSignalFilter: vi.fn(),
65
+ setEmissionValidator: vi.fn(),
66
+ } as unknown as MessageRouter;
67
+ }
68
+
69
+ let spawnCounter = 0;
70
+ let capturedInterceptor: SpawnInterceptor | null = null;
71
+ let interceptedSpawnOptions: SpawnAgentOptions[] = [];
72
+
73
+ function createMockAgentManager(roleRegistry: DefaultRoleRegistry): AgentManager {
74
+ capturedInterceptor = null;
75
+ spawnCounter = 0;
76
+ interceptedSpawnOptions = [];
77
+
78
+ return {
79
+ spawn: vi.fn(async (options: SpawnAgentOptions) => {
80
+ // Apply interceptor if set and record the intercepted options
81
+ const opts = capturedInterceptor ? capturedInterceptor(options) : options;
82
+ interceptedSpawnOptions.push(opts);
83
+ const id = `agent_${spawnCounter++}`;
84
+ return {
85
+ id,
86
+ session_id: `session_${id}`,
87
+ task: opts.task ?? "test",
88
+ state: "running" as const,
89
+ created_at: Date.now(),
90
+ parent: opts.parent ?? null,
91
+ role: opts.role,
92
+ config: opts.config,
93
+ _spawnOptions: opts,
94
+ };
95
+ }),
96
+ terminate: vi.fn().mockResolvedValue(undefined),
97
+ get: vi.fn().mockReturnValue(null),
98
+ list: vi.fn().mockReturnValue([]),
99
+ getChildren: vi.fn().mockReturnValue([]),
100
+ getHierarchy: vi.fn().mockReturnValue(null),
101
+ getSession: vi.fn().mockReturnValue(null),
102
+ hasActiveSession: vi.fn().mockReturnValue(false),
103
+ setSpawnInterceptor: vi.fn((interceptor: SpawnInterceptor | null) => {
104
+ capturedInterceptor = interceptor;
105
+ }),
106
+ getRoleRegistry: vi.fn(() => roleRegistry),
107
+ onLifecycleEvent: vi.fn(() => vi.fn()),
108
+ continueAgent: vi.fn().mockResolvedValue({ id: "continued_0" }),
109
+ close: vi.fn().mockResolvedValue(undefined),
110
+ getOrCreateHeadManager: vi.fn(),
111
+ prompt: vi.fn(),
112
+ isPrompting: vi.fn().mockReturnValue(false),
113
+ } as unknown as AgentManager;
114
+ }
115
+
116
+ // =============================================================================
117
+ // Tests: Team Loading
118
+ // =============================================================================
119
+
120
+ describe("Team Template Loading", () => {
121
+ let roleRegistry: DefaultRoleRegistry;
122
+
123
+ beforeEach(() => {
124
+ roleRegistry = new DefaultRoleRegistry();
125
+ });
126
+
127
+ it("loads self-driving team template", async () => {
128
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
129
+
130
+ expect(manifest.name).toBe("self-driving");
131
+ expect(manifest.version).toBe(1);
132
+ expect(manifest.roles).toEqual(["planner", "grinder", "judge"]);
133
+ });
134
+
135
+ it("resolves self-driving roles with correct base roles", async () => {
136
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
137
+
138
+ const planner = manifest._resolvedRoles.get("planner");
139
+ expect(planner).toBeDefined();
140
+ expect(planner!.baseRole).toBe("coordinator");
141
+
142
+ const grinder = manifest._resolvedRoles.get("grinder");
143
+ expect(grinder).toBeDefined();
144
+ expect(grinder!.baseRole).toBe("worker");
145
+
146
+ const judge = manifest._resolvedRoles.get("judge");
147
+ expect(judge).toBeDefined();
148
+ expect(judge!.baseRole).toBe("monitor");
149
+ });
150
+
151
+ it("resolves capability additions and removals", async () => {
152
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
153
+
154
+ const planner = manifest._resolvedRoles.get("planner");
155
+ expect(planner!.capabilities).toContain("task.claim");
156
+ expect(planner!.capabilities).not.toContain("agent.spawn.integrator");
157
+ expect(planner!.capabilities).not.toContain("agent.spawn.monitor");
158
+
159
+ const grinder = manifest._resolvedRoles.get("grinder");
160
+ expect(grinder!.capabilities).toContain("task.claim");
161
+ expect(grinder!.capabilities).toContain("git.push");
162
+ });
163
+
164
+ it("translates spawn_rules into capabilities", async () => {
165
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
166
+
167
+ const planner = manifest._resolvedRoles.get("planner");
168
+ expect(planner!.capabilities).toContain("agent.spawn.grinder");
169
+ expect(planner!.capabilities).toContain("agent.spawn.planner");
170
+
171
+ // Judge and grinder have no spawn rules → no spawn capabilities
172
+ const judge = manifest._resolvedRoles.get("judge");
173
+ expect(judge!.capabilities.filter((c) => c.startsWith("agent.spawn."))).toEqual([]);
174
+ });
175
+
176
+ it("loads prompt files", async () => {
177
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
178
+
179
+ expect(manifest._loadedPrompts.has("prompts/planner.md")).toBe(true);
180
+ expect(manifest._loadedPrompts.has("prompts/grinder.md")).toBe(true);
181
+ expect(manifest._loadedPrompts.has("prompts/judge.md")).toBe(true);
182
+
183
+ const plannerPrompt = manifest._loadedPrompts.get("prompts/planner.md")!;
184
+ expect(plannerPrompt).toContain("Planner");
185
+ });
186
+
187
+ it("parses macro_agent extensions", async () => {
188
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
189
+
190
+ expect(manifest.macro_agent.task_assignment?.mode).toBe("pull");
191
+ expect(manifest.macro_agent.integration?.strategy).toBe("trunk");
192
+ expect(manifest.macro_agent.lifecycle?.continuations?.enabled).toBe(true);
193
+ expect(manifest.macro_agent.lifecycle?.scaling?.max_workers).toBe(20);
194
+ });
195
+
196
+ it("validates communication topology", async () => {
197
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
198
+
199
+ expect(manifest.communication.channels).toBeDefined();
200
+ expect(Object.keys(manifest.communication.channels!)).toContain("task_updates");
201
+ expect(Object.keys(manifest.communication.channels!)).toContain("work_coordination");
202
+ expect(Object.keys(manifest.communication.channels!)).toContain("health");
203
+
204
+ expect(manifest.communication.subscriptions?.planner).toBeDefined();
205
+ expect(manifest.communication.emissions?.planner).toContain("TASK_CREATED");
206
+ });
207
+
208
+ it("loads structured team template", async () => {
209
+ const manifest = await loadTeam("structured", roleRegistry, PROJECT_ROOT);
210
+
211
+ expect(manifest.name).toBe("structured");
212
+ expect(manifest.roles).toEqual(["lead", "developer", "reviewer"]);
213
+ expect(manifest.macro_agent.task_assignment?.mode).toBe("push");
214
+ expect(manifest.macro_agent.integration?.strategy).toBe("queue");
215
+ });
216
+
217
+ it("resolves structured roles", async () => {
218
+ const manifest = await loadTeam("structured", roleRegistry, PROJECT_ROOT);
219
+
220
+ const lead = manifest._resolvedRoles.get("lead");
221
+ expect(lead!.baseRole).toBe("coordinator");
222
+
223
+ const developer = manifest._resolvedRoles.get("developer");
224
+ expect(developer!.baseRole).toBe("worker");
225
+
226
+ const reviewer = manifest._resolvedRoles.get("reviewer");
227
+ expect(reviewer!.baseRole).toBe("monitor");
228
+ expect(reviewer!.capabilities).toContain("exec.build");
229
+ expect(reviewer!.capabilities).toContain("exec.test");
230
+ });
231
+ });
232
+
233
+ // =============================================================================
234
+ // Tests: TeamRuntime
235
+ // =============================================================================
236
+
237
+ describe("TeamRuntime", () => {
238
+ let roleRegistry: DefaultRoleRegistry;
239
+ let agentManager: AgentManager;
240
+ let messageRouter: MessageRouter;
241
+ let eventStore: EventStore & { _events: Event[] };
242
+ let services: TeamServices;
243
+
244
+ beforeEach(() => {
245
+ roleRegistry = new DefaultRoleRegistry();
246
+ eventStore = createMockEventStore() as EventStore & { _events: Event[] };
247
+ messageRouter = createMockMessageRouter();
248
+ agentManager = createMockAgentManager(roleRegistry);
249
+ services = { agentManager, messageRouter, eventStore };
250
+ });
251
+
252
+ describe("initialize()", () => {
253
+ it("registers team roles in the role registry", async () => {
254
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
255
+ const runtime = new TeamRuntime(manifest, services);
256
+
257
+ await runtime.initialize();
258
+
259
+ // Roles should be registered in the registry
260
+ expect(roleRegistry.getRole("planner")).toBeDefined();
261
+ expect(roleRegistry.getRole("grinder")).toBeDefined();
262
+ expect(roleRegistry.getRole("judge")).toBeDefined();
263
+ });
264
+
265
+ it("emits team_config event to EventStore", async () => {
266
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
267
+ const runtime = new TeamRuntime(manifest, services);
268
+
269
+ await runtime.initialize();
270
+
271
+ expect(eventStore.emit).toHaveBeenCalledWith(
272
+ expect.objectContaining({
273
+ type: "status",
274
+ payload: expect.objectContaining({
275
+ status_type: "discovery",
276
+ team_config: expect.objectContaining({
277
+ teamName: "self-driving",
278
+ strategy: "trunk",
279
+ taskMode: "pull",
280
+ }),
281
+ }),
282
+ })
283
+ );
284
+ });
285
+
286
+ it("sets spawn interceptor on agent manager", async () => {
287
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
288
+ const runtime = new TeamRuntime(manifest, services);
289
+
290
+ await runtime.initialize();
291
+
292
+ expect(agentManager.setSpawnInterceptor).toHaveBeenCalledWith(
293
+ expect.any(Function)
294
+ );
295
+ });
296
+ });
297
+
298
+ describe("bootstrap()", () => {
299
+ it("spawns root and companion agents", async () => {
300
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
301
+ const runtime = new TeamRuntime(manifest, services);
302
+
303
+ await runtime.initialize();
304
+ const result = await runtime.bootstrap();
305
+
306
+ expect(result.rootId).toBeDefined();
307
+ expect(result.companionIds).toHaveLength(1); // judge is the companion
308
+
309
+ // Two spawn calls: planner (root) + judge (companion)
310
+ expect(agentManager.spawn).toHaveBeenCalledTimes(2);
311
+ });
312
+
313
+ it("spawns root with correct role and model", async () => {
314
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
315
+ const runtime = new TeamRuntime(manifest, services);
316
+
317
+ await runtime.initialize();
318
+ await runtime.bootstrap();
319
+
320
+ const spawnCalls = vi.mocked(agentManager.spawn).mock.calls;
321
+ const rootCall = spawnCalls[0][0];
322
+
323
+ expect(rootCall.role).toBe("planner");
324
+ expect(rootCall.config?.model).toBe("sonnet");
325
+ expect(rootCall.parent).toBeNull();
326
+ });
327
+
328
+ it("spawns companion as peer (not child)", async () => {
329
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
330
+ const runtime = new TeamRuntime(manifest, services);
331
+
332
+ await runtime.initialize();
333
+ await runtime.bootstrap();
334
+
335
+ const spawnCalls = vi.mocked(agentManager.spawn).mock.calls;
336
+ const companionCall = spawnCalls[1][0];
337
+
338
+ expect(companionCall.role).toBe("judge");
339
+ expect(companionCall.config?.model).toBe("haiku");
340
+ expect(companionCall.parent).toBeNull();
341
+ });
342
+
343
+ it("wires config-driven peer subscriptions from routing.peers", async () => {
344
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
345
+ const runtime = new TeamRuntime(manifest, services);
346
+
347
+ await runtime.initialize();
348
+ const result = await runtime.bootstrap();
349
+
350
+ // self-driving has 2 peer entries: judge→planner + planner→judge, both via: "direct"
351
+ // Each creates one directional subtree subscription
352
+ expect(messageRouter.subscribe).toHaveBeenCalledTimes(2);
353
+
354
+ // judge (agent_1) subscribes to planner's (agent_0) subtree
355
+ expect(messageRouter.subscribe).toHaveBeenCalledWith(
356
+ result.companionIds[0], // judge = agent_1
357
+ { type: "subtree", target: result.rootId } // planner = agent_0
358
+ );
359
+
360
+ // planner (agent_0) subscribes to judge's (agent_1) subtree
361
+ expect(messageRouter.subscribe).toHaveBeenCalledWith(
362
+ result.rootId, // planner = agent_0
363
+ { type: "subtree", target: result.companionIds[0] } // judge = agent_1
364
+ );
365
+ });
366
+
367
+ it("injects interaction patterns for pull mode", async () => {
368
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
369
+ const runtime = new TeamRuntime(manifest, services);
370
+
371
+ await runtime.initialize();
372
+ await runtime.bootstrap();
373
+
374
+ const spawnCalls = vi.mocked(agentManager.spawn).mock.calls;
375
+ const rootCall = spawnCalls[0][0];
376
+
377
+ expect(rootCall.interactionPatterns).toBeDefined();
378
+ expect(rootCall.interactionPatterns!.length).toBeGreaterThan(0);
379
+ expect(rootCall.interactionPatterns!.some((p) => p.includes("PULL mode"))).toBe(true);
380
+ expect(rootCall.interactionPatterns!.some((p) => p.includes("trunk"))).toBe(true);
381
+ });
382
+
383
+ it("provides team prompts to spawned agents", async () => {
384
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
385
+ const runtime = new TeamRuntime(manifest, services);
386
+
387
+ await runtime.initialize();
388
+ await runtime.bootstrap();
389
+
390
+ const spawnCalls = vi.mocked(agentManager.spawn).mock.calls;
391
+ const rootCall = spawnCalls[0][0];
392
+
393
+ expect(rootCall.customPrompt).toBeDefined();
394
+ expect(rootCall.customPrompt).toContain("Planner");
395
+ });
396
+ });
397
+
398
+ describe("spawn interceptor", () => {
399
+ it("injects team topics into spawned agent options", async () => {
400
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
401
+ const runtime = new TeamRuntime(manifest, services);
402
+
403
+ await runtime.initialize();
404
+ await runtime.bootstrap();
405
+
406
+ // Now spawn a grinder through the interceptor
407
+ await agentManager.spawn({
408
+ task: "test grinder task",
409
+ role: "grinder",
410
+ parent: "agent_0",
411
+ });
412
+
413
+ // Check the intercepted options (not the original args)
414
+ const lastOpts = interceptedSpawnOptions.at(-1)!;
415
+ // Interceptor should have added topics for grinder subscriptions
416
+ expect(lastOpts.topics).toBeDefined();
417
+ expect(lastOpts.topics).toContain("work_coordination");
418
+ });
419
+
420
+ it("injects team environment variables", async () => {
421
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
422
+ const runtime = new TeamRuntime(manifest, services);
423
+
424
+ await runtime.initialize();
425
+ await runtime.bootstrap();
426
+
427
+ // Spawn a grinder
428
+ await agentManager.spawn({
429
+ task: "test task",
430
+ role: "grinder",
431
+ parent: "agent_0",
432
+ });
433
+
434
+ const lastOpts = interceptedSpawnOptions.at(-1)!;
435
+ expect(lastOpts.config?.env?.MACRO_TEAM_NAME).toBe("self-driving");
436
+ expect(lastOpts.config?.env?.MACRO_TASK_MODE).toBe("pull");
437
+ expect(lastOpts.config?.env?.MACRO_INTEGRATION_STRATEGY).toBe("trunk");
438
+ });
439
+
440
+ it("does not override caller-provided options", async () => {
441
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
442
+ const runtime = new TeamRuntime(manifest, services);
443
+
444
+ await runtime.initialize();
445
+ await runtime.bootstrap();
446
+
447
+ const customPrompt = "My custom prompt";
448
+ await agentManager.spawn({
449
+ task: "test task",
450
+ role: "grinder",
451
+ parent: "agent_0",
452
+ customPrompt,
453
+ });
454
+
455
+ const lastOpts = interceptedSpawnOptions.at(-1)!;
456
+ expect(lastOpts.customPrompt).toBe(customPrompt);
457
+ });
458
+ });
459
+
460
+ describe("getters", () => {
461
+ it("returns task mode and strategy", async () => {
462
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
463
+ const runtime = new TeamRuntime(manifest, services);
464
+
465
+ expect(runtime.getTaskMode()).toBe("pull");
466
+ expect(runtime.getStrategyName()).toBe("trunk");
467
+ });
468
+
469
+ it("returns agent IDs after bootstrap", async () => {
470
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
471
+ const runtime = new TeamRuntime(manifest, services);
472
+
473
+ await runtime.initialize();
474
+ const result = await runtime.bootstrap();
475
+
476
+ expect(runtime.getRootAgentId()).toBe(result.rootId);
477
+ expect(runtime.getCompanionAgentIds()).toEqual(result.companionIds);
478
+ });
479
+ });
480
+
481
+ describe("teardown()", () => {
482
+ it("clears spawn interceptor", async () => {
483
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
484
+ const runtime = new TeamRuntime(manifest, services);
485
+
486
+ await runtime.initialize();
487
+ await runtime.bootstrap();
488
+ await runtime.teardown();
489
+
490
+ expect(agentManager.setSpawnInterceptor).toHaveBeenLastCalledWith(null);
491
+ });
492
+ });
493
+
494
+ describe("peer routing", () => {
495
+ it("stores signal filters from peer connections", async () => {
496
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
497
+ const runtime = new TeamRuntime(manifest, services);
498
+
499
+ await runtime.initialize();
500
+ const result = await runtime.bootstrap();
501
+
502
+ const filters = runtime.getPeerSignalFilters();
503
+
504
+ // judge→planner has signals: [FIXUP_CREATED, GREEN_SNAPSHOT]
505
+ const judgeToPlanner = filters.get(`${result.companionIds[0]}→${result.rootId}`);
506
+ expect(judgeToPlanner).toEqual(["FIXUP_CREATED", "GREEN_SNAPSHOT"]);
507
+
508
+ // planner→judge has signals: [CONVERGENCE_CHECK]
509
+ const plannerToJudge = filters.get(`${result.rootId}→${result.companionIds[0]}`);
510
+ expect(plannerToJudge).toEqual(["CONVERGENCE_CHECK"]);
511
+ });
512
+
513
+ it("falls back to legacy subtree subscriptions when no peers config", async () => {
514
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
515
+
516
+ // Remove routing.peers to test fallback
517
+ manifest.communication.routing = { status: "upstream" };
518
+
519
+ const runtime = new TeamRuntime(manifest, services);
520
+
521
+ await runtime.initialize();
522
+ const result = await runtime.bootstrap();
523
+
524
+ // Legacy: 2 bidirectional subtree subs (root→companion + companion→root)
525
+ expect(messageRouter.subscribe).toHaveBeenCalledTimes(2);
526
+ expect(messageRouter.subscribe).toHaveBeenCalledWith(
527
+ result.rootId,
528
+ { type: "subtree", target: result.companionIds[0] }
529
+ );
530
+ expect(messageRouter.subscribe).toHaveBeenCalledWith(
531
+ result.companionIds[0],
532
+ { type: "subtree", target: result.rootId }
533
+ );
534
+ });
535
+
536
+ it("defers wiring for roles not spawned at bootstrap", async () => {
537
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
538
+
539
+ // Add a peer connection involving grinder (not spawned at bootstrap)
540
+ manifest.communication.routing!.peers!.push({
541
+ from: "grinder",
542
+ to: "planner",
543
+ via: "direct",
544
+ signals: ["WORKER_DONE"],
545
+ });
546
+
547
+ const runtime = new TeamRuntime(manifest, services);
548
+
549
+ await runtime.initialize();
550
+ const result = await runtime.bootstrap();
551
+
552
+ // 2 wired at bootstrap (judge↔planner) + 1 deferred (grinder→planner)
553
+ expect(messageRouter.subscribe).toHaveBeenCalledTimes(2);
554
+
555
+ // onLifecycleEvent should have been called twice: once for deferred wiring, once for continuations
556
+ expect(agentManager.onLifecycleEvent).toHaveBeenCalledTimes(2);
557
+
558
+ // Simulate grinder spawn via lifecycle event
559
+ const lifecycleCallbacks = vi.mocked(agentManager.onLifecycleEvent).mock.calls;
560
+ // The deferred wiring callback is the first one registered (wirePeerRoutes before monitorContinuations)
561
+ const deferredCallback = lifecycleCallbacks[0][0];
562
+
563
+ deferredCallback({
564
+ type: "spawned",
565
+ agent: { id: "grinder_agent", role: "grinder", state: "running" },
566
+ } as any);
567
+
568
+ // Now the deferred route should be wired
569
+ expect(messageRouter.subscribe).toHaveBeenCalledTimes(3);
570
+ expect(messageRouter.subscribe).toHaveBeenCalledWith(
571
+ "grinder_agent",
572
+ { type: "subtree", target: result.rootId }
573
+ );
574
+
575
+ // Signal filter should be stored
576
+ const filters = runtime.getPeerSignalFilters();
577
+ expect(filters.get(`grinder_agent→${result.rootId}`)).toEqual(["WORKER_DONE"]);
578
+ });
579
+
580
+ it("serializes peerRoutes in team_config event", async () => {
581
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
582
+ const runtime = new TeamRuntime(manifest, services);
583
+
584
+ await runtime.initialize();
585
+
586
+ expect(eventStore.emit).toHaveBeenCalledWith(
587
+ expect.objectContaining({
588
+ type: "status",
589
+ payload: expect.objectContaining({
590
+ team_config: expect.objectContaining({
591
+ peerRoutes: expect.arrayContaining([
592
+ expect.objectContaining({
593
+ from: "judge",
594
+ to: "planner",
595
+ via: "direct",
596
+ signals: ["FIXUP_CREATED", "GREEN_SNAPSHOT"],
597
+ }),
598
+ ]),
599
+ }),
600
+ }),
601
+ })
602
+ );
603
+ });
604
+
605
+ it("teardown cleans up deferred wiring listener", async () => {
606
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
607
+
608
+ // Add a deferred route to ensure the wiring listener is set up
609
+ manifest.communication.routing!.peers!.push({
610
+ from: "grinder",
611
+ to: "judge",
612
+ via: "direct",
613
+ });
614
+
615
+ const runtime = new TeamRuntime(manifest, services);
616
+
617
+ await runtime.initialize();
618
+ await runtime.bootstrap();
619
+
620
+ // onLifecycleEvent called twice: deferred wiring + continuations
621
+ expect(agentManager.onLifecycleEvent).toHaveBeenCalledTimes(2);
622
+
623
+ // Both return unsubscribe fns (index 0 = deferred wiring, index 1 = continuations)
624
+ const peerWiringUnsub = vi.mocked(agentManager.onLifecycleEvent).mock.results[0].value;
625
+ const continuationsUnsub = vi.mocked(agentManager.onLifecycleEvent).mock.results[1].value;
626
+
627
+ await runtime.teardown();
628
+
629
+ // Both unsubscribe fns should be called
630
+ expect(peerWiringUnsub).toHaveBeenCalled();
631
+ expect(continuationsUnsub).toHaveBeenCalled();
632
+ });
633
+
634
+ it("via topic creates shared topic subscription", async () => {
635
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
636
+
637
+ // Replace peers with a topic-based connection
638
+ manifest.communication.routing!.peers = [
639
+ { from: "planner", to: "judge", via: "topic" },
640
+ ];
641
+
642
+ const runtime = new TeamRuntime(manifest, services);
643
+
644
+ await runtime.initialize();
645
+ const result = await runtime.bootstrap();
646
+
647
+ // Topic creates 2 subscriptions: both agents to the same topic
648
+ expect(messageRouter.subscribe).toHaveBeenCalledTimes(2);
649
+ expect(messageRouter.subscribe).toHaveBeenCalledWith(
650
+ result.rootId, // planner
651
+ { type: "topic", target: "peer:planner:judge" }
652
+ );
653
+ expect(messageRouter.subscribe).toHaveBeenCalledWith(
654
+ result.companionIds[0], // judge
655
+ { type: "topic", target: "peer:planner:judge" }
656
+ );
657
+ });
658
+
659
+ it("via scope creates role subscription", async () => {
660
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
661
+
662
+ // Replace peers with a scope-based connection
663
+ manifest.communication.routing!.peers = [
664
+ { from: "planner", to: "judge", via: "scope" },
665
+ ];
666
+
667
+ const runtime = new TeamRuntime(manifest, services);
668
+
669
+ await runtime.initialize();
670
+ const result = await runtime.bootstrap();
671
+
672
+ // Scope creates 1 subscription: from subscribes to to's role channel
673
+ expect(messageRouter.subscribe).toHaveBeenCalledTimes(1);
674
+ expect(messageRouter.subscribe).toHaveBeenCalledWith(
675
+ result.rootId, // planner
676
+ { type: "role", target: "judge" }
677
+ );
678
+ });
679
+ });
680
+
681
+ describe("signal filtering", () => {
682
+ it("installs signal filter on message router after bootstrap", async () => {
683
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
684
+ const runtime = new TeamRuntime(manifest, services);
685
+
686
+ await runtime.initialize();
687
+ await runtime.bootstrap();
688
+
689
+ expect(messageRouter.setSignalFilter).toHaveBeenCalledTimes(1);
690
+ expect(messageRouter.setSignalFilter).toHaveBeenCalledWith(expect.any(Function));
691
+ });
692
+
693
+ it("peer connection filter allows matching signals", async () => {
694
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
695
+ const runtime = new TeamRuntime(manifest, services);
696
+
697
+ await runtime.initialize();
698
+ const result = await runtime.bootstrap();
699
+
700
+ // Extract the installed filter
701
+ const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls[0][0] as (
702
+ from: string, to: string, signal: string | undefined
703
+ ) => boolean;
704
+
705
+ const judgeId = result.companionIds[0]; // judge
706
+ const plannerId = result.rootId; // planner
707
+
708
+ // judge→planner peer has signals: [FIXUP_CREATED, GREEN_SNAPSHOT]
709
+ expect(filterFn(judgeId, plannerId, "FIXUP_CREATED")).toBe(true);
710
+ expect(filterFn(judgeId, plannerId, "GREEN_SNAPSHOT")).toBe(true);
711
+ });
712
+
713
+ it("peer connection filter blocks non-matching signals", async () => {
714
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
715
+ const runtime = new TeamRuntime(manifest, services);
716
+
717
+ await runtime.initialize();
718
+ const result = await runtime.bootstrap();
719
+
720
+ const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls[0][0] as (
721
+ from: string, to: string, signal: string | undefined
722
+ ) => boolean;
723
+
724
+ const judgeId = result.companionIds[0];
725
+ const plannerId = result.rootId;
726
+
727
+ // judge→planner peer does NOT include WORKER_DONE
728
+ expect(filterFn(judgeId, plannerId, "WORKER_DONE")).toBe(false);
729
+ });
730
+
731
+ it("untagged status events always pass through", async () => {
732
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
733
+ const runtime = new TeamRuntime(manifest, services);
734
+
735
+ await runtime.initialize();
736
+ const result = await runtime.bootstrap();
737
+
738
+ const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls[0][0] as (
739
+ from: string, to: string, signal: string | undefined
740
+ ) => boolean;
741
+
742
+ const judgeId = result.companionIds[0];
743
+ const plannerId = result.rootId;
744
+
745
+ // No signal (undefined) should always pass
746
+ expect(filterFn(judgeId, plannerId, undefined)).toBe(true);
747
+ });
748
+
749
+ it("channel subscription filter allows role's configured signals", async () => {
750
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
751
+ const runtime = new TeamRuntime(manifest, services);
752
+
753
+ await runtime.initialize();
754
+ const result = await runtime.bootstrap();
755
+
756
+ const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls[0][0] as (
757
+ from: string, to: string, signal: string | undefined
758
+ ) => boolean;
759
+
760
+ // Use a "grinder" agent as recipient - grinder only allows WORK_ASSIGNED
761
+ // Simulate spawning a grinder by triggering deferred wiring
762
+ // But grinder has no peer connection, so we test channel sub filter directly
763
+ // by spawning through the lifecycle event to populate agentRoleMap
764
+
765
+ // Add a grinder peer route so deferred wiring populates agentRoleMap
766
+ manifest.communication.routing!.peers!.push({
767
+ from: "grinder",
768
+ to: "planner",
769
+ via: "direct",
770
+ });
771
+
772
+ // Re-bootstrap with updated manifest
773
+ const runtime2 = new TeamRuntime(manifest, services);
774
+ await runtime2.initialize();
775
+ const result2 = await runtime2.bootstrap();
776
+
777
+ // Simulate grinder spawn via lifecycle event
778
+ const deferredCallback = vi.mocked(agentManager.onLifecycleEvent).mock.calls.at(-2)![0];
779
+ deferredCallback({
780
+ type: "spawned",
781
+ agent: { id: "grinder_1", role: "grinder", state: "running" },
782
+ } as any);
783
+
784
+ // Get the latest filter (from runtime2's installSignalFilter)
785
+ const filterFn2 = vi.mocked(messageRouter.setSignalFilter).mock.calls.at(-1)![0] as (
786
+ from: string, to: string, signal: string | undefined
787
+ ) => boolean;
788
+
789
+ // grinder allows WORK_ASSIGNED from channel subs
790
+ // But grinder→planner is a peer route (no signal filter), so test from a non-peer source
791
+ // From planner to grinder_1 (no peer filter exists for this direction)
792
+ expect(filterFn2(result2.rootId, "grinder_1", "WORK_ASSIGNED")).toBe(true);
793
+ expect(filterFn2(result2.rootId, "grinder_1", "WORKER_DONE")).toBe(false);
794
+ });
795
+
796
+ it("roles with any unfiltered subscription receive all signals", async () => {
797
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
798
+ const runtime = new TeamRuntime(manifest, services);
799
+
800
+ await runtime.initialize();
801
+ const result = await runtime.bootstrap();
802
+
803
+ const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls[0][0] as (
804
+ from: string, to: string, signal: string | undefined
805
+ ) => boolean;
806
+
807
+ // planner has task_updates subscription with no signals filter → receives all
808
+ // But planner's peer connections have explicit filters, so test from a non-peer agent
809
+ // From an unknown agent to planner — falls through to channel sub filter
810
+ expect(filterFn("unknown_agent", result.rootId, "ANY_SIGNAL")).toBe(true);
811
+ expect(filterFn("unknown_agent", result.rootId, "RANDOM")).toBe(true);
812
+ });
813
+
814
+ it("peer filter takes precedence over channel subscription filter", async () => {
815
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
816
+ const runtime = new TeamRuntime(manifest, services);
817
+
818
+ await runtime.initialize();
819
+ const result = await runtime.bootstrap();
820
+
821
+ const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls[0][0] as (
822
+ from: string, to: string, signal: string | undefined
823
+ ) => boolean;
824
+
825
+ const judgeId = result.companionIds[0];
826
+ const plannerId = result.rootId;
827
+
828
+ // judge→planner peer only allows FIXUP_CREATED, GREEN_SNAPSHOT
829
+ // Even though planner's channel subs allow "all" (via unfiltered task_updates),
830
+ // the peer filter takes precedence
831
+ expect(filterFn(judgeId, plannerId, "TASK_CREATED")).toBe(false);
832
+ });
833
+ });
834
+
835
+ describe("emission validation", () => {
836
+ it("installs emission validator on message router after bootstrap", async () => {
837
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
838
+ const runtime = new TeamRuntime(manifest, services);
839
+
840
+ await runtime.initialize();
841
+ await runtime.bootstrap();
842
+
843
+ expect(messageRouter.setEmissionValidator).toHaveBeenCalledTimes(1);
844
+ expect(messageRouter.setEmissionValidator).toHaveBeenCalledWith(expect.any(Function));
845
+ });
846
+
847
+ it("allows emissions in role's allowed list", async () => {
848
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
849
+ const runtime = new TeamRuntime(manifest, services);
850
+
851
+ await runtime.initialize();
852
+ const result = await runtime.bootstrap();
853
+
854
+ const validatorFn = vi.mocked(messageRouter.setEmissionValidator).mock.calls[0][0] as (
855
+ agentId: string, signal: string | undefined
856
+ ) => { action: string; message?: string };
857
+
858
+ const plannerId = result.rootId;
859
+
860
+ // planner's emissions: [TASK_CREATED, WORK_ASSIGNED]
861
+ expect(validatorFn(plannerId, "TASK_CREATED").action).toBe("allow");
862
+ expect(validatorFn(plannerId, "WORK_ASSIGNED").action).toBe("allow");
863
+ });
864
+
865
+ it("rejects disallowed emissions in strict mode", async () => {
866
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
867
+ manifest.communication.enforcement = "strict";
868
+ const runtime = new TeamRuntime(manifest, services);
869
+
870
+ await runtime.initialize();
871
+ const result = await runtime.bootstrap();
872
+
873
+ const validatorFn = vi.mocked(messageRouter.setEmissionValidator).mock.calls[0][0] as (
874
+ agentId: string, signal: string | undefined
875
+ ) => { action: string; message?: string };
876
+
877
+ const plannerId = result.rootId;
878
+
879
+ // WORKER_DONE is not in planner's allowed emissions
880
+ const res = validatorFn(plannerId, "WORKER_DONE");
881
+ expect(res.action).toBe("reject");
882
+ expect(res.message).toContain("WORKER_DONE");
883
+ expect(res.message).toContain("planner");
884
+ });
885
+
886
+ it("warns on disallowed emissions in permissive mode", async () => {
887
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
888
+ manifest.communication.enforcement = "permissive";
889
+ const runtime = new TeamRuntime(manifest, services);
890
+
891
+ await runtime.initialize();
892
+ const result = await runtime.bootstrap();
893
+
894
+ const validatorFn = vi.mocked(messageRouter.setEmissionValidator).mock.calls[0][0] as (
895
+ agentId: string, signal: string | undefined
896
+ ) => { action: string; message?: string };
897
+
898
+ const plannerId = result.rootId;
899
+
900
+ const res = validatorFn(plannerId, "HEALTH_CHECK");
901
+ expect(res.action).toBe("warn");
902
+ expect(res.message).toContain("HEALTH_CHECK");
903
+ });
904
+
905
+ it("audits disallowed emissions in audit mode", async () => {
906
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
907
+ manifest.communication.enforcement = "audit";
908
+ const runtime = new TeamRuntime(manifest, services);
909
+
910
+ await runtime.initialize();
911
+ const result = await runtime.bootstrap();
912
+
913
+ const validatorFn = vi.mocked(messageRouter.setEmissionValidator).mock.calls[0][0] as (
914
+ agentId: string, signal: string | undefined
915
+ ) => { action: string; message?: string };
916
+
917
+ const plannerId = result.rootId;
918
+
919
+ const res = validatorFn(plannerId, "FORBIDDEN");
920
+ expect(res.action).toBe("audit");
921
+ expect(res.message).toContain("FORBIDDEN");
922
+ });
923
+
924
+ it("allows untagged emissions for any role", async () => {
925
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
926
+ manifest.communication.enforcement = "strict";
927
+ const runtime = new TeamRuntime(manifest, services);
928
+
929
+ await runtime.initialize();
930
+ const result = await runtime.bootstrap();
931
+
932
+ const validatorFn = vi.mocked(messageRouter.setEmissionValidator).mock.calls[0][0] as (
933
+ agentId: string, signal: string | undefined
934
+ ) => { action: string; message?: string };
935
+
936
+ // Undefined signal always passes even in strict mode
937
+ expect(validatorFn(result.rootId, undefined).action).toBe("allow");
938
+ expect(validatorFn(result.companionIds[0], undefined).action).toBe("allow");
939
+ });
940
+
941
+ it("allows emissions from agents with no role mapping", async () => {
942
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
943
+ manifest.communication.enforcement = "strict";
944
+ const runtime = new TeamRuntime(manifest, services);
945
+
946
+ await runtime.initialize();
947
+ await runtime.bootstrap();
948
+
949
+ const validatorFn = vi.mocked(messageRouter.setEmissionValidator).mock.calls[0][0] as (
950
+ agentId: string, signal: string | undefined
951
+ ) => { action: string; message?: string };
952
+
953
+ // Unknown agent — no role mapping, so allowed
954
+ expect(validatorFn("unknown_agent", "ANYTHING").action).toBe("allow");
955
+ });
956
+
957
+ it("does not install validator when no emissions config", async () => {
958
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
959
+ manifest.communication.emissions = undefined;
960
+ const runtime = new TeamRuntime(manifest, services);
961
+
962
+ await runtime.initialize();
963
+ await runtime.bootstrap();
964
+
965
+ expect(messageRouter.setEmissionValidator).not.toHaveBeenCalled();
966
+ });
967
+
968
+ it("serializes emissions in team_config event", async () => {
969
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
970
+ const runtime = new TeamRuntime(manifest, services);
971
+
972
+ await runtime.initialize();
973
+
974
+ expect(eventStore.emit).toHaveBeenCalledWith(
975
+ expect.objectContaining({
976
+ type: "status",
977
+ payload: expect.objectContaining({
978
+ team_config: expect.objectContaining({
979
+ emissions: expect.objectContaining({
980
+ planner: ["TASK_CREATED", "WORK_ASSIGNED"],
981
+ judge: ["HEALTH_CHECK", "GREEN_SNAPSHOT", "FIXUP_CREATED"],
982
+ grinder: ["WORKER_DONE"],
983
+ }),
984
+ }),
985
+ }),
986
+ })
987
+ );
988
+ });
989
+ });
990
+
991
+ describe("monitorContinuations()", () => {
992
+ it("auto-continues root agent on unexpected stop", async () => {
993
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
994
+ const runtime = new TeamRuntime(manifest, services);
995
+
996
+ await runtime.initialize();
997
+ const result = await runtime.bootstrap();
998
+
999
+ // Capture the lifecycle callback registered during bootstrap
1000
+ const onLifecycleEventMock = vi.mocked(agentManager.onLifecycleEvent);
1001
+ expect(onLifecycleEventMock).toHaveBeenCalled();
1002
+ const lifecycleCallback = onLifecycleEventMock.mock.calls[0][0];
1003
+
1004
+ // Simulate unexpected stop of root agent (no reason = unexpected)
1005
+ lifecycleCallback({
1006
+ type: "stopped",
1007
+ agent: { id: result.rootId, role: "planner", state: "stopped" },
1008
+ } as any);
1009
+
1010
+ // Wait for the setTimeout (1s) + async continuation
1011
+ await vi.waitFor(
1012
+ () => {
1013
+ expect(agentManager.continueAgent).toHaveBeenCalledWith(result.rootId);
1014
+ },
1015
+ { timeout: 3000 }
1016
+ );
1017
+
1018
+ // Root agent ID should be updated to the continued agent
1019
+ expect(runtime.getRootAgentId()).toBe("continued_0");
1020
+ });
1021
+
1022
+ it("auto-continues companion agent on unexpected stop", async () => {
1023
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1024
+ const runtime = new TeamRuntime(manifest, services);
1025
+
1026
+ await runtime.initialize();
1027
+ const result = await runtime.bootstrap();
1028
+ const companionId = result.companionIds[0];
1029
+
1030
+ const lifecycleCallback = vi.mocked(agentManager.onLifecycleEvent).mock.calls[0][0];
1031
+
1032
+ // Simulate unexpected stop of companion
1033
+ lifecycleCallback({
1034
+ type: "stopped",
1035
+ agent: { id: companionId, role: "judge", state: "stopped" },
1036
+ } as any);
1037
+
1038
+ await vi.waitFor(
1039
+ () => {
1040
+ expect(agentManager.continueAgent).toHaveBeenCalledWith(companionId);
1041
+ },
1042
+ { timeout: 3000 }
1043
+ );
1044
+
1045
+ // Companion ID should be updated
1046
+ expect(runtime.getCompanionAgentIds()).toContain("continued_0");
1047
+ });
1048
+
1049
+ it("does NOT auto-continue on completed stop", async () => {
1050
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1051
+ const runtime = new TeamRuntime(manifest, services);
1052
+
1053
+ await runtime.initialize();
1054
+ const result = await runtime.bootstrap();
1055
+
1056
+ const lifecycleCallback = vi.mocked(agentManager.onLifecycleEvent).mock.calls[0][0];
1057
+
1058
+ // Simulate completed stop (should NOT trigger continuation)
1059
+ lifecycleCallback({
1060
+ type: "stopped",
1061
+ agent: { id: result.rootId, role: "planner", state: "stopped" },
1062
+ reason: "completed",
1063
+ } as any);
1064
+
1065
+ // Wait a bit to ensure no continuation is triggered
1066
+ await new Promise((resolve) => setTimeout(resolve, 1500));
1067
+ expect(agentManager.continueAgent).not.toHaveBeenCalled();
1068
+ });
1069
+
1070
+ it("does NOT auto-continue on cancelled stop", async () => {
1071
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1072
+ const runtime = new TeamRuntime(manifest, services);
1073
+
1074
+ await runtime.initialize();
1075
+ const result = await runtime.bootstrap();
1076
+
1077
+ const lifecycleCallback = vi.mocked(agentManager.onLifecycleEvent).mock.calls[0][0];
1078
+
1079
+ lifecycleCallback({
1080
+ type: "stopped",
1081
+ agent: { id: result.rootId, role: "planner", state: "stopped" },
1082
+ reason: "cancelled",
1083
+ } as any);
1084
+
1085
+ await new Promise((resolve) => setTimeout(resolve, 1500));
1086
+ expect(agentManager.continueAgent).not.toHaveBeenCalled();
1087
+ });
1088
+
1089
+ it("does NOT trigger for non-monitored agents", async () => {
1090
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1091
+ const runtime = new TeamRuntime(manifest, services);
1092
+
1093
+ await runtime.initialize();
1094
+ await runtime.bootstrap();
1095
+
1096
+ const lifecycleCallback = vi.mocked(agentManager.onLifecycleEvent).mock.calls[0][0];
1097
+
1098
+ // Simulate stop of an unrelated agent
1099
+ lifecycleCallback({
1100
+ type: "stopped",
1101
+ agent: { id: "unrelated_agent", role: "worker", state: "stopped" },
1102
+ } as any);
1103
+
1104
+ await new Promise((resolve) => setTimeout(resolve, 1500));
1105
+ expect(agentManager.continueAgent).not.toHaveBeenCalled();
1106
+ });
1107
+
1108
+ it("unsubscribes lifecycle listener on teardown", async () => {
1109
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1110
+ const runtime = new TeamRuntime(manifest, services);
1111
+
1112
+ await runtime.initialize();
1113
+ await runtime.bootstrap();
1114
+
1115
+ // onLifecycleEvent returns an unsubscribe function
1116
+ const unsubscribeFn = vi.mocked(agentManager.onLifecycleEvent).mock.results[0].value;
1117
+
1118
+ await runtime.teardown();
1119
+
1120
+ // The unsubscribe function should have been called
1121
+ expect(unsubscribeFn).toHaveBeenCalled();
1122
+ });
1123
+ });
1124
+
1125
+ describe("structured team", () => {
1126
+ it("bootstraps with push mode and queue strategy", async () => {
1127
+ const manifest = await loadTeam("structured", roleRegistry, PROJECT_ROOT);
1128
+ const runtime = new TeamRuntime(manifest, services);
1129
+
1130
+ expect(runtime.getTaskMode()).toBe("push");
1131
+ expect(runtime.getStrategyName()).toBe("queue");
1132
+
1133
+ await runtime.initialize();
1134
+ const result = await runtime.bootstrap();
1135
+
1136
+ // Root (lead) + 1 companion (reviewer)
1137
+ expect(agentManager.spawn).toHaveBeenCalledTimes(2);
1138
+ expect(result.companionIds).toHaveLength(1);
1139
+
1140
+ // Verify no pull-mode interaction patterns injected
1141
+ const rootCall = vi.mocked(agentManager.spawn).mock.calls[0][0];
1142
+ const pullPatterns = rootCall.interactionPatterns?.filter((p) =>
1143
+ p.includes("PULL mode")
1144
+ ) ?? [];
1145
+ expect(pullPatterns).toHaveLength(0);
1146
+ });
1147
+ });
1148
+ });
1149
+
1150
+ // =============================================================================
1151
+ // Tests: Integration Strategies
1152
+ // =============================================================================
1153
+
1154
+ describe("Integration Strategies", () => {
1155
+ it("imports trunk strategy module", async () => {
1156
+ const { TrunkIntegrationStrategy } = await import(
1157
+ "../../workspace/strategies/trunk.js"
1158
+ );
1159
+ const strategy = new TrunkIntegrationStrategy();
1160
+ expect(strategy.name).toBe("trunk");
1161
+ });
1162
+
1163
+ it("imports optimistic strategy module", async () => {
1164
+ const { OptimisticIntegrationStrategy } = await import(
1165
+ "../../workspace/strategies/optimistic.js"
1166
+ );
1167
+ const strategy = new OptimisticIntegrationStrategy();
1168
+ expect(strategy.name).toBe("optimistic");
1169
+ });
1170
+
1171
+ it("imports queue strategy module", async () => {
1172
+ const { QueueIntegrationStrategy } = await import(
1173
+ "../../workspace/strategies/queue.js"
1174
+ );
1175
+ const strategy = new QueueIntegrationStrategy();
1176
+ expect(strategy.name).toBe("queue");
1177
+ });
1178
+
1179
+ it("registry provides all built-in strategies", async () => {
1180
+ const { defaultStrategyRegistry } = await import(
1181
+ "../../workspace/strategies/registry.js"
1182
+ );
1183
+ expect(defaultStrategyRegistry.has("queue")).toBe(true);
1184
+ expect(defaultStrategyRegistry.has("trunk")).toBe(true);
1185
+ expect(defaultStrategyRegistry.has("optimistic")).toBe(true);
1186
+ });
1187
+ });
1188
+
1189
+ // =============================================================================
1190
+ // Tests: Task Pull Model
1191
+ // =============================================================================
1192
+
1193
+ describe("Task Pull Model", () => {
1194
+ it("claim_task tool module exports correctly", async () => {
1195
+ const { CLAIM_TASK_TOOL_INFO, ClaimTaskSchema, createClaimTaskHandler } =
1196
+ await import("../../mcp/tools/claim_task.js");
1197
+
1198
+ expect(CLAIM_TASK_TOOL_INFO.name).toBe("claim_task");
1199
+ expect(createClaimTaskHandler).toBeInstanceOf(Function);
1200
+ expect(ClaimTaskSchema).toBeDefined();
1201
+ });
1202
+
1203
+ it("unclaim_task tool module exports correctly", async () => {
1204
+ const { UNCLAIM_TASK_TOOL_INFO, createUnclaimTaskHandler } = await import(
1205
+ "../../mcp/tools/unclaim_task.js"
1206
+ );
1207
+
1208
+ expect(UNCLAIM_TASK_TOOL_INFO.name).toBe("unclaim_task");
1209
+ expect(createUnclaimTaskHandler).toBeInstanceOf(Function);
1210
+ });
1211
+
1212
+ it("list_claimable_tasks tool module exports correctly", async () => {
1213
+ const { LIST_CLAIMABLE_TASKS_TOOL_INFO, createListClaimableTasksHandler } =
1214
+ await import("../../mcp/tools/list_claimable_tasks.js");
1215
+
1216
+ expect(LIST_CLAIMABLE_TASKS_TOOL_INFO.name).toBe("list_claimable_tasks");
1217
+ expect(createListClaimableTasksHandler).toBeInstanceOf(Function);
1218
+ });
1219
+
1220
+ it("task.claim capability is registered", async () => {
1221
+ const { TASK_CAPABILITIES, ALL_CAPABILITIES } = await import(
1222
+ "../../roles/capabilities.js"
1223
+ );
1224
+
1225
+ expect(TASK_CAPABILITIES.CLAIM).toBe("task.claim");
1226
+ expect(ALL_CAPABILITIES.has("task.claim")).toBe(true);
1227
+ });
1228
+ });
1229
+
1230
+ // =============================================================================
1231
+ // Tests: Metrics Module
1232
+ // =============================================================================
1233
+
1234
+ describe("Metrics Module", () => {
1235
+ it("exports all metric functions", async () => {
1236
+ const {
1237
+ getThroughputMetrics,
1238
+ getUtilizationMetrics,
1239
+ getErrorMetrics,
1240
+ } = await import("../../metrics/index.js");
1241
+
1242
+ expect(getThroughputMetrics).toBeInstanceOf(Function);
1243
+ expect(getUtilizationMetrics).toBeInstanceOf(Function);
1244
+ expect(getErrorMetrics).toBeInstanceOf(Function);
1245
+ });
1246
+
1247
+ it("computes throughput metrics from empty store", async () => {
1248
+ const { getThroughputMetrics } = await import("../../metrics/index.js");
1249
+ const store = createMockEventStore();
1250
+
1251
+ const metrics = getThroughputMetrics(store, 60000);
1252
+
1253
+ expect(metrics.tasksCompleted).toBe(0);
1254
+ expect(metrics.tasksFailed).toBe(0);
1255
+ expect(metrics.tasksCreated).toBe(0);
1256
+ expect(metrics.completedPerMinute).toBe(0);
1257
+ expect(metrics.avgCompletionTimeMs).toBeNull();
1258
+ });
1259
+
1260
+ it("computes utilization metrics from empty store", async () => {
1261
+ const { getUtilizationMetrics } = await import("../../metrics/index.js");
1262
+ const store = createMockEventStore();
1263
+
1264
+ const metrics = getUtilizationMetrics(store);
1265
+
1266
+ expect(metrics.activeAgents).toBe(0);
1267
+ expect(metrics.totalSpawned).toBe(0);
1268
+ expect(metrics.totalStopped).toBe(0);
1269
+ });
1270
+
1271
+ it("computes error metrics from empty store", async () => {
1272
+ const { getErrorMetrics } = await import("../../metrics/index.js");
1273
+ const store = createMockEventStore();
1274
+
1275
+ const metrics = getErrorMetrics(store);
1276
+
1277
+ expect(metrics.totalErrors).toBe(0);
1278
+ expect(metrics.recentErrors).toEqual([]);
1279
+ });
1280
+ });