macro-agent 0.0.17 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/.sudocode/specs.jsonl +4 -0
  3. package/CLAUDE.md +16 -14
  4. package/README.md +11 -29
  5. package/dist/acp/macro-agent.d.ts +17 -0
  6. package/dist/acp/macro-agent.d.ts.map +1 -1
  7. package/dist/acp/macro-agent.js +183 -55
  8. package/dist/acp/macro-agent.js.map +1 -1
  9. package/dist/acp/types.d.ts +32 -1
  10. package/dist/acp/types.d.ts.map +1 -1
  11. package/dist/acp/types.js.map +1 -1
  12. package/dist/agent/agent-manager.d.ts +65 -1
  13. package/dist/agent/agent-manager.d.ts.map +1 -1
  14. package/dist/agent/agent-manager.js +464 -183
  15. package/dist/agent/agent-manager.js.map +1 -1
  16. package/dist/agent/types.d.ts +1 -1
  17. package/dist/agent/types.d.ts.map +1 -1
  18. package/dist/api/server.d.ts +3 -0
  19. package/dist/api/server.d.ts.map +1 -1
  20. package/dist/api/server.js +37 -6
  21. package/dist/api/server.js.map +1 -1
  22. package/dist/auth/index.d.ts +2 -0
  23. package/dist/auth/index.d.ts.map +1 -0
  24. package/dist/auth/index.js +2 -0
  25. package/dist/auth/index.js.map +1 -0
  26. package/dist/auth/token.d.ts +41 -0
  27. package/dist/auth/token.d.ts.map +1 -0
  28. package/dist/auth/token.js +73 -0
  29. package/dist/auth/token.js.map +1 -0
  30. package/dist/cli/acp.d.ts +2 -23
  31. package/dist/cli/acp.d.ts.map +1 -1
  32. package/dist/cli/acp.js +127 -61
  33. package/dist/cli/acp.js.map +1 -1
  34. package/dist/cli/index.js +147 -15
  35. package/dist/cli/index.js.map +1 -1
  36. package/dist/cli/mcp.d.ts +6 -0
  37. package/dist/cli/mcp.d.ts.map +1 -1
  38. package/dist/cli/mcp.js +268 -181
  39. package/dist/cli/mcp.js.map +1 -1
  40. package/dist/cli/parse-args.d.ts +20 -0
  41. package/dist/cli/parse-args.d.ts.map +1 -0
  42. package/dist/cli/parse-args.js +43 -0
  43. package/dist/cli/parse-args.js.map +1 -0
  44. package/dist/cli/stable-instance-id.d.ts +8 -0
  45. package/dist/cli/stable-instance-id.d.ts.map +1 -0
  46. package/dist/cli/stable-instance-id.js +14 -0
  47. package/dist/cli/stable-instance-id.js.map +1 -0
  48. package/dist/config/project-config.d.ts +74 -7
  49. package/dist/config/project-config.d.ts.map +1 -1
  50. package/dist/config/project-config.js +123 -20
  51. package/dist/config/project-config.js.map +1 -1
  52. package/dist/map/adapter/acp-over-map.d.ts +23 -0
  53. package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
  54. package/dist/map/adapter/acp-over-map.js +482 -55
  55. package/dist/map/adapter/acp-over-map.js.map +1 -1
  56. package/dist/map/adapter/connection-manager.d.ts.map +1 -1
  57. package/dist/map/adapter/connection-manager.js +3 -0
  58. package/dist/map/adapter/connection-manager.js.map +1 -1
  59. package/dist/map/adapter/event-log.d.ts +87 -0
  60. package/dist/map/adapter/event-log.d.ts.map +1 -0
  61. package/dist/map/adapter/event-log.js +122 -0
  62. package/dist/map/adapter/event-log.js.map +1 -0
  63. package/dist/map/adapter/event-translator.js +6 -6
  64. package/dist/map/adapter/event-translator.js.map +1 -1
  65. package/dist/map/adapter/extensions/agent-lifecycle.d.ts +82 -0
  66. package/dist/map/adapter/extensions/agent-lifecycle.d.ts.map +1 -0
  67. package/dist/map/adapter/extensions/agent-lifecycle.js +164 -0
  68. package/dist/map/adapter/extensions/agent-lifecycle.js.map +1 -0
  69. package/dist/map/adapter/extensions/index.d.ts +10 -1
  70. package/dist/map/adapter/extensions/index.d.ts.map +1 -1
  71. package/dist/map/adapter/extensions/index.js +34 -0
  72. package/dist/map/adapter/extensions/index.js.map +1 -1
  73. package/dist/map/adapter/extensions/mcp-bridge.d.ts +57 -0
  74. package/dist/map/adapter/extensions/mcp-bridge.d.ts.map +1 -0
  75. package/dist/map/adapter/extensions/mcp-bridge.js +745 -0
  76. package/dist/map/adapter/extensions/mcp-bridge.js.map +1 -0
  77. package/dist/map/adapter/extensions/rename.d.ts +29 -0
  78. package/dist/map/adapter/extensions/rename.d.ts.map +1 -0
  79. package/dist/map/adapter/extensions/rename.js +49 -0
  80. package/dist/map/adapter/extensions/rename.js.map +1 -0
  81. package/dist/map/adapter/extensions/task.d.ts.map +1 -1
  82. package/dist/map/adapter/extensions/task.js +10 -0
  83. package/dist/map/adapter/extensions/task.js.map +1 -1
  84. package/dist/map/adapter/extensions/update-metadata.d.ts +29 -0
  85. package/dist/map/adapter/extensions/update-metadata.d.ts.map +1 -0
  86. package/dist/map/adapter/extensions/update-metadata.js +67 -0
  87. package/dist/map/adapter/extensions/update-metadata.js.map +1 -0
  88. package/dist/map/adapter/index.d.ts +2 -1
  89. package/dist/map/adapter/index.d.ts.map +1 -1
  90. package/dist/map/adapter/index.js +8 -2
  91. package/dist/map/adapter/index.js.map +1 -1
  92. package/dist/map/adapter/interface.d.ts +2 -0
  93. package/dist/map/adapter/interface.d.ts.map +1 -1
  94. package/dist/map/adapter/map-adapter.d.ts +4 -0
  95. package/dist/map/adapter/map-adapter.d.ts.map +1 -1
  96. package/dist/map/adapter/map-adapter.js +302 -30
  97. package/dist/map/adapter/map-adapter.js.map +1 -1
  98. package/dist/map/adapter/subscription-manager.d.ts.map +1 -1
  99. package/dist/map/adapter/subscription-manager.js +5 -1
  100. package/dist/map/adapter/subscription-manager.js.map +1 -1
  101. package/dist/map/adapter/types.d.ts +2 -0
  102. package/dist/map/adapter/types.d.ts.map +1 -1
  103. package/dist/mcp/map-client.d.ts +39 -0
  104. package/dist/mcp/map-client.d.ts.map +1 -0
  105. package/dist/mcp/map-client.js +129 -0
  106. package/dist/mcp/map-client.js.map +1 -0
  107. package/dist/mcp/mcp-server.d.ts +14 -0
  108. package/dist/mcp/mcp-server.d.ts.map +1 -1
  109. package/dist/mcp/mcp-server.js +113 -85
  110. package/dist/mcp/mcp-server.js.map +1 -1
  111. package/dist/mcp/types.d.ts +9 -1
  112. package/dist/mcp/types.d.ts.map +1 -1
  113. package/dist/mcp/types.js.map +1 -1
  114. package/dist/metrics/metrics.js +1 -1
  115. package/dist/metrics/metrics.js.map +1 -1
  116. package/dist/roles/capabilities.d.ts +3 -1
  117. package/dist/roles/capabilities.d.ts.map +1 -1
  118. package/dist/roles/capabilities.js +17 -7
  119. package/dist/roles/capabilities.js.map +1 -1
  120. package/dist/roles/config-loader.d.ts +6 -6
  121. package/dist/roles/config-loader.d.ts.map +1 -1
  122. package/dist/roles/config-loader.js +6 -6
  123. package/dist/roles/config-loader.js.map +1 -1
  124. package/dist/roles/registry.d.ts +2 -2
  125. package/dist/roles/registry.js +2 -2
  126. package/dist/server/combined-server.d.ts +20 -0
  127. package/dist/server/combined-server.d.ts.map +1 -1
  128. package/dist/server/combined-server.js +107 -8
  129. package/dist/server/combined-server.js.map +1 -1
  130. package/dist/store/event-store.d.ts +7 -1
  131. package/dist/store/event-store.d.ts.map +1 -1
  132. package/dist/store/event-store.js +91 -8
  133. package/dist/store/event-store.js.map +1 -1
  134. package/dist/store/types/agents.d.ts +23 -0
  135. package/dist/store/types/agents.d.ts.map +1 -1
  136. package/dist/store/types/events.d.ts +1 -1
  137. package/dist/store/types/events.d.ts.map +1 -1
  138. package/dist/task/backend/index.d.ts +47 -29
  139. package/dist/task/backend/index.d.ts.map +1 -1
  140. package/dist/task/backend/index.js +109 -71
  141. package/dist/task/backend/index.js.map +1 -1
  142. package/dist/task/backend/memory.d.ts +1 -0
  143. package/dist/task/backend/memory.d.ts.map +1 -1
  144. package/dist/task/backend/memory.js +3 -0
  145. package/dist/task/backend/memory.js.map +1 -1
  146. package/dist/task/backend/opentasks/backend.d.ts +140 -0
  147. package/dist/task/backend/opentasks/backend.d.ts.map +1 -0
  148. package/dist/task/backend/opentasks/backend.js +1023 -0
  149. package/dist/task/backend/opentasks/backend.js.map +1 -0
  150. package/dist/task/backend/opentasks/client.d.ts +337 -0
  151. package/dist/task/backend/opentasks/client.d.ts.map +1 -0
  152. package/dist/task/backend/opentasks/client.js +225 -0
  153. package/dist/task/backend/opentasks/client.js.map +1 -0
  154. package/dist/task/backend/opentasks/daemon-manager.d.ts +89 -0
  155. package/dist/task/backend/opentasks/daemon-manager.d.ts.map +1 -0
  156. package/dist/task/backend/opentasks/daemon-manager.js +195 -0
  157. package/dist/task/backend/opentasks/daemon-manager.js.map +1 -0
  158. package/dist/task/backend/opentasks/index.d.ts +21 -0
  159. package/dist/task/backend/opentasks/index.d.ts.map +1 -0
  160. package/dist/task/backend/opentasks/index.js +21 -0
  161. package/dist/task/backend/opentasks/index.js.map +1 -0
  162. package/dist/task/backend/opentasks/mapping.d.ts +48 -0
  163. package/dist/task/backend/opentasks/mapping.d.ts.map +1 -0
  164. package/dist/task/backend/opentasks/mapping.js +77 -0
  165. package/dist/task/backend/opentasks/mapping.js.map +1 -0
  166. package/dist/task/backend/types.d.ts +33 -53
  167. package/dist/task/backend/types.d.ts.map +1 -1
  168. package/dist/task/backend/types.js +7 -11
  169. package/dist/task/backend/types.js.map +1 -1
  170. package/dist/task/backend/unified-tool-provider.d.ts +57 -0
  171. package/dist/task/backend/unified-tool-provider.d.ts.map +1 -0
  172. package/dist/task/backend/unified-tool-provider.js +623 -0
  173. package/dist/task/backend/unified-tool-provider.js.map +1 -0
  174. package/dist/teams/team-loader.d.ts +2 -2
  175. package/dist/teams/team-loader.js +3 -3
  176. package/dist/teams/team-loader.js.map +1 -1
  177. package/dist/teams/team-runtime.d.ts.map +1 -1
  178. package/dist/teams/team-runtime.js +2 -0
  179. package/dist/teams/team-runtime.js.map +1 -1
  180. package/docs/architecture.md +7 -6
  181. package/docs/configuration.md +26 -62
  182. package/docs/implementation-details.md +5 -5
  183. package/docs/implementation-summary.md +17 -17
  184. package/docs/plan-self-driving-support.md +4 -4
  185. package/docs/spec-self-driving-support.md +10 -10
  186. package/docs/team-templates.md +2 -2
  187. package/docs/teams.md +3 -3
  188. package/docs/troubleshooting.md +10 -11
  189. package/package.json +6 -4
  190. package/src/__tests__/e2e/agent-spawn-visibility.e2e.test.ts +761 -0
  191. package/src/__tests__/e2e/full-agent-conflict-resolution.e2e.test.ts +2 -2
  192. package/src/__tests__/e2e/mcp-thin-client-bridge.e2e.test.ts +304 -0
  193. package/src/__tests__/e2e/mcp-tools-available.e2e.test.ts +324 -0
  194. package/src/__tests__/e2e/multi-agent.e2e.test.ts +5 -5
  195. package/src/__tests__/e2e/spawn-session-streaming.e2e.test.ts +563 -0
  196. package/src/acp/__tests__/history.test.ts +8 -4
  197. package/src/acp/__tests__/integration.test.ts +56 -31
  198. package/src/acp/__tests__/macro-agent.test.ts +16 -7
  199. package/src/acp/macro-agent.ts +230 -62
  200. package/src/acp/types.ts +46 -1
  201. package/src/agent/__tests__/agent-manager.test.ts +228 -2
  202. package/src/agent/agent-manager.ts +714 -261
  203. package/src/agent/types.ts +3 -1
  204. package/src/api/server.ts +41 -7
  205. package/src/auth/__tests__/token.test.ts +100 -0
  206. package/src/auth/index.ts +1 -0
  207. package/src/auth/token.ts +82 -0
  208. package/src/cli/__tests__/acp.test.ts +1 -1
  209. package/src/cli/__tests__/stable-instance-id.test.ts +1 -1
  210. package/src/cli/acp.ts +130 -72
  211. package/src/cli/index.ts +120 -14
  212. package/src/cli/mcp.ts +311 -207
  213. package/src/cli/parse-args.ts +54 -0
  214. package/src/cli/stable-instance-id.ts +14 -0
  215. package/src/config/project-config.ts +190 -27
  216. package/src/lifecycle/__tests__/cascade-termination.test.ts +1 -1
  217. package/src/map/adapter/__tests__/acp-over-map-cancel.test.ts +820 -0
  218. package/src/map/adapter/__tests__/acp-over-map-getmodels.test.ts +355 -0
  219. package/src/map/adapter/__tests__/acp-over-map-history.test.ts +724 -2
  220. package/src/map/adapter/__tests__/acp-over-map-persistence.e2e.test.ts +1 -1
  221. package/src/map/adapter/__tests__/event-broadcast.test.ts +420 -0
  222. package/src/map/adapter/__tests__/event-log.test.ts +527 -0
  223. package/src/map/adapter/__tests__/event-translator.test.ts +3 -3
  224. package/src/map/adapter/__tests__/extensions.test.ts +408 -0
  225. package/src/map/adapter/__tests__/map-adapter.test.ts +99 -0
  226. package/src/map/adapter/__tests__/mcp-bridge.test.ts +1187 -0
  227. package/src/map/adapter/__tests__/multi-client-broadcast.test.ts +711 -0
  228. package/src/map/adapter/__tests__/websocket-integration.test.ts +218 -0
  229. package/src/map/adapter/acp-over-map.ts +777 -92
  230. package/src/map/adapter/connection-manager.ts +3 -0
  231. package/src/map/adapter/event-log.ts +208 -0
  232. package/src/map/adapter/event-translator.ts +6 -6
  233. package/src/map/adapter/extensions/agent-lifecycle.ts +267 -0
  234. package/src/map/adapter/extensions/index.ts +60 -0
  235. package/src/map/adapter/extensions/mcp-bridge.ts +995 -0
  236. package/src/map/adapter/extensions/task.ts +11 -0
  237. package/src/map/adapter/extensions/update-metadata.ts +126 -0
  238. package/src/map/adapter/index.ts +28 -0
  239. package/src/map/adapter/interface.ts +2 -0
  240. package/src/map/adapter/map-adapter.ts +373 -38
  241. package/src/map/adapter/subscription-manager.ts +5 -1
  242. package/src/map/adapter/types.ts +2 -0
  243. package/src/mcp/__tests__/map-client.test.ts +386 -0
  244. package/src/mcp/__tests__/mcp-server-thin-client.test.ts +368 -0
  245. package/src/mcp/__tests__/mcp-server.test.ts +100 -1
  246. package/src/mcp/map-client.ts +177 -0
  247. package/src/mcp/mcp-server.ts +191 -100
  248. package/src/mcp/types.ts +6 -1
  249. package/src/metrics/metrics.ts +1 -1
  250. package/src/monitor/__tests__/stale-agent-flow.integration.test.ts +1 -1
  251. package/src/roles/__tests__/config-loader.test.ts +7 -7
  252. package/src/roles/capabilities.ts +17 -7
  253. package/src/roles/config-loader.ts +6 -6
  254. package/src/roles/registry.ts +2 -2
  255. package/src/server/__tests__/combined-server.test.ts +94 -21
  256. package/src/server/combined-server.ts +189 -33
  257. package/src/steering/__tests__/steering-integration.test.ts +1 -1
  258. package/src/store/__tests__/event-store.test.ts +236 -1
  259. package/src/store/__tests__/instance.test.ts +3 -3
  260. package/src/store/event-store.ts +109 -8
  261. package/src/store/types/agents.ts +16 -0
  262. package/src/store/types/events.ts +1 -1
  263. package/src/task/backend/__tests__/create-task-backend.test.ts +225 -0
  264. package/src/task/backend/__tests__/e2e/unified-tool-provider-opentasks.e2e.test.ts +524 -0
  265. package/src/task/backend/__tests__/unified-tool-provider.test.ts +579 -0
  266. package/src/task/backend/index.ts +156 -106
  267. package/src/task/backend/memory.ts +4 -0
  268. package/src/task/backend/opentasks/__tests__/backend.test.ts +968 -0
  269. package/src/task/backend/opentasks/__tests__/daemon-manager.test.ts +406 -0
  270. package/src/task/backend/opentasks/__tests__/mapping.test.ts +84 -0
  271. package/src/task/backend/opentasks/__tests__/opentasks-backend.e2e.test.ts +1338 -0
  272. package/src/task/backend/opentasks/backend.ts +1323 -0
  273. package/src/task/backend/opentasks/client.ts +652 -0
  274. package/src/task/backend/opentasks/daemon-manager.ts +253 -0
  275. package/src/task/backend/opentasks/index.ts +69 -0
  276. package/src/task/backend/opentasks/mapping.ts +94 -0
  277. package/src/task/backend/types.ts +42 -66
  278. package/src/task/backend/unified-tool-provider.ts +779 -0
  279. package/src/teams/__tests__/cross-subsystem.integration.test.ts +1 -1
  280. package/src/teams/team-loader.ts +3 -3
  281. package/src/teams/team-runtime.ts +2 -0
  282. package/test_fixtures/README.md +2 -3
  283. package/test_fixtures/fixtures/index.ts +0 -3
  284. package/test_fixtures/fixtures/projects/project-with-specs.ts +7 -149
  285. package/test_fixtures/fixtures/repos/index.ts +1 -3
  286. package/test_fixtures/fixtures/repos/temp-repo-factory.ts +0 -116
  287. package/test_fixtures/fixtures/repos/types.ts +0 -11
  288. package/test_fixtures/harness/__tests__/fixtures.test.ts +10 -102
  289. package/test_fixtures/harness/__tests__/temp-repo-and-simulator.test.ts +0 -33
  290. package/test_fixtures/harness/simulator/agent-simulator.ts +4 -4
  291. package/vitest.config.ts +1 -1
  292. package/vitest.e2e.config.ts +1 -1
  293. package/vitest.setup.ts +1 -30
  294. package/.macro-agent/teams/self-driving/prompts/grinder.md +0 -27
  295. package/.macro-agent/teams/self-driving/prompts/judge.md +0 -27
  296. package/.macro-agent/teams/self-driving/prompts/planner.md +0 -33
  297. package/.macro-agent/teams/self-driving/roles/grinder.yaml +0 -17
  298. package/.macro-agent/teams/self-driving/roles/judge.yaml +0 -24
  299. package/.macro-agent/teams/self-driving/roles/planner.yaml +0 -18
  300. package/.macro-agent/teams/self-driving/team.yaml +0 -103
  301. package/.macro-agent/teams/structured/prompts/developer.md +0 -26
  302. package/.macro-agent/teams/structured/prompts/lead.md +0 -25
  303. package/.macro-agent/teams/structured/prompts/reviewer.md +0 -24
  304. package/.macro-agent/teams/structured/roles/developer.yaml +0 -12
  305. package/.macro-agent/teams/structured/roles/lead.yaml +0 -11
  306. package/.macro-agent/teams/structured/roles/reviewer.yaml +0 -19
  307. package/.macro-agent/teams/structured/team.yaml +0 -89
  308. package/docs/sudocode-integration.md +0 -383
  309. package/src/task/backend/__tests__/backend-parity.test.ts +0 -451
  310. package/src/task/backend/__tests__/tool-provider-edge-cases.test.ts +0 -430
  311. package/src/task/backend/__tests__/tool-provider.test.ts +0 -983
  312. package/src/task/backend/sudocode/__tests__/backend-edge-cases.test.ts +0 -575
  313. package/src/task/backend/sudocode/__tests__/backend.test.ts +0 -1194
  314. package/src/task/backend/sudocode/__tests__/client-integration.test.ts +0 -418
  315. package/src/task/backend/sudocode/__tests__/client.test.ts +0 -345
  316. package/src/task/backend/sudocode/__tests__/e2e/backend.e2e.test.ts +0 -753
  317. package/src/task/backend/sudocode/__tests__/e2e/server-client.e2e.test.ts +0 -680
  318. package/src/task/backend/sudocode/__tests__/e2e-workflow.test.ts +0 -666
  319. package/src/task/backend/sudocode/__tests__/integration/standalone-client.integration.test.ts +0 -396
  320. package/src/task/backend/sudocode/__tests__/integration/sudocode-cli.integration.test.ts +0 -328
  321. package/src/task/backend/sudocode/__tests__/integration/test-utils.ts +0 -175
  322. package/src/task/backend/sudocode/__tests__/mapping-edge-cases.test.ts +0 -265
  323. package/src/task/backend/sudocode/__tests__/server-client.test.ts +0 -675
  324. package/src/task/backend/sudocode/__tests__/sync-policy-edge-cases.test.ts +0 -521
  325. package/src/task/backend/sudocode/__tests__/sync-policy.test.ts +0 -519
  326. package/src/task/backend/sudocode/__tests__/tools.test.ts +0 -471
  327. package/src/task/backend/sudocode/backend.ts +0 -1237
  328. package/src/task/backend/sudocode/client.ts +0 -515
  329. package/src/task/backend/sudocode/index.ts +0 -120
  330. package/src/task/backend/sudocode/mapping.ts +0 -93
  331. package/src/task/backend/sudocode/server-client.ts +0 -522
  332. package/src/task/backend/sudocode/standalone-client.ts +0 -623
  333. package/src/task/backend/sudocode/sync-policy.ts +0 -387
  334. package/src/task/backend/sudocode/tools.ts +0 -896
  335. package/src/task/backend/tool-provider.ts +0 -506
  336. package/test_fixtures/fixtures/sudocode/index.ts +0 -29
  337. package/test_fixtures/fixtures/sudocode/issues.ts +0 -185
  338. package/test_fixtures/fixtures/sudocode/specs.ts +0 -159
@@ -23,6 +23,7 @@ import type {
23
23
  EventFilter,
24
24
  Agent,
25
25
  AgentState,
26
+ AgentMetadataUpdate,
26
27
  Task,
27
28
  TaskStatus,
28
29
  QueuedMessage,
@@ -131,8 +132,8 @@ function createTabularBetterSqlite3Persister(
131
132
  undefined,
132
133
  // onIgnoredError
133
134
  (error: any) => console.warn('[EventStore] Persister error:', error),
134
- // destroy
135
- () => db.close(),
135
+ // destroy — do NOT close DB here; close() handles it after the persister drains
136
+ () => {},
136
137
  // persist mode (1 = StoreOnly)
137
138
  1 as any,
138
139
  // thing (the db instance)
@@ -203,6 +204,8 @@ export interface EventStore {
203
204
  // Agent view
204
205
  getAgent(agentId: AgentId): Agent | null;
205
206
  listAgents(filter?: { state?: AgentState; parent?: AgentId | null }): Agent[];
207
+ updateAgentPlan(agentId: AgentId, plan: Array<{ content: string; priority: string; status: string }>): void;
208
+ updateAgentMetadata(agentId: AgentId, updates: AgentMetadataUpdate): void;
206
209
 
207
210
  // Task view
208
211
  getTask(taskId: TaskId): Task | null;
@@ -519,6 +522,51 @@ export async function createEventStore(config: StoreConfig = {}): Promise<EventS
519
522
  return agents;
520
523
  }
521
524
 
525
+ /**
526
+ * Update agent metadata fields (name, plan, metadata).
527
+ * Only provided fields are updated. Metadata is shallow-merged with existing.
528
+ */
529
+ function updateAgentMetadata(
530
+ agentId: AgentId,
531
+ updates: AgentMetadataUpdate,
532
+ ): void {
533
+ const row = store.getRow('agents', agentId);
534
+ if (!row.id) return;
535
+
536
+ const partial: Record<string, string | number | boolean> = {
537
+ last_activity_at: Date.now(),
538
+ };
539
+
540
+ if (updates.name !== undefined) {
541
+ partial.name = updates.name;
542
+ }
543
+ if (updates.plan !== undefined) {
544
+ partial.plan = JSON.stringify(updates.plan);
545
+ }
546
+ if (updates.metadata !== undefined) {
547
+ // Shallow merge with existing metadata
548
+ const existing = row.metadata ? JSON.parse(row.metadata as string) : {};
549
+ partial.metadata = JSON.stringify({ ...existing, ...updates.metadata });
550
+ }
551
+
552
+ store.setPartialRow('agents', agentId, partial);
553
+
554
+ const agent = rowToAgent(store.getRow('agents', agentId));
555
+ notifyAgentChange(agentId, agent);
556
+ }
557
+
558
+ /**
559
+ * Update an agent's plan entries (persisted to SQLite via TinyBase).
560
+ * Convenience wrapper around updateAgentMetadata.
561
+ */
562
+ function updateAgentPlan(
563
+ agentId: AgentId,
564
+ plan: Array<{ content: string; priority: string; status: string }>,
565
+ ): void {
566
+ updateAgentMetadata(agentId, { plan });
567
+ }
568
+
569
+
522
570
  /**
523
571
  * Get task by ID
524
572
  */
@@ -927,8 +975,18 @@ export async function createEventStore(config: StoreConfig = {}): Promise<EventS
927
975
  */
928
976
  async function close(): Promise<void> {
929
977
  if (persister) {
978
+ // Stop auto-save first to prevent race conditions between
979
+ // auto-save callbacks and the explicit save/destroy sequence.
980
+ await persister.stopAutoSave();
930
981
  await persister.save();
982
+ // Destroy the persister (removes store listeners) BEFORE closing the DB.
983
+ // The destroy callback is a no-op — we close the DB ourselves below
984
+ // after giving TinyBase's internal async queues time to drain.
931
985
  persister.destroy();
986
+ // Allow any in-flight TinyBase microtasks to settle before closing
987
+ // the database connection. Without this, pending writes from store
988
+ // change listeners can race against db.close().
989
+ await new Promise(resolve => setTimeout(resolve, 50));
932
990
  }
933
991
  if (db) {
934
992
  db.close();
@@ -1203,6 +1261,8 @@ export async function createEventStore(config: StoreConfig = {}): Promise<EventS
1203
1261
  // Views
1204
1262
  getAgent,
1205
1263
  listAgents,
1264
+ updateAgentPlan,
1265
+ updateAgentMetadata,
1206
1266
  getTask,
1207
1267
  listTasks,
1208
1268
  getMessages,
@@ -1269,6 +1329,25 @@ function initializeTables(store: Store): void {
1269
1329
  * Rebuild materialized views from the event log
1270
1330
  */
1271
1331
  function rebuildViews(store: Store): void {
1332
+ // Preserve out-of-band agent fields that aren't derived from events.
1333
+ // These fields are written directly (not through events),
1334
+ // so they would be lost when we clear and replay.
1335
+ const OUT_OF_BAND_FIELDS = ['plan', 'name', 'metadata'] as const;
1336
+ const savedOutOfBand = new Map<string, Record<string, string>>();
1337
+ for (const rowId of store.getRowIds('agents')) {
1338
+ const row = store.getRow('agents', rowId);
1339
+ const saved: Record<string, string> = {};
1340
+ for (const field of OUT_OF_BAND_FIELDS) {
1341
+ const val = row[field] as string | undefined;
1342
+ if (val && val !== '' && val !== '[]') {
1343
+ saved[field] = val;
1344
+ }
1345
+ }
1346
+ if (Object.keys(saved).length > 0) {
1347
+ savedOutOfBand.set(rowId, saved);
1348
+ }
1349
+ }
1350
+
1272
1351
  // Clear existing views
1273
1352
  for (const rowId of store.getRowIds('agents')) {
1274
1353
  store.delRow('agents', rowId);
@@ -1324,6 +1403,14 @@ function rebuildViews(store: Store): void {
1324
1403
  for (const event of events) {
1325
1404
  applyEventToViews(store, event, noop, noop, noop, noop, noop, noop);
1326
1405
  }
1406
+
1407
+ // Restore out-of-band agent fields preserved before the wipe
1408
+ for (const [agentId, fields] of savedOutOfBand) {
1409
+ const row = store.getRow('agents', agentId);
1410
+ if (row.id) {
1411
+ store.setPartialRow('agents', agentId, fields);
1412
+ }
1413
+ }
1327
1414
  }
1328
1415
 
1329
1416
  /**
@@ -1343,8 +1430,8 @@ function applyEventToViews(
1343
1430
  case 'spawn':
1344
1431
  applySpawnEvent(store, event, notifyAgentChange);
1345
1432
  break;
1346
- case 'terminate':
1347
- applyTerminateEvent(store, event, notifyAgentChange);
1433
+ case 'stop':
1434
+ applyStopEvent(store, event, notifyAgentChange);
1348
1435
  break;
1349
1436
  case 'status':
1350
1437
  applyStatusEvent(store, event, notifyAgentChange);
@@ -1411,6 +1498,7 @@ function applySpawnEvent(
1411
1498
 
1412
1499
  store.setRow('agents', agentId, {
1413
1500
  id: agentId,
1501
+ name: '',
1414
1502
  session_id: payload.session_id,
1415
1503
  provider_session_id: '',
1416
1504
  parent: parent ?? '',
@@ -1422,6 +1510,8 @@ function applySpawnEvent(
1422
1510
  role: payload.role ?? '',
1423
1511
  config: JSON.stringify(payload.config ?? {}),
1424
1512
  cwd: payload.cwd ?? process.cwd(),
1513
+ plan: '[]',
1514
+ metadata: '',
1425
1515
  created_at: event.timestamp,
1426
1516
  started_at: 0,
1427
1517
  stopped_at: 0,
@@ -1433,9 +1523,9 @@ function applySpawnEvent(
1433
1523
  }
1434
1524
 
1435
1525
  /**
1436
- * Apply terminate event to agents view
1526
+ * Apply stop event to agents view
1437
1527
  */
1438
- function applyTerminateEvent(
1528
+ function applyStopEvent(
1439
1529
  store: Store,
1440
1530
  event: Event,
1441
1531
  notify: (agentId: AgentId, agent: Agent | null) => void,
@@ -1577,6 +1667,7 @@ function applyTaskEvent(
1577
1667
  description: string;
1578
1668
  parent_task?: TaskId;
1579
1669
  inputs?: Record<string, unknown>;
1670
+ tags?: string[];
1580
1671
  retryPolicy?: unknown;
1581
1672
  };
1582
1673
  store.setRow('tasks', taskId, {
@@ -1587,6 +1678,7 @@ function applyTaskEvent(
1587
1678
  parent_task: details.parent_task ?? '',
1588
1679
  subtasks: JSON.stringify([]),
1589
1680
  blockers: JSON.stringify([]),
1681
+ tags: details.tags ? JSON.stringify(details.tags) : '',
1590
1682
  created_at: event.timestamp,
1591
1683
  started_at: 0,
1592
1684
  completed_at: 0,
@@ -1633,10 +1725,15 @@ function applyTaskEvent(
1633
1725
  break;
1634
1726
  }
1635
1727
  }
1636
- store.setPartialRow('tasks', taskId, {
1728
+ const updates: Record<string, string | number | boolean> = {
1637
1729
  assigned_agent: '',
1638
1730
  agent_history: JSON.stringify(history),
1639
- });
1731
+ };
1732
+ // Reset to pending if task was only assigned (not yet started)
1733
+ if (existing.status === 'assigned') {
1734
+ updates.status = 'pending';
1735
+ }
1736
+ store.setPartialRow('tasks', taskId, updates);
1640
1737
  break;
1641
1738
  }
1642
1739
  case 'status_change': {
@@ -1759,6 +1856,7 @@ function rowToAgent(row: Record<string, unknown>): Agent {
1759
1856
  const stopReason = row.stop_reason as string;
1760
1857
  return {
1761
1858
  id: row.id as AgentId,
1859
+ name: (row.name as string) || undefined,
1762
1860
  session_id: row.session_id as string,
1763
1861
  provider_session_id: (row.provider_session_id as string) || undefined,
1764
1862
  parent: (row.parent as string) || null,
@@ -1770,6 +1868,8 @@ function rowToAgent(row: Record<string, unknown>): Agent {
1770
1868
  role: (row.role as string) || undefined,
1771
1869
  config: row.config ? JSON.parse(row.config as string) : {},
1772
1870
  cwd: (row.cwd as string) || process.cwd(),
1871
+ plan: row.plan ? JSON.parse(row.plan as string) : [],
1872
+ metadata: row.metadata ? JSON.parse(row.metadata as string) : undefined,
1773
1873
  created_at: row.created_at as Timestamp,
1774
1874
  started_at: (row.started_at as number) || undefined,
1775
1875
  stopped_at: (row.stopped_at as number) || undefined,
@@ -1797,6 +1897,7 @@ function rowToTask(row: Record<string, unknown>): Task {
1797
1897
  outputs: row.outputs ? JSON.parse(row.outputs as string) : undefined,
1798
1898
  artifacts: row.artifacts ? JSON.parse(row.artifacts as string) : undefined,
1799
1899
  agent_history: row.agent_history ? JSON.parse(row.agent_history as string) : undefined,
1900
+ tags: row.tags ? JSON.parse(row.tags as string) : undefined,
1800
1901
  retryPolicy: row.retry_policy ? JSON.parse(row.retry_policy as string) : undefined,
1801
1902
  retryState: row.retry_state ? JSON.parse(row.retry_state as string) : undefined,
1802
1903
  };
@@ -28,6 +28,8 @@ export interface AgentConfig {
28
28
  // Agent record in materialized view
29
29
  export interface Agent {
30
30
  id: AgentId;
31
+ /** Optional display name (set via rename, not derived from events) */
32
+ name?: string;
31
33
  session_id: SessionId;
32
34
  /** Session ID from the underlying agent provider (e.g., Claude Code UUID for --resume) */
33
35
  provider_session_id?: string;
@@ -40,9 +42,23 @@ export interface Agent {
40
42
  role?: string;
41
43
  config: AgentConfig;
42
44
  cwd: string;
45
+ plan: Array<{ content: string; priority: string; status: string }>;
46
+ /** Arbitrary metadata (persisted out-of-band, not derived from events) */
47
+ metadata?: Record<string, unknown>;
43
48
  created_at: Timestamp;
44
49
  started_at?: Timestamp;
45
50
  stopped_at?: Timestamp;
46
51
  /** Last time this agent emitted an event (for health monitoring) */
47
52
  last_activity_at?: Timestamp;
48
53
  }
54
+
55
+ /**
56
+ * Partial update for agent metadata fields.
57
+ * All fields are optional — only provided fields are updated.
58
+ * `metadata` is merged (shallow) with existing metadata.
59
+ */
60
+ export interface AgentMetadataUpdate {
61
+ name?: string;
62
+ plan?: Array<{ content: string; priority: string; status: string }>;
63
+ metadata?: Record<string, unknown>;
64
+ }
@@ -11,7 +11,7 @@ export const CURRENT_EVENT_VERSION = 1;
11
11
  // Event types
12
12
  export type EventType =
13
13
  | "spawn"
14
- | "terminate"
14
+ | "stop"
15
15
  | "status"
16
16
  | "message"
17
17
  | "task"
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Tests for createTaskBackend factory function
3
+ *
4
+ * Covers:
5
+ * - Memory backend returns no socketPath
6
+ * - OpenTasks backend with explicit socketPath returns it
7
+ * - OpenTasks backend with autoStart returns daemon's socketPath
8
+ * - Unknown backend type throws
9
+ *
10
+ * @module task/backend/__tests__/create-task-backend.test
11
+ */
12
+
13
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
14
+ import { createEventStore, type EventStore } from "../../../store/event-store.js";
15
+
16
+ // Mock opentasks to avoid real daemon operations
17
+ vi.mock("opentasks", () => ({
18
+ checkExistingDaemon: vi.fn(),
19
+ createDaemonWithStore: vi.fn(),
20
+ OpenTasksClient: vi.fn(),
21
+ }));
22
+
23
+ // Mock the opentasks client module
24
+ vi.mock("../opentasks/client.js", () => ({
25
+ IPCOpenTasksClient: vi.fn().mockImplementation(() => ({
26
+ connect: vi.fn().mockResolvedValue(undefined),
27
+ disconnect: vi.fn(),
28
+ isConnected: vi.fn().mockReturnValue(true),
29
+ call: vi.fn().mockResolvedValue({}),
30
+ createIssue: vi.fn(),
31
+ getIssue: vi.fn(),
32
+ updateIssue: vi.fn(),
33
+ deleteIssue: vi.fn(),
34
+ listIssues: vi.fn(),
35
+ getReadyIssues: vi.fn(),
36
+ createEdge: vi.fn(),
37
+ removeEdge: vi.fn(),
38
+ getBlockers: vi.fn(),
39
+ getBlocking: vi.fn(),
40
+ task: vi.fn(),
41
+ taskTransition: vi.fn(),
42
+ taskReady: vi.fn(),
43
+ taskAssign: vi.fn(),
44
+ taskValidActions: vi.fn(),
45
+ listProviders: vi.fn(),
46
+ })),
47
+ createOpenTasksClient: vi.fn().mockImplementation(async () => ({
48
+ connect: vi.fn().mockResolvedValue(undefined),
49
+ disconnect: vi.fn(),
50
+ isConnected: vi.fn().mockReturnValue(true),
51
+ call: vi.fn().mockResolvedValue({}),
52
+ createIssue: vi.fn(),
53
+ getIssue: vi.fn(),
54
+ updateIssue: vi.fn(),
55
+ deleteIssue: vi.fn(),
56
+ listIssues: vi.fn(),
57
+ getReadyIssues: vi.fn(),
58
+ createEdge: vi.fn(),
59
+ removeEdge: vi.fn(),
60
+ getBlockers: vi.fn(),
61
+ getBlocking: vi.fn(),
62
+ task: vi.fn(),
63
+ taskTransition: vi.fn(),
64
+ taskReady: vi.fn(),
65
+ taskAssign: vi.fn(),
66
+ taskValidActions: vi.fn(),
67
+ listProviders: vi.fn(),
68
+ })),
69
+ OpenTasksClientError: class extends Error {
70
+ code: string;
71
+ constructor(msg: string, code: string) {
72
+ super(msg);
73
+ this.code = code;
74
+ }
75
+ },
76
+ }));
77
+
78
+ // Mock the opentasks backend
79
+ vi.mock("../opentasks/backend.js", () => ({
80
+ OpenTasksTaskBackend: vi.fn().mockImplementation(() => ({
81
+ create: vi.fn(),
82
+ get: vi.fn(),
83
+ update: vi.fn(),
84
+ list: vi.fn(),
85
+ delete: vi.fn(),
86
+ onChange: vi.fn(),
87
+ })),
88
+ }));
89
+
90
+ // Mock daemon-manager
91
+ vi.mock("../opentasks/daemon-manager.js", () => ({
92
+ DaemonManager: vi.fn().mockImplementation(() => ({
93
+ ensureDaemon: vi.fn().mockResolvedValue({
94
+ client: {
95
+ connect: vi.fn(),
96
+ disconnect: vi.fn(),
97
+ isConnected: vi.fn().mockReturnValue(true),
98
+ },
99
+ socketPath: "/tmp/auto-daemon.sock",
100
+ ownsDaemon: true,
101
+ }),
102
+ shutdown: vi.fn().mockResolvedValue(undefined),
103
+ connectProject: vi.fn().mockResolvedValue(undefined),
104
+ getConnectedProjects: vi.fn().mockReturnValue([]),
105
+ })),
106
+ }));
107
+
108
+ describe("createTaskBackend", () => {
109
+ let eventStore: EventStore;
110
+
111
+ beforeEach(async () => {
112
+ eventStore = await createEventStore({ inMemory: true });
113
+ vi.clearAllMocks();
114
+ });
115
+
116
+ afterEach(async () => {
117
+ await eventStore.close();
118
+ });
119
+
120
+ it("should return no socketPath for memory backend", async () => {
121
+ const { createTaskBackend } = await import("../index.js");
122
+
123
+ const result = await createTaskBackend(
124
+ { backend: { type: "memory" } },
125
+ eventStore
126
+ );
127
+
128
+ expect(result.backend).toBeDefined();
129
+ expect(result.socketPath).toBeUndefined();
130
+ expect(result.shutdown).toBeUndefined();
131
+ });
132
+
133
+ it("should return explicit socketPath for opentasks with socketPath config", async () => {
134
+ const { createTaskBackend } = await import("../index.js");
135
+
136
+ const result = await createTaskBackend(
137
+ { backend: { type: "opentasks", socketPath: "/tmp/explicit.sock" } },
138
+ eventStore
139
+ );
140
+
141
+ expect(result.backend).toBeDefined();
142
+ expect(result.socketPath).toBe("/tmp/explicit.sock");
143
+ expect(result.openTasksClient).toBeDefined();
144
+ expect(result.shutdown).toBeDefined();
145
+ // No connectProject when using direct socket (no DaemonManager)
146
+ expect(result.connectProject).toBeUndefined();
147
+ });
148
+
149
+ it("should return daemon socketPath for opentasks with autoStart", async () => {
150
+ const { createTaskBackend } = await import("../index.js");
151
+
152
+ const result = await createTaskBackend(
153
+ { backend: { type: "opentasks" } }, // autoStart defaults to true
154
+ eventStore
155
+ );
156
+
157
+ expect(result.backend).toBeDefined();
158
+ expect(result.socketPath).toBe("/tmp/auto-daemon.sock");
159
+ expect(result.openTasksClient).toBeDefined();
160
+ expect(result.shutdown).toBeDefined();
161
+ expect(result.connectProject).toBeDefined();
162
+ expect(result.getConnectedProjects).toBeDefined();
163
+ });
164
+
165
+ it("should return no socketPath for opentasks with autoStart disabled", async () => {
166
+ const { createTaskBackend } = await import("../index.js");
167
+
168
+ const result = await createTaskBackend(
169
+ { backend: { type: "opentasks", autoStart: false } },
170
+ eventStore
171
+ );
172
+
173
+ expect(result.backend).toBeDefined();
174
+ // autoStart=false, no socketPath in config → socket path unknown
175
+ expect(result.socketPath).toBeUndefined();
176
+ expect(result.openTasksClient).toBeDefined();
177
+ expect(result.shutdown).toBeDefined();
178
+ // No DaemonManager when autoStart is false
179
+ expect(result.connectProject).toBeUndefined();
180
+ });
181
+
182
+ it("should throw for unknown backend type", async () => {
183
+ const { createTaskBackend } = await import("../index.js");
184
+
185
+ await expect(
186
+ createTaskBackend(
187
+ { backend: { type: "unknown" as any } },
188
+ eventStore
189
+ )
190
+ ).rejects.toThrow("Unknown backend type");
191
+ });
192
+
193
+ it("should call backend.close() before daemon shutdown", async () => {
194
+ const { createTaskBackend } = await import("../index.js");
195
+
196
+ const result = await createTaskBackend(
197
+ { backend: { type: "opentasks" } }, // autoStart defaults to true
198
+ eventStore
199
+ );
200
+
201
+ expect(result.shutdown).toBeDefined();
202
+
203
+ // Track call order
204
+ const callOrder: string[] = [];
205
+ const { DaemonManager } = await import("../opentasks/daemon-manager.js");
206
+ const mockInstance = vi.mocked(DaemonManager).mock.results[0]?.value;
207
+ if (mockInstance) {
208
+ mockInstance.shutdown.mockImplementation(async () => {
209
+ callOrder.push("daemon_shutdown");
210
+ });
211
+ }
212
+
213
+ // backend.close() is called within the shutdown wrapper
214
+ const backend = result.backend as any;
215
+ const originalClose = backend.close?.bind(backend);
216
+ backend.close = vi.fn(async () => {
217
+ callOrder.push("backend_close");
218
+ if (originalClose) await originalClose();
219
+ });
220
+
221
+ await result.shutdown!();
222
+
223
+ expect(callOrder).toEqual(["backend_close", "daemon_shutdown"]);
224
+ });
225
+ });