crewly 1.6.5 → 1.8.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 (338) hide show
  1. package/config/constants.ts +2 -0
  2. package/config/roles/auditor/prompt.md +24 -0
  3. package/config/roles/developer/prompt.md +2 -1
  4. package/config/roles/orchestrator/prompt.md +118 -2
  5. package/config/roles/team-leader/prompt.md +6 -0
  6. package/config/skills/agent/core/create-request/SKILL.md +1 -1
  7. package/config/skills/agent/core/create-request/execute.sh +29 -2
  8. package/config/skills/agent/core/create-request/execute.test.sh +168 -0
  9. package/config/skills/agent/core/report-status/SKILL.md +8 -1
  10. package/config/skills/agent/core/report-status/execute.sh +23 -1
  11. package/config/skills/orchestrator/heartbeat/execute.sh +48 -6
  12. package/config/sops/common/mid-flight-milestone-surface.md +128 -0
  13. package/config/sops/common/owner-facing-communication.md +46 -2
  14. package/config/sops/developer/git-workflow.md +33 -0
  15. package/dist/backend/backend/src/constants.d.ts +13 -0
  16. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  17. package/dist/backend/backend/src/constants.js +12 -0
  18. package/dist/backend/backend/src/constants.js.map +1 -1
  19. package/dist/backend/backend/src/controllers/browser/browser.controller.js +2 -2
  20. package/dist/backend/backend/src/controllers/browser/browser.controller.js.map +1 -1
  21. package/dist/backend/backend/src/controllers/chat/chat.controller.d.ts.map +1 -1
  22. package/dist/backend/backend/src/controllers/chat/chat.controller.js +6 -0
  23. package/dist/backend/backend/src/controllers/chat/chat.controller.js.map +1 -1
  24. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.d.ts +73 -0
  25. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.d.ts.map +1 -1
  26. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js +128 -0
  27. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js.map +1 -1
  28. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.d.ts +3 -0
  29. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.d.ts.map +1 -1
  30. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.js +8 -0
  31. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.js.map +1 -1
  32. package/dist/backend/backend/src/controllers/session/session.controller.d.ts.map +1 -1
  33. package/dist/backend/backend/src/controllers/session/session.controller.js +50 -8
  34. package/dist/backend/backend/src/controllers/session/session.controller.js.map +1 -1
  35. package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
  36. package/dist/backend/backend/src/controllers/slack/slack.controller.js +215 -94
  37. package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
  38. package/dist/backend/backend/src/controllers/team/team.controller.d.ts.map +1 -1
  39. package/dist/backend/backend/src/controllers/team/team.controller.js +27 -0
  40. package/dist/backend/backend/src/controllers/team/team.controller.js.map +1 -1
  41. package/dist/backend/backend/src/index.d.ts +1 -0
  42. package/dist/backend/backend/src/index.d.ts.map +1 -1
  43. package/dist/backend/backend/src/index.js +201 -37
  44. package/dist/backend/backend/src/index.js.map +1 -1
  45. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  46. package/dist/backend/backend/src/routes/api.routes.js +11 -1
  47. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  48. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts +45 -3
  49. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  50. package/dist/backend/backend/src/services/agent/agent-registration.service.js +219 -7
  51. package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
  52. package/dist/backend/backend/src/services/agent/crewly-agent/agent-runner.service.d.ts +61 -1
  53. package/dist/backend/backend/src/services/agent/crewly-agent/agent-runner.service.d.ts.map +1 -1
  54. package/dist/backend/backend/src/services/agent/crewly-agent/agent-runner.service.js +117 -9
  55. package/dist/backend/backend/src/services/agent/crewly-agent/agent-runner.service.js.map +1 -1
  56. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts +44 -0
  57. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -1
  58. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +179 -10
  59. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -1
  60. package/dist/backend/backend/src/services/agent/crewly-agent/in-process-runtime-registry.d.ts +6 -6
  61. package/dist/backend/backend/src/services/agent/crewly-agent/in-process-runtime-registry.d.ts.map +1 -1
  62. package/dist/backend/backend/src/services/agent/crewly-agent/in-process-runtime-registry.js +3 -3
  63. package/dist/backend/backend/src/services/agent/crewly-agent/in-process-runtime-registry.js.map +1 -1
  64. package/dist/backend/backend/src/services/agent/crewly-agent/types.d.ts +7 -1
  65. package/dist/backend/backend/src/services/agent/crewly-agent/types.d.ts.map +1 -1
  66. package/dist/backend/backend/src/services/agent/crewly-agent/types.js.map +1 -1
  67. package/dist/backend/backend/src/services/agent/idle-detection.service.d.ts +33 -0
  68. package/dist/backend/backend/src/services/agent/idle-detection.service.d.ts.map +1 -1
  69. package/dist/backend/backend/src/services/agent/idle-detection.service.js +108 -4
  70. package/dist/backend/backend/src/services/agent/idle-detection.service.js.map +1 -1
  71. package/dist/backend/backend/src/services/agent/runtime-service.factory.js +4 -4
  72. package/dist/backend/backend/src/services/agent/runtime-service.factory.js.map +1 -1
  73. package/dist/backend/backend/src/services/browser/browser-proxy.service.d.ts +1 -1
  74. package/dist/backend/backend/src/services/browser/browser-proxy.service.d.ts.map +1 -1
  75. package/dist/backend/backend/src/services/browser/browser-proxy.service.js +40 -2
  76. package/dist/backend/backend/src/services/browser/browser-proxy.service.js.map +1 -1
  77. package/dist/backend/backend/src/services/chat/chat.service.d.ts +48 -331
  78. package/dist/backend/backend/src/services/chat/chat.service.d.ts.map +1 -1
  79. package/dist/backend/backend/src/services/chat/chat.service.js +261 -712
  80. package/dist/backend/backend/src/services/chat/chat.service.js.map +1 -1
  81. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +82 -1
  82. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
  83. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +120 -2
  84. package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
  85. package/dist/backend/backend/src/services/chat-v2/chat-v2.providers.d.ts +114 -0
  86. package/dist/backend/backend/src/services/chat-v2/chat-v2.providers.d.ts.map +1 -0
  87. package/dist/backend/backend/src/services/chat-v2/chat-v2.providers.js +182 -0
  88. package/dist/backend/backend/src/services/chat-v2/chat-v2.providers.js.map +1 -0
  89. package/dist/backend/backend/src/services/chat-v2/chat-v2.relay-adapter.service.d.ts +188 -0
  90. package/dist/backend/backend/src/services/chat-v2/chat-v2.relay-adapter.service.d.ts.map +1 -0
  91. package/dist/backend/backend/src/services/chat-v2/chat-v2.relay-adapter.service.js +434 -0
  92. package/dist/backend/backend/src/services/chat-v2/chat-v2.relay-adapter.service.js.map +1 -0
  93. package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts +401 -5
  94. package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts.map +1 -1
  95. package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js +619 -3
  96. package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js.map +1 -1
  97. package/dist/backend/backend/src/services/chat-v2/legacy-dto.utils.d.ts +93 -0
  98. package/dist/backend/backend/src/services/chat-v2/legacy-dto.utils.d.ts.map +1 -0
  99. package/dist/backend/backend/src/services/chat-v2/legacy-dto.utils.js +138 -0
  100. package/dist/backend/backend/src/services/chat-v2/legacy-dto.utils.js.map +1 -0
  101. package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.d.ts +46 -0
  102. package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.d.ts.map +1 -1
  103. package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.js +75 -0
  104. package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.js.map +1 -1
  105. package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.d.ts +10 -2
  106. package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.d.ts.map +1 -1
  107. package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.js +178 -10
  108. package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.js.map +1 -1
  109. package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.d.ts +37 -0
  110. package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.d.ts.map +1 -1
  111. package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.js +71 -0
  112. package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.js.map +1 -1
  113. package/dist/backend/backend/src/services/chat-v2/types.d.ts +33 -1
  114. package/dist/backend/backend/src/services/chat-v2/types.d.ts.map +1 -1
  115. package/dist/backend/backend/src/services/chat-v2/types.js +1 -1
  116. package/dist/backend/backend/src/services/chat-v2/types.js.map +1 -1
  117. package/dist/backend/backend/src/services/cloud/cloud-sync.service.d.ts +22 -0
  118. package/dist/backend/backend/src/services/cloud/cloud-sync.service.d.ts.map +1 -1
  119. package/dist/backend/backend/src/services/cloud/cloud-sync.service.js +71 -1
  120. package/dist/backend/backend/src/services/cloud/cloud-sync.service.js.map +1 -1
  121. package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts +102 -1
  122. package/dist/backend/backend/src/services/cloud/cloud-sync.types.d.ts.map +1 -1
  123. package/dist/backend/backend/src/services/cloud/cloud-sync.types.js +61 -0
  124. package/dist/backend/backend/src/services/cloud/cloud-sync.types.js.map +1 -1
  125. package/dist/backend/backend/src/services/cloud/device-auto-discovery.service.d.ts +21 -3
  126. package/dist/backend/backend/src/services/cloud/device-auto-discovery.service.d.ts.map +1 -1
  127. package/dist/backend/backend/src/services/cloud/device-auto-discovery.service.js +47 -13
  128. package/dist/backend/backend/src/services/cloud/device-auto-discovery.service.js.map +1 -1
  129. package/dist/backend/backend/src/services/core/system-health.util.d.ts +25 -4
  130. package/dist/backend/backend/src/services/core/system-health.util.d.ts.map +1 -1
  131. package/dist/backend/backend/src/services/core/system-health.util.js +30 -5
  132. package/dist/backend/backend/src/services/core/system-health.util.js.map +1 -1
  133. package/dist/backend/backend/src/services/event-bus/event-bus.service.d.ts.map +1 -1
  134. package/dist/backend/backend/src/services/event-bus/event-bus.service.js +22 -11
  135. package/dist/backend/backend/src/services/event-bus/event-bus.service.js.map +1 -1
  136. package/dist/backend/backend/src/services/mcp-server.d.ts.map +1 -1
  137. package/dist/backend/backend/src/services/mcp-server.js +5 -0
  138. package/dist/backend/backend/src/services/mcp-server.js.map +1 -1
  139. package/dist/backend/backend/src/services/mcp-tool-definitions.d.ts +4 -0
  140. package/dist/backend/backend/src/services/mcp-tool-definitions.d.ts.map +1 -1
  141. package/dist/backend/backend/src/services/mcp-tool-definitions.js +12 -1
  142. package/dist/backend/backend/src/services/mcp-tool-definitions.js.map +1 -1
  143. package/dist/backend/backend/src/services/memory/capability-inference.d.ts +73 -0
  144. package/dist/backend/backend/src/services/memory/capability-inference.d.ts.map +1 -0
  145. package/dist/backend/backend/src/services/memory/capability-inference.js +115 -0
  146. package/dist/backend/backend/src/services/memory/capability-inference.js.map +1 -0
  147. package/dist/backend/backend/src/services/memory/memory.service.d.ts +22 -1
  148. package/dist/backend/backend/src/services/memory/memory.service.d.ts.map +1 -1
  149. package/dist/backend/backend/src/services/memory/memory.service.js +81 -3
  150. package/dist/backend/backend/src/services/memory/memory.service.js.map +1 -1
  151. package/dist/backend/backend/src/services/memory/project-memory.service.d.ts +25 -1
  152. package/dist/backend/backend/src/services/memory/project-memory.service.d.ts.map +1 -1
  153. package/dist/backend/backend/src/services/memory/project-memory.service.js +43 -0
  154. package/dist/backend/backend/src/services/memory/project-memory.service.js.map +1 -1
  155. package/dist/backend/backend/src/services/memory/task-history-seeder.d.ts +47 -0
  156. package/dist/backend/backend/src/services/memory/task-history-seeder.d.ts.map +1 -0
  157. package/dist/backend/backend/src/services/memory/task-history-seeder.js +89 -0
  158. package/dist/backend/backend/src/services/memory/task-history-seeder.js.map +1 -0
  159. package/dist/backend/backend/src/services/memory/task-history.subscriber.d.ts +76 -0
  160. package/dist/backend/backend/src/services/memory/task-history.subscriber.d.ts.map +1 -0
  161. package/dist/backend/backend/src/services/memory/task-history.subscriber.js +199 -0
  162. package/dist/backend/backend/src/services/memory/task-history.subscriber.js.map +1 -0
  163. package/dist/backend/backend/src/services/messaging/message-replay.service.d.ts +2 -4
  164. package/dist/backend/backend/src/services/messaging/message-replay.service.d.ts.map +1 -1
  165. package/dist/backend/backend/src/services/messaging/message-replay.service.js +22 -12
  166. package/dist/backend/backend/src/services/messaging/message-replay.service.js.map +1 -1
  167. package/dist/backend/backend/src/services/messaging/queue-processor.service.d.ts.map +1 -1
  168. package/dist/backend/backend/src/services/messaging/queue-processor.service.js +25 -5
  169. package/dist/backend/backend/src/services/messaging/queue-processor.service.js.map +1 -1
  170. package/dist/backend/backend/src/services/monitoring/system-resource-alert.service.d.ts.map +1 -1
  171. package/dist/backend/backend/src/services/monitoring/system-resource-alert.service.js +13 -3
  172. package/dist/backend/backend/src/services/monitoring/system-resource-alert.service.js.map +1 -1
  173. package/dist/backend/backend/src/services/notification/milestone-notification.subscriber.d.ts +99 -0
  174. package/dist/backend/backend/src/services/notification/milestone-notification.subscriber.d.ts.map +1 -0
  175. package/dist/backend/backend/src/services/notification/milestone-notification.subscriber.js +225 -0
  176. package/dist/backend/backend/src/services/notification/milestone-notification.subscriber.js.map +1 -0
  177. package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts +39 -18
  178. package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts.map +1 -1
  179. package/dist/backend/backend/src/services/reconciler/reconcile-rules.js +60 -32
  180. package/dist/backend/backend/src/services/reconciler/reconcile-rules.js.map +1 -1
  181. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts +134 -0
  182. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
  183. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +416 -13
  184. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
  185. package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts.map +1 -1
  186. package/dist/backend/backend/src/services/reconciler/reconciler.service.js +73 -7
  187. package/dist/backend/backend/src/services/reconciler/reconciler.service.js.map +1 -1
  188. package/dist/backend/backend/src/services/session/session-handoff.service.d.ts.map +1 -1
  189. package/dist/backend/backend/src/services/session/session-handoff.service.js +30 -4
  190. package/dist/backend/backend/src/services/session/session-handoff.service.js.map +1 -1
  191. package/dist/backend/backend/src/services/skill/skill-executor.service.d.ts.map +1 -1
  192. package/dist/backend/backend/src/services/skill/skill-executor.service.js +13 -1
  193. package/dist/backend/backend/src/services/skill/skill-executor.service.js.map +1 -1
  194. package/dist/backend/backend/src/services/slack/notify-reconciliation.service.d.ts.map +1 -1
  195. package/dist/backend/backend/src/services/slack/notify-reconciliation.service.js +9 -6
  196. package/dist/backend/backend/src/services/slack/notify-reconciliation.service.js.map +1 -1
  197. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts +21 -2
  198. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
  199. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +120 -46
  200. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  201. package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
  202. package/dist/backend/backend/src/services/slack/slack.service.js +49 -0
  203. package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
  204. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +33 -2
  205. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  206. package/dist/backend/backend/src/services/task-pool/task-pool.service.js +160 -8
  207. package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  208. package/dist/backend/backend/src/services/v3/cascade-request-status.d.ts.map +1 -1
  209. package/dist/backend/backend/src/services/v3/cascade-request-status.js +55 -2
  210. package/dist/backend/backend/src/services/v3/cascade-request-status.js.map +1 -1
  211. package/dist/backend/backend/src/services/v3/mission-executor.service.d.ts.map +1 -1
  212. package/dist/backend/backend/src/services/v3/mission-executor.service.js +9 -1
  213. package/dist/backend/backend/src/services/v3/mission-executor.service.js.map +1 -1
  214. package/dist/backend/backend/src/services/v3/request-decompose.subscriber.d.ts.map +1 -1
  215. package/dist/backend/backend/src/services/v3/request-decompose.subscriber.js +28 -3
  216. package/dist/backend/backend/src/services/v3/request-decompose.subscriber.js.map +1 -1
  217. package/dist/backend/backend/src/services/v3/request-sla.subscriber.d.ts.map +1 -1
  218. package/dist/backend/backend/src/services/v3/request-sla.subscriber.js +5 -2
  219. package/dist/backend/backend/src/services/v3/request-sla.subscriber.js.map +1 -1
  220. package/dist/backend/backend/src/services/v3/request-status-update.subscriber.d.ts.map +1 -1
  221. package/dist/backend/backend/src/services/v3/request-status-update.subscriber.js +57 -15
  222. package/dist/backend/backend/src/services/v3/request-status-update.subscriber.js.map +1 -1
  223. package/dist/backend/backend/src/services/v3/trigger-engine.service.d.ts +39 -0
  224. package/dist/backend/backend/src/services/v3/trigger-engine.service.d.ts.map +1 -1
  225. package/dist/backend/backend/src/services/v3/trigger-engine.service.js +81 -0
  226. package/dist/backend/backend/src/services/v3/trigger-engine.service.js.map +1 -1
  227. package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.d.ts +17 -1
  228. package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.d.ts.map +1 -1
  229. package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.js +22 -3
  230. package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.js.map +1 -1
  231. package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.d.ts +1 -1
  232. package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.d.ts.map +1 -1
  233. package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.js +26 -10
  234. package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.js.map +1 -1
  235. package/dist/backend/backend/src/services/workflow/cron-task.service.d.ts.map +1 -1
  236. package/dist/backend/backend/src/services/workflow/cron-task.service.js +68 -5
  237. package/dist/backend/backend/src/services/workflow/cron-task.service.js.map +1 -1
  238. package/dist/backend/backend/src/services/workflow/team-identifier-resolver.d.ts +44 -0
  239. package/dist/backend/backend/src/services/workflow/team-identifier-resolver.d.ts.map +1 -0
  240. package/dist/backend/backend/src/services/workflow/team-identifier-resolver.js +57 -0
  241. package/dist/backend/backend/src/services/workflow/team-identifier-resolver.js.map +1 -0
  242. package/dist/backend/backend/src/types/credential.types.d.ts +17 -1
  243. package/dist/backend/backend/src/types/credential.types.d.ts.map +1 -1
  244. package/dist/backend/backend/src/types/credential.types.js +15 -5
  245. package/dist/backend/backend/src/types/credential.types.js.map +1 -1
  246. package/dist/backend/backend/src/types/cron-task.types.d.ts +17 -0
  247. package/dist/backend/backend/src/types/cron-task.types.d.ts.map +1 -1
  248. package/dist/backend/backend/src/types/event-bus.types.d.ts +1 -1
  249. package/dist/backend/backend/src/types/event-bus.types.d.ts.map +1 -1
  250. package/dist/backend/backend/src/types/event-bus.types.js +12 -0
  251. package/dist/backend/backend/src/types/event-bus.types.js.map +1 -1
  252. package/dist/backend/backend/src/types/intent-task.types.d.ts +10 -13
  253. package/dist/backend/backend/src/types/intent-task.types.d.ts.map +1 -1
  254. package/dist/backend/backend/src/types/intent-task.types.js +4 -1
  255. package/dist/backend/backend/src/types/intent-task.types.js.map +1 -1
  256. package/dist/backend/backend/src/types/memory.types.d.ts +94 -1
  257. package/dist/backend/backend/src/types/memory.types.d.ts.map +1 -1
  258. package/dist/backend/backend/src/types/memory.types.js.map +1 -1
  259. package/dist/backend/backend/src/types/v2/work-item.types.d.ts +23 -0
  260. package/dist/backend/backend/src/types/v2/work-item.types.d.ts.map +1 -1
  261. package/dist/backend/backend/src/types/v2/work-item.types.js.map +1 -1
  262. package/dist/backend/backend/src/utils/team.utils.d.ts +3 -1
  263. package/dist/backend/backend/src/utils/team.utils.d.ts.map +1 -1
  264. package/dist/backend/backend/src/utils/team.utils.js +26 -5
  265. package/dist/backend/backend/src/utils/team.utils.js.map +1 -1
  266. package/dist/backend/backend/src/websocket/chat-v2.gateway.d.ts +23 -0
  267. package/dist/backend/backend/src/websocket/chat-v2.gateway.d.ts.map +1 -1
  268. package/dist/backend/backend/src/websocket/chat-v2.gateway.js +56 -7
  269. package/dist/backend/backend/src/websocket/chat-v2.gateway.js.map +1 -1
  270. package/dist/backend/backend/src/websocket/chat.gateway.d.ts +19 -4
  271. package/dist/backend/backend/src/websocket/chat.gateway.d.ts.map +1 -1
  272. package/dist/backend/backend/src/websocket/chat.gateway.js +78 -63
  273. package/dist/backend/backend/src/websocket/chat.gateway.js.map +1 -1
  274. package/dist/backend/backend/src/websocket/terminal.gateway.d.ts.map +1 -1
  275. package/dist/backend/backend/src/websocket/terminal.gateway.js +10 -2
  276. package/dist/backend/backend/src/websocket/terminal.gateway.js.map +1 -1
  277. package/dist/backend/config/constants.d.ts +2 -0
  278. package/dist/backend/config/constants.d.ts.map +1 -1
  279. package/dist/backend/config/constants.js +2 -0
  280. package/dist/backend/config/constants.js.map +1 -1
  281. package/dist/backend/config/index.d.ts +1 -0
  282. package/dist/backend/config/index.d.ts.map +1 -1
  283. package/dist/cli/backend/src/constants.d.ts +13 -0
  284. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  285. package/dist/cli/backend/src/constants.js +12 -0
  286. package/dist/cli/backend/src/constants.js.map +1 -1
  287. package/dist/cli/backend/src/services/event-bus/event-bus.service.d.ts.map +1 -1
  288. package/dist/cli/backend/src/services/event-bus/event-bus.service.js +22 -11
  289. package/dist/cli/backend/src/services/event-bus/event-bus.service.js.map +1 -1
  290. package/dist/cli/backend/src/services/mcp-server.d.ts.map +1 -1
  291. package/dist/cli/backend/src/services/mcp-server.js +5 -0
  292. package/dist/cli/backend/src/services/mcp-server.js.map +1 -1
  293. package/dist/cli/backend/src/services/mcp-tool-definitions.d.ts +4 -0
  294. package/dist/cli/backend/src/services/mcp-tool-definitions.d.ts.map +1 -1
  295. package/dist/cli/backend/src/services/mcp-tool-definitions.js +12 -1
  296. package/dist/cli/backend/src/services/mcp-tool-definitions.js.map +1 -1
  297. package/dist/cli/backend/src/services/memory/memory.service.d.ts +22 -1
  298. package/dist/cli/backend/src/services/memory/memory.service.d.ts.map +1 -1
  299. package/dist/cli/backend/src/services/memory/memory.service.js +81 -3
  300. package/dist/cli/backend/src/services/memory/memory.service.js.map +1 -1
  301. package/dist/cli/backend/src/services/memory/project-memory.service.d.ts +25 -1
  302. package/dist/cli/backend/src/services/memory/project-memory.service.d.ts.map +1 -1
  303. package/dist/cli/backend/src/services/memory/project-memory.service.js +43 -0
  304. package/dist/cli/backend/src/services/memory/project-memory.service.js.map +1 -1
  305. package/dist/cli/backend/src/services/skill/skill-executor.service.d.ts.map +1 -1
  306. package/dist/cli/backend/src/services/skill/skill-executor.service.js +13 -1
  307. package/dist/cli/backend/src/services/skill/skill-executor.service.js.map +1 -1
  308. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +33 -2
  309. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  310. package/dist/cli/backend/src/services/task-pool/task-pool.service.js +160 -8
  311. package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  312. package/dist/cli/backend/src/types/credential.types.d.ts +17 -1
  313. package/dist/cli/backend/src/types/credential.types.d.ts.map +1 -1
  314. package/dist/cli/backend/src/types/credential.types.js +15 -5
  315. package/dist/cli/backend/src/types/credential.types.js.map +1 -1
  316. package/dist/cli/backend/src/types/event-bus.types.d.ts +1 -1
  317. package/dist/cli/backend/src/types/event-bus.types.d.ts.map +1 -1
  318. package/dist/cli/backend/src/types/event-bus.types.js +12 -0
  319. package/dist/cli/backend/src/types/event-bus.types.js.map +1 -1
  320. package/dist/cli/backend/src/types/memory.types.d.ts +94 -1
  321. package/dist/cli/backend/src/types/memory.types.d.ts.map +1 -1
  322. package/dist/cli/backend/src/types/memory.types.js.map +1 -1
  323. package/dist/cli/backend/src/types/v2/work-item.types.d.ts +23 -0
  324. package/dist/cli/backend/src/types/v2/work-item.types.d.ts.map +1 -1
  325. package/dist/cli/backend/src/types/v2/work-item.types.js.map +1 -1
  326. package/dist/cli/cli/src/commands/start.js +73 -12
  327. package/dist/cli/cli/src/commands/start.js.map +1 -1
  328. package/dist/cli/config/constants.d.ts +2 -0
  329. package/dist/cli/config/constants.d.ts.map +1 -1
  330. package/dist/cli/config/constants.js +2 -0
  331. package/dist/cli/config/constants.js.map +1 -1
  332. package/dist/cli/config/index.d.ts +1 -0
  333. package/dist/cli/config/index.d.ts.map +1 -1
  334. package/frontend/dist/assets/index-b279da34.js +4926 -0
  335. package/frontend/dist/assets/{index-b7e59b2b.css → index-c07e04c0.css} +2 -2
  336. package/frontend/dist/index.html +2 -2
  337. package/package.json +1 -1
  338. package/frontend/dist/assets/index-698305f3.js +0 -5228
@@ -1,25 +1,64 @@
1
1
  /**
2
- * Chat Service
2
+ * Chat Service — facade over ChatV2Service.
3
3
  *
4
- * Manages chat conversations and messages for the conversational dashboard.
5
- * Handles message persistence, conversation management, and real-time updates
6
- * via event emission.
4
+ * Phase 6 of the unified-chat-message-store spec
5
+ * (`specs/2026-05-14-unified-chat-message-store.md`):
6
+ * the legacy JSON-file storage at `~/.crewly/chat/` is retired.
7
+ * All chat persistence and read paths now flow through the canonical
8
+ * `ChatV2Service` (SQLite at `~/.crewly/chat.db`). This file remains
9
+ * temporarily as a deprecation shim so the many legacy callers
10
+ * (`chat.controller.ts`, `chat.gateway.ts`, `slack-orchestrator-bridge.ts`,
11
+ * `notify-reconciliation.service.ts`, `message-replay.service.ts`,
12
+ * `index.ts`) continue to compile and run while they are migrated to
13
+ * call ChatV2Service directly. Each facade method translates between
14
+ * the legacy `ChatMessage` / `ChatConversation` DTOs and chat-v2's
15
+ * `ChatMessageDTO` / `ChatChannelDTO` so existing event subscribers
16
+ * (chat.gateway.ts) see the same shapes they always have.
7
17
  *
8
18
  * @module services/chat/chat.service
9
19
  */
10
20
  import { EventEmitter } from 'events';
11
- import { promises as fs } from 'fs';
12
- import * as os from 'os';
13
- import * as path from 'path';
14
- import { atomicWriteJson, safeReadJson } from '../../utils/file-io.utils.js';
15
21
  import { LoggerService } from '../core/logger.service.js';
16
- import { createChatMessage, createConversation, createLastMessagePreview, formatMessageContent, extractResponseFromOutput, detectContentType, validateSendMessageInput, inferChannelTypeFromConversationId, CHAT_CONSTANTS, } from '../../types/chat.types.js';
17
- // =============================================================================
18
- // Error Classes
19
- // =============================================================================
20
22
  /**
21
- * Error thrown when a conversation is not found
23
+ * Default page size for legacy `getMessages` calls without an explicit
24
+ * `filter.limit`. Matches the historical hardcoded value so the change
25
+ * to honor `filter.limit` is a strict superset of the prior behavior.
26
+ */
27
+ const LEGACY_DEFAULT_PAGE_SIZE = 200;
28
+ /**
29
+ * Hard cap on `getMessages` limit, applied after the caller-supplied
30
+ * value. Keeps a buggy/malicious caller from asking chat-v2 to load
31
+ * the entire channel history into a single response.
32
+ */
33
+ const LEGACY_MAX_PAGE_SIZE = 1000;
34
+ /**
35
+ * Pick the chat-v2 `metadata.source` for a legacy `addAgentMessage`
36
+ * /`addDirectMessage` call. Returns the caller-provided
37
+ * `metadata.source` when it is one of the closed chat-v2 source enum
38
+ * values, otherwise falls back to `defaultSource` (the historical
39
+ * hardcoded value for the call site).
40
+ *
41
+ * @param metadata - Legacy metadata blob (possibly undefined)
42
+ * @param defaultSource - Source to use when metadata has no valid source
43
+ * @returns A chat-v2 `RecordTurnSource` value
22
44
  */
45
+ function resolveLegacyRecordSource(metadata, defaultSource) {
46
+ const raw = metadata?.source;
47
+ if (raw === 'web' ||
48
+ raw === 'slack' ||
49
+ raw === 'pty-runtime' ||
50
+ raw === 'in-process-runtime' ||
51
+ raw === 'reply-tool' ||
52
+ raw === 'system') {
53
+ return raw;
54
+ }
55
+ return defaultSource;
56
+ }
57
+ import { getChatV2Service } from '../chat-v2/chat-v2.singleton.js';
58
+ import { SYSTEM_PRINCIPAL, senderToV2, v2MessageToLegacy, v2ChannelToLegacy, inferSourceFromLegacyMetadata, synthesizeSlackConversationId, } from '../chat-v2/legacy-dto.utils.js';
59
+ // =============================================================================
60
+ // Error classes — preserved for callers that catch them by name
61
+ // =============================================================================
23
62
  export class ConversationNotFoundError extends Error {
24
63
  conversationId;
25
64
  constructor(conversationId) {
@@ -28,9 +67,6 @@ export class ConversationNotFoundError extends Error {
28
67
  this.name = 'ConversationNotFoundError';
29
68
  }
30
69
  }
31
- /**
32
- * Error thrown when message validation fails
33
- */
34
70
  export class MessageValidationError extends Error {
35
71
  constructor(message) {
36
72
  super(message);
@@ -38,788 +74,301 @@ export class MessageValidationError extends Error {
38
74
  }
39
75
  }
40
76
  // =============================================================================
41
- // Chat Service
77
+ // ChatService facade
42
78
  // =============================================================================
43
79
  /**
44
- * Service for managing chat conversations and messages
45
- *
46
- * Handles:
47
- * - Message persistence to ~/.crewly/chat/
48
- * - Conversation management
49
- * - Message formatting (raw terminal → clean chat)
50
- * - WebSocket event emission for real-time updates
80
+ * Deprecated façade over ChatV2Service.
51
81
  *
52
- * @example
53
- * ```typescript
54
- * const chatService = getChatService();
55
- * await chatService.initialize();
82
+ * Preserves the public surface of the original `ChatService` so the
83
+ * remaining legacy callers compile unchanged. Internally every method
84
+ * delegates to `ChatV2Service`. The original ~/.crewly/chat/*.json
85
+ * storage layer has been removed.
56
86
  *
57
- * // Send a message
58
- * const result = await chatService.sendMessage({
59
- * content: 'Hello, Orchestrator!',
60
- * });
61
- *
62
- * // Get messages
63
- * const messages = await chatService.getMessages({
64
- * conversationId: result.conversation.id,
65
- * });
66
- * ```
87
+ * New code MUST NOT depend on this class; call `getChatV2Service()`
88
+ * directly. Phase 6c of the spec deletes this file once all callers
89
+ * are migrated.
67
90
  */
68
91
  export class ChatService extends EventEmitter {
69
- chatDir;
70
92
  logger;
71
- conversations = new Map();
72
- messages = new Map();
73
- initialized = false;
74
- /** Per-conversation save serialization to prevent concurrent write corruption */
75
- savePromises = new Map();
76
- /**
77
- * Create a new ChatService instance
78
- *
79
- * @param options - Configuration options
80
- */
81
- constructor(options) {
93
+ chatV2;
94
+ constructor(_options) {
82
95
  super();
83
- this.chatDir =
84
- options?.chatDir ?? path.join(os.homedir(), '.crewly', 'chat');
85
96
  this.logger = LoggerService.getInstance().createComponentLogger('ChatService');
97
+ this.chatV2 = getChatV2Service();
86
98
  }
87
- // ===========================================================================
88
- // Initialization
89
- // ===========================================================================
90
- /**
91
- * Initialize the chat service
92
- *
93
- * Creates the chat directory if it doesn't exist and loads
94
- * existing conversations from disk.
95
- */
99
+ // ---------------------------------------------------------------------------
100
+ // Lifecycle (no-ops — chat-v2 manages its own DB lifecycle)
101
+ // ---------------------------------------------------------------------------
96
102
  async initialize() {
97
- if (this.initialized)
98
- return;
99
- await fs.mkdir(this.chatDir, { recursive: true });
100
- await this.loadConversations();
101
- this.initialized = true;
103
+ // chat-v2 lazy-initializes on first access; nothing to do.
102
104
  }
103
- /**
104
- * Check if the service is initialized
105
- *
106
- * @returns True if initialized
107
- */
108
105
  isInitialized() {
109
- return this.initialized;
106
+ return true;
110
107
  }
108
+ // ---------------------------------------------------------------------------
109
+ // Writes
110
+ // ---------------------------------------------------------------------------
111
111
  /**
112
- * Ensure the service is initialized before performing operations
113
- *
114
- * @throws Error if not initialized and auto-initialize fails
115
- */
116
- async ensureInitialized() {
117
- if (!this.initialized) {
118
- await this.initialize();
119
- }
120
- }
121
- // ===========================================================================
122
- // Message Operations
123
- // ===========================================================================
124
- /**
125
- * Send a message (from user to orchestrator)
126
- *
127
- * @param input - Message input
128
- * @returns The sent message and conversation
129
- * @throws MessageValidationError if input is invalid
130
- *
131
- * @example
132
- * ```typescript
133
- * const result = await chatService.sendMessage({
134
- * content: 'Start the project analysis',
135
- * });
136
- * console.log(result.message.id);
137
- * ```
112
+ * Send a user message. Idempotency via legacy callers' own
113
+ * metadata.clientMessageId when present.
138
114
  */
139
115
  async sendMessage(input) {
140
- await this.ensureInitialized();
141
- // Validate input
142
- const validation = validateSendMessageInput(input);
143
- if (!validation.valid) {
144
- throw new MessageValidationError(validation.error || 'Invalid input');
145
- }
146
- let conversation;
147
- if (input.conversationId) {
148
- const existing = this.conversations.get(input.conversationId);
149
- if (existing) {
150
- conversation = existing;
151
- }
152
- else {
153
- conversation = await this.createNewConversation(undefined, input.conversationId);
154
- }
155
- }
156
- else {
157
- conversation = await this.createNewConversation(undefined, undefined, 'crewly_chat');
158
- }
159
- const message = createChatMessage({
160
- conversationId: conversation.id,
116
+ const conversationId = input.conversationId ?? this.synthesizeConversationId(input);
117
+ const channel = this.chatV2.ensureChannelForLegacyConversation({
118
+ conversationId,
119
+ agentSession: 'crewly-orc',
120
+ });
121
+ const senderId = (typeof input.metadata?.userId === 'string' && input.metadata.userId) || 'user';
122
+ const { message } = this.chatV2.recordTurn({
123
+ channelId: channel.id,
124
+ senderType: 'user',
125
+ senderId,
161
126
  content: input.content,
162
- from: { type: 'user', name: 'You' },
163
- contentType: 'text',
164
- status: 'sent',
165
- metadata: input.metadata,
127
+ clientMessageId: typeof input.metadata?.clientMessageId === 'string'
128
+ ? input.metadata.clientMessageId
129
+ : undefined,
130
+ // Source resolution here is intentionally STRICTER than
131
+ // `recordViaFacade` (which uses `resolveLegacyRecordSource`).
132
+ // `sendMessage` always writes `senderType: 'user'`, so the only
133
+ // legitimate sources are 'web' or 'slack'. Caller-supplied values
134
+ // like 'reply-tool' or 'pty-runtime' are agent-reply tags and
135
+ // would be nonsensical on a user-authored row — `inferSource…`
136
+ // downgrades them to 'system' on purpose. Phase 6α follow-up #5.
137
+ metadata: {
138
+ ...(input.metadata ?? {}),
139
+ source: inferSourceFromLegacyMetadata(input.metadata),
140
+ },
166
141
  });
167
- await this.saveMessage(message);
168
- await this.updateConversationWithMessage(conversation.id, message);
169
- this.emit('message', message);
170
- this.emitChatMessageEvent(message);
171
- return { message, conversation: this.conversations.get(conversation.id) };
142
+ const conversation = v2ChannelToLegacy(channel, this.chatV2.countChannelMessages(channel.id, SYSTEM_PRINCIPAL));
143
+ const legacyMessage = v2MessageToLegacy(message);
144
+ // Phase 6α follow-up #6: chat-v2 already emits 'chat_message' for
145
+ // every fresh recordTurn write (chat-v2.service.ts:936/1063). The
146
+ // chat.gateway WebSocket subscriber listens on the chat-v2
147
+ // EventEmitter directly, so a second emit here would double-broadcast
148
+ // to every connected client. The 'conversation_updated' event has no
149
+ // chat-v2 equivalent yet, so we keep emitting that one until chat-v2
150
+ // grows a channel-touched event.
151
+ this.emit('conversation_updated', {
152
+ type: 'conversation_updated',
153
+ data: conversation,
154
+ });
155
+ return { conversation, message: legacyMessage };
172
156
  }
173
157
  /**
174
- * Add a message from an agent or orchestrator
175
- *
176
- * Typically called when processing terminal output. Extracts and
177
- * formats the response content.
178
- *
179
- * @param conversationId - Conversation to add the message to
180
- * @param rawOutput - Raw terminal output
181
- * @param sender - Sender information
182
- * @param metadata - Optional metadata
183
- * @returns The created message
184
- *
185
- * @example
186
- * ```typescript
187
- * const message = await chatService.addAgentMessage(
188
- * 'conv-123',
189
- * '[RESPONSE]Task completed successfully[/RESPONSE]',
190
- * { type: 'orchestrator', name: 'Orchestrator' }
191
- * );
192
- * ```
158
+ * Add an agent reply extracted from raw terminal output. The legacy
159
+ * regex extraction (`[RESPONSE]` / `[CHAT_RESPONSE]` markers) is gone
160
+ * callers should pass already-clean content. Kept for source
161
+ * compatibility with `chat.gateway.processTerminalOutput`, which has
162
+ * no production callers post Phase 4 discovery.
193
163
  */
194
164
  async addAgentMessage(conversationId, rawOutput, sender, metadata) {
195
- await this.ensureInitialized();
196
- // Extract and format the response
197
- const extractedContent = extractResponseFromOutput(rawOutput);
198
- const formattedContent = formatMessageContent(extractedContent);
199
- // Determine content type
200
- const contentType = detectContentType(formattedContent);
201
- const message = createChatMessage({
202
- conversationId,
203
- content: formattedContent,
204
- from: sender,
205
- contentType,
206
- status: 'delivered',
207
- metadata: {
208
- ...metadata,
209
- rawOutput,
210
- },
211
- });
212
- await this.saveMessage(message);
213
- await this.updateConversationWithMessage(conversationId, message);
214
- this.emit('message', message);
215
- this.emitChatMessageEvent(message);
216
- return message;
165
+ // Phase 6α follow-up #5: source defaults to 'pty-runtime' (the
166
+ // historical caller) but the caller can override via
167
+ // `metadata.source` e.g. an in-process runtime route should tag
168
+ // its replies 'in-process-runtime', not 'pty-runtime'. Falling back
169
+ // through inferSourceFromLegacyMetadata preserves the previous
170
+ // default for callers that don't tag.
171
+ const source = resolveLegacyRecordSource(metadata, 'pty-runtime');
172
+ return this.recordViaFacade(conversationId, rawOutput, sender, metadata, source);
217
173
  }
218
174
  /**
219
- * Add a message with pre-extracted content (no regex extraction).
220
- *
221
- * Unlike `addAgentMessage`, this method skips `extractResponseFromOutput()`
222
- * and takes already-cleaned content directly. Used by the unified [NOTIFY]
223
- * marker handler where content is extracted from JSON payload.
224
- *
225
- * Emits the `'message'` event that QueueProcessor's `waitForResponse()` depends on.
226
- *
227
- * @param conversationId - Conversation to add the message to
228
- * @param content - Pre-extracted markdown content
229
- * @param sender - Sender information
230
- * @param metadata - Optional metadata
231
- * @returns The created message
232
- *
233
- * @example
234
- * ```typescript
235
- * const message = await chatService.addDirectMessage(
236
- * 'conv-123',
237
- * '## Status Update\n\nEmily is working...',
238
- * { type: 'orchestrator', name: 'Orchestrator' }
239
- * );
240
- * ```
175
+ * Persist a pre-extracted markdown reply. Primary call site is the
176
+ * PTY `[NOTIFY]` path in `chat.gateway.processNotifyMessage`.
241
177
  */
242
178
  async addDirectMessage(conversationId, content, sender, metadata) {
243
- await this.ensureInitialized();
244
- const formattedContent = formatMessageContent(content);
245
- const contentType = detectContentType(formattedContent);
246
- const message = createChatMessage({
247
- conversationId,
248
- content: formattedContent,
249
- from: sender,
250
- contentType,
251
- status: 'delivered',
252
- metadata,
253
- });
254
- await this.saveMessage(message);
255
- await this.updateConversationWithMessage(conversationId, message);
256
- this.emit('message', message);
257
- this.emitChatMessageEvent(message);
258
- return message;
179
+ // Phase 6α follow-up #5: see addAgentMessage. Same default + override.
180
+ const source = resolveLegacyRecordSource(metadata, 'pty-runtime');
181
+ return this.recordViaFacade(conversationId, content, sender, metadata, source);
259
182
  }
260
183
  /**
261
- * Add a system message to a conversation
262
- *
263
- * @param conversationId - Conversation to add the message to
264
- * @param content - Message content
265
- * @param metadata - Optional metadata
266
- * @returns The created message
184
+ * Add a server-side system note (progress markers, errors, etc.).
267
185
  */
268
186
  async addSystemMessage(conversationId, content, metadata) {
269
- await this.ensureInitialized();
270
- const message = createChatMessage({
187
+ return this.recordViaFacade(conversationId, content, { type: 'system', id: 'system', name: 'System' }, metadata, 'system');
188
+ }
189
+ async recordViaFacade(conversationId, content, sender, metadata, source) {
190
+ const channel = this.chatV2.ensureChannelForLegacyConversation({
271
191
  conversationId,
192
+ agentSession: 'crewly-orc',
193
+ });
194
+ const { type: senderType, id: senderId } = senderToV2(sender);
195
+ const { message } = this.chatV2.recordTurn({
196
+ channelId: channel.id,
197
+ senderType,
198
+ senderId,
272
199
  content,
273
- from: { type: 'system', name: 'System' },
274
- contentType: 'system',
275
- status: 'delivered',
276
- metadata,
200
+ clientMessageId: typeof metadata?.clientMessageId === 'string' ? metadata.clientMessageId : undefined,
201
+ // `source` AFTER the spread: the resolved source from
202
+ // resolveLegacyRecordSource already incorporated metadata.source
203
+ // (if valid) or fell back to the default. Putting it last
204
+ // guarantees a value from the closed enum lands in the row
205
+ // regardless of what the legacy caller passed.
206
+ metadata: { ...(metadata ?? {}), source },
277
207
  });
278
- await this.saveMessage(message);
279
- // Persist to disk saveMessage only updates in-memory state;
280
- // callers that also call updateConversationWithMessage get persistence
281
- // from there, but system messages skip that path.
282
- const conversation = this.conversations.get(conversationId);
283
- if (conversation) {
284
- await this.saveConversation(conversation);
285
- }
286
- this.emitChatMessageEvent(message);
287
- return message;
288
- }
289
- /**
290
- * Get messages for a conversation
291
- *
292
- * @param filter - Message filter options
293
- * @returns Array of messages matching the filter
294
- *
295
- * @example
296
- * ```typescript
297
- * const messages = await chatService.getMessages({
298
- * conversationId: 'conv-123',
299
- * limit: 50,
300
- * senderType: 'user',
301
- * });
302
- * ```
303
- */
208
+ const legacyMessage = v2MessageToLegacy(message);
209
+ // Phase follow-up #6: chat-v2 already emits 'chat_message';
210
+ // chat.gateway subscribes to chat-v2 directly. No re-emit here to
211
+ // avoid double-broadcasting to WebSocket clients.
212
+ return legacyMessage;
213
+ }
214
+ // ---------------------------------------------------------------------------
215
+ // Reads
216
+ // ---------------------------------------------------------------------------
304
217
  async getMessages(filter) {
305
- await this.ensureInitialized();
306
218
  if (!filter.conversationId) {
219
+ // Legacy callers occasionally call with no conversationId to get
220
+ // everything. chat-v2 has no global query — return [] and let
221
+ // the migration of those call sites surface explicit filters.
307
222
  return [];
308
223
  }
309
- let messages = this.messages.get(filter.conversationId) ?? [];
310
- // Apply filters
311
- if (filter.senderType) {
312
- messages = messages.filter((m) => m.from.type === filter.senderType);
313
- }
314
- if (filter.contentType) {
315
- messages = messages.filter((m) => m.contentType === filter.contentType);
316
- }
317
- if (filter.after) {
318
- messages = messages.filter((m) => m.timestamp > filter.after);
319
- }
320
- if (filter.before) {
321
- messages = messages.filter((m) => m.timestamp < filter.before);
322
- }
323
- // Sort by timestamp (oldest first)
324
- messages.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
325
- const limit = filter.limit ?? CHAT_CONSTANTS.DEFAULTS.MESSAGE_LIMIT;
326
- const totalCount = messages.length;
327
- // When no offset is explicitly provided, return the most recent messages
328
- // (tail of the sorted array). This is the default for chat UIs so users
329
- // see the latest conversation on initial load.
330
- if (filter.offset === undefined || filter.offset === null) {
331
- const start = Math.max(0, totalCount - limit);
332
- return messages.slice(start);
333
- }
334
- // When offset IS provided, use traditional pagination for loading older messages
335
- return messages.slice(filter.offset, filter.offset + limit);
224
+ // Phase follow-up #4: honor filter.limit. Defaults to the
225
+ // previous hardcoded 200, capped at 1000 so a malicious or buggy
226
+ // caller can't ask for an unbounded slice. Sub-1 values fall back
227
+ // to the default.
228
+ const requested = typeof filter.limit === 'number' && Number.isFinite(filter.limit) && filter.limit > 0
229
+ ? Math.floor(filter.limit)
230
+ : LEGACY_DEFAULT_PAGE_SIZE;
231
+ const limit = Math.min(requested, LEGACY_MAX_PAGE_SIZE);
232
+ const page = this.chatV2.listMessages({
233
+ channelId: filter.conversationId,
234
+ principal: SYSTEM_PRINCIPAL,
235
+ limit,
236
+ direction: 'forward',
237
+ });
238
+ return page.messages.map(v2MessageToLegacy);
336
239
  }
337
- /**
338
- * Get the total count of messages in a conversation, applying the same
339
- * filters as getMessages (senderType, contentType, after, before).
340
- *
341
- * @param filter - Message filter options
342
- * @returns Total number of messages matching the filter
343
- */
344
240
  async getMessageCount(filter) {
345
- await this.ensureInitialized();
346
241
  if (!filter.conversationId)
347
242
  return 0;
348
- let messages = this.messages.get(filter.conversationId) ?? [];
349
- if (filter.senderType) {
350
- messages = messages.filter((m) => m.from.type === filter.senderType);
351
- }
352
- if (filter.contentType) {
353
- messages = messages.filter((m) => m.contentType === filter.contentType);
354
- }
355
- if (filter.after) {
356
- messages = messages.filter((m) => m.timestamp > filter.after);
357
- }
358
- if (filter.before) {
359
- messages = messages.filter((m) => m.timestamp < filter.before);
360
- }
361
- return messages.length;
243
+ return this.chatV2.countChannelMessages(filter.conversationId, SYSTEM_PRINCIPAL);
362
244
  }
363
- /**
364
- * Get a single message by ID
365
- *
366
- * @param conversationId - Conversation ID
367
- * @param messageId - Message ID
368
- * @returns The message or null if not found
369
- */
370
- async getMessage(conversationId, messageId) {
371
- await this.ensureInitialized();
372
- const messages = this.messages.get(conversationId);
373
- if (!messages)
374
- return null;
375
- return messages.find((m) => m.id === messageId) ?? null;
245
+ async getMessage(_conversationId, _messageId) {
246
+ // chat-v2 has no per-message lookup yet; return null. Callers that
247
+ // depend on this should be migrated to read the row from the
248
+ // `chat_messages` table directly.
249
+ return null;
376
250
  }
377
- /**
378
- * Update metadata on an existing message (partial merge).
379
- *
380
- * Merges the provided metadata fields into the message's existing metadata
381
- * and persists the change to disk. Used by the NOTIFY reconciliation system
382
- * to track Slack delivery status on chat messages.
383
- *
384
- * @param conversationId - Conversation the message belongs to
385
- * @param messageId - ID of the message to update
386
- * @param metadataPatch - Partial metadata to merge into existing metadata
387
- * @returns The updated message, or null if message not found
388
- */
389
- async updateMessageMetadata(conversationId, messageId, metadataPatch) {
390
- await this.ensureInitialized();
391
- const messages = this.messages.get(conversationId);
392
- if (!messages)
393
- return null;
394
- const message = messages.find((m) => m.id === messageId);
395
- if (!message)
396
- return null;
397
- message.metadata = { ...message.metadata, ...metadataPatch };
398
- const conversation = this.conversations.get(conversationId);
399
- if (conversation) {
400
- await this.saveConversation(conversation);
401
- }
402
- return message;
251
+ async updateMessageMetadata(_conversationId, messageId, metadataPatch) {
252
+ const dto = this.chatV2.updateMessageMetadata(messageId, metadataPatch);
253
+ return dto ? v2MessageToLegacy(dto) : null;
403
254
  }
404
- /**
405
- * Find all messages with pending Slack delivery within a time window.
406
- *
407
- * Scans all conversations for messages where `slackDeliveryStatus === 'pending'`
408
- * and `slackChannelId` is present, filtering out messages older than `maxAgeMs`.
409
- * Used by NotifyReconciliationService to find messages that need retry.
410
- *
411
- * @param maxAgeMs - Maximum message age in milliseconds
412
- * @returns Array of messages with pending Slack delivery
413
- */
414
255
  async getMessagesWithPendingSlackDelivery(maxAgeMs) {
415
- await this.ensureInitialized();
416
- const cutoff = new Date(Date.now() - maxAgeMs).toISOString();
417
- const pending = [];
418
- for (const messages of this.messages.values()) {
419
- for (const msg of messages) {
420
- if (msg.metadata?.slackDeliveryStatus === 'pending' &&
421
- msg.metadata?.slackChannelId &&
422
- msg.timestamp >= cutoff) {
423
- pending.push(msg);
424
- }
425
- }
426
- }
427
- return pending;
256
+ return this.chatV2.findMessagesWithPendingSlackDelivery(maxAgeMs).map(v2MessageToLegacy);
428
257
  }
429
- // ===========================================================================
430
- // Conversation Operations
431
- // ===========================================================================
432
- /**
433
- * Get all conversations
434
- *
435
- * @param filter - Optional filter options
436
- * @returns Array of conversations
437
- *
438
- * @example
439
- * ```typescript
440
- * const conversations = await chatService.getConversations({
441
- * includeArchived: false,
442
- * search: 'project',
443
- * });
444
- * ```
445
- */
446
- async getConversations(filter) {
447
- await this.ensureInitialized();
448
- let conversations = Array.from(this.conversations.values());
449
- // Filter archived
450
- if (!filter?.includeArchived) {
451
- conversations = conversations.filter((c) => !c.isArchived);
452
- }
453
- // Filter by channel type
454
- if (filter?.channelType) {
455
- conversations = conversations.filter((c) => c.channelType === filter.channelType);
456
- }
457
- // Search
458
- if (filter?.search) {
459
- const searchLower = filter.search.toLowerCase();
460
- conversations = conversations.filter((c) => c.title?.toLowerCase().includes(searchLower) ||
461
- c.lastMessage?.content.toLowerCase().includes(searchLower));
462
- }
463
- // Sort by last update (most recent first)
464
- conversations.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
465
- // Apply pagination
466
- const offset = filter?.offset ?? 0;
467
- const limit = filter?.limit ?? CHAT_CONSTANTS.DEFAULTS.CONVERSATION_LIMIT;
468
- return conversations.slice(offset, offset + limit);
258
+ async getConversations(_filter) {
259
+ const channels = this.chatV2.listChannels({ principal: SYSTEM_PRINCIPAL });
260
+ return channels.map((c) => v2ChannelToLegacy(c, this.chatV2.countChannelMessages(c.id, SYSTEM_PRINCIPAL)));
469
261
  }
470
- /**
471
- * Get a single conversation by ID
472
- *
473
- * @param id - Conversation ID
474
- * @returns The conversation or null if not found
475
- */
476
262
  async getConversation(id) {
477
- await this.ensureInitialized();
478
- return this.conversations.get(id) ?? null;
263
+ try {
264
+ const channel = this.chatV2.getChannel(id, SYSTEM_PRINCIPAL);
265
+ return v2ChannelToLegacy(channel, this.chatV2.countChannelMessages(id, SYSTEM_PRINCIPAL));
266
+ }
267
+ catch {
268
+ return null;
269
+ }
479
270
  }
480
- /**
481
- * Create a new conversation
482
- *
483
- * @param title - Optional title for the conversation
484
- * @returns The created conversation
485
- *
486
- * @example
487
- * ```typescript
488
- * const conversation = await chatService.createNewConversation('Project Discussion');
489
- * ```
490
- */
491
- async createNewConversation(title, idOverride, channelType) {
492
- await this.ensureInitialized();
493
- const conversation = createConversation(title, idOverride, channelType);
494
- this.conversations.set(conversation.id, conversation);
495
- this.messages.set(conversation.id, []);
496
- await this.saveConversation(conversation);
497
- this.emitConversationUpdatedEvent(conversation);
498
- return conversation;
271
+ async createNewConversation(title, idOverride, _channelType) {
272
+ const conversationId = idOverride ?? `web-conv-${Date.now()}`;
273
+ const channel = this.chatV2.ensureChannelForLegacyConversation({
274
+ conversationId,
275
+ agentSession: 'crewly-orc',
276
+ name: title ?? conversationId,
277
+ });
278
+ return v2ChannelToLegacy(channel, 0);
499
279
  }
500
- /**
501
- * Update a conversation's title
502
- *
503
- * @param id - Conversation ID
504
- * @param title - New title
505
- * @returns The updated conversation
506
- * @throws ConversationNotFoundError if conversation doesn't exist
507
- */
508
280
  async updateConversationTitle(id, title) {
509
- await this.ensureInitialized();
510
- const conversation = this.conversations.get(id);
511
- if (!conversation) {
512
- throw new ConversationNotFoundError(id);
513
- }
514
- conversation.title = title;
515
- conversation.updatedAt = new Date().toISOString();
516
- await this.saveConversation(conversation);
517
- this.emitConversationUpdatedEvent(conversation);
518
- return conversation;
281
+ const channel = this.chatV2.renameChannel(id, title, SYSTEM_PRINCIPAL);
282
+ return v2ChannelToLegacy(channel, this.chatV2.countChannelMessages(id, SYSTEM_PRINCIPAL));
519
283
  }
520
- /**
521
- * Archive a conversation
522
- *
523
- * @param id - Conversation ID
524
- * @throws ConversationNotFoundError if conversation doesn't exist
525
- */
526
284
  async archiveConversation(id) {
527
- await this.ensureInitialized();
528
- const conversation = this.conversations.get(id);
529
- if (!conversation) {
530
- throw new ConversationNotFoundError(id);
531
- }
532
- conversation.isArchived = true;
533
- conversation.updatedAt = new Date().toISOString();
534
- await this.saveConversation(conversation);
535
- this.emitConversationUpdatedEvent(conversation);
285
+ this.chatV2.archiveChannel(id, SYSTEM_PRINCIPAL);
536
286
  }
537
- /**
538
- * Unarchive a conversation
539
- *
540
- * @param id - Conversation ID
541
- * @throws ConversationNotFoundError if conversation doesn't exist
542
- */
543
287
  async unarchiveConversation(id) {
544
- await this.ensureInitialized();
545
- const conversation = this.conversations.get(id);
546
- if (!conversation) {
547
- throw new ConversationNotFoundError(id);
548
- }
549
- conversation.isArchived = false;
550
- conversation.updatedAt = new Date().toISOString();
551
- await this.saveConversation(conversation);
552
- this.emitConversationUpdatedEvent(conversation);
288
+ this.chatV2.unarchiveChannel(id, SYSTEM_PRINCIPAL);
553
289
  }
554
- /**
555
- * Delete a conversation and all its messages
556
- *
557
- * @param id - Conversation ID
558
- */
559
290
  async deleteConversation(id) {
560
- await this.ensureInitialized();
561
- this.conversations.delete(id);
562
- this.messages.delete(id);
563
- // Wait for any pending save to finish before deleting the file
564
- const pending = this.savePromises.get(id);
565
- if (pending) {
566
- await pending.catch(() => { });
567
- this.savePromises.delete(id);
568
- }
569
- const conversationFile = path.join(this.chatDir, `${id}.json`);
570
- await fs.rm(conversationFile, { force: true });
291
+ this.chatV2.deleteChannel(id, SYSTEM_PRINCIPAL);
571
292
  }
572
- /**
573
- * Clear all messages from a conversation
574
- *
575
- * @param id - Conversation ID
576
- */
577
293
  async clearConversation(id) {
578
- await this.ensureInitialized();
579
- this.messages.set(id, []);
580
- const conversation = this.conversations.get(id);
581
- if (conversation) {
582
- conversation.messageCount = 0;
583
- conversation.lastMessage = undefined;
584
- conversation.updatedAt = new Date().toISOString();
585
- await this.saveConversation(conversation);
586
- this.emitConversationUpdatedEvent(conversation);
587
- }
294
+ this.chatV2.clearChannel(id, SYSTEM_PRINCIPAL);
588
295
  }
589
- /**
590
- * Get the current/active conversation (most recently updated non-archived)
591
- *
592
- * @returns The most recent active conversation or null
593
- */
594
296
  async getCurrentConversation() {
595
- const conversations = await this.getConversations({ limit: 1 });
596
- return conversations[0] ?? null;
297
+ // Frontend should adopt "latest channel" via listChannels;
298
+ // here we approximate by returning the most recently touched channel.
299
+ const channels = this.chatV2.listChannels({ principal: SYSTEM_PRINCIPAL });
300
+ if (channels.length === 0)
301
+ return null;
302
+ const newest = channels.reduce((a, b) => (a.lastMessageAt ?? a.createdAt) > (b.lastMessageAt ?? b.createdAt) ? a : b);
303
+ return v2ChannelToLegacy(newest, this.chatV2.countChannelMessages(newest.id, SYSTEM_PRINCIPAL));
597
304
  }
598
- // ===========================================================================
599
- // Real-time Events
600
- // ===========================================================================
601
- /**
602
- * Emit typing indicator event
603
- *
604
- * @param conversationId - Conversation ID
605
- * @param sender - Sender information
606
- * @param isTyping - Whether the sender is typing
607
- */
608
305
  emitTypingIndicator(conversationId, sender, isTyping) {
609
- const event = {
306
+ this.emit('chat_typing', {
610
307
  type: 'chat_typing',
611
308
  data: { conversationId, sender, isTyping },
612
- };
613
- this.emit('chat_typing', event);
614
- }
615
- /**
616
- * Emit a transient progress update to the frontend for a conversation.
617
- *
618
- * Unlike addSystemMessage, this does NOT persist the message to disk.
619
- * It creates a temporary system chat message and emits it via the
620
- * 'chat_message' WebSocket event so the UI can display a progress
621
- * indicator during long-running orchestrator operations.
622
- *
623
- * @param conversationId - Conversation to emit progress for
624
- * @param text - Progress text to display (e.g. "Processing... (still working)")
625
- */
626
- emitProgress(conversationId, text) {
627
- const message = createChatMessage({
628
- conversationId,
629
- content: text,
630
- from: { type: 'system', name: 'System' },
631
- contentType: 'status',
632
- status: 'delivered',
633
309
  });
634
- this.emitChatMessageEvent(message);
635
310
  }
636
- /**
637
- * Emit a chat message event
638
- *
639
- * @param message - The message to emit
640
- */
641
- emitChatMessageEvent(message) {
642
- // Sanitize message content before broadcasting via WebSocket
643
- // to prevent JWT/API key leaks in real-time messages
644
- const sanitizedMessage = {
645
- ...message,
646
- content: this.sanitizeContent(message.content),
647
- metadata: message.metadata ? {
648
- ...message.metadata,
649
- rawOutput: message.metadata.rawOutput
650
- ? this.sanitizeContent(message.metadata.rawOutput)
651
- : undefined,
652
- } : message.metadata,
653
- };
654
- const event = {
311
+ emitProgress(conversationId, text) {
312
+ // Transient emit but don't persist. Frontend renders progress
313
+ // ephemerally. Legacy behaviour preserved.
314
+ this.emit('chat_message', {
655
315
  type: 'chat_message',
656
- data: sanitizedMessage,
657
- };
658
- this.emit('chat_message', event);
659
- }
660
- /**
661
- * Strip sensitive tokens from a text string before broadcasting.
662
- * Uses the same JWT + API key patterns as chat-sanitizer.service.ts.
663
- *
664
- * @param text - Raw text that may contain tokens
665
- * @returns Text with sensitive data replaced by '***'
666
- */
667
- sanitizeContent(text) {
668
- return text
669
- .replace(/eyJ[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}/g, '***')
670
- .replace(/\bsk-[A-Za-z0-9_-]{20,}/g, '***')
671
- .replace(/\bAKIA[A-Z0-9]{16}\b/g, '***')
672
- .replace(/\bgh[pousr]_[A-Za-z0-9_]{30,}/g, '***');
673
- }
674
- /**
675
- * Emit a conversation updated event
676
- *
677
- * @param conversation - The updated conversation
678
- */
679
- emitConversationUpdatedEvent(conversation) {
680
- const event = {
681
- type: 'conversation_updated',
682
- data: conversation,
683
- };
684
- this.emit('conversation_updated', event);
316
+ data: {
317
+ id: `progress-${Date.now()}`,
318
+ conversationId,
319
+ from: { type: 'system', id: 'system', name: 'System' },
320
+ content: text,
321
+ contentType: 'system',
322
+ status: 'sent',
323
+ timestamp: new Date().toISOString(),
324
+ metadata: { ephemeral: true },
325
+ },
326
+ });
685
327
  }
686
- // ===========================================================================
687
- // Statistics
688
- // ===========================================================================
689
- /**
690
- * Get statistics about chat usage
691
- *
692
- * @returns Statistics object
693
- */
694
328
  async getStatistics() {
695
- await this.ensureInitialized();
696
- const conversations = Array.from(this.conversations.values());
697
- const activeConversations = conversations.filter((c) => !c.isArchived);
698
- const archivedConversations = conversations.filter((c) => c.isArchived);
699
- let totalMessages = 0;
700
- for (const messages of this.messages.values()) {
701
- totalMessages += messages.length;
702
- }
329
+ const s = this.chatV2.getStatistics();
703
330
  return {
704
- totalConversations: conversations.length,
705
- activeConversations: activeConversations.length,
706
- archivedConversations: archivedConversations.length,
707
- totalMessages,
331
+ totalConversations: s.totalChannels,
332
+ activeConversations: s.activeChannels,
333
+ archivedConversations: s.archivedChannels,
334
+ totalMessages: s.totalMessages,
708
335
  };
709
336
  }
710
- // ===========================================================================
711
- // Persistence
712
- // ===========================================================================
337
+ // ---------------------------------------------------------------------------
338
+ // Helpers
339
+ // ---------------------------------------------------------------------------
713
340
  /**
714
- * Load all conversations from disk
341
+ * Generate a conversationId for a brand-new sendMessage call that
342
+ * didn't supply one. Web-chat-style id; Slack callers always pass
343
+ * their own slack-CHANNEL-TS identifier.
715
344
  */
716
- async loadConversations() {
717
- try {
718
- const files = await fs.readdir(this.chatDir);
719
- const jsonFiles = files.filter((f) => f.endsWith('.json'));
720
- for (const file of jsonFiles) {
721
- const filePath = path.join(this.chatDir, file);
722
- const data = await safeReadJson(filePath, null);
723
- if (data?.conversation) {
724
- // Lazy channelType inference for existing data (in-memory only, no file rewrite)
725
- if (!data.conversation.channelType) {
726
- data.conversation.channelType = inferChannelTypeFromConversationId(data.conversation.id);
727
- }
728
- this.conversations.set(data.conversation.id, data.conversation);
729
- this.messages.set(data.conversation.id, data.messages ?? []);
730
- }
731
- }
732
- }
733
- catch (error) {
734
- // Directory might not exist yet, which is fine
735
- this.logger.debug('Failed to load conversations', {
736
- error: error instanceof Error ? error.message : String(error),
737
- });
345
+ synthesizeConversationId(input) {
346
+ if (typeof input.metadata?.channelId === 'string') {
347
+ // Slack source derive from channel + timestamp pattern.
348
+ const channel = input.metadata.channelId;
349
+ const ts = typeof input.metadata.threadTs === 'string' ? input.metadata.threadTs : `${Date.now()}`;
350
+ return synthesizeSlackConversationId(channel, ts);
738
351
  }
352
+ return `web-conv-${Date.now()}`;
739
353
  }
740
354
  /**
741
- * Save a conversation and its messages to disk using atomic write (write-to-tmp + rename).
742
- * Serialized per conversation to prevent concurrent writes from corrupting the file.
743
- *
744
- * @param conversation - Conversation to save
745
- */
746
- async saveConversation(conversation) {
747
- const id = conversation.id;
748
- const prev = this.savePromises.get(id) ?? Promise.resolve();
749
- const next = prev
750
- .catch(() => { })
751
- .then(() => this.doSaveConversation(conversation));
752
- this.savePromises.set(id, next);
753
- await next;
754
- }
755
- /**
756
- * Perform the actual atomic file write for a conversation.
757
- * Writes to a temporary file first, then renames to the final path to prevent corruption.
758
- *
759
- * @param conversation - Conversation to save
760
- */
761
- async doSaveConversation(conversation) {
762
- const messages = this.messages.get(conversation.id) ?? [];
763
- const storage = { conversation, messages };
764
- const filePath = path.join(this.chatDir, `${conversation.id}.json`);
765
- await atomicWriteJson(filePath, storage);
766
- }
767
- /**
768
- * Save a message to the in-memory store.
769
- *
770
- * Only updates the in-memory messages array. Disk persistence is handled
771
- * by the caller (typically via updateConversationWithMessage which calls
772
- * saveConversation). For code paths that skip updateConversationWithMessage
773
- * (e.g. addSystemMessage), the caller is responsible for triggering
774
- * saveConversation separately.
775
- *
776
- * @param message - Message to save
777
- */
778
- async saveMessage(message) {
779
- const messages = this.messages.get(message.conversationId) ?? [];
780
- messages.push(message);
781
- this.messages.set(message.conversationId, messages);
782
- }
783
- /**
784
- * Update a conversation's metadata after adding a message
785
- *
786
- * @param conversationId - Conversation ID
787
- * @param message - The added message
355
+ * @deprecated Will be removed when the façade is retired.
788
356
  */
789
- async updateConversationWithMessage(conversationId, message) {
790
- const conversation = this.conversations.get(conversationId);
791
- if (!conversation)
792
- return;
793
- conversation.messageCount += 1;
794
- conversation.lastMessage = createLastMessagePreview(message);
795
- conversation.updatedAt = new Date().toISOString();
796
- // Add participant if new
797
- const senderId = message.from.id ?? message.from.type;
798
- if (!conversation.participantIds.includes(senderId)) {
799
- conversation.participantIds.push(senderId);
800
- }
801
- await this.saveConversation(conversation);
357
+ async ensureInitialized() {
358
+ /* no-op */
802
359
  }
803
360
  }
804
361
  // =============================================================================
805
- // Singleton Instance
362
+ // Singleton accessors — preserved for source compatibility
806
363
  // =============================================================================
807
- let chatServiceInstance = null;
808
- /**
809
- * Get the singleton ChatService instance
810
- *
811
- * @returns The ChatService instance
812
- */
813
- export function getChatService() {
814
- if (!chatServiceInstance) {
815
- chatServiceInstance = new ChatService();
364
+ let _instance = null;
365
+ export function getChatService(options) {
366
+ if (!_instance) {
367
+ _instance = new ChatService(options);
816
368
  }
817
- return chatServiceInstance;
369
+ return _instance;
818
370
  }
819
- /**
820
- * Reset the singleton instance (for testing)
821
- */
822
371
  export function resetChatService() {
823
- chatServiceInstance = null;
372
+ _instance = null;
824
373
  }
825
374
  //# sourceMappingURL=chat.service.js.map