@webex/contact-center 3.12.0-next.13 → 3.12.0-next.130

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 (312) hide show
  1. package/.sdd/manifest.json +884 -0
  2. package/AGENTS.md +94 -0
  3. package/ai-docs/ARCHITECTURE.md +168 -0
  4. package/ai-docs/CONTRACTS.md +53 -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 +382 -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 +971 -42
  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 +31 -3
  46. package/dist/constants.js.map +1 -1
  47. package/dist/index.js +45 -15
  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 +205 -0
  52. package/dist/metrics/behavioral-events.js.map +1 -1
  53. package/dist/metrics/constants.js +48 -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 +269 -9
  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/WebexCrossClientService.js +222 -0
  68. package/dist/services/WebexCrossClientService.js.map +1 -0
  69. package/dist/services/WxAppTelephonyMercurySync.js +108 -0
  70. package/dist/services/WxAppTelephonyMercurySync.js.map +1 -0
  71. package/dist/services/config/Util.js +11 -4
  72. package/dist/services/config/Util.js.map +1 -1
  73. package/dist/services/config/constants.js +41 -7
  74. package/dist/services/config/constants.js.map +1 -1
  75. package/dist/services/config/index.js +51 -0
  76. package/dist/services/config/index.js.map +1 -1
  77. package/dist/services/config/types.js +57 -12
  78. package/dist/services/config/types.js.map +1 -1
  79. package/dist/services/core/Err.js.map +1 -1
  80. package/dist/services/core/Utils.js +111 -32
  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 +28 -14
  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 +2 -1
  88. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  89. package/dist/services/core/websocket/types.js.map +1 -1
  90. package/dist/services/index.js +1 -1
  91. package/dist/services/index.js.map +1 -1
  92. package/dist/services/task/Task.js +754 -0
  93. package/dist/services/task/Task.js.map +1 -0
  94. package/dist/services/task/TaskFactory.js +49 -0
  95. package/dist/services/task/TaskFactory.js.map +1 -0
  96. package/dist/services/task/TaskManager.js +1172 -521
  97. package/dist/services/task/TaskManager.js.map +1 -1
  98. package/dist/services/task/TaskUtils.js +211 -27
  99. package/dist/services/task/TaskUtils.js.map +1 -1
  100. package/dist/services/task/WebexCallingUtils.js +70 -0
  101. package/dist/services/task/WebexCallingUtils.js.map +1 -0
  102. package/dist/services/task/constants.js +12 -2
  103. package/dist/services/task/constants.js.map +1 -1
  104. package/dist/services/task/contact.js +29 -0
  105. package/dist/services/task/contact.js.map +1 -1
  106. package/dist/services/task/dialer.js +78 -0
  107. package/dist/services/task/dialer.js.map +1 -1
  108. package/dist/services/task/digital/Digital.js +78 -0
  109. package/dist/services/task/digital/Digital.js.map +1 -0
  110. package/dist/services/task/state-machine/TaskStateMachine.js +982 -0
  111. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  112. package/dist/services/task/state-machine/actions.js +572 -0
  113. package/dist/services/task/state-machine/actions.js.map +1 -0
  114. package/dist/services/task/state-machine/constants.js +161 -0
  115. package/dist/services/task/state-machine/constants.js.map +1 -0
  116. package/dist/services/task/state-machine/guards.js +379 -0
  117. package/dist/services/task/state-machine/guards.js.map +1 -0
  118. package/dist/services/task/state-machine/index.js +53 -0
  119. package/dist/services/task/state-machine/index.js.map +1 -0
  120. package/dist/services/task/state-machine/types.js +54 -0
  121. package/dist/services/task/state-machine/types.js.map +1 -0
  122. package/dist/services/task/state-machine/uiControlsComputer.js +703 -0
  123. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  124. package/dist/services/task/taskDataNormalizer.js +99 -0
  125. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  126. package/dist/services/task/types.js +228 -5
  127. package/dist/services/task/types.js.map +1 -1
  128. package/dist/services/task/voice/Voice.js +1485 -0
  129. package/dist/services/task/voice/Voice.js.map +1 -0
  130. package/dist/services/task/voice/WebRTC.js +152 -0
  131. package/dist/services/task/voice/WebRTC.js.map +1 -0
  132. package/dist/services/task/voice/wxAppVoiceMethods.js +354 -0
  133. package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
  134. package/dist/services/wxAppDiagnosticLogging.js +122 -0
  135. package/dist/services/wxAppDiagnosticLogging.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 +193 -6
  139. package/dist/types/config.d.ts +13 -0
  140. package/dist/types/constants.d.ts +29 -1
  141. package/dist/types/index.d.ts +35 -8
  142. package/dist/types/metrics/MetricsManager.d.ts +6 -0
  143. package/dist/types/metrics/constants.d.ts +43 -1
  144. package/dist/types/services/AddressBook.d.ts +2 -1
  145. package/dist/types/services/AnswerCallOnWebexService.d.ts +38 -0
  146. package/dist/types/services/ApiAiAssistant.d.ts +56 -5
  147. package/dist/types/services/EntryPoint.d.ts +7 -5
  148. package/dist/types/services/Queue.d.ts +5 -3
  149. package/dist/types/services/UserPreference.d.ts +118 -0
  150. package/dist/types/services/WebexCrossClientService.d.ts +32 -0
  151. package/dist/types/services/WxAppTelephonyMercurySync.d.ts +28 -0
  152. package/dist/types/services/config/constants.d.ts +34 -5
  153. package/dist/types/services/config/types.d.ts +197 -12
  154. package/dist/types/services/core/Err.d.ts +6 -0
  155. package/dist/types/services/core/Utils.d.ts +35 -13
  156. package/dist/types/services/core/WebexRequest.d.ts +1 -0
  157. package/dist/types/services/core/types.d.ts +2 -0
  158. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
  159. package/dist/types/services/core/websocket/types.d.ts +1 -1
  160. package/dist/types/services/index.d.ts +1 -1
  161. package/dist/types/services/task/Task.d.ts +175 -0
  162. package/dist/types/services/task/TaskFactory.d.ts +13 -0
  163. package/dist/types/services/task/TaskUtils.d.ts +48 -2
  164. package/dist/types/services/task/WebexCallingUtils.d.ts +11 -0
  165. package/dist/types/services/task/constants.d.ts +10 -0
  166. package/dist/types/services/task/contact.d.ts +4 -0
  167. package/dist/types/services/task/dialer.d.ts +30 -0
  168. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  169. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1442 -0
  170. package/dist/types/services/task/state-machine/actions.d.ts +10 -0
  171. package/dist/types/services/task/state-machine/constants.d.ts +107 -0
  172. package/dist/types/services/task/state-machine/guards.d.ts +103 -0
  173. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  174. package/dist/types/services/task/state-machine/types.d.ts +277 -0
  175. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  176. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  177. package/dist/types/services/task/types.d.ts +680 -66
  178. package/dist/types/services/task/voice/Voice.d.ts +227 -0
  179. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  180. package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +55 -0
  181. package/dist/types/services/wxAppDiagnosticLogging.d.ts +51 -0
  182. package/dist/types/services/wxAppTelephonyUtils.d.ts +5 -0
  183. package/dist/types/types.d.ts +205 -9
  184. package/dist/types/utils/PageCache.d.ts +20 -3
  185. package/dist/types/webex.d.ts +1 -0
  186. package/dist/types.js +162 -4
  187. package/dist/types.js.map +1 -1
  188. package/dist/utils/PageCache.js +19 -5
  189. package/dist/utils/PageCache.js.map +1 -1
  190. package/dist/webex.js +22 -2
  191. package/dist/webex.js.map +1 -1
  192. package/package.json +16 -12
  193. package/src/cc.ts +1289 -112
  194. package/src/config.ts +13 -0
  195. package/src/constants.ts +29 -1
  196. package/src/index.ts +36 -5
  197. package/src/metrics/MetricsManager.ts +16 -0
  198. package/src/metrics/ai-docs/AGENTS.md +350 -0
  199. package/src/metrics/ai-docs/ARCHITECTURE.md +338 -0
  200. package/src/metrics/ai-docs/metrics-spec.md +921 -0
  201. package/src/metrics/behavioral-events.ts +212 -0
  202. package/src/metrics/constants.ts +52 -4
  203. package/src/services/AddressBook.ts +17 -6
  204. package/src/services/AnswerCallOnWebexService.ts +239 -0
  205. package/src/services/ApiAiAssistant.ts +356 -9
  206. package/src/services/EntryPoint.ts +59 -60
  207. package/src/services/Queue.ts +29 -12
  208. package/src/services/UserPreference.ts +509 -0
  209. package/src/services/WebexCrossClientService.ts +293 -0
  210. package/src/services/WxAppTelephonyMercurySync.ts +142 -0
  211. package/src/services/agent/ai-docs/AGENTS.md +240 -0
  212. package/src/services/agent/ai-docs/ARCHITECTURE.md +304 -0
  213. package/src/services/agent/ai-docs/agent-spec.md +505 -0
  214. package/src/services/ai-docs/AGENTS.md +386 -0
  215. package/src/services/ai-docs/services-spec.md +505 -0
  216. package/src/services/config/Util.ts +12 -3
  217. package/src/services/config/ai-docs/AGENTS.md +255 -0
  218. package/src/services/config/ai-docs/ARCHITECTURE.md +426 -0
  219. package/src/services/config/ai-docs/config-spec.md +686 -0
  220. package/src/services/config/constants.ts +46 -6
  221. package/src/services/config/index.ts +65 -0
  222. package/src/services/config/types.ts +197 -13
  223. package/src/services/core/Err.ts +3 -0
  224. package/src/services/core/Utils.ts +127 -37
  225. package/src/services/core/WebexRequest.ts +3 -1
  226. package/src/services/core/ai-docs/AGENTS.md +381 -0
  227. package/src/services/core/ai-docs/ARCHITECTURE.md +698 -0
  228. package/src/services/core/ai-docs/core-spec.md +787 -0
  229. package/src/services/core/aqm-reqs.ts +30 -15
  230. package/src/services/core/types.ts +2 -0
  231. package/src/services/core/websocket/WebSocketManager.ts +2 -0
  232. package/src/services/core/websocket/types.ts +1 -1
  233. package/src/services/index.ts +1 -1
  234. package/src/services/task/Task.ts +908 -0
  235. package/src/services/task/TaskFactory.ts +60 -0
  236. package/src/services/task/TaskManager.ts +1419 -608
  237. package/src/services/task/TaskUtils.ts +303 -25
  238. package/src/services/task/WebexCallingUtils.ts +136 -0
  239. package/src/services/task/ai-docs/AGENTS.md +457 -0
  240. package/src/services/task/ai-docs/ARCHITECTURE.md +595 -0
  241. package/src/services/task/ai-docs/task-spec.md +1474 -0
  242. package/src/services/task/constants.ts +10 -0
  243. package/src/services/task/contact.ts +30 -0
  244. package/src/services/task/dialer.ts +80 -0
  245. package/src/services/task/digital/Digital.ts +97 -0
  246. package/src/services/task/state-machine/TaskStateMachine.ts +1320 -0
  247. package/src/services/task/state-machine/actions.ts +741 -0
  248. package/src/services/task/state-machine/ai-docs/AGENTS.md +460 -0
  249. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1156 -0
  250. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2247 -0
  251. package/src/services/task/state-machine/constants.ts +172 -0
  252. package/src/services/task/state-machine/guards.ts +442 -0
  253. package/src/services/task/state-machine/index.ts +28 -0
  254. package/src/services/task/state-machine/types.ts +258 -0
  255. package/src/services/task/state-machine/uiControlsComputer.ts +1135 -0
  256. package/src/services/task/taskDataNormalizer.ts +137 -0
  257. package/src/services/task/types.ts +819 -70
  258. package/src/services/task/voice/Voice.ts +1851 -0
  259. package/src/services/task/voice/WebRTC.ts +191 -0
  260. package/src/services/task/voice/wxAppVoiceMethods.ts +516 -0
  261. package/src/services/wxAppDiagnosticLogging.ts +174 -0
  262. package/src/services/wxAppTelephonyUtils.ts +14 -0
  263. package/src/types.ts +262 -11
  264. package/src/utils/AGENTS.md +289 -0
  265. package/src/utils/PageCache.ts +38 -5
  266. package/src/utils/ai-docs/utils-spec.md +391 -0
  267. package/src/webex.js +10 -0
  268. package/test/unit/spec/cc.ts +2210 -119
  269. package/test/unit/spec/logger-proxy.ts +70 -0
  270. package/test/unit/spec/metrics/MetricsManager.ts +25 -0
  271. package/test/unit/spec/metrics/behavioral-events.ts +60 -0
  272. package/test/unit/spec/services/AddressBook.ts +37 -6
  273. package/test/unit/spec/services/AnswerCallOnWebexService.ts +264 -0
  274. package/test/unit/spec/services/ApiAiAssistant.ts +311 -21
  275. package/test/unit/spec/services/EntryPoint.ts +87 -40
  276. package/test/unit/spec/services/Queue.ts +123 -12
  277. package/test/unit/spec/services/UserPreference.ts +401 -0
  278. package/test/unit/spec/services/WebCallingService.ts +7 -1
  279. package/test/unit/spec/services/WebexCrossClientService.ts +467 -0
  280. package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +136 -0
  281. package/test/unit/spec/services/config/Util.ts +129 -0
  282. package/test/unit/spec/services/config/index.ts +97 -29
  283. package/test/unit/spec/services/core/Utils.ts +425 -8
  284. package/test/unit/spec/services/core/WebexRequest.ts +3 -1
  285. package/test/unit/spec/services/core/aqm-reqs.ts +113 -1
  286. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +66 -40
  287. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  288. package/test/unit/spec/services/task/Task.ts +745 -0
  289. package/test/unit/spec/services/task/TaskFactory.ts +99 -0
  290. package/test/unit/spec/services/task/TaskManager.ts +2518 -1494
  291. package/test/unit/spec/services/task/TaskUtils.ts +235 -0
  292. package/test/unit/spec/services/task/WebexCallingUtils.ts +153 -0
  293. package/test/unit/spec/services/task/contact.ts +33 -0
  294. package/test/unit/spec/services/task/dialer.ts +190 -0
  295. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  296. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +4060 -0
  297. package/test/unit/spec/services/task/state-machine/guards.ts +839 -0
  298. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  299. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +3101 -0
  300. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  301. package/test/unit/spec/services/task/voice/Voice.ts +2061 -0
  302. package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
  303. package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +603 -0
  304. package/test/unit/spec/services/wxAppDiagnosticLogging.ts +143 -0
  305. package/test/unit/spec/wellnessSampleUtils.ts +288 -0
  306. package/umd/contact-center.min.js +2 -2
  307. package/umd/contact-center.min.js.map +1 -1
  308. package/dist/services/task/index.js +0 -1525
  309. package/dist/services/task/index.js.map +0 -1
  310. package/dist/types/services/task/index.d.ts +0 -650
  311. package/src/services/task/index.ts +0 -1801
  312. package/test/unit/spec/services/task/index.ts +0 -2184
@@ -0,0 +1,603 @@
1
+ import {expect} from '@jest/globals';
2
+ import {
3
+ isWebexAppCallingOffer,
4
+ getCallingDeviceDetails,
5
+ getWebexCallingCallId,
6
+ acceptOnWebex,
7
+ rejectOnWebex,
8
+ toggleMuteOnWebex,
9
+ transmitDtmfOnWebex,
10
+ runWxAppAccept,
11
+ runWxAppOutdialDecline,
12
+ runWxAppReject,
13
+ runWxAppToggleMute,
14
+ runWxAppTransmitDtmf,
15
+ mapWxAppVoiceError,
16
+ WxAppVoiceDeps,
17
+ WxAppVoiceLifecycle,
18
+ } from '../../../../../../src/services/task/voice/wxAppVoiceMethods';
19
+ import {METHODS} from '../../../../../../src/constants';
20
+ import {TaskState} from '../../../../../../src/services/task/state-machine';
21
+ import {TaskData} from '../../../../../../src/services/task/types';
22
+ import AnswerCallOnWebexService from '../../../../../../src/services/AnswerCallOnWebexService';
23
+ import MetricsManager from '../../../../../../src/metrics/MetricsManager';
24
+ import {METRIC_EVENT_NAMES} from '../../../../../../src/metrics/constants';
25
+
26
+ const WX_APP_DEVICE = {
27
+ deviceType: 'wxApp',
28
+ deviceId: 'device-id-1',
29
+ deviceCallId: 'call-id-1',
30
+ };
31
+
32
+ function makeParticipants() {
33
+ return {
34
+ 'agent-1': {id: 'agent-1', ...WX_APP_DEVICE},
35
+ 'customer-1': {id: 'customer-1', pType: 'Customer'},
36
+ };
37
+ }
38
+
39
+ function makeTaskData(overrides: Partial<TaskData> = {}): TaskData {
40
+ return {
41
+ interactionId: 'interaction-1',
42
+ agentId: 'agent-1',
43
+ mediaResourceId: 'media-1',
44
+ interaction: {
45
+ participants: makeParticipants(),
46
+ },
47
+ ...overrides,
48
+ } as unknown as TaskData;
49
+ }
50
+
51
+ function makeMockService(): jest.Mocked<AnswerCallOnWebexService> {
52
+ return {
53
+ answerCall: jest.fn().mockResolvedValue({}),
54
+ rejectCall: jest.fn().mockResolvedValue({}),
55
+ muteCall: jest.fn().mockResolvedValue({}),
56
+ unmuteCall: jest.fn().mockResolvedValue({}),
57
+ transmitDtmf: jest.fn().mockResolvedValue({}),
58
+ } as unknown as jest.Mocked<AnswerCallOnWebexService>;
59
+ }
60
+
61
+ function makeMockMetricsManager(): jest.Mocked<Pick<MetricsManager, 'timeEvent' | 'trackEvent'>> {
62
+ return {
63
+ timeEvent: jest.fn(),
64
+ trackEvent: jest.fn(),
65
+ };
66
+ }
67
+
68
+ function makeDeps(overrides: Partial<WxAppVoiceDeps> = {}): WxAppVoiceDeps {
69
+ let muted = false;
70
+
71
+ return {
72
+ enableWxBetterTogether: true,
73
+ answerCallOnWebexService: makeMockService(),
74
+ agentId: 'agent-1',
75
+ metricsManager: makeMockMetricsManager() as unknown as MetricsManager,
76
+ getTaskData: () => makeTaskData(),
77
+ getTaskState: () => TaskState.OFFERED,
78
+ getWxAppMuted: () => muted,
79
+ setWxAppMuted: (val) => {
80
+ muted = val;
81
+ },
82
+ ...overrides,
83
+ };
84
+ }
85
+
86
+ function makeLifecycle(overrides: Partial<WxAppVoiceLifecycle> = {}): WxAppVoiceLifecycle {
87
+ return {
88
+ setWxAppAcceptInFlight: jest.fn(),
89
+ setWxAppAnswerPending: jest.fn(),
90
+ resetWxAppMuted: jest.fn(),
91
+ syncWxAppMuteFromCallDetails: jest.fn().mockResolvedValue(undefined),
92
+ mapWxAppVoiceError: jest.fn((error: unknown) => {
93
+ throw error;
94
+ }),
95
+ ...overrides,
96
+ };
97
+ }
98
+
99
+ describe('isWebexAppCallingOffer', () => {
100
+ it('returns true when enableWxBetterTogether is true, state is OFFERED, and wxApp device details exist', () => {
101
+ expect(isWebexAppCallingOffer(makeDeps())).toBe(true);
102
+ });
103
+
104
+ it('returns false when enableWxBetterTogether is false', () => {
105
+ expect(isWebexAppCallingOffer(makeDeps({enableWxBetterTogether: false}))).toBe(false);
106
+ });
107
+
108
+ it('returns false when task state is not OFFERED', () => {
109
+ expect(isWebexAppCallingOffer(makeDeps({getTaskState: () => TaskState.CONNECTED}))).toBe(false);
110
+ });
111
+
112
+ it('returns false when no wxApp participant is found', () => {
113
+ const deps = makeDeps({
114
+ getTaskData: () =>
115
+ makeTaskData({
116
+ interaction: {
117
+ participants: {'customer-1': {id: 'customer-1'}},
118
+ } as any,
119
+ }),
120
+ });
121
+
122
+ expect(isWebexAppCallingOffer(deps)).toBe(false);
123
+ });
124
+ });
125
+
126
+ describe('getCallingDeviceDetails', () => {
127
+ it('returns device details for wxApp participant', () => {
128
+ const result = getCallingDeviceDetails(makeDeps());
129
+
130
+ expect(result).toEqual({
131
+ deviceType: 'wxApp',
132
+ deviceId: 'device-id-1',
133
+ deviceCallId: 'call-id-1',
134
+ });
135
+ });
136
+
137
+ it('returns undefined when no wxApp participant is present', () => {
138
+ const deps = makeDeps({
139
+ getTaskData: () =>
140
+ makeTaskData({
141
+ interaction: {
142
+ participants: {'customer-1': {id: 'customer-1'}},
143
+ } as any,
144
+ }),
145
+ });
146
+
147
+ expect(getCallingDeviceDetails(deps)).toBeUndefined();
148
+ });
149
+ });
150
+
151
+ describe('getWebexCallingCallId', () => {
152
+ it('returns null when state is OFFERED', () => {
153
+ expect(getWebexCallingCallId(makeDeps({getTaskState: () => TaskState.OFFERED}))).toBeNull();
154
+ });
155
+
156
+ it('returns null when state is IDLE', () => {
157
+ expect(getWebexCallingCallId(makeDeps({getTaskState: () => TaskState.IDLE}))).toBeNull();
158
+ });
159
+
160
+ it('returns null when state is TERMINATED', () => {
161
+ expect(getWebexCallingCallId(makeDeps({getTaskState: () => TaskState.TERMINATED}))).toBeNull();
162
+ });
163
+
164
+ it('returns null when state is COMPLETED', () => {
165
+ expect(getWebexCallingCallId(makeDeps({getTaskState: () => TaskState.COMPLETED}))).toBeNull();
166
+ });
167
+
168
+ it('returns null when state is WRAPPING_UP', () => {
169
+ expect(getWebexCallingCallId(makeDeps({getTaskState: () => TaskState.WRAPPING_UP}))).toBeNull();
170
+ });
171
+
172
+ it('returns the deviceCallId when state is CONNECTED', () => {
173
+ expect(getWebexCallingCallId(makeDeps({getTaskState: () => TaskState.CONNECTED}))).toBe(
174
+ 'call-id-1'
175
+ );
176
+ });
177
+
178
+ it('returns null when state is CONNECTED but no wxApp participant', () => {
179
+ const deps = makeDeps({
180
+ getTaskState: () => TaskState.CONNECTED,
181
+ getTaskData: () =>
182
+ makeTaskData({
183
+ interaction: {
184
+ participants: {'customer-1': {id: 'customer-1'}},
185
+ } as any,
186
+ }),
187
+ });
188
+
189
+ expect(getWebexCallingCallId(deps)).toBeNull();
190
+ });
191
+ });
192
+
193
+ describe('acceptOnWebex', () => {
194
+ it('calls answerCall with deviceCallId and deviceId', async () => {
195
+ const mockSvc = makeMockService();
196
+ const deps = makeDeps({answerCallOnWebexService: mockSvc});
197
+
198
+ await acceptOnWebex(deps);
199
+
200
+ expect(mockSvc.answerCall).toHaveBeenCalledWith({
201
+ callId: 'call-id-1',
202
+ endpointId: 'device-id-1',
203
+ lineOwnerId: undefined,
204
+ });
205
+ });
206
+
207
+ it('passes lineOwnerId when provided', async () => {
208
+ const mockSvc = makeMockService();
209
+ const deps = makeDeps({answerCallOnWebexService: mockSvc});
210
+
211
+ await acceptOnWebex(deps, {lineOwnerId: 'lo-1'});
212
+
213
+ expect(mockSvc.answerCall).toHaveBeenCalledWith(
214
+ expect.objectContaining({lineOwnerId: 'lo-1'})
215
+ );
216
+ });
217
+
218
+ it('defaults lineOwnerId from encoded participant owner when not provided', async () => {
219
+ const encodedOwner = Buffer.from('people/lo-decoded').toString('base64');
220
+ const mockSvc = makeMockService();
221
+ const deps = makeDeps({
222
+ answerCallOnWebexService: mockSvc,
223
+ getTaskData: () =>
224
+ makeTaskData({
225
+ interaction: {
226
+ participants: {
227
+ 'agent-1': {id: 'agent-1', ...WX_APP_DEVICE, lineOwnerId: encodedOwner},
228
+ 'customer-1': {id: 'customer-1', pType: 'Customer'},
229
+ },
230
+ } as any,
231
+ }),
232
+ });
233
+
234
+ await acceptOnWebex(deps);
235
+
236
+ expect(mockSvc.answerCall).toHaveBeenCalledWith(
237
+ expect.objectContaining({lineOwnerId: 'lo-decoded'})
238
+ );
239
+ });
240
+
241
+ it('throws when enableWxBetterTogether is false', async () => {
242
+ const deps = makeDeps({enableWxBetterTogether: false});
243
+
244
+ await expect(acceptOnWebex(deps)).rejects.toThrow();
245
+ });
246
+
247
+ it('throws when task is not in OFFERED state', async () => {
248
+ const deps = makeDeps({getTaskState: () => TaskState.CONNECTED});
249
+
250
+ await expect(acceptOnWebex(deps)).rejects.toThrow();
251
+ });
252
+
253
+ it('throws when device details are unavailable', async () => {
254
+ const deps = makeDeps({
255
+ getTaskData: () =>
256
+ makeTaskData({
257
+ interaction: {
258
+ participants: {'customer-1': {id: 'customer-1'}},
259
+ } as any,
260
+ }),
261
+ });
262
+
263
+ await expect(acceptOnWebex(deps)).rejects.toThrow();
264
+ });
265
+ });
266
+
267
+ describe('rejectOnWebex', () => {
268
+ it('calls rejectCall with deviceCallId', async () => {
269
+ const mockSvc = makeMockService();
270
+ const deps = makeDeps({answerCallOnWebexService: mockSvc});
271
+
272
+ await rejectOnWebex(deps);
273
+
274
+ expect(mockSvc.rejectCall).toHaveBeenCalledWith({
275
+ callId: 'call-id-1',
276
+ lineOwnerId: undefined,
277
+ });
278
+ });
279
+
280
+ it('throws when task is not in OFFERED state', async () => {
281
+ const deps = makeDeps({getTaskState: () => TaskState.CONNECTED});
282
+
283
+ await expect(rejectOnWebex(deps)).rejects.toThrow();
284
+ });
285
+
286
+ it('throws when enableWxBetterTogether is false', async () => {
287
+ const deps = makeDeps({enableWxBetterTogether: false});
288
+
289
+ await expect(rejectOnWebex(deps)).rejects.toThrow();
290
+ });
291
+ });
292
+
293
+ describe('toggleMuteOnWebex', () => {
294
+ it('calls muteCall when not currently muted', async () => {
295
+ const mockSvc = makeMockService();
296
+ let muted = false;
297
+ const deps = makeDeps({
298
+ answerCallOnWebexService: mockSvc,
299
+ getTaskState: () => TaskState.CONNECTED,
300
+ getWxAppMuted: () => muted,
301
+ setWxAppMuted: (val) => {
302
+ muted = val;
303
+ },
304
+ });
305
+
306
+ await toggleMuteOnWebex(deps);
307
+
308
+ expect(mockSvc.muteCall).toHaveBeenCalledWith({callId: 'call-id-1', lineOwnerId: undefined});
309
+ expect(muted).toBe(true);
310
+ });
311
+
312
+ it('calls unmuteCall when currently muted', async () => {
313
+ const mockSvc = makeMockService();
314
+ let muted = true;
315
+ const deps = makeDeps({
316
+ answerCallOnWebexService: mockSvc,
317
+ getTaskState: () => TaskState.CONNECTED,
318
+ getWxAppMuted: () => muted,
319
+ setWxAppMuted: (val) => {
320
+ muted = val;
321
+ },
322
+ });
323
+
324
+ await toggleMuteOnWebex(deps);
325
+
326
+ expect(mockSvc.unmuteCall).toHaveBeenCalled();
327
+ expect(muted).toBe(false);
328
+ });
329
+
330
+ it('throws when call ID is unavailable (state is OFFERED)', async () => {
331
+ const deps = makeDeps({getTaskState: () => TaskState.OFFERED});
332
+
333
+ await expect(toggleMuteOnWebex(deps)).rejects.toThrow('WxApp call ID is unavailable');
334
+ });
335
+
336
+ it('throws when enableWxBetterTogether is false', async () => {
337
+ const deps = makeDeps({enableWxBetterTogether: false, getTaskState: () => TaskState.CONNECTED});
338
+
339
+ await expect(toggleMuteOnWebex(deps)).rejects.toThrow();
340
+ });
341
+
342
+ it('uses options.muted=true even when internal wxAppMuted is true (desync regression)', async () => {
343
+ const mockSvc = makeMockService();
344
+ let muted = true;
345
+ const deps = makeDeps({
346
+ answerCallOnWebexService: mockSvc,
347
+ getTaskState: () => TaskState.CONNECTED,
348
+ getWxAppMuted: () => muted,
349
+ setWxAppMuted: (val) => {
350
+ muted = val;
351
+ },
352
+ });
353
+
354
+ await toggleMuteOnWebex(deps, {muted: true});
355
+
356
+ expect(mockSvc.muteCall).toHaveBeenCalledWith({callId: 'call-id-1', lineOwnerId: undefined});
357
+ expect(mockSvc.unmuteCall).not.toHaveBeenCalled();
358
+ expect(muted).toBe(true);
359
+ });
360
+
361
+ it('uses options.muted=false even when internal wxAppMuted is false (desync regression)', async () => {
362
+ const mockSvc = makeMockService();
363
+ let muted = false;
364
+ const deps = makeDeps({
365
+ answerCallOnWebexService: mockSvc,
366
+ getTaskState: () => TaskState.CONNECTED,
367
+ getWxAppMuted: () => muted,
368
+ setWxAppMuted: (val) => {
369
+ muted = val;
370
+ },
371
+ });
372
+
373
+ await toggleMuteOnWebex(deps, {muted: false});
374
+
375
+ expect(mockSvc.unmuteCall).toHaveBeenCalledWith({callId: 'call-id-1', lineOwnerId: undefined});
376
+ expect(mockSvc.muteCall).not.toHaveBeenCalled();
377
+ expect(muted).toBe(false);
378
+ });
379
+ });
380
+
381
+ describe('transmitDtmfOnWebex', () => {
382
+ it('calls transmitDtmf with callId and dtmf', async () => {
383
+ const mockSvc = makeMockService();
384
+ const deps = makeDeps({
385
+ answerCallOnWebexService: mockSvc,
386
+ getTaskState: () => TaskState.CONNECTED,
387
+ });
388
+
389
+ await transmitDtmfOnWebex(deps, {dtmf: '5'});
390
+
391
+ expect(mockSvc.transmitDtmf).toHaveBeenCalledWith({
392
+ callId: 'call-id-1',
393
+ dtmf: '5',
394
+ lineOwnerId: undefined,
395
+ });
396
+ });
397
+
398
+ it('passes lineOwnerId when provided', async () => {
399
+ const mockSvc = makeMockService();
400
+ const deps = makeDeps({
401
+ answerCallOnWebexService: mockSvc,
402
+ getTaskState: () => TaskState.CONNECTED,
403
+ });
404
+
405
+ await transmitDtmfOnWebex(deps, {dtmf: '9', lineOwnerId: 'lo-1'});
406
+
407
+ expect(mockSvc.transmitDtmf).toHaveBeenCalledWith(
408
+ expect.objectContaining({lineOwnerId: 'lo-1'})
409
+ );
410
+ });
411
+
412
+ it('throws when call ID is unavailable', async () => {
413
+ const deps = makeDeps({getTaskState: () => TaskState.OFFERED});
414
+
415
+ await expect(transmitDtmfOnWebex(deps, {dtmf: '1'})).rejects.toThrow(
416
+ 'WxApp call ID is unavailable'
417
+ );
418
+ });
419
+ });
420
+
421
+ describe('runWxAppAccept', () => {
422
+ it('sets in-flight and pending flags, resets mute, and syncs after answer', async () => {
423
+ const deps = makeDeps();
424
+ const lifecycle = makeLifecycle();
425
+
426
+ await runWxAppAccept(deps, lifecycle);
427
+
428
+ expect(deps.metricsManager.timeEvent).toHaveBeenCalledWith([
429
+ METRIC_EVENT_NAMES.WXAPP_TASK_ACCEPT_SUCCESS,
430
+ METRIC_EVENT_NAMES.WXAPP_TASK_ACCEPT_FAILED,
431
+ ]);
432
+ expect(deps.metricsManager.trackEvent).toHaveBeenCalledWith(
433
+ METRIC_EVENT_NAMES.WXAPP_TASK_ACCEPT_SUCCESS,
434
+ {taskId: 'interaction-1'},
435
+ ['operational', 'behavioral']
436
+ );
437
+ expect(lifecycle.setWxAppAcceptInFlight).toHaveBeenNthCalledWith(1, true);
438
+ expect(lifecycle.setWxAppAnswerPending).toHaveBeenCalledWith(true);
439
+ expect(deps.answerCallOnWebexService!.answerCall).toHaveBeenCalled();
440
+ expect(lifecycle.resetWxAppMuted).toHaveBeenCalled();
441
+ expect(lifecycle.syncWxAppMuteFromCallDetails).toHaveBeenCalled();
442
+ expect(lifecycle.setWxAppAcceptInFlight).toHaveBeenLastCalledWith(false);
443
+ });
444
+
445
+ it('clears pending and maps error when answer fails', async () => {
446
+ const deps = makeDeps();
447
+ const error = Object.assign(new Error('answer failed'), {trackingId: 'track-1'});
448
+ deps.answerCallOnWebexService!.answerCall = jest.fn().mockRejectedValue(error);
449
+ const lifecycle = makeLifecycle();
450
+
451
+ await expect(runWxAppAccept(deps, lifecycle)).rejects.toThrow('answer failed');
452
+
453
+ expect(deps.metricsManager.trackEvent).toHaveBeenCalledWith(
454
+ METRIC_EVENT_NAMES.WXAPP_TASK_ACCEPT_FAILED,
455
+ expect.objectContaining({
456
+ taskId: 'interaction-1',
457
+ trackingId: 'track-1',
458
+ }),
459
+ ['operational', 'behavioral']
460
+ );
461
+ expect(lifecycle.setWxAppAnswerPending).toHaveBeenCalledWith(false);
462
+ expect(lifecycle.mapWxAppVoiceError).toHaveBeenCalledWith(error, METHODS.ACCEPT);
463
+ expect(lifecycle.setWxAppAcceptInFlight).toHaveBeenLastCalledWith(false);
464
+ });
465
+ });
466
+
467
+ describe('runWxAppReject', () => {
468
+ it('tracks wxApp decline success metric', async () => {
469
+ const deps = makeDeps();
470
+ const lifecycle = makeLifecycle();
471
+
472
+ await runWxAppReject(deps, lifecycle);
473
+
474
+ expect(deps.metricsManager.trackEvent).toHaveBeenCalledWith(
475
+ METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_SUCCESS,
476
+ {taskId: 'interaction-1'},
477
+ ['operational', 'behavioral']
478
+ );
479
+ });
480
+
481
+ it('maps error when reject fails', async () => {
482
+ const deps = makeDeps();
483
+ const error = new Error('reject failed');
484
+ deps.answerCallOnWebexService!.rejectCall = jest.fn().mockRejectedValue(error);
485
+ const lifecycle = makeLifecycle();
486
+
487
+ await expect(runWxAppReject(deps, lifecycle)).rejects.toThrow('reject failed');
488
+
489
+ expect(deps.metricsManager.trackEvent).toHaveBeenCalledWith(
490
+ METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_FAILED,
491
+ expect.objectContaining({taskId: 'interaction-1'}),
492
+ ['operational', 'behavioral']
493
+ );
494
+ expect(lifecycle.mapWxAppVoiceError).toHaveBeenCalledWith(error, METHODS.REJECT);
495
+ });
496
+ });
497
+
498
+ describe('runWxAppOutdialDecline', () => {
499
+ it('tracks wxApp decline success metric for outdial cancel', async () => {
500
+ const deps = makeDeps();
501
+ const cancelResult = {type: 'RoutingMessage', trackingId: 'track-1', data: {}};
502
+
503
+ const result = await runWxAppOutdialDecline(deps, async () => cancelResult);
504
+
505
+ expect(result).toBe(cancelResult);
506
+ expect(deps.metricsManager.timeEvent).toHaveBeenCalledWith([
507
+ METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_SUCCESS,
508
+ METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_FAILED,
509
+ ]);
510
+ expect(deps.metricsManager.trackEvent).toHaveBeenCalledWith(
511
+ METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_SUCCESS,
512
+ {taskId: 'interaction-1'},
513
+ ['operational', 'behavioral']
514
+ );
515
+ });
516
+
517
+ it('tracks wxApp decline failed metric and rethrows when cancel fails', async () => {
518
+ const deps = makeDeps();
519
+ const error = new Error('cancel failed');
520
+
521
+ await expect(
522
+ runWxAppOutdialDecline(deps, async () => {
523
+ throw error;
524
+ })
525
+ ).rejects.toThrow('cancel failed');
526
+
527
+ expect(deps.metricsManager.trackEvent).toHaveBeenCalledWith(
528
+ METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_FAILED,
529
+ expect.objectContaining({taskId: 'interaction-1'}),
530
+ ['operational', 'behavioral']
531
+ );
532
+ });
533
+
534
+ it('preserves AQM correlation fields when cancel fails with error.details', async () => {
535
+ const deps = makeDeps();
536
+ const error = Object.assign(new Error('AQM failed'), {
537
+ details: {
538
+ trackingId: 'aqm-track-1',
539
+ orgId: 'org-1',
540
+ type: 'Service.aqm.task.cancel',
541
+ data: {reason: 'TASK_NOT_FOUND', reasonCode: 404, agentId: 'agent-1'},
542
+ },
543
+ });
544
+
545
+ await expect(
546
+ runWxAppOutdialDecline(deps, async () => {
547
+ throw error;
548
+ })
549
+ ).rejects.toThrow('AQM failed');
550
+
551
+ expect(deps.metricsManager.trackEvent).toHaveBeenCalledWith(
552
+ METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_FAILED,
553
+ expect.objectContaining({
554
+ taskId: 'interaction-1',
555
+ trackingId: 'aqm-track-1',
556
+ failureReason: 'TASK_NOT_FOUND',
557
+ reasonCode: 404,
558
+ }),
559
+ ['operational', 'behavioral']
560
+ );
561
+ });
562
+ });
563
+
564
+ describe('mapWxAppVoiceError', () => {
565
+ it('rethrows normalized wxApp telephony errors without remapping', () => {
566
+ const normalized = Object.assign(new Error('TELEPHONY_ERROR'), {
567
+ isWxAppTelephonyError: true,
568
+ trackingId: 'track-wxapp-1',
569
+ });
570
+
571
+ expect(() => mapWxAppVoiceError(normalized, 'accept', 'cc')).toThrow(normalized);
572
+ });
573
+ });
574
+
575
+ describe('runWxAppToggleMute metrics', () => {
576
+ it('tracks mute success metric', async () => {
577
+ const deps = makeDeps({getTaskState: () => TaskState.CONNECTED});
578
+ const lifecycle = makeLifecycle();
579
+
580
+ await runWxAppToggleMute(deps, lifecycle);
581
+
582
+ expect(deps.metricsManager.trackEvent).toHaveBeenCalledWith(
583
+ METRIC_EVENT_NAMES.WXAPP_TASK_MUTE_SUCCESS,
584
+ expect.objectContaining({taskId: 'interaction-1', targetMuted: true}),
585
+ ['operational', 'behavioral']
586
+ );
587
+ });
588
+ });
589
+
590
+ describe('runWxAppTransmitDtmf metrics', () => {
591
+ it('tracks dtmf success metric with digit length only', async () => {
592
+ const deps = makeDeps({getTaskState: () => TaskState.CONNECTED});
593
+ const lifecycle = makeLifecycle();
594
+
595
+ await runWxAppTransmitDtmf(deps, lifecycle, {dtmf: '123'});
596
+
597
+ expect(deps.metricsManager.trackEvent).toHaveBeenCalledWith(
598
+ METRIC_EVENT_NAMES.WXAPP_TASK_DTMF_SUCCESS,
599
+ {taskId: 'interaction-1', dtmfLength: 3},
600
+ ['operational', 'behavioral']
601
+ );
602
+ });
603
+ });