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,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
+ }