@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
@@ -0,0 +1,1211 @@
1
+ import {EventEmitter} from 'events';
2
+ import {createActor} from 'xstate';
3
+ import type {ActorRefFrom, SnapshotFrom} from 'xstate';
4
+ import {
5
+ ITask,
6
+ TaskData,
7
+ TaskResponse,
8
+ WrapupPayLoad,
9
+ TaskId,
10
+ TransferPayLoad,
11
+ DESTINATION_TYPE,
12
+ TASK_EVENTS,
13
+ TaskUIControls,
14
+ DropConferenceParticipantPayload,
15
+ ConsultEndPayload,
16
+ ConsultPayload,
17
+ ConsultTransferPayLoad,
18
+ ResumeRecordingPayload,
19
+ MEDIA_CHANNEL,
20
+ TASK_CHANNEL_TYPE,
21
+ VOICE_VARIANT,
22
+ CallId,
23
+ AISummaryAction,
24
+ AISummary,
25
+ AISummaryResponse,
26
+ AISummaryFeatureEnablement,
27
+ AISummaryCapabilities,
28
+ TaskToggleMuteOptions,
29
+ TaskTransmitDtmfOptions,
30
+ } from './types';
31
+ import {AI_SUMMARY_DURATION_MS, ENTRY_POINT_TRANSFER_DESTINATION_TYPE, METHODS} from './constants';
32
+ import {
33
+ AI_ASSISTANT_CLIENT_TYPE,
34
+ AI_SUMMARY_ERROR_CODES,
35
+ AI_SUMMARY_INVALID_RESPONSE_PAYLOAD,
36
+ CC_FILE,
37
+ TASK_FILE,
38
+ } from '../../constants';
39
+ import {getErrorDetails} from '../core/Utils';
40
+ import routingContact from './contact';
41
+ import MetricsManager from '../../metrics/MetricsManager';
42
+ import {METRIC_EVENT_NAMES} from '../../metrics/constants';
43
+ import LoggerProxy from '../../logger-proxy';
44
+ import {createTaskStateMachine, TaskState} from './state-machine';
45
+ import type {
46
+ TaskEventPayload,
47
+ TaskStateMachine,
48
+ UIControlConfig,
49
+ TaskContext,
50
+ TaskActionsMap,
51
+ TaskActionArgs,
52
+ } from './state-machine';
53
+ import {
54
+ computeUIControls,
55
+ getDefaultUIControls,
56
+ haveUIControlsChanged,
57
+ } from './state-machine/uiControlsComputer';
58
+ import AutoWrapup from './AutoWrapup';
59
+ import type {WrapupData} from '../config/types';
60
+ import {AIAssistantEventName, AIAssistantEventType} from '../../types';
61
+ import {createSummaryError} from './TaskUtils';
62
+ import type ApiAIAssistant from '../ApiAiAssistant';
63
+
64
+ type UIControlConfigInput = Omit<UIControlConfig, 'channelType'> & {
65
+ channelType?: UIControlConfig['channelType'];
66
+ };
67
+
68
+ export default abstract class Task extends EventEmitter implements ITask {
69
+ protected contact: ReturnType<typeof routingContact>;
70
+ protected metricsManager: MetricsManager;
71
+ public stateMachineService?: ActorRefFrom<TaskStateMachine>;
72
+ public data: TaskData;
73
+ public webCallMap: Record<TaskId, CallId>;
74
+ public state?: SnapshotFrom<TaskStateMachine>;
75
+ private lastState?: TaskState;
76
+ protected currentUiControls: TaskUIControls;
77
+ protected uiControlConfig: UIControlConfig;
78
+ protected wrapupData?: WrapupData;
79
+ public autoWrapup?: AutoWrapup;
80
+ protected agentId?: string;
81
+ protected agentName?: string;
82
+ private apiAIAssistant?: ApiAIAssistant;
83
+ public aiSummaryCapabilities: Readonly<AISummaryCapabilities> = {
84
+ midCallEnabled: false,
85
+ postCallEnabled: false,
86
+ };
87
+
88
+ constructor(
89
+ contact: ReturnType<typeof routingContact>,
90
+ data: TaskData,
91
+ uiControlConfig: UIControlConfigInput,
92
+ wrapupData?: WrapupData,
93
+ agentId?: string,
94
+ agentName?: string,
95
+ apiAIAssistant?: ApiAIAssistant
96
+ ) {
97
+ super();
98
+ this.contact = contact;
99
+ this.data = data;
100
+ const channelType = uiControlConfig.channelType ?? Task.resolveChannelType(data);
101
+ // Include agentId in the config for ownership checks (transfer conference)
102
+ this.uiControlConfig = {...uiControlConfig, channelType, agentId};
103
+ this.wrapupData = wrapupData;
104
+ this.agentId = agentId;
105
+ this.agentName = agentName;
106
+ this.apiAIAssistant = apiAIAssistant;
107
+ this.metricsManager = MetricsManager.getInstance();
108
+ this.webCallMap = {};
109
+ this.currentUiControls = getDefaultUIControls();
110
+ this.initializeStateMachine();
111
+ this.setupAutoWrapupTimer();
112
+ }
113
+
114
+ private static resolveChannelType(data: TaskData): UIControlConfig['channelType'] {
115
+ const mediaType = data?.interaction?.mediaType ?? MEDIA_CHANNEL.TELEPHONY;
116
+
117
+ return mediaType === MEDIA_CHANNEL.TELEPHONY
118
+ ? TASK_CHANNEL_TYPE.VOICE
119
+ : TASK_CHANNEL_TYPE.DIGITAL;
120
+ }
121
+
122
+ // Abstract methods that all child classes must implement
123
+ public abstract accept(): Promise<TaskResponse>;
124
+
125
+ // Voice-specific methods with default implementations that throw errors
126
+ // Voice class will override these with actual implementations
127
+ public async decline(): Promise<TaskResponse> {
128
+ this.unsupportedMethodError('decline');
129
+ }
130
+
131
+ public async pauseRecording(): Promise<TaskResponse> {
132
+ this.unsupportedMethodError('pauseRecording');
133
+ }
134
+
135
+ public async resumeRecording(
136
+ resumeRecordingPayload: ResumeRecordingPayload
137
+ ): Promise<TaskResponse> {
138
+ if (resumeRecordingPayload) {
139
+ // parameter intentionally unused
140
+ }
141
+ this.unsupportedMethodError('resumeRecording');
142
+ }
143
+
144
+ public async consult(consultPayload: ConsultPayload): Promise<TaskResponse> {
145
+ if (consultPayload) {
146
+ // parameter intentionally unused
147
+ }
148
+ this.unsupportedMethodError('consult');
149
+ }
150
+
151
+ public async endConsult(consultEndPayload: ConsultEndPayload): Promise<TaskResponse> {
152
+ if (consultEndPayload) {
153
+ // parameter intentionally unused
154
+ }
155
+ this.unsupportedMethodError('endConsult');
156
+ }
157
+
158
+ public async consultTransfer(
159
+ consultTransferPayload?: ConsultTransferPayLoad
160
+ ): Promise<TaskResponse> {
161
+ if (consultTransferPayload) {
162
+ // parameter intentionally unused
163
+ }
164
+ this.unsupportedMethodError('consultTransfer');
165
+ }
166
+
167
+ public async consultConference(): Promise<TaskResponse> {
168
+ this.unsupportedMethodError('consultConference');
169
+ }
170
+
171
+ public async dropConferenceParticipant(
172
+ // The base task preserves the public signature; Voice provides the implementation.
173
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
174
+ payload: DropConferenceParticipantPayload
175
+ ): Promise<TaskResponse> {
176
+ this.unsupportedMethodError(METHODS.DROP_CONFERENCE_PARTICIPANT);
177
+ }
178
+
179
+ public async exitConference(): Promise<TaskResponse> {
180
+ this.unsupportedMethodError('exitConference');
181
+ }
182
+
183
+ public async transferConference(): Promise<TaskResponse> {
184
+ this.unsupportedMethodError('transferConference');
185
+ }
186
+
187
+ public async switchCall(): Promise<TaskResponse> {
188
+ this.unsupportedMethodError('switchCall');
189
+ }
190
+
191
+ public async toggleMute(options?: TaskToggleMuteOptions): Promise<void> {
192
+ if (options) {
193
+ // parameter intentionally unused
194
+ }
195
+ this.unsupportedMethodError('toggleMute');
196
+ }
197
+
198
+ public async transmitDtmf(options: TaskTransmitDtmfOptions): Promise<void> {
199
+ if (options) {
200
+ // parameter intentionally unused
201
+ }
202
+ this.unsupportedMethodError('transmitDtmf');
203
+ }
204
+
205
+ public unregisterWebCallListeners(): void {
206
+ // Default implementation - child classes can override
207
+ LoggerProxy.log('unregisterWebCallListeners called', {
208
+ module: CC_FILE,
209
+ method: 'unregisterWebCallListeners',
210
+ interactionId: this.data?.interactionId,
211
+ });
212
+ }
213
+
214
+ /**
215
+ * Cancel any in-progress auto wrap-up timer.
216
+ * Base implementation just clears the timer reference so subclasses inherit the behavior.
217
+ */
218
+ public cancelAutoWrapupTimer(): void {
219
+ if (this.autoWrapup) {
220
+ this.autoWrapup.clear();
221
+ this.autoWrapup = undefined;
222
+ LoggerProxy.log('Auto wrap-up timer cancelled', {
223
+ module: CC_FILE,
224
+ method: 'cancelAutoWrapupTimer',
225
+ interactionId: this.data?.interactionId,
226
+ });
227
+ }
228
+ }
229
+
230
+ // Voice tasks use holdResume(), but provide separate methods for interface compliance
231
+ public async hold(): Promise<TaskResponse> {
232
+ this.unsupportedMethodError('hold');
233
+ }
234
+
235
+ public async resume(): Promise<TaskResponse> {
236
+ this.unsupportedMethodError('resume');
237
+ }
238
+
239
+ public async holdResume(): Promise<TaskResponse> {
240
+ this.unsupportedMethodError('holdResume');
241
+ }
242
+
243
+ public setFeatureEnablement(enablement: AISummaryFeatureEnablement): void {
244
+ if (enablement.interactionId !== this.data.interactionId) {
245
+ return;
246
+ }
247
+
248
+ this.aiSummaryCapabilities = {
249
+ midCallEnabled: enablement.midCallEnabled === true,
250
+ postCallEnabled: enablement.postCallEnabled === true,
251
+ };
252
+ }
253
+
254
+ public clearFeatureEnablement(): void {
255
+ this.aiSummaryCapabilities = {
256
+ midCallEnabled: false,
257
+ postCallEnabled: false,
258
+ };
259
+ }
260
+
261
+ public async requestPostCallSummary(): Promise<AISummary> {
262
+ const metricFields: Record<string, unknown> = {operation: METHODS.REQUEST_POST_CALL_SUMMARY};
263
+
264
+ try {
265
+ this.metricsManager.timeEvent([
266
+ METRIC_EVENT_NAMES.AI_SUMMARY_GET_POST_CALL_SUCCESS,
267
+ METRIC_EVENT_NAMES.AI_SUMMARY_GET_POST_CALL_FAILED,
268
+ ]);
269
+
270
+ const interactionId = this.data.interactionId;
271
+ const conversationId = this.data.interaction?.mainInteractionId || interactionId;
272
+ Object.assign(metricFields, {conversationId, interactionId});
273
+
274
+ if (
275
+ !this.apiAIAssistant ||
276
+ this.apiAIAssistant.aiFeature?.generatedSummaries?.wrapUpSummariesEnabled !== true ||
277
+ !this.aiSummaryCapabilities.postCallEnabled
278
+ ) {
279
+ throw createSummaryError(AI_SUMMARY_ERROR_CODES.POST_CALL_SUMMARY_DISABLED);
280
+ }
281
+
282
+ const result = await this.apiAIAssistant.requestAndWaitForRtd<AISummary>({
283
+ correlationId: conversationId,
284
+ rtdEventType: 'POST_CALL_SUMMARY',
285
+ timeoutMs: AI_SUMMARY_DURATION_MS,
286
+ createTimeoutError: () =>
287
+ createSummaryError(AI_SUMMARY_ERROR_CODES.POST_CALL_SUMMARY_TIMEOUT),
288
+ agentId: this.agentId as string,
289
+ interactionId,
290
+ eventType: AIAssistantEventType.CTI_EVENT,
291
+ eventName: AIAssistantEventName.GET_POST_CALL_SUMMARY,
292
+ eventMetaData: {
293
+ conversationId,
294
+ clientType: AI_ASSISTANT_CLIENT_TYPE,
295
+ },
296
+ timeout: AI_SUMMARY_DURATION_MS,
297
+ });
298
+
299
+ this.metricsManager.trackEvent(
300
+ METRIC_EVENT_NAMES.AI_SUMMARY_GET_POST_CALL_SUCCESS,
301
+ {taskId: this.data?.interactionId, ...metricFields},
302
+ ['operational']
303
+ );
304
+
305
+ return result;
306
+ } catch (error) {
307
+ this.metricsManager.trackEvent(
308
+ METRIC_EVENT_NAMES.AI_SUMMARY_GET_POST_CALL_FAILED,
309
+ {
310
+ taskId: this.data?.interactionId,
311
+ ...metricFields,
312
+ failureCode: Task.getAISummaryFailureCode(error),
313
+ },
314
+ ['operational']
315
+ );
316
+ throw error;
317
+ }
318
+ }
319
+
320
+ public async sendPostCallSummaryResponse(response: AISummaryResponse): Promise<void> {
321
+ const metricFields: Record<string, unknown> = {
322
+ operation: METHODS.SEND_POST_CALL_SUMMARY_RESPONSE,
323
+ };
324
+
325
+ try {
326
+ this.metricsManager.timeEvent([
327
+ METRIC_EVENT_NAMES.AI_SUMMARY_POST_CALL_RESPONSE_SUCCESS,
328
+ METRIC_EVENT_NAMES.AI_SUMMARY_POST_CALL_RESPONSE_FAILED,
329
+ ]);
330
+ const interactionId = this.data.interactionId;
331
+ const conversationId = this.data.interaction?.mainInteractionId || interactionId;
332
+ Object.assign(metricFields, {conversationId, interactionId});
333
+
334
+ const publishTimestamp = Date.now();
335
+ const eventName = AIAssistantEventName.POST_CALL_SUMMARY_RESPONSE;
336
+ const eventPayload = {
337
+ conversationId,
338
+ clientType: AI_ASSISTANT_CLIENT_TYPE,
339
+ action: eventName,
340
+ summary: response.summary,
341
+ numberOfTimesViewed: response.numberOfTimesViewed,
342
+ numberOfTimesEdited: response.numberOfTimesEdited,
343
+ numberOfTimesCopied: response.numberOfTimesCopied,
344
+ feedback: response.feedback,
345
+ state: response.state,
346
+ wrapUpCode: response.wrapUpCode,
347
+ };
348
+ await this.apiAIAssistant.sendEvent(
349
+ this.agentId as string,
350
+ interactionId,
351
+ AIAssistantEventType.CTI_EVENT,
352
+ eventName,
353
+ eventPayload,
354
+ undefined,
355
+ undefined,
356
+ publishTimestamp,
357
+ AI_SUMMARY_DURATION_MS
358
+ );
359
+
360
+ this.metricsManager.trackEvent(
361
+ METRIC_EVENT_NAMES.AI_SUMMARY_POST_CALL_RESPONSE_SUCCESS,
362
+ {taskId: this.data?.interactionId, ...metricFields},
363
+ ['operational']
364
+ );
365
+ } catch (error) {
366
+ this.metricsManager.trackEvent(
367
+ METRIC_EVENT_NAMES.AI_SUMMARY_POST_CALL_RESPONSE_FAILED,
368
+ {
369
+ taskId: this.data?.interactionId,
370
+ ...metricFields,
371
+ failureCode: Task.getAISummaryFailureCode(error),
372
+ },
373
+ ['operational']
374
+ );
375
+ throw error;
376
+ }
377
+ }
378
+
379
+ public async requestMidCallSummary(action: AISummaryAction): Promise<AISummary> {
380
+ const metricFields: Record<string, unknown> = {
381
+ operation: METHODS.REQUEST_MID_CALL_SUMMARY,
382
+ actionType: action,
383
+ };
384
+
385
+ try {
386
+ this.metricsManager.timeEvent([
387
+ METRIC_EVENT_NAMES.AI_SUMMARY_GET_MID_CALL_SUCCESS,
388
+ METRIC_EVENT_NAMES.AI_SUMMARY_GET_MID_CALL_FAILED,
389
+ ]);
390
+
391
+ const interactionId = this.data.interactionId;
392
+ const conversationId = this.data.interaction?.mainInteractionId || interactionId;
393
+ Object.assign(metricFields, {conversationId, interactionId});
394
+
395
+ if (
396
+ !this.apiAIAssistant ||
397
+ this.apiAIAssistant.aiFeature?.generatedSummaries?.consultTransferSummariesEnabled !==
398
+ true ||
399
+ !this.aiSummaryCapabilities.midCallEnabled
400
+ ) {
401
+ throw createSummaryError(AI_SUMMARY_ERROR_CODES.MID_CALL_SUMMARY_DISABLED);
402
+ }
403
+
404
+ const eventName =
405
+ action === 'CONSULT'
406
+ ? AIAssistantEventName.GET_MID_CALL_CONSULT_SUMMARY
407
+ : AIAssistantEventName.GET_MID_CALL_TRANSFER_SUMMARY;
408
+ const result = await this.apiAIAssistant.requestAndWaitForRtd<AISummary>({
409
+ correlationId: conversationId,
410
+ rtdEventType: 'MID_CALL_SUMMARY',
411
+ timeoutMs: AI_SUMMARY_DURATION_MS,
412
+ createTimeoutError: () =>
413
+ createSummaryError(AI_SUMMARY_ERROR_CODES.MID_CALL_SUMMARY_TIMEOUT),
414
+ agentId: this.agentId as string,
415
+ interactionId,
416
+ eventType: AIAssistantEventType.CTI_EVENT,
417
+ eventName,
418
+ eventMetaData: {
419
+ conversationId,
420
+ clientType: AI_ASSISTANT_CLIENT_TYPE,
421
+ },
422
+ timeout: AI_SUMMARY_DURATION_MS,
423
+ });
424
+
425
+ this.metricsManager.trackEvent(
426
+ METRIC_EVENT_NAMES.AI_SUMMARY_GET_MID_CALL_SUCCESS,
427
+ {taskId: this.data?.interactionId, ...metricFields},
428
+ ['operational']
429
+ );
430
+
431
+ return result;
432
+ } catch (error) {
433
+ this.metricsManager.trackEvent(
434
+ METRIC_EVENT_NAMES.AI_SUMMARY_GET_MID_CALL_FAILED,
435
+ {
436
+ taskId: this.data?.interactionId,
437
+ ...metricFields,
438
+ failureCode: Task.getAISummaryFailureCode(error),
439
+ },
440
+ ['operational']
441
+ );
442
+ throw error;
443
+ }
444
+ }
445
+
446
+ public async sendMidCallSummaryResponse(
447
+ response: AISummaryResponse,
448
+ action: AISummaryAction
449
+ ): Promise<void> {
450
+ const metricFields: Record<string, unknown> = {
451
+ operation: METHODS.SEND_MID_CALL_SUMMARY_RESPONSE,
452
+ actionType: action,
453
+ };
454
+
455
+ try {
456
+ this.metricsManager.timeEvent([
457
+ METRIC_EVENT_NAMES.AI_SUMMARY_MID_CALL_RESPONSE_SUCCESS,
458
+ METRIC_EVENT_NAMES.AI_SUMMARY_MID_CALL_RESPONSE_FAILED,
459
+ ]);
460
+ const interactionId = this.data.interactionId;
461
+ const conversationId = this.data.interaction?.mainInteractionId || interactionId;
462
+ Object.assign(metricFields, {conversationId, interactionId});
463
+
464
+ const publishTimestamp = Date.now();
465
+ const eventName =
466
+ action === 'CONSULT'
467
+ ? AIAssistantEventName.MID_CALL_CONSULT_SUMMARY_RESPONSE
468
+ : AIAssistantEventName.MID_CALL_TRANSFER_SUMMARY_RESPONSE;
469
+ const eventPayload = {
470
+ conversationId,
471
+ clientType: AI_ASSISTANT_CLIENT_TYPE,
472
+ action: eventName,
473
+ summary: response.summary,
474
+ numberOfTimesViewed: response.numberOfTimesViewed,
475
+ numberOfTimesEdited: response.numberOfTimesEdited,
476
+ numberOfTimesCopied: response.numberOfTimesCopied,
477
+ feedback: response.feedback,
478
+ state: response.state,
479
+ agentName: this.agentName ?? '',
480
+ };
481
+ await this.apiAIAssistant.sendEvent(
482
+ this.agentId as string,
483
+ interactionId,
484
+ AIAssistantEventType.CTI_EVENT,
485
+ eventName,
486
+ eventPayload,
487
+ undefined,
488
+ undefined,
489
+ publishTimestamp,
490
+ AI_SUMMARY_DURATION_MS
491
+ );
492
+
493
+ this.metricsManager.trackEvent(
494
+ METRIC_EVENT_NAMES.AI_SUMMARY_MID_CALL_RESPONSE_SUCCESS,
495
+ {taskId: this.data?.interactionId, ...metricFields},
496
+ ['operational']
497
+ );
498
+ } catch (error) {
499
+ this.metricsManager.trackEvent(
500
+ METRIC_EVENT_NAMES.AI_SUMMARY_MID_CALL_RESPONSE_FAILED,
501
+ {
502
+ taskId: this.data?.interactionId,
503
+ ...metricFields,
504
+ failureCode: Task.getAISummaryFailureCode(error),
505
+ },
506
+ ['operational']
507
+ );
508
+ throw error;
509
+ }
510
+ }
511
+
512
+ private static getAISummaryFailureCode(error: unknown): string {
513
+ const errorCode = (error as {data?: {errorCode?: unknown}})?.data?.errorCode;
514
+
515
+ if (typeof errorCode === 'string') {
516
+ return errorCode;
517
+ }
518
+
519
+ return AI_SUMMARY_INVALID_RESPONSE_PAYLOAD;
520
+ }
521
+
522
+ /**
523
+ * Latest UI controls derived from state machine state and context.
524
+ */
525
+ public get uiControls(): TaskUIControls {
526
+ return this.currentUiControls;
527
+ }
528
+
529
+ protected updateUiControls(forceEmit = false): void {
530
+ const nextControls = this.computeUIControls();
531
+ const shouldEmit = forceEmit || haveUIControlsChanged(this.currentUiControls, nextControls);
532
+ this.currentUiControls = nextControls;
533
+
534
+ if (shouldEmit) {
535
+ this.emit(TASK_EVENTS.TASK_UI_CONTROLS_UPDATED, this.currentUiControls);
536
+ }
537
+ }
538
+
539
+ /**
540
+ * Initialize the state machine
541
+ */
542
+ private initializeStateMachine(): void {
543
+ const machine: TaskStateMachine = createTaskStateMachine(this.uiControlConfig, {
544
+ actions: this.getStateMachineActionOverrides(),
545
+ });
546
+
547
+ this.stateMachineService = createActor(machine);
548
+
549
+ this.stateMachineService.subscribe((snapshot) => {
550
+ const previousState = this.lastState;
551
+ const currentState = snapshot.value as TaskState;
552
+ LoggerProxy.log(`State machine transition: ${previousState || 'N/A'} -> ${currentState}`, {
553
+ module: CC_FILE,
554
+ method: 'onTransition',
555
+ // @ts-ignore - snapshot may include event detail depending on XState version
556
+ eventType: (snapshot as any)?.event?.type,
557
+ });
558
+ this.lastState = currentState;
559
+ this.state = snapshot;
560
+
561
+ this.updateUiControls(previousState !== currentState);
562
+ });
563
+
564
+ this.stateMachineService.start();
565
+ this.updateUiControls(true);
566
+ }
567
+
568
+ /**
569
+ * Send an event to the state machine
570
+ */
571
+ public sendStateMachineEvent(event: TaskEventPayload): void {
572
+ if (this.stateMachineService) {
573
+ LoggerProxy.log(`Sending state machine event: ${event?.type}`, {
574
+ module: CC_FILE,
575
+ method: 'sendStateMachineEvent',
576
+ interactionId: this.data?.interactionId,
577
+ });
578
+
579
+ this.stateMachineService.send(event);
580
+ }
581
+ }
582
+
583
+ /**
584
+ * Get the current state machine state
585
+ */
586
+ protected getCurrentState(): TaskState | undefined {
587
+ return this.stateMachineService?.getSnapshot()?.value as TaskState;
588
+ }
589
+
590
+ /**
591
+ * Compute UI controls based on current state machine state.
592
+ *
593
+ * @returns UI control states for all task actions
594
+ */
595
+ /**
596
+ * Update wxApp thick-client answer flag at runtime (Voice overrides).
597
+ */
598
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
599
+ public setEnableWxBetterTogether(_enabled: boolean): void {
600
+ // no-op for non-voice tasks
601
+ }
602
+
603
+ /**
604
+ * Apply wxApp mute state from external sync (Voice overrides).
605
+ */
606
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
607
+ public applyWxAppMuteStateFromSync(_incomingCallId: string, _muted: boolean): void {
608
+ // no-op for non-voice tasks
609
+ }
610
+
611
+ /**
612
+ * Hook for post-assign wxApp sync (Voice overrides).
613
+ */
614
+ protected onTaskAssigned(): void {
615
+ // no-op by default
616
+ }
617
+
618
+ /**
619
+ * Hook for post-hydrate wxApp sync (Voice overrides).
620
+ */
621
+ protected onTaskHydrated(): void {
622
+ // no-op by default
623
+ }
624
+
625
+ protected computeUIControls(): TaskUIControls {
626
+ const snapshot = this.stateMachineService?.getSnapshot?.();
627
+
628
+ if (!snapshot) {
629
+ return getDefaultUIControls();
630
+ }
631
+
632
+ const currentState = snapshot.value as TaskState;
633
+ const context = snapshot.context as TaskContext;
634
+ const mergedContext: TaskContext = {
635
+ ...context,
636
+ uiControlConfig: {...context.uiControlConfig, ...this.uiControlConfig},
637
+ };
638
+
639
+ const uiControls = computeUIControls(currentState, mergedContext, this.data);
640
+
641
+ return uiControls;
642
+ }
643
+
644
+ /**
645
+ * Stop the state machine service
646
+ */
647
+ protected stopStateMachine(): void {
648
+ if (this.stateMachineService) {
649
+ this.stateMachineService.stop();
650
+ this.stateMachineService = undefined;
651
+ }
652
+ }
653
+
654
+ private static extractTaskDataFromEvent(event?: TaskEventPayload): TaskData | undefined {
655
+ if (!event || typeof event !== 'object') {
656
+ return undefined;
657
+ }
658
+
659
+ if ('taskData' in event) {
660
+ return (event as {taskData?: TaskData}).taskData;
661
+ }
662
+
663
+ return undefined;
664
+ }
665
+
666
+ private async autoAnswerIfNeeded(): Promise<void> {
667
+ if (!this.data) {
668
+ return;
669
+ }
670
+
671
+ const autoAnswerSupported =
672
+ this.uiControlConfig.channelType === TASK_CHANNEL_TYPE.DIGITAL ||
673
+ this.uiControlConfig.voiceVariant === VOICE_VARIANT.WEBRTC;
674
+
675
+ if (!autoAnswerSupported) {
676
+ return;
677
+ }
678
+
679
+ const shouldAutoAnswer = this.data.isAutoAnswering === true;
680
+
681
+ if (!shouldAutoAnswer) {
682
+ return;
683
+ }
684
+
685
+ LoggerProxy.info(`Auto-answering task`, {
686
+ module: TASK_FILE,
687
+ method: 'autoAnswerIfNeeded',
688
+ interactionId: this.data.interactionId,
689
+ });
690
+
691
+ try {
692
+ await this.accept();
693
+ LoggerProxy.info(`Task auto-answered successfully`, {
694
+ module: TASK_FILE,
695
+ method: 'autoAnswerIfNeeded',
696
+ interactionId: this.data.interactionId,
697
+ });
698
+
699
+ this.metricsManager.trackEvent(
700
+ METRIC_EVENT_NAMES.TASK_AUTO_ANSWER_SUCCESS,
701
+ {
702
+ taskId: this.data.interactionId,
703
+ mediaType: this.data.interaction.mediaType,
704
+ isAutoAnswered: true,
705
+ },
706
+ ['behavioral', 'operational']
707
+ );
708
+
709
+ this.emit(TASK_EVENTS.TASK_AUTO_ANSWERED, this);
710
+ } catch (error) {
711
+ this.updateTaskData({...this.data, isAutoAnswering: false});
712
+ LoggerProxy.error(`Failed to auto-answer task`, {
713
+ module: TASK_FILE,
714
+ method: 'autoAnswerIfNeeded',
715
+ interactionId: this.data.interactionId,
716
+ error,
717
+ });
718
+
719
+ this.metricsManager.trackEvent(
720
+ METRIC_EVENT_NAMES.TASK_AUTO_ANSWER_FAILED,
721
+ {
722
+ taskId: this.data.interactionId,
723
+ mediaType: this.data.interaction.mediaType,
724
+ error: error?.message || 'Unknown error',
725
+ isAutoAnswered: false,
726
+ },
727
+ ['behavioral', 'operational']
728
+ );
729
+ }
730
+ }
731
+
732
+ private updateTaskFromEvent(event?: TaskEventPayload): void {
733
+ const taskData = Task.extractTaskDataFromEvent(event);
734
+ if (taskData) {
735
+ this.updateTaskData(taskData);
736
+ }
737
+ }
738
+
739
+ protected getStateMachineActionOverrides(): Partial<TaskActionsMap> {
740
+ return {
741
+ ...this.getCommonActionOverrides(),
742
+ ...this.getChannelSpecificActionOverrides(),
743
+ };
744
+ }
745
+
746
+ protected getChannelSpecificActionOverrides(): Partial<TaskActionsMap> {
747
+ return {};
748
+ }
749
+
750
+ protected createEmitSelfAction(
751
+ taskEvent: TASK_EVENTS,
752
+ {updateTaskData = false}: {updateTaskData?: boolean} = {}
753
+ ) {
754
+ return ({event}: TaskActionArgs) => {
755
+ if (updateTaskData) {
756
+ this.updateTaskFromEvent(event);
757
+ }
758
+ LoggerProxy.info(`Emitting task event ${taskEvent}`, {
759
+ module: TASK_FILE,
760
+ method: 'emitTaskEvent',
761
+ interactionId: this.data?.interactionId,
762
+ });
763
+ this.emit(taskEvent, this);
764
+ };
765
+ }
766
+
767
+ private getCommonActionOverrides(): Partial<TaskActionsMap> {
768
+ return {
769
+ syncTaskDataFromEvent: ({event}: {event: TaskEventPayload}) => {
770
+ this.updateTaskFromEvent(event);
771
+ },
772
+ emitTaskIncoming: this.createEmitSelfAction(TASK_EVENTS.TASK_INCOMING, {
773
+ updateTaskData: true,
774
+ }),
775
+ emitTaskCampaignPreviewReservation: this.createEmitSelfAction(
776
+ TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION,
777
+ {updateTaskData: true}
778
+ ),
779
+ emitTaskCampaignPreviewAcceptFailed: this.createEmitSelfAction(
780
+ TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_ACCEPT_FAILED
781
+ ),
782
+ emitTaskCampaignPreviewSkipFailed: this.createEmitSelfAction(
783
+ TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_SKIP_FAILED
784
+ ),
785
+ emitTaskCampaignPreviewRemoveFailed: this.createEmitSelfAction(
786
+ TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_REMOVE_FAILED
787
+ ),
788
+ emitTaskHydrate: ({event}: TaskActionArgs) => {
789
+ this.updateTaskFromEvent(event);
790
+ LoggerProxy.info(`Emitting task event ${TASK_EVENTS.TASK_HYDRATE}`, {
791
+ module: TASK_FILE,
792
+ method: 'emitTaskEvent',
793
+ interactionId: this.data?.interactionId,
794
+ });
795
+ this.emit(TASK_EVENTS.TASK_HYDRATE, this);
796
+ this.onTaskHydrated();
797
+ },
798
+ emitTaskOfferContact: this.createEmitSelfAction(TASK_EVENTS.TASK_OFFER_CONTACT, {
799
+ updateTaskData: true,
800
+ }),
801
+ emitTaskAssigned: ({event}: TaskActionArgs) => {
802
+ this.updateTaskFromEvent(event);
803
+ this.emit(TASK_EVENTS.TASK_ASSIGNED, this);
804
+ this.emit(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, this);
805
+ this.onTaskAssigned();
806
+ },
807
+ emitTaskEnd: this.createEmitSelfAction(TASK_EVENTS.TASK_END, {updateTaskData: true}),
808
+ emitTaskOfferConsult: this.createEmitSelfAction(TASK_EVENTS.TASK_OFFER_CONSULT, {
809
+ updateTaskData: true,
810
+ }),
811
+ emitTaskConsultCreated: this.createEmitSelfAction(TASK_EVENTS.TASK_CONSULT_CREATED, {
812
+ updateTaskData: true,
813
+ }),
814
+ emitTaskConsulting: ({event}: TaskActionArgs) => {
815
+ this.updateTaskFromEvent(event);
816
+ if (this.data.isConsulted) {
817
+ this.emit(TASK_EVENTS.TASK_CONSULT_ACCEPTED, this);
818
+ } else {
819
+ this.emit(TASK_EVENTS.TASK_CONSULTING, this);
820
+ }
821
+ this.emit(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, this);
822
+ },
823
+ emitTaskConsultAccepted: this.createEmitSelfAction(TASK_EVENTS.TASK_CONSULT_ACCEPTED),
824
+ emitTaskConsultEnd: this.createEmitSelfAction(TASK_EVENTS.TASK_CONSULT_END, {
825
+ updateTaskData: true,
826
+ }),
827
+ emitTaskConsultQueueCancelled: this.createEmitSelfAction(
828
+ TASK_EVENTS.TASK_CONSULT_QUEUE_CANCELLED,
829
+ {
830
+ updateTaskData: true,
831
+ }
832
+ ),
833
+ emitTaskConsultQueueFailed: this.createEmitSelfAction(TASK_EVENTS.TASK_CONSULT_QUEUE_FAILED, {
834
+ updateTaskData: true,
835
+ }),
836
+ emitTaskReject: ({event}: TaskActionArgs) => {
837
+ this.updateTaskFromEvent(event);
838
+ const reason =
839
+ event && typeof event === 'object' && 'reason' in event
840
+ ? (event as {reason?: string}).reason
841
+ : undefined;
842
+ this.emit(TASK_EVENTS.TASK_REJECT, reason);
843
+ },
844
+ emitTaskWrapup: ({event}: {event?: TaskEventPayload}) => {
845
+ this.updateTaskFromEvent(event);
846
+
847
+ const shouldEmitWrapup = Boolean(this.data.wrapUpRequired);
848
+ if (!shouldEmitWrapup) {
849
+ LoggerProxy.info(`Skipping task:wrapup event - wrapUpRequired is false`, {
850
+ module: TASK_FILE,
851
+ method: 'emitTaskEvent',
852
+ interactionId: this.data?.interactionId,
853
+ });
854
+
855
+ return;
856
+ }
857
+ LoggerProxy.info(`Emitting task event ${TASK_EVENTS.TASK_WRAPUP}`, {
858
+ module: TASK_FILE,
859
+ method: 'emitTaskEvent',
860
+ interactionId: this.data?.interactionId,
861
+ });
862
+ this.emit(TASK_EVENTS.TASK_WRAPUP, this);
863
+ },
864
+ emitTaskWrappedup: this.createEmitSelfAction(TASK_EVENTS.TASK_WRAPPEDUP, {
865
+ updateTaskData: true,
866
+ }),
867
+ requestAutoAnswer: ({event}: TaskActionArgs) => {
868
+ if (event) {
869
+ // parameter intentionally unused
870
+ }
871
+ this.autoAnswerIfNeeded();
872
+ },
873
+ requestCleanup: () => {
874
+ this.emit(TASK_EVENTS.TASK_CLEANUP, this, {removeFromCollection: false});
875
+ },
876
+ cleanupResources: () => {
877
+ this.emit(TASK_EVENTS.TASK_CLEANUP, this, {removeFromCollection: true});
878
+ },
879
+ };
880
+ }
881
+
882
+ /**
883
+ * Sets up the automatic wrap-up timer if wrap-up is required
884
+ */
885
+ protected setupAutoWrapupTimer(): void {
886
+ if (
887
+ this.data.wrapUpRequired &&
888
+ !this.autoWrapup &&
889
+ this.wrapupData &&
890
+ this.wrapupData.wrapUpProps
891
+ ) {
892
+ const wrapUpProps = this.wrapupData.wrapUpProps;
893
+ if (!wrapUpProps || wrapUpProps.autoWrapup === false) {
894
+ LoggerProxy.info(`Auto wrap-up is not required for this task`, {
895
+ module: TASK_FILE,
896
+ method: METHODS.SETUP_AUTO_WRAPUP_TIMER,
897
+ interactionId: this.data.interactionId,
898
+ });
899
+
900
+ return;
901
+ }
902
+ const defaultWrapupReason =
903
+ wrapUpProps.wrapUpReasonList?.find((r) => r.isDefault) ?? wrapUpProps.wrapUpReasonList?.[0];
904
+ if (!defaultWrapupReason) {
905
+ LoggerProxy.error('No wrap-up reason configured', {
906
+ module: TASK_FILE,
907
+ method: METHODS.SETUP_AUTO_WRAPUP_TIMER,
908
+ });
909
+
910
+ return;
911
+ }
912
+ const intervalMs = wrapUpProps.autoWrapupInterval;
913
+ if (!intervalMs || intervalMs <= 0) {
914
+ LoggerProxy.error(`Invalid auto wrap-up interval: ${intervalMs}`, {
915
+ module: TASK_FILE,
916
+ method: METHODS.SETUP_AUTO_WRAPUP_TIMER,
917
+ });
918
+
919
+ return;
920
+ }
921
+ this.autoWrapup = new AutoWrapup(intervalMs, wrapUpProps.allowCancelAutoWrapup);
922
+ this.autoWrapup.start(async () => {
923
+ LoggerProxy.info(`Auto wrap-up timer triggered`, {
924
+ module: TASK_FILE,
925
+ method: METHODS.SETUP_AUTO_WRAPUP_TIMER,
926
+ interactionId: this.data.interactionId,
927
+ });
928
+ await this.wrapup({
929
+ wrapUpReason: defaultWrapupReason.name,
930
+ auxCodeId: defaultWrapupReason.id,
931
+ });
932
+ });
933
+ }
934
+ }
935
+
936
+ /**
937
+ * Cancels the automatic wrap-up timer if it's running
938
+ */
939
+ private reconcileData(oldData: TaskData, newData: TaskData): TaskData {
940
+ Object.keys(newData).forEach((key) => {
941
+ if (newData[key] && typeof newData[key] === 'object' && !Array.isArray(newData[key])) {
942
+ oldData[key] = this.reconcileData({...oldData[key]}, newData[key]);
943
+ } else {
944
+ oldData[key] = newData[key];
945
+ }
946
+ });
947
+
948
+ return oldData;
949
+ }
950
+
951
+ /**
952
+ *
953
+ * @param methodName - The name of the method that is unsupported
954
+ * @throws Error
955
+ */
956
+ protected unsupportedMethodError(methodName: string): never {
957
+ LoggerProxy.error(`Unsupported operation`, {
958
+ module: 'TASK',
959
+ method: methodName,
960
+ interactionId: this.data?.interactionId,
961
+ });
962
+ throw new Error(`Unsupported operation: ${methodName}`);
963
+ }
964
+
965
+ /**
966
+ * This method is used to update the task data.
967
+ * @param updatedData - TaskData
968
+ * @param shouldOverwrite - boolean
969
+ * @example
970
+ * ```typescript
971
+ * task.updateTaskData(updatedData, true);
972
+ * ```
973
+ */
974
+ public updateTaskData(updatedData: TaskData, shouldOverwrite = false): ITask {
975
+ this.data = shouldOverwrite ? updatedData : this.reconcileData(this.data, updatedData);
976
+ if (!shouldOverwrite) {
977
+ this.pruneStaleInteractionMaps(updatedData);
978
+ }
979
+ this.updateUiControls();
980
+ this.setupAutoWrapupTimer();
981
+
982
+ return this;
983
+ }
984
+
985
+ /**
986
+ * The backend sends `interaction.media` and `interaction.participants` as complete snapshots
987
+ * of the current call state. `reconcileData` deep-merges and never removes keys, so entries the
988
+ * backend dropped (e.g. a consult leg's media and consultee participant after the consult ends)
989
+ * linger in `this.data`. That stale data drives incorrect UI controls (e.g. the consult button
990
+ * staying disabled after the consult leg is gone, until a page refresh re-hydrates cleanly).
991
+ * Make only these two snapshot maps authoritative to the incoming payload, leaving every other
992
+ * field on the generic deep-merge path (CAD and other partial updates still merge as before).
993
+ */
994
+ private pruneStaleInteractionMaps(incoming: TaskData): void {
995
+ const incomingInteraction = incoming?.interaction;
996
+ const currentInteraction = this.data?.interaction;
997
+ if (!incomingInteraction || !currentInteraction) {
998
+ return;
999
+ }
1000
+
1001
+ (['media', 'participants'] as const).forEach((mapKey) => {
1002
+ const incomingMap = incomingInteraction[mapKey] as Record<string, unknown> | undefined;
1003
+ const currentMap = currentInteraction[mapKey] as Record<string, unknown> | undefined;
1004
+ if (!incomingMap || !currentMap || typeof incomingMap !== 'object') {
1005
+ return;
1006
+ }
1007
+
1008
+ Object.keys(currentMap).forEach((id) => {
1009
+ if (!(id in incomingMap)) {
1010
+ delete currentMap[id];
1011
+ }
1012
+ });
1013
+ });
1014
+ }
1015
+
1016
+ /**
1017
+ * This is used to blind transfer or vTeam transfer the task
1018
+ * @param transferPayload
1019
+ * @returns Promise<TaskResponse>
1020
+ * @throws Error
1021
+ * @example
1022
+ * ```typescript
1023
+ * const transferPayload = {
1024
+ * to: 'myQueueId',
1025
+ * destinationType: 'queue',
1026
+ * }
1027
+ * task.transfer(transferPayload).then(()=>{}).catch(()=>{});
1028
+ * ```
1029
+ */
1030
+ public async transfer(transferPayload: TransferPayLoad): Promise<TaskResponse> {
1031
+ LoggerProxy.log(`Starting task transfer for taskId:${this.data.interactionId}`, {
1032
+ module: 'Task',
1033
+ method: 'transfer',
1034
+ });
1035
+ try {
1036
+ this.metricsManager.timeEvent([
1037
+ METRIC_EVENT_NAMES.TASK_TRANSFER_SUCCESS,
1038
+ METRIC_EVENT_NAMES.TASK_TRANSFER_FAILED,
1039
+ ]);
1040
+ let result: TaskResponse;
1041
+ const isQueueTransfer = transferPayload.destinationType === DESTINATION_TYPE.QUEUE;
1042
+ const isEntryPointTransfer = transferPayload.destinationType === DESTINATION_TYPE.ENTRYPOINT;
1043
+
1044
+ if (isQueueTransfer || isEntryPointTransfer) {
1045
+ result = await this.contact.vteamTransfer({
1046
+ interactionId: this.data.interactionId,
1047
+ data: {
1048
+ ...transferPayload,
1049
+ destinationType: isEntryPointTransfer
1050
+ ? ENTRY_POINT_TRANSFER_DESTINATION_TYPE
1051
+ : DESTINATION_TYPE.QUEUE,
1052
+ },
1053
+ });
1054
+ } else {
1055
+ result = await this.contact.blindTransfer({
1056
+ interactionId: this.data.interactionId,
1057
+ data: transferPayload,
1058
+ });
1059
+ }
1060
+
1061
+ this.metricsManager.trackEvent(
1062
+ METRIC_EVENT_NAMES.TASK_TRANSFER_SUCCESS,
1063
+ {
1064
+ taskId: this.data.interactionId,
1065
+ destination: transferPayload.to,
1066
+ destinationType: transferPayload.destinationType,
1067
+ isConsultTransfer: false,
1068
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
1069
+ },
1070
+ ['operational', 'behavioral', 'business']
1071
+ );
1072
+
1073
+ return result;
1074
+ } catch (error) {
1075
+ const {error: detailedError} = getErrorDetails(error, 'transfer', CC_FILE);
1076
+ this.metricsManager.trackEvent(
1077
+ METRIC_EVENT_NAMES.TASK_TRANSFER_FAILED,
1078
+ {
1079
+ taskId: this.data.interactionId,
1080
+ destination: transferPayload.to,
1081
+ destinationType: transferPayload.destinationType,
1082
+ isConsultTransfer: false,
1083
+ error: error.toString(),
1084
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(
1085
+ (error as any).details || {}
1086
+ ),
1087
+ },
1088
+ ['operational', 'behavioral', 'business']
1089
+ );
1090
+ throw detailedError;
1091
+ }
1092
+ }
1093
+
1094
+ /**
1095
+ * This is used to end the task.
1096
+ * @returns Promise<TaskResponse>
1097
+ * @throws Error
1098
+ * @example
1099
+ * ```typescript
1100
+ * task.end().then(()=>{}).catch(()=>{})
1101
+ * ```
1102
+ */
1103
+ public async end(): Promise<TaskResponse> {
1104
+ LoggerProxy.log(`Ending task for taskId:${this.data.interactionId}`, {
1105
+ module: 'Task',
1106
+ method: 'end',
1107
+ });
1108
+ const requestInteractionId =
1109
+ this.data.interaction?.mainInteractionId || this.data.interactionId;
1110
+
1111
+ try {
1112
+ this.metricsManager.timeEvent([
1113
+ METRIC_EVENT_NAMES.TASK_END_SUCCESS,
1114
+ METRIC_EVENT_NAMES.TASK_END_FAILED,
1115
+ ]);
1116
+ const response = await this.contact.end({interactionId: requestInteractionId});
1117
+
1118
+ this.metricsManager.trackEvent(
1119
+ METRIC_EVENT_NAMES.TASK_END_SUCCESS,
1120
+ {
1121
+ taskId: this.data.interactionId,
1122
+ requestInteractionId,
1123
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
1124
+ },
1125
+ ['operational', 'behavioral', 'business']
1126
+ );
1127
+
1128
+ return response;
1129
+ } catch (error) {
1130
+ const {error: detailedError} = getErrorDetails(error, 'end', CC_FILE);
1131
+ this.metricsManager.trackEvent(
1132
+ METRIC_EVENT_NAMES.TASK_END_FAILED,
1133
+ {
1134
+ taskId: this.data.interactionId,
1135
+ requestInteractionId,
1136
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(
1137
+ (error as any).details || {}
1138
+ ),
1139
+ },
1140
+ ['operational', 'behavioral', 'business']
1141
+ );
1142
+ throw detailedError;
1143
+ }
1144
+ }
1145
+
1146
+ /**
1147
+ * This is used to wrap up the task.
1148
+ * @param wrapupPayload - WrapupPayLoad
1149
+ * @returns Promise<TaskResponse>
1150
+ * @throws Error
1151
+ * @example
1152
+ * ```typescript
1153
+ * task.wrapup(wrapupPayload).then(()=>{}).catch(()=>{})
1154
+ * ```
1155
+ */
1156
+ public async wrapup(wrapupPayload: WrapupPayLoad): Promise<TaskResponse> {
1157
+ this.cancelAutoWrapupTimer();
1158
+ LoggerProxy.log(`Starting task wrapup for taskId:${this.data.interactionId}`, {
1159
+ module: 'Task',
1160
+ method: 'wrapup',
1161
+ });
1162
+ try {
1163
+ this.metricsManager.timeEvent([
1164
+ METRIC_EVENT_NAMES.TASK_WRAPUP_SUCCESS,
1165
+ METRIC_EVENT_NAMES.TASK_WRAPUP_FAILED,
1166
+ ]);
1167
+ if (!this.data) {
1168
+ throw new Error('No task data available');
1169
+ }
1170
+ if (!wrapupPayload.auxCodeId || wrapupPayload.auxCodeId.length === 0) {
1171
+ throw new Error('AuxCodeId is required');
1172
+ }
1173
+ if (!wrapupPayload.wrapUpReason || wrapupPayload.wrapUpReason.length === 0) {
1174
+ throw new Error('WrapUpReason is required');
1175
+ }
1176
+
1177
+ const response = await this.contact.wrapup({
1178
+ interactionId: this.data.interactionId,
1179
+ data: wrapupPayload,
1180
+ });
1181
+
1182
+ this.metricsManager.trackEvent(
1183
+ METRIC_EVENT_NAMES.TASK_WRAPUP_SUCCESS,
1184
+ {
1185
+ taskId: this.data.interactionId,
1186
+ wrapUpCode: wrapupPayload.auxCodeId,
1187
+ wrapUpReason: wrapupPayload.wrapUpReason,
1188
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
1189
+ },
1190
+ ['operational', 'behavioral', 'business']
1191
+ );
1192
+
1193
+ return response;
1194
+ } catch (error) {
1195
+ const {error: detailedError} = getErrorDetails(error, 'wrapup', CC_FILE);
1196
+ this.metricsManager.trackEvent(
1197
+ METRIC_EVENT_NAMES.TASK_WRAPUP_FAILED,
1198
+ {
1199
+ taskId: this.data.interactionId,
1200
+ wrapUpCode: wrapupPayload.auxCodeId,
1201
+ wrapUpReason: wrapupPayload.wrapUpReason,
1202
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(
1203
+ (error as any).details || {}
1204
+ ),
1205
+ },
1206
+ ['operational', 'behavioral', 'business']
1207
+ );
1208
+ throw detailedError;
1209
+ }
1210
+ }
1211
+ }