@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
@@ -0,0 +1,123 @@
1
+ /**
2
+ * ConduitClient — High-level agent messaging that wraps a Transport adapter.
3
+ *
4
+ * This is the WHAT layer: send messages, subscribe to events, manage presence.
5
+ * The Transport adapter (HttpTransport, LocalTransport, etc.) handles the HOW.
6
+ *
7
+ * @see docs/specs/SIGNALDOCK-UNIFIED-AGENT-REGISTRY.md Section 4.3
8
+ * @task T177
9
+ */
10
+
11
+ import type {
12
+ AgentCredential,
13
+ Conduit,
14
+ ConduitMessage,
15
+ ConduitSendOptions,
16
+ ConduitSendResult,
17
+ ConduitState,
18
+ ConduitUnsubscribe,
19
+ Transport,
20
+ } from '@cleocode/contracts';
21
+
22
+ /** ConduitClient wraps a Transport, adding high-level messaging semantics. */
23
+ export class ConduitClient implements Conduit {
24
+ private transport: Transport;
25
+ private credential: AgentCredential;
26
+ private state: ConduitState = 'disconnected';
27
+
28
+ /** Create a ConduitClient backed by the given transport and credential. */
29
+ constructor(transport: Transport, credential: AgentCredential) {
30
+ this.transport = transport;
31
+ this.credential = credential;
32
+ }
33
+
34
+ /** The agent ID from the bound credential. */
35
+ get agentId(): string {
36
+ return this.credential.agentId;
37
+ }
38
+
39
+ /** Current connection state (disconnected → connecting → connected | error). */
40
+ getState(): ConduitState {
41
+ return this.state;
42
+ }
43
+
44
+ /** Connect the underlying transport using the bound credential. */
45
+ async connect(): Promise<void> {
46
+ this.state = 'connecting';
47
+ try {
48
+ await this.transport.connect({
49
+ agentId: this.credential.agentId,
50
+ apiKey: this.credential.apiKey,
51
+ apiBaseUrl: this.credential.apiBaseUrl,
52
+ ...this.credential.transportConfig,
53
+ });
54
+ this.state = 'connected';
55
+ } catch (err) {
56
+ // H6 fix: transition to 'error' state instead of stuck at 'connecting'
57
+ this.state = 'error';
58
+ throw err;
59
+ }
60
+ }
61
+
62
+ /** Send a message to another agent, optionally within a thread. */
63
+ async send(
64
+ to: string,
65
+ content: string,
66
+ options?: ConduitSendOptions,
67
+ ): Promise<ConduitSendResult> {
68
+ const result = await this.transport.push(to, content, {
69
+ conversationId: options?.threadId,
70
+ });
71
+ return {
72
+ messageId: result.messageId,
73
+ deliveredAt: new Date().toISOString(),
74
+ };
75
+ }
76
+
77
+ /** Subscribe to incoming messages. Uses real-time transport when available, else polls. */
78
+ onMessage(handler: (message: ConduitMessage) => void): ConduitUnsubscribe {
79
+ // Prefer real-time subscription if transport supports it
80
+ if (this.transport.subscribe) {
81
+ return this.transport.subscribe(handler);
82
+ }
83
+ // Fallback: polling loop
84
+ const interval = setInterval(async () => {
85
+ const messages = await this.transport.poll();
86
+ for (const msg of messages) handler(msg);
87
+ if (messages.length > 0) {
88
+ await this.transport.ack(messages.map((m) => m.id));
89
+ }
90
+ }, this.credential.transportConfig.pollIntervalMs ?? 5000);
91
+ return () => clearInterval(interval);
92
+ }
93
+
94
+ /** Send an empty heartbeat to maintain presence on the relay. */
95
+ async heartbeat(): Promise<void> {
96
+ // Send empty heartbeat via transport
97
+ await this.transport.push(this.credential.agentId, '', {});
98
+ }
99
+
100
+ /** Check whether a remote agent is currently online via the cloud API. */
101
+ async isOnline(agentId: string): Promise<boolean> {
102
+ // Delegate to cloud API check — stub for now
103
+ try {
104
+ const response = await fetch(`${this.credential.apiBaseUrl}/agents/${agentId}`, {
105
+ headers: {
106
+ Authorization: `Bearer ${this.credential.apiKey}`,
107
+ 'X-Agent-Id': this.credential.agentId,
108
+ },
109
+ });
110
+ if (!response.ok) return false;
111
+ const data = (await response.json()) as { data?: { agent?: { status?: string } } };
112
+ return data.data?.agent?.status === 'online';
113
+ } catch {
114
+ return false;
115
+ }
116
+ }
117
+
118
+ /** Disconnect the transport and reset state to disconnected. */
119
+ async disconnect(): Promise<void> {
120
+ await this.transport.disconnect();
121
+ this.state = 'disconnected';
122
+ }
123
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Conduit factory — creates a Conduit instance from the agent registry.
3
+ *
4
+ * Auto-selects the appropriate Transport based on the agent's credential
5
+ * configuration. Priority: Local (napi-rs) > WebSocket > SSE > HTTP polling.
6
+ *
7
+ * @see docs/specs/SIGNALDOCK-UNIFIED-AGENT-REGISTRY.md Section 4.5
8
+ * @task T177
9
+ */
10
+
11
+ import type { AgentCredential, AgentRegistryAPI, Conduit, Transport } from '@cleocode/contracts';
12
+ import { ConduitClient } from './conduit-client.js';
13
+ import { HttpTransport } from './http-transport.js';
14
+ import { LocalTransport } from './local-transport.js';
15
+ import { SseTransport } from './sse-transport.js';
16
+
17
+ /** Resolve the best available transport for a credential. */
18
+ export function resolveTransport(credential: AgentCredential): Transport {
19
+ // Priority: Local (SQLite) > WebSocket > SSE > HTTP polling
20
+ if (LocalTransport.isAvailable()) {
21
+ return new LocalTransport();
22
+ }
23
+ if (credential.transportConfig.wsUrl) {
24
+ // WsTransport — fall through to SSE/HTTP for now
25
+ }
26
+ if (credential.transportConfig.sseEndpoint) {
27
+ return new SseTransport();
28
+ }
29
+ return new HttpTransport();
30
+ }
31
+
32
+ /** Create a Conduit instance from the agent registry. */
33
+ export async function createConduit(
34
+ registry: AgentRegistryAPI,
35
+ agentId?: string,
36
+ ): Promise<Conduit> {
37
+ const credential = agentId ? await registry.get(agentId) : await registry.getActive();
38
+
39
+ if (!credential) {
40
+ throw new Error(
41
+ 'No agent credential found. Run: cleo agent register --id <id> --api-key <key>',
42
+ );
43
+ }
44
+
45
+ const transport = resolveTransport(credential);
46
+ const conduit = new ConduitClient(transport, credential);
47
+ await conduit.connect();
48
+ return conduit;
49
+ }
@@ -0,0 +1,201 @@
1
+ /**
2
+ * HttpTransport — HTTP polling transport with automatic failover.
3
+ *
4
+ * Tries the primary API URL (api.signaldock.io) first. If unreachable,
5
+ * falls back to the legacy URL (api.clawmsgr.com). Failover is transparent
6
+ * to callers — they see a single transport that always works if either
7
+ * endpoint is up.
8
+ *
9
+ * @see docs/specs/SIGNALDOCK-UNIFIED-AGENT-REGISTRY.md Section 4.4
10
+ * @task T177
11
+ */
12
+
13
+ import type { ConduitMessage, Transport, TransportConnectConfig } from '@cleocode/contracts';
14
+
15
+ /** Internal connection state. */
16
+ interface HttpTransportState {
17
+ agentId: string;
18
+ apiKey: string;
19
+ primaryUrl: string;
20
+ fallbackUrl: string | null;
21
+ activeUrl: string;
22
+ connected: boolean;
23
+ }
24
+
25
+ /** HTTP transport with automatic primary/fallback failover. */
26
+ export class HttpTransport implements Transport {
27
+ readonly name = 'http';
28
+ private state: HttpTransportState | null = null;
29
+
30
+ /** Connect to the SignalDock API, probing primary/fallback health when both are configured. */
31
+ async connect(config: TransportConnectConfig): Promise<void> {
32
+ const primaryUrl = config.apiBaseUrl;
33
+ const fallbackUrl = config.apiBaseUrlFallback ?? null;
34
+
35
+ // Only probe health when there's a fallback to choose between
36
+ let activeUrl = primaryUrl;
37
+ if (fallbackUrl) {
38
+ const [primaryResult, fallbackResult] = await Promise.allSettled([
39
+ fetch(`${primaryUrl}/health`, { method: 'GET', signal: AbortSignal.timeout(5000) }),
40
+ fetch(`${fallbackUrl}/health`, { method: 'GET', signal: AbortSignal.timeout(5000) }),
41
+ ]);
42
+ const primaryOk = primaryResult.status === 'fulfilled' && primaryResult.value.ok;
43
+ const fallbackOk = fallbackResult.status === 'fulfilled' && fallbackResult.value.ok;
44
+ if (!primaryOk && fallbackOk) {
45
+ activeUrl = fallbackUrl;
46
+ }
47
+ }
48
+
49
+ this.state = {
50
+ agentId: config.agentId,
51
+ apiKey: config.apiKey,
52
+ primaryUrl,
53
+ fallbackUrl,
54
+ activeUrl,
55
+ connected: true,
56
+ };
57
+ }
58
+
59
+ /** Disconnect and clear connection state. */
60
+ async disconnect(): Promise<void> {
61
+ this.state = null;
62
+ }
63
+
64
+ /** Send a message to an agent (direct or within a conversation thread). */
65
+ async push(
66
+ to: string,
67
+ content: string,
68
+ options?: { conversationId?: string; replyTo?: string },
69
+ ): Promise<{ messageId: string }> {
70
+ this.ensureConnected();
71
+
72
+ const body: Record<string, string> = { content };
73
+
74
+ let path: string;
75
+ if (options?.conversationId) {
76
+ path = `/conversations/${options.conversationId}/messages`;
77
+ if (options.replyTo) {
78
+ body['replyTo'] = options.replyTo;
79
+ }
80
+ } else {
81
+ path = '/messages';
82
+ body['toAgentId'] = to;
83
+ }
84
+
85
+ const response = await this.fetchWithFallback(path, {
86
+ method: 'POST',
87
+ headers: this.headers(),
88
+ body: JSON.stringify(body),
89
+ });
90
+
91
+ if (!response.ok) {
92
+ const text = await response.text().catch(() => '');
93
+ throw new Error(`HttpTransport push failed: ${response.status} ${text}`);
94
+ }
95
+
96
+ const data = (await response.json()) as {
97
+ success?: boolean;
98
+ data?: { message?: { id?: string }; id?: string };
99
+ };
100
+ const messageId = data.data?.message?.id ?? data.data?.id ?? 'unknown';
101
+ return { messageId };
102
+ }
103
+
104
+ /** Poll for new messages mentioning this agent. Returns empty array on HTTP error. */
105
+ async poll(options?: { limit?: number; since?: string }): Promise<ConduitMessage[]> {
106
+ this.ensureConnected();
107
+
108
+ const params = new URLSearchParams();
109
+ params.set('mentioned', this.state!.agentId);
110
+ if (options?.limit) params.set('limit', String(options.limit));
111
+ if (options?.since) params.set('since', options.since);
112
+
113
+ const response = await this.fetchWithFallback(`/messages/peek?${params}`, {
114
+ method: 'GET',
115
+ headers: this.headers(),
116
+ });
117
+
118
+ if (!response.ok) return [];
119
+
120
+ const data = (await response.json()) as {
121
+ data?: {
122
+ messages?: Array<{
123
+ id: string;
124
+ fromAgentId?: string;
125
+ content?: string;
126
+ conversationId?: string;
127
+ createdAt?: string;
128
+ }>;
129
+ };
130
+ };
131
+
132
+ return (data.data?.messages ?? []).map((m) => ({
133
+ id: m.id,
134
+ from: m.fromAgentId ?? 'unknown',
135
+ content: m.content ?? '',
136
+ threadId: m.conversationId,
137
+ timestamp: m.createdAt ?? new Date().toISOString(),
138
+ }));
139
+ }
140
+
141
+ /** Acknowledge messages by ID so they are not returned by future polls. */
142
+ async ack(messageIds: string[]): Promise<void> {
143
+ this.ensureConnected();
144
+
145
+ await this.fetchWithFallback('/messages/ack', {
146
+ method: 'POST',
147
+ headers: this.headers(),
148
+ body: JSON.stringify({ messageIds }),
149
+ });
150
+ }
151
+
152
+ /**
153
+ * Fetch with automatic failover. Tries activeUrl first.
154
+ * If it fails and a fallback exists, retries on the other URL
155
+ * and swaps activeUrl for subsequent calls.
156
+ */
157
+ private async fetchWithFallback(path: string, init: RequestInit): Promise<Response> {
158
+ const timeout = AbortSignal.timeout(10000);
159
+ const signal = init.signal ? AbortSignal.any([init.signal, timeout]) : timeout;
160
+ const url = `${this.state!.activeUrl}${path}`;
161
+
162
+ try {
163
+ return await fetch(url, { ...init, signal });
164
+ } catch (primaryErr) {
165
+ const otherUrl =
166
+ this.state!.activeUrl === this.state!.primaryUrl
167
+ ? this.state!.fallbackUrl
168
+ : this.state!.primaryUrl;
169
+
170
+ if (!otherUrl) throw primaryErr;
171
+
172
+ try {
173
+ const fallbackSignal = init.signal
174
+ ? AbortSignal.any([init.signal, AbortSignal.timeout(10000)])
175
+ : AbortSignal.timeout(10000);
176
+ const fallbackResponse = await fetch(`${otherUrl}${path}`, {
177
+ ...init,
178
+ signal: fallbackSignal,
179
+ });
180
+ this.state!.activeUrl = otherUrl;
181
+ return fallbackResponse;
182
+ } catch {
183
+ throw primaryErr;
184
+ }
185
+ }
186
+ }
187
+
188
+ private headers(): Record<string, string> {
189
+ return {
190
+ 'Content-Type': 'application/json',
191
+ Authorization: `Bearer ${this.state!.apiKey}`,
192
+ 'X-Agent-Id': this.state!.agentId,
193
+ };
194
+ }
195
+
196
+ private ensureConnected(): void {
197
+ if (!this.state?.connected) {
198
+ throw new Error('HttpTransport not connected. Call connect() first.');
199
+ }
200
+ }
201
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Conduit — High-level agent messaging for the CLEO ecosystem.
3
+ *
4
+ * Exports the ConduitClient (high-level messaging), HttpTransport
5
+ * (HTTP polling to cloud), LocalTransport (offline SQLite), and
6
+ * createConduit factory.
7
+ *
8
+ * @module conduit
9
+ */
10
+
11
+ export { ConduitClient } from './conduit-client.js';
12
+ export { createConduit, resolveTransport } from './factory.js';
13
+ export { HttpTransport } from './http-transport.js';
14
+ export { LocalTransport } from './local-transport.js';
15
+ export { SseTransport } from './sse-transport.js';
@@ -0,0 +1,309 @@
1
+ /**
2
+ * LocalTransport — In-process SQLite transport for fully offline agent messaging.
3
+ *
4
+ * Reads and writes messages directly to signaldock.db via node:sqlite.
5
+ * No network calls. Works fully offline. Messages are stored in the
6
+ * same schema that the Rust signaldock-storage crate manages, so both
7
+ * the local CLI and the cloud backend see the same data.
8
+ *
9
+ * Priority: LocalTransport is preferred over HttpTransport when
10
+ * signaldock.db is available (see factory.ts).
11
+ *
12
+ * @see docs/specs/SIGNALDOCK-UNIFIED-AGENT-REGISTRY.md Section 4.4
13
+ * @task T213
14
+ */
15
+
16
+ import { randomUUID } from 'node:crypto';
17
+ import { existsSync } from 'node:fs';
18
+ import { createRequire } from 'node:module';
19
+ import type { DatabaseSync } from 'node:sqlite';
20
+ import type { ConduitMessage, Transport, TransportConnectConfig } from '@cleocode/contracts';
21
+ import { getSignaldockDbPath } from '../store/signaldock-sqlite.js';
22
+
23
+ const _require = createRequire(import.meta.url);
24
+ const { DatabaseSync: DatabaseSyncClass } = _require('node:sqlite') as {
25
+ DatabaseSync: new (...args: ConstructorParameters<typeof DatabaseSync>) => DatabaseSync;
26
+ };
27
+
28
+ /** Internal state for an active local transport connection. */
29
+ interface LocalTransportState {
30
+ agentId: string;
31
+ db: DatabaseSync;
32
+ dbPath: string;
33
+ subscribers: Set<(message: ConduitMessage) => void>;
34
+ pollTimer: ReturnType<typeof setInterval> | null;
35
+ }
36
+
37
+ /** In-process SQLite transport for fully offline agent messaging. */
38
+ export class LocalTransport implements Transport {
39
+ readonly name = 'local';
40
+ private state: LocalTransportState | null = null;
41
+
42
+ /**
43
+ * Connect to signaldock.db for in-process messaging.
44
+ *
45
+ * Opens the database, sets WAL mode pragmas, and verifies
46
+ * the messages table exists. Throws if signaldock.db is missing
47
+ * or uninitialized (run `cleo init` first).
48
+ */
49
+ async connect(config: TransportConnectConfig): Promise<void> {
50
+ const dbPath = getSignaldockDbPath();
51
+
52
+ if (!existsSync(dbPath)) {
53
+ throw new Error(`LocalTransport: signaldock.db not found at ${dbPath}. Run: cleo init`);
54
+ }
55
+
56
+ const db = new DatabaseSyncClass(dbPath);
57
+ db.exec('PRAGMA journal_mode = WAL');
58
+ db.exec('PRAGMA busy_timeout = 5000');
59
+ db.exec('PRAGMA foreign_keys = ON');
60
+
61
+ // Verify the messages table exists
62
+ const hasMessages = db
63
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='messages'")
64
+ .get() as { name: string } | undefined;
65
+
66
+ if (!hasMessages) {
67
+ db.close();
68
+ throw new Error(
69
+ 'LocalTransport: signaldock.db exists but messages table missing. Run: cleo upgrade',
70
+ );
71
+ }
72
+
73
+ this.state = {
74
+ agentId: config.agentId,
75
+ db,
76
+ dbPath,
77
+ subscribers: new Set(),
78
+ pollTimer: null,
79
+ };
80
+ }
81
+
82
+ /** Close the database connection and stop any subscriber polling. */
83
+ async disconnect(): Promise<void> {
84
+ if (!this.state) return;
85
+
86
+ if (this.state.pollTimer) {
87
+ clearInterval(this.state.pollTimer);
88
+ }
89
+ this.state.subscribers.clear();
90
+ this.state.db.close();
91
+ this.state = null;
92
+ }
93
+
94
+ /**
95
+ * Store a message in signaldock.db.
96
+ *
97
+ * Inserts into the messages table with status 'pending'.
98
+ * For conversation messages, also links via conversation_participants
99
+ * if not already present.
100
+ */
101
+ async push(
102
+ to: string,
103
+ content: string,
104
+ options?: { conversationId?: string; replyTo?: string },
105
+ ): Promise<{ messageId: string }> {
106
+ this.ensureConnected();
107
+ const { db, agentId } = this.state!;
108
+ const messageId = randomUUID();
109
+ const nowUnix = Math.floor(Date.now() / 1000);
110
+
111
+ if (options?.conversationId) {
112
+ db.prepare(
113
+ `INSERT INTO messages (id, conversation_id, from_agent_id, to_agent_id, content, content_type, status, created_at)
114
+ VALUES (?, ?, ?, ?, ?, 'text', 'pending', ?)`,
115
+ ).run(messageId, options.conversationId, agentId, to, content, nowUnix);
116
+ } else {
117
+ // Direct message — create or reuse a DM conversation
118
+ const convId = this.ensureDmConversation(agentId, to);
119
+ db.prepare(
120
+ `INSERT INTO messages (id, conversation_id, from_agent_id, to_agent_id, content, content_type, status, created_at)
121
+ VALUES (?, ?, ?, ?, ?, 'text', 'pending', ?)`,
122
+ ).run(messageId, convId, agentId, to, content, nowUnix);
123
+ }
124
+
125
+ // Notify local subscribers
126
+ this.notifySubscribers({
127
+ id: messageId,
128
+ from: agentId,
129
+ content,
130
+ threadId: options?.conversationId,
131
+ timestamp: new Date(nowUnix * 1000).toISOString(),
132
+ });
133
+
134
+ return { messageId };
135
+ }
136
+
137
+ /**
138
+ * Poll for messages addressed to this agent.
139
+ *
140
+ * Returns messages with status 'pending' where to_agent_id matches
141
+ * the connected agent. Messages are returned oldest-first.
142
+ */
143
+ async poll(options?: { limit?: number; since?: string }): Promise<ConduitMessage[]> {
144
+ this.ensureConnected();
145
+ const { db, agentId } = this.state!;
146
+ const limit = options?.limit ?? 50;
147
+
148
+ let query: string;
149
+ let params: (string | number)[];
150
+
151
+ if (options?.since) {
152
+ query = `SELECT id, from_agent_id, content, conversation_id, created_at
153
+ FROM messages
154
+ WHERE to_agent_id = ? AND status = 'pending' AND created_at > ?
155
+ ORDER BY created_at ASC
156
+ LIMIT ?`;
157
+ params = [agentId, options.since, limit];
158
+ } else {
159
+ query = `SELECT id, from_agent_id, content, conversation_id, created_at
160
+ FROM messages
161
+ WHERE to_agent_id = ? AND status = 'pending'
162
+ ORDER BY created_at ASC
163
+ LIMIT ?`;
164
+ params = [agentId, limit];
165
+ }
166
+
167
+ const rows = db.prepare(query).all(...params) as Array<{
168
+ id: string;
169
+ from_agent_id: string;
170
+ content: string;
171
+ conversation_id: string | null;
172
+ created_at: number;
173
+ }>;
174
+
175
+ return rows.map((r) => ({
176
+ id: r.id,
177
+ from: r.from_agent_id,
178
+ content: r.content,
179
+ threadId: r.conversation_id ?? undefined,
180
+ timestamp: new Date(r.created_at * 1000).toISOString(),
181
+ }));
182
+ }
183
+
184
+ /**
185
+ * Acknowledge messages by marking them as 'delivered'.
186
+ *
187
+ * Updates the status and delivered_at timestamp for each message ID.
188
+ */
189
+ async ack(messageIds: string[]): Promise<void> {
190
+ this.ensureConnected();
191
+ if (messageIds.length === 0) return;
192
+
193
+ const { db } = this.state!;
194
+ const nowUnix = Math.floor(Date.now() / 1000);
195
+
196
+ const placeholders = messageIds.map(() => '?').join(', ');
197
+ db.prepare(
198
+ `UPDATE messages SET status = 'delivered', delivered_at = ? WHERE id IN (${placeholders})`,
199
+ ).run(nowUnix, ...messageIds);
200
+ }
201
+
202
+ /**
203
+ * Subscribe to real-time local messages.
204
+ *
205
+ * Since this is in-process, subscribers are notified synchronously
206
+ * when push() is called. Additionally, a polling interval checks
207
+ * for messages inserted by other processes (e.g., Rust CLI).
208
+ *
209
+ * @returns Unsubscribe function.
210
+ */
211
+ subscribe(handler: (message: ConduitMessage) => void): () => void {
212
+ this.ensureConnected();
213
+ this.state!.subscribers.add(handler);
214
+
215
+ // Start cross-process polling if not already running
216
+ if (!this.state!.pollTimer && this.state!.subscribers.size === 1) {
217
+ this.state!.pollTimer = setInterval(() => {
218
+ void this.pollAndNotify();
219
+ }, 1000);
220
+ }
221
+
222
+ return () => {
223
+ this.state?.subscribers.delete(handler);
224
+ if (this.state?.subscribers.size === 0 && this.state.pollTimer) {
225
+ clearInterval(this.state.pollTimer);
226
+ this.state.pollTimer = null;
227
+ }
228
+ };
229
+ }
230
+
231
+ /**
232
+ * Check whether signaldock.db is available for local transport.
233
+ *
234
+ * Used by factory.ts to decide whether to use LocalTransport.
235
+ */
236
+ static isAvailable(cwd?: string): boolean {
237
+ const dbPath = getSignaldockDbPath(cwd);
238
+ return existsSync(dbPath);
239
+ }
240
+
241
+ /** Poll for new messages and notify subscribers (cross-process sync). */
242
+ private async pollAndNotify(): Promise<void> {
243
+ if (!this.state || this.state.subscribers.size === 0) return;
244
+
245
+ const messages = await this.poll({ limit: 20 });
246
+ for (const msg of messages) {
247
+ this.notifySubscribers(msg);
248
+ }
249
+ if (messages.length > 0) {
250
+ await this.ack(messages.map((m) => m.id));
251
+ }
252
+ }
253
+
254
+ /** Notify all active subscribers of a new message. */
255
+ private notifySubscribers(message: ConduitMessage): void {
256
+ if (!this.state) return;
257
+ for (const handler of this.state.subscribers) {
258
+ try {
259
+ handler(message);
260
+ } catch {
261
+ // Subscriber errors must not break the transport
262
+ }
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Ensure a DM conversation exists between two agents.
268
+ *
269
+ * Conversations store participants as a comma-separated TEXT field.
270
+ * We search for existing private conversations containing both agents.
271
+ *
272
+ * @returns The conversation ID.
273
+ */
274
+ private ensureDmConversation(fromAgentId: string, toAgentId: string): string {
275
+ const { db } = this.state!;
276
+
277
+ // Participants are stored as comma-separated text, sorted alphabetically
278
+ const sortedParticipants = [fromAgentId, toAgentId].sort().join(',');
279
+
280
+ // Check for existing DM conversation with these exact participants
281
+ const existing = db
282
+ .prepare(
283
+ `SELECT id FROM conversations
284
+ WHERE visibility = 'private' AND participants = ?
285
+ LIMIT 1`,
286
+ )
287
+ .get(sortedParticipants) as { id: string } | undefined;
288
+
289
+ if (existing) return existing.id;
290
+
291
+ // Create new DM conversation
292
+ const convId = randomUUID();
293
+ const nowUnix = Math.floor(Date.now() / 1000);
294
+
295
+ db.prepare(
296
+ `INSERT INTO conversations (id, participants, visibility, message_count, created_at, updated_at)
297
+ VALUES (?, ?, 'private', 0, ?, ?)`,
298
+ ).run(convId, sortedParticipants, nowUnix, nowUnix);
299
+
300
+ return convId;
301
+ }
302
+
303
+ /** Throw if not connected. */
304
+ private ensureConnected(): void {
305
+ if (!this.state) {
306
+ throw new Error('LocalTransport not connected. Call connect() first.');
307
+ }
308
+ }
309
+ }