crewly 1.20.133 → 1.20.135

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 (270) hide show
  1. package/config/skills/agent/core/block-task/SKILL.md +16 -3
  2. package/dist/backend/backend/src/constants.d.ts +82 -0
  3. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  4. package/dist/backend/backend/src/constants.js +87 -0
  5. package/dist/backend/backend/src/constants.js.map +1 -1
  6. package/dist/backend/backend/src/controllers/bundle/bundle.controller.d.ts +44 -0
  7. package/dist/backend/backend/src/controllers/bundle/bundle.controller.d.ts.map +1 -0
  8. package/dist/backend/backend/src/controllers/bundle/bundle.controller.js +122 -0
  9. package/dist/backend/backend/src/controllers/bundle/bundle.controller.js.map +1 -0
  10. package/dist/backend/backend/src/controllers/bundle/bundle.routes.d.ts +25 -0
  11. package/dist/backend/backend/src/controllers/bundle/bundle.routes.d.ts.map +1 -0
  12. package/dist/backend/backend/src/controllers/bundle/bundle.routes.js +33 -0
  13. package/dist/backend/backend/src/controllers/bundle/bundle.routes.js.map +1 -0
  14. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts +6 -1
  15. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts.map +1 -1
  16. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js +9 -2
  17. package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
  18. package/dist/backend/backend/src/index.d.ts.map +1 -1
  19. package/dist/backend/backend/src/index.js +28 -0
  20. package/dist/backend/backend/src/index.js.map +1 -1
  21. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  22. package/dist/backend/backend/src/routes/api.routes.js +4 -0
  23. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  24. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts +43 -7
  25. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  26. package/dist/backend/backend/src/services/agent/agent-registration.service.js +144 -51
  27. package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
  28. package/dist/backend/backend/src/services/agent/registration-flow-registry.d.ts +92 -0
  29. package/dist/backend/backend/src/services/agent/registration-flow-registry.d.ts.map +1 -0
  30. package/dist/backend/backend/src/services/agent/registration-flow-registry.js +121 -0
  31. package/dist/backend/backend/src/services/agent/registration-flow-registry.js.map +1 -0
  32. package/dist/backend/backend/src/services/agent/resumed-kickoff.d.ts +66 -0
  33. package/dist/backend/backend/src/services/agent/resumed-kickoff.d.ts.map +1 -0
  34. package/dist/backend/backend/src/services/agent/resumed-kickoff.js +72 -0
  35. package/dist/backend/backend/src/services/agent/resumed-kickoff.js.map +1 -0
  36. package/dist/backend/backend/src/services/bundle/bundle-apply.factory.d.ts +62 -0
  37. package/dist/backend/backend/src/services/bundle/bundle-apply.factory.d.ts.map +1 -0
  38. package/dist/backend/backend/src/services/bundle/bundle-apply.factory.js +173 -0
  39. package/dist/backend/backend/src/services/bundle/bundle-apply.factory.js.map +1 -0
  40. package/dist/backend/backend/src/services/bundle/bundle-apply.service.d.ts +359 -0
  41. package/dist/backend/backend/src/services/bundle/bundle-apply.service.d.ts.map +1 -0
  42. package/dist/backend/backend/src/services/bundle/bundle-apply.service.js +888 -0
  43. package/dist/backend/backend/src/services/bundle/bundle-apply.service.js.map +1 -0
  44. package/dist/backend/backend/src/services/bundle/bundle-catalog.d.ts +167 -0
  45. package/dist/backend/backend/src/services/bundle/bundle-catalog.d.ts.map +1 -0
  46. package/dist/backend/backend/src/services/bundle/bundle-catalog.js +248 -0
  47. package/dist/backend/backend/src/services/bundle/bundle-catalog.js.map +1 -0
  48. package/dist/backend/backend/src/services/bundle/bundle-connectors.d.ts +43 -0
  49. package/dist/backend/backend/src/services/bundle/bundle-connectors.d.ts.map +1 -0
  50. package/dist/backend/backend/src/services/bundle/bundle-connectors.js +57 -0
  51. package/dist/backend/backend/src/services/bundle/bundle-connectors.js.map +1 -0
  52. package/dist/backend/backend/src/services/bundle/bundle-deps.d.ts +37 -0
  53. package/dist/backend/backend/src/services/bundle/bundle-deps.d.ts.map +1 -0
  54. package/dist/backend/backend/src/services/bundle/bundle-deps.js +60 -0
  55. package/dist/backend/backend/src/services/bundle/bundle-deps.js.map +1 -0
  56. package/dist/backend/backend/src/services/bundle/bundle-manifest.d.ts +93 -0
  57. package/dist/backend/backend/src/services/bundle/bundle-manifest.d.ts.map +1 -0
  58. package/dist/backend/backend/src/services/bundle/bundle-manifest.js +532 -0
  59. package/dist/backend/backend/src/services/bundle/bundle-manifest.js.map +1 -0
  60. package/dist/backend/backend/src/services/bundle/bundle-placeholders.d.ts +100 -0
  61. package/dist/backend/backend/src/services/bundle/bundle-placeholders.d.ts.map +1 -0
  62. package/dist/backend/backend/src/services/bundle/bundle-placeholders.js +183 -0
  63. package/dist/backend/backend/src/services/bundle/bundle-placeholders.js.map +1 -0
  64. package/dist/backend/backend/src/services/bundle/bundle-runtime.d.ts +36 -0
  65. package/dist/backend/backend/src/services/bundle/bundle-runtime.d.ts.map +1 -0
  66. package/dist/backend/backend/src/services/bundle/bundle-runtime.js +38 -0
  67. package/dist/backend/backend/src/services/bundle/bundle-runtime.js.map +1 -0
  68. package/dist/backend/backend/src/services/bundle/bundle-skill-installer.d.ts +105 -0
  69. package/dist/backend/backend/src/services/bundle/bundle-skill-installer.d.ts.map +1 -0
  70. package/dist/backend/backend/src/services/bundle/bundle-skill-installer.js +144 -0
  71. package/dist/backend/backend/src/services/bundle/bundle-skill-installer.js.map +1 -0
  72. package/dist/backend/backend/src/services/bundle/bundle-state.store.d.ts +48 -0
  73. package/dist/backend/backend/src/services/bundle/bundle-state.store.d.ts.map +1 -0
  74. package/dist/backend/backend/src/services/bundle/bundle-state.store.js +90 -0
  75. package/dist/backend/backend/src/services/bundle/bundle-state.store.js.map +1 -0
  76. package/dist/backend/backend/src/services/bundle/bundle-team.builder.d.ts +81 -0
  77. package/dist/backend/backend/src/services/bundle/bundle-team.builder.d.ts.map +1 -0
  78. package/dist/backend/backend/src/services/bundle/bundle-team.builder.js +172 -0
  79. package/dist/backend/backend/src/services/bundle/bundle-team.builder.js.map +1 -0
  80. package/dist/backend/backend/src/services/bundle/bundle-time.d.ts +59 -0
  81. package/dist/backend/backend/src/services/bundle/bundle-time.d.ts.map +1 -0
  82. package/dist/backend/backend/src/services/bundle/bundle-time.js +98 -0
  83. package/dist/backend/backend/src/services/bundle/bundle-time.js.map +1 -0
  84. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
  85. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +5 -0
  86. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -1
  87. package/dist/backend/backend/src/services/core/storage.service.js +1 -1
  88. package/dist/backend/backend/src/services/core/storage.service.js.map +1 -1
  89. package/dist/backend/backend/src/services/core/teams-backup.service.d.ts.map +1 -1
  90. package/dist/backend/backend/src/services/core/teams-backup.service.js +4 -2
  91. package/dist/backend/backend/src/services/core/teams-backup.service.js.map +1 -1
  92. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.factory.d.ts.map +1 -1
  93. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.factory.js +2 -0
  94. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.factory.js.map +1 -1
  95. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.d.ts +14 -2
  96. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.d.ts.map +1 -1
  97. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.js +37 -1
  98. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.js.map +1 -1
  99. package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts.map +1 -1
  100. package/dist/backend/backend/src/services/reconciler/reconcile-rules.js +9 -1
  101. package/dist/backend/backend/src/services/reconciler/reconcile-rules.js.map +1 -1
  102. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
  103. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +8 -0
  104. package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
  105. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +21 -0
  106. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  107. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +85 -0
  108. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  109. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +32 -0
  110. package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  111. package/dist/backend/backend/src/services/task-pool/task-pool.service.js +60 -1
  112. package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  113. package/dist/backend/backend/src/services/template/template.service.d.ts.map +1 -1
  114. package/dist/backend/backend/src/services/template/template.service.js +5 -2
  115. package/dist/backend/backend/src/services/template/template.service.js.map +1 -1
  116. package/dist/backend/backend/src/services/workflow/cron-task.service.d.ts.map +1 -1
  117. package/dist/backend/backend/src/services/workflow/cron-task.service.js +4 -2
  118. package/dist/backend/backend/src/services/workflow/cron-task.service.js.map +1 -1
  119. package/dist/backend/backend/src/types/solution-bundle.types.d.ts +367 -0
  120. package/dist/backend/backend/src/types/solution-bundle.types.d.ts.map +1 -0
  121. package/dist/backend/backend/src/types/solution-bundle.types.js +17 -0
  122. package/dist/backend/backend/src/types/solution-bundle.types.js.map +1 -0
  123. package/dist/backend/backend/src/types/team-template.types.d.ts +8 -0
  124. package/dist/backend/backend/src/types/team-template.types.d.ts.map +1 -1
  125. package/dist/backend/backend/src/types/team-template.types.js.map +1 -1
  126. package/dist/backend/backend/src/types/v2/work-item.types.d.ts +37 -0
  127. package/dist/backend/backend/src/types/v2/work-item.types.d.ts.map +1 -1
  128. package/dist/backend/backend/src/types/v2/work-item.types.js +33 -0
  129. package/dist/backend/backend/src/types/v2/work-item.types.js.map +1 -1
  130. package/dist/backend/build-info.json +2 -2
  131. package/dist/cli/backend/src/constants.d.ts +82 -0
  132. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  133. package/dist/cli/backend/src/constants.js +87 -0
  134. package/dist/cli/backend/src/constants.js.map +1 -1
  135. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts +43 -7
  136. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  137. package/dist/cli/backend/src/services/agent/agent-registration.service.js +144 -51
  138. package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
  139. package/dist/cli/backend/src/services/agent/registration-flow-registry.d.ts +92 -0
  140. package/dist/cli/backend/src/services/agent/registration-flow-registry.d.ts.map +1 -0
  141. package/dist/cli/backend/src/services/agent/registration-flow-registry.js +121 -0
  142. package/dist/cli/backend/src/services/agent/registration-flow-registry.js.map +1 -0
  143. package/dist/cli/backend/src/services/agent/resumed-kickoff.d.ts +66 -0
  144. package/dist/cli/backend/src/services/agent/resumed-kickoff.d.ts.map +1 -0
  145. package/dist/cli/backend/src/services/agent/resumed-kickoff.js +72 -0
  146. package/dist/cli/backend/src/services/agent/resumed-kickoff.js.map +1 -0
  147. package/dist/cli/backend/src/services/bundle/bundle-apply.service.d.ts +359 -0
  148. package/dist/cli/backend/src/services/bundle/bundle-apply.service.d.ts.map +1 -0
  149. package/dist/cli/backend/src/services/bundle/bundle-apply.service.js +888 -0
  150. package/dist/cli/backend/src/services/bundle/bundle-apply.service.js.map +1 -0
  151. package/dist/cli/backend/src/services/bundle/bundle-catalog.d.ts +167 -0
  152. package/dist/cli/backend/src/services/bundle/bundle-catalog.d.ts.map +1 -0
  153. package/dist/cli/backend/src/services/bundle/bundle-catalog.js +248 -0
  154. package/dist/cli/backend/src/services/bundle/bundle-catalog.js.map +1 -0
  155. package/dist/cli/backend/src/services/bundle/bundle-deps.d.ts +37 -0
  156. package/dist/cli/backend/src/services/bundle/bundle-deps.d.ts.map +1 -0
  157. package/dist/cli/backend/src/services/bundle/bundle-deps.js +60 -0
  158. package/dist/cli/backend/src/services/bundle/bundle-deps.js.map +1 -0
  159. package/dist/cli/backend/src/services/bundle/bundle-manifest.d.ts +93 -0
  160. package/dist/cli/backend/src/services/bundle/bundle-manifest.d.ts.map +1 -0
  161. package/dist/cli/backend/src/services/bundle/bundle-manifest.js +532 -0
  162. package/dist/cli/backend/src/services/bundle/bundle-manifest.js.map +1 -0
  163. package/dist/cli/backend/src/services/bundle/bundle-placeholders.d.ts +100 -0
  164. package/dist/cli/backend/src/services/bundle/bundle-placeholders.d.ts.map +1 -0
  165. package/dist/cli/backend/src/services/bundle/bundle-placeholders.js +183 -0
  166. package/dist/cli/backend/src/services/bundle/bundle-placeholders.js.map +1 -0
  167. package/dist/cli/backend/src/services/bundle/bundle-runtime.d.ts +36 -0
  168. package/dist/cli/backend/src/services/bundle/bundle-runtime.d.ts.map +1 -0
  169. package/dist/cli/backend/src/services/bundle/bundle-runtime.js +38 -0
  170. package/dist/cli/backend/src/services/bundle/bundle-runtime.js.map +1 -0
  171. package/dist/cli/backend/src/services/bundle/bundle-skill-installer.d.ts +105 -0
  172. package/dist/cli/backend/src/services/bundle/bundle-skill-installer.d.ts.map +1 -0
  173. package/dist/cli/backend/src/services/bundle/bundle-skill-installer.js +144 -0
  174. package/dist/cli/backend/src/services/bundle/bundle-skill-installer.js.map +1 -0
  175. package/dist/cli/backend/src/services/bundle/bundle-state.store.d.ts +48 -0
  176. package/dist/cli/backend/src/services/bundle/bundle-state.store.d.ts.map +1 -0
  177. package/dist/cli/backend/src/services/bundle/bundle-state.store.js +90 -0
  178. package/dist/cli/backend/src/services/bundle/bundle-state.store.js.map +1 -0
  179. package/dist/cli/backend/src/services/bundle/bundle-team.builder.d.ts +81 -0
  180. package/dist/cli/backend/src/services/bundle/bundle-team.builder.d.ts.map +1 -0
  181. package/dist/cli/backend/src/services/bundle/bundle-team.builder.js +172 -0
  182. package/dist/cli/backend/src/services/bundle/bundle-team.builder.js.map +1 -0
  183. package/dist/cli/backend/src/services/bundle/bundle-time.d.ts +59 -0
  184. package/dist/cli/backend/src/services/bundle/bundle-time.d.ts.map +1 -0
  185. package/dist/cli/backend/src/services/bundle/bundle-time.js +98 -0
  186. package/dist/cli/backend/src/services/bundle/bundle-time.js.map +1 -0
  187. package/dist/cli/backend/src/services/core/storage.service.js +1 -1
  188. package/dist/cli/backend/src/services/core/storage.service.js.map +1 -1
  189. package/dist/cli/backend/src/services/core/teams-backup.service.d.ts.map +1 -1
  190. package/dist/cli/backend/src/services/core/teams-backup.service.js +4 -2
  191. package/dist/cli/backend/src/services/core/teams-backup.service.js.map +1 -1
  192. package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.d.ts +72 -0
  193. package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.d.ts.map +1 -0
  194. package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.js +389 -0
  195. package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.js.map +1 -0
  196. package/dist/cli/backend/src/services/messaging/index.d.ts +15 -0
  197. package/dist/cli/backend/src/services/messaging/index.d.ts.map +1 -0
  198. package/dist/cli/backend/src/services/messaging/index.js +20 -0
  199. package/dist/cli/backend/src/services/messaging/index.js.map +1 -0
  200. package/dist/cli/backend/src/services/messaging/queue-processor.service.d.ts +224 -0
  201. package/dist/cli/backend/src/services/messaging/queue-processor.service.d.ts.map +1 -0
  202. package/dist/cli/backend/src/services/messaging/queue-processor.service.js +969 -0
  203. package/dist/cli/backend/src/services/messaging/queue-processor.service.js.map +1 -0
  204. package/dist/cli/backend/src/services/messaging/response-router.service.d.ts +110 -0
  205. package/dist/cli/backend/src/services/messaging/response-router.service.d.ts.map +1 -0
  206. package/dist/cli/backend/src/services/messaging/response-router.service.js +268 -0
  207. package/dist/cli/backend/src/services/messaging/response-router.service.js.map +1 -0
  208. package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.d.ts +14 -2
  209. package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.d.ts.map +1 -1
  210. package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.js +37 -1
  211. package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.js.map +1 -1
  212. package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.d.ts +133 -0
  213. package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.d.ts.map +1 -0
  214. package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.js +232 -0
  215. package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.js.map +1 -0
  216. package/dist/cli/backend/src/services/skill/skill-catalog.service.d.ts +277 -0
  217. package/dist/cli/backend/src/services/skill/skill-catalog.service.d.ts.map +1 -0
  218. package/dist/cli/backend/src/services/skill/skill-catalog.service.js +708 -0
  219. package/dist/cli/backend/src/services/skill/skill-catalog.service.js.map +1 -0
  220. package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.d.ts +243 -0
  221. package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.d.ts.map +1 -0
  222. package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.js +439 -0
  223. package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.js.map +1 -0
  224. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +21 -0
  225. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  226. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +85 -0
  227. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  228. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +32 -0
  229. package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
  230. package/dist/cli/backend/src/services/task-pool/task-pool.service.js +60 -1
  231. package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
  232. package/dist/cli/backend/src/services/template/template.service.d.ts.map +1 -1
  233. package/dist/cli/backend/src/services/template/template.service.js +5 -2
  234. package/dist/cli/backend/src/services/template/template.service.js.map +1 -1
  235. package/dist/cli/backend/src/services/workflow/cron-task.service.d.ts +316 -0
  236. package/dist/cli/backend/src/services/workflow/cron-task.service.d.ts.map +1 -0
  237. package/dist/cli/backend/src/services/workflow/cron-task.service.js +1043 -0
  238. package/dist/cli/backend/src/services/workflow/cron-task.service.js.map +1 -0
  239. package/dist/cli/backend/src/types/cron-task.types.d.ts +95 -0
  240. package/dist/cli/backend/src/types/cron-task.types.d.ts.map +1 -0
  241. package/dist/cli/backend/src/types/cron-task.types.js +10 -0
  242. package/dist/cli/backend/src/types/cron-task.types.js.map +1 -0
  243. package/dist/cli/backend/src/types/solution-bundle.types.d.ts +367 -0
  244. package/dist/cli/backend/src/types/solution-bundle.types.d.ts.map +1 -0
  245. package/dist/cli/backend/src/types/solution-bundle.types.js +17 -0
  246. package/dist/cli/backend/src/types/solution-bundle.types.js.map +1 -0
  247. package/dist/cli/backend/src/types/team-template.types.d.ts +8 -0
  248. package/dist/cli/backend/src/types/team-template.types.d.ts.map +1 -1
  249. package/dist/cli/backend/src/types/team-template.types.js.map +1 -1
  250. package/dist/cli/backend/src/types/v2/work-item.types.d.ts +37 -0
  251. package/dist/cli/backend/src/types/v2/work-item.types.d.ts.map +1 -1
  252. package/dist/cli/backend/src/types/v2/work-item.types.js +33 -0
  253. package/dist/cli/backend/src/types/v2/work-item.types.js.map +1 -1
  254. package/dist/cli/cli/src/commands/deploy-bundle.d.ts +160 -0
  255. package/dist/cli/cli/src/commands/deploy-bundle.d.ts.map +1 -0
  256. package/dist/cli/cli/src/commands/deploy-bundle.js +386 -0
  257. package/dist/cli/cli/src/commands/deploy-bundle.js.map +1 -0
  258. package/dist/cli/cli/src/commands/onboard.d.ts +32 -1
  259. package/dist/cli/cli/src/commands/onboard.d.ts.map +1 -1
  260. package/dist/cli/cli/src/commands/onboard.js +80 -3
  261. package/dist/cli/cli/src/commands/onboard.js.map +1 -1
  262. package/dist/cli/cli/src/constants.d.ts +5 -0
  263. package/dist/cli/cli/src/constants.d.ts.map +1 -1
  264. package/dist/cli/cli/src/constants.js +5 -0
  265. package/dist/cli/cli/src/constants.js.map +1 -1
  266. package/dist/cli/cli/src/index.js +15 -0
  267. package/dist/cli/cli/src/index.js.map +1 -1
  268. package/frontend/dist/assets/{index-e6c403b6.js → index-2c7ee4a6.js} +276 -276
  269. package/frontend/dist/index.html +1 -1
  270. package/package.json +1 -1
@@ -0,0 +1,969 @@
1
+ /**
2
+ * Queue Processor Service
3
+ *
4
+ * Processes messages from the queue one-at-a-time. Dequeues the next message,
5
+ * delivers it to the orchestrator via AgentRegistrationService using a
6
+ * fire-and-forget pattern. Responses are handled asynchronously by the
7
+ * orchestrator through reply-* skills (reply-slack, reply-chat, reply-gchat).
8
+ *
9
+ * #239: Force-delivered user messages (Slack/web chat) are tracked in a
10
+ * pending-ack list. When the agent returns to prompt, the PTY output is
11
+ * checked for evidence the message was processed. If not found, the message
12
+ * is re-delivered to prevent silent loss.
13
+ *
14
+ * @module services/messaging/queue-processor
15
+ */
16
+ import { EventEmitter } from 'events';
17
+ import { LoggerService } from '../core/logger.service.js';
18
+ import { getChatV2Service } from '../chat-v2/chat-v2.singleton.js';
19
+ import { getTerminalGateway } from '../../websocket/terminal.gateway.js';
20
+ import { MESSAGE_QUEUE_CONSTANTS, ORCHESTRATOR_SESSION_NAME, CHAT_ROUTING_CONSTANTS, EVENT_DELIVERY_CONSTANTS, RUNTIME_TYPES, ORCHESTRATOR_HEARTBEAT_CONSTANTS, MESSAGE_SOURCES, } from '../../constants.js';
21
+ import { PtyActivityTrackerService } from '../agent/pty-activity-tracker.service.js';
22
+ import { InFlightTurnTracker } from '../restart/in-flight-turn-tracker.service.js';
23
+ import { RestartDrainService } from '../restart/restart-drain.service.js';
24
+ import { StorageService } from '../core/storage.service.js';
25
+ /**
26
+ * QueueProcessorService dequeues messages one-at-a-time and delivers them
27
+ * to the target agent using a fire-and-forget pattern. Responses are handled
28
+ * asynchronously by the orchestrator through reply-* skills.
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const processor = new QueueProcessorService(
33
+ * queueService,
34
+ * responseRouter,
35
+ * agentRegistrationService
36
+ * );
37
+ * processor.start();
38
+ * ```
39
+ */
40
+ /** Time in milliseconds before a delivered message ID is purged from the dedup set */
41
+ const DEDUP_TTL_MS = 300_000; // 5 minutes
42
+ /**
43
+ * Keep only the JSON-safe scalar fields of a message's source metadata
44
+ * (channelId, threadTs, userId, …). Callbacks such as `slackResolve` cannot
45
+ * outlive the process and are dropped.
46
+ *
47
+ * @param metadata - Source metadata of a queued message
48
+ * @returns Scalar fields, or undefined when there are none
49
+ */
50
+ export function serializableSourceMetadata(metadata) {
51
+ if (!metadata)
52
+ return undefined;
53
+ const out = {};
54
+ for (const [key, value] of Object.entries(metadata)) {
55
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
56
+ out[key] = value;
57
+ }
58
+ }
59
+ return Object.keys(out).length > 0 ? out : undefined;
60
+ }
61
+ export class QueueProcessorService extends EventEmitter {
62
+ logger;
63
+ queueService;
64
+ responseRouter;
65
+ agentRegistrationService;
66
+ running = false;
67
+ processing = false;
68
+ processNextTimeout = null;
69
+ /** Set to true when an early-return path has already scheduled the next run. */
70
+ nextAlreadyScheduled = false;
71
+ /**
72
+ * Tracks message IDs that were successfully delivered to an agent.
73
+ * Prevents duplicate delivery when the queue requeues a message that
74
+ * the agent has already received and started processing.
75
+ * Entries are purged after DEDUP_TTL_MS to prevent memory leaks.
76
+ */
77
+ deliveredMessageIds = new Map();
78
+ /** Timer for periodic cleanup of stale dedup entries */
79
+ dedupCleanupTimer = null;
80
+ /** Timer for periodic cleanup of stale pending-ack entries */
81
+ pendingAckCleanupTimer = null;
82
+ /**
83
+ * #239: Tracks force-delivered user messages awaiting acknowledgment.
84
+ * When a user message is force-delivered (agent not at prompt), it is
85
+ * added here instead of being marked completed. On the next successful
86
+ * waitForAgentReady (agent returns to prompt), the PTY output is checked
87
+ * for evidence that each pending message was actually processed.
88
+ */
89
+ pendingAckMessages = new Map();
90
+ /** Thread status queue for tracking message lifecycle across restarts */
91
+ threadStatusQueue = null;
92
+ /**
93
+ * Optional Task Pool routing callback. When set, task-assignment messages
94
+ * (content starting with "[TASK]") are routed through the Task Pool before
95
+ * delivery, allowing the Reconciler to manage assignment lifecycle.
96
+ */
97
+ taskPoolRouter = null;
98
+ constructor(queueService, responseRouter, agentRegistrationService) {
99
+ super();
100
+ this.logger = LoggerService.getInstance().createComponentLogger('QueueProcessor');
101
+ this.queueService = queueService;
102
+ this.responseRouter = responseRouter;
103
+ this.agentRegistrationService = agentRegistrationService;
104
+ }
105
+ /**
106
+ * Set the thread status queue service for tracking message delivery lifecycle.
107
+ *
108
+ * @param service - The ThreadStatusQueueService instance
109
+ */
110
+ setThreadStatusQueue(service) {
111
+ this.threadStatusQueue = service;
112
+ }
113
+ /**
114
+ * Set the Task Pool routing callback.
115
+ * When provided, messages with "[TASK]" prefix are offered to the Task Pool
116
+ * before standard delivery. If the callback returns true, the message is
117
+ * considered routed and delivery is skipped.
118
+ *
119
+ * @param router - Async callback that returns true if the message was routed to the pool
120
+ */
121
+ setTaskPoolRouter(router) {
122
+ this.taskPoolRouter = router;
123
+ }
124
+ /**
125
+ * Start the processor. Listens to queue 'enqueued' events and
126
+ * triggers processing.
127
+ */
128
+ start() {
129
+ if (this.running)
130
+ return;
131
+ this.running = true;
132
+ this.queueService.on('enqueued', this.onMessageEnqueued);
133
+ this.startDedupCleanup();
134
+ this.startPendingAckCleanup();
135
+ this.logger.info('Queue processor started');
136
+ // Process any messages already in the queue
137
+ if (this.queueService.hasPending()) {
138
+ this.scheduleProcessNext(0);
139
+ }
140
+ }
141
+ /**
142
+ * Stop the processor. Clears timers and removes listeners.
143
+ */
144
+ stop() {
145
+ if (!this.running)
146
+ return;
147
+ this.running = false;
148
+ this.queueService.removeListener('enqueued', this.onMessageEnqueued);
149
+ if (this.processNextTimeout) {
150
+ clearTimeout(this.processNextTimeout);
151
+ this.processNextTimeout = null;
152
+ }
153
+ this.stopDedupCleanup();
154
+ this.stopPendingAckCleanup();
155
+ this.deliveredMessageIds.clear();
156
+ this.pendingAckMessages.clear();
157
+ this.logger.info('Queue processor stopped');
158
+ }
159
+ /**
160
+ * Check if the processor is currently running.
161
+ *
162
+ * @returns True if running
163
+ */
164
+ isRunning() {
165
+ return this.running;
166
+ }
167
+ /**
168
+ * Check if a message is currently being processed.
169
+ *
170
+ * @returns True if processing
171
+ */
172
+ isProcessingMessage() {
173
+ return this.processing;
174
+ }
175
+ /**
176
+ * Handler for queue 'enqueued' events. Triggers processing if idle.
177
+ */
178
+ onMessageEnqueued = () => {
179
+ if (!this.processing) {
180
+ this.scheduleProcessNext(0);
181
+ }
182
+ };
183
+ /**
184
+ * Schedule the next message processing after a delay.
185
+ *
186
+ * @param delayMs - Delay in milliseconds before processing
187
+ */
188
+ scheduleProcessNext(delayMs) {
189
+ if (this.processNextTimeout) {
190
+ clearTimeout(this.processNextTimeout);
191
+ }
192
+ this.processNextTimeout = setTimeout(() => {
193
+ this.processNextTimeout = null;
194
+ this.processNext().catch((error) => {
195
+ this.logger.error('Unhandled error in processNext', {
196
+ error: error instanceof Error ? error.message : String(error),
197
+ });
198
+ });
199
+ }, delayMs);
200
+ }
201
+ /**
202
+ * Process the next message in the queue.
203
+ * This is the core processing loop.
204
+ */
205
+ async processNext() {
206
+ if (!this.running || this.processing) {
207
+ return;
208
+ }
209
+ // Safe restart: shutdown has begun — leave everything on the persistent
210
+ // queue. It is flushed to disk during shutdown and delivered after boot.
211
+ if (RestartDrainService.getInstance().isDeliveryPaused()) {
212
+ return;
213
+ }
214
+ // Don't process messages until orchestrator has finished initialization.
215
+ // 'started' means runtime is running but init prompt is still being processed.
216
+ // Only deliver when 'active' (agent registered via register-self skill).
217
+ const orchestratorInfo = await StorageService.getInstance().getOrchestratorStatus();
218
+ const agentStatus = orchestratorInfo?.agentStatus;
219
+ if (agentStatus !== 'active') {
220
+ this.logger.debug('Orchestrator not active yet, deferring message delivery', {
221
+ agentStatus: agentStatus || 'unknown',
222
+ });
223
+ this.scheduleProcessNext(EVENT_DELIVERY_CONSTANTS.AGENT_READY_POLL_INTERVAL);
224
+ return;
225
+ }
226
+ const message = this.queueService.dequeue();
227
+ if (!message) {
228
+ // #239: Even with no new messages, flush any pending-ack entries
229
+ // when the agent is potentially idle. This handles the case where
230
+ // the queue is empty but force-delivered messages need verification.
231
+ if (this.pendingAckMessages.size > 0) {
232
+ const storedRuntimeType = orchestratorInfo?.runtimeType;
233
+ const rt = storedRuntimeType || RUNTIME_TYPES.CLAUDE_CODE;
234
+ const isReady = await this.agentRegistrationService.waitForAgentReady(ORCHESTRATOR_SESSION_NAME, EVENT_DELIVERY_CONSTANTS.USER_MESSAGE_TIMEOUT, rt);
235
+ if (isReady) {
236
+ await this.flushPendingAcks(ORCHESTRATOR_SESSION_NAME, rt);
237
+ }
238
+ // Schedule another check if there are still pending acks
239
+ if (this.pendingAckMessages.size > 0) {
240
+ this.scheduleProcessNext(EVENT_DELIVERY_CONSTANTS.AGENT_READY_POLL_INTERVAL);
241
+ }
242
+ }
243
+ return;
244
+ }
245
+ this.processing = true;
246
+ // Keep the orchestrator's activity tracker alive while processing so the
247
+ // heartbeat monitor doesn't falsely declare it idle and auto-restart it.
248
+ // Interval is half the heartbeat request threshold to guarantee at least
249
+ // one activity ping before the monitor considers the orchestrator idle.
250
+ const KEEPALIVE_INTERVAL_MS = ORCHESTRATOR_HEARTBEAT_CONSTANTS.HEARTBEAT_REQUEST_THRESHOLD_MS / 2;
251
+ const keepaliveInterval = setInterval(() => {
252
+ PtyActivityTrackerService.getInstance().recordApiActivity(ORCHESTRATOR_SESSION_NAME);
253
+ }, KEEPALIVE_INTERVAL_MS);
254
+ try {
255
+ this.logger.info('Processing message', {
256
+ messageId: message.id,
257
+ source: message.source,
258
+ conversationId: message.conversationId,
259
+ });
260
+ // Task Pool routing: if a Task Pool router is configured, offer task
261
+ // assignment messages to the pool before standard delivery. Messages
262
+ // with "[TASK]" prefix are candidates for pool-based assignment.
263
+ if (this.taskPoolRouter && message.content.startsWith('[TASK]')) {
264
+ const deliveryTarget = message.targetSession || ORCHESTRATOR_SESSION_NAME;
265
+ try {
266
+ const routed = await this.taskPoolRouter(message.content, deliveryTarget);
267
+ if (routed) {
268
+ this.logger.info('Message routed to Task Pool, skipping direct delivery', {
269
+ messageId: message.id,
270
+ targetSession: deliveryTarget,
271
+ });
272
+ this.queueService.markCompleted(message.id);
273
+ clearInterval(keepaliveInterval);
274
+ return;
275
+ }
276
+ }
277
+ catch (poolErr) {
278
+ this.logger.warn('Task Pool routing failed, falling through to direct delivery', {
279
+ messageId: message.id,
280
+ error: poolErr instanceof Error ? poolErr.message : String(poolErr),
281
+ });
282
+ // Fall through to normal delivery
283
+ }
284
+ }
285
+ const isSystemEvent = message.source === MESSAGE_SOURCES.SYSTEM_EVENT;
286
+ // Set active conversation ID for response routing (skip for system events)
287
+ if (!isSystemEvent) {
288
+ const terminalGateway = getTerminalGateway();
289
+ if (terminalGateway) {
290
+ terminalGateway.setActiveConversationId(message.conversationId);
291
+ }
292
+ }
293
+ // Reuse the orchestrator status fetched above to determine runtime type
294
+ // for prompt detection in waitForAgentReady. Without runtime-aware detection,
295
+ // the generic PROMPT_STREAM regex can false-positive on markdown `> `
296
+ // lines in Claude Code output, causing premature delivery attempts.
297
+ const storedRuntimeType = orchestratorInfo?.runtimeType;
298
+ if (!storedRuntimeType) {
299
+ this.logger.warn('No runtimeType stored for orchestrator, defaulting to CLAUDE_CODE', {
300
+ messageId: message.id,
301
+ });
302
+ }
303
+ const runtimeType = storedRuntimeType || RUNTIME_TYPES.CLAUDE_CODE;
304
+ // Determine if this is a user message (Slack/web chat) vs system event.
305
+ // User messages and system events get shorter timeouts and force-delivery
306
+ // to reduce delay. System events are fire-and-forget so force-delivery is
307
+ // lower risk — prevents the 5×120s=10min retry loop that blocks notifications.
308
+ const isUserMessage = message.source === MESSAGE_SOURCES.SLACK || message.source === MESSAGE_SOURCES.WEB_CHAT || message.source === MESSAGE_SOURCES.WHATSAPP || message.source === MESSAGE_SOURCES.GOOGLE_CHAT || message.source === MESSAGE_SOURCES.REMOTE;
309
+ const readyTimeout = isUserMessage
310
+ ? EVENT_DELIVERY_CONSTANTS.USER_MESSAGE_TIMEOUT
311
+ : isSystemEvent
312
+ ? EVENT_DELIVERY_CONSTANTS.SYSTEM_EVENT_TIMEOUT
313
+ : EVENT_DELIVERY_CONSTANTS.AGENT_READY_TIMEOUT;
314
+ // Resolve delivery target — system events may target non-orchestrator agents
315
+ const deliveryTarget = message.targetSession || ORCHESTRATOR_SESSION_NAME;
316
+ // Wait for target agent to be at prompt before attempting delivery.
317
+ // After processing a previous message the agent may still be busy
318
+ // (managing agents, running commands) before returning to the input prompt.
319
+ const isReady = await this.agentRegistrationService.waitForAgentReady(deliveryTarget, readyTimeout, runtimeType);
320
+ // #239: When the agent returns to prompt, flush any force-delivered
321
+ // messages that are pending acknowledgment. This is the key mechanism
322
+ // to detect and redeliver messages that were silently consumed by the PTY.
323
+ if (isReady && this.pendingAckMessages.size > 0) {
324
+ await this.flushPendingAcks(deliveryTarget, runtimeType);
325
+ }
326
+ // Check if message was force-cancelled while waiting for agent readiness
327
+ if (message.status === 'cancelled') {
328
+ this.logger.info('Message was cancelled during processing, skipping delivery', {
329
+ messageId: message.id,
330
+ });
331
+ clearInterval(keepaliveInterval);
332
+ return;
333
+ }
334
+ // #239: Track whether this delivery was forced (agent not at prompt).
335
+ // Force-delivered user messages need pending-ack tracking.
336
+ let wasForceDelivered = false;
337
+ if (!isReady) {
338
+ // For user messages and system events: force-deliver immediately instead
339
+ // of re-queuing to avoid the retry loop that causes multi-minute delays.
340
+ // System events are fire-and-forget (no response expected), so force-delivery
341
+ // is lower risk. The orchestrator will process input when it returns to prompt.
342
+ //
343
+ // EXCEPTION (2026-06-02 autonomy incident): do NOT force-deliver a
344
+ // SYSTEM_EVENT into the ORCHESTRATOR while it is busy. Agent-status
345
+ // events ("[IN_PROGRESS] Agent X: …") are informational, but
346
+ // force-injecting one mid-work hands the orchestrator a spurious
347
+ // agentic turn — during which it acted on a stale pending decision and
348
+ // launched a team on a FABRICATED owner approval. The orchestrator is
349
+ // the autonomous decision-maker; an FYI status ping must not interrupt
350
+ // it into action. Let it requeue and deliver when the orc returns to
351
+ // prompt (status is ephemeral; a dropped one is harmless). User
352
+ // messages and system events to NON-orchestrator targets are unchanged.
353
+ const isOrchestratorTarget = deliveryTarget === ORCHESTRATOR_SESSION_NAME;
354
+ const shouldForceDeliver = (isUserMessage && EVENT_DELIVERY_CONSTANTS.USER_MESSAGE_FORCE_DELIVER) ||
355
+ (isSystemEvent && EVENT_DELIVERY_CONSTANTS.SYSTEM_EVENT_FORCE_DELIVER && !isOrchestratorTarget);
356
+ if (shouldForceDeliver) {
357
+ wasForceDelivered = true;
358
+ this.logger.warn('Agent not ready but force-delivering message to reduce delay', {
359
+ messageId: message.id,
360
+ source: message.source,
361
+ timeoutMs: readyTimeout,
362
+ isUserMessage,
363
+ isSystemEvent,
364
+ });
365
+ // Fall through to delivery below — the message will be sent even though
366
+ // the orchestrator may not be at prompt. This is acceptable because:
367
+ // 1. The user expects a timely response (user messages)
368
+ // 2. System events are fire-and-forget notifications
369
+ // 3. The orchestrator will process the input when it returns to prompt
370
+ }
371
+ else {
372
+ const currentRetries = message.retryCount || 0;
373
+ const maxRetries = MESSAGE_QUEUE_CONSTANTS.MAX_REQUEUE_RETRIES;
374
+ if (currentRetries >= maxRetries) {
375
+ // Exceeded max retries — permanently fail the message
376
+ const errorMsg = `Orchestrator not available after ${currentRetries} retries (~${Math.round(currentRetries * EVENT_DELIVERY_CONSTANTS.AGENT_READY_TIMEOUT / 60000)} minutes). The orchestrator may be busy or unresponsive.`;
377
+ this.logger.error('Message exceeded max requeue retries, marking as failed', {
378
+ messageId: message.id,
379
+ retryCount: currentRetries,
380
+ maxRetries,
381
+ });
382
+ this.queueService.markFailed(message.id, errorMsg);
383
+ this.responseRouter.routeError(message, errorMsg);
384
+ // Notify user in conversation
385
+ if (message.source !== MESSAGE_SOURCES.SYSTEM_EVENT) {
386
+ try {
387
+ const chatV2 = getChatV2Service();
388
+ const channel = chatV2.ensureChannelForLegacyConversation({
389
+ conversationId: message.conversationId,
390
+ agentSession: 'crewly-orc',
391
+ });
392
+ chatV2.recordTurn({
393
+ channelId: channel.id,
394
+ senderType: 'system',
395
+ senderId: 'system',
396
+ content: `Message delivery failed: ${errorMsg} Please try again later.`,
397
+ metadata: { source: 'system' },
398
+ });
399
+ }
400
+ catch (sysErr) {
401
+ this.logger.warn('Failed to send max-retry failure system message', {
402
+ error: sysErr instanceof Error ? sysErr.message : String(sysErr),
403
+ });
404
+ }
405
+ }
406
+ clearInterval(keepaliveInterval);
407
+ return;
408
+ }
409
+ this.logger.warn('Agent not ready, re-queuing message for retry', {
410
+ messageId: message.id,
411
+ timeoutMs: readyTimeout,
412
+ retryCount: currentRetries + 1,
413
+ maxRetries,
414
+ });
415
+ // Re-enqueue the message so it gets retried instead of permanently failing
416
+ this.queueService.requeue(message);
417
+ // Use a longer delay before retrying to give the orchestrator more time.
418
+ // Mark as already scheduled so the finally block doesn't overwrite with
419
+ // a shorter INTER_MESSAGE_DELAY.
420
+ this.scheduleProcessNext(EVENT_DELIVERY_CONSTANTS.AGENT_READY_POLL_INTERVAL);
421
+ this.nextAlreadyScheduled = true;
422
+ clearInterval(keepaliveInterval);
423
+ return;
424
+ }
425
+ }
426
+ // Batch system events: if the current message is a system event and more
427
+ // are pending, combine up to MAX_SYSTEM_EVENT_BATCH into one delivery.
428
+ // This reduces context window consumption on the orchestrator when many
429
+ // events fire in quick succession (agent status changes, etc.).
430
+ let batchedMessages = [];
431
+ if (isSystemEvent) {
432
+ const maxAdditional = MESSAGE_QUEUE_CONSTANTS.MAX_SYSTEM_EVENT_BATCH - 1;
433
+ batchedMessages = this.queueService.dequeueSystemEventBatch(maxAdditional);
434
+ if (batchedMessages.length > 0) {
435
+ this.logger.info('Batched system events for delivery', {
436
+ primaryId: message.id,
437
+ batchSize: 1 + batchedMessages.length,
438
+ });
439
+ }
440
+ }
441
+ // Format message: system events use raw content, chat uses [CHAT:id] or [GCHAT:id] prefix.
442
+ // A short message fingerprint (last 8 chars of messageId) is embedded in the prefix
443
+ // so pending-ack verification can distinguish between different messages in the same
444
+ // conversation/thread. Without this, multiple messages sharing the same conversationId
445
+ // cause false-positive ack confirmations.
446
+ let deliveryContent;
447
+ const msgFingerprint = message.id.slice(-8);
448
+ if (isSystemEvent) {
449
+ // 2026-05-13 dogfood incident: a scheduler check-in body began with
450
+ // "启动分支 A" (which orc had earlier proposed in its Slack reply
451
+ // "you nod and I'll start branch A"). When the check-in landed in
452
+ // orc's PTY as plain text under the `❯` Claude-Code prompt prefix,
453
+ // orc read "启动分支 A" as a user reply and announced "收到「启动
454
+ // 分支 A」绿灯 ✅" to Slack — fabricating an approval the user
455
+ // never gave. Two messages batched in the same delivery were also
456
+ // joined with a bare `\n`, so a tail like "启动分支 A" could mash
457
+ // against the next message's head ("Check think-tank-atlas...")
458
+ // and the boundary disappeared.
459
+ //
460
+ // Wrap every system-injected message in explicit `[SYSTEM]` /
461
+ // `[/SYSTEM]` markers and surround the whole block with newlines.
462
+ // This gives the orc (and any future agent reading the PTY)
463
+ // an unambiguous "this is a scheduler / SLA / reconciler ping,
464
+ // NOT a user reply" signal. Batched messages each get their own
465
+ // wrapper so adjacent payloads can't accidentally fuse.
466
+ const wrap = (content) => `[SYSTEM]\n${content}\n[/SYSTEM]`;
467
+ const wrappedContents = [
468
+ wrap(message.content),
469
+ ...batchedMessages.map((m) => wrap(m.content)),
470
+ ];
471
+ deliveryContent = `\n${wrappedContents.join('\n')}\n`;
472
+ }
473
+ else if (message.source === MESSAGE_SOURCES.GOOGLE_CHAT) {
474
+ const prefix = CHAT_ROUTING_CONSTANTS.GOOGLE_CHAT_PREFIX;
475
+ const threadId = message.sourceMetadata?.threadId;
476
+ const threadSuffix = threadId ? ` thread=${threadId}` : '';
477
+ deliveryContent = `[${prefix}:${message.conversationId}:${msgFingerprint}${threadSuffix}] ${message.content}`;
478
+ }
479
+ else {
480
+ deliveryContent = `[${CHAT_ROUTING_CONSTANTS.MESSAGE_PREFIX}:${message.conversationId}:${msgFingerprint}] ${message.content}`;
481
+ }
482
+ // Inject [REMOTE:deviceId:deviceName] marker for cross-machine messages so
483
+ // the orchestrator knows to use reply-remote skill for responses.
484
+ if (message.source === MESSAGE_SOURCES.REMOTE && message.sourceMetadata?.fromDeviceId) {
485
+ const fromDeviceId = message.sourceMetadata.fromDeviceId;
486
+ const fromDeviceName = message.sourceMetadata.fromDeviceName || fromDeviceId;
487
+ deliveryContent = `${deliveryContent} [REMOTE:${fromDeviceId}:${fromDeviceName}]`;
488
+ }
489
+ // Inject [SLACK:channelId:threadTs] marker for Slack-sourced messages so
490
+ // crewly-agent runtimes can auto-fill reply_slack with the correct thread.
491
+ // Without this, in-process agents have no way to discover the originating
492
+ // Slack channel/thread — they only see the [CHAT:conversationId] prefix.
493
+ if (message.source === MESSAGE_SOURCES.SLACK && message.sourceMetadata?.channelId) {
494
+ const channelId = message.sourceMetadata.channelId;
495
+ const threadTs = message.sourceMetadata.threadTs;
496
+ const slackMarker = threadTs
497
+ ? `[SLACK:${channelId}:${threadTs}]`
498
+ : `[SLACK:${channelId}]`;
499
+ deliveryContent = `${deliveryContent} ${slackMarker}`;
500
+ }
501
+ // Route to the correct target session. System events may target
502
+ // non-orchestrator agents (e.g. crewly-agent subscribers).
503
+ const targetSession = message.targetSession || ORCHESTRATOR_SESSION_NAME;
504
+ // Resolve runtime type for non-orchestrator targets
505
+ let deliveryRuntimeType = runtimeType;
506
+ if (targetSession !== ORCHESTRATOR_SESSION_NAME) {
507
+ try {
508
+ const memberResult = await StorageService.getInstance().findMemberBySessionName(targetSession);
509
+ if (memberResult?.member?.runtimeType) {
510
+ deliveryRuntimeType = memberResult.member.runtimeType;
511
+ }
512
+ }
513
+ catch {
514
+ // Fall back to orchestrator's runtime type
515
+ }
516
+ }
517
+ // Deduplication: skip delivery if this message was already successfully
518
+ // delivered to the agent. This prevents duplicate responses when a message
519
+ // is requeued after AGENT_BUSY — the agent received it but is still processing.
520
+ if (this.deliveredMessageIds.has(message.id)) {
521
+ this.logger.info('Skipping duplicate delivery for already-delivered message', {
522
+ messageId: message.id,
523
+ source: message.source,
524
+ retryCount: message.retryCount || 0,
525
+ });
526
+ this.queueService.markCompleted(message.id, '');
527
+ if (batchedMessages.length > 0) {
528
+ this.queueService.markBatchCompleted(batchedMessages);
529
+ }
530
+ clearInterval(keepaliveInterval);
531
+ return;
532
+ }
533
+ // Safe restart: shutdown may have begun while we waited for the agent to
534
+ // be ready. Put the message(s) back so they survive the restart with
535
+ // their source metadata, instead of starting a turn the drain must wait for.
536
+ if (RestartDrainService.getInstance().isDeliveryPaused()) {
537
+ this.logger.info('Shutdown in progress — message left on the queue for after the restart', {
538
+ messageId: message.id,
539
+ targetSession,
540
+ });
541
+ this.queueService.requeue(message);
542
+ for (const batchedMsg of batchedMessages) {
543
+ this.queueService.requeue(batchedMsg);
544
+ }
545
+ this.nextAlreadyScheduled = true;
546
+ clearInterval(keepaliveInterval);
547
+ return;
548
+ }
549
+ const deliveryResult = await this.agentRegistrationService.sendMessageToAgent(targetSession, deliveryContent, deliveryRuntimeType);
550
+ // Safe restart: tell the in-flight tracker where this message came from,
551
+ // so a turn cut off by a restart can be re-enqueued with its original
552
+ // source (Slack thread, chat conversation) after boot.
553
+ if (deliveryResult.success && !deliveryResult.queued) {
554
+ InFlightTurnTracker.getInstance().annotate(targetSession, deliveryContent, {
555
+ messageId: message.id,
556
+ source: message.source,
557
+ conversationId: message.conversationId,
558
+ originalContent: message.content,
559
+ sourceMetadata: serializableSourceMetadata(message.sourceMetadata),
560
+ systemEvent: isSystemEvent,
561
+ });
562
+ }
563
+ // Record delivery timestamp for ACK detection
564
+ message.deliveredAt = new Date().toISOString();
565
+ if (!deliveryResult.success) {
566
+ const errorMsg = deliveryResult.error || 'Failed to deliver message to orchestrator';
567
+ // If agent is busy, the message was actually received by the agent process
568
+ // (it just can't accept new messages right now). Mark it as delivered to
569
+ // prevent duplicate processing if it gets requeued.
570
+ const isAgentBusy = errorMsg.includes('[AGENT_BUSY]');
571
+ if (isAgentBusy) {
572
+ this.deliveredMessageIds.set(message.id, Date.now());
573
+ }
574
+ const currentRetries = message.retryCount || 0;
575
+ if (isAgentBusy && currentRetries < MESSAGE_QUEUE_CONSTANTS.MAX_REQUEUE_RETRIES) {
576
+ this.logger.info('Agent busy, re-queuing message for later delivery', {
577
+ messageId: message.id,
578
+ retryCount: currentRetries + 1,
579
+ maxRetries: MESSAGE_QUEUE_CONSTANTS.MAX_REQUEUE_RETRIES,
580
+ });
581
+ this.queueService.requeue(message);
582
+ // Also re-queue any batched system event messages
583
+ for (const batchedMsg of batchedMessages) {
584
+ this.queueService.requeue(batchedMsg);
585
+ }
586
+ this.scheduleProcessNext(EVENT_DELIVERY_CONSTANTS.AGENT_READY_POLL_INTERVAL);
587
+ this.nextAlreadyScheduled = true;
588
+ clearInterval(keepaliveInterval);
589
+ return;
590
+ }
591
+ this.logger.warn('Message delivery failed', {
592
+ messageId: message.id,
593
+ error: errorMsg,
594
+ });
595
+ this.queueService.markFailed(message.id, errorMsg);
596
+ // Also fail any batched system event messages
597
+ if (batchedMessages.length > 0) {
598
+ this.queueService.markBatchFailed(batchedMessages, errorMsg);
599
+ }
600
+ this.responseRouter.routeError(message, errorMsg);
601
+ // Post a system message to the conversation so the user sees the error
602
+ // (skip for system events — no user conversation to notify)
603
+ if (!isSystemEvent) {
604
+ try {
605
+ const chatV2 = getChatV2Service();
606
+ const channel = chatV2.ensureChannelForLegacyConversation({
607
+ conversationId: message.conversationId,
608
+ agentSession: 'crewly-orc',
609
+ });
610
+ chatV2.recordTurn({
611
+ channelId: channel.id,
612
+ senderType: 'system',
613
+ senderId: 'system',
614
+ content: `Failed to deliver message to orchestrator: ${errorMsg}. Please try again.`,
615
+ metadata: { source: 'system' },
616
+ });
617
+ }
618
+ catch (sysErr) {
619
+ this.logger.warn('Failed to send delivery-failure system message', {
620
+ error: sysErr instanceof Error ? sysErr.message : String(sysErr),
621
+ });
622
+ }
623
+ }
624
+ return;
625
+ }
626
+ // Mark thread as delivered in the status queue for lifecycle tracking
627
+ if (this.threadStatusQueue && message.sourceMetadata) {
628
+ try {
629
+ const channelId = message.sourceMetadata.channelId;
630
+ const threadTs = message.sourceMetadata.threadTs;
631
+ if (channelId) {
632
+ const threadKey = threadTs ? `${channelId}:${threadTs}` : `${channelId}:${message.enqueuedAt}`;
633
+ this.threadStatusQueue.markDelivered(threadKey);
634
+ }
635
+ }
636
+ catch (err) {
637
+ this.logger.warn('Failed to mark thread as delivered', {
638
+ messageId: message.id,
639
+ error: err instanceof Error ? err.message : String(err),
640
+ });
641
+ }
642
+ }
643
+ // #239: For force-delivered user messages, add to pending-ack list
644
+ // instead of marking completed. The message will be verified or
645
+ // redelivered when the agent next returns to prompt.
646
+ if (wasForceDelivered && isUserMessage) {
647
+ this.pendingAckMessages.set(message.id, {
648
+ messageId: message.id,
649
+ conversationId: message.conversationId,
650
+ deliveryContent,
651
+ targetSession,
652
+ runtimeType: deliveryRuntimeType,
653
+ deliveredAt: Date.now(),
654
+ retryCount: 0,
655
+ originalMessage: message,
656
+ });
657
+ // Resolve source callbacks immediately to unblock callers (e.g. Slack bridge).
658
+ // The actual reply comes via reply-* skills; pending-ack only guards against
659
+ // the message being silently swallowed by the PTY.
660
+ this.responseRouter.routeResponse(message, '');
661
+ this.logger.info('Force-delivered user message added to pending-ack list', {
662
+ messageId: message.id,
663
+ source: message.source,
664
+ conversationId: message.conversationId,
665
+ pendingAckCount: this.pendingAckMessages.size,
666
+ });
667
+ }
668
+ else {
669
+ // Normal path: mark as completed immediately after delivery.
670
+ // Responses are handled asynchronously by the orchestrator through
671
+ // reply-* skills (reply-slack, reply-chat, reply-gchat).
672
+ this.queueService.markCompleted(message.id, '');
673
+ // Mark all batched system event messages as completed too
674
+ if (batchedMessages.length > 0) {
675
+ this.queueService.markBatchCompleted(batchedMessages);
676
+ }
677
+ // Resolve any pending source callbacks (e.g. slackResolve, googleChatResolve)
678
+ // with empty response to unblock callers. The actual reply comes via reply-* skills.
679
+ this.responseRouter.routeResponse(message, '');
680
+ this.logger.info('Message delivered (fire-and-forget)', {
681
+ messageId: message.id,
682
+ source: message.source,
683
+ batchSize: isSystemEvent ? 1 + batchedMessages.length : 1,
684
+ });
685
+ }
686
+ // Wait for orchestrator to finish post-delivery work before next message.
687
+ // Skip for system events: they're fire-and-forget notifications.
688
+ // The next processNext() iteration already calls waitForAgentReady before
689
+ // delivery, so we don't need to block here for system events.
690
+ if (!isSystemEvent) {
691
+ await this.agentRegistrationService.waitForAgentReady(targetSession, EVENT_DELIVERY_CONSTANTS.AGENT_READY_TIMEOUT, deliveryRuntimeType);
692
+ }
693
+ }
694
+ catch (error) {
695
+ const errorMsg = error instanceof Error ? error.message : String(error);
696
+ this.logger.error('Error processing message', {
697
+ messageId: message.id,
698
+ error: errorMsg,
699
+ });
700
+ this.queueService.markFailed(message.id, errorMsg);
701
+ this.responseRouter.routeError(message, errorMsg);
702
+ }
703
+ finally {
704
+ clearInterval(keepaliveInterval);
705
+ this.processing = false;
706
+ if (this.nextAlreadyScheduled) {
707
+ this.nextAlreadyScheduled = false;
708
+ }
709
+ else {
710
+ this.scheduleNextIfPending();
711
+ }
712
+ }
713
+ }
714
+ /**
715
+ * Schedule processing of the next message if there are pending messages.
716
+ * Uses INTER_MESSAGE_DELAY to avoid overwhelming the orchestrator.
717
+ *
718
+ * #218: Skip the inter-message delay when a user message is pending.
719
+ * This ensures user messages (from Slack, web chat, etc.) are never
720
+ * blocked behind the 500ms cooldown after system event delivery.
721
+ */
722
+ scheduleNextIfPending() {
723
+ if (!this.running)
724
+ return;
725
+ if (this.queueService.hasPending()) {
726
+ const delay = this.queueService.hasUserMessagePending()
727
+ ? 0
728
+ : MESSAGE_QUEUE_CONSTANTS.INTER_MESSAGE_DELAY;
729
+ this.scheduleProcessNext(delay);
730
+ }
731
+ else if (this.pendingAckMessages.size > 0) {
732
+ // #239: No new queue messages but pending-ack entries need flushing.
733
+ // Schedule a poll to check if agent returned to prompt.
734
+ this.scheduleProcessNext(EVENT_DELIVERY_CONSTANTS.AGENT_READY_POLL_INTERVAL);
735
+ }
736
+ }
737
+ /**
738
+ * Start periodic cleanup of stale entries in the delivered message dedup set.
739
+ * Runs every DEDUP_TTL_MS and removes entries older than DEDUP_TTL_MS.
740
+ */
741
+ startDedupCleanup() {
742
+ if (this.dedupCleanupTimer)
743
+ return;
744
+ this.dedupCleanupTimer = setInterval(() => {
745
+ this.purgeStaleDedup();
746
+ }, DEDUP_TTL_MS);
747
+ if (this.dedupCleanupTimer.unref) {
748
+ this.dedupCleanupTimer.unref();
749
+ }
750
+ }
751
+ /**
752
+ * Stop the periodic dedup cleanup timer.
753
+ */
754
+ stopDedupCleanup() {
755
+ if (this.dedupCleanupTimer) {
756
+ clearInterval(this.dedupCleanupTimer);
757
+ this.dedupCleanupTimer = null;
758
+ }
759
+ }
760
+ /**
761
+ * Remove entries from the dedup set that are older than DEDUP_TTL_MS.
762
+ */
763
+ purgeStaleDedup() {
764
+ const cutoff = Date.now() - DEDUP_TTL_MS;
765
+ let purged = 0;
766
+ for (const [id, timestamp] of this.deliveredMessageIds) {
767
+ if (timestamp < cutoff) {
768
+ this.deliveredMessageIds.delete(id);
769
+ purged++;
770
+ }
771
+ }
772
+ if (purged > 0) {
773
+ this.logger.debug('Purged stale dedup entries', { purged, remaining: this.deliveredMessageIds.size });
774
+ }
775
+ }
776
+ /**
777
+ * Get the number of tracked delivered message IDs (for testing).
778
+ *
779
+ * @returns Number of entries in the dedup set
780
+ */
781
+ getDeliveredMessageCount() {
782
+ return this.deliveredMessageIds.size;
783
+ }
784
+ /**
785
+ * Get the number of messages awaiting acknowledgment (for testing).
786
+ *
787
+ * @returns Number of entries in the pending-ack list
788
+ */
789
+ getPendingAckCount() {
790
+ return this.pendingAckMessages.size;
791
+ }
792
+ /**
793
+ * Start periodic cleanup of stale pending-ack entries.
794
+ * Runs at PENDING_ACK_TTL_MS intervals and fails entries that have exceeded
795
+ * the TTL — prevents memory leaks if an agent crashes and never returns to prompt.
796
+ */
797
+ startPendingAckCleanup() {
798
+ if (this.pendingAckCleanupTimer)
799
+ return;
800
+ const ttlMs = EVENT_DELIVERY_CONSTANTS.PENDING_ACK_TTL_MS ?? 600_000;
801
+ this.pendingAckCleanupTimer = setInterval(() => {
802
+ this.purgeExpiredPendingAcks();
803
+ }, ttlMs);
804
+ if (this.pendingAckCleanupTimer.unref) {
805
+ this.pendingAckCleanupTimer.unref();
806
+ }
807
+ }
808
+ /**
809
+ * Stop the periodic pending-ack cleanup timer.
810
+ */
811
+ stopPendingAckCleanup() {
812
+ if (this.pendingAckCleanupTimer) {
813
+ clearInterval(this.pendingAckCleanupTimer);
814
+ this.pendingAckCleanupTimer = null;
815
+ }
816
+ }
817
+ /**
818
+ * Remove pending-ack entries that have exceeded the TTL.
819
+ * Marks them as failed and routes error to the source so the user is notified.
820
+ */
821
+ purgeExpiredPendingAcks() {
822
+ const ttlMs = EVENT_DELIVERY_CONSTANTS.PENDING_ACK_TTL_MS ?? 600_000;
823
+ const now = Date.now();
824
+ let purged = 0;
825
+ for (const [id, entry] of this.pendingAckMessages) {
826
+ if (now - entry.deliveredAt > ttlMs) {
827
+ this.queueService.markFailed(id, 'Force-delivered message expired without acknowledgment (TTL cleanup)');
828
+ this.responseRouter.routeError(entry.originalMessage, 'Message delivery failed: the orchestrator did not process your message in time. Please try again.');
829
+ this.pendingAckMessages.delete(id);
830
+ purged++;
831
+ }
832
+ }
833
+ if (purged > 0) {
834
+ this.logger.warn('Purged expired pending-ack entries', { purged, remaining: this.pendingAckMessages.size });
835
+ }
836
+ }
837
+ /**
838
+ * Check if a pending-ack message's fingerprint appears in PTY output.
839
+ *
840
+ * Matches the full [CHAT:conversationId:fingerprint] or [GCHAT:conversationId:fingerprint]
841
+ * prefix. The fingerprint (last 8 chars of messageId) distinguishes between different
842
+ * messages in the same conversation/thread, preventing false-positive ack confirmations
843
+ * when a user sends multiple messages in quick succession.
844
+ *
845
+ * Falls back to conversationId-only matching for messages delivered before the
846
+ * fingerprint format was introduced (backward compat).
847
+ *
848
+ * @param output - Captured PTY output string
849
+ * @param conversationId - The conversation ID to search for
850
+ * @param messageId - The unique message ID (last 8 chars used as fingerprint)
851
+ * @returns true if the message fingerprint was found
852
+ */
853
+ isPendingAckInOutput(output, conversationId, messageId) {
854
+ // Primary check: match with message fingerprint for precise per-message ack
855
+ if (messageId) {
856
+ const fingerprint = messageId.slice(-8);
857
+ if (output.includes(`[CHAT:${conversationId}:${fingerprint}]`)
858
+ || output.includes(`[GCHAT:${conversationId}:${fingerprint}]`)
859
+ || output.includes(`[CHAT:${conversationId}:${fingerprint} `)
860
+ || output.includes(`[GCHAT:${conversationId}:${fingerprint} `)) {
861
+ return true;
862
+ }
863
+ }
864
+ // Fallback: conversationId-only matching for backward compatibility
865
+ // (messages delivered before fingerprint format was added)
866
+ if (!messageId) {
867
+ return output.includes(`[CHAT:${conversationId}]`)
868
+ || output.includes(`[GCHAT:${conversationId}]`)
869
+ || output.includes(`[CHAT:${conversationId} `)
870
+ || output.includes(`[GCHAT:${conversationId} `);
871
+ }
872
+ return false;
873
+ }
874
+ /**
875
+ * #239: Flush pending-ack messages by verifying them against PTY output.
876
+ *
877
+ * Called when the agent returns to prompt (waitForAgentReady succeeds).
878
+ * For each pending-ack entry:
879
+ * - Captures the agent's recent PTY output
880
+ * - Checks if the conversationId or CHAT prefix appears in the output
881
+ * - If found → message was processed, mark completed
882
+ * - If not found → message was swallowed, redeliver
883
+ * - If max retries exceeded → mark failed
884
+ *
885
+ * @param sessionName - The agent session to check
886
+ * @param runtimeType - Runtime type for redelivery
887
+ */
888
+ async flushPendingAcks(sessionName, runtimeType) {
889
+ if (this.pendingAckMessages.size === 0)
890
+ return;
891
+ const scanLines = EVENT_DELIVERY_CONSTANTS.PENDING_ACK_SCAN_LINES ?? 300;
892
+ const maxRetries = EVENT_DELIVERY_CONSTANTS.PENDING_ACK_MAX_RETRIES ?? 3;
893
+ const ttlMs = EVENT_DELIVERY_CONSTANTS.PENDING_ACK_TTL_MS ?? 600_000;
894
+ // Capture PTY output once for all pending checks
895
+ const ptyOutput = await this.agentRegistrationService.captureAgentOutput(sessionName, scanLines);
896
+ const now = Date.now();
897
+ const entriesToProcess = Array.from(this.pendingAckMessages.values());
898
+ for (const entry of entriesToProcess) {
899
+ // Skip entries for different sessions
900
+ if (entry.targetSession !== sessionName)
901
+ continue;
902
+ // Purge stale entries beyond TTL
903
+ if (now - entry.deliveredAt > ttlMs) {
904
+ this.logger.warn('Pending-ack entry expired beyond TTL, marking failed', {
905
+ messageId: entry.messageId,
906
+ conversationId: entry.conversationId,
907
+ ageMs: now - entry.deliveredAt,
908
+ });
909
+ this.queueService.markFailed(entry.messageId, 'Force-delivered message expired without acknowledgment');
910
+ this.pendingAckMessages.delete(entry.messageId);
911
+ continue;
912
+ }
913
+ // Check if the delivery prefix [CHAT:conversationId:fingerprint] appears in
914
+ // the PTY output. The fingerprint (from messageId) ensures we match THIS
915
+ // specific message, not a previous message in the same conversation thread.
916
+ const wasProcessed = this.isPendingAckInOutput(ptyOutput, entry.conversationId, entry.messageId);
917
+ if (wasProcessed) {
918
+ this.logger.info('Pending-ack message confirmed in PTY output', {
919
+ messageId: entry.messageId,
920
+ conversationId: entry.conversationId,
921
+ });
922
+ this.queueService.markCompleted(entry.messageId, '');
923
+ this.pendingAckMessages.delete(entry.messageId);
924
+ continue;
925
+ }
926
+ // Message not found in output — was likely swallowed by the PTY
927
+ if (entry.retryCount >= maxRetries) {
928
+ this.logger.error('Force-delivered message not acknowledged after max retries, marking failed', {
929
+ messageId: entry.messageId,
930
+ conversationId: entry.conversationId,
931
+ retryCount: entry.retryCount,
932
+ });
933
+ this.queueService.markFailed(entry.messageId, `Force-delivered message was not processed by the agent after ${entry.retryCount} redelivery attempts`);
934
+ this.responseRouter.routeError(entry.originalMessage, 'Message delivery failed: the orchestrator did not process your message. Please try again.');
935
+ this.pendingAckMessages.delete(entry.messageId);
936
+ continue;
937
+ }
938
+ // Redeliver: double-check PTY output right before sending to minimize duplicates
939
+ const freshOutput = await this.agentRegistrationService.captureAgentOutput(sessionName, scanLines);
940
+ if (this.isPendingAckInOutput(freshOutput, entry.conversationId, entry.messageId)) {
941
+ this.logger.info('Pending-ack message found on re-check, marking completed', {
942
+ messageId: entry.messageId,
943
+ conversationId: entry.conversationId,
944
+ });
945
+ this.queueService.markCompleted(entry.messageId, '');
946
+ this.pendingAckMessages.delete(entry.messageId);
947
+ continue;
948
+ }
949
+ // Redeliver the message
950
+ entry.retryCount++;
951
+ this.logger.warn('Redelivering force-delivered message (not found in PTY output)', {
952
+ messageId: entry.messageId,
953
+ conversationId: entry.conversationId,
954
+ retryCount: entry.retryCount,
955
+ maxRetries,
956
+ });
957
+ const redeliveryResult = await this.agentRegistrationService.sendMessageToAgent(entry.targetSession, entry.deliveryContent, entry.runtimeType);
958
+ if (!redeliveryResult.success) {
959
+ this.logger.warn('Redelivery failed, will retry on next idle', {
960
+ messageId: entry.messageId,
961
+ error: redeliveryResult.error,
962
+ });
963
+ }
964
+ // Keep in pendingAckMessages for next flush attempt regardless of success,
965
+ // because we still need to verify the agent actually processes it.
966
+ }
967
+ }
968
+ }
969
+ //# sourceMappingURL=queue-processor.service.js.map