@webex/contact-center 3.11.0 → 3.12.0-auth-prejoin-fetch.1

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