crewly 1.6.1 → 1.6.3

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 (367) hide show
  1. package/config/roles/orchestrator/prompt.md +16 -0
  2. package/config/skills/agent/core/get-my-active-work/SKILL.md +101 -0
  3. package/config/skills/agent/core/get-my-active-work/execute.sh +122 -0
  4. package/config/skills/agent/core/record-learning/SKILL.md +29 -0
  5. package/config/skills/agent/core/reply-channel/SKILL.md +41 -0
  6. package/config/skills/agent/core/reply-channel/execute.sh +165 -0
  7. package/config/skills/agent/core/reply-channel/execute.test.sh +148 -0
  8. package/config/skills/agent/remote-browser/execute.sh +296 -14
  9. package/config/skills/agent/remote-browser/execute.test.sh +482 -0
  10. package/config/skills/orchestrator/send-message/SKILL.md +30 -7
  11. package/config/skills/orchestrator/team-health-scan/SKILL.md +98 -0
  12. package/config/skills/orchestrator/team-health-scan/execute.sh +44 -0
  13. package/config/skills/registry.json +62 -1
  14. package/config/slack-app-manifest.json +2 -1
  15. package/config/sops/developer/git-workflow.md +38 -3
  16. package/dist/backend/backend/src/constants.d.ts +69 -1
  17. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  18. package/dist/backend/backend/src/constants.js +69 -2
  19. package/dist/backend/backend/src/constants.js.map +1 -1
  20. package/dist/backend/backend/src/controllers/active-work/active-work.controller.d.ts +53 -0
  21. package/dist/backend/backend/src/controllers/active-work/active-work.controller.d.ts.map +1 -0
  22. package/dist/backend/backend/src/controllers/active-work/active-work.controller.js +92 -0
  23. package/dist/backend/backend/src/controllers/active-work/active-work.controller.js.map +1 -0
  24. package/dist/backend/backend/src/controllers/agent-stream/agent-stream.controller.d.ts.map +1 -1
  25. package/dist/backend/backend/src/controllers/agent-stream/agent-stream.controller.js +18 -1
  26. package/dist/backend/backend/src/controllers/agent-stream/agent-stream.controller.js.map +1 -1
  27. package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts +68 -0
  28. package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts.map +1 -1
  29. package/dist/backend/backend/src/controllers/browser/browser.controller.js +233 -5
  30. package/dist/backend/backend/src/controllers/browser/browser.controller.js.map +1 -1
  31. package/dist/backend/backend/src/controllers/browser/browser.routes.d.ts.map +1 -1
  32. package/dist/backend/backend/src/controllers/browser/browser.routes.js +10 -1
  33. package/dist/backend/backend/src/controllers/browser/browser.routes.js.map +1 -1
  34. package/dist/backend/backend/src/controllers/chat/chat.controller.d.ts.map +1 -1
  35. package/dist/backend/backend/src/controllers/chat/chat.controller.js +8 -3
  36. package/dist/backend/backend/src/controllers/chat/chat.controller.js.map +1 -1
  37. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.d.ts +132 -0
  38. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.d.ts.map +1 -0
  39. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js +401 -0
  40. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js.map +1 -0
  41. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.d.ts +29 -0
  42. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.d.ts.map +1 -0
  43. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.js +39 -0
  44. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.js.map +1 -0
  45. package/dist/backend/backend/src/controllers/chat-v2/index.d.ts +8 -0
  46. package/dist/backend/backend/src/controllers/chat-v2/index.d.ts.map +1 -0
  47. package/dist/backend/backend/src/controllers/chat-v2/index.js +8 -0
  48. package/dist/backend/backend/src/controllers/chat-v2/index.js.map +1 -0
  49. package/dist/backend/backend/src/controllers/onboarding/onboarding.routes.d.ts +13 -13
  50. package/dist/backend/backend/src/controllers/onboarding/onboarding.routes.d.ts.map +1 -1
  51. package/dist/backend/backend/src/controllers/onboarding/onboarding.routes.js +74 -234
  52. package/dist/backend/backend/src/controllers/onboarding/onboarding.routes.js.map +1 -1
  53. package/dist/backend/backend/src/controllers/orchestrator/orchestrator.controller.d.ts.map +1 -1
  54. package/dist/backend/backend/src/controllers/orchestrator/orchestrator.controller.js +76 -15
  55. package/dist/backend/backend/src/controllers/orchestrator/orchestrator.controller.js.map +1 -1
  56. package/dist/backend/backend/src/controllers/request/request.controller.d.ts.map +1 -1
  57. package/dist/backend/backend/src/controllers/request/request.controller.js +4 -6
  58. package/dist/backend/backend/src/controllers/request/request.controller.js.map +1 -1
  59. package/dist/backend/backend/src/controllers/task-management/tasks.controller.d.ts +43 -0
  60. package/dist/backend/backend/src/controllers/task-management/tasks.controller.d.ts.map +1 -1
  61. package/dist/backend/backend/src/controllers/task-management/tasks.controller.js +200 -72
  62. package/dist/backend/backend/src/controllers/task-management/tasks.controller.js.map +1 -1
  63. package/dist/backend/backend/src/controllers/team/team.controller.d.ts.map +1 -1
  64. package/dist/backend/backend/src/controllers/team/team.controller.js +49 -0
  65. package/dist/backend/backend/src/controllers/team/team.controller.js.map +1 -1
  66. package/dist/backend/backend/src/controllers/team-health/team-health.controller.d.ts +59 -0
  67. package/dist/backend/backend/src/controllers/team-health/team-health.controller.d.ts.map +1 -0
  68. package/dist/backend/backend/src/controllers/team-health/team-health.controller.js +127 -0
  69. package/dist/backend/backend/src/controllers/team-health/team-health.controller.js.map +1 -0
  70. package/dist/backend/backend/src/controllers/team-health/team-health.routes.d.ts +13 -0
  71. package/dist/backend/backend/src/controllers/team-health/team-health.routes.d.ts.map +1 -0
  72. package/dist/backend/backend/src/controllers/team-health/team-health.routes.js +20 -0
  73. package/dist/backend/backend/src/controllers/team-health/team-health.routes.js.map +1 -0
  74. package/dist/backend/backend/src/index.d.ts +9 -0
  75. package/dist/backend/backend/src/index.d.ts.map +1 -1
  76. package/dist/backend/backend/src/index.js +233 -0
  77. package/dist/backend/backend/src/index.js.map +1 -1
  78. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  79. package/dist/backend/backend/src/routes/api.routes.js +40 -6
  80. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  81. package/dist/backend/backend/src/services/agent/active-work-briefing.service.d.ts +498 -0
  82. package/dist/backend/backend/src/services/agent/active-work-briefing.service.d.ts.map +1 -0
  83. package/dist/backend/backend/src/services/agent/active-work-briefing.service.js +759 -0
  84. package/dist/backend/backend/src/services/agent/active-work-briefing.service.js.map +1 -0
  85. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts +25 -0
  86. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  87. package/dist/backend/backend/src/services/agent/agent-registration.service.js +221 -58
  88. package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
  89. package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.d.ts +9 -2
  90. package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.d.ts.map +1 -1
  91. package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.js +35 -2
  92. package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.js.map +1 -1
  93. package/dist/backend/backend/src/services/agent/crewly-agent/types.d.ts +8 -2
  94. package/dist/backend/backend/src/services/agent/crewly-agent/types.d.ts.map +1 -1
  95. package/dist/backend/backend/src/services/agent/crewly-agent/types.js +1 -0
  96. package/dist/backend/backend/src/services/agent/crewly-agent/types.js.map +1 -1
  97. package/dist/backend/backend/src/services/agent/tmux-command.service.d.ts.map +1 -1
  98. package/dist/backend/backend/src/services/agent/tmux-command.service.js +2 -1
  99. package/dist/backend/backend/src/services/agent/tmux-command.service.js.map +1 -1
  100. package/dist/backend/backend/src/services/agent/tmux.service.d.ts.map +1 -1
  101. package/dist/backend/backend/src/services/agent/tmux.service.js +2 -1
  102. package/dist/backend/backend/src/services/agent/tmux.service.js.map +1 -1
  103. package/dist/backend/backend/src/services/ai/prompt-builder.service.d.ts +148 -3
  104. package/dist/backend/backend/src/services/ai/prompt-builder.service.d.ts.map +1 -1
  105. package/dist/backend/backend/src/services/ai/prompt-builder.service.js +241 -2
  106. package/dist/backend/backend/src/services/ai/prompt-builder.service.js.map +1 -1
  107. package/dist/backend/backend/src/services/ai/prompt-modules/recovery.module.d.ts.map +1 -1
  108. package/dist/backend/backend/src/services/ai/prompt-modules/recovery.module.js +13 -0
  109. package/dist/backend/backend/src/services/ai/prompt-modules/recovery.module.js.map +1 -1
  110. package/dist/backend/backend/src/services/ai/prompt-modules/role-boundary.module.d.ts.map +1 -1
  111. package/dist/backend/backend/src/services/ai/prompt-modules/role-boundary.module.js +26 -1
  112. package/dist/backend/backend/src/services/ai/prompt-modules/role-boundary.module.js.map +1 -1
  113. package/dist/backend/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.d.ts +79 -0
  114. package/dist/backend/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.d.ts.map +1 -0
  115. package/dist/backend/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.js +118 -0
  116. package/dist/backend/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.js.map +1 -0
  117. package/dist/backend/backend/src/services/browser/browser-bridge.service.d.ts +161 -0
  118. package/dist/backend/backend/src/services/browser/browser-bridge.service.d.ts.map +1 -1
  119. package/dist/backend/backend/src/services/browser/browser-bridge.service.js +382 -2
  120. package/dist/backend/backend/src/services/browser/browser-bridge.service.js.map +1 -1
  121. package/dist/backend/backend/src/services/browser/browser-proxy.service.d.ts +105 -0
  122. package/dist/backend/backend/src/services/browser/browser-proxy.service.d.ts.map +1 -1
  123. package/dist/backend/backend/src/services/browser/browser-proxy.service.js +232 -13
  124. package/dist/backend/backend/src/services/browser/browser-proxy.service.js.map +1 -1
  125. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +178 -0
  126. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -0
  127. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +254 -0
  128. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -0
  129. package/dist/backend/backend/src/services/chat-v2/chat-v2.mention-resolver.d.ts +134 -0
  130. package/dist/backend/backend/src/services/chat-v2/chat-v2.mention-resolver.d.ts.map +1 -0
  131. package/dist/backend/backend/src/services/chat-v2/chat-v2.mention-resolver.js +232 -0
  132. package/dist/backend/backend/src/services/chat-v2/chat-v2.mention-resolver.js.map +1 -0
  133. package/dist/backend/backend/src/services/chat-v2/chat-v2.realtime-holder.d.ts +25 -0
  134. package/dist/backend/backend/src/services/chat-v2/chat-v2.realtime-holder.d.ts.map +1 -0
  135. package/dist/backend/backend/src/services/chat-v2/chat-v2.realtime-holder.js +23 -0
  136. package/dist/backend/backend/src/services/chat-v2/chat-v2.realtime-holder.js.map +1 -0
  137. package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts +254 -0
  138. package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts.map +1 -0
  139. package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js +467 -0
  140. package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js.map +1 -0
  141. package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.d.ts +27 -0
  142. package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.d.ts.map +1 -0
  143. package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.js +57 -0
  144. package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.js.map +1 -0
  145. package/dist/backend/backend/src/services/chat-v2/chat-v2.team-membership.d.ts +43 -0
  146. package/dist/backend/backend/src/services/chat-v2/chat-v2.team-membership.d.ts.map +1 -0
  147. package/dist/backend/backend/src/services/chat-v2/chat-v2.team-membership.js +54 -0
  148. package/dist/backend/backend/src/services/chat-v2/chat-v2.team-membership.js.map +1 -0
  149. package/dist/backend/backend/src/services/chat-v2/config.d.ts +100 -0
  150. package/dist/backend/backend/src/services/chat-v2/config.d.ts.map +1 -0
  151. package/dist/backend/backend/src/services/chat-v2/config.js +174 -0
  152. package/dist/backend/backend/src/services/chat-v2/config.js.map +1 -0
  153. package/dist/backend/backend/src/services/chat-v2/index.d.ts +11 -0
  154. package/dist/backend/backend/src/services/chat-v2/index.d.ts.map +1 -0
  155. package/dist/backend/backend/src/services/chat-v2/index.js +12 -0
  156. package/dist/backend/backend/src/services/chat-v2/index.js.map +1 -0
  157. package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.d.ts +114 -0
  158. package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.d.ts.map +1 -0
  159. package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.js +194 -0
  160. package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.js.map +1 -0
  161. package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.d.ts +100 -0
  162. package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.d.ts.map +1 -0
  163. package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.js +351 -0
  164. package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.js.map +1 -0
  165. package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.d.ts +132 -0
  166. package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.d.ts.map +1 -0
  167. package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.js +281 -0
  168. package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.js.map +1 -0
  169. package/dist/backend/backend/src/services/chat-v2/types.d.ts +295 -0
  170. package/dist/backend/backend/src/services/chat-v2/types.d.ts.map +1 -0
  171. package/dist/backend/backend/src/services/chat-v2/types.js +61 -0
  172. package/dist/backend/backend/src/services/chat-v2/types.js.map +1 -0
  173. package/dist/backend/backend/src/services/cloud/cloud-event-bridge.service.d.ts +113 -0
  174. package/dist/backend/backend/src/services/cloud/cloud-event-bridge.service.d.ts.map +1 -0
  175. package/dist/backend/backend/src/services/cloud/cloud-event-bridge.service.js +179 -0
  176. package/dist/backend/backend/src/services/cloud/cloud-event-bridge.service.js.map +1 -0
  177. package/dist/backend/backend/src/services/cloud/cloud-event-forwarder.service.d.ts +131 -0
  178. package/dist/backend/backend/src/services/cloud/cloud-event-forwarder.service.d.ts.map +1 -0
  179. package/dist/backend/backend/src/services/cloud/cloud-event-forwarder.service.js +227 -0
  180. package/dist/backend/backend/src/services/cloud/cloud-event-forwarder.service.js.map +1 -0
  181. package/dist/backend/backend/src/services/core/config.service.js +3 -3
  182. package/dist/backend/backend/src/services/core/config.service.js.map +1 -1
  183. package/dist/backend/backend/src/services/core/storage.service.d.ts +22 -0
  184. package/dist/backend/backend/src/services/core/storage.service.d.ts.map +1 -1
  185. package/dist/backend/backend/src/services/core/storage.service.js +57 -0
  186. package/dist/backend/backend/src/services/core/storage.service.js.map +1 -1
  187. package/dist/backend/backend/src/services/event-bus/event-bus.service.d.ts +69 -1
  188. package/dist/backend/backend/src/services/event-bus/event-bus.service.d.ts.map +1 -1
  189. package/dist/backend/backend/src/services/event-bus/event-bus.service.js +118 -0
  190. package/dist/backend/backend/src/services/event-bus/event-bus.service.js.map +1 -1
  191. package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.d.ts +275 -0
  192. package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.d.ts.map +1 -0
  193. package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js +736 -0
  194. package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js.map +1 -0
  195. package/dist/backend/backend/src/services/knowledge/fts5-index.service.d.ts.map +1 -1
  196. package/dist/backend/backend/src/services/knowledge/fts5-index.service.js +18 -2
  197. package/dist/backend/backend/src/services/knowledge/fts5-index.service.js.map +1 -1
  198. package/dist/backend/backend/src/services/knowledge/knowledge-search.service.d.ts +49 -13
  199. package/dist/backend/backend/src/services/knowledge/knowledge-search.service.d.ts.map +1 -1
  200. package/dist/backend/backend/src/services/knowledge/knowledge-search.service.js +123 -29
  201. package/dist/backend/backend/src/services/knowledge/knowledge-search.service.js.map +1 -1
  202. package/dist/backend/backend/src/services/knowledge/learnings-index.service.d.ts +159 -0
  203. package/dist/backend/backend/src/services/knowledge/learnings-index.service.d.ts.map +1 -0
  204. package/dist/backend/backend/src/services/knowledge/learnings-index.service.js +304 -0
  205. package/dist/backend/backend/src/services/knowledge/learnings-index.service.js.map +1 -0
  206. package/dist/backend/backend/src/services/knowledge/vector-store.service.d.ts.map +1 -1
  207. package/dist/backend/backend/src/services/knowledge/vector-store.service.js +24 -4
  208. package/dist/backend/backend/src/services/knowledge/vector-store.service.js.map +1 -1
  209. package/dist/backend/backend/src/services/memory/auto-learning.subscriber.d.ts +174 -0
  210. package/dist/backend/backend/src/services/memory/auto-learning.subscriber.d.ts.map +1 -0
  211. package/dist/backend/backend/src/services/memory/auto-learning.subscriber.js +375 -0
  212. package/dist/backend/backend/src/services/memory/auto-learning.subscriber.js.map +1 -0
  213. package/dist/backend/backend/src/services/memory/learning-format.validator.d.ts +97 -0
  214. package/dist/backend/backend/src/services/memory/learning-format.validator.d.ts.map +1 -0
  215. package/dist/backend/backend/src/services/memory/learning-format.validator.js +209 -0
  216. package/dist/backend/backend/src/services/memory/learning-format.validator.js.map +1 -0
  217. package/dist/backend/backend/src/services/memory/vector-store.service.d.ts.map +1 -1
  218. package/dist/backend/backend/src/services/memory/vector-store.service.js +19 -4
  219. package/dist/backend/backend/src/services/memory/vector-store.service.js.map +1 -1
  220. package/dist/backend/backend/src/services/onboarding/onboarding-provision.service.d.ts +16 -5
  221. package/dist/backend/backend/src/services/onboarding/onboarding-provision.service.d.ts.map +1 -1
  222. package/dist/backend/backend/src/services/onboarding/onboarding-provision.service.js +32 -5
  223. package/dist/backend/backend/src/services/onboarding/onboarding-provision.service.js.map +1 -1
  224. package/dist/backend/backend/src/services/onboarding/onboarding.service.d.ts +157 -0
  225. package/dist/backend/backend/src/services/onboarding/onboarding.service.d.ts.map +1 -0
  226. package/dist/backend/backend/src/services/onboarding/onboarding.service.js +229 -0
  227. package/dist/backend/backend/src/services/onboarding/onboarding.service.js.map +1 -0
  228. package/dist/backend/backend/src/services/onboarding/onboarding.types.d.ts +141 -0
  229. package/dist/backend/backend/src/services/onboarding/onboarding.types.d.ts.map +1 -0
  230. package/dist/backend/backend/src/services/onboarding/onboarding.types.js +18 -0
  231. package/dist/backend/backend/src/services/onboarding/onboarding.types.js.map +1 -0
  232. package/dist/backend/backend/src/services/pr-review/pr-review.service.d.ts.map +1 -1
  233. package/dist/backend/backend/src/services/pr-review/pr-review.service.js +1 -1
  234. package/dist/backend/backend/src/services/pr-review/pr-review.service.js.map +1 -1
  235. package/dist/backend/backend/src/services/slack/cross-machine-message.service.d.ts.map +1 -1
  236. package/dist/backend/backend/src/services/slack/cross-machine-message.service.js +17 -1
  237. package/dist/backend/backend/src/services/slack/cross-machine-message.service.js.map +1 -1
  238. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts +39 -1
  239. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
  240. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +158 -26
  241. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  242. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +248 -6
  243. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  244. package/dist/backend/backend/src/services/task-pool/task-pool.service.js +531 -51
  245. package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  246. package/dist/backend/backend/src/services/team-health/index.d.ts +16 -0
  247. package/dist/backend/backend/src/services/team-health/index.d.ts.map +1 -0
  248. package/dist/backend/backend/src/services/team-health/index.js +16 -0
  249. package/dist/backend/backend/src/services/team-health/index.js.map +1 -0
  250. package/dist/backend/backend/src/services/team-health/live-team-health-data-provider.d.ts +52 -0
  251. package/dist/backend/backend/src/services/team-health/live-team-health-data-provider.d.ts.map +1 -0
  252. package/dist/backend/backend/src/services/team-health/live-team-health-data-provider.js +161 -0
  253. package/dist/backend/backend/src/services/team-health/live-team-health-data-provider.js.map +1 -0
  254. package/dist/backend/backend/src/services/team-health/lost-dispatch-detector.d.ts +53 -0
  255. package/dist/backend/backend/src/services/team-health/lost-dispatch-detector.d.ts.map +1 -0
  256. package/dist/backend/backend/src/services/team-health/lost-dispatch-detector.js +88 -0
  257. package/dist/backend/backend/src/services/team-health/lost-dispatch-detector.js.map +1 -0
  258. package/dist/backend/backend/src/services/team-health/stale-trigger-detector.d.ts +44 -0
  259. package/dist/backend/backend/src/services/team-health/stale-trigger-detector.d.ts.map +1 -0
  260. package/dist/backend/backend/src/services/team-health/stale-trigger-detector.js +83 -0
  261. package/dist/backend/backend/src/services/team-health/stale-trigger-detector.js.map +1 -0
  262. package/dist/backend/backend/src/services/team-health/team-health-alert-router.d.ts +92 -0
  263. package/dist/backend/backend/src/services/team-health/team-health-alert-router.d.ts.map +1 -0
  264. package/dist/backend/backend/src/services/team-health/team-health-alert-router.js +328 -0
  265. package/dist/backend/backend/src/services/team-health/team-health-alert-router.js.map +1 -0
  266. package/dist/backend/backend/src/services/team-health/team-health-config.d.ts +41 -0
  267. package/dist/backend/backend/src/services/team-health/team-health-config.d.ts.map +1 -0
  268. package/dist/backend/backend/src/services/team-health/team-health-config.js +213 -0
  269. package/dist/backend/backend/src/services/team-health/team-health-config.js.map +1 -0
  270. package/dist/backend/backend/src/services/team-health/team-health-detector.d.ts +46 -0
  271. package/dist/backend/backend/src/services/team-health/team-health-detector.d.ts.map +1 -0
  272. package/dist/backend/backend/src/services/team-health/team-health-detector.js +347 -0
  273. package/dist/backend/backend/src/services/team-health/team-health-detector.js.map +1 -0
  274. package/dist/backend/backend/src/services/team-health/team-health-types.d.ts +154 -0
  275. package/dist/backend/backend/src/services/team-health/team-health-types.d.ts.map +1 -0
  276. package/dist/backend/backend/src/services/team-health/team-health-types.js +94 -0
  277. package/dist/backend/backend/src/services/team-health/team-health-types.js.map +1 -0
  278. package/dist/backend/backend/src/services/team-health/team-health-watchdog.service.d.ts +111 -0
  279. package/dist/backend/backend/src/services/team-health/team-health-watchdog.service.d.ts.map +1 -0
  280. package/dist/backend/backend/src/services/team-health/team-health-watchdog.service.js +226 -0
  281. package/dist/backend/backend/src/services/team-health/team-health-watchdog.service.js.map +1 -0
  282. package/dist/backend/backend/src/services/v3/mission-reminder.service.d.ts +148 -0
  283. package/dist/backend/backend/src/services/v3/mission-reminder.service.d.ts.map +1 -0
  284. package/dist/backend/backend/src/services/v3/mission-reminder.service.js +545 -0
  285. package/dist/backend/backend/src/services/v3/mission-reminder.service.js.map +1 -0
  286. package/dist/backend/backend/src/services/v3/request-sla.subscriber.d.ts +499 -0
  287. package/dist/backend/backend/src/services/v3/request-sla.subscriber.d.ts.map +1 -0
  288. package/dist/backend/backend/src/services/v3/request-sla.subscriber.js +1105 -0
  289. package/dist/backend/backend/src/services/v3/request-sla.subscriber.js.map +1 -0
  290. package/dist/backend/backend/src/services/v3/request.service.d.ts +22 -0
  291. package/dist/backend/backend/src/services/v3/request.service.d.ts.map +1 -1
  292. package/dist/backend/backend/src/services/v3/request.service.js +71 -0
  293. package/dist/backend/backend/src/services/v3/request.service.js.map +1 -1
  294. package/dist/backend/backend/src/services/v3/v3-data.service.d.ts +1 -0
  295. package/dist/backend/backend/src/services/v3/v3-data.service.d.ts.map +1 -1
  296. package/dist/backend/backend/src/services/v3/v3-data.service.js +22 -6
  297. package/dist/backend/backend/src/services/v3/v3-data.service.js.map +1 -1
  298. package/dist/backend/backend/src/types/event-bus.types.d.ts +19 -1
  299. package/dist/backend/backend/src/types/event-bus.types.d.ts.map +1 -1
  300. package/dist/backend/backend/src/types/event-bus.types.js +43 -0
  301. package/dist/backend/backend/src/types/event-bus.types.js.map +1 -1
  302. package/dist/backend/backend/src/types/index.d.ts +22 -1
  303. package/dist/backend/backend/src/types/index.d.ts.map +1 -1
  304. package/dist/backend/backend/src/types/index.js.map +1 -1
  305. package/dist/backend/backend/src/types/review-reason.types.d.ts +63 -0
  306. package/dist/backend/backend/src/types/review-reason.types.d.ts.map +1 -0
  307. package/dist/backend/backend/src/types/review-reason.types.js +50 -0
  308. package/dist/backend/backend/src/types/review-reason.types.js.map +1 -0
  309. package/dist/backend/backend/src/types/slack.types.d.ts +4 -1
  310. package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
  311. package/dist/backend/backend/src/types/slack.types.js.map +1 -1
  312. package/dist/backend/backend/src/types/v2/mission.types.d.ts +18 -0
  313. package/dist/backend/backend/src/types/v2/mission.types.d.ts.map +1 -1
  314. package/dist/backend/backend/src/types/v2/mission.types.js +1 -0
  315. package/dist/backend/backend/src/types/v2/mission.types.js.map +1 -1
  316. package/dist/backend/backend/src/types/v2/work-item.types.d.ts.map +1 -1
  317. package/dist/backend/backend/src/types/v2/work-item.types.js +25 -1
  318. package/dist/backend/backend/src/types/v2/work-item.types.js.map +1 -1
  319. package/dist/backend/backend/src/utils/team.utils.d.ts +38 -0
  320. package/dist/backend/backend/src/utils/team.utils.d.ts.map +1 -0
  321. package/dist/backend/backend/src/utils/team.utils.js +45 -0
  322. package/dist/backend/backend/src/utils/team.utils.js.map +1 -0
  323. package/dist/backend/backend/src/websocket/chat-v2.gateway.d.ts +195 -0
  324. package/dist/backend/backend/src/websocket/chat-v2.gateway.d.ts.map +1 -0
  325. package/dist/backend/backend/src/websocket/chat-v2.gateway.js +401 -0
  326. package/dist/backend/backend/src/websocket/chat-v2.gateway.js.map +1 -0
  327. package/dist/backend/backend/src/websocket/terminal.gateway.d.ts +37 -2
  328. package/dist/backend/backend/src/websocket/terminal.gateway.d.ts.map +1 -1
  329. package/dist/backend/backend/src/websocket/terminal.gateway.js +106 -5
  330. package/dist/backend/backend/src/websocket/terminal.gateway.js.map +1 -1
  331. package/dist/cli/backend/src/constants.d.ts +69 -1
  332. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  333. package/dist/cli/backend/src/constants.js +69 -2
  334. package/dist/cli/backend/src/constants.js.map +1 -1
  335. package/dist/cli/backend/src/services/core/config.service.js +3 -3
  336. package/dist/cli/backend/src/services/core/config.service.js.map +1 -1
  337. package/dist/cli/backend/src/services/core/storage.service.d.ts +22 -0
  338. package/dist/cli/backend/src/services/core/storage.service.d.ts.map +1 -1
  339. package/dist/cli/backend/src/services/core/storage.service.js +57 -0
  340. package/dist/cli/backend/src/services/core/storage.service.js.map +1 -1
  341. package/dist/cli/backend/src/services/knowledge/fts5-index.service.d.ts.map +1 -1
  342. package/dist/cli/backend/src/services/knowledge/fts5-index.service.js +18 -2
  343. package/dist/cli/backend/src/services/knowledge/fts5-index.service.js.map +1 -1
  344. package/dist/cli/backend/src/services/knowledge/knowledge-search.service.d.ts +49 -13
  345. package/dist/cli/backend/src/services/knowledge/knowledge-search.service.d.ts.map +1 -1
  346. package/dist/cli/backend/src/services/knowledge/knowledge-search.service.js +123 -29
  347. package/dist/cli/backend/src/services/knowledge/knowledge-search.service.js.map +1 -1
  348. package/dist/cli/backend/src/services/knowledge/vector-store.service.d.ts.map +1 -1
  349. package/dist/cli/backend/src/services/knowledge/vector-store.service.js +24 -4
  350. package/dist/cli/backend/src/services/knowledge/vector-store.service.js.map +1 -1
  351. package/dist/cli/backend/src/types/index.d.ts +22 -1
  352. package/dist/cli/backend/src/types/index.d.ts.map +1 -1
  353. package/dist/cli/backend/src/types/index.js.map +1 -1
  354. package/dist/cli/backend/src/types/v2/work-item.types.d.ts.map +1 -1
  355. package/dist/cli/backend/src/types/v2/work-item.types.js +25 -1
  356. package/dist/cli/backend/src/types/v2/work-item.types.js.map +1 -1
  357. package/frontend/dist/assets/{index-70356616.js → index-7a4e7df5.js} +328 -326
  358. package/frontend/dist/assets/index-b7e59b2b.css +33 -0
  359. package/frontend/dist/index.html +2 -2
  360. package/package.json +2 -1
  361. package/config/skills/orchestrator/recall/SKILL.md +0 -47
  362. package/config/skills/orchestrator/recall/execute.sh +0 -13
  363. package/config/skills/orchestrator/record-learning/SKILL.md +0 -47
  364. package/config/skills/orchestrator/record-learning/execute.sh +0 -13
  365. package/config/skills/orchestrator/remember/SKILL.md +0 -55
  366. package/config/skills/orchestrator/remember/execute.sh +0 -15
  367. package/frontend/dist/assets/index-6aaa0630.css +0 -33
@@ -0,0 +1,194 @@
1
+ /**
2
+ * ChannelStore — CRUD for `chat_channels`.
3
+ *
4
+ * All authorization is handled at a higher layer (`ChatV2Service`);
5
+ * this store only enforces DB-level invariants (FKs, unique indexes).
6
+ *
7
+ * @module services/chat-v2/sqlite/channel.store
8
+ */
9
+ import { randomUUID } from 'crypto';
10
+ import { CHAT_ERROR_CODES, ChatError, } from '../types.js';
11
+ // ---------------------------------------------------------------------------
12
+ // Constants — shared SELECT column list, single source of truth
13
+ // ---------------------------------------------------------------------------
14
+ /**
15
+ * Canonical column list for `chat_channels` SELECTs. Including all Phase A
16
+ * columns (type / team_id / project_id / target_member_id) here keeps every
17
+ * read path in this store mapping to a fully-populated `ChatChannelRow`.
18
+ */
19
+ const CHANNEL_SELECT_COLUMNS = `
20
+ id, agent_session, owner_user_id, name, purpose,
21
+ created_at, archived_at, last_message_at,
22
+ type, team_id, project_id, target_member_id
23
+ `;
24
+ // ---------------------------------------------------------------------------
25
+ // Store
26
+ // ---------------------------------------------------------------------------
27
+ /** SQLite-backed store for chat channels. */
28
+ export class ChannelStore {
29
+ db;
30
+ constructor(db) {
31
+ this.db = db;
32
+ }
33
+ /**
34
+ * Create a new channel. Enforces the 1:1 agent-binding by catching
35
+ * the SQLite unique-constraint error and surfacing it as a
36
+ * `ChatError(agent_already_bound, 409)`.
37
+ *
38
+ * @param input - The channel creation payload
39
+ * @returns The inserted channel row
40
+ * @throws {ChatError} `agent_already_bound` (409) if the agent is already bound
41
+ * to another active channel.
42
+ */
43
+ create(input) {
44
+ const id = input.id ?? randomUUID();
45
+ const createdAt = input.nowMs ?? Date.now();
46
+ const purpose = input.purpose ?? null;
47
+ const channelType = input.type ?? 'dm';
48
+ const teamId = input.teamId ?? null;
49
+ const projectId = input.projectId ?? null;
50
+ const targetMemberId = input.targetMemberId ?? null;
51
+ const stmt = this.db.prepare(`INSERT INTO chat_channels
52
+ (id, agent_session, owner_user_id, name, purpose, created_at,
53
+ type, team_id, project_id, target_member_id)
54
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
55
+ try {
56
+ stmt.run(id, input.agentSession, input.ownerUserId, input.name, purpose, createdAt, channelType, teamId, projectId, targetMemberId);
57
+ }
58
+ catch (err) {
59
+ // Unique constraint means either the partial index on agent_session fired
60
+ // (the common case — 1:1 binding violated) or we raced an id collision.
61
+ // In either case it's safer to check `findActiveByAgentSession` before
62
+ // surfacing a typed error.
63
+ const msg = err instanceof Error ? err.message : String(err);
64
+ if (msg.includes('UNIQUE')) {
65
+ const existing = this.findActiveByAgentSession(input.agentSession);
66
+ if (existing) {
67
+ throw new ChatError(CHAT_ERROR_CODES.AGENT_ALREADY_BOUND, 409, `Agent "${input.agentSession}" is already bound to another active channel.`, { existingChannelId: existing.id });
68
+ }
69
+ }
70
+ throw err;
71
+ }
72
+ const created = this.getById(id);
73
+ // Row must exist — insert just succeeded. This branch is for type-narrowing.
74
+ if (!created) {
75
+ throw new ChatError(CHAT_ERROR_CODES.INTERNAL, 500, 'Channel disappeared immediately after insert');
76
+ }
77
+ return created;
78
+ }
79
+ /**
80
+ * Look up a channel by its id (active or archived).
81
+ *
82
+ * @param id - The channel id
83
+ * @returns The row, or null if not found
84
+ */
85
+ getById(id) {
86
+ const row = this.db
87
+ .prepare(`SELECT ${CHANNEL_SELECT_COLUMNS}
88
+ FROM chat_channels
89
+ WHERE id = ?`)
90
+ .get(id);
91
+ return row ?? null;
92
+ }
93
+ /**
94
+ * Find the single active channel bound to an agent session, if any.
95
+ * Uses the partial unique index, so at most one row is returned.
96
+ *
97
+ * @param agentSession - The agent session id
98
+ * @returns The active channel row, or null
99
+ */
100
+ findActiveByAgentSession(agentSession) {
101
+ // Phase A: scope to type='dm' so this method's contract matches the
102
+ // post-migration `uq_channel_agent_dm_active` partial unique index.
103
+ // type='channel' rows can share the empty agent_session sentinel; we
104
+ // never want this lookup to surface those.
105
+ const row = this.db
106
+ .prepare(`SELECT ${CHANNEL_SELECT_COLUMNS}
107
+ FROM chat_channels
108
+ WHERE agent_session = ? AND archived_at IS NULL AND type = 'dm'
109
+ LIMIT 1`)
110
+ .get(agentSession);
111
+ return row ?? null;
112
+ }
113
+ /**
114
+ * List channels owned by a user.
115
+ *
116
+ * Phase C — extended with `type` + `teamId` filter options so the
117
+ * `GET /api/chat/channels` endpoint can serve the channel-rail's
118
+ * grouped/workspace-scoped views without shipping every row to the
119
+ * client. Filters compose with AND; passing `undefined` for either is
120
+ * a no-op (the existing all-channels behavior).
121
+ *
122
+ * @param ownerUserId - The user_id whose channels to return
123
+ * @param options - Listing options
124
+ * @param options.includeArchived - When false (default), filter out archived rows
125
+ * @param options.limit - Max rows to return (capped at 100)
126
+ * @param options.type - Phase C: when set, filter rows to this channel type
127
+ * (`'dm'` or `'channel'`). Useful for the channel-rail's "DMs only" /
128
+ * "Channels only" views.
129
+ * @param options.teamId - Phase C: when set, filter rows whose `team_id`
130
+ * matches. Used by the workspace-scoped Channels group; empty / null
131
+ * `team_id` rows are excluded by this filter on purpose (DMs and
132
+ * workspace-less rows belong to no team).
133
+ * @returns Channel rows sorted by `last_message_at DESC, created_at DESC`
134
+ */
135
+ listByOwner(ownerUserId, options) {
136
+ const includeArchived = options?.includeArchived ?? false;
137
+ const limit = Math.min(options?.limit ?? 50, 100);
138
+ // Build WHERE clauses + bound params positionally so the filter set
139
+ // composes cleanly. Each branch is independent — no implicit coupling.
140
+ const where = ['owner_user_id = ?'];
141
+ const params = [ownerUserId];
142
+ if (!includeArchived) {
143
+ where.push('archived_at IS NULL');
144
+ }
145
+ if (options?.type !== undefined) {
146
+ where.push('type = ?');
147
+ params.push(options.type);
148
+ }
149
+ if (options?.teamId !== undefined) {
150
+ where.push('team_id = ?');
151
+ params.push(options.teamId);
152
+ }
153
+ const sql = `
154
+ SELECT ${CHANNEL_SELECT_COLUMNS}
155
+ FROM chat_channels
156
+ WHERE ${where.join(' AND ')}
157
+ ORDER BY COALESCE(last_message_at, created_at) DESC
158
+ LIMIT ?
159
+ `;
160
+ params.push(limit);
161
+ return this.db.prepare(sql).all(...params);
162
+ }
163
+ /**
164
+ * Mark a channel as archived (soft-delete). No-op if already archived.
165
+ *
166
+ * @param id - The channel id
167
+ * @param nowMs - Optional override for the archive timestamp
168
+ * @returns True if a row was updated (was active), false otherwise
169
+ */
170
+ archive(id, nowMs) {
171
+ const result = this.db
172
+ .prepare(`UPDATE chat_channels
173
+ SET archived_at = ?
174
+ WHERE id = ? AND archived_at IS NULL`)
175
+ .run(nowMs ?? Date.now(), id);
176
+ return result.changes > 0;
177
+ }
178
+ /**
179
+ * Bump `last_message_at` to a provided timestamp if it is greater than
180
+ * the current value. Safe to call many times.
181
+ *
182
+ * @param id - The channel id
183
+ * @param timestampMs - The timestamp to record
184
+ */
185
+ touchLastMessageAt(id, timestampMs) {
186
+ this.db
187
+ .prepare(`UPDATE chat_channels
188
+ SET last_message_at = ?
189
+ WHERE id = ?
190
+ AND (last_message_at IS NULL OR last_message_at < ?)`)
191
+ .run(timestampMs, id, timestampMs);
192
+ }
193
+ }
194
+ //# sourceMappingURL=channel.store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.store.js","sourceRoot":"","sources":["../../../../../../../backend/src/services/chat-v2/sqlite/channel.store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Chat V2 SQLite bootstrap.
3
+ *
4
+ * Opens (or creates) the chat database at the configured path, applies
5
+ * the Phase 1 migration idempotently, and returns a ready-to-use
6
+ * `better-sqlite3` Database instance.
7
+ *
8
+ * Schema matches tech-spec §3.2. All tables use `INTEGER ms-since-epoch (UTC)`
9
+ * for timestamps and raw UUID strings for IDs.
10
+ *
11
+ * @module services/chat-v2/sqlite/chat-db
12
+ */
13
+ import { type ComponentLogger } from '../../core/logger.service.js';
14
+ /** Type alias for a `better-sqlite3` Database instance. */
15
+ export type ChatDatabase = import('better-sqlite3').Database;
16
+ /**
17
+ * Idempotent base DDL — Phase 1 schema with Phase A column additions baked in.
18
+ *
19
+ * Safe to run every boot.
20
+ *
21
+ * Differences vs. the literal spec §3.2:
22
+ * - Added `IF NOT EXISTS` on every CREATE so reboots don't fail.
23
+ * - PRAGMAs set outside the transaction (SQLite disallows PRAGMA in txn).
24
+ *
25
+ * Phase A additions (SEALED design 2026-04-25 §3.1 + §3.2) are reflected
26
+ * directly in the CREATE TABLE bodies so a fresh-install database lands on
27
+ * the new schema in one shot. Pre-existing databases (created under the
28
+ * Phase 1 schema) get the same columns added by `applyPhaseAColumnUpgrades`
29
+ * below, which uses `ALTER TABLE ADD COLUMN` guarded by `PRAGMA table_info`.
30
+ */
31
+ export declare const CHAT_V2_MIGRATION_SQL = "\nCREATE TABLE IF NOT EXISTS chat_channels (\n id TEXT PRIMARY KEY,\n agent_session TEXT NOT NULL,\n owner_user_id TEXT NOT NULL,\n name TEXT NOT NULL,\n purpose TEXT,\n created_at INTEGER NOT NULL,\n archived_at INTEGER,\n last_message_at INTEGER,\n -- Phase A (SEALED \u00A73.1): channel-type discriminator. 'dm' preserves the\n -- Phase 1 1:1 user\u2194agent contract; 'channel' is the Slack-like team\n -- surface. Existing rows backfill to 'dm' via applyPhaseAColumnUpgrades.\n type TEXT NOT NULL DEFAULT 'dm' CHECK(type IN ('dm','channel')),\n -- Phase A (SEALED \u00A73.1): team workspace link. Required at the service\n -- layer when type='channel'; null for type='dm'.\n team_id TEXT,\n -- Phase A (SEALED \u00A73.1): optional project link for project-scoped channels.\n project_id TEXT,\n -- Phase A (SEALED \u00A73.1): for type='dm', the resolved member-ID being DM'd\n -- (distinct from agent_session which is the wire-level binding key).\n target_member_id TEXT\n);\n\nCREATE INDEX IF NOT EXISTS ix_channels_owner\n ON chat_channels(owner_user_id, archived_at);\n\n-- Phase A indexes that reference the new columns (type, team_id, thread_id)\n-- live in CHAT_V2_PHASE_A_INDEX_SQL below \u2014 they must be created AFTER\n-- applyPhaseAColumnUpgrades runs so the columns exist on legacy DBs.\n\nCREATE TABLE IF NOT EXISTS chat_messages (\n id TEXT PRIMARY KEY,\n channel_id TEXT NOT NULL REFERENCES chat_channels(id) ON DELETE CASCADE,\n seq INTEGER NOT NULL,\n sender_type TEXT NOT NULL CHECK(sender_type IN ('user','agent','system')),\n sender_id TEXT NOT NULL,\n content TEXT NOT NULL,\n content_type TEXT NOT NULL CHECK(content_type IN ('text','markdown','image_ref','system_note'))\n DEFAULT 'markdown',\n created_at INTEGER NOT NULL,\n metadata TEXT,\n -- Phase A (SEALED \u00A73.2): JSON-encoded array of mention IDs (member or\n -- team) referenced inline in the content field. Stored as a JSON string;\n -- service layer treats null as []. Bounded at insert time; see types.ts.\n mentions TEXT,\n -- Phase A (SEALED \u00A73.2): optional Slack-style thread root. When set,\n -- this message is a reply within the thread rooted at thread_id.\n thread_id TEXT\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS uq_messages_channel_seq\n ON chat_messages(channel_id, seq);\n\nCREATE INDEX IF NOT EXISTS ix_messages_channel_created\n ON chat_messages(channel_id, created_at DESC);\n\n-- Phase 1: partial unique index for clientMessageId-based idempotency (spec \u00A74.4)\nCREATE UNIQUE INDEX IF NOT EXISTS uq_messages_client_id\n ON chat_messages(channel_id, json_extract(metadata, '$.clientMessageId'))\n WHERE json_extract(metadata, '$.clientMessageId') IS NOT NULL;\n\nCREATE TABLE IF NOT EXISTS chat_attachments (\n id TEXT PRIMARY KEY,\n message_id TEXT NOT NULL REFERENCES chat_messages(id) ON DELETE CASCADE,\n kind TEXT NOT NULL CHECK(kind IN ('image')),\n mime_type TEXT NOT NULL,\n size_bytes INTEGER NOT NULL,\n local_path TEXT NOT NULL,\n original_name TEXT,\n created_at INTEGER NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS ix_attachments_message\n ON chat_attachments(message_id);\n\nCREATE TABLE IF NOT EXISTS chat_offline_queue (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n channel_id TEXT NOT NULL,\n agent_session TEXT NOT NULL,\n message_id TEXT NOT NULL REFERENCES chat_messages(id) ON DELETE CASCADE,\n queued_at INTEGER NOT NULL,\n delivered_at INTEGER,\n attempts INTEGER NOT NULL DEFAULT 0\n);\n\nCREATE INDEX IF NOT EXISTS ix_queue_pending\n ON chat_offline_queue(agent_session, delivered_at)\n WHERE delivered_at IS NULL;\n";
32
+ /**
33
+ * Phase A indexes that reference Phase A columns. Must run AFTER
34
+ * `applyPhaseAColumnUpgrades` adds those columns on pre-Phase-A databases —
35
+ * otherwise the partial-index `WHERE` clauses fail with "no such column".
36
+ *
37
+ * On a fresh database this runs after `CHAT_V2_MIGRATION_SQL` has already
38
+ * baked the columns into the CREATE TABLE bodies, so each step is a
39
+ * harmless no-op (`CREATE INDEX IF NOT EXISTS`).
40
+ */
41
+ export declare const CHAT_V2_PHASE_A_INDEX_SQL = "\n-- Phase A (SEALED \u00A73.1): the 1:1 agent-binding only applies to type='dm'\n-- channels. type='channel' rows have agent_session='' and many such rows\n-- can coexist for the same team; the partial index intentionally excludes\n-- them so the unique constraint doesn't fire on multi-agent channels.\nCREATE UNIQUE INDEX IF NOT EXISTS uq_channel_agent_dm_active\n ON chat_channels(agent_session)\n WHERE archived_at IS NULL AND type = 'dm';\n\n-- Phase A (SEALED \u00A73.1): scoped lookup for \"all channels in team T\" and\n-- \"project P channels\". Indexes by team first because team-scoped reads\n-- are the dominant Phase B+ access pattern.\nCREATE INDEX IF NOT EXISTS ix_channels_team\n ON chat_channels(team_id, archived_at)\n WHERE team_id IS NOT NULL;\n\n-- Phase A (SEALED \u00A73.2): thread-pane lookup. Filtered to non-null so the\n-- (smaller) index only covers actual threaded replies.\nCREATE INDEX IF NOT EXISTS ix_messages_thread\n ON chat_messages(thread_id, seq)\n WHERE thread_id IS NOT NULL;\n";
42
+ /**
43
+ * Bring a pre-existing chat database up to Phase A's schema by adding
44
+ * any missing columns and the new indexes / dropping the superseded ones.
45
+ *
46
+ * Safe to run on a fresh database too — every step is no-op idempotent.
47
+ * Specifically:
48
+ * - `ensureColumn` is a no-op when the column is already present (the
49
+ * fresh-install case, since the columns are baked into the CREATE
50
+ * TABLE in `CHAT_V2_MIGRATION_SQL`).
51
+ * - `DROP INDEX IF EXISTS uq_channel_agent_active` removes the legacy
52
+ * unconditional partial index so the new dm-scoped one isn't shadowed.
53
+ * The DROP is metadata-only — no row data is affected.
54
+ *
55
+ * Exported (separately from `openChatDatabase`) so tests can simulate the
56
+ * pre-Phase-A → Phase A migration path explicitly.
57
+ *
58
+ * @param db - The chat database handle
59
+ * @returns A small report describing what changed (for logging)
60
+ */
61
+ export declare function applyPhaseAColumnUpgrades(db: ChatDatabase): {
62
+ channelsAdded: string[];
63
+ messagesAdded: string[];
64
+ legacyIndexDropped: boolean;
65
+ };
66
+ /**
67
+ * Options accepted by `openChatDatabase`.
68
+ */
69
+ export interface OpenChatDatabaseOptions {
70
+ /** Absolute path to the SQLite file. Parent dirs will be created. */
71
+ dbPath: string;
72
+ /**
73
+ * When true, uses SQLite `:memory:` regardless of `dbPath`.
74
+ * Intended for unit tests. Defaults to false.
75
+ */
76
+ inMemory?: boolean;
77
+ /**
78
+ * When true, suppress the integrity-check warning on open. Tests use this
79
+ * to avoid noisy logs. Defaults to false.
80
+ */
81
+ skipIntegrityCheck?: boolean;
82
+ /** Optional logger override — defaults to LoggerService. */
83
+ logger?: ComponentLogger;
84
+ }
85
+ /**
86
+ * Open (or create) the chat database, apply PRAGMAs, run the Phase 1
87
+ * migration idempotently, and run a boot-time integrity check.
88
+ *
89
+ * @param options - Opener options
90
+ * @returns A ready-to-use better-sqlite3 Database handle
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * const db = openChatDatabase({ dbPath: '/tmp/chat.db' });
95
+ * // ... use db ...
96
+ * db.close();
97
+ * ```
98
+ */
99
+ export declare function openChatDatabase(options: OpenChatDatabaseOptions): ChatDatabase;
100
+ //# sourceMappingURL=chat-db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-db.d.ts","sourceRoot":"","sources":["../../../../../../../backend/src/services/chat-v2/sqlite/chat-db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,8BAA8B,CAAC;AA+CnF,2DAA2D;AAC3D,MAAM,MAAM,YAAY,GAAG,OAAO,gBAAgB,EAAE,QAAQ,CAAC;AAM7D;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qBAAqB,ovHAyFjC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,qgCAqBrC,CAAC;AA2DF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,YAAY,GAAG;IAC3D,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,kBAAkB,EAAE,OAAO,CAAC;CAC7B,CA0CA;AAMD;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,4DAA4D;IAC5D,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,YAAY,CAwD/E"}
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Chat V2 SQLite bootstrap.
3
+ *
4
+ * Opens (or creates) the chat database at the configured path, applies
5
+ * the Phase 1 migration idempotently, and returns a ready-to-use
6
+ * `better-sqlite3` Database instance.
7
+ *
8
+ * Schema matches tech-spec §3.2. All tables use `INTEGER ms-since-epoch (UTC)`
9
+ * for timestamps and raw UUID strings for IDs.
10
+ *
11
+ * @module services/chat-v2/sqlite/chat-db
12
+ */
13
+ import * as path from 'path';
14
+ import { existsSync, mkdirSync } from 'fs';
15
+ import { createRequire } from 'module';
16
+ import { pathToFileURL } from 'url';
17
+ import { LoggerService } from '../../core/logger.service.js';
18
+ // ---------------------------------------------------------------------------
19
+ // Lazy better-sqlite3 loader — avoids a hard native-module dep at import time
20
+ // ---------------------------------------------------------------------------
21
+ /**
22
+ * CJS-style `require` scoped to this module. `better-sqlite3` is a native
23
+ * addon and must be loaded via CJS `require`, but this module compiles to
24
+ * ESM where the bare `require` global is undefined.
25
+ *
26
+ * Anchor `createRequire` to `process.argv[1]` (entry script) instead of
27
+ * `import.meta.url`. We previously used `new Function('return import.meta.url')()`
28
+ * to dodge ts-jest's TS1343 (CJS test compile rejects literal `import.meta`),
29
+ * but that trick fails at RUNTIME under ESM because `new Function(...)`
30
+ * evaluates in non-module scope where `import.meta` is a SyntaxError.
31
+ * `process.argv[1]` is always inside the project tree and lets Node's
32
+ * resolver walk up to find `node_modules`.
33
+ */
34
+ const nodeRequire = typeof require === 'function'
35
+ ? require
36
+ : createRequire(pathToFileURL(process.argv[1] || process.cwd()).href);
37
+ /** Cached reference to the better-sqlite3 module after first successful load. */
38
+ let _BetterSqlite3 = null;
39
+ /**
40
+ * Load `better-sqlite3` on demand. Throws a clear error if the native
41
+ * addon cannot be loaded (a common symptom after Node upgrades).
42
+ *
43
+ * @returns The lazily-imported better-sqlite3 module
44
+ */
45
+ function getBetterSqlite3() {
46
+ if (!_BetterSqlite3) {
47
+ try {
48
+ _BetterSqlite3 = nodeRequire('better-sqlite3');
49
+ }
50
+ catch (err) {
51
+ throw new Error('better-sqlite3 native module failed to load. Run `npm rebuild better-sqlite3` to fix. ' +
52
+ `Original error: ${err instanceof Error ? err.message : String(err)}`);
53
+ }
54
+ }
55
+ return _BetterSqlite3;
56
+ }
57
+ // ---------------------------------------------------------------------------
58
+ // Migration DDL (Phase 1 base + Phase A Slack-like extensions)
59
+ // ---------------------------------------------------------------------------
60
+ /**
61
+ * Idempotent base DDL — Phase 1 schema with Phase A column additions baked in.
62
+ *
63
+ * Safe to run every boot.
64
+ *
65
+ * Differences vs. the literal spec §3.2:
66
+ * - Added `IF NOT EXISTS` on every CREATE so reboots don't fail.
67
+ * - PRAGMAs set outside the transaction (SQLite disallows PRAGMA in txn).
68
+ *
69
+ * Phase A additions (SEALED design 2026-04-25 §3.1 + §3.2) are reflected
70
+ * directly in the CREATE TABLE bodies so a fresh-install database lands on
71
+ * the new schema in one shot. Pre-existing databases (created under the
72
+ * Phase 1 schema) get the same columns added by `applyPhaseAColumnUpgrades`
73
+ * below, which uses `ALTER TABLE ADD COLUMN` guarded by `PRAGMA table_info`.
74
+ */
75
+ export const CHAT_V2_MIGRATION_SQL = `
76
+ CREATE TABLE IF NOT EXISTS chat_channels (
77
+ id TEXT PRIMARY KEY,
78
+ agent_session TEXT NOT NULL,
79
+ owner_user_id TEXT NOT NULL,
80
+ name TEXT NOT NULL,
81
+ purpose TEXT,
82
+ created_at INTEGER NOT NULL,
83
+ archived_at INTEGER,
84
+ last_message_at INTEGER,
85
+ -- Phase A (SEALED §3.1): channel-type discriminator. 'dm' preserves the
86
+ -- Phase 1 1:1 user↔agent contract; 'channel' is the Slack-like team
87
+ -- surface. Existing rows backfill to 'dm' via applyPhaseAColumnUpgrades.
88
+ type TEXT NOT NULL DEFAULT 'dm' CHECK(type IN ('dm','channel')),
89
+ -- Phase A (SEALED §3.1): team workspace link. Required at the service
90
+ -- layer when type='channel'; null for type='dm'.
91
+ team_id TEXT,
92
+ -- Phase A (SEALED §3.1): optional project link for project-scoped channels.
93
+ project_id TEXT,
94
+ -- Phase A (SEALED §3.1): for type='dm', the resolved member-ID being DM'd
95
+ -- (distinct from agent_session which is the wire-level binding key).
96
+ target_member_id TEXT
97
+ );
98
+
99
+ CREATE INDEX IF NOT EXISTS ix_channels_owner
100
+ ON chat_channels(owner_user_id, archived_at);
101
+
102
+ -- Phase A indexes that reference the new columns (type, team_id, thread_id)
103
+ -- live in CHAT_V2_PHASE_A_INDEX_SQL below — they must be created AFTER
104
+ -- applyPhaseAColumnUpgrades runs so the columns exist on legacy DBs.
105
+
106
+ CREATE TABLE IF NOT EXISTS chat_messages (
107
+ id TEXT PRIMARY KEY,
108
+ channel_id TEXT NOT NULL REFERENCES chat_channels(id) ON DELETE CASCADE,
109
+ seq INTEGER NOT NULL,
110
+ sender_type TEXT NOT NULL CHECK(sender_type IN ('user','agent','system')),
111
+ sender_id TEXT NOT NULL,
112
+ content TEXT NOT NULL,
113
+ content_type TEXT NOT NULL CHECK(content_type IN ('text','markdown','image_ref','system_note'))
114
+ DEFAULT 'markdown',
115
+ created_at INTEGER NOT NULL,
116
+ metadata TEXT,
117
+ -- Phase A (SEALED §3.2): JSON-encoded array of mention IDs (member or
118
+ -- team) referenced inline in the content field. Stored as a JSON string;
119
+ -- service layer treats null as []. Bounded at insert time; see types.ts.
120
+ mentions TEXT,
121
+ -- Phase A (SEALED §3.2): optional Slack-style thread root. When set,
122
+ -- this message is a reply within the thread rooted at thread_id.
123
+ thread_id TEXT
124
+ );
125
+
126
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_messages_channel_seq
127
+ ON chat_messages(channel_id, seq);
128
+
129
+ CREATE INDEX IF NOT EXISTS ix_messages_channel_created
130
+ ON chat_messages(channel_id, created_at DESC);
131
+
132
+ -- Phase 1: partial unique index for clientMessageId-based idempotency (spec §4.4)
133
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_messages_client_id
134
+ ON chat_messages(channel_id, json_extract(metadata, '$.clientMessageId'))
135
+ WHERE json_extract(metadata, '$.clientMessageId') IS NOT NULL;
136
+
137
+ CREATE TABLE IF NOT EXISTS chat_attachments (
138
+ id TEXT PRIMARY KEY,
139
+ message_id TEXT NOT NULL REFERENCES chat_messages(id) ON DELETE CASCADE,
140
+ kind TEXT NOT NULL CHECK(kind IN ('image')),
141
+ mime_type TEXT NOT NULL,
142
+ size_bytes INTEGER NOT NULL,
143
+ local_path TEXT NOT NULL,
144
+ original_name TEXT,
145
+ created_at INTEGER NOT NULL
146
+ );
147
+
148
+ CREATE INDEX IF NOT EXISTS ix_attachments_message
149
+ ON chat_attachments(message_id);
150
+
151
+ CREATE TABLE IF NOT EXISTS chat_offline_queue (
152
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
153
+ channel_id TEXT NOT NULL,
154
+ agent_session TEXT NOT NULL,
155
+ message_id TEXT NOT NULL REFERENCES chat_messages(id) ON DELETE CASCADE,
156
+ queued_at INTEGER NOT NULL,
157
+ delivered_at INTEGER,
158
+ attempts INTEGER NOT NULL DEFAULT 0
159
+ );
160
+
161
+ CREATE INDEX IF NOT EXISTS ix_queue_pending
162
+ ON chat_offline_queue(agent_session, delivered_at)
163
+ WHERE delivered_at IS NULL;
164
+ `;
165
+ /**
166
+ * Phase A indexes that reference Phase A columns. Must run AFTER
167
+ * `applyPhaseAColumnUpgrades` adds those columns on pre-Phase-A databases —
168
+ * otherwise the partial-index `WHERE` clauses fail with "no such column".
169
+ *
170
+ * On a fresh database this runs after `CHAT_V2_MIGRATION_SQL` has already
171
+ * baked the columns into the CREATE TABLE bodies, so each step is a
172
+ * harmless no-op (`CREATE INDEX IF NOT EXISTS`).
173
+ */
174
+ export const CHAT_V2_PHASE_A_INDEX_SQL = `
175
+ -- Phase A (SEALED §3.1): the 1:1 agent-binding only applies to type='dm'
176
+ -- channels. type='channel' rows have agent_session='' and many such rows
177
+ -- can coexist for the same team; the partial index intentionally excludes
178
+ -- them so the unique constraint doesn't fire on multi-agent channels.
179
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_channel_agent_dm_active
180
+ ON chat_channels(agent_session)
181
+ WHERE archived_at IS NULL AND type = 'dm';
182
+
183
+ -- Phase A (SEALED §3.1): scoped lookup for "all channels in team T" and
184
+ -- "project P channels". Indexes by team first because team-scoped reads
185
+ -- are the dominant Phase B+ access pattern.
186
+ CREATE INDEX IF NOT EXISTS ix_channels_team
187
+ ON chat_channels(team_id, archived_at)
188
+ WHERE team_id IS NOT NULL;
189
+
190
+ -- Phase A (SEALED §3.2): thread-pane lookup. Filtered to non-null so the
191
+ -- (smaller) index only covers actual threaded replies.
192
+ CREATE INDEX IF NOT EXISTS ix_messages_thread
193
+ ON chat_messages(thread_id, seq)
194
+ WHERE thread_id IS NOT NULL;
195
+ `;
196
+ /** Phase A column additions for `chat_channels`. */
197
+ const CHAT_CHANNELS_PHASE_A_COLUMNS = [
198
+ // NOT NULL DEFAULT 'dm' lets ALTER TABLE backfill existing rows in one shot.
199
+ { name: 'type', addClause: "type TEXT NOT NULL DEFAULT 'dm'" },
200
+ { name: 'team_id', addClause: 'team_id TEXT' },
201
+ { name: 'project_id', addClause: 'project_id TEXT' },
202
+ { name: 'target_member_id', addClause: 'target_member_id TEXT' },
203
+ ];
204
+ /** Phase A column additions for `chat_messages`. */
205
+ const CHAT_MESSAGES_PHASE_A_COLUMNS = [
206
+ { name: 'mentions', addClause: 'mentions TEXT' },
207
+ { name: 'thread_id', addClause: 'thread_id TEXT' },
208
+ ];
209
+ /**
210
+ * Add a column to `table` if (and only if) it isn't already present.
211
+ *
212
+ * Uses `PRAGMA table_info(<table>)` to enumerate existing columns. SQLite
213
+ * does not have an `ADD COLUMN IF NOT EXISTS` form, so this guarded
214
+ * approach is the standard idempotent shape.
215
+ *
216
+ * @param db - The chat database handle
217
+ * @param table - Target table
218
+ * @param spec - Column to ensure
219
+ * @returns `true` if a column was added, `false` if it already existed
220
+ */
221
+ function ensureColumn(db, table, spec) {
222
+ // PRAGMA cannot be parameterized; the table name is inlined. Callers
223
+ // supply only literal table names from the constants above — never
224
+ // user input — so SQL injection is not a concern here.
225
+ const cols = db.pragma(`table_info(${table})`);
226
+ if (cols.some((c) => c.name === spec.name)) {
227
+ return false;
228
+ }
229
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${spec.addClause}`);
230
+ return true;
231
+ }
232
+ /**
233
+ * Bring a pre-existing chat database up to Phase A's schema by adding
234
+ * any missing columns and the new indexes / dropping the superseded ones.
235
+ *
236
+ * Safe to run on a fresh database too — every step is no-op idempotent.
237
+ * Specifically:
238
+ * - `ensureColumn` is a no-op when the column is already present (the
239
+ * fresh-install case, since the columns are baked into the CREATE
240
+ * TABLE in `CHAT_V2_MIGRATION_SQL`).
241
+ * - `DROP INDEX IF EXISTS uq_channel_agent_active` removes the legacy
242
+ * unconditional partial index so the new dm-scoped one isn't shadowed.
243
+ * The DROP is metadata-only — no row data is affected.
244
+ *
245
+ * Exported (separately from `openChatDatabase`) so tests can simulate the
246
+ * pre-Phase-A → Phase A migration path explicitly.
247
+ *
248
+ * @param db - The chat database handle
249
+ * @returns A small report describing what changed (for logging)
250
+ */
251
+ export function applyPhaseAColumnUpgrades(db) {
252
+ const channelsAdded = [];
253
+ for (const spec of CHAT_CHANNELS_PHASE_A_COLUMNS) {
254
+ if (ensureColumn(db, 'chat_channels', spec)) {
255
+ channelsAdded.push(spec.name);
256
+ }
257
+ }
258
+ const messagesAdded = [];
259
+ for (const spec of CHAT_MESSAGES_PHASE_A_COLUMNS) {
260
+ if (ensureColumn(db, 'chat_messages', spec)) {
261
+ messagesAdded.push(spec.name);
262
+ }
263
+ }
264
+ // Drop the legacy `uq_channel_agent_active` partial index — its constraint
265
+ // (`agent_session unique among archived_at IS NULL`) is too strict in the
266
+ // post-Phase-A world where multiple type='channel' rows legitimately share
267
+ // an empty agent_session. The replacement `uq_channel_agent_dm_active`
268
+ // (created below) carries the same semantics scoped to type='dm' rows only.
269
+ //
270
+ // The drop must happen BEFORE creating the new index — if the legacy
271
+ // index were left in place, an INSERT into a type='dm' row would still
272
+ // be checked by both indexes (same expression, different WHERE), which
273
+ // is harmless functionally but wastes write amplification.
274
+ const legacyIdx = db
275
+ .prepare(`SELECT name FROM sqlite_master
276
+ WHERE type = 'index' AND name = 'uq_channel_agent_active'`)
277
+ .get();
278
+ let legacyIndexDropped = false;
279
+ if (legacyIdx) {
280
+ db.exec('DROP INDEX IF EXISTS uq_channel_agent_active');
281
+ legacyIndexDropped = true;
282
+ }
283
+ // Now that all Phase A columns are guaranteed to exist, create the
284
+ // Phase A indexes that reference them. Idempotent — `CREATE INDEX IF NOT
285
+ // EXISTS` makes this a no-op on subsequent boots.
286
+ db.exec(CHAT_V2_PHASE_A_INDEX_SQL);
287
+ return { channelsAdded, messagesAdded, legacyIndexDropped };
288
+ }
289
+ /**
290
+ * Open (or create) the chat database, apply PRAGMAs, run the Phase 1
291
+ * migration idempotently, and run a boot-time integrity check.
292
+ *
293
+ * @param options - Opener options
294
+ * @returns A ready-to-use better-sqlite3 Database handle
295
+ *
296
+ * @example
297
+ * ```ts
298
+ * const db = openChatDatabase({ dbPath: '/tmp/chat.db' });
299
+ * // ... use db ...
300
+ * db.close();
301
+ * ```
302
+ */
303
+ export function openChatDatabase(options) {
304
+ const logger = options.logger ??
305
+ LoggerService.getInstance().createComponentLogger('ChatV2Db');
306
+ if (!options.inMemory) {
307
+ const dir = path.dirname(options.dbPath);
308
+ if (dir && dir !== '.' && !existsSync(dir)) {
309
+ mkdirSync(dir, { recursive: true });
310
+ }
311
+ }
312
+ const Database = getBetterSqlite3();
313
+ const target = options.inMemory ? ':memory:' : options.dbPath;
314
+ const db = new Database(target);
315
+ // PRAGMAs — must be set outside any transaction (better-sqlite3 opens statements
316
+ // in autocommit mode by default, so a bare pragma() call is safe).
317
+ db.pragma('journal_mode = WAL');
318
+ db.pragma('foreign_keys = ON');
319
+ db.pragma('synchronous = NORMAL');
320
+ // Apply the Phase 1 migration idempotently. `exec` accepts multiple statements.
321
+ // For fresh databases this creates every table + index in one shot, including
322
+ // the Phase A columns baked into the CREATE TABLE bodies.
323
+ db.exec(CHAT_V2_MIGRATION_SQL);
324
+ // For pre-existing databases (created under Phase 1 schema), additively
325
+ // bring them up to Phase A: add missing columns and drop the superseded
326
+ // legacy `uq_channel_agent_active` index. Every step is no-op idempotent
327
+ // on a fresh DB.
328
+ const upgradeReport = applyPhaseAColumnUpgrades(db);
329
+ if (upgradeReport.channelsAdded.length > 0 ||
330
+ upgradeReport.messagesAdded.length > 0 ||
331
+ upgradeReport.legacyIndexDropped) {
332
+ logger.info('Chat DB Phase A schema upgrade applied', upgradeReport);
333
+ }
334
+ if (!options.skipIntegrityCheck) {
335
+ try {
336
+ const rows = db.pragma('integrity_check');
337
+ const first = rows[0]?.integrity_check;
338
+ if (first && first !== 'ok') {
339
+ logger.warn('Chat DB integrity check reported issues', { result: first });
340
+ }
341
+ }
342
+ catch (err) {
343
+ logger.warn('Chat DB integrity check threw — continuing without gating startup', {
344
+ error: err instanceof Error ? err.message : String(err),
345
+ });
346
+ }
347
+ }
348
+ logger.info('Chat DB opened', { path: target });
349
+ return db;
350
+ }
351
+ //# sourceMappingURL=chat-db.js.map