macro-agent 0.1.0 → 0.1.2

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 (660) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/.sudocode/issues.jsonl +28 -0
  3. package/.sudocode/specs.jsonl +8 -0
  4. package/CLAUDE.md +25 -17
  5. package/README.md +11 -29
  6. package/dist/acp/macro-agent.d.ts +15 -0
  7. package/dist/acp/macro-agent.d.ts.map +1 -1
  8. package/dist/acp/macro-agent.js +131 -35
  9. package/dist/acp/macro-agent.js.map +1 -1
  10. package/dist/acp/types.d.ts +32 -1
  11. package/dist/acp/types.d.ts.map +1 -1
  12. package/dist/acp/types.js.map +1 -1
  13. package/dist/agent/agent-manager.d.ts +65 -1
  14. package/dist/agent/agent-manager.d.ts.map +1 -1
  15. package/dist/agent/agent-manager.js +544 -200
  16. package/dist/agent/agent-manager.js.map +1 -1
  17. package/dist/agent/types.d.ts +8 -1
  18. package/dist/agent/types.d.ts.map +1 -1
  19. package/dist/agent/types.js.map +1 -1
  20. package/dist/api/server.d.ts +8 -1
  21. package/dist/api/server.d.ts.map +1 -1
  22. package/dist/api/server.js +136 -8
  23. package/dist/api/server.js.map +1 -1
  24. package/dist/api/types.d.ts +1 -1
  25. package/dist/api/types.d.ts.map +1 -1
  26. package/dist/auth/index.d.ts +2 -0
  27. package/dist/auth/index.d.ts.map +1 -0
  28. package/dist/auth/index.js +2 -0
  29. package/dist/auth/index.js.map +1 -0
  30. package/dist/auth/token.d.ts +41 -0
  31. package/dist/auth/token.d.ts.map +1 -0
  32. package/dist/auth/token.js +73 -0
  33. package/dist/auth/token.js.map +1 -0
  34. package/dist/cli/acp.d.ts +2 -23
  35. package/dist/cli/acp.d.ts.map +1 -1
  36. package/dist/cli/acp.js +197 -61
  37. package/dist/cli/acp.js.map +1 -1
  38. package/dist/cli/index.js +152 -16
  39. package/dist/cli/index.js.map +1 -1
  40. package/dist/cli/mcp.d.ts +6 -0
  41. package/dist/cli/mcp.d.ts.map +1 -1
  42. package/dist/cli/mcp.js +279 -173
  43. package/dist/cli/mcp.js.map +1 -1
  44. package/dist/cli/parse-args.d.ts +20 -0
  45. package/dist/cli/parse-args.d.ts.map +1 -0
  46. package/dist/cli/parse-args.js +43 -0
  47. package/dist/cli/parse-args.js.map +1 -0
  48. package/dist/cli/stable-instance-id.d.ts +8 -0
  49. package/dist/cli/stable-instance-id.d.ts.map +1 -0
  50. package/dist/cli/stable-instance-id.js +14 -0
  51. package/dist/cli/stable-instance-id.js.map +1 -0
  52. package/dist/config/project-config.d.ts +85 -7
  53. package/dist/config/project-config.d.ts.map +1 -1
  54. package/dist/config/project-config.js +133 -20
  55. package/dist/config/project-config.js.map +1 -1
  56. package/dist/index.d.ts +1 -0
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +2 -0
  59. package/dist/index.js.map +1 -1
  60. package/dist/lifecycle/handlers/index.d.ts +7 -3
  61. package/dist/lifecycle/handlers/index.d.ts.map +1 -1
  62. package/dist/lifecycle/handlers/index.js +25 -8
  63. package/dist/lifecycle/handlers/index.js.map +1 -1
  64. package/dist/lifecycle/types.d.ts +2 -0
  65. package/dist/lifecycle/types.d.ts.map +1 -1
  66. package/dist/lifecycle/types.js.map +1 -1
  67. package/dist/map/adapter/acp-over-map.d.ts +17 -0
  68. package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
  69. package/dist/map/adapter/acp-over-map.js +384 -23
  70. package/dist/map/adapter/acp-over-map.js.map +1 -1
  71. package/dist/map/adapter/connection-manager.d.ts.map +1 -1
  72. package/dist/map/adapter/connection-manager.js +3 -0
  73. package/dist/map/adapter/connection-manager.js.map +1 -1
  74. package/dist/map/adapter/event-log.d.ts +87 -0
  75. package/dist/map/adapter/event-log.d.ts.map +1 -0
  76. package/dist/map/adapter/event-log.js +122 -0
  77. package/dist/map/adapter/event-log.js.map +1 -0
  78. package/dist/map/adapter/event-translator.js +6 -6
  79. package/dist/map/adapter/event-translator.js.map +1 -1
  80. package/dist/map/adapter/extensions/agent-lifecycle.d.ts +82 -0
  81. package/dist/map/adapter/extensions/agent-lifecycle.d.ts.map +1 -0
  82. package/dist/map/adapter/extensions/agent-lifecycle.js +164 -0
  83. package/dist/map/adapter/extensions/agent-lifecycle.js.map +1 -0
  84. package/dist/map/adapter/extensions/index.d.ts +13 -1
  85. package/dist/map/adapter/extensions/index.d.ts.map +1 -1
  86. package/dist/map/adapter/extensions/index.js +61 -0
  87. package/dist/map/adapter/extensions/index.js.map +1 -1
  88. package/dist/map/adapter/extensions/mcp-bridge.d.ts +57 -0
  89. package/dist/map/adapter/extensions/mcp-bridge.d.ts.map +1 -0
  90. package/dist/map/adapter/extensions/mcp-bridge.js +745 -0
  91. package/dist/map/adapter/extensions/mcp-bridge.js.map +1 -0
  92. package/dist/map/adapter/extensions/rename.d.ts +29 -0
  93. package/dist/map/adapter/extensions/rename.d.ts.map +1 -0
  94. package/dist/map/adapter/extensions/rename.js +49 -0
  95. package/dist/map/adapter/extensions/rename.js.map +1 -0
  96. package/dist/map/adapter/extensions/streams.d.ts +95 -0
  97. package/dist/map/adapter/extensions/streams.d.ts.map +1 -0
  98. package/dist/map/adapter/extensions/streams.js +515 -0
  99. package/dist/map/adapter/extensions/streams.js.map +1 -0
  100. package/dist/map/adapter/extensions/task.d.ts.map +1 -1
  101. package/dist/map/adapter/extensions/task.js +10 -0
  102. package/dist/map/adapter/extensions/task.js.map +1 -1
  103. package/dist/map/adapter/extensions/update-metadata.d.ts +29 -0
  104. package/dist/map/adapter/extensions/update-metadata.d.ts.map +1 -0
  105. package/dist/map/adapter/extensions/update-metadata.js +67 -0
  106. package/dist/map/adapter/extensions/update-metadata.js.map +1 -0
  107. package/dist/map/adapter/index.d.ts +2 -1
  108. package/dist/map/adapter/index.d.ts.map +1 -1
  109. package/dist/map/adapter/index.js +10 -2
  110. package/dist/map/adapter/index.js.map +1 -1
  111. package/dist/map/adapter/interface.d.ts +2 -0
  112. package/dist/map/adapter/interface.d.ts.map +1 -1
  113. package/dist/map/adapter/map-adapter.d.ts +3 -0
  114. package/dist/map/adapter/map-adapter.d.ts.map +1 -1
  115. package/dist/map/adapter/map-adapter.js +258 -35
  116. package/dist/map/adapter/map-adapter.js.map +1 -1
  117. package/dist/map/adapter/subscription-manager.d.ts.map +1 -1
  118. package/dist/map/adapter/subscription-manager.js +5 -1
  119. package/dist/map/adapter/subscription-manager.js.map +1 -1
  120. package/dist/map/adapter/types.d.ts +3 -1
  121. package/dist/map/adapter/types.d.ts.map +1 -1
  122. package/dist/mcp/map-client.d.ts +39 -0
  123. package/dist/mcp/map-client.d.ts.map +1 -0
  124. package/dist/mcp/map-client.js +129 -0
  125. package/dist/mcp/map-client.js.map +1 -0
  126. package/dist/mcp/mcp-server.d.ts +16 -0
  127. package/dist/mcp/mcp-server.d.ts.map +1 -1
  128. package/dist/mcp/mcp-server.js +125 -88
  129. package/dist/mcp/mcp-server.js.map +1 -1
  130. package/dist/mcp/tools/done.d.ts.map +1 -1
  131. package/dist/mcp/tools/done.js +18 -0
  132. package/dist/mcp/tools/done.js.map +1 -1
  133. package/dist/mcp/types.d.ts +9 -1
  134. package/dist/mcp/types.d.ts.map +1 -1
  135. package/dist/mcp/types.js.map +1 -1
  136. package/dist/metrics/metrics.js +1 -1
  137. package/dist/metrics/metrics.js.map +1 -1
  138. package/dist/roles/builtin/coordinator.d.ts.map +1 -1
  139. package/dist/roles/builtin/coordinator.js +2 -1
  140. package/dist/roles/builtin/coordinator.js.map +1 -1
  141. package/dist/roles/builtin/integrator.d.ts.map +1 -1
  142. package/dist/roles/builtin/integrator.js +2 -1
  143. package/dist/roles/builtin/integrator.js.map +1 -1
  144. package/dist/roles/builtin/worker.d.ts.map +1 -1
  145. package/dist/roles/builtin/worker.js +3 -1
  146. package/dist/roles/builtin/worker.js.map +1 -1
  147. package/dist/roles/capabilities.d.ts +9 -1
  148. package/dist/roles/capabilities.d.ts.map +1 -1
  149. package/dist/roles/capabilities.js +27 -7
  150. package/dist/roles/capabilities.js.map +1 -1
  151. package/dist/roles/config-loader.d.ts +6 -6
  152. package/dist/roles/config-loader.d.ts.map +1 -1
  153. package/dist/roles/config-loader.js +8 -7
  154. package/dist/roles/config-loader.js.map +1 -1
  155. package/dist/roles/registry.d.ts +2 -2
  156. package/dist/roles/registry.js +2 -2
  157. package/dist/roles/types.d.ts +3 -1
  158. package/dist/roles/types.d.ts.map +1 -1
  159. package/dist/server/combined-server.d.ts +28 -1
  160. package/dist/server/combined-server.d.ts.map +1 -1
  161. package/dist/server/combined-server.js +111 -8
  162. package/dist/server/combined-server.js.map +1 -1
  163. package/dist/store/event-store.d.ts +2 -1
  164. package/dist/store/event-store.d.ts.map +1 -1
  165. package/dist/store/event-store.js +80 -24
  166. package/dist/store/event-store.js.map +1 -1
  167. package/dist/store/instance.d.ts +1 -1
  168. package/dist/store/instance.d.ts.map +1 -1
  169. package/dist/store/instance.js +2 -2
  170. package/dist/store/instance.js.map +1 -1
  171. package/dist/store/types/agents.d.ts +23 -0
  172. package/dist/store/types/agents.d.ts.map +1 -1
  173. package/dist/store/types/events.d.ts +1 -1
  174. package/dist/store/types/events.d.ts.map +1 -1
  175. package/dist/task/backend/index.d.ts +47 -29
  176. package/dist/task/backend/index.d.ts.map +1 -1
  177. package/dist/task/backend/index.js +109 -71
  178. package/dist/task/backend/index.js.map +1 -1
  179. package/dist/task/backend/memory.d.ts +1 -0
  180. package/dist/task/backend/memory.d.ts.map +1 -1
  181. package/dist/task/backend/memory.js +3 -0
  182. package/dist/task/backend/memory.js.map +1 -1
  183. package/dist/task/backend/opentasks/backend.d.ts +140 -0
  184. package/dist/task/backend/opentasks/backend.d.ts.map +1 -0
  185. package/dist/task/backend/opentasks/backend.js +1023 -0
  186. package/dist/task/backend/opentasks/backend.js.map +1 -0
  187. package/dist/task/backend/opentasks/client.d.ts +337 -0
  188. package/dist/task/backend/opentasks/client.d.ts.map +1 -0
  189. package/dist/task/backend/opentasks/client.js +225 -0
  190. package/dist/task/backend/opentasks/client.js.map +1 -0
  191. package/dist/task/backend/opentasks/daemon-manager.d.ts +89 -0
  192. package/dist/task/backend/opentasks/daemon-manager.d.ts.map +1 -0
  193. package/dist/task/backend/opentasks/daemon-manager.js +195 -0
  194. package/dist/task/backend/opentasks/daemon-manager.js.map +1 -0
  195. package/dist/task/backend/opentasks/index.d.ts +21 -0
  196. package/dist/task/backend/opentasks/index.d.ts.map +1 -0
  197. package/dist/task/backend/opentasks/index.js +21 -0
  198. package/dist/task/backend/opentasks/index.js.map +1 -0
  199. package/dist/task/backend/opentasks/mapping.d.ts +48 -0
  200. package/dist/task/backend/opentasks/mapping.d.ts.map +1 -0
  201. package/dist/task/backend/opentasks/mapping.js +77 -0
  202. package/dist/task/backend/opentasks/mapping.js.map +1 -0
  203. package/dist/task/backend/types.d.ts +33 -53
  204. package/dist/task/backend/types.d.ts.map +1 -1
  205. package/dist/task/backend/types.js +7 -11
  206. package/dist/task/backend/types.js.map +1 -1
  207. package/dist/task/backend/unified-tool-provider.d.ts +57 -0
  208. package/dist/task/backend/unified-tool-provider.d.ts.map +1 -0
  209. package/dist/task/backend/unified-tool-provider.js +623 -0
  210. package/dist/task/backend/unified-tool-provider.js.map +1 -0
  211. package/dist/teams/index.d.ts +3 -1
  212. package/dist/teams/index.d.ts.map +1 -1
  213. package/dist/teams/index.js +2 -0
  214. package/dist/teams/index.js.map +1 -1
  215. package/dist/teams/seed-defaults.d.ts +20 -0
  216. package/dist/teams/seed-defaults.d.ts.map +1 -0
  217. package/dist/teams/seed-defaults.js +71 -0
  218. package/dist/teams/seed-defaults.js.map +1 -0
  219. package/dist/teams/team-loader.d.ts +7 -3
  220. package/dist/teams/team-loader.d.ts.map +1 -1
  221. package/dist/teams/team-loader.js +156 -164
  222. package/dist/teams/team-loader.js.map +1 -1
  223. package/dist/teams/team-manager.d.ts +112 -0
  224. package/dist/teams/team-manager.d.ts.map +1 -0
  225. package/dist/teams/team-manager.js +305 -0
  226. package/dist/teams/team-manager.js.map +1 -0
  227. package/dist/teams/team-runtime.d.ts +125 -19
  228. package/dist/teams/team-runtime.d.ts.map +1 -1
  229. package/dist/teams/team-runtime.js +529 -119
  230. package/dist/teams/team-runtime.js.map +1 -1
  231. package/dist/teams/types.d.ts +41 -151
  232. package/dist/teams/types.d.ts.map +1 -1
  233. package/dist/teams/types.js +2 -3
  234. package/dist/teams/types.js.map +1 -1
  235. package/docs/architecture.md +7 -6
  236. package/docs/configuration.md +26 -62
  237. package/docs/implementation-details.md +5 -5
  238. package/docs/implementation-summary.md +17 -17
  239. package/docs/plan-self-driving-support.md +4 -4
  240. package/docs/spec-self-driving-support.md +10 -10
  241. package/docs/team-templates.md +2 -2
  242. package/docs/teams.md +76 -3
  243. package/docs/troubleshooting.md +10 -11
  244. package/package.json +7 -4
  245. package/references/minimem/.claude/settings.json +7 -0
  246. package/references/minimem/.sudocode/issues.jsonl +18 -0
  247. package/references/minimem/.sudocode/specs.jsonl +1 -0
  248. package/references/minimem/CLAUDE.md +310 -0
  249. package/references/minimem/README.md +562 -0
  250. package/references/minimem/claude-plugin/.claude-plugin/plugin.json +10 -0
  251. package/references/minimem/claude-plugin/.mcp.json +7 -0
  252. package/references/minimem/claude-plugin/README.md +158 -0
  253. package/references/minimem/claude-plugin/commands/recall.md +47 -0
  254. package/references/minimem/claude-plugin/commands/remember.md +41 -0
  255. package/references/minimem/claude-plugin/hooks/__tests__/hooks.test.ts +272 -0
  256. package/references/minimem/claude-plugin/hooks/hooks.json +27 -0
  257. package/references/minimem/claude-plugin/hooks/session-end.sh +86 -0
  258. package/references/minimem/claude-plugin/hooks/session-start.sh +85 -0
  259. package/references/minimem/claude-plugin/skills/memory/SKILL.md +108 -0
  260. package/references/minimem/media/banner.png +0 -0
  261. package/references/minimem/package-lock.json +5373 -0
  262. package/references/minimem/package.json +72 -0
  263. package/references/minimem/scripts/postbuild.js +35 -0
  264. package/references/minimem/src/__tests__/edge-cases.test.ts +371 -0
  265. package/references/minimem/src/__tests__/errors.test.ts +265 -0
  266. package/references/minimem/src/__tests__/helpers.ts +199 -0
  267. package/references/minimem/src/__tests__/internal.test.ts +407 -0
  268. package/references/minimem/src/__tests__/knowledge.test.ts +287 -0
  269. package/references/minimem/src/__tests__/minimem.integration.test.ts +1127 -0
  270. package/references/minimem/src/__tests__/session.test.ts +190 -0
  271. package/references/minimem/src/cli/__tests__/commands.test.ts +759 -0
  272. package/references/minimem/src/cli/commands/__tests__/conflicts.test.ts +141 -0
  273. package/references/minimem/src/cli/commands/append.ts +76 -0
  274. package/references/minimem/src/cli/commands/config.ts +262 -0
  275. package/references/minimem/src/cli/commands/conflicts.ts +413 -0
  276. package/references/minimem/src/cli/commands/daemon.ts +169 -0
  277. package/references/minimem/src/cli/commands/index.ts +12 -0
  278. package/references/minimem/src/cli/commands/init.ts +88 -0
  279. package/references/minimem/src/cli/commands/mcp.ts +177 -0
  280. package/references/minimem/src/cli/commands/push-pull.ts +213 -0
  281. package/references/minimem/src/cli/commands/search.ts +158 -0
  282. package/references/minimem/src/cli/commands/status.ts +84 -0
  283. package/references/minimem/src/cli/commands/sync-init.ts +290 -0
  284. package/references/minimem/src/cli/commands/sync.ts +70 -0
  285. package/references/minimem/src/cli/commands/upsert.ts +197 -0
  286. package/references/minimem/src/cli/config.ts +584 -0
  287. package/references/minimem/src/cli/index.ts +264 -0
  288. package/references/minimem/src/cli/shared.ts +161 -0
  289. package/references/minimem/src/cli/sync/__tests__/central.test.ts +152 -0
  290. package/references/minimem/src/cli/sync/__tests__/conflicts.test.ts +209 -0
  291. package/references/minimem/src/cli/sync/__tests__/daemon.test.ts +118 -0
  292. package/references/minimem/src/cli/sync/__tests__/detection.test.ts +207 -0
  293. package/references/minimem/src/cli/sync/__tests__/integration.test.ts +476 -0
  294. package/references/minimem/src/cli/sync/__tests__/registry.test.ts +363 -0
  295. package/references/minimem/src/cli/sync/__tests__/state.test.ts +255 -0
  296. package/references/minimem/src/cli/sync/__tests__/validation.test.ts +193 -0
  297. package/references/minimem/src/cli/sync/__tests__/watcher.test.ts +178 -0
  298. package/references/minimem/src/cli/sync/central.ts +292 -0
  299. package/references/minimem/src/cli/sync/conflicts.ts +204 -0
  300. package/references/minimem/src/cli/sync/daemon.ts +407 -0
  301. package/references/minimem/src/cli/sync/detection.ts +138 -0
  302. package/references/minimem/src/cli/sync/index.ts +107 -0
  303. package/references/minimem/src/cli/sync/operations.ts +373 -0
  304. package/references/minimem/src/cli/sync/registry.ts +279 -0
  305. package/references/minimem/src/cli/sync/state.ts +355 -0
  306. package/references/minimem/src/cli/sync/validation.ts +206 -0
  307. package/references/minimem/src/cli/sync/watcher.ts +234 -0
  308. package/references/minimem/src/cli/version.ts +34 -0
  309. package/references/minimem/src/core/index.ts +9 -0
  310. package/references/minimem/src/core/indexer.ts +628 -0
  311. package/references/minimem/src/core/searcher.ts +221 -0
  312. package/references/minimem/src/db/schema.ts +183 -0
  313. package/references/minimem/src/db/sqlite-vec.ts +24 -0
  314. package/references/minimem/src/embeddings/__tests__/embeddings.test.ts +431 -0
  315. package/references/minimem/src/embeddings/batch-gemini.ts +392 -0
  316. package/references/minimem/src/embeddings/batch-openai.ts +409 -0
  317. package/references/minimem/src/embeddings/embeddings.ts +434 -0
  318. package/references/minimem/src/index.ts +109 -0
  319. package/references/minimem/src/internal.ts +299 -0
  320. package/references/minimem/src/minimem.ts +1276 -0
  321. package/references/minimem/src/search/__tests__/hybrid.test.ts +247 -0
  322. package/references/minimem/src/search/graph.ts +234 -0
  323. package/references/minimem/src/search/hybrid.ts +151 -0
  324. package/references/minimem/src/search/search.ts +256 -0
  325. package/references/minimem/src/server/__tests__/mcp.test.ts +341 -0
  326. package/references/minimem/src/server/__tests__/tools.test.ts +364 -0
  327. package/references/minimem/src/server/mcp.ts +326 -0
  328. package/references/minimem/src/server/tools.ts +720 -0
  329. package/references/minimem/src/session.ts +460 -0
  330. package/references/minimem/tsconfig.json +19 -0
  331. package/references/minimem/tsup.config.ts +26 -0
  332. package/references/minimem/vitest.config.ts +24 -0
  333. package/references/openteams/.claude/settings.json +6 -0
  334. package/references/openteams/README.md +1 -0
  335. package/references/openteams/SKILL.md +341 -0
  336. package/references/openteams/design.md +411 -0
  337. package/references/openteams/examples/bmad-method/prompts/analyst/ROLE.md +16 -0
  338. package/references/openteams/examples/bmad-method/prompts/analyst/SOUL.md +5 -0
  339. package/references/openteams/examples/bmad-method/prompts/architect/ROLE.md +24 -0
  340. package/references/openteams/examples/bmad-method/prompts/architect/SOUL.md +5 -0
  341. package/references/openteams/examples/bmad-method/prompts/developer/ROLE.md +25 -0
  342. package/references/openteams/examples/bmad-method/prompts/developer/SOUL.md +5 -0
  343. package/references/openteams/examples/bmad-method/prompts/master/ROLE.md +21 -0
  344. package/references/openteams/examples/bmad-method/prompts/master/SOUL.md +5 -0
  345. package/references/openteams/examples/bmad-method/prompts/pm/ROLE.md +20 -0
  346. package/references/openteams/examples/bmad-method/prompts/pm/SOUL.md +5 -0
  347. package/references/openteams/examples/bmad-method/prompts/qa/ROLE.md +17 -0
  348. package/references/openteams/examples/bmad-method/prompts/qa/SOUL.md +5 -0
  349. package/references/openteams/examples/bmad-method/prompts/quick-flow-dev/ROLE.md +23 -0
  350. package/references/openteams/examples/bmad-method/prompts/quick-flow-dev/SOUL.md +5 -0
  351. package/references/openteams/examples/bmad-method/prompts/scrum-master/ROLE.md +27 -0
  352. package/references/openteams/examples/bmad-method/prompts/scrum-master/SOUL.md +5 -0
  353. package/references/openteams/examples/bmad-method/prompts/tech-writer/ROLE.md +21 -0
  354. package/references/openteams/examples/bmad-method/prompts/tech-writer/SOUL.md +5 -0
  355. package/references/openteams/examples/bmad-method/prompts/ux-designer/ROLE.md +16 -0
  356. package/references/openteams/examples/bmad-method/prompts/ux-designer/SOUL.md +5 -0
  357. package/references/openteams/examples/bmad-method/roles/analyst.yaml +9 -0
  358. package/references/openteams/examples/bmad-method/roles/architect.yaml +9 -0
  359. package/references/openteams/examples/bmad-method/roles/developer.yaml +8 -0
  360. package/references/openteams/examples/bmad-method/roles/master.yaml +8 -0
  361. package/references/openteams/examples/bmad-method/roles/pm.yaml +9 -0
  362. package/references/openteams/examples/bmad-method/roles/qa.yaml +8 -0
  363. package/references/openteams/examples/bmad-method/roles/quick-flow-dev.yaml +8 -0
  364. package/references/openteams/examples/bmad-method/roles/scrum-master.yaml +9 -0
  365. package/references/openteams/examples/bmad-method/roles/tech-writer.yaml +8 -0
  366. package/references/openteams/examples/bmad-method/roles/ux-designer.yaml +8 -0
  367. package/references/openteams/examples/bmad-method/team.yaml +161 -0
  368. package/references/openteams/examples/get-shit-done/prompts/codebase-mapper/ROLE.md +17 -0
  369. package/references/openteams/examples/get-shit-done/prompts/codebase-mapper/SOUL.md +5 -0
  370. package/references/openteams/examples/get-shit-done/prompts/debugger/ROLE.md +25 -0
  371. package/references/openteams/examples/get-shit-done/prompts/debugger/SOUL.md +5 -0
  372. package/references/openteams/examples/get-shit-done/prompts/executor/ROLE.md +34 -0
  373. package/references/openteams/examples/get-shit-done/prompts/executor/SOUL.md +5 -0
  374. package/references/openteams/examples/get-shit-done/prompts/integration-checker/ROLE.md +18 -0
  375. package/references/openteams/examples/get-shit-done/prompts/integration-checker/SOUL.md +3 -0
  376. package/references/openteams/examples/get-shit-done/prompts/orchestrator/ROLE.md +42 -0
  377. package/references/openteams/examples/get-shit-done/prompts/orchestrator/SOUL.md +5 -0
  378. package/references/openteams/examples/get-shit-done/prompts/phase-researcher/ROLE.md +15 -0
  379. package/references/openteams/examples/get-shit-done/prompts/phase-researcher/SOUL.md +3 -0
  380. package/references/openteams/examples/get-shit-done/prompts/plan-checker/ROLE.md +17 -0
  381. package/references/openteams/examples/get-shit-done/prompts/plan-checker/SOUL.md +3 -0
  382. package/references/openteams/examples/get-shit-done/prompts/planner/ROLE.md +28 -0
  383. package/references/openteams/examples/get-shit-done/prompts/planner/SOUL.md +5 -0
  384. package/references/openteams/examples/get-shit-done/prompts/project-researcher/ROLE.md +16 -0
  385. package/references/openteams/examples/get-shit-done/prompts/project-researcher/SOUL.md +3 -0
  386. package/references/openteams/examples/get-shit-done/prompts/research-synthesizer/ROLE.md +13 -0
  387. package/references/openteams/examples/get-shit-done/prompts/research-synthesizer/SOUL.md +3 -0
  388. package/references/openteams/examples/get-shit-done/prompts/roadmapper/ROLE.md +14 -0
  389. package/references/openteams/examples/get-shit-done/prompts/roadmapper/SOUL.md +3 -0
  390. package/references/openteams/examples/get-shit-done/prompts/verifier/ROLE.md +19 -0
  391. package/references/openteams/examples/get-shit-done/prompts/verifier/SOUL.md +5 -0
  392. package/references/openteams/examples/get-shit-done/roles/codebase-mapper.yaml +8 -0
  393. package/references/openteams/examples/get-shit-done/roles/debugger.yaml +8 -0
  394. package/references/openteams/examples/get-shit-done/roles/executor.yaml +8 -0
  395. package/references/openteams/examples/get-shit-done/roles/integration-checker.yaml +8 -0
  396. package/references/openteams/examples/get-shit-done/roles/orchestrator.yaml +9 -0
  397. package/references/openteams/examples/get-shit-done/roles/phase-researcher.yaml +7 -0
  398. package/references/openteams/examples/get-shit-done/roles/plan-checker.yaml +8 -0
  399. package/references/openteams/examples/get-shit-done/roles/planner.yaml +8 -0
  400. package/references/openteams/examples/get-shit-done/roles/project-researcher.yaml +8 -0
  401. package/references/openteams/examples/get-shit-done/roles/research-synthesizer.yaml +7 -0
  402. package/references/openteams/examples/get-shit-done/roles/roadmapper.yaml +7 -0
  403. package/references/openteams/examples/get-shit-done/roles/verifier.yaml +8 -0
  404. package/references/openteams/examples/get-shit-done/team.yaml +154 -0
  405. package/references/openteams/package-lock.json +2181 -0
  406. package/references/openteams/package.json +48 -0
  407. package/references/openteams/schema/role.schema.json +125 -0
  408. package/references/openteams/schema/team.schema.json +284 -0
  409. package/references/openteams/src/cli/agent.ts +104 -0
  410. package/references/openteams/src/cli/cli.test.ts +381 -0
  411. package/references/openteams/src/cli/generate.ts +220 -0
  412. package/references/openteams/src/cli/message.ts +241 -0
  413. package/references/openteams/src/cli/task.ts +154 -0
  414. package/references/openteams/src/cli/team.ts +104 -0
  415. package/references/openteams/src/cli/template.ts +207 -0
  416. package/references/openteams/src/cli.ts +45 -0
  417. package/references/openteams/src/db/database.test.ts +185 -0
  418. package/references/openteams/src/db/database.ts +240 -0
  419. package/references/openteams/src/generators/agent-prompt-generator.test.ts +332 -0
  420. package/references/openteams/src/generators/agent-prompt-generator.ts +521 -0
  421. package/references/openteams/src/generators/package-generator.test.ts +129 -0
  422. package/references/openteams/src/generators/package-generator.ts +102 -0
  423. package/references/openteams/src/generators/skill-generator.test.ts +246 -0
  424. package/references/openteams/src/generators/skill-generator.ts +374 -0
  425. package/references/openteams/src/index.ts +104 -0
  426. package/references/openteams/src/services/agent-service.test.ts +158 -0
  427. package/references/openteams/src/services/agent-service.ts +84 -0
  428. package/references/openteams/src/services/communication-service.test.ts +455 -0
  429. package/references/openteams/src/services/communication-service.ts +371 -0
  430. package/references/openteams/src/services/message-service.test.ts +342 -0
  431. package/references/openteams/src/services/message-service.ts +203 -0
  432. package/references/openteams/src/services/task-service.test.ts +434 -0
  433. package/references/openteams/src/services/task-service.ts +239 -0
  434. package/references/openteams/src/services/team-service.test.ts +181 -0
  435. package/references/openteams/src/services/team-service.ts +139 -0
  436. package/references/openteams/src/services/template-service.test.ts +306 -0
  437. package/references/openteams/src/services/template-service.ts +182 -0
  438. package/references/openteams/src/spawner/acp-factory.ts +96 -0
  439. package/references/openteams/src/spawner/interface.ts +31 -0
  440. package/references/openteams/src/spawner/mock.test.ts +93 -0
  441. package/references/openteams/src/spawner/mock.ts +59 -0
  442. package/references/openteams/src/template/loader.test.ts +1319 -0
  443. package/references/openteams/src/template/loader.ts +698 -0
  444. package/references/openteams/src/template/types.ts +200 -0
  445. package/references/openteams/src/types.ts +205 -0
  446. package/references/openteams/tsconfig.json +18 -0
  447. package/references/openteams/vitest.config.ts +9 -0
  448. package/references/skill-tree/.claude/settings.json +6 -0
  449. package/references/skill-tree/.sudocode/issues.jsonl +11 -0
  450. package/references/skill-tree/.sudocode/specs.jsonl +1 -0
  451. package/references/skill-tree/CLAUDE.md +150 -0
  452. package/references/skill-tree/README.md +324 -0
  453. package/references/skill-tree/docs/GAPS_v1.md +221 -0
  454. package/references/skill-tree/docs/INTEGRATION_PLAN.md +467 -0
  455. package/references/skill-tree/docs/TODOS.md +91 -0
  456. package/references/skill-tree/docs/anthropic_skill_guide.md +1364 -0
  457. package/references/skill-tree/docs/design/federated-skill-trees.md +524 -0
  458. package/references/skill-tree/docs/design/multi-agent-sync.md +759 -0
  459. package/references/skill-tree/docs/scraper/BRAINSTORM.md +583 -0
  460. package/references/skill-tree/docs/scraper/POC_PLAN.md +420 -0
  461. package/references/skill-tree/docs/scraper/README.md +170 -0
  462. package/references/skill-tree/examples/basic-usage.ts +190 -0
  463. package/references/skill-tree/package-lock.json +1509 -0
  464. package/references/skill-tree/package.json +66 -0
  465. package/references/skill-tree/scraper/README.md +123 -0
  466. package/references/skill-tree/scraper/docs/DESIGN.md +683 -0
  467. package/references/skill-tree/scraper/docs/PLAN.md +336 -0
  468. package/references/skill-tree/scraper/drizzle.config.ts +10 -0
  469. package/references/skill-tree/scraper/package-lock.json +6329 -0
  470. package/references/skill-tree/scraper/package.json +68 -0
  471. package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-description.md +7 -0
  472. package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-name.md +7 -0
  473. package/references/skill-tree/scraper/test/fixtures/minimal-skill/SKILL.md +27 -0
  474. package/references/skill-tree/scraper/test/fixtures/skill-json/SKILL.json +21 -0
  475. package/references/skill-tree/scraper/test/fixtures/skill-with-meta/SKILL.md +54 -0
  476. package/references/skill-tree/scraper/test/fixtures/skill-with-meta/_meta.json +24 -0
  477. package/references/skill-tree/scraper/test/fixtures/valid-skill/SKILL.md +93 -0
  478. package/references/skill-tree/scraper/test/fixtures/valid-skill/_meta.json +22 -0
  479. package/references/skill-tree/scraper/tsup.config.ts +14 -0
  480. package/references/skill-tree/scraper/vitest.config.ts +17 -0
  481. package/references/skill-tree/scripts/convert-to-vitest.ts +166 -0
  482. package/references/skill-tree/skills/skill-writer/SKILL.md +339 -0
  483. package/references/skill-tree/skills/skill-writer/references/examples.md +326 -0
  484. package/references/skill-tree/skills/skill-writer/references/patterns.md +210 -0
  485. package/references/skill-tree/skills/skill-writer/references/quality-checklist.md +123 -0
  486. package/references/skill-tree/test/run-all.ts +106 -0
  487. package/references/skill-tree/test/utils.ts +128 -0
  488. package/references/skill-tree/vitest.config.ts +16 -0
  489. package/src/__tests__/e2e/agent-spawn-visibility.e2e.test.ts +761 -0
  490. package/src/__tests__/e2e/full-agent-conflict-resolution.e2e.test.ts +2 -2
  491. package/src/__tests__/e2e/mcp-thin-client-bridge.e2e.test.ts +304 -0
  492. package/src/__tests__/e2e/mcp-tools-available.e2e.test.ts +324 -0
  493. package/src/__tests__/e2e/multi-agent.e2e.test.ts +5 -5
  494. package/src/__tests__/e2e/spawn-session-streaming.e2e.test.ts +563 -0
  495. package/src/acp/__tests__/integration.test.ts +56 -31
  496. package/src/acp/__tests__/macro-agent.test.ts +16 -7
  497. package/src/acp/macro-agent.ts +170 -36
  498. package/src/acp/types.ts +46 -1
  499. package/src/agent/__tests__/agent-manager.test.ts +228 -2
  500. package/src/agent/agent-manager.ts +809 -285
  501. package/src/agent/types.ts +12 -1
  502. package/src/api/__tests__/server.test.ts +203 -4
  503. package/src/api/server.ts +169 -10
  504. package/src/api/types.ts +3 -1
  505. package/src/auth/__tests__/token.test.ts +100 -0
  506. package/src/auth/index.ts +1 -0
  507. package/src/auth/token.ts +82 -0
  508. package/src/cli/__tests__/acp.test.ts +1 -1
  509. package/src/cli/__tests__/stable-instance-id.test.ts +1 -1
  510. package/src/cli/acp.ts +197 -72
  511. package/src/cli/index.ts +125 -15
  512. package/src/cli/mcp.ts +315 -197
  513. package/src/cli/parse-args.ts +54 -0
  514. package/src/cli/stable-instance-id.ts +14 -0
  515. package/src/config/project-config.ts +214 -27
  516. package/src/index.ts +3 -0
  517. package/src/lifecycle/__tests__/cascade-termination.test.ts +1 -1
  518. package/src/lifecycle/__tests__/handlers.test.ts +53 -0
  519. package/src/lifecycle/handlers/index.ts +25 -8
  520. package/src/lifecycle/types.ts +3 -0
  521. package/src/map/adapter/__tests__/acp-over-map-cancel.test.ts +22 -4
  522. package/src/map/adapter/__tests__/acp-over-map-getmodels.test.ts +355 -0
  523. package/src/map/adapter/__tests__/acp-over-map-history.test.ts +263 -0
  524. package/src/map/adapter/__tests__/acp-over-map-persistence.e2e.test.ts +1 -1
  525. package/src/map/adapter/__tests__/event-broadcast.test.ts +420 -0
  526. package/src/map/adapter/__tests__/event-log.test.ts +527 -0
  527. package/src/map/adapter/__tests__/event-translator.test.ts +3 -3
  528. package/src/map/adapter/__tests__/extensions.test.ts +408 -0
  529. package/src/map/adapter/__tests__/map-adapter.test.ts +99 -0
  530. package/src/map/adapter/__tests__/mcp-bridge.test.ts +1187 -0
  531. package/src/map/adapter/__tests__/multi-client-broadcast.test.ts +711 -0
  532. package/src/map/adapter/__tests__/stream-extensions.test.ts +494 -0
  533. package/src/map/adapter/__tests__/websocket-integration.test.ts +218 -0
  534. package/src/map/adapter/acp-over-map.ts +678 -66
  535. package/src/map/adapter/connection-manager.ts +3 -0
  536. package/src/map/adapter/event-log.ts +208 -0
  537. package/src/map/adapter/event-translator.ts +6 -6
  538. package/src/map/adapter/extensions/agent-lifecycle.ts +267 -0
  539. package/src/map/adapter/extensions/index.ts +96 -0
  540. package/src/map/adapter/extensions/mcp-bridge.ts +995 -0
  541. package/src/map/adapter/extensions/streams.ts +839 -0
  542. package/src/map/adapter/extensions/task.ts +11 -0
  543. package/src/map/adapter/extensions/update-metadata.ts +126 -0
  544. package/src/map/adapter/index.ts +33 -0
  545. package/src/map/adapter/interface.ts +2 -0
  546. package/src/map/adapter/map-adapter.ts +312 -47
  547. package/src/map/adapter/subscription-manager.ts +5 -1
  548. package/src/map/adapter/types.ts +10 -1
  549. package/src/mcp/__tests__/map-client.test.ts +386 -0
  550. package/src/mcp/__tests__/mcp-server-thin-client.test.ts +368 -0
  551. package/src/mcp/__tests__/mcp-server.test.ts +100 -1
  552. package/src/mcp/map-client.ts +177 -0
  553. package/src/mcp/mcp-server.ts +205 -103
  554. package/src/mcp/tools/done.ts +19 -0
  555. package/src/mcp/types.ts +6 -1
  556. package/src/metrics/metrics.ts +1 -1
  557. package/src/monitor/__tests__/stale-agent-flow.integration.test.ts +1 -1
  558. package/src/roles/__tests__/config-loader.test.ts +7 -7
  559. package/src/roles/builtin/coordinator.ts +2 -0
  560. package/src/roles/builtin/integrator.ts +2 -0
  561. package/src/roles/builtin/worker.ts +3 -0
  562. package/src/roles/capabilities.ts +28 -7
  563. package/src/roles/config-loader.ts +8 -7
  564. package/src/roles/registry.ts +2 -2
  565. package/src/roles/types.ts +7 -0
  566. package/src/server/__tests__/combined-server.test.ts +94 -21
  567. package/src/server/combined-server.ts +203 -33
  568. package/src/steering/__tests__/steering-integration.test.ts +1 -1
  569. package/src/store/__tests__/event-store-oob.test.ts +109 -0
  570. package/src/store/__tests__/event-store.test.ts +196 -1
  571. package/src/store/__tests__/instance.test.ts +3 -3
  572. package/src/store/event-store.ts +92 -23
  573. package/src/store/instance.ts +2 -2
  574. package/src/store/types/agents.ts +20 -0
  575. package/src/store/types/events.ts +1 -1
  576. package/src/task/backend/__tests__/create-task-backend.test.ts +225 -0
  577. package/src/task/backend/__tests__/e2e/unified-tool-provider-opentasks.e2e.test.ts +524 -0
  578. package/src/task/backend/__tests__/memory-pull-mode.test.ts +153 -0
  579. package/src/task/backend/__tests__/unified-tool-provider.test.ts +579 -0
  580. package/src/task/backend/index.ts +156 -106
  581. package/src/task/backend/memory.ts +4 -0
  582. package/src/task/backend/opentasks/__tests__/backend.test.ts +968 -0
  583. package/src/task/backend/opentasks/__tests__/daemon-manager.test.ts +406 -0
  584. package/src/task/backend/opentasks/__tests__/mapping.test.ts +84 -0
  585. package/src/task/backend/opentasks/__tests__/opentasks-backend.e2e.test.ts +1338 -0
  586. package/src/task/backend/opentasks/backend.ts +1323 -0
  587. package/src/task/backend/opentasks/client.ts +652 -0
  588. package/src/task/backend/opentasks/daemon-manager.ts +256 -0
  589. package/src/task/backend/opentasks/index.ts +69 -0
  590. package/src/task/backend/opentasks/mapping.ts +94 -0
  591. package/src/task/backend/types.ts +42 -66
  592. package/src/task/backend/unified-tool-provider.ts +779 -0
  593. package/src/teams/CLAUDE.md +180 -0
  594. package/src/teams/__tests__/cross-subsystem.integration.test.ts +1 -1
  595. package/src/teams/__tests__/e2e/workspace-isolation.e2e.test.ts +1263 -0
  596. package/src/teams/__tests__/team-manager.test.ts +814 -0
  597. package/src/teams/__tests__/team-system.test.ts +1291 -8
  598. package/src/teams/index.ts +21 -3
  599. package/src/teams/seed-defaults.ts +79 -0
  600. package/src/teams/team-loader.ts +202 -236
  601. package/src/teams/team-manager.ts +387 -0
  602. package/src/teams/team-runtime.ts +592 -121
  603. package/src/teams/types.ts +99 -200
  604. package/test_fixtures/README.md +2 -3
  605. package/test_fixtures/fixtures/index.ts +0 -3
  606. package/test_fixtures/fixtures/projects/project-with-specs.ts +7 -149
  607. package/test_fixtures/fixtures/repos/index.ts +1 -3
  608. package/test_fixtures/fixtures/repos/temp-repo-factory.ts +0 -116
  609. package/test_fixtures/fixtures/repos/types.ts +0 -11
  610. package/test_fixtures/harness/__tests__/fixtures.test.ts +10 -102
  611. package/test_fixtures/harness/__tests__/temp-repo-and-simulator.test.ts +0 -33
  612. package/test_fixtures/harness/simulator/agent-simulator.ts +4 -4
  613. package/vitest.config.ts +1 -1
  614. package/vitest.e2e.config.ts +1 -1
  615. package/vitest.setup.ts +1 -30
  616. package/.macro-agent/teams/self-driving/prompts/grinder.md +0 -27
  617. package/.macro-agent/teams/self-driving/prompts/judge.md +0 -27
  618. package/.macro-agent/teams/self-driving/prompts/planner.md +0 -33
  619. package/.macro-agent/teams/self-driving/roles/grinder.yaml +0 -17
  620. package/.macro-agent/teams/self-driving/roles/judge.yaml +0 -24
  621. package/.macro-agent/teams/self-driving/roles/planner.yaml +0 -18
  622. package/.macro-agent/teams/self-driving/team.yaml +0 -103
  623. package/.macro-agent/teams/structured/prompts/developer.md +0 -26
  624. package/.macro-agent/teams/structured/prompts/lead.md +0 -25
  625. package/.macro-agent/teams/structured/prompts/reviewer.md +0 -24
  626. package/.macro-agent/teams/structured/roles/developer.yaml +0 -12
  627. package/.macro-agent/teams/structured/roles/lead.yaml +0 -11
  628. package/.macro-agent/teams/structured/roles/reviewer.yaml +0 -19
  629. package/.macro-agent/teams/structured/team.yaml +0 -89
  630. package/docs/sudocode-integration.md +0 -383
  631. package/src/task/backend/__tests__/backend-parity.test.ts +0 -451
  632. package/src/task/backend/__tests__/tool-provider-edge-cases.test.ts +0 -430
  633. package/src/task/backend/__tests__/tool-provider.test.ts +0 -983
  634. package/src/task/backend/sudocode/__tests__/backend-edge-cases.test.ts +0 -575
  635. package/src/task/backend/sudocode/__tests__/backend.test.ts +0 -1194
  636. package/src/task/backend/sudocode/__tests__/client-integration.test.ts +0 -418
  637. package/src/task/backend/sudocode/__tests__/client.test.ts +0 -345
  638. package/src/task/backend/sudocode/__tests__/e2e/backend.e2e.test.ts +0 -753
  639. package/src/task/backend/sudocode/__tests__/e2e/server-client.e2e.test.ts +0 -680
  640. package/src/task/backend/sudocode/__tests__/e2e-workflow.test.ts +0 -666
  641. package/src/task/backend/sudocode/__tests__/integration/standalone-client.integration.test.ts +0 -396
  642. package/src/task/backend/sudocode/__tests__/integration/sudocode-cli.integration.test.ts +0 -328
  643. package/src/task/backend/sudocode/__tests__/integration/test-utils.ts +0 -175
  644. package/src/task/backend/sudocode/__tests__/mapping-edge-cases.test.ts +0 -265
  645. package/src/task/backend/sudocode/__tests__/server-client.test.ts +0 -675
  646. package/src/task/backend/sudocode/__tests__/sync-policy-edge-cases.test.ts +0 -521
  647. package/src/task/backend/sudocode/__tests__/sync-policy.test.ts +0 -519
  648. package/src/task/backend/sudocode/__tests__/tools.test.ts +0 -471
  649. package/src/task/backend/sudocode/backend.ts +0 -1237
  650. package/src/task/backend/sudocode/client.ts +0 -515
  651. package/src/task/backend/sudocode/index.ts +0 -120
  652. package/src/task/backend/sudocode/mapping.ts +0 -93
  653. package/src/task/backend/sudocode/server-client.ts +0 -522
  654. package/src/task/backend/sudocode/standalone-client.ts +0 -623
  655. package/src/task/backend/sudocode/sync-policy.ts +0 -387
  656. package/src/task/backend/sudocode/tools.ts +0 -896
  657. package/src/task/backend/tool-provider.ts +0 -506
  658. package/test_fixtures/fixtures/sudocode/index.ts +0 -29
  659. package/test_fixtures/fixtures/sudocode/issues.ts +0 -185
  660. package/test_fixtures/fixtures/sudocode/specs.ts +0 -159
@@ -0,0 +1,1323 @@
1
+ /**
2
+ * OpenTasksTaskBackend Implementation
3
+ *
4
+ * Implements TaskBackend using OpenTasks as the source of truth for task storage,
5
+ * with EventStore for local event tracking and subscriptions.
6
+ *
7
+ * Tasks are stored as OpenTasks issues. Blocking dependencies use OpenTasks
8
+ * 'blocks' edges. The pull model uses OpenTasks' ready query and claimed_by field.
9
+ *
10
+ * @module task/backend/opentasks/backend
11
+ */
12
+
13
+ import { nanoid } from "nanoid";
14
+ import type { EventStore } from "../../../store/event-store.js";
15
+ import type {
16
+ Task,
17
+ TaskStatus,
18
+ AgentId,
19
+ TaskId,
20
+ AgentHistoryEntry,
21
+ } from "../../../store/types/index.js";
22
+ import type {
23
+ TaskBackend,
24
+ ExtendedTask,
25
+ CreateTaskOptions,
26
+ UpdateTaskOptions,
27
+ TaskFilter,
28
+ TaskOutputs,
29
+ TaskError,
30
+ SubtaskStatus,
31
+ AssignOptions,
32
+ ClaimFilter,
33
+ TaskChangeCallback,
34
+ TaskChangeEvent,
35
+ Unsubscribe,
36
+ } from "../types.js";
37
+ import type { OpenTasksClient, OpenTasksIssue } from "./client.js";
38
+ import { mapOpenTasksStatus, mapTaskStatus, isIssueComplete } from "./mapping.js";
39
+
40
+ // Valid status transitions
41
+ const VALID_STATUS_TRANSITIONS: Record<TaskStatus, TaskStatus[]> = {
42
+ pending: ["assigned", "in_progress", "failed"],
43
+ assigned: ["in_progress", "pending", "failed"],
44
+ in_progress: ["completed", "failed", "pending"],
45
+ completed: [],
46
+ failed: ["pending"],
47
+ };
48
+
49
+ /**
50
+ * Error thrown by OpenTasksTaskBackend operations
51
+ */
52
+ export class OpenTasksBackendError extends Error {
53
+ constructor(
54
+ message: string,
55
+ public readonly code: string,
56
+ public readonly taskId?: TaskId
57
+ ) {
58
+ super(message);
59
+ this.name = "OpenTasksBackendError";
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Configuration for OpenTasksTaskBackend
65
+ */
66
+ export interface OpenTasksBackendConfig {
67
+ /** Path to daemon socket (auto-discovered if not set) */
68
+ socketPath?: string;
69
+
70
+ /** Whether to sync status changes to OpenTasks (default: true) */
71
+ syncStatus?: boolean;
72
+
73
+ /** Source identifier for issues created by this backend (default: "macro-agent") */
74
+ sourceLabel?: string;
75
+ }
76
+
77
+ const DEFAULT_CONFIG: Required<OpenTasksBackendConfig> = {
78
+ socketPath: "",
79
+ syncStatus: true,
80
+ sourceLabel: "macro-agent",
81
+ };
82
+
83
+ /**
84
+ * OpenTasksTaskBackend implements TaskBackend using:
85
+ * - OpenTasks daemon for issue storage and graph relationships
86
+ * - EventStore for local event tracking and subscriptions
87
+ *
88
+ * Key features:
89
+ * - Tasks are stored as OpenTasks issues with macro-agent metadata
90
+ * - Blocking dependencies use OpenTasks 'blocks' edges
91
+ * - Pull model uses OpenTasks ready query and claimed_by
92
+ * - Bidirectional ID mapping (task_id <-> issue_id) via metadata
93
+ */
94
+ export class OpenTasksTaskBackend implements TaskBackend {
95
+ private readonly config: Required<OpenTasksBackendConfig>;
96
+ private closed = false;
97
+
98
+ /** Map from macro-agent task ID to OpenTasks issue ID */
99
+ private readonly taskToIssue = new Map<TaskId, string>();
100
+
101
+ /** Map from OpenTasks issue ID to macro-agent task ID */
102
+ private readonly issueToTask = new Map<string, TaskId>();
103
+
104
+ constructor(
105
+ private readonly eventStore: EventStore,
106
+ private readonly client: OpenTasksClient,
107
+ config?: Partial<OpenTasksBackendConfig>
108
+ ) {
109
+ this.config = { ...DEFAULT_CONFIG, ...config };
110
+ }
111
+
112
+ /**
113
+ * Throw if the backend has been closed.
114
+ */
115
+ private ensureOpen(): void {
116
+ if (this.closed) {
117
+ throw new OpenTasksBackendError("Backend is closed", "BACKEND_CLOSED");
118
+ }
119
+ }
120
+
121
+ // ─────────────────────────────────────────────────────────────────────────────
122
+ // Lifecycle
123
+ // ─────────────────────────────────────────────────────────────────────────────
124
+
125
+ async close(): Promise<void> {
126
+ this.closed = true;
127
+ }
128
+
129
+ async create(options: CreateTaskOptions): Promise<ExtendedTask> {
130
+ this.ensureOpen();
131
+ const taskId = `task_${nanoid(12)}`;
132
+
133
+ // Resolve parent issue ID if parent task specified
134
+ let parentIssueId: string | undefined;
135
+ if (options.parent_task) {
136
+ parentIssueId = this.taskToIssue.get(options.parent_task);
137
+ if (!parentIssueId) {
138
+ // Check EventStore as fallback
139
+ const parent = this.eventStore.getTask(options.parent_task);
140
+ if (!parent) {
141
+ throw new OpenTasksBackendError(
142
+ `Parent task not found: ${options.parent_task}`,
143
+ "PARENT_TASK_NOT_FOUND",
144
+ options.parent_task
145
+ );
146
+ }
147
+ }
148
+ }
149
+
150
+ // Create issue in OpenTasks
151
+ const issue = await this.client.createIssue({
152
+ title: options.description,
153
+ status: "open",
154
+ tags: options.tags,
155
+ parent_id: parentIssueId,
156
+ metadata: {
157
+ macro_agent_task_id: taskId,
158
+ created_by: options.created_by,
159
+ source: this.config.sourceLabel,
160
+ },
161
+ });
162
+
163
+ // Store bidirectional mapping
164
+ this.taskToIssue.set(taskId, issue.id);
165
+ this.issueToTask.set(issue.id, taskId);
166
+
167
+ // Emit to EventStore for local tracking
168
+ this.eventStore.emit({
169
+ type: "task",
170
+ source: { agent_id: options.created_by },
171
+ payload: {
172
+ task_id: taskId,
173
+ action: "created",
174
+ details: {
175
+ description: options.description,
176
+ parent_task: options.parent_task,
177
+ tags: options.tags,
178
+ external_id: issue.id,
179
+ },
180
+ },
181
+ });
182
+
183
+ // Update parent subtasks in EventStore
184
+ if (options.parent_task) {
185
+ this.eventStore.emit({
186
+ type: "task",
187
+ source: { agent_id: options.created_by },
188
+ payload: {
189
+ task_id: options.parent_task,
190
+ action: "status_change",
191
+ details: { subtask_added: taskId },
192
+ },
193
+ });
194
+ }
195
+
196
+ // Return from EventStore (which has the canonical local state)
197
+ const task = this.eventStore.getTask(taskId)!;
198
+ return this.toExtendedTask(task);
199
+ }
200
+
201
+ async get(id: TaskId): Promise<ExtendedTask | null> {
202
+ // EventStore is the local mirror with richer state
203
+ // (assigned status, outputs, agent_history, etc.)
204
+ const task = this.eventStore.getTask(id);
205
+ if (!task) return null;
206
+ return this.toExtendedTask(task);
207
+ }
208
+
209
+ async update(id: TaskId, updates: UpdateTaskOptions): Promise<ExtendedTask> {
210
+ this.ensureOpen();
211
+ const task = this.eventStore.getTask(id);
212
+ if (!task) {
213
+ throw new OpenTasksBackendError(
214
+ `Task not found: ${id}`,
215
+ "TASK_NOT_FOUND",
216
+ id
217
+ );
218
+ }
219
+
220
+ const source = task.assigned_agent ?? task.created_by;
221
+
222
+ // Handle status update with validation
223
+ if (updates.status !== undefined) {
224
+ const validTransitions = VALID_STATUS_TRANSITIONS[task.status];
225
+ if (!validTransitions.includes(updates.status)) {
226
+ throw new OpenTasksBackendError(
227
+ `Invalid status transition: ${task.status} -> ${updates.status}`,
228
+ "INVALID_STATUS_TRANSITION",
229
+ id
230
+ );
231
+ }
232
+
233
+ this.eventStore.emit({
234
+ type: "task",
235
+ source: { agent_id: source },
236
+ payload: {
237
+ task_id: id,
238
+ action: "status_change",
239
+ details: { status: updates.status },
240
+ },
241
+ });
242
+
243
+ // Sync to OpenTasks
244
+ await this.syncStatusToOpenTasks(id, updates.status);
245
+ }
246
+
247
+ // Handle other updates
248
+ if (updates.outputs !== undefined) {
249
+ this.eventStore.emit({
250
+ type: "task",
251
+ source: { agent_id: source },
252
+ payload: {
253
+ task_id: id,
254
+ action: "status_change",
255
+ details: { outputs: updates.outputs },
256
+ },
257
+ });
258
+ }
259
+
260
+ if (updates.artifacts !== undefined) {
261
+ this.eventStore.emit({
262
+ type: "task",
263
+ source: { agent_id: source },
264
+ payload: {
265
+ task_id: id,
266
+ action: "status_change",
267
+ details: { artifacts: updates.artifacts },
268
+ },
269
+ });
270
+ }
271
+
272
+ if (updates.description !== undefined) {
273
+ this.eventStore.emit({
274
+ type: "task",
275
+ source: { agent_id: source },
276
+ payload: {
277
+ task_id: id,
278
+ action: "status_change",
279
+ details: { description: updates.description },
280
+ },
281
+ });
282
+
283
+ // Sync description to OpenTasks
284
+ const issueId = this.taskToIssue.get(id);
285
+ if (issueId) {
286
+ await this.client.updateIssue(issueId, {
287
+ title: updates.description,
288
+ });
289
+ }
290
+ }
291
+
292
+ const updated = this.eventStore.getTask(id)!;
293
+ return this.toExtendedTask(updated);
294
+ }
295
+
296
+ async delete(id: TaskId): Promise<void> {
297
+ this.ensureOpen();
298
+ const issueId = this.taskToIssue.get(id);
299
+ if (issueId) {
300
+ await this.client.deleteIssue(issueId);
301
+ this.taskToIssue.delete(id);
302
+ this.issueToTask.delete(issueId);
303
+ }
304
+ }
305
+
306
+ // ─────────────────────────────────────────────────────────────────────────────
307
+ // Status Transitions
308
+ // ─────────────────────────────────────────────────────────────────────────────
309
+
310
+ async assign(
311
+ id: TaskId,
312
+ agentId: AgentId,
313
+ options?: AssignOptions
314
+ ): Promise<void> {
315
+ this.ensureOpen();
316
+ const task = this.eventStore.getTask(id);
317
+ if (!task) {
318
+ throw new OpenTasksBackendError(
319
+ `Task not found: ${id}`,
320
+ "TASK_NOT_FOUND",
321
+ id
322
+ );
323
+ }
324
+
325
+ this.eventStore.emit({
326
+ type: "task",
327
+ source: { agent_id: agentId },
328
+ payload: {
329
+ task_id: id,
330
+ action: "assigned",
331
+ details: {
332
+ agent_id: agentId,
333
+ role: options?.role,
334
+ },
335
+ },
336
+ });
337
+
338
+ // Sync assignee to OpenTasks
339
+ const issueId = this.taskToIssue.get(id);
340
+ if (issueId) {
341
+ await this.client.updateIssue(issueId, {
342
+ assignee: agentId,
343
+ metadata: { claimed_by: agentId },
344
+ });
345
+ }
346
+ }
347
+
348
+ async unassign(id: TaskId): Promise<void> {
349
+ this.ensureOpen();
350
+ const task = this.eventStore.getTask(id);
351
+ if (!task) {
352
+ throw new OpenTasksBackendError(
353
+ `Task not found: ${id}`,
354
+ "TASK_NOT_FOUND",
355
+ id
356
+ );
357
+ }
358
+
359
+ if (!task.assigned_agent) {
360
+ throw new OpenTasksBackendError(
361
+ `Task is not assigned: ${id}`,
362
+ "TASK_NOT_ASSIGNED",
363
+ id
364
+ );
365
+ }
366
+
367
+ this.eventStore.emit({
368
+ type: "task",
369
+ source: { agent_id: task.assigned_agent },
370
+ payload: {
371
+ task_id: id,
372
+ action: "unassigned",
373
+ details: { agent_id: task.assigned_agent },
374
+ },
375
+ });
376
+
377
+ // Clear assignee in OpenTasks
378
+ const issueId = this.taskToIssue.get(id);
379
+ if (issueId) {
380
+ await this.client.updateIssue(issueId, {
381
+ assignee: null,
382
+ metadata: { claimed_by: null },
383
+ });
384
+ }
385
+ }
386
+
387
+ async start(id: TaskId): Promise<void> {
388
+ this.ensureOpen();
389
+ const task = this.eventStore.getTask(id);
390
+ if (!task) {
391
+ throw new OpenTasksBackendError(
392
+ `Task not found: ${id}`,
393
+ "TASK_NOT_FOUND",
394
+ id
395
+ );
396
+ }
397
+
398
+ const validTransitions = VALID_STATUS_TRANSITIONS[task.status];
399
+ if (!validTransitions.includes("in_progress")) {
400
+ throw new OpenTasksBackendError(
401
+ `Invalid status transition: ${task.status} -> in_progress`,
402
+ "INVALID_STATUS_TRANSITION",
403
+ id
404
+ );
405
+ }
406
+
407
+ this.eventStore.emit({
408
+ type: "task",
409
+ source: { agent_id: task.assigned_agent ?? task.created_by },
410
+ payload: {
411
+ task_id: id,
412
+ action: "status_change",
413
+ details: { status: "in_progress" },
414
+ },
415
+ });
416
+
417
+ await this.syncStatusToOpenTasks(id, "in_progress");
418
+ }
419
+
420
+ async complete(id: TaskId, outputs?: TaskOutputs): Promise<void> {
421
+ this.ensureOpen();
422
+ const task = this.eventStore.getTask(id);
423
+ if (!task) {
424
+ throw new OpenTasksBackendError(
425
+ `Task not found: ${id}`,
426
+ "TASK_NOT_FOUND",
427
+ id
428
+ );
429
+ }
430
+
431
+ const validTransitions = VALID_STATUS_TRANSITIONS[task.status];
432
+ if (!validTransitions.includes("completed")) {
433
+ throw new OpenTasksBackendError(
434
+ `Invalid status transition: ${task.status} -> completed`,
435
+ "INVALID_STATUS_TRANSITION",
436
+ id
437
+ );
438
+ }
439
+
440
+ const agent = task.assigned_agent ?? task.created_by;
441
+
442
+ // Store outputs
443
+ if (outputs) {
444
+ const outputsToStore: Record<string, unknown> = {
445
+ ...(outputs.data ?? {}),
446
+ };
447
+ if (outputs.summary !== undefined) {
448
+ outputsToStore.summary = outputs.summary;
449
+ }
450
+ if (Object.keys(outputsToStore).length > 0) {
451
+ this.eventStore.emit({
452
+ type: "task",
453
+ source: { agent_id: agent },
454
+ payload: {
455
+ task_id: id,
456
+ action: "status_change",
457
+ details: { outputs: outputsToStore },
458
+ },
459
+ });
460
+ }
461
+ if (outputs.artifacts) {
462
+ this.eventStore.emit({
463
+ type: "task",
464
+ source: { agent_id: agent },
465
+ payload: {
466
+ task_id: id,
467
+ action: "status_change",
468
+ details: { artifacts: outputs.artifacts },
469
+ },
470
+ });
471
+ }
472
+ }
473
+
474
+ this.eventStore.emit({
475
+ type: "task",
476
+ source: { agent_id: agent },
477
+ payload: {
478
+ task_id: id,
479
+ action: "completed",
480
+ details: {},
481
+ },
482
+ });
483
+
484
+ // Close the issue in OpenTasks
485
+ await this.syncStatusToOpenTasks(id, "completed");
486
+ }
487
+
488
+ async fail(id: TaskId, error: TaskError): Promise<void> {
489
+ this.ensureOpen();
490
+ const task = this.eventStore.getTask(id);
491
+ if (!task) {
492
+ throw new OpenTasksBackendError(
493
+ `Task not found: ${id}`,
494
+ "TASK_NOT_FOUND",
495
+ id
496
+ );
497
+ }
498
+
499
+ const validTransitions = VALID_STATUS_TRANSITIONS[task.status];
500
+ if (!validTransitions.includes("failed")) {
501
+ throw new OpenTasksBackendError(
502
+ `Invalid status transition: ${task.status} -> failed`,
503
+ "INVALID_STATUS_TRANSITION",
504
+ id
505
+ );
506
+ }
507
+
508
+ const agent = task.assigned_agent ?? task.created_by;
509
+
510
+ // Store error in outputs
511
+ this.eventStore.emit({
512
+ type: "task",
513
+ source: { agent_id: agent },
514
+ payload: {
515
+ task_id: id,
516
+ action: "status_change",
517
+ details: {
518
+ outputs: {
519
+ error: {
520
+ message: error.message,
521
+ code: error.code,
522
+ details: error.details,
523
+ },
524
+ },
525
+ },
526
+ },
527
+ });
528
+
529
+ this.eventStore.emit({
530
+ type: "task",
531
+ source: { agent_id: agent },
532
+ payload: {
533
+ task_id: id,
534
+ action: "failed",
535
+ details: {},
536
+ },
537
+ });
538
+
539
+ // Close in OpenTasks with error metadata
540
+ const issueId = this.taskToIssue.get(id);
541
+ if (issueId) {
542
+ await this.client.updateIssue(issueId, {
543
+ status: "closed",
544
+ metadata: {
545
+ macro_agent_failed: true,
546
+ macro_agent_error: error.message,
547
+ },
548
+ });
549
+ }
550
+ }
551
+
552
+ // ─────────────────────────────────────────────────────────────────────────────
553
+ // Queries
554
+ // ─────────────────────────────────────────────────────────────────────────────
555
+
556
+ async list(filter?: TaskFilter): Promise<ExtendedTask[]> {
557
+ let tasks = this.eventStore.listTasks();
558
+
559
+ if (filter) {
560
+ if (filter.status) {
561
+ const statuses = Array.isArray(filter.status)
562
+ ? filter.status
563
+ : [filter.status];
564
+ tasks = tasks.filter((t) => statuses.includes(t.status));
565
+ }
566
+
567
+ if (filter.assigned_agent) {
568
+ tasks = tasks.filter((t) => t.assigned_agent === filter.assigned_agent);
569
+ }
570
+
571
+ if (filter.parent_task) {
572
+ tasks = tasks.filter((t) => t.parent_task === filter.parent_task);
573
+ }
574
+
575
+ if (filter.created_by) {
576
+ tasks = tasks.filter((t) => t.created_by === filter.created_by);
577
+ }
578
+
579
+ if (filter.rootTasksOnly) {
580
+ tasks = tasks.filter((t) => !t.parent_task);
581
+ }
582
+
583
+ if (filter.tags && filter.tags.length > 0) {
584
+ const filterTags = new Set(filter.tags);
585
+ tasks = tasks.filter(
586
+ (t) => t.tags?.some((tag) => filterTags.has(tag))
587
+ );
588
+ }
589
+ }
590
+
591
+ // Compute isBlocked using OpenTasks graph for mapped tasks,
592
+ // EventStore blockers for unmapped tasks
593
+ const extended = await Promise.all(
594
+ tasks.map((t) => this.toExtendedTaskAsync(t))
595
+ );
596
+
597
+ if (!filter?.includeBlocked) {
598
+ return extended.filter((t) => !t.isBlocked);
599
+ }
600
+
601
+ return extended;
602
+ }
603
+
604
+ async listReady(filter?: TaskFilter): Promise<ExtendedTask[]> {
605
+ return this.list({
606
+ ...filter,
607
+ status: filter?.status ?? ["pending", "assigned"],
608
+ includeBlocked: false,
609
+ });
610
+ }
611
+
612
+ async getChildren(parentId: TaskId): Promise<ExtendedTask[]> {
613
+ const tasks = this.eventStore.listTasks();
614
+ const children = tasks.filter((t) => t.parent_task === parentId);
615
+ return Promise.all(children.map((t) => this.toExtendedTaskAsync(t)));
616
+ }
617
+
618
+ async getSubtaskStatus(parentId: TaskId): Promise<SubtaskStatus> {
619
+ const children = await this.getChildren(parentId);
620
+
621
+ const status: SubtaskStatus = {
622
+ total: children.length,
623
+ pending: 0,
624
+ assigned: 0,
625
+ in_progress: 0,
626
+ completed: 0,
627
+ failed: 0,
628
+ allCompleted: false,
629
+ anyFailed: false,
630
+ };
631
+
632
+ for (const task of children) {
633
+ switch (task.status) {
634
+ case "pending":
635
+ status.pending++;
636
+ break;
637
+ case "assigned":
638
+ status.assigned++;
639
+ break;
640
+ case "in_progress":
641
+ status.in_progress++;
642
+ break;
643
+ case "completed":
644
+ status.completed++;
645
+ break;
646
+ case "failed":
647
+ status.failed++;
648
+ break;
649
+ }
650
+ }
651
+
652
+ status.allCompleted =
653
+ status.total > 0 && status.completed === status.total;
654
+ status.anyFailed = status.failed > 0;
655
+
656
+ return status;
657
+ }
658
+
659
+ // ─────────────────────────────────────────────────────────────────────────────
660
+ // Hierarchy
661
+ // ─────────────────────────────────────────────────────────────────────────────
662
+
663
+ async createSubtask(
664
+ parentId: TaskId,
665
+ options: CreateTaskOptions
666
+ ): Promise<ExtendedTask> {
667
+ return this.create({
668
+ ...options,
669
+ parent_task: parentId,
670
+ });
671
+ }
672
+
673
+ // ─────────────────────────────────────────────────────────────────────────────
674
+ // Dependencies (via OpenTasks edges)
675
+ // ─────────────────────────────────────────────────────────────────────────────
676
+
677
+ async addBlocker(taskId: TaskId, blockerId: TaskId): Promise<void> {
678
+ this.ensureOpen();
679
+ const task = this.eventStore.getTask(taskId);
680
+ if (!task) {
681
+ throw new OpenTasksBackendError(
682
+ `Task not found: ${taskId}`,
683
+ "TASK_NOT_FOUND",
684
+ taskId
685
+ );
686
+ }
687
+
688
+ const blocker = this.eventStore.getTask(blockerId);
689
+ if (!blocker) {
690
+ throw new OpenTasksBackendError(
691
+ `Blocker task not found: ${blockerId}`,
692
+ "TASK_NOT_FOUND",
693
+ blockerId
694
+ );
695
+ }
696
+
697
+ // Record in EventStore
698
+ this.eventStore.emit({
699
+ type: "task",
700
+ source: { agent_id: task.assigned_agent ?? task.created_by },
701
+ payload: {
702
+ task_id: taskId,
703
+ action: "blocker_added",
704
+ details: { blocker_id: blockerId },
705
+ },
706
+ });
707
+
708
+ // Create 'blocks' edge in OpenTasks if both tasks are mapped
709
+ const blockerIssueId = this.taskToIssue.get(blockerId);
710
+ const taskIssueId = this.taskToIssue.get(taskId);
711
+ if (blockerIssueId && taskIssueId) {
712
+ await this.client.createEdge(blockerIssueId, taskIssueId, "blocks");
713
+ }
714
+ }
715
+
716
+ async removeBlocker(taskId: TaskId, blockerId: TaskId): Promise<void> {
717
+ this.ensureOpen();
718
+ const task = this.eventStore.getTask(taskId);
719
+ if (!task) {
720
+ throw new OpenTasksBackendError(
721
+ `Task not found: ${taskId}`,
722
+ "TASK_NOT_FOUND",
723
+ taskId
724
+ );
725
+ }
726
+
727
+ // Record in EventStore
728
+ this.eventStore.emit({
729
+ type: "task",
730
+ source: { agent_id: task.assigned_agent ?? task.created_by },
731
+ payload: {
732
+ task_id: taskId,
733
+ action: "blocker_removed",
734
+ details: { blocker_id: blockerId },
735
+ },
736
+ });
737
+
738
+ // Remove 'blocks' edge in OpenTasks if both are mapped
739
+ const blockerIssueId = this.taskToIssue.get(blockerId);
740
+ const taskIssueId = this.taskToIssue.get(taskId);
741
+ if (blockerIssueId && taskIssueId) {
742
+ await this.client.removeEdge(blockerIssueId, taskIssueId, "blocks");
743
+ }
744
+ }
745
+
746
+ async getBlockers(taskId: TaskId): Promise<ExtendedTask[]> {
747
+ const task = this.eventStore.getTask(taskId);
748
+ if (!task) {
749
+ throw new OpenTasksBackendError(
750
+ `Task not found: ${taskId}`,
751
+ "TASK_NOT_FOUND",
752
+ taskId
753
+ );
754
+ }
755
+
756
+ // Try OpenTasks first for mapped tasks
757
+ const issueId = this.taskToIssue.get(taskId);
758
+ if (issueId) {
759
+ try {
760
+ const blockerSummaries = await this.client.getBlockers(issueId);
761
+ const blockers: ExtendedTask[] = [];
762
+ for (const summary of blockerSummaries) {
763
+ const blockerTaskId = this.issueToTask.get(summary.id);
764
+ if (blockerTaskId) {
765
+ const blockerTask = this.eventStore.getTask(blockerTaskId);
766
+ if (blockerTask) {
767
+ blockers.push(this.toExtendedTask(blockerTask));
768
+ }
769
+ }
770
+ }
771
+ return blockers;
772
+ } catch {
773
+ // Fall through to EventStore
774
+ }
775
+ }
776
+
777
+ // Fallback: use EventStore blockers
778
+ const blockerIds = task.blockers ?? [];
779
+ const blockers: ExtendedTask[] = [];
780
+ for (const blockerId of blockerIds) {
781
+ const blocker = this.eventStore.getTask(blockerId);
782
+ if (blocker) {
783
+ blockers.push(this.toExtendedTask(blocker));
784
+ }
785
+ }
786
+ return blockers;
787
+ }
788
+
789
+ async getBlocking(taskId: TaskId): Promise<ExtendedTask[]> {
790
+ const task = this.eventStore.getTask(taskId);
791
+ if (!task) {
792
+ throw new OpenTasksBackendError(
793
+ `Task not found: ${taskId}`,
794
+ "TASK_NOT_FOUND",
795
+ taskId
796
+ );
797
+ }
798
+
799
+ // Try OpenTasks first for mapped tasks
800
+ const issueId = this.taskToIssue.get(taskId);
801
+ if (issueId) {
802
+ try {
803
+ const blockingSummaries = await this.client.getBlocking(issueId);
804
+ const blocking: ExtendedTask[] = [];
805
+ for (const summary of blockingSummaries) {
806
+ const blockedTaskId = this.issueToTask.get(summary.id);
807
+ if (blockedTaskId) {
808
+ const blockedTask = this.eventStore.getTask(blockedTaskId);
809
+ if (blockedTask) {
810
+ blocking.push(this.toExtendedTask(blockedTask));
811
+ }
812
+ }
813
+ }
814
+ return blocking;
815
+ } catch {
816
+ // Fall through to EventStore
817
+ }
818
+ }
819
+
820
+ // Fallback: scan EventStore
821
+ const allTasks = this.eventStore.listTasks();
822
+ const blocking = allTasks.filter((t) => t.blockers?.includes(taskId));
823
+ return blocking.map((t) => this.toExtendedTask(t));
824
+ }
825
+
826
+ // ─────────────────────────────────────────────────────────────────────────────
827
+ // Pull Model (Claim/Unclaim)
828
+ // ─────────────────────────────────────────────────────────────────────────────
829
+
830
+ async claim(
831
+ agentId: AgentId,
832
+ filter?: ClaimFilter
833
+ ): Promise<ExtendedTask | null> {
834
+ this.ensureOpen();
835
+ const candidates = await this.listClaimable(filter);
836
+
837
+ if (candidates.length === 0) {
838
+ return null;
839
+ }
840
+
841
+ const task = candidates[0];
842
+
843
+ // Re-check for contention
844
+ const current = this.eventStore.getTask(task.id);
845
+ if (!current || current.status !== "pending" || current.assigned_agent) {
846
+ return null;
847
+ }
848
+
849
+ // Assign locally
850
+ this.eventStore.emit({
851
+ type: "task",
852
+ source: { agent_id: agentId },
853
+ payload: {
854
+ task_id: task.id,
855
+ action: "assigned",
856
+ details: { agent_id: agentId },
857
+ },
858
+ });
859
+
860
+ // Claim in OpenTasks
861
+ const issueId = this.taskToIssue.get(task.id);
862
+ if (issueId) {
863
+ await this.client.updateIssue(issueId, {
864
+ assignee: agentId,
865
+ metadata: { claimed_by: agentId },
866
+ });
867
+ }
868
+
869
+ const assigned = this.eventStore.getTask(task.id)!;
870
+ return this.toExtendedTask(assigned);
871
+ }
872
+
873
+ async unclaim(taskId: TaskId): Promise<void> {
874
+ this.ensureOpen();
875
+ const task = this.eventStore.getTask(taskId);
876
+ if (!task) {
877
+ throw new OpenTasksBackendError(
878
+ `Task not found: ${taskId}`,
879
+ "TASK_NOT_FOUND",
880
+ taskId
881
+ );
882
+ }
883
+
884
+ if (!task.assigned_agent) {
885
+ throw new OpenTasksBackendError(
886
+ `Task is not assigned: ${taskId}`,
887
+ "TASK_NOT_ASSIGNED",
888
+ taskId
889
+ );
890
+ }
891
+
892
+ this.eventStore.emit({
893
+ type: "task",
894
+ source: { agent_id: task.assigned_agent },
895
+ payload: {
896
+ task_id: taskId,
897
+ action: "unassigned",
898
+ details: { agent_id: task.assigned_agent },
899
+ },
900
+ });
901
+
902
+ // Unclaim in OpenTasks
903
+ const issueId = this.taskToIssue.get(taskId);
904
+ if (issueId) {
905
+ await this.client.updateIssue(issueId, {
906
+ assignee: null,
907
+ metadata: { claimed_by: null },
908
+ });
909
+ }
910
+ }
911
+
912
+ async listClaimable(filter?: ClaimFilter): Promise<ExtendedTask[]> {
913
+ let tasks = this.eventStore.listTasks();
914
+
915
+ // Only pending, unassigned tasks
916
+ tasks = tasks.filter(
917
+ (t) => t.status === "pending" && !t.assigned_agent
918
+ );
919
+
920
+ if (filter) {
921
+ if (filter.tags && filter.tags.length > 0) {
922
+ const filterTags = new Set(filter.tags);
923
+ tasks = tasks.filter(
924
+ (t) => t.tags?.some((tag) => filterTags.has(tag))
925
+ );
926
+ }
927
+
928
+ if (filter.rootTasksOnly) {
929
+ tasks = tasks.filter((t) => !t.parent_task);
930
+ }
931
+
932
+ if (filter.created_by) {
933
+ tasks = tasks.filter((t) => t.created_by === filter.created_by);
934
+ }
935
+ }
936
+
937
+ // Filter out blocked tasks
938
+ const extended = await Promise.all(
939
+ tasks.map((t) => this.toExtendedTaskAsync(t))
940
+ );
941
+ return extended.filter((t) => !t.isBlocked);
942
+ }
943
+
944
+ // ─────────────────────────────────────────────────────────────────────────────
945
+ // History
946
+ // ─────────────────────────────────────────────────────────────────────────────
947
+
948
+ async getAgentHistory(taskId: TaskId): Promise<AgentHistoryEntry[]> {
949
+ const task = this.eventStore.getTask(taskId);
950
+ if (!task) {
951
+ throw new OpenTasksBackendError(
952
+ `Task not found: ${taskId}`,
953
+ "TASK_NOT_FOUND",
954
+ taskId
955
+ );
956
+ }
957
+ return task.agent_history ?? [];
958
+ }
959
+
960
+ // ─────────────────────────────────────────────────────────────────────────────
961
+ // Event Subscriptions
962
+ // ─────────────────────────────────────────────────────────────────────────────
963
+
964
+ onTaskChange(callback: TaskChangeCallback): Unsubscribe;
965
+ onTaskChange(taskId: TaskId, callback: TaskChangeCallback): Unsubscribe;
966
+ onTaskChange(
967
+ callbackOrTaskId: TaskChangeCallback | TaskId,
968
+ maybeCallback?: TaskChangeCallback
969
+ ): Unsubscribe {
970
+ const filterTaskId =
971
+ typeof callbackOrTaskId === "string" ? callbackOrTaskId : undefined;
972
+ const callback =
973
+ typeof callbackOrTaskId === "function"
974
+ ? callbackOrTaskId
975
+ : maybeCallback!;
976
+
977
+ const seenTaskIds = new Set<TaskId>();
978
+
979
+ return this.eventStore.onTaskChange((taskId, task) => {
980
+ if (filterTaskId && taskId !== filterTaskId) return;
981
+
982
+ let eventType: TaskChangeEvent["type"];
983
+ if (!task) {
984
+ eventType = "deleted";
985
+ } else if (seenTaskIds.has(taskId)) {
986
+ eventType = "updated";
987
+ } else {
988
+ eventType = "created";
989
+ seenTaskIds.add(taskId);
990
+ }
991
+
992
+ const event: TaskChangeEvent = {
993
+ type: eventType,
994
+ taskId,
995
+ task: task ? this.toExtendedTask(task) : ({} as ExtendedTask),
996
+ };
997
+
998
+ callback(event);
999
+ });
1000
+ }
1001
+
1002
+ // ─────────────────────────────────────────────────────────────────────────────
1003
+ // Public Utility Methods
1004
+ // ─────────────────────────────────────────────────────────────────────────────
1005
+
1006
+ /**
1007
+ * Get the OpenTasks issue ID for a macro-agent task.
1008
+ * Returns undefined if the task is not mapped to an issue.
1009
+ */
1010
+ getIssueForTask(taskId: TaskId): string | undefined {
1011
+ return this.taskToIssue.get(taskId);
1012
+ }
1013
+
1014
+ /**
1015
+ * Get the macro-agent task ID for an OpenTasks issue.
1016
+ * Returns undefined if the issue is not mapped to a task.
1017
+ */
1018
+ getTaskForIssue(issueId: string): TaskId | undefined {
1019
+ return this.issueToTask.get(issueId);
1020
+ }
1021
+
1022
+ /**
1023
+ * Import an existing OpenTasks issue as a macro-agent task.
1024
+ * This is useful for pulling tasks from OpenTasks into macro-agent.
1025
+ */
1026
+ async importIssue(
1027
+ issueId: string,
1028
+ createdBy: AgentId
1029
+ ): Promise<ExtendedTask> {
1030
+ this.ensureOpen();
1031
+ // Check if already imported
1032
+ const existingTaskId = this.issueToTask.get(issueId);
1033
+ if (existingTaskId) {
1034
+ const existing = await this.get(existingTaskId);
1035
+ if (existing) return existing;
1036
+ }
1037
+
1038
+ const issue = await this.client.getIssue(issueId);
1039
+ if (!issue) {
1040
+ throw new OpenTasksBackendError(
1041
+ `OpenTasks issue not found: ${issueId}`,
1042
+ "NOT_FOUND"
1043
+ );
1044
+ }
1045
+
1046
+ const taskId = `task_${nanoid(12)}`;
1047
+
1048
+ // Store mapping
1049
+ this.taskToIssue.set(taskId, issueId);
1050
+ this.issueToTask.set(issueId, taskId);
1051
+
1052
+ // Determine task status from issue
1053
+ const taskStatus = mapOpenTasksStatus(issue.status);
1054
+
1055
+ // Create in EventStore
1056
+ this.eventStore.emit({
1057
+ type: "task",
1058
+ source: { agent_id: createdBy },
1059
+ payload: {
1060
+ task_id: taskId,
1061
+ action: "created",
1062
+ details: {
1063
+ description: issue.title,
1064
+ tags: issue.tags,
1065
+ external_id: issueId,
1066
+ },
1067
+ },
1068
+ });
1069
+
1070
+ // If issue is not open/pending, transition to the right status
1071
+ if (taskStatus === "in_progress") {
1072
+ this.eventStore.emit({
1073
+ type: "task",
1074
+ source: { agent_id: createdBy },
1075
+ payload: {
1076
+ task_id: taskId,
1077
+ action: "status_change",
1078
+ details: { status: "in_progress" },
1079
+ },
1080
+ });
1081
+ } else if (taskStatus === "completed") {
1082
+ this.eventStore.emit({
1083
+ type: "task",
1084
+ source: { agent_id: createdBy },
1085
+ payload: {
1086
+ task_id: taskId,
1087
+ action: "completed",
1088
+ details: {},
1089
+ },
1090
+ });
1091
+ }
1092
+
1093
+ // If issue has an assignee, assign
1094
+ if (issue.assignee) {
1095
+ this.eventStore.emit({
1096
+ type: "task",
1097
+ source: { agent_id: issue.assignee },
1098
+ payload: {
1099
+ task_id: taskId,
1100
+ action: "assigned",
1101
+ details: { agent_id: issue.assignee },
1102
+ },
1103
+ });
1104
+ }
1105
+
1106
+ return this.issueToExtendedTask(issue, taskId);
1107
+ }
1108
+
1109
+ /**
1110
+ * Bulk import all open issues from OpenTasks as tasks.
1111
+ */
1112
+ async importOpenIssues(createdBy: AgentId): Promise<ExtendedTask[]> {
1113
+ this.ensureOpen();
1114
+ const issues = await this.client.listIssues({
1115
+ status: ["open", "in_progress"],
1116
+ archived: false,
1117
+ });
1118
+
1119
+ const tasks: ExtendedTask[] = [];
1120
+ for (const issue of issues) {
1121
+ // Skip already-imported issues
1122
+ if (this.issueToTask.has(issue.id)) continue;
1123
+
1124
+ // Skip issues not created by macro-agent (unless they have no source)
1125
+ // This allows importing issues from other sources too
1126
+ const task = await this.importIssue(issue.id, createdBy);
1127
+ tasks.push(task);
1128
+ }
1129
+
1130
+ return tasks;
1131
+ }
1132
+
1133
+ // ─────────────────────────────────────────────────────────────────────────────
1134
+ // Private Helpers
1135
+ // ─────────────────────────────────────────────────────────────────────────────
1136
+
1137
+ /**
1138
+ * Sync a task status change to OpenTasks.
1139
+ */
1140
+ private async syncStatusToOpenTasks(
1141
+ taskId: TaskId,
1142
+ status: TaskStatus
1143
+ ): Promise<void> {
1144
+ if (!this.config.syncStatus) return;
1145
+
1146
+ const issueId = this.taskToIssue.get(taskId);
1147
+ if (!issueId) return;
1148
+
1149
+ const openTasksStatus = mapTaskStatus(status);
1150
+ try {
1151
+ await this.client.updateIssue(issueId, {
1152
+ status: openTasksStatus,
1153
+ });
1154
+ } catch (error) {
1155
+ // Log but don't fail - sync is best-effort
1156
+ console.warn(
1157
+ `Failed to sync status to OpenTasks for ${taskId} (${issueId}): ${error}`
1158
+ );
1159
+ }
1160
+ }
1161
+
1162
+ /**
1163
+ * Sync a transition that happened via the opentasks daemon's `task` tool.
1164
+ * Updates the EventStore without re-syncing back to opentasks (since the
1165
+ * daemon already processed the transition).
1166
+ *
1167
+ * Accepts either an opentasks issue ID (e.g., "i-xxxx") or an EventStore
1168
+ * task ID (e.g., "task-xxx") — resolves to the EventStore ID either way.
1169
+ *
1170
+ * @param externalId - The opentasks issue ID or EventStore task ID
1171
+ * @param action - The transition action ("complete", "start", "close", "block", "reopen", "assign")
1172
+ * @param agentId - The agent that performed the transition or the assignee for "assign"
1173
+ */
1174
+ async syncExternalTransition(externalId: string, action: string, agentId?: string): Promise<void> {
1175
+ // Resolve to EventStore task ID: try opentasks ID lookup first, then direct
1176
+ const taskId = this.issueToTask.get(externalId)
1177
+ ?? (this.eventStore.getTask(externalId as TaskId) ? externalId as TaskId : undefined);
1178
+ if (!taskId) return;
1179
+
1180
+ const task = this.eventStore.getTask(taskId);
1181
+ if (!task) return;
1182
+
1183
+ const agent = (agentId as AgentId | undefined) ?? task.assigned_agent ?? task.created_by;
1184
+
1185
+ // Handle assignment separately — it updates assignee, not status
1186
+ if (action === "assign") {
1187
+ if (task.assigned_agent !== agent) {
1188
+ this.eventStore.emit({
1189
+ type: "task",
1190
+ source: { agent_id: agent },
1191
+ payload: {
1192
+ task_id: taskId,
1193
+ action: "assigned",
1194
+ details: { agent_id: agent },
1195
+ },
1196
+ });
1197
+ }
1198
+ return;
1199
+ }
1200
+
1201
+ // Map action to target status
1202
+ const ACTION_TO_STATUS: Record<string, TaskStatus> = {
1203
+ complete: "completed",
1204
+ close: "completed",
1205
+ start: "in_progress",
1206
+ block: "pending",
1207
+ reopen: "pending",
1208
+ };
1209
+ const targetStatus = ACTION_TO_STATUS[action];
1210
+ if (!targetStatus || task.status === targetStatus) return;
1211
+
1212
+ // Emit the appropriate EventStore event
1213
+ if (targetStatus === "completed") {
1214
+ this.eventStore.emit({
1215
+ type: "task",
1216
+ source: { agent_id: agent },
1217
+ payload: { task_id: taskId, action: "completed", details: {} },
1218
+ });
1219
+ } else {
1220
+ this.eventStore.emit({
1221
+ type: "task",
1222
+ source: { agent_id: agent },
1223
+ payload: {
1224
+ task_id: taskId,
1225
+ action: "status_change",
1226
+ details: { status: targetStatus },
1227
+ },
1228
+ });
1229
+ }
1230
+ }
1231
+
1232
+ /**
1233
+ * Convert an EventStore Task to ExtendedTask with isBlocked computed
1234
+ * from local blockers.
1235
+ */
1236
+ private toExtendedTask(task: Task): ExtendedTask {
1237
+ const blockerIds = task.blockers ?? [];
1238
+ let isBlocked = false;
1239
+
1240
+ for (const blockerId of blockerIds) {
1241
+ const blocker = this.eventStore.getTask(blockerId);
1242
+ if (blocker && blocker.status !== "completed") {
1243
+ isBlocked = true;
1244
+ break;
1245
+ }
1246
+ }
1247
+
1248
+ return {
1249
+ ...task,
1250
+ isBlocked,
1251
+ external_id: this.taskToIssue.get(task.id),
1252
+ };
1253
+ }
1254
+
1255
+ /**
1256
+ * Convert an EventStore Task to ExtendedTask with isBlocked computed
1257
+ * from OpenTasks graph (async, checks remote blockers).
1258
+ */
1259
+ private async toExtendedTaskAsync(task: Task): Promise<ExtendedTask> {
1260
+ const issueId = this.taskToIssue.get(task.id);
1261
+
1262
+ // If mapped to OpenTasks, use graph-based blocking
1263
+ if (issueId) {
1264
+ try {
1265
+ const blockers = await this.client.getBlockers(issueId);
1266
+ const hasActiveBlockers = blockers.some(
1267
+ (b) => b.status && !isIssueComplete(b.status)
1268
+ );
1269
+ return {
1270
+ ...task,
1271
+ isBlocked: hasActiveBlockers,
1272
+ external_id: issueId,
1273
+ };
1274
+ } catch {
1275
+ // Fall through to local check
1276
+ }
1277
+ }
1278
+
1279
+ return this.toExtendedTask(task);
1280
+ }
1281
+
1282
+ /**
1283
+ * Convert an OpenTasks issue to an ExtendedTask.
1284
+ */
1285
+ private issueToExtendedTask(
1286
+ issue: OpenTasksIssue,
1287
+ taskId: TaskId
1288
+ ): ExtendedTask {
1289
+ const taskStatus = mapOpenTasksStatus(issue.status);
1290
+
1291
+ return {
1292
+ id: taskId,
1293
+ description: issue.title,
1294
+ status: taskStatus,
1295
+ assigned_agent: issue.assignee,
1296
+ parent_task: undefined, // Resolved separately if needed
1297
+ subtasks: undefined,
1298
+ blockers: undefined,
1299
+ created_at: new Date(issue.created_at).getTime(),
1300
+ started_at: taskStatus === "in_progress"
1301
+ ? new Date(issue.updated_at).getTime()
1302
+ : undefined,
1303
+ completed_at: issue.closed_at
1304
+ ? new Date(issue.closed_at).getTime()
1305
+ : undefined,
1306
+ created_by: (issue.metadata?.created_by as string) ?? "unknown",
1307
+ tags: issue.tags,
1308
+ isBlocked: issue.status === "blocked",
1309
+ external_id: issue.id,
1310
+ };
1311
+ }
1312
+ }
1313
+
1314
+ /**
1315
+ * Create an OpenTasksTaskBackend instance.
1316
+ */
1317
+ export function createOpenTasksTaskBackend(
1318
+ eventStore: EventStore,
1319
+ client: OpenTasksClient,
1320
+ config?: Partial<OpenTasksBackendConfig>
1321
+ ): OpenTasksTaskBackend {
1322
+ return new OpenTasksTaskBackend(eventStore, client, config);
1323
+ }