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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. package/.sdd/manifest.json +883 -0
  2. package/AGENTS.md +94 -0
  3. package/ai-docs/ARCHITECTURE.md +168 -0
  4. package/ai-docs/CONTRACTS.md +50 -0
  5. package/ai-docs/GETTING_STARTED.md +168 -0
  6. package/ai-docs/GLOSSARY.md +43 -0
  7. package/ai-docs/README.md +138 -0
  8. package/ai-docs/REVIEW_CHECKLIST.md +41 -0
  9. package/ai-docs/RULES.md +444 -0
  10. package/ai-docs/SECURITY.md +52 -0
  11. package/ai-docs/SERVICE_STATE.md +48 -0
  12. package/ai-docs/SPEC_INDEX.md +65 -0
  13. package/ai-docs/adr/0001-spec-source-policy.md +55 -0
  14. package/ai-docs/adr/README.md +8 -0
  15. package/ai-docs/adr/_adr-template.md +31 -0
  16. package/ai-docs/contact-center-spec.md +359 -0
  17. package/ai-docs/features/consult-transfer-list-policy/spec/feature-spec.md +362 -0
  18. package/ai-docs/features/generated-spec-conformance-fidelity-remediation/spec/feature-spec.md +117 -0
  19. package/ai-docs/features/residual-warning-coverage-completion/spec/feature-spec.md +203 -0
  20. package/ai-docs/features/validator-code-fidelity-drift-fix/spec/feature-spec.md +315 -0
  21. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  22. package/ai-docs/patterns/testing-patterns.md +480 -0
  23. package/ai-docs/patterns/typescript-patterns.md +365 -0
  24. package/ai-docs/templates/README.md +102 -0
  25. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  26. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  27. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  28. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  29. package/ai-docs/templates/new-method/00-master.md +80 -0
  30. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  31. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  32. package/ai-docs/templates/new-method/03-tests.md +201 -0
  33. package/ai-docs/templates/new-method/04-validation.md +141 -0
  34. package/ai-docs/templates/new-service/00-master.md +109 -0
  35. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  36. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  37. package/ai-docs/templates/new-service/03-integration.md +178 -0
  38. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  39. package/ai-docs/templates/new-service/05-validation.md +145 -0
  40. package/dist/cc.js +818 -59
  41. package/dist/cc.js.map +1 -1
  42. package/dist/config.js +13 -0
  43. package/dist/config.js.map +1 -1
  44. package/dist/constants.js +31 -3
  45. package/dist/constants.js.map +1 -1
  46. package/dist/index.js +27 -5
  47. package/dist/index.js.map +1 -1
  48. package/dist/metrics/behavioral-events.js +127 -0
  49. package/dist/metrics/behavioral-events.js.map +1 -1
  50. package/dist/metrics/constants.js +34 -3
  51. package/dist/metrics/constants.js.map +1 -1
  52. package/dist/services/AddressBook.js +18 -15
  53. package/dist/services/AddressBook.js.map +1 -1
  54. package/dist/services/AnswerCallOnWebexService.js +174 -0
  55. package/dist/services/AnswerCallOnWebexService.js.map +1 -0
  56. package/dist/services/ApiAiAssistant.js +318 -0
  57. package/dist/services/ApiAiAssistant.js.map +1 -0
  58. package/dist/services/EntryPoint.js +46 -68
  59. package/dist/services/EntryPoint.js.map +1 -1
  60. package/dist/services/Queue.js +27 -22
  61. package/dist/services/Queue.js.map +1 -1
  62. package/dist/services/UserPreference.js +427 -0
  63. package/dist/services/UserPreference.js.map +1 -0
  64. package/dist/services/WebexCrossClientService.js +171 -0
  65. package/dist/services/WebexCrossClientService.js.map +1 -0
  66. package/dist/services/WxAppTelephonyMercurySync.js +93 -0
  67. package/dist/services/WxAppTelephonyMercurySync.js.map +1 -0
  68. package/dist/services/agent/types.js.map +1 -1
  69. package/dist/services/config/Util.js +11 -4
  70. package/dist/services/config/Util.js.map +1 -1
  71. package/dist/services/config/constants.js +45 -8
  72. package/dist/services/config/constants.js.map +1 -1
  73. package/dist/services/config/index.js +41 -2
  74. package/dist/services/config/index.js.map +1 -1
  75. package/dist/services/config/types.js +70 -8
  76. package/dist/services/config/types.js.map +1 -1
  77. package/dist/services/constants.js +27 -1
  78. package/dist/services/constants.js.map +1 -1
  79. package/dist/services/core/Err.js.map +1 -1
  80. package/dist/services/core/Utils.js +122 -25
  81. package/dist/services/core/Utils.js.map +1 -1
  82. package/dist/services/core/WebexRequest.js +6 -2
  83. package/dist/services/core/WebexRequest.js.map +1 -1
  84. package/dist/services/core/aqm-reqs.js +119 -30
  85. package/dist/services/core/aqm-reqs.js.map +1 -1
  86. package/dist/services/core/types.js.map +1 -1
  87. package/dist/services/core/websocket/WebSocketManager.js +22 -6
  88. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  89. package/dist/services/core/websocket/connection-service.js +3 -1
  90. package/dist/services/core/websocket/connection-service.js.map +1 -1
  91. package/dist/services/core/websocket/types.js.map +1 -1
  92. package/dist/services/index.js +6 -0
  93. package/dist/services/index.js.map +1 -1
  94. package/dist/services/task/Task.js +754 -0
  95. package/dist/services/task/Task.js.map +1 -0
  96. package/dist/services/task/TaskFactory.js +49 -0
  97. package/dist/services/task/TaskFactory.js.map +1 -0
  98. package/dist/services/task/TaskManager.js +1073 -447
  99. package/dist/services/task/TaskManager.js.map +1 -1
  100. package/dist/services/task/TaskUtils.js +220 -23
  101. package/dist/services/task/TaskUtils.js.map +1 -1
  102. package/dist/services/task/WebexCallingUtils.js +70 -0
  103. package/dist/services/task/WebexCallingUtils.js.map +1 -0
  104. package/dist/services/task/constants.js +26 -2
  105. package/dist/services/task/constants.js.map +1 -1
  106. package/dist/services/task/contact.js +29 -0
  107. package/dist/services/task/contact.js.map +1 -1
  108. package/dist/services/task/dialer.js +129 -0
  109. package/dist/services/task/dialer.js.map +1 -1
  110. package/dist/services/task/digital/Digital.js +78 -0
  111. package/dist/services/task/digital/Digital.js.map +1 -0
  112. package/dist/services/task/state-machine/TaskStateMachine.js +971 -0
  113. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  114. package/dist/services/task/state-machine/actions.js +572 -0
  115. package/dist/services/task/state-machine/actions.js.map +1 -0
  116. package/dist/services/task/state-machine/constants.js +161 -0
  117. package/dist/services/task/state-machine/constants.js.map +1 -0
  118. package/dist/services/task/state-machine/guards.js +409 -0
  119. package/dist/services/task/state-machine/guards.js.map +1 -0
  120. package/dist/services/task/state-machine/index.js +53 -0
  121. package/dist/services/task/state-machine/index.js.map +1 -0
  122. package/dist/services/task/state-machine/types.js +54 -0
  123. package/dist/services/task/state-machine/types.js.map +1 -0
  124. package/dist/services/task/state-machine/uiControlsComputer.js +703 -0
  125. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  126. package/dist/services/task/taskDataNormalizer.js +99 -0
  127. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  128. package/dist/services/task/types.js +243 -5
  129. package/dist/services/task/types.js.map +1 -1
  130. package/dist/services/task/voice/Voice.js +1380 -0
  131. package/dist/services/task/voice/Voice.js.map +1 -0
  132. package/dist/services/task/voice/WebRTC.js +152 -0
  133. package/dist/services/task/voice/WebRTC.js.map +1 -0
  134. package/dist/services/task/voice/wxAppVoiceMethods.js +201 -0
  135. package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
  136. package/dist/services/wxAppTelephonyUtils.js +19 -0
  137. package/dist/services/wxAppTelephonyUtils.js.map +1 -0
  138. package/dist/types/cc.d.ts +967 -0
  139. package/dist/types/config.d.ts +79 -0
  140. package/dist/types/constants.d.ts +74 -0
  141. package/dist/types/index.d.ts +201 -0
  142. package/dist/types/logger-proxy.d.ts +71 -0
  143. package/dist/types/metrics/MetricsManager.d.ts +223 -0
  144. package/dist/types/metrics/behavioral-events.d.ts +29 -0
  145. package/dist/types/metrics/constants.d.ts +183 -0
  146. package/dist/types/services/AddressBook.d.ts +75 -0
  147. package/dist/types/services/AnswerCallOnWebexService.d.ts +37 -0
  148. package/dist/types/services/ApiAiAssistant.d.ts +49 -0
  149. package/dist/types/services/EntryPoint.d.ts +69 -0
  150. package/dist/types/services/Queue.d.ts +78 -0
  151. package/dist/types/services/UserPreference.d.ts +118 -0
  152. package/dist/types/services/WebCallingService.d.ts +1 -0
  153. package/dist/types/services/WebexCrossClientService.d.ts +28 -0
  154. package/dist/types/services/WxAppTelephonyMercurySync.d.ts +28 -0
  155. package/dist/types/services/agent/index.d.ts +46 -0
  156. package/dist/types/services/agent/types.d.ts +413 -0
  157. package/dist/types/services/config/Util.d.ts +20 -0
  158. package/dist/types/services/config/constants.d.ts +273 -0
  159. package/dist/types/services/config/index.d.ts +177 -0
  160. package/dist/types/services/config/types.d.ts +1381 -0
  161. package/dist/types/services/constants.d.ts +110 -0
  162. package/dist/types/services/core/Err.d.ts +127 -0
  163. package/dist/types/services/core/GlobalTypes.d.ts +58 -0
  164. package/dist/types/services/core/Utils.d.ts +121 -0
  165. package/dist/types/services/core/WebexRequest.d.ts +23 -0
  166. package/dist/types/services/core/aqm-reqs.d.ts +65 -0
  167. package/dist/types/services/core/constants.d.ts +99 -0
  168. package/dist/types/services/core/types.d.ts +49 -0
  169. package/dist/types/services/core/websocket/WebSocketManager.d.ts +36 -0
  170. package/dist/types/services/core/websocket/connection-service.d.ts +27 -0
  171. package/dist/types/services/core/websocket/keepalive.worker.d.ts +2 -0
  172. package/dist/types/services/core/websocket/types.d.ts +37 -0
  173. package/dist/types/services/index.d.ts +54 -0
  174. package/dist/types/services/task/AutoWrapup.d.ts +40 -0
  175. package/dist/types/services/task/Task.d.ts +175 -0
  176. package/dist/types/services/task/TaskFactory.d.ts +13 -0
  177. package/dist/types/services/task/TaskManager.d.ts +1 -0
  178. package/dist/types/services/task/TaskUtils.d.ts +138 -0
  179. package/dist/types/services/task/WebexCallingUtils.d.ts +11 -0
  180. package/dist/types/services/task/constants.d.ts +94 -0
  181. package/dist/types/services/task/contact.d.ts +73 -0
  182. package/dist/types/services/task/dialer.d.ts +73 -0
  183. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  184. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1398 -0
  185. package/dist/types/services/task/state-machine/actions.d.ts +10 -0
  186. package/dist/types/services/task/state-machine/constants.d.ts +107 -0
  187. package/dist/types/services/task/state-machine/guards.d.ts +103 -0
  188. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  189. package/dist/types/services/task/state-machine/types.d.ts +277 -0
  190. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  191. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  192. package/dist/types/services/task/types.d.ts +1933 -0
  193. package/dist/types/services/task/voice/Voice.d.ts +223 -0
  194. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  195. package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +52 -0
  196. package/dist/types/services/wxAppTelephonyUtils.d.ts +5 -0
  197. package/dist/types/types.d.ts +784 -0
  198. package/dist/types/utils/PageCache.d.ts +190 -0
  199. package/dist/types/webex-config.d.ts +53 -0
  200. package/dist/types/webex.d.ts +8 -0
  201. package/dist/types.js +137 -3
  202. package/dist/types.js.map +1 -1
  203. package/dist/utils/PageCache.js +19 -5
  204. package/dist/utils/PageCache.js.map +1 -1
  205. package/dist/webex.js +14 -2
  206. package/dist/webex.js.map +1 -1
  207. package/package.json +16 -12
  208. package/src/cc.ts +983 -60
  209. package/src/config.ts +13 -0
  210. package/src/constants.ts +29 -1
  211. package/src/index.ts +26 -5
  212. package/src/metrics/ai-docs/AGENTS.md +350 -0
  213. package/src/metrics/ai-docs/ARCHITECTURE.md +338 -0
  214. package/src/metrics/ai-docs/metrics-spec.md +860 -0
  215. package/src/metrics/behavioral-events.ts +134 -0
  216. package/src/metrics/constants.ts +39 -3
  217. package/src/services/AddressBook.ts +17 -6
  218. package/src/services/AnswerCallOnWebexService.ts +206 -0
  219. package/src/services/ApiAiAssistant.ts +412 -0
  220. package/src/services/EntryPoint.ts +59 -60
  221. package/src/services/Queue.ts +29 -12
  222. package/src/services/UserPreference.ts +509 -0
  223. package/src/services/WebexCrossClientService.ts +212 -0
  224. package/src/services/WxAppTelephonyMercurySync.ts +115 -0
  225. package/src/services/agent/ai-docs/AGENTS.md +240 -0
  226. package/src/services/agent/ai-docs/ARCHITECTURE.md +304 -0
  227. package/src/services/agent/ai-docs/agent-spec.md +504 -0
  228. package/src/services/agent/types.ts +1 -1
  229. package/src/services/ai-docs/AGENTS.md +386 -0
  230. package/src/services/ai-docs/services-spec.md +497 -0
  231. package/src/services/config/Util.ts +13 -2
  232. package/src/services/config/ai-docs/AGENTS.md +255 -0
  233. package/src/services/config/ai-docs/ARCHITECTURE.md +426 -0
  234. package/src/services/config/ai-docs/config-spec.md +675 -0
  235. package/src/services/config/constants.ts +47 -7
  236. package/src/services/config/index.ts +45 -1
  237. package/src/services/config/types.ts +253 -11
  238. package/src/services/constants.ts +29 -0
  239. package/src/services/core/Err.ts +4 -0
  240. package/src/services/core/Utils.ts +143 -30
  241. package/src/services/core/WebexRequest.ts +3 -1
  242. package/src/services/core/ai-docs/AGENTS.md +381 -0
  243. package/src/services/core/ai-docs/ARCHITECTURE.md +698 -0
  244. package/src/services/core/ai-docs/core-spec.md +787 -0
  245. package/src/services/core/aqm-reqs.ts +125 -32
  246. package/src/services/core/types.ts +2 -0
  247. package/src/services/core/websocket/WebSocketManager.ts +23 -6
  248. package/src/services/core/websocket/connection-service.ts +5 -1
  249. package/src/services/core/websocket/types.ts +1 -1
  250. package/src/services/index.ts +4 -0
  251. package/src/services/task/Task.ts +908 -0
  252. package/src/services/task/TaskFactory.ts +60 -0
  253. package/src/services/task/TaskManager.ts +1291 -513
  254. package/src/services/task/TaskUtils.ts +314 -24
  255. package/src/services/task/WebexCallingUtils.ts +136 -0
  256. package/src/services/task/ai-docs/AGENTS.md +457 -0
  257. package/src/services/task/ai-docs/ARCHITECTURE.md +595 -0
  258. package/src/services/task/ai-docs/task-spec.md +1469 -0
  259. package/src/services/task/constants.ts +26 -0
  260. package/src/services/task/contact.ts +30 -0
  261. package/src/services/task/dialer.ts +136 -1
  262. package/src/services/task/digital/Digital.ts +97 -0
  263. package/src/services/task/state-machine/TaskStateMachine.ts +1313 -0
  264. package/src/services/task/state-machine/actions.ts +741 -0
  265. package/src/services/task/state-machine/ai-docs/AGENTS.md +462 -0
  266. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1146 -0
  267. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2209 -0
  268. package/src/services/task/state-machine/constants.ts +172 -0
  269. package/src/services/task/state-machine/guards.ts +498 -0
  270. package/src/services/task/state-machine/index.ts +28 -0
  271. package/src/services/task/state-machine/types.ts +258 -0
  272. package/src/services/task/state-machine/uiControlsComputer.ts +1135 -0
  273. package/src/services/task/taskDataNormalizer.ts +137 -0
  274. package/src/services/task/types.ts +843 -70
  275. package/src/services/task/voice/Voice.ts +1720 -0
  276. package/src/services/task/voice/WebRTC.ts +191 -0
  277. package/src/services/task/voice/wxAppVoiceMethods.ts +307 -0
  278. package/src/services/wxAppTelephonyUtils.ts +14 -0
  279. package/src/types.ts +238 -11
  280. package/src/utils/AGENTS.md +289 -0
  281. package/src/utils/PageCache.ts +38 -5
  282. package/src/utils/ai-docs/utils-spec.md +391 -0
  283. package/src/webex.js +2 -0
  284. package/test/unit/spec/cc.ts +1856 -122
  285. package/test/unit/spec/logger-proxy.ts +70 -0
  286. package/test/unit/spec/metrics/behavioral-events.ts +18 -0
  287. package/test/unit/spec/services/AddressBook.ts +37 -6
  288. package/test/unit/spec/services/AnswerCallOnWebexService.ts +223 -0
  289. package/test/unit/spec/services/ApiAiAssistant.ts +273 -0
  290. package/test/unit/spec/services/EntryPoint.ts +87 -40
  291. package/test/unit/spec/services/Queue.ts +123 -12
  292. package/test/unit/spec/services/UserPreference.ts +401 -0
  293. package/test/unit/spec/services/WebCallingService.ts +7 -1
  294. package/test/unit/spec/services/WebexCrossClientService.ts +261 -0
  295. package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +113 -0
  296. package/test/unit/spec/services/config/Util.ts +85 -0
  297. package/test/unit/spec/services/config/index.ts +85 -29
  298. package/test/unit/spec/services/core/Utils.ts +481 -2
  299. package/test/unit/spec/services/core/WebexRequest.ts +3 -1
  300. package/test/unit/spec/services/core/aqm-reqs.ts +113 -1
  301. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +137 -41
  302. package/test/unit/spec/services/core/websocket/connection-service.ts +3 -1
  303. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  304. package/test/unit/spec/services/task/Task.ts +677 -0
  305. package/test/unit/spec/services/task/TaskFactory.ts +99 -0
  306. package/test/unit/spec/services/task/TaskManager.ts +2209 -918
  307. package/test/unit/spec/services/task/TaskUtils.ts +235 -0
  308. package/test/unit/spec/services/task/WebexCallingUtils.ts +153 -0
  309. package/test/unit/spec/services/task/contact.ts +33 -0
  310. package/test/unit/spec/services/task/dialer.ts +372 -96
  311. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  312. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +3433 -0
  313. package/test/unit/spec/services/task/state-machine/guards.ts +839 -0
  314. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  315. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +3101 -0
  316. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  317. package/test/unit/spec/services/task/voice/Voice.ts +1523 -0
  318. package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
  319. package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +459 -0
  320. package/umd/contact-center.min.js +2 -2
  321. package/umd/contact-center.min.js.map +1 -1
  322. package/dist/services/task/index.js +0 -1525
  323. package/dist/services/task/index.js.map +0 -1
  324. package/src/services/task/index.ts +0 -1801
  325. package/test/unit/spec/services/task/index.ts +0 -2184
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Constants for the task state machine.
3
+ * These enums define the allowed states, events, and built-in action identifiers.
4
+ */
5
+
6
+ // ============================================
7
+ // Conference Constants
8
+ // ============================================
9
+
10
+ /**
11
+ * Maximum number of participants allowed in a multi-party conference.
12
+ * Max 7 counted agents + 1 customer.
13
+ */
14
+ export const MAX_PARTICIPANTS_IN_MULTIPARTY_CONFERENCE = 7;
15
+
16
+ // ============================================
17
+ // Participant Type Constants
18
+ // ============================================
19
+
20
+ /** Participant types for conference filtering */
21
+ export const PARTICIPANT_TYPE = {
22
+ CUSTOMER: 'Customer',
23
+ SUPERVISOR: 'Supervisor',
24
+ VVA: 'VVA',
25
+ } as const;
26
+
27
+ export type ParticipantType = (typeof PARTICIPANT_TYPE)[keyof typeof PARTICIPANT_TYPE];
28
+
29
+ // ============================================
30
+ // Media Type Constants
31
+ // ============================================
32
+
33
+ /** Media type for consult calls */
34
+ export const MEDIA_TYPE_CONSULT = 'consult';
35
+
36
+ /** Media type for main calls */
37
+ export const MEDIA_TYPE_MAIN_CALL = 'mainCall';
38
+
39
+ // ============================================
40
+ // Backend Interaction State Constants
41
+ // ============================================
42
+
43
+ /** Backend interaction state values (from server payloads) */
44
+ export const INTERACTION_STATE = {
45
+ CONSULTING: 'consulting',
46
+ POST_CALL: 'post_call',
47
+ CONFERENCE: 'conference',
48
+ CONNECTED: 'connected',
49
+ NEW: 'new',
50
+ } as const;
51
+
52
+ /** Backend participant consultState values */
53
+ export const CONSULT_STATE = {
54
+ CONSULTING: 'consulting',
55
+ CONFERENCING: 'conferencing',
56
+ } as const;
57
+
58
+ // ============================================
59
+ // State Machine Enums
60
+ // ============================================
61
+
62
+ export enum TaskState {
63
+ IDLE = 'IDLE',
64
+ OFFERED = 'OFFERED',
65
+ CONNECTED = 'CONNECTED',
66
+
67
+ // Intermediate states for async operations
68
+ HOLD_INITIATING = 'HOLD_INITIATING',
69
+ HELD = 'HELD',
70
+ RESUME_INITIATING = 'RESUME_INITIATING',
71
+ CONSULT_INITIATING = 'CONSULT_INITIATING',
72
+ CONSULTING = 'CONSULTING',
73
+ CONF_INITIATING = 'CONF_INITIATING',
74
+
75
+ CONFERENCING = 'CONFERENCING',
76
+ WRAPPING_UP = 'WRAPPING_UP',
77
+ COMPLETED = 'COMPLETED',
78
+ TERMINATED = 'TERMINATED',
79
+
80
+ // NOT IMPLEMENTED: MPC (Multi-Party Conference) states
81
+ CONSULT_INITIATED = 'CONSULT_INITIATED',
82
+ CONSULT_COMPLETED = 'CONSULT_COMPLETED',
83
+ // NOT IMPLEMENTED: Post-call state (isWxccPostCallEnabled feature flag)
84
+ POST_CALL = 'POST_CALL',
85
+ // NOT IMPLEMENTED: Parked state
86
+ PARKED = 'PARKED',
87
+ // NOT IMPLEMENTED: Monitoring/Supervisory states
88
+ MONITORING = 'MONITORING',
89
+ }
90
+
91
+ export enum TaskEvent {
92
+ TASK_INCOMING = 'TASK_INCOMING',
93
+ TASK_OFFERED = 'TASK_OFFERED',
94
+
95
+ // Offer events
96
+ OFFER_CONSULT = 'OFFER_CONSULT',
97
+ HYDRATE = 'HYDRATE',
98
+
99
+ // Internal "data refresh" events
100
+ CONTACT_UPDATED = 'CONTACT_UPDATED',
101
+ CONTACT_OWNER_CHANGED = 'CONTACT_OWNER_CHANGED',
102
+
103
+ // Assignment events
104
+ ASSIGN = 'ASSIGN',
105
+
106
+ // Hold/Resume events
107
+ HOLD_SUCCESS = 'HOLD_SUCCESS',
108
+ HOLD_FAILED = 'HOLD_FAILED',
109
+ UNHOLD_SUCCESS = 'UNHOLD_SUCCESS',
110
+ UNHOLD_FAILED = 'UNHOLD_FAILED',
111
+ HOLD_INITIATED = 'HOLD_INITIATED',
112
+ UNHOLD_INITIATED = 'UNHOLD_INITIATED',
113
+
114
+ // Consult events
115
+ CONSULT = 'CONSULT',
116
+ CONSULT_SUCCESS = 'CONSULT_SUCCESS',
117
+ CONSULT_CREATED = 'CONSULT_CREATED',
118
+ CONSULTING_ACTIVE = 'CONSULTING_ACTIVE',
119
+ CONSULT_END = 'CONSULT_END',
120
+ CONSULT_FAILED = 'CONSULT_FAILED',
121
+
122
+ // Conference events
123
+ MERGE_TO_CONFERENCE = 'MERGE_TO_CONFERENCE',
124
+ CONFERENCE_START = 'CONFERENCE_START',
125
+ CONFERENCE_FAILED = 'CONFERENCE_FAILED',
126
+ CONFERENCE_END = 'CONFERENCE_END',
127
+ TRANSFER_CONFERENCE = 'TRANSFER_CONFERENCE',
128
+ TRANSFER_CONFERENCE_SUCCESS = 'TRANSFER_CONFERENCE_SUCCESS',
129
+ TRANSFER_CONFERENCE_FAILED = 'TRANSFER_CONFERENCE_FAILED',
130
+ PARTICIPANT_LEAVE = 'PARTICIPANT_LEAVE',
131
+ EXIT_CONFERENCE = 'EXIT_CONFERENCE',
132
+ EXIT_CONFERENCE_SUCCESS = 'EXIT_CONFERENCE_SUCCESS',
133
+ EXIT_CONFERENCE_FAILED = 'EXIT_CONFERENCE_FAILED',
134
+
135
+ // Recording events
136
+ RECORDING_STARTED = 'RECORDING_STARTED',
137
+ PAUSE_RECORDING = 'PAUSE_RECORDING',
138
+ RESUME_RECORDING = 'RESUME_RECORDING',
139
+
140
+ // Transfer events
141
+ TRANSFER_SUCCESS = 'TRANSFER_SUCCESS',
142
+ TRANSFER_FAILED = 'TRANSFER_FAILED',
143
+
144
+ // Wrapup events
145
+ WRAPUP_COMPLETE = 'WRAPUP_COMPLETE',
146
+
147
+ // End events
148
+ TASK_WRAPUP = 'TASK_WRAPUP',
149
+ RONA = 'RONA', // Ring On No Answer
150
+ CONTACT_ENDED = 'CONTACT_ENDED',
151
+
152
+ // Failure events
153
+ ASSIGN_FAILED = 'ASSIGN_FAILED',
154
+ INVITE_FAILED = 'INVITE_FAILED',
155
+ OUTBOUND_FAILED = 'OUTBOUND_FAILED',
156
+ CAMPAIGN_PREVIEW_ACCEPT_FAILED = 'CAMPAIGN_PREVIEW_ACCEPT_FAILED',
157
+ CAMPAIGN_PREVIEW_SKIP_FAILED = 'CAMPAIGN_PREVIEW_SKIP_FAILED',
158
+ CAMPAIGN_PREVIEW_REMOVE_FAILED = 'CAMPAIGN_PREVIEW_REMOVE_FAILED',
159
+
160
+ // Switch events (toggle between consult and main call)
161
+ SWITCH_TO_MAIN_CALL = 'SWITCH_TO_MAIN_CALL',
162
+ SWITCH_TO_CONSULT = 'SWITCH_TO_CONSULT',
163
+
164
+ // Accept/Decline (WebRTC)
165
+ ACCEPT = 'ACCEPT',
166
+ DECLINE = 'DECLINE',
167
+ END = 'END',
168
+
169
+ // Queue events
170
+ CTQ_CANCEL = 'CTQ_CANCEL', // Cancel To Queue
171
+ CTQ_CANCEL_FAILED = 'CTQ_CANCEL_FAILED',
172
+ }
@@ -0,0 +1,498 @@
1
+ /**
2
+ * Task State Machine Guards
3
+ *
4
+ * Guard functions that determine if a state transition is allowed.
5
+ * These functions validate the current context before allowing transitions.
6
+ *
7
+ * All guards are consolidated here for:
8
+ * - Single source of truth
9
+ * - Easy testing
10
+ * - Reusability across state machine transitions
11
+ *
12
+ * Guards are organized by category:
13
+ * 1. Helper Functions - Extract data from events/context
14
+ * 2. Hydrate Guards - For state restoration on page refresh
15
+ * 3. Conference Guards - Conference state checks
16
+ * 4. Customer Guards - Customer presence checks
17
+ * 5. Consult Guards - Consult flow checks
18
+ * 6. Wrapup Guards - End-of-call flow checks
19
+ * 7. Server State Guards - Check backend-reported state
20
+ * 8. Recording Guards - Recording state checks
21
+ */
22
+
23
+ import {TaskContext, TaskEventPayload} from './types';
24
+ import {TaskData} from '../types';
25
+ import {
26
+ getIsCustomerInCall,
27
+ getConferenceParticipantsCount,
28
+ getIsConferenceInProgress,
29
+ isCampaignPreviewTask,
30
+ isEpDnConsultPendingConferenceMerge,
31
+ } from '../TaskUtils';
32
+ import {TaskEvent, INTERACTION_STATE, CONSULT_STATE, MEDIA_TYPE_CONSULT} from './constants';
33
+
34
+ export const getTaskDataFromEvent = (event?: TaskEventPayload): TaskData | undefined =>
35
+ event && typeof event === 'object' && 'taskData' in event
36
+ ? (event as {taskData?: TaskData}).taskData
37
+ : undefined;
38
+
39
+ export const getSelfAgentId = (context: TaskContext, taskData?: TaskData): string | undefined =>
40
+ context.uiControlConfig?.agentId ?? context.taskData?.agentId ?? taskData?.agentId;
41
+
42
+ export const isSelfConsultingAgent = (context: TaskContext, taskData?: TaskData): boolean => {
43
+ const selfAgentId = getSelfAgentId(context, taskData);
44
+ if (!selfAgentId) return false;
45
+
46
+ return taskData?.consultingAgentId === selfAgentId;
47
+ };
48
+
49
+ /**
50
+ * Detects an active consult during post_call state (customer left but agents still consulting).
51
+ * Shared by hydration guard (isInteractionConsulting) and action (deriveTaskDataUpdates).
52
+ */
53
+ export const hasActiveConsultInPostCall = (
54
+ taskData: TaskData | undefined,
55
+ selfAgentId?: string
56
+ ): boolean => {
57
+ if (taskData?.interaction?.state !== INTERACTION_STATE.POST_CALL || !selfAgentId) return false;
58
+
59
+ const selfParticipant = taskData.interaction?.participants?.[selfAgentId];
60
+ const hasConsultMedia = Object.values(taskData.interaction?.media ?? {}).some(
61
+ (media) => (media as {mType?: string})?.mType === MEDIA_TYPE_CONSULT
62
+ );
63
+
64
+ return selfParticipant?.consultState === CONSULT_STATE.CONSULTING && hasConsultMedia;
65
+ };
66
+
67
+ /**
68
+ * Determines if this agent should enter WRAPPING_UP state.
69
+ * Priority: agentsPendingWrapUp > wrapUpRequired / participant.isWrapUp > ownership > !isConsulted
70
+ */
71
+ export const shouldWrapUpForThisAgent = (context: TaskContext, taskData: TaskData): boolean => {
72
+ const selfAgentId = getSelfAgentId(context, taskData);
73
+ if (!selfAgentId) return false;
74
+
75
+ const pending = taskData?.agentsPendingWrapUp;
76
+ if (Array.isArray(pending) && pending.length > 0) {
77
+ return pending.includes(selfAgentId);
78
+ }
79
+
80
+ const participantWrapUp = taskData?.interaction?.participants?.[selfAgentId]?.isWrapUp === true;
81
+ const wrapUpRequired = taskData?.wrapUpRequired === true;
82
+ if (wrapUpRequired || participantWrapUp) {
83
+ return true;
84
+ }
85
+
86
+ const owner = taskData?.interaction?.owner;
87
+ if (owner && owner === selfAgentId) {
88
+ return true;
89
+ }
90
+
91
+ if (taskData?.isConsulted === false) {
92
+ return true;
93
+ }
94
+
95
+ return false;
96
+ };
97
+
98
+ export interface GuardParams {
99
+ context: TaskContext;
100
+ event?: TaskEventPayload;
101
+ }
102
+
103
+ export type GuardFunction = (params: GuardParams) => boolean;
104
+
105
+ export const guards = {
106
+ // Hydrate Guards
107
+ isInteractionTerminated: ({context, event}: GuardParams): boolean => {
108
+ const taskData = getTaskDataFromEvent(event);
109
+
110
+ if (taskData?.interaction?.isTerminated === true) return true;
111
+
112
+ const selfAgentId = getSelfAgentId(context, taskData);
113
+ if (selfAgentId && taskData?.interaction?.participants?.[selfAgentId]?.isWrapUp === true) {
114
+ return true;
115
+ }
116
+
117
+ return false;
118
+ },
119
+
120
+ isInteractionConsulting: ({event, context}: GuardParams): boolean => {
121
+ const taskData = getTaskDataFromEvent(event);
122
+
123
+ if (taskData?.interaction?.state === INTERACTION_STATE.CONSULTING) return true;
124
+
125
+ // Hydrate can report interaction as conference/hold while this agent is actively consulting.
126
+ // Detect consult by participant consultState + consult media, independent of top-level state.
127
+ const selfAgentId = getSelfAgentId(context, taskData);
128
+ const selfParticipant = selfAgentId ? taskData?.interaction?.participants?.[selfAgentId] : null;
129
+ const hasConsultMedia = Object.values(taskData?.interaction?.media ?? {}).some(
130
+ (media: any) => media?.mType === MEDIA_TYPE_CONSULT
131
+ );
132
+ const selfActiveConsult = selfParticipant?.consultState === CONSULT_STATE.CONSULTING;
133
+ const selfPendingConsultInitiated =
134
+ selfParticipant?.consultState === 'consultInitiated' && taskData?.isConsulted === false;
135
+ if ((selfActiveConsult || selfPendingConsultInitiated) && hasConsultMedia) {
136
+ return true;
137
+ }
138
+
139
+ // EP_DN consulted agent: backend reports state as 'connected' but CPD indicates consult
140
+ const cpd = taskData?.interaction?.callProcessingDetails;
141
+ if (
142
+ cpd?.relationshipType === 'consult' &&
143
+ taskData?.interaction?.state === INTERACTION_STATE.CONNECTED
144
+ ) {
145
+ return true;
146
+ }
147
+
148
+ // Customer left during consult: interaction state is "post_call" but consult
149
+ // between agents is still active. Detect via agent's consultState + consult media.
150
+ if (hasActiveConsultInPostCall(taskData, getSelfAgentId(context, taskData))) {
151
+ return true;
152
+ }
153
+
154
+ // Customer left during consult: interaction state is "post_call" but consult
155
+ // between agents is still active. Detect via agent's consultState + consult media.
156
+ if (taskData?.interaction?.state === INTERACTION_STATE.POST_CALL) {
157
+ const postCallSelfAgentId = getSelfAgentId(context, taskData);
158
+ const postCallSelfParticipant = postCallSelfAgentId
159
+ ? taskData?.interaction?.participants?.[postCallSelfAgentId]
160
+ : null;
161
+ const hasPostCallConsultMedia = Object.values(taskData?.interaction?.media ?? {}).some(
162
+ (media: any) => media?.mType === MEDIA_TYPE_CONSULT
163
+ );
164
+ if (
165
+ postCallSelfParticipant?.consultState === CONSULT_STATE.CONSULTING &&
166
+ hasPostCallConsultMedia
167
+ ) {
168
+ return true;
169
+ }
170
+ }
171
+
172
+ return false;
173
+ },
174
+
175
+ isEpDnPendingConferenceMergeHydrate: ({event, context}: GuardParams): boolean => {
176
+ const taskData = getTaskDataFromEvent(event);
177
+
178
+ return isEpDnConsultPendingConferenceMerge(taskData, getSelfAgentId(context, taskData));
179
+ },
180
+
181
+ isInteractionHeld: ({event}: GuardParams): boolean => {
182
+ const taskData = getTaskDataFromEvent(event);
183
+
184
+ if (taskData?.interaction?.state === 'hold') return true;
185
+
186
+ const mainMediaId = taskData?.interaction?.mainInteractionId || taskData?.interactionId;
187
+ if (mainMediaId && taskData?.interaction?.media?.[mainMediaId]?.isHold === true) {
188
+ return true;
189
+ }
190
+
191
+ return false;
192
+ },
193
+
194
+ isInteractionConnected: ({event}: GuardParams): boolean => {
195
+ const taskData = getTaskDataFromEvent(event);
196
+
197
+ return taskData?.interaction?.state === INTERACTION_STATE.CONNECTED;
198
+ },
199
+
200
+ isConferencingByParticipants: ({event}: GuardParams): boolean => {
201
+ const taskData = getTaskDataFromEvent(event);
202
+ if (!taskData) return false;
203
+ const mainCallId = taskData.interaction?.mainInteractionId || taskData.interactionId;
204
+ const media = taskData.interaction?.media?.[mainCallId];
205
+ const participants = taskData.interaction?.participants;
206
+ if (!media?.participants || !participants) return false;
207
+ let agentCount = 0;
208
+ for (const pId of media.participants) {
209
+ const p = participants[pId];
210
+ if (p && p.pType !== 'Customer' && p.pType !== 'Supervisor' && !p.hasLeft) {
211
+ agentCount += 1;
212
+ }
213
+ }
214
+
215
+ return agentCount >= 2;
216
+ },
217
+
218
+ // Conference Guards
219
+ conferenceInProgressFromEvent: ({event}: GuardParams): boolean => {
220
+ const taskData = getTaskDataFromEvent(event);
221
+ if (!taskData?.interaction) return false;
222
+
223
+ return getIsConferenceInProgress(taskData);
224
+ },
225
+
226
+ /**
227
+ * Conference hold signal for conference-downgraded consult-end transitions.
228
+ * Backend can report this as boolean or string.
229
+ */
230
+ isConferenceHoldParticipantFromEvent: ({context, event}: GuardParams): boolean => {
231
+ const taskData = getTaskDataFromEvent(event) ?? context.taskData;
232
+ const callProcessingDetails = taskData?.interaction?.callProcessingDetails as
233
+ | {conferenceHoldParticipant?: boolean | string}
234
+ | undefined;
235
+ const conferenceHoldParticipant = callProcessingDetails?.conferenceHoldParticipant;
236
+
237
+ return conferenceHoldParticipant === true || conferenceHoldParticipant === 'true';
238
+ },
239
+
240
+ /**
241
+ * Conference downgrade check specifically for transitioning back to CONNECTED.
242
+ *
243
+ * Returns true only when:
244
+ * - conference has downgraded (fewer than 2 active agent participants in main call)
245
+ * - customer is still in the call
246
+ * - current agent is still in the main call
247
+ */
248
+ shouldDowngradeConferenceToConnected: ({context, event}: GuardParams): boolean => {
249
+ const eventTaskData = getTaskDataFromEvent(event);
250
+ const taskData = eventTaskData ?? context.taskData;
251
+ if (!taskData?.interaction) return false;
252
+
253
+ const selfAgentId = getSelfAgentId(context, taskData);
254
+ if (!selfAgentId) return false;
255
+
256
+ const mainCallId = taskData.interaction.mainInteractionId || taskData.interactionId;
257
+ if (!mainCallId) return false;
258
+
259
+ // Don't downgrade while backend still reports conference.
260
+ if (taskData.interaction.state === INTERACTION_STATE.CONFERENCE) return false;
261
+
262
+ const agentParticipantsCount = getConferenceParticipantsCount(taskData.interaction, mainCallId);
263
+ if (agentParticipantsCount >= 2) return false;
264
+
265
+ const customerInCall = getIsCustomerInCall(taskData.interaction, mainCallId);
266
+ if (!customerInCall) return false;
267
+
268
+ const selfInMainCall = Boolean(
269
+ taskData.interaction.media?.[mainCallId]?.participants?.includes(selfAgentId)
270
+ );
271
+
272
+ return selfInMainCall;
273
+ },
274
+
275
+ // Consult Guards
276
+ /**
277
+ * Check if this agent initiated the consult (using event data)
278
+ * Handles both consultingAgentId and fallback to context flag
279
+ */
280
+ didInitiateConsult: ({context, event}: GuardParams): boolean => {
281
+ const taskData = getTaskDataFromEvent(event);
282
+ if (taskData?.isConsulted === true) return false;
283
+
284
+ return taskData?.consultingAgentId
285
+ ? isSelfConsultingAgent(context, taskData)
286
+ : context.consultInitiator === true;
287
+ },
288
+
289
+ /**
290
+ * EP-DN / consulted consult legs can arrive as AGENT_CONTACT_ASSIGNED without a preceding
291
+ * AgentConsulting event. When that happens, we should enter CONSULTING (not CONNECTED).
292
+ */
293
+ isConsultingAssignment: ({event}: GuardParams): boolean => {
294
+ const taskData = getTaskDataFromEvent(event);
295
+ if (!taskData) return false;
296
+
297
+ const relationshipType = taskData.interaction?.callProcessingDetails?.relationshipType;
298
+
299
+ return (
300
+ taskData.isConsulted === true ||
301
+ relationshipType === 'consult' ||
302
+ taskData.interaction?.state === INTERACTION_STATE.CONSULTING
303
+ );
304
+ },
305
+
306
+ // Wrapup Guards
307
+ isCampaignPreviewContactEnded: ({context, event}: GuardParams): boolean => {
308
+ if (event?.type !== TaskEvent.CONTACT_ENDED) {
309
+ return false;
310
+ }
311
+
312
+ const taskData = getTaskDataFromEvent(event);
313
+ if (!isCampaignPreviewTask(taskData)) {
314
+ return false;
315
+ }
316
+
317
+ const selfAgentId = getSelfAgentId(context, taskData);
318
+ const pending = taskData?.agentsPendingWrapUp;
319
+ const isPendingWrapUp =
320
+ Array.isArray(pending) && !!selfAgentId && pending.includes(selfAgentId);
321
+ const participantIsWrapUp =
322
+ !!selfAgentId && taskData?.interaction?.participants?.[selfAgentId]?.isWrapUp === true;
323
+ const explicitWrapUp =
324
+ taskData?.wrapUpRequired === true ||
325
+ participantIsWrapUp ||
326
+ isPendingWrapUp ||
327
+ taskData?.interaction?.state === 'wrapUp';
328
+
329
+ return !explicitWrapUp;
330
+ },
331
+
332
+ shouldWrapUp: ({context, event}: GuardParams): boolean => {
333
+ const taskData = getTaskDataFromEvent(event);
334
+ if (!taskData) return false;
335
+
336
+ if (event?.type === TaskEvent.CONFERENCE_END) {
337
+ const selfAgentId = getSelfAgentId(context, taskData);
338
+ if (!selfAgentId) return false;
339
+
340
+ const pending = taskData?.agentsPendingWrapUp;
341
+ if (Array.isArray(pending) && pending.length > 0) {
342
+ return pending.includes(selfAgentId);
343
+ }
344
+
345
+ const participantWrapUp =
346
+ taskData?.interaction?.participants?.[selfAgentId]?.isWrapUp === true;
347
+ const wrapUpRequired = taskData?.wrapUpRequired === true;
348
+
349
+ return wrapUpRequired || participantWrapUp;
350
+ }
351
+
352
+ return shouldWrapUpForThisAgent(context, taskData);
353
+ },
354
+
355
+ /**
356
+ * Check if wrapUpRequired in payload OR is consult initiator
357
+ */
358
+ shouldWrapUpOrIsInitiator: ({context, event}: GuardParams): boolean => {
359
+ const taskData = getTaskDataFromEvent(event);
360
+
361
+ return Boolean(taskData?.wrapUpRequired || context.consultInitiator);
362
+ },
363
+
364
+ /**
365
+ * True when an updated lifecycle event shows that this agent left the main interaction.
366
+ *
367
+ * Explicit self-participant evidence is authoritative. Main-call membership is used only
368
+ * for the from-conference nested-consult race where CONSULT_END still represents the agent as
369
+ * active on the consult leg after removing them from the main leg. Partial or ordinary call
370
+ * snapshots are not treated as a departure.
371
+ */
372
+ didCurrentAgentLeaveMainInteraction: ({context, event}: GuardParams): boolean => {
373
+ const taskData = getTaskDataFromEvent(event);
374
+ const selfAgentId = getSelfAgentId(context, taskData);
375
+ if (!selfAgentId) return false;
376
+
377
+ const participantIdFromEvent =
378
+ event && typeof event === 'object' && 'participantId' in event
379
+ ? (event as {participantId?: string}).participantId
380
+ : undefined;
381
+ const participantId = participantIdFromEvent ?? taskData?.participantId;
382
+
383
+ if (Boolean(participantId) && participantId === selfAgentId) {
384
+ return true;
385
+ }
386
+
387
+ // Routing represents self-departure either by naming the participant or by retaining the
388
+ // participant entry with hasLeft set. Agent Desktop treats both representations as terminal.
389
+ const currentParticipant = taskData?.interaction?.participants?.[selfAgentId];
390
+ if (currentParticipant?.hasLeft === true) {
391
+ return true;
392
+ }
393
+
394
+ // A participant-left event that explicitly identifies somebody else must not use a
395
+ // potentially partial participant or media roster to infer that self also departed.
396
+ if (
397
+ event?.type === TaskEvent.PARTICIPANT_LEAVE &&
398
+ Boolean(participantId) &&
399
+ participantId !== selfAgentId
400
+ ) {
401
+ return false;
402
+ }
403
+
404
+ const previousParticipants = context.taskData?.interaction?.participants;
405
+ const updatedParticipants = taskData?.interaction?.participants;
406
+ const wasPreviouslyActive = Boolean(
407
+ previousParticipants?.[selfAgentId] && previousParticipants[selfAgentId].hasLeft !== true
408
+ );
409
+ // Some EP-DN lifecycle payloads remove the participant entry instead of setting hasLeft.
410
+ // Requiring a previously active entry prevents an incomplete initial snapshot from ending a task.
411
+ if (updatedParticipants && wasPreviouslyActive && !(selfAgentId in updatedParticipants)) {
412
+ return true;
413
+ }
414
+
415
+ const previousMainMedia = Object.values(context.taskData?.interaction?.media ?? {}).filter(
416
+ (media) => media?.mType === 'mainCall'
417
+ );
418
+ const updatedMainMedia = Object.values(taskData?.interaction?.media ?? {}).filter(
419
+ (media) => media?.mType === 'mainCall'
420
+ );
421
+ const wasPreviouslyInMainCall = previousMainMedia.some((media) =>
422
+ media.participants?.includes(selfAgentId)
423
+ );
424
+ const remainsInMainCall = updatedMainMedia.some((media) =>
425
+ media.participants?.includes(selfAgentId)
426
+ );
427
+ // A hasLeft participant returned above, so presence here means the updated map keeps self active.
428
+ const remainsActiveInParticipantMap = Boolean(currentParticipant);
429
+ const remainsOnConsultLeg = Object.values(taskData?.interaction?.media ?? {}).some(
430
+ (media) => media?.mType === MEDIA_TYPE_CONSULT && media.participants?.includes(selfAgentId)
431
+ );
432
+
433
+ // Agent Desktop uses mainCall membership for AgentConsultEnded cleanup. Restrict that fallback
434
+ // to a consult that originated from conference and require corroborating active-map and
435
+ // consult-leg membership so ordinary partial CONNECTED/HELD payloads cannot terminate a task.
436
+ if (
437
+ event?.type === TaskEvent.CONSULT_END &&
438
+ context.consultFromConference === true &&
439
+ updatedMainMedia.length > 0 &&
440
+ wasPreviouslyInMainCall &&
441
+ !remainsInMainCall &&
442
+ remainsActiveInParticipantMap &&
443
+ remainsOnConsultLeg
444
+ ) {
445
+ return true;
446
+ }
447
+
448
+ return false;
449
+ },
450
+
451
+ /**
452
+ * True when this agent initiated the conference transfer (widgets or desktop).
453
+ * Mirrors determineConsultInitiator: consultingAgentId === self only (not consultState).
454
+ */
455
+ isSelfConferenceTransferInitiator: ({context, event}: GuardParams): boolean => {
456
+ if (context.transferConferenceRequested === true) return true;
457
+
458
+ const taskData = getTaskDataFromEvent(event);
459
+ const selfAgentId = getSelfAgentId(context, taskData);
460
+ if (!selfAgentId || !taskData) return false;
461
+
462
+ return taskData.consultingAgentId === selfAgentId;
463
+ },
464
+
465
+ /**
466
+ * True when another agent performed conference transfer and this agent should
467
+ * only refresh task data (stay in the current state). False when this agent
468
+ * initiated the transfer (desktop or widgets) or has left the interaction.
469
+ */
470
+ isPassiveConferenceTransferObserver: ({context, event}: GuardParams): boolean => {
471
+ if (guards.isSelfConferenceTransferInitiator({context, event})) return false;
472
+
473
+ const taskData = getTaskDataFromEvent(event);
474
+ const selfAgentId = getSelfAgentId(context, taskData);
475
+ if (selfAgentId && taskData?.interaction?.participants) {
476
+ if (!(selfAgentId in taskData.interaction.participants)) return false;
477
+ if (taskData.interaction.participants[selfAgentId]?.hasLeft === true) return false;
478
+ }
479
+
480
+ return true;
481
+ },
482
+
483
+ isCampaignReservationAccept: ({event}: GuardParams): boolean => {
484
+ return event?.type === TaskEvent.TASK_INCOMING && event.isCampaignReservationAccept === true;
485
+ },
486
+
487
+ // Server State Guards
488
+ isPrimaryMediaOnHold: ({event}: GuardParams): boolean => {
489
+ const taskData = getTaskDataFromEvent(event);
490
+ if (!taskData) return false;
491
+ const mediaId = taskData.mediaResourceId;
492
+ if (!mediaId) return false;
493
+
494
+ return taskData.interaction?.media?.[mediaId]?.isHold === true;
495
+ },
496
+ };
497
+
498
+ export type GuardName = keyof typeof guards;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Task State Machine
3
+ *
4
+ * Export all state machine components for easy importing
5
+ */
6
+
7
+ // Main state machine
8
+ export {createTaskStateMachine} from './TaskStateMachine';
9
+ export type {TaskStateMachine} from './TaskStateMachine';
10
+
11
+ // Types & enums
12
+ export {TaskState, TaskEvent} from './constants';
13
+ export {isEventOfType} from './types';
14
+ export type {
15
+ TaskContext,
16
+ TaskEventPayload,
17
+ TaskStateMachineConfig,
18
+ UIControlConfig,
19
+ TaskActionsMap,
20
+ TaskActionArgs,
21
+ } from './types';
22
+
23
+ // Guards
24
+ export {guards} from './guards';
25
+ export type {GuardParams, GuardFunction} from './guards';
26
+
27
+ // Actions
28
+ export {actions, createInitialContext} from './actions';