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,1068 @@
1
+ /**
2
+ * Mail MAP Protocol E2E Tests
3
+ *
4
+ * Tests MAP mail/* protocol methods via real WebSocket connections,
5
+ * REST API endpoints, and WebSocket subscription channels.
6
+ *
7
+ * Uses a real CombinedServer with in-memory EventStore.
8
+ */
9
+
10
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
11
+ import { WebSocket } from "ws";
12
+ import { createEventStore, type EventStore } from "../../store/event-store.js";
13
+ import {
14
+ createAgentManager,
15
+ type AgentManager,
16
+ } from "../../agent/agent-manager.js";
17
+ import { createTaskManager, type TaskManager } from "../../task/task-manager.js";
18
+ import {
19
+ createMessageRouter,
20
+ type MessageRouter,
21
+ } from "../../router/message-router.js";
22
+ import {
23
+ createCombinedServer,
24
+ type CombinedServer,
25
+ type CombinedServerServices,
26
+ } from "../../server/combined-server.js";
27
+ import type { AgentId } from "../../store/types/index.js";
28
+
29
+ // ─────────────────────────────────────────────────────────────────
30
+ // Helpers
31
+ // ─────────────────────────────────────────────────────────────────
32
+
33
+ function getRandomPort(): number {
34
+ return 10000 + Math.floor(Math.random() * 50000);
35
+ }
36
+
37
+ interface JsonRpcResponse {
38
+ jsonrpc: "2.0";
39
+ id: number;
40
+ result?: any;
41
+ error?: { code: number; message: string; data?: unknown };
42
+ }
43
+
44
+ /**
45
+ * JSON-RPC WebSocket client for MAP protocol at /map.
46
+ */
47
+ class TestMAPClient {
48
+ private ws!: WebSocket;
49
+ private waiters: Map<number, { resolve: (r: JsonRpcResponse) => void; reject: (e: Error) => void }> = new Map();
50
+ private nextId = 1;
51
+ private url: string;
52
+
53
+ constructor(url: string) {
54
+ this.url = url;
55
+ }
56
+
57
+ async connect(): Promise<void> {
58
+ this.ws = new WebSocket(this.url);
59
+ return new Promise((resolve, reject) => {
60
+ const timeout = setTimeout(() => reject(new Error("MAP connection timeout")), 5000);
61
+ this.ws.on("open", () => {
62
+ clearTimeout(timeout);
63
+ resolve();
64
+ });
65
+ this.ws.on("error", (err) => {
66
+ clearTimeout(timeout);
67
+ reject(err);
68
+ });
69
+ this.ws.on("message", (data: Buffer) => {
70
+ try {
71
+ const msg = JSON.parse(data.toString());
72
+ if (msg.id != null) {
73
+ const waiter = this.waiters.get(msg.id);
74
+ if (waiter) {
75
+ this.waiters.delete(msg.id);
76
+ waiter.resolve(msg as JsonRpcResponse);
77
+ }
78
+ }
79
+ } catch {
80
+ // ignore
81
+ }
82
+ });
83
+ });
84
+ }
85
+
86
+ async request(method: string, params?: unknown): Promise<JsonRpcResponse> {
87
+ const id = this.nextId++;
88
+ return new Promise((resolve, reject) => {
89
+ const timeout = setTimeout(() => {
90
+ this.waiters.delete(id);
91
+ reject(new Error(`MAP request timeout: ${method}`));
92
+ }, 10000);
93
+ this.waiters.set(id, {
94
+ resolve: (r) => { clearTimeout(timeout); resolve(r); },
95
+ reject: (e) => { clearTimeout(timeout); reject(e); },
96
+ });
97
+ this.ws.send(JSON.stringify({ jsonrpc: "2.0", method, params, id }));
98
+ });
99
+ }
100
+
101
+ close(): void {
102
+ if (this.ws?.readyState === WebSocket.OPEN) {
103
+ this.ws.close();
104
+ }
105
+ }
106
+ }
107
+
108
+ interface WSMessage {
109
+ type: string;
110
+ [key: string]: unknown;
111
+ }
112
+
113
+ /**
114
+ * Simple WebSocket client for API WebSocket at /api/ws.
115
+ * Uses subscribe/unsubscribe protocol (not JSON-RPC).
116
+ */
117
+ class TestAPIWSClient {
118
+ private ws!: WebSocket;
119
+ private messages: WSMessage[] = [];
120
+ private messageWaiters: Array<{
121
+ check: (msg: WSMessage) => boolean;
122
+ resolve: (msg: WSMessage) => void;
123
+ reject: (err: Error) => void;
124
+ timeout: ReturnType<typeof setTimeout>;
125
+ }> = [];
126
+ private url: string;
127
+
128
+ constructor(url: string) {
129
+ this.url = url;
130
+ }
131
+
132
+ async connect(): Promise<void> {
133
+ this.ws = new WebSocket(this.url);
134
+ return new Promise((resolve, reject) => {
135
+ const timeout = setTimeout(() => reject(new Error("API WS connection timeout")), 5000);
136
+ this.ws.on("open", () => {
137
+ clearTimeout(timeout);
138
+ resolve();
139
+ });
140
+ this.ws.on("error", (err) => {
141
+ clearTimeout(timeout);
142
+ reject(err);
143
+ });
144
+ this.ws.on("message", (data: Buffer) => {
145
+ try {
146
+ const msg = JSON.parse(data.toString()) as WSMessage;
147
+ this.messages.push(msg);
148
+
149
+ // Check pending waiters
150
+ for (let i = this.messageWaiters.length - 1; i >= 0; i--) {
151
+ const waiter = this.messageWaiters[i];
152
+ if (waiter.check(msg)) {
153
+ clearTimeout(waiter.timeout);
154
+ this.messageWaiters.splice(i, 1);
155
+ waiter.resolve(msg);
156
+ }
157
+ }
158
+ } catch {
159
+ // ignore
160
+ }
161
+ });
162
+ });
163
+ }
164
+
165
+ async subscribe(channel: string): Promise<void> {
166
+ this.ws.send(JSON.stringify({ type: "subscribe", channel }));
167
+ // Wait for subscribed confirmation
168
+ await this.waitForMessage((msg) => msg.type === "subscribed" && msg.channel === channel);
169
+ }
170
+
171
+ waitForMessage(
172
+ check: (msg: WSMessage) => boolean,
173
+ timeoutMs = 5000
174
+ ): Promise<WSMessage> {
175
+ // Check existing messages first
176
+ const existing = this.messages.find(check);
177
+ if (existing) return Promise.resolve(existing);
178
+
179
+ return new Promise((resolve, reject) => {
180
+ const timeout = setTimeout(() => {
181
+ const idx = this.messageWaiters.findIndex((w) => w.resolve === resolve);
182
+ if (idx >= 0) this.messageWaiters.splice(idx, 1);
183
+ reject(new Error("Timeout waiting for WebSocket message"));
184
+ }, timeoutMs);
185
+
186
+ this.messageWaiters.push({ check, resolve, reject, timeout });
187
+ });
188
+ }
189
+
190
+ getMessages(): WSMessage[] {
191
+ return [...this.messages];
192
+ }
193
+
194
+ clearMessages(): void {
195
+ this.messages.length = 0;
196
+ }
197
+
198
+ close(): void {
199
+ if (this.ws?.readyState === WebSocket.OPEN) {
200
+ this.ws.close();
201
+ }
202
+ }
203
+ }
204
+
205
+ function spawnAgent(eventStore: EventStore, agentId: string, parentId?: string): void {
206
+ const lineage: string[] = [];
207
+ if (parentId) {
208
+ const parent = eventStore.getAgent(parentId as AgentId);
209
+ if (parent) {
210
+ lineage.push(...parent.lineage, parentId);
211
+ } else {
212
+ lineage.push(parentId);
213
+ }
214
+ }
215
+ eventStore.emit({
216
+ type: "spawn",
217
+ source: { agent_id: (parentId ?? agentId) as AgentId },
218
+ payload: {
219
+ agent_id: agentId,
220
+ session_id: `session-${agentId}`,
221
+ task: `Task for ${agentId}`,
222
+ parent: parentId ?? null,
223
+ lineage,
224
+ role: "worker",
225
+ },
226
+ });
227
+ }
228
+
229
+ // ─────────────────────────────────────────────────────────────────
230
+ // Tests
231
+ // ─────────────────────────────────────────────────────────────────
232
+
233
+ describe("Mail MAP Protocol E2E", () => {
234
+ let eventStore: EventStore;
235
+ let agentManager: AgentManager;
236
+ let taskManager: TaskManager;
237
+ let messageRouter: MessageRouter;
238
+ let server: CombinedServer;
239
+ let port: number;
240
+ let baseUrl: string;
241
+ const clients: Array<{ close(): void }> = [];
242
+
243
+ beforeEach(async () => {
244
+ port = getRandomPort();
245
+ eventStore = await createEventStore({ inMemory: true });
246
+ messageRouter = createMessageRouter(eventStore);
247
+ taskManager = createTaskManager(eventStore);
248
+ agentManager = createAgentManager(eventStore, messageRouter, {
249
+ defaultPermissionMode: "auto-approve",
250
+ defaultCwd: process.cwd(),
251
+ });
252
+
253
+ const services: CombinedServerServices = {
254
+ eventStore,
255
+ agentManager,
256
+ taskManager,
257
+ messageRouter,
258
+ };
259
+
260
+ server = createCombinedServer(services, { port, host: "localhost" });
261
+ await server.start();
262
+ baseUrl = server.getUrl();
263
+ });
264
+
265
+ afterEach(async () => {
266
+ for (const c of clients) {
267
+ c.close();
268
+ }
269
+ clients.length = 0;
270
+ await server.stop().catch(() => {});
271
+ await agentManager.close();
272
+ await eventStore.close();
273
+ });
274
+
275
+ function createMAPClient(): TestMAPClient {
276
+ const client = new TestMAPClient(`ws://localhost:${port}/map`);
277
+ clients.push(client);
278
+ return client;
279
+ }
280
+
281
+ function createAPIWSClient(): TestAPIWSClient {
282
+ const client = new TestAPIWSClient(`ws://localhost:${port}/api/ws`);
283
+ clients.push(client);
284
+ return client;
285
+ }
286
+
287
+ // ─────────────────────────────────────────────────────────────────
288
+ // MAP WebSocket mail/* methods
289
+ // ─────────────────────────────────────────────────────────────────
290
+
291
+ describe("MAP WebSocket mail/* methods", () => {
292
+ describe("mail/create", () => {
293
+ it("creates a conversation via MAP protocol", async () => {
294
+ const client = createMAPClient();
295
+ await client.connect();
296
+
297
+
298
+ const res = await client.request("mail/create", {
299
+ type: "session",
300
+ subject: "Test session from MAP",
301
+ });
302
+
303
+ expect(res.error).toBeUndefined();
304
+ expect(res.result.conversation).toBeDefined();
305
+ expect(res.result.conversation.id).toMatch(/^conv_/);
306
+ expect(res.result.conversation.type).toBe("session");
307
+ expect(res.result.conversation.status).toBe("active");
308
+ expect(res.result.conversation.subject).toBe("Test session from MAP");
309
+ expect(res.result.conversation.participantCount).toBeGreaterThanOrEqual(1);
310
+ });
311
+
312
+ it("creates conversation with initial participants", async () => {
313
+ const client = createMAPClient();
314
+ await client.connect();
315
+
316
+
317
+ const res = await client.request("mail/create", {
318
+ type: "task",
319
+ subject: "Task with participants",
320
+ initialParticipants: [{ id: "agent-1", role: "worker" }],
321
+ });
322
+
323
+ expect(res.error).toBeUndefined();
324
+ expect(res.result.conversation.participantCount).toBe(2);
325
+ });
326
+ });
327
+
328
+ describe("mail/list", () => {
329
+ it("lists conversations created by internal agents", async () => {
330
+ // Pre-populate via server's mailService
331
+ const ms = server.mailService!;
332
+ ms.createConversation({ type: "session", subject: "Session 1", createdBy: "user" });
333
+ ms.createConversation({ type: "task", subject: "Task 1", createdBy: "agent-1" });
334
+ ms.createConversation({ type: "task", subject: "Task 2", createdBy: "agent-2" });
335
+
336
+ const client = createMAPClient();
337
+ await client.connect();
338
+
339
+
340
+ const res = await client.request("mail/list");
341
+ expect(res.error).toBeUndefined();
342
+ expect(res.result.conversations).toHaveLength(3);
343
+ expect(res.result.hasMore).toBe(false);
344
+ });
345
+
346
+ it("filters by type", async () => {
347
+ const ms = server.mailService!;
348
+ ms.createConversation({ type: "session", subject: "S", createdBy: "user" });
349
+ ms.createConversation({ type: "task", subject: "T1", createdBy: "a" });
350
+ ms.createConversation({ type: "task", subject: "T2", createdBy: "b" });
351
+
352
+ const client = createMAPClient();
353
+ await client.connect();
354
+
355
+
356
+ const res = await client.request("mail/list", {
357
+ filter: { type: "session" },
358
+ });
359
+ expect(res.result.conversations).toHaveLength(1);
360
+ expect(res.result.conversations[0].type).toBe("session");
361
+ });
362
+
363
+ it("supports cursor pagination with limit", async () => {
364
+ const ms = server.mailService!;
365
+ for (let i = 0; i < 5; i++) {
366
+ ms.createConversation({ type: "task", subject: `Task ${i}`, createdBy: "a" });
367
+ }
368
+
369
+ const client = createMAPClient();
370
+ await client.connect();
371
+
372
+
373
+ const res = await client.request("mail/list", { limit: 2 });
374
+ expect(res.result.conversations).toHaveLength(2);
375
+ expect(res.result.hasMore).toBe(true);
376
+ expect(res.result.nextCursor).toBeDefined();
377
+ });
378
+ });
379
+
380
+ describe("mail/get", () => {
381
+ it("gets conversation details", async () => {
382
+ const ms = server.mailService!;
383
+ const { conversationId } = ms.createConversation({
384
+ type: "session", subject: "Detail test", createdBy: "user",
385
+ });
386
+
387
+ const client = createMAPClient();
388
+ await client.connect();
389
+
390
+
391
+ const res = await client.request("mail/get", { conversationId });
392
+ expect(res.error).toBeUndefined();
393
+ expect(res.result.conversation.id).toBe(conversationId);
394
+ expect(res.result.conversation.subject).toBe("Detail test");
395
+ });
396
+
397
+ it("includes participants when requested", async () => {
398
+ const ms = server.mailService!;
399
+ const { conversationId } = ms.createConversation({
400
+ type: "task", subject: "P test", createdBy: "a",
401
+ });
402
+ ms.joinConversation({ conversationId, participantId: "a", role: "initiator" });
403
+ ms.joinConversation({ conversationId, participantId: "b", role: "worker" });
404
+
405
+ const client = createMAPClient();
406
+ await client.connect();
407
+
408
+
409
+ const res = await client.request("mail/get", {
410
+ conversationId,
411
+ include: { participants: true },
412
+ });
413
+ expect(res.result.participants).toBeDefined();
414
+ expect(res.result.participants).toHaveLength(2);
415
+ });
416
+
417
+ it("includes recentTurns when requested", async () => {
418
+ const ms = server.mailService!;
419
+ const { conversationId } = ms.createConversation({
420
+ type: "session", subject: "Turns test", createdBy: "user",
421
+ });
422
+ for (let i = 0; i < 5; i++) {
423
+ ms.recordTurn({
424
+ conversationId,
425
+ participant: "user",
426
+ contentType: "text",
427
+ content: `Message ${i}`,
428
+ });
429
+ }
430
+
431
+ const client = createMAPClient();
432
+ await client.connect();
433
+
434
+
435
+ const res = await client.request("mail/get", {
436
+ conversationId,
437
+ include: { recentTurns: 3 },
438
+ });
439
+ expect(res.result.recentTurns).toBeDefined();
440
+ expect(res.result.recentTurns).toHaveLength(3);
441
+ });
442
+
443
+ it("includes stats when requested", async () => {
444
+ const ms = server.mailService!;
445
+ const { conversationId } = ms.createConversation({
446
+ type: "session", subject: "Stats test", createdBy: "user",
447
+ });
448
+ ms.joinConversation({ conversationId, participantId: "a" });
449
+ ms.recordTurn({ conversationId, participant: "a", contentType: "text", content: "Hello" });
450
+
451
+ const client = createMAPClient();
452
+ await client.connect();
453
+
454
+
455
+ const res = await client.request("mail/get", {
456
+ conversationId,
457
+ include: { stats: true },
458
+ });
459
+ expect(res.result.stats).toBeDefined();
460
+ expect(res.result.stats.totalTurns).toBe(1);
461
+ expect(res.result.stats.activeParticipants).toBeGreaterThanOrEqual(1);
462
+ });
463
+
464
+ it("returns error for non-existent conversation", async () => {
465
+ const client = createMAPClient();
466
+ await client.connect();
467
+
468
+
469
+ const res = await client.request("mail/get", { conversationId: "nonexistent" });
470
+ expect(res.error).toBeDefined();
471
+ });
472
+ });
473
+
474
+ describe("mail/turn and mail/turns/list", () => {
475
+ it("records a turn from external MAP client", async () => {
476
+ const ms = server.mailService!;
477
+ const { conversationId } = ms.createConversation({
478
+ type: "session", subject: "Turn test", createdBy: "user",
479
+ });
480
+
481
+ const client = createMAPClient();
482
+ await client.connect();
483
+
484
+
485
+ const res = await client.request("mail/turn", {
486
+ conversationId,
487
+ contentType: "text",
488
+ content: "Hello from MAP client",
489
+ });
490
+
491
+ expect(res.error).toBeUndefined();
492
+ expect(res.result.turn).toBeDefined();
493
+ expect(res.result.turn.content).toBe("Hello from MAP client");
494
+
495
+ // Verify via mailService
496
+ const turns = ms.listTurns({ conversationId });
497
+ expect(turns).toHaveLength(1);
498
+ });
499
+
500
+ it("lists turns from MessageRouter-intercepted messages", async () => {
501
+ const ms = server.mailService!;
502
+ const cm = server.conversationMap!;
503
+
504
+ // Spawn parent + child in EventStore
505
+ spawnAgent(eventStore, "parent-1");
506
+ spawnAgent(eventStore, "child-1", "parent-1");
507
+
508
+ // Set up subscriptions so messages can be delivered
509
+ messageRouter.setupDefaultSubscriptions({
510
+ agent_id: "parent-1" as AgentId,
511
+ });
512
+ messageRouter.setupDefaultSubscriptions({
513
+ agent_id: "child-1" as AgentId,
514
+ parent_id: "parent-1" as AgentId,
515
+ });
516
+
517
+ // Create task conversation and wire ConversationMap
518
+ const { conversationId } = ms.createConversation({
519
+ type: "task", subject: "Task conv", createdBy: "parent-1",
520
+ });
521
+ ms.joinConversation({ conversationId, participantId: "parent-1", role: "initiator" });
522
+ ms.joinConversation({ conversationId, participantId: "child-1", role: "worker" });
523
+ cm.setAgentConversation("child-1", conversationId);
524
+
525
+ // Send messages via router (TurnRecorder intercepts)
526
+ await messageRouter.sendToAddress({
527
+ from: "parent-1" as AgentId,
528
+ to: { agent: "child-1" as AgentId },
529
+ content: "Do this task",
530
+ });
531
+ await messageRouter.sendToAddress({
532
+ from: "child-1" as AgentId,
533
+ to: { agent: "parent-1" as AgentId },
534
+ content: "Done with task",
535
+ });
536
+
537
+ // Query via MAP protocol
538
+ const client = createMAPClient();
539
+ await client.connect();
540
+
541
+
542
+ const res = await client.request("mail/turns/list", { conversationId });
543
+ expect(res.error).toBeUndefined();
544
+ expect(res.result.turns).toHaveLength(2);
545
+ expect(res.result.turns[0].participant).toBe("parent-1");
546
+ expect(res.result.turns[1].participant).toBe("child-1");
547
+ });
548
+
549
+ it("supports pagination", async () => {
550
+ const ms = server.mailService!;
551
+ const { conversationId } = ms.createConversation({
552
+ type: "session", subject: "Paginated", createdBy: "user",
553
+ });
554
+ for (let i = 0; i < 10; i++) {
555
+ ms.recordTurn({ conversationId, participant: "user", contentType: "text", content: `Msg ${i}` });
556
+ }
557
+
558
+ const client = createMAPClient();
559
+ await client.connect();
560
+
561
+
562
+ const res = await client.request("mail/turns/list", { conversationId, limit: 3 });
563
+ expect(res.result.turns).toHaveLength(3);
564
+ expect(res.result.hasMore).toBe(true);
565
+ });
566
+ });
567
+
568
+ describe("mail/close", () => {
569
+ it("closes a conversation via MAP protocol", async () => {
570
+ const ms = server.mailService!;
571
+ const { conversationId } = ms.createConversation({
572
+ type: "session", subject: "Close test", createdBy: "user",
573
+ });
574
+
575
+ const client = createMAPClient();
576
+ await client.connect();
577
+
578
+
579
+ const res = await client.request("mail/close", { conversationId, reason: "completed" });
580
+ expect(res.error).toBeUndefined();
581
+ expect(res.result.conversation.status).toBe("completed");
582
+
583
+ // Verify via mailService
584
+ expect(ms.getConversation(conversationId)!.status).toBe("completed");
585
+ });
586
+ });
587
+
588
+ describe("mail/join and mail/leave", () => {
589
+ it("joins with catch-up history", async () => {
590
+ const ms = server.mailService!;
591
+ const { conversationId } = ms.createConversation({
592
+ type: "task", subject: "Join test", createdBy: "a",
593
+ });
594
+ ms.recordTurn({ conversationId, participant: "a", contentType: "text", content: "Msg 1" });
595
+ ms.recordTurn({ conversationId, participant: "a", contentType: "text", content: "Msg 2" });
596
+
597
+ const client = createMAPClient();
598
+ await client.connect();
599
+
600
+
601
+ const res = await client.request("mail/join", {
602
+ conversationId,
603
+ catchUp: { limit: 10 },
604
+ });
605
+ expect(res.error).toBeUndefined();
606
+ expect(res.result.conversation).toBeDefined();
607
+ expect(res.result.history).toBeDefined();
608
+ expect(res.result.history).toHaveLength(2);
609
+ });
610
+
611
+ it("leaves a conversation", async () => {
612
+ const ms = server.mailService!;
613
+ const { conversationId } = ms.createConversation({
614
+ type: "task", subject: "Leave test", createdBy: "a",
615
+ });
616
+
617
+ const client = createMAPClient();
618
+ await client.connect();
619
+
620
+
621
+ // Join first
622
+ await client.request("mail/join", { conversationId });
623
+
624
+ // Leave
625
+ const res = await client.request("mail/leave", { conversationId });
626
+ expect(res.error).toBeUndefined();
627
+ expect(res.result.success).toBe(true);
628
+ });
629
+ });
630
+
631
+ describe("mail/replay", () => {
632
+ it("replays turns in ascending order", async () => {
633
+ const ms = server.mailService!;
634
+ const { conversationId } = ms.createConversation({
635
+ type: "session", subject: "Replay test", createdBy: "user",
636
+ });
637
+ ms.recordTurn({ conversationId, participant: "user", contentType: "text", content: "First" });
638
+ ms.recordTurn({ conversationId, participant: "agent", contentType: "text", content: "Second" });
639
+ ms.recordTurn({ conversationId, participant: "user", contentType: "text", content: "Third" });
640
+
641
+ const client = createMAPClient();
642
+ await client.connect();
643
+
644
+
645
+ const res = await client.request("mail/replay", { conversationId });
646
+ expect(res.error).toBeUndefined();
647
+ expect(res.result.turns).toHaveLength(3);
648
+ expect(res.result.turns[0].content).toBe("First");
649
+ expect(res.result.turns[2].content).toBe("Third");
650
+ });
651
+ });
652
+ });
653
+
654
+ // ─────────────────────────────────────────────────────────────────
655
+ // REST API /api/conversations
656
+ // ─────────────────────────────────────────────────────────────────
657
+
658
+ describe("REST API /api/conversations", () => {
659
+ it("lists conversations via GET /api/conversations", async () => {
660
+ const ms = server.mailService!;
661
+ ms.createConversation({ type: "session", subject: "S1", createdBy: "user" });
662
+ ms.createConversation({ type: "task", subject: "T1", createdBy: "a" });
663
+
664
+ const res = await fetch(`${baseUrl}/api/conversations`);
665
+ expect(res.status).toBe(200);
666
+
667
+ const body = await res.json();
668
+ expect(body.conversations).toHaveLength(2);
669
+ expect(body.total).toBe(2);
670
+ });
671
+
672
+ it("returns conversation detail via GET /api/conversations/:id", async () => {
673
+ const ms = server.mailService!;
674
+ const { conversationId } = ms.createConversation({
675
+ type: "session", subject: "Detail", createdBy: "user",
676
+ });
677
+
678
+ const res = await fetch(`${baseUrl}/api/conversations/${conversationId}`);
679
+ expect(res.status).toBe(200);
680
+
681
+ const body = await res.json();
682
+ expect(body.id).toBe(conversationId);
683
+ expect(body.type).toBe("session");
684
+ expect(body.status).toBe("active");
685
+ expect(body.subject).toBe("Detail");
686
+ });
687
+
688
+ it("returns turns via GET /api/conversations/:id/turns", async () => {
689
+ const ms = server.mailService!;
690
+ const { conversationId } = ms.createConversation({
691
+ type: "session", subject: "Turns", createdBy: "user",
692
+ });
693
+ ms.recordTurn({ conversationId, participant: "user", contentType: "text", content: "Hello" });
694
+ ms.recordTurn({ conversationId, participant: "agent", contentType: "text", content: "Hi" });
695
+
696
+ const res = await fetch(`${baseUrl}/api/conversations/${conversationId}/turns`);
697
+ expect(res.status).toBe(200);
698
+
699
+ const body = await res.json();
700
+ expect(body.turns).toHaveLength(2);
701
+ expect(body.total).toBe(2);
702
+ expect(body.turns[0].participant).toBe("user");
703
+ expect(body.turns[0].content).toBe("Hello");
704
+ });
705
+
706
+ it("closes via POST /api/conversations/:id/close", async () => {
707
+ const ms = server.mailService!;
708
+ const { conversationId } = ms.createConversation({
709
+ type: "session", subject: "Close", createdBy: "user",
710
+ });
711
+
712
+ const res = await fetch(`${baseUrl}/api/conversations/${conversationId}/close`, {
713
+ method: "POST",
714
+ headers: { "Content-Type": "application/json" },
715
+ body: JSON.stringify({ reason: "completed" }),
716
+ });
717
+ expect(res.status).toBe(200);
718
+
719
+ // Verify closed
720
+ expect(ms.getConversation(conversationId)!.status).toBe("completed");
721
+ });
722
+
723
+ it("returns participants via GET /api/conversations/:id/participants", async () => {
724
+ const ms = server.mailService!;
725
+ const { conversationId } = ms.createConversation({
726
+ type: "task", subject: "Participants", createdBy: "a",
727
+ });
728
+ ms.joinConversation({ conversationId, participantId: "a", role: "initiator" });
729
+ ms.joinConversation({ conversationId, participantId: "b", role: "worker" });
730
+
731
+ const res = await fetch(`${baseUrl}/api/conversations/${conversationId}/participants`);
732
+ expect(res.status).toBe(200);
733
+
734
+ const body = await res.json();
735
+ expect(body.participants).toHaveLength(2);
736
+ expect(body.total).toBe(2);
737
+ });
738
+
739
+ it("returns 404 for non-existent conversation", async () => {
740
+ const res = await fetch(`${baseUrl}/api/conversations/nonexistent`);
741
+ expect(res.status).toBe(404);
742
+
743
+ const body = await res.json();
744
+ expect(body.code).toBe("CONVERSATION_NOT_FOUND");
745
+ });
746
+ });
747
+
748
+ // ─────────────────────────────────────────────────────────────────
749
+ // WebSocket subscription channels
750
+ // ─────────────────────────────────────────────────────────────────
751
+
752
+ describe("WebSocket subscription channels", () => {
753
+ it("receives conversation_update on 'conversations' channel", async () => {
754
+ const ws = createAPIWSClient();
755
+ await ws.connect();
756
+ await ws.subscribe("conversations");
757
+ ws.clearMessages();
758
+
759
+ // Create conversation — triggers onConversationChange
760
+ const ms = server.mailService!;
761
+ ms.createConversation({ type: "session", subject: "WS test", createdBy: "user" });
762
+
763
+ const msg = await ws.waitForMessage((m) => m.type === "conversation_update");
764
+ expect(msg.type).toBe("conversation_update");
765
+ // Strict: wire format is flat { type, conversation }, NOT nested in data
766
+ expect(msg).not.toHaveProperty("data");
767
+ expect((msg as any).conversation).toBeDefined();
768
+ expect((msg as any).conversation.type).toBe("session");
769
+ expect((msg as any).conversation.subject).toBe("WS test");
770
+ });
771
+
772
+ it("receives turn_added on 'conversation:${id}' channel", async () => {
773
+ const ms = server.mailService!;
774
+ const { conversationId } = ms.createConversation({
775
+ type: "session", subject: "Turn WS", createdBy: "user",
776
+ });
777
+
778
+ const ws = createAPIWSClient();
779
+ await ws.connect();
780
+ await ws.subscribe(`conversation:${conversationId}`);
781
+ ws.clearMessages();
782
+
783
+ // Record turn — triggers onTurnChange
784
+ ms.recordTurn({ conversationId, participant: "user", contentType: "text", content: "WS turn" });
785
+
786
+ const msg = await ws.waitForMessage((m) => m.type === "turn_added");
787
+ expect(msg.type).toBe("turn_added");
788
+ // Strict: wire format is flat { type, conversation_id, turn }
789
+ expect(msg).not.toHaveProperty("data");
790
+ expect((msg as any).conversation_id).toBe(conversationId);
791
+ expect((msg as any).turn).toBeDefined();
792
+ expect((msg as any).turn.content).toBe("WS turn");
793
+ expect((msg as any).turn.participant).toBe("user");
794
+ expect((msg as any).turn.content_type).toBe("text");
795
+ });
796
+
797
+ it("receives turn_added from MessageRouter-intercepted turns", async () => {
798
+ const ms = server.mailService!;
799
+ const cm = server.conversationMap!;
800
+
801
+ // Spawn parent + child
802
+ spawnAgent(eventStore, "ws-parent");
803
+ spawnAgent(eventStore, "ws-child", "ws-parent");
804
+ messageRouter.setupDefaultSubscriptions({ agent_id: "ws-parent" as AgentId });
805
+ messageRouter.setupDefaultSubscriptions({ agent_id: "ws-child" as AgentId, parent_id: "ws-parent" as AgentId });
806
+
807
+ // Create task conversation
808
+ const { conversationId } = ms.createConversation({
809
+ type: "task", subject: "WS intercept", createdBy: "ws-parent",
810
+ });
811
+ ms.joinConversation({ conversationId, participantId: "ws-parent", role: "initiator" });
812
+ ms.joinConversation({ conversationId, participantId: "ws-child", role: "worker" });
813
+ cm.setAgentConversation("ws-child", conversationId);
814
+
815
+ // Subscribe to conversation channel
816
+ const ws = createAPIWSClient();
817
+ await ws.connect();
818
+ await ws.subscribe(`conversation:${conversationId}`);
819
+ ws.clearMessages();
820
+
821
+ // Send message via router — TurnRecorder intercepts
822
+ await messageRouter.sendToAddress({
823
+ from: "ws-parent" as AgentId,
824
+ to: { agent: "ws-child" as AgentId },
825
+ content: "Intercepted for WS",
826
+ });
827
+
828
+ const msg = await ws.waitForMessage((m) => m.type === "turn_added");
829
+ expect(msg.type).toBe("turn_added");
830
+ expect(msg).not.toHaveProperty("data");
831
+ expect((msg as any).turn.participant).toBe("ws-parent");
832
+ expect((msg as any).turn.content).toBe("Intercepted for WS");
833
+ expect((msg as any).turn.source_type).toBe("intercepted");
834
+ });
835
+
836
+ it("receives conversation_update when conversation is closed", async () => {
837
+ const ms = server.mailService!;
838
+ const { conversationId } = ms.createConversation({
839
+ type: "session", subject: "Close WS", createdBy: "user",
840
+ });
841
+
842
+ const ws = createAPIWSClient();
843
+ await ws.connect();
844
+ await ws.subscribe(`conversation:${conversationId}`);
845
+ ws.clearMessages();
846
+
847
+ ms.closeConversation({ conversationId, closedBy: "user", reason: "completed" });
848
+
849
+ const msg = await ws.waitForMessage((m) => m.type === "conversation_update");
850
+ expect(msg).not.toHaveProperty("data");
851
+ expect((msg as any).conversation.status).toBe("completed");
852
+ expect((msg as any).conversation.id).toBe(conversationId);
853
+ });
854
+
855
+ it("does not receive events for unsubscribed conversations", async () => {
856
+ const ms = server.mailService!;
857
+ const { conversationId: convA } = ms.createConversation({
858
+ type: "session", subject: "Conv A", createdBy: "user",
859
+ });
860
+ const { conversationId: convB } = ms.createConversation({
861
+ type: "session", subject: "Conv B", createdBy: "user",
862
+ });
863
+
864
+ const ws = createAPIWSClient();
865
+ await ws.connect();
866
+ await ws.subscribe(`conversation:${convA}`);
867
+ ws.clearMessages();
868
+
869
+ // Record turn in conv B (not subscribed)
870
+ ms.recordTurn({ conversationId: convB, participant: "user", contentType: "text", content: "B msg" });
871
+
872
+ // Wait briefly — no message should arrive
873
+ await new Promise((resolve) => setTimeout(resolve, 200));
874
+
875
+ const turnMessages = ws.getMessages().filter((m) => m.type === "turn_added");
876
+ expect(turnMessages).toHaveLength(0);
877
+ });
878
+ });
879
+
880
+ // ─────────────────────────────────────────────────────────────────
881
+ // Validation and error handling
882
+ // ─────────────────────────────────────────────────────────────────
883
+
884
+ describe("Validation and error handling", () => {
885
+ it("mail/close on non-existent conversation returns error", async () => {
886
+ const client = createMAPClient();
887
+ await client.connect();
888
+
889
+ const res = await client.request("mail/close", {
890
+ conversationId: "nonexistent-conv",
891
+ });
892
+ expect(res.error).toBeDefined();
893
+ expect(res.error!.message).toContain("not found");
894
+ });
895
+
896
+ it("mail/close on already-closed conversation returns error", async () => {
897
+ const client = createMAPClient();
898
+ await client.connect();
899
+
900
+ const createRes = await client.request("mail/create", {
901
+ type: "session",
902
+ subject: "Close twice test",
903
+ });
904
+ const convId = createRes.result.conversation.id;
905
+
906
+ // Close once — should succeed
907
+ const closeRes1 = await client.request("mail/close", {
908
+ conversationId: convId,
909
+ });
910
+ expect(closeRes1.error).toBeUndefined();
911
+
912
+ // Close again — should fail
913
+ const closeRes2 = await client.request("mail/close", {
914
+ conversationId: convId,
915
+ });
916
+ expect(closeRes2.error).toBeDefined();
917
+ expect(closeRes2.error!.message).toContain("already");
918
+ });
919
+
920
+ it("mail/turn on non-existent conversation returns error", async () => {
921
+ const client = createMAPClient();
922
+ await client.connect();
923
+
924
+ const res = await client.request("mail/turn", {
925
+ conversationId: "nonexistent-conv",
926
+ contentType: "text",
927
+ content: "should fail",
928
+ });
929
+ expect(res.error).toBeDefined();
930
+ expect(res.error!.message).toContain("not found");
931
+ });
932
+
933
+ it("mail/join on non-existent conversation returns error", async () => {
934
+ const client = createMAPClient();
935
+ await client.connect();
936
+
937
+ const res = await client.request("mail/join", {
938
+ conversationId: "nonexistent-conv",
939
+ });
940
+ expect(res.error).toBeDefined();
941
+ expect(res.error!.message).toContain("not found");
942
+ });
943
+
944
+ it("mail/join duplicate participant is idempotent", async () => {
945
+ const client = createMAPClient();
946
+ await client.connect();
947
+
948
+ const createRes = await client.request("mail/create", {
949
+ type: "session",
950
+ subject: "Dup join test",
951
+ });
952
+ const convId = createRes.result.conversation.id;
953
+
954
+ // Creator is auto-joined. Join again — should be idempotent.
955
+ const joinRes = await client.request("mail/join", {
956
+ conversationId: convId,
957
+ role: "worker",
958
+ });
959
+ expect(joinRes.error).toBeUndefined();
960
+
961
+ // Verify no duplicate participants — get the participant list
962
+ const ms = server.mailService!;
963
+ const participants = ms.listParticipants(convId, true);
964
+
965
+ // Group by ID — no participant should appear more than once
966
+ const idCounts = new Map<string, number>();
967
+ for (const p of participants) {
968
+ idCounts.set(p.id, (idCounts.get(p.id) ?? 0) + 1);
969
+ }
970
+ for (const [id, count] of idCounts) {
971
+ expect(count, `Participant ${id} should appear only once`).toBe(1);
972
+ }
973
+ });
974
+
975
+ it("mail/leave on non-existent conversation returns error", async () => {
976
+ const client = createMAPClient();
977
+ await client.connect();
978
+
979
+ const res = await client.request("mail/leave", {
980
+ conversationId: "nonexistent-conv",
981
+ });
982
+ expect(res.error).toBeDefined();
983
+ expect(res.error!.message).toContain("not found");
984
+ });
985
+
986
+ it("mail/thread/create on non-existent conversation returns error", async () => {
987
+ const client = createMAPClient();
988
+ await client.connect();
989
+
990
+ const res = await client.request("mail/thread/create", {
991
+ conversationId: "nonexistent-conv",
992
+ rootTurnId: "turn-1",
993
+ subject: "Thread in void",
994
+ });
995
+ expect(res.error).toBeDefined();
996
+ expect(res.error!.message).toContain("not found");
997
+ });
998
+
999
+ it("mail/thread/create returns thread object with nanoid-style ID", async () => {
1000
+ const client = createMAPClient();
1001
+ await client.connect();
1002
+
1003
+ const createRes = await client.request("mail/create", {
1004
+ type: "session",
1005
+ subject: "Thread create test",
1006
+ });
1007
+ const convId = createRes.result.conversation.id;
1008
+
1009
+ // Record a turn to use as root
1010
+ const turnRes = await client.request("mail/turn", {
1011
+ conversationId: convId,
1012
+ contentType: "text",
1013
+ content: "Root turn",
1014
+ });
1015
+ const rootTurnId = turnRes.result.turn.id;
1016
+
1017
+ const threadRes = await client.request("mail/thread/create", {
1018
+ conversationId: convId,
1019
+ rootTurnId,
1020
+ subject: "Discussion thread",
1021
+ });
1022
+
1023
+ expect(threadRes.error).toBeUndefined();
1024
+ expect(threadRes.result.thread).toBeDefined();
1025
+ expect(threadRes.result.thread.id).toMatch(/^thread_[A-Za-z0-9_-]+$/);
1026
+ expect(threadRes.result.thread.subject).toBe("Discussion thread");
1027
+ expect(threadRes.result.thread.conversationId).toBe(convId);
1028
+ });
1029
+ });
1030
+
1031
+ // ─────────────────────────────────────────────────────────────────
1032
+ // Cross-protocol consistency
1033
+ // ─────────────────────────────────────────────────────────────────
1034
+
1035
+ describe("Cross-protocol consistency", () => {
1036
+ it("MAP-created conversations appear in REST API", async () => {
1037
+ const client = createMAPClient();
1038
+ await client.connect();
1039
+
1040
+ // Create conversation via MAP
1041
+ const createRes = await client.request("mail/create", {
1042
+ type: "task",
1043
+ subject: "Cross-protocol test",
1044
+ });
1045
+ const convId = createRes.result.conversation.id;
1046
+
1047
+ // Record turn via MAP
1048
+ await client.request("mail/turn", {
1049
+ conversationId: convId,
1050
+ contentType: "text",
1051
+ content: "MAP turn content",
1052
+ });
1053
+
1054
+ // Query via REST
1055
+ const restConvRes = await fetch(`${baseUrl}/api/conversations/${convId}`);
1056
+ expect(restConvRes.status).toBe(200);
1057
+ const restConv = await restConvRes.json();
1058
+ expect(restConv.subject).toBe("Cross-protocol test");
1059
+
1060
+ // Query turns via REST
1061
+ const restTurnsRes = await fetch(`${baseUrl}/api/conversations/${convId}/turns`);
1062
+ expect(restTurnsRes.status).toBe(200);
1063
+ const restTurns = await restTurnsRes.json();
1064
+ expect(restTurns.turns).toHaveLength(1);
1065
+ expect(restTurns.turns[0].content).toBe("MAP turn content");
1066
+ });
1067
+ });
1068
+ });