crewly 1.12.1 → 1.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (625) hide show
  1. package/config/skills/_common/complete-body-shape.test.sh +113 -0
  2. package/config/skills/_common/no-dangling-symlinks.test.sh +63 -0
  3. package/config/skills/_common/task-pool-body-shape.test.sh +471 -0
  4. package/config/skills/agent/core/accept-task/execute.sh +30 -5
  5. package/config/skills/agent/core/block-task/execute.sh +59 -6
  6. package/config/skills/agent/core/complete-task/SKILL.md +37 -5
  7. package/config/skills/agent/core/complete-task/execute.sh +40 -9
  8. package/config/skills/agent/core/complete-task/execute.test.sh +213 -0
  9. package/config/skills/agent/core/create-task/SKILL.md +33 -0
  10. package/config/skills/agent/core/create-task/execute.sh +82 -23
  11. package/config/skills/agent/core/delegate-task/execute.sh +46 -0
  12. package/config/skills/agent/core/get-team-norms/SKILL.md +1 -1
  13. package/config/skills/agent/core/get-team-norms/execute.sh +73 -2
  14. package/config/skills/agent/core/handoff-task/execute.sh +8 -1
  15. package/config/skills/agent/core/report-status/SKILL.md +26 -0
  16. package/config/skills/agent/core/report-status/execute.sh +40 -5
  17. package/config/skills/orchestrator/delegate-task/SKILL.md +1 -1
  18. package/config/skills/orchestrator/delegate-task/execute.sh +45 -11
  19. package/config/skills/orchestrator/delegate-task/execute.test.sh +153 -1
  20. package/config/skills/team-leader/aggregate-results/SKILL.md +1 -1
  21. package/config/skills/team-leader/decompose-goal/execute.sh +11 -16
  22. package/config/skills/team-leader/delegate-task/SKILL.md +14 -1
  23. package/config/skills/team-leader/delegate-task/execute.sh +132 -43
  24. package/config/skills/team-leader/delegate-task/execute.test.sh +180 -27
  25. package/config/vitest-exclude.constants.ts +56 -0
  26. package/dist/backend/backend/src/controllers/chat/chat.controller.d.ts.map +1 -1
  27. package/dist/backend/backend/src/controllers/chat/chat.controller.js +29 -7
  28. package/dist/backend/backend/src/controllers/chat/chat.controller.js.map +1 -1
  29. package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts.map +1 -1
  30. package/dist/backend/backend/src/controllers/cloud/cloud.controller.js +1 -1
  31. package/dist/backend/backend/src/controllers/cloud/cloud.controller.js.map +1 -1
  32. package/dist/backend/backend/src/controllers/monitoring/terminal.controller.d.ts.map +1 -1
  33. package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js +18 -3
  34. package/dist/backend/backend/src/controllers/monitoring/terminal.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 +18 -15
  37. package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
  38. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts +6 -1
  39. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts.map +1 -1
  40. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js +19 -10
  41. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
  42. package/dist/backend/backend/src/index.d.ts.map +1 -1
  43. package/dist/backend/backend/src/index.js +20 -0
  44. package/dist/backend/backend/src/index.js.map +1 -1
  45. package/dist/backend/backend/src/scripts/stamp-build.d.ts +25 -0
  46. package/dist/backend/backend/src/scripts/stamp-build.d.ts.map +1 -0
  47. package/dist/backend/backend/src/scripts/stamp-build.js +48 -0
  48. package/dist/backend/backend/src/scripts/stamp-build.js.map +1 -0
  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 +33 -1
  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/crewly-agent-external-runtime.service.d.ts +78 -2
  53. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -1
  54. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +248 -44
  55. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -1
  56. package/dist/backend/backend/src/services/agent/crewly-agent/types.d.ts +51 -3
  57. package/dist/backend/backend/src/services/agent/crewly-agent/types.d.ts.map +1 -1
  58. package/dist/backend/backend/src/services/agent/crewly-agent/types.js +31 -1
  59. package/dist/backend/backend/src/services/agent/crewly-agent/types.js.map +1 -1
  60. package/dist/backend/backend/src/services/cloud/cloud-client.service.d.ts +5 -0
  61. package/dist/backend/backend/src/services/cloud/cloud-client.service.d.ts.map +1 -1
  62. package/dist/backend/backend/src/services/cloud/cloud-client.service.js +7 -0
  63. package/dist/backend/backend/src/services/cloud/cloud-client.service.js.map +1 -1
  64. package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.d.ts +15 -0
  65. package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.d.ts.map +1 -1
  66. package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js +40 -0
  67. package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js.map +1 -1
  68. package/dist/backend/backend/src/services/orchestrator/commitment-approval-guard.d.ts.map +1 -1
  69. package/dist/backend/backend/src/services/orchestrator/commitment-approval-guard.js +26 -0
  70. package/dist/backend/backend/src/services/orchestrator/commitment-approval-guard.js.map +1 -1
  71. package/dist/backend/backend/src/services/orchestrator/onboarding/materialize-team.d.ts.map +1 -1
  72. package/dist/backend/backend/src/services/orchestrator/onboarding/materialize-team.js +19 -3
  73. package/dist/backend/backend/src/services/orchestrator/onboarding/materialize-team.js.map +1 -1
  74. package/dist/backend/backend/src/services/orchestrator/orchestrator-status.service.d.ts +28 -3
  75. package/dist/backend/backend/src/services/orchestrator/orchestrator-status.service.d.ts.map +1 -1
  76. package/dist/backend/backend/src/services/orchestrator/orchestrator-status.service.js +65 -8
  77. package/dist/backend/backend/src/services/orchestrator/orchestrator-status.service.js.map +1 -1
  78. package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts +214 -4
  79. package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts.map +1 -1
  80. package/dist/backend/backend/src/services/reconciler/reconcile-rules.js +330 -48
  81. package/dist/backend/backend/src/services/reconciler/reconcile-rules.js.map +1 -1
  82. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts +13 -1
  83. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
  84. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +84 -5
  85. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
  86. package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts +14 -0
  87. package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts.map +1 -1
  88. package/dist/backend/backend/src/services/reconciler/reconciler.service.js +77 -3
  89. package/dist/backend/backend/src/services/reconciler/reconciler.service.js.map +1 -1
  90. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +152 -7
  91. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  92. package/dist/backend/backend/src/services/task-pool/task-pool.service.js +268 -36
  93. package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  94. package/dist/backend/backend/src/services/v3/escalation-router.service.js +2 -2
  95. package/dist/backend/backend/src/services/v3/escalation-router.service.js.map +1 -1
  96. package/dist/backend/backend/src/services/v3/request-sla.subscriber.d.ts.map +1 -1
  97. package/dist/backend/backend/src/services/v3/request-sla.subscriber.js +39 -2
  98. package/dist/backend/backend/src/services/v3/request-sla.subscriber.js.map +1 -1
  99. package/dist/backend/backend/src/services/wiki/wiki-migrate.service.d.ts +3 -0
  100. package/dist/backend/backend/src/services/wiki/wiki-migrate.service.d.ts.map +1 -1
  101. package/dist/backend/backend/src/services/wiki/wiki-migrate.service.js +21 -10
  102. package/dist/backend/backend/src/services/wiki/wiki-migrate.service.js.map +1 -1
  103. package/dist/backend/backend/src/types/v2/index.d.ts +1 -1
  104. package/dist/backend/backend/src/types/v2/index.d.ts.map +1 -1
  105. package/dist/backend/backend/src/types/v2/index.js +1 -1
  106. package/dist/backend/backend/src/types/v2/index.js.map +1 -1
  107. package/dist/backend/backend/src/types/v2/work-item.types.d.ts +181 -1
  108. package/dist/backend/backend/src/types/v2/work-item.types.d.ts.map +1 -1
  109. package/dist/backend/backend/src/types/v2/work-item.types.js +104 -17
  110. package/dist/backend/backend/src/types/v2/work-item.types.js.map +1 -1
  111. package/dist/backend/backend/src/utils/build-provenance.d.ts +150 -0
  112. package/dist/backend/backend/src/utils/build-provenance.d.ts.map +1 -0
  113. package/dist/backend/backend/src/utils/build-provenance.js +211 -0
  114. package/dist/backend/backend/src/utils/build-provenance.js.map +1 -0
  115. package/dist/backend/backend/src/websocket/chat.gateway.d.ts.map +1 -1
  116. package/dist/backend/backend/src/websocket/chat.gateway.js +7 -1
  117. package/dist/backend/backend/src/websocket/chat.gateway.js.map +1 -1
  118. package/dist/backend/build-info.json +4 -0
  119. package/dist/backend/config/vitest-exclude.constants.d.ts +53 -0
  120. package/dist/backend/config/vitest-exclude.constants.d.ts.map +1 -0
  121. package/dist/backend/config/vitest-exclude.constants.js +55 -0
  122. package/dist/backend/config/vitest-exclude.constants.js.map +1 -0
  123. package/dist/cli/backend/src/services/agent/active-work-briefing.service.d.ts +538 -0
  124. package/dist/cli/backend/src/services/agent/active-work-briefing.service.d.ts.map +1 -0
  125. package/dist/cli/backend/src/services/agent/active-work-briefing.service.js +802 -0
  126. package/dist/cli/backend/src/services/agent/active-work-briefing.service.js.map +1 -0
  127. package/dist/cli/backend/src/services/agent/agent-heartbeat.service.d.ts +240 -0
  128. package/dist/cli/backend/src/services/agent/agent-heartbeat.service.d.ts.map +1 -0
  129. package/dist/cli/backend/src/services/agent/agent-heartbeat.service.js +524 -0
  130. package/dist/cli/backend/src/services/agent/agent-heartbeat.service.js.map +1 -0
  131. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts +629 -0
  132. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -0
  133. package/dist/cli/backend/src/services/agent/agent-registration.service.js +4548 -0
  134. package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -0
  135. package/dist/cli/backend/src/services/agent/agent-suspend.service.d.ts +118 -0
  136. package/dist/cli/backend/src/services/agent/agent-suspend.service.d.ts.map +1 -0
  137. package/dist/cli/backend/src/services/agent/agent-suspend.service.js +304 -0
  138. package/dist/cli/backend/src/services/agent/agent-suspend.service.js.map +1 -0
  139. package/dist/cli/backend/src/services/agent/auditor-scheduler.service.d.ts +196 -0
  140. package/dist/cli/backend/src/services/agent/auditor-scheduler.service.d.ts.map +1 -0
  141. package/dist/cli/backend/src/services/agent/auditor-scheduler.service.js +504 -0
  142. package/dist/cli/backend/src/services/agent/auditor-scheduler.service.js.map +1 -0
  143. package/dist/cli/backend/src/services/agent/claude-runtime.service.d.ts +97 -0
  144. package/dist/cli/backend/src/services/agent/claude-runtime.service.d.ts.map +1 -0
  145. package/dist/cli/backend/src/services/agent/claude-runtime.service.js +334 -0
  146. package/dist/cli/backend/src/services/agent/claude-runtime.service.js.map +1 -0
  147. package/dist/cli/backend/src/services/agent/codex-runtime.service.d.ts +47 -0
  148. package/dist/cli/backend/src/services/agent/codex-runtime.service.d.ts.map +1 -0
  149. package/dist/cli/backend/src/services/agent/codex-runtime.service.js +112 -0
  150. package/dist/cli/backend/src/services/agent/codex-runtime.service.js.map +1 -0
  151. package/dist/cli/backend/src/services/agent/context-window-monitor.service.d.ts +323 -0
  152. package/dist/cli/backend/src/services/agent/context-window-monitor.service.d.ts.map +1 -0
  153. package/dist/cli/backend/src/services/agent/context-window-monitor.service.js +1056 -0
  154. package/dist/cli/backend/src/services/agent/context-window-monitor.service.js.map +1 -0
  155. package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts +198 -0
  156. package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -0
  157. package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +816 -0
  158. package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -0
  159. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-log-buffer.d.ts +155 -0
  160. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-log-buffer.d.ts.map +1 -0
  161. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-log-buffer.js +272 -0
  162. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-log-buffer.js.map +1 -0
  163. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.d.ts +91 -0
  164. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.d.ts.map +1 -0
  165. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.js +120 -0
  166. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.js.map +1 -0
  167. package/dist/cli/backend/src/services/agent/crewly-agent/types.d.ts +536 -0
  168. package/dist/cli/backend/src/services/agent/crewly-agent/types.d.ts.map +1 -0
  169. package/dist/cli/backend/src/services/agent/crewly-agent/types.js +282 -0
  170. package/dist/cli/backend/src/services/agent/crewly-agent/types.js.map +1 -0
  171. package/dist/cli/backend/src/services/agent/disk-cleanup.service.d.ts +100 -0
  172. package/dist/cli/backend/src/services/agent/disk-cleanup.service.d.ts.map +1 -0
  173. package/dist/cli/backend/src/services/agent/disk-cleanup.service.js +149 -0
  174. package/dist/cli/backend/src/services/agent/disk-cleanup.service.js.map +1 -0
  175. package/dist/cli/backend/src/services/agent/gemini-runtime.service.d.ts +130 -0
  176. package/dist/cli/backend/src/services/agent/gemini-runtime.service.d.ts.map +1 -0
  177. package/dist/cli/backend/src/services/agent/gemini-runtime.service.js +758 -0
  178. package/dist/cli/backend/src/services/agent/gemini-runtime.service.js.map +1 -0
  179. package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.d.ts +216 -0
  180. package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.d.ts.map +1 -0
  181. package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.js +508 -0
  182. package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.js.map +1 -0
  183. package/dist/cli/backend/src/services/agent/pty-activity-tracker.service.d.ts +113 -0
  184. package/dist/cli/backend/src/services/agent/pty-activity-tracker.service.d.ts.map +1 -0
  185. package/dist/cli/backend/src/services/agent/pty-activity-tracker.service.js +161 -0
  186. package/dist/cli/backend/src/services/agent/pty-activity-tracker.service.js.map +1 -0
  187. package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.d.ts +156 -0
  188. package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -0
  189. package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js +641 -0
  190. package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -0
  191. package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.d.ts +295 -0
  192. package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.d.ts.map +1 -0
  193. package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.js +1166 -0
  194. package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.js.map +1 -0
  195. package/dist/cli/backend/src/services/agent/runtime-service.factory.d.ts +71 -0
  196. package/dist/cli/backend/src/services/agent/runtime-service.factory.d.ts.map +1 -0
  197. package/dist/cli/backend/src/services/agent/runtime-service.factory.js +174 -0
  198. package/dist/cli/backend/src/services/agent/runtime-service.factory.js.map +1 -0
  199. package/dist/cli/backend/src/services/ai/prompt-builder.service.d.ts +435 -0
  200. package/dist/cli/backend/src/services/ai/prompt-builder.service.d.ts.map +1 -0
  201. package/dist/cli/backend/src/services/ai/prompt-builder.service.js +1331 -0
  202. package/dist/cli/backend/src/services/ai/prompt-builder.service.js.map +1 -0
  203. package/dist/cli/backend/src/services/ai/prompt-modules/capability-overlay.module.d.ts +33 -0
  204. package/dist/cli/backend/src/services/ai/prompt-modules/capability-overlay.module.d.ts.map +1 -0
  205. package/dist/cli/backend/src/services/ai/prompt-modules/capability-overlay.module.js +70 -0
  206. package/dist/cli/backend/src/services/ai/prompt-modules/capability-overlay.module.js.map +1 -0
  207. package/dist/cli/backend/src/services/ai/prompt-modules/communication.module.d.ts +100 -0
  208. package/dist/cli/backend/src/services/ai/prompt-modules/communication.module.d.ts.map +1 -0
  209. package/dist/cli/backend/src/services/ai/prompt-modules/communication.module.js +296 -0
  210. package/dist/cli/backend/src/services/ai/prompt-modules/communication.module.js.map +1 -0
  211. package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.d.ts +59 -0
  212. package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.d.ts.map +1 -0
  213. package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.js +87 -0
  214. package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.js.map +1 -0
  215. package/dist/cli/backend/src/services/ai/prompt-modules/default-execution-loop.module.d.ts +67 -0
  216. package/dist/cli/backend/src/services/ai/prompt-modules/default-execution-loop.module.d.ts.map +1 -0
  217. package/dist/cli/backend/src/services/ai/prompt-modules/default-execution-loop.module.js +84 -0
  218. package/dist/cli/backend/src/services/ai/prompt-modules/default-execution-loop.module.js.map +1 -0
  219. package/dist/cli/backend/src/services/ai/prompt-modules/domain-sop.module.d.ts +35 -0
  220. package/dist/cli/backend/src/services/ai/prompt-modules/domain-sop.module.d.ts.map +1 -0
  221. package/dist/cli/backend/src/services/ai/prompt-modules/domain-sop.module.js +50 -0
  222. package/dist/cli/backend/src/services/ai/prompt-modules/domain-sop.module.js.map +1 -0
  223. package/dist/cli/backend/src/services/ai/prompt-modules/expert-profile.module.d.ts +41 -0
  224. package/dist/cli/backend/src/services/ai/prompt-modules/expert-profile.module.d.ts.map +1 -0
  225. package/dist/cli/backend/src/services/ai/prompt-modules/expert-profile.module.js +95 -0
  226. package/dist/cli/backend/src/services/ai/prompt-modules/expert-profile.module.js.map +1 -0
  227. package/dist/cli/backend/src/services/ai/prompt-modules/identity.module.d.ts +28 -0
  228. package/dist/cli/backend/src/services/ai/prompt-modules/identity.module.d.ts.map +1 -0
  229. package/dist/cli/backend/src/services/ai/prompt-modules/identity.module.js +47 -0
  230. package/dist/cli/backend/src/services/ai/prompt-modules/identity.module.js.map +1 -0
  231. package/dist/cli/backend/src/services/ai/prompt-modules/lazy-anti-patterns.module.d.ts +65 -0
  232. package/dist/cli/backend/src/services/ai/prompt-modules/lazy-anti-patterns.module.d.ts.map +1 -0
  233. package/dist/cli/backend/src/services/ai/prompt-modules/lazy-anti-patterns.module.js +79 -0
  234. package/dist/cli/backend/src/services/ai/prompt-modules/lazy-anti-patterns.module.js.map +1 -0
  235. package/dist/cli/backend/src/services/ai/prompt-modules/learning-reference.module.d.ts +28 -0
  236. package/dist/cli/backend/src/services/ai/prompt-modules/learning-reference.module.d.ts.map +1 -0
  237. package/dist/cli/backend/src/services/ai/prompt-modules/learning-reference.module.js +55 -0
  238. package/dist/cli/backend/src/services/ai/prompt-modules/learning-reference.module.js.map +1 -0
  239. package/dist/cli/backend/src/services/ai/prompt-modules/lifecycle.module.d.ts +44 -0
  240. package/dist/cli/backend/src/services/ai/prompt-modules/lifecycle.module.d.ts.map +1 -0
  241. package/dist/cli/backend/src/services/ai/prompt-modules/lifecycle.module.js +120 -0
  242. package/dist/cli/backend/src/services/ai/prompt-modules/lifecycle.module.js.map +1 -0
  243. package/dist/cli/backend/src/services/ai/prompt-modules/markdown-file-module.d.ts +46 -0
  244. package/dist/cli/backend/src/services/ai/prompt-modules/markdown-file-module.d.ts.map +1 -0
  245. package/dist/cli/backend/src/services/ai/prompt-modules/markdown-file-module.js +44 -0
  246. package/dist/cli/backend/src/services/ai/prompt-modules/markdown-file-module.js.map +1 -0
  247. package/dist/cli/backend/src/services/ai/prompt-modules/memory-reference.module.d.ts +28 -0
  248. package/dist/cli/backend/src/services/ai/prompt-modules/memory-reference.module.d.ts.map +1 -0
  249. package/dist/cli/backend/src/services/ai/prompt-modules/memory-reference.module.js +46 -0
  250. package/dist/cli/backend/src/services/ai/prompt-modules/memory-reference.module.js.map +1 -0
  251. package/dist/cli/backend/src/services/ai/prompt-modules/mission-context.module.d.ts +60 -0
  252. package/dist/cli/backend/src/services/ai/prompt-modules/mission-context.module.d.ts.map +1 -0
  253. package/dist/cli/backend/src/services/ai/prompt-modules/mission-context.module.js +104 -0
  254. package/dist/cli/backend/src/services/ai/prompt-modules/mission-context.module.js.map +1 -0
  255. package/dist/cli/backend/src/services/ai/prompt-modules/project-reference.module.d.ts +29 -0
  256. package/dist/cli/backend/src/services/ai/prompt-modules/project-reference.module.d.ts.map +1 -0
  257. package/dist/cli/backend/src/services/ai/prompt-modules/project-reference.module.js +50 -0
  258. package/dist/cli/backend/src/services/ai/prompt-modules/project-reference.module.js.map +1 -0
  259. package/dist/cli/backend/src/services/ai/prompt-modules/prompt-assembly.service.d.ts +103 -0
  260. package/dist/cli/backend/src/services/ai/prompt-modules/prompt-assembly.service.d.ts.map +1 -0
  261. package/dist/cli/backend/src/services/ai/prompt-modules/prompt-assembly.service.js +383 -0
  262. package/dist/cli/backend/src/services/ai/prompt-modules/prompt-assembly.service.js.map +1 -0
  263. package/dist/cli/backend/src/services/ai/prompt-modules/recovery.module.d.ts +34 -0
  264. package/dist/cli/backend/src/services/ai/prompt-modules/recovery.module.d.ts.map +1 -0
  265. package/dist/cli/backend/src/services/ai/prompt-modules/recovery.module.js +96 -0
  266. package/dist/cli/backend/src/services/ai/prompt-modules/recovery.module.js.map +1 -0
  267. package/dist/cli/backend/src/services/ai/prompt-modules/request-contract.module.d.ts +106 -0
  268. package/dist/cli/backend/src/services/ai/prompt-modules/request-contract.module.d.ts.map +1 -0
  269. package/dist/cli/backend/src/services/ai/prompt-modules/request-contract.module.js +167 -0
  270. package/dist/cli/backend/src/services/ai/prompt-modules/request-contract.module.js.map +1 -0
  271. package/dist/cli/backend/src/services/ai/prompt-modules/risk-policy.module.d.ts +35 -0
  272. package/dist/cli/backend/src/services/ai/prompt-modules/risk-policy.module.d.ts.map +1 -0
  273. package/dist/cli/backend/src/services/ai/prompt-modules/risk-policy.module.js +50 -0
  274. package/dist/cli/backend/src/services/ai/prompt-modules/risk-policy.module.js.map +1 -0
  275. package/dist/cli/backend/src/services/ai/prompt-modules/role-boundary.module.d.ts +91 -0
  276. package/dist/cli/backend/src/services/ai/prompt-modules/role-boundary.module.d.ts.map +1 -0
  277. package/dist/cli/backend/src/services/ai/prompt-modules/role-boundary.module.js +411 -0
  278. package/dist/cli/backend/src/services/ai/prompt-modules/role-boundary.module.js.map +1 -0
  279. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts +62 -0
  280. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -0
  281. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +199 -0
  282. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -0
  283. package/dist/cli/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.d.ts +79 -0
  284. package/dist/cli/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.d.ts.map +1 -0
  285. package/dist/cli/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.js +118 -0
  286. package/dist/cli/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.js.map +1 -0
  287. package/dist/cli/backend/src/services/ai/prompt-modules/soul.module.d.ts +102 -0
  288. package/dist/cli/backend/src/services/ai/prompt-modules/soul.module.d.ts.map +1 -0
  289. package/dist/cli/backend/src/services/ai/prompt-modules/soul.module.js +229 -0
  290. package/dist/cli/backend/src/services/ai/prompt-modules/soul.module.js.map +1 -0
  291. package/dist/cli/backend/src/services/ai/prompt-modules/team-norms.module.d.ts +61 -0
  292. package/dist/cli/backend/src/services/ai/prompt-modules/team-norms.module.d.ts.map +1 -0
  293. package/dist/cli/backend/src/services/ai/prompt-modules/team-norms.module.js +197 -0
  294. package/dist/cli/backend/src/services/ai/prompt-modules/team-norms.module.js.map +1 -0
  295. package/dist/cli/backend/src/services/ai/prompt-modules/team-reference.module.d.ts +54 -0
  296. package/dist/cli/backend/src/services/ai/prompt-modules/team-reference.module.d.ts.map +1 -0
  297. package/dist/cli/backend/src/services/ai/prompt-modules/team-reference.module.js +149 -0
  298. package/dist/cli/backend/src/services/ai/prompt-modules/team-reference.module.js.map +1 -0
  299. package/dist/cli/backend/src/services/ai/prompt-modules/user-profile-reference.module.d.ts +32 -0
  300. package/dist/cli/backend/src/services/ai/prompt-modules/user-profile-reference.module.d.ts.map +1 -0
  301. package/dist/cli/backend/src/services/ai/prompt-modules/user-profile-reference.module.js +69 -0
  302. package/dist/cli/backend/src/services/ai/prompt-modules/user-profile-reference.module.js.map +1 -0
  303. package/dist/cli/backend/src/services/ai/prompt-modules/working-memory.module.d.ts +91 -0
  304. package/dist/cli/backend/src/services/ai/prompt-modules/working-memory.module.d.ts.map +1 -0
  305. package/dist/cli/backend/src/services/ai/prompt-modules/working-memory.module.js +136 -0
  306. package/dist/cli/backend/src/services/ai/prompt-modules/working-memory.module.js.map +1 -0
  307. package/dist/cli/backend/src/services/ai/self-improvement/growth-areas.service.d.ts +81 -0
  308. package/dist/cli/backend/src/services/ai/self-improvement/growth-areas.service.d.ts.map +1 -0
  309. package/dist/cli/backend/src/services/ai/self-improvement/growth-areas.service.js +135 -0
  310. package/dist/cli/backend/src/services/ai/self-improvement/growth-areas.service.js.map +1 -0
  311. package/dist/cli/backend/src/services/chat-v2/chat-v2.service.d.ts +737 -0
  312. package/dist/cli/backend/src/services/chat-v2/chat-v2.service.d.ts.map +1 -0
  313. package/dist/cli/backend/src/services/chat-v2/chat-v2.service.js +1213 -0
  314. package/dist/cli/backend/src/services/chat-v2/chat-v2.service.js.map +1 -0
  315. package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.d.ts +27 -0
  316. package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.d.ts.map +1 -0
  317. package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.js +57 -0
  318. package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.js.map +1 -0
  319. package/dist/cli/backend/src/services/chat-v2/config.d.ts +100 -0
  320. package/dist/cli/backend/src/services/chat-v2/config.d.ts.map +1 -0
  321. package/dist/cli/backend/src/services/chat-v2/config.js +174 -0
  322. package/dist/cli/backend/src/services/chat-v2/config.js.map +1 -0
  323. package/dist/cli/backend/src/services/chat-v2/legacy-dto.utils.d.ts +118 -0
  324. package/dist/cli/backend/src/services/chat-v2/legacy-dto.utils.d.ts.map +1 -0
  325. package/dist/cli/backend/src/services/chat-v2/legacy-dto.utils.js +171 -0
  326. package/dist/cli/backend/src/services/chat-v2/legacy-dto.utils.js.map +1 -0
  327. package/dist/cli/backend/src/services/chat-v2/sqlite/channel.store.d.ts +188 -0
  328. package/dist/cli/backend/src/services/chat-v2/sqlite/channel.store.d.ts.map +1 -0
  329. package/dist/cli/backend/src/services/chat-v2/sqlite/channel.store.js +317 -0
  330. package/dist/cli/backend/src/services/chat-v2/sqlite/channel.store.js.map +1 -0
  331. package/dist/cli/backend/src/services/chat-v2/sqlite/chat-db.d.ts +119 -0
  332. package/dist/cli/backend/src/services/chat-v2/sqlite/chat-db.d.ts.map +1 -0
  333. package/dist/cli/backend/src/services/chat-v2/sqlite/chat-db.js +551 -0
  334. package/dist/cli/backend/src/services/chat-v2/sqlite/chat-db.js.map +1 -0
  335. package/dist/cli/backend/src/services/chat-v2/sqlite/message.store.d.ts +208 -0
  336. package/dist/cli/backend/src/services/chat-v2/sqlite/message.store.d.ts.map +1 -0
  337. package/dist/cli/backend/src/services/chat-v2/sqlite/message.store.js +405 -0
  338. package/dist/cli/backend/src/services/chat-v2/sqlite/message.store.js.map +1 -0
  339. package/dist/cli/backend/src/services/chat-v2/types.d.ts +341 -0
  340. package/dist/cli/backend/src/services/chat-v2/types.d.ts.map +1 -0
  341. package/dist/cli/backend/src/services/chat-v2/types.js +61 -0
  342. package/dist/cli/backend/src/services/chat-v2/types.js.map +1 -0
  343. package/dist/cli/backend/src/services/cloud/cloud-client.service.d.ts +5 -0
  344. package/dist/cli/backend/src/services/cloud/cloud-client.service.d.ts.map +1 -1
  345. package/dist/cli/backend/src/services/cloud/cloud-client.service.js +7 -0
  346. package/dist/cli/backend/src/services/cloud/cloud-client.service.js.map +1 -1
  347. package/dist/cli/backend/src/services/continuation/patterns/idle-patterns.d.ts +37 -0
  348. package/dist/cli/backend/src/services/continuation/patterns/idle-patterns.d.ts.map +1 -0
  349. package/dist/cli/backend/src/services/continuation/patterns/idle-patterns.js +63 -0
  350. package/dist/cli/backend/src/services/continuation/patterns/idle-patterns.js.map +1 -0
  351. package/dist/cli/backend/src/services/intent-task/intent-classifier.rules.d.ts +335 -0
  352. package/dist/cli/backend/src/services/intent-task/intent-classifier.rules.d.ts.map +1 -0
  353. package/dist/cli/backend/src/services/intent-task/intent-classifier.rules.js +421 -0
  354. package/dist/cli/backend/src/services/intent-task/intent-classifier.rules.js.map +1 -0
  355. package/dist/cli/backend/src/services/mcp-client.d.ts +233 -0
  356. package/dist/cli/backend/src/services/mcp-client.d.ts.map +1 -0
  357. package/dist/cli/backend/src/services/mcp-client.js +302 -0
  358. package/dist/cli/backend/src/services/mcp-client.js.map +1 -0
  359. package/dist/cli/backend/src/services/memory/mission-context.service.d.ts +168 -0
  360. package/dist/cli/backend/src/services/memory/mission-context.service.d.ts.map +1 -0
  361. package/dist/cli/backend/src/services/memory/mission-context.service.js +365 -0
  362. package/dist/cli/backend/src/services/memory/mission-context.service.js.map +1 -0
  363. package/dist/cli/backend/src/services/memory/session-memory.service.d.ts +197 -0
  364. package/dist/cli/backend/src/services/memory/session-memory.service.d.ts.map +1 -0
  365. package/dist/cli/backend/src/services/memory/session-memory.service.js +369 -0
  366. package/dist/cli/backend/src/services/memory/session-memory.service.js.map +1 -0
  367. package/dist/cli/backend/src/services/memory/user-profile.service.d.ts +77 -0
  368. package/dist/cli/backend/src/services/memory/user-profile.service.d.ts.map +1 -0
  369. package/dist/cli/backend/src/services/memory/user-profile.service.js +171 -0
  370. package/dist/cli/backend/src/services/memory/user-profile.service.js.map +1 -0
  371. package/dist/cli/backend/src/services/memory/working-memory.service.d.ts +232 -0
  372. package/dist/cli/backend/src/services/memory/working-memory.service.d.ts.map +1 -0
  373. package/dist/cli/backend/src/services/memory/working-memory.service.js +417 -0
  374. package/dist/cli/backend/src/services/memory/working-memory.service.js.map +1 -0
  375. package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts +84 -0
  376. package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts.map +1 -0
  377. package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js +136 -0
  378. package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -0
  379. package/dist/cli/backend/src/services/messaging/thread-status-queue.service.d.ts +358 -0
  380. package/dist/cli/backend/src/services/messaging/thread-status-queue.service.d.ts.map +1 -0
  381. package/dist/cli/backend/src/services/messaging/thread-status-queue.service.js +755 -0
  382. package/dist/cli/backend/src/services/messaging/thread-status-queue.service.js.map +1 -0
  383. package/dist/cli/backend/src/services/monitoring/token-usage.service.d.ts +269 -0
  384. package/dist/cli/backend/src/services/monitoring/token-usage.service.d.ts.map +1 -0
  385. package/dist/cli/backend/src/services/monitoring/token-usage.service.js +391 -0
  386. package/dist/cli/backend/src/services/monitoring/token-usage.service.js.map +1 -0
  387. package/dist/cli/backend/src/services/observability/agent-behavior-log.service.d.ts +132 -0
  388. package/dist/cli/backend/src/services/observability/agent-behavior-log.service.d.ts.map +1 -0
  389. package/dist/cli/backend/src/services/observability/agent-behavior-log.service.js +284 -0
  390. package/dist/cli/backend/src/services/observability/agent-behavior-log.service.js.map +1 -0
  391. package/dist/cli/backend/src/services/observability/agent-behavior-log.singleton.d.ts +70 -0
  392. package/dist/cli/backend/src/services/observability/agent-behavior-log.singleton.d.ts.map +1 -0
  393. package/dist/cli/backend/src/services/observability/agent-behavior-log.singleton.js +121 -0
  394. package/dist/cli/backend/src/services/observability/agent-behavior-log.singleton.js.map +1 -0
  395. package/dist/cli/backend/src/services/observability/agent-behavior-log.types.d.ts +130 -0
  396. package/dist/cli/backend/src/services/observability/agent-behavior-log.types.d.ts.map +1 -0
  397. package/dist/cli/backend/src/services/observability/agent-behavior-log.types.js +48 -0
  398. package/dist/cli/backend/src/services/observability/agent-behavior-log.types.js.map +1 -0
  399. package/dist/cli/backend/src/services/observability/observability-db.d.ts +84 -0
  400. package/dist/cli/backend/src/services/observability/observability-db.d.ts.map +1 -0
  401. package/dist/cli/backend/src/services/observability/observability-db.js +165 -0
  402. package/dist/cli/backend/src/services/observability/observability-db.js.map +1 -0
  403. package/dist/cli/backend/src/services/onboarding/content-approval.service.d.ts +124 -0
  404. package/dist/cli/backend/src/services/onboarding/content-approval.service.d.ts.map +1 -0
  405. package/dist/cli/backend/src/services/onboarding/content-approval.service.js +256 -0
  406. package/dist/cli/backend/src/services/onboarding/content-approval.service.js.map +1 -0
  407. package/dist/cli/backend/src/services/onboarding/content-approval.types.d.ts +80 -0
  408. package/dist/cli/backend/src/services/onboarding/content-approval.types.d.ts.map +1 -0
  409. package/dist/cli/backend/src/services/onboarding/content-approval.types.js +16 -0
  410. package/dist/cli/backend/src/services/onboarding/content-approval.types.js.map +1 -0
  411. package/dist/cli/backend/src/services/orc/orc-delivery-enforcer.service.d.ts +142 -0
  412. package/dist/cli/backend/src/services/orc/orc-delivery-enforcer.service.d.ts.map +1 -0
  413. package/dist/cli/backend/src/services/orc/orc-delivery-enforcer.service.js +265 -0
  414. package/dist/cli/backend/src/services/orc/orc-delivery-enforcer.service.js.map +1 -0
  415. package/dist/cli/backend/src/services/orchestrator/improvement-marker.service.d.ts +231 -0
  416. package/dist/cli/backend/src/services/orchestrator/improvement-marker.service.d.ts.map +1 -0
  417. package/dist/cli/backend/src/services/orchestrator/improvement-marker.service.js +353 -0
  418. package/dist/cli/backend/src/services/orchestrator/improvement-marker.service.js.map +1 -0
  419. package/dist/cli/backend/src/services/orchestrator/improvement-startup.service.d.ts +157 -0
  420. package/dist/cli/backend/src/services/orchestrator/improvement-startup.service.d.ts.map +1 -0
  421. package/dist/cli/backend/src/services/orchestrator/improvement-startup.service.js +483 -0
  422. package/dist/cli/backend/src/services/orchestrator/improvement-startup.service.js.map +1 -0
  423. package/dist/cli/backend/src/services/orchestrator/index.d.ts +16 -0
  424. package/dist/cli/backend/src/services/orchestrator/index.d.ts.map +1 -0
  425. package/dist/cli/backend/src/services/orchestrator/index.js +16 -0
  426. package/dist/cli/backend/src/services/orchestrator/index.js.map +1 -0
  427. package/dist/cli/backend/src/services/orchestrator/orchestrator-restart.service.d.ts +117 -0
  428. package/dist/cli/backend/src/services/orchestrator/orchestrator-restart.service.d.ts.map +1 -0
  429. package/dist/cli/backend/src/services/orchestrator/orchestrator-restart.service.js +300 -0
  430. package/dist/cli/backend/src/services/orchestrator/orchestrator-restart.service.js.map +1 -0
  431. package/dist/cli/backend/src/services/orchestrator/orchestrator-setup.service.d.ts +114 -0
  432. package/dist/cli/backend/src/services/orchestrator/orchestrator-setup.service.d.ts.map +1 -0
  433. package/dist/cli/backend/src/services/orchestrator/orchestrator-setup.service.js +195 -0
  434. package/dist/cli/backend/src/services/orchestrator/orchestrator-setup.service.js.map +1 -0
  435. package/dist/cli/backend/src/services/orchestrator/orchestrator-status.service.d.ts +94 -0
  436. package/dist/cli/backend/src/services/orchestrator/orchestrator-status.service.d.ts.map +1 -0
  437. package/dist/cli/backend/src/services/orchestrator/orchestrator-status.service.js +279 -0
  438. package/dist/cli/backend/src/services/orchestrator/orchestrator-status.service.js.map +1 -0
  439. package/dist/cli/backend/src/services/orchestrator/safe-restart.service.d.ts +192 -0
  440. package/dist/cli/backend/src/services/orchestrator/safe-restart.service.d.ts.map +1 -0
  441. package/dist/cli/backend/src/services/orchestrator/safe-restart.service.js +395 -0
  442. package/dist/cli/backend/src/services/orchestrator/safe-restart.service.js.map +1 -0
  443. package/dist/cli/backend/src/services/orchestrator/self-improvement.service.d.ts +201 -0
  444. package/dist/cli/backend/src/services/orchestrator/self-improvement.service.d.ts.map +1 -0
  445. package/dist/cli/backend/src/services/orchestrator/self-improvement.service.js +396 -0
  446. package/dist/cli/backend/src/services/orchestrator/self-improvement.service.js.map +1 -0
  447. package/dist/cli/backend/src/services/orchestrator/slack-bridge-lazy.d.ts +17 -0
  448. package/dist/cli/backend/src/services/orchestrator/slack-bridge-lazy.d.ts.map +1 -0
  449. package/dist/cli/backend/src/services/orchestrator/slack-bridge-lazy.js +30 -0
  450. package/dist/cli/backend/src/services/orchestrator/slack-bridge-lazy.js.map +1 -0
  451. package/dist/cli/backend/src/services/orchestrator/state-persistence.service.d.ts +249 -0
  452. package/dist/cli/backend/src/services/orchestrator/state-persistence.service.d.ts.map +1 -0
  453. package/dist/cli/backend/src/services/orchestrator/state-persistence.service.js +569 -0
  454. package/dist/cli/backend/src/services/orchestrator/state-persistence.service.js.map +1 -0
  455. package/dist/cli/backend/src/services/session/index.d.ts +47 -0
  456. package/dist/cli/backend/src/services/session/index.d.ts.map +1 -0
  457. package/dist/cli/backend/src/services/session/index.js +50 -0
  458. package/dist/cli/backend/src/services/session/index.js.map +1 -0
  459. package/dist/cli/backend/src/services/session/log-rotation.service.d.ts +163 -0
  460. package/dist/cli/backend/src/services/session/log-rotation.service.d.ts.map +1 -0
  461. package/dist/cli/backend/src/services/session/log-rotation.service.js +362 -0
  462. package/dist/cli/backend/src/services/session/log-rotation.service.js.map +1 -0
  463. package/dist/cli/backend/src/services/session/pty/index.d.ts +11 -0
  464. package/dist/cli/backend/src/services/session/pty/index.d.ts.map +1 -0
  465. package/dist/cli/backend/src/services/session/pty/index.js +11 -0
  466. package/dist/cli/backend/src/services/session/pty/index.js.map +1 -0
  467. package/dist/cli/backend/src/services/session/pty/pty-session-backend.d.ts +445 -0
  468. package/dist/cli/backend/src/services/session/pty/pty-session-backend.d.ts.map +1 -0
  469. package/dist/cli/backend/src/services/session/pty/pty-session-backend.js +804 -0
  470. package/dist/cli/backend/src/services/session/pty/pty-session-backend.js.map +1 -0
  471. package/dist/cli/backend/src/services/session/pty/pty-session.d.ts +252 -0
  472. package/dist/cli/backend/src/services/session/pty/pty-session.d.ts.map +1 -0
  473. package/dist/cli/backend/src/services/session/pty/pty-session.js +611 -0
  474. package/dist/cli/backend/src/services/session/pty/pty-session.js.map +1 -0
  475. package/dist/cli/backend/src/services/session/pty/pty-terminal-buffer.d.ts +275 -0
  476. package/dist/cli/backend/src/services/session/pty/pty-terminal-buffer.d.ts.map +1 -0
  477. package/dist/cli/backend/src/services/session/pty/pty-terminal-buffer.js +403 -0
  478. package/dist/cli/backend/src/services/session/pty/pty-terminal-buffer.js.map +1 -0
  479. package/dist/cli/backend/src/services/session/runtime-pid-registry.service.d.ts +86 -0
  480. package/dist/cli/backend/src/services/session/runtime-pid-registry.service.d.ts.map +1 -0
  481. package/dist/cli/backend/src/services/session/runtime-pid-registry.service.js +214 -0
  482. package/dist/cli/backend/src/services/session/runtime-pid-registry.service.js.map +1 -0
  483. package/dist/cli/backend/src/services/session/session-backend.factory.d.ts +127 -0
  484. package/dist/cli/backend/src/services/session/session-backend.factory.d.ts.map +1 -0
  485. package/dist/cli/backend/src/services/session/session-backend.factory.js +226 -0
  486. package/dist/cli/backend/src/services/session/session-backend.factory.js.map +1 -0
  487. package/dist/cli/backend/src/services/session/session-backend.interface.d.ts +390 -0
  488. package/dist/cli/backend/src/services/session/session-backend.interface.d.ts.map +1 -0
  489. package/dist/cli/backend/src/services/session/session-backend.interface.js +19 -0
  490. package/dist/cli/backend/src/services/session/session-backend.interface.js.map +1 -0
  491. package/dist/cli/backend/src/services/session/session-command-helper.d.ts +350 -0
  492. package/dist/cli/backend/src/services/session/session-command-helper.d.ts.map +1 -0
  493. package/dist/cli/backend/src/services/session/session-command-helper.js +810 -0
  494. package/dist/cli/backend/src/services/session/session-command-helper.js.map +1 -0
  495. package/dist/cli/backend/src/services/session/session-handoff.service.d.ts +307 -0
  496. package/dist/cli/backend/src/services/session/session-handoff.service.d.ts.map +1 -0
  497. package/dist/cli/backend/src/services/session/session-handoff.service.js +742 -0
  498. package/dist/cli/backend/src/services/session/session-handoff.service.js.map +1 -0
  499. package/dist/cli/backend/src/services/session/session-state-persistence.d.ts +231 -0
  500. package/dist/cli/backend/src/services/session/session-state-persistence.d.ts.map +1 -0
  501. package/dist/cli/backend/src/services/session/session-state-persistence.js +407 -0
  502. package/dist/cli/backend/src/services/session/session-state-persistence.js.map +1 -0
  503. package/dist/cli/backend/src/services/settings/role.service.d.ts +277 -0
  504. package/dist/cli/backend/src/services/settings/role.service.d.ts.map +1 -0
  505. package/dist/cli/backend/src/services/settings/role.service.js +728 -0
  506. package/dist/cli/backend/src/services/settings/role.service.js.map +1 -0
  507. package/dist/cli/backend/src/services/slack/cross-machine-message.service.d.ts +152 -0
  508. package/dist/cli/backend/src/services/slack/cross-machine-message.service.d.ts.map +1 -0
  509. package/dist/cli/backend/src/services/slack/cross-machine-message.service.js +354 -0
  510. package/dist/cli/backend/src/services/slack/cross-machine-message.service.js.map +1 -0
  511. package/dist/cli/backend/src/services/slack/slack-image.service.d.ts +114 -0
  512. package/dist/cli/backend/src/services/slack/slack-image.service.d.ts.map +1 -0
  513. package/dist/cli/backend/src/services/slack/slack-image.service.js +331 -0
  514. package/dist/cli/backend/src/services/slack/slack-image.service.js.map +1 -0
  515. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts +499 -0
  516. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -0
  517. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js +1928 -0
  518. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -0
  519. package/dist/cli/backend/src/services/slack/slack.service.d.ts +315 -0
  520. package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -0
  521. package/dist/cli/backend/src/services/slack/slack.service.js +1094 -0
  522. package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -0
  523. package/dist/cli/backend/src/services/sop/sop.service.d.ts +364 -0
  524. package/dist/cli/backend/src/services/sop/sop.service.d.ts.map +1 -0
  525. package/dist/cli/backend/src/services/sop/sop.service.js +734 -0
  526. package/dist/cli/backend/src/services/sop/sop.service.js.map +1 -0
  527. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +152 -7
  528. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  529. package/dist/cli/backend/src/services/task-pool/task-pool.service.js +268 -36
  530. package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  531. package/dist/cli/backend/src/services/v3/escalation-router.service.d.ts +178 -0
  532. package/dist/cli/backend/src/services/v3/escalation-router.service.d.ts.map +1 -0
  533. package/dist/cli/backend/src/services/v3/escalation-router.service.js +692 -0
  534. package/dist/cli/backend/src/services/v3/escalation-router.service.js.map +1 -0
  535. package/dist/cli/backend/src/services/v3/kr-tracking.service.d.ts +200 -0
  536. package/dist/cli/backend/src/services/v3/kr-tracking.service.d.ts.map +1 -0
  537. package/dist/cli/backend/src/services/v3/kr-tracking.service.js +482 -0
  538. package/dist/cli/backend/src/services/v3/kr-tracking.service.js.map +1 -0
  539. package/dist/cli/backend/src/services/v3/mission-executor.service.d.ts +117 -0
  540. package/dist/cli/backend/src/services/v3/mission-executor.service.d.ts.map +1 -0
  541. package/dist/cli/backend/src/services/v3/mission-executor.service.js +321 -0
  542. package/dist/cli/backend/src/services/v3/mission-executor.service.js.map +1 -0
  543. package/dist/cli/backend/src/services/v3/request-sla.subscriber.d.ts +610 -0
  544. package/dist/cli/backend/src/services/v3/request-sla.subscriber.d.ts.map +1 -0
  545. package/dist/cli/backend/src/services/v3/request-sla.subscriber.js +1529 -0
  546. package/dist/cli/backend/src/services/v3/request-sla.subscriber.js.map +1 -0
  547. package/dist/cli/backend/src/services/v3/request-tracker.service.d.ts +78 -0
  548. package/dist/cli/backend/src/services/v3/request-tracker.service.d.ts.map +1 -0
  549. package/dist/cli/backend/src/services/v3/request-tracker.service.js +108 -0
  550. package/dist/cli/backend/src/services/v3/request-tracker.service.js.map +1 -0
  551. package/dist/cli/backend/src/services/v3/request.service.d.ts +295 -0
  552. package/dist/cli/backend/src/services/v3/request.service.d.ts.map +1 -0
  553. package/dist/cli/backend/src/services/v3/request.service.js +534 -0
  554. package/dist/cli/backend/src/services/v3/request.service.js.map +1 -0
  555. package/dist/cli/backend/src/services/v3/v3-data.service.d.ts +340 -0
  556. package/dist/cli/backend/src/services/v3/v3-data.service.d.ts.map +1 -0
  557. package/dist/cli/backend/src/services/v3/v3-data.service.js +1113 -0
  558. package/dist/cli/backend/src/services/v3/v3-data.service.js.map +1 -0
  559. package/dist/cli/backend/src/types/cross-machine.types.d.ts +103 -0
  560. package/dist/cli/backend/src/types/cross-machine.types.d.ts.map +1 -0
  561. package/dist/cli/backend/src/types/cross-machine.types.js +47 -0
  562. package/dist/cli/backend/src/types/cross-machine.types.js.map +1 -0
  563. package/dist/cli/backend/src/types/intent-task.types.d.ts +402 -0
  564. package/dist/cli/backend/src/types/intent-task.types.d.ts.map +1 -0
  565. package/dist/cli/backend/src/types/intent-task.types.js +403 -0
  566. package/dist/cli/backend/src/types/intent-task.types.js.map +1 -0
  567. package/dist/cli/backend/src/types/orchestrator-state.types.d.ts +531 -0
  568. package/dist/cli/backend/src/types/orchestrator-state.types.d.ts.map +1 -0
  569. package/dist/cli/backend/src/types/orchestrator-state.types.js +110 -0
  570. package/dist/cli/backend/src/types/orchestrator-state.types.js.map +1 -0
  571. package/dist/cli/backend/src/types/v2/index.d.ts +30 -0
  572. package/dist/cli/backend/src/types/v2/index.d.ts.map +1 -0
  573. package/dist/cli/backend/src/types/v2/index.js +22 -0
  574. package/dist/cli/backend/src/types/v2/index.js.map +1 -0
  575. package/dist/cli/backend/src/types/v2/key-result.types.d.ts +279 -0
  576. package/dist/cli/backend/src/types/v2/key-result.types.d.ts.map +1 -0
  577. package/dist/cli/backend/src/types/v2/key-result.types.js +264 -0
  578. package/dist/cli/backend/src/types/v2/key-result.types.js.map +1 -0
  579. package/dist/cli/backend/src/types/v2/mission.types.d.ts +661 -0
  580. package/dist/cli/backend/src/types/v2/mission.types.d.ts.map +1 -0
  581. package/dist/cli/backend/src/types/v2/mission.types.js +713 -0
  582. package/dist/cli/backend/src/types/v2/mission.types.js.map +1 -0
  583. package/dist/cli/backend/src/types/v2/reconcile.types.d.ts +204 -0
  584. package/dist/cli/backend/src/types/v2/reconcile.types.d.ts.map +1 -0
  585. package/dist/cli/backend/src/types/v2/reconcile.types.js +121 -0
  586. package/dist/cli/backend/src/types/v2/reconcile.types.js.map +1 -0
  587. package/dist/cli/backend/src/types/v2/request.types.d.ts +192 -0
  588. package/dist/cli/backend/src/types/v2/request.types.d.ts.map +1 -0
  589. package/dist/cli/backend/src/types/v2/request.types.js +179 -0
  590. package/dist/cli/backend/src/types/v2/request.types.js.map +1 -0
  591. package/dist/cli/backend/src/types/v2/work-item.types.d.ts +181 -1
  592. package/dist/cli/backend/src/types/v2/work-item.types.d.ts.map +1 -1
  593. package/dist/cli/backend/src/types/v2/work-item.types.js +104 -17
  594. package/dist/cli/backend/src/types/v2/work-item.types.js.map +1 -1
  595. package/dist/cli/backend/src/types/v2/workspace.types.d.ts +157 -0
  596. package/dist/cli/backend/src/types/v2/workspace.types.d.ts.map +1 -0
  597. package/dist/cli/backend/src/types/v2/workspace.types.js +173 -0
  598. package/dist/cli/backend/src/types/v2/workspace.types.js.map +1 -0
  599. package/dist/cli/backend/src/utils/async.utils.d.ts +68 -0
  600. package/dist/cli/backend/src/utils/async.utils.d.ts.map +1 -0
  601. package/dist/cli/backend/src/utils/async.utils.js +80 -0
  602. package/dist/cli/backend/src/utils/async.utils.js.map +1 -0
  603. package/dist/cli/backend/src/utils/native-binding.utils.d.ts +128 -0
  604. package/dist/cli/backend/src/utils/native-binding.utils.d.ts.map +1 -0
  605. package/dist/cli/backend/src/utils/native-binding.utils.js +206 -0
  606. package/dist/cli/backend/src/utils/native-binding.utils.js.map +1 -0
  607. package/dist/cli/backend/src/websocket/chat.gateway.d.ts +142 -0
  608. package/dist/cli/backend/src/websocket/chat.gateway.d.ts.map +1 -0
  609. package/dist/cli/backend/src/websocket/chat.gateway.js +327 -0
  610. package/dist/cli/backend/src/websocket/chat.gateway.js.map +1 -0
  611. package/dist/cli/backend/src/websocket/terminal.gateway.d.ts +341 -0
  612. package/dist/cli/backend/src/websocket/terminal.gateway.d.ts.map +1 -0
  613. package/dist/cli/backend/src/websocket/terminal.gateway.js +933 -0
  614. package/dist/cli/backend/src/websocket/terminal.gateway.js.map +1 -0
  615. package/dist/cli/config/vitest-exclude.constants.d.ts +53 -0
  616. package/dist/cli/config/vitest-exclude.constants.d.ts.map +1 -0
  617. package/dist/cli/config/vitest-exclude.constants.js +55 -0
  618. package/dist/cli/config/vitest-exclude.constants.js.map +1 -0
  619. package/package.json +2 -2
  620. package/packages/crewly-agent/src/cli.ts +16 -6
  621. package/packages/crewly-agent/src/runtime/agent-runner.service.test.ts +311 -33
  622. package/packages/crewly-agent/src/runtime/agent-runner.service.ts +221 -12
  623. package/packages/crewly-agent/src/runtime/tool-registry.test.ts +19 -0
  624. package/packages/crewly-agent/src/runtime/tool-registry.ts +4 -1
  625. package/packages/crewly-agent/src/runtime/types.ts +65 -5
@@ -0,0 +1,1213 @@
1
+ /**
2
+ * ChatV2Service — orchestrator for the Agent-First Chat MVP.
3
+ *
4
+ * Wires ChannelStore + MessageStore, enforces the authorization rules
5
+ * laid out in the tech spec §7.2, and maps DB rows to wire-format DTOs.
6
+ *
7
+ * WebSocket push, offline queuing, and attachment durability land in
8
+ * later passes of Phase 1 — see §18 rollout plan.
9
+ *
10
+ * @module services/chat-v2/chat-v2.service
11
+ */
12
+ import { ChannelStore } from './sqlite/channel.store.js';
13
+ import { EventEmitter } from 'events';
14
+ import { MessageStore } from './sqlite/message.store.js';
15
+ import { openChatDatabase } from './sqlite/chat-db.js';
16
+ import { CHAT_CHANNEL_TYPES, CHAT_CONTENT_TYPES, CHAT_ERROR_CODES, CHAT_SENDER_TYPES, ChatError, } from './types.js';
17
+ /**
18
+ * Allowed values for `RecordTurnInput.metadata.source` — the audit-trail
19
+ * discriminator that identifies which subsystem produced the message.
20
+ *
21
+ * Per spec `2026-05-14-unified-chat-message-store.md`, every {@link
22
+ * ChatV2Service.recordTurn} caller MUST set `metadata.source` to one of
23
+ * these values. The set is intentionally closed so future audits can
24
+ * `GROUP BY metadata->>'$.source'` without surprise values.
25
+ */
26
+ export const RECORD_TURN_SOURCES = [
27
+ 'web',
28
+ 'slack',
29
+ 'pty-runtime',
30
+ 'in-process-runtime',
31
+ 'reply-tool',
32
+ 'system',
33
+ ];
34
+ // ---------------------------------------------------------------------------
35
+ // Service
36
+ // ---------------------------------------------------------------------------
37
+ /** Default presence provider used until AgentRegistrationService is wired. */
38
+ const DEFAULT_PRESENCE = () => ({
39
+ status: 'offline',
40
+ lastSeenAt: null,
41
+ });
42
+ /**
43
+ * Orchestrator for chat domain operations.
44
+ *
45
+ * Responsibilities:
46
+ * - Owns the DB handle + stores.
47
+ * - Enforces authorization per §7.2.
48
+ * - Maps rows → DTOs.
49
+ * - Fans out to WebSocket / adapters in later phases.
50
+ */
51
+ export class ChatV2Service extends EventEmitter {
52
+ /** Phase A spec §3.2: max mention count per message. */
53
+ static MAX_MENTIONS_PER_MESSAGE = 50;
54
+ /** Phase A spec §3.2: max JSON-encoded byte size of the mentions array. */
55
+ static MAX_MENTIONS_JSON_BYTES = 1024;
56
+ config;
57
+ db;
58
+ channels;
59
+ messages;
60
+ presence;
61
+ validateTeamMembership;
62
+ now;
63
+ constructor(options) {
64
+ super();
65
+ this.config = options.config;
66
+ this.db = options.db ?? openChatDatabase({ dbPath: options.config.storage.dbPath });
67
+ this.channels = new ChannelStore(this.db);
68
+ this.messages = new MessageStore(this.db);
69
+ this.presence = options.getPresence ?? DEFAULT_PRESENCE;
70
+ this.validateTeamMembership = options.validateTeamMembership;
71
+ this.now = options.now ?? Date.now;
72
+ }
73
+ /**
74
+ * Wire (or replace) the synchronous presence provider used by the channel
75
+ * DTO mapper — i.e. what drives the DM presence dots in the conversation
76
+ * list. Settable POST-construction because the singleton is first-constructed
77
+ * by whichever caller wins the race (slack bridge, ws gateway, replay, …),
78
+ * usually with no presence wired; the composition root then calls this once
79
+ * so the dots reflect real agent liveness regardless of construction order.
80
+ *
81
+ * @param getPresence - Sync presence lookup (e.g. `createOssSyncPresence()`)
82
+ */
83
+ setPresenceProvider(getPresence) {
84
+ this.presence = getPresence;
85
+ }
86
+ /** Release the DB handle. Safe to call during graceful shutdown / in tests. */
87
+ close() {
88
+ try {
89
+ this.db.close();
90
+ }
91
+ catch {
92
+ // swallow — nothing to do if already closed
93
+ }
94
+ }
95
+ /**
96
+ * Count every persisted chat message across every channel.
97
+ *
98
+ * Onboarding v3 (B1) — surfaced for the cold-start detector. Defers to
99
+ * `MessageStore.countAll()`. Intentionally bypasses the principal /
100
+ * authorization layer because the caller is the orchestrator bootstrap,
101
+ * which runs without an HTTP-request principal context. The detector
102
+ * only needs an unsigned "is the store empty" answer; no row contents
103
+ * are exposed.
104
+ *
105
+ * @returns Total message count across all channels (0 on a fresh install)
106
+ */
107
+ countAllMessages() {
108
+ return this.messages.countAll();
109
+ }
110
+ /**
111
+ * Phase 6.0 of unified-chat-message-store spec — replacement for the
112
+ * legacy `ChatService.updateMessageMetadata`. Merges a partial
113
+ * metadata object into the stored row's `metadata` JSON column using
114
+ * SQLite's `json_patch` (atomic, server-side).
115
+ *
116
+ * No principal check — this is a server-internal mutation path used
117
+ * by reconciliation jobs (Slack delivery status updates) and never
118
+ * exposed directly to user HTTP traffic. Phase 6c will retire the
119
+ * legacy method that called this; until then it is the only callable
120
+ * write-through for the existing reconciliation code.
121
+ *
122
+ * @param messageId - Message id to update
123
+ * @param patch - Shallow metadata patch to merge
124
+ * @returns The updated message DTO, or null if no such message exists
125
+ */
126
+ updateMessageMetadata(messageId, patch) {
127
+ const row = this.messages.updateMetadata(messageId, patch);
128
+ if (!row)
129
+ return null;
130
+ return this.toMessageDTO(row, []);
131
+ }
132
+ /**
133
+ * Phase 6.0 — replacement for the legacy
134
+ * `ChatService.getMessagesWithPendingSlackDelivery`. Returns the
135
+ * messages still marked `slackDeliveryStatus='pending'` within the
136
+ * caller-supplied lookback window, used by NotifyReconciliationService
137
+ * to retry stuck Slack deliveries.
138
+ *
139
+ * @param maxAgeMs - Lookback window in milliseconds
140
+ * @returns Pending-delivery messages, newest first, capped at MAX_LIMIT
141
+ */
142
+ findMessagesWithPendingSlackDelivery(maxAgeMs) {
143
+ const rows = this.messages.findPendingSlackDelivery(maxAgeMs);
144
+ return rows.map((r) => this.toMessageDTO(r, []));
145
+ }
146
+ /**
147
+ * Phase 6.0 — replacement for the legacy `ChatService.getStatistics`.
148
+ * Aggregate counts used by the boot-time telemetry and the
149
+ * admin/audit dashboards.
150
+ *
151
+ * @returns Active/archived channel counts plus total message count
152
+ */
153
+ getStatistics() {
154
+ const activeChannels = this.db
155
+ .prepare(`SELECT COUNT(*) AS n FROM chat_channels WHERE archived_at IS NULL`)
156
+ .get().n;
157
+ const archivedChannels = this.db
158
+ .prepare(`SELECT COUNT(*) AS n FROM chat_channels WHERE archived_at IS NOT NULL`)
159
+ .get().n;
160
+ return {
161
+ totalChannels: activeChannels + archivedChannels,
162
+ activeChannels,
163
+ archivedChannels,
164
+ totalMessages: this.messages.countAll(),
165
+ };
166
+ }
167
+ // -------------------------------------------------------------------------
168
+ // Channel operations
169
+ // -------------------------------------------------------------------------
170
+ /**
171
+ * Create a channel bound 1:1 to an agent session. Server always assigns
172
+ * `owner_user_id = principal.userId` — the body's owner fields are ignored.
173
+ *
174
+ * F2b (#333): when `type='channel'` and a `validateTeamMembership` provider
175
+ * is wired into the service, the principal must be authorized for the
176
+ * requested `teamId`. Failures throw `forbidden_team` (403) — distinct
177
+ * from generic `forbidden` so the FE can surface a tenant-specific
178
+ * message.
179
+ *
180
+ * @param args - Channel creation args
181
+ * @returns The created channel as a DTO
182
+ * @throws {ChatError} `validation_error` (400) / `forbidden_team` (403) /
183
+ * `agent_already_bound` (409)
184
+ */
185
+ createChannel(args) {
186
+ const name = (args.name ?? '').trim();
187
+ if (name.length === 0) {
188
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, 'name is required');
189
+ }
190
+ if (name.length > this.config.maxChannelNameChars) {
191
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, `name exceeds ${this.config.maxChannelNameChars} characters`);
192
+ }
193
+ // Phase A: validate channel type (defaults to 'dm' for backwards compat).
194
+ const channelType = args.type ?? 'dm';
195
+ if (!CHAT_CHANNEL_TYPES.includes(channelType)) {
196
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, `unknown channel type: ${channelType}`);
197
+ }
198
+ // agentSession requirement is type-dependent:
199
+ // - 'dm' → required (existing Phase 1 contract).
200
+ // - 'channel' → not 1:1-bound; server stores '' even if caller passed
201
+ // something. Keep this strict so the wire shape matches
202
+ // the design and we don't accidentally bind a team
203
+ // channel to a single agent.
204
+ const rawAgentSession = (args.agentSession ?? '').trim();
205
+ let agentSession;
206
+ if (channelType === 'dm') {
207
+ if (rawAgentSession.length === 0) {
208
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, "agentSession is required when type='dm'");
209
+ }
210
+ agentSession = rawAgentSession;
211
+ }
212
+ else {
213
+ // channel: discard any caller-supplied agentSession.
214
+ agentSession = '';
215
+ }
216
+ // Phase A: type='channel' must specify a teamId; type='dm' may
217
+ // optionally carry a targetMemberId. Reject the contradicting cases.
218
+ const teamId = args.teamId?.trim() || undefined;
219
+ const projectId = args.projectId?.trim() || undefined;
220
+ const targetMemberId = args.targetMemberId?.trim() || undefined;
221
+ if (channelType === 'channel' && !teamId) {
222
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, "teamId is required when type='channel'");
223
+ }
224
+ if (channelType === 'dm' && teamId) {
225
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, "teamId must be omitted when type='dm'");
226
+ }
227
+ if (channelType === 'channel' && targetMemberId) {
228
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, "targetMemberId must be omitted when type='channel'");
229
+ }
230
+ // F2b (#333) — outer-ring tenant defense. type='channel' bindings
231
+ // must pass the membership check when one is wired. Pre-checks
232
+ // already confirmed `teamId` is present and non-empty by here.
233
+ if (channelType === 'channel' && teamId && this.validateTeamMembership) {
234
+ const isMember = this.validateTeamMembership(args.principal, teamId);
235
+ if (!isMember) {
236
+ throw new ChatError(CHAT_ERROR_CODES.FORBIDDEN_TEAM, 403, 'caller is not a member of the requested team', { teamId, userId: args.principal.userId });
237
+ }
238
+ }
239
+ const purpose = args.purpose?.trim();
240
+ if (purpose && purpose.length > this.config.maxPurposeChars) {
241
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, `purpose exceeds ${this.config.maxPurposeChars} characters`);
242
+ }
243
+ const row = this.channels.create({
244
+ agentSession,
245
+ ownerUserId: args.principal.userId,
246
+ name,
247
+ purpose: purpose || null,
248
+ type: channelType,
249
+ teamId: teamId ?? null,
250
+ projectId: projectId ?? null,
251
+ targetMemberId: targetMemberId ?? null,
252
+ nowMs: this.now(),
253
+ });
254
+ return this.toChannelDTO(row);
255
+ }
256
+ /**
257
+ * Phase B-2 (2026-05-17) — create a huddle (ad-hoc multi-agent group
258
+ * channel). Creates a `type='huddle'` channel row with no team
259
+ * binding, then inserts one row per member into
260
+ * `chat_channel_members`. The dispatcher uses that roster to fan out
261
+ * subsequent user messages to every member; agents whose session is
262
+ * in the outgoing message's `mentions[]` get a "must respond"
263
+ * prompt, others get an "optional" one.
264
+ *
265
+ * @param args - name, optional purpose, member roster, owning principal
266
+ * @returns The created huddle channel as a DTO, with `members` populated
267
+ * @throws {ChatError} `validation_error` (400) when name is empty/too long,
268
+ * purpose too long, or memberSessions is empty / has too many entries.
269
+ */
270
+ createHuddle(args) {
271
+ const name = (args.name ?? '').trim();
272
+ if (name.length === 0) {
273
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, 'name is required');
274
+ }
275
+ if (name.length > this.config.maxChannelNameChars) {
276
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, `name exceeds ${this.config.maxChannelNameChars} characters`);
277
+ }
278
+ const purpose = args.purpose?.trim();
279
+ if (purpose && purpose.length > this.config.maxPurposeChars) {
280
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, `purpose exceeds ${this.config.maxPurposeChars} characters`);
281
+ }
282
+ // Dedupe + trim member sessions. We accept any non-empty trimmed
283
+ // string here — actual agent existence is validated by the
284
+ // dispatcher when it tries to resolve the session at fan-out time.
285
+ const seen = new Set();
286
+ const members = [];
287
+ for (const raw of args.memberSessions ?? []) {
288
+ const s = (raw ?? '').trim();
289
+ if (!s)
290
+ continue;
291
+ if (seen.has(s))
292
+ continue;
293
+ seen.add(s);
294
+ members.push(s);
295
+ }
296
+ if (members.length === 0) {
297
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, 'memberSessions must include at least one agent session');
298
+ }
299
+ // Defensive upper bound — a 200-member huddle would tax both the
300
+ // dispatcher fan-out and downstream rate limits. The cap can be
301
+ // raised when we have a real use case.
302
+ const MAX_HUDDLE_MEMBERS = 50;
303
+ if (members.length > MAX_HUDDLE_MEMBERS) {
304
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, `huddle exceeds the ${MAX_HUDDLE_MEMBERS}-member cap`);
305
+ }
306
+ const nowMs = this.now();
307
+ const row = this.channels.create({
308
+ // Huddle isn't 1:1-bound — keep agent_session empty (same convention
309
+ // as type='channel'). No team_id either: huddles are ad-hoc groups,
310
+ // not team-scoped surfaces.
311
+ agentSession: '',
312
+ ownerUserId: args.principal.userId,
313
+ name,
314
+ purpose: purpose || null,
315
+ type: 'huddle',
316
+ teamId: null,
317
+ projectId: null,
318
+ targetMemberId: null,
319
+ nowMs,
320
+ });
321
+ // Insert membership rows. INSERT OR IGNORE is defensive against the
322
+ // dedupe above getting bypassed (e.g., when callers reuse this
323
+ // method via the relay adapter with raw input).
324
+ const memberStmt = this.db.prepare(`INSERT OR IGNORE INTO chat_channel_members
325
+ (channel_id, member_session, joined_at)
326
+ VALUES (?, ?, ?)`);
327
+ const insertMany = this.db.transaction((rows) => {
328
+ for (const s of rows)
329
+ memberStmt.run(row.id, s, nowMs);
330
+ });
331
+ insertMany(members);
332
+ return this.toChannelDTO(row);
333
+ }
334
+ /**
335
+ * Phase B-2 — list the members of a huddle channel. Returns an empty
336
+ * array (not an error) for non-huddle channels so consumers can call
337
+ * this unconditionally during channel rendering.
338
+ *
339
+ * @param channelId - The channel to enumerate
340
+ * @param principal - The caller; used to verify ownership
341
+ * @returns Array of `{ sessionName, joinedAt }` rows
342
+ * @throws {ChatError} `not_found` (404) when the channel doesn't exist or
343
+ * isn't owned by `principal`.
344
+ */
345
+ listHuddleMembers(channelId, principal) {
346
+ const row = this.requireOwnedChannel(channelId, principal);
347
+ if (row.type !== 'huddle')
348
+ return [];
349
+ return this.queryHuddleMembers(channelId);
350
+ }
351
+ /**
352
+ * Phase B-2 — dispatcher-facing roster lookup. Returns the session
353
+ * names of every member in a huddle, in `joined_at ASC` order.
354
+ * Unlike {@link listHuddleMembers} this is NOT principal-scoped:
355
+ * the dispatcher runs server-side and already holds the channel
356
+ * (it just persisted a message into it). Returns an empty array for
357
+ * non-huddle channels or unknown ids so the dispatcher's
358
+ * `members.length === 0` skip path stays clean.
359
+ *
360
+ * @param channelId - The huddle channel id
361
+ * @returns Array of agent session names
362
+ */
363
+ queryHuddleMembersForDispatch(channelId) {
364
+ return this.queryHuddleMembers(channelId).map((m) => m.sessionName);
365
+ }
366
+ /** Internal: read members straight from the DB (no ownership check). */
367
+ queryHuddleMembers(channelId) {
368
+ const rows = this.db
369
+ .prepare(`SELECT member_session AS sessionName, joined_at AS joinedAt
370
+ FROM chat_channel_members
371
+ WHERE channel_id = ?
372
+ ORDER BY joined_at ASC`)
373
+ .all(channelId);
374
+ return rows.map((r) => ({ sessionName: r.sessionName, joinedAt: r.joinedAt }));
375
+ }
376
+ /**
377
+ * Idempotent DM channel lookup-or-create for the /agents page.
378
+ *
379
+ * Returns the most-recent active DM channel owned by `principal.userId`
380
+ * and bound to `agentSession`; creates a new one when none exists. The
381
+ * caller is the human owner (auth principal), unlike
382
+ * {@link ensureChannelForLegacyConversation} which runs as `'system'`
383
+ * for server-internal bridge paths.
384
+ *
385
+ * Used by `POST /api/chat/channels/dm/ensure` so the /agents page can
386
+ * map "user clicked an agent in the directory" → "send/receive messages
387
+ * on this channel" without leaking duplicate DMs every time the page
388
+ * is reloaded.
389
+ *
390
+ * @param args - Lookup-or-create args
391
+ * @returns The channel DTO plus a `created` flag (true when a new row
392
+ * was inserted; false when an existing row was reused).
393
+ * @throws {ChatError} `validation_error` (400) on empty `agentSession`
394
+ * or oversize `name` / `purpose`.
395
+ */
396
+ ensureDmChannel(args) {
397
+ const agentSession = (args.agentSession ?? '').trim();
398
+ if (agentSession.length === 0) {
399
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, 'agentSession is required');
400
+ }
401
+ const existing = this.channels.findActiveDmByOwnerAndAgent(args.principal.userId, agentSession);
402
+ if (existing) {
403
+ return { channel: this.toChannelDTO(existing), created: false };
404
+ }
405
+ // Fall through to createChannel so the full validation + tenant
406
+ // checks (purpose length, name length, etc.) run consistently with
407
+ // the public POST /channels endpoint.
408
+ const channel = this.createChannel({
409
+ agentSession,
410
+ name: (args.name ?? agentSession).trim(),
411
+ purpose: args.purpose,
412
+ principal: args.principal,
413
+ type: 'dm',
414
+ });
415
+ return { channel, created: true };
416
+ }
417
+ /**
418
+ * Find-or-create the canonical `type='channel'` channel for a team.
419
+ *
420
+ * Backs the consolidated team-chat surface: opening a team's workspace
421
+ * deep-link must always land on a real channel, but nothing else
422
+ * auto-creates one. This is idempotent — if the team already has any
423
+ * active channel it is returned untouched; otherwise a single default
424
+ * channel (e.g. `#general`) is created. The same tenant/validation rules
425
+ * as the public {@link createChannel} apply (the caller must pass team
426
+ * membership when a `validateTeamMembership` check is wired).
427
+ *
428
+ * @param args - Ensure args
429
+ * @param args.teamId - The team to ensure a channel for (required)
430
+ * @param args.name - Display name for a freshly created channel; defaults to `#general`
431
+ * @param args.purpose - Optional channel purpose
432
+ * @param args.principal - The authenticated caller
433
+ * @returns The existing or freshly created channel, with `created` flag
434
+ * @throws {ChatError} `validation_error` (400) when `teamId` is blank
435
+ * @throws {ChatError} `forbidden_team` (403) when the caller fails the membership check
436
+ */
437
+ ensureTeamChannel(args) {
438
+ const teamId = (args.teamId ?? '').trim();
439
+ if (teamId.length === 0) {
440
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, 'teamId is required');
441
+ }
442
+ const existing = this.channels.findActiveChannelByTeam(teamId);
443
+ if (existing) {
444
+ return { channel: this.toChannelDTO(existing), created: false };
445
+ }
446
+ const channel = this.createChannel({
447
+ name: (args.name ?? '#general').trim(),
448
+ purpose: args.purpose,
449
+ principal: args.principal,
450
+ type: 'channel',
451
+ teamId,
452
+ });
453
+ return { channel, created: true };
454
+ }
455
+ /**
456
+ * Server-internal idempotent helper used by migration / bridge code to
457
+ * map a legacy conversationId (e.g. `slack-D0AC7-1234`, `web-conv-abc`)
458
+ * onto a chat-v2 channel row with the conversationId as the primary key.
459
+ *
460
+ * Unlike {@link createChannel}, this method:
461
+ * - Returns the existing channel when one already lives at the given
462
+ * id — idempotent for runtimes that call it before every recordTurn.
463
+ * - Accepts a synthetic owner (`'system'`) for paths where no human
464
+ * principal is on the call stack (PTY finish hooks, Slack inbound
465
+ * bridge, in-process runtime auto-route).
466
+ * - Sets `type='dm'` so the channel matches the conversation-per-thread
467
+ * legacy model the user approved (spec Option B).
468
+ *
469
+ * The `agent_already_bound` failure mode that existed in chat-v2 Phase A
470
+ * does not apply — the `uq_channel_agent_dm_active` index was dropped
471
+ * per the unified-chat-message-store spec exactly so this helper can
472
+ * lazy-create N concurrent DM channels for a single agent.
473
+ *
474
+ * @param args - Legacy bridge args
475
+ * @returns The existing or freshly created channel DTO
476
+ * @throws {ChatError} `validation_error` on missing id / agentSession
477
+ *
478
+ * @example
479
+ * ```typescript
480
+ * // Called from `routeInProcessResponseToChat` before `recordTurn`:
481
+ * const channel = chatV2.ensureChannelForLegacyConversation({
482
+ * conversationId: 'slack-D0AC7-1700000000.000111',
483
+ * agentSession: 'crewly-orc',
484
+ * });
485
+ * chatV2.recordTurn({ channelId: channel.id, ... });
486
+ * ```
487
+ */
488
+ ensureChannelForLegacyConversation(args) {
489
+ const conversationId = (args.conversationId ?? '').trim();
490
+ if (conversationId.length === 0) {
491
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, 'conversationId is required');
492
+ }
493
+ const agentSession = (args.agentSession ?? '').trim();
494
+ if (agentSession.length === 0) {
495
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, 'agentSession is required');
496
+ }
497
+ const existing = this.channels.getById(conversationId);
498
+ if (existing) {
499
+ return this.toChannelDTO(existing);
500
+ }
501
+ const name = (args.name ?? conversationId).trim();
502
+ const ownerUserId = (args.ownerUserId ?? 'system').trim();
503
+ const row = this.channels.create({
504
+ id: conversationId,
505
+ agentSession,
506
+ ownerUserId,
507
+ name,
508
+ purpose: null,
509
+ type: 'dm',
510
+ teamId: null,
511
+ projectId: null,
512
+ targetMemberId: null,
513
+ nowMs: this.now(),
514
+ });
515
+ return this.toChannelDTO(row);
516
+ }
517
+ /**
518
+ * Phase 5 of unified-chat-message-store spec — idempotent import of one
519
+ * legacy conversation file (`~/.crewly/chat/<conversationId>.json`)
520
+ * into the chat-v2 SQLite store. Each legacy message becomes one
521
+ * `chat_messages` row keyed by a deterministic `clientMessageId` so
522
+ * re-running the import is safe — the underlying `MessageStore`
523
+ * dedups on `(channel_id, clientMessageId)`.
524
+ *
525
+ * Designed as a service method (not a free function) so the CLI
526
+ * migration script can call it per file and so callers can unit-test
527
+ * the mapping in isolation.
528
+ *
529
+ * The mapping:
530
+ * - `conversation.id` → `chat_channels.id` (via
531
+ * {@link ensureChannelForLegacyConversation}). `agentSession`
532
+ * defaults to `'crewly-orc'` because every legacy conversation was
533
+ * a DM between the user and the orchestrator.
534
+ * - `messages[].from.type === 'user'` → `senderType: 'user'`
535
+ * - `messages[].from.type === 'orchestrator'` (or 'agent') →
536
+ * `senderType: 'agent'`, `senderId: 'crewly-orc'`
537
+ * - Anything else → `senderType: 'system'`, `senderId: 'system'`
538
+ * - `messages[].id` → `clientMessageId = 'legacy-' + msg.id` for
539
+ * stable idempotency across re-runs.
540
+ * - `messages[].metadata.source` (legacy slack/web) → carried
541
+ * through; `recordTurn`'s required outer `metadata.source` is set
542
+ * to `'system'` to identify the migration as the writer.
543
+ *
544
+ * @param input - Parsed legacy JSON (the entire file body)
545
+ * @returns Per-message outcome (imported vs deduped) + the channel id
546
+ *
547
+ * @example
548
+ * ```typescript
549
+ * const json = JSON.parse(await readFile(filePath, 'utf-8'));
550
+ * const result = chatV2.importLegacyConversation(json);
551
+ * console.log(`Imported ${result.imported} new, ${result.deduped} dedup`);
552
+ * ```
553
+ */
554
+ importLegacyConversation(input) {
555
+ if (!input?.conversation?.id) {
556
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, 'legacy conversation.id is required');
557
+ }
558
+ if (!Array.isArray(input.messages)) {
559
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, 'legacy messages must be an array');
560
+ }
561
+ const channel = this.ensureChannelForLegacyConversation({
562
+ conversationId: input.conversation.id,
563
+ agentSession: 'crewly-orc',
564
+ name: input.conversation.id,
565
+ });
566
+ let imported = 0;
567
+ let deduped = 0;
568
+ let skipped = 0;
569
+ const skippedReasons = [];
570
+ for (let i = 0; i < input.messages.length; i++) {
571
+ const msg = input.messages[i];
572
+ if (!msg?.id) {
573
+ skipped++;
574
+ skippedReasons.push({ index: i, reason: 'missing-id', id: msg?.id });
575
+ continue;
576
+ }
577
+ if (typeof msg.content !== 'string' || msg.content.length === 0) {
578
+ skipped++;
579
+ skippedReasons.push({ index: i, reason: 'empty-content', id: msg.id });
580
+ continue;
581
+ }
582
+ const fromType = (msg.from?.type ?? '').toLowerCase();
583
+ let senderType;
584
+ let senderId;
585
+ if (fromType === 'user') {
586
+ senderType = 'user';
587
+ senderId =
588
+ (typeof msg.metadata?.userId === 'string' && msg.metadata.userId) ||
589
+ msg.from?.name ||
590
+ 'legacy-user';
591
+ }
592
+ else if (fromType === 'agent' || fromType === 'orchestrator') {
593
+ senderType = 'agent';
594
+ senderId = 'crewly-orc';
595
+ }
596
+ else {
597
+ senderType = 'system';
598
+ senderId = 'system';
599
+ }
600
+ const result = this.recordTurn({
601
+ channelId: channel.id,
602
+ senderType,
603
+ senderId,
604
+ content: msg.content,
605
+ clientMessageId: `legacy-${msg.id}`,
606
+ metadata: {
607
+ source: 'system',
608
+ // Carry through legacy metadata for forensic completeness —
609
+ // future audits can still see "this row was originally a slack
610
+ // inbound" via metadata.legacySource etc.
611
+ legacySource: typeof msg.metadata?.source === 'string' ? msg.metadata.source : undefined,
612
+ legacyMessageId: msg.id,
613
+ legacyTimestamp: msg.timestamp,
614
+ },
615
+ });
616
+ if (result.deduped) {
617
+ deduped++;
618
+ }
619
+ else {
620
+ imported++;
621
+ }
622
+ }
623
+ if (skipped > 0) {
624
+ // Surface malformed legacy rows so the migration operator can
625
+ // decide whether to repair the source JSON before re-running, or
626
+ // accept that some history is unrecoverable. Without this log the
627
+ // earlier silent-skip behavior turned data-loss into a counter
628
+ // mismatch that nobody noticed. ChatV2Service has no injected
629
+ // logger; the migration runs as a CLI script so console output
630
+ // is the right sink.
631
+ // eslint-disable-next-line no-console
632
+ console.warn(`[chat-v2] importLegacyConversation: skipped ${skipped}/${input.messages.length} malformed row(s) in ${input.conversation.id}`, {
633
+ channelId: channel.id,
634
+ skipped,
635
+ totalRows: input.messages.length,
636
+ reasons: skippedReasons.slice(0, 10),
637
+ truncated: skippedReasons.length > 10,
638
+ });
639
+ }
640
+ return { channelId: channel.id, imported, deduped, skipped };
641
+ }
642
+ /**
643
+ * List channels owned by the caller.
644
+ *
645
+ * Phase C: extended with optional `type` + `teamId` filters so the
646
+ * channel-rail can request a focused slice (e.g. "channels in this
647
+ * workspace only") without paging the full owner-scoped list. An
648
+ * unknown `type` value is rejected as `validation_error`. Blank
649
+ * `teamId` is normalized to "no filter" so callers don't need to
650
+ * sanitize empty query strings.
651
+ *
652
+ * @param args - List args (principal + optional filters)
653
+ * @returns DTO-mapped channels
654
+ * @throws {ChatError} `validation_error` (400) when `type` is set
655
+ * to a value outside {@link CHAT_CHANNEL_TYPES}.
656
+ */
657
+ listChannels(args) {
658
+ if (args.type !== undefined && !CHAT_CHANNEL_TYPES.includes(args.type)) {
659
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, `unknown channel type: ${args.type}`);
660
+ }
661
+ // Treat blank teamId the same as omitted — saves the channel-rail FE
662
+ // from having to strip empty query strings before issuing the GET.
663
+ const teamIdFilter = args.teamId !== undefined && args.teamId.trim().length > 0
664
+ ? args.teamId
665
+ : undefined;
666
+ const rows = this.channels.listByOwner(args.principal.userId, {
667
+ includeArchived: args.includeArchived,
668
+ limit: args.limit,
669
+ type: args.type,
670
+ teamId: teamIdFilter,
671
+ });
672
+ // Surface bridged (Slack) threads alongside the user's own channels on
673
+ // the unfiltered list. They are owned by `'system'`, so listByOwner
674
+ // never returns them; merge + dedupe by id (the user's own row wins).
675
+ const includeBridged = (args.includeBridged ?? true) && args.type === undefined && teamIdFilter === undefined;
676
+ if (includeBridged) {
677
+ const seen = new Set(rows.map((r) => r.id));
678
+ const bridged = this.channels
679
+ .listBridged({ includeArchived: args.includeArchived, limit: args.limit })
680
+ .filter((r) => !seen.has(r.id));
681
+ return [...rows, ...bridged].map((r) => this.toChannelDTO(r));
682
+ }
683
+ return rows.map((r) => this.toChannelDTO(r));
684
+ }
685
+ /**
686
+ * Look up a single channel the caller owns.
687
+ *
688
+ * @param channelId - Channel id
689
+ * @param principal - Auth principal
690
+ * @returns DTO shape
691
+ * @throws {ChatError} `channel_not_found` (404) if the caller doesn't own it
692
+ */
693
+ getChannel(channelId, principal) {
694
+ const row = this.requireOwnedChannel(channelId, principal);
695
+ return this.toChannelDTO(row);
696
+ }
697
+ /**
698
+ * Archive a channel. Returns true if a row was modified.
699
+ *
700
+ * @param channelId - Channel id
701
+ * @param principal - Auth principal
702
+ * @returns True if newly archived, false if already archived
703
+ * @throws {ChatError} `channel_not_found` (404)
704
+ */
705
+ archiveChannel(channelId, principal) {
706
+ this.requireOwnedChannel(channelId, principal);
707
+ return this.channels.archive(channelId, this.now());
708
+ }
709
+ /**
710
+ * Phase 6.0b — clear the `archived_at` flag on a channel. Inverse of
711
+ * {@link archiveChannel}; required to retire the legacy
712
+ * `unarchiveConversation` route.
713
+ *
714
+ * @param channelId - The channel to unarchive
715
+ * @param principal - Auth principal (must own the channel)
716
+ * @returns True if newly unarchived, false if already active
717
+ * @throws {ChatError} `channel_not_found` (404)
718
+ */
719
+ unarchiveChannel(channelId, principal) {
720
+ this.requireOwnedChannel(channelId, principal);
721
+ return this.channels.unarchive(channelId);
722
+ }
723
+ /**
724
+ * Phase 6.0b — rename a channel. Replaces the legacy
725
+ * `updateConversationTitle` route. Server validates the same name
726
+ * constraints as `createChannel`.
727
+ *
728
+ * @param channelId - The channel to rename
729
+ * @param name - New name (trimmed, ≤ maxChannelNameChars)
730
+ * @param principal - Auth principal (must own the channel)
731
+ * @returns The renamed channel DTO
732
+ * @throws {ChatError} `validation_error` (400) on empty / oversize name,
733
+ * `channel_not_found` (404)
734
+ */
735
+ renameChannel(channelId, name, principal) {
736
+ const trimmed = (name ?? '').trim();
737
+ if (trimmed.length === 0) {
738
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, 'name is required');
739
+ }
740
+ if (trimmed.length > this.config.maxChannelNameChars) {
741
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, `name exceeds ${this.config.maxChannelNameChars} characters`);
742
+ }
743
+ const row = this.requireOwnedChannel(channelId, principal);
744
+ this.channels.rename(channelId, trimmed);
745
+ return this.toChannelDTO({ ...row, name: trimmed });
746
+ }
747
+ /**
748
+ * Phase 6.0b — hard-delete a channel and all its messages. Distinct
749
+ * from {@link archiveChannel} (soft delete). Replaces the legacy
750
+ * `deleteConversation` route. Uses SQLite FK cascade so the row
751
+ * deletion atomically removes child messages.
752
+ *
753
+ * @param channelId - The channel to delete
754
+ * @param principal - Auth principal (must own the channel)
755
+ * @returns True if removed, false if the channel didn't exist
756
+ * @throws {ChatError} `channel_not_found` (404)
757
+ */
758
+ deleteChannel(channelId, principal) {
759
+ this.requireOwnedChannel(channelId, principal);
760
+ return this.channels.hardDelete(channelId);
761
+ }
762
+ /**
763
+ * Phase 6.0b — delete all messages in a channel while keeping the
764
+ * channel row. Replaces the legacy `clearConversation` route. Useful
765
+ * when the user wants a "fresh start" without losing the channel
766
+ * itself (and its bookkeeping like `agent_session` binding).
767
+ *
768
+ * @param channelId - The channel to clear
769
+ * @param principal - Auth principal (must own the channel)
770
+ * @returns Number of messages deleted
771
+ * @throws {ChatError} `channel_not_found` (404)
772
+ */
773
+ clearChannel(channelId, principal) {
774
+ this.requireOwnedChannel(channelId, principal);
775
+ return this.messages.deleteAllByChannel(channelId);
776
+ }
777
+ /**
778
+ * Phase 6.0b — count messages in a single channel. Replaces the
779
+ * legacy `getMessageCount` filtered to one conversation.
780
+ *
781
+ * @param channelId - The channel to count
782
+ * @param principal - Auth principal (must own the channel)
783
+ * @returns Message count (0 for empty channels)
784
+ * @throws {ChatError} `channel_not_found` (404)
785
+ */
786
+ countChannelMessages(channelId, principal) {
787
+ this.requireOwnedChannel(channelId, principal);
788
+ return this.messages.count(channelId);
789
+ }
790
+ /**
791
+ * Returns the text of recent OWNER-authored messages (`sender_type='user'`)
792
+ * across ALL channels, newest first, created at or after `sinceMs`.
793
+ *
794
+ * Purpose: the Commitment Approval Guard (2026-06-02 autonomy incident) needs
795
+ * an UNFAKEABLE signal of owner approval — the orchestrator can fabricate a
796
+ * WorkItem title claiming "owner approved", but it cannot fabricate the
797
+ * owner's actual chat history. This is a deliberate cross-channel read (no
798
+ * per-channel authorization) used only server-side by the guard; it returns
799
+ * message TEXT only, never agent/system messages.
800
+ *
801
+ * @param sinceMs - Unix epoch ms; only messages created at/after this are returned.
802
+ * @param limit - Max messages to return (default 100, capped at 500).
803
+ * @returns Owner message contents, newest first.
804
+ */
805
+ getRecentOwnerMessageContents(sinceMs, limit = 100) {
806
+ const capped = Math.min(Math.max(1, Math.floor(limit)), 500);
807
+ const rows = this.db
808
+ .prepare(`SELECT content FROM chat_messages
809
+ WHERE sender_type = 'user' AND created_at >= ?
810
+ ORDER BY created_at DESC
811
+ LIMIT ?`)
812
+ .all(sinceMs, capped);
813
+ return rows.map((r) => r.content);
814
+ }
815
+ // -------------------------------------------------------------------------
816
+ // Message operations
817
+ // -------------------------------------------------------------------------
818
+ /**
819
+ * Persist a message sent by the caller. The server decides `sender_type`
820
+ * and `sender_id` from the auth principal; client fields are ignored.
821
+ *
822
+ * @param args - Send args
823
+ * @returns The persisted message as a DTO (fresh or deduped)
824
+ * @throws {ChatError} on validation, authorization, or size limit
825
+ */
826
+ sendMessage(args) {
827
+ const row = this.requireReadableChannel(args.channelId, args.principal);
828
+ const content = args.content ?? '';
829
+ if (content.length === 0) {
830
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, 'content is required');
831
+ }
832
+ const byteLen = Buffer.byteLength(content, 'utf-8');
833
+ if (byteLen > this.config.maxMessageBytes) {
834
+ throw new ChatError(CHAT_ERROR_CODES.PAYLOAD_TOO_LARGE, 413, `content exceeds max bytes (${this.config.maxMessageBytes})`, { maxBytes: this.config.maxMessageBytes, yourBytes: byteLen });
835
+ }
836
+ const contentType = args.contentType ?? 'markdown';
837
+ if (!CHAT_CONTENT_TYPES.includes(contentType)) {
838
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, `unknown contentType: ${contentType}`);
839
+ }
840
+ // Agents/users cannot self-tag as system.
841
+ if (contentType === 'system_note' && this.resolveSender(row, args.principal).type !== 'system') {
842
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, 'system_note can only be emitted server-side');
843
+ }
844
+ const { type: senderType, id: senderId } = this.resolveSender(row, args.principal);
845
+ // Phase A: validate mentions array. Bounded count + JSON-byte cap so
846
+ // a misbehaving client cannot blow past the spec §3.2 1KB ceiling.
847
+ const mentions = this.validateMentions(args.mentions);
848
+ // Phase A: validate threadId — must reference an existing message in
849
+ // this channel; refusing dangling thread refs prevents orphan replies
850
+ // and contains UX confusion if the FE composes against a stale id.
851
+ const threadId = this.validateThreadId(args.threadId, args.channelId);
852
+ const { row: persisted } = this.messages.insert({
853
+ channelId: args.channelId,
854
+ senderType,
855
+ senderId,
856
+ content,
857
+ contentType,
858
+ clientMessageId: args.clientMessageId,
859
+ mentions,
860
+ threadId,
861
+ nowMs: this.now(),
862
+ });
863
+ const dto = this.toMessageDTO(persisted, args.attachments ?? []);
864
+ // Phase 6c: broadcast so the WebSocket gateway (and any other
865
+ // in-process subscribers) can fan the new message out to connected
866
+ // clients. The legacy ChatService.EventEmitter contract is now
867
+ // owned by chat-v2 directly.
868
+ this.emit('chat_message', dto);
869
+ return dto;
870
+ }
871
+ /**
872
+ * Canonical server-internal write entry for chat messages.
873
+ *
874
+ * Unlike {@link sendMessage}, which derives `sender_type` / `sender_id`
875
+ * from an authenticated request principal, `recordTurn` is the path
876
+ * used by runtimes, controllers, and bridges that have already
877
+ * resolved exactly who the sender is — e.g.:
878
+ *
879
+ * - In-process agent runtime finishing a turn
880
+ * - PTY runtime emitting a complete reply
881
+ * - Slack inbound bridge persisting a user DM
882
+ * - `/slack/send` controller persisting the agent's outbound reply
883
+ *
884
+ * Per spec `2026-05-14-unified-chat-message-store.md`, every chat
885
+ * write in the system funnels through this method. No caller should
886
+ * write to {@link MessageStore} directly; no caller should reach
887
+ * into legacy {@link ChatService} (Phase 6 retires it).
888
+ *
889
+ * Idempotent via `clientMessageId` — the underlying store dedups by
890
+ * `(channel_id, clientMessageId)` and returns the existing row with
891
+ * `deduped=true` instead of inserting a duplicate.
892
+ *
893
+ * @param input - Turn payload (channel, sender, content, metadata)
894
+ * @returns The persisted message DTO + dedupe flag
895
+ * @throws {ChatError} `channel_not_found` (404) if the channel is missing
896
+ * @throws {ChatError} `validation_error` (400) on empty content or invalid contentType
897
+ * @throws {ChatError} `payload_too_large` (413) if content exceeds maxMessageBytes
898
+ *
899
+ * @example
900
+ * ```typescript
901
+ * const { message, deduped } = chatV2.recordTurn({
902
+ * channelId: 'slack-D0AC7-1234',
903
+ * senderType: 'agent',
904
+ * senderId: 'crewly-orc',
905
+ * content: 'Hello!',
906
+ * clientMessageId: 'agent-finish-2026-05-14T22:30:00Z',
907
+ * metadata: {
908
+ * source: 'in-process-runtime',
909
+ * runtime: 'crewly-agent',
910
+ * slackChannelId: 'D0AC7',
911
+ * slackThreadTs: '1234',
912
+ * },
913
+ * });
914
+ * ```
915
+ */
916
+ recordTurn(input) {
917
+ const content = input.content ?? '';
918
+ if (content.length === 0) {
919
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, 'content is required');
920
+ }
921
+ const byteLen = Buffer.byteLength(content, 'utf-8');
922
+ if (byteLen > this.config.maxMessageBytes) {
923
+ throw new ChatError(CHAT_ERROR_CODES.PAYLOAD_TOO_LARGE, 413, `content exceeds max bytes (${this.config.maxMessageBytes})`, { maxBytes: this.config.maxMessageBytes, yourBytes: byteLen });
924
+ }
925
+ const contentType = input.contentType ?? 'markdown';
926
+ if (!CHAT_CONTENT_TYPES.includes(contentType)) {
927
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, `unknown contentType: ${contentType}`);
928
+ }
929
+ if (!CHAT_SENDER_TYPES.includes(input.senderType)) {
930
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, `unknown senderType: ${input.senderType}`);
931
+ }
932
+ if (!input.senderId || input.senderId.length === 0) {
933
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, 'senderId is required');
934
+ }
935
+ const mentions = this.validateMentions(input.mentions);
936
+ const threadId = this.validateThreadId(input.threadId, input.channelId);
937
+ // `metadata.source` is the audit-trail discriminator that lets
938
+ // future tooling tell "this message came from PTY" vs "from
939
+ // in-process runtime" vs "from /slack/send" without parsing
940
+ // content. Spec success criterion #4 depends on this tag being
941
+ // present for every recordTurn caller.
942
+ const metadata = { ...(input.metadata ?? {}) };
943
+ if (!metadata.source) {
944
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, 'metadata.source is required for recordTurn (audit trail)');
945
+ }
946
+ if (!RECORD_TURN_SOURCES.includes(metadata.source)) {
947
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, `unknown metadata.source: ${String(metadata.source)}`, { allowed: RECORD_TURN_SOURCES });
948
+ }
949
+ const { row: persisted, deduped } = this.messages.insert({
950
+ channelId: input.channelId,
951
+ senderType: input.senderType,
952
+ senderId: input.senderId,
953
+ content,
954
+ contentType,
955
+ clientMessageId: input.clientMessageId,
956
+ mentions,
957
+ threadId,
958
+ metadata,
959
+ nowMs: this.now(),
960
+ });
961
+ const dto = this.toMessageDTO(persisted, []);
962
+ // Phase 6c: emit only for freshly inserted rows. Skipping dedup hits
963
+ // prevents replay-loop subscribers from seeing the same message twice
964
+ // on idempotent retries.
965
+ if (!deduped) {
966
+ this.emit('chat_message', dto);
967
+ }
968
+ return { message: dto, deduped };
969
+ }
970
+ /**
971
+ * Phase A — validate the mentions array passed to sendMessage.
972
+ * Returns the cleaned array (or undefined when input is empty/missing).
973
+ *
974
+ * @param raw - The raw `mentions` field from the request body
975
+ * @returns Validated mention IDs (caller passes to messageStore)
976
+ * @throws {ChatError} `validation_error` (400) on type / size / count violation
977
+ */
978
+ validateMentions(raw) {
979
+ if (raw === undefined || raw === null)
980
+ return undefined;
981
+ if (!Array.isArray(raw)) {
982
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, 'mentions must be an array');
983
+ }
984
+ if (raw.length === 0)
985
+ return undefined;
986
+ if (raw.length > ChatV2Service.MAX_MENTIONS_PER_MESSAGE) {
987
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, `mentions exceeds max count (${ChatV2Service.MAX_MENTIONS_PER_MESSAGE})`);
988
+ }
989
+ const cleaned = [];
990
+ for (const item of raw) {
991
+ if (typeof item !== 'string') {
992
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, 'mentions entries must be strings');
993
+ }
994
+ const trimmed = item.trim();
995
+ if (trimmed.length === 0) {
996
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, 'mentions entries must be non-empty');
997
+ }
998
+ cleaned.push(trimmed);
999
+ }
1000
+ // Bound the JSON size — spec §3.2 caps mentions JSON at 1KB.
1001
+ const jsonByteLen = Buffer.byteLength(JSON.stringify(cleaned), 'utf-8');
1002
+ if (jsonByteLen > ChatV2Service.MAX_MENTIONS_JSON_BYTES) {
1003
+ throw new ChatError(CHAT_ERROR_CODES.PAYLOAD_TOO_LARGE, 413, `mentions JSON exceeds max bytes (${ChatV2Service.MAX_MENTIONS_JSON_BYTES})`);
1004
+ }
1005
+ return cleaned;
1006
+ }
1007
+ /**
1008
+ * Phase A — validate the threadId passed to sendMessage.
1009
+ *
1010
+ * Returns the original threadId if it's a valid reference to a message
1011
+ * in the same channel, or undefined when input is empty/missing.
1012
+ * Refuses dangling references and cross-channel thread roots.
1013
+ *
1014
+ * @param raw - The raw `threadId` field from the request body
1015
+ * @param channelId - The channel this message will be inserted into
1016
+ * @returns Validated thread root id (caller passes to messageStore)
1017
+ * @throws {ChatError} `validation_error` (400) when invalid
1018
+ */
1019
+ validateThreadId(raw, channelId) {
1020
+ if (raw === undefined || raw === null)
1021
+ return undefined;
1022
+ if (typeof raw !== 'string') {
1023
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, 'threadId must be a string');
1024
+ }
1025
+ const trimmed = raw.trim();
1026
+ if (trimmed.length === 0)
1027
+ return undefined;
1028
+ const root = this.messages.getById(trimmed);
1029
+ if (!root) {
1030
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, `threadId references a non-existent message`, { threadId: trimmed });
1031
+ }
1032
+ if (root.channel_id !== channelId) {
1033
+ // Returning validation_error (not channel_not_found) because the
1034
+ // caller's intent IS valid — they're just pointing at the wrong
1035
+ // channel's thread root. Distinct error helps debugging.
1036
+ throw new ChatError(CHAT_ERROR_CODES.VALIDATION, 400, `threadId belongs to a different channel`, { threadId: trimmed, expectedChannelId: channelId });
1037
+ }
1038
+ return trimmed;
1039
+ }
1040
+ /**
1041
+ * Page messages for a channel the caller may read.
1042
+ *
1043
+ * @param args - List args
1044
+ * @returns A pagination envelope
1045
+ * @throws {ChatError} on auth or invalid cursor
1046
+ */
1047
+ listMessages(args) {
1048
+ this.requireReadableChannel(args.channelId, args.principal);
1049
+ const page = this.messages.listByChannel(args.channelId, {
1050
+ cursor: args.cursor ?? null,
1051
+ limit: args.limit,
1052
+ direction: args.direction,
1053
+ });
1054
+ // Slack-style thread reply counts: one aggregate query for the whole
1055
+ // channel, then attach `replyCount`/`lastReplyAt` to the root-message
1056
+ // DTOs on this page. Root messages are those whose own `threadId` is
1057
+ // unset; replies never carry a count.
1058
+ const threadSummary = this.messages.threadReplySummary(args.channelId);
1059
+ const messages = page.rows.map((r) => {
1060
+ const dto = this.toMessageDTO(r, []);
1061
+ if (dto.threadId === undefined) {
1062
+ const summary = threadSummary.get(dto.id);
1063
+ if (summary) {
1064
+ dto.replyCount = summary.replyCount;
1065
+ dto.lastReplyAt = new Date(summary.lastReplyAtMs).toISOString();
1066
+ }
1067
+ }
1068
+ return dto;
1069
+ });
1070
+ return {
1071
+ channelId: args.channelId,
1072
+ messages,
1073
+ nextCursor: page.nextCursor,
1074
+ prevCursor: page.prevCursor,
1075
+ };
1076
+ }
1077
+ // -------------------------------------------------------------------------
1078
+ // Authorization helpers (§7.2)
1079
+ // -------------------------------------------------------------------------
1080
+ /** Fetch the channel and enforce caller = owner. */
1081
+ requireOwnedChannel(channelId, principal) {
1082
+ const row = this.channels.getById(channelId);
1083
+ if (!row) {
1084
+ throw new ChatError(CHAT_ERROR_CODES.CHANNEL_NOT_FOUND, 404, 'Channel not found');
1085
+ }
1086
+ if (row.owner_user_id !== principal.userId) {
1087
+ throw new ChatError(CHAT_ERROR_CODES.CHANNEL_NOT_FOUND, 404, 'Channel not found');
1088
+ }
1089
+ return row;
1090
+ }
1091
+ /**
1092
+ * Fetch the channel and allow if the caller is the owner OR the bound agent
1093
+ * OR the channel is a shared bridged Slack conversation.
1094
+ *
1095
+ * Slack-bridged channels (`slack-…`, persisted under the synthetic `'system'`
1096
+ * owner by the inbound bridge) belong to no single Crewly user — exactly like
1097
+ * {@link ChannelStore.listBridged} surfaces them in the consolidated list
1098
+ * regardless of owner, this lets the web user READ them (e.g. the team-chat
1099
+ * surface merges them inline into the Orchestrator timeline). Writes are still
1100
+ * gated downstream by {@link resolveSender}, which throws `forbidden` unless
1101
+ * the caller is the owner or bound agent — so this read allowance does not let
1102
+ * a user post into a bridged channel.
1103
+ *
1104
+ * Used for read + send; agents must always be acting from their own session.
1105
+ */
1106
+ requireReadableChannel(channelId, principal) {
1107
+ const row = this.channels.getById(channelId);
1108
+ if (!row) {
1109
+ throw new ChatError(CHAT_ERROR_CODES.CHANNEL_NOT_FOUND, 404, 'Channel not found');
1110
+ }
1111
+ const isOwner = row.owner_user_id === principal.userId;
1112
+ const isBoundAgent = !!principal.agentSession && principal.agentSession === row.agent_session;
1113
+ const isSharedBridged = row.id.startsWith('slack-');
1114
+ if (!isOwner && !isBoundAgent && !isSharedBridged) {
1115
+ throw new ChatError(CHAT_ERROR_CODES.CHANNEL_NOT_FOUND, 404, 'Channel not found');
1116
+ }
1117
+ if (row.archived_at) {
1118
+ throw new ChatError(CHAT_ERROR_CODES.CHANNEL_ARCHIVED, 404, 'Channel is archived');
1119
+ }
1120
+ return row;
1121
+ }
1122
+ /** Decide sender_type + sender_id for a message based on principal + channel. */
1123
+ resolveSender(channel, principal) {
1124
+ if (principal.agentSession && principal.agentSession === channel.agent_session) {
1125
+ return { type: 'agent', id: principal.agentSession };
1126
+ }
1127
+ if (principal.userId === channel.owner_user_id) {
1128
+ return { type: 'user', id: principal.userId };
1129
+ }
1130
+ // Server-minted system messages go through an internal path, not this one.
1131
+ throw new ChatError(CHAT_ERROR_CODES.FORBIDDEN, 403, 'Principal cannot send in this channel');
1132
+ }
1133
+ // -------------------------------------------------------------------------
1134
+ // DTO mappers
1135
+ // -------------------------------------------------------------------------
1136
+ /** Map a channel row + live presence into the wire DTO. */
1137
+ toChannelDTO(row) {
1138
+ // Phase B backwards-compat: legacy rows (pre-migration) lack `type` /
1139
+ // team scope fields. The migration backfills `type='dm'` for existing
1140
+ // rows; here we defend the in-memory path against rows that may not
1141
+ // yet have the column populated (e.g. mid-migration test runs).
1142
+ const channelType = row.type ?? 'dm';
1143
+ const presenceSource = channelType === 'dm' ? row.agent_session : '';
1144
+ const presence = (this.presence ?? DEFAULT_PRESENCE)(presenceSource);
1145
+ return {
1146
+ id: row.id,
1147
+ agentSession: row.agent_session,
1148
+ name: row.name,
1149
+ purpose: row.purpose ?? undefined,
1150
+ createdAt: row.created_at,
1151
+ archivedAt: row.archived_at ?? null,
1152
+ lastMessageAt: row.last_message_at ?? null,
1153
+ agentPresence: {
1154
+ status: presence.status,
1155
+ lastSeenAt: presence.lastSeenAt,
1156
+ },
1157
+ type: channelType,
1158
+ teamId: row.team_id ?? undefined,
1159
+ projectId: row.project_id ?? undefined,
1160
+ targetMemberId: row.target_member_id ?? undefined,
1161
+ // Phase B-2: huddle channels surface their roster inline so the
1162
+ // Portal can render member avatars without a second round-trip.
1163
+ // Non-huddle rows leave this undefined.
1164
+ ...(channelType === 'huddle'
1165
+ ? { members: this.queryHuddleMembers(row.id) }
1166
+ : {}),
1167
+ };
1168
+ }
1169
+ /** Map a message row to the wire DTO. Attachments passed in by the caller. */
1170
+ toMessageDTO(row, attachments) {
1171
+ let metadata;
1172
+ if (row.metadata) {
1173
+ try {
1174
+ metadata = JSON.parse(row.metadata);
1175
+ }
1176
+ catch {
1177
+ metadata = undefined;
1178
+ }
1179
+ }
1180
+ // Phase B backwards-compat: parse mentions from JSON-encoded array
1181
+ // column. Legacy rows (pre-migration) have null; we surface as
1182
+ // empty array so the wire contract `mentions: string[]` is never
1183
+ // violated. A malformed JSON column also falls back to `[]` rather
1184
+ // than throwing — the wire contract is the priority.
1185
+ let mentions = [];
1186
+ if (row.mentions) {
1187
+ try {
1188
+ const parsed = JSON.parse(row.mentions);
1189
+ if (Array.isArray(parsed) && parsed.every((x) => typeof x === 'string')) {
1190
+ mentions = parsed;
1191
+ }
1192
+ }
1193
+ catch {
1194
+ mentions = [];
1195
+ }
1196
+ }
1197
+ return {
1198
+ id: row.id,
1199
+ channelId: row.channel_id,
1200
+ seq: row.seq,
1201
+ senderType: row.sender_type,
1202
+ senderId: row.sender_id,
1203
+ content: row.content,
1204
+ contentType: row.content_type,
1205
+ createdAt: row.created_at,
1206
+ attachments,
1207
+ metadata,
1208
+ mentions,
1209
+ threadId: row.thread_id ?? undefined,
1210
+ };
1211
+ }
1212
+ }
1213
+ //# sourceMappingURL=chat-v2.service.js.map