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,666 +0,0 @@
1
- /**
2
- * End-to-End Workflow Tests
3
- *
4
- * Tests for complete task lifecycle with sudocode integration.
5
- * These tests verify the interaction between all components.
6
- *
7
- * @module task/backend/sudocode/__tests__/e2e-workflow.test
8
- * @see s-8472 Pluggable Task Backend Integration
9
- * @see s-1zcx Multi-Agent Orchestration Testing Strategy
10
- */
11
-
12
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
13
- import { createEventStore, type EventStore } from "../../../../store/event-store.js";
14
- import {
15
- SudocodeTaskBackend,
16
- createSudocodeTaskBackend,
17
- } from "../backend.js";
18
- import type { SudocodeClient, Issue, IssueChangeCallback } from "../client.js";
19
- import type { TaskChangeEvent, SyncEvent } from "../sync-policy.js";
20
-
21
- // Create a realistic mock client
22
- function createRealisticMockClient(): SudocodeClient & {
23
- _issues: Map<string, Issue>;
24
- _blockers: Map<string, Issue[]>;
25
- _blocking: Map<string, Issue[]>;
26
- _callbacks: IssueChangeCallback[];
27
- _triggerIssueChange: (event: Parameters<IssueChangeCallback>[0]) => void;
28
- _createIssue: (issue: Issue) => void;
29
- } {
30
- const issues = new Map<string, Issue>();
31
- const blockers = new Map<string, Issue[]>();
32
- const blocking = new Map<string, Issue[]>();
33
- const callbacks: IssueChangeCallback[] = [];
34
-
35
- return {
36
- _issues: issues,
37
- _blockers: blockers,
38
- _blocking: blocking,
39
- _callbacks: callbacks,
40
- _triggerIssueChange: (event) => {
41
- for (const cb of callbacks) {
42
- cb(event);
43
- }
44
- },
45
- _createIssue: (issue) => {
46
- issues.set(issue.id, issue);
47
- },
48
-
49
- getIssue: vi.fn(async (id: string) => issues.get(id) ?? null),
50
- listIssues: vi.fn(async () => Array.from(issues.values())),
51
- createIssue: vi.fn(async (data: Partial<Issue>) => {
52
- const id = data.id ?? `i-${Date.now()}`;
53
- const issue: Issue = {
54
- id,
55
- uuid: `uuid-${id}`,
56
- title: data.title ?? "New Issue",
57
- status: data.status ?? "open",
58
- priority: data.priority ?? 2,
59
- created_at: new Date().toISOString(),
60
- updated_at: new Date().toISOString(),
61
- ...data,
62
- } as Issue;
63
- issues.set(id, issue);
64
- return issue;
65
- }),
66
- updateIssue: vi.fn(async (id: string, updates: Partial<Issue>) => {
67
- const issue = issues.get(id);
68
- if (!issue) throw new Error(`Issue not found: ${id}`);
69
- const previousStatus = issue.status;
70
- Object.assign(issue, updates);
71
- issue.updated_at = new Date().toISOString();
72
-
73
- // Trigger status_changed if status changed
74
- if (updates.status && updates.status !== previousStatus) {
75
- for (const cb of callbacks) {
76
- cb({
77
- type: "status_changed",
78
- issueId: id,
79
- issue,
80
- previousIssue: { ...issue, status: previousStatus },
81
- });
82
- }
83
- }
84
-
85
- return issue;
86
- }),
87
- getReadyIssues: vi.fn(async () => {
88
- return Array.from(issues.values()).filter((i) => {
89
- const issueBlockers = blockers.get(i.id) ?? [];
90
- return (
91
- issueBlockers.every((b) => b.status === "closed") &&
92
- i.status !== "closed"
93
- );
94
- });
95
- }),
96
- getBlockers: vi.fn(async (id: string) => blockers.get(id) ?? []),
97
- getBlocking: vi.fn(async (id: string) => blocking.get(id) ?? []),
98
- createLink: vi.fn(async (from: string, to: string, type: string) => {
99
- if (type === "blocks") {
100
- const fromIssue = issues.get(from);
101
- if (!fromIssue) return;
102
- const existing = blockers.get(to) ?? [];
103
- if (!existing.some((b) => b.id === from)) {
104
- blockers.set(to, [...existing, fromIssue]);
105
- }
106
- const blockingList = blocking.get(from) ?? [];
107
- const toIssue = issues.get(to);
108
- if (toIssue && !blockingList.some((b) => b.id === to)) {
109
- blocking.set(from, [...blockingList, toIssue]);
110
- }
111
- }
112
- }),
113
- removeLink: vi.fn(async (from: string, to: string, type: string) => {
114
- if (type === "blocks") {
115
- const existing = blockers.get(to) ?? [];
116
- blockers.set(to, existing.filter((b) => b.id !== from));
117
- const blockingList = blocking.get(from) ?? [];
118
- blocking.set(from, blockingList.filter((b) => b.id !== to));
119
- }
120
- }),
121
- getSpec: vi.fn(async () => null),
122
- listSpecs: vi.fn(async () => []),
123
- addFeedback: vi.fn(async () => {}),
124
- onIssueChange: vi.fn((callback: IssueChangeCallback) => {
125
- callbacks.push(callback);
126
- return () => {
127
- const idx = callbacks.indexOf(callback);
128
- if (idx >= 0) callbacks.splice(idx, 1);
129
- };
130
- }),
131
- close: vi.fn(),
132
- isReady: vi.fn(() => true),
133
- } as unknown as SudocodeClient & {
134
- _issues: Map<string, Issue>;
135
- _blockers: Map<string, Issue[]>;
136
- _blocking: Map<string, Issue[]>;
137
- _callbacks: IssueChangeCallback[];
138
- _triggerIssueChange: (event: Parameters<IssueChangeCallback>[0]) => void;
139
- _createIssue: (issue: Issue) => void;
140
- };
141
- }
142
-
143
- describe("E2E Workflow", () => {
144
- let eventStore: EventStore;
145
- let backend: SudocodeTaskBackend;
146
- let mockClient: ReturnType<typeof createRealisticMockClient>;
147
- const testAgentId = "agent_test";
148
-
149
- beforeEach(async () => {
150
- eventStore = await createEventStore({ inMemory: true });
151
- mockClient = createRealisticMockClient();
152
- });
153
-
154
- afterEach(async () => {
155
- backend?.close();
156
- await eventStore.close();
157
- });
158
-
159
- describe("Task creation and issue binding", () => {
160
- beforeEach(() => {
161
- backend = createSudocodeTaskBackend(eventStore, mockClient, {
162
- syncStatus: true,
163
- });
164
- });
165
-
166
- it("should create a task bound to an existing issue", async () => {
167
- // Create an issue first
168
- mockClient._createIssue({
169
- id: "i-feature1",
170
- uuid: "uuid-1",
171
- title: "Implement feature 1",
172
- status: "open",
173
- priority: 1,
174
- created_at: new Date().toISOString(),
175
- updated_at: new Date().toISOString(),
176
- });
177
-
178
- // Create task bound to the issue
179
- const task = await backend.create({
180
- description: "Implement feature 1",
181
- created_by: testAgentId,
182
- external_id: "i-feature1",
183
- });
184
-
185
- expect(task.id).toBeDefined();
186
- expect(task.external_id).toBe("i-feature1");
187
- expect(backend.getIssueForTask(task.id)).toBe("i-feature1");
188
- expect(backend.getTasksByIssue("i-feature1")).toContain(task.id);
189
- });
190
-
191
- it("should fail to create task bound to non-existent issue", async () => {
192
- await expect(
193
- backend.create({
194
- description: "Test",
195
- created_by: testAgentId,
196
- external_id: "i-nonexistent",
197
- })
198
- ).rejects.toThrow("Issue not found");
199
- });
200
- });
201
-
202
- describe("Complete workflow: create -> assign -> start -> complete", () => {
203
- beforeEach(() => {
204
- mockClient._createIssue({
205
- id: "i-workflow",
206
- uuid: "uuid-workflow",
207
- title: "Workflow test issue",
208
- status: "open",
209
- priority: 2,
210
- created_at: new Date().toISOString(),
211
- updated_at: new Date().toISOString(),
212
- });
213
-
214
- backend = createSudocodeTaskBackend(eventStore, mockClient, {
215
- syncStatus: true,
216
- autoCloseIssues: true,
217
- });
218
- });
219
-
220
- it("should complete full task lifecycle", async () => {
221
- // Create task
222
- const task = await backend.create({
223
- description: "Complete workflow test",
224
- created_by: testAgentId,
225
- external_id: "i-workflow",
226
- });
227
- expect(task.status).toBe("pending");
228
-
229
- // Assign task
230
- await backend.assign(task.id, "worker-1");
231
- const afterAssign = await backend.get(task.id);
232
- expect(afterAssign?.status).toBe("assigned");
233
- expect(afterAssign?.assigned_agent).toBe("worker-1");
234
-
235
- // Start task
236
- await backend.start(task.id);
237
- const afterStart = await backend.get(task.id);
238
- expect(afterStart?.status).toBe("in_progress");
239
-
240
- // Complete task
241
- await backend.complete(task.id, {
242
- summary: "Work completed successfully",
243
- data: { result: "success" },
244
- });
245
- const afterComplete = await backend.get(task.id);
246
- expect(afterComplete?.status).toBe("completed");
247
-
248
- // Issue should have been closed (autoCloseIssues: true)
249
- expect(mockClient.updateIssue).toHaveBeenCalledWith("i-workflow", {
250
- status: "closed",
251
- });
252
- });
253
-
254
- it("should track status changes correctly", async () => {
255
- const statusHistory: string[] = [];
256
- backend.onTaskChange((event) => {
257
- if (event.task.status) {
258
- statusHistory.push(event.task.status);
259
- }
260
- });
261
-
262
- const task = await backend.create({
263
- description: "Track status",
264
- created_by: testAgentId,
265
- });
266
-
267
- await backend.assign(task.id, "worker-1");
268
- await backend.start(task.id);
269
- await backend.complete(task.id);
270
-
271
- // Should have recorded: pending -> assigned -> in_progress -> completed
272
- expect(statusHistory).toContain("pending");
273
- expect(statusHistory).toContain("assigned");
274
- expect(statusHistory).toContain("in_progress");
275
- expect(statusHistory).toContain("completed");
276
- });
277
- });
278
-
279
- describe("Blocker workflow", () => {
280
- beforeEach(() => {
281
- mockClient._createIssue({
282
- id: "i-blocker",
283
- uuid: "uuid-blocker",
284
- title: "Blocker issue",
285
- status: "open",
286
- priority: 1,
287
- created_at: new Date().toISOString(),
288
- updated_at: new Date().toISOString(),
289
- });
290
- mockClient._createIssue({
291
- id: "i-blocked",
292
- uuid: "uuid-blocked",
293
- title: "Blocked issue",
294
- status: "open",
295
- priority: 2,
296
- created_at: new Date().toISOString(),
297
- updated_at: new Date().toISOString(),
298
- });
299
-
300
- backend = createSudocodeTaskBackend(eventStore, mockClient, {
301
- syncStatus: true,
302
- });
303
- });
304
-
305
- it("should handle task blocked by another task", async () => {
306
- const blocker = await backend.create({
307
- description: "Blocker task",
308
- created_by: testAgentId,
309
- external_id: "i-blocker",
310
- });
311
- const blocked = await backend.create({
312
- description: "Blocked task",
313
- created_by: testAgentId,
314
- external_id: "i-blocked",
315
- });
316
-
317
- // Add blocker
318
- await backend.addBlocker(blocked.id, blocker.id);
319
-
320
- // Blocked task should be blocked
321
- const blockedTask = await backend.get(blocked.id);
322
- expect(blockedTask?.isBlocked).toBe(true);
323
-
324
- // Should not appear in ready list
325
- const ready = await backend.listReady();
326
- expect(ready.find((t) => t.id === blocked.id)).toBeUndefined();
327
- expect(ready.find((t) => t.id === blocker.id)).toBeDefined();
328
-
329
- // Complete the blocker
330
- await backend.start(blocker.id);
331
- await backend.complete(blocker.id);
332
-
333
- // Blocked task should now be unblocked
334
- const afterComplete = await backend.get(blocked.id);
335
- expect(afterComplete?.isBlocked).toBe(false);
336
-
337
- // Should now appear in ready list
338
- const readyAfter = await backend.listReady();
339
- expect(readyAfter.find((t) => t.id === blocked.id)).toBeDefined();
340
- });
341
-
342
- it("should sync blocker relationships to sudocode", async () => {
343
- const blocker = await backend.create({
344
- description: "Blocker",
345
- created_by: testAgentId,
346
- external_id: "i-blocker",
347
- });
348
- const blocked = await backend.create({
349
- description: "Blocked",
350
- created_by: testAgentId,
351
- external_id: "i-blocked",
352
- });
353
-
354
- await backend.addBlocker(blocked.id, blocker.id);
355
-
356
- // Should have created link in sudocode
357
- expect(mockClient.createLink).toHaveBeenCalledWith(
358
- "i-blocker",
359
- "i-blocked",
360
- "blocks"
361
- );
362
- });
363
- });
364
-
365
- describe("Subtask workflow", () => {
366
- beforeEach(() => {
367
- backend = createSudocodeTaskBackend(eventStore, mockClient, {
368
- syncStatus: false,
369
- });
370
- });
371
-
372
- it("should create and track subtasks", async () => {
373
- const parent = await backend.create({
374
- description: "Parent task",
375
- created_by: testAgentId,
376
- });
377
-
378
- const child1 = await backend.createSubtask(parent.id, {
379
- description: "Child 1",
380
- created_by: testAgentId,
381
- });
382
- const child2 = await backend.createSubtask(parent.id, {
383
- description: "Child 2",
384
- created_by: testAgentId,
385
- });
386
-
387
- expect(child1.parent_task).toBe(parent.id);
388
- expect(child2.parent_task).toBe(parent.id);
389
-
390
- // Get children
391
- const children = await backend.getChildren(parent.id);
392
- expect(children).toHaveLength(2);
393
-
394
- // Get subtask status
395
- const status = await backend.getSubtaskStatus(parent.id);
396
- expect(status.total).toBe(2);
397
- expect(status.pending).toBe(2);
398
- expect(status.completed).toBe(0);
399
- expect(status.allCompleted).toBe(false);
400
-
401
- // Complete one child
402
- await backend.start(child1.id);
403
- await backend.complete(child1.id);
404
-
405
- const afterOne = await backend.getSubtaskStatus(parent.id);
406
- expect(afterOne.completed).toBe(1);
407
- expect(afterOne.pending).toBe(1);
408
- expect(afterOne.allCompleted).toBe(false);
409
-
410
- // Complete second child
411
- await backend.start(child2.id);
412
- await backend.complete(child2.id);
413
-
414
- const afterAll = await backend.getSubtaskStatus(parent.id);
415
- expect(afterAll.completed).toBe(2);
416
- expect(afterAll.allCompleted).toBe(true);
417
- });
418
- });
419
-
420
- describe("Issue change propagation", () => {
421
- beforeEach(() => {
422
- mockClient._createIssue({
423
- id: "i-sync",
424
- uuid: "uuid-sync",
425
- title: "Sync test issue",
426
- status: "open",
427
- priority: 2,
428
- created_at: new Date().toISOString(),
429
- updated_at: new Date().toISOString(),
430
- });
431
-
432
- backend = createSudocodeTaskBackend(eventStore, mockClient, {
433
- syncStatus: true,
434
- });
435
- });
436
-
437
- it("should update task when issue status changes", async () => {
438
- const task = await backend.create({
439
- description: "Sync test",
440
- created_by: testAgentId,
441
- external_id: "i-sync",
442
- });
443
-
444
- // Initially pending
445
- expect((await backend.get(task.id))?.status).toBe("pending");
446
-
447
- // Simulate issue status change to in_progress
448
- mockClient._issues.get("i-sync")!.status = "in_progress";
449
- mockClient._triggerIssueChange({
450
- type: "status_changed",
451
- issueId: "i-sync",
452
- issue: mockClient._issues.get("i-sync")!,
453
- });
454
-
455
- // Wait for async handler
456
- await new Promise((resolve) => setTimeout(resolve, 10));
457
-
458
- // Task should be in_progress
459
- expect((await backend.get(task.id))?.status).toBe("in_progress");
460
- });
461
-
462
- it("should not overwrite completed task status", async () => {
463
- const task = await backend.create({
464
- description: "Completed task",
465
- created_by: testAgentId,
466
- external_id: "i-sync",
467
- });
468
-
469
- await backend.start(task.id);
470
- await backend.complete(task.id);
471
- expect((await backend.get(task.id))?.status).toBe("completed");
472
-
473
- // Simulate issue reopened
474
- mockClient._issues.get("i-sync")!.status = "open";
475
- mockClient._triggerIssueChange({
476
- type: "status_changed",
477
- issueId: "i-sync",
478
- issue: mockClient._issues.get("i-sync")!,
479
- });
480
-
481
- await new Promise((resolve) => setTimeout(resolve, 10));
482
-
483
- // Task should remain completed
484
- expect((await backend.get(task.id))?.status).toBe("completed");
485
- });
486
- });
487
-
488
- describe("Error handling in workflows", () => {
489
- beforeEach(() => {
490
- backend = createSudocodeTaskBackend(eventStore, mockClient, {
491
- syncStatus: false,
492
- });
493
- });
494
-
495
- it("should handle task failure correctly", async () => {
496
- const task = await backend.create({
497
- description: "Will fail",
498
- created_by: testAgentId,
499
- });
500
-
501
- await backend.start(task.id);
502
- await backend.fail(task.id, {
503
- code: "TEST_ERROR",
504
- message: "Test failure",
505
- details: { reason: "testing" },
506
- });
507
-
508
- const failed = await backend.get(task.id);
509
- expect(failed?.status).toBe("failed");
510
- expect((failed?.outputs as any)?.error?.code).toBe("TEST_ERROR");
511
- });
512
-
513
- it("should reject invalid status transitions", async () => {
514
- const task = await backend.create({
515
- description: "Test",
516
- created_by: testAgentId,
517
- });
518
-
519
- // Can't complete pending task directly
520
- await expect(backend.complete(task.id)).rejects.toThrow(
521
- "Invalid status transition"
522
- );
523
-
524
- // Start first
525
- await backend.start(task.id);
526
- await backend.complete(task.id);
527
-
528
- // Can't start completed task
529
- await expect(backend.start(task.id)).rejects.toThrow(
530
- "Invalid status transition"
531
- );
532
- });
533
- });
534
-
535
- describe("Multiple tasks per issue", () => {
536
- beforeEach(() => {
537
- mockClient._createIssue({
538
- id: "i-multi",
539
- uuid: "uuid-multi",
540
- title: "Multi-task issue",
541
- status: "open",
542
- priority: 2,
543
- created_at: new Date().toISOString(),
544
- updated_at: new Date().toISOString(),
545
- });
546
-
547
- backend = createSudocodeTaskBackend(eventStore, mockClient, {
548
- syncStatus: true,
549
- });
550
- });
551
-
552
- it("should handle multiple tasks bound to same issue", async () => {
553
- const task1 = await backend.create({
554
- description: "Task 1",
555
- created_by: testAgentId,
556
- external_id: "i-multi",
557
- });
558
- const task2 = await backend.create({
559
- description: "Task 2",
560
- created_by: testAgentId,
561
- external_id: "i-multi",
562
- });
563
-
564
- const tasksByIssue = backend.getTasksByIssue("i-multi");
565
- expect(tasksByIssue).toHaveLength(2);
566
- expect(tasksByIssue).toContain(task1.id);
567
- expect(tasksByIssue).toContain(task2.id);
568
- });
569
-
570
- it("should update all tasks when issue status changes", async () => {
571
- const task1 = await backend.create({
572
- description: "Task 1",
573
- created_by: testAgentId,
574
- external_id: "i-multi",
575
- });
576
- const task2 = await backend.create({
577
- description: "Task 2",
578
- created_by: testAgentId,
579
- external_id: "i-multi",
580
- });
581
-
582
- // Simulate issue status change
583
- mockClient._issues.get("i-multi")!.status = "in_progress";
584
- mockClient._triggerIssueChange({
585
- type: "status_changed",
586
- issueId: "i-multi",
587
- issue: mockClient._issues.get("i-multi")!,
588
- });
589
-
590
- await new Promise((resolve) => setTimeout(resolve, 10));
591
-
592
- expect((await backend.get(task1.id))?.status).toBe("in_progress");
593
- expect((await backend.get(task2.id))?.status).toBe("in_progress");
594
- });
595
- });
596
-
597
- describe("listReady with mixed tasks", () => {
598
- beforeEach(() => {
599
- mockClient._createIssue({
600
- id: "i-ready",
601
- uuid: "uuid-ready",
602
- title: "Ready issue",
603
- status: "open",
604
- priority: 2,
605
- created_at: new Date().toISOString(),
606
- updated_at: new Date().toISOString(),
607
- });
608
- mockClient._createIssue({
609
- id: "i-blocked-ext",
610
- uuid: "uuid-blocked-ext",
611
- title: "Externally blocked issue",
612
- status: "blocked",
613
- priority: 2,
614
- created_at: new Date().toISOString(),
615
- updated_at: new Date().toISOString(),
616
- });
617
-
618
- backend = createSudocodeTaskBackend(eventStore, mockClient, {
619
- syncStatus: false,
620
- });
621
- });
622
-
623
- it("should correctly filter ready tasks", async () => {
624
- // Unbound task (always ready if not blocked locally)
625
- const unboundReady = await backend.create({
626
- description: "Unbound ready",
627
- created_by: testAgentId,
628
- });
629
-
630
- // Bound to ready issue
631
- const boundReady = await backend.create({
632
- description: "Bound to ready issue",
633
- created_by: testAgentId,
634
- external_id: "i-ready",
635
- });
636
-
637
- // Bound to blocked issue
638
- const boundBlocked = await backend.create({
639
- description: "Bound to blocked issue",
640
- created_by: testAgentId,
641
- external_id: "i-blocked-ext",
642
- });
643
-
644
- // Locally blocked (by another task)
645
- const locallyBlocked = await backend.create({
646
- description: "Locally blocked",
647
- created_by: testAgentId,
648
- });
649
- const localBlocker = await backend.create({
650
- description: "Local blocker",
651
- created_by: testAgentId,
652
- });
653
- await backend.addBlocker(locallyBlocked.id, localBlocker.id);
654
-
655
- const ready = await backend.listReady();
656
-
657
- // Should include: unboundReady, boundReady, localBlocker
658
- // Should exclude: boundBlocked (blocked issue), locallyBlocked (blocked by task)
659
- expect(ready.find((t) => t.id === unboundReady.id)).toBeDefined();
660
- expect(ready.find((t) => t.id === boundReady.id)).toBeDefined();
661
- expect(ready.find((t) => t.id === localBlocker.id)).toBeDefined();
662
- expect(ready.find((t) => t.id === boundBlocked.id)).toBeUndefined();
663
- expect(ready.find((t) => t.id === locallyBlocked.id)).toBeUndefined();
664
- });
665
- });
666
- });