@webex/contact-center 3.12.0-next.17 → 3.12.0-next.170

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 (328) hide show
  1. package/.sdd/manifest.json +887 -0
  2. package/AGENTS.md +94 -0
  3. package/ai-docs/ARCHITECTURE.md +168 -0
  4. package/ai-docs/CONTRACTS.md +62 -0
  5. package/ai-docs/GETTING_STARTED.md +168 -0
  6. package/ai-docs/GLOSSARY.md +43 -0
  7. package/ai-docs/README.md +138 -0
  8. package/ai-docs/REVIEW_CHECKLIST.md +41 -0
  9. package/ai-docs/RULES.md +444 -0
  10. package/ai-docs/SECURITY.md +52 -0
  11. package/ai-docs/SERVICE_STATE.md +52 -0
  12. package/ai-docs/SPEC_INDEX.md +65 -0
  13. package/ai-docs/adr/0001-spec-source-policy.md +55 -0
  14. package/ai-docs/adr/README.md +8 -0
  15. package/ai-docs/adr/_adr-template.md +31 -0
  16. package/ai-docs/contact-center-spec.md +385 -0
  17. package/ai-docs/features/consult-transfer-list-policy/spec/feature-spec.md +362 -0
  18. package/ai-docs/features/contact-owner-change-refactor.md +78 -0
  19. package/ai-docs/features/generated-spec-conformance-fidelity-remediation/spec/feature-spec.md +117 -0
  20. package/ai-docs/features/residual-warning-coverage-completion/spec/feature-spec.md +203 -0
  21. package/ai-docs/features/validator-code-fidelity-drift-fix/spec/feature-spec.md +315 -0
  22. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  23. package/ai-docs/patterns/testing-patterns.md +480 -0
  24. package/ai-docs/patterns/typescript-patterns.md +365 -0
  25. package/ai-docs/templates/README.md +102 -0
  26. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  27. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  28. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  29. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  30. package/ai-docs/templates/new-method/00-master.md +80 -0
  31. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  32. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  33. package/ai-docs/templates/new-method/03-tests.md +201 -0
  34. package/ai-docs/templates/new-method/04-validation.md +141 -0
  35. package/ai-docs/templates/new-service/00-master.md +109 -0
  36. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  37. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  38. package/ai-docs/templates/new-service/03-integration.md +178 -0
  39. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  40. package/ai-docs/templates/new-service/05-validation.md +145 -0
  41. package/dist/cc.js +888 -49
  42. package/dist/cc.js.map +1 -1
  43. package/dist/config.js +13 -0
  44. package/dist/config.js.map +1 -1
  45. package/dist/constants.js +38 -3
  46. package/dist/constants.js.map +1 -1
  47. package/dist/index.js +54 -17
  48. package/dist/index.js.map +1 -1
  49. package/dist/metrics/MetricsManager.js +16 -0
  50. package/dist/metrics/MetricsManager.js.map +1 -1
  51. package/dist/metrics/behavioral-events.js +185 -0
  52. package/dist/metrics/behavioral-events.js.map +1 -1
  53. package/dist/metrics/constants.js +54 -4
  54. package/dist/metrics/constants.js.map +1 -1
  55. package/dist/services/AddressBook.js +18 -15
  56. package/dist/services/AddressBook.js.map +1 -1
  57. package/dist/services/AnswerCallOnWebexService.js +202 -0
  58. package/dist/services/AnswerCallOnWebexService.js.map +1 -0
  59. package/dist/services/ApiAiAssistant.js +369 -14
  60. package/dist/services/ApiAiAssistant.js.map +1 -1
  61. package/dist/services/EntryPoint.js +46 -68
  62. package/dist/services/EntryPoint.js.map +1 -1
  63. package/dist/services/Queue.js +27 -22
  64. package/dist/services/Queue.js.map +1 -1
  65. package/dist/services/UserPreference.js +427 -0
  66. package/dist/services/UserPreference.js.map +1 -0
  67. package/dist/services/WebCallingService.js +10 -2
  68. package/dist/services/WebCallingService.js.map +1 -1
  69. package/dist/services/WebexCrossClientService.js +225 -0
  70. package/dist/services/WebexCrossClientService.js.map +1 -0
  71. package/dist/services/WxAppTelephonyMercurySync.js +111 -0
  72. package/dist/services/WxAppTelephonyMercurySync.js.map +1 -0
  73. package/dist/services/config/Util.js +48 -48
  74. package/dist/services/config/Util.js.map +1 -1
  75. package/dist/services/config/constants.js +56 -78
  76. package/dist/services/config/constants.js.map +1 -1
  77. package/dist/services/config/index.js +100 -226
  78. package/dist/services/config/index.js.map +1 -1
  79. package/dist/services/config/types.js +93 -24
  80. package/dist/services/config/types.js.map +1 -1
  81. package/dist/services/constants.js +11 -1
  82. package/dist/services/constants.js.map +1 -1
  83. package/dist/services/core/Err.js.map +1 -1
  84. package/dist/services/core/Utils.js +131 -32
  85. package/dist/services/core/Utils.js.map +1 -1
  86. package/dist/services/core/WebexRequest.js +6 -2
  87. package/dist/services/core/WebexRequest.js.map +1 -1
  88. package/dist/services/core/aqm-reqs.js +17 -11
  89. package/dist/services/core/aqm-reqs.js.map +1 -1
  90. package/dist/services/core/constants.js +9 -1
  91. package/dist/services/core/constants.js.map +1 -1
  92. package/dist/services/core/types.js.map +1 -1
  93. package/dist/services/core/websocket/WebSocketManager.js +19 -2
  94. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  95. package/dist/services/core/websocket/types.js.map +1 -1
  96. package/dist/services/index.js +1 -1
  97. package/dist/services/index.js.map +1 -1
  98. package/dist/services/task/Task.js +955 -0
  99. package/dist/services/task/Task.js.map +1 -0
  100. package/dist/services/task/TaskFactory.js +50 -0
  101. package/dist/services/task/TaskFactory.js.map +1 -0
  102. package/dist/services/task/TaskManager.js +1272 -605
  103. package/dist/services/task/TaskManager.js.map +1 -1
  104. package/dist/services/task/TaskUtils.js +221 -27
  105. package/dist/services/task/TaskUtils.js.map +1 -1
  106. package/dist/services/task/WebexCallingUtils.js +70 -0
  107. package/dist/services/task/WebexCallingUtils.js.map +1 -0
  108. package/dist/services/task/constants.js +19 -2
  109. package/dist/services/task/constants.js.map +1 -1
  110. package/dist/services/task/contact.js +29 -0
  111. package/dist/services/task/contact.js.map +1 -1
  112. package/dist/services/task/digital/Digital.js +78 -0
  113. package/dist/services/task/digital/Digital.js.map +1 -0
  114. package/dist/services/task/state-machine/TaskStateMachine.js +982 -0
  115. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  116. package/dist/services/task/state-machine/actions.js +572 -0
  117. package/dist/services/task/state-machine/actions.js.map +1 -0
  118. package/dist/services/task/state-machine/constants.js +161 -0
  119. package/dist/services/task/state-machine/constants.js.map +1 -0
  120. package/dist/services/task/state-machine/guards.js +379 -0
  121. package/dist/services/task/state-machine/guards.js.map +1 -0
  122. package/dist/services/task/state-machine/index.js +53 -0
  123. package/dist/services/task/state-machine/index.js.map +1 -0
  124. package/dist/services/task/state-machine/types.js +54 -0
  125. package/dist/services/task/state-machine/types.js.map +1 -0
  126. package/dist/services/task/state-machine/uiControlsComputer.js +703 -0
  127. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  128. package/dist/services/task/taskDataNormalizer.js +99 -0
  129. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  130. package/dist/services/task/types.js +215 -5
  131. package/dist/services/task/types.js.map +1 -1
  132. package/dist/services/task/voice/Voice.js +1504 -0
  133. package/dist/services/task/voice/Voice.js.map +1 -0
  134. package/dist/services/task/voice/WebRTC.js +152 -0
  135. package/dist/services/task/voice/WebRTC.js.map +1 -0
  136. package/dist/services/task/voice/wxAppOfferObservability.js +145 -0
  137. package/dist/services/task/voice/wxAppOfferObservability.js.map +1 -0
  138. package/dist/services/task/voice/wxAppVoiceMethods.js +395 -0
  139. package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
  140. package/dist/services/wxAppDiagnosticLogging.js +159 -0
  141. package/dist/services/wxAppDiagnosticLogging.js.map +1 -0
  142. package/dist/services/wxAppTelephonyUtils.js +19 -0
  143. package/dist/services/wxAppTelephonyUtils.js.map +1 -0
  144. package/dist/types/cc.d.ts +152 -9
  145. package/dist/types/config.d.ts +13 -0
  146. package/dist/types/constants.d.ts +36 -1
  147. package/dist/types/index.d.ts +54 -20
  148. package/dist/types/metrics/MetricsManager.d.ts +6 -0
  149. package/dist/types/metrics/constants.d.ts +48 -1
  150. package/dist/types/services/AddressBook.d.ts +2 -1
  151. package/dist/types/services/AnswerCallOnWebexService.d.ts +38 -0
  152. package/dist/types/services/ApiAiAssistant.d.ts +67 -5
  153. package/dist/types/services/EntryPoint.d.ts +7 -5
  154. package/dist/types/services/Queue.d.ts +5 -3
  155. package/dist/types/services/UserPreference.d.ts +118 -0
  156. package/dist/types/services/WebexCrossClientService.d.ts +32 -0
  157. package/dist/types/services/WxAppTelephonyMercurySync.d.ts +28 -0
  158. package/dist/types/services/config/Util.d.ts +4 -8
  159. package/dist/types/services/config/constants.d.ts +49 -76
  160. package/dist/types/services/config/index.d.ts +16 -64
  161. package/dist/types/services/config/types.d.ts +240 -114
  162. package/dist/types/services/constants.d.ts +9 -0
  163. package/dist/types/services/core/Err.d.ts +2 -0
  164. package/dist/types/services/core/Utils.d.ts +43 -13
  165. package/dist/types/services/core/WebexRequest.d.ts +1 -0
  166. package/dist/types/services/core/constants.d.ts +7 -0
  167. package/dist/types/services/core/types.d.ts +2 -0
  168. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
  169. package/dist/types/services/core/websocket/types.d.ts +1 -1
  170. package/dist/types/services/index.d.ts +1 -1
  171. package/dist/types/services/task/Task.d.ts +186 -0
  172. package/dist/types/services/task/TaskFactory.d.ts +14 -0
  173. package/dist/types/services/task/TaskUtils.d.ts +54 -2
  174. package/dist/types/services/task/WebexCallingUtils.d.ts +11 -0
  175. package/dist/types/services/task/constants.d.ts +17 -0
  176. package/dist/types/services/task/contact.d.ts +4 -0
  177. package/dist/types/services/task/digital/Digital.d.ts +23 -0
  178. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1442 -0
  179. package/dist/types/services/task/state-machine/actions.d.ts +10 -0
  180. package/dist/types/services/task/state-machine/constants.d.ts +107 -0
  181. package/dist/types/services/task/state-machine/guards.d.ts +103 -0
  182. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  183. package/dist/types/services/task/state-machine/types.d.ts +277 -0
  184. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  185. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  186. package/dist/types/services/task/types.d.ts +738 -66
  187. package/dist/types/services/task/voice/Voice.d.ts +241 -0
  188. package/dist/types/services/task/voice/WebRTC.d.ts +54 -0
  189. package/dist/types/services/task/voice/wxAppOfferObservability.d.ts +39 -0
  190. package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +68 -0
  191. package/dist/types/services/wxAppDiagnosticLogging.d.ts +63 -0
  192. package/dist/types/services/wxAppTelephonyUtils.d.ts +5 -0
  193. package/dist/types/types.d.ts +213 -11
  194. package/dist/types/utils/PageCache.d.ts +20 -3
  195. package/dist/types/webex.d.ts +1 -0
  196. package/dist/types.js +168 -6
  197. package/dist/types.js.map +1 -1
  198. package/dist/utils/PageCache.js +19 -5
  199. package/dist/utils/PageCache.js.map +1 -1
  200. package/dist/webex.js +22 -2
  201. package/dist/webex.js.map +1 -1
  202. package/package.json +16 -12
  203. package/src/cc.ts +1137 -67
  204. package/src/config.ts +13 -0
  205. package/src/constants.ts +39 -1
  206. package/src/index.ts +62 -15
  207. package/src/metrics/MetricsManager.ts +16 -0
  208. package/src/metrics/ai-docs/AGENTS.md +350 -0
  209. package/src/metrics/ai-docs/ARCHITECTURE.md +338 -0
  210. package/src/metrics/ai-docs/metrics-spec.md +971 -0
  211. package/src/metrics/behavioral-events.ts +190 -0
  212. package/src/metrics/constants.ts +59 -4
  213. package/src/services/AddressBook.ts +17 -6
  214. package/src/services/AnswerCallOnWebexService.ts +239 -0
  215. package/src/services/ApiAiAssistant.ts +494 -15
  216. package/src/services/EntryPoint.ts +59 -60
  217. package/src/services/Queue.ts +29 -12
  218. package/src/services/UserPreference.ts +509 -0
  219. package/src/services/WebCallingService.ts +16 -2
  220. package/src/services/WebexCrossClientService.ts +298 -0
  221. package/src/services/WxAppTelephonyMercurySync.ts +142 -0
  222. package/src/services/agent/ai-docs/AGENTS.md +240 -0
  223. package/src/services/agent/ai-docs/ARCHITECTURE.md +304 -0
  224. package/src/services/agent/ai-docs/agent-spec.md +505 -0
  225. package/src/services/ai-docs/AGENTS.md +386 -0
  226. package/src/services/ai-docs/services-spec.md +512 -0
  227. package/src/services/config/Util.ts +54 -84
  228. package/src/services/config/ai-docs/AGENTS.md +250 -0
  229. package/src/services/config/ai-docs/ARCHITECTURE.md +413 -0
  230. package/src/services/config/ai-docs/config-spec.md +669 -0
  231. package/src/services/config/constants.ts +63 -85
  232. package/src/services/config/index.ts +116 -277
  233. package/src/services/config/types.ts +229 -125
  234. package/src/services/constants.ts +10 -0
  235. package/src/services/core/Err.ts +1 -0
  236. package/src/services/core/Utils.ts +151 -37
  237. package/src/services/core/WebexRequest.ts +3 -1
  238. package/src/services/core/ai-docs/AGENTS.md +381 -0
  239. package/src/services/core/ai-docs/ARCHITECTURE.md +698 -0
  240. package/src/services/core/ai-docs/core-spec.md +797 -0
  241. package/src/services/core/aqm-reqs.ts +18 -12
  242. package/src/services/core/constants.ts +8 -0
  243. package/src/services/core/types.ts +33 -1
  244. package/src/services/core/websocket/WebSocketManager.ts +28 -2
  245. package/src/services/core/websocket/types.ts +1 -1
  246. package/src/services/index.ts +1 -1
  247. package/src/services/task/Task.ts +1211 -0
  248. package/src/services/task/TaskFactory.ts +82 -0
  249. package/src/services/task/TaskManager.ts +1585 -724
  250. package/src/services/task/TaskUtils.ts +316 -25
  251. package/src/services/task/WebexCallingUtils.ts +136 -0
  252. package/src/services/task/ai-docs/AGENTS.md +457 -0
  253. package/src/services/task/ai-docs/ARCHITECTURE.md +595 -0
  254. package/src/services/task/ai-docs/task-spec.md +1501 -0
  255. package/src/services/task/constants.ts +18 -0
  256. package/src/services/task/contact.ts +30 -0
  257. package/src/services/task/digital/Digital.ts +102 -0
  258. package/src/services/task/state-machine/TaskStateMachine.ts +1320 -0
  259. package/src/services/task/state-machine/actions.ts +741 -0
  260. package/src/services/task/state-machine/ai-docs/AGENTS.md +460 -0
  261. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1156 -0
  262. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2247 -0
  263. package/src/services/task/state-machine/constants.ts +172 -0
  264. package/src/services/task/state-machine/guards.ts +442 -0
  265. package/src/services/task/state-machine/index.ts +28 -0
  266. package/src/services/task/state-machine/types.ts +258 -0
  267. package/src/services/task/state-machine/uiControlsComputer.ts +1135 -0
  268. package/src/services/task/taskDataNormalizer.ts +137 -0
  269. package/src/services/task/types.ts +918 -70
  270. package/src/services/task/voice/Voice.ts +1878 -0
  271. package/src/services/task/voice/WebRTC.ts +194 -0
  272. package/src/services/task/voice/wxAppOfferObservability.ts +204 -0
  273. package/src/services/task/voice/wxAppVoiceMethods.ts +575 -0
  274. package/src/services/wxAppDiagnosticLogging.ts +286 -0
  275. package/src/services/wxAppTelephonyUtils.ts +14 -0
  276. package/src/types.ts +272 -13
  277. package/src/utils/AGENTS.md +289 -0
  278. package/src/utils/PageCache.ts +38 -5
  279. package/src/utils/ai-docs/utils-spec.md +391 -0
  280. package/src/webex.js +10 -0
  281. package/test/unit/fixtures/aiSummaryTestUtils.ts +40 -0
  282. package/test/unit/spec/cc.ts +3553 -107
  283. package/test/unit/spec/index.ts +885 -0
  284. package/test/unit/spec/logger-proxy.ts +70 -0
  285. package/test/unit/spec/metrics/MetricsManager.ts +25 -0
  286. package/test/unit/spec/metrics/behavioral-events.ts +60 -0
  287. package/test/unit/spec/services/AddressBook.ts +37 -6
  288. package/test/unit/spec/services/AnswerCallOnWebexService.ts +264 -0
  289. package/test/unit/spec/services/ApiAiAssistant.ts +442 -23
  290. package/test/unit/spec/services/EntryPoint.ts +87 -40
  291. package/test/unit/spec/services/Queue.ts +123 -12
  292. package/test/unit/spec/services/UserPreference.ts +401 -0
  293. package/test/unit/spec/services/WebCallingService.ts +45 -1
  294. package/test/unit/spec/services/WebexCrossClientService.ts +509 -0
  295. package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +136 -0
  296. package/test/unit/spec/services/config/Util.ts +501 -0
  297. package/test/unit/spec/services/config/index.ts +317 -547
  298. package/test/unit/spec/services/core/Utils.ts +447 -8
  299. package/test/unit/spec/services/core/WebexRequest.ts +8 -7
  300. package/test/unit/spec/services/core/aqm-reqs.ts +142 -1
  301. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +129 -53
  302. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  303. package/test/unit/spec/services/task/Task.ts +2723 -0
  304. package/test/unit/spec/services/task/TaskFactory.ts +116 -0
  305. package/test/unit/spec/services/task/TaskManager.ts +4734 -1769
  306. package/test/unit/spec/services/task/TaskUtils.ts +307 -0
  307. package/test/unit/spec/services/task/WebexCallingUtils.ts +153 -0
  308. package/test/unit/spec/services/task/contact.ts +33 -0
  309. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  310. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +4060 -0
  311. package/test/unit/spec/services/task/state-machine/guards.ts +839 -0
  312. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  313. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +3101 -0
  314. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  315. package/test/unit/spec/services/task/voice/Voice.ts +2355 -0
  316. package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
  317. package/test/unit/spec/services/task/voice/wxAppOfferObservability.ts +361 -0
  318. package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +719 -0
  319. package/test/unit/spec/services/wxAppDiagnosticLogging.ts +179 -0
  320. package/test/unit/spec/wellnessSampleUtils.ts +288 -0
  321. package/typedoc.md +1 -1
  322. package/umd/contact-center.min.js +2 -2
  323. package/umd/contact-center.min.js.map +1 -1
  324. package/dist/services/task/index.js +0 -1530
  325. package/dist/services/task/index.js.map +0 -1
  326. package/dist/types/services/task/index.d.ts +0 -650
  327. package/src/services/task/index.ts +0 -1806
  328. package/test/unit/spec/services/task/index.ts +0 -2205
@@ -12,11 +12,20 @@ var _constants2 = require("./constants");
12
12
  var _types2 = require("../config/types");
13
13
  var _types3 = require("../../types");
14
14
  var _loggerProxy = _interopRequireDefault(require("../../logger-proxy"));
15
- var _ = _interopRequireDefault(require("."));
16
- var _MetricsManager = _interopRequireDefault(require("../../metrics/MetricsManager"));
17
- var _constants3 = require("../../metrics/constants");
18
15
  var _TaskUtils = require("./TaskUtils");
16
+ var _TaskFactory = _interopRequireDefault(require("./TaskFactory"));
17
+ var _WebexCallingUtils = require("./WebexCallingUtils");
18
+ var _WebRTC = _interopRequireDefault(require("./voice/WebRTC"));
19
+ var _stateMachine = require("./state-machine");
20
+ var _constants3 = require("./state-machine/constants");
21
+ var _taskDataNormalizer = require("./taskDataNormalizer");
19
22
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
23
+ const CC_EVENT_SET = new Set(Object.values(_types2.CC_EVENTS));
24
+ const MAIN_INTERACTION_CORRELATED_EVENTS = new Set([_types2.CC_EVENTS.AGENT_CONSULT_ENDED, _types2.CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE]);
25
+ const isCcEvent = value => CC_EVENT_SET.has(value);
26
+ const WRAP_UP_STAMP_EVENTS = new Set([_types2.CC_EVENTS.AGENT_WRAPUP, _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCE_ENDED, _types2.CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE, _types2.CC_EVENTS.AGENT_CONFERENCE_TRANSFERRED]);
27
+ const PARTICIPANT_LEAVE_WRAP_STATES = new Set([_stateMachine.TaskState.HELD, _stateMachine.TaskState.RESUME_INITIATING, _stateMachine.TaskState.CONSULTING, _stateMachine.TaskState.CONSULT_INITIATING, _stateMachine.TaskState.CONFERENCING]);
28
+
20
29
  /** @internal */
21
30
  class TaskManager extends _events.default {
22
31
  /**
@@ -25,46 +34,79 @@ class TaskManager extends _events.default {
25
34
  * @private
26
35
  */
27
36
 
37
+ // eslint-disable-next-line no-use-before-define
38
+
39
+ receivingSummaryBuffer = new Map();
40
+
41
+ // RTD feature frames can arrive before AQM creates the task.
42
+ pendingFeatureEnablement = new Map();
43
+
28
44
  /**
29
45
  * @param contact - Routing Contact layer. Talks to AQMReq layer to convert events to promises
30
46
  * @param webCallingService - Webrtc Service Layer
31
47
  * @param webSocketManager - Websocket Manager to maintain websocket connection and keepalives
32
48
  */
33
- constructor(apiAIAssistant, contact, webCallingService, webSocketManager) {
49
+ constructor(apiAIAssistant, contact, webCallingService, webSocketManager, rtdWebSocketManager) {
34
50
  super();
35
51
  this.apiAIAssistant = apiAIAssistant;
36
52
  this.contact = contact;
37
- this.taskCollection = {};
38
53
  this.webCallingService = webCallingService;
39
54
  this.webSocketManager = webSocketManager;
40
- this.metricsManager = _MetricsManager.default.getInstance();
55
+ this.rtdWebSocketManager = rtdWebSocketManager;
56
+ this.taskCollection = {};
57
+ this.webRtcEnabled = false;
41
58
  this.registerTaskListeners();
42
59
  this.registerIncomingCallEvent();
43
60
  }
44
- setWrapupData(wrapupData) {
45
- this.wrapupData = wrapupData;
46
- }
47
- setAgentId(agentId) {
48
- this.agentId = agentId;
49
- }
50
-
51
- /**
52
- * Gets the current agent ID
53
- * @returns {string} The agent ID set for this task manager instance
54
- * @public
55
- */
56
- getAgentId() {
57
- return this.agentId;
58
- }
59
- setWebRtcEnabled(webRtcEnabled) {
60
- this.webRtcEnabled = webRtcEnabled;
61
- }
62
61
  handleRealtimeWebsocketEvent(event) {
63
62
  try {
64
63
  const payload = JSON.parse(event);
65
- const eventType = payload?.type || payload?.data?.notifType;
64
+ switch (payload?.type) {
65
+ case _types2.CC_TASK_EVENTS.FEATURE_ENABLEMENT:
66
+ {
67
+ const enablement = payload?.data?.data;
68
+ if (!enablement?.interactionId) return;
69
+ const task = this.taskCollection[enablement.interactionId];
70
+ if (task) {
71
+ this.pendingFeatureEnablement.delete(enablement.interactionId);
72
+ task.setFeatureEnablement?.(enablement);
73
+ task.emit(_types.TASK_EVENTS.TASK_FEATURE_ENABLEMENT, enablement);
74
+ } else {
75
+ this.pendingFeatureEnablement.set(enablement.interactionId, enablement);
76
+ }
77
+ return;
78
+ }
79
+ case _types2.CC_TASK_EVENTS.POST_CALL_SUMMARY:
80
+ case _types2.CC_TASK_EVENTS.MID_CALL_SUMMARY:
81
+ {
82
+ if (!payload?.data?.data?.conversationId) return;
83
+ this.apiAIAssistant?.resolveFromRtdEvent(payload.type, payload.data.data.conversationId, payload.data.data);
84
+ return;
85
+ }
86
+ case _types2.CC_TASK_EVENTS.MID_CALL_SUMMARY_RESPONSE_SUBSEQUENT_AGENT:
87
+ {
88
+ if (!payload?.data?.data?.conversationId) return;
89
+ const summary = payload.data.data;
90
+ const {
91
+ conversationId
92
+ } = summary;
93
+ const task = this.taskCollection[conversationId];
94
+ const taskState = task?.state?.value;
95
+
96
+ // Emit only once the task is answered; until then it waits for the answer event.
97
+ if (task && taskState !== _stateMachine.TaskState.IDLE && taskState !== _stateMachine.TaskState.OFFERED) {
98
+ this.receivingSummaryBuffer.delete(conversationId);
99
+ task.emit(_types.TASK_EVENTS.TASK_MID_CALL_SUMMARY_RECEIVED, summary);
100
+ } else {
101
+ this.receivingSummaryBuffer.set(conversationId, summary);
102
+ }
103
+ return;
104
+ }
105
+ default:
106
+ break;
107
+ }
66
108
  const interactionId = payload?.data?.data?.conversationId;
67
- if (!eventType || !interactionId) return;
109
+ if (!interactionId) return;
68
110
  const task = this.taskCollection[interactionId];
69
111
  if (!task) {
70
112
  _loggerProxy.default.info(`Realtime transcription task not found`, {
@@ -74,7 +116,14 @@ class TaskManager extends _events.default {
74
116
  });
75
117
  return;
76
118
  }
77
- task.emit(eventType, payload.data);
119
+ switch (payload.type) {
120
+ case _types2.CC_EVENTS.REAL_TIME_TRANSCRIPTION:
121
+ case _types2.CC_EVENTS.SUGGESTED_RESPONSE:
122
+ task.emit(payload.type, payload.data);
123
+ break;
124
+ default:
125
+ break;
126
+ }
78
127
  } catch (error) {
79
128
  _loggerProxy.default.error('Failed to parse RTD WebSocket message', {
80
129
  module: _constants.TASK_MANAGER_FILE,
@@ -83,6 +132,51 @@ class TaskManager extends _events.default {
83
132
  });
84
133
  }
85
134
  }
135
+
136
+ /**
137
+ * Set config flags for task creation
138
+ */
139
+ setConfigFlags(configFlags) {
140
+ this.configFlags = configFlags;
141
+ }
142
+ clearAISummaryState() {
143
+ this.apiAIAssistant?.clearAllRtdRequests();
144
+ Object.values(this.taskCollection).forEach(task => task.clearFeatureEnablement?.());
145
+ this.receivingSummaryBuffer.clear();
146
+ this.pendingFeatureEnablement.clear();
147
+ }
148
+
149
+ /**
150
+ * Set wrapup configuration data
151
+ */
152
+ setWrapupData(wrapupData) {
153
+ this.wrapupData = wrapupData;
154
+ }
155
+
156
+ /**
157
+ * Set agent ID for task operations
158
+ */
159
+ setAgentId(agentId) {
160
+ this.agentId = agentId;
161
+ }
162
+ setAgentName(agentName) {
163
+ this.agentName = agentName;
164
+ }
165
+
166
+ /**
167
+ * Gets the current agent ID
168
+ * @returns {string} The agent ID set for this task manager instance
169
+ * @public
170
+ */
171
+ getAgentId() {
172
+ return this.agentId;
173
+ }
174
+ setWebRtcEnabled(webRtcEnabled) {
175
+ this.webRtcEnabled = webRtcEnabled;
176
+ }
177
+ setAnswerCallOnWebexService(answerCallOnWebexService) {
178
+ this.answerCallOnWebexService = answerCallOnWebexService;
179
+ }
86
180
  handleIncomingWebCall = call => {
87
181
  const currentTask = Object.values(this.taskCollection).find(task => task.data.interaction.mediaType === 'telephony' && !(0, _TaskUtils.isCampaignPreviewReservation)(task));
88
182
  if (currentTask) {
@@ -92,7 +186,12 @@ class TaskManager extends _events.default {
92
186
  method: _constants2.METHODS.HANDLE_INCOMING_WEB_CALL,
93
187
  interactionId: currentTask.data.interactionId
94
188
  });
95
- this.emit(_types.TASK_EVENTS.TASK_INCOMING, currentTask);
189
+
190
+ // Send TASK_INCOMING to state machine - it will emit on the task object
191
+ const eventPayload = TaskManager.mapEventToTaskStateMachineEvent(_types2.CC_EVENTS.AGENT_CONTACT_RESERVED, currentTask.data);
192
+ if (eventPayload && currentTask) {
193
+ currentTask.sendStateMachineEvent(eventPayload);
194
+ }
96
195
  }
97
196
  this.call = call;
98
197
  };
@@ -102,552 +201,1058 @@ class TaskManager extends _events.default {
102
201
  unregisterIncomingCallEvent() {
103
202
  this.webCallingService.off(_calling.LINE_EVENTS.INCOMING_CALL, this.handleIncomingWebCall);
104
203
  }
105
- registerTaskListeners() {
106
- this.webSocketManager.on('message', event => {
107
- const payload = JSON.parse(event);
108
- // Re-emit the task events to the task object
109
- let task;
110
- if (payload.data?.type || payload.type) {
111
- if (Object.values(_types2.CC_TASK_EVENTS).includes(payload.data.type || payload.type)) {
112
- task = this.taskCollection[payload.data?.interactionId] || this.taskCollection[payload.data?.data?.conversationId];
204
+
205
+ /**
206
+ * True when this agent left on the current payload.
207
+ * A named other participantId is never self-leave (even if the participants
208
+ * map is partial). Missing-map fallback is EP-DN only: participantId absent
209
+ * and self omitted now, with previous-task evidence when a prior map exists.
210
+ */
211
+ static didSelfLeaveConference(payload, agentId, task) {
212
+ if (!agentId) {
213
+ return false;
214
+ }
215
+ if (payload.participantId === agentId) {
216
+ return true;
217
+ }
218
+ if (payload.participantId && payload.participantId !== agentId) {
219
+ return false;
220
+ }
221
+ const participants = payload.interaction?.participants;
222
+ if (!participants || agentId in participants) {
223
+ return false;
224
+ }
225
+ const previousParticipants = task?.data?.interaction?.participants;
226
+ if (previousParticipants && !(agentId in previousParticipants)) {
227
+ return false;
228
+ }
229
+ return true;
230
+ }
231
+
232
+ /**
233
+ * Consulted non-owner: Voice exitConference / shouldWrapUpForThisAgent stays
234
+ * false unless an explicit wrap-up signal includes this agent.
235
+ */
236
+ static isConsultedNonOwner(payload, agentId, task) {
237
+ const isConsulted = payload.isConsulted ?? task?.data?.isConsulted;
238
+ if (isConsulted !== true) {
239
+ return false;
240
+ }
241
+ const owner = payload.interaction?.owner ?? task?.data?.interaction?.owner;
242
+ return Boolean(agentId && owner && owner !== agentId);
243
+ }
244
+ static hasNonemptyPendingWrapUp(payload) {
245
+ return Array.isArray(payload.agentsPendingWrapUp) && payload.agentsPendingWrapUp.length > 0;
246
+ }
247
+
248
+ /**
249
+ * Transfer initiator fallback: widgets set transferConferenceRequested on the
250
+ * actor; desktop success payloads may include consultingAgentId. Only used
251
+ * when agentsPendingWrapUp is empty or absent.
252
+ */
253
+ static isSelfConferenceTransferInitiator(payload, agentId, task) {
254
+ const snapshotContext = task?.stateMachineService?.getSnapshot?.()?.context;
255
+ if (snapshotContext?.transferConferenceRequested === true) {
256
+ return true;
257
+ }
258
+ return Boolean(agentId && payload.consultingAgentId === agentId);
259
+ }
260
+
261
+ /**
262
+ * Explicit wrap-up signals only (no owner / isConsulted fallback).
263
+ * Used for events that remaining conference agents also receive.
264
+ * nonempty agentsPendingWrapUp is authoritative, then wrapUpRequired / isWrapUp.
265
+ */
266
+ static wrapUpRequiredFromExplicitSignals(payload, agentId) {
267
+ const pending = payload.agentsPendingWrapUp;
268
+ if (Array.isArray(pending) && pending.length > 0) {
269
+ return Boolean(agentId && pending.includes(agentId));
270
+ }
271
+ if (payload.wrapUpRequired === true) {
272
+ return true;
273
+ }
274
+ if (!agentId) {
275
+ return false;
276
+ }
277
+ return payload.interaction?.participants?.[agentId]?.isWrapUp === true;
278
+ }
279
+
280
+ /**
281
+ * Stamp wrapUpRequired once at ingress (prepareEventContext) so updateTaskData
282
+ * and the mapper see the same flag. Remaining agents stay false.
283
+ */
284
+ static stampWrapUpRequiredForEvent(eventType, payload, agentId, task) {
285
+ if (!WRAP_UP_STAMP_EVENTS.has(eventType)) {
286
+ return payload;
287
+ }
288
+ if (eventType === _types2.CC_EVENTS.AGENT_WRAPUP) {
289
+ return {
290
+ ...payload,
291
+ wrapUpRequired: true
292
+ };
293
+ }
294
+ if (eventType === _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCE_ENDED) {
295
+ return {
296
+ ...payload,
297
+ wrapUpRequired: TaskManager.wrapUpRequiredFromExplicitSignals(payload, agentId)
298
+ };
299
+ }
300
+ if (eventType === _types2.CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE) {
301
+ const actorState = task?.stateMachineService?.getSnapshot?.()?.value;
302
+ if (actorState && !PARTICIPANT_LEAVE_WRAP_STATES.has(actorState)) {
303
+ return payload;
304
+ }
305
+ const left = TaskManager.didSelfLeaveConference(payload, agentId, task);
306
+ if (!left) {
307
+ return {
308
+ ...payload,
309
+ wrapUpRequired: TaskManager.wrapUpRequiredFromExplicitSignals(payload, agentId)
310
+ };
311
+ }
312
+ if (TaskManager.hasNonemptyPendingWrapUp(payload)) {
313
+ return {
314
+ ...payload,
315
+ wrapUpRequired: Boolean(agentId && payload.agentsPendingWrapUp?.includes(agentId))
316
+ };
317
+ }
318
+ return {
319
+ ...payload,
320
+ wrapUpRequired: TaskManager.wrapUpRequiredFromExplicitSignals(payload, agentId) || !TaskManager.isConsultedNonOwner(payload, agentId, task)
321
+ };
322
+ }
323
+
324
+ // AGENT_CONFERENCE_TRANSFERRED — pending list wins; initiator only if empty/absent
325
+ if (TaskManager.hasNonemptyPendingWrapUp(payload)) {
326
+ return {
327
+ ...payload,
328
+ wrapUpRequired: TaskManager.wrapUpRequiredFromExplicitSignals(payload, agentId)
329
+ };
330
+ }
331
+ return {
332
+ ...payload,
333
+ wrapUpRequired: TaskManager.wrapUpRequiredFromExplicitSignals(payload, agentId) || TaskManager.isSelfConferenceTransferInitiator(payload, agentId, task)
334
+ };
335
+ }
336
+
337
+ /**
338
+ * Map WebSocket CC_EVENTS to state machine TaskEvent
339
+ * @param ccEvent - The CC_EVENT type from WebSocket
340
+ * @param payload - The event payload
341
+ * @param agentId - Optional agent ID for state detection (needed for HYDRATE)
342
+ * @param task - Optional task instance for state-aware CONTACT_ENDED mapping
343
+ * @returns TaskEventPayload for state machine or null if no mapping
344
+ */
345
+ static mapEventToTaskStateMachineEvent(ccEvent, payload, agentId, task, enableWxBetterTogether) {
346
+ const mediaResourceId = payload.mediaResourceId || payload.interaction?.media?.[payload.interactionId]?.mediaResourceId;
347
+ switch (ccEvent) {
348
+ // CC -> TaskEvent mappings (see TaskStateMachine comment for quick reference)
349
+ case _types2.CC_EVENTS.AGENT_CONTACT_RESERVED:
350
+ // AgentContactReserved -> TASK_INCOMING
351
+ if ((0, _TaskUtils.isCampaignPreviewTask)(payload)) {
352
+ return {
353
+ type: _stateMachine.TaskEvent.TASK_INCOMING,
354
+ taskData: payload,
355
+ isCampaignReservationAccept: true
356
+ };
113
357
  }
114
- _loggerProxy.default.info(`Handling task event ${payload.data?.type}`, {
115
- module: _constants.TASK_MANAGER_FILE,
116
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
117
- interactionId: payload.data?.interactionId
118
- });
119
- switch (payload.data.type) {
120
- case _types2.CC_EVENTS.AGENT_CONTACT:
121
- // Case1 : Task is already present in taskCollection
122
- if (this.taskCollection[payload.data.interactionId]) {
123
- _loggerProxy.default.log(`Got AGENT_CONTACT: Task already exists in collection`, {
124
- module: _constants.TASK_MANAGER_FILE,
125
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
126
- interactionId: payload.data.interactionId
127
- });
128
- break;
129
- } else if (!this.taskCollection[payload.data.interactionId]) {
130
- // Case2 : Task is not present in taskCollection
131
- _loggerProxy.default.log(`Got AGENT_CONTACT : Creating new task in taskManager`, {
132
- module: _constants.TASK_MANAGER_FILE,
133
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
134
- interactionId: payload.data.interactionId
135
- });
136
-
137
- // Check if auto-answer should happen for this task
138
- const shouldAutoAnswer = (0, _TaskUtils.shouldAutoAnswerTask)(payload.data, this.agentId, this.webCallingService.loginOption, this.webRtcEnabled);
139
- task = new _.default(this.contact, this.webCallingService, {
140
- ...payload.data,
141
- wrapUpRequired: payload.data.interaction?.participants?.[this.agentId]?.isWrapUp || false,
142
- isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload.data),
143
- isAutoAnswering: shouldAutoAnswer // Set flag before emitting
144
- }, this.wrapupData, this.agentId);
145
- this.taskCollection[payload.data.interactionId] = task;
146
- // Condition 1: The state is=new i.e it is a incoming task
147
- if (payload.data.interaction.state === 'new') {
148
- _loggerProxy.default.log(`Got AGENT_CONTACT for a task with state=new, sending TASK_INCOMING event`, {
149
- module: _constants.TASK_MANAGER_FILE,
150
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
151
- interactionId: payload.data.interactionId
152
- });
153
- this.emit(_types.TASK_EVENTS.TASK_INCOMING, task);
154
- } else {
155
- // Condition 2: The state is anything else i.e the task was connected
156
- _loggerProxy.default.log(`Got AGENT_CONTACT for a task with state=${payload.data.interaction.state}, sending TASK_HYDRATE event`, {
157
- module: _constants.TASK_MANAGER_FILE,
158
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
159
- interactionId: payload.data.interactionId
160
- });
161
- this.emit(_types.TASK_EVENTS.TASK_HYDRATE, task);
162
- }
163
- }
164
- break;
165
- case _types2.CC_EVENTS.AGENT_CONTACT_RESERVED:
166
- {
167
- // Check if auto-answer should happen for this task
168
- const shouldAutoAnswerReserved = (0, _TaskUtils.shouldAutoAnswerTask)(payload.data, this.agentId, this.webCallingService.loginOption, this.webRtcEnabled);
169
- task = new _.default(this.contact, this.webCallingService, {
170
- ...payload.data,
171
- isConsulted: false,
172
- isAutoAnswering: shouldAutoAnswerReserved // Set flag before emitting
173
- }, this.wrapupData, this.agentId);
174
- this.taskCollection[payload.data.interactionId] = task;
175
- if (this.webCallingService.loginOption !== _types3.LoginOption.BROWSER || task.data.interaction.mediaType !== _types.MEDIA_CHANNEL.TELEPHONY // for digital channels
176
- ) {
177
- this.emit(_types.TASK_EVENTS.TASK_INCOMING, task);
178
- } else if (this.call) {
179
- this.emit(_types.TASK_EVENTS.TASK_INCOMING, task);
180
- }
181
- break;
182
- }
183
- case _types2.CC_EVENTS.AGENT_OFFER_CONTACT:
184
- // We don't have to emit any event here since this will be result of promise.
185
- task = this.updateTaskData(task, payload.data);
186
- _loggerProxy.default.log(`Agent offer contact received for task`, {
187
- module: _constants.TASK_MANAGER_FILE,
188
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
189
- interactionId: payload.data?.interactionId
190
- });
191
- this.emit(_types.TASK_EVENTS.TASK_OFFER_CONTACT, task);
192
-
193
- // Handle auto-answer for offer contact
194
- this.handleAutoAnswer(task);
195
- break;
196
- case _types2.CC_EVENTS.AGENT_OUTBOUND_FAILED:
197
- if (task) {
198
- task = this.updateTaskData(task, payload.data);
199
- this.metricsManager.trackEvent(_constants3.METRIC_EVENT_NAMES.TASK_OUTDIAL_FAILED, {
200
- ..._MetricsManager.default.getCommonTrackingFieldForAQMResponse(payload.data),
201
- taskId: payload.data.interactionId,
202
- reason: payload.data.reasonCode || payload.data.reason
203
- }, ['behavioral', 'operational']);
204
- _loggerProxy.default.log(`Agent outbound failed for task`, {
205
- module: _constants.TASK_MANAGER_FILE,
206
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
207
- interactionId: payload.data.interactionId
208
- });
209
- task.emit(_types.TASK_EVENTS.TASK_OUTDIAL_FAILED, payload.data.reason ?? 'UNKNOWN_REASON');
210
- }
211
- break;
212
- case _types2.CC_EVENTS.AGENT_CONTACT_ASSIGNED:
213
- // When a campaign preview contact is accepted, the assigned event may arrive
214
- // with a new interactionId while the task is stored under the original
215
- // reservationInteractionId. Fall back to that key so the task is found.
216
- if (!task && payload.data.reservationInteractionId) {
217
- task = this.taskCollection[payload.data.reservationInteractionId];
218
- if (task) {
219
- // Re-key the task under the new interaction ID and remove the old entry
220
- delete this.taskCollection[payload.data.reservationInteractionId];
221
- this.taskCollection[payload.data.interactionId] = task;
222
- }
223
- }
224
- if (task) {
225
- task = this.updateTaskData(task, payload.data);
226
- task.emit(_types.TASK_EVENTS.TASK_ASSIGNED, task);
227
- }
228
- break;
229
- case _types2.CC_EVENTS.AGENT_CONTACT_UNASSIGNED:
230
- task = this.updateTaskData(task, {
231
- ...payload.data,
232
- wrapUpRequired: true
233
- });
234
- task.emit(_types.TASK_EVENTS.TASK_END, task);
235
- break;
236
- case _types2.CC_EVENTS.AGENT_CONTACT_OFFER_RONA:
237
- case _types2.CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED:
238
- case _types2.CC_EVENTS.AGENT_INVITE_FAILED:
239
- {
240
- _loggerProxy.default.info(`Task removal triggered by ${payload.data.type}`, {
241
- module: _constants.TASK_MANAGER_FILE,
242
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
243
- interactionId: payload.data.interactionId
244
- });
245
- task = this.updateTaskData(task, payload.data);
246
- const eventTypeToMetricMap = {
247
- [_types2.CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED]: 'AGENT_CONTACT_ASSIGN_FAILED',
248
- [_types2.CC_EVENTS.AGENT_INVITE_FAILED]: 'AGENT_INVITE_FAILED'
249
- };
250
- const metricEventName = eventTypeToMetricMap[payload.data.type] || 'AGENT_RONA';
251
- this.metricsManager.trackEvent(_constants3.METRIC_EVENT_NAMES[metricEventName], {
252
- ..._MetricsManager.default.getCommonTrackingFieldForAQMResponse(payload.data),
253
- taskId: payload.data.interactionId,
254
- reason: payload.data.reason
255
- }, ['behavioral', 'operational']);
256
- this.handleTaskCleanup(task);
257
- task.emit(_types.TASK_EVENTS.TASK_REJECT, payload.data.reason);
258
- break;
259
- }
260
- case _types2.CC_EVENTS.CONTACT_ENDED:
261
- // Update task data.
262
- if (task) {
263
- _loggerProxy.default.info(`Contact ended for interaction`, {
264
- module: _constants.TASK_MANAGER_FILE,
265
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
266
- interactionId: payload.data.interactionId
267
- });
268
-
269
- // Campaign preview tasks should never trigger wrapup on ContactEnded —
270
- // they are terminal cleanup events. For all other tasks, derive
271
- // wrapUpRequired from agentsPendingWrapUp as before.
272
- const CAMPAIGN_OUTBOUND_TYPES = ['STANDARD_PREVIEW_CAMPAIGN', 'DIRECT_PREVIEW_CAMPAIGN'];
273
- const isCampaignPreview = CAMPAIGN_OUTBOUND_TYPES.includes(task.data?.interaction?.outboundType ?? '');
274
- const wrapUpRequired = isCampaignPreview ? false : payload.data.agentsPendingWrapUp?.includes(this.agentId) || false;
275
- task = this.updateTaskData(task, {
276
- ...payload.data,
277
- wrapUpRequired
278
- });
279
-
280
- // Handle cleanup based on whether task should be deleted
281
- this.handleTaskCleanup(task);
282
- task?.emit(_types.TASK_EVENTS.TASK_END, task);
283
- }
284
- break;
285
- case _types2.CC_EVENTS.CAMPAIGN_CONTACT_UPDATED:
286
- {
287
- // CampaignContactUpdated is a non-terminal event (e.g., next contact after skip/remove).
288
- // Update the task data and emit an event so consumers can react to the updated contact.
289
- // Do NOT remove the task or emit TASK_END — cleanup is handled by CONTACT_ENDED.
290
- if (task) {
291
- // Carry forward campaign preview fields from existing task data since the updated
292
- // contact payload may not include them, and reconcileData would delete them.
293
- const existingCpd = task.data?.interaction?.callProcessingDetails;
294
- const updatedData = {
295
- ...payload.data
296
- };
297
- if (existingCpd) {
298
- const campaignFields = {
299
- ...(existingCpd.campaignPreviewAutoAction && {
300
- campaignPreviewAutoAction: existingCpd.campaignPreviewAutoAction
301
- }),
302
- ...(existingCpd.campaignPreviewOfferTimeout && {
303
- campaignPreviewOfferTimeout: existingCpd.campaignPreviewOfferTimeout
304
- }),
305
- ...(existingCpd.campaignPreviewSkipDisabled && {
306
- campaignPreviewSkipDisabled: existingCpd.campaignPreviewSkipDisabled
307
- }),
308
- ...(existingCpd.campaignPreviewRemoveDisabled && {
309
- campaignPreviewRemoveDisabled: existingCpd.campaignPreviewRemoveDisabled
310
- })
311
- };
312
- if (!updatedData.interaction) {
313
- updatedData.interaction = {};
314
- }
315
- updatedData.interaction = {
316
- ...updatedData.interaction,
317
- callProcessingDetails: {
318
- ...campaignFields,
319
- ...(updatedData.interaction.callProcessingDetails || {})
320
- }
321
- };
322
- }
323
- _loggerProxy.default.log('Campaign contact updated - carrying forward preview fields', {
324
- module: _constants.TASK_MANAGER_FILE,
325
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
326
- interactionId: payload.data.interactionId,
327
- data: {
328
- hasCpd: !!updatedData.interaction?.callProcessingDetails,
329
- autoAction: updatedData.interaction?.callProcessingDetails?.campaignPreviewAutoAction,
330
- skipDisabled: updatedData.interaction?.callProcessingDetails?.campaignPreviewSkipDisabled,
331
- removeDisabled: updatedData.interaction?.callProcessingDetails?.campaignPreviewRemoveDisabled
332
- }
333
- });
334
- task = this.updateTaskData(task, updatedData);
335
- task.emit(_types.TASK_EVENTS.TASK_CAMPAIGN_CONTACT_UPDATED, task);
336
- }
337
- break;
338
- }
339
- case _types2.CC_EVENTS.CAMPAIGN_PREVIEW_ACCEPT_FAILED:
340
- if (task) {
341
- // Failure payloads are sparse (no interaction field). Spread existing
342
- // task data first so reconcileData doesn't delete interaction/cpd.
343
- task = this.updateTaskData(task, {
344
- ...task.data,
345
- ...payload.data
346
- });
347
- task.emit(_types.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_ACCEPT_FAILED, task);
348
- }
349
- break;
350
- case _types2.CC_EVENTS.CAMPAIGN_PREVIEW_SKIP_FAILED:
351
- if (task) {
352
- task = this.updateTaskData(task, {
353
- ...task.data,
354
- ...payload.data
355
- });
356
- task.emit(_types.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_SKIP_FAILED, task);
357
- }
358
- break;
359
- case _types2.CC_EVENTS.CAMPAIGN_PREVIEW_REMOVE_FAILED:
360
- if (task) {
361
- task = this.updateTaskData(task, {
362
- ...task.data,
363
- ...payload.data
364
- });
365
- task.emit(_types.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_REMOVE_FAILED, task);
366
- }
367
- break;
368
- case _types2.CC_EVENTS.CONTACT_MERGED:
369
- task = this.handleContactMerged(task, payload.data);
370
- break;
371
- case _types2.CC_EVENTS.AGENT_CONTACT_HELD:
372
- // As soon as the main interaction is held, we need to emit TASK_HOLD
373
- task = this.updateTaskData(task, payload.data);
374
- task.emit(_types.TASK_EVENTS.TASK_HOLD, task);
375
- break;
376
- case _types2.CC_EVENTS.AGENT_CONTACT_UNHELD:
377
- // As soon as the main interaction is unheld, we need to emit TASK_RESUME
378
- task = this.updateTaskData(task, payload.data);
379
- task.emit(_types.TASK_EVENTS.TASK_RESUME, task);
380
- break;
381
- case _types2.CC_EVENTS.AGENT_VTEAM_TRANSFERRED:
382
- task = this.updateTaskData(task, {
383
- ...payload.data,
384
- wrapUpRequired: true
385
- });
386
- task.emit(_types.TASK_EVENTS.TASK_END, task);
387
- break;
388
- case _types2.CC_EVENTS.AGENT_CTQ_CANCEL_FAILED:
389
- task = this.updateTaskData(task, payload.data);
390
- task.emit(_types.TASK_EVENTS.TASK_CONSULT_QUEUE_FAILED, task);
391
- break;
392
- case _types2.CC_EVENTS.AGENT_CONSULT_CREATED:
393
- // Received when self agent initiates a consult
394
- task = this.updateTaskData(task, {
395
- ...payload.data,
396
- isConsulted: false // This ensures that the task consult status is always reset
397
- });
398
- task.emit(_types.TASK_EVENTS.TASK_CONSULT_CREATED, task);
399
- break;
400
- case _types2.CC_EVENTS.AGENT_OFFER_CONSULT:
401
- // Received when other agent sends us a consult offer
402
- task = this.updateTaskData(task, {
403
- ...payload.data,
404
- isConsulted: true // This ensures that the task is marked as us being requested for a consult
405
- });
406
- task.emit(_types.TASK_EVENTS.TASK_OFFER_CONSULT, task);
407
-
408
- // Handle auto-answer for consult offer
409
- this.handleAutoAnswer(task);
410
- break;
411
- case _types2.CC_EVENTS.AGENT_CONSULTING:
412
- // Received when agent is in an active consult state
413
- // TODO: Check if we can use backend consult state instead of isConsulted
414
- task = this.updateTaskData(task, payload.data);
415
- if (task.data.isConsulted) {
416
- // Fire only if you are the agent who received the consult request
417
- task.emit(_types.TASK_EVENTS.TASK_CONSULT_ACCEPTED, task);
418
- } else {
419
- // Fire only if you are the agent who initiated the consult
420
- task.emit(_types.TASK_EVENTS.TASK_CONSULTING, task);
421
- }
422
- break;
423
- case _types2.CC_EVENTS.AGENT_CONSULT_FAILED:
424
- // This can only be received by the agent who initiated the consult.
425
- // We need not emit any event here since this will be result of promise
426
- task = this.updateTaskData(task, payload.data);
427
- break;
428
- case _types2.CC_EVENTS.AGENT_CONSULT_ENDED:
429
- task = this.updateTaskData(task, payload.data);
430
- if (task.data.isConsulted) {
431
- // This will be the end state of the task as soon as we end the consult in case of
432
- // us being offered a consult
433
- this.removeTaskFromCollection(task);
434
- }
435
- task.emit(_types.TASK_EVENTS.TASK_CONSULT_END, task);
436
- break;
437
- case _types2.CC_EVENTS.AGENT_CTQ_CANCELLED:
438
- // This event is received when the consult using queue is cancelled using API
439
- task = this.updateTaskData(task, payload.data);
440
- task.emit(_types.TASK_EVENTS.TASK_CONSULT_QUEUE_CANCELLED, task);
441
- break;
442
- case _types2.CC_EVENTS.AGENT_WRAPUP:
443
- task = this.updateTaskData(task, {
444
- ...payload.data,
445
- wrapUpRequired: true
446
- });
447
- task.emit(_types.TASK_EVENTS.TASK_END, task);
448
- break;
449
- case _types2.CC_EVENTS.AGENT_WRAPPEDUP:
450
- task.cancelAutoWrapupTimer();
451
- this.removeTaskFromCollection(task);
452
- task.emit(_types.TASK_EVENTS.TASK_WRAPPEDUP, task);
453
- break;
454
- case _types2.CC_EVENTS.CONTACT_RECORDING_PAUSED:
455
- task = this.updateTaskData(task, payload.data);
456
- task.emit(_types.TASK_EVENTS.TASK_RECORDING_PAUSED, task);
457
- break;
458
- case _types2.CC_EVENTS.CONTACT_RECORDING_PAUSE_FAILED:
459
- task = this.updateTaskData(task, payload.data);
460
- task.emit(_types.TASK_EVENTS.TASK_RECORDING_PAUSE_FAILED, task);
461
- break;
462
- case _types2.CC_EVENTS.CONTACT_RECORDING_RESUMED:
463
- task = this.updateTaskData(task, payload.data);
464
- task.emit(_types.TASK_EVENTS.TASK_RECORDING_RESUMED, task);
465
- break;
466
- case _types2.CC_EVENTS.CONTACT_RECORDING_RESUME_FAILED:
467
- task = this.updateTaskData(task, payload.data);
468
- task.emit(_types.TASK_EVENTS.TASK_RECORDING_RESUME_FAILED, task);
469
- break;
470
- case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCING:
471
- // Conference is being established - update task state and emit establishing event
472
- task = this.updateTaskData(task, payload.data);
473
- task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_ESTABLISHING, task);
474
- break;
475
- case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCED:
476
- // Conference started successfully - update task state and emit event
477
- task = this.updateTaskData(task, payload.data);
478
- task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_STARTED, task);
479
- break;
480
- case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCE_FAILED:
481
- // Conference failed - update task state and emit failure event
482
- task = this.updateTaskData(task, payload.data);
483
- task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_FAILED, task);
484
- break;
485
- case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCE_ENDED:
486
- // Conference ended - update task state and emit event
487
- task = this.updateTaskData(task, payload.data);
488
- if (!task || (0, _TaskUtils.isPrimary)(task, this.agentId) || (0, _TaskUtils.isParticipantInMainInteraction)(task, this.agentId)) {
489
- _loggerProxy.default.log('Primary or main interaction participant leaving conference');
490
- } else {
491
- this.removeTaskFromCollection(task);
492
- }
493
- task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_ENDED, task);
494
- break;
495
- case _types2.CC_EVENTS.PARTICIPANT_JOINED_CONFERENCE:
496
- {
497
- task = this.updateTaskData(task, {
498
- ...payload.data,
499
- isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload.data)
500
- });
501
- task.emit(_types.TASK_EVENTS.TASK_PARTICIPANT_JOINED, task);
502
- break;
503
- }
504
- case _types2.CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE:
505
- {
506
- // Conference ended - update task state and emit event
507
-
508
- task = this.updateTaskData(task, {
509
- ...payload.data,
510
- isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload.data)
511
- });
512
- if ((0, _TaskUtils.checkParticipantNotInInteraction)(task, this.agentId)) {
513
- if ((0, _TaskUtils.isParticipantInMainInteraction)(task, this.agentId) || (0, _TaskUtils.isPrimary)(task, this.agentId)) {
514
- _loggerProxy.default.log('Primary or main interaction participant leaving conference');
515
- } else {
516
- this.removeTaskFromCollection(task);
517
- }
518
- }
519
- task.emit(_types.TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
520
- break;
521
- }
522
- case _types2.CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE_FAILED:
523
- // Conference exit failed - update task state and emit failure event
524
- task = this.updateTaskData(task, payload.data);
525
- task.emit(_types.TASK_EVENTS.TASK_PARTICIPANT_LEFT_FAILED, task);
526
- break;
527
- case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCE_END_FAILED:
528
- // Conference end failed - update task state with error details and emit failure event
529
- task = this.updateTaskData(task, payload.data);
530
- task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_END_FAILED, task);
531
- break;
532
- case _types2.CC_EVENTS.AGENT_CONFERENCE_TRANSFERRED:
533
- // Conference was transferred - update task state and emit transfer success event
534
- // Note: Backend should provide hasLeft and wrapUpRequired status
535
- task = this.updateTaskData(task, payload.data);
536
- task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_TRANSFERRED, task);
537
- break;
538
- case _types2.CC_EVENTS.AGENT_CONFERENCE_TRANSFER_FAILED:
539
- // Conference transfer failed - update task state with error details and emit failure event
540
- task = this.updateTaskData(task, payload.data);
541
- task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_TRANSFER_FAILED, task);
542
- break;
543
- case _types2.CC_EVENTS.PARTICIPANT_POST_CALL_ACTIVITY:
544
- // Post-call activity for participant - update task state with activity details
545
- task = this.updateTaskData(task, payload.data);
546
- task.emit(_types.TASK_EVENTS.TASK_POST_CALL_ACTIVITY, task);
547
- break;
548
- case _types2.CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION:
549
- {
550
- // Campaign preview contact offered to agent
551
- // Create a task in the collection so subsequent events (e.g. AGENT_CONTACT_ASSIGNED
552
- // after acceptPreviewContact) can find and update it.
553
- // Emit TASK_CAMPAIGN_PREVIEW_RESERVATION instead of TASK_INCOMING so the call
554
- // does not ring out to the customer before the agent explicitly accepts the preview contact.
555
- _loggerProxy.default.log('Campaign preview reservation received', {
556
- module: _constants.TASK_MANAGER_FILE,
557
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
558
- interactionId: payload.data.interactionId
559
- });
560
- if (!this.taskCollection[payload.data.interactionId]) {
561
- task = new _.default(this.contact, this.webCallingService, {
562
- ...payload.data,
563
- wrapUpRequired: false,
564
- isConferenceInProgress: false,
565
- isAutoAnswering: false
566
- }, this.wrapupData, this.agentId);
567
- this.taskCollection[payload.data.interactionId] = task;
568
- } else {
569
- task = this.updateTaskData(task, payload.data);
358
+ return {
359
+ type: _stateMachine.TaskEvent.TASK_INCOMING,
360
+ taskData: payload
361
+ };
362
+ case _types2.CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION:
363
+ // -> TASK_INCOMING (campaign branch via guard)
364
+ return {
365
+ type: _stateMachine.TaskEvent.TASK_INCOMING,
366
+ taskData: payload,
367
+ isCampaignReservationAccept: true
368
+ };
369
+ case _types2.CC_EVENTS.AGENT_OFFER_CONTACT:
370
+ // AgentOfferContact -> TASK_OFFERED
371
+ return {
372
+ type: _stateMachine.TaskEvent.TASK_OFFERED,
373
+ taskData: payload
374
+ };
375
+ case _types2.CC_EVENTS.AGENT_CONTACT:
376
+ // AgentContact -> HYDRATE
377
+ // Include agentId for state detection (e.g., checking isWrapUp in participant data)
378
+ return {
379
+ type: _stateMachine.TaskEvent.HYDRATE,
380
+ taskData: payload,
381
+ agentId
382
+ };
383
+ case _types2.CC_EVENTS.CONTACT_UPDATED:
384
+ if (task && typeof payload.interaction?.owner === 'string' && payload.interaction.owner.trim().length > 0 && payload.interaction.owner !== task.data?.interaction?.owner) {
385
+ return {
386
+ type: _stateMachine.TaskEvent.CONTACT_OWNER_CHANGED,
387
+ taskData: payload
388
+ };
389
+ }
390
+ return {
391
+ type: _stateMachine.TaskEvent.CONTACT_UPDATED,
392
+ taskData: payload
393
+ };
394
+ case _types2.CC_EVENTS.CONTACT_OWNER_CHANGED:
395
+ return {
396
+ type: _stateMachine.TaskEvent.CONTACT_OWNER_CHANGED,
397
+ taskData: payload
398
+ };
399
+ case _types2.CC_EVENTS.AGENT_OFFER_CONSULT:
400
+ // AgentOfferConsult -> OFFER_CONSULT
401
+ return {
402
+ type: _stateMachine.TaskEvent.OFFER_CONSULT,
403
+ taskData: {
404
+ ...payload,
405
+ isConsulted: true
406
+ }
407
+ };
408
+ case _types2.CC_EVENTS.AGENT_CONTACT_ASSIGNED:
409
+ // AgentContactAssigned -> ASSIGN
410
+ return {
411
+ type: _stateMachine.TaskEvent.ASSIGN,
412
+ taskData: payload
413
+ };
414
+ case _types2.CC_EVENTS.AGENT_CONTACT_HELD:
415
+ return {
416
+ type: _stateMachine.TaskEvent.HOLD_SUCCESS,
417
+ mediaResourceId: mediaResourceId || '',
418
+ taskData: payload
419
+ };
420
+ case _types2.CC_EVENTS.AGENT_CONTACT_UNHELD:
421
+ return {
422
+ type: _stateMachine.TaskEvent.UNHOLD_SUCCESS,
423
+ mediaResourceId: mediaResourceId || '',
424
+ taskData: payload
425
+ };
426
+ case _types2.CC_EVENTS.AGENT_CONSULT_CREATED:
427
+ return {
428
+ type: _stateMachine.TaskEvent.CONSULT_CREATED,
429
+ taskData: {
430
+ ...payload,
431
+ isConsulted: false
432
+ }
433
+ };
434
+ case _types2.CC_EVENTS.AGENT_CONSULTING:
435
+ // AgentConsulting -> CONSULTING_ACTIVE
436
+ // use context to figure out if it's the initiator or receiver using consultInitiator from context
437
+ return {
438
+ type: _stateMachine.TaskEvent.CONSULTING_ACTIVE,
439
+ consultDestinationAgentJoined: true,
440
+ taskData: payload
441
+ };
442
+ case _types2.CC_EVENTS.AGENT_CONSULT_ENDED:
443
+ // AgentConsultEnded -> CONSULT_END
444
+ return {
445
+ type: _stateMachine.TaskEvent.CONSULT_END,
446
+ taskData: payload
447
+ };
448
+ case _types2.CC_EVENTS.AGENT_CONSULT_FAILED:
449
+ case _types2.CC_EVENTS.AGENT_CTQ_FAILED:
450
+ return {
451
+ type: _stateMachine.TaskEvent.CONSULT_FAILED,
452
+ reason: payload.reason,
453
+ taskData: payload
454
+ };
455
+ case _types2.CC_EVENTS.AGENT_CTQ_CANCELLED:
456
+ return {
457
+ type: _stateMachine.TaskEvent.CTQ_CANCEL,
458
+ taskData: payload
459
+ };
460
+ case _types2.CC_EVENTS.AGENT_CTQ_CANCEL_FAILED:
461
+ return {
462
+ type: _stateMachine.TaskEvent.CTQ_CANCEL_FAILED,
463
+ taskData: payload
464
+ };
465
+ case _types2.CC_EVENTS.AGENT_BLIND_TRANSFERRED: // AgentBlindTransferred -> TRANSFER_SUCCESS
466
+ case _types2.CC_EVENTS.AGENT_CONSULT_TRANSFERRED: // AgentConsultTransferred -> TRANSFER_SUCCESS
467
+ case _types2.CC_EVENTS.AGENT_VTEAM_TRANSFERRED:
468
+ // AgentVTeamTransferred -> TRANSFER_SUCCESS
469
+ return {
470
+ type: _stateMachine.TaskEvent.TRANSFER_SUCCESS,
471
+ taskData: payload
472
+ };
473
+ case _types2.CC_EVENTS.AGENT_WRAPUP:
474
+ return {
475
+ type: _stateMachine.TaskEvent.TASK_WRAPUP,
476
+ taskData: {
477
+ ...payload,
478
+ wrapUpRequired: true
479
+ }
480
+ };
481
+ case _types2.CC_EVENTS.AGENT_CONTACT_UNASSIGNED:
482
+ return null;
483
+ // Add WRAPUP if needed
484
+
485
+ case _types2.CC_EVENTS.AGENT_BLIND_TRANSFER_FAILED:
486
+ case _types2.CC_EVENTS.AGENT_VTEAM_TRANSFER_FAILED:
487
+ case _types2.CC_EVENTS.AGENT_CONSULT_TRANSFER_FAILED:
488
+ case _types2.CC_EVENTS.AGENT_CONFERENCE_TRANSFER_FAILED:
489
+ return {
490
+ type: _stateMachine.TaskEvent.TRANSFER_FAILED,
491
+ taskData: payload
492
+ };
493
+ case _types2.CC_EVENTS.CONTACT_ENDED:
494
+ {
495
+ const wrapUpRequired = (0, _TaskUtils.isCampaignPreviewTask)(payload) ? false : payload.agentsPendingWrapUp?.includes(agentId || '') || false;
496
+
497
+ // Post-accept wxApp outdial agent-end delivers ContactEnded instead of
498
+ // AgentOutboundFailed. Normalize to OUTBOUND_FAILED so state machine
499
+ // enters WRAPPING_UP and emits TASK_OUTDIAL_FAILED (AGENT_ENDS).
500
+ // Pre-accept cancelTask decline stays CONTACT_ENDED → TERMINATED (no wrapup).
501
+ if (TaskManager.isAgentTerminatedOutdialWrapup(payload, wrapUpRequired, task, agentId, enableWxBetterTogether)) {
502
+ return {
503
+ type: _stateMachine.TaskEvent.OUTBOUND_FAILED,
504
+ reason: 'AGENT_ENDS',
505
+ taskData: {
506
+ ...payload,
507
+ wrapUpRequired: true
570
508
  }
571
- this.emit(_types.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, task);
572
- break;
509
+ };
510
+ }
511
+ return {
512
+ type: _stateMachine.TaskEvent.CONTACT_ENDED,
513
+ taskData: {
514
+ ...payload,
515
+ wrapUpRequired
573
516
  }
574
- default:
575
- break;
576
- }
577
- if (task) {
578
- task.emit(payload.data.type, payload.data);
517
+ };
579
518
  }
580
- const transcriptInteractionId = payload.data?.interactionId || payload.data?.data?.conversationId || task?.data?.interactionId;
581
- if (_constants2.TRANSCRIPT_EVENT_MAP[payload.data.type] && transcriptInteractionId) {
582
- this.requestRealTimeTranscripts(payload.data.type, transcriptInteractionId);
519
+ case _types2.CC_EVENTS.AGENT_INVITE_FAILED:
520
+ return {
521
+ type: _stateMachine.TaskEvent.INVITE_FAILED,
522
+ reason: payload.reason
523
+ };
524
+ case _types2.CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED:
525
+ return {
526
+ type: _stateMachine.TaskEvent.ASSIGN_FAILED,
527
+ reason: payload.reason
528
+ };
529
+ case _types2.CC_EVENTS.AGENT_CONTACT_OFFER_RONA:
530
+ return {
531
+ type: _stateMachine.TaskEvent.RONA,
532
+ taskData: payload,
533
+ reason: payload.reason
534
+ };
535
+ case _types2.CC_EVENTS.AGENT_OUTBOUND_FAILED:
536
+ {
537
+ const isAgentTerminatedOutdial = payload.interaction?.outboundType === 'OUTDIAL' && (payload.terminatingParty === 'Agent' || payload.reason === 'AGENT_ENDS');
538
+ const suppressOutdialFailedPopup = isAgentTerminatedOutdial && !TaskManager.isWxAppManagedOutdialTask(agentId, payload, task, enableWxBetterTogether);
539
+ return {
540
+ type: _stateMachine.TaskEvent.OUTBOUND_FAILED,
541
+ taskData: suppressOutdialFailedPopup ? {
542
+ ...payload,
543
+ suppressOutdialFailedPopup: true
544
+ } : payload,
545
+ reason: payload.reason
546
+ };
583
547
  }
584
- }
585
- });
548
+ case _types2.CC_EVENTS.CAMPAIGN_PREVIEW_ACCEPT_FAILED:
549
+ return {
550
+ type: _stateMachine.TaskEvent.CAMPAIGN_PREVIEW_ACCEPT_FAILED,
551
+ taskData: payload
552
+ };
553
+ case _types2.CC_EVENTS.CAMPAIGN_PREVIEW_SKIP_FAILED:
554
+ return {
555
+ type: _stateMachine.TaskEvent.CAMPAIGN_PREVIEW_SKIP_FAILED,
556
+ taskData: payload
557
+ };
558
+ case _types2.CC_EVENTS.CAMPAIGN_PREVIEW_REMOVE_FAILED:
559
+ return {
560
+ type: _stateMachine.TaskEvent.CAMPAIGN_PREVIEW_REMOVE_FAILED,
561
+ taskData: payload
562
+ };
563
+ case _types2.CC_EVENTS.CONTACT_RECORDING_STARTED:
564
+ return {
565
+ type: _stateMachine.TaskEvent.RECORDING_STARTED,
566
+ taskData: payload
567
+ };
568
+ case _types2.CC_EVENTS.CONTACT_RECORDING_PAUSED:
569
+ return {
570
+ type: _stateMachine.TaskEvent.PAUSE_RECORDING,
571
+ taskData: payload
572
+ };
573
+ case _types2.CC_EVENTS.CONTACT_RECORDING_RESUMED:
574
+ return {
575
+ type: _stateMachine.TaskEvent.RESUME_RECORDING,
576
+ taskData: payload
577
+ };
578
+ case _types2.CC_EVENTS.AGENT_WRAPPEDUP:
579
+ return {
580
+ type: _stateMachine.TaskEvent.WRAPUP_COMPLETE,
581
+ taskData: payload
582
+ };
583
+
584
+ // Conference events - these trigger state machine transition to CONFERENCING
585
+ case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCED:
586
+ case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCING:
587
+ case _types2.CC_EVENTS.PARTICIPANT_JOINED_CONFERENCE:
588
+ return {
589
+ type: _stateMachine.TaskEvent.CONFERENCE_START,
590
+ taskData: payload
591
+ };
592
+ case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCE_FAILED:
593
+ return {
594
+ type: _stateMachine.TaskEvent.CONFERENCE_FAILED,
595
+ reason: payload.reason,
596
+ taskData: payload
597
+ };
598
+ case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCE_ENDED:
599
+ return {
600
+ type: _stateMachine.TaskEvent.CONFERENCE_END,
601
+ taskData: payload
602
+ };
603
+ case _types2.CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE:
604
+ return {
605
+ type: _stateMachine.TaskEvent.PARTICIPANT_LEAVE,
606
+ taskData: payload,
607
+ participantId: payload?.participantId
608
+ };
609
+ case _types2.CC_EVENTS.AGENT_CONFERENCE_TRANSFERRED:
610
+ return {
611
+ type: _stateMachine.TaskEvent.TRANSFER_CONFERENCE_SUCCESS,
612
+ taskData: payload
613
+ };
614
+ default:
615
+ // Not all events need state machine mapping
616
+ return null;
617
+ }
586
618
  }
587
- updateTaskData(task, taskData) {
588
- if (!task) {
589
- return undefined;
619
+
620
+ /**
621
+ * True when the current agent's task is a wxApp-managed outdial (thick-client telephony).
622
+ */
623
+ static isWxAppManagedOutdialTask(agentId, payload, task, enableWxBetterTogether) {
624
+ const wxAppEnabled = task?.uiControlConfig?.enableWxBetterTogether ?? enableWxBetterTogether ?? false;
625
+ if (!wxAppEnabled) {
626
+ return false;
590
627
  }
591
- if (!taskData?.interactionId) {
592
- _loggerProxy.default.warn('Received task update with missing interactionId', {
593
- module: _constants.TASK_MANAGER_FILE,
594
- method: _constants2.METHODS.UPDATE_TASK_DATA
595
- });
628
+ const effectiveAgentId = agentId ?? task?.data?.agentId;
629
+ const participants = payload.interaction?.participants ?? task?.data?.interaction?.participants;
630
+ const details = (0, _WebexCallingUtils.getWebexCallingDeviceDetailsForAgent)(effectiveAgentId, participants);
631
+ return details?.deviceType === 'wxApp';
632
+ }
633
+
634
+ /**
635
+ * True when ContactEnded represents an agent-terminated wxApp outdial that should
636
+ * enter wrapup with an outdial-failed popup (legacy AgentOutboundFailed parity).
637
+ */
638
+ static isAgentTerminatedOutdialWrapup(payload, wrapUpRequired, task, agentId, enableWxBetterTogether) {
639
+ const isOutdial = payload.interaction?.outboundType === 'OUTDIAL';
640
+ const agentTerminated = payload.terminatingParty === 'Agent';
641
+ if (!isOutdial || !agentTerminated) {
642
+ return false;
643
+ }
644
+ if (!TaskManager.isWxAppManagedOutdialTask(agentId, payload, task, enableWxBetterTogether)) {
645
+ return false;
596
646
  }
647
+ const wxAppAnswerPending = task?.uiControlConfig?.wxAppAnswerPending ?? false;
648
+
649
+ // Pre-accept decline: decline() clears wxAppAnswerPending; backend may still send state wrapUp.
650
+ if (!wxAppAnswerPending && !wrapUpRequired) {
651
+ return false;
652
+ }
653
+ const interactionWrapUp = payload.interaction?.state === 'wrapUp';
654
+ return wrapUpRequired || interactionWrapUp || wxAppAnswerPending;
655
+ }
656
+
657
+ /**
658
+ * Register WebSocket message listeners for task events
659
+ *
660
+ * Main entry point that orchestrates event processing through a clear pipeline:
661
+ * 1. Parse and validate incoming WebSocket messages
662
+ * 2. Prepare event context with task and state machine mappings
663
+ * 3. Handle task lifecycle (creation, updates, collection management)
664
+ * 4. Send events to state machine (task-level transitions/emissions)
665
+ * 5. Cleanup is triggered via task events emitted by the state machine
666
+ *
667
+ * This architecture separates concerns:
668
+ * - TaskManager: Manages task collection lifecycle and operational concerns
669
+ * - State Machine: Manages individual task state and event emissions
670
+ */
671
+ registerTaskListeners() {
672
+ this.webSocketManager.on('message', event => {
673
+ // Step 1: Parse and validate the message
674
+ const message = TaskManager.parseWebSocketMessage(event);
675
+ if (!message) return;
676
+
677
+ // Step 2: Prepare event context
678
+ const eventContext = this.prepareEventContext(message);
679
+ if (!eventContext) return;
680
+ const actions = this.handleTaskLifecycleEvent(eventContext);
681
+ const {
682
+ task
683
+ } = actions;
684
+ if (!task) return;
685
+ const {
686
+ payload,
687
+ stateMachineEvent
688
+ } = eventContext;
689
+
690
+ // Always keep task.data updated (even for mapped events) so consumers relying
691
+ // on TaskManager-managed task instances see the latest payload.
692
+ if (payload) {
693
+ this.updateTaskData(task, payload);
694
+ }
695
+
696
+ // Send event to state machine - this will trigger all TASK_EVENTS emissions
697
+ // including TASK_INCOMING which is now handled via the state machine callbacks
698
+ if (stateMachineEvent) {
699
+ task.sendStateMachineEvent(stateMachineEvent);
700
+ }
701
+
702
+ // A receiving summary held while the task rang is emitted right after the answer
703
+ // event above (AgentContactAssigned / AgentConsulting) has been emitted.
704
+ const bufferedSummary = this.receivingSummaryBuffer.get(task.data.interactionId);
705
+ const taskState = task.state?.value;
706
+ if (bufferedSummary && taskState !== _stateMachine.TaskState.IDLE && taskState !== _stateMachine.TaskState.OFFERED) {
707
+ this.receivingSummaryBuffer.delete(task.data.interactionId);
708
+ task.emit(_types.TASK_EVENTS.TASK_MID_CALL_SUMMARY_RECEIVED, bufferedSummary);
709
+ }
710
+
711
+ // Emit TASK_POST_CALL_ACTIVITY for ParticipantPostCallActivity events so
712
+ // consumers (Widgets) can detect the interaction state change to post_call.
713
+ if (eventContext.eventType === _types2.CC_EVENTS.PARTICIPANT_POST_CALL_ACTIVITY) {
714
+ task.emit(_types.TASK_EVENTS.TASK_POST_CALL_ACTIVITY, task);
715
+ }
716
+
717
+ // Send transcript start/stop events for relevant CC events
718
+ this.requestRealTimeTranscripts(eventContext.eventType, payload.interactionId);
719
+ });
720
+ }
721
+
722
+ /**
723
+ * Parse and validate WebSocket message
724
+ * @returns Parsed message or null if invalid/keepalive
725
+ */
726
+ static parseWebSocketMessage(event) {
597
727
  try {
598
- const currentTask = task.updateTaskData(taskData);
599
- this.taskCollection[taskData.interactionId] = currentTask;
600
- return currentTask;
728
+ const payload = JSON.parse(event);
729
+
730
+ // Filter out keepalive messages
731
+ if (payload?.keepalive === 'true' || payload?.keepalive === true) {
732
+ return null;
733
+ }
734
+
735
+ // Normalize task data if present
736
+ if (payload?.data?.interaction) {
737
+ payload.data = (0, _taskDataNormalizer.normalizeTaskData)(payload.data);
738
+ }
739
+ return payload;
601
740
  } catch (error) {
602
- _loggerProxy.default.error(`Failed to update task`, {
741
+ _loggerProxy.default.error('Failed to parse WebSocket message', {
603
742
  module: _constants.TASK_MANAGER_FILE,
604
- method: _constants2.METHODS.UPDATE_TASK_DATA,
605
- interactionId: taskData.interactionId
743
+ method: 'parseWebSocketMessage',
744
+ error
606
745
  });
607
- return task;
746
+ return null;
608
747
  }
609
748
  }
610
749
 
611
750
  /**
612
- * Handles CONTACT_MERGED event logic
613
- * @param task - The task to process
614
- * @param taskData - The task data from the event payload
615
- * @returns Updated or newly created task
616
- * @private
751
+ * Prepare context for event processing
752
+ * @returns Event context or null if event type is invalid
617
753
  */
618
- handleContactMerged(task, taskData) {
619
- if (taskData.childInteractionId) {
620
- // remove the child task from collection
621
- this.removeTaskFromCollection(this.taskCollection[taskData.childInteractionId]);
754
+ prepareEventContext(message) {
755
+ const eventType = message.data?.type || message.type;
756
+ if (!eventType || !isCcEvent(eventType)) {
757
+ return null;
622
758
  }
623
- if (this.taskCollection[taskData.interactionId]) {
624
- _loggerProxy.default.log(`Got CONTACT_MERGED: Task already exists in collection`, {
759
+ const interactionId = message.data.interactionId;
760
+ const reservationInteractionId = message.data.reservationInteractionId;
761
+ let task = this.taskCollection[interactionId];
762
+ let ownerChangeResolvedByRelatedInteraction = false;
763
+
764
+ // When a campaign preview contact is accepted, the assigned event may arrive
765
+ // with a new interactionId while the task is stored under the original
766
+ // reservationInteractionId. Fall back to that key so the task is found.
767
+ if (!task && reservationInteractionId) {
768
+ task = this.taskCollection[reservationInteractionId];
769
+ if (task) {
770
+ // Re-key the task under the new interaction ID and remove the old entry
771
+ delete this.taskCollection[reservationInteractionId];
772
+ this.taskCollection[interactionId] = task;
773
+ this.pendingFeatureEnablement.delete(reservationInteractionId);
774
+ }
775
+ }
776
+ if (!task && eventType === _types2.CC_EVENTS.CONTACT_OWNER_CHANGED) {
777
+ const incomingShowsCurrentAgentPromotion = Boolean(this.agentId && message.data.interaction?.owner === this.agentId && TaskManager.isParticipantActiveOnMainInteraction(message.data, this.agentId));
778
+ task = this.findUniqueTaskByRelatedInteraction(message.data, candidate => {
779
+ const currentAgentId = this.agentId || candidate.data?.agentId;
780
+ return Boolean(currentAgentId && (TaskManager.isParticipantActiveOnMainInteraction(candidate.data, currentAgentId) || incomingShowsCurrentAgentPromotion));
781
+ }, {
782
+ includeCollectionKeys: true,
783
+ includeMainCallMediaKeys: true
784
+ });
785
+ ownerChangeResolvedByRelatedInteraction = Boolean(task);
786
+ }
787
+ let ownerChangeMainInteractionId;
788
+ if (task && eventType === _types2.CC_EVENTS.CONTACT_OWNER_CHANGED) {
789
+ const taskMainInteractionId = task.data?.interaction?.mainInteractionId;
790
+ const payloadMainInteractionId = message.data.interaction?.mainInteractionId;
791
+ const hasConflictingMainInteractionIds = Boolean(taskMainInteractionId && payloadMainInteractionId && taskMainInteractionId !== payloadMainInteractionId);
792
+ // A promoted child task can retain stale embedded IDs. Prefer the
793
+ // authoritative incoming main-call identity before local snapshot IDs.
794
+ const stableMainInteractionId = payloadMainInteractionId || TaskManager.getMainCallMediaId(message.data.interaction) || TaskManager.getMainCallMediaId(task.data?.interaction) || taskMainInteractionId || message.data.interaction?.interactionId || task.data?.interaction?.interactionId || task.data?.interactionId;
795
+ const taskAtStableKey = stableMainInteractionId ? this.taskCollection[stableMainInteractionId] : undefined;
796
+ if (!stableMainInteractionId || hasConflictingMainInteractionIds || taskAtStableKey && taskAtStableKey !== task) {
797
+ _loggerProxy.default.warn('Unable to safely correlate owner change to the main interaction task', {
798
+ module: _constants.TASK_MANAGER_FILE,
799
+ method: 'prepareEventContext',
800
+ interactionId: payloadMainInteractionId || taskMainInteractionId || message.data.interactionId
801
+ });
802
+ task = undefined;
803
+ } else if (ownerChangeResolvedByRelatedInteraction) {
804
+ ownerChangeMainInteractionId = stableMainInteractionId;
805
+ }
806
+ }
807
+ if (!task && MAIN_INTERACTION_CORRELATED_EVENTS.has(eventType)) {
808
+ task = this.findUniqueTaskByRelatedInteraction(message.data);
809
+ }
810
+ const wasConsultedTask = Boolean(task?.data?.isConsulted);
811
+ const computeWrapUpRequired = () => {
812
+ if (message.data.wrapUpRequired !== undefined) {
813
+ return message.data.wrapUpRequired;
814
+ }
815
+ if (message.data.isConsulted !== undefined) {
816
+ return !message.data.isConsulted;
817
+ }
818
+ return !wasConsultedTask;
819
+ };
820
+ let adjustedPayload = eventType === _types2.CC_EVENTS.AGENT_CONSULT_TRANSFERRED || eventType === _types2.CC_EVENTS.AGENT_BLIND_TRANSFERRED || eventType === _types2.CC_EVENTS.AGENT_VTEAM_TRANSFERRED ? {
821
+ ...message.data,
822
+ wrapUpRequired: computeWrapUpRequired()
823
+ } : message.data;
824
+ if (task && ownerChangeMainInteractionId) {
825
+ adjustedPayload = {
826
+ ...adjustedPayload,
827
+ interactionId: ownerChangeMainInteractionId
828
+ };
829
+ }
830
+ if (task && eventType === _types2.CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE) {
831
+ adjustedPayload = TaskManager.preserveConfirmedOwner(task, adjustedPayload);
832
+ }
833
+ if (WRAP_UP_STAMP_EVENTS.has(eventType)) {
834
+ adjustedPayload = TaskManager.stampWrapUpRequiredForEvent(eventType, adjustedPayload, this.agentId, task);
835
+ }
836
+ const stateMachineEvent = TaskManager.mapEventToTaskStateMachineEvent(eventType, adjustedPayload, this.agentId, task, this.configFlags?.enableWxBetterTogether);
837
+ _loggerProxy.default.info(`Handling task event ${eventType}`, {
838
+ module: _constants.TASK_MANAGER_FILE,
839
+ method: 'prepareEventContext',
840
+ interactionId
841
+ });
842
+ return {
843
+ eventType,
844
+ payload: adjustedPayload,
845
+ task,
846
+ stateMachineEvent
847
+ };
848
+ }
849
+ static isParticipantActiveOnMainInteraction(taskData, participantId) {
850
+ const interaction = taskData?.interaction;
851
+ const participant = interaction?.participants?.[participantId];
852
+ if (!participant || participant.hasLeft === true) return false;
853
+ return Object.values(interaction?.media ?? {}).some(media => media?.mType === _constants3.MEDIA_TYPE_MAIN_CALL && media.participants?.includes(participantId));
854
+ }
855
+ static getMainCallMediaId(interaction) {
856
+ return Object.entries(interaction?.media ?? {}).find(([, media]) => media?.mType === _constants3.MEDIA_TYPE_MAIN_CALL)?.[0];
857
+ }
858
+ static getStableMainInteractionId(payload) {
859
+ return payload.interaction?.mainInteractionId || TaskManager.getMainCallMediaId(payload.interaction) || payload.interaction?.interactionId || payload.interactionId;
860
+ }
861
+ canRecoverTaskFromContactOwnerChanged(payload) {
862
+ const interaction = payload.interaction;
863
+ const stableInteractionId = TaskManager.getStableMainInteractionId(payload);
864
+ return Boolean(this.agentId && stableInteractionId && interaction?.mediaType === _types.MEDIA_CHANNEL.TELEPHONY && interaction.isTerminated !== true && interaction.owner === this.agentId && TaskManager.isParticipantActiveOnMainInteraction(payload, this.agentId));
865
+ }
866
+
867
+ /**
868
+ * ParticipantLeftConference may arrive after an owner update while still carrying
869
+ * the departed owner. Keep the confirmed owner only when the new roster proves that
870
+ * owner is active on mainCall and identifies the incoming owner as departed.
871
+ */
872
+ static preserveConfirmedOwner(task, payload) {
873
+ const confirmedOwner = task.data?.interaction?.owner;
874
+ const incomingOwner = payload.interaction?.owner;
875
+ if (!confirmedOwner || !incomingOwner || confirmedOwner === incomingOwner) {
876
+ return payload;
877
+ }
878
+ const confirmedOwnerIsActive = TaskManager.isParticipantActiveOnMainInteraction(payload, confirmedOwner);
879
+ const incomingOwnerIsExplicitlyDeparted = payload.participantId === incomingOwner || payload.interaction?.participants?.[incomingOwner]?.hasLeft === true;
880
+ if (!confirmedOwnerIsActive || !incomingOwnerIsExplicitlyDeparted) {
881
+ return payload;
882
+ }
883
+ return {
884
+ ...payload,
885
+ owner: confirmedOwner,
886
+ interaction: {
887
+ ...payload.interaction,
888
+ owner: confirmedOwner
889
+ }
890
+ };
891
+ }
892
+
893
+ /**
894
+ * Resolve lifecycle events that are emitted for the main interaction while the
895
+ * local task can still be indexed by a child consult interaction. Exact task
896
+ * keys are handled before this fallback. Multiple aliases of the same task are
897
+ * treated as one candidate; genuinely ambiguous matches are intentionally ignored.
898
+ */
899
+ findUniqueTaskByRelatedInteraction(payload, candidateFilter = () => true, options = {}) {
900
+ const candidates = this.findRelatedTasks(payload, options).filter(candidateFilter);
901
+ if (candidates.length === 1) {
902
+ return candidates[0];
903
+ }
904
+ if (candidates.length > 1) {
905
+ _loggerProxy.default.warn('Unable to correlate task event to a unique main interaction task', {
625
906
  module: _constants.TASK_MANAGER_FILE,
626
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
627
- interactionId: taskData.interactionId
907
+ method: 'findUniqueTaskByRelatedInteraction',
908
+ interactionId: payload.interaction?.mainInteractionId || payload.interaction?.parentInteractionId || payload.interaction?.callProcessingDetails?.parentInteractionId || payload.interactionId
628
909
  });
629
- // update the task data
630
- task = this.updateTaskData(task, taskData);
631
- } else {
632
- // Case2 : Task is not present in taskCollection
633
- _loggerProxy.default.log(`Got CONTACT_MERGED : Creating new task in taskManager`, {
910
+ }
911
+ return undefined;
912
+ }
913
+ findRelatedTasks(payload, options = {}) {
914
+ const {
915
+ includeCollectionKeys = false,
916
+ includeMainCallMediaKeys = false
917
+ } = options;
918
+ const payloadInteractionIds = new Set([payload.interactionId, payload.interaction?.interactionId, payload.interaction?.mainInteractionId, payload.interaction?.parentInteractionId, payload.interaction?.callProcessingDetails?.parentInteractionId, ...(includeMainCallMediaKeys ? [TaskManager.getMainCallMediaId(payload.interaction)] : [])].filter(relatedInteractionId => Boolean(relatedInteractionId)));
919
+ if (payloadInteractionIds.size === 0) return [];
920
+ return [...new Set(Object.entries(this.taskCollection).filter(([taskId, candidate]) => {
921
+ const taskInteraction = candidate?.data?.interaction;
922
+ const candidateInteractionIds = [...(includeCollectionKeys ? [taskId] : []), candidate?.data?.interactionId, taskInteraction?.interactionId, taskInteraction?.mainInteractionId, taskInteraction?.parentInteractionId, taskInteraction?.callProcessingDetails?.parentInteractionId, ...(includeMainCallMediaKeys ? [TaskManager.getMainCallMediaId(taskInteraction)] : [])];
923
+ return candidateInteractionIds.some(relatedInteractionId => Boolean(relatedInteractionId) && payloadInteractionIds.has(relatedInteractionId));
924
+ }).map(([, candidate]) => candidate))];
925
+ }
926
+
927
+ /**
928
+ * Handle task lifecycle events and determine required actions
929
+ *
930
+ * Delegates to specific event handlers based on event type. Each handler
931
+ * is responsible for TaskManager-level concerns:
932
+ * - Task creation and collection management
933
+ * - Metrics tracking
934
+ * - Resource cleanup decisions
935
+ *
936
+ * Note: Task-level state transitions and event emissions are handled by
937
+ * the task state machine via sendStateMachineEvent()
938
+ */
939
+ handleTaskLifecycleEvent(context) {
940
+ const {
941
+ eventType
942
+ } = context;
943
+ switch (eventType) {
944
+ case _types2.CC_EVENTS.AGENT_CONTACT_RESERVED:
945
+ return this.handleContactReserved(context);
946
+ case _types2.CC_EVENTS.AGENT_CONTACT:
947
+ return this.handleAgentContact(context);
948
+ case _types2.CC_EVENTS.CONTACT_MERGED:
949
+ return this.handleContactMergedEvent(context);
950
+ case _types2.CC_EVENTS.CONTACT_OWNER_CHANGED:
951
+ return this.handleContactOwnerChanged(context);
952
+ case _types2.CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION:
953
+ return this.handleCampaignPreviewReservation(context);
954
+ case _types2.CC_EVENTS.CAMPAIGN_CONTACT_UPDATED:
955
+ return this.handleCampaignContactUpdated(context);
956
+ default:
957
+ return {
958
+ task: context.task
959
+ };
960
+ }
961
+ }
962
+
963
+ /**
964
+ * Recover the promoted agent's active voice task when local task state was lost.
965
+ * Existing related tasks are never replaced: an unresolved or ambiguous relation
966
+ * is safer to ignore than to create a duplicate task.
967
+ */
968
+ handleContactOwnerChanged(context) {
969
+ if (context.task) {
970
+ return {
971
+ task: context.task
972
+ };
973
+ }
974
+ const {
975
+ payload
976
+ } = context;
977
+ if (this.findRelatedTasks(payload, {
978
+ includeCollectionKeys: true,
979
+ includeMainCallMediaKeys: true
980
+ }).length > 0 || !this.canRecoverTaskFromContactOwnerChanged(payload)) {
981
+ return {};
982
+ }
983
+ const stableInteractionId = TaskManager.getStableMainInteractionId(payload);
984
+ if (!stableInteractionId || this.taskCollection[stableInteractionId]) {
985
+ return {};
986
+ }
987
+ const normalizedPayload = payload.interactionId === stableInteractionId ? payload : {
988
+ ...payload,
989
+ interactionId: stableInteractionId
990
+ };
991
+ const taskData = {
992
+ ...normalizedPayload,
993
+ owner: normalizedPayload.interaction.owner,
994
+ wrapUpRequired: normalizedPayload.interaction.participants?.[this.agentId]?.isWrapUp || false,
995
+ isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(normalizedPayload),
996
+ isConsulted: false,
997
+ isAutoAnswering: false
998
+ };
999
+ const task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId, this.agentName, this.answerCallOnWebexService, this.apiAIAssistant);
1000
+ this.taskCollection[stableInteractionId] = task;
1001
+
1002
+ // Restore the actor before installing listeners so the internal hydrate does
1003
+ // not leak as a second public hydrate or as an incoming-task notification.
1004
+ task.sendStateMachineEvent({
1005
+ type: _stateMachine.TaskEvent.HYDRATE,
1006
+ taskData,
1007
+ agentId: this.agentId
1008
+ });
1009
+ this.setupTaskListeners(task);
1010
+ context.payload = taskData;
1011
+ context.stateMachineEvent = {
1012
+ type: _stateMachine.TaskEvent.CONTACT_OWNER_CHANGED,
1013
+ taskData
1014
+ };
1015
+ return {
1016
+ task
1017
+ };
1018
+ }
1019
+ handleCampaignContactUpdated(context) {
1020
+ const {
1021
+ payload
1022
+ } = context;
1023
+ let {
1024
+ task
1025
+ } = context;
1026
+ // CampaignContactUpdated is a non-terminal event (e.g., next contact after skip/remove).
1027
+ // Update the task data and emit an event so consumers can react to the updated contact.
1028
+ // Do NOT remove the task or emit TASK_END — cleanup is handled by CONTACT_ENDED.
1029
+ if (task) {
1030
+ // Carry forward campaign preview fields from existing task data since the updated
1031
+ // contact payload may not include them, and reconcileData would delete them.
1032
+ const existingCpd = task.data?.interaction?.callProcessingDetails;
1033
+ const updatedData = {
1034
+ ...payload
1035
+ };
1036
+ if (existingCpd) {
1037
+ const campaignFields = {
1038
+ ...(existingCpd.campaignPreviewAutoAction && {
1039
+ campaignPreviewAutoAction: existingCpd.campaignPreviewAutoAction
1040
+ }),
1041
+ ...(existingCpd.campaignPreviewOfferTimeout && {
1042
+ campaignPreviewOfferTimeout: existingCpd.campaignPreviewOfferTimeout
1043
+ }),
1044
+ ...(existingCpd.campaignPreviewSkipDisabled && {
1045
+ campaignPreviewSkipDisabled: existingCpd.campaignPreviewSkipDisabled
1046
+ }),
1047
+ ...(existingCpd.campaignPreviewRemoveDisabled && {
1048
+ campaignPreviewRemoveDisabled: existingCpd.campaignPreviewRemoveDisabled
1049
+ })
1050
+ };
1051
+ if (!updatedData.interaction) {
1052
+ updatedData.interaction = {};
1053
+ }
1054
+ updatedData.interaction = {
1055
+ ...updatedData.interaction,
1056
+ callProcessingDetails: {
1057
+ ...campaignFields,
1058
+ ...(updatedData.interaction.callProcessingDetails || {})
1059
+ }
1060
+ };
1061
+ }
1062
+ _loggerProxy.default.log('Campaign contact updated - carrying forward preview fields', {
634
1063
  module: _constants.TASK_MANAGER_FILE,
635
1064
  method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
636
- interactionId: taskData.interactionId
1065
+ interactionId: payload.interactionId,
1066
+ data: {
1067
+ hasCpd: !!updatedData.interaction?.callProcessingDetails,
1068
+ autoAction: updatedData.interaction?.callProcessingDetails?.campaignPreviewAutoAction,
1069
+ skipDisabled: updatedData.interaction?.callProcessingDetails?.campaignPreviewSkipDisabled,
1070
+ removeDisabled: updatedData.interaction?.callProcessingDetails?.campaignPreviewRemoveDisabled
1071
+ }
637
1072
  });
638
- task = new _.default(this.contact, this.webCallingService, {
639
- ...taskData,
640
- wrapUpRequired: taskData.interaction?.participants?.[this.agentId]?.isWrapUp || false,
641
- isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(taskData)
642
- }, this.wrapupData, this.agentId);
643
- this.taskCollection[taskData.interactionId] = task;
644
- }
645
- this.emit(_types.TASK_EVENTS.TASK_MERGED, task);
1073
+ task = this.updateTaskData(task, updatedData);
1074
+ task.emit(_types.TASK_EVENTS.TASK_CAMPAIGN_CONTACT_UPDATED, task);
1075
+ }
1076
+ return {
1077
+ task
1078
+ };
1079
+ }
1080
+
1081
+ /**
1082
+ * Creates or updates a task for campaign preview reservation.
1083
+ * TASK_CAMPAIGN_PREVIEW_RESERVATION is emitted by the state machine (campaign TASK_INCOMING branch).
1084
+ */
1085
+ handleCampaignPreviewReservation(context) {
1086
+ const {
1087
+ payload
1088
+ } = context;
1089
+ let {
1090
+ task
1091
+ } = context;
1092
+ _loggerProxy.default.log('Campaign preview reservation received', {
1093
+ module: _constants.TASK_MANAGER_FILE,
1094
+ method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
1095
+ interactionId: payload.interactionId
1096
+ });
1097
+ if (!task) {
1098
+ task = _TaskFactory.default.createTask(this.contact, this.webCallingService, {
1099
+ ...payload,
1100
+ wrapUpRequired: false,
1101
+ isConferenceInProgress: false,
1102
+ isAutoAnswering: false
1103
+ }, this.configFlags, this.wrapupData, this.agentId, this.agentName, this.answerCallOnWebexService, this.apiAIAssistant);
1104
+ this.setupTaskListeners(task);
1105
+ this.taskCollection[payload.interactionId] = task;
1106
+ } else {
1107
+ task = this.updateTaskData(task, payload);
1108
+ }
1109
+ return {
1110
+ task
1111
+ };
1112
+ }
1113
+
1114
+ /**
1115
+ * Handle AGENT_CONTACT_RESERVED event
1116
+ * Creates a new task; state machine event is sent during processing
1117
+ */
1118
+ handleContactReserved(context) {
1119
+ const {
1120
+ payload
1121
+ } = context;
1122
+ const isConsultedTask = payload.isConsulted === true || (0, _TaskUtils.isSecondaryEpDnAgent)(payload.interaction);
1123
+ const shouldAutoAnswer = (0, _TaskUtils.shouldAutoAnswerTask)(payload, this.agentId, this.webCallingService.loginOption, this.webRtcEnabled);
1124
+ const taskData = {
1125
+ ...payload,
1126
+ isConsulted: isConsultedTask,
1127
+ isAutoAnswering: shouldAutoAnswer
1128
+ };
1129
+ const task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId, this.agentName, this.answerCallOnWebexService, this.apiAIAssistant);
1130
+ this.setupTaskListeners(task);
1131
+ this.taskCollection[payload.interactionId] = task;
1132
+ return {
1133
+ task
1134
+ };
1135
+ }
1136
+
1137
+ /**
1138
+ * Handle AGENT_CONTACT event
1139
+ * Re-creates task if missing (multi-session scenario)
1140
+ */
1141
+ handleAgentContact(context) {
1142
+ let {
1143
+ task
1144
+ } = context;
1145
+ const {
1146
+ payload
1147
+ } = context;
1148
+ if (!task) {
1149
+ const isConsultedTask = payload.isConsulted === true || (0, _TaskUtils.isSecondaryEpDnAgent)(payload.interaction);
1150
+ const shouldAutoAnswer = (0, _TaskUtils.shouldAutoAnswerTask)(payload, this.agentId, this.webCallingService.loginOption, this.webRtcEnabled);
1151
+ const taskData = {
1152
+ ...payload,
1153
+ isConsulted: isConsultedTask,
1154
+ wrapUpRequired: payload.interaction?.participants?.[this.agentId]?.isWrapUp || false,
1155
+ isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload),
1156
+ isAutoAnswering: shouldAutoAnswer
1157
+ };
1158
+ task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId, this.agentName, this.answerCallOnWebexService, this.apiAIAssistant);
1159
+ this.setupTaskListeners(task);
1160
+ this.taskCollection[payload.interactionId] = task;
1161
+ }
1162
+ return {
1163
+ task
1164
+ };
1165
+ }
1166
+ updateTaskData(task, taskData) {
1167
+ if (!task) {
1168
+ throw new Error('Task not found for update');
1169
+ }
1170
+ const snapshot = task.stateMachineService?.getSnapshot?.();
1171
+ const isConsultingFlow = snapshot?.value === 'CONSULTING' || taskData.interaction?.state === 'consulting';
1172
+ const updateTaskData = isConsultingFlow ? {
1173
+ ...taskData,
1174
+ destAgentId: taskData.destAgentId ?? snapshot?.context?.consultDestinationAgentId ?? null,
1175
+ destinationType: taskData.destinationType ?? snapshot?.context?.consultDestinationType ?? null
1176
+ } : taskData;
1177
+ task.updateTaskData(updateTaskData);
1178
+
1179
+ // A main-interaction lifecycle event can re-key a child consult task. Remove
1180
+ // any aliases first so consumers never observe duplicate task entries.
1181
+ Object.entries(this.taskCollection).forEach(([taskId, candidate]) => {
1182
+ if (candidate === task && taskId !== taskData.interactionId) {
1183
+ delete this.taskCollection[taskId];
1184
+ }
1185
+ });
1186
+ this.taskCollection[taskData.interactionId] = task;
1187
+ const enablement = this.pendingFeatureEnablement.get(task.data.interactionId);
1188
+ if (enablement) {
1189
+ task.setFeatureEnablement?.(enablement);
1190
+ }
646
1191
  return task;
647
1192
  }
1193
+
1194
+ /**
1195
+ * Setup listeners for task events that need to be bubbled up to TaskManager
1196
+ * This replaces the previous callback injection pattern
1197
+ */
1198
+ setupTaskListeners(task) {
1199
+ // Apply flags before publication; retain the frame until consumers can attach listeners.
1200
+ const enablement = this.pendingFeatureEnablement.get(task.data.interactionId);
1201
+ if (enablement) {
1202
+ task.setFeatureEnablement?.(enablement);
1203
+ }
1204
+ [_types.TASK_EVENTS.TASK_INCOMING, _types.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, _types.TASK_EVENTS.TASK_HYDRATE, _types.TASK_EVENTS.TASK_ASSIGNED].forEach(eventName => {
1205
+ task.on(eventName, t => {
1206
+ if (eventName === _types.TASK_EVENTS.TASK_INCOMING || eventName === _types.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION) {
1207
+ _loggerProxy.default.log(eventName === _types.TASK_EVENTS.TASK_INCOMING ? 'Task incoming event received' : 'Campaign preview reservation event received', {
1208
+ module: _constants.TASK_MANAGER_FILE,
1209
+ method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
1210
+ interactionId: t.data?.interactionId
1211
+ });
1212
+ }
1213
+
1214
+ // Assignment is already emitted on the task; the other events publish it to consumers.
1215
+ if (eventName !== _types.TASK_EVENTS.TASK_ASSIGNED) {
1216
+ this.emit(eventName, t);
1217
+ }
1218
+ const pendingEnablement = this.pendingFeatureEnablement.get(task.data.interactionId);
1219
+ if (pendingEnablement) {
1220
+ this.pendingFeatureEnablement.delete(task.data.interactionId);
1221
+ task.emit(_types.TASK_EVENTS.TASK_FEATURE_ENABLEMENT, pendingEnablement);
1222
+ }
1223
+ });
1224
+ });
1225
+ task.on(_types.TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, t => {
1226
+ this.emit(_types.TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, t);
1227
+ });
1228
+
1229
+ // Listen for internal cleanup signal emitted by the state machine
1230
+ task.on(_types.TASK_EVENTS.TASK_CLEANUP, (t, options) => {
1231
+ this.handleTaskCleanup(t);
1232
+ if (options?.removeFromCollection) {
1233
+ const taskIsStillCollected = Object.values(this.taskCollection).some(candidate => candidate === t);
1234
+ if (taskIsStillCollected) {
1235
+ this.removeTaskFromCollection(t);
1236
+ }
1237
+ }
1238
+ });
1239
+ }
648
1240
  removeTaskFromCollection(task) {
1241
+ task.clearFeatureEnablement?.();
1242
+ task.disposeWxAppOfferObservability?.();
1243
+ if (typeof task.cancelAutoWrapupTimer === 'function') {
1244
+ task.cancelAutoWrapupTimer();
1245
+ }
649
1246
  if (task?.data?.interactionId) {
650
- delete this.taskCollection[task.data.interactionId];
1247
+ this.pendingFeatureEnablement.delete(task.data.interactionId);
1248
+ // An offer that ends unanswered (RONA, declined, consult cancelled) is removed here,
1249
+ // so its held receiving summary is dropped instead of being emitted.
1250
+ this.receivingSummaryBuffer.delete(task.data.interactionId);
1251
+ Object.entries(this.taskCollection).forEach(([taskId, candidate]) => {
1252
+ if (candidate === task) {
1253
+ delete this.taskCollection[taskId];
1254
+ }
1255
+ });
651
1256
  _loggerProxy.default.info(`Task removed from collection`, {
652
1257
  module: _constants.TASK_MANAGER_FILE,
653
1258
  method: _constants2.METHODS.REMOVE_TASK_FROM_COLLECTION,
@@ -657,62 +1262,67 @@ class TaskManager extends _events.default {
657
1262
  }
658
1263
 
659
1264
  /**
660
- * Handles auto-answer logic for incoming tasks
661
- * Automatically accepts tasks when isAutoAnswering flag is set
662
- * The flag is set during task creation based on:
663
- * 1. WebRTC calls with auto-answer enabled in agent profile
664
- * 2. Agent-initiated WebRTC outdial calls
665
- * 3. Agent-initiated digital outbound (Email/SMS) without previous transfers
666
- *
667
- * @param task - The task to auto-answer
1265
+ * Handles CONTACT_MERGED event logic
1266
+ * @param task - The task to process
1267
+ * @param taskData - The task data from the event payload
1268
+ * @returns Updated or newly created task
668
1269
  * @private
669
1270
  */
670
- async handleAutoAnswer(task) {
671
- if (!task || !task.data || !task.data.isAutoAnswering) {
672
- return;
1271
+ handleContactMergedEvent(context) {
1272
+ const {
1273
+ payload
1274
+ } = context;
1275
+ let task = context.task;
1276
+ if (payload.childInteractionId && this.taskCollection[payload.childInteractionId]) {
1277
+ this.removeTaskFromCollection(this.taskCollection[payload.childInteractionId]);
673
1278
  }
674
- _loggerProxy.default.info(`Auto-answering task`, {
675
- module: _constants.TASK_MANAGER_FILE,
676
- method: 'handleAutoAnswer',
677
- interactionId: task.data.interactionId
678
- });
679
- try {
680
- await task.accept();
681
- _loggerProxy.default.info(`Task auto-answered successfully`, {
1279
+ if (task) {
1280
+ _loggerProxy.default.log(`Got CONTACT_MERGED: Task already exists in collection`, {
682
1281
  module: _constants.TASK_MANAGER_FILE,
683
- method: 'handleAutoAnswer',
684
- interactionId: task.data.interactionId
685
- });
686
-
687
- // Track successful auto-answer
688
- this.metricsManager.trackEvent(_constants3.METRIC_EVENT_NAMES.TASK_AUTO_ANSWER_SUCCESS, {
689
- taskId: task.data.interactionId,
690
- mediaType: task.data.interaction.mediaType,
691
- isAutoAnswered: true
692
- }, ['behavioral', 'operational']);
693
- // Emit task:autoAnswered event for widgets/UI to react
694
- task.emit(_types.TASK_EVENTS.TASK_AUTO_ANSWERED, task);
695
- } catch (error) {
696
- // Reset isAutoAnswering flag on failure
697
- task.updateTaskData({
698
- ...task.data,
699
- isAutoAnswering: false
1282
+ method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
1283
+ interactionId: payload.interactionId
700
1284
  });
701
- _loggerProxy.default.error(`Failed to auto-answer task`, {
1285
+ // update the task data
1286
+ this.updateTaskData(task, payload);
1287
+ } else {
1288
+ // Case2 : Task is not present in taskCollection
1289
+ _loggerProxy.default.log(`Got CONTACT_MERGED : Creating new task in taskManager`, {
702
1290
  module: _constants.TASK_MANAGER_FILE,
703
- method: 'handleAutoAnswer',
704
- interactionId: task.data.interactionId,
705
- error
1291
+ method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
1292
+ interactionId: payload.interactionId
706
1293
  });
1294
+ const taskData = {
1295
+ ...payload,
1296
+ wrapUpRequired: payload.interaction?.participants?.[this.agentId]?.isWrapUp || false,
1297
+ isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload),
1298
+ isConsulted: false
1299
+ };
1300
+ task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId, this.agentName, this.answerCallOnWebexService, this.apiAIAssistant);
1301
+ this.taskCollection[payload.interactionId] = task;
707
1302
 
708
- // Track auto-answer failure
709
- this.metricsManager.trackEvent(_constants3.METRIC_EVENT_NAMES.TASK_AUTO_ANSWER_FAILED, {
710
- taskId: task.data.interactionId,
711
- mediaType: task.data.interaction.mediaType,
712
- error: error?.message || 'Unknown error',
713
- isAutoAnswered: false
714
- }, ['behavioral', 'operational']);
1303
+ // Transition the new task out of IDLE immediately so UI controls are
1304
+ // computed before TASK_MERGED is emitted. This handles the race where
1305
+ // AgentContactAssigned arrives before ContactMerged and gets dropped.
1306
+ // Send HYDRATE before setupTaskListeners so the emitTaskHydrate action
1307
+ // doesn't bubble up to the Widget (avoids duplicate listener registration).
1308
+ task.sendStateMachineEvent({
1309
+ type: _stateMachine.TaskEvent.HYDRATE,
1310
+ taskData,
1311
+ agentId: this.agentId
1312
+ });
1313
+ this.setupTaskListeners(task);
715
1314
  }
1315
+ if (task) {
1316
+ this.emit(_types.TASK_EVENTS.TASK_MERGED, task);
1317
+ const enablement = this.pendingFeatureEnablement.get(task.data.interactionId);
1318
+ if (enablement) {
1319
+ this.pendingFeatureEnablement.delete(task.data.interactionId);
1320
+ task.emit(_types.TASK_EVENTS.TASK_FEATURE_ENABLEMENT, enablement);
1321
+ }
1322
+ }
1323
+ return {
1324
+ task
1325
+ };
716
1326
  }
717
1327
 
718
1328
  /**
@@ -722,7 +1332,7 @@ class TaskManager extends _events.default {
722
1332
  */
723
1333
  handleTaskCleanup(task) {
724
1334
  // Clean up Desktop/WebRTC calling resources for browser-based telephony tasks
725
- if (this.webCallingService.loginOption === _types3.LoginOption.BROWSER && task.data.interaction.mediaType === 'telephony') {
1335
+ if (this.webCallingService.loginOption === _types3.LoginOption.BROWSER && task.data.interaction.mediaType === _types.MEDIA_CHANNEL.TELEPHONY && task instanceof _WebRTC.default) {
726
1336
  task.unregisterWebCallListeners();
727
1337
  this.webCallingService.cleanUpCall();
728
1338
  }
@@ -745,11 +1355,14 @@ class TaskManager extends _events.default {
745
1355
  */
746
1356
  requestRealTimeTranscripts(eventType, interactionId) {
747
1357
  const action = _constants2.TRANSCRIPT_EVENT_MAP[eventType];
748
- if (!action || !this.apiAIAssistant || this.apiAIAssistant.aiFeature?.realtimeTranscripts?.enable === false) return;
749
- this.apiAIAssistant.sendEvent(this.agentId, interactionId, _types3.AIAssistantEventType.CUSTOM_EVENT, _types3.AIAssistantEventName.GET_TRANSCRIPTS, action).catch(error => {
1358
+ if (!action || !this.apiAIAssistant) return;
1359
+ if (this.configFlags?.aiFeature?.realtimeTranscripts?.enable !== true) return;
1360
+ this.apiAIAssistant.sendEvent(this.agentId, interactionId, _types3.AIAssistantEventType.CUSTOM_EVENT, _types3.AIAssistantEventName.GET_TRANSCRIPTS, {
1361
+ action
1362
+ }).catch(error => {
750
1363
  _loggerProxy.default.error(`Failed to send transcript ${action} event`, {
751
1364
  module: _constants.TASK_MANAGER_FILE,
752
- method: 'requestRealTimeTranscripts',
1365
+ method: _constants2.METHODS.REQUEST_REAL_TIME_TRANSCRIPTS,
753
1366
  interactionId,
754
1367
  error
755
1368
  });
@@ -758,18 +1371,72 @@ class TaskManager extends _events.default {
758
1371
  getTask(taskId) {
759
1372
  return this.taskCollection[taskId];
760
1373
  }
1374
+ getAllTasks() {
1375
+ return {
1376
+ ...this.taskCollection
1377
+ };
1378
+ }
761
1379
 
762
1380
  /**
763
- * @param taskId - Unique identifier for each task
1381
+ * Propagate runtime enableWxBetterTogether changes to config flags and active tasks.
764
1382
  */
765
- getAllTasks = () => {
766
- return this.taskCollection;
767
- };
768
- static getTaskManager(apiAIAssistant, contact, webCallingService, webSocketManager) {
1383
+ applyEnableWxBetterTogether(enabled) {
1384
+ if (this.configFlags) {
1385
+ this.configFlags = {
1386
+ ...this.configFlags,
1387
+ enableWxBetterTogether: enabled
1388
+ };
1389
+ }
1390
+ Object.values(this.taskCollection).forEach(task => {
1391
+ task.setEnableWxBetterTogether(enabled);
1392
+ if (enabled) {
1393
+ TaskManager.syncWxAppMuteFromCallDetailsForTask(task);
1394
+ }
1395
+ });
1396
+ }
1397
+
1398
+ /**
1399
+ * Propagate wxApp mute state from Mercury sync to active voice tasks.
1400
+ */
1401
+ applyWxAppMuteStateFromSync(callId, muted) {
1402
+ Object.values(this.taskCollection).forEach(task => {
1403
+ task.applyWxAppMuteStateFromSync(callId, muted);
1404
+ });
1405
+ }
1406
+
1407
+ /**
1408
+ * Re-seeds wxApp mute state from GET telephony/calls/{callId} for one task.
1409
+ */
1410
+ static syncWxAppMuteFromCallDetailsForTask(task) {
1411
+ const sync = task.syncWxAppMuteFromCallDetails?.bind(task);
1412
+ if (typeof sync === 'function') {
1413
+ sync().catch(() => undefined);
1414
+ }
1415
+ }
1416
+
1417
+ /**
1418
+ * Re-seeds wxApp mute for all engaged voice tasks (reload / runtime enable backfill).
1419
+ */
1420
+ syncWxAppMuteFromCallDetailsForAllTasks() {
1421
+ Object.values(this.taskCollection).forEach(task => {
1422
+ TaskManager.syncWxAppMuteFromCallDetailsForTask(task);
1423
+ });
1424
+ }
1425
+
1426
+ /**
1427
+ * Re-run OFFERED wxApp offer diagnostics for collected voice tasks.
1428
+ * Used after usersub publish so pre-publish offers can schedule mismatch grace.
1429
+ */
1430
+ refreshWxAppOfferObservabilityForAllTasks() {
1431
+ Object.values(this.taskCollection).forEach(task => {
1432
+ task.refreshWxAppOfferObservability?.();
1433
+ });
1434
+ }
1435
+ static getTaskManager(apiAIAssistant, contact, webCallingService, webSocketManager, rtdWebSocketManager) {
769
1436
  if (!TaskManager.taskManager) {
770
- TaskManager.taskManager = new TaskManager(apiAIAssistant, contact, webCallingService, webSocketManager);
1437
+ TaskManager.taskManager = new TaskManager(apiAIAssistant, contact, webCallingService, webSocketManager, rtdWebSocketManager);
771
1438
  }
772
- return this.taskManager;
1439
+ return TaskManager.taskManager;
773
1440
  }
774
1441
  }
775
1442
  exports.default = TaskManager;