@open-mercato/core 0.6.5-develop.4384.1.ce2ec6eaaa → 0.6.5-develop.4393.1.de282b5dfd

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 (533) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/channel_ingest_dead_letter/index.js +25 -0
  3. package/dist/generated/entities/channel_ingest_dead_letter/index.js.map +7 -0
  4. package/dist/generated/entities/channel_thread_mapping/index.js +25 -0
  5. package/dist/generated/entities/channel_thread_mapping/index.js.map +7 -0
  6. package/dist/generated/entities/channel_thread_token/index.js +17 -0
  7. package/dist/generated/entities/channel_thread_token/index.js.map +7 -0
  8. package/dist/generated/entities/communication_channel/index.js +43 -0
  9. package/dist/generated/entities/communication_channel/index.js.map +7 -0
  10. package/dist/generated/entities/customer_interaction/index.js +4 -0
  11. package/dist/generated/entities/customer_interaction/index.js.map +2 -2
  12. package/dist/generated/entities/external_conversation/index.js +25 -0
  13. package/dist/generated/entities/external_conversation/index.js.map +7 -0
  14. package/dist/generated/entities/external_message/index.js +25 -0
  15. package/dist/generated/entities/external_message/index.js.map +7 -0
  16. package/dist/generated/entities/integration_credentials/index.js +3 -1
  17. package/dist/generated/entities/integration_credentials/index.js.map +2 -2
  18. package/dist/generated/entities/message/index.js +2 -0
  19. package/dist/generated/entities/message/index.js.map +2 -2
  20. package/dist/generated/entities/message_channel_link/index.js +33 -0
  21. package/dist/generated/entities/message_channel_link/index.js.map +7 -0
  22. package/dist/generated/entities/message_reaction/index.js +25 -0
  23. package/dist/generated/entities/message_reaction/index.js.map +7 -0
  24. package/dist/generated/entities.ids.generated.js +11 -0
  25. package/dist/generated/entities.ids.generated.js.map +2 -2
  26. package/dist/generated/entity-fields-registry.js +117 -0
  27. package/dist/generated/entity-fields-registry.js.map +2 -2
  28. package/dist/helpers/integration/authFixtures.js +2 -1
  29. package/dist/helpers/integration/authFixtures.js.map +2 -2
  30. package/dist/helpers/integration/communicationChannelsFixtures.js +58 -0
  31. package/dist/helpers/integration/communicationChannelsFixtures.js.map +7 -0
  32. package/dist/modules/communication_channels/acl.js +47 -0
  33. package/dist/modules/communication_channels/acl.js.map +7 -0
  34. package/dist/modules/communication_channels/api/delete/channels/[id]/route.js +133 -0
  35. package/dist/modules/communication_channels/api/delete/channels/[id]/route.js.map +7 -0
  36. package/dist/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.js +113 -0
  37. package/dist/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.js.map +7 -0
  38. package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js +138 -0
  39. package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js.map +7 -0
  40. package/dist/modules/communication_channels/api/get/channels/[id]/route.js +93 -0
  41. package/dist/modules/communication_channels/api/get/channels/[id]/route.js.map +7 -0
  42. package/dist/modules/communication_channels/api/get/channels/route.js +96 -0
  43. package/dist/modules/communication_channels/api/get/channels/route.js.map +7 -0
  44. package/dist/modules/communication_channels/api/get/me/channels/route.js +82 -0
  45. package/dist/modules/communication_channels/api/get/me/channels/route.js.map +7 -0
  46. package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js +274 -0
  47. package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js.map +7 -0
  48. package/dist/modules/communication_channels/api/post/channels/[id]/import-history/route.js +168 -0
  49. package/dist/modules/communication_channels/api/post/channels/[id]/import-history/route.js.map +7 -0
  50. package/dist/modules/communication_channels/api/post/channels/[id]/poll-now/route.js +143 -0
  51. package/dist/modules/communication_channels/api/post/channels/[id]/poll-now/route.js.map +7 -0
  52. package/dist/modules/communication_channels/api/post/channels/[id]/push/register/route.js +127 -0
  53. package/dist/modules/communication_channels/api/post/channels/[id]/push/register/route.js.map +7 -0
  54. package/dist/modules/communication_channels/api/post/channels/[id]/set-primary/route.js +99 -0
  55. package/dist/modules/communication_channels/api/post/channels/[id]/set-primary/route.js.map +7 -0
  56. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +197 -0
  57. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +7 -0
  58. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +124 -0
  59. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +7 -0
  60. package/dist/modules/communication_channels/api/post/messages/[messageId]/reactions/route.js +120 -0
  61. package/dist/modules/communication_channels/api/post/messages/[messageId]/reactions/route.js.map +7 -0
  62. package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js +157 -0
  63. package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js.map +7 -0
  64. package/dist/modules/communication_channels/api/post/send-as-user/route.js +115 -0
  65. package/dist/modules/communication_channels/api/post/send-as-user/route.js.map +7 -0
  66. package/dist/modules/communication_channels/api/post/test-seed/route.js +217 -0
  67. package/dist/modules/communication_channels/api/post/test-seed/route.js.map +7 -0
  68. package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js +175 -0
  69. package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js.map +7 -0
  70. package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js +123 -0
  71. package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js.map +7 -0
  72. package/dist/modules/communication_channels/api/put/threads/[threadId]/assign/route.js +117 -0
  73. package/dist/modules/communication_channels/api/put/threads/[threadId]/assign/route.js.map +7 -0
  74. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js +180 -0
  75. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js.map +7 -0
  76. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.js +36 -0
  77. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.js.map +7 -0
  78. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js +107 -0
  79. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +7 -0
  80. package/dist/modules/communication_channels/backend/communication_channels/channels/page.meta.js +38 -0
  81. package/dist/modules/communication_channels/backend/communication_channels/channels/page.meta.js.map +7 -0
  82. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +727 -0
  83. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +7 -0
  84. package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js +38 -0
  85. package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js.map +7 -0
  86. package/dist/modules/communication_channels/commands/connect-credential-channel.js +154 -0
  87. package/dist/modules/communication_channels/commands/connect-credential-channel.js.map +7 -0
  88. package/dist/modules/communication_channels/commands/delete-channel.js +137 -0
  89. package/dist/modules/communication_channels/commands/delete-channel.js.map +7 -0
  90. package/dist/modules/communication_channels/commands/deliver-outbound-message.js +400 -0
  91. package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +7 -0
  92. package/dist/modules/communication_channels/commands/disconnect-channel.js +163 -0
  93. package/dist/modules/communication_channels/commands/disconnect-channel.js.map +7 -0
  94. package/dist/modules/communication_channels/commands/ingest-inbound-message.js +413 -0
  95. package/dist/modules/communication_channels/commands/ingest-inbound-message.js.map +7 -0
  96. package/dist/modules/communication_channels/commands/interceptors.js +68 -0
  97. package/dist/modules/communication_channels/commands/interceptors.js.map +7 -0
  98. package/dist/modules/communication_channels/commands/process-inbound-reaction.js +198 -0
  99. package/dist/modules/communication_channels/commands/process-inbound-reaction.js.map +7 -0
  100. package/dist/modules/communication_channels/commands/push-register.js +146 -0
  101. package/dist/modules/communication_channels/commands/push-register.js.map +7 -0
  102. package/dist/modules/communication_channels/commands/push-renew.js +23 -0
  103. package/dist/modules/communication_channels/commands/push-renew.js.map +7 -0
  104. package/dist/modules/communication_channels/commands/push-unregister.js +108 -0
  105. package/dist/modules/communication_channels/commands/push-unregister.js.map +7 -0
  106. package/dist/modules/communication_channels/commands/queue-import-history.js +113 -0
  107. package/dist/modules/communication_channels/commands/queue-import-history.js.map +7 -0
  108. package/dist/modules/communication_channels/commands/reassign-conversation.js +193 -0
  109. package/dist/modules/communication_channels/commands/reassign-conversation.js.map +7 -0
  110. package/dist/modules/communication_channels/commands/set-primary-channel.js +114 -0
  111. package/dist/modules/communication_channels/commands/set-primary-channel.js.map +7 -0
  112. package/dist/modules/communication_channels/commands/toggle-outbound-reaction.js +260 -0
  113. package/dist/modules/communication_channels/commands/toggle-outbound-reaction.js.map +7 -0
  114. package/dist/modules/communication_channels/data/enrichers.js +286 -0
  115. package/dist/modules/communication_channels/data/enrichers.js.map +7 -0
  116. package/dist/modules/communication_channels/data/entities.js +447 -0
  117. package/dist/modules/communication_channels/data/entities.js.map +7 -0
  118. package/dist/modules/communication_channels/data/extensions.js +67 -0
  119. package/dist/modules/communication_channels/data/extensions.js.map +7 -0
  120. package/dist/modules/communication_channels/data/validators.js +123 -0
  121. package/dist/modules/communication_channels/data/validators.js.map +7 -0
  122. package/dist/modules/communication_channels/di.js +35 -0
  123. package/dist/modules/communication_channels/di.js.map +7 -0
  124. package/dist/modules/communication_channels/encryption.js +12 -0
  125. package/dist/modules/communication_channels/encryption.js.map +7 -0
  126. package/dist/modules/communication_channels/events.js +124 -0
  127. package/dist/modules/communication_channels/events.js.map +7 -0
  128. package/dist/modules/communication_channels/index.js +20 -0
  129. package/dist/modules/communication_channels/index.js.map +7 -0
  130. package/dist/modules/communication_channels/lib/access-control.js +43 -0
  131. package/dist/modules/communication_channels/lib/access-control.js.map +7 -0
  132. package/dist/modules/communication_channels/lib/adapter-compat.js +36 -0
  133. package/dist/modules/communication_channels/lib/adapter-compat.js.map +7 -0
  134. package/dist/modules/communication_channels/lib/adapter-registry-singleton.js +22 -0
  135. package/dist/modules/communication_channels/lib/adapter-registry-singleton.js.map +7 -0
  136. package/dist/modules/communication_channels/lib/adapter.js +1 -0
  137. package/dist/modules/communication_channels/lib/adapter.js.map +7 -0
  138. package/dist/modules/communication_channels/lib/connect-channel.js +95 -0
  139. package/dist/modules/communication_channels/lib/connect-channel.js.map +7 -0
  140. package/dist/modules/communication_channels/lib/contact-resolver.js +79 -0
  141. package/dist/modules/communication_channels/lib/contact-resolver.js.map +7 -0
  142. package/dist/modules/communication_channels/lib/credential-refresh.js +97 -0
  143. package/dist/modules/communication_channels/lib/credential-refresh.js.map +7 -0
  144. package/dist/modules/communication_channels/lib/dead-letter.js +62 -0
  145. package/dist/modules/communication_channels/lib/dead-letter.js.map +7 -0
  146. package/dist/modules/communication_channels/lib/email-capabilities.js +47 -0
  147. package/dist/modules/communication_channels/lib/email-capabilities.js.map +7 -0
  148. package/dist/modules/communication_channels/lib/email-contact.js +14 -0
  149. package/dist/modules/communication_channels/lib/email-contact.js.map +7 -0
  150. package/dist/modules/communication_channels/lib/email-mime.js +259 -0
  151. package/dist/modules/communication_channels/lib/email-mime.js.map +7 -0
  152. package/dist/modules/communication_channels/lib/error-classification.js +101 -0
  153. package/dist/modules/communication_channels/lib/error-classification.js.map +7 -0
  154. package/dist/modules/communication_channels/lib/gmail-pubsub-jwt.js +185 -0
  155. package/dist/modules/communication_channels/lib/gmail-pubsub-jwt.js.map +7 -0
  156. package/dist/modules/communication_channels/lib/mutation-guards.js +114 -0
  157. package/dist/modules/communication_channels/lib/mutation-guards.js.map +7 -0
  158. package/dist/modules/communication_channels/lib/oauth-client-config.js +32 -0
  159. package/dist/modules/communication_channels/lib/oauth-client-config.js.map +7 -0
  160. package/dist/modules/communication_channels/lib/oauth-state.js +128 -0
  161. package/dist/modules/communication_channels/lib/oauth-state.js.map +7 -0
  162. package/dist/modules/communication_channels/lib/oauth-token.js +45 -0
  163. package/dist/modules/communication_channels/lib/oauth-token.js.map +7 -0
  164. package/dist/modules/communication_channels/lib/pg-errors.js +11 -0
  165. package/dist/modules/communication_channels/lib/pg-errors.js.map +7 -0
  166. package/dist/modules/communication_channels/lib/provider-health.js +24 -0
  167. package/dist/modules/communication_channels/lib/provider-health.js.map +7 -0
  168. package/dist/modules/communication_channels/lib/push-state.js +19 -0
  169. package/dist/modules/communication_channels/lib/push-state.js.map +7 -0
  170. package/dist/modules/communication_channels/lib/queue.js +54 -0
  171. package/dist/modules/communication_channels/lib/queue.js.map +7 -0
  172. package/dist/modules/communication_channels/lib/reaction-processor-types.js +5 -0
  173. package/dist/modules/communication_channels/lib/reaction-processor-types.js.map +7 -0
  174. package/dist/modules/communication_channels/lib/reaction-semantics.js +11 -0
  175. package/dist/modules/communication_channels/lib/reaction-semantics.js.map +7 -0
  176. package/dist/modules/communication_channels/lib/registry.js +67 -0
  177. package/dist/modules/communication_channels/lib/registry.js.map +7 -0
  178. package/dist/modules/communication_channels/lib/route-mutation-guard.js +43 -0
  179. package/dist/modules/communication_channels/lib/route-mutation-guard.js.map +7 -0
  180. package/dist/modules/communication_channels/lib/sanitize-channel-html.js +96 -0
  181. package/dist/modules/communication_channels/lib/sanitize-channel-html.js.map +7 -0
  182. package/dist/modules/communication_channels/lib/send-as-user.js +194 -0
  183. package/dist/modules/communication_channels/lib/send-as-user.js.map +7 -0
  184. package/dist/modules/communication_channels/lib/system-user.js +22 -0
  185. package/dist/modules/communication_channels/lib/system-user.js.map +7 -0
  186. package/dist/modules/communication_channels/lib/test-seed.js +68 -0
  187. package/dist/modules/communication_channels/lib/test-seed.js.map +7 -0
  188. package/dist/modules/communication_channels/lib/thread-matcher.js +263 -0
  189. package/dist/modules/communication_channels/lib/thread-matcher.js.map +7 -0
  190. package/dist/modules/communication_channels/lib/thread-token.js +219 -0
  191. package/dist/modules/communication_channels/lib/thread-token.js.map +7 -0
  192. package/dist/modules/communication_channels/lib/use-connect-channel.js +61 -0
  193. package/dist/modules/communication_channels/lib/use-connect-channel.js.map +7 -0
  194. package/dist/modules/communication_channels/migrations/Migration20260526134719_communication_channels.js +50 -0
  195. package/dist/modules/communication_channels/migrations/Migration20260526134719_communication_channels.js.map +7 -0
  196. package/dist/modules/communication_channels/migrations/Migration20260527195446_communication_channels.js +19 -0
  197. package/dist/modules/communication_channels/migrations/Migration20260527195446_communication_channels.js.map +7 -0
  198. package/dist/modules/communication_channels/migrations/Migration20260529231848_communication_channels.js +13 -0
  199. package/dist/modules/communication_channels/migrations/Migration20260529231848_communication_channels.js.map +7 -0
  200. package/dist/modules/communication_channels/migrations/Migration20260531120000_communication_channels.js +17 -0
  201. package/dist/modules/communication_channels/migrations/Migration20260531120000_communication_channels.js.map +7 -0
  202. package/dist/modules/communication_channels/notifications.client.js +51 -0
  203. package/dist/modules/communication_channels/notifications.client.js.map +7 -0
  204. package/dist/modules/communication_channels/notifications.handlers.js +53 -0
  205. package/dist/modules/communication_channels/notifications.handlers.js.map +7 -0
  206. package/dist/modules/communication_channels/notifications.js +56 -0
  207. package/dist/modules/communication_channels/notifications.js.map +7 -0
  208. package/dist/modules/communication_channels/setup.js +105 -0
  209. package/dist/modules/communication_channels/setup.js.map +7 -0
  210. package/dist/modules/communication_channels/subscribers/channel-requires-reauth-notification.js +71 -0
  211. package/dist/modules/communication_channels/subscribers/channel-requires-reauth-notification.js.map +7 -0
  212. package/dist/modules/communication_channels/subscribers/outbound-bridge.js +103 -0
  213. package/dist/modules/communication_channels/subscribers/outbound-bridge.js.map +7 -0
  214. package/dist/modules/communication_channels/subscribers/user-deleted-cascade.js +51 -0
  215. package/dist/modules/communication_channels/subscribers/user-deleted-cascade.js.map +7 -0
  216. package/dist/modules/communication_channels/widgets/components.js +7 -0
  217. package/dist/modules/communication_channels/widgets/components.js.map +7 -0
  218. package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.client.js +18 -0
  219. package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.client.js.map +7 -0
  220. package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.js +30 -0
  221. package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.js.map +7 -0
  222. package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.js +185 -0
  223. package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.js.map +7 -0
  224. package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.js +17 -0
  225. package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.js.map +7 -0
  226. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js +44 -0
  227. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js.map +7 -0
  228. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js +17 -0
  229. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js.map +7 -0
  230. package/dist/modules/communication_channels/widgets/injection/profile-channels-menu/widget.js +23 -0
  231. package/dist/modules/communication_channels/widgets/injection/profile-channels-menu/widget.js.map +7 -0
  232. package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.client.js +141 -0
  233. package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.client.js.map +7 -0
  234. package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.js +17 -0
  235. package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.js.map +7 -0
  236. package/dist/modules/communication_channels/widgets/injection-table.js +38 -0
  237. package/dist/modules/communication_channels/widgets/injection-table.js.map +7 -0
  238. package/dist/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.js +25 -0
  239. package/dist/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.js.map +7 -0
  240. package/dist/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.js +19 -0
  241. package/dist/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.js.map +7 -0
  242. package/dist/modules/communication_channels/widgets/notifications/index.js +7 -0
  243. package/dist/modules/communication_channels/widgets/notifications/index.js.map +7 -0
  244. package/dist/modules/communication_channels/workers/channel-import-history.js +185 -0
  245. package/dist/modules/communication_channels/workers/channel-import-history.js.map +7 -0
  246. package/dist/modules/communication_channels/workers/gmail-history-sync.js +154 -0
  247. package/dist/modules/communication_channels/workers/gmail-history-sync.js.map +7 -0
  248. package/dist/modules/communication_channels/workers/gmail-renew-watch.js +95 -0
  249. package/dist/modules/communication_channels/workers/gmail-renew-watch.js.map +7 -0
  250. package/dist/modules/communication_channels/workers/inbound-processor.js +56 -0
  251. package/dist/modules/communication_channels/workers/inbound-processor.js.map +7 -0
  252. package/dist/modules/communication_channels/workers/outbound-delivery.js +85 -0
  253. package/dist/modules/communication_channels/workers/outbound-delivery.js.map +7 -0
  254. package/dist/modules/communication_channels/workers/poll-channel.js +240 -0
  255. package/dist/modules/communication_channels/workers/poll-channel.js.map +7 -0
  256. package/dist/modules/communication_channels/workers/poll-tick.js +132 -0
  257. package/dist/modules/communication_channels/workers/poll-tick.js.map +7 -0
  258. package/dist/modules/communication_channels/workers/reaction-processor.js +192 -0
  259. package/dist/modules/communication_channels/workers/reaction-processor.js.map +7 -0
  260. package/dist/modules/customers/acl.js +18 -0
  261. package/dist/modules/customers/acl.js.map +2 -2
  262. package/dist/modules/customers/api/activities/route.js +9 -0
  263. package/dist/modules/customers/api/activities/route.js.map +2 -2
  264. package/dist/modules/customers/api/companies/[id]/route.js +18 -7
  265. package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
  266. package/dist/modules/customers/api/interactions/[id]/visibility/route.js +151 -0
  267. package/dist/modules/customers/api/interactions/[id]/visibility/route.js.map +7 -0
  268. package/dist/modules/customers/api/interactions/counts/route.js +6 -0
  269. package/dist/modules/customers/api/interactions/counts/route.js.map +2 -2
  270. package/dist/modules/customers/api/interactions/route.js +26 -7
  271. package/dist/modules/customers/api/interactions/route.js.map +2 -2
  272. package/dist/modules/customers/api/people/[id]/email-threads/route.js +82 -0
  273. package/dist/modules/customers/api/people/[id]/email-threads/route.js.map +7 -0
  274. package/dist/modules/customers/api/people/[id]/emails/route.js +157 -0
  275. package/dist/modules/customers/api/people/[id]/emails/route.js.map +7 -0
  276. package/dist/modules/customers/api/people/[id]/route.js +12 -4
  277. package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
  278. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +10 -0
  279. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  280. package/dist/modules/customers/commands/deals.js +46 -5
  281. package/dist/modules/customers/commands/deals.js.map +2 -2
  282. package/dist/modules/customers/commands/interactions.js +16 -0
  283. package/dist/modules/customers/commands/interactions.js.map +2 -2
  284. package/dist/modules/customers/components/detail/ActivityCard.js +32 -0
  285. package/dist/modules/customers/components/detail/ActivityCard.js.map +2 -2
  286. package/dist/modules/customers/components/detail/ComposeEmailDialog.js +242 -0
  287. package/dist/modules/customers/components/detail/ComposeEmailDialog.js.map +7 -0
  288. package/dist/modules/customers/components/detail/DealForm.js +2 -1
  289. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  290. package/dist/modules/customers/components/detail/DealsSection.js +10 -0
  291. package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
  292. package/dist/modules/customers/components/detail/EmailCardActions.js +179 -0
  293. package/dist/modules/customers/components/detail/EmailCardActions.js.map +7 -0
  294. package/dist/modules/customers/components/detail/EmailReplyForwardActions.js +52 -0
  295. package/dist/modules/customers/components/detail/EmailReplyForwardActions.js.map +7 -0
  296. package/dist/modules/customers/components/detail/PersonDetailTabs.js +7 -1
  297. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  298. package/dist/modules/customers/components/detail/PersonEmailThreadsTab.js +366 -0
  299. package/dist/modules/customers/components/detail/PersonEmailThreadsTab.js.map +7 -0
  300. package/dist/modules/customers/data/enrichers.js +133 -2
  301. package/dist/modules/customers/data/enrichers.js.map +2 -2
  302. package/dist/modules/customers/data/entities.js +18 -0
  303. package/dist/modules/customers/data/entities.js.map +2 -2
  304. package/dist/modules/customers/data/extensions.js +16 -0
  305. package/dist/modules/customers/data/extensions.js.map +7 -0
  306. package/dist/modules/customers/encryption.js +11 -0
  307. package/dist/modules/customers/encryption.js.map +2 -2
  308. package/dist/modules/customers/events.js +4 -1
  309. package/dist/modules/customers/events.js.map +2 -2
  310. package/dist/modules/customers/lib/findPeopleByAddresses.js +64 -0
  311. package/dist/modules/customers/lib/findPeopleByAddresses.js.map +7 -0
  312. package/dist/modules/customers/lib/kysely.js.map +2 -2
  313. package/dist/modules/customers/lib/link-channel-message-handler.js +303 -0
  314. package/dist/modules/customers/lib/link-channel-message-handler.js.map +7 -0
  315. package/dist/modules/customers/lib/personEmailThreads.js +205 -0
  316. package/dist/modules/customers/lib/personEmailThreads.js.map +7 -0
  317. package/dist/modules/customers/lib/visibilityFilter.js +51 -0
  318. package/dist/modules/customers/lib/visibilityFilter.js.map +7 -0
  319. package/dist/modules/customers/migrations/Migration20260527012240_customers.js +20 -0
  320. package/dist/modules/customers/migrations/Migration20260527012240_customers.js.map +7 -0
  321. package/dist/modules/customers/setup.js +2 -1
  322. package/dist/modules/customers/setup.js.map +2 -2
  323. package/dist/modules/customers/subscribers/link-channel-message-received.js +12 -0
  324. package/dist/modules/customers/subscribers/link-channel-message-received.js.map +7 -0
  325. package/dist/modules/customers/subscribers/link-channel-message-sent.js +12 -0
  326. package/dist/modules/customers/subscribers/link-channel-message-sent.js.map +7 -0
  327. package/dist/modules/integrations/data/entities.js +8 -1
  328. package/dist/modules/integrations/data/entities.js.map +2 -2
  329. package/dist/modules/integrations/lib/credentials-service.js +29 -14
  330. package/dist/modules/integrations/lib/credentials-service.js.map +2 -2
  331. package/dist/modules/integrations/migrations/Migration20260526154136_integrations.js +15 -0
  332. package/dist/modules/integrations/migrations/Migration20260526154136_integrations.js.map +7 -0
  333. package/dist/modules/messages/commands/messages.js +70 -8
  334. package/dist/modules/messages/commands/messages.js.map +2 -2
  335. package/dist/modules/messages/components/ComposeMessagePageClient.js +24 -13
  336. package/dist/modules/messages/components/ComposeMessagePageClient.js.map +2 -2
  337. package/dist/modules/messages/components/MessageDetailPageClient.js +39 -2
  338. package/dist/modules/messages/components/MessageDetailPageClient.js.map +2 -2
  339. package/dist/modules/messages/components/MessagesInboxPageClient.js +1 -0
  340. package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
  341. package/dist/modules/messages/data/entities.js +8 -1
  342. package/dist/modules/messages/data/entities.js.map +2 -2
  343. package/dist/modules/messages/migrations/Migration20260531130000.js +15 -0
  344. package/dist/modules/messages/migrations/Migration20260531130000.js.map +7 -0
  345. package/dist/modules/messages/widgets/injection-table.js +7 -0
  346. package/dist/modules/messages/widgets/injection-table.js.map +7 -0
  347. package/generated/entities/channel_ingest_dead_letter/index.ts +11 -0
  348. package/generated/entities/channel_thread_mapping/index.ts +11 -0
  349. package/generated/entities/channel_thread_token/index.ts +7 -0
  350. package/generated/entities/communication_channel/index.ts +20 -0
  351. package/generated/entities/customer_interaction/index.ts +2 -0
  352. package/generated/entities/external_conversation/index.ts +11 -0
  353. package/generated/entities/external_message/index.ts +11 -0
  354. package/generated/entities/integration_credentials/index.ts +1 -0
  355. package/generated/entities/message/index.ts +1 -0
  356. package/generated/entities/message_channel_link/index.ts +15 -0
  357. package/generated/entities/message_reaction/index.ts +11 -0
  358. package/generated/entities.ids.generated.ts +11 -0
  359. package/generated/entity-fields-registry.ts +117 -0
  360. package/package.json +9 -7
  361. package/src/helpers/integration/authFixtures.ts +4 -1
  362. package/src/helpers/integration/communicationChannelsFixtures.ts +124 -0
  363. package/src/modules/communication_channels/acl.ts +43 -0
  364. package/src/modules/communication_channels/api/delete/channels/[id]/route.ts +163 -0
  365. package/src/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.ts +143 -0
  366. package/src/modules/communication_channels/api/get/channels/[id]/health/route.ts +173 -0
  367. package/src/modules/communication_channels/api/get/channels/[id]/route.ts +111 -0
  368. package/src/modules/communication_channels/api/get/channels/route.ts +109 -0
  369. package/src/modules/communication_channels/api/get/me/channels/route.ts +100 -0
  370. package/src/modules/communication_channels/api/get/oauth/[provider]/callback/route.ts +355 -0
  371. package/src/modules/communication_channels/api/post/channels/[id]/import-history/route.ts +206 -0
  372. package/src/modules/communication_channels/api/post/channels/[id]/poll-now/route.ts +174 -0
  373. package/src/modules/communication_channels/api/post/channels/[id]/push/register/route.ts +158 -0
  374. package/src/modules/communication_channels/api/post/channels/[id]/set-primary/route.ts +114 -0
  375. package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +241 -0
  376. package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +134 -0
  377. package/src/modules/communication_channels/api/post/messages/[messageId]/reactions/route.ts +143 -0
  378. package/src/modules/communication_channels/api/post/oauth/[provider]/initiate/route.ts +192 -0
  379. package/src/modules/communication_channels/api/post/send-as-user/route.ts +125 -0
  380. package/src/modules/communication_channels/api/post/test-seed/route.ts +267 -0
  381. package/src/modules/communication_channels/api/post/webhook/[provider]/route.ts +227 -0
  382. package/src/modules/communication_channels/api/post/webhooks/gmail/route.ts +161 -0
  383. package/src/modules/communication_channels/api/put/threads/[threadId]/assign/route.ts +132 -0
  384. package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.ts +34 -0
  385. package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.tsx +250 -0
  386. package/src/modules/communication_channels/backend/communication_channels/channels/page.meta.ts +36 -0
  387. package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +137 -0
  388. package/src/modules/communication_channels/backend/profile/communication-channels/page.meta.ts +36 -0
  389. package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +907 -0
  390. package/src/modules/communication_channels/commands/connect-credential-channel.ts +243 -0
  391. package/src/modules/communication_channels/commands/delete-channel.ts +193 -0
  392. package/src/modules/communication_channels/commands/deliver-outbound-message.ts +579 -0
  393. package/src/modules/communication_channels/commands/disconnect-channel.ts +241 -0
  394. package/src/modules/communication_channels/commands/ingest-inbound-message.ts +602 -0
  395. package/src/modules/communication_channels/commands/interceptors.ts +104 -0
  396. package/src/modules/communication_channels/commands/process-inbound-reaction.ts +265 -0
  397. package/src/modules/communication_channels/commands/push-register.ts +203 -0
  398. package/src/modules/communication_channels/commands/push-renew.ts +49 -0
  399. package/src/modules/communication_channels/commands/push-unregister.ts +168 -0
  400. package/src/modules/communication_channels/commands/queue-import-history.ts +180 -0
  401. package/src/modules/communication_channels/commands/reassign-conversation.ts +273 -0
  402. package/src/modules/communication_channels/commands/set-primary-channel.ts +154 -0
  403. package/src/modules/communication_channels/commands/toggle-outbound-reaction.ts +347 -0
  404. package/src/modules/communication_channels/data/enrichers.ts +413 -0
  405. package/src/modules/communication_channels/data/entities.ts +546 -0
  406. package/src/modules/communication_channels/data/extensions.ts +76 -0
  407. package/src/modules/communication_channels/data/validators.ts +138 -0
  408. package/src/modules/communication_channels/di.ts +40 -0
  409. package/src/modules/communication_channels/encryption.ts +44 -0
  410. package/src/modules/communication_channels/events.ts +122 -0
  411. package/src/modules/communication_channels/i18n/de.json +138 -0
  412. package/src/modules/communication_channels/i18n/en.json +138 -0
  413. package/src/modules/communication_channels/i18n/es.json +138 -0
  414. package/src/modules/communication_channels/i18n/pl.json +138 -0
  415. package/src/modules/communication_channels/index.ts +19 -0
  416. package/src/modules/communication_channels/lib/access-control.ts +110 -0
  417. package/src/modules/communication_channels/lib/adapter-compat.ts +57 -0
  418. package/src/modules/communication_channels/lib/adapter-registry-singleton.ts +35 -0
  419. package/src/modules/communication_channels/lib/adapter.ts +605 -0
  420. package/src/modules/communication_channels/lib/connect-channel.ts +163 -0
  421. package/src/modules/communication_channels/lib/contact-resolver.ts +162 -0
  422. package/src/modules/communication_channels/lib/credential-refresh.ts +197 -0
  423. package/src/modules/communication_channels/lib/dead-letter.ts +87 -0
  424. package/src/modules/communication_channels/lib/email-capabilities.ts +60 -0
  425. package/src/modules/communication_channels/lib/email-contact.ts +17 -0
  426. package/src/modules/communication_channels/lib/email-mime.ts +425 -0
  427. package/src/modules/communication_channels/lib/error-classification.ts +144 -0
  428. package/src/modules/communication_channels/lib/gmail-pubsub-jwt.ts +278 -0
  429. package/src/modules/communication_channels/lib/mutation-guards.ts +215 -0
  430. package/src/modules/communication_channels/lib/oauth-client-config.ts +79 -0
  431. package/src/modules/communication_channels/lib/oauth-state.ts +228 -0
  432. package/src/modules/communication_channels/lib/oauth-token.ts +81 -0
  433. package/src/modules/communication_channels/lib/pg-errors.ts +12 -0
  434. package/src/modules/communication_channels/lib/provider-health.ts +47 -0
  435. package/src/modules/communication_channels/lib/push-state.ts +38 -0
  436. package/src/modules/communication_channels/lib/queue.ts +66 -0
  437. package/src/modules/communication_channels/lib/reaction-processor-types.ts +51 -0
  438. package/src/modules/communication_channels/lib/reaction-semantics.ts +48 -0
  439. package/src/modules/communication_channels/lib/registry.ts +99 -0
  440. package/src/modules/communication_channels/lib/route-mutation-guard.ts +68 -0
  441. package/src/modules/communication_channels/lib/sanitize-channel-html.ts +129 -0
  442. package/src/modules/communication_channels/lib/send-as-user.ts +284 -0
  443. package/src/modules/communication_channels/lib/system-user.ts +74 -0
  444. package/src/modules/communication_channels/lib/test-seed.ts +140 -0
  445. package/src/modules/communication_channels/lib/thread-matcher.ts +430 -0
  446. package/src/modules/communication_channels/lib/thread-token.ts +355 -0
  447. package/src/modules/communication_channels/lib/use-connect-channel.ts +73 -0
  448. package/src/modules/communication_channels/migrations/.snapshot-open-mercato.json +2142 -0
  449. package/src/modules/communication_channels/migrations/Migration20260526134719_communication_channels.ts +55 -0
  450. package/src/modules/communication_channels/migrations/Migration20260527195446_communication_channels.ts +20 -0
  451. package/src/modules/communication_channels/migrations/Migration20260529231848_communication_channels.ts +13 -0
  452. package/src/modules/communication_channels/migrations/Migration20260531120000_communication_channels.ts +24 -0
  453. package/src/modules/communication_channels/notifications.client.ts +50 -0
  454. package/src/modules/communication_channels/notifications.handlers.ts +86 -0
  455. package/src/modules/communication_channels/notifications.ts +52 -0
  456. package/src/modules/communication_channels/setup.ts +158 -0
  457. package/src/modules/communication_channels/subscribers/channel-requires-reauth-notification.ts +118 -0
  458. package/src/modules/communication_channels/subscribers/outbound-bridge.ts +175 -0
  459. package/src/modules/communication_channels/subscribers/user-deleted-cascade.ts +100 -0
  460. package/src/modules/communication_channels/widgets/components.ts +36 -0
  461. package/src/modules/communication_channels/widgets/injection/channel-badge/widget.client.tsx +38 -0
  462. package/src/modules/communication_channels/widgets/injection/channel-badge/widget.ts +51 -0
  463. package/src/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.tsx +278 -0
  464. package/src/modules/communication_channels/widgets/injection/channel-info-panel/widget.ts +24 -0
  465. package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.tsx +63 -0
  466. package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.ts +29 -0
  467. package/src/modules/communication_channels/widgets/injection/profile-channels-menu/widget.ts +34 -0
  468. package/src/modules/communication_channels/widgets/injection/reaction-bar/widget.client.tsx +177 -0
  469. package/src/modules/communication_channels/widgets/injection/reaction-bar/widget.ts +26 -0
  470. package/src/modules/communication_channels/widgets/injection-table.ts +47 -0
  471. package/src/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.tsx +48 -0
  472. package/src/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.tsx +45 -0
  473. package/src/modules/communication_channels/widgets/notifications/index.ts +2 -0
  474. package/src/modules/communication_channels/workers/channel-import-history.ts +252 -0
  475. package/src/modules/communication_channels/workers/gmail-history-sync.ts +223 -0
  476. package/src/modules/communication_channels/workers/gmail-renew-watch.ts +141 -0
  477. package/src/modules/communication_channels/workers/inbound-processor.ts +114 -0
  478. package/src/modules/communication_channels/workers/outbound-delivery.ts +155 -0
  479. package/src/modules/communication_channels/workers/poll-channel.ts +391 -0
  480. package/src/modules/communication_channels/workers/poll-tick.ts +210 -0
  481. package/src/modules/communication_channels/workers/reaction-processor.ts +264 -0
  482. package/src/modules/customers/acl.ts +18 -0
  483. package/src/modules/customers/api/activities/route.ts +13 -0
  484. package/src/modules/customers/api/companies/[id]/route.ts +21 -1
  485. package/src/modules/customers/api/interactions/[id]/visibility/route.ts +179 -0
  486. package/src/modules/customers/api/interactions/counts/route.ts +10 -0
  487. package/src/modules/customers/api/interactions/route.ts +51 -5
  488. package/src/modules/customers/api/people/[id]/email-threads/route.ts +92 -0
  489. package/src/modules/customers/api/people/[id]/emails/route.ts +184 -0
  490. package/src/modules/customers/api/people/[id]/route.ts +17 -2
  491. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +11 -1
  492. package/src/modules/customers/commands/deals.ts +65 -6
  493. package/src/modules/customers/commands/interactions.ts +30 -0
  494. package/src/modules/customers/components/detail/ActivityCard.tsx +48 -0
  495. package/src/modules/customers/components/detail/ComposeEmailDialog.tsx +329 -0
  496. package/src/modules/customers/components/detail/DealForm.tsx +2 -1
  497. package/src/modules/customers/components/detail/DealsSection.tsx +26 -0
  498. package/src/modules/customers/components/detail/EmailCardActions.tsx +258 -0
  499. package/src/modules/customers/components/detail/EmailReplyForwardActions.tsx +53 -0
  500. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +8 -1
  501. package/src/modules/customers/components/detail/PersonEmailThreadsTab.tsx +448 -0
  502. package/src/modules/customers/data/enrichers.ts +252 -1
  503. package/src/modules/customers/data/entities.ts +46 -1
  504. package/src/modules/customers/data/extensions.ts +26 -0
  505. package/src/modules/customers/encryption.ts +11 -0
  506. package/src/modules/customers/events.ts +4 -0
  507. package/src/modules/customers/i18n/de.json +41 -0
  508. package/src/modules/customers/i18n/en.json +41 -0
  509. package/src/modules/customers/i18n/es.json +41 -0
  510. package/src/modules/customers/i18n/pl.json +41 -0
  511. package/src/modules/customers/lib/findPeopleByAddresses.ts +107 -0
  512. package/src/modules/customers/lib/kysely.ts +16 -0
  513. package/src/modules/customers/lib/link-channel-message-handler.ts +571 -0
  514. package/src/modules/customers/lib/personEmailThreads.ts +325 -0
  515. package/src/modules/customers/lib/visibilityFilter.ts +152 -0
  516. package/src/modules/customers/migrations/.snapshot-open-mercato.json +61 -0
  517. package/src/modules/customers/migrations/Migration20260527012240_customers.ts +23 -0
  518. package/src/modules/customers/setup.ts +1 -0
  519. package/src/modules/customers/subscribers/link-channel-message-received.ts +21 -0
  520. package/src/modules/customers/subscribers/link-channel-message-sent.ts +21 -0
  521. package/src/modules/integrations/AGENTS.md +9 -0
  522. package/src/modules/integrations/data/entities.ts +21 -1
  523. package/src/modules/integrations/lib/credentials-service.ts +49 -13
  524. package/src/modules/integrations/migrations/.snapshot-open-mercato.json +26 -1
  525. package/src/modules/integrations/migrations/Migration20260526154136_integrations.ts +15 -0
  526. package/src/modules/messages/commands/messages.ts +101 -8
  527. package/src/modules/messages/components/ComposeMessagePageClient.tsx +17 -0
  528. package/src/modules/messages/components/MessageDetailPageClient.tsx +43 -0
  529. package/src/modules/messages/components/MessagesInboxPageClient.tsx +4 -0
  530. package/src/modules/messages/data/entities.ts +11 -0
  531. package/src/modules/messages/migrations/.snapshot-open-mercato.json +18 -0
  532. package/src/modules/messages/migrations/Migration20260531130000.ts +15 -0
  533. package/src/modules/messages/widgets/injection-table.ts +29 -0
@@ -0,0 +1,602 @@
1
+ import { randomUUID } from 'node:crypto'
2
+ import { z } from 'zod'
3
+ import type { EntityManager } from '@mikro-orm/postgresql'
4
+ import type { CommandBus, CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'
5
+ import { registerCommand } from '@open-mercato/shared/lib/commands'
6
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
7
+ import { emitCommunicationChannelsEvent } from '../events'
8
+ import { resolveContact } from '../lib/contact-resolver'
9
+ import type { ChannelAdapterRegistry } from '../lib/registry'
10
+ import type { NormalizedInboundMessage } from '../lib/adapter'
11
+ import { matchThread, type ThreadMatch } from '../lib/thread-matcher'
12
+ import {
13
+ ChannelThreadMapping,
14
+ CommunicationChannel,
15
+ ExternalConversation,
16
+ ExternalMessage,
17
+ MessageChannelLink,
18
+ } from '../data/entities'
19
+ import { normalizedInboundMessageSchema } from '../data/validators'
20
+ import { resolveCommunicationChannelsSystemUserId } from '../lib/system-user'
21
+ import { isUniqueViolation } from '../lib/pg-errors'
22
+
23
+ const ingestInputSchema = z.object({
24
+ channelId: z.string().uuid(),
25
+ providerKey: z.string().min(1),
26
+ channelType: z.string().min(1),
27
+ scope: z.object({
28
+ tenantId: z.string().uuid(),
29
+ organizationId: z.string().uuid().nullable(),
30
+ }),
31
+ message: normalizedInboundMessageSchema,
32
+ })
33
+
34
+ export type IngestInboundMessageInput = z.infer<typeof ingestInputSchema>
35
+
36
+ export type IngestInboundMessageResult = {
37
+ status: 'created' | 'duplicate'
38
+ messageId?: string
39
+ externalConversationId?: string
40
+ externalMessageId?: string
41
+ channelLinkId?: string
42
+ threadMappingId?: string
43
+ contactPersonId?: string | null
44
+ }
45
+
46
+ export const COMMUNICATION_CHANNELS_INGEST_INBOUND_COMMAND_ID = 'communication_channels.message.ingest_inbound'
47
+
48
+ /**
49
+ * Idempotently ingest a normalized inbound channel message.
50
+ *
51
+ * Steps (per SPEC-045d §6):
52
+ * 1. Dedup by `(channel_id, external_message_id)` — if a MessageChannelLink already
53
+ * exists for that pair, return `{ status: 'duplicate' }` without side effects.
54
+ * 2. Create or load `ExternalConversation` by `(channel_id, external_conversation_id)`.
55
+ * 3. Create or load `ChannelThreadMapping` (1:1 with ExternalConversation).
56
+ * 4. Resolve CRM contact via adapter + QueryEngine (best-effort).
57
+ * 5. Compose the platform `Message` via `messages.messages.compose` (separate transaction).
58
+ * 6. Create `ExternalMessage` + `MessageChannelLink`.
59
+ * 7. Emit `communication_channels.message.received` (and `.conversation.created` / `.contact.resolved` when applicable).
60
+ *
61
+ * The two-transaction model (compose-message-then-record-link) is acceptable for v1;
62
+ * the link's unique-on-message-id constraint is the safety net against orphans. See
63
+ * the pre-implementation analysis for a discussion of single-transaction alternatives.
64
+ */
65
+ const ingestInboundMessageCommand: CommandHandler<IngestInboundMessageInput, IngestInboundMessageResult> = {
66
+ id: COMMUNICATION_CHANNELS_INGEST_INBOUND_COMMAND_ID,
67
+ async execute(rawInput, ctx) {
68
+ const input = ingestInputSchema.parse(rawInput) as IngestInboundMessageInput
69
+
70
+ const em = (ctx.container.resolve('em') as EntityManager).fork()
71
+ const adapterRegistry = ctx.container.resolve('channelAdapterRegistry') as ChannelAdapterRegistry
72
+ const dscope = {
73
+ tenantId: input.scope.tenantId,
74
+ organizationId: input.scope.organizationId ?? null,
75
+ }
76
+
77
+ // (1) Dedup: short-circuit if we've already processed this provider message.
78
+ // The unique constraint is on `messageId`, not (channel, externalMessageId).
79
+ // We must dedup by joining against ExternalMessage which IS uniquely indexed by
80
+ // (channel_id, external_message_id). Hub-side dedup is the authoritative gate.
81
+ const existingExternal = await findOneWithDecryption(
82
+ em,
83
+ ExternalMessage,
84
+ {
85
+ channelId: input.channelId,
86
+ externalMessageId: input.message.externalMessageId,
87
+ tenantId: input.scope.tenantId,
88
+ organizationId: input.scope.organizationId ?? null,
89
+ },
90
+ undefined,
91
+ dscope,
92
+ )
93
+ if (existingExternal) {
94
+ return {
95
+ status: 'duplicate',
96
+ externalConversationId: existingExternal.conversationId,
97
+ externalMessageId: existingExternal.id,
98
+ }
99
+ }
100
+
101
+ // (1b) Spec B § Sent-folder dedup.
102
+ //
103
+ // When an outbound message lands in the user's IMAP Sent folder (or
104
+ // when Gmail's "send and archive" deposits it in All Mail), the next
105
+ // poll will re-fetch it from INBOX as if it were inbound. Skip it
106
+ // here using the RFC 5322 `Message-ID` header — we recorded it on the
107
+ // outbound `MessageChannelLink.channelMetadata.messageId` at send time.
108
+ //
109
+ // We dedup ONLY on outbound links (direction='outbound') for the same
110
+ // channel — that way an inbound copy of someone ELSE's email that
111
+ // happens to share a Message-ID is still ingested normally.
112
+ const incomingMessageId = (() => {
113
+ const fromMeta = (input.message.channelMetadata as Record<string, unknown> | undefined)?.messageId
114
+ if (typeof fromMeta === 'string' && fromMeta.length > 0) return fromMeta
115
+ return null
116
+ })()
117
+ if (incomingMessageId) {
118
+ // MikroORM v7 dropped the Knex builder in favour of Kysely/raw SQL.
119
+ // We use a positional-placeholder raw query for the JSONB
120
+ // `channel_metadata->>messageId` comparison.
121
+ try {
122
+ const sentFolderHit = await em.getConnection().execute<Array<{ id: string }>>(
123
+ `SELECT link.id FROM message_channel_links AS link
124
+ INNER JOIN external_conversations AS conv
125
+ ON conv.id = link.external_conversation_id
126
+ WHERE link.tenant_id = ?
127
+ AND ((?::uuid IS NULL AND link.organization_id IS NULL) OR link.organization_id = ?::uuid)
128
+ AND conv.tenant_id = ?
129
+ AND ((?::uuid IS NULL AND conv.organization_id IS NULL) OR conv.organization_id = ?::uuid)
130
+ AND conv.channel_id = ?
131
+ AND link.direction = 'outbound'
132
+ AND link.channel_metadata->>'messageId' = ?
133
+ LIMIT 1`,
134
+ [
135
+ input.scope.tenantId,
136
+ input.scope.organizationId ?? null,
137
+ input.scope.organizationId ?? null,
138
+ input.scope.tenantId,
139
+ input.scope.organizationId ?? null,
140
+ input.scope.organizationId ?? null,
141
+ input.channelId,
142
+ incomingMessageId,
143
+ ],
144
+ )
145
+ if (Array.isArray(sentFolderHit) && sentFolderHit.length > 0) {
146
+ return {
147
+ status: 'duplicate',
148
+ externalConversationId: input.message.externalConversationId,
149
+ externalMessageId: input.message.externalMessageId,
150
+ }
151
+ }
152
+ } catch (dedupErr) {
153
+ // Sent-folder dedup is best-effort — a failure here must not abort
154
+ // ingest (better a possible duplicate than a lost inbound message).
155
+ console.warn(
156
+ '[communication_channels:ingest-inbound] sent-folder dedup query failed, continuing:',
157
+ dedupErr instanceof Error ? dedupErr.message : dedupErr,
158
+ )
159
+ }
160
+ }
161
+
162
+ // Channel + adapter lookup (the channel must exist + be active).
163
+ const channel = await findOneWithDecryption(
164
+ em,
165
+ CommunicationChannel,
166
+ {
167
+ id: input.channelId,
168
+ tenantId: input.scope.tenantId,
169
+ organizationId: input.scope.organizationId ?? null,
170
+ deletedAt: null,
171
+ },
172
+ undefined,
173
+ dscope,
174
+ )
175
+ if (!channel) {
176
+ throw new Error(
177
+ `[internal] Channel ${input.channelId} not found for tenant ${input.scope.tenantId} (or has been deleted)`,
178
+ )
179
+ }
180
+ if (!channel.isActive) {
181
+ throw new Error(`[internal] Channel ${input.channelId} is inactive; refusing to ingest`)
182
+ }
183
+
184
+ const adapter = adapterRegistry.get(input.providerKey)
185
+ if (!adapter) {
186
+ throw new Error(
187
+ `[internal] No ChannelAdapter registered for providerKey '${input.providerKey}'. ` +
188
+ 'Check that the provider package is enabled in modules.ts.',
189
+ )
190
+ }
191
+
192
+ // (2) ExternalConversation upsert by (channel_id, externalConversationId).
193
+ const m = input.message
194
+ let conversation = await findOneWithDecryption(
195
+ em,
196
+ ExternalConversation,
197
+ {
198
+ channelId: input.channelId,
199
+ externalConversationId: m.externalConversationId,
200
+ tenantId: input.scope.tenantId,
201
+ organizationId: input.scope.organizationId ?? null,
202
+ },
203
+ undefined,
204
+ dscope,
205
+ )
206
+ let conversationCreated = false
207
+ if (!conversation) {
208
+ conversation = em.create(ExternalConversation, {
209
+ channelId: input.channelId,
210
+ externalConversationId: m.externalConversationId,
211
+ subject: m.subject ?? null,
212
+ tenantId: input.scope.tenantId,
213
+ organizationId: input.scope.organizationId ?? null,
214
+ lastMessageAt: m.timestamp ?? new Date(),
215
+ })
216
+ em.persist(conversation)
217
+ conversationCreated = true
218
+ }
219
+
220
+ // (3) ChannelThreadMapping upsert (1:1 with ExternalConversation per tenant).
221
+ let mapping = await findOneWithDecryption(
222
+ em,
223
+ ChannelThreadMapping,
224
+ {
225
+ externalConversationId: conversation.id,
226
+ tenantId: input.scope.tenantId,
227
+ organizationId: input.scope.organizationId ?? null,
228
+ },
229
+ undefined,
230
+ dscope,
231
+ )
232
+
233
+ // Last-activity bump on an existing conversation. Applied AFTER the mapping
234
+ // lookup, immediately before the flush, so the scalar mutation and its flush
235
+ // stay adjacent with no query in between (core flush-ordering rule — a query
236
+ // between a scalar mutation and `em.flush()` can drop the change under some
237
+ // flush modes / subscriber configurations).
238
+ if (
239
+ !conversationCreated &&
240
+ m.timestamp &&
241
+ (!conversation.lastMessageAt || m.timestamp > conversation.lastMessageAt)
242
+ ) {
243
+ conversation.lastMessageAt = m.timestamp
244
+ }
245
+ // We'll fill `messageThreadId` after composing the platform Message (since the
246
+ // first inbound message becomes the thread root in the messages module).
247
+ await em.flush()
248
+
249
+ // (3b) Spec B — layered thread match.
250
+ //
251
+ // Resolve the inbound message to an existing platform thread using
252
+ // (in priority order):
253
+ // 1. Crypto token in References / In-Reply-To header (high confidence)
254
+ // 2. Crypto token in body hidden span or plain-text marker (high)
255
+ // 3. JWZ on Message-Id ↔ stored `MessageChannelLink.channelMetadata.messageId` (medium)
256
+ // 4. Subject + participants in last 30 days, same channel (low)
257
+ //
258
+ // The matcher returns `null` when nothing hits — in that case we fall
259
+ // back to the existing `ChannelThreadMapping`-by-conversation-id lookup
260
+ // (which also returns null on first-ever inbound, in which case the
261
+ // compose command opens a new thread).
262
+ const metaForMatcher = (m.channelMetadata ?? {}) as Record<string, unknown>
263
+ let threadMatch: ThreadMatch | null = null
264
+ try {
265
+ threadMatch = await matchThread(
266
+ {
267
+ channelId: input.channelId,
268
+ tenantId: input.scope.tenantId,
269
+ organizationId: input.scope.organizationId ?? null,
270
+ messageId: extractStringFromMeta(metaForMatcher, 'messageId') ?? m.externalMessageId,
271
+ inReplyTo:
272
+ m.replyToExternalId ?? extractStringFromMeta(metaForMatcher, 'inReplyTo'),
273
+ references: extractStringArrayFromMeta(metaForMatcher, 'references'),
274
+ subject: m.subject ?? '',
275
+ fromAddress:
276
+ extractStringFromMeta(metaForMatcher, 'from') ?? m.senderIdentifier,
277
+ toAddresses: extractStringArrayFromMeta(metaForMatcher, 'to'),
278
+ ccAddresses: extractStringArrayFromMeta(metaForMatcher, 'cc'),
279
+ bodyPlain: m.bodyFormat === 'html' ? null : m.body ?? null,
280
+ bodyHtml: m.bodyFormat === 'html' ? m.body ?? null : null,
281
+ receivedAt: m.timestamp ?? new Date(),
282
+ },
283
+ { em },
284
+ )
285
+ } catch (matcherErr) {
286
+ // Matcher failure must not block ingest — fall back to the existing
287
+ // conversation-based thread mapping so the message still lands.
288
+ console.warn(
289
+ '[communication_channels:ingest-inbound] thread matcher failed, falling back to conversation mapping:',
290
+ matcherErr instanceof Error ? matcherErr.message : matcherErr,
291
+ )
292
+ }
293
+
294
+ // (4) Contact resolution (best-effort, advisory).
295
+ let contactHint: {
296
+ matchedPersonId?: string | null
297
+ email?: string
298
+ displayName?: string
299
+ } | null = null
300
+ try {
301
+ contactHint = await resolveContact(
302
+ {
303
+ adapter,
304
+ senderIdentifier: m.senderIdentifier,
305
+ senderDisplayName: m.senderDisplayName,
306
+ channelMetadata: m.channelMetadata,
307
+ credentials: {}, // credentials decrypted at the webhook route; resolver doesn't re-fetch
308
+ scope: {
309
+ tenantId: input.scope.tenantId,
310
+ organizationId: input.scope.organizationId ?? input.scope.tenantId,
311
+ },
312
+ },
313
+ { container: ctx.container },
314
+ )
315
+ } catch (contactErr) {
316
+ // Best-effort: contact resolution is advisory and must not abort ingest.
317
+ // Log like the sibling dedup/matcher catches so a misbehaving resolver is
318
+ // visible in operator logs instead of failing silently.
319
+ console.warn(
320
+ '[communication_channels:ingest-inbound] contact resolution failed, continuing without a CRM match:',
321
+ contactErr instanceof Error ? contactErr.message : contactErr,
322
+ )
323
+ contactHint = null
324
+ }
325
+ const matchedPersonId = contactHint?.matchedPersonId ?? null
326
+ if (matchedPersonId && conversation.contactPersonId !== matchedPersonId) {
327
+ conversation.contactPersonId = matchedPersonId
328
+ // Flush this scalar mutation before the system-user lookup below queries the
329
+ // same EntityManager. SPEC-018: a query between a scalar mutation and its
330
+ // flush can silently discard the pending UPDATE (mirrors the lastMessageAt
331
+ // bump above).
332
+ await em.flush()
333
+ }
334
+
335
+ // (5) Compose the platform Message via the messages module command.
336
+ //
337
+ // Sanitize against the `messages` module's validators (max 50_000 char body
338
+ // + non-empty subject) so real-world emails don't get rejected mid-ingest:
339
+ // - HTML emails routinely exceed 50k (Gmail signatures, marketing
340
+ // templates, RFC 5322 multipart). Truncate with a marker rather than
341
+ // drop the whole message — the full raw body is still in
342
+ // ExternalMessage.rawPayload if needed for forensic / forward use.
343
+ // - Some legitimate messages have no subject (notifications, bounce
344
+ // digests). Substitute a placeholder instead of failing ingest.
345
+ const MAX_COMPOSE_BODY = 50_000
346
+ const TRUNCATE_MARKER =
347
+ '\n\n[…message truncated by Open Mercato — full body preserved in ExternalMessage.rawPayload]'
348
+ const rawBody = m.body ?? ''
349
+ const truncatedBody =
350
+ rawBody.length > MAX_COMPOSE_BODY
351
+ ? rawBody.slice(0, MAX_COMPOSE_BODY - TRUNCATE_MARKER.length) + TRUNCATE_MARKER
352
+ : rawBody
353
+ const safeSubject = (m.subject ?? '').trim() || '(no subject)'
354
+
355
+ const composeInput = {
356
+ type: `channel.${input.providerKey}`,
357
+ visibility: 'public' as const,
358
+ sourceEntityType: 'communication_channels.external_conversation',
359
+ sourceEntityId: conversation.id,
360
+ externalEmail: contactHint?.email ?? undefined,
361
+ externalName: contactHint?.displayName ?? m.senderDisplayName,
362
+ recipients: mapping?.assignedUserId
363
+ ? [{ userId: mapping.assignedUserId, type: 'to' as const }]
364
+ : [],
365
+ subject: safeSubject,
366
+ body: truncatedBody,
367
+ bodyFormat: (m.bodyFormat === 'html' ? 'text' : m.bodyFormat) as 'text' | 'markdown',
368
+ priority: 'normal' as const,
369
+ sendViaEmail: false,
370
+ // Spec B: matcher-resolved thread id takes priority over the existing
371
+ // conversation-based mapping. Falls through to `mapping?.messageThreadId`
372
+ // when the matcher returned null (no token / JWZ / subject hit).
373
+ parentMessageId: threadMatch?.messageThreadId ?? mapping?.messageThreadId,
374
+ isDraft: false,
375
+ // Stable dedup key so a retried ingest (after a transient failure between
376
+ // compose and the ExternalMessage anchor insert) reuses the message
377
+ // composed by the first attempt instead of duplicating it. Mirrors the
378
+ // (channel, externalMessageId) ExternalMessage anchor's natural key.
379
+ idempotencyKey: m.externalMessageId
380
+ ? `cc:${input.channelId}:${m.externalMessageId}`
381
+ : undefined,
382
+ tenantId: input.scope.tenantId,
383
+ organizationId: input.scope.organizationId,
384
+ userId: await resolveCommunicationChannelsSystemUserId(
385
+ em,
386
+ input.scope.tenantId,
387
+ mapping?.assignedUserId ?? null,
388
+ ),
389
+ }
390
+
391
+ const commandBus = ctx.container.resolve('commandBus') as CommandBus
392
+ const composeResult = await commandBus.execute<typeof composeInput, { id: string; threadId: string | null }>(
393
+ 'messages.messages.compose',
394
+ {
395
+ input: composeInput,
396
+ ctx: passthroughCommandCtx(ctx, input.scope),
397
+ },
398
+ )
399
+ const message = composeResult.result
400
+ if (!message?.id) {
401
+ throw new Error('messages.messages.compose did not return a message id')
402
+ }
403
+
404
+ // (3 continued) Create or update ChannelThreadMapping now that we have a threadId.
405
+ if (!mapping) {
406
+ mapping = em.create(ChannelThreadMapping, {
407
+ externalConversationId: conversation.id,
408
+ messageThreadId: message.threadId ?? message.id,
409
+ channelId: input.channelId,
410
+ providerKey: input.providerKey,
411
+ externalThreadRef: m.externalConversationId,
412
+ tenantId: input.scope.tenantId,
413
+ organizationId: input.scope.organizationId ?? null,
414
+ })
415
+ em.persist(mapping)
416
+ }
417
+
418
+ // (6) Create ExternalMessage + MessageChannelLink (hub-side records).
419
+ //
420
+ // The PrimaryKey for both uses `defaultRaw: 'gen_random_uuid()'` — a
421
+ // Postgres-side default that doesn't populate the entity's `id` field
422
+ // until after the INSERT returns. If we let MikroORM generate both, then
423
+ // `em.create(MessageChannelLink, { externalMessageId: externalMessage.id })`
424
+ // reads `undefined` for `externalMessage.id` (it hasn't been flushed yet)
425
+ // and writes NULL to `message_channel_links.external_message_id`,
426
+ // breaking the FK and causing downstream joins to silently return 0 rows.
427
+ //
428
+ // Pre-generating both UUIDs client-side fixes the cross-row reference
429
+ // problem and keeps the single-transaction flush semantics intact.
430
+ const externalMessageRowId = randomUUID()
431
+ const channelLinkRowId = randomUUID()
432
+ const externalMessage = em.create(ExternalMessage, {
433
+ id: externalMessageRowId,
434
+ channelId: input.channelId,
435
+ conversationId: conversation.id,
436
+ externalMessageId: m.externalMessageId,
437
+ direction: 'inbound',
438
+ senderIdentifier: m.senderIdentifier,
439
+ senderDisplayName: m.senderDisplayName ?? null,
440
+ providerTimestamp: m.timestamp,
441
+ tenantId: input.scope.tenantId,
442
+ organizationId: input.scope.organizationId ?? null,
443
+ })
444
+ em.persist(externalMessage)
445
+
446
+ // Spec B: annotate the link with which thread-matcher strategy resolved
447
+ // this message (or `'new-thread'` when matcher returned null and we
448
+ // opened a fresh thread). Surfaced to observability + future UI ("this
449
+ // thread match is low-confidence — confirm or move").
450
+ const matcherAnnotatedMetadata: Record<string, unknown> = {
451
+ ...((m.channelMetadata as Record<string, unknown> | undefined) ?? {}),
452
+ threadMatchStrategy: threadMatch?.matchedBy ?? 'new-thread',
453
+ threadMatchConfidence: threadMatch?.confidence ?? 'low',
454
+ }
455
+
456
+ const channelLink = em.create(MessageChannelLink, {
457
+ id: channelLinkRowId,
458
+ messageId: message.id,
459
+ externalConversationId: conversation.id,
460
+ externalMessageId: externalMessageRowId,
461
+ providerKey: input.providerKey,
462
+ channelType: input.channelType,
463
+ direction: 'inbound',
464
+ deliveryStatus: 'received',
465
+ channelPayload: m.channelPayload,
466
+ channelContentType: m.channelContentType,
467
+ channelMetadata: matcherAnnotatedMetadata,
468
+ tenantId: input.scope.tenantId,
469
+ organizationId: input.scope.organizationId ?? null,
470
+ })
471
+ em.persist(channelLink)
472
+
473
+ try {
474
+ await em.flush()
475
+ } catch (flushErr) {
476
+ // Concurrency guard: the pre-check at (1) is not atomic with this insert,
477
+ // so a poll re-fetch racing a push notification (or two push deliveries)
478
+ // can both pass the check and reach here. The `(channel_id,
479
+ // external_message_id)` unique index rejects the loser with a 23505. Treat
480
+ // that as a duplicate — returning here (instead of throwing) prevents the
481
+ // message from being dead-lettered and retried forever. The winning job
482
+ // already recorded the message + link.
483
+ if (isUniqueViolation(flushErr)) {
484
+ return {
485
+ status: 'duplicate',
486
+ externalConversationId: conversation.id,
487
+ externalMessageId: m.externalMessageId,
488
+ }
489
+ }
490
+ throw flushErr
491
+ }
492
+
493
+ // (7) Emit events — order matters for downstream subscribers.
494
+ if (conversationCreated) {
495
+ await emitCommunicationChannelsEvent(
496
+ 'communication_channels.conversation.created',
497
+ {
498
+ conversationId: conversation.id,
499
+ channelId: input.channelId,
500
+ providerKey: input.providerKey,
501
+ channelType: input.channelType,
502
+ externalConversationId: m.externalConversationId,
503
+ tenantId: input.scope.tenantId,
504
+ organizationId: input.scope.organizationId ?? null,
505
+ },
506
+ { persistent: true },
507
+ )
508
+ }
509
+ if (matchedPersonId) {
510
+ await emitCommunicationChannelsEvent(
511
+ 'communication_channels.contact.resolved',
512
+ {
513
+ conversationId: conversation.id,
514
+ contactPersonId: matchedPersonId,
515
+ providerKey: input.providerKey,
516
+ tenantId: input.scope.tenantId,
517
+ organizationId: input.scope.organizationId ?? null,
518
+ },
519
+ { persistent: true },
520
+ )
521
+ }
522
+ await emitCommunicationChannelsEvent(
523
+ 'communication_channels.message.received',
524
+ {
525
+ messageId: message.id,
526
+ externalMessageId: externalMessage.id,
527
+ channelLinkId: channelLink.id,
528
+ conversationId: conversation.id,
529
+ channelId: input.channelId,
530
+ providerKey: input.providerKey,
531
+ channelType: input.channelType,
532
+ direction: 'inbound',
533
+ tenantId: input.scope.tenantId,
534
+ organizationId: input.scope.organizationId ?? null,
535
+ },
536
+ { persistent: true },
537
+ )
538
+
539
+ return {
540
+ status: 'created',
541
+ messageId: message.id,
542
+ externalConversationId: conversation.id,
543
+ externalMessageId: externalMessage.id,
544
+ channelLinkId: channelLink.id,
545
+ threadMappingId: mapping.id,
546
+ contactPersonId: matchedPersonId,
547
+ }
548
+ },
549
+ }
550
+
551
+
552
+ /**
553
+ * Build a runtime context for the nested `messages.messages.compose` call.
554
+ *
555
+ * The compose command expects a `CommandRuntimeContext`. For inbound webhook
556
+ * processing there is no platform user; we pass `auth: null` and use the tenant
557
+ * scope from our input.
558
+ */
559
+ function passthroughCommandCtx(
560
+ parent: CommandRuntimeContext,
561
+ scope: IngestInboundMessageInput['scope'],
562
+ ): CommandRuntimeContext {
563
+ return {
564
+ container: parent.container,
565
+ auth: null,
566
+ organizationScope: null,
567
+ selectedOrganizationId: scope.organizationId ?? null,
568
+ organizationIds: scope.organizationId ? [scope.organizationId] : null,
569
+ }
570
+ }
571
+
572
+ /**
573
+ * Pull a string value from the provider's `channelMetadata` map. Returns
574
+ * `null` (not `undefined`) when the key is absent or the value isn't a
575
+ * string — keeps the matcher's input shape predictable.
576
+ */
577
+ function extractStringFromMeta(
578
+ meta: Record<string, unknown>,
579
+ key: string,
580
+ ): string | null {
581
+ const value = meta[key]
582
+ if (typeof value === 'string' && value.length > 0) return value
583
+ return null
584
+ }
585
+
586
+ /**
587
+ * Pull a string[] value from the provider's `channelMetadata` map.
588
+ * Filters out non-string entries defensively. Returns an empty array
589
+ * when the key is absent or the value isn't an array.
590
+ */
591
+ function extractStringArrayFromMeta(
592
+ meta: Record<string, unknown>,
593
+ key: string,
594
+ ): string[] {
595
+ const value = meta[key]
596
+ if (!Array.isArray(value)) return []
597
+ return value.filter((item): item is string => typeof item === 'string')
598
+ }
599
+
600
+ registerCommand(ingestInboundMessageCommand)
601
+
602
+ export default ingestInboundMessageCommand