macro-agent 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/.sudocode/specs.jsonl +4 -0
  3. package/CLAUDE.md +16 -14
  4. package/README.md +11 -29
  5. package/dist/acp/macro-agent.d.ts +15 -0
  6. package/dist/acp/macro-agent.d.ts.map +1 -1
  7. package/dist/acp/macro-agent.js +131 -35
  8. package/dist/acp/macro-agent.js.map +1 -1
  9. package/dist/acp/types.d.ts +32 -1
  10. package/dist/acp/types.d.ts.map +1 -1
  11. package/dist/acp/types.js.map +1 -1
  12. package/dist/agent/agent-manager.d.ts +65 -1
  13. package/dist/agent/agent-manager.d.ts.map +1 -1
  14. package/dist/agent/agent-manager.js +464 -183
  15. package/dist/agent/agent-manager.js.map +1 -1
  16. package/dist/agent/types.d.ts +1 -1
  17. package/dist/agent/types.d.ts.map +1 -1
  18. package/dist/api/server.d.ts +3 -0
  19. package/dist/api/server.d.ts.map +1 -1
  20. package/dist/api/server.js +37 -6
  21. package/dist/api/server.js.map +1 -1
  22. package/dist/auth/index.d.ts +2 -0
  23. package/dist/auth/index.d.ts.map +1 -0
  24. package/dist/auth/index.js +2 -0
  25. package/dist/auth/index.js.map +1 -0
  26. package/dist/auth/token.d.ts +41 -0
  27. package/dist/auth/token.d.ts.map +1 -0
  28. package/dist/auth/token.js +73 -0
  29. package/dist/auth/token.js.map +1 -0
  30. package/dist/cli/acp.d.ts +2 -23
  31. package/dist/cli/acp.d.ts.map +1 -1
  32. package/dist/cli/acp.js +127 -61
  33. package/dist/cli/acp.js.map +1 -1
  34. package/dist/cli/index.js +147 -15
  35. package/dist/cli/index.js.map +1 -1
  36. package/dist/cli/mcp.d.ts +6 -0
  37. package/dist/cli/mcp.d.ts.map +1 -1
  38. package/dist/cli/mcp.js +268 -181
  39. package/dist/cli/mcp.js.map +1 -1
  40. package/dist/cli/parse-args.d.ts +20 -0
  41. package/dist/cli/parse-args.d.ts.map +1 -0
  42. package/dist/cli/parse-args.js +43 -0
  43. package/dist/cli/parse-args.js.map +1 -0
  44. package/dist/cli/stable-instance-id.d.ts +8 -0
  45. package/dist/cli/stable-instance-id.d.ts.map +1 -0
  46. package/dist/cli/stable-instance-id.js +14 -0
  47. package/dist/cli/stable-instance-id.js.map +1 -0
  48. package/dist/config/project-config.d.ts +74 -7
  49. package/dist/config/project-config.d.ts.map +1 -1
  50. package/dist/config/project-config.js +123 -20
  51. package/dist/config/project-config.js.map +1 -1
  52. package/dist/map/adapter/acp-over-map.d.ts +17 -0
  53. package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
  54. package/dist/map/adapter/acp-over-map.js +384 -23
  55. package/dist/map/adapter/acp-over-map.js.map +1 -1
  56. package/dist/map/adapter/connection-manager.d.ts.map +1 -1
  57. package/dist/map/adapter/connection-manager.js +3 -0
  58. package/dist/map/adapter/connection-manager.js.map +1 -1
  59. package/dist/map/adapter/event-log.d.ts +87 -0
  60. package/dist/map/adapter/event-log.d.ts.map +1 -0
  61. package/dist/map/adapter/event-log.js +122 -0
  62. package/dist/map/adapter/event-log.js.map +1 -0
  63. package/dist/map/adapter/event-translator.js +6 -6
  64. package/dist/map/adapter/event-translator.js.map +1 -1
  65. package/dist/map/adapter/extensions/agent-lifecycle.d.ts +82 -0
  66. package/dist/map/adapter/extensions/agent-lifecycle.d.ts.map +1 -0
  67. package/dist/map/adapter/extensions/agent-lifecycle.js +164 -0
  68. package/dist/map/adapter/extensions/agent-lifecycle.js.map +1 -0
  69. package/dist/map/adapter/extensions/index.d.ts +10 -1
  70. package/dist/map/adapter/extensions/index.d.ts.map +1 -1
  71. package/dist/map/adapter/extensions/index.js +34 -0
  72. package/dist/map/adapter/extensions/index.js.map +1 -1
  73. package/dist/map/adapter/extensions/mcp-bridge.d.ts +57 -0
  74. package/dist/map/adapter/extensions/mcp-bridge.d.ts.map +1 -0
  75. package/dist/map/adapter/extensions/mcp-bridge.js +745 -0
  76. package/dist/map/adapter/extensions/mcp-bridge.js.map +1 -0
  77. package/dist/map/adapter/extensions/rename.d.ts +29 -0
  78. package/dist/map/adapter/extensions/rename.d.ts.map +1 -0
  79. package/dist/map/adapter/extensions/rename.js +49 -0
  80. package/dist/map/adapter/extensions/rename.js.map +1 -0
  81. package/dist/map/adapter/extensions/task.d.ts.map +1 -1
  82. package/dist/map/adapter/extensions/task.js +10 -0
  83. package/dist/map/adapter/extensions/task.js.map +1 -1
  84. package/dist/map/adapter/extensions/update-metadata.d.ts +29 -0
  85. package/dist/map/adapter/extensions/update-metadata.d.ts.map +1 -0
  86. package/dist/map/adapter/extensions/update-metadata.js +67 -0
  87. package/dist/map/adapter/extensions/update-metadata.js.map +1 -0
  88. package/dist/map/adapter/index.d.ts +2 -1
  89. package/dist/map/adapter/index.d.ts.map +1 -1
  90. package/dist/map/adapter/index.js +8 -2
  91. package/dist/map/adapter/index.js.map +1 -1
  92. package/dist/map/adapter/interface.d.ts +2 -0
  93. package/dist/map/adapter/interface.d.ts.map +1 -1
  94. package/dist/map/adapter/map-adapter.d.ts +3 -0
  95. package/dist/map/adapter/map-adapter.d.ts.map +1 -1
  96. package/dist/map/adapter/map-adapter.js +258 -35
  97. package/dist/map/adapter/map-adapter.js.map +1 -1
  98. package/dist/map/adapter/subscription-manager.d.ts.map +1 -1
  99. package/dist/map/adapter/subscription-manager.js +5 -1
  100. package/dist/map/adapter/subscription-manager.js.map +1 -1
  101. package/dist/map/adapter/types.d.ts +2 -0
  102. package/dist/map/adapter/types.d.ts.map +1 -1
  103. package/dist/mcp/map-client.d.ts +39 -0
  104. package/dist/mcp/map-client.d.ts.map +1 -0
  105. package/dist/mcp/map-client.js +129 -0
  106. package/dist/mcp/map-client.js.map +1 -0
  107. package/dist/mcp/mcp-server.d.ts +14 -0
  108. package/dist/mcp/mcp-server.d.ts.map +1 -1
  109. package/dist/mcp/mcp-server.js +113 -85
  110. package/dist/mcp/mcp-server.js.map +1 -1
  111. package/dist/mcp/types.d.ts +9 -1
  112. package/dist/mcp/types.d.ts.map +1 -1
  113. package/dist/mcp/types.js.map +1 -1
  114. package/dist/metrics/metrics.js +1 -1
  115. package/dist/metrics/metrics.js.map +1 -1
  116. package/dist/roles/capabilities.d.ts +3 -1
  117. package/dist/roles/capabilities.d.ts.map +1 -1
  118. package/dist/roles/capabilities.js +17 -7
  119. package/dist/roles/capabilities.js.map +1 -1
  120. package/dist/roles/config-loader.d.ts +6 -6
  121. package/dist/roles/config-loader.d.ts.map +1 -1
  122. package/dist/roles/config-loader.js +6 -6
  123. package/dist/roles/config-loader.js.map +1 -1
  124. package/dist/roles/registry.d.ts +2 -2
  125. package/dist/roles/registry.js +2 -2
  126. package/dist/server/combined-server.d.ts +20 -0
  127. package/dist/server/combined-server.d.ts.map +1 -1
  128. package/dist/server/combined-server.js +107 -8
  129. package/dist/server/combined-server.js.map +1 -1
  130. package/dist/store/event-store.d.ts +2 -1
  131. package/dist/store/event-store.d.ts.map +1 -1
  132. package/dist/store/event-store.js +69 -20
  133. package/dist/store/event-store.js.map +1 -1
  134. package/dist/store/types/agents.d.ts +18 -0
  135. package/dist/store/types/agents.d.ts.map +1 -1
  136. package/dist/store/types/events.d.ts +1 -1
  137. package/dist/store/types/events.d.ts.map +1 -1
  138. package/dist/task/backend/index.d.ts +47 -29
  139. package/dist/task/backend/index.d.ts.map +1 -1
  140. package/dist/task/backend/index.js +109 -71
  141. package/dist/task/backend/index.js.map +1 -1
  142. package/dist/task/backend/memory.d.ts +1 -0
  143. package/dist/task/backend/memory.d.ts.map +1 -1
  144. package/dist/task/backend/memory.js +3 -0
  145. package/dist/task/backend/memory.js.map +1 -1
  146. package/dist/task/backend/opentasks/backend.d.ts +140 -0
  147. package/dist/task/backend/opentasks/backend.d.ts.map +1 -0
  148. package/dist/task/backend/opentasks/backend.js +1023 -0
  149. package/dist/task/backend/opentasks/backend.js.map +1 -0
  150. package/dist/task/backend/opentasks/client.d.ts +337 -0
  151. package/dist/task/backend/opentasks/client.d.ts.map +1 -0
  152. package/dist/task/backend/opentasks/client.js +225 -0
  153. package/dist/task/backend/opentasks/client.js.map +1 -0
  154. package/dist/task/backend/opentasks/daemon-manager.d.ts +89 -0
  155. package/dist/task/backend/opentasks/daemon-manager.d.ts.map +1 -0
  156. package/dist/task/backend/opentasks/daemon-manager.js +195 -0
  157. package/dist/task/backend/opentasks/daemon-manager.js.map +1 -0
  158. package/dist/task/backend/opentasks/index.d.ts +21 -0
  159. package/dist/task/backend/opentasks/index.d.ts.map +1 -0
  160. package/dist/task/backend/opentasks/index.js +21 -0
  161. package/dist/task/backend/opentasks/index.js.map +1 -0
  162. package/dist/task/backend/opentasks/mapping.d.ts +48 -0
  163. package/dist/task/backend/opentasks/mapping.d.ts.map +1 -0
  164. package/dist/task/backend/opentasks/mapping.js +77 -0
  165. package/dist/task/backend/opentasks/mapping.js.map +1 -0
  166. package/dist/task/backend/types.d.ts +33 -53
  167. package/dist/task/backend/types.d.ts.map +1 -1
  168. package/dist/task/backend/types.js +7 -11
  169. package/dist/task/backend/types.js.map +1 -1
  170. package/dist/task/backend/unified-tool-provider.d.ts +57 -0
  171. package/dist/task/backend/unified-tool-provider.d.ts.map +1 -0
  172. package/dist/task/backend/unified-tool-provider.js +623 -0
  173. package/dist/task/backend/unified-tool-provider.js.map +1 -0
  174. package/dist/teams/team-loader.d.ts +2 -2
  175. package/dist/teams/team-loader.js +3 -3
  176. package/dist/teams/team-loader.js.map +1 -1
  177. package/dist/teams/team-runtime.d.ts.map +1 -1
  178. package/dist/teams/team-runtime.js +2 -0
  179. package/dist/teams/team-runtime.js.map +1 -1
  180. package/docs/architecture.md +7 -6
  181. package/docs/configuration.md +26 -62
  182. package/docs/implementation-details.md +5 -5
  183. package/docs/implementation-summary.md +17 -17
  184. package/docs/plan-self-driving-support.md +4 -4
  185. package/docs/spec-self-driving-support.md +10 -10
  186. package/docs/team-templates.md +2 -2
  187. package/docs/teams.md +3 -3
  188. package/docs/troubleshooting.md +10 -11
  189. package/package.json +6 -4
  190. package/src/__tests__/e2e/agent-spawn-visibility.e2e.test.ts +761 -0
  191. package/src/__tests__/e2e/full-agent-conflict-resolution.e2e.test.ts +2 -2
  192. package/src/__tests__/e2e/mcp-thin-client-bridge.e2e.test.ts +304 -0
  193. package/src/__tests__/e2e/mcp-tools-available.e2e.test.ts +324 -0
  194. package/src/__tests__/e2e/multi-agent.e2e.test.ts +5 -5
  195. package/src/__tests__/e2e/spawn-session-streaming.e2e.test.ts +563 -0
  196. package/src/acp/__tests__/integration.test.ts +56 -31
  197. package/src/acp/__tests__/macro-agent.test.ts +16 -7
  198. package/src/acp/macro-agent.ts +170 -36
  199. package/src/acp/types.ts +46 -1
  200. package/src/agent/__tests__/agent-manager.test.ts +228 -2
  201. package/src/agent/agent-manager.ts +714 -261
  202. package/src/agent/types.ts +3 -1
  203. package/src/api/server.ts +41 -7
  204. package/src/auth/__tests__/token.test.ts +100 -0
  205. package/src/auth/index.ts +1 -0
  206. package/src/auth/token.ts +82 -0
  207. package/src/cli/__tests__/acp.test.ts +1 -1
  208. package/src/cli/__tests__/stable-instance-id.test.ts +1 -1
  209. package/src/cli/acp.ts +130 -72
  210. package/src/cli/index.ts +120 -14
  211. package/src/cli/mcp.ts +311 -207
  212. package/src/cli/parse-args.ts +54 -0
  213. package/src/cli/stable-instance-id.ts +14 -0
  214. package/src/config/project-config.ts +190 -27
  215. package/src/lifecycle/__tests__/cascade-termination.test.ts +1 -1
  216. package/src/map/adapter/__tests__/acp-over-map-cancel.test.ts +22 -4
  217. package/src/map/adapter/__tests__/acp-over-map-getmodels.test.ts +355 -0
  218. package/src/map/adapter/__tests__/acp-over-map-history.test.ts +263 -0
  219. package/src/map/adapter/__tests__/acp-over-map-persistence.e2e.test.ts +1 -1
  220. package/src/map/adapter/__tests__/event-broadcast.test.ts +420 -0
  221. package/src/map/adapter/__tests__/event-log.test.ts +527 -0
  222. package/src/map/adapter/__tests__/event-translator.test.ts +3 -3
  223. package/src/map/adapter/__tests__/extensions.test.ts +408 -0
  224. package/src/map/adapter/__tests__/map-adapter.test.ts +99 -0
  225. package/src/map/adapter/__tests__/mcp-bridge.test.ts +1187 -0
  226. package/src/map/adapter/__tests__/multi-client-broadcast.test.ts +711 -0
  227. package/src/map/adapter/__tests__/websocket-integration.test.ts +218 -0
  228. package/src/map/adapter/acp-over-map.ts +678 -66
  229. package/src/map/adapter/connection-manager.ts +3 -0
  230. package/src/map/adapter/event-log.ts +208 -0
  231. package/src/map/adapter/event-translator.ts +6 -6
  232. package/src/map/adapter/extensions/agent-lifecycle.ts +267 -0
  233. package/src/map/adapter/extensions/index.ts +60 -0
  234. package/src/map/adapter/extensions/mcp-bridge.ts +995 -0
  235. package/src/map/adapter/extensions/task.ts +11 -0
  236. package/src/map/adapter/extensions/update-metadata.ts +126 -0
  237. package/src/map/adapter/index.ts +28 -0
  238. package/src/map/adapter/interface.ts +2 -0
  239. package/src/map/adapter/map-adapter.ts +312 -47
  240. package/src/map/adapter/subscription-manager.ts +5 -1
  241. package/src/map/adapter/types.ts +2 -0
  242. package/src/mcp/__tests__/map-client.test.ts +386 -0
  243. package/src/mcp/__tests__/mcp-server-thin-client.test.ts +368 -0
  244. package/src/mcp/__tests__/mcp-server.test.ts +100 -1
  245. package/src/mcp/map-client.ts +177 -0
  246. package/src/mcp/mcp-server.ts +191 -100
  247. package/src/mcp/types.ts +6 -1
  248. package/src/metrics/metrics.ts +1 -1
  249. package/src/monitor/__tests__/stale-agent-flow.integration.test.ts +1 -1
  250. package/src/roles/__tests__/config-loader.test.ts +7 -7
  251. package/src/roles/capabilities.ts +17 -7
  252. package/src/roles/config-loader.ts +6 -6
  253. package/src/roles/registry.ts +2 -2
  254. package/src/server/__tests__/combined-server.test.ts +94 -21
  255. package/src/server/combined-server.ts +189 -33
  256. package/src/steering/__tests__/steering-integration.test.ts +1 -1
  257. package/src/store/__tests__/event-store.test.ts +196 -1
  258. package/src/store/__tests__/instance.test.ts +3 -3
  259. package/src/store/event-store.ts +80 -21
  260. package/src/store/types/agents.ts +15 -0
  261. package/src/store/types/events.ts +1 -1
  262. package/src/task/backend/__tests__/create-task-backend.test.ts +225 -0
  263. package/src/task/backend/__tests__/e2e/unified-tool-provider-opentasks.e2e.test.ts +524 -0
  264. package/src/task/backend/__tests__/unified-tool-provider.test.ts +579 -0
  265. package/src/task/backend/index.ts +156 -106
  266. package/src/task/backend/memory.ts +4 -0
  267. package/src/task/backend/opentasks/__tests__/backend.test.ts +968 -0
  268. package/src/task/backend/opentasks/__tests__/daemon-manager.test.ts +406 -0
  269. package/src/task/backend/opentasks/__tests__/mapping.test.ts +84 -0
  270. package/src/task/backend/opentasks/__tests__/opentasks-backend.e2e.test.ts +1338 -0
  271. package/src/task/backend/opentasks/backend.ts +1323 -0
  272. package/src/task/backend/opentasks/client.ts +652 -0
  273. package/src/task/backend/opentasks/daemon-manager.ts +253 -0
  274. package/src/task/backend/opentasks/index.ts +69 -0
  275. package/src/task/backend/opentasks/mapping.ts +94 -0
  276. package/src/task/backend/types.ts +42 -66
  277. package/src/task/backend/unified-tool-provider.ts +779 -0
  278. package/src/teams/__tests__/cross-subsystem.integration.test.ts +1 -1
  279. package/src/teams/team-loader.ts +3 -3
  280. package/src/teams/team-runtime.ts +2 -0
  281. package/test_fixtures/README.md +2 -3
  282. package/test_fixtures/fixtures/index.ts +0 -3
  283. package/test_fixtures/fixtures/projects/project-with-specs.ts +7 -149
  284. package/test_fixtures/fixtures/repos/index.ts +1 -3
  285. package/test_fixtures/fixtures/repos/temp-repo-factory.ts +0 -116
  286. package/test_fixtures/fixtures/repos/types.ts +0 -11
  287. package/test_fixtures/harness/__tests__/fixtures.test.ts +10 -102
  288. package/test_fixtures/harness/__tests__/temp-repo-and-simulator.test.ts +0 -33
  289. package/test_fixtures/harness/simulator/agent-simulator.ts +4 -4
  290. package/vitest.config.ts +1 -1
  291. package/vitest.e2e.config.ts +1 -1
  292. package/vitest.setup.ts +1 -30
  293. package/.macro-agent/teams/self-driving/prompts/grinder.md +0 -27
  294. package/.macro-agent/teams/self-driving/prompts/judge.md +0 -27
  295. package/.macro-agent/teams/self-driving/prompts/planner.md +0 -33
  296. package/.macro-agent/teams/self-driving/roles/grinder.yaml +0 -17
  297. package/.macro-agent/teams/self-driving/roles/judge.yaml +0 -24
  298. package/.macro-agent/teams/self-driving/roles/planner.yaml +0 -18
  299. package/.macro-agent/teams/self-driving/team.yaml +0 -103
  300. package/.macro-agent/teams/structured/prompts/developer.md +0 -26
  301. package/.macro-agent/teams/structured/prompts/lead.md +0 -25
  302. package/.macro-agent/teams/structured/prompts/reviewer.md +0 -24
  303. package/.macro-agent/teams/structured/roles/developer.yaml +0 -12
  304. package/.macro-agent/teams/structured/roles/lead.yaml +0 -11
  305. package/.macro-agent/teams/structured/roles/reviewer.yaml +0 -19
  306. package/.macro-agent/teams/structured/team.yaml +0 -89
  307. package/docs/sudocode-integration.md +0 -383
  308. package/src/task/backend/__tests__/backend-parity.test.ts +0 -451
  309. package/src/task/backend/__tests__/tool-provider-edge-cases.test.ts +0 -430
  310. package/src/task/backend/__tests__/tool-provider.test.ts +0 -983
  311. package/src/task/backend/sudocode/__tests__/backend-edge-cases.test.ts +0 -575
  312. package/src/task/backend/sudocode/__tests__/backend.test.ts +0 -1194
  313. package/src/task/backend/sudocode/__tests__/client-integration.test.ts +0 -418
  314. package/src/task/backend/sudocode/__tests__/client.test.ts +0 -345
  315. package/src/task/backend/sudocode/__tests__/e2e/backend.e2e.test.ts +0 -753
  316. package/src/task/backend/sudocode/__tests__/e2e/server-client.e2e.test.ts +0 -680
  317. package/src/task/backend/sudocode/__tests__/e2e-workflow.test.ts +0 -666
  318. package/src/task/backend/sudocode/__tests__/integration/standalone-client.integration.test.ts +0 -396
  319. package/src/task/backend/sudocode/__tests__/integration/sudocode-cli.integration.test.ts +0 -328
  320. package/src/task/backend/sudocode/__tests__/integration/test-utils.ts +0 -175
  321. package/src/task/backend/sudocode/__tests__/mapping-edge-cases.test.ts +0 -265
  322. package/src/task/backend/sudocode/__tests__/server-client.test.ts +0 -675
  323. package/src/task/backend/sudocode/__tests__/sync-policy-edge-cases.test.ts +0 -521
  324. package/src/task/backend/sudocode/__tests__/sync-policy.test.ts +0 -519
  325. package/src/task/backend/sudocode/__tests__/tools.test.ts +0 -471
  326. package/src/task/backend/sudocode/backend.ts +0 -1237
  327. package/src/task/backend/sudocode/client.ts +0 -515
  328. package/src/task/backend/sudocode/index.ts +0 -120
  329. package/src/task/backend/sudocode/mapping.ts +0 -93
  330. package/src/task/backend/sudocode/server-client.ts +0 -522
  331. package/src/task/backend/sudocode/standalone-client.ts +0 -623
  332. package/src/task/backend/sudocode/sync-policy.ts +0 -387
  333. package/src/task/backend/sudocode/tools.ts +0 -896
  334. package/src/task/backend/tool-provider.ts +0 -506
  335. package/test_fixtures/fixtures/sudocode/index.ts +0 -29
  336. package/test_fixtures/fixtures/sudocode/issues.ts +0 -185
  337. package/test_fixtures/fixtures/sudocode/specs.ts +0 -159
@@ -0,0 +1,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
+ });