macro-agent 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/.sudocode/specs.jsonl +4 -0
  3. package/CLAUDE.md +16 -14
  4. package/README.md +11 -29
  5. package/dist/acp/macro-agent.d.ts +15 -0
  6. package/dist/acp/macro-agent.d.ts.map +1 -1
  7. package/dist/acp/macro-agent.js +131 -35
  8. package/dist/acp/macro-agent.js.map +1 -1
  9. package/dist/acp/types.d.ts +32 -1
  10. package/dist/acp/types.d.ts.map +1 -1
  11. package/dist/acp/types.js.map +1 -1
  12. package/dist/agent/agent-manager.d.ts +65 -1
  13. package/dist/agent/agent-manager.d.ts.map +1 -1
  14. package/dist/agent/agent-manager.js +464 -183
  15. package/dist/agent/agent-manager.js.map +1 -1
  16. package/dist/agent/types.d.ts +1 -1
  17. package/dist/agent/types.d.ts.map +1 -1
  18. package/dist/api/server.d.ts +3 -0
  19. package/dist/api/server.d.ts.map +1 -1
  20. package/dist/api/server.js +37 -6
  21. package/dist/api/server.js.map +1 -1
  22. package/dist/auth/index.d.ts +2 -0
  23. package/dist/auth/index.d.ts.map +1 -0
  24. package/dist/auth/index.js +2 -0
  25. package/dist/auth/index.js.map +1 -0
  26. package/dist/auth/token.d.ts +41 -0
  27. package/dist/auth/token.d.ts.map +1 -0
  28. package/dist/auth/token.js +73 -0
  29. package/dist/auth/token.js.map +1 -0
  30. package/dist/cli/acp.d.ts +2 -23
  31. package/dist/cli/acp.d.ts.map +1 -1
  32. package/dist/cli/acp.js +127 -61
  33. package/dist/cli/acp.js.map +1 -1
  34. package/dist/cli/index.js +147 -15
  35. package/dist/cli/index.js.map +1 -1
  36. package/dist/cli/mcp.d.ts +6 -0
  37. package/dist/cli/mcp.d.ts.map +1 -1
  38. package/dist/cli/mcp.js +268 -181
  39. package/dist/cli/mcp.js.map +1 -1
  40. package/dist/cli/parse-args.d.ts +20 -0
  41. package/dist/cli/parse-args.d.ts.map +1 -0
  42. package/dist/cli/parse-args.js +43 -0
  43. package/dist/cli/parse-args.js.map +1 -0
  44. package/dist/cli/stable-instance-id.d.ts +8 -0
  45. package/dist/cli/stable-instance-id.d.ts.map +1 -0
  46. package/dist/cli/stable-instance-id.js +14 -0
  47. package/dist/cli/stable-instance-id.js.map +1 -0
  48. package/dist/config/project-config.d.ts +74 -7
  49. package/dist/config/project-config.d.ts.map +1 -1
  50. package/dist/config/project-config.js +123 -20
  51. package/dist/config/project-config.js.map +1 -1
  52. package/dist/map/adapter/acp-over-map.d.ts +17 -0
  53. package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
  54. package/dist/map/adapter/acp-over-map.js +384 -23
  55. package/dist/map/adapter/acp-over-map.js.map +1 -1
  56. package/dist/map/adapter/connection-manager.d.ts.map +1 -1
  57. package/dist/map/adapter/connection-manager.js +3 -0
  58. package/dist/map/adapter/connection-manager.js.map +1 -1
  59. package/dist/map/adapter/event-log.d.ts +87 -0
  60. package/dist/map/adapter/event-log.d.ts.map +1 -0
  61. package/dist/map/adapter/event-log.js +122 -0
  62. package/dist/map/adapter/event-log.js.map +1 -0
  63. package/dist/map/adapter/event-translator.js +6 -6
  64. package/dist/map/adapter/event-translator.js.map +1 -1
  65. package/dist/map/adapter/extensions/agent-lifecycle.d.ts +82 -0
  66. package/dist/map/adapter/extensions/agent-lifecycle.d.ts.map +1 -0
  67. package/dist/map/adapter/extensions/agent-lifecycle.js +164 -0
  68. package/dist/map/adapter/extensions/agent-lifecycle.js.map +1 -0
  69. package/dist/map/adapter/extensions/index.d.ts +10 -1
  70. package/dist/map/adapter/extensions/index.d.ts.map +1 -1
  71. package/dist/map/adapter/extensions/index.js +34 -0
  72. package/dist/map/adapter/extensions/index.js.map +1 -1
  73. package/dist/map/adapter/extensions/mcp-bridge.d.ts +57 -0
  74. package/dist/map/adapter/extensions/mcp-bridge.d.ts.map +1 -0
  75. package/dist/map/adapter/extensions/mcp-bridge.js +745 -0
  76. package/dist/map/adapter/extensions/mcp-bridge.js.map +1 -0
  77. package/dist/map/adapter/extensions/rename.d.ts +29 -0
  78. package/dist/map/adapter/extensions/rename.d.ts.map +1 -0
  79. package/dist/map/adapter/extensions/rename.js +49 -0
  80. package/dist/map/adapter/extensions/rename.js.map +1 -0
  81. package/dist/map/adapter/extensions/task.d.ts.map +1 -1
  82. package/dist/map/adapter/extensions/task.js +10 -0
  83. package/dist/map/adapter/extensions/task.js.map +1 -1
  84. package/dist/map/adapter/extensions/update-metadata.d.ts +29 -0
  85. package/dist/map/adapter/extensions/update-metadata.d.ts.map +1 -0
  86. package/dist/map/adapter/extensions/update-metadata.js +67 -0
  87. package/dist/map/adapter/extensions/update-metadata.js.map +1 -0
  88. package/dist/map/adapter/index.d.ts +2 -1
  89. package/dist/map/adapter/index.d.ts.map +1 -1
  90. package/dist/map/adapter/index.js +8 -2
  91. package/dist/map/adapter/index.js.map +1 -1
  92. package/dist/map/adapter/interface.d.ts +2 -0
  93. package/dist/map/adapter/interface.d.ts.map +1 -1
  94. package/dist/map/adapter/map-adapter.d.ts +3 -0
  95. package/dist/map/adapter/map-adapter.d.ts.map +1 -1
  96. package/dist/map/adapter/map-adapter.js +258 -35
  97. package/dist/map/adapter/map-adapter.js.map +1 -1
  98. package/dist/map/adapter/subscription-manager.d.ts.map +1 -1
  99. package/dist/map/adapter/subscription-manager.js +5 -1
  100. package/dist/map/adapter/subscription-manager.js.map +1 -1
  101. package/dist/map/adapter/types.d.ts +2 -0
  102. package/dist/map/adapter/types.d.ts.map +1 -1
  103. package/dist/mcp/map-client.d.ts +39 -0
  104. package/dist/mcp/map-client.d.ts.map +1 -0
  105. package/dist/mcp/map-client.js +129 -0
  106. package/dist/mcp/map-client.js.map +1 -0
  107. package/dist/mcp/mcp-server.d.ts +14 -0
  108. package/dist/mcp/mcp-server.d.ts.map +1 -1
  109. package/dist/mcp/mcp-server.js +113 -85
  110. package/dist/mcp/mcp-server.js.map +1 -1
  111. package/dist/mcp/types.d.ts +9 -1
  112. package/dist/mcp/types.d.ts.map +1 -1
  113. package/dist/mcp/types.js.map +1 -1
  114. package/dist/metrics/metrics.js +1 -1
  115. package/dist/metrics/metrics.js.map +1 -1
  116. package/dist/roles/capabilities.d.ts +3 -1
  117. package/dist/roles/capabilities.d.ts.map +1 -1
  118. package/dist/roles/capabilities.js +17 -7
  119. package/dist/roles/capabilities.js.map +1 -1
  120. package/dist/roles/config-loader.d.ts +6 -6
  121. package/dist/roles/config-loader.d.ts.map +1 -1
  122. package/dist/roles/config-loader.js +6 -6
  123. package/dist/roles/config-loader.js.map +1 -1
  124. package/dist/roles/registry.d.ts +2 -2
  125. package/dist/roles/registry.js +2 -2
  126. package/dist/server/combined-server.d.ts +20 -0
  127. package/dist/server/combined-server.d.ts.map +1 -1
  128. package/dist/server/combined-server.js +107 -8
  129. package/dist/server/combined-server.js.map +1 -1
  130. package/dist/store/event-store.d.ts +2 -1
  131. package/dist/store/event-store.d.ts.map +1 -1
  132. package/dist/store/event-store.js +69 -20
  133. package/dist/store/event-store.js.map +1 -1
  134. package/dist/store/types/agents.d.ts +18 -0
  135. package/dist/store/types/agents.d.ts.map +1 -1
  136. package/dist/store/types/events.d.ts +1 -1
  137. package/dist/store/types/events.d.ts.map +1 -1
  138. package/dist/task/backend/index.d.ts +47 -29
  139. package/dist/task/backend/index.d.ts.map +1 -1
  140. package/dist/task/backend/index.js +109 -71
  141. package/dist/task/backend/index.js.map +1 -1
  142. package/dist/task/backend/memory.d.ts +1 -0
  143. package/dist/task/backend/memory.d.ts.map +1 -1
  144. package/dist/task/backend/memory.js +3 -0
  145. package/dist/task/backend/memory.js.map +1 -1
  146. package/dist/task/backend/opentasks/backend.d.ts +140 -0
  147. package/dist/task/backend/opentasks/backend.d.ts.map +1 -0
  148. package/dist/task/backend/opentasks/backend.js +1023 -0
  149. package/dist/task/backend/opentasks/backend.js.map +1 -0
  150. package/dist/task/backend/opentasks/client.d.ts +337 -0
  151. package/dist/task/backend/opentasks/client.d.ts.map +1 -0
  152. package/dist/task/backend/opentasks/client.js +225 -0
  153. package/dist/task/backend/opentasks/client.js.map +1 -0
  154. package/dist/task/backend/opentasks/daemon-manager.d.ts +89 -0
  155. package/dist/task/backend/opentasks/daemon-manager.d.ts.map +1 -0
  156. package/dist/task/backend/opentasks/daemon-manager.js +195 -0
  157. package/dist/task/backend/opentasks/daemon-manager.js.map +1 -0
  158. package/dist/task/backend/opentasks/index.d.ts +21 -0
  159. package/dist/task/backend/opentasks/index.d.ts.map +1 -0
  160. package/dist/task/backend/opentasks/index.js +21 -0
  161. package/dist/task/backend/opentasks/index.js.map +1 -0
  162. package/dist/task/backend/opentasks/mapping.d.ts +48 -0
  163. package/dist/task/backend/opentasks/mapping.d.ts.map +1 -0
  164. package/dist/task/backend/opentasks/mapping.js +77 -0
  165. package/dist/task/backend/opentasks/mapping.js.map +1 -0
  166. package/dist/task/backend/types.d.ts +33 -53
  167. package/dist/task/backend/types.d.ts.map +1 -1
  168. package/dist/task/backend/types.js +7 -11
  169. package/dist/task/backend/types.js.map +1 -1
  170. package/dist/task/backend/unified-tool-provider.d.ts +57 -0
  171. package/dist/task/backend/unified-tool-provider.d.ts.map +1 -0
  172. package/dist/task/backend/unified-tool-provider.js +623 -0
  173. package/dist/task/backend/unified-tool-provider.js.map +1 -0
  174. package/dist/teams/team-loader.d.ts +2 -2
  175. package/dist/teams/team-loader.js +3 -3
  176. package/dist/teams/team-loader.js.map +1 -1
  177. package/dist/teams/team-runtime.d.ts.map +1 -1
  178. package/dist/teams/team-runtime.js +2 -0
  179. package/dist/teams/team-runtime.js.map +1 -1
  180. package/docs/architecture.md +7 -6
  181. package/docs/configuration.md +26 -62
  182. package/docs/implementation-details.md +5 -5
  183. package/docs/implementation-summary.md +17 -17
  184. package/docs/plan-self-driving-support.md +4 -4
  185. package/docs/spec-self-driving-support.md +10 -10
  186. package/docs/team-templates.md +2 -2
  187. package/docs/teams.md +3 -3
  188. package/docs/troubleshooting.md +10 -11
  189. package/package.json +6 -4
  190. package/src/__tests__/e2e/agent-spawn-visibility.e2e.test.ts +761 -0
  191. package/src/__tests__/e2e/full-agent-conflict-resolution.e2e.test.ts +2 -2
  192. package/src/__tests__/e2e/mcp-thin-client-bridge.e2e.test.ts +304 -0
  193. package/src/__tests__/e2e/mcp-tools-available.e2e.test.ts +324 -0
  194. package/src/__tests__/e2e/multi-agent.e2e.test.ts +5 -5
  195. package/src/__tests__/e2e/spawn-session-streaming.e2e.test.ts +563 -0
  196. package/src/acp/__tests__/integration.test.ts +56 -31
  197. package/src/acp/__tests__/macro-agent.test.ts +16 -7
  198. package/src/acp/macro-agent.ts +170 -36
  199. package/src/acp/types.ts +46 -1
  200. package/src/agent/__tests__/agent-manager.test.ts +228 -2
  201. package/src/agent/agent-manager.ts +714 -261
  202. package/src/agent/types.ts +3 -1
  203. package/src/api/server.ts +41 -7
  204. package/src/auth/__tests__/token.test.ts +100 -0
  205. package/src/auth/index.ts +1 -0
  206. package/src/auth/token.ts +82 -0
  207. package/src/cli/__tests__/acp.test.ts +1 -1
  208. package/src/cli/__tests__/stable-instance-id.test.ts +1 -1
  209. package/src/cli/acp.ts +130 -72
  210. package/src/cli/index.ts +120 -14
  211. package/src/cli/mcp.ts +311 -207
  212. package/src/cli/parse-args.ts +54 -0
  213. package/src/cli/stable-instance-id.ts +14 -0
  214. package/src/config/project-config.ts +190 -27
  215. package/src/lifecycle/__tests__/cascade-termination.test.ts +1 -1
  216. package/src/map/adapter/__tests__/acp-over-map-cancel.test.ts +22 -4
  217. package/src/map/adapter/__tests__/acp-over-map-getmodels.test.ts +355 -0
  218. package/src/map/adapter/__tests__/acp-over-map-history.test.ts +263 -0
  219. package/src/map/adapter/__tests__/acp-over-map-persistence.e2e.test.ts +1 -1
  220. package/src/map/adapter/__tests__/event-broadcast.test.ts +420 -0
  221. package/src/map/adapter/__tests__/event-log.test.ts +527 -0
  222. package/src/map/adapter/__tests__/event-translator.test.ts +3 -3
  223. package/src/map/adapter/__tests__/extensions.test.ts +408 -0
  224. package/src/map/adapter/__tests__/map-adapter.test.ts +99 -0
  225. package/src/map/adapter/__tests__/mcp-bridge.test.ts +1187 -0
  226. package/src/map/adapter/__tests__/multi-client-broadcast.test.ts +711 -0
  227. package/src/map/adapter/__tests__/websocket-integration.test.ts +218 -0
  228. package/src/map/adapter/acp-over-map.ts +678 -66
  229. package/src/map/adapter/connection-manager.ts +3 -0
  230. package/src/map/adapter/event-log.ts +208 -0
  231. package/src/map/adapter/event-translator.ts +6 -6
  232. package/src/map/adapter/extensions/agent-lifecycle.ts +267 -0
  233. package/src/map/adapter/extensions/index.ts +60 -0
  234. package/src/map/adapter/extensions/mcp-bridge.ts +995 -0
  235. package/src/map/adapter/extensions/task.ts +11 -0
  236. package/src/map/adapter/extensions/update-metadata.ts +126 -0
  237. package/src/map/adapter/index.ts +28 -0
  238. package/src/map/adapter/interface.ts +2 -0
  239. package/src/map/adapter/map-adapter.ts +312 -47
  240. package/src/map/adapter/subscription-manager.ts +5 -1
  241. package/src/map/adapter/types.ts +2 -0
  242. package/src/mcp/__tests__/map-client.test.ts +386 -0
  243. package/src/mcp/__tests__/mcp-server-thin-client.test.ts +368 -0
  244. package/src/mcp/__tests__/mcp-server.test.ts +100 -1
  245. package/src/mcp/map-client.ts +177 -0
  246. package/src/mcp/mcp-server.ts +191 -100
  247. package/src/mcp/types.ts +6 -1
  248. package/src/metrics/metrics.ts +1 -1
  249. package/src/monitor/__tests__/stale-agent-flow.integration.test.ts +1 -1
  250. package/src/roles/__tests__/config-loader.test.ts +7 -7
  251. package/src/roles/capabilities.ts +17 -7
  252. package/src/roles/config-loader.ts +6 -6
  253. package/src/roles/registry.ts +2 -2
  254. package/src/server/__tests__/combined-server.test.ts +94 -21
  255. package/src/server/combined-server.ts +189 -33
  256. package/src/steering/__tests__/steering-integration.test.ts +1 -1
  257. package/src/store/__tests__/event-store.test.ts +196 -1
  258. package/src/store/__tests__/instance.test.ts +3 -3
  259. package/src/store/event-store.ts +80 -21
  260. package/src/store/types/agents.ts +15 -0
  261. package/src/store/types/events.ts +1 -1
  262. package/src/task/backend/__tests__/create-task-backend.test.ts +225 -0
  263. package/src/task/backend/__tests__/e2e/unified-tool-provider-opentasks.e2e.test.ts +524 -0
  264. package/src/task/backend/__tests__/unified-tool-provider.test.ts +579 -0
  265. package/src/task/backend/index.ts +156 -106
  266. package/src/task/backend/memory.ts +4 -0
  267. package/src/task/backend/opentasks/__tests__/backend.test.ts +968 -0
  268. package/src/task/backend/opentasks/__tests__/daemon-manager.test.ts +406 -0
  269. package/src/task/backend/opentasks/__tests__/mapping.test.ts +84 -0
  270. package/src/task/backend/opentasks/__tests__/opentasks-backend.e2e.test.ts +1338 -0
  271. package/src/task/backend/opentasks/backend.ts +1323 -0
  272. package/src/task/backend/opentasks/client.ts +652 -0
  273. package/src/task/backend/opentasks/daemon-manager.ts +253 -0
  274. package/src/task/backend/opentasks/index.ts +69 -0
  275. package/src/task/backend/opentasks/mapping.ts +94 -0
  276. package/src/task/backend/types.ts +42 -66
  277. package/src/task/backend/unified-tool-provider.ts +779 -0
  278. package/src/teams/__tests__/cross-subsystem.integration.test.ts +1 -1
  279. package/src/teams/team-loader.ts +3 -3
  280. package/src/teams/team-runtime.ts +2 -0
  281. package/test_fixtures/README.md +2 -3
  282. package/test_fixtures/fixtures/index.ts +0 -3
  283. package/test_fixtures/fixtures/projects/project-with-specs.ts +7 -149
  284. package/test_fixtures/fixtures/repos/index.ts +1 -3
  285. package/test_fixtures/fixtures/repos/temp-repo-factory.ts +0 -116
  286. package/test_fixtures/fixtures/repos/types.ts +0 -11
  287. package/test_fixtures/harness/__tests__/fixtures.test.ts +10 -102
  288. package/test_fixtures/harness/__tests__/temp-repo-and-simulator.test.ts +0 -33
  289. package/test_fixtures/harness/simulator/agent-simulator.ts +4 -4
  290. package/vitest.config.ts +1 -1
  291. package/vitest.e2e.config.ts +1 -1
  292. package/vitest.setup.ts +1 -30
  293. package/.macro-agent/teams/self-driving/prompts/grinder.md +0 -27
  294. package/.macro-agent/teams/self-driving/prompts/judge.md +0 -27
  295. package/.macro-agent/teams/self-driving/prompts/planner.md +0 -33
  296. package/.macro-agent/teams/self-driving/roles/grinder.yaml +0 -17
  297. package/.macro-agent/teams/self-driving/roles/judge.yaml +0 -24
  298. package/.macro-agent/teams/self-driving/roles/planner.yaml +0 -18
  299. package/.macro-agent/teams/self-driving/team.yaml +0 -103
  300. package/.macro-agent/teams/structured/prompts/developer.md +0 -26
  301. package/.macro-agent/teams/structured/prompts/lead.md +0 -25
  302. package/.macro-agent/teams/structured/prompts/reviewer.md +0 -24
  303. package/.macro-agent/teams/structured/roles/developer.yaml +0 -12
  304. package/.macro-agent/teams/structured/roles/lead.yaml +0 -11
  305. package/.macro-agent/teams/structured/roles/reviewer.yaml +0 -19
  306. package/.macro-agent/teams/structured/team.yaml +0 -89
  307. package/docs/sudocode-integration.md +0 -383
  308. package/src/task/backend/__tests__/backend-parity.test.ts +0 -451
  309. package/src/task/backend/__tests__/tool-provider-edge-cases.test.ts +0 -430
  310. package/src/task/backend/__tests__/tool-provider.test.ts +0 -983
  311. package/src/task/backend/sudocode/__tests__/backend-edge-cases.test.ts +0 -575
  312. package/src/task/backend/sudocode/__tests__/backend.test.ts +0 -1194
  313. package/src/task/backend/sudocode/__tests__/client-integration.test.ts +0 -418
  314. package/src/task/backend/sudocode/__tests__/client.test.ts +0 -345
  315. package/src/task/backend/sudocode/__tests__/e2e/backend.e2e.test.ts +0 -753
  316. package/src/task/backend/sudocode/__tests__/e2e/server-client.e2e.test.ts +0 -680
  317. package/src/task/backend/sudocode/__tests__/e2e-workflow.test.ts +0 -666
  318. package/src/task/backend/sudocode/__tests__/integration/standalone-client.integration.test.ts +0 -396
  319. package/src/task/backend/sudocode/__tests__/integration/sudocode-cli.integration.test.ts +0 -328
  320. package/src/task/backend/sudocode/__tests__/integration/test-utils.ts +0 -175
  321. package/src/task/backend/sudocode/__tests__/mapping-edge-cases.test.ts +0 -265
  322. package/src/task/backend/sudocode/__tests__/server-client.test.ts +0 -675
  323. package/src/task/backend/sudocode/__tests__/sync-policy-edge-cases.test.ts +0 -521
  324. package/src/task/backend/sudocode/__tests__/sync-policy.test.ts +0 -519
  325. package/src/task/backend/sudocode/__tests__/tools.test.ts +0 -471
  326. package/src/task/backend/sudocode/backend.ts +0 -1237
  327. package/src/task/backend/sudocode/client.ts +0 -515
  328. package/src/task/backend/sudocode/index.ts +0 -120
  329. package/src/task/backend/sudocode/mapping.ts +0 -93
  330. package/src/task/backend/sudocode/server-client.ts +0 -522
  331. package/src/task/backend/sudocode/standalone-client.ts +0 -623
  332. package/src/task/backend/sudocode/sync-policy.ts +0 -387
  333. package/src/task/backend/sudocode/tools.ts +0 -896
  334. package/src/task/backend/tool-provider.ts +0 -506
  335. package/test_fixtures/fixtures/sudocode/index.ts +0 -29
  336. package/test_fixtures/fixtures/sudocode/issues.ts +0 -185
  337. package/test_fixtures/fixtures/sudocode/specs.ts +0 -159
@@ -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)
@@ -204,6 +205,7 @@ export interface EventStore {
204
205
  getAgent(agentId: AgentId): Agent | null;
205
206
  listAgents(filter?: { state?: AgentState; parent?: AgentId | null }): Agent[];
206
207
  updateAgentPlan(agentId: AgentId, plan: Array<{ content: string; priority: string; status: string }>): void;
208
+ updateAgentMetadata(agentId: AgentId, updates: AgentMetadataUpdate): void;
207
209
 
208
210
  // Task view
209
211
  getTask(taskId: TaskId): Task | null;
@@ -521,24 +523,50 @@ export async function createEventStore(config: StoreConfig = {}): Promise<EventS
521
523
  }
522
524
 
523
525
  /**
524
- * Update an agent's plan entries (persisted to SQLite via TinyBase)
526
+ * Update agent metadata fields (name, plan, metadata).
527
+ * Only provided fields are updated. Metadata is shallow-merged with existing.
525
528
  */
526
- function updateAgentPlan(
529
+ function updateAgentMetadata(
527
530
  agentId: AgentId,
528
- plan: Array<{ content: string; priority: string; status: string }>,
531
+ updates: AgentMetadataUpdate,
529
532
  ): void {
530
533
  const row = store.getRow('agents', agentId);
531
534
  if (!row.id) return;
532
535
 
533
- store.setPartialRow('agents', agentId, {
534
- plan: JSON.stringify(plan),
536
+ const partial: Record<string, string | number | boolean> = {
535
537
  last_activity_at: Date.now(),
536
- });
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);
537
553
 
538
554
  const agent = rowToAgent(store.getRow('agents', agentId));
539
555
  notifyAgentChange(agentId, agent);
540
556
  }
541
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
+
542
570
  /**
543
571
  * Get task by ID
544
572
  */
@@ -947,8 +975,18 @@ export async function createEventStore(config: StoreConfig = {}): Promise<EventS
947
975
  */
948
976
  async function close(): Promise<void> {
949
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();
950
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.
951
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));
952
990
  }
953
991
  if (db) {
954
992
  db.close();
@@ -1224,6 +1262,7 @@ export async function createEventStore(config: StoreConfig = {}): Promise<EventS
1224
1262
  getAgent,
1225
1263
  listAgents,
1226
1264
  updateAgentPlan,
1265
+ updateAgentMetadata,
1227
1266
  getTask,
1228
1267
  listTasks,
1229
1268
  getMessages,
@@ -1291,13 +1330,21 @@ function initializeTables(store: Store): void {
1291
1330
  */
1292
1331
  function rebuildViews(store: Store): void {
1293
1332
  // Preserve out-of-band agent fields that aren't derived from events.
1294
- // `plan` is written directly via updateAgentPlan(), not through events,
1295
- // so it would be lost when we clear and replay.
1296
- const savedAgentPlan = new Map<string, string>();
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>>();
1297
1337
  for (const rowId of store.getRowIds('agents')) {
1298
1338
  const row = store.getRow('agents', rowId);
1299
- if (row.plan && row.plan !== '[]') {
1300
- savedAgentPlan.set(rowId, row.plan as string);
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);
1301
1348
  }
1302
1349
  }
1303
1350
 
@@ -1358,10 +1405,10 @@ function rebuildViews(store: Store): void {
1358
1405
  }
1359
1406
 
1360
1407
  // Restore out-of-band agent fields preserved before the wipe
1361
- for (const [agentId, plan] of savedAgentPlan) {
1408
+ for (const [agentId, fields] of savedOutOfBand) {
1362
1409
  const row = store.getRow('agents', agentId);
1363
1410
  if (row.id) {
1364
- store.setPartialRow('agents', agentId, { plan });
1411
+ store.setPartialRow('agents', agentId, fields);
1365
1412
  }
1366
1413
  }
1367
1414
  }
@@ -1383,8 +1430,8 @@ function applyEventToViews(
1383
1430
  case 'spawn':
1384
1431
  applySpawnEvent(store, event, notifyAgentChange);
1385
1432
  break;
1386
- case 'terminate':
1387
- applyTerminateEvent(store, event, notifyAgentChange);
1433
+ case 'stop':
1434
+ applyStopEvent(store, event, notifyAgentChange);
1388
1435
  break;
1389
1436
  case 'status':
1390
1437
  applyStatusEvent(store, event, notifyAgentChange);
@@ -1451,6 +1498,7 @@ function applySpawnEvent(
1451
1498
 
1452
1499
  store.setRow('agents', agentId, {
1453
1500
  id: agentId,
1501
+ name: '',
1454
1502
  session_id: payload.session_id,
1455
1503
  provider_session_id: '',
1456
1504
  parent: parent ?? '',
@@ -1463,6 +1511,7 @@ function applySpawnEvent(
1463
1511
  config: JSON.stringify(payload.config ?? {}),
1464
1512
  cwd: payload.cwd ?? process.cwd(),
1465
1513
  plan: '[]',
1514
+ metadata: '',
1466
1515
  created_at: event.timestamp,
1467
1516
  started_at: 0,
1468
1517
  stopped_at: 0,
@@ -1474,9 +1523,9 @@ function applySpawnEvent(
1474
1523
  }
1475
1524
 
1476
1525
  /**
1477
- * Apply terminate event to agents view
1526
+ * Apply stop event to agents view
1478
1527
  */
1479
- function applyTerminateEvent(
1528
+ function applyStopEvent(
1480
1529
  store: Store,
1481
1530
  event: Event,
1482
1531
  notify: (agentId: AgentId, agent: Agent | null) => void,
@@ -1618,6 +1667,7 @@ function applyTaskEvent(
1618
1667
  description: string;
1619
1668
  parent_task?: TaskId;
1620
1669
  inputs?: Record<string, unknown>;
1670
+ tags?: string[];
1621
1671
  retryPolicy?: unknown;
1622
1672
  };
1623
1673
  store.setRow('tasks', taskId, {
@@ -1628,6 +1678,7 @@ function applyTaskEvent(
1628
1678
  parent_task: details.parent_task ?? '',
1629
1679
  subtasks: JSON.stringify([]),
1630
1680
  blockers: JSON.stringify([]),
1681
+ tags: details.tags ? JSON.stringify(details.tags) : '',
1631
1682
  created_at: event.timestamp,
1632
1683
  started_at: 0,
1633
1684
  completed_at: 0,
@@ -1674,10 +1725,15 @@ function applyTaskEvent(
1674
1725
  break;
1675
1726
  }
1676
1727
  }
1677
- store.setPartialRow('tasks', taskId, {
1728
+ const updates: Record<string, string | number | boolean> = {
1678
1729
  assigned_agent: '',
1679
1730
  agent_history: JSON.stringify(history),
1680
- });
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);
1681
1737
  break;
1682
1738
  }
1683
1739
  case 'status_change': {
@@ -1800,6 +1856,7 @@ function rowToAgent(row: Record<string, unknown>): Agent {
1800
1856
  const stopReason = row.stop_reason as string;
1801
1857
  return {
1802
1858
  id: row.id as AgentId,
1859
+ name: (row.name as string) || undefined,
1803
1860
  session_id: row.session_id as string,
1804
1861
  provider_session_id: (row.provider_session_id as string) || undefined,
1805
1862
  parent: (row.parent as string) || null,
@@ -1812,6 +1869,7 @@ function rowToAgent(row: Record<string, unknown>): Agent {
1812
1869
  config: row.config ? JSON.parse(row.config as string) : {},
1813
1870
  cwd: (row.cwd as string) || process.cwd(),
1814
1871
  plan: row.plan ? JSON.parse(row.plan as string) : [],
1872
+ metadata: row.metadata ? JSON.parse(row.metadata as string) : undefined,
1815
1873
  created_at: row.created_at as Timestamp,
1816
1874
  started_at: (row.started_at as number) || undefined,
1817
1875
  stopped_at: (row.stopped_at as number) || undefined,
@@ -1839,6 +1897,7 @@ function rowToTask(row: Record<string, unknown>): Task {
1839
1897
  outputs: row.outputs ? JSON.parse(row.outputs as string) : undefined,
1840
1898
  artifacts: row.artifacts ? JSON.parse(row.artifacts as string) : undefined,
1841
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,
1842
1901
  retryPolicy: row.retry_policy ? JSON.parse(row.retry_policy as string) : undefined,
1843
1902
  retryState: row.retry_state ? JSON.parse(row.retry_state as string) : undefined,
1844
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;
@@ -41,9 +43,22 @@ export interface Agent {
41
43
  config: AgentConfig;
42
44
  cwd: string;
43
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>;
44
48
  created_at: Timestamp;
45
49
  started_at?: Timestamp;
46
50
  stopped_at?: Timestamp;
47
51
  /** Last time this agent emitted an event (for health monitoring) */
48
52
  last_activity_at?: Timestamp;
49
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
+ });