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
@@ -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
+ });