crewly 1.13.0 → 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 (597) 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/monitoring/terminal.controller.d.ts.map +1 -1
  27. package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js +18 -3
  28. package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js.map +1 -1
  29. package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
  30. package/dist/backend/backend/src/controllers/slack/slack.controller.js +18 -15
  31. package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
  32. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts +6 -1
  33. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts.map +1 -1
  34. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js +19 -10
  35. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
  36. package/dist/backend/backend/src/index.d.ts.map +1 -1
  37. package/dist/backend/backend/src/index.js +20 -0
  38. package/dist/backend/backend/src/index.js.map +1 -1
  39. package/dist/backend/backend/src/scripts/stamp-build.d.ts +25 -0
  40. package/dist/backend/backend/src/scripts/stamp-build.d.ts.map +1 -0
  41. package/dist/backend/backend/src/scripts/stamp-build.js +48 -0
  42. package/dist/backend/backend/src/scripts/stamp-build.js.map +1 -0
  43. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  44. package/dist/backend/backend/src/services/agent/agent-registration.service.js +25 -0
  45. package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
  46. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -1
  47. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +9 -1
  48. package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -1
  49. package/dist/backend/backend/src/services/agent/crewly-agent/types.d.ts +42 -3
  50. package/dist/backend/backend/src/services/agent/crewly-agent/types.d.ts.map +1 -1
  51. package/dist/backend/backend/src/services/agent/crewly-agent/types.js +22 -1
  52. package/dist/backend/backend/src/services/agent/crewly-agent/types.js.map +1 -1
  53. package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.d.ts +15 -0
  54. package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.d.ts.map +1 -1
  55. package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js +40 -0
  56. package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js.map +1 -1
  57. package/dist/backend/backend/src/services/orchestrator/orchestrator-status.service.d.ts +28 -3
  58. package/dist/backend/backend/src/services/orchestrator/orchestrator-status.service.d.ts.map +1 -1
  59. package/dist/backend/backend/src/services/orchestrator/orchestrator-status.service.js +65 -8
  60. package/dist/backend/backend/src/services/orchestrator/orchestrator-status.service.js.map +1 -1
  61. package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts +214 -4
  62. package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts.map +1 -1
  63. package/dist/backend/backend/src/services/reconciler/reconcile-rules.js +330 -48
  64. package/dist/backend/backend/src/services/reconciler/reconcile-rules.js.map +1 -1
  65. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts +13 -1
  66. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
  67. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +84 -5
  68. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
  69. package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts +14 -0
  70. package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts.map +1 -1
  71. package/dist/backend/backend/src/services/reconciler/reconciler.service.js +77 -3
  72. package/dist/backend/backend/src/services/reconciler/reconciler.service.js.map +1 -1
  73. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +152 -7
  74. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  75. package/dist/backend/backend/src/services/task-pool/task-pool.service.js +268 -36
  76. package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  77. package/dist/backend/backend/src/services/v3/escalation-router.service.js +2 -2
  78. package/dist/backend/backend/src/services/v3/escalation-router.service.js.map +1 -1
  79. package/dist/backend/backend/src/services/v3/request-sla.subscriber.d.ts.map +1 -1
  80. package/dist/backend/backend/src/services/v3/request-sla.subscriber.js +39 -2
  81. package/dist/backend/backend/src/services/v3/request-sla.subscriber.js.map +1 -1
  82. package/dist/backend/backend/src/types/v2/index.d.ts +1 -1
  83. package/dist/backend/backend/src/types/v2/index.d.ts.map +1 -1
  84. package/dist/backend/backend/src/types/v2/index.js +1 -1
  85. package/dist/backend/backend/src/types/v2/index.js.map +1 -1
  86. package/dist/backend/backend/src/types/v2/work-item.types.d.ts +181 -1
  87. package/dist/backend/backend/src/types/v2/work-item.types.d.ts.map +1 -1
  88. package/dist/backend/backend/src/types/v2/work-item.types.js +104 -17
  89. package/dist/backend/backend/src/types/v2/work-item.types.js.map +1 -1
  90. package/dist/backend/backend/src/utils/build-provenance.d.ts +150 -0
  91. package/dist/backend/backend/src/utils/build-provenance.d.ts.map +1 -0
  92. package/dist/backend/backend/src/utils/build-provenance.js +211 -0
  93. package/dist/backend/backend/src/utils/build-provenance.js.map +1 -0
  94. package/dist/backend/build-info.json +4 -0
  95. package/dist/backend/config/vitest-exclude.constants.d.ts +53 -0
  96. package/dist/backend/config/vitest-exclude.constants.d.ts.map +1 -0
  97. package/dist/backend/config/vitest-exclude.constants.js +55 -0
  98. package/dist/backend/config/vitest-exclude.constants.js.map +1 -0
  99. package/dist/cli/backend/src/services/agent/active-work-briefing.service.d.ts +538 -0
  100. package/dist/cli/backend/src/services/agent/active-work-briefing.service.d.ts.map +1 -0
  101. package/dist/cli/backend/src/services/agent/active-work-briefing.service.js +802 -0
  102. package/dist/cli/backend/src/services/agent/active-work-briefing.service.js.map +1 -0
  103. package/dist/cli/backend/src/services/agent/agent-heartbeat.service.d.ts +240 -0
  104. package/dist/cli/backend/src/services/agent/agent-heartbeat.service.d.ts.map +1 -0
  105. package/dist/cli/backend/src/services/agent/agent-heartbeat.service.js +524 -0
  106. package/dist/cli/backend/src/services/agent/agent-heartbeat.service.js.map +1 -0
  107. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts +629 -0
  108. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -0
  109. package/dist/cli/backend/src/services/agent/agent-registration.service.js +4548 -0
  110. package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -0
  111. package/dist/cli/backend/src/services/agent/agent-suspend.service.d.ts +118 -0
  112. package/dist/cli/backend/src/services/agent/agent-suspend.service.d.ts.map +1 -0
  113. package/dist/cli/backend/src/services/agent/agent-suspend.service.js +304 -0
  114. package/dist/cli/backend/src/services/agent/agent-suspend.service.js.map +1 -0
  115. package/dist/cli/backend/src/services/agent/auditor-scheduler.service.d.ts +196 -0
  116. package/dist/cli/backend/src/services/agent/auditor-scheduler.service.d.ts.map +1 -0
  117. package/dist/cli/backend/src/services/agent/auditor-scheduler.service.js +504 -0
  118. package/dist/cli/backend/src/services/agent/auditor-scheduler.service.js.map +1 -0
  119. package/dist/cli/backend/src/services/agent/claude-runtime.service.d.ts +97 -0
  120. package/dist/cli/backend/src/services/agent/claude-runtime.service.d.ts.map +1 -0
  121. package/dist/cli/backend/src/services/agent/claude-runtime.service.js +334 -0
  122. package/dist/cli/backend/src/services/agent/claude-runtime.service.js.map +1 -0
  123. package/dist/cli/backend/src/services/agent/codex-runtime.service.d.ts +47 -0
  124. package/dist/cli/backend/src/services/agent/codex-runtime.service.d.ts.map +1 -0
  125. package/dist/cli/backend/src/services/agent/codex-runtime.service.js +112 -0
  126. package/dist/cli/backend/src/services/agent/codex-runtime.service.js.map +1 -0
  127. package/dist/cli/backend/src/services/agent/context-window-monitor.service.d.ts +323 -0
  128. package/dist/cli/backend/src/services/agent/context-window-monitor.service.d.ts.map +1 -0
  129. package/dist/cli/backend/src/services/agent/context-window-monitor.service.js +1056 -0
  130. package/dist/cli/backend/src/services/agent/context-window-monitor.service.js.map +1 -0
  131. package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts +198 -0
  132. package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -0
  133. package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +816 -0
  134. package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -0
  135. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-log-buffer.d.ts +155 -0
  136. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-log-buffer.d.ts.map +1 -0
  137. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-log-buffer.js +272 -0
  138. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-log-buffer.js.map +1 -0
  139. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.d.ts +91 -0
  140. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.d.ts.map +1 -0
  141. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.js +120 -0
  142. package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.js.map +1 -0
  143. package/dist/cli/backend/src/services/agent/crewly-agent/types.d.ts +536 -0
  144. package/dist/cli/backend/src/services/agent/crewly-agent/types.d.ts.map +1 -0
  145. package/dist/cli/backend/src/services/agent/crewly-agent/types.js +282 -0
  146. package/dist/cli/backend/src/services/agent/crewly-agent/types.js.map +1 -0
  147. package/dist/cli/backend/src/services/agent/disk-cleanup.service.d.ts +100 -0
  148. package/dist/cli/backend/src/services/agent/disk-cleanup.service.d.ts.map +1 -0
  149. package/dist/cli/backend/src/services/agent/disk-cleanup.service.js +149 -0
  150. package/dist/cli/backend/src/services/agent/disk-cleanup.service.js.map +1 -0
  151. package/dist/cli/backend/src/services/agent/gemini-runtime.service.d.ts +130 -0
  152. package/dist/cli/backend/src/services/agent/gemini-runtime.service.d.ts.map +1 -0
  153. package/dist/cli/backend/src/services/agent/gemini-runtime.service.js +758 -0
  154. package/dist/cli/backend/src/services/agent/gemini-runtime.service.js.map +1 -0
  155. package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.d.ts +216 -0
  156. package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.d.ts.map +1 -0
  157. package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.js +508 -0
  158. package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.js.map +1 -0
  159. package/dist/cli/backend/src/services/agent/pty-activity-tracker.service.d.ts +113 -0
  160. package/dist/cli/backend/src/services/agent/pty-activity-tracker.service.d.ts.map +1 -0
  161. package/dist/cli/backend/src/services/agent/pty-activity-tracker.service.js +161 -0
  162. package/dist/cli/backend/src/services/agent/pty-activity-tracker.service.js.map +1 -0
  163. package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.d.ts +156 -0
  164. package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -0
  165. package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js +641 -0
  166. package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -0
  167. package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.d.ts +295 -0
  168. package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.d.ts.map +1 -0
  169. package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.js +1166 -0
  170. package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.js.map +1 -0
  171. package/dist/cli/backend/src/services/agent/runtime-service.factory.d.ts +71 -0
  172. package/dist/cli/backend/src/services/agent/runtime-service.factory.d.ts.map +1 -0
  173. package/dist/cli/backend/src/services/agent/runtime-service.factory.js +174 -0
  174. package/dist/cli/backend/src/services/agent/runtime-service.factory.js.map +1 -0
  175. package/dist/cli/backend/src/services/ai/prompt-builder.service.d.ts +435 -0
  176. package/dist/cli/backend/src/services/ai/prompt-builder.service.d.ts.map +1 -0
  177. package/dist/cli/backend/src/services/ai/prompt-builder.service.js +1331 -0
  178. package/dist/cli/backend/src/services/ai/prompt-builder.service.js.map +1 -0
  179. package/dist/cli/backend/src/services/ai/prompt-modules/capability-overlay.module.d.ts +33 -0
  180. package/dist/cli/backend/src/services/ai/prompt-modules/capability-overlay.module.d.ts.map +1 -0
  181. package/dist/cli/backend/src/services/ai/prompt-modules/capability-overlay.module.js +70 -0
  182. package/dist/cli/backend/src/services/ai/prompt-modules/capability-overlay.module.js.map +1 -0
  183. package/dist/cli/backend/src/services/ai/prompt-modules/communication.module.d.ts +100 -0
  184. package/dist/cli/backend/src/services/ai/prompt-modules/communication.module.d.ts.map +1 -0
  185. package/dist/cli/backend/src/services/ai/prompt-modules/communication.module.js +296 -0
  186. package/dist/cli/backend/src/services/ai/prompt-modules/communication.module.js.map +1 -0
  187. package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.d.ts +59 -0
  188. package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.d.ts.map +1 -0
  189. package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.js +87 -0
  190. package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.js.map +1 -0
  191. package/dist/cli/backend/src/services/ai/prompt-modules/default-execution-loop.module.d.ts +67 -0
  192. package/dist/cli/backend/src/services/ai/prompt-modules/default-execution-loop.module.d.ts.map +1 -0
  193. package/dist/cli/backend/src/services/ai/prompt-modules/default-execution-loop.module.js +84 -0
  194. package/dist/cli/backend/src/services/ai/prompt-modules/default-execution-loop.module.js.map +1 -0
  195. package/dist/cli/backend/src/services/ai/prompt-modules/domain-sop.module.d.ts +35 -0
  196. package/dist/cli/backend/src/services/ai/prompt-modules/domain-sop.module.d.ts.map +1 -0
  197. package/dist/cli/backend/src/services/ai/prompt-modules/domain-sop.module.js +50 -0
  198. package/dist/cli/backend/src/services/ai/prompt-modules/domain-sop.module.js.map +1 -0
  199. package/dist/cli/backend/src/services/ai/prompt-modules/expert-profile.module.d.ts +41 -0
  200. package/dist/cli/backend/src/services/ai/prompt-modules/expert-profile.module.d.ts.map +1 -0
  201. package/dist/cli/backend/src/services/ai/prompt-modules/expert-profile.module.js +95 -0
  202. package/dist/cli/backend/src/services/ai/prompt-modules/expert-profile.module.js.map +1 -0
  203. package/dist/cli/backend/src/services/ai/prompt-modules/identity.module.d.ts +28 -0
  204. package/dist/cli/backend/src/services/ai/prompt-modules/identity.module.d.ts.map +1 -0
  205. package/dist/cli/backend/src/services/ai/prompt-modules/identity.module.js +47 -0
  206. package/dist/cli/backend/src/services/ai/prompt-modules/identity.module.js.map +1 -0
  207. package/dist/cli/backend/src/services/ai/prompt-modules/lazy-anti-patterns.module.d.ts +65 -0
  208. package/dist/cli/backend/src/services/ai/prompt-modules/lazy-anti-patterns.module.d.ts.map +1 -0
  209. package/dist/cli/backend/src/services/ai/prompt-modules/lazy-anti-patterns.module.js +79 -0
  210. package/dist/cli/backend/src/services/ai/prompt-modules/lazy-anti-patterns.module.js.map +1 -0
  211. package/dist/cli/backend/src/services/ai/prompt-modules/learning-reference.module.d.ts +28 -0
  212. package/dist/cli/backend/src/services/ai/prompt-modules/learning-reference.module.d.ts.map +1 -0
  213. package/dist/cli/backend/src/services/ai/prompt-modules/learning-reference.module.js +55 -0
  214. package/dist/cli/backend/src/services/ai/prompt-modules/learning-reference.module.js.map +1 -0
  215. package/dist/cli/backend/src/services/ai/prompt-modules/lifecycle.module.d.ts +44 -0
  216. package/dist/cli/backend/src/services/ai/prompt-modules/lifecycle.module.d.ts.map +1 -0
  217. package/dist/cli/backend/src/services/ai/prompt-modules/lifecycle.module.js +120 -0
  218. package/dist/cli/backend/src/services/ai/prompt-modules/lifecycle.module.js.map +1 -0
  219. package/dist/cli/backend/src/services/ai/prompt-modules/markdown-file-module.d.ts +46 -0
  220. package/dist/cli/backend/src/services/ai/prompt-modules/markdown-file-module.d.ts.map +1 -0
  221. package/dist/cli/backend/src/services/ai/prompt-modules/markdown-file-module.js +44 -0
  222. package/dist/cli/backend/src/services/ai/prompt-modules/markdown-file-module.js.map +1 -0
  223. package/dist/cli/backend/src/services/ai/prompt-modules/memory-reference.module.d.ts +28 -0
  224. package/dist/cli/backend/src/services/ai/prompt-modules/memory-reference.module.d.ts.map +1 -0
  225. package/dist/cli/backend/src/services/ai/prompt-modules/memory-reference.module.js +46 -0
  226. package/dist/cli/backend/src/services/ai/prompt-modules/memory-reference.module.js.map +1 -0
  227. package/dist/cli/backend/src/services/ai/prompt-modules/mission-context.module.d.ts +60 -0
  228. package/dist/cli/backend/src/services/ai/prompt-modules/mission-context.module.d.ts.map +1 -0
  229. package/dist/cli/backend/src/services/ai/prompt-modules/mission-context.module.js +104 -0
  230. package/dist/cli/backend/src/services/ai/prompt-modules/mission-context.module.js.map +1 -0
  231. package/dist/cli/backend/src/services/ai/prompt-modules/project-reference.module.d.ts +29 -0
  232. package/dist/cli/backend/src/services/ai/prompt-modules/project-reference.module.d.ts.map +1 -0
  233. package/dist/cli/backend/src/services/ai/prompt-modules/project-reference.module.js +50 -0
  234. package/dist/cli/backend/src/services/ai/prompt-modules/project-reference.module.js.map +1 -0
  235. package/dist/cli/backend/src/services/ai/prompt-modules/prompt-assembly.service.d.ts +103 -0
  236. package/dist/cli/backend/src/services/ai/prompt-modules/prompt-assembly.service.d.ts.map +1 -0
  237. package/dist/cli/backend/src/services/ai/prompt-modules/prompt-assembly.service.js +383 -0
  238. package/dist/cli/backend/src/services/ai/prompt-modules/prompt-assembly.service.js.map +1 -0
  239. package/dist/cli/backend/src/services/ai/prompt-modules/recovery.module.d.ts +34 -0
  240. package/dist/cli/backend/src/services/ai/prompt-modules/recovery.module.d.ts.map +1 -0
  241. package/dist/cli/backend/src/services/ai/prompt-modules/recovery.module.js +96 -0
  242. package/dist/cli/backend/src/services/ai/prompt-modules/recovery.module.js.map +1 -0
  243. package/dist/cli/backend/src/services/ai/prompt-modules/request-contract.module.d.ts +106 -0
  244. package/dist/cli/backend/src/services/ai/prompt-modules/request-contract.module.d.ts.map +1 -0
  245. package/dist/cli/backend/src/services/ai/prompt-modules/request-contract.module.js +167 -0
  246. package/dist/cli/backend/src/services/ai/prompt-modules/request-contract.module.js.map +1 -0
  247. package/dist/cli/backend/src/services/ai/prompt-modules/risk-policy.module.d.ts +35 -0
  248. package/dist/cli/backend/src/services/ai/prompt-modules/risk-policy.module.d.ts.map +1 -0
  249. package/dist/cli/backend/src/services/ai/prompt-modules/risk-policy.module.js +50 -0
  250. package/dist/cli/backend/src/services/ai/prompt-modules/risk-policy.module.js.map +1 -0
  251. package/dist/cli/backend/src/services/ai/prompt-modules/role-boundary.module.d.ts +91 -0
  252. package/dist/cli/backend/src/services/ai/prompt-modules/role-boundary.module.d.ts.map +1 -0
  253. package/dist/cli/backend/src/services/ai/prompt-modules/role-boundary.module.js +411 -0
  254. package/dist/cli/backend/src/services/ai/prompt-modules/role-boundary.module.js.map +1 -0
  255. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts +62 -0
  256. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -0
  257. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +199 -0
  258. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -0
  259. package/dist/cli/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.d.ts +79 -0
  260. package/dist/cli/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.d.ts.map +1 -0
  261. package/dist/cli/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.js +118 -0
  262. package/dist/cli/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.js.map +1 -0
  263. package/dist/cli/backend/src/services/ai/prompt-modules/soul.module.d.ts +102 -0
  264. package/dist/cli/backend/src/services/ai/prompt-modules/soul.module.d.ts.map +1 -0
  265. package/dist/cli/backend/src/services/ai/prompt-modules/soul.module.js +229 -0
  266. package/dist/cli/backend/src/services/ai/prompt-modules/soul.module.js.map +1 -0
  267. package/dist/cli/backend/src/services/ai/prompt-modules/team-norms.module.d.ts +61 -0
  268. package/dist/cli/backend/src/services/ai/prompt-modules/team-norms.module.d.ts.map +1 -0
  269. package/dist/cli/backend/src/services/ai/prompt-modules/team-norms.module.js +197 -0
  270. package/dist/cli/backend/src/services/ai/prompt-modules/team-norms.module.js.map +1 -0
  271. package/dist/cli/backend/src/services/ai/prompt-modules/team-reference.module.d.ts +54 -0
  272. package/dist/cli/backend/src/services/ai/prompt-modules/team-reference.module.d.ts.map +1 -0
  273. package/dist/cli/backend/src/services/ai/prompt-modules/team-reference.module.js +149 -0
  274. package/dist/cli/backend/src/services/ai/prompt-modules/team-reference.module.js.map +1 -0
  275. package/dist/cli/backend/src/services/ai/prompt-modules/user-profile-reference.module.d.ts +32 -0
  276. package/dist/cli/backend/src/services/ai/prompt-modules/user-profile-reference.module.d.ts.map +1 -0
  277. package/dist/cli/backend/src/services/ai/prompt-modules/user-profile-reference.module.js +69 -0
  278. package/dist/cli/backend/src/services/ai/prompt-modules/user-profile-reference.module.js.map +1 -0
  279. package/dist/cli/backend/src/services/ai/prompt-modules/working-memory.module.d.ts +91 -0
  280. package/dist/cli/backend/src/services/ai/prompt-modules/working-memory.module.d.ts.map +1 -0
  281. package/dist/cli/backend/src/services/ai/prompt-modules/working-memory.module.js +136 -0
  282. package/dist/cli/backend/src/services/ai/prompt-modules/working-memory.module.js.map +1 -0
  283. package/dist/cli/backend/src/services/ai/self-improvement/growth-areas.service.d.ts +81 -0
  284. package/dist/cli/backend/src/services/ai/self-improvement/growth-areas.service.d.ts.map +1 -0
  285. package/dist/cli/backend/src/services/ai/self-improvement/growth-areas.service.js +135 -0
  286. package/dist/cli/backend/src/services/ai/self-improvement/growth-areas.service.js.map +1 -0
  287. package/dist/cli/backend/src/services/chat-v2/chat-v2.service.d.ts +737 -0
  288. package/dist/cli/backend/src/services/chat-v2/chat-v2.service.d.ts.map +1 -0
  289. package/dist/cli/backend/src/services/chat-v2/chat-v2.service.js +1213 -0
  290. package/dist/cli/backend/src/services/chat-v2/chat-v2.service.js.map +1 -0
  291. package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.d.ts +27 -0
  292. package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.d.ts.map +1 -0
  293. package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.js +57 -0
  294. package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.js.map +1 -0
  295. package/dist/cli/backend/src/services/chat-v2/config.d.ts +100 -0
  296. package/dist/cli/backend/src/services/chat-v2/config.d.ts.map +1 -0
  297. package/dist/cli/backend/src/services/chat-v2/config.js +174 -0
  298. package/dist/cli/backend/src/services/chat-v2/config.js.map +1 -0
  299. package/dist/cli/backend/src/services/chat-v2/legacy-dto.utils.d.ts +118 -0
  300. package/dist/cli/backend/src/services/chat-v2/legacy-dto.utils.d.ts.map +1 -0
  301. package/dist/cli/backend/src/services/chat-v2/legacy-dto.utils.js +171 -0
  302. package/dist/cli/backend/src/services/chat-v2/legacy-dto.utils.js.map +1 -0
  303. package/dist/cli/backend/src/services/chat-v2/sqlite/channel.store.d.ts +188 -0
  304. package/dist/cli/backend/src/services/chat-v2/sqlite/channel.store.d.ts.map +1 -0
  305. package/dist/cli/backend/src/services/chat-v2/sqlite/channel.store.js +317 -0
  306. package/dist/cli/backend/src/services/chat-v2/sqlite/channel.store.js.map +1 -0
  307. package/dist/cli/backend/src/services/chat-v2/sqlite/chat-db.d.ts +119 -0
  308. package/dist/cli/backend/src/services/chat-v2/sqlite/chat-db.d.ts.map +1 -0
  309. package/dist/cli/backend/src/services/chat-v2/sqlite/chat-db.js +551 -0
  310. package/dist/cli/backend/src/services/chat-v2/sqlite/chat-db.js.map +1 -0
  311. package/dist/cli/backend/src/services/chat-v2/sqlite/message.store.d.ts +208 -0
  312. package/dist/cli/backend/src/services/chat-v2/sqlite/message.store.d.ts.map +1 -0
  313. package/dist/cli/backend/src/services/chat-v2/sqlite/message.store.js +405 -0
  314. package/dist/cli/backend/src/services/chat-v2/sqlite/message.store.js.map +1 -0
  315. package/dist/cli/backend/src/services/chat-v2/types.d.ts +341 -0
  316. package/dist/cli/backend/src/services/chat-v2/types.d.ts.map +1 -0
  317. package/dist/cli/backend/src/services/chat-v2/types.js +61 -0
  318. package/dist/cli/backend/src/services/chat-v2/types.js.map +1 -0
  319. package/dist/cli/backend/src/services/continuation/patterns/idle-patterns.d.ts +37 -0
  320. package/dist/cli/backend/src/services/continuation/patterns/idle-patterns.d.ts.map +1 -0
  321. package/dist/cli/backend/src/services/continuation/patterns/idle-patterns.js +63 -0
  322. package/dist/cli/backend/src/services/continuation/patterns/idle-patterns.js.map +1 -0
  323. package/dist/cli/backend/src/services/intent-task/intent-classifier.rules.d.ts +335 -0
  324. package/dist/cli/backend/src/services/intent-task/intent-classifier.rules.d.ts.map +1 -0
  325. package/dist/cli/backend/src/services/intent-task/intent-classifier.rules.js +421 -0
  326. package/dist/cli/backend/src/services/intent-task/intent-classifier.rules.js.map +1 -0
  327. package/dist/cli/backend/src/services/mcp-client.d.ts +233 -0
  328. package/dist/cli/backend/src/services/mcp-client.d.ts.map +1 -0
  329. package/dist/cli/backend/src/services/mcp-client.js +302 -0
  330. package/dist/cli/backend/src/services/mcp-client.js.map +1 -0
  331. package/dist/cli/backend/src/services/memory/mission-context.service.d.ts +168 -0
  332. package/dist/cli/backend/src/services/memory/mission-context.service.d.ts.map +1 -0
  333. package/dist/cli/backend/src/services/memory/mission-context.service.js +365 -0
  334. package/dist/cli/backend/src/services/memory/mission-context.service.js.map +1 -0
  335. package/dist/cli/backend/src/services/memory/session-memory.service.d.ts +197 -0
  336. package/dist/cli/backend/src/services/memory/session-memory.service.d.ts.map +1 -0
  337. package/dist/cli/backend/src/services/memory/session-memory.service.js +369 -0
  338. package/dist/cli/backend/src/services/memory/session-memory.service.js.map +1 -0
  339. package/dist/cli/backend/src/services/memory/user-profile.service.d.ts +77 -0
  340. package/dist/cli/backend/src/services/memory/user-profile.service.d.ts.map +1 -0
  341. package/dist/cli/backend/src/services/memory/user-profile.service.js +171 -0
  342. package/dist/cli/backend/src/services/memory/user-profile.service.js.map +1 -0
  343. package/dist/cli/backend/src/services/memory/working-memory.service.d.ts +232 -0
  344. package/dist/cli/backend/src/services/memory/working-memory.service.d.ts.map +1 -0
  345. package/dist/cli/backend/src/services/memory/working-memory.service.js +417 -0
  346. package/dist/cli/backend/src/services/memory/working-memory.service.js.map +1 -0
  347. package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts +84 -0
  348. package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts.map +1 -0
  349. package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js +136 -0
  350. package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -0
  351. package/dist/cli/backend/src/services/messaging/thread-status-queue.service.d.ts +358 -0
  352. package/dist/cli/backend/src/services/messaging/thread-status-queue.service.d.ts.map +1 -0
  353. package/dist/cli/backend/src/services/messaging/thread-status-queue.service.js +755 -0
  354. package/dist/cli/backend/src/services/messaging/thread-status-queue.service.js.map +1 -0
  355. package/dist/cli/backend/src/services/monitoring/token-usage.service.d.ts +269 -0
  356. package/dist/cli/backend/src/services/monitoring/token-usage.service.d.ts.map +1 -0
  357. package/dist/cli/backend/src/services/monitoring/token-usage.service.js +391 -0
  358. package/dist/cli/backend/src/services/monitoring/token-usage.service.js.map +1 -0
  359. package/dist/cli/backend/src/services/observability/agent-behavior-log.service.d.ts +132 -0
  360. package/dist/cli/backend/src/services/observability/agent-behavior-log.service.d.ts.map +1 -0
  361. package/dist/cli/backend/src/services/observability/agent-behavior-log.service.js +284 -0
  362. package/dist/cli/backend/src/services/observability/agent-behavior-log.service.js.map +1 -0
  363. package/dist/cli/backend/src/services/observability/agent-behavior-log.singleton.d.ts +70 -0
  364. package/dist/cli/backend/src/services/observability/agent-behavior-log.singleton.d.ts.map +1 -0
  365. package/dist/cli/backend/src/services/observability/agent-behavior-log.singleton.js +121 -0
  366. package/dist/cli/backend/src/services/observability/agent-behavior-log.singleton.js.map +1 -0
  367. package/dist/cli/backend/src/services/observability/agent-behavior-log.types.d.ts +130 -0
  368. package/dist/cli/backend/src/services/observability/agent-behavior-log.types.d.ts.map +1 -0
  369. package/dist/cli/backend/src/services/observability/agent-behavior-log.types.js +48 -0
  370. package/dist/cli/backend/src/services/observability/agent-behavior-log.types.js.map +1 -0
  371. package/dist/cli/backend/src/services/observability/observability-db.d.ts +84 -0
  372. package/dist/cli/backend/src/services/observability/observability-db.d.ts.map +1 -0
  373. package/dist/cli/backend/src/services/observability/observability-db.js +165 -0
  374. package/dist/cli/backend/src/services/observability/observability-db.js.map +1 -0
  375. package/dist/cli/backend/src/services/onboarding/content-approval.service.d.ts +124 -0
  376. package/dist/cli/backend/src/services/onboarding/content-approval.service.d.ts.map +1 -0
  377. package/dist/cli/backend/src/services/onboarding/content-approval.service.js +256 -0
  378. package/dist/cli/backend/src/services/onboarding/content-approval.service.js.map +1 -0
  379. package/dist/cli/backend/src/services/onboarding/content-approval.types.d.ts +80 -0
  380. package/dist/cli/backend/src/services/onboarding/content-approval.types.d.ts.map +1 -0
  381. package/dist/cli/backend/src/services/onboarding/content-approval.types.js +16 -0
  382. package/dist/cli/backend/src/services/onboarding/content-approval.types.js.map +1 -0
  383. package/dist/cli/backend/src/services/orc/orc-delivery-enforcer.service.d.ts +142 -0
  384. package/dist/cli/backend/src/services/orc/orc-delivery-enforcer.service.d.ts.map +1 -0
  385. package/dist/cli/backend/src/services/orc/orc-delivery-enforcer.service.js +265 -0
  386. package/dist/cli/backend/src/services/orc/orc-delivery-enforcer.service.js.map +1 -0
  387. package/dist/cli/backend/src/services/orchestrator/improvement-marker.service.d.ts +231 -0
  388. package/dist/cli/backend/src/services/orchestrator/improvement-marker.service.d.ts.map +1 -0
  389. package/dist/cli/backend/src/services/orchestrator/improvement-marker.service.js +353 -0
  390. package/dist/cli/backend/src/services/orchestrator/improvement-marker.service.js.map +1 -0
  391. package/dist/cli/backend/src/services/orchestrator/improvement-startup.service.d.ts +157 -0
  392. package/dist/cli/backend/src/services/orchestrator/improvement-startup.service.d.ts.map +1 -0
  393. package/dist/cli/backend/src/services/orchestrator/improvement-startup.service.js +483 -0
  394. package/dist/cli/backend/src/services/orchestrator/improvement-startup.service.js.map +1 -0
  395. package/dist/cli/backend/src/services/orchestrator/index.d.ts +16 -0
  396. package/dist/cli/backend/src/services/orchestrator/index.d.ts.map +1 -0
  397. package/dist/cli/backend/src/services/orchestrator/index.js +16 -0
  398. package/dist/cli/backend/src/services/orchestrator/index.js.map +1 -0
  399. package/dist/cli/backend/src/services/orchestrator/orchestrator-restart.service.d.ts +117 -0
  400. package/dist/cli/backend/src/services/orchestrator/orchestrator-restart.service.d.ts.map +1 -0
  401. package/dist/cli/backend/src/services/orchestrator/orchestrator-restart.service.js +300 -0
  402. package/dist/cli/backend/src/services/orchestrator/orchestrator-restart.service.js.map +1 -0
  403. package/dist/cli/backend/src/services/orchestrator/orchestrator-setup.service.d.ts +114 -0
  404. package/dist/cli/backend/src/services/orchestrator/orchestrator-setup.service.d.ts.map +1 -0
  405. package/dist/cli/backend/src/services/orchestrator/orchestrator-setup.service.js +195 -0
  406. package/dist/cli/backend/src/services/orchestrator/orchestrator-setup.service.js.map +1 -0
  407. package/dist/cli/backend/src/services/orchestrator/orchestrator-status.service.d.ts +94 -0
  408. package/dist/cli/backend/src/services/orchestrator/orchestrator-status.service.d.ts.map +1 -0
  409. package/dist/cli/backend/src/services/orchestrator/orchestrator-status.service.js +279 -0
  410. package/dist/cli/backend/src/services/orchestrator/orchestrator-status.service.js.map +1 -0
  411. package/dist/cli/backend/src/services/orchestrator/safe-restart.service.d.ts +192 -0
  412. package/dist/cli/backend/src/services/orchestrator/safe-restart.service.d.ts.map +1 -0
  413. package/dist/cli/backend/src/services/orchestrator/safe-restart.service.js +395 -0
  414. package/dist/cli/backend/src/services/orchestrator/safe-restart.service.js.map +1 -0
  415. package/dist/cli/backend/src/services/orchestrator/self-improvement.service.d.ts +201 -0
  416. package/dist/cli/backend/src/services/orchestrator/self-improvement.service.d.ts.map +1 -0
  417. package/dist/cli/backend/src/services/orchestrator/self-improvement.service.js +396 -0
  418. package/dist/cli/backend/src/services/orchestrator/self-improvement.service.js.map +1 -0
  419. package/dist/cli/backend/src/services/orchestrator/slack-bridge-lazy.d.ts +17 -0
  420. package/dist/cli/backend/src/services/orchestrator/slack-bridge-lazy.d.ts.map +1 -0
  421. package/dist/cli/backend/src/services/orchestrator/slack-bridge-lazy.js +30 -0
  422. package/dist/cli/backend/src/services/orchestrator/slack-bridge-lazy.js.map +1 -0
  423. package/dist/cli/backend/src/services/orchestrator/state-persistence.service.d.ts +249 -0
  424. package/dist/cli/backend/src/services/orchestrator/state-persistence.service.d.ts.map +1 -0
  425. package/dist/cli/backend/src/services/orchestrator/state-persistence.service.js +569 -0
  426. package/dist/cli/backend/src/services/orchestrator/state-persistence.service.js.map +1 -0
  427. package/dist/cli/backend/src/services/session/index.d.ts +47 -0
  428. package/dist/cli/backend/src/services/session/index.d.ts.map +1 -0
  429. package/dist/cli/backend/src/services/session/index.js +50 -0
  430. package/dist/cli/backend/src/services/session/index.js.map +1 -0
  431. package/dist/cli/backend/src/services/session/log-rotation.service.d.ts +163 -0
  432. package/dist/cli/backend/src/services/session/log-rotation.service.d.ts.map +1 -0
  433. package/dist/cli/backend/src/services/session/log-rotation.service.js +362 -0
  434. package/dist/cli/backend/src/services/session/log-rotation.service.js.map +1 -0
  435. package/dist/cli/backend/src/services/session/pty/index.d.ts +11 -0
  436. package/dist/cli/backend/src/services/session/pty/index.d.ts.map +1 -0
  437. package/dist/cli/backend/src/services/session/pty/index.js +11 -0
  438. package/dist/cli/backend/src/services/session/pty/index.js.map +1 -0
  439. package/dist/cli/backend/src/services/session/pty/pty-session-backend.d.ts +445 -0
  440. package/dist/cli/backend/src/services/session/pty/pty-session-backend.d.ts.map +1 -0
  441. package/dist/cli/backend/src/services/session/pty/pty-session-backend.js +804 -0
  442. package/dist/cli/backend/src/services/session/pty/pty-session-backend.js.map +1 -0
  443. package/dist/cli/backend/src/services/session/pty/pty-session.d.ts +252 -0
  444. package/dist/cli/backend/src/services/session/pty/pty-session.d.ts.map +1 -0
  445. package/dist/cli/backend/src/services/session/pty/pty-session.js +611 -0
  446. package/dist/cli/backend/src/services/session/pty/pty-session.js.map +1 -0
  447. package/dist/cli/backend/src/services/session/pty/pty-terminal-buffer.d.ts +275 -0
  448. package/dist/cli/backend/src/services/session/pty/pty-terminal-buffer.d.ts.map +1 -0
  449. package/dist/cli/backend/src/services/session/pty/pty-terminal-buffer.js +403 -0
  450. package/dist/cli/backend/src/services/session/pty/pty-terminal-buffer.js.map +1 -0
  451. package/dist/cli/backend/src/services/session/runtime-pid-registry.service.d.ts +86 -0
  452. package/dist/cli/backend/src/services/session/runtime-pid-registry.service.d.ts.map +1 -0
  453. package/dist/cli/backend/src/services/session/runtime-pid-registry.service.js +214 -0
  454. package/dist/cli/backend/src/services/session/runtime-pid-registry.service.js.map +1 -0
  455. package/dist/cli/backend/src/services/session/session-backend.factory.d.ts +127 -0
  456. package/dist/cli/backend/src/services/session/session-backend.factory.d.ts.map +1 -0
  457. package/dist/cli/backend/src/services/session/session-backend.factory.js +226 -0
  458. package/dist/cli/backend/src/services/session/session-backend.factory.js.map +1 -0
  459. package/dist/cli/backend/src/services/session/session-backend.interface.d.ts +390 -0
  460. package/dist/cli/backend/src/services/session/session-backend.interface.d.ts.map +1 -0
  461. package/dist/cli/backend/src/services/session/session-backend.interface.js +19 -0
  462. package/dist/cli/backend/src/services/session/session-backend.interface.js.map +1 -0
  463. package/dist/cli/backend/src/services/session/session-command-helper.d.ts +350 -0
  464. package/dist/cli/backend/src/services/session/session-command-helper.d.ts.map +1 -0
  465. package/dist/cli/backend/src/services/session/session-command-helper.js +810 -0
  466. package/dist/cli/backend/src/services/session/session-command-helper.js.map +1 -0
  467. package/dist/cli/backend/src/services/session/session-handoff.service.d.ts +307 -0
  468. package/dist/cli/backend/src/services/session/session-handoff.service.d.ts.map +1 -0
  469. package/dist/cli/backend/src/services/session/session-handoff.service.js +742 -0
  470. package/dist/cli/backend/src/services/session/session-handoff.service.js.map +1 -0
  471. package/dist/cli/backend/src/services/session/session-state-persistence.d.ts +231 -0
  472. package/dist/cli/backend/src/services/session/session-state-persistence.d.ts.map +1 -0
  473. package/dist/cli/backend/src/services/session/session-state-persistence.js +407 -0
  474. package/dist/cli/backend/src/services/session/session-state-persistence.js.map +1 -0
  475. package/dist/cli/backend/src/services/settings/role.service.d.ts +277 -0
  476. package/dist/cli/backend/src/services/settings/role.service.d.ts.map +1 -0
  477. package/dist/cli/backend/src/services/settings/role.service.js +728 -0
  478. package/dist/cli/backend/src/services/settings/role.service.js.map +1 -0
  479. package/dist/cli/backend/src/services/slack/cross-machine-message.service.d.ts +152 -0
  480. package/dist/cli/backend/src/services/slack/cross-machine-message.service.d.ts.map +1 -0
  481. package/dist/cli/backend/src/services/slack/cross-machine-message.service.js +354 -0
  482. package/dist/cli/backend/src/services/slack/cross-machine-message.service.js.map +1 -0
  483. package/dist/cli/backend/src/services/slack/slack-image.service.d.ts +114 -0
  484. package/dist/cli/backend/src/services/slack/slack-image.service.d.ts.map +1 -0
  485. package/dist/cli/backend/src/services/slack/slack-image.service.js +331 -0
  486. package/dist/cli/backend/src/services/slack/slack-image.service.js.map +1 -0
  487. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts +499 -0
  488. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -0
  489. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js +1928 -0
  490. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -0
  491. package/dist/cli/backend/src/services/slack/slack.service.d.ts +315 -0
  492. package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -0
  493. package/dist/cli/backend/src/services/slack/slack.service.js +1094 -0
  494. package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -0
  495. package/dist/cli/backend/src/services/sop/sop.service.d.ts +364 -0
  496. package/dist/cli/backend/src/services/sop/sop.service.d.ts.map +1 -0
  497. package/dist/cli/backend/src/services/sop/sop.service.js +734 -0
  498. package/dist/cli/backend/src/services/sop/sop.service.js.map +1 -0
  499. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +152 -7
  500. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  501. package/dist/cli/backend/src/services/task-pool/task-pool.service.js +268 -36
  502. package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  503. package/dist/cli/backend/src/services/v3/escalation-router.service.d.ts +178 -0
  504. package/dist/cli/backend/src/services/v3/escalation-router.service.d.ts.map +1 -0
  505. package/dist/cli/backend/src/services/v3/escalation-router.service.js +692 -0
  506. package/dist/cli/backend/src/services/v3/escalation-router.service.js.map +1 -0
  507. package/dist/cli/backend/src/services/v3/kr-tracking.service.d.ts +200 -0
  508. package/dist/cli/backend/src/services/v3/kr-tracking.service.d.ts.map +1 -0
  509. package/dist/cli/backend/src/services/v3/kr-tracking.service.js +482 -0
  510. package/dist/cli/backend/src/services/v3/kr-tracking.service.js.map +1 -0
  511. package/dist/cli/backend/src/services/v3/mission-executor.service.d.ts +117 -0
  512. package/dist/cli/backend/src/services/v3/mission-executor.service.d.ts.map +1 -0
  513. package/dist/cli/backend/src/services/v3/mission-executor.service.js +321 -0
  514. package/dist/cli/backend/src/services/v3/mission-executor.service.js.map +1 -0
  515. package/dist/cli/backend/src/services/v3/request-sla.subscriber.d.ts +610 -0
  516. package/dist/cli/backend/src/services/v3/request-sla.subscriber.d.ts.map +1 -0
  517. package/dist/cli/backend/src/services/v3/request-sla.subscriber.js +1529 -0
  518. package/dist/cli/backend/src/services/v3/request-sla.subscriber.js.map +1 -0
  519. package/dist/cli/backend/src/services/v3/request-tracker.service.d.ts +78 -0
  520. package/dist/cli/backend/src/services/v3/request-tracker.service.d.ts.map +1 -0
  521. package/dist/cli/backend/src/services/v3/request-tracker.service.js +108 -0
  522. package/dist/cli/backend/src/services/v3/request-tracker.service.js.map +1 -0
  523. package/dist/cli/backend/src/services/v3/request.service.d.ts +295 -0
  524. package/dist/cli/backend/src/services/v3/request.service.d.ts.map +1 -0
  525. package/dist/cli/backend/src/services/v3/request.service.js +534 -0
  526. package/dist/cli/backend/src/services/v3/request.service.js.map +1 -0
  527. package/dist/cli/backend/src/services/v3/v3-data.service.d.ts +340 -0
  528. package/dist/cli/backend/src/services/v3/v3-data.service.d.ts.map +1 -0
  529. package/dist/cli/backend/src/services/v3/v3-data.service.js +1113 -0
  530. package/dist/cli/backend/src/services/v3/v3-data.service.js.map +1 -0
  531. package/dist/cli/backend/src/types/cross-machine.types.d.ts +103 -0
  532. package/dist/cli/backend/src/types/cross-machine.types.d.ts.map +1 -0
  533. package/dist/cli/backend/src/types/cross-machine.types.js +47 -0
  534. package/dist/cli/backend/src/types/cross-machine.types.js.map +1 -0
  535. package/dist/cli/backend/src/types/intent-task.types.d.ts +402 -0
  536. package/dist/cli/backend/src/types/intent-task.types.d.ts.map +1 -0
  537. package/dist/cli/backend/src/types/intent-task.types.js +403 -0
  538. package/dist/cli/backend/src/types/intent-task.types.js.map +1 -0
  539. package/dist/cli/backend/src/types/orchestrator-state.types.d.ts +531 -0
  540. package/dist/cli/backend/src/types/orchestrator-state.types.d.ts.map +1 -0
  541. package/dist/cli/backend/src/types/orchestrator-state.types.js +110 -0
  542. package/dist/cli/backend/src/types/orchestrator-state.types.js.map +1 -0
  543. package/dist/cli/backend/src/types/v2/index.d.ts +30 -0
  544. package/dist/cli/backend/src/types/v2/index.d.ts.map +1 -0
  545. package/dist/cli/backend/src/types/v2/index.js +22 -0
  546. package/dist/cli/backend/src/types/v2/index.js.map +1 -0
  547. package/dist/cli/backend/src/types/v2/key-result.types.d.ts +279 -0
  548. package/dist/cli/backend/src/types/v2/key-result.types.d.ts.map +1 -0
  549. package/dist/cli/backend/src/types/v2/key-result.types.js +264 -0
  550. package/dist/cli/backend/src/types/v2/key-result.types.js.map +1 -0
  551. package/dist/cli/backend/src/types/v2/mission.types.d.ts +661 -0
  552. package/dist/cli/backend/src/types/v2/mission.types.d.ts.map +1 -0
  553. package/dist/cli/backend/src/types/v2/mission.types.js +713 -0
  554. package/dist/cli/backend/src/types/v2/mission.types.js.map +1 -0
  555. package/dist/cli/backend/src/types/v2/reconcile.types.d.ts +204 -0
  556. package/dist/cli/backend/src/types/v2/reconcile.types.d.ts.map +1 -0
  557. package/dist/cli/backend/src/types/v2/reconcile.types.js +121 -0
  558. package/dist/cli/backend/src/types/v2/reconcile.types.js.map +1 -0
  559. package/dist/cli/backend/src/types/v2/request.types.d.ts +192 -0
  560. package/dist/cli/backend/src/types/v2/request.types.d.ts.map +1 -0
  561. package/dist/cli/backend/src/types/v2/request.types.js +179 -0
  562. package/dist/cli/backend/src/types/v2/request.types.js.map +1 -0
  563. package/dist/cli/backend/src/types/v2/work-item.types.d.ts +181 -1
  564. package/dist/cli/backend/src/types/v2/work-item.types.d.ts.map +1 -1
  565. package/dist/cli/backend/src/types/v2/work-item.types.js +104 -17
  566. package/dist/cli/backend/src/types/v2/work-item.types.js.map +1 -1
  567. package/dist/cli/backend/src/types/v2/workspace.types.d.ts +157 -0
  568. package/dist/cli/backend/src/types/v2/workspace.types.d.ts.map +1 -0
  569. package/dist/cli/backend/src/types/v2/workspace.types.js +173 -0
  570. package/dist/cli/backend/src/types/v2/workspace.types.js.map +1 -0
  571. package/dist/cli/backend/src/utils/async.utils.d.ts +68 -0
  572. package/dist/cli/backend/src/utils/async.utils.d.ts.map +1 -0
  573. package/dist/cli/backend/src/utils/async.utils.js +80 -0
  574. package/dist/cli/backend/src/utils/async.utils.js.map +1 -0
  575. package/dist/cli/backend/src/utils/native-binding.utils.d.ts +128 -0
  576. package/dist/cli/backend/src/utils/native-binding.utils.d.ts.map +1 -0
  577. package/dist/cli/backend/src/utils/native-binding.utils.js +206 -0
  578. package/dist/cli/backend/src/utils/native-binding.utils.js.map +1 -0
  579. package/dist/cli/backend/src/websocket/chat.gateway.d.ts +142 -0
  580. package/dist/cli/backend/src/websocket/chat.gateway.d.ts.map +1 -0
  581. package/dist/cli/backend/src/websocket/chat.gateway.js +327 -0
  582. package/dist/cli/backend/src/websocket/chat.gateway.js.map +1 -0
  583. package/dist/cli/backend/src/websocket/terminal.gateway.d.ts +341 -0
  584. package/dist/cli/backend/src/websocket/terminal.gateway.d.ts.map +1 -0
  585. package/dist/cli/backend/src/websocket/terminal.gateway.js +933 -0
  586. package/dist/cli/backend/src/websocket/terminal.gateway.js.map +1 -0
  587. package/dist/cli/config/vitest-exclude.constants.d.ts +53 -0
  588. package/dist/cli/config/vitest-exclude.constants.d.ts.map +1 -0
  589. package/dist/cli/config/vitest-exclude.constants.js +55 -0
  590. package/dist/cli/config/vitest-exclude.constants.js.map +1 -0
  591. package/frontend/dist/assets/index-890d3f9d.js +4961 -0
  592. package/frontend/dist/index.html +1 -1
  593. package/package.json +2 -2
  594. package/packages/crewly-agent/src/runtime/agent-runner.service.test.ts +174 -30
  595. package/packages/crewly-agent/src/runtime/agent-runner.service.ts +121 -6
  596. package/packages/crewly-agent/src/runtime/types.ts +56 -5
  597. package/frontend/dist/assets/index-aa737fd3.js +0 -5254
@@ -0,0 +1,1928 @@
1
+ /**
2
+ * Slack-Orchestrator Bridge
3
+ *
4
+ * Routes messages between Slack and the Crewly orchestrator,
5
+ * enabling mobile control of AI teams.
6
+ *
7
+ * @module services/slack/bridge
8
+ */
9
+ import { EventEmitter } from 'events';
10
+ import { promises as fs, createWriteStream } from 'fs';
11
+ import path from 'path';
12
+ import os from 'os';
13
+ import { Readable } from 'stream';
14
+ import { pipeline } from 'stream/promises';
15
+ import { PDFParse } from 'pdf-parse';
16
+ import { getSlackService } from './slack.service.js';
17
+ import { getChatV2Service } from '../chat-v2/chat-v2.singleton.js';
18
+ import { synthesizeSlackConversationId } from '../chat-v2/legacy-dto.utils.js';
19
+ import { isOrchestratorActive, isAgentActive, getOrchestratorOfflineMessage, triggerOrchestratorSetup, } from '../orchestrator/index.js';
20
+ import { parseCommandIntent, } from '../../types/slack.types.js';
21
+ import { ContentApprovalService } from '../onboarding/content-approval.service.js';
22
+ import { getSlackImageService } from './slack-image.service.js';
23
+ import { ORCHESTRATOR_SESSION_NAME, MESSAGE_QUEUE_CONSTANTS, SLACK_IMAGE_CONSTANTS, SLACK_FILE_DOWNLOAD_CONSTANTS, SLACK_BRIDGE_CONSTANTS, AUDITOR_SCHEDULER_CONSTANTS, THREAD_STATUS_CONSTANTS } from '../../constants.js';
24
+ import { LoggerService } from '../core/logger.service.js';
25
+ import { CROSS_MACHINE_PREFIX } from '../../types/cross-machine.types.js';
26
+ import { getCrossMachineMessageService } from './cross-machine-message.service.js';
27
+ import { TERMINAL_REQUEST_STATUSES } from '../../types/v2/request.types.js';
28
+ /**
29
+ * Default bridge configuration
30
+ */
31
+ const DEFAULT_CONFIG = {
32
+ orchestratorSession: ORCHESTRATOR_SESSION_NAME,
33
+ showTypingIndicator: true,
34
+ maxResponseLength: 3000,
35
+ enableNotifications: true,
36
+ responseTimeoutMs: (MESSAGE_QUEUE_CONSTANTS?.DEFAULT_MESSAGE_TIMEOUT ?? 120000) + 5000,
37
+ skillDeliveryWaitMs: SLACK_BRIDGE_CONSTANTS?.SKILL_DELIVERY_WAIT_MS ?? 3000,
38
+ };
39
+ /**
40
+ * Maximum time to wait for an auto-recovery setup attempt before giving
41
+ * up and falling through to the offline path. Capped tight to avoid
42
+ * blocking the message ingress thread when setup is wedged.
43
+ */
44
+ const AUTO_RECOVERY_TIMEOUT_MS = 5_000;
45
+ // ---------------------------------------------------------------------------
46
+ // Auto-Request suppression gates (Pipeline-#4 fix, Patch D)
47
+ //
48
+ // Spec: 2026-05-05-request-decompose-pipeline-gap.md §4 Patch D.
49
+ // Live data measured ~80% of auto-Requests in D0AC7NF5N7L matched one of
50
+ // these gates and were never actionable.
51
+ // ---------------------------------------------------------------------------
52
+ /**
53
+ * Minimum trimmed text length required to create an auto-Request. Anything
54
+ * shorter is treated as "trivial" (length-gate). Set deliberately tight so a
55
+ * one-sentence command like "fix the build" (15 chars) still passes.
56
+ */
57
+ const MIN_AUTO_REQUEST_TEXT_LENGTH = 12;
58
+ /**
59
+ * Trimmed-text regex for trivial acknowledgement messages — anchored,
60
+ * case-insensitive, allows trailing punctuation/emoji whitespace. Matches
61
+ * "ok", "好的", "thx", "👍", etc. Add new locale tokens here as needed.
62
+ */
63
+ const TRIVIAL_ACK_PATTERN = /^(ok|好的|收到|thx|thanks|谢谢|👍|✅|got it|sure|yes|是|对|对的)\W*$/i;
64
+ /**
65
+ * Trimmed-text regex matching the synthetic `[Slack File: …]` titles
66
+ * generated by the file-handling path when a user uploads a file with no
67
+ * narrative. Caught before the thread-continuation gate to avoid the lookup.
68
+ */
69
+ const FILE_ONLY_TITLE_PATTERN = /^\[Slack File:[^\]]*\]\.?\s*$/;
70
+ /**
71
+ * Pure pre-fetch gates for auto-Request suppression (Patch D). Exported for
72
+ * focused unit testing — the full thread-continuation gate also lives in
73
+ * {@link SlackOrchestratorBridge.shouldSuppressAutoRequest} but requires a
74
+ * RequestService lookup so cannot be a pure function.
75
+ *
76
+ * Each function returns the suppression reason string (matches the values
77
+ * logged at debug level) or `null` when the gate does not match.
78
+ */
79
+ export function suppressTrivialOrShort(rawText) {
80
+ const trimmed = rawText.trim();
81
+ if (trimmed.length < MIN_AUTO_REQUEST_TEXT_LENGTH || TRIVIAL_ACK_PATTERN.test(trimmed)) {
82
+ return 'trivial_or_short';
83
+ }
84
+ return null;
85
+ }
86
+ export function suppressFileOnly(rawText) {
87
+ const trimmed = rawText.trim();
88
+ return FILE_ONLY_TITLE_PATTERN.test(trimmed) ? 'file_only' : null;
89
+ }
90
+ /**
91
+ * Slack-Orchestrator Bridge singleton
92
+ */
93
+ let bridgeInstance = null;
94
+ /**
95
+ * Derive a short, readable chat-v2 channel name from a Slack thread's first
96
+ * message — so the consolidated chat shows a meaningful title rather than the
97
+ * synthesized `slack-<chan>-<ts>` id (which is identical across a channel's
98
+ * threads). Collapses whitespace and truncates; falls back to "Slack thread".
99
+ *
100
+ * @param content - The first inbound message of the thread.
101
+ * @returns A title like `Slack: ship the v2 launch deck`.
102
+ */
103
+ export function deriveSlackThreadName(content) {
104
+ const oneLine = (content ?? '').replace(/\s+/g, ' ').trim();
105
+ if (!oneLine)
106
+ return 'Slack thread';
107
+ const snippet = oneLine.length > 48 ? `${oneLine.slice(0, 48)}…` : oneLine;
108
+ return `Slack: ${snippet}`;
109
+ }
110
+ /**
111
+ * SlackOrchestratorBridge class
112
+ *
113
+ * Routes messages between Slack and the Crewly orchestrator.
114
+ * Handles command parsing, response formatting, and proactive notifications.
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * const bridge = getSlackOrchestratorBridge();
119
+ * await bridge.initialize();
120
+ *
121
+ * // Send notification when task completes
122
+ * await bridge.notifyTaskCompleted('Fix bug', 'Developer', 'MyProject');
123
+ * ```
124
+ */
125
+ export class SlackOrchestratorBridge extends EventEmitter {
126
+ logger = LoggerService.getInstance().createComponentLogger('SlackBridge');
127
+ slackService;
128
+ chatV2;
129
+ messageQueueService = null;
130
+ threadStatusQueue = null;
131
+ threadStore = null;
132
+ config;
133
+ initialized = false;
134
+ /** Track if we've already logged the missing scope warning */
135
+ loggedMissingScope = false;
136
+ /**
137
+ * Pending completion reactions keyed by "channelId:threadTs".
138
+ * Stores the original message ts so that the ✅ reaction is added to the
139
+ * correct message when the reply-slack skill delivers a response.
140
+ */
141
+ pendingReactions = new Map();
142
+ /**
143
+ * Track channel+thread pairs where Slack delivery was already handled
144
+ * by the reply-slack skill (execute.sh). Used by sendSlackResponse to
145
+ * avoid sending a duplicate fallback message to the same thread.
146
+ */
147
+ /**
148
+ * Create a new SlackOrchestratorBridge
149
+ *
150
+ * @param config - Partial configuration to override defaults
151
+ */
152
+ constructor(config = {}) {
153
+ super();
154
+ this.slackService = getSlackService();
155
+ this.chatV2 = getChatV2Service();
156
+ this.config = { ...DEFAULT_CONFIG, ...config };
157
+ }
158
+ /**
159
+ * Initialize the bridge
160
+ *
161
+ * Sets up message listeners and orchestrator subscriptions.
162
+ *
163
+ * @returns Promise that resolves when initialized
164
+ */
165
+ async initialize() {
166
+ if (this.initialized)
167
+ return;
168
+ // Listen for Slack messages
169
+ this.slackService.on('message', this.handleSlackMessage.bind(this));
170
+ // Initialize cross-machine messaging and route received messages to orchestrator
171
+ try {
172
+ const crossMachineService = getCrossMachineMessageService();
173
+ await crossMachineService.initialize();
174
+ crossMachineService.on('message', (msg) => {
175
+ this.handleCrossMachineMessage(msg);
176
+ });
177
+ this.logger.info('Cross-machine message listener registered');
178
+ }
179
+ catch (err) {
180
+ this.logger.warn('Cross-machine messaging not available', {
181
+ error: err instanceof Error ? err.message : String(err),
182
+ });
183
+ }
184
+ this.initialized = true;
185
+ this.logger.info('Initialized');
186
+ }
187
+ /**
188
+ * Check if bridge is initialized
189
+ *
190
+ * @returns True if initialized
191
+ */
192
+ isInitialized() {
193
+ return this.initialized;
194
+ }
195
+ /**
196
+ * Set the message queue service for enqueuing messages to the orchestrator.
197
+ * Called during server initialization.
198
+ *
199
+ * @param service - The MessageQueueService instance
200
+ */
201
+ setMessageQueueService(service) {
202
+ this.messageQueueService = service;
203
+ }
204
+ /**
205
+ * Set the thread status queue service for tracking inbound message lifecycle.
206
+ * Called during server initialization.
207
+ *
208
+ * @param service - The ThreadStatusQueueService instance
209
+ */
210
+ setThreadStatusQueue(service) {
211
+ this.threadStatusQueue = service;
212
+ }
213
+ /**
214
+ * Set the Slack thread store service for persisting thread conversations.
215
+ * Called during server initialization.
216
+ *
217
+ * @param store - The SlackThreadStoreService instance
218
+ */
219
+ setSlackThreadStore(store) {
220
+ this.threadStore = store;
221
+ }
222
+ /**
223
+ * Get current configuration
224
+ *
225
+ * @returns A copy of the current configuration
226
+ */
227
+ getConfig() {
228
+ return { ...this.config };
229
+ }
230
+ /**
231
+ * Handle incoming Slack message
232
+ *
233
+ * Parses the command, routes to appropriate handler, and sends response.
234
+ *
235
+ * @param message - Incoming Slack message
236
+ */
237
+ async handleSlackMessage(message) {
238
+ this.logger.info('Received message', {
239
+ preview: (message.text || '').substring(0, 50),
240
+ hasImages: message.hasImages,
241
+ hasFiles: message.hasFiles,
242
+ fileCount: message.files?.length || 0,
243
+ });
244
+ try {
245
+ // Cross-machine message intercept — detect and route before normal processing
246
+ if (message.text && message.text.startsWith(CROSS_MACHINE_PREFIX)) {
247
+ const crossMachineService = getCrossMachineMessageService();
248
+ if (crossMachineService.isEnabled()) {
249
+ const handled = crossMachineService.handleIncomingMessage(message);
250
+ if (handled) {
251
+ this.logger.debug('Cross-machine message handled', {
252
+ preview: message.text.substring(0, 80),
253
+ });
254
+ return; // Don't process as a normal message
255
+ }
256
+ }
257
+ }
258
+ // Download files if present (both images and non-image files)
259
+ if (message.hasFiles && message.files) {
260
+ const imageFiles = message.files.filter(f => f.mimetype?.startsWith('image/'));
261
+ const nonImageFiles = message.files.filter(f => !f.mimetype?.startsWith('image/'));
262
+ // Download images via existing path (pass only imageFiles to avoid redundant API calls)
263
+ if (imageFiles.length > 0) {
264
+ await this.downloadMessageImages(message, imageFiles);
265
+ }
266
+ // Download non-image files
267
+ if (nonImageFiles.length > 0) {
268
+ await this.downloadMessageFiles(message, nonImageFiles);
269
+ }
270
+ }
271
+ // Get or create conversation context
272
+ const context = this.slackService.getConversationContext(message.threadTs || message.ts, message.channelId, message.userId);
273
+ // Build enriched text with file references for the agent
274
+ const enrichedText = this.enrichTextWithFiles(message);
275
+ // Store inbound message in thread file (with image metadata)
276
+ if (this.threadStore) {
277
+ const threadTs = message.threadTs || message.ts;
278
+ try {
279
+ const userName = message.user?.realName || message.user?.name || message.userId;
280
+ await this.threadStore.appendUserMessage(message.channelId, threadTs, userName, enrichedText, message.images);
281
+ }
282
+ catch (err) {
283
+ this.logger.warn('Failed to store thread message', { error: err instanceof Error ? err.message : String(err) });
284
+ }
285
+ }
286
+ // Override message text with enriched version for downstream processing
287
+ message.text = enrichedText;
288
+ // Auditor prefix routing — intercept "auditor ...", "/auditor ...", or "@auditor ..." messages
289
+ const auditorMatch = enrichedText.match(/^[/@]?auditor[\s:]+(.+)/is);
290
+ if (auditorMatch) {
291
+ const auditorMessage = auditorMatch[1].trim();
292
+ try {
293
+ if (this.config.showTypingIndicator) {
294
+ await this.addTypingIndicator(message);
295
+ }
296
+ const { AuditorSchedulerService } = await import('../agent/auditor-scheduler.service.js');
297
+ const scheduler = AuditorSchedulerService.getInstance();
298
+ await scheduler.handleUserMessage(auditorMessage, {
299
+ channelId: message.channelId,
300
+ threadTs: message.threadTs || message.ts,
301
+ });
302
+ if (this.config.showTypingIndicator) {
303
+ await this.markComplete(message);
304
+ }
305
+ }
306
+ catch (error) {
307
+ await this.sendErrorResponse(message, error);
308
+ }
309
+ return;
310
+ }
311
+ // #177: @mention routing — route to specific agent if @name is detected
312
+ const mentionTarget = await this.resolveMentionTarget(enrichedText);
313
+ if (mentionTarget) {
314
+ const isActive = await isAgentActive(mentionTarget.sessionName);
315
+ if (isActive) {
316
+ this.logger.info('Routing message to mentioned agent', {
317
+ agent: mentionTarget.name,
318
+ session: mentionTarget.sessionName,
319
+ });
320
+ if (this.config.showTypingIndicator) {
321
+ await this.addTypingIndicator(message);
322
+ }
323
+ // Strip the @mention prefix and send to the target agent
324
+ const cleanMessage = enrichedText.replace(new RegExp(`^@${mentionTarget.name}\\s*`, 'i'), '').trim();
325
+ const agentResponse = await this.sendToAgent(mentionTarget.sessionName, cleanMessage || enrichedText, context);
326
+ // Issue #394: `fromOrcReply` comes from the envelope — only
327
+ // `true` when slackResolve fired with a non-empty body. The
328
+ // pre-fix `response.length > 0` proxy false-positived on the
329
+ // 3 placeholder strings (timeout, direct-delivery, error),
330
+ // any of which would have auto-resolved the SLA tracker on a
331
+ // non-reply.
332
+ await this.sendSlackResponse(message, agentResponse.response, agentResponse.fromOrcReply);
333
+ if (this.config.showTypingIndicator) {
334
+ await this.markComplete(message);
335
+ }
336
+ return;
337
+ }
338
+ else {
339
+ this.logger.info('Mentioned agent is inactive, falling back to orchestrator', {
340
+ agent: mentionTarget.name,
341
+ });
342
+ }
343
+ }
344
+ // Parse command intent
345
+ const command = this.parseCommand(message.text);
346
+ // Add typing indicator
347
+ if (this.config.showTypingIndicator) {
348
+ await this.addTypingIndicator(message);
349
+ }
350
+ // Handle based on intent.
351
+ // `response` is the user-visible text; `fromOrcReply` tracks whether
352
+ // the response came from the orc's reply-slack callback (true) or a
353
+ // local string / fallback / placeholder (false). Only true responses
354
+ // may auto-resolve the V3 SLA tracker downstream.
355
+ let response;
356
+ let fromOrcReply = false;
357
+ let isOrchestratorRoute = false;
358
+ switch (command.intent) {
359
+ case 'help':
360
+ response = this.getHelpMessage();
361
+ break;
362
+ case 'status': {
363
+ const r = await this.handleStatusCommand(command, context);
364
+ response = r.response;
365
+ fromOrcReply = r.fromOrcReply;
366
+ break;
367
+ }
368
+ case 'list_projects':
369
+ case 'list_teams':
370
+ case 'list_agents': {
371
+ const r = await this.handleListCommand(command, context);
372
+ response = r.response;
373
+ fromOrcReply = r.fromOrcReply;
374
+ break;
375
+ }
376
+ case 'pause':
377
+ case 'resume': {
378
+ const r = await this.handleControlCommand(command, context);
379
+ response = r.response;
380
+ fromOrcReply = r.fromOrcReply;
381
+ break;
382
+ }
383
+ default:
384
+ // V3 Request creation for Slack user messages — fire-and-forget
385
+ setImmediate(async () => {
386
+ try {
387
+ const { RequestService } = await import('../v3/request.service.js');
388
+ const svc = RequestService.getInstance();
389
+ // 2026-05-13 dogfood fix: encode BOTH thread root AND message
390
+ // ts in sourceConversationItemId. The previous form was
391
+ // `slack-{channel}-{message.ts}`, which made
392
+ // `extractSlackThreadTs(sourceConv)` return the per-message
393
+ // ts — never the actual Slack thread root. The SLA
394
+ // threadIndex therefore mapped per-message ts → requestId,
395
+ // but when orc replied via reply-slack → POST /api/slack/send
396
+ // → markResolvedByThread(realThreadRoot), the lookup missed
397
+ // and the Request never auto-closed (PR #538 hook was a
398
+ // no-op for thread-reply messages).
399
+ //
400
+ // New form:
401
+ // Top-level message (msg.ts == thread root):
402
+ // `slack-{channelId}-{ts}` (unchanged)
403
+ // Reply within an existing thread:
404
+ // `slack-{channelId}-{threadRoot}-msg-{messageTs}`
405
+ //
406
+ // The `-msg-{...}` suffix preserves per-message uniqueness
407
+ // so `findBySourceConversationItemId` still works. The
408
+ // updated `parseSlackThreadContext` strips the suffix
409
+ // before extracting the threadTs, so the SLA's threadIndex
410
+ // now keys on the actual thread root for both shapes.
411
+ const threadRoot = message.threadTs || message.ts;
412
+ const msgId = message.threadTs && message.threadTs !== message.ts
413
+ ? `slack-${message.channelId}-${threadRoot}-msg-${message.ts}`
414
+ : `slack-${message.channelId}-${message.ts}`;
415
+ const existing = await svc.findBySourceConversationItemId(msgId);
416
+ if (existing)
417
+ return;
418
+ const rawText = message.text || '';
419
+ // Pipeline-#4 fix (spec 2026-05-05-request-decompose-pipeline-gap.md,
420
+ // Patch D): suppress trivial / continuation / file-only Slack messages
421
+ // that pollute the Request stream without ever becoming actionable
422
+ // work. ~80% of historical auto-Requests in D0AC7NF5N7L matched one
423
+ // of these gates (dogfood data 2026-05-05).
424
+ const suppressionReason = await this.shouldSuppressAutoRequest(message, rawText, svc);
425
+ if (suppressionReason) {
426
+ this.logger.debug('V3 Request creation suppressed', { msgId, reason: suppressionReason });
427
+ return;
428
+ }
429
+ const { generateRequestTitle, classifyIntent } = await import('../v3/v3-data.service.js');
430
+ // Auto-classify so request-decompose.subscriber sees correct
431
+ // intentLevel/intentCategory. Without this, every Slack-auto
432
+ // Request defaulted to L1+other and was never decomposed
433
+ // into delegation WorkItems — the entire Slack→WorkItem
434
+ // pipeline was silently no-op.
435
+ const { intentLevel, intentCategory } = classifyIntent(rawText);
436
+ const request = await svc.create({
437
+ title: generateRequestTitle(rawText, intentCategory),
438
+ description: rawText,
439
+ sourceConversationItemId: msgId,
440
+ priority: 'normal',
441
+ tags: ['slack'],
442
+ intentLevel,
443
+ intentCategory,
444
+ });
445
+ // P2-2: RequestTracker.setActiveRequest write removed. The
446
+ // companion read in v3-data.service.ts no longer falls back
447
+ // to time-window correlation; downstream delegate-task
448
+ // skills must now pass requestId explicitly.
449
+ this.logger.debug('V3 Request created from Slack message', { msgId, requestId: request.id });
450
+ }
451
+ catch (err) {
452
+ this.logger.warn('V3 Request creation from Slack failed (non-critical)', {
453
+ error: err instanceof Error ? err.message : String(err),
454
+ });
455
+ }
456
+ });
457
+ // Send to orchestrator for processing
458
+ {
459
+ const r = await this.sendToOrchestrator(message.text, context);
460
+ response = r.response;
461
+ fromOrcReply = r.fromOrcReply;
462
+ }
463
+ isOrchestratorRoute = true;
464
+ }
465
+ // Send response back to Slack. The `fromOrcReply` flag gates the V3
466
+ // SLA auto-resolve hook inside sendSlackResponse — placeholders and
467
+ // fallbacks must NOT mark the respond_to_user WI as resolved.
468
+ await this.sendSlackResponse(message, response, fromOrcReply);
469
+ // For orchestrator-routed messages, defer ✅ until reply-slack delivers
470
+ // the actual response. Store pending reaction keyed by channel+thread
471
+ // so addCompletionReaction() can find it when the reply arrives.
472
+ if (isOrchestratorRoute && this.config.showTypingIndicator) {
473
+ const threadTs = message.threadTs || message.ts;
474
+ const key = `${message.channelId}:${threadTs}`;
475
+ this.pendingReactions.set(key, message.ts);
476
+ }
477
+ else if (this.config.showTypingIndicator) {
478
+ // Non-orchestrator commands complete immediately
479
+ await this.markComplete(message);
480
+ }
481
+ this.emit('message_handled', { message, response });
482
+ }
483
+ catch (error) {
484
+ this.logger.error('Error handling message', { error: error instanceof Error ? error.message : String(error) });
485
+ await this.sendErrorResponse(message, error);
486
+ this.emit('error', error);
487
+ }
488
+ }
489
+ /**
490
+ * Handle an incoming cross-machine message.
491
+ * Routes the message payload to the local orchestrator via the message queue.
492
+ *
493
+ * @param msg - Parsed cross-machine message
494
+ */
495
+ async handleCrossMachineMessage(msg) {
496
+ this.logger.info('Routing cross-machine message to orchestrator', {
497
+ from: msg.fromName,
498
+ type: msg.type,
499
+ messageId: msg.messageId,
500
+ });
501
+ // Format as a human-readable message for the orchestrator
502
+ const orchestratorMessage = [
503
+ `[Cross-Machine] from ${msg.fromName} (${msg.from})`,
504
+ `Type: ${msg.type}`,
505
+ msg.payload.message ? `Message: ${msg.payload.message}` : '',
506
+ msg.payload.task ? `Task: ${msg.payload.task}` : '',
507
+ msg.payload.priority ? `Priority: ${msg.payload.priority}` : '',
508
+ msg.payload.context ? `Context: ${msg.payload.context}` : '',
509
+ msg.payload.summary ? `Summary: ${msg.payload.summary}` : '',
510
+ ].filter(Boolean).join('\n');
511
+ if (this.messageQueueService) {
512
+ try {
513
+ this.messageQueueService.enqueue({
514
+ content: orchestratorMessage,
515
+ conversationId: `x-machine-${msg.from}-${Date.now()}`,
516
+ source: 'cross-machine',
517
+ sourceMetadata: {
518
+ fromDevice: msg.from,
519
+ fromDeviceName: msg.fromName,
520
+ messageType: msg.type,
521
+ messageId: msg.messageId,
522
+ },
523
+ });
524
+ this.logger.info('Cross-machine message enqueued to orchestrator');
525
+ }
526
+ catch (err) {
527
+ this.logger.error('Failed to enqueue cross-machine message', {
528
+ error: err instanceof Error ? err.message : String(err),
529
+ });
530
+ }
531
+ }
532
+ else {
533
+ this.logger.warn('Cannot route cross-machine message — MessageQueueService not available');
534
+ }
535
+ }
536
+ /**
537
+ * Parse command from message text
538
+ *
539
+ * Cleans the message and extracts the command intent.
540
+ *
541
+ * @param text - Raw message text
542
+ * @returns Parsed command with intent and parameters
543
+ */
544
+ parseCommand(text) {
545
+ // Remove bot mention if present
546
+ const cleanedText = text.replace(/<@[A-Z0-9]+>/g, '').trim();
547
+ return {
548
+ intent: parseCommandIntent(cleanedText),
549
+ rawText: cleanedText,
550
+ parameters: this.extractParameters(cleanedText),
551
+ };
552
+ }
553
+ /**
554
+ * Extract parameters from command text
555
+ *
556
+ * @param text - Command text
557
+ * @returns Extracted parameters
558
+ */
559
+ extractParameters(text) {
560
+ const params = {};
561
+ // Extract quoted strings
562
+ const quotedMatch = text.match(/"([^"]+)"/);
563
+ if (quotedMatch) {
564
+ params.quoted = quotedMatch[1];
565
+ }
566
+ // Extract @mentions
567
+ const mentionMatch = text.match(/@(\w+)/);
568
+ if (mentionMatch) {
569
+ params.mention = mentionMatch[1];
570
+ }
571
+ // Extract project/team names after keywords
572
+ const forMatch = text.match(/(?:for|on|in)\s+(\w+)/i);
573
+ if (forMatch) {
574
+ params.target = forMatch[1];
575
+ }
576
+ return params;
577
+ }
578
+ /**
579
+ * Get help message
580
+ *
581
+ * @returns Formatted help message for Slack
582
+ */
583
+ getHelpMessage() {
584
+ return `*Crewly Commands:*
585
+
586
+ :clipboard: *Status*
587
+ • \`status\` - Get overall status
588
+ • \`status [project name]\` - Get project status
589
+ • \`how is [team] doing\` - Get team progress
590
+
591
+ :page_facing_up: *Lists*
592
+ • \`list projects\` - Show all projects
593
+ • \`list teams\` - Show all teams
594
+ • \`list agents\` or \`who's working\` - Show active agents
595
+
596
+ :gear: *Actions*
597
+ • \`assign [task] to [agent/team]\` - Assign work
598
+ • \`create task: [description]\` - Create a new task
599
+ • \`pause [agent/team]\` - Pause work
600
+ • \`resume [agent/team]\` - Resume work
601
+
602
+ :speech_balloon: *Chat*
603
+ Just type naturally to chat with the orchestrator!`;
604
+ }
605
+ /**
606
+ * Determine whether an inbound Slack message should be suppressed from
607
+ * auto-Request creation. Returns a reason string when the message should
608
+ * be skipped, or `null` when a Request should be created normally.
609
+ *
610
+ * Three gates (Pipeline-#4 fix, spec
611
+ * 2026-05-05-request-decompose-pipeline-gap.md, Patch D):
612
+ *
613
+ * 1. **Length / acknowledgement gate.** Trimmed text is shorter than
614
+ * {@link MIN_AUTO_REQUEST_TEXT_LENGTH} OR matches the trivial
615
+ * acknowledgement allowlist ({@link TRIVIAL_ACK_PATTERN}). Catches
616
+ * "好的", "ok", "👍", "thx" — class noise that is never actionable.
617
+ *
618
+ * 2. **Thread continuation gate.** When the inbound message is a reply
619
+ * inside an existing thread (`message.threadTs` ≠ `message.ts`) AND a
620
+ * Request already exists for the **parent** message, suppress to
621
+ * prevent every "are we still in this thread?" follow-up from creating
622
+ * a new sibling Request. Existing Request continues to receive
623
+ * orchestrator routing.
624
+ *
625
+ * 3. **File-only / synthetic-title gate.** Messages whose text is a
626
+ * synthetic `[Slack File: …]` placeholder with no narrative are
627
+ * artefacts of slack-bridge file-handling, not user requests.
628
+ *
629
+ * Each gate returns a stable reason string for log auditability. The
630
+ * caller logs at `debug` so suppression decisions are observable without
631
+ * noise.
632
+ *
633
+ * @param message - The incoming Slack message (already validated)
634
+ * @param rawText - The raw text body, trimmed by caller if needed
635
+ * @param svc - The RequestService instance for the thread-continuation lookup
636
+ * @returns A reason string when suppressed; `null` when allowed
637
+ */
638
+ async shouldSuppressAutoRequest(message, rawText, svc) {
639
+ const trimmed = rawText.trim();
640
+ // Gate 1 — length + ack allowlist
641
+ if (trimmed.length < MIN_AUTO_REQUEST_TEXT_LENGTH || TRIVIAL_ACK_PATTERN.test(trimmed)) {
642
+ return 'trivial_or_short';
643
+ }
644
+ // Gate 3 — file-only synthetic title (run before gate 2 to skip the I/O)
645
+ if (FILE_ONLY_TITLE_PATTERN.test(trimmed)) {
646
+ return 'file_only';
647
+ }
648
+ // Gate 2 — thread continuation: this message is a reply, and the parent
649
+ // message already has a NON-TERMINAL Request. Cheaper than threading
650
+ // detection at the SLA layer.
651
+ //
652
+ // ARCH 2026-05-05 PR #453 review (NC-1): the gate MUST check
653
+ // `parentRequest.status` is non-terminal — otherwise a thread that
654
+ // completed (parent → done via orc_reply / orc_reply_recheck) and then
655
+ // received a NEW actionable user message would silently suppress the new
656
+ // Request, re-opening the user-visible reliability gap that INBOUND-1
657
+ // was built to close. Terminal-parent threads release suppression so the
658
+ // child message creates a fresh Request.
659
+ const threadTs = message.threadTs;
660
+ if (threadTs && threadTs !== message.ts) {
661
+ const parentMsgId = `slack-${message.channelId}-${threadTs}`;
662
+ const parentRequest = await svc.findBySourceConversationItemId(parentMsgId);
663
+ if (parentRequest && !TERMINAL_REQUEST_STATUSES.has(parentRequest.status)) {
664
+ return 'thread_continuation';
665
+ }
666
+ }
667
+ return null;
668
+ }
669
+ /**
670
+ * Handle status command
671
+ *
672
+ * @param command - Parsed command
673
+ * @param context - Conversation context
674
+ * @returns Status response
675
+ */
676
+ async handleStatusCommand(command, context) {
677
+ return await this.sendToOrchestrator(`Give me a brief status update. ${command.rawText}`, context);
678
+ }
679
+ /**
680
+ * Handle list commands (projects, teams, agents)
681
+ *
682
+ * @param command - Parsed command
683
+ * @param context - Conversation context
684
+ * @returns List response
685
+ */
686
+ async handleListCommand(command, context) {
687
+ const prompts = {
688
+ list_projects: 'List all active projects with their status.',
689
+ list_teams: 'List all teams and their current assignments.',
690
+ list_agents: 'List all agents, their status, and what they are working on.',
691
+ };
692
+ return await this.sendToOrchestrator(prompts[command.intent] || command.rawText, context);
693
+ }
694
+ /**
695
+ * Handle control commands (pause, resume)
696
+ *
697
+ * @param command - Parsed command
698
+ * @param context - Conversation context
699
+ * @returns Control response
700
+ */
701
+ async handleControlCommand(command, context) {
702
+ const action = command.intent === 'pause' ? 'Pause' : 'Resume';
703
+ const target = command.parameters.target || command.parameters.mention || 'all agents';
704
+ return await this.sendToOrchestrator(`${action} ${target}.`, context);
705
+ }
706
+ /**
707
+ * Attempt to auto-recover the orchestrator with a hard timeout.
708
+ *
709
+ * Calls `triggerOrchestratorSetup()` from the orchestrator service module
710
+ * directly (bypassing HTTP). Wraps the attempt in a 5-second deadline so
711
+ * a wedged setup cannot stall the Slack message ingress thread.
712
+ *
713
+ * Behavior:
714
+ * - Timeout: rejects with a TimeoutError-like message after 5s.
715
+ * - Setup error: rejects with the underlying error.
716
+ * - Setup returning `success: false`: rejects with the recorded error
717
+ * (the caller treats this the same as a thrown error and falls
718
+ * through to the offline path).
719
+ *
720
+ * Caller is expected to wrap the call in try/catch and continue down
721
+ * the offline path on rejection — auto-recovery is best-effort.
722
+ *
723
+ * @returns Resolves when setup completes successfully (or was skipped
724
+ * because the orc was already healthy)
725
+ * @throws Error on timeout or setup failure
726
+ * @internal Visible for tests via class member access.
727
+ */
728
+ async attemptAutoRecovery() {
729
+ let timeoutHandle;
730
+ const timeoutPromise = new Promise((_, reject) => {
731
+ timeoutHandle = setTimeout(() => {
732
+ reject(new Error(`auto-recovery setup timed out after ${AUTO_RECOVERY_TIMEOUT_MS}ms`));
733
+ }, AUTO_RECOVERY_TIMEOUT_MS);
734
+ // Don't keep the process alive just for this timer
735
+ timeoutHandle.unref?.();
736
+ });
737
+ try {
738
+ const result = await Promise.race([triggerOrchestratorSetup(), timeoutPromise]);
739
+ if (!result.success) {
740
+ throw new Error(result.error || 'orchestrator setup returned success=false');
741
+ }
742
+ }
743
+ finally {
744
+ if (timeoutHandle) {
745
+ clearTimeout(timeoutHandle);
746
+ }
747
+ }
748
+ }
749
+ /**
750
+ * Send message to orchestrator via the message queue and wait for response.
751
+ *
752
+ * Checks if the orchestrator is active before sending. Enqueues the message
753
+ * and waits for the response via a resolve callback injected into sourceMetadata.
754
+ *
755
+ * @param message - Message to send
756
+ * @param context - Optional conversation context
757
+ * @returns Orchestrator response or offline/error message
758
+ */
759
+ async sendToOrchestrator(message, context) {
760
+ try {
761
+ // Check if orchestrator is active before attempting to send
762
+ let isActive = await isOrchestratorActive();
763
+ // Auto-recovery (B0 hot-fix, defense-in-depth):
764
+ // ESTestNode regression — `isActive` falsely reports offline when the
765
+ // orchestrator is an in-process Crewly Agent runtime that has lost its
766
+ // status registration but is otherwise healthy. Before falling
767
+ // through to the offline path, attempt one synchronous setup call.
768
+ // This is intentionally once-per-message (not retry-loop). On
769
+ // success we re-check isActive and proceed normally; on failure we
770
+ // continue down the existing offline branch (Auditor + queue).
771
+ if (!isActive) {
772
+ const recoveryStart = Date.now();
773
+ this.logger.info('Orchestrator offline — attempting auto-recovery via triggerOrchestratorSetup', {
774
+ timeoutMs: AUTO_RECOVERY_TIMEOUT_MS,
775
+ });
776
+ try {
777
+ await this.attemptAutoRecovery();
778
+ isActive = await isOrchestratorActive();
779
+ this.logger.info('Auto-recovery setup attempt complete', {
780
+ elapsedMs: Date.now() - recoveryStart,
781
+ isActiveAfter: isActive,
782
+ });
783
+ }
784
+ catch (err) {
785
+ this.logger.warn('Auto-recovery setup attempt failed (falling through to offline path)', {
786
+ elapsedMs: Date.now() - recoveryStart,
787
+ error: err instanceof Error ? err.message : String(err),
788
+ });
789
+ }
790
+ }
791
+ if (!isActive) {
792
+ // Fallback: route to Auditor agent if it's active
793
+ const auditorSession = AUDITOR_SCHEDULER_CONSTANTS.AUDITOR_SESSION_NAME;
794
+ const auditorActive = await isAgentActive(auditorSession);
795
+ if (auditorActive) {
796
+ this.logger.info('Orchestrator offline — routing message to Auditor agent');
797
+ // Auditor fallback is NOT an orc reply — caller must not auto-resolve SLA.
798
+ const fallback = await this.sendToAuditorFallback(message, context);
799
+ return { response: fallback, fromOrcReply: false };
800
+ }
801
+ // #247: Queue the message for replay when orchestrator comes back online,
802
+ // instead of silently dropping it. The queue processor defers delivery
803
+ // until the orchestrator registers (agentStatus === 'active').
804
+ // NOTE: This is an offline-path acknowledgement, NOT a real orc reply —
805
+ // every return from this branch must set `fromOrcReply: false`.
806
+ if (this.messageQueueService) {
807
+ this.logger.info('Orchestrator offline — queuing message for replay when it comes back online');
808
+ // Enrich message with thread file path hint for orchestrator context
809
+ let enrichedMessage = message;
810
+ if (this.threadStore && context) {
811
+ const threadFilePath = this.threadStore.getThreadFilePath(context.channelId, context.threadTs);
812
+ enrichedMessage = `${message}\n\n[Thread context file: ${threadFilePath}]`;
813
+ }
814
+ // Store in canonical chat-v2 for persistence
815
+ const result = {
816
+ conversation: {
817
+ id: this.persistSlackInbound({
818
+ content: enrichedMessage,
819
+ conversationId: context?.conversationId,
820
+ agentSession: 'crewly-orc',
821
+ channelId: context?.channelId,
822
+ threadTs: context?.threadTs,
823
+ userId: context?.userId,
824
+ }).conversationId,
825
+ },
826
+ };
827
+ try {
828
+ this.messageQueueService.enqueue({
829
+ content: enrichedMessage,
830
+ conversationId: result.conversation.id,
831
+ source: 'slack',
832
+ sourceMetadata: {
833
+ userId: context?.userId,
834
+ channelId: context?.channelId,
835
+ threadTs: context?.threadTs,
836
+ },
837
+ });
838
+ // Track inbound thread for offline message recovery
839
+ if (this.threadStatusQueue && context?.channelId) {
840
+ try {
841
+ const threadKey = context.threadTs
842
+ ? `${context.channelId}:${context.threadTs}`
843
+ : `${context.channelId}:${Date.now()}`;
844
+ this.threadStatusQueue.trackInbound({
845
+ source: 'slack',
846
+ threadKey,
847
+ conversationId: result.conversation.id,
848
+ messagePreview: message.slice(0, THREAD_STATUS_CONSTANTS.MAX_PREVIEW_LENGTH),
849
+ sourceMetadata: {
850
+ channelId: context.channelId,
851
+ threadTs: context.threadTs,
852
+ userId: context.userId,
853
+ },
854
+ });
855
+ }
856
+ catch (trackErr) {
857
+ this.logger.warn('Failed to track inbound thread status (offline path)', {
858
+ error: trackErr instanceof Error ? trackErr.message : String(trackErr),
859
+ });
860
+ }
861
+ }
862
+ return {
863
+ response: 'The orchestrator is currently offline. Your message has been queued and will be processed when it comes back online.',
864
+ fromOrcReply: false,
865
+ };
866
+ }
867
+ catch (enqueueErr) {
868
+ this.logger.warn('Failed to queue message for offline orchestrator', {
869
+ error: enqueueErr instanceof Error ? enqueueErr.message : String(enqueueErr),
870
+ });
871
+ }
872
+ }
873
+ this.logger.info('Orchestrator is not active, returning offline message');
874
+ return { response: getOrchestratorOfflineMessage(true), fromOrcReply: false };
875
+ }
876
+ // Check if message queue service is available
877
+ if (!this.messageQueueService) {
878
+ this.logger.error('Message queue service not configured');
879
+ return {
880
+ response: 'The Slack bridge is not properly configured. Please restart the server.',
881
+ fromOrcReply: false,
882
+ };
883
+ }
884
+ // Enrich message with thread file path hint for orchestrator context
885
+ let enrichedMessage = message;
886
+ if (this.threadStore && context) {
887
+ const threadFilePath = this.threadStore.getThreadFilePath(context.channelId, context.threadTs);
888
+ enrichedMessage = `${message}\n\n[Thread context file: ${threadFilePath}]`;
889
+ }
890
+ // Persist user message in canonical chat-v2 store
891
+ const result = {
892
+ conversation: {
893
+ id: this.persistSlackInbound({
894
+ content: enrichedMessage,
895
+ conversationId: context?.conversationId,
896
+ agentSession: 'crewly-orc',
897
+ channelId: context?.channelId,
898
+ threadTs: context?.threadTs,
899
+ userId: context?.userId,
900
+ }).conversationId,
901
+ },
902
+ };
903
+ // Enqueue the message with a resolve callback for response routing.
904
+ // The QueueProcessorService will call slackResolve() when the
905
+ // orchestrator responds, unblocking this promise.
906
+ //
907
+ // The promise resolves with `{response, fromOrcReply}`. `fromOrcReply`
908
+ // is `true` ONLY when slackResolve fires (orc's reply-slack skill
909
+ // delivered an actual reply). On responseTimeoutMs or enqueue failure
910
+ // it is `false` — the placeholder must NOT auto-resolve the SLA
911
+ // tracker (Steve 2026-04-30 incident: false-done Request cascade).
912
+ const orcResponse = await new Promise((resolve) => {
913
+ let resolved = false;
914
+ const timeoutId = setTimeout(() => {
915
+ if (!resolved) {
916
+ resolved = true;
917
+ resolve({
918
+ response: 'The orchestrator is still processing your request. It will reply here when ready — no need to resend.',
919
+ fromOrcReply: false,
920
+ });
921
+ }
922
+ }, this.config.responseTimeoutMs);
923
+ try {
924
+ this.messageQueueService.enqueue({
925
+ content: enrichedMessage,
926
+ conversationId: result.conversation.id,
927
+ source: 'slack',
928
+ sourceMetadata: {
929
+ slackResolve: (resp) => {
930
+ if (!resolved) {
931
+ resolved = true;
932
+ clearTimeout(timeoutId);
933
+ // 2026-05-12 dogfood bug: the QueueProcessor fires
934
+ // `routeResponse(message, '')` immediately after PTY
935
+ // delivery to unblock this promise (see
936
+ // queue-processor.service.ts:692 "fire-and-forget"
937
+ // comment). That call carries an EMPTY response — orc
938
+ // has only RECEIVED the message, not REPLIED. The
939
+ // previous shape of this callback always returned
940
+ // `fromOrcReply: true`, so every Slack delivery
941
+ // triggered `markResolvedByThread('orc_reply')` →
942
+ // `maybeCloseRequest` → Request cascaded to `done`
943
+ // with `result: "Auto-closed by SLA: orc_reply"`
944
+ // before the user ever saw a reply.
945
+ //
946
+ // Real orc replies (via the reply-slack skill) come
947
+ // through a different route — the orc invokes
948
+ // `POST /slack/send`, which posts to Slack directly;
949
+ // the SLA tracker is resolved when the
950
+ // `respond_to_user` WI transitions to `verified` and
951
+ // the cascade subscriber picks it up. That path
952
+ // produces a non-empty response payload only when
953
+ // a worker explicitly drives reply text back through
954
+ // the queue (rare; most orc replies are direct API).
955
+ //
956
+ // Gate: treat empty resp as a delivery ack ONLY
957
+ // (fromOrcReply=false). Non-empty resp keeps the
958
+ // legacy `true` semantics for any worker path that
959
+ // does echo a reply through the queue.
960
+ const fromOrcReply = resp.length > 0;
961
+ resolve({ response: resp, fromOrcReply });
962
+ }
963
+ },
964
+ userId: context?.userId,
965
+ channelId: context?.channelId,
966
+ threadTs: context?.threadTs,
967
+ },
968
+ });
969
+ // Track inbound thread for lifecycle monitoring and restart recovery
970
+ if (this.threadStatusQueue && context?.channelId) {
971
+ try {
972
+ const threadKey = context.threadTs
973
+ ? `${context.channelId}:${context.threadTs}`
974
+ : `${context.channelId}:${Date.now()}`;
975
+ this.threadStatusQueue.trackInbound({
976
+ source: 'slack',
977
+ threadKey,
978
+ conversationId: result.conversation.id,
979
+ messagePreview: message.slice(0, THREAD_STATUS_CONSTANTS.MAX_PREVIEW_LENGTH),
980
+ sourceMetadata: {
981
+ channelId: context.channelId,
982
+ threadTs: context.threadTs,
983
+ userId: context.userId,
984
+ },
985
+ });
986
+ }
987
+ catch (trackErr) {
988
+ this.logger.warn('Failed to track inbound thread status', {
989
+ error: trackErr instanceof Error ? trackErr.message : String(trackErr),
990
+ });
991
+ }
992
+ }
993
+ }
994
+ catch (enqueueErr) {
995
+ if (!resolved) {
996
+ resolved = true;
997
+ clearTimeout(timeoutId);
998
+ resolve({
999
+ response: `Failed to enqueue message: ${enqueueErr instanceof Error ? enqueueErr.message : String(enqueueErr)}`,
1000
+ fromOrcReply: false,
1001
+ });
1002
+ }
1003
+ }
1004
+ });
1005
+ return orcResponse;
1006
+ }
1007
+ catch (error) {
1008
+ this.logger.error('Error sending to orchestrator', { error: error instanceof Error ? error.message : String(error) });
1009
+ throw error;
1010
+ }
1011
+ }
1012
+ /**
1013
+ * Route a message to the Auditor agent as a fallback when the orchestrator is offline.
1014
+ *
1015
+ * Enqueues the message targeting the Auditor's session so the queue processor
1016
+ * delivers it to the Auditor PTY instead of the orchestrator. Includes Slack
1017
+ * context (channel, thread) so the Auditor can respond.
1018
+ *
1019
+ * **Thread file enrichment (parity with `sendToOrchestrator` ~line 712):**
1020
+ * When a `threadStore` and Slack `context` are available, the enriched
1021
+ * message also carries a `[Thread context file: <path>]` line. Without
1022
+ * this, the Auditor receives only the bare `[SLACK_CONTEXT:...]` prefix
1023
+ * and cannot read prior thread history, causing context-blind replies.
1024
+ * This is the bug Steve hit on 2026-04-25 (StevesPrompt deploy thread):
1025
+ * orchestrator dropped, Slack fell through here, Auditor responded with
1026
+ * "I cannot see the rest of the conversation" because the thread file
1027
+ * pointer was missing. Bringing the enrichment in line with the
1028
+ * orchestrator path closes the gap.
1029
+ *
1030
+ * @param message - Original user message
1031
+ * @param context - Slack conversation context
1032
+ * @returns Acknowledgement message to the user
1033
+ */
1034
+ async sendToAuditorFallback(message, context) {
1035
+ const auditorSession = AUDITOR_SCHEDULER_CONSTANTS.AUDITOR_SESSION_NAME;
1036
+ // Build a context-enriched message so the Auditor knows the source
1037
+ const slackPrefix = context
1038
+ ? `[SLACK_CONTEXT:channelId=${context.channelId},threadTs=${context.threadTs || ''}]`
1039
+ : '';
1040
+ let enrichedMessage = `${slackPrefix} [FALLBACK] Orchestrator is offline. User message:\n${message}`;
1041
+ if (this.threadStore && context) {
1042
+ const threadFilePath = this.threadStore.getThreadFilePath(context.channelId, context.threadTs);
1043
+ enrichedMessage += `\n\n[Thread context file: ${threadFilePath}]`;
1044
+ }
1045
+ // Try to enqueue via the message queue (same path as orchestrator delivery)
1046
+ if (this.messageQueueService) {
1047
+ try {
1048
+ const result = {
1049
+ conversation: {
1050
+ id: this.persistSlackInbound({
1051
+ content: enrichedMessage,
1052
+ conversationId: context?.conversationId,
1053
+ agentSession: auditorSession,
1054
+ channelId: context?.channelId,
1055
+ threadTs: context?.threadTs,
1056
+ userId: context?.userId,
1057
+ }).conversationId,
1058
+ },
1059
+ };
1060
+ this.messageQueueService.enqueue({
1061
+ content: enrichedMessage,
1062
+ conversationId: result.conversation.id,
1063
+ source: 'slack',
1064
+ targetSession: auditorSession,
1065
+ sourceMetadata: {
1066
+ slackResolve: undefined,
1067
+ userId: context?.userId,
1068
+ channelId: context?.channelId,
1069
+ },
1070
+ });
1071
+ this.logger.info('Message routed to Auditor agent via queue', { auditorSession });
1072
+ return 'The orchestrator is currently offline. Your message has been forwarded to the Auditor agent.';
1073
+ }
1074
+ catch (err) {
1075
+ this.logger.error('Failed to route message to Auditor via queue', {
1076
+ error: err instanceof Error ? err.message : String(err),
1077
+ });
1078
+ }
1079
+ }
1080
+ // Last resort: direct delivery via AuditorSchedulerService
1081
+ try {
1082
+ const { AuditorSchedulerService } = await import('../agent/auditor-scheduler.service.js');
1083
+ const scheduler = AuditorSchedulerService.getInstance();
1084
+ await scheduler.handleUserMessage(`[FALLBACK] Orchestrator is offline. User message: ${message}`, { channelId: context?.channelId || '', threadTs: context?.threadTs || '' });
1085
+ return 'The orchestrator is currently offline. Your message has been forwarded to the Auditor agent.';
1086
+ }
1087
+ catch (err) {
1088
+ this.logger.error('Failed to route message to Auditor via scheduler', {
1089
+ error: err instanceof Error ? err.message : String(err),
1090
+ });
1091
+ }
1092
+ return getOrchestratorOfflineMessage(true);
1093
+ }
1094
+ /**
1095
+ * Refresh file download URLs via the Slack files.info API.
1096
+ * The event payload URLs may not work with just the bot token — the
1097
+ * files.info API returns authenticated URLs and validates scope.
1098
+ *
1099
+ * @param files - Slack file objects to refresh URLs for (mutated in place)
1100
+ * @returns true if downloads can proceed, false if scope is missing
1101
+ */
1102
+ async refreshFileUrls(files) {
1103
+ for (const file of files) {
1104
+ try {
1105
+ const freshInfo = await this.slackService.getFileInfo(file.id);
1106
+ if (freshInfo.url_private_download) {
1107
+ file.url_private_download = freshInfo.url_private_download;
1108
+ }
1109
+ if (freshInfo.url_private) {
1110
+ file.url_private = freshInfo.url_private;
1111
+ }
1112
+ }
1113
+ catch (err) {
1114
+ const errMsg = err instanceof Error ? err.message : String(err);
1115
+ if (errMsg.includes('missing_scope') || errMsg.includes('not_allowed_token_type')) {
1116
+ this.logger.error('Bot token lacks files:read scope — cannot download files. Add the "files:read" scope to your Slack app.');
1117
+ return false;
1118
+ }
1119
+ this.logger.warn('Could not refresh URL via files.info', { fileName: file.name, error: errMsg });
1120
+ }
1121
+ }
1122
+ return true;
1123
+ }
1124
+ /**
1125
+ * Download image files from a Slack message using SlackImageService.
1126
+ * Populates `message.images` with successfully downloaded image info.
1127
+ * Downloads are batched with a concurrency limit of MAX_CONCURRENT_DOWNLOADS.
1128
+ *
1129
+ * @param message - Incoming message to attach downloaded images to
1130
+ * @param imageFiles - Pre-filtered list of image files to download
1131
+ */
1132
+ async downloadMessageImages(message, imageFiles) {
1133
+ const botToken = this.slackService.getBotToken();
1134
+ if (!botToken) {
1135
+ this.logger.warn('Cannot download images: no bot token available');
1136
+ return;
1137
+ }
1138
+ if (imageFiles.length === 0) {
1139
+ return;
1140
+ }
1141
+ const slackImageService = getSlackImageService();
1142
+ const files = imageFiles;
1143
+ const downloadedImages = [];
1144
+ const maxConcurrent = SLACK_IMAGE_CONSTANTS.MAX_CONCURRENT_DOWNLOADS;
1145
+ const rejectionMessages = [];
1146
+ // Refresh file URLs via files.info API before downloading.
1147
+ const canProceed = await this.refreshFileUrls(files);
1148
+ if (!canProceed)
1149
+ return;
1150
+ for (let i = 0; i < files.length; i += maxConcurrent) {
1151
+ const batch = files.slice(i, i + maxConcurrent);
1152
+ const results = await Promise.allSettled(batch.map((file) => slackImageService.downloadImage(file, botToken)));
1153
+ for (let j = 0; j < results.length; j++) {
1154
+ const result = results[j];
1155
+ if (result.status === 'fulfilled') {
1156
+ downloadedImages.push(result.value);
1157
+ }
1158
+ else {
1159
+ const fileName = batch[j].name;
1160
+ const reason = result.reason instanceof Error ? result.reason.message : String(result.reason);
1161
+ this.logger.warn('Failed to download image', { fileName, reason });
1162
+ // Track files rejected due to validation (size/type) to warn the Slack user.
1163
+ // These match the error prefixes from SlackImageService.downloadImage().
1164
+ if (reason.startsWith('File too large:') || reason.startsWith('Unsupported image type:')) {
1165
+ rejectionMessages.push(`${fileName}: ${reason}`);
1166
+ }
1167
+ }
1168
+ }
1169
+ }
1170
+ if (downloadedImages.length > 0) {
1171
+ message.images = downloadedImages;
1172
+ }
1173
+ // Send a warning back to Slack if any files were rejected
1174
+ if (rejectionMessages.length > 0) {
1175
+ try {
1176
+ const maxMB = Math.round(SLACK_IMAGE_CONSTANTS.MAX_FILE_SIZE / (1024 * 1024));
1177
+ const supportedTypes = SLACK_IMAGE_CONSTANTS.SUPPORTED_MIMES.map(m => m.replace('image/', '').toUpperCase()).join(', ');
1178
+ const warningText = `:warning: Some image(s) could not be processed:\n${rejectionMessages.map(f => `• ${f}`).join('\n')}\n\nSupported types: ${supportedTypes}. Max size: ${maxMB} MB.`;
1179
+ await this.slackService.sendMessage({
1180
+ channelId: message.channelId,
1181
+ text: warningText,
1182
+ threadTs: message.threadTs || message.ts,
1183
+ });
1184
+ }
1185
+ catch (err) {
1186
+ this.logger.warn('Failed to send rejection warning to Slack', { error: err instanceof Error ? err.message : String(err) });
1187
+ }
1188
+ }
1189
+ }
1190
+ /**
1191
+ * Download non-image file attachments from a Slack message.
1192
+ * Uses authenticated fetch (same pattern as SlackImageService) to download
1193
+ * files to a temp directory. Populates `message.attachments` with results.
1194
+ * Skips magic-byte validation since these are generic files.
1195
+ *
1196
+ * @param message - Incoming message to populate with downloaded file info
1197
+ * @param nonImageFiles - Non-image SlackFile objects to download
1198
+ */
1199
+ async downloadMessageFiles(message, nonImageFiles) {
1200
+ const botToken = this.slackService.getBotToken();
1201
+ if (!botToken) {
1202
+ this.logger.warn('Cannot download files: no bot token available');
1203
+ return;
1204
+ }
1205
+ const crewlyHome = path.join(os.homedir(), '.crewly');
1206
+ const tempDir = path.join(crewlyHome, SLACK_FILE_DOWNLOAD_CONSTANTS.TEMP_DIR);
1207
+ await fs.mkdir(tempDir, { recursive: true });
1208
+ const downloadedFiles = [];
1209
+ const rejectionMessages = [];
1210
+ const maxConcurrent = SLACK_FILE_DOWNLOAD_CONSTANTS.MAX_CONCURRENT_DOWNLOADS;
1211
+ // Refresh file URLs via files.info API
1212
+ const canProceed = await this.refreshFileUrls(nonImageFiles);
1213
+ if (!canProceed)
1214
+ return;
1215
+ for (let i = 0; i < nonImageFiles.length; i += maxConcurrent) {
1216
+ const batch = nonImageFiles.slice(i, i + maxConcurrent);
1217
+ const results = await Promise.allSettled(batch.map(async (file) => {
1218
+ // Size check
1219
+ if (file.size > SLACK_FILE_DOWNLOAD_CONSTANTS.MAX_FILE_SIZE) {
1220
+ const maxMB = Math.round(SLACK_FILE_DOWNLOAD_CONSTANTS.MAX_FILE_SIZE / (1024 * 1024));
1221
+ throw new Error(`File too large: ${file.size} bytes (max ${maxMB} MB)`);
1222
+ }
1223
+ const safeName = file.name.replace(/[^a-zA-Z0-9._-]/g, '_');
1224
+ const localPath = path.join(tempDir, `${file.id}-${safeName}`);
1225
+ const downloadUrl = file.url_private_download || file.url_private;
1226
+ // Authenticated fetch with manual redirect handling, timeout, and resource cleanup
1227
+ const maxRedirects = SLACK_FILE_DOWNLOAD_CONSTANTS.MAX_DOWNLOAD_REDIRECTS;
1228
+ const timeoutMs = SLACK_FILE_DOWNLOAD_CONSTANTS.DOWNLOAD_TIMEOUT_MS;
1229
+ const controller = new AbortController();
1230
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
1231
+ let currentUrl = downloadUrl;
1232
+ let response = null;
1233
+ try {
1234
+ let exhaustedRedirects = true;
1235
+ for (let r = 0; r <= maxRedirects; r++) {
1236
+ response = await fetch(currentUrl, {
1237
+ headers: { 'Authorization': `Bearer ${botToken}` },
1238
+ redirect: 'manual',
1239
+ signal: controller.signal,
1240
+ });
1241
+ if (response.status >= 300 && response.status < 400) {
1242
+ const location = response.headers.get('location');
1243
+ if (!location)
1244
+ throw new Error(`Redirect without Location header`);
1245
+ // Drain response body to free the socket before following redirect
1246
+ await response.body?.cancel();
1247
+ currentUrl = location;
1248
+ continue;
1249
+ }
1250
+ exhaustedRedirects = false;
1251
+ break;
1252
+ }
1253
+ if (exhaustedRedirects) {
1254
+ throw new Error(`Too many redirects (${maxRedirects}) while downloading file from Slack`);
1255
+ }
1256
+ if (!response || !response.ok) {
1257
+ throw new Error(`Download failed with status ${response?.status || 'unknown'}`);
1258
+ }
1259
+ if (!response.body) {
1260
+ throw new Error('Empty response body from Slack');
1261
+ }
1262
+ const fileStream = createWriteStream(localPath);
1263
+ const nodeStream = Readable.fromWeb(response.body);
1264
+ await pipeline(nodeStream, fileStream);
1265
+ }
1266
+ catch (downloadErr) {
1267
+ // Clean up partial file on failure
1268
+ try {
1269
+ await fs.unlink(localPath);
1270
+ }
1271
+ catch { /* ignore if file doesn't exist */ }
1272
+ throw downloadErr;
1273
+ }
1274
+ finally {
1275
+ clearTimeout(timeoutId);
1276
+ }
1277
+ // Extract text from PDFs after download
1278
+ let extractedText;
1279
+ if (SLACK_FILE_DOWNLOAD_CONSTANTS.EXTRACTABLE_MIMES.includes(file.mimetype)) {
1280
+ extractedText = await this.extractPdfText(localPath, file.name);
1281
+ }
1282
+ return {
1283
+ id: file.id,
1284
+ name: file.name,
1285
+ mimetype: file.mimetype,
1286
+ localPath,
1287
+ size: file.size,
1288
+ permalink: file.permalink,
1289
+ extractedText,
1290
+ };
1291
+ }));
1292
+ for (let j = 0; j < results.length; j++) {
1293
+ const result = results[j];
1294
+ if (result.status === 'fulfilled') {
1295
+ downloadedFiles.push(result.value);
1296
+ }
1297
+ else {
1298
+ const fileName = batch[j].name;
1299
+ const reason = result.reason instanceof Error ? result.reason.message : String(result.reason);
1300
+ this.logger.warn('Failed to download file', { fileName, reason });
1301
+ if (reason.startsWith('File too large:')) {
1302
+ rejectionMessages.push(`${fileName}: ${reason}`);
1303
+ }
1304
+ }
1305
+ }
1306
+ }
1307
+ if (downloadedFiles.length > 0) {
1308
+ message.attachments = downloadedFiles;
1309
+ }
1310
+ if (rejectionMessages.length > 0) {
1311
+ try {
1312
+ const maxMB = Math.round(SLACK_FILE_DOWNLOAD_CONSTANTS.MAX_FILE_SIZE / (1024 * 1024));
1313
+ const warningText = `:warning: Some file(s) could not be downloaded:\n${rejectionMessages.map(f => `• ${f}`).join('\n')}\n\nMax size: ${maxMB} MB.`;
1314
+ await this.slackService.sendMessage({
1315
+ channelId: message.channelId,
1316
+ text: warningText,
1317
+ threadTs: message.threadTs || message.ts,
1318
+ });
1319
+ }
1320
+ catch (err) {
1321
+ this.logger.warn('Failed to send file rejection warning to Slack', { error: err instanceof Error ? err.message : String(err) });
1322
+ }
1323
+ }
1324
+ }
1325
+ /**
1326
+ * Enrich message text with file path references so agents
1327
+ * can read them via their file-reading tools (Claude Code Read, Gemini @file).
1328
+ * Includes both images and non-image file attachments.
1329
+ *
1330
+ * @param message - Message with optional downloaded images and files
1331
+ * @returns Enriched text with file references appended
1332
+ */
1333
+ enrichTextWithFiles(message) {
1334
+ let text = message.text || '';
1335
+ if (message.images && message.images.length > 0) {
1336
+ for (const img of message.images) {
1337
+ const dims = img.width && img.height ? ` (${img.width}x${img.height})` : '';
1338
+ text += `\n[Slack Image: ${img.localPath}${dims}, ${img.mimetype}]`;
1339
+ }
1340
+ }
1341
+ if (message.attachments && message.attachments.length > 0) {
1342
+ for (const file of message.attachments) {
1343
+ const sizeStr = file.size < 1024 ? `${file.size}B` : `${Math.round(file.size / 1024)}KB`;
1344
+ text += `\n[Slack File: ${file.localPath} (${file.name}, ${file.mimetype}, ${sizeStr})]`;
1345
+ if (file.extractedText) {
1346
+ text += `\n--- Extracted content from ${file.name} ---\n${file.extractedText}\n--- End of ${file.name} ---`;
1347
+ }
1348
+ }
1349
+ }
1350
+ return text;
1351
+ }
1352
+ /**
1353
+ * Extract text content from a downloaded PDF file.
1354
+ * Returns undefined if extraction fails or produces no text.
1355
+ *
1356
+ * @param filePath - Absolute path to the downloaded PDF
1357
+ * @param fileName - Original file name (for logging)
1358
+ * @returns Extracted text (possibly truncated) or undefined
1359
+ */
1360
+ async extractPdfText(filePath, fileName) {
1361
+ try {
1362
+ const fileBuffer = await fs.readFile(filePath);
1363
+ const parser = new PDFParse({ data: fileBuffer });
1364
+ const pdfData = await parser.getText();
1365
+ const rawText = typeof pdfData === 'string' ? pdfData : pdfData?.text;
1366
+ if (rawText && rawText.trim().length > 0) {
1367
+ const maxLen = SLACK_FILE_DOWNLOAD_CONSTANTS.MAX_EXTRACTED_TEXT_LENGTH;
1368
+ return rawText.trim().length > maxLen
1369
+ ? rawText.trim().substring(0, maxLen) + '\n... [truncated]'
1370
+ : rawText.trim();
1371
+ }
1372
+ return undefined;
1373
+ }
1374
+ catch (extractErr) {
1375
+ this.logger.warn('Failed to extract text from PDF', {
1376
+ fileName,
1377
+ error: extractErr instanceof Error ? extractErr.message : String(extractErr),
1378
+ });
1379
+ return undefined;
1380
+ }
1381
+ }
1382
+ /**
1383
+ * Add typing indicator to message
1384
+ *
1385
+ * @param message - Message to add indicator to
1386
+ */
1387
+ async addTypingIndicator(message) {
1388
+ try {
1389
+ await this.slackService.addReaction(message.channelId, message.ts, 'eyes');
1390
+ }
1391
+ catch (error) {
1392
+ // Non-critical - reactions require 'reactions:write' scope which is optional
1393
+ const errorMessage = error instanceof Error ? error.message : String(error);
1394
+ if (errorMessage.includes('missing_scope')) {
1395
+ // Only log once about missing scope, not on every message
1396
+ if (!this.loggedMissingScope) {
1397
+ this.logger.warn('Reactions disabled - add reactions:write scope to Slack app for typing indicators');
1398
+ this.loggedMissingScope = true;
1399
+ }
1400
+ }
1401
+ else {
1402
+ this.logger.warn('Could not add typing indicator', { error: errorMessage });
1403
+ }
1404
+ }
1405
+ }
1406
+ /**
1407
+ * Mark message as complete
1408
+ *
1409
+ * @param message - Message to mark complete
1410
+ */
1411
+ async markComplete(message) {
1412
+ try {
1413
+ await this.slackService.addReaction(message.channelId, message.ts, 'white_check_mark');
1414
+ }
1415
+ catch (error) {
1416
+ // Non-critical - reactions require 'reactions:write' scope which is optional
1417
+ const errorMessage = error instanceof Error ? error.message : String(error);
1418
+ if (!errorMessage.includes('missing_scope')) {
1419
+ this.logger.warn('Could not add completion indicator', { error: errorMessage });
1420
+ }
1421
+ // Silent fail for missing_scope since we already logged about it
1422
+ }
1423
+ }
1424
+ /**
1425
+ * Add a completion reaction (✅) to a message that was previously
1426
+ * stored as a pending reaction. Consumes the pending reaction.
1427
+ *
1428
+ * @param channelId - Slack channel ID
1429
+ * @param threadTs - Thread timestamp (key for lookup)
1430
+ */
1431
+ async addCompletionReaction(channelId, threadTs) {
1432
+ const key = `${channelId}:${threadTs}`;
1433
+ const messageTs = this.pendingReactions.get(key);
1434
+ if (messageTs) {
1435
+ // Consume the pending reaction
1436
+ this.pendingReactions.delete(key);
1437
+ try {
1438
+ await this.slackService.addReaction(channelId, messageTs, 'white_check_mark');
1439
+ }
1440
+ catch (error) {
1441
+ const errMsg = error instanceof Error ? error.message : String(error);
1442
+ // Suppress "already_reacted" errors as they are common and non-fatal
1443
+ if (errMsg.includes('already_reacted')) {
1444
+ return;
1445
+ }
1446
+ // Suppress "missing_scope" since we already log/warn about that elsewhere
1447
+ if (!errMsg.includes('missing_scope')) {
1448
+ this.logger.warn('Failed to add completion reaction', { channelId, messageTs, error: errMsg });
1449
+ }
1450
+ }
1451
+ }
1452
+ }
1453
+ /**
1454
+ * Record response to thread store. Slack delivery is handled exclusively
1455
+ * by the reply-slack skill via the API — no terminal output fallback needed.
1456
+ *
1457
+ * The `fromOrcReply` flag gates the V3 SLA auto-resolve hook
1458
+ * (`markResolvedByThread`). Only real orc replies (delivered via the
1459
+ * orc's reply-slack skill → `slackResolve` callback) may auto-resolve
1460
+ * the SLA tracker. Placeholder / timeout / offline / fallback responses
1461
+ * must NOT trigger the cascade — doing so closes the parent Request to
1462
+ * `done` even though the orc never actually replied (Steve 2026-04-30
1463
+ * incident: false-done Request, paired with Bug 1 silent-drop in
1464
+ * agent-registration.service.ts).
1465
+ *
1466
+ * Default `fromOrcReply = true` preserves the legacy behaviour for the
1467
+ * mention-routing call site (line ~327) which currently has no
1468
+ * placeholder semantics — its `sendToAgent` path delivers a direct
1469
+ * agent reply when reachable. If that path later grows a timeout
1470
+ * fallback, the call site should pass `false` explicitly.
1471
+ *
1472
+ * @param originalMessage - Original incoming message
1473
+ * @param response - Response content (real reply or placeholder)
1474
+ * @param fromOrcReply - `true` only when the response came from a
1475
+ * real agent/orc reply path. `false` for any timeout / fallback /
1476
+ * offline acknowledgement. Drives whether the SLA tracker is
1477
+ * auto-resolved for the matching Slack thread.
1478
+ */
1479
+ async sendSlackResponse(originalMessage, response, fromOrcReply = true) {
1480
+ await this.recordThreadReply(originalMessage, response);
1481
+ // INBOUND-1.4: notify the RequestSlaSubscriber that the orc replied to
1482
+ // this Slack thread so any tracked respond_to_user WorkItem can
1483
+ // auto-transition and the SLA timers no-op. Lazy import breaks the
1484
+ // static cycle (subscriber boot wires this bridge).
1485
+ if (!fromOrcReply) {
1486
+ this.logger.debug('Skipping SLA auto-resolve — response is not a real orc reply', {
1487
+ threadTs: originalMessage.ts,
1488
+ responsePreview: response.slice(0, 60),
1489
+ });
1490
+ return;
1491
+ }
1492
+ try {
1493
+ const { getRequestSlaSubscriber } = await import('../v3/request-sla.subscriber.js');
1494
+ const sub = getRequestSlaSubscriber();
1495
+ if (sub) {
1496
+ // sourceConversationItemId for slack messages is `slack-${channelId}-${ts}`
1497
+ // (see line ~372 above); the threadTs we index on is the original
1498
+ // user message ts. originalMessage.ts is exactly that.
1499
+ await sub.markResolvedByThread(originalMessage.ts);
1500
+ }
1501
+ }
1502
+ catch (err) {
1503
+ this.logger.debug('SLA auto-resolve hook failed (non-critical)', {
1504
+ error: err instanceof Error ? err.message : String(err),
1505
+ });
1506
+ }
1507
+ }
1508
+ /**
1509
+ * Send error response
1510
+ *
1511
+ * @param message - Original message
1512
+ * @param error - Error that occurred
1513
+ */
1514
+ async sendErrorResponse(message, error) {
1515
+ await this.slackService.sendMessage({
1516
+ channelId: message.channelId,
1517
+ text: `:warning: Sorry, I encountered an error: ${error.message}`,
1518
+ threadTs: message.threadTs || message.ts,
1519
+ });
1520
+ }
1521
+ /**
1522
+ * Format response for Slack
1523
+ *
1524
+ * Converts markdown to Slack-compatible formatting.
1525
+ *
1526
+ * @param text - Text to format
1527
+ * @returns Formatted text
1528
+ */
1529
+ formatForSlack(text) {
1530
+ // Convert markdown headers to bold
1531
+ let formatted = text.replace(/^### (.+)$/gm, '*$1*');
1532
+ formatted = formatted.replace(/^## (.+)$/gm, '*$1*');
1533
+ formatted = formatted.replace(/^# (.+)$/gm, '*$1*');
1534
+ // Slack already supports backtick code formatting
1535
+ // Convert code blocks (simplified)
1536
+ formatted = formatted.replace(/```[\w]*\n([\s\S]*?)```/g, '```$1```');
1537
+ return formatted;
1538
+ }
1539
+ /**
1540
+ * Persist Slack reply content to the thread store when available.
1541
+ */
1542
+ async recordThreadReply(originalMessage, content) {
1543
+ if (!this.threadStore)
1544
+ return;
1545
+ const trimmed = content?.trim();
1546
+ if (!trimmed)
1547
+ return;
1548
+ const threadTs = originalMessage.threadTs || originalMessage.ts;
1549
+ if (!threadTs)
1550
+ return;
1551
+ try {
1552
+ await this.threadStore.appendOrchestratorReply(originalMessage.channelId, threadTs, trimmed);
1553
+ }
1554
+ catch (err) {
1555
+ this.logger.warn('Failed to store thread reply', { error: err instanceof Error ? err.message : String(err) });
1556
+ }
1557
+ }
1558
+ /**
1559
+ * Send proactive notification to Slack
1560
+ *
1561
+ * @param notification - Notification to send
1562
+ */
1563
+ async sendNotification(notification) {
1564
+ if (!this.config.enableNotifications)
1565
+ return;
1566
+ await this.slackService.sendNotification(notification);
1567
+ }
1568
+ /**
1569
+ * Send a content approval notification to Slack with Block Kit buttons.
1570
+ *
1571
+ * Posts a rich message with content preview and approve/reject buttons.
1572
+ * Links the Slack message back to the approval record so interactive
1573
+ * button clicks can resolve the approval.
1574
+ *
1575
+ * @param approvalId - The content approval ID
1576
+ * @param channelId - Slack channel ID
1577
+ * @param threadTs - Optional thread timestamp
1578
+ * @returns The Slack message timestamp, or null if failed
1579
+ */
1580
+ async sendContentApproval(approvalId, channelId, threadTs) {
1581
+ if (!channelId)
1582
+ return null;
1583
+ try {
1584
+ const approval = ContentApprovalService.getInstance().get(approvalId);
1585
+ const fallbackText = `Content approval requested (ID: ${approvalId.slice(0, 8)}). Use the buttons to approve/reject.`;
1586
+ const blocks = [
1587
+ {
1588
+ type: 'header',
1589
+ text: { type: 'plain_text', text: '\ud83d\udccb Content Approval Required', emoji: true },
1590
+ },
1591
+ {
1592
+ type: 'section',
1593
+ fields: [
1594
+ { type: 'mrkdwn', text: `*Platform:*\n${approval?.platform ?? 'N/A'}` },
1595
+ { type: 'mrkdwn', text: `*Type:*\n${approval?.contentType ?? 'N/A'}` },
1596
+ { type: 'mrkdwn', text: `*Submitted by:*\n${approval?.submittedBy ?? 'Unknown'}` },
1597
+ { type: 'mrkdwn', text: `*ID:*\n${approvalId.slice(0, 8)}` },
1598
+ ],
1599
+ },
1600
+ {
1601
+ type: 'section',
1602
+ text: {
1603
+ type: 'mrkdwn',
1604
+ text: `*Content Preview:*\n${(approval?.content ?? '').slice(0, 300)}${(approval?.content ?? '').length > 300 ? '...' : ''}`,
1605
+ },
1606
+ },
1607
+ ];
1608
+ // Add hashtags and scheduled time if present
1609
+ const metaFields = [];
1610
+ if (approval?.hashtags && approval.hashtags.length > 0) {
1611
+ metaFields.push({ type: 'mrkdwn', text: `*Hashtags:*\n${approval.hashtags.join(' ')}` });
1612
+ }
1613
+ if (approval?.scheduledTime) {
1614
+ metaFields.push({ type: 'mrkdwn', text: `*Scheduled:*\n${approval.scheduledTime}` });
1615
+ }
1616
+ if (metaFields.length > 0) {
1617
+ blocks.push({ type: 'section', fields: metaFields });
1618
+ }
1619
+ blocks.push({ type: 'divider' });
1620
+ // Actions block with approve/reject buttons
1621
+ blocks.push({
1622
+ type: 'actions',
1623
+ block_id: `content_approval_${approvalId}`,
1624
+ elements: [
1625
+ {
1626
+ type: 'button',
1627
+ action_id: 'content_approval_approve',
1628
+ text: { type: 'plain_text', text: '\u2705 Approve', emoji: true },
1629
+ value: approvalId,
1630
+ style: 'primary',
1631
+ },
1632
+ {
1633
+ type: 'button',
1634
+ action_id: 'content_approval_reject',
1635
+ text: { type: 'plain_text', text: '\u274c Reject', emoji: true },
1636
+ value: approvalId,
1637
+ style: 'danger',
1638
+ },
1639
+ ],
1640
+ });
1641
+ const messageTs = await this.slackService.sendMessage({
1642
+ channelId,
1643
+ text: fallbackText,
1644
+ threadTs,
1645
+ blocks,
1646
+ });
1647
+ // Link the Slack message to the approval for tracking
1648
+ if (messageTs) {
1649
+ ContentApprovalService.getInstance().linkSlackMessage(approvalId, channelId, messageTs);
1650
+ }
1651
+ return messageTs ?? null;
1652
+ }
1653
+ catch (err) {
1654
+ this.logger.warn('Failed to send content approval to Slack', {
1655
+ approvalId: approvalId.slice(0, 8),
1656
+ error: err instanceof Error ? err.message : String(err),
1657
+ });
1658
+ return null;
1659
+ }
1660
+ }
1661
+ /**
1662
+ * Notify about task completion
1663
+ *
1664
+ * @param taskTitle - Title of completed task
1665
+ * @param agentName - Name of agent that completed the task
1666
+ * @param projectName - Name of the project
1667
+ */
1668
+ async notifyTaskCompleted(taskTitle, agentName, projectName) {
1669
+ await this.sendNotification({
1670
+ type: 'task_completed',
1671
+ title: 'Task Completed',
1672
+ message: `*${taskTitle}*\n\nCompleted by ${agentName} on project ${projectName}`,
1673
+ urgency: 'normal',
1674
+ timestamp: new Date().toISOString(),
1675
+ metadata: { projectId: projectName },
1676
+ });
1677
+ }
1678
+ /**
1679
+ * Notify about agent needing assistance
1680
+ *
1681
+ * @param agentName - Name of agent with question
1682
+ * @param question - The question from the agent
1683
+ * @param projectName - Name of the project
1684
+ */
1685
+ async notifyAgentQuestion(agentName, question, projectName) {
1686
+ await this.sendNotification({
1687
+ type: 'agent_question',
1688
+ title: 'Agent Needs Input',
1689
+ message: `*${agentName}* has a question:\n\n_${question}_`,
1690
+ urgency: 'high',
1691
+ timestamp: new Date().toISOString(),
1692
+ metadata: { agentId: agentName, projectId: projectName },
1693
+ });
1694
+ }
1695
+ /**
1696
+ * Notify about errors
1697
+ *
1698
+ * @param errorMessage - Error message
1699
+ * @param agentName - Optional agent name
1700
+ * @param projectName - Optional project name
1701
+ */
1702
+ async notifyError(errorMessage, agentName, projectName) {
1703
+ await this.sendNotification({
1704
+ type: 'agent_error',
1705
+ title: 'Error Occurred',
1706
+ message: errorMessage,
1707
+ urgency: 'critical',
1708
+ timestamp: new Date().toISOString(),
1709
+ metadata: { agentId: agentName, projectId: projectName },
1710
+ });
1711
+ }
1712
+ /**
1713
+ * Notify about daily summary
1714
+ *
1715
+ * @param summary - Summary content
1716
+ */
1717
+ async notifyDailySummary(summary) {
1718
+ await this.sendNotification({
1719
+ type: 'daily_summary',
1720
+ title: 'Daily Summary',
1721
+ message: summary,
1722
+ urgency: 'low',
1723
+ timestamp: new Date().toISOString(),
1724
+ });
1725
+ }
1726
+ /**
1727
+ * Resolve @mention in message text to an agent session (#177).
1728
+ *
1729
+ * Matches patterns: @assistant, @auditor, @sam, @leo, etc.
1730
+ * Looks up the agent name in team configs to find the session name.
1731
+ *
1732
+ * @param text - Message text to scan for @mentions
1733
+ * @returns Target agent info, or null if no mention found
1734
+ */
1735
+ async resolveMentionTarget(text) {
1736
+ // Match @name at the start of the message
1737
+ const mentionMatch = text.match(/^@(\w+)\s/i);
1738
+ if (!mentionMatch)
1739
+ return null;
1740
+ const mentionedName = mentionMatch[1].toLowerCase();
1741
+ // Built-in aliases
1742
+ const aliases = {
1743
+ assistant: 'crewly-orc-assistant',
1744
+ auditor: 'crewly-auditor',
1745
+ orc: 'crewly-orc',
1746
+ orchestrator: 'crewly-orc',
1747
+ };
1748
+ if (aliases[mentionedName]) {
1749
+ return { name: mentionedName, sessionName: aliases[mentionedName] };
1750
+ }
1751
+ // Look up in team configs
1752
+ try {
1753
+ const StorageService = (await import('../core/storage.service.js')).StorageService;
1754
+ const storage = StorageService.getInstance();
1755
+ const teams = await storage.getTeams();
1756
+ for (const team of teams) {
1757
+ for (const member of (team.members || [])) {
1758
+ // Match by name (case-insensitive) or session name fragment
1759
+ const memberName = member.name?.toLowerCase() || '';
1760
+ const sessionParts = (member.sessionName || '').split('-');
1761
+ const shortName = sessionParts.length >= 3 ? sessionParts[2] : '';
1762
+ if (memberName === mentionedName || shortName === mentionedName) {
1763
+ return { name: mentionedName, sessionName: member.sessionName };
1764
+ }
1765
+ }
1766
+ }
1767
+ }
1768
+ catch (err) {
1769
+ this.logger.warn('Failed to look up mention target', {
1770
+ mention: mentionedName,
1771
+ error: err instanceof Error ? err.message : String(err),
1772
+ });
1773
+ }
1774
+ return null;
1775
+ }
1776
+ /**
1777
+ * Send a message to a specific agent session via terminal deliver (#177).
1778
+ *
1779
+ * @param sessionName - Target agent session
1780
+ * @param message - Message to send
1781
+ * @param context - Slack context for response routing
1782
+ * @returns OrcResponse envelope — `fromOrcReply` is `true` ONLY when the
1783
+ * reply came from the agent's `slackResolve` callback (a real reply
1784
+ * via the reply-slack skill). Timeout / direct-delivery / error
1785
+ * placeholders return `fromOrcReply: false` so callers can gate the
1786
+ * SLA cascade. Issue #394.
1787
+ */
1788
+ async sendToAgent(sessionName, message, context) {
1789
+ try {
1790
+ // Enrich with Slack context for reply routing
1791
+ let enrichedMessage = message;
1792
+ if (context) {
1793
+ enrichedMessage = `[SLACK_CONTEXT:channelId=${context.channelId},threadTs=${context.threadTs || ''}]\n${message}`;
1794
+ }
1795
+ // Use message queue for agents that support it, or deliver directly
1796
+ if (this.messageQueueService) {
1797
+ const chatResult = {
1798
+ conversation: {
1799
+ id: this.persistSlackInbound({
1800
+ content: enrichedMessage,
1801
+ conversationId: context?.conversationId,
1802
+ agentSession: sessionName,
1803
+ channelId: context?.channelId,
1804
+ threadTs: context?.threadTs,
1805
+ userId: context?.userId,
1806
+ }).conversationId,
1807
+ },
1808
+ };
1809
+ return new Promise((resolve) => {
1810
+ let resolved = false;
1811
+ const timeoutId = setTimeout(() => {
1812
+ if (!resolved) {
1813
+ resolved = true;
1814
+ // Timeout placeholder — explicitly NOT a real reply.
1815
+ resolve({
1816
+ response: 'Agent is processing your message. Check back shortly.',
1817
+ fromOrcReply: false,
1818
+ });
1819
+ }
1820
+ }, this.config.responseTimeoutMs);
1821
+ this.messageQueueService.enqueue({
1822
+ content: enrichedMessage,
1823
+ conversationId: chatResult.conversation.id,
1824
+ source: 'slack',
1825
+ targetSession: sessionName,
1826
+ sourceMetadata: {
1827
+ slackResolve: (resp) => {
1828
+ if (!resolved) {
1829
+ resolved = true;
1830
+ clearTimeout(timeoutId);
1831
+ // Real agent reply path. Empty `resp` is the
1832
+ // QueueProcessor fire-and-forget delivery ack — treat
1833
+ // it as a non-reply too (mirrors the orchestrator
1834
+ // path's empty-string gate in sendToOrchestrator).
1835
+ resolve({ response: resp, fromOrcReply: resp.length > 0 });
1836
+ }
1837
+ },
1838
+ channelId: context?.channelId,
1839
+ threadTs: context?.threadTs,
1840
+ },
1841
+ });
1842
+ });
1843
+ }
1844
+ // Fallback: direct terminal delivery — the agent acknowledges the
1845
+ // write, but the actual reply hasn't been generated yet. Return a
1846
+ // placeholder with fromOrcReply=false so the SLA cascade does NOT
1847
+ // close on this.
1848
+ const { default: fetch } = await import('node-fetch').catch(() => ({ default: globalThis.fetch }));
1849
+ const apiUrl = process.env.CREWLY_API_URL || 'http://localhost:8787';
1850
+ await fetch(`${apiUrl}/api/terminal/${sessionName}/deliver`, {
1851
+ method: 'POST',
1852
+ headers: { 'Content-Type': 'application/json' },
1853
+ body: JSON.stringify({ message: enrichedMessage, force: true }),
1854
+ });
1855
+ return {
1856
+ response: 'Message delivered to agent. Response will arrive shortly.',
1857
+ fromOrcReply: false,
1858
+ };
1859
+ }
1860
+ catch (err) {
1861
+ this.logger.error('Failed to send to agent', {
1862
+ session: sessionName,
1863
+ error: err instanceof Error ? err.message : String(err),
1864
+ });
1865
+ return {
1866
+ response: `Failed to reach agent. Error: ${err instanceof Error ? err.message : String(err)}`,
1867
+ fromOrcReply: false,
1868
+ };
1869
+ }
1870
+ }
1871
+ /**
1872
+ * Persist a Slack inbound user message to chat-v2 and return the
1873
+ * `(conversationId, messageId)` pair callers thread downstream
1874
+ * into `MessageQueueService.enqueue` and `ThreadStatusQueueService.trackInbound`.
1875
+ *
1876
+ * Phase 6c of unified-chat-message-store spec: replaces the
1877
+ * legacy `chatService.sendMessage` call sites on this bridge.
1878
+ * Synthesizes a stable conversationId from Slack channel + thread
1879
+ * ts when the caller didn't supply one, so re-enqueue paths
1880
+ * (offline replay, recovery) reuse the same channel.
1881
+ *
1882
+ * @param args - Persistence inputs (content + slack context + recipient agent)
1883
+ * @returns The chat-v2 channelId and the freshly persisted messageId
1884
+ */
1885
+ persistSlackInbound(args) {
1886
+ const cid = args.conversationId ??
1887
+ synthesizeSlackConversationId(args.channelId ?? 'unknown', args.threadTs ?? String(Date.now()));
1888
+ const channel = this.chatV2.ensureChannelForLegacyConversation({
1889
+ conversationId: cid,
1890
+ agentSession: args.agentSession,
1891
+ // Name the thread by its first message so the consolidated chat shows a
1892
+ // readable title (e.g. "Slack: ship the v2 launch…") instead of N
1893
+ // identical synthesized ids. ensure… only sets the name on create, so
1894
+ // this stamps the thread's opening message. Falls back to the id.
1895
+ name: deriveSlackThreadName(args.content),
1896
+ });
1897
+ const { message } = this.chatV2.recordTurn({
1898
+ channelId: channel.id,
1899
+ senderType: 'user',
1900
+ senderId: args.userId ?? 'slack-user',
1901
+ content: args.content,
1902
+ metadata: {
1903
+ source: 'slack',
1904
+ slackChannelId: args.channelId,
1905
+ slackThreadTs: args.threadTs,
1906
+ },
1907
+ });
1908
+ return { conversationId: channel.id, messageId: message.id };
1909
+ }
1910
+ }
1911
+ /**
1912
+ * Get bridge singleton
1913
+ *
1914
+ * @returns SlackOrchestratorBridge instance
1915
+ */
1916
+ export function getSlackOrchestratorBridge() {
1917
+ if (!bridgeInstance) {
1918
+ bridgeInstance = new SlackOrchestratorBridge();
1919
+ }
1920
+ return bridgeInstance;
1921
+ }
1922
+ /**
1923
+ * Reset bridge (for testing)
1924
+ */
1925
+ export function resetSlackOrchestratorBridge() {
1926
+ bridgeInstance = null;
1927
+ }
1928
+ //# sourceMappingURL=slack-orchestrator-bridge.js.map