macro-agent 0.0.10 → 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 (518) 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 +56 -51
  16. package/.sudocode/specs.jsonl +8 -1
  17. package/CLAUDE.md +121 -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 +40 -1
  31. package/dist/agent/agent-manager.d.ts.map +1 -1
  32. package/dist/agent/agent-manager.js +172 -8
  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/wake.d.ts +15 -0
  38. package/dist/agent/wake.d.ts.map +1 -1
  39. package/dist/agent/wake.js +15 -0
  40. package/dist/agent/wake.js.map +1 -1
  41. package/dist/agent-detection/command-builder.d.ts +30 -0
  42. package/dist/agent-detection/command-builder.d.ts.map +1 -0
  43. package/dist/agent-detection/command-builder.js +71 -0
  44. package/dist/agent-detection/command-builder.js.map +1 -0
  45. package/dist/agent-detection/detector.d.ts +84 -0
  46. package/dist/agent-detection/detector.d.ts.map +1 -0
  47. package/dist/agent-detection/detector.js +240 -0
  48. package/dist/agent-detection/detector.js.map +1 -0
  49. package/dist/agent-detection/index.d.ts +12 -0
  50. package/dist/agent-detection/index.d.ts.map +1 -0
  51. package/dist/agent-detection/index.js +14 -0
  52. package/dist/agent-detection/index.js.map +1 -0
  53. package/dist/agent-detection/registry.d.ts +53 -0
  54. package/dist/agent-detection/registry.d.ts.map +1 -0
  55. package/dist/agent-detection/registry.js +177 -0
  56. package/dist/agent-detection/registry.js.map +1 -0
  57. package/dist/agent-detection/types.d.ts +121 -0
  58. package/dist/agent-detection/types.d.ts.map +1 -0
  59. package/dist/agent-detection/types.js +20 -0
  60. package/dist/agent-detection/types.js.map +1 -0
  61. package/dist/api/server.d.ts +5 -1
  62. package/dist/api/server.d.ts.map +1 -1
  63. package/dist/api/server.js +362 -0
  64. package/dist/api/server.js.map +1 -1
  65. package/dist/api/types.d.ts +50 -1
  66. package/dist/api/types.d.ts.map +1 -1
  67. package/dist/cli/acp.d.ts +2 -0
  68. package/dist/cli/acp.d.ts.map +1 -1
  69. package/dist/cli/acp.js +8 -1
  70. package/dist/cli/acp.js.map +1 -1
  71. package/dist/cli/index.js +29 -0
  72. package/dist/cli/index.js.map +1 -1
  73. package/dist/cli/mcp.js +38 -0
  74. package/dist/cli/mcp.js.map +1 -1
  75. package/dist/config/index.d.ts +2 -0
  76. package/dist/config/index.d.ts.map +1 -0
  77. package/dist/config/index.js +2 -0
  78. package/dist/config/index.js.map +1 -0
  79. package/dist/config/project-config.d.ts +46 -0
  80. package/dist/config/project-config.d.ts.map +1 -0
  81. package/dist/config/project-config.js +68 -0
  82. package/dist/config/project-config.js.map +1 -0
  83. package/dist/lifecycle/cascade.d.ts +1 -1
  84. package/dist/lifecycle/cascade.d.ts.map +1 -1
  85. package/dist/lifecycle/handlers/index.d.ts +4 -0
  86. package/dist/lifecycle/handlers/index.d.ts.map +1 -1
  87. package/dist/lifecycle/handlers/index.js +2 -0
  88. package/dist/lifecycle/handlers/index.js.map +1 -1
  89. package/dist/lifecycle/handlers/worker.d.ts +4 -0
  90. package/dist/lifecycle/handlers/worker.d.ts.map +1 -1
  91. package/dist/lifecycle/handlers/worker.js +35 -3
  92. package/dist/lifecycle/handlers/worker.js.map +1 -1
  93. package/dist/mail/conversation-map.d.ts +33 -0
  94. package/dist/mail/conversation-map.d.ts.map +1 -0
  95. package/dist/mail/conversation-map.js +61 -0
  96. package/dist/mail/conversation-map.js.map +1 -0
  97. package/dist/mail/index.d.ts +11 -0
  98. package/dist/mail/index.d.ts.map +1 -0
  99. package/dist/mail/index.js +11 -0
  100. package/dist/mail/index.js.map +1 -0
  101. package/dist/mail/mail-service.d.ts +85 -0
  102. package/dist/mail/mail-service.d.ts.map +1 -0
  103. package/dist/mail/mail-service.js +121 -0
  104. package/dist/mail/mail-service.js.map +1 -0
  105. package/dist/mail/stores/eventstore-conversation-store.d.ts +40 -0
  106. package/dist/mail/stores/eventstore-conversation-store.d.ts.map +1 -0
  107. package/dist/mail/stores/eventstore-conversation-store.js +131 -0
  108. package/dist/mail/stores/eventstore-conversation-store.js.map +1 -0
  109. package/dist/mail/stores/eventstore-participant-store.d.ts +43 -0
  110. package/dist/mail/stores/eventstore-participant-store.d.ts.map +1 -0
  111. package/dist/mail/stores/eventstore-participant-store.js +145 -0
  112. package/dist/mail/stores/eventstore-participant-store.js.map +1 -0
  113. package/dist/mail/stores/eventstore-thread-store.d.ts +46 -0
  114. package/dist/mail/stores/eventstore-thread-store.d.ts.map +1 -0
  115. package/dist/mail/stores/eventstore-thread-store.js +118 -0
  116. package/dist/mail/stores/eventstore-thread-store.js.map +1 -0
  117. package/dist/mail/stores/eventstore-turn-store.d.ts +47 -0
  118. package/dist/mail/stores/eventstore-turn-store.d.ts.map +1 -0
  119. package/dist/mail/stores/eventstore-turn-store.js +153 -0
  120. package/dist/mail/stores/eventstore-turn-store.js.map +1 -0
  121. package/dist/mail/stores/index.d.ts +12 -0
  122. package/dist/mail/stores/index.d.ts.map +1 -0
  123. package/dist/mail/stores/index.js +12 -0
  124. package/dist/mail/stores/index.js.map +1 -0
  125. package/dist/mail/stores/types.d.ts +146 -0
  126. package/dist/mail/stores/types.d.ts.map +1 -0
  127. package/dist/mail/stores/types.js +13 -0
  128. package/dist/mail/stores/types.js.map +1 -0
  129. package/dist/mail/turn-recorder.d.ts +30 -0
  130. package/dist/mail/turn-recorder.d.ts.map +1 -0
  131. package/dist/mail/turn-recorder.js +98 -0
  132. package/dist/mail/turn-recorder.js.map +1 -0
  133. package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
  134. package/dist/map/adapter/acp-over-map.js +32 -2
  135. package/dist/map/adapter/acp-over-map.js.map +1 -1
  136. package/dist/map/adapter/event-translator.d.ts.map +1 -1
  137. package/dist/map/adapter/event-translator.js +4 -0
  138. package/dist/map/adapter/event-translator.js.map +1 -1
  139. package/dist/map/adapter/extensions/agent-detection.d.ts +49 -0
  140. package/dist/map/adapter/extensions/agent-detection.d.ts.map +1 -0
  141. package/dist/map/adapter/extensions/agent-detection.js +91 -0
  142. package/dist/map/adapter/extensions/agent-detection.js.map +1 -0
  143. package/dist/map/adapter/extensions/index.d.ts +10 -1
  144. package/dist/map/adapter/extensions/index.d.ts.map +1 -1
  145. package/dist/map/adapter/extensions/index.js +39 -0
  146. package/dist/map/adapter/extensions/index.js.map +1 -1
  147. package/dist/map/adapter/extensions/resume.d.ts +47 -0
  148. package/dist/map/adapter/extensions/resume.d.ts.map +1 -0
  149. package/dist/map/adapter/extensions/resume.js +59 -0
  150. package/dist/map/adapter/extensions/resume.js.map +1 -0
  151. package/dist/map/adapter/extensions/workspace-files.d.ts +42 -0
  152. package/dist/map/adapter/extensions/workspace-files.d.ts.map +1 -0
  153. package/dist/map/adapter/extensions/workspace-files.js +338 -0
  154. package/dist/map/adapter/extensions/workspace-files.js.map +1 -0
  155. package/dist/map/adapter/mail-handler-adapter.d.ts +27 -0
  156. package/dist/map/adapter/mail-handler-adapter.d.ts.map +1 -0
  157. package/dist/map/adapter/mail-handler-adapter.js +292 -0
  158. package/dist/map/adapter/mail-handler-adapter.js.map +1 -0
  159. package/dist/map/adapter/map-adapter.d.ts +34 -10
  160. package/dist/map/adapter/map-adapter.d.ts.map +1 -1
  161. package/dist/map/adapter/map-adapter.js +110 -14
  162. package/dist/map/adapter/map-adapter.js.map +1 -1
  163. package/dist/map/adapter/rpc-handler.d.ts +4 -1
  164. package/dist/map/adapter/rpc-handler.d.ts.map +1 -1
  165. package/dist/map/adapter/rpc-handler.js +6 -0
  166. package/dist/map/adapter/rpc-handler.js.map +1 -1
  167. package/dist/map/index.d.ts +1 -0
  168. package/dist/map/index.d.ts.map +1 -1
  169. package/dist/map/index.js +2 -0
  170. package/dist/map/index.js.map +1 -1
  171. package/dist/map/types.d.ts +3 -1
  172. package/dist/map/types.d.ts.map +1 -1
  173. package/dist/map/types.js.map +1 -1
  174. package/dist/mcp/mcp-server.d.ts +6 -0
  175. package/dist/mcp/mcp-server.d.ts.map +1 -1
  176. package/dist/mcp/mcp-server.js +45 -0
  177. package/dist/mcp/mcp-server.js.map +1 -1
  178. package/dist/mcp/tools/claim_task.d.ts +35 -0
  179. package/dist/mcp/tools/claim_task.d.ts.map +1 -0
  180. package/dist/mcp/tools/claim_task.js +58 -0
  181. package/dist/mcp/tools/claim_task.js.map +1 -0
  182. package/dist/mcp/tools/done.d.ts +15 -2
  183. package/dist/mcp/tools/done.d.ts.map +1 -1
  184. package/dist/mcp/tools/done.js +45 -10
  185. package/dist/mcp/tools/done.js.map +1 -1
  186. package/dist/mcp/tools/list_claimable_tasks.d.ts +38 -0
  187. package/dist/mcp/tools/list_claimable_tasks.d.ts.map +1 -0
  188. package/dist/mcp/tools/list_claimable_tasks.js +63 -0
  189. package/dist/mcp/tools/list_claimable_tasks.js.map +1 -0
  190. package/dist/mcp/tools/unclaim_task.d.ts +31 -0
  191. package/dist/mcp/tools/unclaim_task.d.ts.map +1 -0
  192. package/dist/mcp/tools/unclaim_task.js +47 -0
  193. package/dist/mcp/tools/unclaim_task.js.map +1 -0
  194. package/dist/metrics/index.d.ts +2 -0
  195. package/dist/metrics/index.d.ts.map +1 -0
  196. package/dist/metrics/index.js +2 -0
  197. package/dist/metrics/index.js.map +1 -0
  198. package/dist/metrics/metrics.d.ts +79 -0
  199. package/dist/metrics/metrics.d.ts.map +1 -0
  200. package/dist/metrics/metrics.js +166 -0
  201. package/dist/metrics/metrics.js.map +1 -0
  202. package/dist/roles/capabilities.d.ts +1 -0
  203. package/dist/roles/capabilities.d.ts.map +1 -1
  204. package/dist/roles/capabilities.js +3 -0
  205. package/dist/roles/capabilities.js.map +1 -1
  206. package/dist/roles/types.d.ts +1 -1
  207. package/dist/roles/types.d.ts.map +1 -1
  208. package/dist/router/channels.d.ts +2 -4
  209. package/dist/router/channels.d.ts.map +1 -1
  210. package/dist/router/channels.js.map +1 -1
  211. package/dist/router/message-router.d.ts +85 -9
  212. package/dist/router/message-router.d.ts.map +1 -1
  213. package/dist/router/message-router.js +203 -14
  214. package/dist/router/message-router.js.map +1 -1
  215. package/dist/router/role-resolver.d.ts +10 -1
  216. package/dist/router/role-resolver.d.ts.map +1 -1
  217. package/dist/router/role-resolver.js +15 -1
  218. package/dist/router/role-resolver.js.map +1 -1
  219. package/dist/router/types.d.ts +30 -1
  220. package/dist/router/types.d.ts.map +1 -1
  221. package/dist/router/types.js.map +1 -1
  222. package/dist/server/combined-server.d.ts +6 -0
  223. package/dist/server/combined-server.d.ts.map +1 -1
  224. package/dist/server/combined-server.js +24 -2
  225. package/dist/server/combined-server.js.map +1 -1
  226. package/dist/store/event-store.d.ts +14 -1
  227. package/dist/store/event-store.d.ts.map +1 -1
  228. package/dist/store/event-store.js +456 -4
  229. package/dist/store/event-store.js.map +1 -1
  230. package/dist/store/types/agents.d.ts +1 -1
  231. package/dist/store/types/agents.d.ts.map +1 -1
  232. package/dist/store/types/conversations.d.ts +91 -0
  233. package/dist/store/types/conversations.d.ts.map +1 -0
  234. package/dist/store/types/conversations.js +8 -0
  235. package/dist/store/types/conversations.js.map +1 -0
  236. package/dist/store/types/events.d.ts +1 -1
  237. package/dist/store/types/events.d.ts.map +1 -1
  238. package/dist/store/types/events.js.map +1 -1
  239. package/dist/store/types/index.d.ts +2 -0
  240. package/dist/store/types/index.d.ts.map +1 -1
  241. package/dist/store/types/index.js +2 -0
  242. package/dist/store/types/index.js.map +1 -1
  243. package/dist/store/types/sessions.d.ts +44 -0
  244. package/dist/store/types/sessions.d.ts.map +1 -0
  245. package/dist/store/types/sessions.js +9 -0
  246. package/dist/store/types/sessions.js.map +1 -0
  247. package/dist/store/types/tasks.d.ts +2 -0
  248. package/dist/store/types/tasks.d.ts.map +1 -1
  249. package/dist/task/backend/memory.d.ts +4 -1
  250. package/dist/task/backend/memory.d.ts.map +1 -1
  251. package/dist/task/backend/memory.js +81 -0
  252. package/dist/task/backend/memory.js.map +1 -1
  253. package/dist/task/backend/types.d.ts +30 -0
  254. package/dist/task/backend/types.d.ts.map +1 -1
  255. package/dist/task/backend/types.js.map +1 -1
  256. package/dist/teams/index.d.ts +4 -0
  257. package/dist/teams/index.d.ts.map +1 -0
  258. package/dist/teams/index.js +4 -0
  259. package/dist/teams/index.js.map +1 -0
  260. package/dist/teams/team-loader.d.ts +20 -0
  261. package/dist/teams/team-loader.d.ts.map +1 -0
  262. package/dist/teams/team-loader.js +293 -0
  263. package/dist/teams/team-loader.js.map +1 -0
  264. package/dist/teams/team-runtime.d.ts +139 -0
  265. package/dist/teams/team-runtime.d.ts.map +1 -0
  266. package/dist/teams/team-runtime.js +613 -0
  267. package/dist/teams/team-runtime.js.map +1 -0
  268. package/dist/teams/types.d.ts +266 -0
  269. package/dist/teams/types.d.ts.map +1 -0
  270. package/dist/teams/types.js +20 -0
  271. package/dist/teams/types.js.map +1 -0
  272. package/dist/trigger/router/trigger-router.d.ts +30 -3
  273. package/dist/trigger/router/trigger-router.d.ts.map +1 -1
  274. package/dist/trigger/router/trigger-router.js +30 -3
  275. package/dist/trigger/router/trigger-router.js.map +1 -1
  276. package/dist/trigger/wake/types.d.ts +31 -5
  277. package/dist/trigger/wake/types.d.ts.map +1 -1
  278. package/dist/trigger/wake/types.js +19 -0
  279. package/dist/trigger/wake/types.js.map +1 -1
  280. package/dist/workspace/dataplane-adapter.d.ts +1 -1
  281. package/dist/workspace/dataplane-adapter.d.ts.map +1 -1
  282. package/dist/workspace/dataplane-adapter.js +1 -1
  283. package/dist/workspace/dataplane-adapter.js.map +1 -1
  284. package/dist/workspace/index.d.ts +1 -1
  285. package/dist/workspace/index.d.ts.map +1 -1
  286. package/dist/workspace/strategies/index.d.ts +6 -0
  287. package/dist/workspace/strategies/index.d.ts.map +1 -0
  288. package/dist/workspace/strategies/index.js +5 -0
  289. package/dist/workspace/strategies/index.js.map +1 -0
  290. package/dist/workspace/strategies/optimistic.d.ts +26 -0
  291. package/dist/workspace/strategies/optimistic.d.ts.map +1 -0
  292. package/dist/workspace/strategies/optimistic.js +121 -0
  293. package/dist/workspace/strategies/optimistic.js.map +1 -0
  294. package/dist/workspace/strategies/queue.d.ts +26 -0
  295. package/dist/workspace/strategies/queue.d.ts.map +1 -0
  296. package/dist/workspace/strategies/queue.js +67 -0
  297. package/dist/workspace/strategies/queue.js.map +1 -0
  298. package/dist/workspace/strategies/registry.d.ts +37 -0
  299. package/dist/workspace/strategies/registry.d.ts.map +1 -0
  300. package/dist/workspace/strategies/registry.js +63 -0
  301. package/dist/workspace/strategies/registry.js.map +1 -0
  302. package/dist/workspace/strategies/trunk.d.ts +20 -0
  303. package/dist/workspace/strategies/trunk.d.ts.map +1 -0
  304. package/dist/workspace/strategies/trunk.js +108 -0
  305. package/dist/workspace/strategies/trunk.js.map +1 -0
  306. package/dist/workspace/strategies/types.d.ts +104 -0
  307. package/dist/workspace/strategies/types.d.ts.map +1 -0
  308. package/dist/workspace/strategies/types.js +11 -0
  309. package/dist/workspace/strategies/types.js.map +1 -0
  310. package/dist/workspace/types.d.ts +1 -1
  311. package/dist/workspace/types.d.ts.map +1 -1
  312. package/dist/workspace/workspace-manager.d.ts +1 -1
  313. package/dist/workspace/workspace-manager.d.ts.map +1 -1
  314. package/docs/implementation-details.md +1127 -0
  315. package/docs/implementation-summary.md +448 -0
  316. package/docs/mail-integration.md +608 -0
  317. package/docs/plan-self-driving-support.md +433 -0
  318. package/docs/spec-self-driving-support.md +462 -0
  319. package/docs/team-templates.md +860 -0
  320. package/docs/teams.md +233 -0
  321. package/package.json +5 -3
  322. package/src/acp/__tests__/integration.test.ts +161 -1
  323. package/src/acp/__tests__/macro-agent.test.ts +95 -0
  324. package/src/acp/__tests__/session-persistence.test.ts +276 -0
  325. package/src/acp/macro-agent.ts +79 -7
  326. package/src/acp/session-mapper.ts +108 -1
  327. package/src/acp/types.ts +33 -1
  328. package/src/agent/agent-manager.ts +278 -6
  329. package/src/agent/types.ts +27 -0
  330. package/src/agent/wake.ts +15 -0
  331. package/src/agent-detection/__tests__/command-builder.test.ts +336 -0
  332. package/src/agent-detection/__tests__/detector.test.ts +768 -0
  333. package/src/agent-detection/__tests__/registry.test.ts +254 -0
  334. package/src/agent-detection/command-builder.ts +90 -0
  335. package/src/agent-detection/detector.ts +307 -0
  336. package/src/agent-detection/index.ts +36 -0
  337. package/src/agent-detection/registry.ts +200 -0
  338. package/src/agent-detection/types.ts +184 -0
  339. package/src/api/__tests__/conversation-api.test.ts +468 -0
  340. package/src/api/server.ts +425 -1
  341. package/src/api/types.ts +64 -1
  342. package/src/cli/acp.ts +9 -1
  343. package/src/cli/index.ts +44 -0
  344. package/src/cli/mcp.ts +47 -0
  345. package/src/config/index.ts +9 -0
  346. package/src/config/project-config.ts +107 -0
  347. package/src/lifecycle/cascade.ts +1 -1
  348. package/src/lifecycle/handlers/index.ts +8 -0
  349. package/src/lifecycle/handlers/worker.ts +48 -3
  350. package/src/mail/__tests__/conversation-lifecycle.test.ts +409 -0
  351. package/src/mail/__tests__/eventstore-stores.test.ts +1073 -0
  352. package/src/mail/__tests__/mail-full-agent.e2e.test.ts +575 -0
  353. package/src/mail/__tests__/mail-integration.test.ts +759 -0
  354. package/src/mail/__tests__/mail-map-protocol.e2e.test.ts +1068 -0
  355. package/src/mail/__tests__/mail-service.test.ts +506 -0
  356. package/src/mail/__tests__/turn-recorder.test.ts +328 -0
  357. package/src/mail/conversation-map.ts +107 -0
  358. package/src/mail/index.ts +25 -0
  359. package/src/mail/mail-service.ts +257 -0
  360. package/src/mail/stores/eventstore-conversation-store.ts +146 -0
  361. package/src/mail/stores/eventstore-participant-store.ts +172 -0
  362. package/src/mail/stores/eventstore-thread-store.ts +129 -0
  363. package/src/mail/stores/eventstore-turn-store.ts +173 -0
  364. package/src/mail/stores/index.ts +12 -0
  365. package/src/mail/stores/types.ts +160 -0
  366. package/src/mail/turn-recorder.ts +124 -0
  367. package/src/map/README.md +79 -0
  368. package/src/map/adapter/__tests__/extensions.test.ts +359 -0
  369. package/src/map/adapter/__tests__/map-adapter.test.ts +90 -0
  370. package/src/map/adapter/__tests__/workspace-files.test.ts +673 -0
  371. package/src/map/adapter/acp-over-map.ts +45 -2
  372. package/src/map/adapter/event-translator.ts +4 -0
  373. package/src/map/adapter/extensions/agent-detection.ts +201 -0
  374. package/src/map/adapter/extensions/index.ts +63 -0
  375. package/src/map/adapter/extensions/resume.ts +114 -0
  376. package/src/map/adapter/extensions/workspace-files.ts +449 -0
  377. package/src/map/adapter/mail-handler-adapter.ts +429 -0
  378. package/src/map/adapter/map-adapter.ts +173 -27
  379. package/src/map/adapter/rpc-handler.ts +8 -1
  380. package/src/map/index.ts +3 -0
  381. package/src/map/types.ts +3 -1
  382. package/src/mcp/mcp-server.ts +67 -0
  383. package/src/mcp/tools/claim_task.ts +86 -0
  384. package/src/mcp/tools/done.ts +59 -10
  385. package/src/mcp/tools/list_claimable_tasks.ts +93 -0
  386. package/src/mcp/tools/unclaim_task.ts +71 -0
  387. package/src/metrics/index.ts +9 -0
  388. package/src/metrics/metrics.ts +280 -0
  389. package/src/roles/capabilities.ts +3 -0
  390. package/src/roles/types.ts +2 -1
  391. package/src/router/README.md +120 -0
  392. package/src/router/__tests__/message-router.test.ts +561 -0
  393. package/src/router/channels.ts +3 -4
  394. package/src/router/message-router.ts +308 -22
  395. package/src/router/role-resolver.ts +22 -1
  396. package/src/router/types.ts +36 -1
  397. package/src/server/combined-server.ts +36 -2
  398. package/src/store/README.md +134 -0
  399. package/src/store/event-store.ts +546 -3
  400. package/src/store/types/agents.ts +1 -1
  401. package/src/store/types/conversations.ts +129 -0
  402. package/src/store/types/events.ts +5 -1
  403. package/src/store/types/index.ts +2 -0
  404. package/src/store/types/sessions.ts +53 -0
  405. package/src/store/types/tasks.ts +3 -0
  406. package/src/task/backend/memory.ts +116 -0
  407. package/src/task/backend/types.ts +43 -0
  408. package/src/teams/__tests__/cross-subsystem.integration.test.ts +983 -0
  409. package/src/teams/__tests__/e2e/team-runtime.e2e.test.ts +553 -0
  410. package/src/teams/__tests__/team-system.test.ts +1280 -0
  411. package/src/teams/index.ts +13 -0
  412. package/src/teams/team-loader.ts +434 -0
  413. package/src/teams/team-runtime.ts +727 -0
  414. package/src/teams/types.ts +377 -0
  415. package/src/trigger/router/trigger-router.ts +30 -3
  416. package/src/trigger/wake/types.ts +32 -5
  417. package/src/trigger/wake/wake-manager.ts +2 -2
  418. package/src/workspace/dataplane-adapter.ts +1 -1
  419. package/src/workspace/index.ts +1 -1
  420. package/src/workspace/strategies/index.ts +18 -0
  421. package/src/workspace/strategies/optimistic.ts +136 -0
  422. package/src/workspace/strategies/queue.ts +81 -0
  423. package/src/workspace/strategies/registry.ts +89 -0
  424. package/src/workspace/strategies/trunk.ts +123 -0
  425. package/src/workspace/strategies/types.ts +145 -0
  426. package/src/workspace/types.ts +1 -1
  427. package/src/workspace/workspace-manager.ts +1 -1
  428. package/.claude/settings.local.json +0 -59
  429. package/dist/map/utils/address-translation.d.ts +0 -99
  430. package/dist/map/utils/address-translation.d.ts.map +0 -1
  431. package/dist/map/utils/address-translation.js +0 -285
  432. package/dist/map/utils/address-translation.js.map +0 -1
  433. package/dist/map/utils/index.d.ts +0 -7
  434. package/dist/map/utils/index.d.ts.map +0 -1
  435. package/dist/map/utils/index.js +0 -7
  436. package/dist/map/utils/index.js.map +0 -1
  437. package/openspec/AGENTS.md +0 -456
  438. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/design.md +0 -128
  439. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/proposal.md +0 -49
  440. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/agent-manager/spec.md +0 -150
  441. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/cli-api/spec.md +0 -258
  442. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/event-store/spec.md +0 -160
  443. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/mcp-tools/spec.md +0 -224
  444. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/message-router/spec.md +0 -153
  445. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/task-manager/spec.md +0 -136
  446. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/tasks.md +0 -147
  447. package/openspec/project.md +0 -31
  448. package/openspec/specs/agent-manager/spec.md +0 -154
  449. package/openspec/specs/cli-api/spec.md +0 -262
  450. package/openspec/specs/event-store/spec.md +0 -164
  451. package/openspec/specs/mcp-tools/spec.md +0 -228
  452. package/openspec/specs/message-router/spec.md +0 -157
  453. package/openspec/specs/task-manager/spec.md +0 -140
  454. package/references/acp-factory-ref/CHANGELOG.md +0 -33
  455. package/references/acp-factory-ref/LICENSE +0 -21
  456. package/references/acp-factory-ref/README.md +0 -341
  457. package/references/acp-factory-ref/package-lock.json +0 -3102
  458. package/references/acp-factory-ref/package.json +0 -96
  459. package/references/acp-factory-ref/python/CHANGELOG.md +0 -33
  460. package/references/acp-factory-ref/python/LICENSE +0 -21
  461. package/references/acp-factory-ref/python/Makefile +0 -57
  462. package/references/acp-factory-ref/python/README.md +0 -253
  463. package/references/acp-factory-ref/python/pyproject.toml +0 -73
  464. package/references/acp-factory-ref/python/tests/__init__.py +0 -0
  465. package/references/acp-factory-ref/python/tests/e2e/__init__.py +0 -1
  466. package/references/acp-factory-ref/python/tests/e2e/test_codex_e2e.py +0 -349
  467. package/references/acp-factory-ref/python/tests/e2e/test_gemini_e2e.py +0 -165
  468. package/references/acp-factory-ref/python/tests/e2e/test_opencode_e2e.py +0 -296
  469. package/references/acp-factory-ref/python/tests/test_client_handler.py +0 -543
  470. package/references/acp-factory-ref/python/tests/test_pushable.py +0 -199
  471. package/references/claude-code-acp/.github/workflows/ci.yml +0 -45
  472. package/references/claude-code-acp/.github/workflows/publish.yml +0 -34
  473. package/references/claude-code-acp/.prettierrc.json +0 -4
  474. package/references/claude-code-acp/CHANGELOG.md +0 -249
  475. package/references/claude-code-acp/LICENSE +0 -222
  476. package/references/claude-code-acp/README.md +0 -53
  477. package/references/claude-code-acp/docs/RELEASES.md +0 -24
  478. package/references/claude-code-acp/eslint.config.js +0 -48
  479. package/references/claude-code-acp/package-lock.json +0 -4570
  480. package/references/claude-code-acp/package.json +0 -88
  481. package/references/claude-code-acp/scripts/release.sh +0 -119
  482. package/references/claude-code-acp/src/acp-agent.ts +0 -2065
  483. package/references/claude-code-acp/src/index.ts +0 -26
  484. package/references/claude-code-acp/src/lib.ts +0 -38
  485. package/references/claude-code-acp/src/mcp-server.ts +0 -911
  486. package/references/claude-code-acp/src/settings.ts +0 -522
  487. package/references/claude-code-acp/src/tests/.claude/commands/quick-math.md +0 -5
  488. package/references/claude-code-acp/src/tests/.claude/commands/say-hello.md +0 -6
  489. package/references/claude-code-acp/src/tests/acp-agent-fork.test.ts +0 -479
  490. package/references/claude-code-acp/src/tests/acp-agent.test.ts +0 -1502
  491. package/references/claude-code-acp/src/tests/extract-lines.test.ts +0 -103
  492. package/references/claude-code-acp/src/tests/fork-session.test.ts +0 -335
  493. package/references/claude-code-acp/src/tests/replace-and-calculate-location.test.ts +0 -334
  494. package/references/claude-code-acp/src/tests/settings.test.ts +0 -617
  495. package/references/claude-code-acp/src/tests/skills-options.test.ts +0 -187
  496. package/references/claude-code-acp/src/tests/tools.test.ts +0 -318
  497. package/references/claude-code-acp/src/tests/typescript-declarations.test.ts +0 -558
  498. package/references/claude-code-acp/src/tools.ts +0 -819
  499. package/references/claude-code-acp/src/utils.ts +0 -171
  500. package/references/claude-code-acp/tsconfig.json +0 -18
  501. package/references/claude-code-acp/vitest.config.ts +0 -19
  502. package/references/multi-agent-protocol/.sudocode/issues.jsonl +0 -82
  503. package/references/multi-agent-protocol/.sudocode/specs.jsonl +0 -9
  504. package/references/multi-agent-protocol/LICENSE +0 -21
  505. package/references/multi-agent-protocol/README.md +0 -113
  506. package/references/multi-agent-protocol/docs/00-design-specification.md +0 -460
  507. package/references/multi-agent-protocol/docs/01-open-questions.md +0 -1050
  508. package/references/multi-agent-protocol/docs/02-wire-protocol.md +0 -296
  509. package/references/multi-agent-protocol/docs/03-streaming-semantics.md +0 -252
  510. package/references/multi-agent-protocol/docs/04-error-handling.md +0 -231
  511. package/references/multi-agent-protocol/docs/05-connection-model.md +0 -244
  512. package/references/multi-agent-protocol/docs/06-visibility-permissions.md +0 -243
  513. package/references/multi-agent-protocol/docs/07-federation.md +0 -259
  514. package/references/multi-agent-protocol/docs/08-macro-agent-migration.md +0 -253
  515. package/references/multi-agent-protocol/package-lock.json +0 -3239
  516. package/references/multi-agent-protocol/package.json +0 -56
  517. package/references/multi-agent-protocol/schema/meta.json +0 -337
  518. package/references/multi-agent-protocol/schema/schema.json +0 -1828
@@ -0,0 +1,759 @@
1
+ /**
2
+ * Mail Integration Tests
3
+ *
4
+ * Full pipeline integration tests verifying MailService, ConversationMap,
5
+ * TurnRecorder, MessageRouter, and EventStore all work together with
6
+ * real (in-memory) services.
7
+ */
8
+
9
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
10
+ import { createEventStore, type EventStore } from "../../store/event-store.js";
11
+ import { createMailService, type MailService } from "../mail-service.js";
12
+ import { createConversationMap, type ConversationMap } from "../conversation-map.js";
13
+ import { createTurnRecorder } from "../turn-recorder.js";
14
+ import {
15
+ createMessageRouter,
16
+ type MessageRouter,
17
+ } from "../../router/message-router.js";
18
+ import type { AgentId } from "../../store/types/index.js";
19
+
20
+ describe("Mail Integration - Full Pipeline", () => {
21
+ let eventStore: EventStore;
22
+ let messageRouter: MessageRouter;
23
+ let mailService: MailService;
24
+ let conversationMap: ConversationMap;
25
+
26
+ beforeEach(async () => {
27
+ eventStore = await createEventStore({ inMemory: true });
28
+ messageRouter = createMessageRouter(eventStore);
29
+ mailService = createMailService({ eventStore });
30
+ conversationMap = createConversationMap();
31
+
32
+ // Wire turn recorder into message router
33
+ const turnRecorder = createTurnRecorder({
34
+ mailService,
35
+ conversationMap,
36
+ eventStore,
37
+ });
38
+ messageRouter.setTurnRecorder(turnRecorder);
39
+ });
40
+
41
+ afterEach(async () => {
42
+ await eventStore.close();
43
+ });
44
+
45
+ // ─────────────────────────────────────────────────────────────────
46
+ // Helpers
47
+ // ─────────────────────────────────────────────────────────────────
48
+
49
+ function spawnAgent(agentId: string, parentId?: string, task?: string): void {
50
+ const lineage: string[] = [];
51
+ if (parentId) {
52
+ const parent = eventStore.getAgent(parentId as AgentId);
53
+ if (parent) {
54
+ lineage.push(...parent.lineage, parentId);
55
+ } else {
56
+ lineage.push(parentId);
57
+ }
58
+ }
59
+ eventStore.emit({
60
+ type: "spawn",
61
+ source: { agent_id: (parentId ?? agentId) as AgentId },
62
+ payload: {
63
+ agent_id: agentId,
64
+ session_id: `session-${agentId}`,
65
+ task: task ?? `Task for ${agentId}`,
66
+ parent: parentId ?? null,
67
+ lineage,
68
+ role: "worker",
69
+ },
70
+ });
71
+ }
72
+
73
+ function setupTaskConversation(agentId: string, parentId: string): string {
74
+ const parentConversationId =
75
+ conversationMap.getAgentConversation(parentId) ??
76
+ conversationMap.getSessionConversation(parentId);
77
+
78
+ const { conversationId } = mailService.createConversation({
79
+ type: "task",
80
+ subject: `Task conversation for ${agentId}`,
81
+ createdBy: parentId,
82
+ parentConversationId,
83
+ });
84
+ mailService.joinConversation({
85
+ conversationId,
86
+ participantId: parentId,
87
+ role: "initiator",
88
+ });
89
+ mailService.joinConversation({
90
+ conversationId,
91
+ participantId: agentId,
92
+ role: "worker",
93
+ });
94
+ conversationMap.setAgentConversation(agentId, conversationId);
95
+ return conversationId;
96
+ }
97
+
98
+ function setupSessionConversation(hmId: string): string {
99
+ const { conversationId } = mailService.createConversation({
100
+ type: "session",
101
+ subject: "User session",
102
+ createdBy: "user",
103
+ });
104
+ mailService.joinConversation({
105
+ conversationId,
106
+ participantId: hmId,
107
+ role: "worker",
108
+ });
109
+ conversationMap.setSessionConversation(hmId, conversationId);
110
+ return conversationId;
111
+ }
112
+
113
+ function subscribeAgent(agentId: string, parentId?: string): void {
114
+ messageRouter.setupDefaultSubscriptions({
115
+ agent_id: agentId as AgentId,
116
+ parent_id: parentId as AgentId | undefined,
117
+ });
118
+ }
119
+
120
+ // ─────────────────────────────────────────────────────────────────
121
+ // Full lifecycle
122
+ // ─────────────────────────────────────────────────────────────────
123
+
124
+ describe("Full lifecycle: spawn → messages → done → terminate", () => {
125
+ it("creates session conv, spawns worker, records turns via router, closes on done", async () => {
126
+ // 1. Setup head manager with session conversation
127
+ spawnAgent("hm-1");
128
+ subscribeAgent("hm-1");
129
+ const sessionConvId = setupSessionConversation("hm-1");
130
+
131
+ // 2. Record user message in session conversation
132
+ mailService.recordTurn({
133
+ conversationId: sessionConvId,
134
+ participant: "user",
135
+ contentType: "text",
136
+ content: "Please refactor the auth module",
137
+ });
138
+
139
+ // 3. Spawn worker, setup task conversation
140
+ spawnAgent("worker-1", "hm-1", "Refactor auth");
141
+ subscribeAgent("worker-1", "hm-1");
142
+ const taskConvId = setupTaskConversation("worker-1", "hm-1");
143
+
144
+ // 4. Head manager sends task instruction to worker via router
145
+ await messageRouter.sendToAddress({
146
+ from: "hm-1" as AgentId,
147
+ to: { agent: "worker-1" as AgentId },
148
+ content: "Refactor the auth module, focus on token storage",
149
+ });
150
+
151
+ // Verify: turn intercepted in worker's task conversation
152
+ let taskTurns = mailService.listTurns({ conversationId: taskConvId });
153
+ expect(taskTurns).toHaveLength(1);
154
+ expect(taskTurns[0].participant).toBe("hm-1");
155
+ expect(taskTurns[0].content).toBe("Refactor the auth module, focus on token storage");
156
+ expect(taskTurns[0].sourceType).toBe("intercepted");
157
+
158
+ // 5. Worker sends reply to head manager
159
+ await messageRouter.sendToAddress({
160
+ from: "worker-1" as AgentId,
161
+ to: { agent: "hm-1" as AgentId },
162
+ content: "Found 3 issues in auth module, proceeding with fix",
163
+ });
164
+
165
+ // Verify: 2 turns in task conversation (parent→child + child→parent)
166
+ taskTurns = mailService.listTurns({ conversationId: taskConvId });
167
+ expect(taskTurns).toHaveLength(2);
168
+ expect(taskTurns[0].participant).toBe("hm-1");
169
+ expect(taskTurns[1].participant).toBe("worker-1");
170
+
171
+ // 6. Simulate worker done() — record completion turn + close
172
+ mailService.recordTurn({
173
+ conversationId: taskConvId,
174
+ participant: "worker-1",
175
+ contentType: "event",
176
+ content: { event: "agent.completed", summary: "Auth refactored" },
177
+ });
178
+ mailService.closeConversation({
179
+ conversationId: taskConvId,
180
+ closedBy: "worker-1",
181
+ reason: "completed",
182
+ });
183
+ conversationMap.removeAgent("worker-1");
184
+
185
+ // 7. Head manager responds in session conversation
186
+ mailService.recordTurn({
187
+ conversationId: sessionConvId,
188
+ participant: "hm-1",
189
+ contentType: "text",
190
+ content: "Done. The auth module has been refactored.",
191
+ });
192
+
193
+ // Verify final state
194
+ const sessionConv = mailService.getConversation(sessionConvId);
195
+ expect(sessionConv!.status).toBe("active");
196
+ const sessionTurns = mailService.listTurns({ conversationId: sessionConvId });
197
+ expect(sessionTurns).toHaveLength(2); // user + assistant
198
+
199
+ const taskConv = mailService.getConversation(taskConvId);
200
+ expect(taskConv!.status).toBe("completed");
201
+ const finalTaskTurns = mailService.listTurns({ conversationId: taskConvId });
202
+ expect(finalTaskTurns).toHaveLength(3); // 2 intercepted + 1 event
203
+
204
+ // Verify total conversations
205
+ const allConvs = mailService.listConversations();
206
+ expect(allConvs).toHaveLength(2);
207
+ });
208
+ });
209
+
210
+ // ─────────────────────────────────────────────────────────────────
211
+ // Multi-level hierarchy
212
+ // ─────────────────────────────────────────────────────────────────
213
+
214
+ describe("Multi-level hierarchy", () => {
215
+ it("creates conversation tree with correct parentConversationId chain", () => {
216
+ // Setup 3-level hierarchy
217
+ spawnAgent("gp");
218
+ const sessionConvId = setupSessionConversation("gp");
219
+
220
+ spawnAgent("parent", "gp");
221
+ const parentTaskConvId = setupTaskConversation("parent", "gp");
222
+
223
+ spawnAgent("child", "parent");
224
+ const childTaskConvId = setupTaskConversation("child", "parent");
225
+
226
+ // Verify parentConversationId chain
227
+ const parentConv = mailService.getConversation(parentTaskConvId);
228
+ expect(parentConv!.parentConversationId).toBe(sessionConvId);
229
+
230
+ const childConv = mailService.getConversation(childTaskConvId);
231
+ expect(childConv!.parentConversationId).toBe(parentTaskConvId);
232
+
233
+ // Verify total: 1 session + 2 task
234
+ expect(mailService.listConversations()).toHaveLength(3);
235
+ });
236
+
237
+ it("routes messages correctly across three levels", async () => {
238
+ // Setup 3-level hierarchy with subscriptions
239
+ spawnAgent("gp");
240
+ subscribeAgent("gp");
241
+ setupSessionConversation("gp");
242
+
243
+ spawnAgent("parent", "gp");
244
+ subscribeAgent("parent", "gp");
245
+ const parentConvId = setupTaskConversation("parent", "gp");
246
+
247
+ spawnAgent("child", "parent");
248
+ subscribeAgent("child", "parent");
249
+ const childConvId = setupTaskConversation("child", "parent");
250
+
251
+ // gp → parent: turn in parent's task conversation
252
+ await messageRouter.sendToAddress({
253
+ from: "gp" as AgentId,
254
+ to: { agent: "parent" as AgentId },
255
+ content: "GP to parent",
256
+ });
257
+ expect(mailService.listTurns({ conversationId: parentConvId })).toHaveLength(1);
258
+ expect(mailService.listTurns({ conversationId: childConvId })).toHaveLength(0);
259
+
260
+ // parent → child: turn in child's task conversation
261
+ await messageRouter.sendToAddress({
262
+ from: "parent" as AgentId,
263
+ to: { agent: "child" as AgentId },
264
+ content: "Parent to child",
265
+ });
266
+ expect(mailService.listTurns({ conversationId: childConvId })).toHaveLength(1);
267
+
268
+ // child → parent: still in child's task conversation
269
+ await messageRouter.sendToAddress({
270
+ from: "child" as AgentId,
271
+ to: { agent: "parent" as AgentId },
272
+ content: "Child to parent",
273
+ });
274
+ expect(mailService.listTurns({ conversationId: childConvId })).toHaveLength(2);
275
+ expect(mailService.listTurns({ conversationId: parentConvId })).toHaveLength(1);
276
+
277
+ // parent → gp: in parent's task conversation
278
+ await messageRouter.sendToAddress({
279
+ from: "parent" as AgentId,
280
+ to: { agent: "gp" as AgentId },
281
+ content: "Parent to GP",
282
+ });
283
+ expect(mailService.listTurns({ conversationId: parentConvId })).toHaveLength(2);
284
+ });
285
+ });
286
+
287
+ // ─────────────────────────────────────────────────────────────────
288
+ // MessageRouter + TurnRecorder integration
289
+ // ─────────────────────────────────────────────────────────────────
290
+
291
+ describe("MessageRouter + TurnRecorder integration", () => {
292
+ it("records turns for agent address messages with correct metadata", async () => {
293
+ spawnAgent("parent");
294
+ subscribeAgent("parent");
295
+ spawnAgent("child", "parent");
296
+ subscribeAgent("child", "parent");
297
+ const taskConvId = setupTaskConversation("child", "parent");
298
+
299
+ await messageRouter.sendToAddress({
300
+ from: "parent" as AgentId,
301
+ to: { agent: "child" as AgentId },
302
+ content: "Hello child",
303
+ });
304
+
305
+ const turns = mailService.listTurns({ conversationId: taskConvId });
306
+ expect(turns).toHaveLength(1);
307
+ expect(turns[0].sourceType).toBe("intercepted");
308
+ expect(turns[0].sourceMessageId).toBeDefined();
309
+ expect(turns[0].participant).toBe("parent");
310
+ expect(turns[0].contentType).toBe("text");
311
+ });
312
+
313
+ it("records turns for task address messages", async () => {
314
+ spawnAgent("parent");
315
+ subscribeAgent("parent");
316
+ spawnAgent("child", "parent");
317
+ subscribeAgent("child", "parent");
318
+ const taskConvId = setupTaskConversation("child", "parent");
319
+
320
+ // Create a task assigned to child
321
+ eventStore.emit({
322
+ type: "task",
323
+ source: { agent_id: "parent" as AgentId },
324
+ payload: {
325
+ action: "created",
326
+ task_id: "task-1",
327
+ details: { description: "Test task" },
328
+ },
329
+ });
330
+ // Assign to child
331
+ eventStore.emit({
332
+ type: "task",
333
+ source: { agent_id: "parent" as AgentId },
334
+ payload: {
335
+ action: "assigned",
336
+ task_id: "task-1",
337
+ details: { agent_id: "child" },
338
+ },
339
+ });
340
+
341
+ await messageRouter.sendToAddress({
342
+ from: "parent" as AgentId,
343
+ to: { task: "task-1" as any },
344
+ content: "Task message",
345
+ });
346
+
347
+ const turns = mailService.listTurns({ conversationId: taskConvId });
348
+ expect(turns).toHaveLength(1);
349
+ expect(turns[0].content).toBe("Task message");
350
+ });
351
+
352
+ it("does NOT record turns for broadcast messages", async () => {
353
+ spawnAgent("broadcaster");
354
+ subscribeAgent("broadcaster");
355
+ spawnAgent("listener", "broadcaster");
356
+ subscribeAgent("listener", "broadcaster");
357
+ setupTaskConversation("listener", "broadcaster");
358
+
359
+ // Subscribe listener to broadcast channel
360
+ messageRouter.subscribe("listener" as AgentId, {
361
+ type: "broadcast",
362
+ name: "announcements",
363
+ });
364
+
365
+ await messageRouter.sendToAddress({
366
+ from: "broadcaster" as AgentId,
367
+ to: { broadcast: true },
368
+ content: "Broadcast announcement",
369
+ });
370
+
371
+ // No turns should be created anywhere
372
+ const allConvs = mailService.listConversations();
373
+ for (const conv of allConvs) {
374
+ const turns = mailService.listTurns({ conversationId: conv.id });
375
+ expect(turns).toHaveLength(0);
376
+ }
377
+ });
378
+ });
379
+
380
+ // ─────────────────────────────────────────────────────────────────
381
+ // Peer conversations
382
+ // ─────────────────────────────────────────────────────────────────
383
+
384
+ describe("Peer conversations", () => {
385
+ it("auto-creates peer conversation when siblings message via router", async () => {
386
+ spawnAgent("coordinator");
387
+ subscribeAgent("coordinator");
388
+ const coordConvId = setupSessionConversation("coordinator");
389
+
390
+ spawnAgent("worker-a", "coordinator");
391
+ subscribeAgent("worker-a", "coordinator");
392
+ setupTaskConversation("worker-a", "coordinator");
393
+
394
+ spawnAgent("worker-b", "coordinator");
395
+ subscribeAgent("worker-b", "coordinator");
396
+ setupTaskConversation("worker-b", "coordinator");
397
+
398
+ // Worker A sends to Worker B (peers, not parent-child)
399
+ await messageRouter.sendToAddress({
400
+ from: "worker-a" as AgentId,
401
+ to: { agent: "worker-b" as AgentId },
402
+ content: "Hey, here's the schema you need",
403
+ });
404
+
405
+ // Peer conversation should exist
406
+ const peerConvId = conversationMap.getPeerConversation("worker-a", "worker-b");
407
+ expect(peerConvId).toBeDefined();
408
+
409
+ const peerConv = mailService.getConversation(peerConvId!);
410
+ expect(peerConv!.type).toBe("peer");
411
+ // Nearest common ancestor is coordinator (session conv)
412
+ expect(peerConv!.parentConversationId).toBe(coordConvId);
413
+
414
+ // Verify participants
415
+ const participants = mailService.listParticipants(peerConvId!);
416
+ expect(participants).toHaveLength(2);
417
+
418
+ // Verify turn
419
+ const turns = mailService.listTurns({ conversationId: peerConvId! });
420
+ expect(turns).toHaveLength(1);
421
+ expect(turns[0].participant).toBe("worker-a");
422
+ });
423
+
424
+ it("reuses peer conversation for reply messages", async () => {
425
+ spawnAgent("parent");
426
+ subscribeAgent("parent");
427
+
428
+ spawnAgent("peer-a", "parent");
429
+ subscribeAgent("peer-a", "parent");
430
+
431
+ spawnAgent("peer-b", "parent");
432
+ subscribeAgent("peer-b", "parent");
433
+
434
+ // First message
435
+ await messageRouter.sendToAddress({
436
+ from: "peer-a" as AgentId,
437
+ to: { agent: "peer-b" as AgentId },
438
+ content: "First",
439
+ });
440
+
441
+ const convId = conversationMap.getPeerConversation("peer-a", "peer-b");
442
+
443
+ // Reply in reverse direction
444
+ await messageRouter.sendToAddress({
445
+ from: "peer-b" as AgentId,
446
+ to: { agent: "peer-a" as AgentId },
447
+ content: "Reply",
448
+ });
449
+
450
+ // Same conversation, 2 turns
451
+ expect(conversationMap.getPeerConversation("peer-a", "peer-b")).toBe(convId);
452
+ const turns = mailService.listTurns({ conversationId: convId! });
453
+ expect(turns).toHaveLength(2);
454
+ expect(turns[0].participant).toBe("peer-a");
455
+ expect(turns[1].participant).toBe("peer-b");
456
+ });
457
+
458
+ it("sets parentConversationId to nearest common ancestor for deep hierarchy", async () => {
459
+ // root → branch-a → leaf-a
460
+ // root → branch-b → leaf-b
461
+ spawnAgent("root");
462
+ subscribeAgent("root");
463
+ const rootConvId = setupSessionConversation("root");
464
+
465
+ spawnAgent("branch-a", "root");
466
+ subscribeAgent("branch-a", "root");
467
+ setupTaskConversation("branch-a", "root");
468
+
469
+ spawnAgent("branch-b", "root");
470
+ subscribeAgent("branch-b", "root");
471
+ setupTaskConversation("branch-b", "root");
472
+
473
+ spawnAgent("leaf-a", "branch-a");
474
+ subscribeAgent("leaf-a", "branch-a");
475
+
476
+ spawnAgent("leaf-b", "branch-b");
477
+ subscribeAgent("leaf-b", "branch-b");
478
+
479
+ // leaf-a messages leaf-b
480
+ await messageRouter.sendToAddress({
481
+ from: "leaf-a" as AgentId,
482
+ to: { agent: "leaf-b" as AgentId },
483
+ content: "Cross-branch message",
484
+ });
485
+
486
+ const peerConvId = conversationMap.getPeerConversation("leaf-a", "leaf-b");
487
+ const peerConv = mailService.getConversation(peerConvId!);
488
+
489
+ // Nearest common ancestor is root (session conv)
490
+ expect(peerConv!.parentConversationId).toBe(rootConvId);
491
+ });
492
+ });
493
+
494
+ // ─────────────────────────────────────────────────────────────────
495
+ // Cascade termination cleanup
496
+ // ─────────────────────────────────────────────────────────────────
497
+
498
+ describe("Cascade termination cleanup", () => {
499
+ it("closes child and peer conversations on terminate", async () => {
500
+ // Setup: coordinator → worker-a, worker-b, peer conv between workers
501
+ spawnAgent("coord");
502
+ subscribeAgent("coord");
503
+ setupSessionConversation("coord");
504
+
505
+ spawnAgent("wa", "coord");
506
+ subscribeAgent("wa", "coord");
507
+ const waConvId = setupTaskConversation("wa", "coord");
508
+
509
+ spawnAgent("wb", "coord");
510
+ subscribeAgent("wb", "coord");
511
+ const wbConvId = setupTaskConversation("wb", "coord");
512
+
513
+ // Create peer conversation
514
+ await messageRouter.sendToAddress({
515
+ from: "wa" as AgentId,
516
+ to: { agent: "wb" as AgentId },
517
+ content: "Peer msg",
518
+ });
519
+ const peerConvId = conversationMap.getPeerConversation("wa", "wb")!;
520
+
521
+ // Simulate terminate cascade (same sequence as agent-manager.ts:709-729)
522
+ // Terminate worker-a
523
+ const waConv = conversationMap.getAgentConversation("wa");
524
+ if (waConv) {
525
+ mailService.closeConversation({ conversationId: waConv, closedBy: "wa", reason: "completed" });
526
+ }
527
+ const waPeerConvIds = conversationMap.closePeerConversationsFor("wa");
528
+ for (const id of waPeerConvIds) {
529
+ mailService.closeConversation({ conversationId: id, closedBy: "wa", reason: "participant_left" });
530
+ }
531
+ conversationMap.removeAgent("wa");
532
+
533
+ // Terminate worker-b
534
+ const wbConv = conversationMap.getAgentConversation("wb");
535
+ if (wbConv) {
536
+ mailService.closeConversation({ conversationId: wbConv, closedBy: "wb", reason: "completed" });
537
+ }
538
+ conversationMap.closePeerConversationsFor("wb");
539
+ conversationMap.removeAgent("wb");
540
+
541
+ // Verify: all task + peer convs closed
542
+ expect(mailService.getConversation(waConvId)!.status).toBe("completed");
543
+ expect(mailService.getConversation(wbConvId)!.status).toBe("completed");
544
+ expect(mailService.getConversation(peerConvId)!.status).toBe("completed");
545
+
546
+ // Agents removed from map
547
+ expect(conversationMap.getAgentConversation("wa")).toBeUndefined();
548
+ expect(conversationMap.getAgentConversation("wb")).toBeUndefined();
549
+ expect(conversationMap.getPeerConversation("wa", "wb")).toBeUndefined();
550
+
551
+ // Session conversation stays active
552
+ const sessionConvId = conversationMap.getSessionConversation("coord");
553
+ expect(mailService.getConversation(sessionConvId!)!.status).toBe("active");
554
+ });
555
+ });
556
+
557
+ // ─────────────────────────────────────────────────────────────────
558
+ // listConversations filters
559
+ // ─────────────────────────────────────────────────────────────────
560
+
561
+ describe("listConversations filters", () => {
562
+ it("filters by type, status, and parentConversationId", () => {
563
+ // Create 1 session, 2 task, 1 peer
564
+ spawnAgent("root");
565
+ const sessionId = setupSessionConversation("root");
566
+
567
+ spawnAgent("child-1", "root");
568
+ const task1Id = setupTaskConversation("child-1", "root");
569
+
570
+ spawnAgent("child-2", "root");
571
+ setupTaskConversation("child-2", "root");
572
+
573
+ // Create peer
574
+ const { conversationId: peerId } = mailService.createConversation({
575
+ type: "peer",
576
+ subject: "Peer conv",
577
+ createdBy: "child-1",
578
+ });
579
+
580
+ // Close one task
581
+ mailService.closeConversation({
582
+ conversationId: task1Id,
583
+ closedBy: "child-1",
584
+ reason: "completed",
585
+ });
586
+
587
+ // Filter by type
588
+ expect(mailService.listConversations({ type: "session" })).toHaveLength(1);
589
+ expect(mailService.listConversations({ type: "task" })).toHaveLength(2);
590
+ expect(mailService.listConversations({ type: "peer" })).toHaveLength(1);
591
+
592
+ // Filter by status
593
+ expect(mailService.listConversations({ status: "active" })).toHaveLength(3);
594
+ expect(mailService.listConversations({ status: "completed" })).toHaveLength(1);
595
+
596
+ // Filter by parentConversationId
597
+ expect(
598
+ mailService.listConversations({ parentConversationId: sessionId })
599
+ ).toHaveLength(2); // both task convs have session as parent
600
+
601
+ // Total
602
+ expect(mailService.listConversations()).toHaveLength(4);
603
+ });
604
+ });
605
+
606
+ // ─────────────────────────────────────────────────────────────────
607
+ // Error paths and edge cases
608
+ // ─────────────────────────────────────────────────────────────────
609
+
610
+ describe("Error paths and edge cases", () => {
611
+ it("allows recording turns in a closed conversation (no validation at service layer)", () => {
612
+ spawnAgent("parent");
613
+ spawnAgent("child", "parent");
614
+ const convId = setupTaskConversation("child", "parent");
615
+
616
+ // Close the conversation
617
+ mailService.closeConversation({ conversationId: convId, closedBy: "child", reason: "completed" });
618
+ expect(mailService.getConversation(convId)!.status).toBe("completed");
619
+
620
+ // MailService does NOT validate — turn still records
621
+ const { turnId } = mailService.recordTurn({
622
+ conversationId: convId,
623
+ participant: "parent",
624
+ contentType: "text",
625
+ content: "Late message",
626
+ });
627
+ expect(turnId).toBeDefined();
628
+
629
+ const turns = mailService.listTurns({ conversationId: convId });
630
+ expect(turns).toHaveLength(1);
631
+ expect(turns[0].content).toBe("Late message");
632
+ });
633
+
634
+ it("allows double-closing a conversation (idempotent at service layer)", () => {
635
+ const { conversationId } = mailService.createConversation({
636
+ type: "session",
637
+ subject: "Double close",
638
+ createdBy: "user",
639
+ });
640
+
641
+ mailService.closeConversation({ conversationId, closedBy: "user", reason: "completed" });
642
+ expect(mailService.getConversation(conversationId)!.status).toBe("completed");
643
+
644
+ // Second close doesn't throw — just re-applies
645
+ mailService.closeConversation({ conversationId, closedBy: "user", reason: "failed" });
646
+ expect(mailService.getConversation(conversationId)!.status).toBe("failed");
647
+ });
648
+
649
+ it("TurnRecorder silently skips when child task conv doesn't exist", async () => {
650
+ spawnAgent("parent");
651
+ subscribeAgent("parent");
652
+ spawnAgent("child", "parent");
653
+ subscribeAgent("child", "parent");
654
+ // No task conversation set up for child
655
+
656
+ // Should not throw
657
+ await messageRouter.sendToAddress({
658
+ from: "parent" as AgentId,
659
+ to: { agent: "child" as AgentId },
660
+ content: "No conv",
661
+ });
662
+
663
+ // No conversations created
664
+ expect(mailService.listConversations()).toHaveLength(0);
665
+ });
666
+
667
+ it("handles messages between agents with no common ancestor gracefully", async () => {
668
+ spawnAgent("root-a");
669
+ subscribeAgent("root-a");
670
+ spawnAgent("root-b");
671
+ subscribeAgent("root-b");
672
+
673
+ // Peers with no common ancestor
674
+ await messageRouter.sendToAddress({
675
+ from: "root-a" as AgentId,
676
+ to: { agent: "root-b" as AgentId },
677
+ content: "No ancestor",
678
+ });
679
+
680
+ const peerConvId = conversationMap.getPeerConversation("root-a", "root-b");
681
+ expect(peerConvId).toBeDefined();
682
+
683
+ const conv = mailService.getConversation(peerConvId!);
684
+ expect(conv!.type).toBe("peer");
685
+ expect(conv!.parentConversationId).toBeUndefined();
686
+ });
687
+
688
+ it("turn-recorder does not fail message routing when recordTurn throws", async () => {
689
+ spawnAgent("parent");
690
+ subscribeAgent("parent");
691
+ spawnAgent("child", "parent");
692
+ subscribeAgent("child", "parent");
693
+
694
+ const taskConvId = setupTaskConversation("child", "parent");
695
+
696
+ // Sabotage the mailService.recordTurn to throw
697
+ const originalRecordTurn = mailService.recordTurn.bind(mailService);
698
+ mailService.recordTurn = () => {
699
+ throw new Error("Simulated store failure");
700
+ };
701
+
702
+ // Message routing should still succeed (no throw)
703
+ await expect(
704
+ messageRouter.sendToAddress({
705
+ from: "parent" as AgentId,
706
+ to: { agent: "child" as AgentId },
707
+ content: "Should still route",
708
+ })
709
+ ).resolves.not.toThrow();
710
+
711
+ // Restore original and send another message to verify routing still works
712
+ mailService.recordTurn = originalRecordTurn;
713
+ await messageRouter.sendToAddress({
714
+ from: "parent" as AgentId,
715
+ to: { agent: "child" as AgentId },
716
+ content: "After recovery",
717
+ });
718
+
719
+ // The recovered turn should be recorded
720
+ const turns = mailService.listTurns({ conversationId: taskConvId });
721
+ expect(turns.length).toBeGreaterThanOrEqual(1);
722
+ expect(turns.some((t) => t.content === "After recovery")).toBe(true);
723
+ });
724
+
725
+ it("does not create duplicate peer conversations for same pair", async () => {
726
+ spawnAgent("parent");
727
+ subscribeAgent("parent");
728
+ spawnAgent("a", "parent");
729
+ subscribeAgent("a", "parent");
730
+ spawnAgent("b", "parent");
731
+ subscribeAgent("b", "parent");
732
+
733
+ // Send multiple messages in both directions
734
+ await messageRouter.sendToAddress({
735
+ from: "a" as AgentId,
736
+ to: { agent: "b" as AgentId },
737
+ content: "msg-1",
738
+ });
739
+ await messageRouter.sendToAddress({
740
+ from: "b" as AgentId,
741
+ to: { agent: "a" as AgentId },
742
+ content: "msg-2",
743
+ });
744
+ await messageRouter.sendToAddress({
745
+ from: "a" as AgentId,
746
+ to: { agent: "b" as AgentId },
747
+ content: "msg-3",
748
+ });
749
+
750
+ // Only 1 peer conversation should exist
751
+ const peerConvs = mailService.listConversations({ type: "peer" });
752
+ expect(peerConvs).toHaveLength(1);
753
+
754
+ // All 3 turns in that single conversation
755
+ const turns = mailService.listTurns({ conversationId: peerConvs[0].id });
756
+ expect(turns).toHaveLength(3);
757
+ });
758
+ });
759
+ });