macro-agent 0.0.17 → 0.1.1

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 (338) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/.sudocode/specs.jsonl +4 -0
  3. package/CLAUDE.md +16 -14
  4. package/README.md +11 -29
  5. package/dist/acp/macro-agent.d.ts +17 -0
  6. package/dist/acp/macro-agent.d.ts.map +1 -1
  7. package/dist/acp/macro-agent.js +183 -55
  8. package/dist/acp/macro-agent.js.map +1 -1
  9. package/dist/acp/types.d.ts +32 -1
  10. package/dist/acp/types.d.ts.map +1 -1
  11. package/dist/acp/types.js.map +1 -1
  12. package/dist/agent/agent-manager.d.ts +65 -1
  13. package/dist/agent/agent-manager.d.ts.map +1 -1
  14. package/dist/agent/agent-manager.js +464 -183
  15. package/dist/agent/agent-manager.js.map +1 -1
  16. package/dist/agent/types.d.ts +1 -1
  17. package/dist/agent/types.d.ts.map +1 -1
  18. package/dist/api/server.d.ts +3 -0
  19. package/dist/api/server.d.ts.map +1 -1
  20. package/dist/api/server.js +37 -6
  21. package/dist/api/server.js.map +1 -1
  22. package/dist/auth/index.d.ts +2 -0
  23. package/dist/auth/index.d.ts.map +1 -0
  24. package/dist/auth/index.js +2 -0
  25. package/dist/auth/index.js.map +1 -0
  26. package/dist/auth/token.d.ts +41 -0
  27. package/dist/auth/token.d.ts.map +1 -0
  28. package/dist/auth/token.js +73 -0
  29. package/dist/auth/token.js.map +1 -0
  30. package/dist/cli/acp.d.ts +2 -23
  31. package/dist/cli/acp.d.ts.map +1 -1
  32. package/dist/cli/acp.js +127 -61
  33. package/dist/cli/acp.js.map +1 -1
  34. package/dist/cli/index.js +147 -15
  35. package/dist/cli/index.js.map +1 -1
  36. package/dist/cli/mcp.d.ts +6 -0
  37. package/dist/cli/mcp.d.ts.map +1 -1
  38. package/dist/cli/mcp.js +268 -181
  39. package/dist/cli/mcp.js.map +1 -1
  40. package/dist/cli/parse-args.d.ts +20 -0
  41. package/dist/cli/parse-args.d.ts.map +1 -0
  42. package/dist/cli/parse-args.js +43 -0
  43. package/dist/cli/parse-args.js.map +1 -0
  44. package/dist/cli/stable-instance-id.d.ts +8 -0
  45. package/dist/cli/stable-instance-id.d.ts.map +1 -0
  46. package/dist/cli/stable-instance-id.js +14 -0
  47. package/dist/cli/stable-instance-id.js.map +1 -0
  48. package/dist/config/project-config.d.ts +74 -7
  49. package/dist/config/project-config.d.ts.map +1 -1
  50. package/dist/config/project-config.js +123 -20
  51. package/dist/config/project-config.js.map +1 -1
  52. package/dist/map/adapter/acp-over-map.d.ts +23 -0
  53. package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
  54. package/dist/map/adapter/acp-over-map.js +482 -55
  55. package/dist/map/adapter/acp-over-map.js.map +1 -1
  56. package/dist/map/adapter/connection-manager.d.ts.map +1 -1
  57. package/dist/map/adapter/connection-manager.js +3 -0
  58. package/dist/map/adapter/connection-manager.js.map +1 -1
  59. package/dist/map/adapter/event-log.d.ts +87 -0
  60. package/dist/map/adapter/event-log.d.ts.map +1 -0
  61. package/dist/map/adapter/event-log.js +122 -0
  62. package/dist/map/adapter/event-log.js.map +1 -0
  63. package/dist/map/adapter/event-translator.js +6 -6
  64. package/dist/map/adapter/event-translator.js.map +1 -1
  65. package/dist/map/adapter/extensions/agent-lifecycle.d.ts +82 -0
  66. package/dist/map/adapter/extensions/agent-lifecycle.d.ts.map +1 -0
  67. package/dist/map/adapter/extensions/agent-lifecycle.js +164 -0
  68. package/dist/map/adapter/extensions/agent-lifecycle.js.map +1 -0
  69. package/dist/map/adapter/extensions/index.d.ts +10 -1
  70. package/dist/map/adapter/extensions/index.d.ts.map +1 -1
  71. package/dist/map/adapter/extensions/index.js +34 -0
  72. package/dist/map/adapter/extensions/index.js.map +1 -1
  73. package/dist/map/adapter/extensions/mcp-bridge.d.ts +57 -0
  74. package/dist/map/adapter/extensions/mcp-bridge.d.ts.map +1 -0
  75. package/dist/map/adapter/extensions/mcp-bridge.js +745 -0
  76. package/dist/map/adapter/extensions/mcp-bridge.js.map +1 -0
  77. package/dist/map/adapter/extensions/rename.d.ts +29 -0
  78. package/dist/map/adapter/extensions/rename.d.ts.map +1 -0
  79. package/dist/map/adapter/extensions/rename.js +49 -0
  80. package/dist/map/adapter/extensions/rename.js.map +1 -0
  81. package/dist/map/adapter/extensions/task.d.ts.map +1 -1
  82. package/dist/map/adapter/extensions/task.js +10 -0
  83. package/dist/map/adapter/extensions/task.js.map +1 -1
  84. package/dist/map/adapter/extensions/update-metadata.d.ts +29 -0
  85. package/dist/map/adapter/extensions/update-metadata.d.ts.map +1 -0
  86. package/dist/map/adapter/extensions/update-metadata.js +67 -0
  87. package/dist/map/adapter/extensions/update-metadata.js.map +1 -0
  88. package/dist/map/adapter/index.d.ts +2 -1
  89. package/dist/map/adapter/index.d.ts.map +1 -1
  90. package/dist/map/adapter/index.js +8 -2
  91. package/dist/map/adapter/index.js.map +1 -1
  92. package/dist/map/adapter/interface.d.ts +2 -0
  93. package/dist/map/adapter/interface.d.ts.map +1 -1
  94. package/dist/map/adapter/map-adapter.d.ts +4 -0
  95. package/dist/map/adapter/map-adapter.d.ts.map +1 -1
  96. package/dist/map/adapter/map-adapter.js +302 -30
  97. package/dist/map/adapter/map-adapter.js.map +1 -1
  98. package/dist/map/adapter/subscription-manager.d.ts.map +1 -1
  99. package/dist/map/adapter/subscription-manager.js +5 -1
  100. package/dist/map/adapter/subscription-manager.js.map +1 -1
  101. package/dist/map/adapter/types.d.ts +2 -0
  102. package/dist/map/adapter/types.d.ts.map +1 -1
  103. package/dist/mcp/map-client.d.ts +39 -0
  104. package/dist/mcp/map-client.d.ts.map +1 -0
  105. package/dist/mcp/map-client.js +129 -0
  106. package/dist/mcp/map-client.js.map +1 -0
  107. package/dist/mcp/mcp-server.d.ts +14 -0
  108. package/dist/mcp/mcp-server.d.ts.map +1 -1
  109. package/dist/mcp/mcp-server.js +113 -85
  110. package/dist/mcp/mcp-server.js.map +1 -1
  111. package/dist/mcp/types.d.ts +9 -1
  112. package/dist/mcp/types.d.ts.map +1 -1
  113. package/dist/mcp/types.js.map +1 -1
  114. package/dist/metrics/metrics.js +1 -1
  115. package/dist/metrics/metrics.js.map +1 -1
  116. package/dist/roles/capabilities.d.ts +3 -1
  117. package/dist/roles/capabilities.d.ts.map +1 -1
  118. package/dist/roles/capabilities.js +17 -7
  119. package/dist/roles/capabilities.js.map +1 -1
  120. package/dist/roles/config-loader.d.ts +6 -6
  121. package/dist/roles/config-loader.d.ts.map +1 -1
  122. package/dist/roles/config-loader.js +6 -6
  123. package/dist/roles/config-loader.js.map +1 -1
  124. package/dist/roles/registry.d.ts +2 -2
  125. package/dist/roles/registry.js +2 -2
  126. package/dist/server/combined-server.d.ts +20 -0
  127. package/dist/server/combined-server.d.ts.map +1 -1
  128. package/dist/server/combined-server.js +107 -8
  129. package/dist/server/combined-server.js.map +1 -1
  130. package/dist/store/event-store.d.ts +7 -1
  131. package/dist/store/event-store.d.ts.map +1 -1
  132. package/dist/store/event-store.js +91 -8
  133. package/dist/store/event-store.js.map +1 -1
  134. package/dist/store/types/agents.d.ts +23 -0
  135. package/dist/store/types/agents.d.ts.map +1 -1
  136. package/dist/store/types/events.d.ts +1 -1
  137. package/dist/store/types/events.d.ts.map +1 -1
  138. package/dist/task/backend/index.d.ts +47 -29
  139. package/dist/task/backend/index.d.ts.map +1 -1
  140. package/dist/task/backend/index.js +109 -71
  141. package/dist/task/backend/index.js.map +1 -1
  142. package/dist/task/backend/memory.d.ts +1 -0
  143. package/dist/task/backend/memory.d.ts.map +1 -1
  144. package/dist/task/backend/memory.js +3 -0
  145. package/dist/task/backend/memory.js.map +1 -1
  146. package/dist/task/backend/opentasks/backend.d.ts +140 -0
  147. package/dist/task/backend/opentasks/backend.d.ts.map +1 -0
  148. package/dist/task/backend/opentasks/backend.js +1023 -0
  149. package/dist/task/backend/opentasks/backend.js.map +1 -0
  150. package/dist/task/backend/opentasks/client.d.ts +337 -0
  151. package/dist/task/backend/opentasks/client.d.ts.map +1 -0
  152. package/dist/task/backend/opentasks/client.js +225 -0
  153. package/dist/task/backend/opentasks/client.js.map +1 -0
  154. package/dist/task/backend/opentasks/daemon-manager.d.ts +89 -0
  155. package/dist/task/backend/opentasks/daemon-manager.d.ts.map +1 -0
  156. package/dist/task/backend/opentasks/daemon-manager.js +195 -0
  157. package/dist/task/backend/opentasks/daemon-manager.js.map +1 -0
  158. package/dist/task/backend/opentasks/index.d.ts +21 -0
  159. package/dist/task/backend/opentasks/index.d.ts.map +1 -0
  160. package/dist/task/backend/opentasks/index.js +21 -0
  161. package/dist/task/backend/opentasks/index.js.map +1 -0
  162. package/dist/task/backend/opentasks/mapping.d.ts +48 -0
  163. package/dist/task/backend/opentasks/mapping.d.ts.map +1 -0
  164. package/dist/task/backend/opentasks/mapping.js +77 -0
  165. package/dist/task/backend/opentasks/mapping.js.map +1 -0
  166. package/dist/task/backend/types.d.ts +33 -53
  167. package/dist/task/backend/types.d.ts.map +1 -1
  168. package/dist/task/backend/types.js +7 -11
  169. package/dist/task/backend/types.js.map +1 -1
  170. package/dist/task/backend/unified-tool-provider.d.ts +57 -0
  171. package/dist/task/backend/unified-tool-provider.d.ts.map +1 -0
  172. package/dist/task/backend/unified-tool-provider.js +623 -0
  173. package/dist/task/backend/unified-tool-provider.js.map +1 -0
  174. package/dist/teams/team-loader.d.ts +2 -2
  175. package/dist/teams/team-loader.js +3 -3
  176. package/dist/teams/team-loader.js.map +1 -1
  177. package/dist/teams/team-runtime.d.ts.map +1 -1
  178. package/dist/teams/team-runtime.js +2 -0
  179. package/dist/teams/team-runtime.js.map +1 -1
  180. package/docs/architecture.md +7 -6
  181. package/docs/configuration.md +26 -62
  182. package/docs/implementation-details.md +5 -5
  183. package/docs/implementation-summary.md +17 -17
  184. package/docs/plan-self-driving-support.md +4 -4
  185. package/docs/spec-self-driving-support.md +10 -10
  186. package/docs/team-templates.md +2 -2
  187. package/docs/teams.md +3 -3
  188. package/docs/troubleshooting.md +10 -11
  189. package/package.json +6 -4
  190. package/src/__tests__/e2e/agent-spawn-visibility.e2e.test.ts +761 -0
  191. package/src/__tests__/e2e/full-agent-conflict-resolution.e2e.test.ts +2 -2
  192. package/src/__tests__/e2e/mcp-thin-client-bridge.e2e.test.ts +304 -0
  193. package/src/__tests__/e2e/mcp-tools-available.e2e.test.ts +324 -0
  194. package/src/__tests__/e2e/multi-agent.e2e.test.ts +5 -5
  195. package/src/__tests__/e2e/spawn-session-streaming.e2e.test.ts +563 -0
  196. package/src/acp/__tests__/history.test.ts +8 -4
  197. package/src/acp/__tests__/integration.test.ts +56 -31
  198. package/src/acp/__tests__/macro-agent.test.ts +16 -7
  199. package/src/acp/macro-agent.ts +230 -62
  200. package/src/acp/types.ts +46 -1
  201. package/src/agent/__tests__/agent-manager.test.ts +228 -2
  202. package/src/agent/agent-manager.ts +714 -261
  203. package/src/agent/types.ts +3 -1
  204. package/src/api/server.ts +41 -7
  205. package/src/auth/__tests__/token.test.ts +100 -0
  206. package/src/auth/index.ts +1 -0
  207. package/src/auth/token.ts +82 -0
  208. package/src/cli/__tests__/acp.test.ts +1 -1
  209. package/src/cli/__tests__/stable-instance-id.test.ts +1 -1
  210. package/src/cli/acp.ts +130 -72
  211. package/src/cli/index.ts +120 -14
  212. package/src/cli/mcp.ts +311 -207
  213. package/src/cli/parse-args.ts +54 -0
  214. package/src/cli/stable-instance-id.ts +14 -0
  215. package/src/config/project-config.ts +190 -27
  216. package/src/lifecycle/__tests__/cascade-termination.test.ts +1 -1
  217. package/src/map/adapter/__tests__/acp-over-map-cancel.test.ts +820 -0
  218. package/src/map/adapter/__tests__/acp-over-map-getmodels.test.ts +355 -0
  219. package/src/map/adapter/__tests__/acp-over-map-history.test.ts +724 -2
  220. package/src/map/adapter/__tests__/acp-over-map-persistence.e2e.test.ts +1 -1
  221. package/src/map/adapter/__tests__/event-broadcast.test.ts +420 -0
  222. package/src/map/adapter/__tests__/event-log.test.ts +527 -0
  223. package/src/map/adapter/__tests__/event-translator.test.ts +3 -3
  224. package/src/map/adapter/__tests__/extensions.test.ts +408 -0
  225. package/src/map/adapter/__tests__/map-adapter.test.ts +99 -0
  226. package/src/map/adapter/__tests__/mcp-bridge.test.ts +1187 -0
  227. package/src/map/adapter/__tests__/multi-client-broadcast.test.ts +711 -0
  228. package/src/map/adapter/__tests__/websocket-integration.test.ts +218 -0
  229. package/src/map/adapter/acp-over-map.ts +777 -92
  230. package/src/map/adapter/connection-manager.ts +3 -0
  231. package/src/map/adapter/event-log.ts +208 -0
  232. package/src/map/adapter/event-translator.ts +6 -6
  233. package/src/map/adapter/extensions/agent-lifecycle.ts +267 -0
  234. package/src/map/adapter/extensions/index.ts +60 -0
  235. package/src/map/adapter/extensions/mcp-bridge.ts +995 -0
  236. package/src/map/adapter/extensions/task.ts +11 -0
  237. package/src/map/adapter/extensions/update-metadata.ts +126 -0
  238. package/src/map/adapter/index.ts +28 -0
  239. package/src/map/adapter/interface.ts +2 -0
  240. package/src/map/adapter/map-adapter.ts +373 -38
  241. package/src/map/adapter/subscription-manager.ts +5 -1
  242. package/src/map/adapter/types.ts +2 -0
  243. package/src/mcp/__tests__/map-client.test.ts +386 -0
  244. package/src/mcp/__tests__/mcp-server-thin-client.test.ts +368 -0
  245. package/src/mcp/__tests__/mcp-server.test.ts +100 -1
  246. package/src/mcp/map-client.ts +177 -0
  247. package/src/mcp/mcp-server.ts +191 -100
  248. package/src/mcp/types.ts +6 -1
  249. package/src/metrics/metrics.ts +1 -1
  250. package/src/monitor/__tests__/stale-agent-flow.integration.test.ts +1 -1
  251. package/src/roles/__tests__/config-loader.test.ts +7 -7
  252. package/src/roles/capabilities.ts +17 -7
  253. package/src/roles/config-loader.ts +6 -6
  254. package/src/roles/registry.ts +2 -2
  255. package/src/server/__tests__/combined-server.test.ts +94 -21
  256. package/src/server/combined-server.ts +189 -33
  257. package/src/steering/__tests__/steering-integration.test.ts +1 -1
  258. package/src/store/__tests__/event-store.test.ts +236 -1
  259. package/src/store/__tests__/instance.test.ts +3 -3
  260. package/src/store/event-store.ts +109 -8
  261. package/src/store/types/agents.ts +16 -0
  262. package/src/store/types/events.ts +1 -1
  263. package/src/task/backend/__tests__/create-task-backend.test.ts +225 -0
  264. package/src/task/backend/__tests__/e2e/unified-tool-provider-opentasks.e2e.test.ts +524 -0
  265. package/src/task/backend/__tests__/unified-tool-provider.test.ts +579 -0
  266. package/src/task/backend/index.ts +156 -106
  267. package/src/task/backend/memory.ts +4 -0
  268. package/src/task/backend/opentasks/__tests__/backend.test.ts +968 -0
  269. package/src/task/backend/opentasks/__tests__/daemon-manager.test.ts +406 -0
  270. package/src/task/backend/opentasks/__tests__/mapping.test.ts +84 -0
  271. package/src/task/backend/opentasks/__tests__/opentasks-backend.e2e.test.ts +1338 -0
  272. package/src/task/backend/opentasks/backend.ts +1323 -0
  273. package/src/task/backend/opentasks/client.ts +652 -0
  274. package/src/task/backend/opentasks/daemon-manager.ts +253 -0
  275. package/src/task/backend/opentasks/index.ts +69 -0
  276. package/src/task/backend/opentasks/mapping.ts +94 -0
  277. package/src/task/backend/types.ts +42 -66
  278. package/src/task/backend/unified-tool-provider.ts +779 -0
  279. package/src/teams/__tests__/cross-subsystem.integration.test.ts +1 -1
  280. package/src/teams/team-loader.ts +3 -3
  281. package/src/teams/team-runtime.ts +2 -0
  282. package/test_fixtures/README.md +2 -3
  283. package/test_fixtures/fixtures/index.ts +0 -3
  284. package/test_fixtures/fixtures/projects/project-with-specs.ts +7 -149
  285. package/test_fixtures/fixtures/repos/index.ts +1 -3
  286. package/test_fixtures/fixtures/repos/temp-repo-factory.ts +0 -116
  287. package/test_fixtures/fixtures/repos/types.ts +0 -11
  288. package/test_fixtures/harness/__tests__/fixtures.test.ts +10 -102
  289. package/test_fixtures/harness/__tests__/temp-repo-and-simulator.test.ts +0 -33
  290. package/test_fixtures/harness/simulator/agent-simulator.ts +4 -4
  291. package/vitest.config.ts +1 -1
  292. package/vitest.e2e.config.ts +1 -1
  293. package/vitest.setup.ts +1 -30
  294. package/.macro-agent/teams/self-driving/prompts/grinder.md +0 -27
  295. package/.macro-agent/teams/self-driving/prompts/judge.md +0 -27
  296. package/.macro-agent/teams/self-driving/prompts/planner.md +0 -33
  297. package/.macro-agent/teams/self-driving/roles/grinder.yaml +0 -17
  298. package/.macro-agent/teams/self-driving/roles/judge.yaml +0 -24
  299. package/.macro-agent/teams/self-driving/roles/planner.yaml +0 -18
  300. package/.macro-agent/teams/self-driving/team.yaml +0 -103
  301. package/.macro-agent/teams/structured/prompts/developer.md +0 -26
  302. package/.macro-agent/teams/structured/prompts/lead.md +0 -25
  303. package/.macro-agent/teams/structured/prompts/reviewer.md +0 -24
  304. package/.macro-agent/teams/structured/roles/developer.yaml +0 -12
  305. package/.macro-agent/teams/structured/roles/lead.yaml +0 -11
  306. package/.macro-agent/teams/structured/roles/reviewer.yaml +0 -19
  307. package/.macro-agent/teams/structured/team.yaml +0 -89
  308. package/docs/sudocode-integration.md +0 -383
  309. package/src/task/backend/__tests__/backend-parity.test.ts +0 -451
  310. package/src/task/backend/__tests__/tool-provider-edge-cases.test.ts +0 -430
  311. package/src/task/backend/__tests__/tool-provider.test.ts +0 -983
  312. package/src/task/backend/sudocode/__tests__/backend-edge-cases.test.ts +0 -575
  313. package/src/task/backend/sudocode/__tests__/backend.test.ts +0 -1194
  314. package/src/task/backend/sudocode/__tests__/client-integration.test.ts +0 -418
  315. package/src/task/backend/sudocode/__tests__/client.test.ts +0 -345
  316. package/src/task/backend/sudocode/__tests__/e2e/backend.e2e.test.ts +0 -753
  317. package/src/task/backend/sudocode/__tests__/e2e/server-client.e2e.test.ts +0 -680
  318. package/src/task/backend/sudocode/__tests__/e2e-workflow.test.ts +0 -666
  319. package/src/task/backend/sudocode/__tests__/integration/standalone-client.integration.test.ts +0 -396
  320. package/src/task/backend/sudocode/__tests__/integration/sudocode-cli.integration.test.ts +0 -328
  321. package/src/task/backend/sudocode/__tests__/integration/test-utils.ts +0 -175
  322. package/src/task/backend/sudocode/__tests__/mapping-edge-cases.test.ts +0 -265
  323. package/src/task/backend/sudocode/__tests__/server-client.test.ts +0 -675
  324. package/src/task/backend/sudocode/__tests__/sync-policy-edge-cases.test.ts +0 -521
  325. package/src/task/backend/sudocode/__tests__/sync-policy.test.ts +0 -519
  326. package/src/task/backend/sudocode/__tests__/tools.test.ts +0 -471
  327. package/src/task/backend/sudocode/backend.ts +0 -1237
  328. package/src/task/backend/sudocode/client.ts +0 -515
  329. package/src/task/backend/sudocode/index.ts +0 -120
  330. package/src/task/backend/sudocode/mapping.ts +0 -93
  331. package/src/task/backend/sudocode/server-client.ts +0 -522
  332. package/src/task/backend/sudocode/standalone-client.ts +0 -623
  333. package/src/task/backend/sudocode/sync-policy.ts +0 -387
  334. package/src/task/backend/sudocode/tools.ts +0 -896
  335. package/src/task/backend/tool-provider.ts +0 -506
  336. package/test_fixtures/fixtures/sudocode/index.ts +0 -29
  337. package/test_fixtures/fixtures/sudocode/issues.ts +0 -185
  338. package/test_fixtures/fixtures/sudocode/specs.ts +0 -159
@@ -208,7 +208,7 @@ describe("ACP-over-MAP history persistence (E2E with file-backed store)", () =>
208
208
  title: "ListFiles",
209
209
  status: "completed",
210
210
  rawInput: { path: "/src" },
211
- output: "index.ts\napp.ts",
211
+ rawOutput: "index.ts\napp.ts",
212
212
  },
213
213
  ]),
214
214
  eventStore: eventStore1,
@@ -0,0 +1,420 @@
1
+ /**
2
+ * Integration tests for multi-client event broadcast.
3
+ *
4
+ * Verifies that when events are emitted via emitEvent(), they are correctly
5
+ * delivered to subscribed participants through their WebSocket streams.
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
9
+ import {
10
+ createMAPAdapter,
11
+ MAPAdapterImpl,
12
+ type MAPAdapterServices,
13
+ } from "../map-adapter.js";
14
+ import type { MAPAdapter, MAPAdapterConfig, Stream } from "../interface.js";
15
+ import type { ParticipantId, EventNotification } from "../types.js";
16
+ import type { AgentId } from "../../../store/types/index.js";
17
+ import type { MAPEventType } from "../subscription-manager.js";
18
+ import { ulid } from "ulid";
19
+
20
+ // =============================================================================
21
+ // Helpers
22
+ // =============================================================================
23
+
24
+ /**
25
+ * Create a mock stream pair that captures messages written by the server.
26
+ * - `serverStream`: pass to adapter.acceptConnection()
27
+ * - `clientMessages`: array of messages the adapter has written to the client
28
+ * - `sendToServer`: simulate client sending a message to the server
29
+ */
30
+ function createMockStreamPair(): {
31
+ serverStream: Stream;
32
+ clientMessages: unknown[];
33
+ sendToServer: (msg: unknown) => void;
34
+ } {
35
+ const clientMessages: unknown[] = [];
36
+
37
+ let clientResolve: ((msg: unknown) => void) | null = null;
38
+ let serverResolve: ((msg: unknown) => void) | null = null;
39
+
40
+ // Server reads from this (client → server)
41
+ const serverReadable = new ReadableStream<unknown>({
42
+ start(controller) {
43
+ serverResolve = (msg) => {
44
+ controller.enqueue(msg);
45
+ };
46
+ },
47
+ });
48
+
49
+ // Server writes to this (server → client)
50
+ const serverWritable = new WritableStream<unknown>({
51
+ write(chunk) {
52
+ clientMessages.push(chunk);
53
+ },
54
+ });
55
+
56
+ return {
57
+ serverStream: { readable: serverReadable, writable: serverWritable },
58
+ clientMessages,
59
+ sendToServer: (msg) => serverResolve?.(msg),
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Wait for async writes to flush (emitEvent fires sendToSession without await).
65
+ */
66
+ async function flushAsync(ms = 50): Promise<void> {
67
+ await new Promise((resolve) => setTimeout(resolve, ms));
68
+ }
69
+
70
+ // =============================================================================
71
+ // Tests
72
+ // =============================================================================
73
+
74
+ describe("Multi-client event broadcast", () => {
75
+ let adapter: MAPAdapter;
76
+ let config: MAPAdapterConfig;
77
+ let services: MAPAdapterServices;
78
+
79
+ beforeEach(() => {
80
+ config = {
81
+ name: "test-broadcast",
82
+ version: "1.0.0",
83
+ limits: {
84
+ maxConnections: 10,
85
+ maxSubscriptionsPerConnection: 5,
86
+ },
87
+ };
88
+
89
+ services = {
90
+ getAgent: vi.fn(),
91
+ listAgents: vi.fn().mockReturnValue([]),
92
+ sendMessage: vi.fn().mockResolvedValue({ delivered: [] }),
93
+ getAncestors: vi.fn().mockReturnValue([]),
94
+ getDescendants: vi.fn().mockReturnValue([]),
95
+ };
96
+
97
+ adapter = createMAPAdapter(config, services);
98
+ });
99
+
100
+ afterEach(async () => {
101
+ if (adapter.isRunning()) {
102
+ await adapter.stop();
103
+ }
104
+ });
105
+
106
+ it("broadcasts event to subscribed participant", async () => {
107
+ await adapter.start();
108
+
109
+ // Connect Client A with subscription
110
+ const pairA = createMockStreamPair();
111
+ const clientA = await adapter.acceptConnection(pairA.serverStream);
112
+ await adapter.createSubscription(clientA.id, {
113
+ eventTypes: [
114
+ "message_sent" as MAPEventType,
115
+ "message_delivered" as MAPEventType,
116
+ "agent_registered" as MAPEventType,
117
+ ],
118
+ });
119
+
120
+ // Connect Client B (no subscription)
121
+ const pairB = createMockStreamPair();
122
+ await adapter.acceptConnection(pairB.serverStream);
123
+
124
+ // Emit a message_delivered event (simulates ACP response broadcast)
125
+ adapter.emitEvent({
126
+ eventId: ulid(),
127
+ type: "message_delivered" as MAPEventType,
128
+ timestamp: Date.now(),
129
+ agentId: "agent-1" as AgentId,
130
+ data: {
131
+ from: "agent-1",
132
+ to: "p-client-b",
133
+ message: {
134
+ id: `acp-notif-${Date.now()}`,
135
+ from: "agent-1",
136
+ payload: { type: "session/update", data: { text: "hello" } },
137
+ },
138
+ },
139
+ } as EventNotification);
140
+
141
+ await flushAsync();
142
+
143
+ // Client A should have received the notification
144
+ expect(pairA.clientMessages.length).toBeGreaterThan(0);
145
+
146
+ const notification = pairA.clientMessages[0] as {
147
+ jsonrpc: string;
148
+ method: string;
149
+ params: {
150
+ subscriptionId: string;
151
+ sequenceNumber: number;
152
+ event: { type: string; data: unknown };
153
+ };
154
+ };
155
+ expect(notification.method).toBe("map/event");
156
+ expect(notification.params.event.type).toBe("message_delivered");
157
+ expect(notification.params.subscriptionId).toMatch(/^sub-/);
158
+
159
+ // Client B should NOT have received the notification (no subscription)
160
+ expect(pairB.clientMessages).toHaveLength(0);
161
+ });
162
+
163
+ it("broadcasts agent_registered event to subscribed participant", async () => {
164
+ await adapter.start();
165
+
166
+ const pairA = createMockStreamPair();
167
+ const clientA = await adapter.acceptConnection(pairA.serverStream);
168
+ await adapter.createSubscription(clientA.id, {
169
+ eventTypes: ["agent_registered" as MAPEventType],
170
+ });
171
+
172
+ // Emit agent_registered (simulates onAgentRegistered callback)
173
+ adapter.emitEvent({
174
+ eventId: ulid(),
175
+ type: "agent_registered" as MAPEventType,
176
+ timestamp: Date.now(),
177
+ agentId: "agent-new" as AgentId,
178
+ data: {
179
+ agentId: "agent-new",
180
+ name: "New Agent",
181
+ role: "assistant",
182
+ },
183
+ } as EventNotification);
184
+
185
+ await flushAsync();
186
+
187
+ expect(pairA.clientMessages.length).toBeGreaterThan(0);
188
+ const notification = pairA.clientMessages[0] as {
189
+ method: string;
190
+ params: { event: { type: string; data: { agentId: string } } };
191
+ };
192
+ expect(notification.method).toBe("map/event");
193
+ expect(notification.params.event.type).toBe("agent_registered");
194
+ expect(notification.params.event.data.agentId).toBe("agent-new");
195
+ });
196
+
197
+ it("broadcasts message_sent event to subscribed participant", async () => {
198
+ await adapter.start();
199
+
200
+ const pairA = createMockStreamPair();
201
+ const clientA = await adapter.acceptConnection(pairA.serverStream);
202
+ await adapter.createSubscription(clientA.id, {
203
+ eventTypes: ["message_sent" as MAPEventType],
204
+ });
205
+
206
+ // Emit message_sent (simulates client→agent ACP request broadcast)
207
+ adapter.emitEvent({
208
+ eventId: ulid(),
209
+ type: "message_sent" as MAPEventType,
210
+ timestamp: Date.now(),
211
+ agentId: "agent-1" as AgentId,
212
+ data: {
213
+ from: "p-client-b",
214
+ to: "agent-1",
215
+ message: {
216
+ id: `acp-req-${Date.now()}`,
217
+ from: "p-client-b",
218
+ to: "agent-1",
219
+ payload: {
220
+ type: "session/prompt",
221
+ data: { text: "hello agent" },
222
+ },
223
+ },
224
+ },
225
+ } as EventNotification);
226
+
227
+ await flushAsync();
228
+
229
+ expect(pairA.clientMessages.length).toBeGreaterThan(0);
230
+ const notification = pairA.clientMessages[0] as {
231
+ method: string;
232
+ params: { event: { type: string; data: { from: string; to: string } } };
233
+ };
234
+ expect(notification.method).toBe("map/event");
235
+ expect(notification.params.event.type).toBe("message_sent");
236
+ expect(notification.params.event.data.from).toBe("p-client-b");
237
+ expect(notification.params.event.data.to).toBe("agent-1");
238
+ });
239
+
240
+ it("both clients receive events when both are subscribed", async () => {
241
+ await adapter.start();
242
+
243
+ const pairA = createMockStreamPair();
244
+ const clientA = await adapter.acceptConnection(pairA.serverStream);
245
+ await adapter.createSubscription(clientA.id, {
246
+ eventTypes: ["message_delivered" as MAPEventType],
247
+ });
248
+
249
+ const pairB = createMockStreamPair();
250
+ const clientB = await adapter.acceptConnection(pairB.serverStream);
251
+ await adapter.createSubscription(clientB.id, {
252
+ eventTypes: ["message_delivered" as MAPEventType],
253
+ });
254
+
255
+ adapter.emitEvent({
256
+ eventId: ulid(),
257
+ type: "message_delivered" as MAPEventType,
258
+ timestamp: Date.now(),
259
+ agentId: "agent-1" as AgentId,
260
+ data: { from: "agent-1", to: "some-client", message: {} },
261
+ } as EventNotification);
262
+
263
+ await flushAsync();
264
+
265
+ // Both should receive
266
+ expect(pairA.clientMessages.length).toBeGreaterThan(0);
267
+ expect(pairB.clientMessages.length).toBeGreaterThan(0);
268
+
269
+ const notifA = pairA.clientMessages[0] as { method: string };
270
+ const notifB = pairB.clientMessages[0] as { method: string };
271
+ expect(notifA.method).toBe("map/event");
272
+ expect(notifB.method).toBe("map/event");
273
+ });
274
+
275
+ it("does NOT broadcast to participant with non-matching filter", async () => {
276
+ await adapter.start();
277
+
278
+ const pairA = createMockStreamPair();
279
+ const clientA = await adapter.acceptConnection(pairA.serverStream);
280
+ // Subscribe only to agent_registered
281
+ await adapter.createSubscription(clientA.id, {
282
+ eventTypes: ["agent_registered" as MAPEventType],
283
+ });
284
+
285
+ // Emit message_delivered — should NOT match
286
+ adapter.emitEvent({
287
+ eventId: ulid(),
288
+ type: "message_delivered" as MAPEventType,
289
+ timestamp: Date.now(),
290
+ agentId: "agent-1" as AgentId,
291
+ data: { from: "agent-1", to: "some-client", message: {} },
292
+ } as EventNotification);
293
+
294
+ await flushAsync();
295
+
296
+ expect(pairA.clientMessages).toHaveLength(0);
297
+ });
298
+
299
+ it("dot format event types match underscore subscriptions via normalization", async () => {
300
+ await adapter.start();
301
+
302
+ const pairA = createMockStreamPair();
303
+ const clientA = await adapter.acceptConnection(pairA.serverStream);
304
+ // Subscribe with underscore format (what SDK sends)
305
+ await adapter.createSubscription(clientA.id, {
306
+ eventTypes: ["agent_registered" as MAPEventType],
307
+ });
308
+
309
+ // Emit with DOT format — SHOULD match after normalization
310
+ adapter.emitEvent({
311
+ eventId: ulid(),
312
+ type: "agent.registered" as MAPEventType,
313
+ timestamp: Date.now(),
314
+ agentId: "agent-1" as AgentId,
315
+ data: { agentId: "agent-1" },
316
+ } as EventNotification);
317
+
318
+ await flushAsync();
319
+
320
+ // Should receive — normalization converts dots to underscores for matching
321
+ expect(pairA.clientMessages.length).toBeGreaterThan(0);
322
+ const notification = pairA.clientMessages[0] as { method: string };
323
+ expect(notification.method).toBe("map/event");
324
+ });
325
+
326
+ it("underscore format event types DO match underscore subscriptions", async () => {
327
+ await adapter.start();
328
+
329
+ const pairA = createMockStreamPair();
330
+ const clientA = await adapter.acceptConnection(pairA.serverStream);
331
+ await adapter.createSubscription(clientA.id, {
332
+ eventTypes: ["agent_registered" as MAPEventType],
333
+ });
334
+
335
+ // Emit with UNDERSCORE format — SHOULD match
336
+ adapter.emitEvent({
337
+ eventId: ulid(),
338
+ type: "agent_registered" as MAPEventType,
339
+ timestamp: Date.now(),
340
+ agentId: "agent-1" as AgentId,
341
+ data: { agentId: "agent-1" },
342
+ } as EventNotification);
343
+
344
+ await flushAsync();
345
+
346
+ expect(pairA.clientMessages.length).toBeGreaterThan(0);
347
+ const notification = pairA.clientMessages[0] as { method: string };
348
+ expect(notification.method).toBe("map/event");
349
+ });
350
+
351
+ it("sequence numbers increment per subscription", async () => {
352
+ await adapter.start();
353
+
354
+ const pairA = createMockStreamPair();
355
+ const clientA = await adapter.acceptConnection(pairA.serverStream);
356
+ await adapter.createSubscription(clientA.id, {
357
+ eventTypes: ["message_delivered" as MAPEventType],
358
+ });
359
+
360
+ // Emit three events
361
+ for (let i = 0; i < 3; i++) {
362
+ adapter.emitEvent({
363
+ eventId: ulid(),
364
+ type: "message_delivered" as MAPEventType,
365
+ timestamp: Date.now(),
366
+ agentId: "agent-1" as AgentId,
367
+ data: { index: i },
368
+ } as EventNotification);
369
+ }
370
+
371
+ await flushAsync();
372
+
373
+ expect(pairA.clientMessages).toHaveLength(3);
374
+
375
+ const seqNums = pairA.clientMessages.map(
376
+ (m) => (m as { params: { sequenceNumber: number } }).params.sequenceNumber,
377
+ );
378
+ expect(seqNums).toEqual([0, 1, 2]);
379
+ });
380
+
381
+ it("paused subscription does not receive events", async () => {
382
+ await adapter.start();
383
+
384
+ const pairA = createMockStreamPair();
385
+ const clientA = await adapter.acceptConnection(pairA.serverStream);
386
+ const subId = await adapter.createSubscription(clientA.id, {
387
+ eventTypes: ["message_delivered" as MAPEventType],
388
+ });
389
+
390
+ // Pause the subscription
391
+ await adapter.pauseSubscription(subId);
392
+
393
+ adapter.emitEvent({
394
+ eventId: ulid(),
395
+ type: "message_delivered" as MAPEventType,
396
+ timestamp: Date.now(),
397
+ agentId: "agent-1" as AgentId,
398
+ data: {},
399
+ } as EventNotification);
400
+
401
+ await flushAsync();
402
+
403
+ expect(pairA.clientMessages).toHaveLength(0);
404
+
405
+ // Resume and emit again
406
+ await adapter.resumeSubscription(subId);
407
+
408
+ adapter.emitEvent({
409
+ eventId: ulid(),
410
+ type: "message_delivered" as MAPEventType,
411
+ timestamp: Date.now(),
412
+ agentId: "agent-1" as AgentId,
413
+ data: {},
414
+ } as EventNotification);
415
+
416
+ await flushAsync();
417
+
418
+ expect(pairA.clientMessages).toHaveLength(1);
419
+ });
420
+ });