@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,1313 @@
1
+ /**
2
+ * Task State Machine Configuration
3
+ *
4
+ * This file defines the XState state machine configuration for contact center tasks.
5
+ * It orchestrates state transitions, guards, and actions for task lifecycle management.
6
+ *
7
+ * GUARD FUNCTIONS: All guard logic is centralized in guards.ts for reusability and testing.
8
+ * This file imports and uses those guards via the `guards` object.
9
+ */
10
+
11
+ import {setup} from 'xstate';
12
+ import {TaskContext, TaskEventPayload, UIControlConfig, TaskActionsMap} from './types';
13
+ import {TaskState, TaskEvent} from './constants';
14
+ import {actions, createInitialContext} from './actions';
15
+ import {guards, shouldWrapUpForThisAgent, getTaskDataFromEvent} from './guards';
16
+ import {getIsCustomerInCall} from '../TaskUtils';
17
+
18
+ type TaskActionConfigMap = {[K in keyof typeof actions]: undefined};
19
+
20
+ const taskStateMachineSetup = setup<
21
+ TaskContext,
22
+ TaskEventPayload,
23
+ Record<string, never>,
24
+ Record<string, never>,
25
+ TaskActionConfigMap
26
+ >({
27
+ actors: {},
28
+ types: {
29
+ context: {} as TaskContext,
30
+ events: {} as TaskEventPayload,
31
+ },
32
+ });
33
+
34
+ /**
35
+ * Get task state machine configuration with UI control config
36
+ * Defines all states, transitions, guards, and actions for task management
37
+ *
38
+ * @param uiControlConfig - UI control configuration
39
+ * @returns State machine configuration object
40
+ */
41
+ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
42
+ /**
43
+ * Event mapping reference (CC WebSocket -> TaskEvent)
44
+ *
45
+ * AgentContactReserved -> TaskEvent.TASK_INCOMING
46
+ * AgentOfferContact -> TaskEvent.TASK_OFFERED
47
+ * AgentOfferConsult -> TaskEvent.OFFER_CONSULT
48
+ * AgentConsulting -> TaskEvent.CONSULTING_ACTIVE
49
+ * AgentConsultCreated -> TaskEvent.CONSULT_CREATED
50
+ * AgentConsultTransferred -> TaskEvent.TRANSFER_SUCCESS
51
+ * AgentContactAssigned -> TaskEvent.ASSIGN
52
+ * AgentContactHeld -> TaskEvent.HOLD_SUCCESS
53
+ * AgentContactUnheld -> TaskEvent.UNHOLD_SUCCESS
54
+ * AgentConsultEnded -> TaskEvent.CONSULT_END
55
+ * ParticipantLeftConference -> TaskEvent.PARTICIPANT_LEAVE
56
+ * AgentContactEnded -> TaskEvent.CONTACT_ENDED
57
+ * AgentWrapup -> TaskEvent.TASK_WRAPUP (wrapUpRequired)
58
+ * AgentWrappedup -> TaskEvent.WRAPUP_COMPLETE
59
+ *
60
+ * (See TaskManager.mapEventToTaskStateMachineEvent for the full mapping table.)
61
+ */
62
+ // XState evaluates these guarded transition arrays in order. Each returned entry is a
63
+ // complete transition with its own target and actions; sharing them keeps lifecycle-event
64
+ // handling consistent across active states where ParticipantLeftConference can arrive.
65
+ const currentAgentParticipantLeaveTransitions = () => [
66
+ {
67
+ guard: (params) =>
68
+ guards.didCurrentAgentLeaveMainInteraction(params) && guards.shouldWrapUp(params),
69
+ target: TaskState.WRAPPING_UP,
70
+ actions: [
71
+ 'updateTaskData',
72
+ 'handleParticipantLeft',
73
+ 'markEnded',
74
+ 'clearConsultState',
75
+ 'emitTaskParticipantLeft',
76
+ 'emitTaskWrapup',
77
+ ],
78
+ },
79
+ {
80
+ guard: guards.didCurrentAgentLeaveMainInteraction,
81
+ target: TaskState.TERMINATED,
82
+ actions: [
83
+ 'updateTaskData',
84
+ 'handleParticipantLeft',
85
+ 'markEnded',
86
+ 'clearConsultState',
87
+ 'emitTaskParticipantLeft',
88
+ 'emitTaskEnd',
89
+ ],
90
+ },
91
+ ];
92
+
93
+ const currentAgentConsultEndTransitions = () => [
94
+ {
95
+ guard: (params) =>
96
+ guards.didCurrentAgentLeaveMainInteraction(params) && guards.shouldWrapUp(params),
97
+ target: TaskState.WRAPPING_UP,
98
+ actions: [
99
+ 'updateTaskData',
100
+ 'markEnded',
101
+ 'clearConsultState',
102
+ 'emitTaskConsultEnd',
103
+ 'emitTaskWrapup',
104
+ ],
105
+ },
106
+ {
107
+ guard: guards.didCurrentAgentLeaveMainInteraction,
108
+ target: TaskState.TERMINATED,
109
+ actions: [
110
+ 'updateTaskData',
111
+ 'markEnded',
112
+ 'clearConsultState',
113
+ 'emitTaskConsultEnd',
114
+ 'emitTaskEnd',
115
+ ],
116
+ },
117
+ ];
118
+
119
+ return {
120
+ id: 'taskStateMachine',
121
+ initial: TaskState.IDLE,
122
+ context: createInitialContext(uiControlConfig, TaskState.IDLE),
123
+ on: {
124
+ [TaskEvent.RECORDING_STARTED]: {
125
+ actions: ['updateTaskData', 'emitTaskRecordingStarted'],
126
+ },
127
+ [TaskEvent.CONTACT_UPDATED]: {
128
+ actions: ['updateTaskData', 'syncTaskDataFromEvent'],
129
+ },
130
+ [TaskEvent.CONTACT_OWNER_CHANGED]: {
131
+ actions: ['updateTaskData', 'syncTaskDataFromEvent', 'emitTaskHydrate'],
132
+ },
133
+ // HYDRATE: Update task data from AgentContact event
134
+ // Note: State restoration with transitions is handled in IDLE state.
135
+ // This root-level handler is for when task is already in another state (just updates data).
136
+ [TaskEvent.HYDRATE]: {
137
+ actions: ['updateTaskData', 'emitTaskHydrate'],
138
+ },
139
+ // AgentConsultCreated from Stable Prod while already HELD/CONNECTED (external consult).
140
+ // Child states do not handle CONSULT_CREATED; wire here so updateTaskData + setConsultInitiator run.
141
+ [TaskEvent.CONSULT_CREATED]: {
142
+ actions: ['updateTaskData', 'setConsultInitiator'],
143
+ },
144
+ // AgentConsultFailed (RONA) while HELD/CONNECTED without passing through CONSULT_INITIATING.
145
+ [TaskEvent.CONSULT_FAILED]: {
146
+ actions: ['updateTaskData', 'handleConsultFailed'],
147
+ },
148
+ },
149
+ states: {
150
+ [TaskState.IDLE]: {
151
+ on: {
152
+ // HYDRATE: Restore state machine to correct state based on hydrated task data
153
+ // This handles page refresh/reconnection scenarios where task needs to be restored
154
+ // IMPORTANT: This MUST be in IDLE state (not root) because root-level events cannot
155
+ // transition to child states in XState
156
+ [TaskEvent.HYDRATE]: [
157
+ {
158
+ guard: guards.isInteractionTerminated,
159
+ target: TaskState.WRAPPING_UP,
160
+ actions: ['updateTaskData', 'markEnded', 'emitTaskHydrate'],
161
+ },
162
+ {
163
+ guard: guards.isEpDnPendingConferenceMergeHydrate,
164
+ target: TaskState.CONFERENCING,
165
+ actions: ['updateTaskData', 'emitTaskHydrate'],
166
+ },
167
+ {
168
+ guard: guards.isInteractionConsulting,
169
+ target: TaskState.CONSULTING,
170
+ actions: ['updateTaskData', 'emitTaskHydrate'],
171
+ },
172
+ {
173
+ guard: guards.isInteractionHeld,
174
+ target: TaskState.HELD,
175
+ actions: ['updateTaskData', 'emitTaskHydrate'],
176
+ },
177
+ {
178
+ guard: guards.isInteractionConnected,
179
+ target: TaskState.CONNECTED,
180
+ actions: ['updateTaskData', 'emitTaskHydrate'],
181
+ },
182
+ {
183
+ guard: guards.isConferencingByParticipants,
184
+ target: TaskState.CONFERENCING,
185
+ actions: ['updateTaskData', 'emitTaskHydrate'],
186
+ },
187
+ {
188
+ // Default: just update data, stay in IDLE
189
+ actions: ['updateTaskData', 'emitTaskHydrate'],
190
+ },
191
+ ],
192
+ // AgentContactReserved (direct incoming/consult/transfer/outdial, or campaign preview accept)
193
+ [TaskEvent.TASK_INCOMING]: [
194
+ {
195
+ guard: guards.isCampaignReservationAccept,
196
+ target: TaskState.OFFERED,
197
+ actions: ['initializeTask', 'emitTaskCampaignPreviewReservation'],
198
+ },
199
+ {
200
+ target: TaskState.OFFERED,
201
+ actions: ['initializeTask', 'emitTaskIncoming'],
202
+ },
203
+ ],
204
+
205
+ // AgentOutboundFailed can arrive before TASK_INCOMING due to race conditions
206
+ [TaskEvent.OUTBOUND_FAILED]: {
207
+ target: TaskState.TERMINATED,
208
+ actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskEnd'],
209
+ },
210
+
211
+ // EP-DN split-leg ordering can deliver AgentConsulting before HYDRATE/TASK_INCOMING.
212
+ // Do not drop it in IDLE; bootstrap to CONSULTING using event taskData.
213
+ [TaskEvent.CONSULTING_ACTIVE]: {
214
+ target: TaskState.CONSULTING,
215
+ actions: [
216
+ 'setConsultInitiator',
217
+ 'setConsultDestination',
218
+ 'setConsultFromConference',
219
+ 'setConsultAgentJoined',
220
+ 'updateTaskData',
221
+ 'emitTaskConsultAccepted',
222
+ 'emitTaskConsulting',
223
+ ],
224
+ },
225
+ },
226
+ },
227
+
228
+ [TaskState.OFFERED]: {
229
+ on: {
230
+ // AgentContactOffer
231
+ [TaskEvent.TASK_OFFERED]: {
232
+ actions: ['updateTaskData', 'emitTaskOfferContact', 'requestAutoAnswer'],
233
+ },
234
+ // AgentContactAssigned
235
+ [TaskEvent.ASSIGN]: [
236
+ {
237
+ guard: guards.isConsultingAssignment,
238
+ target: TaskState.CONSULTING,
239
+ actions: ['updateTaskData', 'emitTaskConsultAccepted', 'emitTaskConsulting'],
240
+ },
241
+ {
242
+ target: TaskState.CONNECTED,
243
+ actions: ['updateTaskData', 'emitTaskAssigned'],
244
+ },
245
+ ],
246
+ // AgentOfferContactRONA
247
+ [TaskEvent.RONA]: {
248
+ target: TaskState.TERMINATED,
249
+ actions: ['updateTaskData', 'markEnded', 'emitTaskReject'],
250
+ },
251
+ [TaskEvent.TASK_WRAPUP]: [
252
+ {
253
+ guard: guards.shouldWrapUp,
254
+ target: TaskState.WRAPPING_UP,
255
+ actions: ['updateTaskData', 'markEnded', 'emitTaskWrapup'],
256
+ },
257
+ {
258
+ target: TaskState.TERMINATED,
259
+ actions: ['updateTaskData', 'markEnded', 'emitTaskEnd'],
260
+ },
261
+ ],
262
+ [TaskEvent.CONTACT_ENDED]: {
263
+ target: TaskState.TERMINATED,
264
+ actions: ['updateTaskData', 'markEnded', 'emitTaskEnd'],
265
+ },
266
+ // This needs to be handled for all assign failed scenarios (contact, buddy)
267
+ // [AgentContactAssignFailed, AgentCtqFailed, AgentBlindTransferFailed,
268
+ // AgentVTeamTransferFailed, AgentConsultTransferFailed]
269
+ [TaskEvent.ASSIGN_FAILED]: {
270
+ target: TaskState.TERMINATED,
271
+ actions: ['updateTaskData', 'markEnded', 'emitTaskReject'],
272
+ },
273
+ [TaskEvent.INVITE_FAILED]: {
274
+ target: TaskState.TERMINATED,
275
+ actions: ['updateTaskData', 'markEnded', 'emitTaskReject'],
276
+ },
277
+ [TaskEvent.OUTBOUND_FAILED]: [
278
+ {
279
+ guard: guards.shouldWrapUp,
280
+ target: TaskState.WRAPPING_UP,
281
+ actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskWrapup'],
282
+ },
283
+ {
284
+ target: TaskState.TERMINATED,
285
+ actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskEnd'],
286
+ },
287
+ ],
288
+ [TaskEvent.CAMPAIGN_PREVIEW_ACCEPT_FAILED]: {
289
+ actions: ['updateTaskData', 'emitTaskCampaignPreviewAcceptFailed'],
290
+ },
291
+ [TaskEvent.CAMPAIGN_PREVIEW_SKIP_FAILED]: {
292
+ actions: ['updateTaskData', 'emitTaskCampaignPreviewSkipFailed'],
293
+ },
294
+ [TaskEvent.CAMPAIGN_PREVIEW_REMOVE_FAILED]: {
295
+ actions: ['updateTaskData', 'emitTaskCampaignPreviewRemoveFailed'],
296
+ },
297
+ // AgentConsulting comes for received after the initial consult is accepted
298
+ [TaskEvent.CONSULTING_ACTIVE]: [
299
+ {
300
+ target: TaskState.CONSULTING,
301
+ actions: [
302
+ 'setConsultAgentJoined',
303
+ 'setConsultDestination',
304
+ 'updateTaskData',
305
+ 'emitTaskConsultAccepted',
306
+ 'emitTaskConsulting',
307
+ ],
308
+ },
309
+ ],
310
+ // agentOfferConsult happens only on the receiver side of consult
311
+ [TaskEvent.OFFER_CONSULT]: {
312
+ actions: ['updateTaskData', 'emitTaskOfferConsult', 'requestAutoAnswer'],
313
+ },
314
+ // AgentConsultFailed - when consulted agent (Agent 2) doesn't answer (RONA or decline)
315
+ // Clears the incoming consult notification by transitioning to TERMINATED
316
+ [TaskEvent.CONSULT_FAILED]: {
317
+ target: TaskState.TERMINATED,
318
+ actions: ['updateTaskData', 'clearConsultState', 'emitTaskReject'],
319
+ },
320
+ // AgentConsultEnded - when consult initiator (Agent 1) ends the consult before
321
+ // the consulted agent (Agent 2) accepts. Clears the incoming notification.
322
+ [TaskEvent.CONSULT_END]: {
323
+ target: TaskState.TERMINATED,
324
+ actions: ['updateTaskData', 'clearConsultState', 'emitTaskConsultEnd'],
325
+ },
326
+ },
327
+ },
328
+
329
+ [TaskState.CONNECTED]: {
330
+ on: {
331
+ // AgentConsultConferenced / ParticipantJoinedConference can arrive while connected.
332
+ [TaskEvent.CONFERENCE_START]: {
333
+ target: TaskState.CONFERENCING,
334
+ actions: [
335
+ 'updateTaskData',
336
+ 'syncTaskDataFromEvent',
337
+ 'clearConsultState',
338
+ 'emitTaskConferenceStarted',
339
+ ],
340
+ },
341
+ // AgentConsulting may arrive while machine is CONNECTED (EP-DN/event ordering).
342
+ // Derive consultInitiator from payload so controls are set correctly.
343
+ [TaskEvent.CONSULTING_ACTIVE]: {
344
+ target: TaskState.CONSULTING,
345
+ actions: [
346
+ 'setConsultInitiator',
347
+ 'setConsultDestination',
348
+ 'setConsultAgentJoined',
349
+ 'updateTaskData',
350
+ 'emitTaskConsultAccepted',
351
+ 'emitTaskConsulting',
352
+ ],
353
+ },
354
+ // AgentContactAssigned can be resent after consult transfers; keep context in sync
355
+ /* TODO: This transition needs to be checked if this is even needed as receiver will
356
+ * be in Consult Accept state which can be consulting state and ASSIGNED will be only
357
+ * for the receiver. So receuving ASSIGNED in Connected state is highly unlikely.
358
+ */
359
+ [TaskEvent.ASSIGN]: {
360
+ target: TaskState.CONNECTED,
361
+ actions: ['updateTaskData', 'emitTaskAssigned'],
362
+ },
363
+ // Click of hold button
364
+ [TaskEvent.HOLD_INITIATED]: {
365
+ target: TaskState.HOLD_INITIATING,
366
+ },
367
+ // Remote hold from another login session (multi-login)
368
+ [TaskEvent.HOLD_SUCCESS]: {
369
+ target: TaskState.HELD,
370
+ actions: ['updateTaskData', 'setHoldState', 'emitTaskHold'],
371
+ },
372
+ [TaskEvent.UNHOLD_INITIATED]: {
373
+ target: TaskState.RESUME_INITIATING,
374
+ },
375
+ // Resume while machine is CONNECTED but main media is still on hold (stale state after consult RONA/decline).
376
+ [TaskEvent.UNHOLD_SUCCESS]: {
377
+ target: TaskState.CONNECTED,
378
+ actions: ['updateTaskData', 'setHoldState', 'emitTaskResume'],
379
+ },
380
+ // Click of the consult button
381
+ [TaskEvent.CONSULT]: {
382
+ target: TaskState.CONSULT_INITIATING,
383
+ actions: ['setConsultInitiator', 'setConsultDestination'],
384
+ },
385
+ // AgentConsultTransferred / AgentVTeamTransferred / AgentBlindTransferred
386
+ [TaskEvent.TRANSFER_SUCCESS]: [
387
+ {
388
+ guard: guards.shouldWrapUpOrIsInitiator,
389
+ target: TaskState.WRAPPING_UP,
390
+ actions: ['updateTaskData', 'markEnded', 'clearConsultState', 'emitTaskWrapup'],
391
+ },
392
+ {
393
+ // Receiver goes to connected as he receives transferSuccess event
394
+ actions: ['updateTaskData', 'clearConsultState'],
395
+ },
396
+ ],
397
+ [TaskEvent.TRANSFER_FAILED]: {
398
+ actions: ['updateTaskData'],
399
+ },
400
+ // AgentConsultEnded from Stable Prod while on connected leg (external end consult).
401
+ [TaskEvent.CONSULT_END]: [
402
+ ...currentAgentConsultEndTransitions(),
403
+ {
404
+ guard: ({context, event}) => {
405
+ if (context.consultInitiator !== true) return false;
406
+ const taskData = getTaskDataFromEvent(event);
407
+ const mainId = taskData?.interaction?.mainInteractionId || taskData?.interactionId;
408
+
409
+ return Boolean(mainId && taskData?.interaction?.media?.[mainId]?.isHold === true);
410
+ },
411
+ target: TaskState.HELD,
412
+ actions: ['updateTaskData', 'clearConsultState', 'emitTaskConsultEnd'],
413
+ },
414
+ {
415
+ guard: ({context}) => context.consultInitiator === true,
416
+ actions: ['updateTaskData', 'clearConsultState', 'emitTaskConsultEnd'],
417
+ },
418
+ ],
419
+ // Routing lifecycle events can arrive out of order in child-task/EP-DN flows. The
420
+ // local actor may still be CONNECTED when ParticipantLeftConference reports that this
421
+ // agent left the main interaction. Handle that self-departure race; for another
422
+ // participant, the action-only fallback updates data while preserving CONNECTED.
423
+ [TaskEvent.PARTICIPANT_LEAVE]: [
424
+ ...currentAgentParticipantLeaveTransitions(),
425
+ {actions: ['updateTaskData', 'handleParticipantLeft', 'emitTaskParticipantLeft']},
426
+ ],
427
+ // AgentContactEnded Event
428
+ [TaskEvent.CONTACT_ENDED]: [
429
+ {
430
+ // Campaign preview ContactEnded is terminal cleanup; never enter wrapup.
431
+ guard: guards.isCampaignPreviewContactEnded,
432
+ target: TaskState.TERMINATED,
433
+ actions: ['updateTaskData', 'markEnded', 'emitTaskEnd'],
434
+ },
435
+ {
436
+ // Conference still active → CONFERENCING
437
+ guard: guards.conferenceInProgressFromEvent,
438
+ target: TaskState.CONFERENCING,
439
+ actions: ['updateTaskData', 'emitTaskConferenceStarted', 'requestCleanup'],
440
+ },
441
+ {
442
+ // Agent should wrap up → WRAPPING_UP
443
+ guard: guards.shouldWrapUp,
444
+ target: TaskState.WRAPPING_UP,
445
+ actions: ['updateTaskData', 'markEnded', 'emitTaskWrapup', 'requestCleanup'],
446
+ },
447
+ {
448
+ // Consulted agent → TERMINATED
449
+ target: TaskState.TERMINATED,
450
+ actions: ['updateTaskData', 'markEnded', 'emitTaskEnd'],
451
+ },
452
+ ],
453
+ [TaskEvent.TASK_WRAPUP]: {
454
+ target: TaskState.WRAPPING_UP,
455
+ actions: ['updateTaskData', 'markEnded', 'emitTaskWrapup'],
456
+ },
457
+ [TaskEvent.OUTBOUND_FAILED]: [
458
+ {
459
+ guard: guards.shouldWrapUp,
460
+ target: TaskState.WRAPPING_UP,
461
+ actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskWrapup'],
462
+ },
463
+ {
464
+ target: TaskState.TERMINATED,
465
+ actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskEnd'],
466
+ },
467
+ ],
468
+ [TaskEvent.PAUSE_RECORDING]: {
469
+ actions: ['updateTaskData', 'setRecordingState', 'emitTaskRecordingPaused'],
470
+ },
471
+ [TaskEvent.RESUME_RECORDING]: {
472
+ actions: ['updateTaskData', 'setRecordingState', 'emitTaskRecordingResumed'],
473
+ },
474
+ },
475
+ },
476
+
477
+ [TaskState.HOLD_INITIATING]: {
478
+ on: {
479
+ // AgentContactHeld Event
480
+ [TaskEvent.HOLD_SUCCESS]: {
481
+ target: TaskState.HELD,
482
+ actions: ['updateTaskData', 'setHoldState', 'emitTaskHold'],
483
+ },
484
+ // AgentContactHoldFailed Event
485
+ [TaskEvent.HOLD_FAILED]: {
486
+ target: TaskState.CONNECTED,
487
+ actions: ['updateTaskData'],
488
+ },
489
+ // Another agent can remove this agent while the local hold request is awaiting its
490
+ // routing acknowledgement. Process that backend lifecycle event independently of
491
+ // whether Drop was initiated from this task; another-participant event preserves state.
492
+ [TaskEvent.PARTICIPANT_LEAVE]: [
493
+ ...currentAgentParticipantLeaveTransitions(),
494
+ {actions: ['updateTaskData', 'handleParticipantLeft', 'emitTaskParticipantLeft']},
495
+ ],
496
+ [TaskEvent.OUTBOUND_FAILED]: [
497
+ {
498
+ guard: guards.shouldWrapUp,
499
+ target: TaskState.WRAPPING_UP,
500
+ actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskWrapup'],
501
+ },
502
+ {
503
+ target: TaskState.TERMINATED,
504
+ actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskEnd'],
505
+ },
506
+ ],
507
+ },
508
+ },
509
+
510
+ [TaskState.HELD]: {
511
+ on: {
512
+ // Conference can be merged while this agent is in held state after refresh/recovery.
513
+ [TaskEvent.CONFERENCE_START]: {
514
+ target: TaskState.CONFERENCING,
515
+ actions: [
516
+ 'updateTaskData',
517
+ 'syncTaskDataFromEvent',
518
+ 'clearConsultState',
519
+ 'emitTaskConferenceStarted',
520
+ ],
521
+ },
522
+ [TaskEvent.PAUSE_RECORDING]: {
523
+ actions: ['updateTaskData', 'setRecordingState', 'emitTaskRecordingPaused'],
524
+ },
525
+ [TaskEvent.RESUME_RECORDING]: {
526
+ actions: ['updateTaskData', 'setRecordingState', 'emitTaskRecordingResumed'],
527
+ },
528
+ // Click of the unhold button
529
+ [TaskEvent.UNHOLD_INITIATED]: {
530
+ target: TaskState.RESUME_INITIATING,
531
+ },
532
+ // Remote resume from another login session (multi-login)
533
+ [TaskEvent.UNHOLD_SUCCESS]: {
534
+ target: TaskState.CONNECTED,
535
+ actions: ['updateTaskData', 'setHoldState', 'emitTaskResume'],
536
+ },
537
+ // Click of the consult button
538
+ [TaskEvent.CONSULT]: {
539
+ target: TaskState.CONSULT_INITIATING,
540
+ actions: ['setConsultInitiator', 'setConsultDestination'],
541
+ },
542
+ // AgentConsulting while main leg is held (Task Refactor / Stable Prod consult accept).
543
+ [TaskEvent.CONSULTING_ACTIVE]: {
544
+ target: TaskState.CONSULTING,
545
+ actions: [
546
+ 'setConsultInitiator',
547
+ 'setConsultDestination',
548
+ 'setConsultAgentJoined',
549
+ 'updateTaskData',
550
+ 'emitTaskConsultAccepted',
551
+ 'emitTaskConsulting',
552
+ ],
553
+ },
554
+ // TODO: This may not be a valid transition, need to be removed
555
+ // AgentConsultTransferred / AgentVTeamTransferred / AgentBlindTransferred
556
+ [TaskEvent.TRANSFER_SUCCESS]: [
557
+ {
558
+ guard: guards.shouldWrapUpOrIsInitiator,
559
+ target: TaskState.WRAPPING_UP,
560
+ actions: ['updateTaskData', 'markEnded', 'emitTaskWrapup'],
561
+ },
562
+ {
563
+ target: TaskState.CONNECTED,
564
+ actions: ['updateTaskData', 'clearConsultState'],
565
+ },
566
+ ],
567
+ [TaskEvent.TRANSFER_FAILED]: {
568
+ actions: ['updateTaskData'],
569
+ },
570
+ [TaskEvent.CONTACT_ENDED]: [
571
+ {
572
+ // Campaign preview ContactEnded is terminal cleanup; never enter wrapup.
573
+ guard: guards.isCampaignPreviewContactEnded,
574
+ target: TaskState.TERMINATED,
575
+ actions: ['updateTaskData', 'markEnded', 'emitTaskEnd'],
576
+ },
577
+ {
578
+ guard: guards.conferenceInProgressFromEvent,
579
+ target: TaskState.CONFERENCING,
580
+ actions: ['updateTaskData', 'emitTaskConferenceStarted', 'requestCleanup'],
581
+ },
582
+ {
583
+ guard: guards.shouldWrapUp,
584
+ target: TaskState.WRAPPING_UP,
585
+ actions: ['updateTaskData', 'markEnded', 'emitTaskWrapup', 'requestCleanup'],
586
+ },
587
+ {
588
+ target: TaskState.TERMINATED,
589
+ actions: ['updateTaskData', 'markEnded', 'emitTaskEnd'],
590
+ },
591
+ ],
592
+ // AgentConsultEnded from Stable Prod while main leg is held (external end consult).
593
+ [TaskEvent.CONSULT_END]: [
594
+ ...currentAgentConsultEndTransitions(),
595
+ {actions: ['updateTaskData', 'clearConsultState', 'emitTaskConsultEnd']},
596
+ ],
597
+ [TaskEvent.PARTICIPANT_LEAVE]: [
598
+ ...currentAgentParticipantLeaveTransitions(),
599
+ {actions: ['updateTaskData', 'handleParticipantLeft', 'emitTaskParticipantLeft']},
600
+ ],
601
+ // TODO: This may not be a valid transition, this needs to be checked as well
602
+ [TaskEvent.TASK_WRAPUP]: {
603
+ target: TaskState.WRAPPING_UP,
604
+ actions: ['updateTaskData', 'markEnded', 'emitTaskWrapup'],
605
+ },
606
+ [TaskEvent.OUTBOUND_FAILED]: [
607
+ {
608
+ guard: guards.shouldWrapUp,
609
+ target: TaskState.WRAPPING_UP,
610
+ actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskWrapup'],
611
+ },
612
+ {
613
+ target: TaskState.TERMINATED,
614
+ actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskEnd'],
615
+ },
616
+ ],
617
+ },
618
+ },
619
+
620
+ [TaskState.RESUME_INITIATING]: {
621
+ on: {
622
+ [TaskEvent.UNHOLD_SUCCESS]: {
623
+ target: TaskState.CONNECTED,
624
+ actions: ['updateTaskData', 'setHoldState', 'emitTaskResume'],
625
+ },
626
+ [TaskEvent.UNHOLD_FAILED]: {
627
+ target: TaskState.HELD,
628
+ },
629
+ // The same remote-removal race can occur while an unhold request is in flight.
630
+ [TaskEvent.PARTICIPANT_LEAVE]: [
631
+ ...currentAgentParticipantLeaveTransitions(),
632
+ {actions: ['updateTaskData', 'handleParticipantLeft', 'emitTaskParticipantLeft']},
633
+ ],
634
+ [TaskEvent.OUTBOUND_FAILED]: [
635
+ {
636
+ guard: guards.shouldWrapUp,
637
+ target: TaskState.WRAPPING_UP,
638
+ actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskWrapup'],
639
+ },
640
+ {
641
+ target: TaskState.TERMINATED,
642
+ actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskEnd'],
643
+ },
644
+ ],
645
+ },
646
+ },
647
+
648
+ [TaskState.CONSULT_INITIATING]: {
649
+ on: {
650
+ [TaskEvent.HOLD_SUCCESS]: {
651
+ actions: ['updateTaskData'],
652
+ },
653
+ [TaskEvent.HOLD_FAILED]: {
654
+ target: TaskState.CONNECTED,
655
+ actions: ['updateTaskData', 'handleConsultFailed'],
656
+ },
657
+ // AgentConsulting
658
+ // NOTE: Don't set consultDestinationAgentJoined here - wait for CONSULTING_ACTIVE
659
+ [TaskEvent.CONSULT_SUCCESS]: {
660
+ target: TaskState.CONSULTING,
661
+ actions: ['updateTaskData', 'setConsultInitiator'],
662
+ },
663
+ // AgentConsulting (Task Refactor: AgentConsulting event, not CONSULT_SUCCESS)
664
+ [TaskEvent.CONSULTING_ACTIVE]: {
665
+ target: TaskState.CONSULTING,
666
+ actions: [
667
+ 'setConsultInitiator',
668
+ 'setConsultDestination',
669
+ 'setConsultAgentJoined',
670
+ 'updateTaskData',
671
+ 'emitTaskConsultAccepted',
672
+ 'emitTaskConsulting',
673
+ ],
674
+ },
675
+ // AgentConsultFailed, API Failures, AgentCtqFailed
676
+ [TaskEvent.CONSULT_FAILED]: [
677
+ {
678
+ // Consult from conference → back to CONFERENCING
679
+ guard: ({context}) => context.consultFromConference === true,
680
+ target: TaskState.CONFERENCING,
681
+ actions: ['updateTaskData', 'handleConsultFailed'],
682
+ },
683
+ {
684
+ guard: guards.isPrimaryMediaOnHold,
685
+ target: TaskState.HELD,
686
+ actions: ['updateTaskData', 'handleConsultFailed'],
687
+ },
688
+ {
689
+ target: TaskState.CONNECTED,
690
+ actions: ['updateTaskData', 'handleConsultFailed'],
691
+ },
692
+ ],
693
+ // AgentCtqCancelled Event
694
+ [TaskEvent.CTQ_CANCEL]: [
695
+ {
696
+ guard: guards.isPrimaryMediaOnHold,
697
+ target: TaskState.HELD,
698
+ actions: ['updateTaskData', 'clearConsultState'],
699
+ },
700
+ {
701
+ target: TaskState.CONNECTED,
702
+ actions: ['updateTaskData', 'clearConsultState'],
703
+ },
704
+ ],
705
+ // AgentConsultEnded from Stable Prod during consult initiation (external end consult).
706
+ [TaskEvent.CONSULT_END]: [
707
+ ...currentAgentConsultEndTransitions(),
708
+ {
709
+ guard: guards.isPrimaryMediaOnHold,
710
+ target: TaskState.HELD,
711
+ actions: ['updateTaskData', 'clearConsultState', 'emitTaskConsultEnd'],
712
+ },
713
+ {
714
+ target: TaskState.CONNECTED,
715
+ actions: ['updateTaskData', 'clearConsultState', 'emitTaskConsultEnd'],
716
+ },
717
+ ],
718
+ [TaskEvent.PARTICIPANT_LEAVE]: [
719
+ ...currentAgentParticipantLeaveTransitions(),
720
+ {actions: ['updateTaskData', 'handleParticipantLeft', 'emitTaskParticipantLeft']},
721
+ ],
722
+ [TaskEvent.OUTBOUND_FAILED]: [
723
+ {
724
+ guard: guards.shouldWrapUp,
725
+ target: TaskState.WRAPPING_UP,
726
+ actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskWrapup'],
727
+ },
728
+ {
729
+ target: TaskState.TERMINATED,
730
+ actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskEnd'],
731
+ },
732
+ ],
733
+ },
734
+ },
735
+
736
+ [TaskState.CONSULTING]: {
737
+ on: {
738
+ // AgentConsulting updates consulted agent arrival
739
+ [TaskEvent.CONSULTING_ACTIVE]: {
740
+ actions: [
741
+ 'setConsultAgentJoined',
742
+ 'setConsultDestination',
743
+ 'updateTaskData',
744
+ 'emitTaskConsulting',
745
+ ],
746
+ },
747
+
748
+ // AgentConsultFailed (RONA / consultee declined) while the initiator is already in
749
+ // CONSULTING (AgentConsulting arrived during ringing). Mirror CONSULT_INITIATING so the
750
+ // initiator returns to their own leg (HELD when main is on hold, else CONNECTED) instead
751
+ // of staying in CONSULTING. Without this, handleConsultFailed clears consultInitiator but
752
+ // the machine stays in CONSULTING, so the trailing AgentConsultEnded falls through the
753
+ // CONSULT_END "consulted agent" branch to TERMINATED and wrongly clears the task.
754
+ [TaskEvent.CONSULT_FAILED]: [
755
+ {
756
+ guard: ({context}) => context.consultFromConference === true,
757
+ target: TaskState.CONFERENCING,
758
+ actions: ['updateTaskData', 'handleConsultFailed'],
759
+ },
760
+ {
761
+ guard: guards.isPrimaryMediaOnHold,
762
+ target: TaskState.HELD,
763
+ actions: ['updateTaskData', 'handleConsultFailed'],
764
+ },
765
+ {
766
+ target: TaskState.CONNECTED,
767
+ actions: ['updateTaskData', 'handleConsultFailed'],
768
+ },
769
+ ],
770
+
771
+ // AgentConsultEnded
772
+ [TaskEvent.CONSULT_END]: [
773
+ ...currentAgentConsultEndTransitions(),
774
+ {
775
+ // Initiator returning to conference only while conference is still active.
776
+ guard: ({context, event}) =>
777
+ context.consultInitiator === true &&
778
+ guards.conferenceInProgressFromEvent({context, event}),
779
+ target: TaskState.CONFERENCING,
780
+ actions: ['updateTaskData', 'clearConsultState', 'emitTaskConsultEnd'],
781
+ },
782
+ {
783
+ // Conference consult ended after conference downgrade while main leg is held.
784
+ guard: ({context, event}) =>
785
+ context.consultInitiator === true &&
786
+ context.consultFromConference === true &&
787
+ !guards.conferenceInProgressFromEvent({context, event}) &&
788
+ guards.isConferenceHoldParticipantFromEvent({context, event}),
789
+ target: TaskState.HELD,
790
+ actions: ['updateTaskData', 'clearConsultState', 'emitTaskConsultEnd'],
791
+ },
792
+ {
793
+ // Conference consult ended after conference downgrade while main leg is connected.
794
+ guard: ({context, event}) =>
795
+ context.consultInitiator === true &&
796
+ context.consultFromConference === true &&
797
+ !guards.conferenceInProgressFromEvent({context, event}) &&
798
+ !guards.isConferenceHoldParticipantFromEvent({context, event}),
799
+ target: TaskState.CONNECTED,
800
+ actions: ['updateTaskData', 'clearConsultState', 'emitTaskConsultEnd'],
801
+ },
802
+ {
803
+ // Initiator already switched back to the main/customer leg
804
+ guard: ({context}) =>
805
+ context.consultInitiator === true && context.consultCallHeld === true,
806
+ target: TaskState.CONNECTED,
807
+ actions: ['updateTaskData', 'clearConsultState', 'emitTaskConsultEnd'],
808
+ },
809
+ {
810
+ // Interaction terminated during consult (customer left) → WRAPPING_UP
811
+ guard: ({context, event}) => {
812
+ // if (context.consultInitiator !== true) return false;
813
+ // const taskData = getTaskDataFromEvent(event);
814
+ const taskData = getTaskDataFromEvent(event);
815
+ const cpd = taskData?.interaction?.callProcessingDetails;
816
+ if (cpd?.hasCustomerLeft !== 'true') return false;
817
+
818
+ return (
819
+ taskData?.interaction?.isTerminated === true &&
820
+ shouldWrapUpForThisAgent(context, taskData)
821
+ );
822
+ },
823
+ target: TaskState.WRAPPING_UP,
824
+ actions: [
825
+ 'updateTaskData',
826
+ 'markEnded',
827
+ 'clearConsultState',
828
+ 'emitTaskWrapup',
829
+ 'requestCleanup',
830
+ ],
831
+ },
832
+ {
833
+ // Initiator (no conference) → HELD
834
+ guard: ({context}) => context.consultInitiator === true,
835
+ target: TaskState.HELD,
836
+ actions: ['updateTaskData', 'clearConsultState', 'emitTaskConsultEnd'],
837
+ },
838
+ {
839
+ // Consulted agent → TERMINATED
840
+ target: TaskState.TERMINATED,
841
+ actions: [
842
+ 'updateTaskData',
843
+ 'markEnded',
844
+ 'clearConsultState',
845
+ 'emitTaskConsultEnd',
846
+ 'emitTaskEnd',
847
+ ],
848
+ },
849
+ ],
850
+
851
+ // Switch between consult and main call (UI-driven toggle)
852
+ [TaskEvent.SWITCH_TO_MAIN_CALL]: {
853
+ actions: ['handleSwitchToMainCall', 'emitTaskSwitchCall'],
854
+ },
855
+ [TaskEvent.SWITCH_TO_CONSULT]: {
856
+ actions: ['handleSwitchToConsult', 'emitTaskSwitchCall'],
857
+ },
858
+ [TaskEvent.HOLD_SUCCESS]: {
859
+ actions: ['updateTaskData', 'setHoldState', 'emitTaskHold'],
860
+ },
861
+ [TaskEvent.UNHOLD_SUCCESS]: {
862
+ actions: ['updateTaskData', 'setHoldState', 'emitTaskResume'],
863
+ },
864
+
865
+ [TaskEvent.PARTICIPANT_LEAVE]: [
866
+ ...currentAgentParticipantLeaveTransitions(),
867
+ {
868
+ guard: (params) =>
869
+ !guards.didCurrentAgentLeaveMainInteraction(params) &&
870
+ guards.shouldDowngradeConferenceToConnected(params),
871
+ target: TaskState.CONNECTED,
872
+ actions: [
873
+ 'updateTaskData',
874
+ 'handleParticipantLeft',
875
+ 'clearConsultState',
876
+ 'emitTaskParticipantLeft',
877
+ 'emitTaskConferenceEnded',
878
+ ],
879
+ },
880
+ {actions: ['updateTaskData', 'handleParticipantLeft', 'emitTaskParticipantLeft']},
881
+ ],
882
+
883
+ [TaskEvent.TRANSFER_SUCCESS]: [
884
+ {
885
+ guard: guards.shouldWrapUpOrIsInitiator,
886
+ target: TaskState.WRAPPING_UP,
887
+ actions: ['updateTaskData', 'markEnded', 'clearConsultState', 'emitTaskWrapup'],
888
+ },
889
+ {
890
+ target: TaskState.CONNECTED,
891
+ actions: ['updateTaskData', 'clearConsultState'],
892
+ },
893
+ ],
894
+ [TaskEvent.TRANSFER_FAILED]: {
895
+ actions: ['updateTaskData'],
896
+ },
897
+ [TaskEvent.TRANSFER_CONFERENCE]: {
898
+ // Track that this agent initiated the conference transfer so we can
899
+ // apply the correct lifecycle transition when success arrives.
900
+ actions: ['setTransferConferenceRequested', 'emitTaskTransferConference'],
901
+ },
902
+ [TaskEvent.TRANSFER_CONFERENCE_SUCCESS]: [
903
+ {
904
+ guard: guards.shouldWrapUp,
905
+ target: TaskState.WRAPPING_UP,
906
+ actions: [
907
+ 'updateTaskData',
908
+ 'markEnded',
909
+ 'clearConsultState',
910
+ 'handleTransferConferenceSuccess',
911
+ 'clearTransferConferenceRequested',
912
+ 'emitTaskWrapup',
913
+ ],
914
+ },
915
+ {
916
+ // Non-initiator (e.g. consultee receiving transfer) moves to CONFERENCING
917
+ guard: (params) => !guards.isSelfConferenceTransferInitiator(params),
918
+ target: TaskState.CONFERENCING,
919
+ actions: [
920
+ 'updateTaskData',
921
+ 'clearConsultState',
922
+ 'handleTransferConferenceSuccess',
923
+ 'clearTransferConferenceRequested',
924
+ ],
925
+ },
926
+ {
927
+ target: TaskState.TERMINATED,
928
+ actions: [
929
+ 'updateTaskData',
930
+ 'markEnded',
931
+ 'clearConsultState',
932
+ 'handleTransferConferenceSuccess',
933
+ 'clearTransferConferenceRequested',
934
+ 'emitTaskEnd',
935
+ ],
936
+ },
937
+ ],
938
+ [TaskEvent.TRANSFER_CONFERENCE_FAILED]: {
939
+ actions: ['clearTransferConferenceRequested', 'emitTaskTransferConferenceFailed'],
940
+ },
941
+
942
+ // AgentContactAssigned - receiver side becomes connected to customer
943
+ [TaskEvent.ASSIGN]: {
944
+ target: TaskState.CONNECTED,
945
+ actions: ['updateTaskData', 'emitTaskAssigned'],
946
+ },
947
+ // AgentContactEnded
948
+ [TaskEvent.CONTACT_ENDED]: {
949
+ target: TaskState.WRAPPING_UP,
950
+ actions: [
951
+ 'updateTaskData',
952
+ 'markEnded',
953
+ 'clearConsultState',
954
+ 'emitTaskWrapup',
955
+ 'requestCleanup',
956
+ ],
957
+ },
958
+ [TaskEvent.TASK_WRAPUP]: {
959
+ target: TaskState.WRAPPING_UP,
960
+ actions: ['updateTaskData', 'markEnded', 'clearConsultState', 'emitTaskWrapup'],
961
+ },
962
+ [TaskEvent.OUTBOUND_FAILED]: [
963
+ {
964
+ guard: guards.shouldWrapUp,
965
+ target: TaskState.WRAPPING_UP,
966
+ actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskWrapup'],
967
+ },
968
+ {
969
+ target: TaskState.TERMINATED,
970
+ actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskEnd'],
971
+ },
972
+ ],
973
+ [TaskEvent.MERGE_TO_CONFERENCE]: {
974
+ target: TaskState.CONF_INITIATING,
975
+ },
976
+ // AgentConsultConferenced, ParticipantJoinedConference
977
+ [TaskEvent.CONFERENCE_START]: {
978
+ target: TaskState.CONFERENCING,
979
+ actions: [
980
+ 'updateTaskData',
981
+ 'syncTaskDataFromEvent',
982
+ 'handleConferenceStarted',
983
+ 'clearConsultState',
984
+ ],
985
+ },
986
+ },
987
+ },
988
+
989
+ [TaskState.CONF_INITIATING]: {
990
+ on: {
991
+ // AgentConsultConferenced, AgentConsultConferencing, ParticipantJoinedConference
992
+ [TaskEvent.CONFERENCE_START]: {
993
+ target: TaskState.CONFERENCING,
994
+ actions: [
995
+ 'updateTaskData',
996
+ 'syncTaskDataFromEvent',
997
+ 'handleConferenceStarted',
998
+ 'clearConsultState',
999
+ ],
1000
+ },
1001
+ // AgentConsultConferenceFailed
1002
+ [TaskEvent.CONFERENCE_FAILED]: {
1003
+ target: TaskState.CONSULTING,
1004
+ actions: ['handleConferenceFailed', 'emitTaskConferenceFailed'],
1005
+ },
1006
+ // AgentConsultEnded while conference is initiating (end call before conference completes)
1007
+ [TaskEvent.CONSULT_END]: [
1008
+ ...currentAgentConsultEndTransitions(),
1009
+ {
1010
+ guard: ({event}) => {
1011
+ const taskData = getTaskDataFromEvent(event);
1012
+
1013
+ return taskData?.interaction?.isTerminated === true;
1014
+ },
1015
+ target: TaskState.WRAPPING_UP,
1016
+ actions: ['updateTaskData', 'markEnded', 'clearConsultState', 'emitTaskWrapup'],
1017
+ },
1018
+ {
1019
+ target: TaskState.CONNECTED,
1020
+ actions: ['updateTaskData', 'clearConsultState'],
1021
+ },
1022
+ ],
1023
+ [TaskEvent.PARTICIPANT_LEAVE]: [
1024
+ ...currentAgentParticipantLeaveTransitions(),
1025
+ {actions: ['updateTaskData', 'handleParticipantLeft', 'emitTaskParticipantLeft']},
1026
+ ],
1027
+ [TaskEvent.OUTBOUND_FAILED]: [
1028
+ {
1029
+ guard: guards.shouldWrapUp,
1030
+ target: TaskState.WRAPPING_UP,
1031
+ actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskWrapup'],
1032
+ },
1033
+ {
1034
+ target: TaskState.TERMINATED,
1035
+ actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskEnd'],
1036
+ },
1037
+ ],
1038
+ },
1039
+ },
1040
+
1041
+ [TaskState.CONFERENCING]: {
1042
+ on: {
1043
+ [TaskEvent.CONFERENCE_START]: {
1044
+ actions: [
1045
+ 'updateTaskData',
1046
+ 'syncTaskDataFromEvent',
1047
+ 'clearConsultState',
1048
+ 'emitTaskConferenceStarted',
1049
+ ],
1050
+ },
1051
+ [TaskEvent.EXIT_CONFERENCE_SUCCESS]: [
1052
+ {
1053
+ guard: guards.shouldWrapUp,
1054
+ target: TaskState.WRAPPING_UP,
1055
+ actions: ['updateTaskData', 'markEnded', 'clearConsultState', 'emitTaskWrapup'],
1056
+ },
1057
+ {
1058
+ target: TaskState.TERMINATED,
1059
+ actions: ['updateTaskData', 'markEnded', 'clearConsultState', 'emitTaskEnd'],
1060
+ },
1061
+ ],
1062
+
1063
+ // Needed as all agents in conference get this event, hence we need to clear the consult state
1064
+ [TaskEvent.CONSULT_END]: [
1065
+ ...currentAgentConsultEndTransitions(),
1066
+ {
1067
+ guard: ({context, event}) => {
1068
+ const taskData = getTaskDataFromEvent(event);
1069
+
1070
+ return (
1071
+ taskData?.interaction?.isTerminated === true &&
1072
+ shouldWrapUpForThisAgent(context, taskData)
1073
+ );
1074
+ },
1075
+ target: TaskState.WRAPPING_UP,
1076
+ actions: ['updateTaskData', 'markEnded', 'clearConsultState', 'emitTaskWrapup'],
1077
+ },
1078
+ {
1079
+ actions: ['updateTaskData', 'clearConsultState'],
1080
+ },
1081
+ ],
1082
+
1083
+ [TaskEvent.HOLD_SUCCESS]: [
1084
+ {
1085
+ // Conference already downgraded (no other agents) and backend hold arrives.
1086
+ // Move to HELD so the UI renders resume action.
1087
+ guard: guards.shouldDowngradeConferenceToConnected,
1088
+ target: TaskState.HELD,
1089
+ actions: ['updateTaskData', 'setHoldState', 'emitTaskHold'],
1090
+ },
1091
+ {
1092
+ actions: ['updateTaskData', 'setHoldState', 'emitTaskHold'],
1093
+ },
1094
+ ],
1095
+ [TaskEvent.UNHOLD_SUCCESS]: [
1096
+ {
1097
+ // Conference already downgraded (no other agents) and backend unhold arrives.
1098
+ // Move to CONNECTED so hold action is available again.
1099
+ guard: guards.shouldDowngradeConferenceToConnected,
1100
+ target: TaskState.CONNECTED,
1101
+ actions: [
1102
+ 'updateTaskData',
1103
+ 'syncTaskDataFromEvent',
1104
+ 'setHoldState',
1105
+ 'emitTaskResume',
1106
+ ],
1107
+ },
1108
+ {
1109
+ actions: [
1110
+ 'updateTaskData',
1111
+ 'syncTaskDataFromEvent',
1112
+ 'setHoldState',
1113
+ 'emitTaskResume',
1114
+ ],
1115
+ },
1116
+ ],
1117
+
1118
+ // Start a new consult from within an active conference
1119
+ [TaskEvent.CONSULT]: {
1120
+ target: TaskState.CONSULT_INITIATING,
1121
+ actions: ['setConsultInitiator', 'setConsultDestination', 'setConsultFromConference'],
1122
+ },
1123
+
1124
+ // AgentConsulting while still in conference (EP-DN/external ordering).
1125
+ [TaskEvent.CONSULTING_ACTIVE]: {
1126
+ target: TaskState.CONSULTING,
1127
+ actions: [
1128
+ 'setConsultInitiator',
1129
+ 'setConsultDestination',
1130
+ 'setConsultFromConference',
1131
+ 'setConsultAgentJoined',
1132
+ 'updateTaskData',
1133
+ 'emitTaskConsultAccepted',
1134
+ 'emitTaskConsulting',
1135
+ ],
1136
+ },
1137
+
1138
+ // Participant leaves - handle conference downgrade scenarios
1139
+ [TaskEvent.PARTICIPANT_LEAVE]: [
1140
+ ...currentAgentParticipantLeaveTransitions(),
1141
+ {
1142
+ // Conference downgraded, customer present → CONNECTED
1143
+ guard: (params) =>
1144
+ !guards.didCurrentAgentLeaveMainInteraction(params) &&
1145
+ guards.shouldDowngradeConferenceToConnected(params),
1146
+ target: TaskState.CONNECTED,
1147
+ actions: [
1148
+ 'updateTaskData',
1149
+ 'handleParticipantLeft',
1150
+ 'clearConsultState',
1151
+ 'emitTaskParticipantLeft',
1152
+ 'emitTaskConferenceEnded',
1153
+ ],
1154
+ },
1155
+ {actions: ['updateTaskData', 'handleParticipantLeft', 'emitTaskParticipantLeft']},
1156
+ ],
1157
+
1158
+ [TaskEvent.TRANSFER_CONFERENCE]: {
1159
+ actions: ['setTransferConferenceRequested', 'emitTaskTransferConference'],
1160
+ },
1161
+ [TaskEvent.TRANSFER_CONFERENCE_SUCCESS]: [
1162
+ {
1163
+ guard: guards.isPassiveConferenceTransferObserver,
1164
+ actions: [
1165
+ 'updateTaskData',
1166
+ 'handleTransferConferenceSuccess',
1167
+ 'clearTransferConferenceRequested',
1168
+ ],
1169
+ },
1170
+ {
1171
+ guard: guards.shouldWrapUp,
1172
+ target: TaskState.WRAPPING_UP,
1173
+ actions: [
1174
+ 'updateTaskData',
1175
+ 'markEnded',
1176
+ 'clearConsultState',
1177
+ 'handleTransferConferenceSuccess',
1178
+ 'clearTransferConferenceRequested',
1179
+ 'emitTaskWrapup',
1180
+ ],
1181
+ },
1182
+ {
1183
+ guard: (params) => !guards.isSelfConferenceTransferInitiator(params),
1184
+ target: TaskState.CONFERENCING,
1185
+ actions: [
1186
+ 'updateTaskData',
1187
+ 'clearConsultState',
1188
+ 'handleTransferConferenceSuccess',
1189
+ 'clearTransferConferenceRequested',
1190
+ ],
1191
+ },
1192
+ {
1193
+ target: TaskState.TERMINATED,
1194
+ actions: [
1195
+ 'updateTaskData',
1196
+ 'markEnded',
1197
+ 'clearConsultState',
1198
+ 'handleTransferConferenceSuccess',
1199
+ 'clearTransferConferenceRequested',
1200
+ 'emitTaskEnd',
1201
+ ],
1202
+ },
1203
+ ],
1204
+ [TaskEvent.TRANSFER_CONFERENCE_FAILED]: {
1205
+ actions: ['clearTransferConferenceRequested', 'emitTaskTransferConferenceFailed'],
1206
+ },
1207
+
1208
+ // Conference ends explicitly
1209
+ [TaskEvent.CONFERENCE_END]: [
1210
+ {
1211
+ // Agent who should wrap up → WRAPPING_UP
1212
+ guard: guards.shouldWrapUp,
1213
+ target: TaskState.WRAPPING_UP,
1214
+ actions: ['updateTaskData', 'markEnded', 'clearConsultState', 'emitTaskWrapup'],
1215
+ },
1216
+ {
1217
+ // Customer still in call → CONNECTED
1218
+ guard: ({context, event}) => {
1219
+ if (context.exitingConference === true) return false;
1220
+ const taskData = (event as any)?.taskData ?? context.taskData;
1221
+ if (!taskData?.interaction) return false;
1222
+ const mainCallId = taskData.interaction.mainInteractionId || taskData.interactionId;
1223
+ if (!mainCallId) return false;
1224
+
1225
+ return getIsCustomerInCall(taskData.interaction, mainCallId);
1226
+ },
1227
+ target: TaskState.CONNECTED,
1228
+ actions: ['updateTaskData', 'clearConsultState', 'emitTaskConferenceEnded'],
1229
+ },
1230
+ {
1231
+ // Otherwise → TERMINATED
1232
+ target: TaskState.TERMINATED,
1233
+ actions: ['updateTaskData', 'markEnded', 'clearConsultState', 'emitTaskEnd'],
1234
+ },
1235
+ ],
1236
+
1237
+ // CONTACT_ENDED in conference
1238
+ [TaskEvent.CONTACT_ENDED]: [
1239
+ {
1240
+ // Conference still active → stay
1241
+ actions: ['updateTaskData', 'requestCleanup'],
1242
+ },
1243
+ ],
1244
+
1245
+ [TaskEvent.CONSULT_FAILED]: {
1246
+ actions: ['updateTaskData', 'handleConsultFailed'],
1247
+ },
1248
+
1249
+ [TaskEvent.TASK_WRAPUP]: {
1250
+ guard: guards.shouldWrapUp,
1251
+ target: TaskState.WRAPPING_UP,
1252
+ actions: ['updateTaskData', 'markEnded', 'clearConsultState', 'emitTaskWrapup'],
1253
+ },
1254
+ [TaskEvent.OUTBOUND_FAILED]: [
1255
+ {
1256
+ guard: guards.shouldWrapUp,
1257
+ target: TaskState.WRAPPING_UP,
1258
+ actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskWrapup'],
1259
+ },
1260
+ {
1261
+ target: TaskState.TERMINATED,
1262
+ actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskEnd'],
1263
+ },
1264
+ ],
1265
+ },
1266
+ },
1267
+
1268
+ [TaskState.WRAPPING_UP]: {
1269
+ // Only emit wrapup event on entry - task:end should only be emitted when COMPLETED
1270
+ entry: ['emitTaskWrapup'],
1271
+ on: {
1272
+ // AgentWrappedup Event
1273
+ [TaskEvent.WRAPUP_COMPLETE]: {
1274
+ target: TaskState.COMPLETED,
1275
+ actions: ['updateTaskData'],
1276
+ },
1277
+ },
1278
+ },
1279
+
1280
+ [TaskState.COMPLETED]: {
1281
+ type: 'final' as const,
1282
+ entry: ['emitTaskWrappedup', 'cleanupResources'],
1283
+ },
1284
+
1285
+ [TaskState.TERMINATED]: {
1286
+ type: 'final' as const,
1287
+ entry: ['cleanupResources'],
1288
+ },
1289
+ },
1290
+ };
1291
+ }
1292
+
1293
+ /**
1294
+ * Create a task state machine instance using only the built-in actions.
1295
+ * The resulting machine is ready for most consumers that rely on the default
1296
+ * context mutators declared in actions.ts.
1297
+ *
1298
+ * @param uiControlConfig - UI control configuration
1299
+ * @returns StateMachine instance for task management
1300
+ */
1301
+ export function createTaskStateMachine(
1302
+ uiControlConfig: UIControlConfig,
1303
+ options?: {actions?: Partial<TaskActionsMap>}
1304
+ ) {
1305
+ return taskStateMachineSetup.createMachine(getTaskStateMachineConfig(uiControlConfig)).provide({
1306
+ actions: {
1307
+ ...actions,
1308
+ ...(options?.actions ?? {}),
1309
+ },
1310
+ });
1311
+ }
1312
+
1313
+ export type TaskStateMachine = ReturnType<typeof createTaskStateMachine>;