@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
package/src/cc.ts CHANGED
@@ -25,6 +25,10 @@ import {
25
25
  UploadLogsResponse,
26
26
  UpdateDeviceTypeResponse,
27
27
  GenericError,
28
+ ConfigFlags,
29
+ WellnessBreakEvent,
30
+ WellnessBreakNotificationAction,
31
+ WELLNESS_BREAK_NOTIFICATION_ACTIONS,
28
32
  } from './types';
29
33
  import {
30
34
  READY,
@@ -37,6 +41,7 @@ import {
37
41
  UNKNOWN_ERROR,
38
42
  MERCURY_DISCONNECTED_SUCCESS,
39
43
  METHODS,
44
+ WELLNESS_BREAK_HANDLER,
40
45
  } from './constants';
41
46
  import {AGENT_STATE_AVAILABLE, AGENT_STATE_AVAILABLE_ID} from './services/config/constants';
42
47
  import {AGENT, RTD_SUBSCRIBE_API, SUBSCRIBE_API, WEB_RTC_PREFIX} from './services/constants';
@@ -44,17 +49,22 @@ import Services from './services';
44
49
  import WebexRequest from './services/core/WebexRequest';
45
50
  import LoggerProxy from './logger-proxy';
46
51
  import {StateChange, Logout, StateChangeSuccess, AGENT_EVENTS} from './services/agent/types';
47
- import {getErrorDetails, isValidDialNumber} from './services/core/Utils';
52
+ import {getErrorDetails, isRecord, isValidDialNumber} from './services/core/Utils';
48
53
  import {
49
54
  Profile,
50
55
  WelcomeEvent,
51
56
  CC_EVENTS,
52
57
  OutdialAniEntriesResponse,
53
58
  OutdialAniParams,
59
+ Entity,
54
60
  } from './services/config/types';
55
61
  import {ConnectionLostDetails} from './services/core/websocket/types';
56
62
  import TaskManager from './services/task/TaskManager';
57
63
  import WebCallingService from './services/WebCallingService';
64
+ import AnswerCallOnWebexService from './services/AnswerCallOnWebexService';
65
+ import WebexCrossClientService from './services/WebexCrossClientService';
66
+ import WxAppTelephonyMercurySync from './services/WxAppTelephonyMercurySync';
67
+ import {logWxAppSessionReadiness} from './services/wxAppDiagnosticLogging';
58
68
  import {
59
69
  ITask,
60
70
  TASK_EVENTS,
@@ -69,6 +79,7 @@ import {EntryPoint} from './services/EntryPoint';
69
79
  import {AddressBook} from './services/AddressBook';
70
80
  import {Queue} from './services/Queue';
71
81
  import {ApiAIAssistant} from './services/ApiAiAssistant';
82
+ import {UserPreference} from './services/UserPreference';
72
83
  import type {
73
84
  EntryPointListResponse,
74
85
  EntryPointSearchParams,
@@ -230,6 +241,34 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
230
241
  */
231
242
  private webCallingService: WebCallingService;
232
243
 
244
+ /**
245
+ * REST telephony for wxApp thick-client answer (WXCC-6026)
246
+ * @private
247
+ */
248
+ private answerCallOnWebexService: AnswerCallOnWebexService;
249
+
250
+ /**
251
+ * usersub cross-client state for Webex App toast suppression
252
+ * @private
253
+ */
254
+ private webexCrossClientService: WebexCrossClientService;
255
+
256
+ /**
257
+ * Mercury telephony mute sync for wxApp thick-client answer
258
+ * @private
259
+ */
260
+ private wxAppTelephonyMercurySync: WxAppTelephonyMercurySync;
261
+
262
+ /** True when wxApp mute sync opened Mercury (Extension/DN-only profiles). */
263
+ private wxAppMercuryConnectedByCc = false;
264
+
265
+ /** True when wxApp mute sync registered the Webex device. */
266
+ private wxAppDeviceRegisteredByCc = false;
267
+
268
+ private static readonly WXAPP_FALSE_PUBLISH_RETRY_DELAY_MS = 30_000;
269
+ private static readonly WXAPP_FALSE_PUBLISH_MAX_RETRIES = 3;
270
+ private wxAppFalsePublishRetryTimer?: ReturnType<typeof setTimeout>;
271
+
233
272
  /**
234
273
  * Core service managers for Contact Center operations
235
274
  * Includes agent, connection, and configuration services
@@ -262,6 +301,19 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
262
301
  */
263
302
  private metricsManager: MetricsManager;
264
303
 
304
+ /** Current login/relogin session used for agent-scoped wellness validation. */
305
+ private currentAgentSessionId?: string;
306
+
307
+ /** Registered-session cache for the system WellbeingBreak idle code. */
308
+ private wellbeingBreakIdleCode?: Entity;
309
+
310
+ /** Registration generation that owns the wellness idle-code cache and in-flight lookups. */
311
+ private wellnessRegistrationGeneration = 0;
312
+
313
+ private updateWellnessSession(agentSessionId?: string): void {
314
+ this.currentAgentSessionId = agentSessionId;
315
+ }
316
+
265
317
  /**
266
318
  * API instance for managing Webex Contact Center entry points
267
319
  * Provides functionality to fetch entry points with caching support
@@ -325,7 +377,38 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
325
377
  * });
326
378
  * ```
327
379
  */
328
- private queue: Queue;
380
+ public queue: Queue;
381
+
382
+ /**
383
+ * API instance for managing Webex Contact Center user preferences
384
+ * Provides functionality to get, create, update, and delete user preferences
385
+ * @type {UserPreference}
386
+ * @public
387
+ * @example
388
+ * ```typescript
389
+ * const cc = webex.cc;
390
+ * await cc.register();
391
+ * await cc.stationLogin({ teamId: 'team123', loginOption: 'BROWSER' });
392
+ *
393
+ * // Get user preferences for current user
394
+ * const preferences = await cc.userPreference.getUserPreference();
395
+ *
396
+ * // Create new user preferences
397
+ * const newPrefs = await cc.userPreference.createUserPreference({
398
+ * userId: 'user123',
399
+ * preferences: { e911Reminder: true }
400
+ * });
401
+ *
402
+ * // Update user preferences
403
+ * const updatedPrefs = await cc.userPreference.updateUserPreference('user123', {
404
+ * preferences: { e911Reminder: false }
405
+ * });
406
+ *
407
+ * // Delete user preferences
408
+ * await cc.userPreference.deleteUserPreference('user123');
409
+ * ```
410
+ */
411
+ public userPreference: UserPreference;
329
412
 
330
413
  /**
331
414
  * API instance for AI Assistant operations such as transcript controls.
@@ -357,6 +440,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
357
440
  this.$webex.once(READY, () => {
358
441
  // @ts-ignore
359
442
  this.$config = this.config;
443
+ this.validatePluginConfig();
360
444
 
361
445
  /**
362
446
  * This is used for handling the async requests by sending webex.request and wait for corresponding websocket event.
@@ -369,17 +453,25 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
369
453
  webex: this.$webex,
370
454
  connectionConfig: this.getConnectionConfig(),
371
455
  });
372
- this.services.webSocketManager.on('message', this.handleWebsocketMessage);
373
456
 
374
457
  this.webCallingService = new WebCallingService(this.$webex);
375
- this.apiAIAssistant = new ApiAIAssistant(this.$webex);
458
+ this.answerCallOnWebexService = new AnswerCallOnWebexService(this.$webex);
459
+ this.webexCrossClientService = new WebexCrossClientService(this.$webex);
460
+ this.wxAppTelephonyMercurySync = new WxAppTelephonyMercurySync(this.$webex);
461
+ this.apiAIAssistant = new ApiAIAssistant(this.$webex, () => ({
462
+ isWellnessBreakEnabled: this.agentConfig?.isWellnessBreakEnabled === true,
463
+ agentId: this.agentConfig?.agentId,
464
+ agentSessionId: this.currentAgentSessionId,
465
+ }));
376
466
  this.metricsManager = MetricsManager.getInstance({webex: this.$webex});
377
467
  this.taskManager = TaskManager.getTaskManager(
378
468
  this.apiAIAssistant,
379
469
  this.services.contact,
380
470
  this.webCallingService,
381
- this.services.webSocketManager
471
+ this.services.webSocketManager,
472
+ this.services.rtdWebSocketManager
382
473
  );
474
+ this.taskManager.setAnswerCallOnWebexService(this.answerCallOnWebexService);
383
475
  this.incomingTaskListener();
384
476
 
385
477
  // Initialize API instances
@@ -387,6 +479,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
387
479
  this.entryPoint = new EntryPoint(this.$webex);
388
480
  this.addressBook = new AddressBook(this.$webex, () => this.agentConfig?.addressBookId);
389
481
  this.queue = new Queue(this.$webex);
482
+ this.userPreference = new UserPreference(this.$webex, () => this.agentConfig?.agentId);
390
483
  LoggerProxy.initialize(this.$webex.logger);
391
484
  });
392
485
  }
@@ -411,6 +504,16 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
411
504
  this.trigger(TASK_EVENTS.TASK_HYDRATE, task);
412
505
  };
413
506
 
507
+ /**
508
+ * Handles multi-login hydrate events for SDK instances without Mobius registration
509
+ * @private
510
+ * @param {ITask} task The task object associated with the multi-login hydrate
511
+ */
512
+ private handleTaskMultiLoginHydrate = (task: ITask) => {
513
+ // @ts-ignore
514
+ this.trigger(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, task);
515
+ };
516
+
414
517
  /**
415
518
  * Handles task merged events when tasks are combined eg: EPDN merge/transfer
416
519
  * @private
@@ -431,8 +534,116 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
431
534
  this.trigger(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, task);
432
535
  };
433
536
 
434
- private handleRTDWebsocketMessage = (payload: string) => {
435
- this.taskManager.handleRealtimeWebsocketEvent(payload);
537
+ private trackInvalidWellnessEvent(reason: string): void {
538
+ this.metricsManager.trackEvent(
539
+ METRIC_EVENT_NAMES.AI_ASSISTANT_WELLNESS_EVENT_INVALID,
540
+ {reason},
541
+ ['operational']
542
+ );
543
+ }
544
+
545
+ private normalizeWellnessBreakEvent(payload: Record<string, unknown>): WellnessBreakEvent | null {
546
+ if (this.agentConfig?.isWellnessBreakEnabled !== true) {
547
+ this.trackInvalidWellnessEvent('wellness_disabled');
548
+
549
+ return null;
550
+ }
551
+
552
+ const notificationData = isRecord(payload.data) ? payload.data : null;
553
+ const notificationDetails =
554
+ notificationData && isRecord(notificationData.notifDetails)
555
+ ? notificationData.notifDetails
556
+ : null;
557
+ const sessionData =
558
+ notificationData && isRecord(notificationData.data) ? notificationData.data : null;
559
+ const actionEvent = notificationDetails?.actionEvent;
560
+
561
+ if (
562
+ payload.type !== WELLNESS_BREAK_HANDLER ||
563
+ notificationData?.notifType !== WELLNESS_BREAK_HANDLER ||
564
+ typeof actionEvent !== 'string'
565
+ ) {
566
+ this.trackInvalidWellnessEvent('malformed_wellness_event');
567
+
568
+ return null;
569
+ }
570
+
571
+ if (
572
+ !Object.values(WELLNESS_BREAK_NOTIFICATION_ACTIONS).includes(
573
+ actionEvent as WellnessBreakNotificationAction
574
+ )
575
+ ) {
576
+ this.trackInvalidWellnessEvent('unknown_wellness_action');
577
+
578
+ return null;
579
+ }
580
+
581
+ const agentId = notificationData.agentId;
582
+ const eventOrgId = notificationData.orgId;
583
+ const sessionOrgId = sessionData?.orgId;
584
+ const envelopeOrgId = payload.orgId;
585
+ const agentSessionId = sessionData?.agentSessionId;
586
+ const currentOrgId = this.$webex.credentials.getOrgId();
587
+
588
+ if (
589
+ typeof agentId !== 'string' ||
590
+ typeof eventOrgId !== 'string' ||
591
+ typeof sessionOrgId !== 'string' ||
592
+ typeof envelopeOrgId !== 'string' ||
593
+ typeof agentSessionId !== 'string' ||
594
+ !agentId ||
595
+ !eventOrgId ||
596
+ !sessionOrgId ||
597
+ !envelopeOrgId ||
598
+ !agentSessionId
599
+ ) {
600
+ this.trackInvalidWellnessEvent('missing_wellness_identity');
601
+
602
+ return null;
603
+ }
604
+
605
+ if (
606
+ agentId !== this.agentConfig?.agentId ||
607
+ eventOrgId !== currentOrgId ||
608
+ sessionOrgId !== currentOrgId ||
609
+ envelopeOrgId !== currentOrgId
610
+ ) {
611
+ this.trackInvalidWellnessEvent('mismatched_wellness_identity');
612
+
613
+ return null;
614
+ }
615
+
616
+ const interactionValue = sessionData.interactionId ?? sessionData.InteractionId;
617
+ const actionText = notificationDetails.actionText;
618
+ const trackingId = payload.trackingId;
619
+
620
+ return {
621
+ agentId,
622
+ orgId: currentOrgId,
623
+ agentSessionId,
624
+ actionEvent: actionEvent as WellnessBreakNotificationAction,
625
+ ...(typeof actionText === 'string' ? {actionText} : {}),
626
+ ...(typeof interactionValue === 'string' ? {interactionId: interactionValue} : {}),
627
+ ...(typeof trackingId === 'string' ? {trackingId} : {}),
628
+ };
629
+ }
630
+
631
+ private handleWellnessBreakWebsocketPayload(payload: Record<string, unknown>): boolean {
632
+ if (payload.type !== WELLNESS_BREAK_HANDLER) {
633
+ return false;
634
+ }
635
+
636
+ const wellnessEvent = this.normalizeWellnessBreakEvent(payload);
637
+ if (wellnessEvent) {
638
+ // @ts-ignore - WebexPlugin emit is available at runtime but absent from its declaration.
639
+ this.emit(CC_EVENTS.WELLNESS_BREAK, wellnessEvent);
640
+ }
641
+
642
+ return true;
643
+ }
644
+
645
+ private handleRTDWebsocketMessage = (event: string) => {
646
+ this.taskManager.handleRealtimeWebsocketEvent(event);
436
647
  };
437
648
 
438
649
  /**
@@ -443,6 +654,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
443
654
  private incomingTaskListener() {
444
655
  this.taskManager.on(TASK_EVENTS.TASK_INCOMING, this.handleIncomingTask);
445
656
  this.taskManager.on(TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
657
+ this.taskManager.on(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, this.handleTaskMultiLoginHydrate);
446
658
  this.taskManager.on(TASK_EVENTS.TASK_MERGED, this.handleTaskMerged);
447
659
  this.taskManager.on(
448
660
  TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION,
@@ -487,7 +699,9 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
487
699
  * ```
488
700
  */
489
701
  public async register(): Promise<Profile> {
490
- LoggerProxy.info('Starting CC SDK registration', {
702
+ this.wellnessRegistrationGeneration += 1;
703
+ this.wellbeingBreakIdleCode = undefined;
704
+ LoggerProxy.log('Starting CC SDK registration', {
491
705
  module: CC_FILE,
492
706
  method: METHODS.REGISTER,
493
707
  });
@@ -497,6 +711,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
497
711
  METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED,
498
712
  ]);
499
713
  this.setupEventListeners();
714
+ this.services.webSocketManager.on('message', this.handleWebsocketMessage);
500
715
 
501
716
  const resp = await this.connectWebsocket();
502
717
  // Ensure 'dn' is always populated from 'defaultDn'
@@ -568,6 +783,9 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
568
783
 
569
784
  */
570
785
  public async deregister(): Promise<void> {
786
+ this.wellnessRegistrationGeneration += 1;
787
+ this.wellbeingBreakIdleCode = undefined;
788
+ this.updateWellnessSession();
571
789
  try {
572
790
  this.metricsManager.timeEvent([
573
791
  METRIC_EVENT_NAMES.WEBSOCKET_DEREGISTER_SUCCESS,
@@ -576,6 +794,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
576
794
 
577
795
  this.taskManager.off(TASK_EVENTS.TASK_INCOMING, this.handleIncomingTask);
578
796
  this.taskManager.off(TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
797
+ this.taskManager.off(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, this.handleTaskMultiLoginHydrate);
579
798
  this.taskManager.off(
580
799
  TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION,
581
800
  this.handleCampaignPreviewReservation
@@ -586,6 +805,10 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
586
805
  this.services.rtdWebSocketManager.off('message', this.handleRTDWebsocketMessage);
587
806
  this.services.connectionService.off('connectionLost', this.handleConnectionLost);
588
807
 
808
+ const {publishError: wxAppPublishError} = await this.teardownWxAppLocalState({
809
+ clearInitFlag: true,
810
+ });
811
+
589
812
  if (
590
813
  this.agentConfig.webRtcEnabled &&
591
814
  this.agentConfig.loginVoiceOptions.includes(LoginOption.BROWSER)
@@ -606,13 +829,22 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
606
829
  if (!this.services.webSocketManager.isSocketClosed) {
607
830
  this.services.webSocketManager.close(false, 'Unregistering the SDK');
608
831
  }
832
+ if (this.services.rtdWebSocketManager && !this.services.rtdWebSocketManager.isSocketClosed) {
833
+ this.services.rtdWebSocketManager.close(false, 'Unregistering the SDK');
834
+ }
609
835
 
610
836
  if (this.services.rtdWebSocketManager && !this.services.rtdWebSocketManager.isSocketClosed) {
611
837
  this.services.rtdWebSocketManager.close(false, 'Unregistering the RTD websocket');
612
838
  }
613
839
 
614
840
  // Clear any cached agent configuration
841
+ this.wellbeingBreakIdleCode = undefined;
615
842
  this.agentConfig = null;
843
+ this.updateWellnessSession();
844
+
845
+ if (wxAppPublishError) {
846
+ throw wxAppPublishError;
847
+ }
616
848
 
617
849
  LoggerProxy.log('Deregistered successfully', {
618
850
  module: CC_FILE,
@@ -676,6 +908,9 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
676
908
  * ```
677
909
  */
678
910
  public async getBuddyAgents(data: BuddyAgents): Promise<BuddyAgentsResponse> {
911
+ const {action, state, mediaType = 'telephony'} = data;
912
+ const buddyAgentState = state ?? (action === 'Transfer' ? AGENT_STATE_AVAILABLE : undefined);
913
+
679
914
  LoggerProxy.info('Fetching buddy agents', {
680
915
  module: CC_FILE,
681
916
  method: METHODS.GET_BUDDY_AGENTS,
@@ -686,15 +921,19 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
686
921
  METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_FAILED,
687
922
  ]);
688
923
  const resp = await this.services.agent.buddyAgents({
689
- data: {agentProfileId: this.agentConfig.agentProfileID, ...data},
924
+ data: {
925
+ agentProfileId: this.agentConfig.agentProfileID,
926
+ mediaType,
927
+ ...(buddyAgentState ? {state: buddyAgentState} : {}),
928
+ },
690
929
  });
691
930
 
692
931
  this.metricsManager.trackEvent(
693
932
  METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_SUCCESS,
694
933
  {
695
934
  ...MetricsManager.getCommonTrackingFieldForAQMResponse(resp),
696
- mediaType: data.mediaType,
697
- buddyAgentState: data.state,
935
+ mediaType,
936
+ buddyAgentState,
698
937
  buddyAgentCount: resp.data.agentList.length,
699
938
  },
700
939
  ['operational']
@@ -714,8 +953,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
714
953
  METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_FAILED,
715
954
  {
716
955
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failureResp),
717
- mediaType: data.mediaType,
718
- buddyAgentState: data.state,
956
+ mediaType,
957
+ buddyAgentState,
719
958
  },
720
959
  ['operational']
721
960
  );
@@ -731,7 +970,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
731
970
  * @private
732
971
  */
733
972
  private async connectWebsocket() {
734
- LoggerProxy.info('Connecting to websocket', {
973
+ LoggerProxy.log('Connecting to websocket', {
735
974
  module: CC_FILE,
736
975
  method: METHODS.CONNECT_WEBSOCKET,
737
976
  });
@@ -751,19 +990,43 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
751
990
  method: METHODS.CONNECT_WEBSOCKET,
752
991
  });
753
992
 
993
+ const configFlags: ConfigFlags = {
994
+ isEndTaskEnabled: this.agentConfig.isEndTaskEnabled,
995
+ isEndConsultEnabled: this.agentConfig.isEndConsultEnabled,
996
+ webRtcEnabled: this.agentConfig.webRtcEnabled,
997
+ autoWrapup: this.agentConfig.wrapUpData?.wrapUpProps?.autoWrapup ?? false,
998
+ aiFeature: this.agentConfig.aiFeature,
999
+ consultTransfer: {
1000
+ allowConsultToQueue: this.agentConfig.allowConsultToQueue,
1001
+ accessQueue: this.agentConfig.accessQueue,
1002
+ accessEntryPoint: this.agentConfig.accessEntryPoint,
1003
+ accessBuddyTeam: this.agentConfig.accessBuddyTeam,
1004
+ },
1005
+ enableWxBetterTogether: this.isWxBetterTogetherEnabled(),
1006
+ };
1007
+ this.taskManager.setConfigFlags(configFlags);
754
1008
  // TODO: Make profile a singleton to make it available throughout app/sdk so we dont need to inject info everywhere
755
1009
  this.taskManager.setWrapupData(this.agentConfig.wrapUpData);
756
1010
  this.taskManager.setAgentId(this.agentConfig.agentId);
757
1011
  this.taskManager.setWebRtcEnabled(this.agentConfig.webRtcEnabled);
758
1012
  this.apiAIAssistant.setAIFeatureFlags(this.agentConfig.aiFeature);
1013
+ this.wellbeingBreakIdleCode = undefined;
1014
+ this.updateWellnessSession();
759
1015
 
760
- if (this.agentConfig.aiFeature?.realtimeTranscripts?.enable) {
1016
+ /**
1017
+ * RTD websocket currently supports realtime transcripts and suggested responses.
1018
+ * Extend this condition when additional AI RTD features are introduced.
1019
+ */
1020
+ if (
1021
+ this.agentConfig.aiFeature?.realtimeTranscripts?.enable ||
1022
+ this.agentConfig.aiFeature?.suggestedResponses?.enable
1023
+ ) {
761
1024
  LoggerProxy.info('Connecting to RTD websocket', {
762
1025
  module: CC_FILE,
763
1026
  method: METHODS.CONNECT_WEBSOCKET,
764
1027
  });
765
1028
 
766
- await this.services.rtdWebSocketManager
1029
+ this.services.rtdWebSocketManager
767
1030
  .initWebSocket({
768
1031
  body: this.getConnectionConfig(),
769
1032
  resource: RTD_SUBSCRIBE_API,
@@ -776,7 +1039,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
776
1039
  this.services.rtdWebSocketManager.on('message', this.handleRTDWebsocketMessage);
777
1040
  })
778
1041
  .catch((error) => {
779
- LoggerProxy.error(`Error during RTD websocket setup: ${error}`, {
1042
+ LoggerProxy.error(`Error connecting to RTD websocket ${error}`, {
780
1043
  module: CC_FILE,
781
1044
  method: METHODS.CONNECT_WEBSOCKET,
782
1045
  });
@@ -785,7 +1048,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
785
1048
 
786
1049
  if (
787
1050
  this.agentConfig.webRtcEnabled &&
788
- this.agentConfig.loginVoiceOptions.includes(LoginOption.BROWSER)
1051
+ this.agentConfig.loginVoiceOptions.includes(LoginOption.BROWSER) &&
1052
+ !this.isWebRTCRegistrationDisabled()
789
1053
  ) {
790
1054
  try {
791
1055
  await this.$webex.internal.mercury.connect();
@@ -799,14 +1063,28 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
799
1063
  method: METHODS.CONNECT_WEBSOCKET,
800
1064
  });
801
1065
  }
1066
+ } else if (this.isWebRTCRegistrationDisabled()) {
1067
+ LoggerProxy.info(
1068
+ 'Skipping Mobius registration because disableWebRTCRegistration is enabled',
1069
+ {
1070
+ module: CC_FILE,
1071
+ method: METHODS.CONNECT_WEBSOCKET,
1072
+ }
1073
+ );
802
1074
  }
803
1075
 
804
1076
  if (this.$config && this.$config.allowAutomatedRelogin) {
805
1077
  await this.silentRelogin();
1078
+ } else {
1079
+ LoggerProxy.log('Skipping silent relogin: allowAutomatedRelogin is disabled', {
1080
+ module: CC_FILE,
1081
+ method: METHODS.CONNECT_WEBSOCKET,
1082
+ });
806
1083
  }
807
1084
 
808
1085
  return this.agentConfig;
809
1086
  } catch (error) {
1087
+ this.updateWellnessSession();
810
1088
  LoggerProxy.error(`Error during register: ${error}`, {
811
1089
  module: CC_FILE,
812
1090
  method: METHODS.CONNECT_WEBSOCKET,
@@ -845,36 +1123,48 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
845
1123
  * ```
846
1124
  */
847
1125
  public async stationLogin(data: AgentLogin): Promise<StationLoginResponse> {
848
- LoggerProxy.info('Starting agent station login', {
849
- module: CC_FILE,
850
- method: METHODS.STATION_LOGIN,
851
- });
1126
+ const loggerContext = {module: CC_FILE, method: METHODS.STATION_LOGIN};
1127
+
852
1128
  try {
1129
+ LoggerProxy.log(
1130
+ `Starting agent station login | loginOption: ${data?.loginOption} teamId: ${data?.teamId}`,
1131
+ loggerContext
1132
+ );
853
1133
  this.metricsManager.timeEvent([
854
1134
  METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS,
855
1135
  METRIC_EVENT_NAMES.STATION_LOGIN_FAILED,
856
1136
  ]);
857
1137
 
858
1138
  const dialPlanEntries = this.agentConfig?.dialPlan?.dialPlanEntity ?? [];
859
- if (
860
- data.loginOption === LoginOption.AGENT_DN &&
861
- !isValidDialNumber(data.dialNumber, dialPlanEntries)
862
- ) {
863
- const error = new Error('INVALID_DIAL_NUMBER');
864
- // @ts-ignore - adding custom key to the error object
865
- error.details = {data: {reason: 'INVALID_DIAL_NUMBER'}} as Failure;
1139
+ if (data.loginOption === LoginOption.AGENT_DN) {
1140
+ LoggerProxy.log(
1141
+ `Validating dial number | dialPlanEnabled: ${!!this.agentConfig
1142
+ ?.dialPlan} | dialPlanEntryCount: ${dialPlanEntries.length}`,
1143
+ loggerContext
1144
+ );
1145
+
1146
+ if (!isValidDialNumber(data.dialNumber, dialPlanEntries)) {
1147
+ LoggerProxy.log(
1148
+ `Dial number validation failed | dialNumber: ${data.dialNumber} | dialPlanEntryCount: ${dialPlanEntries.length}`,
1149
+ loggerContext
1150
+ );
1151
+ const error = new Error('INVALID_DIAL_NUMBER');
1152
+ // @ts-ignore - adding custom key to the error object
1153
+ error.details = {data: {reason: 'INVALID_DIAL_NUMBER'}} as Failure;
866
1154
 
867
- throw error;
1155
+ throw error;
1156
+ }
868
1157
  }
869
1158
 
1159
+ const deviceId = this.getDeviceId(data.loginOption, data.dialNumber);
870
1160
  const loginResponse = await this.services.agent.stationLogin({
871
1161
  data: {
872
- dialNumber:
873
- data.loginOption === LoginOption.BROWSER ? this.agentConfig.agentId : data.dialNumber,
1162
+ // BROWSER DN must be webrtc-{agentId} so VPOP/RTMS do not treat a bare UUID as PSTN.
1163
+ dialNumber: data.loginOption === LoginOption.BROWSER ? deviceId : data.dialNumber,
874
1164
  teamId: data.teamId,
875
1165
  deviceType: data.loginOption,
876
1166
  isExtension: data.loginOption === LoginOption.EXTENSION,
877
- deviceId: this.getDeviceId(data.loginOption, data.dialNumber),
1167
+ deviceId,
878
1168
  roles: [AGENT],
879
1169
  teamName: EMPTY_STRING,
880
1170
  siteId: EMPTY_STRING,
@@ -883,12 +1173,29 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
883
1173
  },
884
1174
  });
885
1175
 
886
- if (this.agentConfig.webRtcEnabled && data.loginOption === LoginOption.BROWSER) {
1176
+ if (
1177
+ this.agentConfig.webRtcEnabled &&
1178
+ data.loginOption === LoginOption.BROWSER &&
1179
+ !this.isWebRTCRegistrationDisabled()
1180
+ ) {
887
1181
  await this.webCallingService.registerWebCallingLine();
1182
+ } else if (
1183
+ this.agentConfig.webRtcEnabled &&
1184
+ data.loginOption === LoginOption.BROWSER &&
1185
+ this.isWebRTCRegistrationDisabled()
1186
+ ) {
1187
+ LoggerProxy.info(
1188
+ 'Skipping web calling line registration because disableWebRTCRegistration is enabled',
1189
+ {
1190
+ module: CC_FILE,
1191
+ method: METHODS.STATION_LOGIN,
1192
+ }
1193
+ );
888
1194
  }
889
1195
 
890
1196
  const resp = await loginResponse;
891
1197
  const {channelsMap, ...loginData} = resp.data;
1198
+ this.updateWellnessSession(resp.data.agentSessionId);
892
1199
  this.agentConfig.currentTeamId = resp.data.teamId;
893
1200
  const response = {
894
1201
  ...loginData,
@@ -902,6 +1209,16 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
902
1209
  };
903
1210
 
904
1211
  this.webCallingService.setLoginOption(data.loginOption);
1212
+ if (this.agentConfig) {
1213
+ this.agentConfig.deviceType = data.loginOption;
1214
+ if (
1215
+ data.loginOption === LoginOption.AGENT_DN ||
1216
+ data.loginOption === LoginOption.EXTENSION
1217
+ ) {
1218
+ this.agentConfig.defaultDn = data.dialNumber;
1219
+ this.agentConfig.dn = data.dialNumber;
1220
+ }
1221
+ }
905
1222
  this.metricsManager.trackEvent(
906
1223
  METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS,
907
1224
  {
@@ -923,6 +1240,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
923
1240
  }
924
1241
  );
925
1242
 
1243
+ await this.ensureWxAppPostStationLogin();
1244
+
926
1245
  return response;
927
1246
  } catch (error) {
928
1247
  const failure = error.details as Failure;
@@ -991,6 +1310,9 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
991
1310
  this.webCallingService.deregisterWebCallingLine();
992
1311
  }
993
1312
 
1313
+ await this.teardownWxAppLocalState();
1314
+ this.updateWellnessSession();
1315
+
994
1316
  LoggerProxy.log(`Agent station logout completed successfully`, {
995
1317
  module: CC_FILE,
996
1318
  method: METHODS.STATION_LOGOUT,
@@ -1127,6 +1449,77 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1127
1449
  }
1128
1450
  }
1129
1451
 
1452
+ /**
1453
+ * Returns the system-managed `WellbeingBreak` idle code for the current registration.
1454
+ * The value is fetched outside Desktop Profile filtering and cached until deregistration.
1455
+ * @returns The normalized system idle-code entity
1456
+ * @throws Structured Contact Center error when wellness is disabled or the code is unavailable
1457
+ * @example
1458
+ * const idleCode = await webex.cc.getWellbeingBreakIdleCode();
1459
+ * @public
1460
+ */
1461
+ public async getWellbeingBreakIdleCode(): Promise<Entity> {
1462
+ const method = METHODS.GET_WELLBEING_BREAK_IDLE_CODE;
1463
+ try {
1464
+ if (!this.agentConfig?.isWellnessBreakEnabled) {
1465
+ const validationError = new Error('WELLNESS_BREAK_NOT_ENABLED') as GenericError;
1466
+ validationError.details = {
1467
+ type: 'SDK_VALIDATION_ERROR',
1468
+ orgId: this.$webex.credentials.getOrgId(),
1469
+ trackingId: EMPTY_STRING,
1470
+ data: {reason: 'WELLNESS_BREAK_NOT_ENABLED'},
1471
+ };
1472
+ throw validationError;
1473
+ }
1474
+
1475
+ if (this.wellbeingBreakIdleCode) {
1476
+ return this.wellbeingBreakIdleCode;
1477
+ }
1478
+
1479
+ this.metricsManager.timeEvent([
1480
+ METRIC_EVENT_NAMES.WELLBEING_BREAK_IDLE_CODE_FETCH_SUCCESS,
1481
+ METRIC_EVENT_NAMES.WELLBEING_BREAK_IDLE_CODE_FETCH_FAILED,
1482
+ ]);
1483
+ const registrationGeneration = this.wellnessRegistrationGeneration;
1484
+ const orgId = this.$webex.credentials.getOrgId();
1485
+ const idleCode = await this.services.config.getWellbeingBreakIdleCode(orgId);
1486
+
1487
+ if (
1488
+ registrationGeneration !== this.wellnessRegistrationGeneration ||
1489
+ orgId !== this.$webex.credentials.getOrgId() ||
1490
+ !this.agentConfig?.isWellnessBreakEnabled
1491
+ ) {
1492
+ const staleRegistrationError = new Error(
1493
+ 'WELLNESS_BREAK_REGISTRATION_CHANGED'
1494
+ ) as GenericError;
1495
+ staleRegistrationError.details = {
1496
+ type: 'SDK_VALIDATION_ERROR',
1497
+ orgId,
1498
+ trackingId: EMPTY_STRING,
1499
+ data: {reason: 'WELLNESS_BREAK_REGISTRATION_CHANGED'},
1500
+ };
1501
+ throw staleRegistrationError;
1502
+ }
1503
+
1504
+ this.wellbeingBreakIdleCode = idleCode;
1505
+ this.metricsManager.trackEvent(
1506
+ METRIC_EVENT_NAMES.WELLBEING_BREAK_IDLE_CODE_FETCH_SUCCESS,
1507
+ {},
1508
+ ['operational']
1509
+ );
1510
+
1511
+ return idleCode;
1512
+ } catch (error) {
1513
+ this.metricsManager.trackEvent(
1514
+ METRIC_EVENT_NAMES.WELLBEING_BREAK_IDLE_CODE_FETCH_FAILED,
1515
+ {reason: error instanceof Error ? error.message : UNKNOWN_ERROR},
1516
+ ['operational']
1517
+ );
1518
+ const {error: detailedError} = getErrorDetails(error, method, CC_FILE);
1519
+ throw detailedError;
1520
+ }
1521
+ }
1522
+
1130
1523
  /**
1131
1524
  * Processes incoming websocket messages and emits corresponding events
1132
1525
  * Handles various event types including agent state changes, login events,
@@ -1165,8 +1558,19 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1165
1558
  ]);
1166
1559
  }
1167
1560
 
1561
+ if (this.handleWellnessBreakWebsocketPayload(eventData)) {
1562
+ return;
1563
+ }
1564
+
1168
1565
  switch (eventData.type) {
1169
1566
  case CC_EVENTS.AGENT_MULTI_LOGIN:
1567
+ if (
1568
+ eventData.data?.type === 'AgentMultiLoginCloseSession' &&
1569
+ (!eventData.data.agentSessionId ||
1570
+ eventData.data.agentSessionId === this.currentAgentSessionId)
1571
+ ) {
1572
+ this.updateWellnessSession();
1573
+ }
1170
1574
  // @ts-ignore
1171
1575
  this.emit(AGENT_EVENTS.AGENT_MULTI_LOGIN, eventData.data);
1172
1576
  break;
@@ -1185,6 +1589,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1185
1589
  switch (eventData.data.type) {
1186
1590
  case CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS: {
1187
1591
  const {channelsMap, ...loginData} = eventData.data;
1592
+ this.updateWellnessSession(loginData.agentSessionId);
1188
1593
  const stationLoginData = {
1189
1594
  ...loginData,
1190
1595
  mmProfile: {
@@ -1200,23 +1605,23 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1200
1605
  this.emit(AGENT_EVENTS.AGENT_STATION_LOGIN_SUCCESS, stationLoginData);
1201
1606
  break;
1202
1607
  }
1203
- case CC_EVENTS.AGENT_RELOGIN_SUCCESS:
1204
- {
1205
- const {channelsMap, ...loginData} = eventData.data;
1206
- const stationReLoginData = {
1207
- ...loginData,
1208
- mmProfile: {
1209
- chat: channelsMap.chat?.length,
1210
- email: channelsMap.email?.length,
1211
- social: channelsMap.social?.length,
1212
- telephony: channelsMap.telephony?.length,
1213
- },
1214
- notifsTrackingId: eventData.trackingId,
1215
- };
1216
- // @ts-ignore
1217
- this.emit(AGENT_EVENTS.AGENT_RELOGIN_SUCCESS, stationReLoginData);
1218
- }
1608
+ case CC_EVENTS.AGENT_RELOGIN_SUCCESS: {
1609
+ const {channelsMap, ...loginData} = eventData.data;
1610
+ this.updateWellnessSession(loginData.agentSessionId);
1611
+ const stationReLoginData = {
1612
+ ...loginData,
1613
+ mmProfile: {
1614
+ chat: channelsMap.chat?.length,
1615
+ email: channelsMap.email?.length,
1616
+ social: channelsMap.social?.length,
1617
+ telephony: channelsMap.telephony?.length,
1618
+ },
1619
+ notifsTrackingId: eventData.trackingId,
1620
+ };
1621
+ // @ts-ignore
1622
+ this.emit(AGENT_EVENTS.AGENT_RELOGIN_SUCCESS, stationReLoginData);
1219
1623
  break;
1624
+ }
1220
1625
  case CC_EVENTS.AGENT_STATE_CHANGE_SUCCESS:
1221
1626
  // @ts-ignore
1222
1627
  this.emit(AGENT_EVENTS.AGENT_STATE_CHANGE_SUCCESS, eventData.data);
@@ -1230,6 +1635,12 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1230
1635
  this.emit(AGENT_EVENTS.AGENT_STATION_LOGIN_FAILED, eventData.data);
1231
1636
  break;
1232
1637
  case CC_EVENTS.AGENT_LOGOUT_SUCCESS:
1638
+ if (
1639
+ !eventData.data.agentSessionId ||
1640
+ eventData.data.agentSessionId === this.currentAgentSessionId
1641
+ ) {
1642
+ this.updateWellnessSession();
1643
+ }
1233
1644
  // @ts-ignore
1234
1645
  this.emit(AGENT_EVENTS.AGENT_LOGOUT_SUCCESS, eventData.data);
1235
1646
  break;
@@ -1247,84 +1658,680 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1247
1658
  };
1248
1659
 
1249
1660
  /**
1250
- * Initializes event listeners for the Contact Center service
1251
- * Sets up handlers for connection state changes and other core events
1661
+ * Checks whether Mobius/WebRTC registration should be skipped by config
1662
+ * @returns {boolean} True when browser WebRTC registration is disabled
1252
1663
  * @private
1253
1664
  */
1254
- private setupEventListeners() {
1255
- this.services.connectionService.on('connectionLost', this.handleConnectionLost.bind(this));
1665
+ private isWebRTCRegistrationDisabled(): boolean {
1666
+ return this.$config?.disableWebRTCRegistration === true;
1256
1667
  }
1257
1668
 
1258
1669
  /**
1259
- * Returns the connection configuration
1260
- * @returns {SubscribeRequest} Connection configuration
1670
+ * Validates contact-center plugin configuration before service initialization
1261
1671
  * @private
1262
1672
  */
1263
- private getConnectionConfig(): SubscribeRequest {
1264
- return {
1265
- force: this.$config?.force ?? true,
1266
- isKeepAliveEnabled: this.$config?.isKeepAliveEnabled ?? false,
1267
- clientType: this.$config?.clientType ?? 'WebexCCSDK',
1268
- allowMultiLogin: this.$config?.allowMultiLogin ?? true,
1269
- };
1673
+ private validatePluginConfig(): void {
1674
+ if (
1675
+ this.$config?.disableWebRTCRegistration === true &&
1676
+ this.$config?.allowMultiLogin === false
1677
+ ) {
1678
+ throw new Error(
1679
+ 'Invalid Contact Center configuration: disableWebRTCRegistration cannot be true when allowMultiLogin is false. Enable allowMultiLogin or allow WebRTC registration so an SDK instance can receive Mobius/WebRTC task events.'
1680
+ );
1681
+ }
1270
1682
  }
1271
1683
 
1272
1684
  /**
1273
- * Handles connection lost events and reconnection attempts
1274
- * @param {ConnectionLostDetails} msg Connection lost details
1685
+ * Whether wxApp thick-client answer is enabled for this SDK instance.
1686
+ * @public
1687
+ */
1688
+ public isWxBetterTogetherEnabled(): boolean {
1689
+ return this.$config?.enableWxBetterTogether === true;
1690
+ }
1691
+
1692
+ /**
1693
+ * Current station login option (Extension, Dial Number, or Browser/Desktop).
1694
+ * Prefer webCallingService — updated on every stationLogin before wxApp hooks run.
1275
1695
  * @private
1276
1696
  */
1277
- private async handleConnectionLost(msg: ConnectionLostDetails): Promise<void> {
1278
- if (msg.isConnectionLost) {
1279
- // TODO: Emit an event saying connection is lost
1280
- LoggerProxy.info('event=handleConnectionLost | Connection lost', {
1281
- module: CC_FILE,
1282
- method: METHODS.HANDLE_CONNECTION_LOST,
1283
- });
1284
- } else if (msg.isSocketReconnected) {
1285
- // TODO: Emit an event saying connection is re-estabilished
1286
- LoggerProxy.info(
1287
- 'event=handleConnectionReconnect | Connection reconnected attempting to request silent relogin',
1288
- {module: CC_FILE, method: METHODS.HANDLE_CONNECTION_LOST}
1697
+ private getCurrentStationLoginOption(): LoginOption | undefined {
1698
+ return this.webCallingService?.loginOption ?? this.agentConfig?.deviceType;
1699
+ }
1700
+
1701
+ /**
1702
+ * wxApp thick-client answer is supported for Extension and Dial Number station login only —
1703
+ * not Browser/Desktop (WebRTC) login.
1704
+ * @private
1705
+ */
1706
+ private assertWxAppStationLoginSupportedForEnable(): void {
1707
+ const loginOption = this.getCurrentStationLoginOption();
1708
+ if (loginOption !== LoginOption.EXTENSION && loginOption !== LoginOption.AGENT_DN) {
1709
+ throw new Error(
1710
+ 'setManageWebexCallingInWxcc requires EXTENSION or AGENT_DN station login. Complete station login before enabling.'
1289
1711
  );
1290
- if (this.$config && this.$config.allowAutomatedRelogin) {
1291
- await this.silentRelogin();
1292
- }
1293
1712
  }
1294
1713
  }
1295
1714
 
1296
1715
  /**
1297
- * Handles silent relogin after registration completion
1716
+ * Clears runtime wxApp config after logout/deregister so stale flags do not apply on relogin.
1298
1717
  * @private
1299
1718
  */
1300
- private async silentRelogin(): Promise<void> {
1301
- LoggerProxy.info('Starting silent relogin process', {
1302
- module: CC_FILE,
1303
- method: METHODS.SILENT_RELOGIN,
1304
- });
1719
+ private resetEnableWxBetterTogetherConfig(): void {
1720
+ if (this.$config) {
1721
+ this.$config.enableWxBetterTogether = false;
1722
+ }
1723
+ this.taskManager.applyEnableWxBetterTogether(false);
1724
+ }
1725
+
1726
+ /**
1727
+ * Runtime enable/disable for wxApp Better Together (toast suppression + Mercury mute sync).
1728
+ *
1729
+ * **Phase 1 (WXCC-6026 / MMT): NOT part of the public host contract.**
1730
+ * Hosts must set `webexConfig.cc.enableWxBetterTogether` before `webex.init()` / `cc.register()`.
1731
+ * To change the flag after the SDK is initialized, re-init the SDK with the updated config.
1732
+ *
1733
+ * **Supported public read API:** {@link isWxBetterTogetherEnabled}.
1734
+ * **Supported telephony API:** unified `task.accept()`, `task.decline()`, `task.toggleMute()`,
1735
+ * `task.transmitDtmf()` — SDK routes wxApp calls internally when the init flag is active.
1736
+ *
1737
+ * **Production lifecycle does not call this method.** Station login, silent relogin, logout,
1738
+ * and deregister use {@link ensureWxAppPostStationLogin} and {@link teardownWxAppLocalState}
1739
+ * (usersub publish + Mercury subscribe/unsubscribe based on the init flag).
1740
+ *
1741
+ * This setter remains as a **private implementation** for Phase 2 runtime toggle and unit tests.
1742
+ * It updates `$config.enableWxBetterTogether`, TaskManager flags, cross-client usersub
1743
+ * (`answer-calls-on-wxcc`), and Mercury mute sync; rolls back config on publish/connect failure.
1744
+ *
1745
+ * @param enabled - `true` to enable wxApp answer + usersub suppression; `false` to disable and tear down.
1746
+ * @throws When enabling without login, on unsupported BROWSER station login, or when usersub/Mercury init fails.
1747
+ * @internal
1748
+ * @see enableWxBetterTogether (CCPluginConfig init flag)
1749
+ * @see ensureWxAppPostStationLogin
1750
+ * @see teardownWxAppLocalState
1751
+ */
1752
+ private async setManageWebexCallingInWxcc(enabled: boolean): Promise<void> {
1753
+ if (enabled && !this.$webex.internal.device?.userId) {
1754
+ throw new Error('Cannot publish answer-calls-on-wxcc: user is not logged in');
1755
+ }
1756
+
1757
+ if (enabled) {
1758
+ this.assertWxAppStationLoginSupportedForEnable();
1759
+ }
1760
+
1761
+ const previousEnabled = this.isWxBetterTogetherEnabled();
1762
+
1763
+ if (this.$config) {
1764
+ this.$config.enableWxBetterTogether = enabled;
1765
+ }
1766
+
1767
+ this.taskManager.applyEnableWxBetterTogether(enabled);
1768
+
1769
+ let publishedEnable = false;
1305
1770
 
1306
1771
  try {
1307
- const reLoginResponse = await this.services.agent.reload();
1308
- const {
1309
- agentId,
1310
- lastStateChangeReason,
1311
- deviceType,
1312
- dn,
1313
- lastStateChangeTimestamp,
1314
- lastIdleCodeChangeTimestamp,
1315
- } = reLoginResponse.data;
1316
- let {auxCodeId} = reLoginResponse.data;
1317
- this.agentConfig.lastStateChangeTimestamp = lastStateChangeTimestamp;
1318
- this.agentConfig.lastIdleCodeChangeTimestamp = lastIdleCodeChangeTimestamp;
1319
- this.agentConfig.currentTeamId = reLoginResponse.data.teamId;
1320
- await this.handleDeviceType(deviceType as LoginOption, dn);
1772
+ if (enabled) {
1773
+ await this.ensureWxAppMercuryAndSubscribe();
1774
+ await this.publishAnswerOnWebexCrossClientState(true);
1775
+ publishedEnable = true;
1776
+ this.taskManager.syncWxAppMuteFromCallDetailsForAllTasks();
1777
+ } else {
1778
+ await this.publishAnswerOnWebexCrossClientState(false);
1779
+ this.wxAppTelephonyMercurySync.unsubscribe();
1780
+ await this.releaseWxAppMercuryResources();
1781
+ }
1782
+ } catch (error) {
1783
+ if (publishedEnable) {
1784
+ await this.revertWxAppCrossClientPublish();
1785
+ } else if (enabled) {
1786
+ this.wxAppTelephonyMercurySync.unsubscribe();
1787
+ await this.releaseWxAppMercuryResources();
1788
+ }
1321
1789
 
1322
- if (lastStateChangeReason === 'agent-wss-disconnect') {
1323
- LoggerProxy.info(
1324
- 'event=requestAutoStateChange | Requesting state change to available on socket reconnect',
1325
- {module: CC_FILE, method: METHODS.SILENT_RELOGIN}
1326
- );
1327
- auxCodeId = AGENT_STATE_AVAILABLE_ID;
1790
+ if (this.$config) {
1791
+ this.$config.enableWxBetterTogether = previousEnabled;
1792
+ }
1793
+ this.taskManager.applyEnableWxBetterTogether(previousEnabled);
1794
+ throw error;
1795
+ }
1796
+ }
1797
+
1798
+ private getWxAppTelephonyTaskType(): 'Voice' | 'WebRTC' | 'unknown' {
1799
+ const loginOption = this.getCurrentStationLoginOption();
1800
+
1801
+ if (loginOption === LoginOption.BROWSER) {
1802
+ return 'WebRTC';
1803
+ }
1804
+
1805
+ if (loginOption === LoginOption.EXTENSION || loginOption === LoginOption.AGENT_DN) {
1806
+ return 'Voice';
1807
+ }
1808
+
1809
+ return 'unknown';
1810
+ }
1811
+
1812
+ private async ensureWxAppPostStationLogin(): Promise<void> {
1813
+ const loginOption = this.getCurrentStationLoginOption();
1814
+ const flagEnabled = this.isWxBetterTogetherEnabled();
1815
+ const telephonyTaskType = this.getWxAppTelephonyTaskType();
1816
+
1817
+ if (loginOption === LoginOption.BROWSER) {
1818
+ if (flagEnabled) {
1819
+ logWxAppSessionReadiness({
1820
+ enableWxBetterTogether: true,
1821
+ loginOption,
1822
+ wxAppHooksApplied: false,
1823
+ usersubPublished: false,
1824
+ mercurySubscribed: false,
1825
+ telephonyTaskType,
1826
+ skipReason: 'unsupported_browser_login',
1827
+ });
1828
+
1829
+ this.metricsManager.trackEvent(
1830
+ METRIC_EVENT_NAMES.WXAPP_SESSION_SKIPPED,
1831
+ {
1832
+ loginOption,
1833
+ enableWxBetterTogether: true,
1834
+ skipReason: 'unsupported_browser_login',
1835
+ },
1836
+ ['operational', 'behavioral']
1837
+ );
1838
+ }
1839
+
1840
+ return;
1841
+ }
1842
+
1843
+ let publishedEnable = false;
1844
+ let wxAppInitStage: 'mercury' | 'publish' = 'mercury';
1845
+
1846
+ try {
1847
+ if (flagEnabled) {
1848
+ this.metricsManager.timeEvent([
1849
+ METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_SUCCESS,
1850
+ METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
1851
+ ]);
1852
+
1853
+ await this.ensureWxAppMercuryAndSubscribe();
1854
+ wxAppInitStage = 'publish';
1855
+ await this.publishAnswerOnWebexCrossClientState(true);
1856
+ publishedEnable = true;
1857
+ this.taskManager.syncWxAppMuteFromCallDetailsForAllTasks();
1858
+
1859
+ const mercurySubscribed = this.wxAppTelephonyMercurySync.isSubscribed();
1860
+ const usersubPublished = this.webexCrossClientService.isAnswerCallsStateActive();
1861
+
1862
+ logWxAppSessionReadiness({
1863
+ enableWxBetterTogether: true,
1864
+ loginOption,
1865
+ wxAppHooksApplied: true,
1866
+ usersubPublished,
1867
+ mercurySubscribed,
1868
+ telephonyTaskType,
1869
+ });
1870
+
1871
+ const sessionInitReady = mercurySubscribed && usersubPublished;
1872
+
1873
+ if (sessionInitReady) {
1874
+ this.metricsManager.trackEvent(
1875
+ METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_SUCCESS,
1876
+ {loginOption, enableWxBetterTogether: true},
1877
+ ['operational', 'behavioral']
1878
+ );
1879
+ } else {
1880
+ this.metricsManager.trackEvent(
1881
+ METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
1882
+ {
1883
+ loginOption,
1884
+ enableWxBetterTogether: true,
1885
+ skipReason: !usersubPublished ? 'usersub_not_published' : 'mercury_not_subscribed',
1886
+ },
1887
+ ['operational', 'behavioral']
1888
+ );
1889
+ }
1890
+ } else {
1891
+ await this.ensureWxAppDeviceRegistered();
1892
+ await this.publishAnswerOnWebexCrossClientState(false, {force: true});
1893
+ this.webexCrossClientService.teardown();
1894
+ this.wxAppTelephonyMercurySync.unsubscribe();
1895
+ await this.releaseWxAppMercuryResources();
1896
+
1897
+ logWxAppSessionReadiness({
1898
+ enableWxBetterTogether: false,
1899
+ loginOption,
1900
+ wxAppHooksApplied: true,
1901
+ usersubPublished: false,
1902
+ mercurySubscribed: false,
1903
+ telephonyTaskType,
1904
+ skipReason: 'flag_disabled',
1905
+ });
1906
+
1907
+ this.metricsManager.trackEvent(
1908
+ METRIC_EVENT_NAMES.WXAPP_SESSION_SKIPPED,
1909
+ {loginOption, enableWxBetterTogether: false, skipReason: 'flag_disabled'},
1910
+ ['operational', 'behavioral']
1911
+ );
1912
+ }
1913
+ } catch (error) {
1914
+ LoggerProxy.error(`Failed to initialize wxApp post-station-login: ${error}`, {
1915
+ module: CC_FILE,
1916
+ method: METHODS.STATION_LOGIN,
1917
+ });
1918
+
1919
+ if (flagEnabled) {
1920
+ const sessionInitFailureReason =
1921
+ wxAppInitStage === 'mercury' ? 'mercury_subscribe_failed' : 'publish_failed';
1922
+
1923
+ this.metricsManager.trackEvent(
1924
+ METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
1925
+ {
1926
+ loginOption,
1927
+ enableWxBetterTogether: true,
1928
+ skipReason: sessionInitFailureReason,
1929
+ error: error instanceof Error ? error.toString() : String(error),
1930
+ },
1931
+ ['operational', 'behavioral']
1932
+ );
1933
+
1934
+ logWxAppSessionReadiness({
1935
+ enableWxBetterTogether: true,
1936
+ loginOption,
1937
+ wxAppHooksApplied: false,
1938
+ usersubPublished:
1939
+ publishedEnable && this.webexCrossClientService.isAnswerCallsStateActive(),
1940
+ mercurySubscribed: this.wxAppTelephonyMercurySync.isSubscribed(),
1941
+ telephonyTaskType,
1942
+ skipReason: sessionInitFailureReason,
1943
+ });
1944
+
1945
+ if (publishedEnable) {
1946
+ await this.revertWxAppCrossClientPublish();
1947
+ } else {
1948
+ this.wxAppTelephonyMercurySync.unsubscribe();
1949
+ await this.releaseWxAppMercuryResources();
1950
+ }
1951
+ this.resetEnableWxBetterTogetherConfig();
1952
+ } else {
1953
+ this.scheduleForcedFalsePublishRetry();
1954
+ }
1955
+ }
1956
+ }
1957
+
1958
+ private clearWxAppFalsePublishRetryTimer(): void {
1959
+ if (this.wxAppFalsePublishRetryTimer) {
1960
+ clearTimeout(this.wxAppFalsePublishRetryTimer);
1961
+ this.wxAppFalsePublishRetryTimer = undefined;
1962
+ }
1963
+ }
1964
+
1965
+ private scheduleForcedFalsePublishRetry(retryAttempt = 0): void {
1966
+ this.scheduleFalsePublishRetry(retryAttempt, {requireDisabled: true});
1967
+ }
1968
+
1969
+ /**
1970
+ * Retries compensating false publish after rollback failure, regardless of enabled flag.
1971
+ * @private
1972
+ */
1973
+ private scheduleCompensatingFalsePublishRetry(retryAttempt = 0): void {
1974
+ this.scheduleFalsePublishRetry(retryAttempt);
1975
+ }
1976
+
1977
+ private scheduleFalsePublishRetry(
1978
+ retryAttempt = 0,
1979
+ options: {requireDisabled?: boolean} = {}
1980
+ ): void {
1981
+ if (retryAttempt >= ContactCenter.WXAPP_FALSE_PUBLISH_MAX_RETRIES) {
1982
+ return;
1983
+ }
1984
+
1985
+ if (options?.requireDisabled && this.isWxBetterTogetherEnabled()) {
1986
+ return;
1987
+ }
1988
+
1989
+ this.clearWxAppFalsePublishRetryTimer();
1990
+
1991
+ this.wxAppFalsePublishRetryTimer = setTimeout(async () => {
1992
+ this.wxAppFalsePublishRetryTimer = undefined;
1993
+
1994
+ if (options?.requireDisabled && this.isWxBetterTogetherEnabled()) {
1995
+ return;
1996
+ }
1997
+
1998
+ if (
1999
+ !options?.requireDisabled &&
2000
+ this.isWxBetterTogetherEnabled() &&
2001
+ this.webexCrossClientService.isAnswerCallsStateActive()
2002
+ ) {
2003
+ return;
2004
+ }
2005
+
2006
+ try {
2007
+ await this.ensureWxAppDeviceRegistered();
2008
+ await this.publishAnswerOnWebexCrossClientState(false, {force: true});
2009
+ this.webexCrossClientService.teardown();
2010
+ this.wxAppTelephonyMercurySync.unsubscribe();
2011
+ await this.releaseWxAppMercuryResources();
2012
+ } catch (retryError) {
2013
+ LoggerProxy.error(
2014
+ `Failed to retry forced answer-calls-on-wxcc false publish: ${retryError}`,
2015
+ {
2016
+ module: CC_FILE,
2017
+ method: METHODS.SET_MANAGE_WEBEX_CALLING_IN_WXCC,
2018
+ }
2019
+ );
2020
+ this.scheduleFalsePublishRetry(retryAttempt + 1, options);
2021
+ }
2022
+ }, ContactCenter.WXAPP_FALSE_PUBLISH_RETRY_DELAY_MS);
2023
+ }
2024
+
2025
+ /**
2026
+ * Best-effort rollback when usersub true was published but wxApp setup did not complete.
2027
+ * @private
2028
+ */
2029
+ private async revertWxAppCrossClientPublish(): Promise<void> {
2030
+ try {
2031
+ await this.publishAnswerOnWebexCrossClientState(false, {force: true});
2032
+ } catch (error) {
2033
+ LoggerProxy.error(
2034
+ `Failed to publish compensating answer-calls-on-wxcc false during wxApp rollback: ${error}`,
2035
+ {
2036
+ module: CC_FILE,
2037
+ method: METHODS.SET_MANAGE_WEBEX_CALLING_IN_WXCC,
2038
+ }
2039
+ );
2040
+ this.scheduleCompensatingFalsePublishRetry();
2041
+ }
2042
+
2043
+ this.webexCrossClientService.teardown();
2044
+ this.wxAppTelephonyMercurySync.unsubscribe();
2045
+ await this.releaseWxAppMercuryResources();
2046
+ }
2047
+
2048
+ private async ensureWxAppDeviceRegistered(): Promise<void> {
2049
+ const device = this.$webex.internal.device;
2050
+
2051
+ if (device && !device.registered && device.register) {
2052
+ await device.register();
2053
+ this.wxAppDeviceRegisteredByCc = true;
2054
+ LoggerProxy.log('WxApp: device registered for cross-client publish', {
2055
+ module: CC_FILE,
2056
+ method: METHODS.ENSURE_WXAPP_MERCURY_CONNECTED,
2057
+ });
2058
+ }
2059
+ }
2060
+
2061
+ private async ensureWxAppMercuryConnected(): Promise<void> {
2062
+ const mercury = this.$webex.internal.mercury;
2063
+
2064
+ await this.ensureWxAppDeviceRegistered();
2065
+
2066
+ if (mercury && !mercury.connected) {
2067
+ await mercury.connect();
2068
+ this.wxAppMercuryConnectedByCc = true;
2069
+ LoggerProxy.log('WxApp mute sync: mercury connected', {
2070
+ module: CC_FILE,
2071
+ method: METHODS.ENSURE_WXAPP_MERCURY_CONNECTED,
2072
+ });
2073
+ }
2074
+ }
2075
+
2076
+ /**
2077
+ * Disconnects Mercury and unregisters the device when wxApp mute sync opened them.
2078
+ * @private
2079
+ */
2080
+ private async releaseWxAppMercuryResources(): Promise<void> {
2081
+ const mercury = this.$webex.internal.mercury;
2082
+ const device = this.$webex.internal.device;
2083
+
2084
+ if (this.wxAppMercuryConnectedByCc && mercury?.connected) {
2085
+ try {
2086
+ await mercury.disconnect();
2087
+ this.wxAppMercuryConnectedByCc = false;
2088
+ LoggerProxy.log('WxApp mute sync: mercury disconnected', {
2089
+ module: CC_FILE,
2090
+ method: METHODS.ENSURE_WXAPP_MERCURY_CONNECTED,
2091
+ });
2092
+ } catch (error) {
2093
+ LoggerProxy.error(`Failed to disconnect wxApp Mercury: ${error}`, {
2094
+ module: CC_FILE,
2095
+ method: METHODS.ENSURE_WXAPP_MERCURY_CONNECTED,
2096
+ });
2097
+ }
2098
+ }
2099
+
2100
+ if (this.wxAppDeviceRegisteredByCc && device?.registered && device.unregister) {
2101
+ try {
2102
+ // @ts-ignore
2103
+ await device.unregister();
2104
+ this.wxAppDeviceRegisteredByCc = false;
2105
+ LoggerProxy.log('WxApp mute sync: device unregistered', {
2106
+ module: CC_FILE,
2107
+ method: METHODS.ENSURE_WXAPP_MERCURY_CONNECTED,
2108
+ });
2109
+ } catch (error) {
2110
+ LoggerProxy.error(`Failed to unregister wxApp device: ${error}`, {
2111
+ module: CC_FILE,
2112
+ method: METHODS.ENSURE_WXAPP_MERCURY_CONNECTED,
2113
+ });
2114
+ }
2115
+ } else if (this.wxAppDeviceRegisteredByCc) {
2116
+ this.wxAppDeviceRegisteredByCc = false;
2117
+ }
2118
+ }
2119
+
2120
+ /**
2121
+ * Clears wxApp runtime state and feature-owned resources. Publish failures are logged;
2122
+ * optional rethrow for deregister when callers need to surface usersub errors.
2123
+ * @private
2124
+ */
2125
+ private async teardownWxAppLocalState(options?: {
2126
+ rethrowPublishError?: boolean;
2127
+ clearInitFlag?: boolean;
2128
+ }): Promise<{publishError?: unknown}> {
2129
+ this.clearWxAppFalsePublishRetryTimer();
2130
+ let publishError: unknown;
2131
+
2132
+ try {
2133
+ const shouldForceFalsePublish =
2134
+ this.isWxBetterTogetherEnabled() || this.webexCrossClientService.isAnswerCallsStateActive();
2135
+
2136
+ await this.publishAnswerOnWebexCrossClientState(false, {
2137
+ force: shouldForceFalsePublish,
2138
+ });
2139
+ } catch (error) {
2140
+ publishError = error;
2141
+ LoggerProxy.error(
2142
+ `Failed to publish answer-calls-on-wxcc false during wxApp teardown: ${error}`,
2143
+ {
2144
+ module: CC_FILE,
2145
+ method: METHODS.SET_MANAGE_WEBEX_CALLING_IN_WXCC,
2146
+ }
2147
+ );
2148
+ }
2149
+
2150
+ this.webexCrossClientService.teardown();
2151
+ this.wxAppTelephonyMercurySync.unsubscribe();
2152
+ await this.releaseWxAppMercuryResources();
2153
+
2154
+ if (options?.clearInitFlag) {
2155
+ this.resetEnableWxBetterTogetherConfig();
2156
+ }
2157
+
2158
+ if (publishError) {
2159
+ this.scheduleCompensatingFalsePublishRetry();
2160
+ }
2161
+
2162
+ if (options?.rethrowPublishError && publishError) {
2163
+ throw publishError;
2164
+ }
2165
+
2166
+ return {publishError: publishError ?? undefined};
2167
+ }
2168
+
2169
+ private async ensureWxAppMercuryAndSubscribe(): Promise<void> {
2170
+ if (!this.isWxBetterTogetherEnabled()) {
2171
+ return;
2172
+ }
2173
+
2174
+ if (this.getCurrentStationLoginOption() === LoginOption.BROWSER) {
2175
+ return;
2176
+ }
2177
+
2178
+ const agentId = this.agentConfig?.agentId;
2179
+ if (!agentId) {
2180
+ LoggerProxy.error('Cannot subscribe wxApp mute sync: agentId unavailable', {
2181
+ module: CC_FILE,
2182
+ method: METHODS.SYNC_WXAPP_MUTE_FROM_MERCURY,
2183
+ });
2184
+
2185
+ this.metricsManager.trackEvent(
2186
+ METRIC_EVENT_NAMES.WXAPP_MERCURY_SUBSCRIBE_FAILED,
2187
+ {error: 'agentId unavailable'},
2188
+ ['operational', 'behavioral']
2189
+ );
2190
+
2191
+ return;
2192
+ }
2193
+
2194
+ try {
2195
+ await this.ensureWxAppMercuryConnected();
2196
+ this.wxAppTelephonyMercurySync.subscribe(agentId, (callId, muted) => {
2197
+ this.taskManager.applyWxAppMuteStateFromSync(callId, muted);
2198
+ });
2199
+ } catch (error) {
2200
+ LoggerProxy.error(`Failed to ensure wxApp Mercury mute sync: ${error}`, {
2201
+ module: CC_FILE,
2202
+ method: METHODS.SYNC_WXAPP_MUTE_FROM_MERCURY,
2203
+ });
2204
+ this.metricsManager.trackEvent(
2205
+ METRIC_EVENT_NAMES.WXAPP_MERCURY_SUBSCRIBE_FAILED,
2206
+ {error: error instanceof Error ? error.toString() : String(error)},
2207
+ ['operational', 'behavioral']
2208
+ );
2209
+ this.wxAppTelephonyMercurySync.unsubscribe();
2210
+ await this.releaseWxAppMercuryResources();
2211
+ throw error;
2212
+ }
2213
+ }
2214
+
2215
+ private async publishAnswerOnWebexCrossClientState(
2216
+ enable: boolean,
2217
+ options?: {force?: boolean}
2218
+ ): Promise<void> {
2219
+ if (enable && !this.isWxBetterTogetherEnabled()) {
2220
+ return;
2221
+ }
2222
+
2223
+ if (!enable && !options?.force && !this.webexCrossClientService.isAnswerCallsStateActive()) {
2224
+ return;
2225
+ }
2226
+
2227
+ const userId = this.$webex.internal.device?.userId;
2228
+ if (!userId) {
2229
+ if (enable && this.isWxBetterTogetherEnabled()) {
2230
+ this.metricsManager.trackEvent(
2231
+ METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
2232
+ {
2233
+ enableWxBetterTogether: true,
2234
+ skipReason: 'user_id_unavailable',
2235
+ error: 'User ID is unavailable for cross-client publish',
2236
+ },
2237
+ ['operational', 'behavioral']
2238
+ );
2239
+ }
2240
+
2241
+ return;
2242
+ }
2243
+
2244
+ await this.webexCrossClientService.setManageWebexCallingInWxcc(enable, {
2245
+ userId,
2246
+ trackPublishMetrics: true,
2247
+ });
2248
+
2249
+ if (enable) {
2250
+ this.clearWxAppFalsePublishRetryTimer();
2251
+ }
2252
+ }
2253
+
2254
+ /**
2255
+ * Initializes event listeners for the Contact Center service
2256
+ * Sets up handlers for connection state changes and other core events
2257
+ * @private
2258
+ */
2259
+ private setupEventListeners() {
2260
+ this.services.connectionService.on('connectionLost', this.handleConnectionLost.bind(this));
2261
+ }
2262
+
2263
+ /**
2264
+ * Returns the connection configuration
2265
+ * @returns {SubscribeRequest} Connection configuration
2266
+ * @private
2267
+ */
2268
+ private getConnectionConfig(): SubscribeRequest {
2269
+ return {
2270
+ force: this.$config?.force ?? true,
2271
+ isKeepAliveEnabled: this.$config?.isKeepAliveEnabled ?? false,
2272
+ clientType: this.$config?.clientType ?? 'WebexCCSDK',
2273
+ allowMultiLogin: this.$config?.allowMultiLogin ?? true,
2274
+ };
2275
+ }
2276
+
2277
+ /**
2278
+ * Handles connection lost events and reconnection attempts
2279
+ * @param {ConnectionLostDetails} msg Connection lost details
2280
+ * @private
2281
+ */
2282
+ private async handleConnectionLost(msg: ConnectionLostDetails): Promise<void> {
2283
+ if (msg.isConnectionLost) {
2284
+ // TODO: Emit an event saying connection is lost
2285
+ LoggerProxy.log('event=handleConnectionLost | Connection lost', {
2286
+ module: CC_FILE,
2287
+ method: METHODS.HANDLE_CONNECTION_LOST,
2288
+ });
2289
+ } else if (msg.isSocketReconnected) {
2290
+ // TODO: Emit an event saying connection is re-estabilished
2291
+ LoggerProxy.log(
2292
+ 'event=handleConnectionReconnect | Connection reconnected attempting to request silent relogin',
2293
+ {module: CC_FILE, method: METHODS.HANDLE_CONNECTION_LOST}
2294
+ );
2295
+ if (this.$config && this.$config.allowAutomatedRelogin) {
2296
+ await this.silentRelogin();
2297
+ }
2298
+ }
2299
+ }
2300
+
2301
+ /**
2302
+ * Handles silent relogin after registration completion
2303
+ * @private
2304
+ */
2305
+ private async silentRelogin(): Promise<void> {
2306
+ LoggerProxy.log('Starting silent relogin process', {
2307
+ module: CC_FILE,
2308
+ method: METHODS.SILENT_RELOGIN,
2309
+ });
2310
+
2311
+ try {
2312
+ const reLoginResponse = await this.services.agent.reload();
2313
+ this.updateWellnessSession(reLoginResponse.data.agentSessionId);
2314
+
2315
+ const {
2316
+ agentId,
2317
+ lastStateChangeReason,
2318
+ deviceType,
2319
+ dn,
2320
+ lastStateChangeTimestamp,
2321
+ lastIdleCodeChangeTimestamp,
2322
+ } = reLoginResponse.data;
2323
+ let {auxCodeId} = reLoginResponse.data;
2324
+ this.agentConfig.lastStateChangeTimestamp = lastStateChangeTimestamp;
2325
+ this.agentConfig.lastIdleCodeChangeTimestamp = lastIdleCodeChangeTimestamp;
2326
+ this.agentConfig.currentTeamId = reLoginResponse.data.teamId;
2327
+ await this.handleDeviceType(deviceType as LoginOption, dn);
2328
+
2329
+ if (lastStateChangeReason === 'agent-wss-disconnect') {
2330
+ LoggerProxy.log(
2331
+ 'event=requestAutoStateChange | Requesting state change to available on socket reconnect',
2332
+ {module: CC_FILE, method: METHODS.SILENT_RELOGIN}
2333
+ );
2334
+ auxCodeId = AGENT_STATE_AVAILABLE_ID;
1328
2335
  const stateChangeData: StateChange = {
1329
2336
  state: AGENT_STATE_AVAILABLE,
1330
2337
  auxCodeId,
@@ -1349,8 +2356,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1349
2356
  }
1350
2357
  this.agentConfig.lastStateAuxCodeId = auxCodeId;
1351
2358
  this.agentConfig.isAgentLoggedIn = true;
1352
- // TODO: https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-626777 Implement the de-register method and close the listener there
1353
- this.services.webSocketManager.on('message', this.handleWebsocketMessage);
2359
+
2360
+ await this.ensureWxAppPostStationLogin();
1354
2361
 
1355
2362
  LoggerProxy.log(
1356
2363
  `Silent relogin process completed successfully with login Option: ${reLoginResponse.data.deviceType} teamId: ${reLoginResponse.data.teamId}`,
@@ -1391,6 +2398,16 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1391
2398
  this.agentConfig.deviceType = deviceType;
1392
2399
  switch (deviceType) {
1393
2400
  case LoginOption.BROWSER:
2401
+ if (this.isWebRTCRegistrationDisabled()) {
2402
+ LoggerProxy.info(
2403
+ 'Skipping web calling line registration because disableWebRTCRegistration is enabled',
2404
+ {
2405
+ module: CC_FILE,
2406
+ method: METHODS.HANDLE_DEVICE_TYPE,
2407
+ }
2408
+ );
2409
+ break;
2410
+ }
1394
2411
  try {
1395
2412
  await this.webCallingService.registerWebCallingLine();
1396
2413
  } catch (error) {
@@ -1656,6 +2673,166 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1656
2673
  }
1657
2674
  }
1658
2675
 
2676
+ /**
2677
+ * Skips a campaign preview contact, requesting the next contact from the campaign.
2678
+ *
2679
+ * When a campaign manager reserves a contact for an agent, the agent receives an
2680
+ * `AgentOfferCampaignReservation` event. Instead of accepting, the agent can skip the
2681
+ * preview contact to move to the next contact in the campaign.
2682
+ *
2683
+ * @param {PreviewContactPayload} payload - The preview contact payload containing interactionId and campaignId (campaign name, not UUID).
2684
+ * @returns {Promise<TaskResponse>} Promise resolving with agent contact on success.
2685
+ * @throws {Error} If the operation fails (network error, etc.)
2686
+ *
2687
+ * @example
2688
+ * ```typescript
2689
+ * webex.cc.on('task:campaignPreviewReservation', async (task) => {
2690
+ * const { interactionId } = task.data;
2691
+ * const campaignId = task.data.interaction.callProcessingDetails.campaignId;
2692
+ *
2693
+ * const result = await webex.cc.skipPreviewContact({ interactionId, campaignId });
2694
+ * });
2695
+ * ```
2696
+ */
2697
+ public async skipPreviewContact(payload: PreviewContactPayload): Promise<TaskResponse> {
2698
+ const task = this.taskManager.getTask(payload.interactionId);
2699
+ if (task?.data?.interaction?.callProcessingDetails?.campaignPreviewSkipDisabled === 'true') {
2700
+ LoggerProxy.warn('Skip action is disabled for this campaign preview contact', {
2701
+ module: CC_FILE,
2702
+ method: METHODS.SKIP_PREVIEW_CONTACT,
2703
+ interactionId: payload.interactionId,
2704
+ });
2705
+ throw new Error('Skip action is disabled for this campaign preview contact');
2706
+ }
2707
+
2708
+ LoggerProxy.info('Skipping campaign preview contact', {
2709
+ module: CC_FILE,
2710
+ method: METHODS.SKIP_PREVIEW_CONTACT,
2711
+ });
2712
+ try {
2713
+ this.metricsManager.timeEvent([
2714
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_SUCCESS,
2715
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_FAILED,
2716
+ ]);
2717
+
2718
+ const result = await this.services.dialer.skipPreviewContact({data: payload});
2719
+
2720
+ this.metricsManager.trackEvent(
2721
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_SUCCESS,
2722
+ {
2723
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
2724
+ interactionId: payload.interactionId,
2725
+ campaignId: payload.campaignId,
2726
+ },
2727
+ ['behavioral', 'business', 'operational']
2728
+ );
2729
+
2730
+ LoggerProxy.log('Campaign preview contact skipped successfully', {
2731
+ module: CC_FILE,
2732
+ method: METHODS.SKIP_PREVIEW_CONTACT,
2733
+ trackingId: result.trackingId,
2734
+ interactionId: payload.interactionId,
2735
+ });
2736
+
2737
+ return result;
2738
+ } catch (error) {
2739
+ const failure = error.details as Failure;
2740
+ this.metricsManager.trackEvent(
2741
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_FAILED,
2742
+ {
2743
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
2744
+ interactionId: payload.interactionId,
2745
+ campaignId: payload.campaignId,
2746
+ },
2747
+ ['behavioral', 'business', 'operational']
2748
+ );
2749
+ const {error: detailedError} = getErrorDetails(error, METHODS.SKIP_PREVIEW_CONTACT, CC_FILE);
2750
+ throw detailedError;
2751
+ }
2752
+ }
2753
+
2754
+ /**
2755
+ * Removes a campaign preview contact from the campaign list entirely.
2756
+ *
2757
+ * When a campaign manager reserves a contact for an agent, the agent receives an
2758
+ * `AgentOfferCampaignReservation` event. Instead of accepting or skipping, the agent can
2759
+ * remove the preview contact to permanently take it out of the campaign contact list.
2760
+ *
2761
+ * @param {PreviewContactPayload} payload - The preview contact payload containing interactionId and campaignId (campaign name, not UUID).
2762
+ * @returns {Promise<TaskResponse>} Promise resolving with agent contact on success.
2763
+ * @throws {Error} If the operation fails (network error, etc.)
2764
+ *
2765
+ * @example
2766
+ * ```typescript
2767
+ * webex.cc.on('task:campaignPreviewReservation', async (task) => {
2768
+ * const { interactionId } = task.data;
2769
+ * const campaignId = task.data.interaction.callProcessingDetails.campaignId;
2770
+ *
2771
+ * const result = await webex.cc.removePreviewContact({ interactionId, campaignId });
2772
+ * });
2773
+ * ```
2774
+ */
2775
+ public async removePreviewContact(payload: PreviewContactPayload): Promise<TaskResponse> {
2776
+ const task = this.taskManager.getTask(payload.interactionId);
2777
+ if (task?.data?.interaction?.callProcessingDetails?.campaignPreviewRemoveDisabled === 'true') {
2778
+ LoggerProxy.warn('Remove action is disabled for this campaign preview contact', {
2779
+ module: CC_FILE,
2780
+ method: METHODS.REMOVE_PREVIEW_CONTACT,
2781
+ interactionId: payload.interactionId,
2782
+ });
2783
+ throw new Error('Remove action is disabled for this campaign preview contact');
2784
+ }
2785
+
2786
+ LoggerProxy.info('Removing campaign preview contact', {
2787
+ module: CC_FILE,
2788
+ method: METHODS.REMOVE_PREVIEW_CONTACT,
2789
+ });
2790
+ try {
2791
+ this.metricsManager.timeEvent([
2792
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_SUCCESS,
2793
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_FAILED,
2794
+ ]);
2795
+
2796
+ const result = await this.services.dialer.removePreviewContact({data: payload});
2797
+
2798
+ this.metricsManager.trackEvent(
2799
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_SUCCESS,
2800
+ {
2801
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
2802
+ interactionId: payload.interactionId,
2803
+ campaignId: payload.campaignId,
2804
+ },
2805
+ ['behavioral', 'business', 'operational']
2806
+ );
2807
+
2808
+ LoggerProxy.log('Campaign preview contact removed successfully', {
2809
+ module: CC_FILE,
2810
+ method: METHODS.REMOVE_PREVIEW_CONTACT,
2811
+ trackingId: result.trackingId,
2812
+ interactionId: payload.interactionId,
2813
+ });
2814
+
2815
+ return result;
2816
+ } catch (error) {
2817
+ const failure = error.details as Failure;
2818
+ this.metricsManager.trackEvent(
2819
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_FAILED,
2820
+ {
2821
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
2822
+ interactionId: payload.interactionId,
2823
+ campaignId: payload.campaignId,
2824
+ },
2825
+ ['behavioral', 'business', 'operational']
2826
+ );
2827
+ const {error: detailedError} = getErrorDetails(
2828
+ error,
2829
+ METHODS.REMOVE_PREVIEW_CONTACT,
2830
+ CC_FILE
2831
+ );
2832
+ throw detailedError;
2833
+ }
2834
+ }
2835
+
1659
2836
  /**
1660
2837
  * Fetches outdial ANI (Automatic Number Identification) entries for an outdial ANI ID.
1661
2838
  *
@@ -1900,8 +3077,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1900
3077
  }
1901
3078
 
1902
3079
  /**
1903
- * Returns paginated entry points for the organization.
1904
- * Thin wrapper around internal EntryPoint instance.
3080
+ * Returns paginated entry points using the EntryPoint service defaults.
3081
+ * Existing request parameters can override those defaults.
1905
3082
  * @public
1906
3083
  */
1907
3084
  public async getEntryPoints(
@@ -1911,8 +3088,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1911
3088
  }
1912
3089
 
1913
3090
  /**
1914
- * Returns paginated contact service queues for the organization.
1915
- * Thin wrapper around internal Queue instance.
3091
+ * Returns paginated contact service queues using the Queue service defaults.
3092
+ * Existing request parameters can override those defaults.
1916
3093
  * @public
1917
3094
  */
1918
3095
  public async getQueues(