macro-agent 0.1.0 → 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 (337) 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 +15 -0
  6. package/dist/acp/macro-agent.d.ts.map +1 -1
  7. package/dist/acp/macro-agent.js +131 -35
  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 +17 -0
  53. package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
  54. package/dist/map/adapter/acp-over-map.js +384 -23
  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 +3 -0
  95. package/dist/map/adapter/map-adapter.d.ts.map +1 -1
  96. package/dist/map/adapter/map-adapter.js +258 -35
  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 +2 -1
  131. package/dist/store/event-store.d.ts.map +1 -1
  132. package/dist/store/event-store.js +69 -20
  133. package/dist/store/event-store.js.map +1 -1
  134. package/dist/store/types/agents.d.ts +18 -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__/integration.test.ts +56 -31
  197. package/src/acp/__tests__/macro-agent.test.ts +16 -7
  198. package/src/acp/macro-agent.ts +170 -36
  199. package/src/acp/types.ts +46 -1
  200. package/src/agent/__tests__/agent-manager.test.ts +228 -2
  201. package/src/agent/agent-manager.ts +714 -261
  202. package/src/agent/types.ts +3 -1
  203. package/src/api/server.ts +41 -7
  204. package/src/auth/__tests__/token.test.ts +100 -0
  205. package/src/auth/index.ts +1 -0
  206. package/src/auth/token.ts +82 -0
  207. package/src/cli/__tests__/acp.test.ts +1 -1
  208. package/src/cli/__tests__/stable-instance-id.test.ts +1 -1
  209. package/src/cli/acp.ts +130 -72
  210. package/src/cli/index.ts +120 -14
  211. package/src/cli/mcp.ts +311 -207
  212. package/src/cli/parse-args.ts +54 -0
  213. package/src/cli/stable-instance-id.ts +14 -0
  214. package/src/config/project-config.ts +190 -27
  215. package/src/lifecycle/__tests__/cascade-termination.test.ts +1 -1
  216. package/src/map/adapter/__tests__/acp-over-map-cancel.test.ts +22 -4
  217. package/src/map/adapter/__tests__/acp-over-map-getmodels.test.ts +355 -0
  218. package/src/map/adapter/__tests__/acp-over-map-history.test.ts +263 -0
  219. package/src/map/adapter/__tests__/acp-over-map-persistence.e2e.test.ts +1 -1
  220. package/src/map/adapter/__tests__/event-broadcast.test.ts +420 -0
  221. package/src/map/adapter/__tests__/event-log.test.ts +527 -0
  222. package/src/map/adapter/__tests__/event-translator.test.ts +3 -3
  223. package/src/map/adapter/__tests__/extensions.test.ts +408 -0
  224. package/src/map/adapter/__tests__/map-adapter.test.ts +99 -0
  225. package/src/map/adapter/__tests__/mcp-bridge.test.ts +1187 -0
  226. package/src/map/adapter/__tests__/multi-client-broadcast.test.ts +711 -0
  227. package/src/map/adapter/__tests__/websocket-integration.test.ts +218 -0
  228. package/src/map/adapter/acp-over-map.ts +678 -66
  229. package/src/map/adapter/connection-manager.ts +3 -0
  230. package/src/map/adapter/event-log.ts +208 -0
  231. package/src/map/adapter/event-translator.ts +6 -6
  232. package/src/map/adapter/extensions/agent-lifecycle.ts +267 -0
  233. package/src/map/adapter/extensions/index.ts +60 -0
  234. package/src/map/adapter/extensions/mcp-bridge.ts +995 -0
  235. package/src/map/adapter/extensions/task.ts +11 -0
  236. package/src/map/adapter/extensions/update-metadata.ts +126 -0
  237. package/src/map/adapter/index.ts +28 -0
  238. package/src/map/adapter/interface.ts +2 -0
  239. package/src/map/adapter/map-adapter.ts +312 -47
  240. package/src/map/adapter/subscription-manager.ts +5 -1
  241. package/src/map/adapter/types.ts +2 -0
  242. package/src/mcp/__tests__/map-client.test.ts +386 -0
  243. package/src/mcp/__tests__/mcp-server-thin-client.test.ts +368 -0
  244. package/src/mcp/__tests__/mcp-server.test.ts +100 -1
  245. package/src/mcp/map-client.ts +177 -0
  246. package/src/mcp/mcp-server.ts +191 -100
  247. package/src/mcp/types.ts +6 -1
  248. package/src/metrics/metrics.ts +1 -1
  249. package/src/monitor/__tests__/stale-agent-flow.integration.test.ts +1 -1
  250. package/src/roles/__tests__/config-loader.test.ts +7 -7
  251. package/src/roles/capabilities.ts +17 -7
  252. package/src/roles/config-loader.ts +6 -6
  253. package/src/roles/registry.ts +2 -2
  254. package/src/server/__tests__/combined-server.test.ts +94 -21
  255. package/src/server/combined-server.ts +189 -33
  256. package/src/steering/__tests__/steering-integration.test.ts +1 -1
  257. package/src/store/__tests__/event-store.test.ts +196 -1
  258. package/src/store/__tests__/instance.test.ts +3 -3
  259. package/src/store/event-store.ts +80 -21
  260. package/src/store/types/agents.ts +15 -0
  261. package/src/store/types/events.ts +1 -1
  262. package/src/task/backend/__tests__/create-task-backend.test.ts +225 -0
  263. package/src/task/backend/__tests__/e2e/unified-tool-provider-opentasks.e2e.test.ts +524 -0
  264. package/src/task/backend/__tests__/unified-tool-provider.test.ts +579 -0
  265. package/src/task/backend/index.ts +156 -106
  266. package/src/task/backend/memory.ts +4 -0
  267. package/src/task/backend/opentasks/__tests__/backend.test.ts +968 -0
  268. package/src/task/backend/opentasks/__tests__/daemon-manager.test.ts +406 -0
  269. package/src/task/backend/opentasks/__tests__/mapping.test.ts +84 -0
  270. package/src/task/backend/opentasks/__tests__/opentasks-backend.e2e.test.ts +1338 -0
  271. package/src/task/backend/opentasks/backend.ts +1323 -0
  272. package/src/task/backend/opentasks/client.ts +652 -0
  273. package/src/task/backend/opentasks/daemon-manager.ts +253 -0
  274. package/src/task/backend/opentasks/index.ts +69 -0
  275. package/src/task/backend/opentasks/mapping.ts +94 -0
  276. package/src/task/backend/types.ts +42 -66
  277. package/src/task/backend/unified-tool-provider.ts +779 -0
  278. package/src/teams/__tests__/cross-subsystem.integration.test.ts +1 -1
  279. package/src/teams/team-loader.ts +3 -3
  280. package/src/teams/team-runtime.ts +2 -0
  281. package/test_fixtures/README.md +2 -3
  282. package/test_fixtures/fixtures/index.ts +0 -3
  283. package/test_fixtures/fixtures/projects/project-with-specs.ts +7 -149
  284. package/test_fixtures/fixtures/repos/index.ts +1 -3
  285. package/test_fixtures/fixtures/repos/temp-repo-factory.ts +0 -116
  286. package/test_fixtures/fixtures/repos/types.ts +0 -11
  287. package/test_fixtures/harness/__tests__/fixtures.test.ts +10 -102
  288. package/test_fixtures/harness/__tests__/temp-repo-and-simulator.test.ts +0 -33
  289. package/test_fixtures/harness/simulator/agent-simulator.ts +4 -4
  290. package/vitest.config.ts +1 -1
  291. package/vitest.e2e.config.ts +1 -1
  292. package/vitest.setup.ts +1 -30
  293. package/.macro-agent/teams/self-driving/prompts/grinder.md +0 -27
  294. package/.macro-agent/teams/self-driving/prompts/judge.md +0 -27
  295. package/.macro-agent/teams/self-driving/prompts/planner.md +0 -33
  296. package/.macro-agent/teams/self-driving/roles/grinder.yaml +0 -17
  297. package/.macro-agent/teams/self-driving/roles/judge.yaml +0 -24
  298. package/.macro-agent/teams/self-driving/roles/planner.yaml +0 -18
  299. package/.macro-agent/teams/self-driving/team.yaml +0 -103
  300. package/.macro-agent/teams/structured/prompts/developer.md +0 -26
  301. package/.macro-agent/teams/structured/prompts/lead.md +0 -25
  302. package/.macro-agent/teams/structured/prompts/reviewer.md +0 -24
  303. package/.macro-agent/teams/structured/roles/developer.yaml +0 -12
  304. package/.macro-agent/teams/structured/roles/lead.yaml +0 -11
  305. package/.macro-agent/teams/structured/roles/reviewer.yaml +0 -19
  306. package/.macro-agent/teams/structured/team.yaml +0 -89
  307. package/docs/sudocode-integration.md +0 -383
  308. package/src/task/backend/__tests__/backend-parity.test.ts +0 -451
  309. package/src/task/backend/__tests__/tool-provider-edge-cases.test.ts +0 -430
  310. package/src/task/backend/__tests__/tool-provider.test.ts +0 -983
  311. package/src/task/backend/sudocode/__tests__/backend-edge-cases.test.ts +0 -575
  312. package/src/task/backend/sudocode/__tests__/backend.test.ts +0 -1194
  313. package/src/task/backend/sudocode/__tests__/client-integration.test.ts +0 -418
  314. package/src/task/backend/sudocode/__tests__/client.test.ts +0 -345
  315. package/src/task/backend/sudocode/__tests__/e2e/backend.e2e.test.ts +0 -753
  316. package/src/task/backend/sudocode/__tests__/e2e/server-client.e2e.test.ts +0 -680
  317. package/src/task/backend/sudocode/__tests__/e2e-workflow.test.ts +0 -666
  318. package/src/task/backend/sudocode/__tests__/integration/standalone-client.integration.test.ts +0 -396
  319. package/src/task/backend/sudocode/__tests__/integration/sudocode-cli.integration.test.ts +0 -328
  320. package/src/task/backend/sudocode/__tests__/integration/test-utils.ts +0 -175
  321. package/src/task/backend/sudocode/__tests__/mapping-edge-cases.test.ts +0 -265
  322. package/src/task/backend/sudocode/__tests__/server-client.test.ts +0 -675
  323. package/src/task/backend/sudocode/__tests__/sync-policy-edge-cases.test.ts +0 -521
  324. package/src/task/backend/sudocode/__tests__/sync-policy.test.ts +0 -519
  325. package/src/task/backend/sudocode/__tests__/tools.test.ts +0 -471
  326. package/src/task/backend/sudocode/backend.ts +0 -1237
  327. package/src/task/backend/sudocode/client.ts +0 -515
  328. package/src/task/backend/sudocode/index.ts +0 -120
  329. package/src/task/backend/sudocode/mapping.ts +0 -93
  330. package/src/task/backend/sudocode/server-client.ts +0 -522
  331. package/src/task/backend/sudocode/standalone-client.ts +0 -623
  332. package/src/task/backend/sudocode/sync-policy.ts +0 -387
  333. package/src/task/backend/sudocode/tools.ts +0 -896
  334. package/src/task/backend/tool-provider.ts +0 -506
  335. package/test_fixtures/fixtures/sudocode/index.ts +0 -29
  336. package/test_fixtures/fixtures/sudocode/issues.ts +0 -185
  337. package/test_fixtures/fixtures/sudocode/specs.ts +0 -159
@@ -1078,6 +1078,269 @@ describe("ACP-over-MAP history persistence", () => {
1078
1078
  expect(result.plan[0].content).toBe("Persistent task");
1079
1079
  });
1080
1080
 
1081
+ // ─────────────────────────────────────────────────────────────────
1082
+ // Fork history tests
1083
+ // ─────────────────────────────────────────────────────────────────
1084
+
1085
+ /** Register a forked agent with fork_of metadata */
1086
+ function registerForkedAgent(
1087
+ agentId: string,
1088
+ sessionId: string,
1089
+ sourceAgentId: string,
1090
+ createdAt?: number,
1091
+ ): void {
1092
+ eventStore.emit({
1093
+ type: "spawn",
1094
+ source: { agent_id: sourceAgentId },
1095
+ payload: {
1096
+ agent_id: agentId,
1097
+ session_id: sessionId,
1098
+ task: `[Fork of ${sourceAgentId}]`,
1099
+ task_id: "task-fork",
1100
+ cwd: "/test/cwd",
1101
+ },
1102
+ });
1103
+ // Set fork_of metadata (mirrors what agent-manager.ts does)
1104
+ eventStore.updateAgentMetadata(agentId as AgentId, {
1105
+ metadata: { fork_of: sourceAgentId },
1106
+ });
1107
+ // Backdate created_at if specified (for timestamp filtering tests)
1108
+ if (createdAt !== undefined) {
1109
+ // Overwrite the agent row's created_at by re-emitting a lifecycle event
1110
+ // at the desired time — but since we can't change created_at directly,
1111
+ // we rely on the spawn event timestamp. For testing, we'll emit the
1112
+ // spawn before recording turns to ensure correct ordering.
1113
+ }
1114
+ // Mark as running
1115
+ eventStore.emit({
1116
+ type: "lifecycle",
1117
+ source: { agent_id: agentId },
1118
+ payload: {
1119
+ agent_id: agentId,
1120
+ action: "started",
1121
+ },
1122
+ });
1123
+ }
1124
+
1125
+ /** Record a turn directly in the event store (bypasses prompt flow) */
1126
+ function recordTurn(
1127
+ agentId: string,
1128
+ conversationId: string,
1129
+ role: "user" | "assistant",
1130
+ content: unknown,
1131
+ ): void {
1132
+ const now = Date.now();
1133
+ eventStore.emit({
1134
+ type: "turn",
1135
+ source: { agent_id: agentId },
1136
+ payload: {
1137
+ action: "recorded",
1138
+ turn_id: `turn_${role}_${now}_${Math.random().toString(36).slice(2, 8)}`,
1139
+ conversation_id: conversationId,
1140
+ participant: role,
1141
+ timestamp: now,
1142
+ content_type: role === "user" ? "user_prompt" : "assistant_response",
1143
+ content,
1144
+ source_type: "acp",
1145
+ },
1146
+ });
1147
+ }
1148
+
1149
+ it("should include source agent history for forked agents", async () => {
1150
+ await setup();
1151
+
1152
+ const sourceAgentId = "agent-source" as AgentId;
1153
+ const sourceSessionId = "session-source";
1154
+ const forkedAgentId = "agent-forked" as AgentId;
1155
+ const forkedSessionId = "session-forked";
1156
+
1157
+ // Register source agent and record turns
1158
+ registerAgent(sourceAgentId, sourceSessionId);
1159
+ recordTurn(sourceAgentId, sourceSessionId, "user", "Hello source");
1160
+ recordTurn(sourceAgentId, sourceSessionId, "assistant", { parts: [{ type: "text", text: "Hi from source" }] });
1161
+
1162
+ // Register forked agent with fork_of metadata
1163
+ registerForkedAgent(forkedAgentId, forkedSessionId, sourceAgentId);
1164
+
1165
+ // Initialize stream for forked agent
1166
+ const streamId = "fork-history-stream";
1167
+ await handler.processRequest(
1168
+ forkedAgentId,
1169
+ envelope(streamId, "initialize", {
1170
+ protocolVersion: 1,
1171
+ capabilities: {},
1172
+ clientInfo: { name: "test", version: "1.0" },
1173
+ }),
1174
+ );
1175
+
1176
+ // Query history for forked agent
1177
+ const historyResult = await handler.processRequest(
1178
+ forkedAgentId,
1179
+ envelope(streamId, "_macro/getHistory", { agentId: forkedAgentId }),
1180
+ );
1181
+
1182
+ const turns = (historyResult.acp.result as { turns: { role: string; content: unknown }[] }).turns;
1183
+
1184
+ // Should include source agent's 2 turns
1185
+ expect(turns).toHaveLength(2);
1186
+ expect(turns[0].role).toBe("user");
1187
+ expect(turns[0].content).toBe("Hello source");
1188
+ expect(turns[1].role).toBe("assistant");
1189
+ });
1190
+
1191
+ it("should combine source and forked agent turns in order", async () => {
1192
+ await setup();
1193
+
1194
+ const sourceAgentId = "agent-src" as AgentId;
1195
+ const sourceSessionId = "session-src";
1196
+ const forkedAgentId = "agent-fork" as AgentId;
1197
+ const forkedSessionId = "session-fork";
1198
+
1199
+ // Source agent conversation
1200
+ registerAgent(sourceAgentId, sourceSessionId);
1201
+ recordTurn(sourceAgentId, sourceSessionId, "user", "Question 1");
1202
+ recordTurn(sourceAgentId, sourceSessionId, "assistant", { parts: [{ type: "text", text: "Answer 1" }] });
1203
+
1204
+ // Fork the agent
1205
+ registerForkedAgent(forkedAgentId, forkedSessionId, sourceAgentId);
1206
+
1207
+ // Forked agent has its own turns
1208
+ recordTurn(forkedAgentId, forkedSessionId, "user", "Question 2 (forked)");
1209
+ recordTurn(forkedAgentId, forkedSessionId, "assistant", { parts: [{ type: "text", text: "Answer 2 (forked)" }] });
1210
+
1211
+ const streamId = "fork-combined-stream";
1212
+ await handler.processRequest(
1213
+ forkedAgentId,
1214
+ envelope(streamId, "initialize", {
1215
+ protocolVersion: 1,
1216
+ capabilities: {},
1217
+ clientInfo: { name: "test", version: "1.0" },
1218
+ }),
1219
+ );
1220
+
1221
+ const historyResult = await handler.processRequest(
1222
+ forkedAgentId,
1223
+ envelope(streamId, "_macro/getHistory", { agentId: forkedAgentId }),
1224
+ );
1225
+
1226
+ const turns = (historyResult.acp.result as { turns: { role: string; content: unknown }[] }).turns;
1227
+
1228
+ // Source turns (2) + forked turns (2) = 4
1229
+ expect(turns).toHaveLength(4);
1230
+ expect(turns[0].content).toBe("Question 1");
1231
+ expect(turns[2].content).toBe("Question 2 (forked)");
1232
+ });
1233
+
1234
+ it("should not include source turns recorded after the fork", async () => {
1235
+ await setup();
1236
+
1237
+ const sourceAgentId = "agent-pre" as AgentId;
1238
+ const sourceSessionId = "session-pre";
1239
+ const forkedAgentId = "agent-post" as AgentId;
1240
+ const forkedSessionId = "session-post";
1241
+
1242
+ // Source agent: record a turn before the fork
1243
+ registerAgent(sourceAgentId, sourceSessionId);
1244
+ recordTurn(sourceAgentId, sourceSessionId, "user", "Before fork");
1245
+ recordTurn(sourceAgentId, sourceSessionId, "assistant", { parts: [{ type: "text", text: "Pre-fork reply" }] });
1246
+
1247
+ // Small delay to ensure fork timestamp is after source turns
1248
+ await new Promise((r) => setTimeout(r, 10));
1249
+
1250
+ // Fork the agent
1251
+ registerForkedAgent(forkedAgentId, forkedSessionId, sourceAgentId);
1252
+
1253
+ // Small delay to ensure post-fork turn timestamp is after fork
1254
+ await new Promise((r) => setTimeout(r, 10));
1255
+
1256
+ // Source agent continues after fork — these should NOT appear in forked history
1257
+ recordTurn(sourceAgentId, sourceSessionId, "user", "After fork on source");
1258
+ recordTurn(sourceAgentId, sourceSessionId, "assistant", { parts: [{ type: "text", text: "Post-fork source reply" }] });
1259
+
1260
+ const streamId = "fork-filter-stream";
1261
+ await handler.processRequest(
1262
+ forkedAgentId,
1263
+ envelope(streamId, "initialize", {
1264
+ protocolVersion: 1,
1265
+ capabilities: {},
1266
+ clientInfo: { name: "test", version: "1.0" },
1267
+ }),
1268
+ );
1269
+
1270
+ const historyResult = await handler.processRequest(
1271
+ forkedAgentId,
1272
+ envelope(streamId, "_macro/getHistory", { agentId: forkedAgentId }),
1273
+ );
1274
+
1275
+ const turns = (historyResult.acp.result as { turns: { role: string; content: unknown }[] }).turns;
1276
+
1277
+ // Should only include the 2 pre-fork turns, not the 2 post-fork ones
1278
+ expect(turns).toHaveLength(2);
1279
+ expect(turns[0].content).toBe("Before fork");
1280
+ expect(turns.find((t: { content: unknown }) => t.content === "After fork on source")).toBeUndefined();
1281
+ });
1282
+
1283
+ it("should return empty history for forked agent when source has no turns", async () => {
1284
+ await setup();
1285
+
1286
+ const sourceAgentId = "agent-empty-src" as AgentId;
1287
+ const sourceSessionId = "session-empty-src";
1288
+ const forkedAgentId = "agent-empty-fork" as AgentId;
1289
+ const forkedSessionId = "session-empty-fork";
1290
+
1291
+ // Source agent exists but has no conversation turns
1292
+ registerAgent(sourceAgentId, sourceSessionId);
1293
+ registerForkedAgent(forkedAgentId, forkedSessionId, sourceAgentId);
1294
+
1295
+ const streamId = "fork-empty-stream";
1296
+ await handler.processRequest(
1297
+ forkedAgentId,
1298
+ envelope(streamId, "initialize", {
1299
+ protocolVersion: 1,
1300
+ capabilities: {},
1301
+ clientInfo: { name: "test", version: "1.0" },
1302
+ }),
1303
+ );
1304
+
1305
+ const historyResult = await handler.processRequest(
1306
+ forkedAgentId,
1307
+ envelope(streamId, "_macro/getHistory", { agentId: forkedAgentId }),
1308
+ );
1309
+
1310
+ const turns = (historyResult.acp.result as { turns: unknown[] }).turns;
1311
+ expect(turns).toEqual([]);
1312
+ });
1313
+
1314
+ it("should not affect non-forked agent history queries", async () => {
1315
+ await setup();
1316
+
1317
+ const agentId = "agent-normal" as AgentId;
1318
+ const sessionId = "session-normal";
1319
+
1320
+ registerAgent(agentId, sessionId);
1321
+ recordTurn(agentId, sessionId, "user", "Normal question");
1322
+ recordTurn(agentId, sessionId, "assistant", { parts: [{ type: "text", text: "Normal reply" }] });
1323
+
1324
+ const streamId = "normal-history-stream";
1325
+ await handler.processRequest(
1326
+ agentId,
1327
+ envelope(streamId, "initialize", {
1328
+ protocolVersion: 1,
1329
+ capabilities: {},
1330
+ clientInfo: { name: "test", version: "1.0" },
1331
+ }),
1332
+ );
1333
+
1334
+ const historyResult = await handler.processRequest(
1335
+ agentId,
1336
+ envelope(streamId, "_macro/getHistory", { agentId }),
1337
+ );
1338
+
1339
+ const turns = (historyResult.acp.result as { turns: { role: string; content: unknown }[] }).turns;
1340
+ expect(turns).toHaveLength(2);
1341
+ expect(turns[0].content).toBe("Normal question");
1342
+ });
1343
+
1081
1344
  it("should include both plan and cwd together in getHistory response", async () => {
1082
1345
  await setup([
1083
1346
  {
@@ -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
+ });