@cleocode/core 2026.3.74 → 2026.4.0

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 (428) hide show
  1. package/README.md +1 -1
  2. package/dist/agents/agent-schema.d.ts.map +1 -1
  3. package/dist/agents/retry.js +26 -21
  4. package/dist/agents/retry.js.map +1 -1
  5. package/dist/cant/approval.d.ts +110 -0
  6. package/dist/cant/approval.d.ts.map +1 -0
  7. package/dist/cant/approval.js +185 -0
  8. package/dist/cant/approval.js.map +1 -0
  9. package/dist/cant/context-builder.d.ts +79 -0
  10. package/dist/cant/context-builder.d.ts.map +1 -0
  11. package/dist/cant/context-builder.js +117 -0
  12. package/dist/cant/context-builder.js.map +1 -0
  13. package/dist/cant/discretion.d.ts +95 -0
  14. package/dist/cant/discretion.d.ts.map +1 -0
  15. package/dist/cant/discretion.js +116 -0
  16. package/dist/cant/discretion.js.map +1 -0
  17. package/dist/cant/index.d.ts +25 -0
  18. package/dist/cant/index.d.ts.map +1 -0
  19. package/dist/cant/index.js +23 -0
  20. package/dist/cant/index.js.map +1 -0
  21. package/dist/cant/parallel-runner.d.ts +38 -0
  22. package/dist/cant/parallel-runner.d.ts.map +1 -0
  23. package/dist/cant/parallel-runner.js +173 -0
  24. package/dist/cant/parallel-runner.js.map +1 -0
  25. package/dist/cant/types.d.ts +127 -0
  26. package/dist/cant/types.d.ts.map +1 -0
  27. package/dist/cant/types.js +11 -0
  28. package/dist/cant/types.js.map +1 -0
  29. package/dist/cant/workflow-executor.d.ts +105 -0
  30. package/dist/cant/workflow-executor.d.ts.map +1 -0
  31. package/dist/cant/workflow-executor.js +440 -0
  32. package/dist/cant/workflow-executor.js.map +1 -0
  33. package/dist/cleo.js +21 -1
  34. package/dist/cleo.js.map +1 -1
  35. package/dist/code/index.d.ts +10 -0
  36. package/dist/code/index.d.ts.map +1 -0
  37. package/dist/code/outline.d.ts +51 -0
  38. package/dist/code/outline.d.ts.map +1 -0
  39. package/dist/code/parser.d.ts +30 -0
  40. package/dist/code/parser.d.ts.map +1 -0
  41. package/dist/code/search.d.ts +42 -0
  42. package/dist/code/search.d.ts.map +1 -0
  43. package/dist/code/unfold.d.ts +44 -0
  44. package/dist/code/unfold.d.ts.map +1 -0
  45. package/dist/conduit/conduit-client.d.ts +35 -0
  46. package/dist/conduit/conduit-client.d.ts.map +1 -0
  47. package/dist/conduit/conduit-client.js +94 -0
  48. package/dist/conduit/conduit-client.js.map +1 -0
  49. package/dist/conduit/factory.d.ts +15 -0
  50. package/dist/conduit/factory.d.ts.map +1 -0
  51. package/dist/conduit/factory.js +35 -0
  52. package/dist/conduit/factory.js.map +1 -0
  53. package/dist/conduit/http-transport.d.ts +44 -0
  54. package/dist/conduit/http-transport.d.ts.map +1 -0
  55. package/dist/conduit/http-transport.js +165 -0
  56. package/dist/conduit/http-transport.js.map +1 -0
  57. package/dist/conduit/index.d.ts +15 -0
  58. package/dist/conduit/index.d.ts.map +1 -0
  59. package/dist/conduit/index.js +12 -0
  60. package/dist/conduit/index.js.map +1 -0
  61. package/dist/conduit/local-transport.d.ts +91 -0
  62. package/dist/conduit/local-transport.d.ts.map +1 -0
  63. package/dist/conduit/sse-transport.d.ts +68 -0
  64. package/dist/conduit/sse-transport.d.ts.map +1 -0
  65. package/dist/config.js +4 -3
  66. package/dist/config.js.map +1 -1
  67. package/dist/crypto/credentials.d.ts +40 -0
  68. package/dist/crypto/credentials.d.ts.map +1 -0
  69. package/dist/crypto/credentials.js +144 -0
  70. package/dist/crypto/credentials.js.map +1 -0
  71. package/dist/engine-result.d.ts +1 -1
  72. package/dist/engine-result.d.ts.map +1 -1
  73. package/dist/error-catalog.d.ts +1 -1
  74. package/dist/error-catalog.d.ts.map +1 -1
  75. package/dist/error-registry.d.ts +1 -1
  76. package/dist/error-registry.d.ts.map +1 -1
  77. package/dist/errors.d.ts +1 -1
  78. package/dist/errors.d.ts.map +1 -1
  79. package/dist/hooks/handlers/agent-hooks.d.ts.map +1 -1
  80. package/dist/hooks/handlers/agent-hooks.js +106 -0
  81. package/dist/hooks/handlers/agent-hooks.js.map +1 -0
  82. package/dist/hooks/handlers/context-hooks.d.ts.map +1 -1
  83. package/dist/hooks/handlers/context-hooks.js +111 -0
  84. package/dist/hooks/handlers/context-hooks.js.map +1 -0
  85. package/dist/hooks/handlers/error-hooks.d.ts +14 -5
  86. package/dist/hooks/handlers/error-hooks.d.ts.map +1 -1
  87. package/dist/hooks/handlers/error-hooks.js +15 -6
  88. package/dist/hooks/handlers/error-hooks.js.map +1 -1
  89. package/dist/hooks/handlers/file-hooks.d.ts.map +1 -1
  90. package/dist/hooks/handlers/file-hooks.js +35 -11
  91. package/dist/hooks/handlers/file-hooks.js.map +1 -1
  92. package/dist/hooks/handlers/handler-helpers.d.ts +41 -0
  93. package/dist/hooks/handlers/handler-helpers.d.ts.map +1 -0
  94. package/dist/hooks/handlers/handler-helpers.js +61 -0
  95. package/dist/hooks/handlers/handler-helpers.js.map +1 -0
  96. package/dist/hooks/handlers/index.js +10 -1
  97. package/dist/hooks/handlers/index.js.map +1 -1
  98. package/dist/hooks/handlers/mcp-hooks.d.ts.map +1 -1
  99. package/dist/hooks/handlers/mcp-hooks.js +88 -21
  100. package/dist/hooks/handlers/mcp-hooks.js.map +1 -1
  101. package/dist/hooks/handlers/session-hooks.d.ts.map +1 -1
  102. package/dist/hooks/handlers/session-hooks.js +5 -10
  103. package/dist/hooks/handlers/session-hooks.js.map +1 -1
  104. package/dist/hooks/handlers/task-hooks.d.ts.map +1 -1
  105. package/dist/hooks/handlers/task-hooks.js +5 -10
  106. package/dist/hooks/handlers/task-hooks.js.map +1 -1
  107. package/dist/hooks/handlers/work-capture-hooks.d.ts.map +1 -1
  108. package/dist/hooks/handlers/work-capture-hooks.js +165 -0
  109. package/dist/hooks/handlers/work-capture-hooks.js.map +1 -0
  110. package/dist/hooks/payload-schemas.js +83 -26
  111. package/dist/hooks/payload-schemas.js.map +1 -1
  112. package/dist/hooks/provider-hooks.js +37 -5
  113. package/dist/hooks/provider-hooks.js.map +1 -1
  114. package/dist/hooks/registry.js +76 -23
  115. package/dist/hooks/registry.js.map +1 -1
  116. package/dist/hooks/types.js +17 -13
  117. package/dist/hooks/types.js.map +1 -1
  118. package/dist/index.d.ts +4 -1
  119. package/dist/index.d.ts.map +1 -1
  120. package/dist/index.js +6452 -3371
  121. package/dist/index.js.map +4 -4
  122. package/dist/init.d.ts.map +1 -1
  123. package/dist/init.js +12 -0
  124. package/dist/init.js.map +1 -1
  125. package/dist/internal.d.ts +11 -1
  126. package/dist/internal.d.ts.map +1 -1
  127. package/dist/internal.js +10 -0
  128. package/dist/internal.js.map +1 -1
  129. package/dist/lib/index.d.ts +1 -0
  130. package/dist/lib/index.d.ts.map +1 -1
  131. package/dist/lib/tree-sitter-languages.d.ts +29 -0
  132. package/dist/lib/tree-sitter-languages.d.ts.map +1 -0
  133. package/dist/memory/brain-links.d.ts.map +1 -1
  134. package/dist/memory/brain-maintenance.d.ts +13 -0
  135. package/dist/memory/brain-maintenance.d.ts.map +1 -1
  136. package/dist/memory/brain-retrieval.d.ts +3 -0
  137. package/dist/memory/brain-retrieval.d.ts.map +1 -1
  138. package/dist/memory/brain-retrieval.js +5 -0
  139. package/dist/memory/brain-retrieval.js.map +1 -1
  140. package/dist/memory/decisions.d.ts.map +1 -1
  141. package/dist/mvi-helpers.d.ts +52 -0
  142. package/dist/mvi-helpers.d.ts.map +1 -0
  143. package/dist/mvi-helpers.js +74 -0
  144. package/dist/mvi-helpers.js.map +1 -0
  145. package/dist/nexus/index.js +2 -0
  146. package/dist/nexus/index.js.map +1 -1
  147. package/dist/nexus/workspace.d.ts.map +1 -1
  148. package/dist/nexus/workspace.js +355 -0
  149. package/dist/nexus/workspace.js.map +1 -0
  150. package/dist/orchestration/hierarchy.d.ts +32 -0
  151. package/dist/orchestration/hierarchy.d.ts.map +1 -0
  152. package/dist/orchestration/index.d.ts +1 -0
  153. package/dist/orchestration/index.d.ts.map +1 -1
  154. package/dist/output.d.ts +2 -2
  155. package/dist/output.d.ts.map +1 -1
  156. package/dist/output.js +40 -8
  157. package/dist/output.js.map +1 -1
  158. package/dist/pagination.d.ts +1 -1
  159. package/dist/pagination.d.ts.map +1 -1
  160. package/dist/sessions/find.d.ts +3 -0
  161. package/dist/sessions/find.d.ts.map +1 -1
  162. package/dist/sessions/find.js +3 -1
  163. package/dist/sessions/find.js.map +1 -1
  164. package/dist/sessions/index.d.ts.map +1 -1
  165. package/dist/sessions/index.js +11 -4
  166. package/dist/sessions/index.js.map +1 -1
  167. package/dist/sessions/snapshot.js +213 -0
  168. package/dist/sessions/snapshot.js.map +1 -0
  169. package/dist/store/agent-registry-accessor.d.ts +31 -0
  170. package/dist/store/agent-registry-accessor.d.ts.map +1 -0
  171. package/dist/store/agent-registry-accessor.js +169 -0
  172. package/dist/store/agent-registry-accessor.js.map +1 -0
  173. package/dist/store/converters.d.ts.map +1 -1
  174. package/dist/store/converters.js +2 -0
  175. package/dist/store/converters.js.map +1 -1
  176. package/dist/store/cross-db-cleanup.d.ts +34 -0
  177. package/dist/store/cross-db-cleanup.d.ts.map +1 -1
  178. package/dist/store/db-helpers.d.ts.map +1 -1
  179. package/dist/store/db-helpers.js +1 -0
  180. package/dist/store/db-helpers.js.map +1 -1
  181. package/dist/store/json.js +2 -2
  182. package/dist/store/safety-data-accessor.d.ts +7 -0
  183. package/dist/store/safety-data-accessor.d.ts.map +1 -1
  184. package/dist/store/safety-data-accessor.js +14 -0
  185. package/dist/store/safety-data-accessor.js.map +1 -1
  186. package/dist/store/signaldock-sqlite.d.ts +48 -0
  187. package/dist/store/signaldock-sqlite.d.ts.map +1 -0
  188. package/dist/store/signaldock-sqlite.js +178 -0
  189. package/dist/store/signaldock-sqlite.js.map +1 -0
  190. package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
  191. package/dist/store/sqlite-data-accessor.js +50 -0
  192. package/dist/store/sqlite-data-accessor.js.map +1 -1
  193. package/dist/store/sqlite.d.ts.map +1 -1
  194. package/dist/store/sqlite.js +30 -1
  195. package/dist/store/sqlite.js.map +1 -1
  196. package/dist/store/task-store.d.ts.map +1 -1
  197. package/dist/store/task-store.js +2 -0
  198. package/dist/store/task-store.js.map +1 -1
  199. package/dist/store/tasks-schema.d.ts +16 -0
  200. package/dist/store/tasks-schema.d.ts.map +1 -1
  201. package/dist/store/tasks-schema.js +33 -0
  202. package/dist/store/tasks-schema.js.map +1 -1
  203. package/dist/store/validation-schemas.d.ts +32 -0
  204. package/dist/store/validation-schemas.d.ts.map +1 -1
  205. package/dist/system/health.d.ts +1 -1
  206. package/dist/system/health.d.ts.map +1 -1
  207. package/dist/system/health.js +35 -0
  208. package/dist/system/health.js.map +1 -1
  209. package/dist/task-work/index.d.ts.map +1 -1
  210. package/dist/task-work/index.js +8 -4
  211. package/dist/task-work/index.js.map +1 -1
  212. package/dist/tasks/complete.js +5 -2
  213. package/dist/tasks/complete.js.map +1 -1
  214. package/dist/tasks/find.d.ts +3 -0
  215. package/dist/tasks/find.d.ts.map +1 -1
  216. package/dist/tasks/find.js +7 -1
  217. package/dist/tasks/find.js.map +1 -1
  218. package/dist/tasks/list.d.ts +5 -2
  219. package/dist/tasks/list.d.ts.map +1 -1
  220. package/dist/tasks/list.js +9 -2
  221. package/dist/tasks/list.js.map +1 -1
  222. package/dist/tasks/show.d.ts +3 -0
  223. package/dist/tasks/show.d.ts.map +1 -1
  224. package/dist/tasks/show.js +2 -0
  225. package/dist/tasks/show.js.map +1 -1
  226. package/dist/upgrade.d.ts.map +1 -1
  227. package/dist/upgrade.js +15 -0
  228. package/dist/upgrade.js.map +1 -1
  229. package/migrations/drizzle-tasks/20260324000000_assignee-column/migration.sql +6 -0
  230. package/migrations/drizzle-tasks/20260324000000_assignee-column/snapshot.json +9 -0
  231. package/migrations/drizzle-tasks/20260327000000_agent-credentials/migration.sql +23 -0
  232. package/package.json +17 -7
  233. package/src/__tests__/cli-parity.test.js +11 -1
  234. package/src/__tests__/cli-parity.test.js.map +1 -1
  235. package/src/__tests__/cli-parity.test.ts +17 -1
  236. package/src/__tests__/human-output.test.js +11 -1
  237. package/src/__tests__/human-output.test.js.map +1 -1
  238. package/src/__tests__/human-output.test.ts +18 -1
  239. package/src/__tests__/injection-chain.test.js +3 -2
  240. package/src/__tests__/injection-chain.test.js.map +1 -1
  241. package/src/__tests__/injection-mvi-tiers.test.d.ts +2 -2
  242. package/src/__tests__/injection-mvi-tiers.test.js +15 -15
  243. package/src/__tests__/injection-mvi-tiers.test.js.map +1 -1
  244. package/src/__tests__/lafs-conformance.test.d.ts +1 -1
  245. package/src/__tests__/lafs-conformance.test.js +2 -2
  246. package/src/__tests__/sharing.test.js +19 -0
  247. package/src/__tests__/sharing.test.js.map +1 -1
  248. package/src/agents/__tests__/agent-registry.test.d.ts +12 -0
  249. package/src/agents/__tests__/agent-registry.test.d.ts.map +1 -0
  250. package/src/agents/__tests__/agent-registry.test.js +262 -0
  251. package/src/agents/__tests__/agent-registry.test.js.map +1 -0
  252. package/src/agents/__tests__/execution-learning.test.d.ts +14 -0
  253. package/src/agents/__tests__/execution-learning.test.d.ts.map +1 -0
  254. package/src/agents/__tests__/execution-learning.test.js +533 -0
  255. package/src/agents/__tests__/execution-learning.test.js.map +1 -0
  256. package/src/agents/__tests__/health-monitor.test.d.ts +10 -0
  257. package/src/agents/__tests__/health-monitor.test.d.ts.map +1 -0
  258. package/src/agents/__tests__/health-monitor.test.js +259 -0
  259. package/src/agents/__tests__/health-monitor.test.js.map +1 -0
  260. package/src/agents/__tests__/registry.test.js +27 -2
  261. package/src/agents/__tests__/registry.test.js.map +1 -1
  262. package/src/agents/agent-schema.ts +2 -5
  263. package/src/cant/__tests__/cant-agent-parse.test.ts +94 -0
  264. package/src/cant/approval.ts +218 -0
  265. package/src/cant/context-builder.ts +135 -0
  266. package/src/cant/discretion.ts +149 -0
  267. package/src/cant/index.ts +58 -0
  268. package/src/cant/parallel-runner.ts +205 -0
  269. package/src/cant/types.ts +158 -0
  270. package/src/cant/workflow-executor.ts +618 -0
  271. package/src/code/index.ts +10 -0
  272. package/src/code/outline.ts +214 -0
  273. package/src/code/parser.ts +299 -0
  274. package/src/code/search.ts +173 -0
  275. package/src/code/unfold.ts +204 -0
  276. package/src/conduit/__tests__/dual-api-e2e.test.ts +212 -0
  277. package/src/conduit/__tests__/local-credential-flow.test.ts +230 -0
  278. package/src/conduit/__tests__/local-transport.test.ts +320 -0
  279. package/src/conduit/__tests__/sse-transport.test.ts +344 -0
  280. package/src/conduit/conduit-client.ts +123 -0
  281. package/src/conduit/factory.ts +49 -0
  282. package/src/conduit/http-transport.ts +201 -0
  283. package/src/conduit/index.ts +15 -0
  284. package/src/conduit/local-transport.ts +309 -0
  285. package/src/conduit/sse-transport.ts +382 -0
  286. package/src/crypto/credentials.ts +166 -0
  287. package/src/engine-result.ts +1 -1
  288. package/src/error-catalog.ts +1 -1
  289. package/src/error-registry.ts +1 -1
  290. package/src/errors.ts +1 -1
  291. package/src/hooks/handlers/__tests__/hook-automation-e2e.test.d.ts +13 -0
  292. package/src/hooks/handlers/__tests__/hook-automation-e2e.test.d.ts.map +1 -0
  293. package/src/hooks/handlers/__tests__/hook-automation-e2e.test.js +501 -0
  294. package/src/hooks/handlers/__tests__/hook-automation-e2e.test.js.map +1 -0
  295. package/src/hooks/handlers/agent-hooks.ts +1 -30
  296. package/src/hooks/handlers/context-hooks.ts +1 -30
  297. package/src/hooks/handlers/error-hooks.ts +14 -5
  298. package/src/hooks/handlers/file-hooks.ts +1 -6
  299. package/src/hooks/handlers/handler-helpers.ts +62 -0
  300. package/src/hooks/handlers/mcp-hooks.ts +2 -14
  301. package/src/hooks/handlers/session-hooks.ts +1 -6
  302. package/src/hooks/handlers/task-hooks.ts +1 -6
  303. package/src/hooks/handlers/work-capture-hooks.ts +1 -10
  304. package/src/index.ts +12 -1
  305. package/src/init.ts +12 -0
  306. package/src/intelligence/__tests__/adaptive-validation.test.d.ts +11 -0
  307. package/src/intelligence/__tests__/adaptive-validation.test.d.ts.map +1 -0
  308. package/src/intelligence/__tests__/adaptive-validation.test.js +517 -0
  309. package/src/intelligence/__tests__/adaptive-validation.test.js.map +1 -0
  310. package/src/intelligence/__tests__/impact.test.d.ts +1 -0
  311. package/src/intelligence/__tests__/impact.test.d.ts.map +1 -1
  312. package/src/intelligence/__tests__/impact.test.js +132 -1
  313. package/src/intelligence/__tests__/impact.test.js.map +1 -1
  314. package/src/internal.ts +22 -0
  315. package/src/lib/__tests__/retry.test.d.ts +7 -0
  316. package/src/lib/__tests__/retry.test.d.ts.map +1 -0
  317. package/src/lib/__tests__/retry.test.js +225 -0
  318. package/src/lib/__tests__/retry.test.js.map +1 -0
  319. package/src/lib/index.ts +8 -0
  320. package/src/lib/tree-sitter-languages.ts +88 -0
  321. package/src/lifecycle/__tests__/chain-store.test.js +6 -0
  322. package/src/lifecycle/__tests__/chain-store.test.js.map +1 -1
  323. package/src/lifecycle/__tests__/tessera-engine.test.js +52 -0
  324. package/src/lifecycle/__tests__/tessera-engine.test.js.map +1 -1
  325. package/src/memory/__tests__/brain-automation.test.d.ts +11 -0
  326. package/src/memory/__tests__/brain-automation.test.d.ts.map +1 -0
  327. package/src/memory/__tests__/brain-automation.test.js +730 -0
  328. package/src/memory/__tests__/brain-automation.test.js.map +1 -0
  329. package/src/memory/__tests__/brain-links.test.ts +14 -0
  330. package/src/memory/__tests__/brain-retrieval.test.ts +10 -0
  331. package/src/memory/__tests__/session-memory.test.ts +17 -0
  332. package/src/memory/brain-links.ts +17 -0
  333. package/src/memory/brain-maintenance.ts +33 -1
  334. package/src/memory/brain-retrieval.ts +27 -2
  335. package/src/memory/decisions.ts +18 -2
  336. package/src/mvi-helpers.ts +81 -0
  337. package/src/nexus/workspace.ts +19 -7
  338. package/src/orchestration/hierarchy.ts +202 -0
  339. package/src/orchestration/index.ts +1 -0
  340. package/src/output.ts +43 -10
  341. package/src/pagination.ts +1 -1
  342. package/src/sessions/__tests__/session-edge-cases.test.js +20 -1
  343. package/src/sessions/__tests__/session-edge-cases.test.js.map +1 -1
  344. package/src/sessions/__tests__/session-find.test.js +1 -1
  345. package/src/sessions/__tests__/session-find.test.js.map +1 -1
  346. package/src/sessions/__tests__/session-find.test.ts +1 -1
  347. package/src/sessions/find.ts +6 -1
  348. package/src/sessions/index.ts +9 -0
  349. package/src/store/__tests__/migration-safety.test.js +3 -0
  350. package/src/store/__tests__/migration-safety.test.js.map +1 -1
  351. package/src/store/__tests__/session-store.test.js +128 -1
  352. package/src/store/__tests__/session-store.test.js.map +1 -1
  353. package/src/store/__tests__/task-store.test.js +18 -1
  354. package/src/store/__tests__/task-store.test.js.map +1 -1
  355. package/src/store/__tests__/test-db-helper.d.ts.map +1 -1
  356. package/src/store/__tests__/test-db-helper.js +12 -0
  357. package/src/store/__tests__/test-db-helper.js.map +1 -1
  358. package/src/store/agent-registry-accessor.ts +375 -0
  359. package/src/store/converters.ts +2 -0
  360. package/src/store/cross-db-cleanup.ts +175 -1
  361. package/src/store/db-helpers.ts +1 -0
  362. package/src/store/safety-data-accessor.ts +23 -0
  363. package/src/store/signaldock-sqlite.ts +429 -0
  364. package/src/store/sqlite-data-accessor.ts +72 -0
  365. package/src/store/sqlite.ts +4 -1
  366. package/src/store/task-store.ts +9 -1
  367. package/src/store/tasks-schema.ts +7 -0
  368. package/src/system/__tests__/health.test.ts +2 -2
  369. package/src/system/health.ts +54 -2
  370. package/src/task-work/index.ts +5 -0
  371. package/src/tasks/__tests__/add.test.js +19 -1
  372. package/src/tasks/__tests__/add.test.js.map +1 -1
  373. package/src/tasks/__tests__/assignee.test.d.ts +14 -0
  374. package/src/tasks/__tests__/assignee.test.d.ts.map +1 -0
  375. package/src/tasks/__tests__/assignee.test.js +125 -0
  376. package/src/tasks/__tests__/assignee.test.js.map +1 -0
  377. package/src/tasks/__tests__/assignee.test.ts +162 -0
  378. package/src/tasks/__tests__/complete-unblocks.test.js +13 -1
  379. package/src/tasks/__tests__/complete-unblocks.test.js.map +1 -1
  380. package/src/tasks/__tests__/complete.test.js +28 -7
  381. package/src/tasks/__tests__/complete.test.js.map +1 -1
  382. package/src/tasks/__tests__/epic-enforcement.test.d.ts +15 -0
  383. package/src/tasks/__tests__/epic-enforcement.test.d.ts.map +1 -0
  384. package/src/tasks/__tests__/epic-enforcement.test.js +669 -0
  385. package/src/tasks/__tests__/epic-enforcement.test.js.map +1 -0
  386. package/src/tasks/__tests__/hierarchy-policy.test.js +5 -0
  387. package/src/tasks/__tests__/hierarchy-policy.test.js.map +1 -1
  388. package/src/tasks/__tests__/minimal-test.test.d.ts +2 -0
  389. package/src/tasks/__tests__/minimal-test.test.d.ts.map +1 -0
  390. package/src/tasks/__tests__/minimal-test.test.js +25 -0
  391. package/src/tasks/__tests__/minimal-test.test.js.map +1 -0
  392. package/src/tasks/__tests__/pipeline-stage.test.d.ts +14 -0
  393. package/src/tasks/__tests__/pipeline-stage.test.d.ts.map +1 -0
  394. package/src/tasks/__tests__/pipeline-stage.test.js +277 -0
  395. package/src/tasks/__tests__/pipeline-stage.test.js.map +1 -0
  396. package/src/tasks/__tests__/update.test.js +43 -6
  397. package/src/tasks/__tests__/update.test.js.map +1 -1
  398. package/src/tasks/find.ts +11 -1
  399. package/src/tasks/list.ts +14 -3
  400. package/src/tasks/show.ts +6 -0
  401. package/src/upgrade.ts +16 -0
  402. package/dist/tasks/reparent.d.ts +0 -38
  403. package/dist/tasks/reparent.d.ts.map +0 -1
  404. package/dist/ui/injection-legacy.d.ts +0 -26
  405. package/dist/ui/injection-legacy.d.ts.map +0 -1
  406. package/dist/ui/injection-legacy.js +0 -42
  407. package/dist/ui/injection-legacy.js.map +0 -1
  408. package/src/signaldock/__tests__/claude-code-transport.test.d.ts +0 -7
  409. package/src/signaldock/__tests__/claude-code-transport.test.d.ts.map +0 -1
  410. package/src/signaldock/__tests__/claude-code-transport.test.js +0 -147
  411. package/src/signaldock/__tests__/claude-code-transport.test.js.map +0 -1
  412. package/src/signaldock/__tests__/claude-code-transport.test.ts +0 -180
  413. package/src/signaldock/__tests__/factory.test.d.ts +0 -7
  414. package/src/signaldock/__tests__/factory.test.d.ts.map +0 -1
  415. package/src/signaldock/__tests__/factory.test.js +0 -55
  416. package/src/signaldock/__tests__/factory.test.js.map +0 -1
  417. package/src/signaldock/__tests__/factory.test.ts +0 -61
  418. package/src/signaldock/__tests__/signaldock-transport.test.d.ts +0 -9
  419. package/src/signaldock/__tests__/signaldock-transport.test.d.ts.map +0 -1
  420. package/src/signaldock/__tests__/signaldock-transport.test.js +0 -321
  421. package/src/signaldock/__tests__/signaldock-transport.test.js.map +0 -1
  422. package/src/signaldock/__tests__/signaldock-transport.test.ts +0 -421
  423. package/src/signaldock/claude-code-transport.ts +0 -137
  424. package/src/signaldock/factory.ts +0 -39
  425. package/src/signaldock/index.ts +0 -28
  426. package/src/signaldock/signaldock-transport.ts +0 -194
  427. package/src/signaldock/transport.ts +0 -78
  428. package/src/signaldock/types.ts +0 -100
@@ -39,7 +39,19 @@ export async function createTestDb() {
39
39
  verification: { enabled: false },
40
40
  });
41
41
  writeFileSync(join(cleoDir, 'config.json'), configContent);
42
+ // Verify write succeeded
43
+ const { readdirSync } = await import('node:fs');
44
+ const contents = readdirSync(cleoDir);
45
+ if (!contents.includes('config.json')) {
46
+ throw new Error(`createTestDb: config.json not found in ${cleoDir} after write (contents: ${JSON.stringify(contents)})`);
47
+ }
42
48
  const accessor = await createSqliteDataAccessor(tempDir);
49
+ // Verify config.json still exists after DB initialization
50
+ const { readdirSync: readdirSync2 } = await import('node:fs');
51
+ const contentsAfterDb = readdirSync2(cleoDir);
52
+ if (!contentsAfterDb.includes('config.json')) {
53
+ throw new Error(`createTestDb: config.json DELETED by createSqliteDataAccessor! ${cleoDir}: ${JSON.stringify(contentsAfterDb)}`);
54
+ }
43
55
  return {
44
56
  tempDir,
45
57
  cleoDir,
@@ -1 +1 @@
1
- {"version":3,"file":"test-db-helper.js","sourceRoot":"","sources":["test-db-helper.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AActE;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC;IAC1D,oDAAoD;IACpD,YAAY,EAAE,CAAC;IAEf,MAAM,QAAQ,GAAG,MAAM,wBAAwB,CAAC,OAAO,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAEvC,OAAO;QACL,OAAO;QACP,OAAO;QACP,QAAQ;QACR,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;YACvB,YAAY,EAAE,CAAC;YACf,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,KAA4C;IACpE,OAAO,KAAK,CAAC,GAAG,CACd,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC;QACC,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,QAAQ,CAAC,CAAC,EAAE,EAAE;QAChC,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,SAAS;QACvC,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,SAAS;QAC7B,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,QAAQ;QAChC,SAAS,EAAE,CAAC,CAAC,SAAS,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAClD,GAAG,CAAC;KACL,CAAS,CACb,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,QAAsB,EACtB,KAA4C;IAE5C,+CAA+C;IAC/C,MAAM,QAAQ,CAAC,YAAY,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;IAExD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO;IACT,CAAC;IAED,wCAAwC;IACxC,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IAEnC,oEAAoE;IACpE,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,MAAM,QAAQ,CAAC,gBAAgB,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;IACnE,CAAC;IAED,0EAA0E;IAC1E,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACrE,IAAI,OAAO,EAAE,CAAC;QACZ,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC7B,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC5C,MAAM,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;YACxC,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"test-db-helper.js","sourceRoot":"","sources":["test-db-helper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Agent Registry Accessor — CRUD operations for agent data in signaldock.db.
3
+ *
4
+ * signaldock.db is the SSoT for ALL agent data: identity, credentials,
5
+ * capabilities, skills, transport config. No agent data lives in tasks.db.
6
+ *
7
+ * API keys are encrypted at rest using the crypto/credentials module.
8
+ *
9
+ * @see docs/specs/DATABASE-ARCHITECTURE.md
10
+ * @task T234
11
+ */
12
+
13
+ import { createRequire } from 'node:module';
14
+ import type { DatabaseSync } from 'node:sqlite';
15
+ import type {
16
+ AgentCredential,
17
+ AgentListFilter,
18
+ AgentRegistryAPI,
19
+ TransportConfig,
20
+ } from '@cleocode/contracts';
21
+ import { decrypt, encrypt } from '../crypto/credentials.js';
22
+ import { ensureSignaldockDb, getSignaldockDbPath } from './signaldock-sqlite.js';
23
+
24
+ const _require = createRequire(import.meta.url);
25
+ const { DatabaseSync: DatabaseSyncClass } = _require('node:sqlite') as {
26
+ DatabaseSync: new (...args: ConstructorParameters<typeof DatabaseSync>) => DatabaseSync;
27
+ };
28
+
29
+ /** Raw row shape from signaldock.db agents table. */
30
+ interface AgentDbRow {
31
+ id: string;
32
+ agent_id: string;
33
+ name: string;
34
+ description: string | null;
35
+ class: string;
36
+ privacy_tier: string;
37
+ capabilities: string;
38
+ skills: string;
39
+ transport_type: string;
40
+ api_key_encrypted: string | null;
41
+ api_base_url: string;
42
+ classification: string | null;
43
+ transport_config: string;
44
+ is_active: number;
45
+ last_used_at: number | null;
46
+ created_at: number;
47
+ updated_at: number;
48
+ }
49
+
50
+ /** Convert a signaldock.db row to an AgentCredential, decrypting the API key. */
51
+ async function rowToCredential(row: AgentDbRow, projectPath: string): Promise<AgentCredential> {
52
+ const apiKey = row.api_key_encrypted ? await decrypt(row.api_key_encrypted, projectPath) : '';
53
+ return {
54
+ agentId: row.agent_id,
55
+ displayName: row.name,
56
+ apiKey,
57
+ apiBaseUrl: row.api_base_url,
58
+ classification: row.classification ?? undefined,
59
+ privacyTier: row.privacy_tier as AgentCredential['privacyTier'],
60
+ capabilities: JSON.parse(row.capabilities) as string[],
61
+ skills: JSON.parse(row.skills) as string[],
62
+ transportType: (row.transport_type ?? 'http') as AgentCredential['transportType'],
63
+ transportConfig: JSON.parse(row.transport_config) as TransportConfig,
64
+ isActive: row.is_active === 1,
65
+ lastUsedAt: row.last_used_at ? new Date(row.last_used_at * 1000).toISOString() : undefined,
66
+ createdAt: new Date(row.created_at * 1000).toISOString(),
67
+ updatedAt: new Date(row.updated_at * 1000).toISOString(),
68
+ };
69
+ }
70
+
71
+ /** Open signaldock.db for read/write operations. Caller must close. */
72
+ function openDb(projectPath: string): DatabaseSync {
73
+ const dbPath = getSignaldockDbPath(projectPath);
74
+ const db = new DatabaseSyncClass(dbPath);
75
+ db.exec('PRAGMA foreign_keys = ON');
76
+ db.exec('PRAGMA journal_mode = WAL');
77
+ return db;
78
+ }
79
+
80
+ /**
81
+ * Sync capabilities/skills to junction tables in signaldock.db.
82
+ * Junction tables are the SSoT — JSON columns are materialized cache.
83
+ */
84
+ function syncJunctionTables(
85
+ db: DatabaseSync,
86
+ agentUuid: string,
87
+ capabilities: string[],
88
+ skills: string[],
89
+ ): void {
90
+ db.prepare('DELETE FROM agent_capabilities WHERE agent_id = ?').run(agentUuid);
91
+ db.prepare('DELETE FROM agent_skills WHERE agent_id = ?').run(agentUuid);
92
+ for (const cap of capabilities) {
93
+ const capRow = db.prepare('SELECT id FROM capabilities WHERE slug = ?').get(cap) as
94
+ | { id: string }
95
+ | undefined;
96
+ if (capRow) {
97
+ db.prepare(
98
+ 'INSERT OR IGNORE INTO agent_capabilities (agent_id, capability_id) VALUES (?, ?)',
99
+ ).run(agentUuid, capRow.id);
100
+ }
101
+ }
102
+ for (const skill of skills) {
103
+ const skillRow = db.prepare('SELECT id FROM skills WHERE slug = ?').get(skill) as
104
+ | { id: string }
105
+ | undefined;
106
+ if (skillRow) {
107
+ db.prepare('INSERT OR IGNORE INTO agent_skills (agent_id, skill_id) VALUES (?, ?)').run(
108
+ agentUuid,
109
+ skillRow.id,
110
+ );
111
+ }
112
+ }
113
+ }
114
+
115
+ /** signaldock.db implementation of the AgentRegistryAPI. */
116
+ export class AgentRegistryAccessor implements AgentRegistryAPI {
117
+ constructor(private projectPath: string) {}
118
+
119
+ /** Ensure signaldock.db exists with full schema before any operation. */
120
+ private async ensureDb(): Promise<void> {
121
+ await ensureSignaldockDb(this.projectPath);
122
+ }
123
+
124
+ async register(
125
+ credential: Omit<AgentCredential, 'createdAt' | 'updatedAt'>,
126
+ ): Promise<AgentCredential> {
127
+ await this.ensureDb();
128
+ const nowTs = Math.floor(Date.now() / 1000);
129
+ const apiKeyEncrypted = credential.apiKey
130
+ ? await encrypt(credential.apiKey, this.projectPath)
131
+ : null;
132
+
133
+ const db = openDb(this.projectPath);
134
+ try {
135
+ const existing = db
136
+ .prepare('SELECT id FROM agents WHERE agent_id = ?')
137
+ .get(credential.agentId) as { id: string } | undefined;
138
+
139
+ if (!existing) {
140
+ const id = crypto.randomUUID();
141
+ db.prepare(
142
+ `INSERT INTO agents (id, agent_id, name, class, privacy_tier, capabilities, skills,
143
+ transport_type, api_key_encrypted, api_base_url, classification, transport_config,
144
+ is_active, last_used_at, status, created_at, updated_at)
145
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'online', ?, ?)`,
146
+ ).run(
147
+ id,
148
+ credential.agentId,
149
+ credential.displayName,
150
+ credential.classification ?? 'custom',
151
+ credential.privacyTier,
152
+ JSON.stringify(credential.capabilities),
153
+ JSON.stringify(credential.skills),
154
+ credential.transportType ?? 'http',
155
+ apiKeyEncrypted,
156
+ credential.apiBaseUrl,
157
+ credential.classification ?? null,
158
+ JSON.stringify(credential.transportConfig),
159
+ credential.isActive ? 1 : 0,
160
+ credential.lastUsedAt
161
+ ? Math.floor(new Date(credential.lastUsedAt).getTime() / 1000)
162
+ : null,
163
+ nowTs,
164
+ nowTs,
165
+ );
166
+ syncJunctionTables(db, id, credential.capabilities, credential.skills);
167
+ } else {
168
+ db.prepare(
169
+ `UPDATE agents SET name = ?, class = ?, privacy_tier = ?, capabilities = ?, skills = ?,
170
+ transport_type = ?, api_key_encrypted = ?, api_base_url = ?, classification = ?,
171
+ transport_config = ?, is_active = ?, updated_at = ? WHERE agent_id = ?`,
172
+ ).run(
173
+ credential.displayName,
174
+ credential.classification ?? 'custom',
175
+ credential.privacyTier,
176
+ JSON.stringify(credential.capabilities),
177
+ JSON.stringify(credential.skills),
178
+ credential.transportType ?? 'http',
179
+ apiKeyEncrypted,
180
+ credential.apiBaseUrl,
181
+ credential.classification ?? null,
182
+ JSON.stringify(credential.transportConfig),
183
+ credential.isActive ? 1 : 0,
184
+ nowTs,
185
+ credential.agentId,
186
+ );
187
+ syncJunctionTables(db, existing.id, credential.capabilities, credential.skills);
188
+ }
189
+ } finally {
190
+ db.close();
191
+ }
192
+
193
+ const result = await this.get(credential.agentId);
194
+ if (!result) throw new Error(`Failed to register agent: ${credential.agentId}`);
195
+ return result;
196
+ }
197
+
198
+ async get(agentId: string): Promise<AgentCredential | null> {
199
+ await this.ensureDb();
200
+ const db = openDb(this.projectPath);
201
+ try {
202
+ const row = db.prepare('SELECT * FROM agents WHERE agent_id = ?').get(agentId) as
203
+ | AgentDbRow
204
+ | undefined;
205
+ if (!row) return null;
206
+ return rowToCredential(row, this.projectPath);
207
+ } finally {
208
+ db.close();
209
+ }
210
+ }
211
+
212
+ async list(filter?: AgentListFilter): Promise<AgentCredential[]> {
213
+ await this.ensureDb();
214
+ const db = openDb(this.projectPath);
215
+ try {
216
+ const rows =
217
+ filter?.active !== undefined
218
+ ? (db
219
+ .prepare('SELECT * FROM agents WHERE is_active = ?')
220
+ .all(filter.active ? 1 : 0) as unknown as AgentDbRow[])
221
+ : (db.prepare('SELECT * FROM agents').all() as unknown as AgentDbRow[]);
222
+ return Promise.all(rows.map((row) => rowToCredential(row, this.projectPath)));
223
+ } finally {
224
+ db.close();
225
+ }
226
+ }
227
+
228
+ async update(
229
+ agentId: string,
230
+ updates: Partial<Omit<AgentCredential, 'agentId' | 'createdAt'>>,
231
+ ): Promise<AgentCredential> {
232
+ const existing = await this.get(agentId);
233
+ if (!existing) throw new Error(`Agent not found: ${agentId}`);
234
+
235
+ const nowTs = Math.floor(Date.now() / 1000);
236
+ const db = openDb(this.projectPath);
237
+ try {
238
+ const sets: string[] = ['updated_at = ?'];
239
+ const params: unknown[] = [nowTs];
240
+
241
+ if (updates.displayName !== undefined) {
242
+ sets.push('name = ?');
243
+ params.push(updates.displayName);
244
+ }
245
+ if (updates.apiBaseUrl !== undefined) {
246
+ sets.push('api_base_url = ?');
247
+ params.push(updates.apiBaseUrl);
248
+ }
249
+ if (updates.classification !== undefined) {
250
+ sets.push('classification = ?');
251
+ params.push(updates.classification);
252
+ }
253
+ if (updates.privacyTier !== undefined) {
254
+ sets.push('privacy_tier = ?');
255
+ params.push(updates.privacyTier);
256
+ }
257
+ if (updates.capabilities !== undefined) {
258
+ sets.push('capabilities = ?');
259
+ params.push(JSON.stringify(updates.capabilities));
260
+ }
261
+ if (updates.skills !== undefined) {
262
+ sets.push('skills = ?');
263
+ params.push(JSON.stringify(updates.skills));
264
+ }
265
+ if (updates.transportType !== undefined) {
266
+ sets.push('transport_type = ?');
267
+ params.push(updates.transportType);
268
+ }
269
+ if (updates.transportConfig !== undefined) {
270
+ sets.push('transport_config = ?');
271
+ params.push(JSON.stringify(updates.transportConfig));
272
+ }
273
+ if (updates.isActive !== undefined) {
274
+ sets.push('is_active = ?');
275
+ params.push(updates.isActive ? 1 : 0);
276
+ }
277
+ if (updates.apiKey !== undefined) {
278
+ const encrypted = await encrypt(updates.apiKey, this.projectPath);
279
+ sets.push('api_key_encrypted = ?');
280
+ params.push(encrypted);
281
+ }
282
+
283
+ params.push(agentId);
284
+ db.prepare(`UPDATE agents SET ${sets.join(', ')} WHERE agent_id = ?`).run(
285
+ ...(params as Array<string | number | null>),
286
+ );
287
+
288
+ // Sync junction tables if capabilities or skills changed
289
+ if (updates.capabilities !== undefined || updates.skills !== undefined) {
290
+ const agentRow = db.prepare('SELECT id FROM agents WHERE agent_id = ?').get(agentId) as {
291
+ id: string;
292
+ };
293
+ syncJunctionTables(
294
+ db,
295
+ agentRow.id,
296
+ updates.capabilities ?? existing.capabilities,
297
+ updates.skills ?? existing.skills,
298
+ );
299
+ }
300
+ } finally {
301
+ db.close();
302
+ }
303
+
304
+ const result = await this.get(agentId);
305
+ if (!result) throw new Error(`Agent not found after update: ${agentId}`);
306
+ return result;
307
+ }
308
+
309
+ async remove(agentId: string): Promise<void> {
310
+ const existing = await this.get(agentId);
311
+ if (!existing) throw new Error(`Agent not found: ${agentId}`);
312
+
313
+ const db = openDb(this.projectPath);
314
+ try {
315
+ db.prepare('DELETE FROM agents WHERE agent_id = ?').run(agentId);
316
+ } finally {
317
+ db.close();
318
+ }
319
+ }
320
+
321
+ async rotateKey(agentId: string): Promise<{ agentId: string; newApiKey: string }> {
322
+ const credential = await this.get(agentId);
323
+ if (!credential) throw new Error(`Agent not found: ${agentId}`);
324
+
325
+ const response = await fetch(`${credential.apiBaseUrl}/agents/${agentId}/rotate-key`, {
326
+ method: 'POST',
327
+ headers: {
328
+ Authorization: `Bearer ${credential.apiKey}`,
329
+ 'X-Agent-Id': agentId,
330
+ },
331
+ });
332
+
333
+ if (!response.ok) {
334
+ throw new Error(`Failed to rotate key on cloud: ${response.status} ${response.statusText}`);
335
+ }
336
+
337
+ const data = (await response.json()) as { data?: { apiKey?: string } };
338
+ const newApiKey = data.data?.apiKey;
339
+ if (!newApiKey) throw new Error('Cloud API did not return a new API key');
340
+
341
+ await this.update(agentId, { apiKey: newApiKey });
342
+ return { agentId, newApiKey: `${newApiKey.substring(0, 8)}...rotated` };
343
+ }
344
+
345
+ async getActive(): Promise<AgentCredential | null> {
346
+ await this.ensureDb();
347
+ const db = openDb(this.projectPath);
348
+ try {
349
+ const row = db
350
+ .prepare(
351
+ 'SELECT * FROM agents WHERE is_active = 1 ORDER BY last_used_at DESC, created_at DESC LIMIT 1',
352
+ )
353
+ .get() as AgentDbRow | undefined;
354
+ if (!row) return null;
355
+ return rowToCredential(row, this.projectPath);
356
+ } finally {
357
+ db.close();
358
+ }
359
+ }
360
+
361
+ async markUsed(agentId: string): Promise<void> {
362
+ await this.ensureDb();
363
+ const nowTs = Math.floor(Date.now() / 1000);
364
+ const db = openDb(this.projectPath);
365
+ try {
366
+ db.prepare('UPDATE agents SET last_used_at = ?, updated_at = ? WHERE agent_id = ?').run(
367
+ nowTs,
368
+ nowTs,
369
+ agentId,
370
+ );
371
+ } finally {
372
+ db.close();
373
+ }
374
+ }
375
+ }
@@ -57,6 +57,7 @@ export function rowToTask(row: TaskRow): Task {
57
57
  }
58
58
  : undefined,
59
59
  pipelineStage: row.pipelineStage ?? undefined,
60
+ assignee: row.assignee ?? undefined,
60
61
  };
61
62
  }
62
63
 
@@ -92,6 +93,7 @@ export function taskToRow(task: Partial<Task> & { id: string }): NewTaskRow {
92
93
  modifiedBy: task.provenance?.modifiedBy ?? null,
93
94
  sessionId: task.provenance?.sessionId ?? null,
94
95
  pipelineStage: task.pipelineStage ?? null,
96
+ assignee: task.assignee ?? null,
95
97
  };
96
98
  }
97
99
 
@@ -11,7 +11,7 @@
11
11
  * @epic T029
12
12
  */
13
13
 
14
- import { eq, or } from 'drizzle-orm';
14
+ import { and, eq, isNotNull, or } from 'drizzle-orm';
15
15
  import * as brainSchema from './brain-schema.js';
16
16
  import { getBrainDb } from './brain-sqlite.js';
17
17
 
@@ -177,6 +177,142 @@ export async function taskExistsInTasksDb(
177
177
  * if (await sessionExistsInTasksDb('ses_abc', db)) { /* safe to reference *\/ }
178
178
  * ```
179
179
  */
180
+ /**
181
+ * Reconcile orphaned cross-DB references in brain.db.
182
+ *
183
+ * Scans brain.db for references to tasks/sessions that no longer exist in
184
+ * tasks.db and cleans them up:
185
+ * - brain_decisions with stale context_task_id or context_epic_id → nullify
186
+ * - brain_observations with stale source_session_id → nullify
187
+ * - brain_memory_links with stale task_id → delete row
188
+ *
189
+ * This is the background reconciliation pass mentioned in the module doc.
190
+ * Safe to run at any frequency — idempotent.
191
+ *
192
+ * @param cwd - Optional working directory
193
+ * @returns Counts of orphaned references cleaned up
194
+ */
195
+ export async function reconcileOrphanedRefs(cwd?: string): Promise<{
196
+ decisionsFixed: number;
197
+ observationsFixed: number;
198
+ linksRemoved: number;
199
+ }> {
200
+ let brainDb: Awaited<ReturnType<typeof getBrainDb>> | null = null;
201
+ const result = { decisionsFixed: 0, observationsFixed: 0, linksRemoved: 0 };
202
+
203
+ try {
204
+ brainDb = await getBrainDb(cwd);
205
+ } catch {
206
+ return result;
207
+ }
208
+
209
+ const { getDb } = await import('./sqlite.js');
210
+ let tasksDb: Awaited<ReturnType<typeof getDb>>;
211
+ try {
212
+ tasksDb = await getDb(cwd);
213
+ } catch {
214
+ return result;
215
+ }
216
+
217
+ try {
218
+ // 1. Find decisions with stale context_task_id
219
+ const decisionsWithTaskRef = await brainDb
220
+ .select({
221
+ id: brainSchema.brainDecisions.id,
222
+ contextTaskId: brainSchema.brainDecisions.contextTaskId,
223
+ contextEpicId: brainSchema.brainDecisions.contextEpicId,
224
+ })
225
+ .from(brainSchema.brainDecisions)
226
+ .where(
227
+ or(
228
+ isNotNull(brainSchema.brainDecisions.contextTaskId),
229
+ isNotNull(brainSchema.brainDecisions.contextEpicId),
230
+ ),
231
+ )
232
+ .all();
233
+
234
+ for (const d of decisionsWithTaskRef) {
235
+ if (d.contextTaskId) {
236
+ const exists = await taskExistsInTasksDb(d.contextTaskId, tasksDb);
237
+ if (!exists) {
238
+ await brainDb
239
+ .update(brainSchema.brainDecisions)
240
+ .set({ contextTaskId: null })
241
+ .where(eq(brainSchema.brainDecisions.id, d.id));
242
+ result.decisionsFixed++;
243
+ }
244
+ }
245
+ if (d.contextEpicId) {
246
+ const exists = await taskExistsInTasksDb(d.contextEpicId, tasksDb);
247
+ if (!exists) {
248
+ await brainDb
249
+ .update(brainSchema.brainDecisions)
250
+ .set({ contextEpicId: null })
251
+ .where(eq(brainSchema.brainDecisions.id, d.id));
252
+ result.decisionsFixed++;
253
+ }
254
+ }
255
+ }
256
+
257
+ // 2. Find observations with stale source_session_id
258
+ const obsWithSessionRef = await brainDb
259
+ .select({
260
+ id: brainSchema.brainObservations.id,
261
+ sourceSessionId: brainSchema.brainObservations.sourceSessionId,
262
+ })
263
+ .from(brainSchema.brainObservations)
264
+ .where(isNotNull(brainSchema.brainObservations.sourceSessionId))
265
+ .all();
266
+
267
+ for (const o of obsWithSessionRef) {
268
+ {
269
+ const exists = o.sourceSessionId
270
+ ? await sessionExistsInTasksDb(o.sourceSessionId, tasksDb)
271
+ : false;
272
+ if (!exists) {
273
+ await brainDb
274
+ .update(brainSchema.brainObservations)
275
+ .set({ sourceSessionId: null })
276
+ .where(eq(brainSchema.brainObservations.id, o.id));
277
+ result.observationsFixed++;
278
+ }
279
+ }
280
+ }
281
+
282
+ // 3. Find memory links with stale task_id
283
+ const allLinks = await brainDb
284
+ .select({
285
+ memoryType: brainSchema.brainMemoryLinks.memoryType,
286
+ memoryId: brainSchema.brainMemoryLinks.memoryId,
287
+ taskId: brainSchema.brainMemoryLinks.taskId,
288
+ linkType: brainSchema.brainMemoryLinks.linkType,
289
+ })
290
+ .from(brainSchema.brainMemoryLinks)
291
+ .all();
292
+
293
+ for (const link of allLinks) {
294
+ const exists = await taskExistsInTasksDb(link.taskId, tasksDb);
295
+ if (!exists) {
296
+ await brainDb
297
+ .delete(brainSchema.brainMemoryLinks)
298
+ .where(
299
+ and(
300
+ eq(brainSchema.brainMemoryLinks.memoryType, link.memoryType),
301
+ eq(brainSchema.brainMemoryLinks.memoryId, link.memoryId),
302
+ eq(brainSchema.brainMemoryLinks.taskId, link.taskId),
303
+ eq(brainSchema.brainMemoryLinks.linkType, link.linkType),
304
+ ),
305
+ );
306
+ result.linksRemoved++;
307
+ }
308
+ }
309
+ } catch {
310
+ // Non-fatal best-effort reconciliation
311
+ }
312
+
313
+ return result;
314
+ }
315
+
180
316
  export async function sessionExistsInTasksDb(
181
317
  sessionId: string,
182
318
  tasksDb: Awaited<ReturnType<typeof import('./sqlite.js').getDb>>,
@@ -190,3 +326,41 @@ export async function sessionExistsInTasksDb(
190
326
  .all();
191
327
  return result.length > 0;
192
328
  }
329
+
330
+ /**
331
+ * Verify an agent exists in signaldock.db before creating cross-DB references.
332
+ * Returns true if the agent_id exists in signaldock.db agents table.
333
+ *
334
+ * Provides write-guard for agent_instances and agent_error_log in tasks.db
335
+ * that reference agents whose identity lives in signaldock.db.
336
+ *
337
+ * @param agentId - Agent slug (e.g. 'cleo-db-lead') to verify
338
+ * @param cwd - Optional working directory
339
+ * @returns True if the agent exists in signaldock.db
340
+ *
341
+ * @task T238
342
+ */
343
+ export async function agentExistsInSignaldockDb(agentId: string, cwd?: string): Promise<boolean> {
344
+ try {
345
+ const { getSignaldockDbPath } = await import('./signaldock-sqlite.js');
346
+ const { existsSync } = await import('node:fs');
347
+ const dbPath = getSignaldockDbPath(cwd);
348
+ if (!existsSync(dbPath)) return false;
349
+
350
+ const { createRequire } = await import('node:module');
351
+ const _require = createRequire(import.meta.url);
352
+ const { DatabaseSync } = _require('node:sqlite') as typeof import('node:sqlite');
353
+ const db = new DatabaseSync(dbPath);
354
+ try {
355
+ const row = db.prepare('SELECT id FROM agents WHERE agent_id = ?').get(agentId) as
356
+ | { id: string }
357
+ | undefined;
358
+ return !!row;
359
+ } finally {
360
+ db.close();
361
+ }
362
+ } catch {
363
+ // signaldock.db may not exist yet — non-fatal
364
+ return false;
365
+ }
366
+ }
@@ -79,6 +79,7 @@ export async function upsertTask(
79
79
  sessionId: row.sessionId,
80
80
  // T060: pipeline stage name (RCASD-IVTR+C)
81
81
  pipelineStage: row.pipelineStage ?? null,
82
+ assignee: row.assignee ?? null,
82
83
  // Always include archive metadata so unarchive clears stale values (T5034)
83
84
  archivedAt: archiveFields?.archivedAt ?? null,
84
85
  archiveReason: archiveFields?.archiveReason ?? null,
@@ -303,6 +303,29 @@ export class SafetyDataAccessor implements DataAccessor {
303
303
  return this.inner.removeSingleSession(sessionId);
304
304
  }
305
305
 
306
+ // ---- Agent instances ----
307
+
308
+ async listAgentInstances(filters?: {
309
+ status?: string | string[];
310
+ agentType?: string | string[];
311
+ }) {
312
+ return this.inner.listAgentInstances(filters);
313
+ }
314
+
315
+ async getAgentInstance(agentId: string) {
316
+ return this.inner.getAgentInstance(agentId);
317
+ }
318
+
319
+ // ---- Agent task claiming ----
320
+
321
+ async claimTask(taskId: string, agentId: string): Promise<void> {
322
+ return this.inner.claimTask(taskId, agentId);
323
+ }
324
+
325
+ async unclaimTask(taskId: string): Promise<void> {
326
+ return this.inner.unclaimTask(taskId);
327
+ }
328
+
306
329
  // ---- Lifecycle ----
307
330
 
308
331
  async close(): Promise<void> {