macro-agent 0.0.17 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/.sudocode/specs.jsonl +4 -0
  3. package/CLAUDE.md +16 -14
  4. package/README.md +11 -29
  5. package/dist/acp/macro-agent.d.ts +17 -0
  6. package/dist/acp/macro-agent.d.ts.map +1 -1
  7. package/dist/acp/macro-agent.js +183 -55
  8. package/dist/acp/macro-agent.js.map +1 -1
  9. package/dist/acp/types.d.ts +32 -1
  10. package/dist/acp/types.d.ts.map +1 -1
  11. package/dist/acp/types.js.map +1 -1
  12. package/dist/agent/agent-manager.d.ts +65 -1
  13. package/dist/agent/agent-manager.d.ts.map +1 -1
  14. package/dist/agent/agent-manager.js +464 -183
  15. package/dist/agent/agent-manager.js.map +1 -1
  16. package/dist/agent/types.d.ts +1 -1
  17. package/dist/agent/types.d.ts.map +1 -1
  18. package/dist/api/server.d.ts +3 -0
  19. package/dist/api/server.d.ts.map +1 -1
  20. package/dist/api/server.js +37 -6
  21. package/dist/api/server.js.map +1 -1
  22. package/dist/auth/index.d.ts +2 -0
  23. package/dist/auth/index.d.ts.map +1 -0
  24. package/dist/auth/index.js +2 -0
  25. package/dist/auth/index.js.map +1 -0
  26. package/dist/auth/token.d.ts +41 -0
  27. package/dist/auth/token.d.ts.map +1 -0
  28. package/dist/auth/token.js +73 -0
  29. package/dist/auth/token.js.map +1 -0
  30. package/dist/cli/acp.d.ts +2 -23
  31. package/dist/cli/acp.d.ts.map +1 -1
  32. package/dist/cli/acp.js +127 -61
  33. package/dist/cli/acp.js.map +1 -1
  34. package/dist/cli/index.js +147 -15
  35. package/dist/cli/index.js.map +1 -1
  36. package/dist/cli/mcp.d.ts +6 -0
  37. package/dist/cli/mcp.d.ts.map +1 -1
  38. package/dist/cli/mcp.js +268 -181
  39. package/dist/cli/mcp.js.map +1 -1
  40. package/dist/cli/parse-args.d.ts +20 -0
  41. package/dist/cli/parse-args.d.ts.map +1 -0
  42. package/dist/cli/parse-args.js +43 -0
  43. package/dist/cli/parse-args.js.map +1 -0
  44. package/dist/cli/stable-instance-id.d.ts +8 -0
  45. package/dist/cli/stable-instance-id.d.ts.map +1 -0
  46. package/dist/cli/stable-instance-id.js +14 -0
  47. package/dist/cli/stable-instance-id.js.map +1 -0
  48. package/dist/config/project-config.d.ts +74 -7
  49. package/dist/config/project-config.d.ts.map +1 -1
  50. package/dist/config/project-config.js +123 -20
  51. package/dist/config/project-config.js.map +1 -1
  52. package/dist/map/adapter/acp-over-map.d.ts +23 -0
  53. package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
  54. package/dist/map/adapter/acp-over-map.js +482 -55
  55. package/dist/map/adapter/acp-over-map.js.map +1 -1
  56. package/dist/map/adapter/connection-manager.d.ts.map +1 -1
  57. package/dist/map/adapter/connection-manager.js +3 -0
  58. package/dist/map/adapter/connection-manager.js.map +1 -1
  59. package/dist/map/adapter/event-log.d.ts +87 -0
  60. package/dist/map/adapter/event-log.d.ts.map +1 -0
  61. package/dist/map/adapter/event-log.js +122 -0
  62. package/dist/map/adapter/event-log.js.map +1 -0
  63. package/dist/map/adapter/event-translator.js +6 -6
  64. package/dist/map/adapter/event-translator.js.map +1 -1
  65. package/dist/map/adapter/extensions/agent-lifecycle.d.ts +82 -0
  66. package/dist/map/adapter/extensions/agent-lifecycle.d.ts.map +1 -0
  67. package/dist/map/adapter/extensions/agent-lifecycle.js +164 -0
  68. package/dist/map/adapter/extensions/agent-lifecycle.js.map +1 -0
  69. package/dist/map/adapter/extensions/index.d.ts +10 -1
  70. package/dist/map/adapter/extensions/index.d.ts.map +1 -1
  71. package/dist/map/adapter/extensions/index.js +34 -0
  72. package/dist/map/adapter/extensions/index.js.map +1 -1
  73. package/dist/map/adapter/extensions/mcp-bridge.d.ts +57 -0
  74. package/dist/map/adapter/extensions/mcp-bridge.d.ts.map +1 -0
  75. package/dist/map/adapter/extensions/mcp-bridge.js +745 -0
  76. package/dist/map/adapter/extensions/mcp-bridge.js.map +1 -0
  77. package/dist/map/adapter/extensions/rename.d.ts +29 -0
  78. package/dist/map/adapter/extensions/rename.d.ts.map +1 -0
  79. package/dist/map/adapter/extensions/rename.js +49 -0
  80. package/dist/map/adapter/extensions/rename.js.map +1 -0
  81. package/dist/map/adapter/extensions/task.d.ts.map +1 -1
  82. package/dist/map/adapter/extensions/task.js +10 -0
  83. package/dist/map/adapter/extensions/task.js.map +1 -1
  84. package/dist/map/adapter/extensions/update-metadata.d.ts +29 -0
  85. package/dist/map/adapter/extensions/update-metadata.d.ts.map +1 -0
  86. package/dist/map/adapter/extensions/update-metadata.js +67 -0
  87. package/dist/map/adapter/extensions/update-metadata.js.map +1 -0
  88. package/dist/map/adapter/index.d.ts +2 -1
  89. package/dist/map/adapter/index.d.ts.map +1 -1
  90. package/dist/map/adapter/index.js +8 -2
  91. package/dist/map/adapter/index.js.map +1 -1
  92. package/dist/map/adapter/interface.d.ts +2 -0
  93. package/dist/map/adapter/interface.d.ts.map +1 -1
  94. package/dist/map/adapter/map-adapter.d.ts +4 -0
  95. package/dist/map/adapter/map-adapter.d.ts.map +1 -1
  96. package/dist/map/adapter/map-adapter.js +302 -30
  97. package/dist/map/adapter/map-adapter.js.map +1 -1
  98. package/dist/map/adapter/subscription-manager.d.ts.map +1 -1
  99. package/dist/map/adapter/subscription-manager.js +5 -1
  100. package/dist/map/adapter/subscription-manager.js.map +1 -1
  101. package/dist/map/adapter/types.d.ts +2 -0
  102. package/dist/map/adapter/types.d.ts.map +1 -1
  103. package/dist/mcp/map-client.d.ts +39 -0
  104. package/dist/mcp/map-client.d.ts.map +1 -0
  105. package/dist/mcp/map-client.js +129 -0
  106. package/dist/mcp/map-client.js.map +1 -0
  107. package/dist/mcp/mcp-server.d.ts +14 -0
  108. package/dist/mcp/mcp-server.d.ts.map +1 -1
  109. package/dist/mcp/mcp-server.js +113 -85
  110. package/dist/mcp/mcp-server.js.map +1 -1
  111. package/dist/mcp/types.d.ts +9 -1
  112. package/dist/mcp/types.d.ts.map +1 -1
  113. package/dist/mcp/types.js.map +1 -1
  114. package/dist/metrics/metrics.js +1 -1
  115. package/dist/metrics/metrics.js.map +1 -1
  116. package/dist/roles/capabilities.d.ts +3 -1
  117. package/dist/roles/capabilities.d.ts.map +1 -1
  118. package/dist/roles/capabilities.js +17 -7
  119. package/dist/roles/capabilities.js.map +1 -1
  120. package/dist/roles/config-loader.d.ts +6 -6
  121. package/dist/roles/config-loader.d.ts.map +1 -1
  122. package/dist/roles/config-loader.js +6 -6
  123. package/dist/roles/config-loader.js.map +1 -1
  124. package/dist/roles/registry.d.ts +2 -2
  125. package/dist/roles/registry.js +2 -2
  126. package/dist/server/combined-server.d.ts +20 -0
  127. package/dist/server/combined-server.d.ts.map +1 -1
  128. package/dist/server/combined-server.js +107 -8
  129. package/dist/server/combined-server.js.map +1 -1
  130. package/dist/store/event-store.d.ts +7 -1
  131. package/dist/store/event-store.d.ts.map +1 -1
  132. package/dist/store/event-store.js +91 -8
  133. package/dist/store/event-store.js.map +1 -1
  134. package/dist/store/types/agents.d.ts +23 -0
  135. package/dist/store/types/agents.d.ts.map +1 -1
  136. package/dist/store/types/events.d.ts +1 -1
  137. package/dist/store/types/events.d.ts.map +1 -1
  138. package/dist/task/backend/index.d.ts +47 -29
  139. package/dist/task/backend/index.d.ts.map +1 -1
  140. package/dist/task/backend/index.js +109 -71
  141. package/dist/task/backend/index.js.map +1 -1
  142. package/dist/task/backend/memory.d.ts +1 -0
  143. package/dist/task/backend/memory.d.ts.map +1 -1
  144. package/dist/task/backend/memory.js +3 -0
  145. package/dist/task/backend/memory.js.map +1 -1
  146. package/dist/task/backend/opentasks/backend.d.ts +140 -0
  147. package/dist/task/backend/opentasks/backend.d.ts.map +1 -0
  148. package/dist/task/backend/opentasks/backend.js +1023 -0
  149. package/dist/task/backend/opentasks/backend.js.map +1 -0
  150. package/dist/task/backend/opentasks/client.d.ts +337 -0
  151. package/dist/task/backend/opentasks/client.d.ts.map +1 -0
  152. package/dist/task/backend/opentasks/client.js +225 -0
  153. package/dist/task/backend/opentasks/client.js.map +1 -0
  154. package/dist/task/backend/opentasks/daemon-manager.d.ts +89 -0
  155. package/dist/task/backend/opentasks/daemon-manager.d.ts.map +1 -0
  156. package/dist/task/backend/opentasks/daemon-manager.js +195 -0
  157. package/dist/task/backend/opentasks/daemon-manager.js.map +1 -0
  158. package/dist/task/backend/opentasks/index.d.ts +21 -0
  159. package/dist/task/backend/opentasks/index.d.ts.map +1 -0
  160. package/dist/task/backend/opentasks/index.js +21 -0
  161. package/dist/task/backend/opentasks/index.js.map +1 -0
  162. package/dist/task/backend/opentasks/mapping.d.ts +48 -0
  163. package/dist/task/backend/opentasks/mapping.d.ts.map +1 -0
  164. package/dist/task/backend/opentasks/mapping.js +77 -0
  165. package/dist/task/backend/opentasks/mapping.js.map +1 -0
  166. package/dist/task/backend/types.d.ts +33 -53
  167. package/dist/task/backend/types.d.ts.map +1 -1
  168. package/dist/task/backend/types.js +7 -11
  169. package/dist/task/backend/types.js.map +1 -1
  170. package/dist/task/backend/unified-tool-provider.d.ts +57 -0
  171. package/dist/task/backend/unified-tool-provider.d.ts.map +1 -0
  172. package/dist/task/backend/unified-tool-provider.js +623 -0
  173. package/dist/task/backend/unified-tool-provider.js.map +1 -0
  174. package/dist/teams/team-loader.d.ts +2 -2
  175. package/dist/teams/team-loader.js +3 -3
  176. package/dist/teams/team-loader.js.map +1 -1
  177. package/dist/teams/team-runtime.d.ts.map +1 -1
  178. package/dist/teams/team-runtime.js +2 -0
  179. package/dist/teams/team-runtime.js.map +1 -1
  180. package/docs/architecture.md +7 -6
  181. package/docs/configuration.md +26 -62
  182. package/docs/implementation-details.md +5 -5
  183. package/docs/implementation-summary.md +17 -17
  184. package/docs/plan-self-driving-support.md +4 -4
  185. package/docs/spec-self-driving-support.md +10 -10
  186. package/docs/team-templates.md +2 -2
  187. package/docs/teams.md +3 -3
  188. package/docs/troubleshooting.md +10 -11
  189. package/package.json +6 -4
  190. package/src/__tests__/e2e/agent-spawn-visibility.e2e.test.ts +761 -0
  191. package/src/__tests__/e2e/full-agent-conflict-resolution.e2e.test.ts +2 -2
  192. package/src/__tests__/e2e/mcp-thin-client-bridge.e2e.test.ts +304 -0
  193. package/src/__tests__/e2e/mcp-tools-available.e2e.test.ts +324 -0
  194. package/src/__tests__/e2e/multi-agent.e2e.test.ts +5 -5
  195. package/src/__tests__/e2e/spawn-session-streaming.e2e.test.ts +563 -0
  196. package/src/acp/__tests__/history.test.ts +8 -4
  197. package/src/acp/__tests__/integration.test.ts +56 -31
  198. package/src/acp/__tests__/macro-agent.test.ts +16 -7
  199. package/src/acp/macro-agent.ts +230 -62
  200. package/src/acp/types.ts +46 -1
  201. package/src/agent/__tests__/agent-manager.test.ts +228 -2
  202. package/src/agent/agent-manager.ts +714 -261
  203. package/src/agent/types.ts +3 -1
  204. package/src/api/server.ts +41 -7
  205. package/src/auth/__tests__/token.test.ts +100 -0
  206. package/src/auth/index.ts +1 -0
  207. package/src/auth/token.ts +82 -0
  208. package/src/cli/__tests__/acp.test.ts +1 -1
  209. package/src/cli/__tests__/stable-instance-id.test.ts +1 -1
  210. package/src/cli/acp.ts +130 -72
  211. package/src/cli/index.ts +120 -14
  212. package/src/cli/mcp.ts +311 -207
  213. package/src/cli/parse-args.ts +54 -0
  214. package/src/cli/stable-instance-id.ts +14 -0
  215. package/src/config/project-config.ts +190 -27
  216. package/src/lifecycle/__tests__/cascade-termination.test.ts +1 -1
  217. package/src/map/adapter/__tests__/acp-over-map-cancel.test.ts +820 -0
  218. package/src/map/adapter/__tests__/acp-over-map-getmodels.test.ts +355 -0
  219. package/src/map/adapter/__tests__/acp-over-map-history.test.ts +724 -2
  220. package/src/map/adapter/__tests__/acp-over-map-persistence.e2e.test.ts +1 -1
  221. package/src/map/adapter/__tests__/event-broadcast.test.ts +420 -0
  222. package/src/map/adapter/__tests__/event-log.test.ts +527 -0
  223. package/src/map/adapter/__tests__/event-translator.test.ts +3 -3
  224. package/src/map/adapter/__tests__/extensions.test.ts +408 -0
  225. package/src/map/adapter/__tests__/map-adapter.test.ts +99 -0
  226. package/src/map/adapter/__tests__/mcp-bridge.test.ts +1187 -0
  227. package/src/map/adapter/__tests__/multi-client-broadcast.test.ts +711 -0
  228. package/src/map/adapter/__tests__/websocket-integration.test.ts +218 -0
  229. package/src/map/adapter/acp-over-map.ts +777 -92
  230. package/src/map/adapter/connection-manager.ts +3 -0
  231. package/src/map/adapter/event-log.ts +208 -0
  232. package/src/map/adapter/event-translator.ts +6 -6
  233. package/src/map/adapter/extensions/agent-lifecycle.ts +267 -0
  234. package/src/map/adapter/extensions/index.ts +60 -0
  235. package/src/map/adapter/extensions/mcp-bridge.ts +995 -0
  236. package/src/map/adapter/extensions/task.ts +11 -0
  237. package/src/map/adapter/extensions/update-metadata.ts +126 -0
  238. package/src/map/adapter/index.ts +28 -0
  239. package/src/map/adapter/interface.ts +2 -0
  240. package/src/map/adapter/map-adapter.ts +373 -38
  241. package/src/map/adapter/subscription-manager.ts +5 -1
  242. package/src/map/adapter/types.ts +2 -0
  243. package/src/mcp/__tests__/map-client.test.ts +386 -0
  244. package/src/mcp/__tests__/mcp-server-thin-client.test.ts +368 -0
  245. package/src/mcp/__tests__/mcp-server.test.ts +100 -1
  246. package/src/mcp/map-client.ts +177 -0
  247. package/src/mcp/mcp-server.ts +191 -100
  248. package/src/mcp/types.ts +6 -1
  249. package/src/metrics/metrics.ts +1 -1
  250. package/src/monitor/__tests__/stale-agent-flow.integration.test.ts +1 -1
  251. package/src/roles/__tests__/config-loader.test.ts +7 -7
  252. package/src/roles/capabilities.ts +17 -7
  253. package/src/roles/config-loader.ts +6 -6
  254. package/src/roles/registry.ts +2 -2
  255. package/src/server/__tests__/combined-server.test.ts +94 -21
  256. package/src/server/combined-server.ts +189 -33
  257. package/src/steering/__tests__/steering-integration.test.ts +1 -1
  258. package/src/store/__tests__/event-store.test.ts +236 -1
  259. package/src/store/__tests__/instance.test.ts +3 -3
  260. package/src/store/event-store.ts +109 -8
  261. package/src/store/types/agents.ts +16 -0
  262. package/src/store/types/events.ts +1 -1
  263. package/src/task/backend/__tests__/create-task-backend.test.ts +225 -0
  264. package/src/task/backend/__tests__/e2e/unified-tool-provider-opentasks.e2e.test.ts +524 -0
  265. package/src/task/backend/__tests__/unified-tool-provider.test.ts +579 -0
  266. package/src/task/backend/index.ts +156 -106
  267. package/src/task/backend/memory.ts +4 -0
  268. package/src/task/backend/opentasks/__tests__/backend.test.ts +968 -0
  269. package/src/task/backend/opentasks/__tests__/daemon-manager.test.ts +406 -0
  270. package/src/task/backend/opentasks/__tests__/mapping.test.ts +84 -0
  271. package/src/task/backend/opentasks/__tests__/opentasks-backend.e2e.test.ts +1338 -0
  272. package/src/task/backend/opentasks/backend.ts +1323 -0
  273. package/src/task/backend/opentasks/client.ts +652 -0
  274. package/src/task/backend/opentasks/daemon-manager.ts +253 -0
  275. package/src/task/backend/opentasks/index.ts +69 -0
  276. package/src/task/backend/opentasks/mapping.ts +94 -0
  277. package/src/task/backend/types.ts +42 -66
  278. package/src/task/backend/unified-tool-provider.ts +779 -0
  279. package/src/teams/__tests__/cross-subsystem.integration.test.ts +1 -1
  280. package/src/teams/team-loader.ts +3 -3
  281. package/src/teams/team-runtime.ts +2 -0
  282. package/test_fixtures/README.md +2 -3
  283. package/test_fixtures/fixtures/index.ts +0 -3
  284. package/test_fixtures/fixtures/projects/project-with-specs.ts +7 -149
  285. package/test_fixtures/fixtures/repos/index.ts +1 -3
  286. package/test_fixtures/fixtures/repos/temp-repo-factory.ts +0 -116
  287. package/test_fixtures/fixtures/repos/types.ts +0 -11
  288. package/test_fixtures/harness/__tests__/fixtures.test.ts +10 -102
  289. package/test_fixtures/harness/__tests__/temp-repo-and-simulator.test.ts +0 -33
  290. package/test_fixtures/harness/simulator/agent-simulator.ts +4 -4
  291. package/vitest.config.ts +1 -1
  292. package/vitest.e2e.config.ts +1 -1
  293. package/vitest.setup.ts +1 -30
  294. package/.macro-agent/teams/self-driving/prompts/grinder.md +0 -27
  295. package/.macro-agent/teams/self-driving/prompts/judge.md +0 -27
  296. package/.macro-agent/teams/self-driving/prompts/planner.md +0 -33
  297. package/.macro-agent/teams/self-driving/roles/grinder.yaml +0 -17
  298. package/.macro-agent/teams/self-driving/roles/judge.yaml +0 -24
  299. package/.macro-agent/teams/self-driving/roles/planner.yaml +0 -18
  300. package/.macro-agent/teams/self-driving/team.yaml +0 -103
  301. package/.macro-agent/teams/structured/prompts/developer.md +0 -26
  302. package/.macro-agent/teams/structured/prompts/lead.md +0 -25
  303. package/.macro-agent/teams/structured/prompts/reviewer.md +0 -24
  304. package/.macro-agent/teams/structured/roles/developer.yaml +0 -12
  305. package/.macro-agent/teams/structured/roles/lead.yaml +0 -11
  306. package/.macro-agent/teams/structured/roles/reviewer.yaml +0 -19
  307. package/.macro-agent/teams/structured/team.yaml +0 -89
  308. package/docs/sudocode-integration.md +0 -383
  309. package/src/task/backend/__tests__/backend-parity.test.ts +0 -451
  310. package/src/task/backend/__tests__/tool-provider-edge-cases.test.ts +0 -430
  311. package/src/task/backend/__tests__/tool-provider.test.ts +0 -983
  312. package/src/task/backend/sudocode/__tests__/backend-edge-cases.test.ts +0 -575
  313. package/src/task/backend/sudocode/__tests__/backend.test.ts +0 -1194
  314. package/src/task/backend/sudocode/__tests__/client-integration.test.ts +0 -418
  315. package/src/task/backend/sudocode/__tests__/client.test.ts +0 -345
  316. package/src/task/backend/sudocode/__tests__/e2e/backend.e2e.test.ts +0 -753
  317. package/src/task/backend/sudocode/__tests__/e2e/server-client.e2e.test.ts +0 -680
  318. package/src/task/backend/sudocode/__tests__/e2e-workflow.test.ts +0 -666
  319. package/src/task/backend/sudocode/__tests__/integration/standalone-client.integration.test.ts +0 -396
  320. package/src/task/backend/sudocode/__tests__/integration/sudocode-cli.integration.test.ts +0 -328
  321. package/src/task/backend/sudocode/__tests__/integration/test-utils.ts +0 -175
  322. package/src/task/backend/sudocode/__tests__/mapping-edge-cases.test.ts +0 -265
  323. package/src/task/backend/sudocode/__tests__/server-client.test.ts +0 -675
  324. package/src/task/backend/sudocode/__tests__/sync-policy-edge-cases.test.ts +0 -521
  325. package/src/task/backend/sudocode/__tests__/sync-policy.test.ts +0 -519
  326. package/src/task/backend/sudocode/__tests__/tools.test.ts +0 -471
  327. package/src/task/backend/sudocode/backend.ts +0 -1237
  328. package/src/task/backend/sudocode/client.ts +0 -515
  329. package/src/task/backend/sudocode/index.ts +0 -120
  330. package/src/task/backend/sudocode/mapping.ts +0 -93
  331. package/src/task/backend/sudocode/server-client.ts +0 -522
  332. package/src/task/backend/sudocode/standalone-client.ts +0 -623
  333. package/src/task/backend/sudocode/sync-policy.ts +0 -387
  334. package/src/task/backend/sudocode/tools.ts +0 -896
  335. package/src/task/backend/tool-provider.ts +0 -506
  336. package/test_fixtures/fixtures/sudocode/index.ts +0 -29
  337. package/test_fixtures/fixtures/sudocode/issues.ts +0 -185
  338. package/test_fixtures/fixtures/sudocode/specs.ts +0 -159
@@ -0,0 +1,579 @@
1
+ /**
2
+ * Tests for UnifiedTaskToolProvider
3
+ *
4
+ * Covers:
5
+ * - Core CRUD tools (create_task, get_task, list_tasks, assign_task) with in-memory backend
6
+ * - OpenTasks graph tools (task, link, annotate) with mock client
7
+ * - Tool exclusion and conditional tool exposure
8
+ *
9
+ * @module task/backend/__tests__/unified-tool-provider.test
10
+ */
11
+
12
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
13
+ import { createEventStore, type EventStore } from "../../../store/event-store.js";
14
+ import { InMemoryTaskBackend, createInMemoryTaskBackend } from "../memory.js";
15
+ import {
16
+ UnifiedTaskToolProvider,
17
+ createUnifiedToolProvider,
18
+ } from "../unified-tool-provider.js";
19
+ import type { OpenTasksClient } from "../opentasks/client.js";
20
+ import type { MCPToolDefinition } from "../types.js";
21
+
22
+ // =============================================================================
23
+ // Helpers
24
+ // =============================================================================
25
+
26
+ const TEST_AGENT_ID = "agent_test123";
27
+ const getContext = () => ({ agent_id: TEST_AGENT_ID });
28
+
29
+ function findTool(
30
+ tools: MCPToolDefinition[],
31
+ name: string
32
+ ): MCPToolDefinition | undefined {
33
+ return tools.find((t) => t.name === name);
34
+ }
35
+
36
+ function createMockOpenTasksClient(): OpenTasksClient {
37
+ return {
38
+ createIssue: vi.fn().mockResolvedValue({ id: "i-abc1", title: "test", status: "open" }),
39
+ getIssue: vi.fn().mockResolvedValue({
40
+ id: "i-abc1",
41
+ type: "issue",
42
+ title: "test",
43
+ content: "body",
44
+ status: "open",
45
+ priority: 2,
46
+ tags: [],
47
+ assignee: null,
48
+ parent_id: null,
49
+ claimed_by: null,
50
+ created_at: "2026-01-01T00:00:00Z",
51
+ updated_at: "2026-01-01T00:00:00Z",
52
+ metadata: {},
53
+ }),
54
+ updateIssue: vi.fn().mockResolvedValue({ id: "i-abc1", title: "test", status: "open" }),
55
+ deleteIssue: vi.fn().mockResolvedValue(undefined),
56
+ listIssues: vi.fn().mockResolvedValue([]),
57
+ getReadyIssues: vi.fn().mockResolvedValue([]),
58
+ createEdge: vi.fn().mockResolvedValue({ id: "e-123", from_id: "i-1", to_id: "i-2", type: "blocks" }),
59
+ removeEdge: vi.fn().mockResolvedValue(undefined),
60
+ getBlockers: vi.fn().mockResolvedValue([]),
61
+ getBlocking: vi.fn().mockResolvedValue([]),
62
+ task: vi.fn().mockResolvedValue({ success: true, data: {} }),
63
+ taskTransition: vi.fn().mockResolvedValue({ success: true, data: { id: "i-1", status: "in_progress" } }),
64
+ taskReady: vi.fn().mockResolvedValue({ success: true, data: { type: "ready", items: [], total: 0 } }),
65
+ taskAssign: vi.fn().mockResolvedValue({ success: true, data: { id: "i-1", assignee: TEST_AGENT_ID } }),
66
+ taskValidActions: vi.fn().mockResolvedValue({ success: true, data: { id: "i-1", actions: ["start", "close"] } }),
67
+ listProviders: vi.fn().mockResolvedValue([]),
68
+ isConnected: vi.fn().mockReturnValue(true),
69
+ connect: vi.fn().mockResolvedValue(undefined),
70
+ disconnect: vi.fn().mockResolvedValue(undefined),
71
+ } as unknown as OpenTasksClient;
72
+ }
73
+
74
+ // =============================================================================
75
+ // Tests
76
+ // =============================================================================
77
+
78
+ describe("UnifiedTaskToolProvider", () => {
79
+ let eventStore: EventStore;
80
+ let backend: InMemoryTaskBackend;
81
+
82
+ beforeEach(async () => {
83
+ eventStore = await createEventStore({ inMemory: true });
84
+ backend = createInMemoryTaskBackend(eventStore);
85
+ });
86
+
87
+ afterEach(async () => {
88
+ await eventStore.close();
89
+ });
90
+
91
+ // ─────────────────────────────────────────────────────────────────────────────
92
+ // Tool Exposure
93
+ // ─────────────────────────────────────────────────────────────────────────────
94
+
95
+ describe("tool exposure", () => {
96
+ it("should expose 4 core tools when no OpenTasks client provided", () => {
97
+ const provider = new UnifiedTaskToolProvider(backend, getContext);
98
+ const tools = provider.getTools();
99
+
100
+ expect(tools).toHaveLength(4);
101
+ expect(tools.map((t) => t.name)).toEqual([
102
+ "create_task",
103
+ "get_task",
104
+ "list_tasks",
105
+ "assign_task",
106
+ ]);
107
+ });
108
+
109
+ it("should expose 8 tools when OpenTasks client is provided", () => {
110
+ const client = createMockOpenTasksClient();
111
+ const provider = new UnifiedTaskToolProvider(backend, getContext, client);
112
+ const tools = provider.getTools();
113
+
114
+ expect(tools).toHaveLength(8);
115
+ expect(tools.map((t) => t.name)).toEqual([
116
+ "create_task",
117
+ "get_task",
118
+ "list_tasks",
119
+ "assign_task",
120
+ "task",
121
+ "link",
122
+ "annotate",
123
+ "list_providers",
124
+ ]);
125
+ });
126
+
127
+ it("should exclude built-in create_task and get_task", () => {
128
+ const provider = new UnifiedTaskToolProvider(backend, getContext);
129
+ const excluded = provider.getExcludedTools();
130
+
131
+ expect(excluded).toEqual(["create_task", "get_task"]);
132
+ });
133
+ });
134
+
135
+ // ─────────────────────────────────────────────────────────────────────────────
136
+ // Factory
137
+ // ─────────────────────────────────────────────────────────────────────────────
138
+
139
+ describe("createUnifiedToolProvider", () => {
140
+ it("should create provider without client", () => {
141
+ const provider = createUnifiedToolProvider(backend, getContext);
142
+ expect(provider).toBeInstanceOf(UnifiedTaskToolProvider);
143
+ expect(provider.getTools()).toHaveLength(4);
144
+ });
145
+
146
+ it("should create provider with client", () => {
147
+ const client = createMockOpenTasksClient();
148
+ const provider = createUnifiedToolProvider(backend, getContext, client);
149
+ expect(provider).toBeInstanceOf(UnifiedTaskToolProvider);
150
+ expect(provider.getTools()).toHaveLength(8);
151
+ });
152
+ });
153
+
154
+ // ─────────────────────────────────────────────────────────────────────────────
155
+ // Core CRUD Tools (backed by in-memory backend)
156
+ // ─────────────────────────────────────────────────────────────────────────────
157
+
158
+ describe("create_task", () => {
159
+ it("should create a task via backend", async () => {
160
+ const provider = new UnifiedTaskToolProvider(backend, getContext);
161
+ const tool = findTool(provider.getTools(), "create_task")!;
162
+
163
+ const result = (await tool.handler({
164
+ description: "Test task",
165
+ })) as { task_id: string; status: string };
166
+
167
+ expect(result.task_id).toMatch(/^task_/);
168
+ expect(result.status).toBe("pending");
169
+ });
170
+
171
+ it("should pass parent_task", async () => {
172
+ const provider = new UnifiedTaskToolProvider(backend, getContext);
173
+ const tool = findTool(provider.getTools(), "create_task")!;
174
+
175
+ // Create parent first
176
+ const parent = await backend.create({
177
+ description: "Parent",
178
+ created_by: TEST_AGENT_ID,
179
+ });
180
+
181
+ const result = (await tool.handler({
182
+ description: "Child task",
183
+ parent_task: parent.id,
184
+ })) as { task_id: string };
185
+
186
+ const task = await backend.get(result.task_id);
187
+ expect(task?.parent_task).toBe(parent.id);
188
+ });
189
+ });
190
+
191
+ describe("get_task", () => {
192
+ it("should return task details", async () => {
193
+ const provider = new UnifiedTaskToolProvider(backend, getContext);
194
+ const tool = findTool(provider.getTools(), "get_task")!;
195
+
196
+ const task = await backend.create({
197
+ description: "Fetch me",
198
+ created_by: TEST_AGENT_ID,
199
+ });
200
+
201
+ const result = (await tool.handler({
202
+ task_id: task.id,
203
+ })) as { id: string; description: string; status: string };
204
+
205
+ expect(result.id).toBe(task.id);
206
+ expect(result.description).toBe("Fetch me");
207
+ expect(result.status).toBe("pending");
208
+ });
209
+
210
+ it("should throw for non-existent task", async () => {
211
+ const provider = new UnifiedTaskToolProvider(backend, getContext);
212
+ const tool = findTool(provider.getTools(), "get_task")!;
213
+
214
+ await expect(tool.handler({ task_id: "task_nonexistent" })).rejects.toThrow(
215
+ "Task not found"
216
+ );
217
+ });
218
+ });
219
+
220
+ describe("list_tasks", () => {
221
+ it("should list all tasks", async () => {
222
+ const provider = new UnifiedTaskToolProvider(backend, getContext);
223
+ const tool = findTool(provider.getTools(), "list_tasks")!;
224
+
225
+ await backend.create({ description: "Task 1", created_by: TEST_AGENT_ID });
226
+ await backend.create({ description: "Task 2", created_by: TEST_AGENT_ID });
227
+
228
+ const result = (await tool.handler({})) as {
229
+ tasks: unknown[];
230
+ total: number;
231
+ };
232
+
233
+ expect(result.total).toBe(2);
234
+ expect(result.tasks).toHaveLength(2);
235
+ });
236
+
237
+ it("should filter by status", async () => {
238
+ const provider = new UnifiedTaskToolProvider(backend, getContext);
239
+ const tool = findTool(provider.getTools(), "list_tasks")!;
240
+
241
+ const task = await backend.create({
242
+ description: "Task 1",
243
+ created_by: TEST_AGENT_ID,
244
+ });
245
+ await backend.assign(task.id, TEST_AGENT_ID);
246
+ await backend.start(task.id);
247
+
248
+ await backend.create({ description: "Task 2", created_by: TEST_AGENT_ID });
249
+
250
+ const result = (await tool.handler({ status: "in_progress" })) as {
251
+ tasks: unknown[];
252
+ total: number;
253
+ };
254
+
255
+ expect(result.total).toBe(1);
256
+ });
257
+ });
258
+
259
+ describe("assign_task", () => {
260
+ it("should assign task to calling agent by default", async () => {
261
+ const provider = new UnifiedTaskToolProvider(backend, getContext);
262
+ const tool = findTool(provider.getTools(), "assign_task")!;
263
+
264
+ const task = await backend.create({
265
+ description: "Assign me",
266
+ created_by: TEST_AGENT_ID,
267
+ });
268
+
269
+ const result = (await tool.handler({
270
+ task_id: task.id,
271
+ })) as { assigned_agent: string; assigned: boolean };
272
+
273
+ expect(result.assigned_agent).toBe(TEST_AGENT_ID);
274
+ expect(result.assigned).toBe(true);
275
+ });
276
+
277
+ it("should assign task to specified agent", async () => {
278
+ const provider = new UnifiedTaskToolProvider(backend, getContext);
279
+ const tool = findTool(provider.getTools(), "assign_task")!;
280
+
281
+ const task = await backend.create({
282
+ description: "Assign me",
283
+ created_by: TEST_AGENT_ID,
284
+ });
285
+
286
+ const result = (await tool.handler({
287
+ task_id: task.id,
288
+ agent_id: "agent_other",
289
+ })) as { assigned_agent: string };
290
+
291
+ expect(result.assigned_agent).toBe("agent_other");
292
+ });
293
+ });
294
+
295
+ // ─────────────────────────────────────────────────────────────────────────────
296
+ // OpenTasks Graph Tools (mock client)
297
+ // ─────────────────────────────────────────────────────────────────────────────
298
+
299
+ describe("task tool", () => {
300
+ it("should handle transition operation", async () => {
301
+ const client = createMockOpenTasksClient();
302
+ const provider = new UnifiedTaskToolProvider(backend, getContext, client);
303
+ const tool = findTool(provider.getTools(), "task")!;
304
+
305
+ const result = await tool.handler({
306
+ transition: { id: "i-abc1", action: "start" },
307
+ });
308
+
309
+ expect(client.taskTransition).toHaveBeenCalledWith("i-abc1", "start");
310
+ expect(result).toEqual({ id: "i-1", status: "in_progress" });
311
+ });
312
+
313
+ it("should handle ready operation", async () => {
314
+ const client = createMockOpenTasksClient();
315
+ const provider = new UnifiedTaskToolProvider(backend, getContext, client);
316
+ const tool = findTool(provider.getTools(), "task")!;
317
+
318
+ const result = await tool.handler({
319
+ ready: { limit: 10 },
320
+ });
321
+
322
+ expect(client.taskReady).toHaveBeenCalledWith({ limit: 10 });
323
+ expect(result).toEqual({ type: "ready", items: [], total: 0 });
324
+ });
325
+
326
+ it("should handle assign operation with default agent", async () => {
327
+ const client = createMockOpenTasksClient();
328
+ const provider = new UnifiedTaskToolProvider(backend, getContext, client);
329
+ const tool = findTool(provider.getTools(), "task")!;
330
+
331
+ await tool.handler({
332
+ assign: { id: "i-abc1" },
333
+ });
334
+
335
+ expect(client.taskAssign).toHaveBeenCalledWith("i-abc1", TEST_AGENT_ID);
336
+ });
337
+
338
+ it("should handle validActions operation", async () => {
339
+ const client = createMockOpenTasksClient();
340
+ const provider = new UnifiedTaskToolProvider(backend, getContext, client);
341
+ const tool = findTool(provider.getTools(), "task")!;
342
+
343
+ const result = await tool.handler({
344
+ validActions: { id: "i-abc1" },
345
+ });
346
+
347
+ expect(client.taskValidActions).toHaveBeenCalledWith("i-abc1");
348
+ expect(result).toEqual({ id: "i-1", actions: ["start", "close"] });
349
+ });
350
+
351
+ it("should throw when no operation specified", async () => {
352
+ const client = createMockOpenTasksClient();
353
+ const provider = new UnifiedTaskToolProvider(backend, getContext, client);
354
+ const tool = findTool(provider.getTools(), "task")!;
355
+
356
+ await expect(tool.handler({})).rejects.toThrow(
357
+ "Specify exactly one operation"
358
+ );
359
+ });
360
+
361
+ it("should throw when transition fails", async () => {
362
+ const client = createMockOpenTasksClient();
363
+ (client.taskTransition as ReturnType<typeof vi.fn>).mockResolvedValue({
364
+ success: false,
365
+ error: "Invalid transition",
366
+ });
367
+ const provider = new UnifiedTaskToolProvider(backend, getContext, client);
368
+ const tool = findTool(provider.getTools(), "task")!;
369
+
370
+ await expect(
371
+ tool.handler({ transition: { id: "i-1", action: "start" } })
372
+ ).rejects.toThrow("Invalid transition");
373
+ });
374
+
375
+ it("should call syncExternalTransition after successful transition", async () => {
376
+ const client = createMockOpenTasksClient();
377
+ const syncFn = vi.fn().mockResolvedValue(undefined);
378
+ const backendWithSync = {
379
+ ...backend,
380
+ syncExternalTransition: syncFn,
381
+ };
382
+ const provider = new UnifiedTaskToolProvider(backendWithSync, getContext, client);
383
+ const tool = findTool(provider.getTools(), "task")!;
384
+
385
+ await tool.handler({
386
+ transition: { id: "i-abc1", action: "complete" },
387
+ });
388
+
389
+ expect(syncFn).toHaveBeenCalledWith("i-abc1", "complete", TEST_AGENT_ID);
390
+ });
391
+
392
+ it("should call syncExternalTransition after successful assign", async () => {
393
+ const client = createMockOpenTasksClient();
394
+ const syncFn = vi.fn().mockResolvedValue(undefined);
395
+ const backendWithSync = {
396
+ ...backend,
397
+ syncExternalTransition: syncFn,
398
+ };
399
+ const provider = new UnifiedTaskToolProvider(backendWithSync, getContext, client);
400
+ const tool = findTool(provider.getTools(), "task")!;
401
+
402
+ await tool.handler({
403
+ assign: { id: "i-abc1" },
404
+ });
405
+
406
+ expect(syncFn).toHaveBeenCalledWith("i-abc1", "assign", TEST_AGENT_ID);
407
+ });
408
+
409
+ it("should not fail tool call if syncExternalTransition throws", async () => {
410
+ const client = createMockOpenTasksClient();
411
+ const syncFn = vi.fn().mockRejectedValue(new Error("sync error"));
412
+ const backendWithSync = {
413
+ ...backend,
414
+ syncExternalTransition: syncFn,
415
+ };
416
+ const provider = new UnifiedTaskToolProvider(backendWithSync, getContext, client);
417
+ const tool = findTool(provider.getTools(), "task")!;
418
+
419
+ // Should not throw even though sync fails
420
+ const result = await tool.handler({
421
+ transition: { id: "i-abc1", action: "start" },
422
+ });
423
+
424
+ expect(result).toBeDefined();
425
+ expect(syncFn).toHaveBeenCalled();
426
+ });
427
+ });
428
+
429
+ describe("link tool", () => {
430
+ it("should create an edge", async () => {
431
+ const client = createMockOpenTasksClient();
432
+ const provider = new UnifiedTaskToolProvider(backend, getContext, client);
433
+ const tool = findTool(provider.getTools(), "link")!;
434
+
435
+ const result = (await tool.handler({
436
+ from_id: "i-1",
437
+ to_id: "i-2",
438
+ type: "blocks",
439
+ })) as { edge_id: string; created: boolean };
440
+
441
+ expect(client.createEdge).toHaveBeenCalledWith("i-1", "i-2", "blocks");
442
+ expect(result.edge_id).toBe("e-123");
443
+ expect(result.created).toBe(true);
444
+ });
445
+
446
+ it("should remove an edge", async () => {
447
+ const client = createMockOpenTasksClient();
448
+ const provider = new UnifiedTaskToolProvider(backend, getContext, client);
449
+ const tool = findTool(provider.getTools(), "link")!;
450
+
451
+ const result = (await tool.handler({
452
+ from_id: "i-1",
453
+ to_id: "i-2",
454
+ type: "blocks",
455
+ remove: true,
456
+ })) as { removed: boolean };
457
+
458
+ expect(client.removeEdge).toHaveBeenCalledWith("i-1", "i-2", "blocks");
459
+ expect(result.removed).toBe(true);
460
+ });
461
+ });
462
+
463
+ describe("annotate tool", () => {
464
+ it("should create feedback", async () => {
465
+ const client = createMockOpenTasksClient();
466
+ const provider = new UnifiedTaskToolProvider(backend, getContext, client);
467
+ const tool = findTool(provider.getTools(), "annotate")!;
468
+
469
+ const result = (await tool.handler({
470
+ target_id: "i-abc1",
471
+ content: "This looks good",
472
+ feedback_type: "comment",
473
+ })) as { feedback_id: string; created: boolean };
474
+
475
+ expect(client.createIssue).toHaveBeenCalledWith(
476
+ expect.objectContaining({
477
+ title: "This looks good",
478
+ content: "This looks good",
479
+ metadata: expect.objectContaining({
480
+ _node_type: "feedback",
481
+ target_id: "i-abc1",
482
+ feedback_type: "comment",
483
+ _created_by_agent: TEST_AGENT_ID,
484
+ }),
485
+ })
486
+ );
487
+ expect(result.feedback_id).toBe("i-abc1");
488
+ expect(result.created).toBe(true);
489
+ });
490
+
491
+ it("should create discovered-from link when from_id provided", async () => {
492
+ const client = createMockOpenTasksClient();
493
+ const provider = new UnifiedTaskToolProvider(backend, getContext, client);
494
+ const tool = findTool(provider.getTools(), "annotate")!;
495
+
496
+ await tool.handler({
497
+ target_id: "i-target",
498
+ content: "Found an issue",
499
+ from_id: "i-source",
500
+ });
501
+
502
+ expect(client.createEdge).toHaveBeenCalledWith(
503
+ "i-source",
504
+ "i-target",
505
+ "discovered-from"
506
+ );
507
+ });
508
+
509
+ it("should resolve feedback", async () => {
510
+ const client = createMockOpenTasksClient();
511
+ const provider = new UnifiedTaskToolProvider(backend, getContext, client);
512
+ const tool = findTool(provider.getTools(), "annotate")!;
513
+
514
+ const result = (await tool.handler({
515
+ target_id: "i-abc1",
516
+ resolve: "f-123",
517
+ })) as { feedback_id: string; resolved: boolean };
518
+
519
+ expect(client.updateIssue).toHaveBeenCalledWith(
520
+ "f-123",
521
+ expect.objectContaining({
522
+ metadata: expect.objectContaining({ resolved: true }),
523
+ })
524
+ );
525
+ expect(result.resolved).toBe(true);
526
+ });
527
+
528
+ it("should dismiss feedback", async () => {
529
+ const client = createMockOpenTasksClient();
530
+ const provider = new UnifiedTaskToolProvider(backend, getContext, client);
531
+ const tool = findTool(provider.getTools(), "annotate")!;
532
+
533
+ const result = (await tool.handler({
534
+ target_id: "i-abc1",
535
+ dismiss: "f-123",
536
+ })) as { feedback_id: string; dismissed: boolean };
537
+
538
+ expect(client.updateIssue).toHaveBeenCalledWith(
539
+ "f-123",
540
+ expect.objectContaining({
541
+ metadata: expect.objectContaining({ dismissed: true }),
542
+ })
543
+ );
544
+ expect(result.dismissed).toBe(true);
545
+ });
546
+
547
+ it("should reopen feedback", async () => {
548
+ const client = createMockOpenTasksClient();
549
+ const provider = new UnifiedTaskToolProvider(backend, getContext, client);
550
+ const tool = findTool(provider.getTools(), "annotate")!;
551
+
552
+ const result = (await tool.handler({
553
+ target_id: "i-abc1",
554
+ reopen: "f-123",
555
+ })) as { feedback_id: string; reopened: boolean };
556
+
557
+ expect(client.updateIssue).toHaveBeenCalledWith(
558
+ "f-123",
559
+ expect.objectContaining({
560
+ metadata: expect.objectContaining({
561
+ resolved: false,
562
+ dismissed: false,
563
+ }),
564
+ })
565
+ );
566
+ expect(result.reopened).toBe(true);
567
+ });
568
+
569
+ it("should throw when no action specified", async () => {
570
+ const client = createMockOpenTasksClient();
571
+ const provider = new UnifiedTaskToolProvider(backend, getContext, client);
572
+ const tool = findTool(provider.getTools(), "annotate")!;
573
+
574
+ await expect(
575
+ tool.handler({ target_id: "i-abc1" })
576
+ ).rejects.toThrow("Must provide content");
577
+ });
578
+ });
579
+ });