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
@@ -1,675 +0,0 @@
1
- /**
2
- * ServerClient Tests
3
- *
4
- * Tests for the ServerClient implementation (managed mode).
5
- */
6
-
7
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
8
- import { ServerClient } from "../server-client.js";
9
- import type { IssueChangeEvent } from "../client.js";
10
-
11
- // Mock fetch
12
- const mockFetch = vi.fn();
13
- vi.stubGlobal("fetch", mockFetch);
14
-
15
- // Mock WebSocket
16
- class MockWebSocket {
17
- static instances: MockWebSocket[] = [];
18
- onopen: (() => void) | null = null;
19
- onclose: (() => void) | null = null;
20
- onerror: ((error: unknown) => void) | null = null;
21
- onmessage: ((event: { data: string }) => void) | null = null;
22
-
23
- constructor(public url: string) {
24
- MockWebSocket.instances.push(this);
25
- // Simulate async connection
26
- setTimeout(() => {
27
- if (this.onopen) this.onopen();
28
- }, 0);
29
- }
30
-
31
- close() {
32
- // Don't trigger onclose to prevent reconnection attempts
33
- }
34
-
35
- // Helper to simulate receiving a message
36
- simulateMessage(data: unknown) {
37
- if (this.onmessage) {
38
- this.onmessage({ data: JSON.stringify(data) });
39
- }
40
- }
41
-
42
- // Helper to simulate connection
43
- simulateOpen() {
44
- if (this.onopen) this.onopen();
45
- }
46
-
47
- // Helper to simulate disconnection
48
- simulateClose() {
49
- if (this.onclose) this.onclose();
50
- }
51
-
52
- static clearInstances() {
53
- MockWebSocket.instances = [];
54
- }
55
-
56
- static getLatest(): MockWebSocket | undefined {
57
- return MockWebSocket.instances[MockWebSocket.instances.length - 1];
58
- }
59
- }
60
-
61
- vi.stubGlobal("WebSocket", MockWebSocket);
62
-
63
- describe("ServerClient", () => {
64
- let client: ServerClient;
65
-
66
- beforeEach(() => {
67
- vi.clearAllMocks();
68
- MockWebSocket.clearInstances();
69
- vi.useFakeTimers();
70
- });
71
-
72
- afterEach(() => {
73
- client?.close();
74
- vi.useRealTimers();
75
- });
76
-
77
- describe("constructor", () => {
78
- it("should initialize with config", () => {
79
- client = new ServerClient({
80
- serverUrl: "http://localhost:3001",
81
- wsUrl: "ws://localhost:3001/ws",
82
- });
83
-
84
- expect(MockWebSocket.instances).toHaveLength(1);
85
- expect(MockWebSocket.instances[0].url).toBe("ws://localhost:3001/ws");
86
- });
87
-
88
- it("should derive wsUrl from serverUrl if not provided", () => {
89
- client = new ServerClient({
90
- serverUrl: "http://localhost:3001",
91
- });
92
-
93
- expect(MockWebSocket.instances[0].url).toBe("ws://localhost:3001/ws");
94
- });
95
-
96
- it("should handle https to wss conversion", () => {
97
- client = new ServerClient({
98
- serverUrl: "https://example.com",
99
- });
100
-
101
- expect(MockWebSocket.instances[0].url).toBe("wss://example.com/ws");
102
- });
103
- });
104
-
105
- describe("isReady", () => {
106
- it("should return false before WebSocket connects", () => {
107
- client = new ServerClient({
108
- serverUrl: "http://localhost:3001",
109
- });
110
-
111
- expect(client.isReady()).toBe(false);
112
- });
113
-
114
- it("should return true after WebSocket connects", async () => {
115
- client = new ServerClient({
116
- serverUrl: "http://localhost:3001",
117
- });
118
-
119
- // Advance timers to trigger onopen
120
- await vi.advanceTimersByTimeAsync(10);
121
-
122
- expect(client.isReady()).toBe(true);
123
- });
124
- });
125
-
126
- describe("HTTP operations", () => {
127
- beforeEach(() => {
128
- client = new ServerClient({
129
- serverUrl: "http://localhost:3001",
130
- projectId: "test-project",
131
- });
132
- });
133
-
134
- describe("getIssue", () => {
135
- it("should fetch an issue by ID", async () => {
136
- const mockIssue = {
137
- id: "i-abc123",
138
- uuid: "uuid-123",
139
- title: "Test Issue",
140
- content: "Test content",
141
- status: "open",
142
- priority: 1,
143
- created_at: "2024-01-01T00:00:00Z",
144
- updated_at: "2024-01-01T00:00:00Z",
145
- };
146
-
147
- mockFetch.mockResolvedValueOnce({
148
- json: async () => ({ success: true, data: mockIssue }),
149
- });
150
-
151
- const issue = await client.getIssue("i-abc123");
152
-
153
- expect(issue).toEqual(mockIssue);
154
- expect(mockFetch).toHaveBeenCalledWith(
155
- "http://localhost:3001/api/issues/i-abc123",
156
- expect.objectContaining({
157
- method: "GET",
158
- headers: {
159
- "Content-Type": "application/json",
160
- "X-Project-ID": "test-project",
161
- },
162
- })
163
- );
164
- });
165
-
166
- it("should return null when issue not found", async () => {
167
- mockFetch.mockResolvedValueOnce({
168
- json: async () => ({
169
- success: false,
170
- message: "Issue not found",
171
- }),
172
- });
173
-
174
- const issue = await client.getIssue("i-nonexistent");
175
-
176
- expect(issue).toBeNull();
177
- });
178
-
179
- it("should throw on other errors", async () => {
180
- mockFetch.mockResolvedValueOnce({
181
- json: async () => ({
182
- success: false,
183
- message: "Server error",
184
- }),
185
- });
186
-
187
- await expect(client.getIssue("i-abc123")).rejects.toThrow(
188
- "Server error"
189
- );
190
- });
191
- });
192
-
193
- describe("listIssues", () => {
194
- it("should list issues with no filter", async () => {
195
- const mockIssues = [
196
- { id: "i-1", title: "Issue 1" },
197
- { id: "i-2", title: "Issue 2" },
198
- ];
199
-
200
- mockFetch.mockResolvedValueOnce({
201
- json: async () => ({ success: true, data: mockIssues }),
202
- });
203
-
204
- const issues = await client.listIssues();
205
-
206
- expect(issues).toEqual(mockIssues);
207
- expect(mockFetch).toHaveBeenCalledWith(
208
- "http://localhost:3001/api/issues",
209
- expect.anything()
210
- );
211
- });
212
-
213
- it("should apply filter parameters", async () => {
214
- mockFetch.mockResolvedValueOnce({
215
- json: async () => ({ success: true, data: [] }),
216
- });
217
-
218
- await client.listIssues({
219
- status: "open",
220
- priority: 1,
221
- search: "test",
222
- archived: false,
223
- limit: 10,
224
- });
225
-
226
- expect(mockFetch).toHaveBeenCalledWith(
227
- "http://localhost:3001/api/issues?status=open&priority=1&search=test&archived=false&limit=10",
228
- expect.anything()
229
- );
230
- });
231
- });
232
-
233
- describe("updateIssue", () => {
234
- it("should update an issue", async () => {
235
- const updatedIssue = {
236
- id: "i-abc123",
237
- title: "Updated Title",
238
- status: "in_progress",
239
- };
240
-
241
- mockFetch.mockResolvedValueOnce({
242
- json: async () => ({ success: true, data: updatedIssue }),
243
- });
244
-
245
- const result = await client.updateIssue("i-abc123", {
246
- title: "Updated Title",
247
- status: "in_progress",
248
- });
249
-
250
- expect(result).toEqual(updatedIssue);
251
- expect(mockFetch).toHaveBeenCalledWith(
252
- "http://localhost:3001/api/issues/i-abc123",
253
- expect.objectContaining({
254
- method: "PUT",
255
- body: JSON.stringify({
256
- title: "Updated Title",
257
- status: "in_progress",
258
- }),
259
- })
260
- );
261
- });
262
- });
263
-
264
- describe("getSpec", () => {
265
- it("should fetch a spec by ID", async () => {
266
- const mockSpec = {
267
- id: "s-abc123",
268
- uuid: "uuid-123",
269
- title: "Test Spec",
270
- content: "Test content",
271
- created_at: "2024-01-01T00:00:00Z",
272
- updated_at: "2024-01-01T00:00:00Z",
273
- };
274
-
275
- mockFetch.mockResolvedValueOnce({
276
- json: async () => ({ success: true, data: mockSpec }),
277
- });
278
-
279
- const spec = await client.getSpec("s-abc123");
280
-
281
- expect(spec).toEqual(mockSpec);
282
- });
283
-
284
- it("should return null when spec not found", async () => {
285
- mockFetch.mockResolvedValueOnce({
286
- json: async () => ({
287
- success: false,
288
- message: "Spec not found",
289
- }),
290
- });
291
-
292
- const spec = await client.getSpec("s-nonexistent");
293
-
294
- expect(spec).toBeNull();
295
- });
296
- });
297
-
298
- describe("listSpecs", () => {
299
- it("should list specs with filter", async () => {
300
- mockFetch.mockResolvedValueOnce({
301
- json: async () => ({ success: true, data: [] }),
302
- });
303
-
304
- await client.listSpecs({ search: "test", limit: 5 });
305
-
306
- expect(mockFetch).toHaveBeenCalledWith(
307
- "http://localhost:3001/api/specs?search=test&limit=5",
308
- expect.anything()
309
- );
310
- });
311
- });
312
- });
313
-
314
- describe("Relationship operations", () => {
315
- beforeEach(() => {
316
- client = new ServerClient({
317
- serverUrl: "http://localhost:3001",
318
- });
319
- });
320
-
321
- describe("createLink", () => {
322
- it("should create a relationship between issues", async () => {
323
- mockFetch.mockResolvedValueOnce({
324
- json: async () => ({ success: true, data: null }),
325
- });
326
-
327
- await client.createLink("i-source", "i-target", "blocks");
328
-
329
- expect(mockFetch).toHaveBeenCalledWith(
330
- "http://localhost:3001/api/relationships",
331
- expect.objectContaining({
332
- method: "POST",
333
- body: JSON.stringify({
334
- from_id: "i-source",
335
- from_type: "issue",
336
- to_id: "i-target",
337
- to_type: "issue",
338
- relationship_type: "blocks",
339
- }),
340
- })
341
- );
342
- });
343
-
344
- it("should handle spec-to-issue relationships", async () => {
345
- mockFetch.mockResolvedValueOnce({
346
- json: async () => ({ success: true, data: null }),
347
- });
348
-
349
- await client.createLink("i-issue", "s-spec", "implements");
350
-
351
- expect(mockFetch).toHaveBeenCalledWith(
352
- "http://localhost:3001/api/relationships",
353
- expect.objectContaining({
354
- body: JSON.stringify({
355
- from_id: "i-issue",
356
- from_type: "issue",
357
- to_id: "s-spec",
358
- to_type: "spec",
359
- relationship_type: "implements",
360
- }),
361
- })
362
- );
363
- });
364
- });
365
-
366
- describe("removeLink", () => {
367
- it("should remove a relationship", async () => {
368
- mockFetch.mockResolvedValueOnce({
369
- json: async () => ({ success: true, data: null }),
370
- });
371
-
372
- await client.removeLink("i-source", "i-target", "blocks");
373
-
374
- expect(mockFetch).toHaveBeenCalledWith(
375
- "http://localhost:3001/api/relationships",
376
- expect.objectContaining({
377
- method: "DELETE",
378
- body: JSON.stringify({
379
- from_id: "i-source",
380
- from_type: "issue",
381
- to_id: "i-target",
382
- to_type: "issue",
383
- relationship_type: "blocks",
384
- }),
385
- })
386
- );
387
- });
388
- });
389
-
390
- describe("getBlockers", () => {
391
- it("should fetch blocking issues", async () => {
392
- // First call: get relationships
393
- mockFetch.mockResolvedValueOnce({
394
- json: async () => ({
395
- success: true,
396
- data: [
397
- { from_id: "i-blocker1", from_type: "issue" },
398
- { from_id: "i-blocker2", from_type: "issue" },
399
- ],
400
- }),
401
- });
402
-
403
- // Subsequent calls: get issue details
404
- mockFetch.mockResolvedValueOnce({
405
- json: async () => ({
406
- success: true,
407
- data: { id: "i-blocker1", title: "Blocker 1" },
408
- }),
409
- });
410
- mockFetch.mockResolvedValueOnce({
411
- json: async () => ({
412
- success: true,
413
- data: { id: "i-blocker2", title: "Blocker 2" },
414
- }),
415
- });
416
-
417
- const blockers = await client.getBlockers("i-target");
418
-
419
- expect(blockers).toHaveLength(2);
420
- expect(blockers[0].id).toBe("i-blocker1");
421
- expect(blockers[1].id).toBe("i-blocker2");
422
- });
423
- });
424
-
425
- describe("getBlocking", () => {
426
- it("should fetch issues that this issue blocks", async () => {
427
- // First call: get relationships
428
- mockFetch.mockResolvedValueOnce({
429
- json: async () => ({
430
- success: true,
431
- data: [{ to_id: "i-blocked1", to_type: "issue" }],
432
- }),
433
- });
434
-
435
- // Get issue details
436
- mockFetch.mockResolvedValueOnce({
437
- json: async () => ({
438
- success: true,
439
- data: { id: "i-blocked1", title: "Blocked 1" },
440
- }),
441
- });
442
-
443
- const blocking = await client.getBlocking("i-source");
444
-
445
- expect(blocking).toHaveLength(1);
446
- expect(blocking[0].id).toBe("i-blocked1");
447
- });
448
- });
449
- });
450
-
451
- describe("Feedback operations", () => {
452
- beforeEach(() => {
453
- client = new ServerClient({
454
- serverUrl: "http://localhost:3001",
455
- });
456
- });
457
-
458
- it("should add feedback to a spec", async () => {
459
- mockFetch.mockResolvedValueOnce({
460
- json: async () => ({ success: true, data: null }),
461
- });
462
-
463
- await client.addFeedback("i-source", "s-target", {
464
- type: "suggestion",
465
- content: "This needs improvement",
466
- agent: "test-agent",
467
- anchor: { line: 10, text: "Some text" },
468
- });
469
-
470
- expect(mockFetch).toHaveBeenCalledWith(
471
- "http://localhost:3001/api/feedback",
472
- expect.objectContaining({
473
- method: "POST",
474
- body: JSON.stringify({
475
- from_id: "i-source",
476
- to_id: "s-target",
477
- feedback_type: "suggestion",
478
- content: "This needs improvement",
479
- agent: "test-agent",
480
- line: 10,
481
- text: "Some text",
482
- }),
483
- })
484
- );
485
- });
486
-
487
- it("should add anonymous feedback when fromIssueId is undefined", async () => {
488
- mockFetch.mockResolvedValueOnce({
489
- json: async () => ({ success: true, data: null }),
490
- });
491
-
492
- await client.addFeedback(undefined, "s-target", {
493
- type: "comment",
494
- content: "Just a comment",
495
- });
496
-
497
- expect(mockFetch).toHaveBeenCalledWith(
498
- "http://localhost:3001/api/feedback",
499
- expect.objectContaining({
500
- method: "POST",
501
- body: JSON.stringify({
502
- to_id: "s-target",
503
- feedback_type: "comment",
504
- content: "Just a comment",
505
- }),
506
- })
507
- );
508
- });
509
- });
510
-
511
- describe("Event subscriptions", () => {
512
- beforeEach(() => {
513
- client = new ServerClient({
514
- serverUrl: "http://localhost:3001",
515
- });
516
- });
517
-
518
- it("should subscribe to all issue changes", async () => {
519
- const callback = vi.fn();
520
- const unsubscribe = client.onIssueChange(callback);
521
-
522
- // Simulate WebSocket message
523
- const ws = MockWebSocket.getLatest()!;
524
- ws.simulateMessage({
525
- type: "issue",
526
- entityId: "i-abc123",
527
- action: "updated",
528
- data: { id: "i-abc123", title: "Updated" },
529
- });
530
-
531
- expect(callback).toHaveBeenCalledWith({
532
- type: "updated",
533
- issueId: "i-abc123",
534
- issue: { id: "i-abc123", title: "Updated" },
535
- });
536
-
537
- unsubscribe();
538
- });
539
-
540
- it("should subscribe to specific issue changes", async () => {
541
- const callback = vi.fn();
542
- const unsubscribe = client.onIssueChange("i-abc123", callback);
543
-
544
- const ws = MockWebSocket.getLatest()!;
545
-
546
- // Message for subscribed issue
547
- ws.simulateMessage({
548
- type: "issue",
549
- entityId: "i-abc123",
550
- action: "updated",
551
- });
552
-
553
- // Message for different issue
554
- ws.simulateMessage({
555
- type: "issue",
556
- entityId: "i-other",
557
- action: "updated",
558
- });
559
-
560
- expect(callback).toHaveBeenCalledTimes(1);
561
- expect(callback).toHaveBeenCalledWith(
562
- expect.objectContaining({ issueId: "i-abc123" })
563
- );
564
-
565
- unsubscribe();
566
- });
567
-
568
- it("should handle unsubscribe correctly", () => {
569
- const callback = vi.fn();
570
- const unsubscribe = client.onIssueChange(callback);
571
-
572
- unsubscribe();
573
-
574
- const ws = MockWebSocket.getLatest()!;
575
- ws.simulateMessage({
576
- type: "issue",
577
- entityId: "i-abc123",
578
- action: "updated",
579
- });
580
-
581
- expect(callback).not.toHaveBeenCalled();
582
- });
583
-
584
- it("should map WebSocket actions to event types", () => {
585
- const callback = vi.fn();
586
- client.onIssueChange(callback);
587
-
588
- const ws = MockWebSocket.getLatest()!;
589
-
590
- const actions = ["created", "updated", "deleted", "status_changed"];
591
- for (const action of actions) {
592
- ws.simulateMessage({
593
- type: "issue",
594
- entityId: "i-abc123",
595
- action,
596
- });
597
- }
598
-
599
- expect(callback).toHaveBeenCalledTimes(4);
600
- const calls = callback.mock.calls.map(
601
- (c: [IssueChangeEvent]) => c[0].type
602
- );
603
- expect(calls).toEqual(["created", "updated", "deleted", "status_changed"]);
604
- });
605
- });
606
-
607
- describe("WebSocket reconnection", () => {
608
- beforeEach(() => {
609
- client = new ServerClient({
610
- serverUrl: "http://localhost:3001",
611
- });
612
- });
613
-
614
- it("should schedule reconnect on disconnect", async () => {
615
- const ws = MockWebSocket.getLatest()!;
616
-
617
- // Simulate connection then disconnect
618
- await vi.advanceTimersByTimeAsync(10);
619
- expect(client.isReady()).toBe(true);
620
-
621
- ws.simulateClose();
622
- expect(client.isReady()).toBe(false);
623
-
624
- // Advance past reconnect delay
625
- await vi.advanceTimersByTimeAsync(1000);
626
-
627
- // Should have created a new WebSocket
628
- expect(MockWebSocket.instances).toHaveLength(2);
629
- });
630
-
631
- it("should use exponential backoff for reconnection", async () => {
632
- // Simulate multiple disconnections
633
- for (let i = 0; i < 3; i++) {
634
- const ws = MockWebSocket.getLatest()!;
635
- ws.simulateClose();
636
-
637
- // Expected delays: 1000, 2000, 4000 (exponential)
638
- const expectedDelay = 1000 * Math.pow(2, i);
639
- await vi.advanceTimersByTimeAsync(expectedDelay);
640
- }
641
-
642
- // Should have original + 3 reconnection attempts
643
- expect(MockWebSocket.instances).toHaveLength(4);
644
- });
645
- });
646
-
647
- describe("close", () => {
648
- it("should clean up resources", async () => {
649
- client = new ServerClient({
650
- serverUrl: "http://localhost:3001",
651
- });
652
-
653
- await vi.advanceTimersByTimeAsync(10);
654
- expect(client.isReady()).toBe(true);
655
-
656
- const callback = vi.fn();
657
- client.onIssueChange(callback);
658
-
659
- client.close();
660
-
661
- expect(client.isReady()).toBe(false);
662
-
663
- // Simulate message after close
664
- const ws = MockWebSocket.getLatest()!;
665
- ws.simulateMessage({
666
- type: "issue",
667
- entityId: "i-abc123",
668
- action: "updated",
669
- });
670
-
671
- // Callback should not be called after close
672
- expect(callback).not.toHaveBeenCalled();
673
- });
674
- });
675
- });