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