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
@@ -0,0 +1,386 @@
1
+ /**
2
+ * Unit tests for mapCall() — the thin MAP client utility.
3
+ *
4
+ * Spins up a real local WebSocket server per test to validate the
5
+ * handshake → RPC → close lifecycle.
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
9
+ import { WebSocketServer, WebSocket as WsWebSocket } from "ws";
10
+ import { mapCall, MapCallError } from "../map-client.js";
11
+
12
+ // =============================================================================
13
+ // Test Helpers
14
+ // =============================================================================
15
+
16
+ function getRandomPort(): number {
17
+ return 10000 + Math.floor(Math.random() * 50000);
18
+ }
19
+
20
+ interface TestServer {
21
+ port: number;
22
+ wss: WebSocketServer;
23
+ connections: WsWebSocket[];
24
+ close: () => Promise<void>;
25
+ }
26
+
27
+ /**
28
+ * Create a test WebSocket server that simulates the MAP protocol.
29
+ * @param handler - Called for each incoming JSON-RPC message with (ws, parsed message).
30
+ */
31
+ function createTestServer(
32
+ handler: (ws: WsWebSocket, msg: { jsonrpc: string; id: number; method: string; params?: unknown }) => void
33
+ ): TestServer {
34
+ const port = getRandomPort();
35
+ const connections: WsWebSocket[] = [];
36
+
37
+ const wss = new WebSocketServer({ port, path: "/map" });
38
+ wss.on("connection", (ws) => {
39
+ connections.push(ws);
40
+ ws.on("message", (data) => {
41
+ try {
42
+ const msg = JSON.parse(data.toString());
43
+ handler(ws, msg);
44
+ } catch {
45
+ // Ignore parse errors
46
+ }
47
+ });
48
+ });
49
+
50
+ return {
51
+ port,
52
+ wss,
53
+ connections,
54
+ close: () =>
55
+ new Promise<void>((resolve) => {
56
+ for (const conn of connections) {
57
+ if (conn.readyState === WsWebSocket.OPEN) conn.close();
58
+ }
59
+ wss.close(() => resolve());
60
+ }),
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Create a test server that handles the MAP handshake and responds to a single RPC.
66
+ */
67
+ function createHandshakeAndRpcServer(rpcResult: unknown): TestServer {
68
+ return createTestServer((ws, msg) => {
69
+ if (msg.method === "map/connect") {
70
+ // Respond to handshake
71
+ ws.send(
72
+ JSON.stringify({
73
+ jsonrpc: "2.0",
74
+ id: msg.id,
75
+ result: { participantId: "p-test", capabilities: {} },
76
+ })
77
+ );
78
+ } else {
79
+ // Respond to RPC
80
+ ws.send(
81
+ JSON.stringify({
82
+ jsonrpc: "2.0",
83
+ id: msg.id,
84
+ result: rpcResult,
85
+ })
86
+ );
87
+ }
88
+ });
89
+ }
90
+
91
+ // =============================================================================
92
+ // Tests
93
+ // =============================================================================
94
+
95
+ describe("mapCall", () => {
96
+ let server: TestServer;
97
+
98
+ afterEach(async () => {
99
+ if (server) {
100
+ await server.close();
101
+ }
102
+ });
103
+
104
+ // ─────────────────────────────────────────────────────────────────
105
+ // Success path
106
+ // ─────────────────────────────────────────────────────────────────
107
+
108
+ describe("success path", () => {
109
+ it("completes handshake and returns RPC result", async () => {
110
+ const expectedResult = { agent_id: "a1", task_id: "t1" };
111
+ server = createHandshakeAndRpcServer(expectedResult);
112
+
113
+ const result = await mapCall<typeof expectedResult>(
114
+ `http://localhost:${server.port}`,
115
+ "_macro/mcp/spawn_agent",
116
+ { task: "test" }
117
+ );
118
+
119
+ expect(result).toEqual(expectedResult);
120
+ });
121
+
122
+ it("converts http:// URL to ws:// with /map path", async () => {
123
+ let receivedConnection = false;
124
+ server = createTestServer((ws, msg) => {
125
+ receivedConnection = true;
126
+ if (msg.method === "map/connect") {
127
+ ws.send(JSON.stringify({ jsonrpc: "2.0", id: msg.id, result: {} }));
128
+ } else {
129
+ ws.send(JSON.stringify({ jsonrpc: "2.0", id: msg.id, result: "ok" }));
130
+ }
131
+ });
132
+
133
+ await mapCall(`http://localhost:${server.port}`, "test");
134
+ expect(receivedConnection).toBe(true);
135
+ });
136
+
137
+ it("passes params through to RPC request", async () => {
138
+ let capturedParams: unknown;
139
+ server = createTestServer((ws, msg) => {
140
+ if (msg.method === "map/connect") {
141
+ ws.send(JSON.stringify({ jsonrpc: "2.0", id: msg.id, result: {} }));
142
+ } else {
143
+ capturedParams = msg.params;
144
+ ws.send(JSON.stringify({ jsonrpc: "2.0", id: msg.id, result: "ok" }));
145
+ }
146
+ });
147
+
148
+ const params = { task: "test", context: { agent_id: "a1" } };
149
+ await mapCall(`http://localhost:${server.port}`, "_macro/mcp/spawn_agent", params);
150
+
151
+ expect(capturedParams).toEqual(params);
152
+ });
153
+
154
+ it("handles undefined params", async () => {
155
+ let capturedParams: unknown = "NOT_SET";
156
+ server = createTestServer((ws, msg) => {
157
+ if (msg.method === "map/connect") {
158
+ ws.send(JSON.stringify({ jsonrpc: "2.0", id: msg.id, result: {} }));
159
+ } else {
160
+ capturedParams = msg.params;
161
+ ws.send(JSON.stringify({ jsonrpc: "2.0", id: msg.id, result: "ok" }));
162
+ }
163
+ });
164
+
165
+ await mapCall(`http://localhost:${server.port}`, "test");
166
+ expect(capturedParams).toBeUndefined();
167
+ });
168
+ });
169
+
170
+ // ─────────────────────────────────────────────────────────────────
171
+ // Timeout
172
+ // ─────────────────────────────────────────────────────────────────
173
+
174
+ describe("timeout", () => {
175
+ it("times out if server never responds to handshake", async () => {
176
+ // Server accepts connection but never responds
177
+ server = createTestServer(() => {
178
+ // intentionally empty — no response
179
+ });
180
+
181
+ await expect(
182
+ mapCall(`http://localhost:${server.port}`, "test", undefined, { timeoutMs: 100 })
183
+ ).rejects.toThrow(MapCallError);
184
+
185
+ try {
186
+ await mapCall(`http://localhost:${server.port}`, "test", undefined, { timeoutMs: 100 });
187
+ } catch (err) {
188
+ expect(err).toBeInstanceOf(MapCallError);
189
+ expect((err as MapCallError).code).toBe(-32000);
190
+ expect((err as MapCallError).message).toContain("timed out");
191
+ }
192
+ });
193
+
194
+ it("times out if server never responds to RPC after handshake", async () => {
195
+ server = createTestServer((ws, msg) => {
196
+ if (msg.method === "map/connect") {
197
+ ws.send(JSON.stringify({ jsonrpc: "2.0", id: msg.id, result: {} }));
198
+ }
199
+ // Don't respond to the RPC
200
+ });
201
+
202
+ await expect(
203
+ mapCall(`http://localhost:${server.port}`, "test", undefined, { timeoutMs: 100 })
204
+ ).rejects.toThrow("timed out");
205
+ });
206
+
207
+ it("uses custom timeout from options", async () => {
208
+ server = createTestServer(() => {});
209
+
210
+ const start = Date.now();
211
+ await expect(
212
+ mapCall(`http://localhost:${server.port}`, "test", undefined, { timeoutMs: 50 })
213
+ ).rejects.toThrow(MapCallError);
214
+ const elapsed = Date.now() - start;
215
+
216
+ // Should have timed out quickly (within ~200ms allowing for jitter)
217
+ expect(elapsed).toBeLessThan(500);
218
+ });
219
+ });
220
+
221
+ // ─────────────────────────────────────────────────────────────────
222
+ // Handshake failure
223
+ // ─────────────────────────────────────────────────────────────────
224
+
225
+ describe("handshake failure", () => {
226
+ it("rejects with MapCallError if handshake returns error response", async () => {
227
+ server = createTestServer((ws, msg) => {
228
+ if (msg.method === "map/connect") {
229
+ ws.send(
230
+ JSON.stringify({
231
+ jsonrpc: "2.0",
232
+ id: msg.id,
233
+ error: { code: -32600, message: "Invalid request" },
234
+ })
235
+ );
236
+ }
237
+ });
238
+
239
+ try {
240
+ await mapCall(`http://localhost:${server.port}`, "test");
241
+ expect.fail("Should have thrown");
242
+ } catch (err) {
243
+ expect(err).toBeInstanceOf(MapCallError);
244
+ expect((err as MapCallError).code).toBe(-32600);
245
+ expect((err as MapCallError).message).toContain("MAP handshake failed");
246
+ expect((err as MapCallError).message).toContain("Invalid request");
247
+ }
248
+ });
249
+
250
+ it("includes error data from handshake failure", async () => {
251
+ server = createTestServer((ws, msg) => {
252
+ if (msg.method === "map/connect") {
253
+ ws.send(
254
+ JSON.stringify({
255
+ jsonrpc: "2.0",
256
+ id: msg.id,
257
+ error: { code: -32600, message: "Bad", data: { reason: "auth_required" } },
258
+ })
259
+ );
260
+ }
261
+ });
262
+
263
+ try {
264
+ await mapCall(`http://localhost:${server.port}`, "test");
265
+ expect.fail("Should have thrown");
266
+ } catch (err) {
267
+ expect(err).toBeInstanceOf(MapCallError);
268
+ expect((err as MapCallError).data).toEqual({ reason: "auth_required" });
269
+ }
270
+ });
271
+ });
272
+
273
+ // ─────────────────────────────────────────────────────────────────
274
+ // RPC error response
275
+ // ─────────────────────────────────────────────────────────────────
276
+
277
+ describe("RPC error response", () => {
278
+ it("rejects with MapCallError on RPC error", async () => {
279
+ server = createTestServer((ws, msg) => {
280
+ if (msg.method === "map/connect") {
281
+ ws.send(JSON.stringify({ jsonrpc: "2.0", id: msg.id, result: {} }));
282
+ } else {
283
+ ws.send(
284
+ JSON.stringify({
285
+ jsonrpc: "2.0",
286
+ id: msg.id,
287
+ error: { code: -32601, message: "Method not found" },
288
+ })
289
+ );
290
+ }
291
+ });
292
+
293
+ try {
294
+ await mapCall(`http://localhost:${server.port}`, "_macro/mcp/nonexistent");
295
+ expect.fail("Should have thrown");
296
+ } catch (err) {
297
+ expect(err).toBeInstanceOf(MapCallError);
298
+ expect((err as MapCallError).code).toBe(-32601);
299
+ expect((err as MapCallError).message).toBe("Method not found");
300
+ }
301
+ });
302
+
303
+ it("includes error data from RPC failure", async () => {
304
+ server = createTestServer((ws, msg) => {
305
+ if (msg.method === "map/connect") {
306
+ ws.send(JSON.stringify({ jsonrpc: "2.0", id: msg.id, result: {} }));
307
+ } else {
308
+ ws.send(
309
+ JSON.stringify({
310
+ jsonrpc: "2.0",
311
+ id: msg.id,
312
+ error: { code: -32602, message: "Bad params", data: { field: "agent_id" } },
313
+ })
314
+ );
315
+ }
316
+ });
317
+
318
+ try {
319
+ await mapCall(`http://localhost:${server.port}`, "test");
320
+ expect.fail("Should have thrown");
321
+ } catch (err) {
322
+ expect(err).toBeInstanceOf(MapCallError);
323
+ expect((err as MapCallError).data).toEqual({ field: "agent_id" });
324
+ }
325
+ });
326
+ });
327
+
328
+ // ─────────────────────────────────────────────────────────────────
329
+ // Connection failures
330
+ // ─────────────────────────────────────────────────────────────────
331
+
332
+ describe("connection failures", () => {
333
+ it("rejects when connection is refused", async () => {
334
+ // Use a port with no server
335
+ const unusedPort = getRandomPort();
336
+
337
+ await expect(
338
+ mapCall(`http://localhost:${unusedPort}`, "test", undefined, { timeoutMs: 2000 })
339
+ ).rejects.toThrow(MapCallError);
340
+ });
341
+
342
+ it("rejects when server closes socket before responding", async () => {
343
+ server = createTestServer((ws, msg) => {
344
+ if (msg.method === "map/connect") {
345
+ ws.send(JSON.stringify({ jsonrpc: "2.0", id: msg.id, result: {} }));
346
+ } else {
347
+ // Close instead of responding
348
+ ws.close();
349
+ }
350
+ });
351
+
352
+ try {
353
+ await mapCall(`http://localhost:${server.port}`, "test");
354
+ expect.fail("Should have thrown");
355
+ } catch (err) {
356
+ expect(err).toBeInstanceOf(MapCallError);
357
+ expect((err as MapCallError).message).toContain("closed unexpectedly");
358
+ }
359
+ });
360
+ });
361
+
362
+ // ─────────────────────────────────────────────────────────────────
363
+ // MapCallError class
364
+ // ─────────────────────────────────────────────────────────────────
365
+
366
+ describe("MapCallError", () => {
367
+ it("extends Error", () => {
368
+ const err = new MapCallError(-32000, "test error");
369
+ expect(err).toBeInstanceOf(Error);
370
+ expect(err).toBeInstanceOf(MapCallError);
371
+ });
372
+
373
+ it("has correct properties", () => {
374
+ const err = new MapCallError(-32601, "Method not found", { detail: "extra" });
375
+ expect(err.name).toBe("MapCallError");
376
+ expect(err.code).toBe(-32601);
377
+ expect(err.message).toBe("Method not found");
378
+ expect(err.data).toEqual({ detail: "extra" });
379
+ });
380
+
381
+ it("has undefined data when not provided", () => {
382
+ const err = new MapCallError(-32000, "test");
383
+ expect(err.data).toBeUndefined();
384
+ });
385
+ });
386
+ });