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,779 @@
1
+ /**
2
+ * Unified Task Tool Provider
3
+ *
4
+ * Single tool provider for all task backends. Provides:
5
+ * - Core CRUD tools (always available): create_task, get_task, list_tasks, assign_task
6
+ * - OpenTasks graph tools (when client available): task, link, annotate
7
+ *
8
+ * Replaces the separate InMemoryTaskToolProvider and OpenTasksTaskToolProvider.
9
+ *
10
+ * @module task/backend/unified-tool-provider
11
+ */
12
+
13
+ import type { AgentId } from "../../store/types/index.js";
14
+ import type {
15
+ TaskBackend,
16
+ TaskToolProvider,
17
+ MCPToolDefinition,
18
+ TaskFilter,
19
+ TaskStatus,
20
+ } from "./types.js";
21
+ import type { OpenTasksClient } from "./opentasks/client.js";
22
+
23
+ // =============================================================================
24
+ // Types
25
+ // =============================================================================
26
+
27
+ /**
28
+ * Context needed for tool execution
29
+ */
30
+ export interface ToolContext {
31
+ /** The agent making the tool call */
32
+ agent_id: AgentId;
33
+ }
34
+
35
+ /**
36
+ * Factory function type for getting context
37
+ */
38
+ export type GetToolContext = () => ToolContext;
39
+
40
+ // =============================================================================
41
+ // UnifiedTaskToolProvider
42
+ // =============================================================================
43
+
44
+ /**
45
+ * UnifiedTaskToolProvider
46
+ *
47
+ * Provides MCP tools for task operations, backed by:
48
+ * - TaskBackend for core CRUD (create_task, get_task, list_tasks, assign_task)
49
+ * - OpenTasksClient (optional) for graph operations (task, link, annotate)
50
+ */
51
+ export class UnifiedTaskToolProvider implements TaskToolProvider {
52
+ constructor(
53
+ private readonly backend: TaskBackend,
54
+ private readonly getContext: GetToolContext,
55
+ private readonly openTasksClient?: OpenTasksClient
56
+ ) {}
57
+
58
+ getTools(): MCPToolDefinition[] {
59
+ const tools: MCPToolDefinition[] = [
60
+ this.createTaskTool(),
61
+ this.getTaskTool(),
62
+ this.listTasksTool(),
63
+ this.assignTaskTool(),
64
+ ];
65
+
66
+ if (this.openTasksClient) {
67
+ tools.push(
68
+ this.taskTool(),
69
+ this.linkTool(),
70
+ this.annotateTool(),
71
+ this.listProvidersTool()
72
+ );
73
+ }
74
+
75
+ return tools;
76
+ }
77
+
78
+ /**
79
+ * Exclude the built-in create_task and get_task from mcp-server.ts
80
+ * since this provider replaces them.
81
+ */
82
+ getExcludedTools(): string[] {
83
+ return ["create_task", "get_task"];
84
+ }
85
+
86
+ // ─────────────────────────────────────────────────────────────────────────────
87
+ // Core CRUD Tools (backed by TaskBackend)
88
+ // ─────────────────────────────────────────────────────────────────────────────
89
+
90
+ private createTaskTool(): MCPToolDefinition {
91
+ return {
92
+ name: "create_task",
93
+ description: "Create a new task",
94
+ schema: {
95
+ type: "object",
96
+ properties: {
97
+ description: {
98
+ type: "string",
99
+ description: "Task description",
100
+ },
101
+ parent_task: {
102
+ type: "string",
103
+ description: "Parent task ID for subtasks",
104
+ },
105
+ tags: {
106
+ type: "array",
107
+ items: { type: "string" },
108
+ description: "Tags for categorization",
109
+ },
110
+ },
111
+ required: ["description"],
112
+ },
113
+ handler: async (params: unknown) => {
114
+ const args = params as {
115
+ description: string;
116
+ parent_task?: string;
117
+ tags?: string[];
118
+ };
119
+ const context = this.getContext();
120
+
121
+ const task = await this.backend.create({
122
+ description: args.description,
123
+ created_by: context.agent_id,
124
+ parent_task: args.parent_task,
125
+ tags: args.tags,
126
+ });
127
+
128
+ return {
129
+ task_id: task.id,
130
+ status: task.status,
131
+ external_id: task.external_id,
132
+ };
133
+ },
134
+ };
135
+ }
136
+
137
+ private getTaskTool(): MCPToolDefinition {
138
+ return {
139
+ name: "get_task",
140
+ description: "Get details of a specific task",
141
+ schema: {
142
+ type: "object",
143
+ properties: {
144
+ task_id: {
145
+ type: "string",
146
+ description: "Task ID to look up",
147
+ },
148
+ },
149
+ required: ["task_id"],
150
+ },
151
+ handler: async (params: unknown) => {
152
+ const args = params as { task_id: string };
153
+
154
+ const task = await this.backend.get(args.task_id);
155
+ if (!task) {
156
+ throw new Error(`Task not found: ${args.task_id}`);
157
+ }
158
+
159
+ return {
160
+ id: task.id,
161
+ description: task.description,
162
+ status: task.status,
163
+ isBlocked: task.isBlocked,
164
+ external_id: task.external_id,
165
+ source_location: task.source_location,
166
+ assigned_agent: task.assigned_agent,
167
+ parent_task: task.parent_task,
168
+ blockers: task.blockers ?? [],
169
+ created_at: task.created_at,
170
+ started_at: task.started_at,
171
+ completed_at: task.completed_at,
172
+ outputs: task.outputs,
173
+ artifacts: task.artifacts,
174
+ };
175
+ },
176
+ };
177
+ }
178
+
179
+ private listTasksTool(): MCPToolDefinition {
180
+ const client = this.openTasksClient;
181
+ return {
182
+ name: "list_tasks",
183
+ description:
184
+ "List tasks with optional filtering. Use federated=true to include " +
185
+ "tasks from connected project locations (requires opentasks backend).",
186
+ schema: {
187
+ type: "object",
188
+ properties: {
189
+ status: {
190
+ type: "string",
191
+ enum: ["pending", "assigned", "in_progress", "completed", "failed"],
192
+ description: "Filter by task status",
193
+ },
194
+ assigned_agent: {
195
+ type: "string",
196
+ description: "Filter by assigned agent",
197
+ },
198
+ parent_task: {
199
+ type: "string",
200
+ description: "Filter by parent task",
201
+ },
202
+ root_only: {
203
+ type: "boolean",
204
+ description: "Only return root tasks (no parent)",
205
+ },
206
+ include_blocked: {
207
+ type: "boolean",
208
+ description: "Include blocked tasks (default: true)",
209
+ },
210
+ federated: {
211
+ type: "boolean",
212
+ description:
213
+ "Include tasks from connected project locations (default: false). " +
214
+ "Queries the opentasks daemon for ready tasks across all connected projects.",
215
+ },
216
+ },
217
+ },
218
+ handler: async (params: unknown) => {
219
+ const args = params as {
220
+ status?: TaskStatus;
221
+ assigned_agent?: string;
222
+ parent_task?: string;
223
+ root_only?: boolean;
224
+ include_blocked?: boolean;
225
+ federated?: boolean;
226
+ };
227
+
228
+ const filter: TaskFilter = {};
229
+ if (args.status) filter.status = args.status;
230
+ if (args.assigned_agent) filter.assigned_agent = args.assigned_agent;
231
+ if (args.parent_task) filter.parent_task = args.parent_task;
232
+ if (args.root_only) filter.rootTasksOnly = true;
233
+ if (args.include_blocked !== undefined)
234
+ filter.includeBlocked = args.include_blocked;
235
+
236
+ // Local tasks from EventStore
237
+ const localTasks = await this.backend.list(filter);
238
+ const localTaskItems = localTasks.map((t) => ({
239
+ id: t.id,
240
+ description: t.description,
241
+ status: t.status,
242
+ isBlocked: t.isBlocked,
243
+ external_id: t.external_id,
244
+ assigned_agent: t.assigned_agent,
245
+ parent_task: t.parent_task,
246
+ source_location: t.source_location,
247
+ }));
248
+
249
+ // If federated requested and client available, also query daemon
250
+ if (args.federated && client) {
251
+ try {
252
+ const result = await client.taskReady({
253
+ tags: filter.tags,
254
+ assignee: args.assigned_agent,
255
+ });
256
+ if (result.success && result.data) {
257
+ const readyData = result.data as { type: string; items: Array<{ id: string; type: string; title: string; status?: string; priority?: number; archived: boolean }>; total: number };
258
+ // Collect external IDs we already know about
259
+ const knownExternalIds = new Set(
260
+ localTaskItems
261
+ .map((t) => t.external_id)
262
+ .filter(Boolean)
263
+ );
264
+ // Add federated items not already in local list
265
+ for (const item of readyData.items ?? []) {
266
+ if (!knownExternalIds.has(item.id)) {
267
+ localTaskItems.push({
268
+ id: item.id,
269
+ description: item.title,
270
+ status: (item.status ?? "pending") as TaskStatus,
271
+ isBlocked: false,
272
+ external_id: item.id,
273
+ assigned_agent: undefined,
274
+ parent_task: undefined,
275
+ source_location: "federated",
276
+ });
277
+ }
278
+ }
279
+ }
280
+ } catch {
281
+ // Non-fatal — federated query failed, return local results only
282
+ }
283
+ }
284
+
285
+ return {
286
+ tasks: localTaskItems,
287
+ total: localTaskItems.length,
288
+ };
289
+ },
290
+ };
291
+ }
292
+
293
+ private assignTaskTool(): MCPToolDefinition {
294
+ return {
295
+ name: "assign_task",
296
+ description: "Assign a task to an agent",
297
+ schema: {
298
+ type: "object",
299
+ properties: {
300
+ task_id: {
301
+ type: "string",
302
+ description: "Task ID to assign",
303
+ },
304
+ agent_id: {
305
+ type: "string",
306
+ description:
307
+ "Agent ID to assign to (defaults to calling agent if not specified)",
308
+ },
309
+ role: {
310
+ type: "string",
311
+ description: "Optional role for the assignment",
312
+ },
313
+ },
314
+ required: ["task_id"],
315
+ },
316
+ handler: async (params: unknown) => {
317
+ const args = params as {
318
+ task_id: string;
319
+ agent_id?: string;
320
+ role?: string;
321
+ };
322
+ const context = this.getContext();
323
+
324
+ const agentId = args.agent_id ?? context.agent_id;
325
+ await this.backend.assign(args.task_id, agentId, { role: args.role });
326
+
327
+ return {
328
+ task_id: args.task_id,
329
+ assigned_agent: agentId,
330
+ assigned: true,
331
+ };
332
+ },
333
+ };
334
+ }
335
+
336
+ // ─────────────────────────────────────────────────────────────────────────────
337
+ // OpenTasks Graph Tools (require OpenTasksClient)
338
+ // ─────────────────────────────────────────────────────────────────────────────
339
+
340
+ private taskTool(): MCPToolDefinition {
341
+ const client = this.openTasksClient!;
342
+ const getContext = this.getContext;
343
+ return {
344
+ name: "task",
345
+ description:
346
+ "Provider-agnostic task lifecycle operations. Routes to the correct " +
347
+ "provider based on task ID or URI. " +
348
+ "Supports: transition (start/complete/block/reopen/close), " +
349
+ "ready (federated across providers), assign, validActions. " +
350
+ "Specify exactly one operation.",
351
+ schema: {
352
+ type: "object",
353
+ properties: {
354
+ transition: {
355
+ type: "object",
356
+ description: "Transition a task's status using a semantic action",
357
+ properties: {
358
+ id: {
359
+ type: "string",
360
+ description: "Task ID or provider URI",
361
+ },
362
+ action: {
363
+ type: "string",
364
+ enum: ["start", "complete", "block", "reopen", "close"],
365
+ description: "Semantic action to apply",
366
+ },
367
+ },
368
+ required: ["id", "action"],
369
+ },
370
+ ready: {
371
+ type: "object",
372
+ description:
373
+ "Get tasks ready to work on (no active blockers), federated across providers",
374
+ properties: {
375
+ providers: {
376
+ type: "array",
377
+ items: { type: "string" },
378
+ description: "Only query these providers. Omit for all.",
379
+ },
380
+ limit: { type: "number", description: "Max results" },
381
+ tags: {
382
+ type: "array",
383
+ items: { type: "string" },
384
+ description: "Filter by tags",
385
+ },
386
+ priority: {
387
+ type: "number",
388
+ description: "Filter by minimum priority",
389
+ },
390
+ assignee: {
391
+ type: "string",
392
+ description: "Filter by assignee",
393
+ },
394
+ },
395
+ },
396
+ assign: {
397
+ type: "object",
398
+ description: "Assign a task to an owner",
399
+ properties: {
400
+ id: {
401
+ type: "string",
402
+ description: "Task ID or provider URI",
403
+ },
404
+ assignee: {
405
+ type: "string",
406
+ description: "Assignee identifier (defaults to calling agent)",
407
+ },
408
+ },
409
+ required: ["id"],
410
+ },
411
+ validActions: {
412
+ type: "object",
413
+ description: "Get valid next actions for a task's current state",
414
+ properties: {
415
+ id: {
416
+ type: "string",
417
+ description: "Task ID or provider URI",
418
+ },
419
+ },
420
+ required: ["id"],
421
+ },
422
+ },
423
+ },
424
+ handler: async (params: unknown) => {
425
+ const args = params as {
426
+ transition?: { id: string; action: string };
427
+ ready?: {
428
+ providers?: string[];
429
+ limit?: number;
430
+ tags?: string[];
431
+ priority?: number;
432
+ assignee?: string;
433
+ };
434
+ assign?: { id: string; assignee?: string };
435
+ validActions?: { id: string };
436
+ };
437
+
438
+ if (args.transition) {
439
+ const result = await client.taskTransition(
440
+ args.transition.id,
441
+ args.transition.action as
442
+ | "start"
443
+ | "complete"
444
+ | "block"
445
+ | "reopen"
446
+ | "close"
447
+ );
448
+ if (!result.success) {
449
+ throw new Error(result.error ?? "Transition failed");
450
+ }
451
+
452
+ // Sync the transition back to the EventStore so MAP events are emitted
453
+ // and the TUI task board stays in sync. The opentasks daemon already
454
+ // processed the transition, so syncExternalTransition only updates the
455
+ // EventStore without re-syncing to opentasks.
456
+ if (this.backend.syncExternalTransition) {
457
+ try {
458
+ await this.backend.syncExternalTransition(
459
+ args.transition.id,
460
+ args.transition.action,
461
+ getContext().agent_id,
462
+ );
463
+ } catch (err) {
464
+ console.warn(
465
+ `[UnifiedTaskToolProvider] syncExternalTransition failed for ${args.transition.id}: ${err}`
466
+ );
467
+ }
468
+ }
469
+
470
+ return result.data;
471
+ }
472
+
473
+ if (args.ready !== undefined) {
474
+ const result = await client.taskReady(args.ready);
475
+ if (!result.success) {
476
+ throw new Error(result.error ?? "Ready query failed");
477
+ }
478
+ return result.data;
479
+ }
480
+
481
+ if (args.assign) {
482
+ const assignee = args.assign.assignee ?? getContext().agent_id;
483
+ const result = await client.taskAssign(args.assign.id, assignee);
484
+ if (!result.success) {
485
+ throw new Error(result.error ?? "Assignment failed");
486
+ }
487
+
488
+ // Sync assignment back to EventStore
489
+ if (this.backend.syncExternalTransition) {
490
+ try {
491
+ await this.backend.syncExternalTransition(
492
+ args.assign.id,
493
+ "assign",
494
+ assignee,
495
+ );
496
+ } catch (err) {
497
+ console.warn(
498
+ `[UnifiedTaskToolProvider] syncExternalTransition (assign) failed for ${args.assign.id}: ${err}`
499
+ );
500
+ }
501
+ }
502
+
503
+ return result.data;
504
+ }
505
+
506
+ if (args.validActions) {
507
+ const result = await client.taskValidActions(args.validActions.id);
508
+ if (!result.success) {
509
+ throw new Error(result.error ?? "Valid actions query failed");
510
+ }
511
+ return result.data;
512
+ }
513
+
514
+ throw new Error(
515
+ "Specify exactly one operation: transition, ready, assign, or validActions"
516
+ );
517
+ },
518
+ };
519
+ }
520
+
521
+ private linkTool(): MCPToolDefinition {
522
+ const client = this.openTasksClient!;
523
+ return {
524
+ name: "link",
525
+ description:
526
+ "Create or remove a relationship between nodes. " +
527
+ "Supports cross-project references via opentasks:// URIs " +
528
+ "(e.g., opentasks://<location-hash>/i-xxxx). " +
529
+ "Types: blocks, implements, references, related, child-of, " +
530
+ "parent-of, depends-on, discovered-from, duplicates, supersedes.",
531
+ schema: {
532
+ type: "object",
533
+ properties: {
534
+ from_id: {
535
+ type: "string",
536
+ description: "Source node ID or provider URI",
537
+ },
538
+ to_id: {
539
+ type: "string",
540
+ description: "Target node ID or provider URI",
541
+ },
542
+ type: {
543
+ type: "string",
544
+ enum: [
545
+ "blocks",
546
+ "implements",
547
+ "references",
548
+ "related",
549
+ "child-of",
550
+ "parent-of",
551
+ "depends-on",
552
+ "discovered-from",
553
+ "duplicates",
554
+ "supersedes",
555
+ ],
556
+ description: "Relationship type",
557
+ },
558
+ remove: {
559
+ type: "boolean",
560
+ description: "Remove the edge instead of creating (default: false)",
561
+ },
562
+ },
563
+ required: ["from_id", "to_id", "type"],
564
+ },
565
+ handler: async (params: unknown) => {
566
+ const args = params as {
567
+ from_id: string;
568
+ to_id: string;
569
+ type: string;
570
+ remove?: boolean;
571
+ };
572
+
573
+ if (args.remove) {
574
+ await client.removeEdge(args.from_id, args.to_id, args.type);
575
+ return {
576
+ from_id: args.from_id,
577
+ to_id: args.to_id,
578
+ type: args.type,
579
+ removed: true,
580
+ };
581
+ }
582
+
583
+ const edge = await client.createEdge(
584
+ args.from_id,
585
+ args.to_id,
586
+ args.type
587
+ );
588
+ return {
589
+ edge_id: edge.id,
590
+ from_id: args.from_id,
591
+ to_id: args.to_id,
592
+ type: args.type,
593
+ created: true,
594
+ };
595
+ },
596
+ };
597
+ }
598
+
599
+ private annotateTool(): MCPToolDefinition {
600
+ const client = this.openTasksClient!;
601
+ const getContext = this.getContext;
602
+ return {
603
+ name: "annotate",
604
+ description:
605
+ "Add feedback to a node, or resolve/dismiss/reopen existing feedback. " +
606
+ "Feedback types: comment, suggestion, request. " +
607
+ "Can anchor to specific line numbers or text.",
608
+ schema: {
609
+ type: "object",
610
+ properties: {
611
+ target_id: {
612
+ type: "string",
613
+ description: "Target node receiving feedback",
614
+ },
615
+ content: {
616
+ type: "string",
617
+ description:
618
+ "Feedback content (markdown). Required for new feedback.",
619
+ },
620
+ feedback_type: {
621
+ type: "string",
622
+ enum: ["comment", "suggestion", "request"],
623
+ description: "Type of feedback (default: comment)",
624
+ },
625
+ line: {
626
+ type: "number",
627
+ description: "Line number to anchor feedback to",
628
+ },
629
+ text: {
630
+ type: "string",
631
+ description: "Text snippet to anchor feedback to",
632
+ },
633
+ from_id: {
634
+ type: "string",
635
+ description:
636
+ "Issue providing the feedback (creates discovered-from link)",
637
+ },
638
+ resolve: {
639
+ type: "string",
640
+ description: "Feedback ID to resolve",
641
+ },
642
+ dismiss: {
643
+ type: "string",
644
+ description: "Feedback ID to dismiss",
645
+ },
646
+ reopen: {
647
+ type: "string",
648
+ description: "Feedback ID to reopen",
649
+ },
650
+ },
651
+ required: ["target_id"],
652
+ },
653
+ handler: async (params: unknown) => {
654
+ const args = params as {
655
+ target_id: string;
656
+ content?: string;
657
+ feedback_type?: "comment" | "suggestion" | "request";
658
+ line?: number;
659
+ text?: string;
660
+ from_id?: string;
661
+ resolve?: string;
662
+ dismiss?: string;
663
+ reopen?: string;
664
+ };
665
+
666
+ if (args.content) {
667
+ // Create new feedback node
668
+ const feedbackNode = await client.createIssue({
669
+ title: args.content.slice(0, 100),
670
+ content: args.content,
671
+ metadata: {
672
+ _node_type: "feedback",
673
+ target_id: args.target_id,
674
+ feedback_type: args.feedback_type ?? "comment",
675
+ from_id: args.from_id,
676
+ anchor_line: args.line,
677
+ anchor_text: args.text,
678
+ _created_by_agent: getContext().agent_id,
679
+ },
680
+ });
681
+
682
+ // Link feedback to target
683
+ if (args.from_id) {
684
+ await client.createEdge(
685
+ args.from_id,
686
+ args.target_id,
687
+ "discovered-from"
688
+ );
689
+ }
690
+
691
+ return {
692
+ feedback_id: feedbackNode.id,
693
+ target_id: args.target_id,
694
+ type: args.feedback_type ?? "comment",
695
+ created: true,
696
+ };
697
+ }
698
+
699
+ if (args.resolve) {
700
+ await client.updateIssue(args.resolve, {
701
+ metadata: { resolved: true, resolved_at: new Date().toISOString() },
702
+ });
703
+ return { feedback_id: args.resolve, resolved: true };
704
+ }
705
+
706
+ if (args.dismiss) {
707
+ await client.updateIssue(args.dismiss, {
708
+ metadata: {
709
+ dismissed: true,
710
+ dismissed_at: new Date().toISOString(),
711
+ },
712
+ });
713
+ return { feedback_id: args.dismiss, dismissed: true };
714
+ }
715
+
716
+ if (args.reopen) {
717
+ await client.updateIssue(args.reopen, {
718
+ metadata: { resolved: false, dismissed: false },
719
+ });
720
+ return { feedback_id: args.reopen, reopened: true };
721
+ }
722
+
723
+ throw new Error(
724
+ "Must provide content (new feedback), resolve, dismiss, or reopen"
725
+ );
726
+ },
727
+ };
728
+ }
729
+
730
+ private listProvidersTool(): MCPToolDefinition {
731
+ const client = this.openTasksClient!;
732
+ return {
733
+ name: "list_providers",
734
+ description:
735
+ "List all registered providers and their capabilities. " +
736
+ "Shows what task systems are connected (native opentasks, external integrations) " +
737
+ "and what operations each supports.",
738
+ schema: {
739
+ type: "object",
740
+ properties: {},
741
+ },
742
+ handler: async () => {
743
+ const providers = await client.listProviders();
744
+ return {
745
+ providers: providers.map((p) => ({
746
+ name: p.name,
747
+ schemes: p.schemes,
748
+ is_default: p.isDefault,
749
+ capabilities: p.capabilities,
750
+ task_capabilities: p.taskCapabilities
751
+ ? {
752
+ actions: p.taskCapabilities.actions,
753
+ supports_assignment: p.taskCapabilities.supportsAssignment,
754
+ supports_ready_query: p.taskCapabilities.supportsReadyQuery,
755
+ status_model: p.taskCapabilities.statusModel,
756
+ }
757
+ : undefined,
758
+ })),
759
+ total: providers.length,
760
+ };
761
+ },
762
+ };
763
+ }
764
+ }
765
+
766
+ // =============================================================================
767
+ // Factory
768
+ // =============================================================================
769
+
770
+ /**
771
+ * Create a UnifiedTaskToolProvider
772
+ */
773
+ export function createUnifiedToolProvider(
774
+ backend: TaskBackend,
775
+ getContext: GetToolContext,
776
+ openTasksClient?: OpenTasksClient
777
+ ): UnifiedTaskToolProvider {
778
+ return new UnifiedTaskToolProvider(backend, getContext, openTasksClient);
779
+ }