@webex/contact-center 3.12.0-llmrefactor.2 → 3.12.0-llmrefactor.4

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 (187) hide show
  1. package/.sdd/manifest.json +5 -4
  2. package/ai-docs/ARCHITECTURE.md +1 -1
  3. package/ai-docs/CONTRACTS.md +6 -2
  4. package/ai-docs/SECURITY.md +1 -1
  5. package/ai-docs/contact-center-spec.md +22 -4
  6. package/ai-docs/features/consult-transfer-list-policy/spec/feature-spec.md +362 -0
  7. package/dist/cc.js +440 -10
  8. package/dist/cc.js.map +1 -1
  9. package/dist/config.js +7 -0
  10. package/dist/config.js.map +1 -1
  11. package/dist/constants.js +10 -2
  12. package/dist/constants.js.map +1 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/metrics/behavioral-events.js +13 -0
  15. package/dist/metrics/behavioral-events.js.map +1 -1
  16. package/dist/metrics/constants.js +2 -0
  17. package/dist/metrics/constants.js.map +1 -1
  18. package/dist/services/AddressBook.js +18 -15
  19. package/dist/services/AddressBook.js.map +1 -1
  20. package/dist/services/AnswerCallOnWebexService.js +174 -0
  21. package/dist/services/AnswerCallOnWebexService.js.map +1 -0
  22. package/dist/services/EntryPoint.js +46 -68
  23. package/dist/services/EntryPoint.js.map +1 -1
  24. package/dist/services/Queue.js +27 -22
  25. package/dist/services/Queue.js.map +1 -1
  26. package/dist/services/WebexCrossClientService.js +171 -0
  27. package/dist/services/WebexCrossClientService.js.map +1 -0
  28. package/dist/services/WxAppTelephonyMercurySync.js +93 -0
  29. package/dist/services/WxAppTelephonyMercurySync.js.map +1 -0
  30. package/dist/services/config/Util.js +3 -0
  31. package/dist/services/config/Util.js.map +1 -1
  32. package/dist/services/config/constants.js +10 -6
  33. package/dist/services/config/constants.js.map +1 -1
  34. package/dist/services/config/types.js +4 -0
  35. package/dist/services/config/types.js.map +1 -1
  36. package/dist/services/core/Err.js.map +1 -1
  37. package/dist/services/core/WebexRequest.js +6 -2
  38. package/dist/services/core/WebexRequest.js.map +1 -1
  39. package/dist/services/core/aqm-reqs.js +28 -14
  40. package/dist/services/core/aqm-reqs.js.map +1 -1
  41. package/dist/services/core/types.js.map +1 -1
  42. package/dist/services/task/Task.js +73 -7
  43. package/dist/services/task/Task.js.map +1 -1
  44. package/dist/services/task/TaskFactory.js +8 -4
  45. package/dist/services/task/TaskFactory.js.map +1 -1
  46. package/dist/services/task/TaskManager.js +353 -21
  47. package/dist/services/task/TaskManager.js.map +1 -1
  48. package/dist/services/task/WebexCallingUtils.js +70 -0
  49. package/dist/services/task/WebexCallingUtils.js.map +1 -0
  50. package/dist/services/task/constants.js +4 -1
  51. package/dist/services/task/constants.js.map +1 -1
  52. package/dist/services/task/contact.js +29 -0
  53. package/dist/services/task/contact.js.map +1 -1
  54. package/dist/services/task/digital/Digital.js +3 -2
  55. package/dist/services/task/digital/Digital.js.map +1 -1
  56. package/dist/services/task/state-machine/TaskStateMachine.js +135 -37
  57. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -1
  58. package/dist/services/task/state-machine/actions.js +6 -1
  59. package/dist/services/task/state-machine/actions.js.map +1 -1
  60. package/dist/services/task/state-machine/guards.js +38 -11
  61. package/dist/services/task/state-machine/guards.js.map +1 -1
  62. package/dist/services/task/state-machine/types.js.map +1 -1
  63. package/dist/services/task/state-machine/uiControlsComputer.js +118 -18
  64. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -1
  65. package/dist/services/task/types.js +16 -1
  66. package/dist/services/task/types.js.map +1 -1
  67. package/dist/services/task/voice/Voice.js +350 -14
  68. package/dist/services/task/voice/Voice.js.map +1 -1
  69. package/dist/services/task/voice/WebRTC.js +4 -1
  70. package/dist/services/task/voice/WebRTC.js.map +1 -1
  71. package/dist/services/task/voice/wxAppVoiceMethods.js +201 -0
  72. package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
  73. package/dist/services/wxAppTelephonyUtils.js +19 -0
  74. package/dist/services/wxAppTelephonyUtils.js.map +1 -0
  75. package/dist/types/cc.d.ts +77 -4
  76. package/dist/types/config.d.ts +7 -0
  77. package/dist/types/constants.d.ts +8 -0
  78. package/dist/types/index.d.ts +3 -1
  79. package/dist/types/metrics/constants.d.ts +2 -0
  80. package/dist/types/services/AddressBook.d.ts +2 -1
  81. package/dist/types/services/AnswerCallOnWebexService.d.ts +37 -0
  82. package/dist/types/services/EntryPoint.d.ts +7 -5
  83. package/dist/types/services/Queue.d.ts +5 -3
  84. package/dist/types/services/WebexCrossClientService.d.ts +28 -0
  85. package/dist/types/services/WxAppTelephonyMercurySync.d.ts +28 -0
  86. package/dist/types/services/config/constants.d.ts +8 -5
  87. package/dist/types/services/config/types.d.ts +17 -4
  88. package/dist/types/services/core/Err.d.ts +2 -0
  89. package/dist/types/services/core/WebexRequest.d.ts +1 -0
  90. package/dist/types/services/core/types.d.ts +2 -0
  91. package/dist/types/services/task/Task.d.ts +20 -2
  92. package/dist/types/services/task/TaskFactory.d.ts +2 -1
  93. package/dist/types/services/task/WebexCallingUtils.d.ts +11 -0
  94. package/dist/types/services/task/constants.d.ts +3 -0
  95. package/dist/types/services/task/contact.d.ts +4 -0
  96. package/dist/types/services/task/digital/Digital.d.ts +2 -2
  97. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +260 -56
  98. package/dist/types/services/task/state-machine/guards.d.ts +6 -5
  99. package/dist/types/services/task/state-machine/types.d.ts +9 -1
  100. package/dist/types/services/task/types.d.ts +81 -4
  101. package/dist/types/services/task/voice/Voice.d.ts +48 -9
  102. package/dist/types/services/task/voice/WebRTC.d.ts +2 -2
  103. package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +52 -0
  104. package/dist/types/services/wxAppTelephonyUtils.d.ts +5 -0
  105. package/dist/types/types.d.ts +14 -8
  106. package/dist/types/utils/PageCache.d.ts +20 -3
  107. package/dist/types.js +7 -2
  108. package/dist/types.js.map +1 -1
  109. package/dist/utils/PageCache.js +19 -5
  110. package/dist/utils/PageCache.js.map +1 -1
  111. package/dist/webex.js +1 -1
  112. package/package.json +9 -9
  113. package/src/cc.ts +506 -9
  114. package/src/config.ts +7 -0
  115. package/src/constants.ts +8 -0
  116. package/src/index.ts +2 -0
  117. package/src/metrics/ai-docs/metrics-spec.md +9 -3
  118. package/src/metrics/behavioral-events.ts +14 -0
  119. package/src/metrics/constants.ts +2 -0
  120. package/src/services/AddressBook.ts +17 -6
  121. package/src/services/AnswerCallOnWebexService.ts +206 -0
  122. package/src/services/EntryPoint.ts +59 -60
  123. package/src/services/Queue.ts +29 -12
  124. package/src/services/WebexCrossClientService.ts +212 -0
  125. package/src/services/WxAppTelephonyMercurySync.ts +115 -0
  126. package/src/services/ai-docs/AGENTS.md +10 -10
  127. package/src/services/ai-docs/services-spec.md +6 -1
  128. package/src/services/config/Util.ts +3 -0
  129. package/src/services/config/ai-docs/AGENTS.md +1 -1
  130. package/src/services/config/ai-docs/ARCHITECTURE.md +2 -2
  131. package/src/services/config/ai-docs/config-spec.md +6 -0
  132. package/src/services/config/constants.ts +10 -6
  133. package/src/services/config/types.ts +16 -4
  134. package/src/services/core/Err.ts +1 -0
  135. package/src/services/core/WebexRequest.ts +3 -1
  136. package/src/services/core/ai-docs/core-spec.md +5 -1
  137. package/src/services/core/aqm-reqs.ts +30 -15
  138. package/src/services/core/types.ts +2 -0
  139. package/src/services/task/Task.ts +79 -8
  140. package/src/services/task/TaskFactory.ts +8 -3
  141. package/src/services/task/TaskManager.ts +522 -16
  142. package/src/services/task/WebexCallingUtils.ts +136 -0
  143. package/src/services/task/ai-docs/ARCHITECTURE.md +1 -0
  144. package/src/services/task/ai-docs/task-spec.md +152 -2
  145. package/src/services/task/constants.ts +3 -0
  146. package/src/services/task/contact.ts +30 -0
  147. package/src/services/task/digital/Digital.ts +4 -2
  148. package/src/services/task/state-machine/TaskStateMachine.ts +210 -63
  149. package/src/services/task/state-machine/actions.ts +5 -2
  150. package/src/services/task/state-machine/ai-docs/AGENTS.md +8 -4
  151. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +17 -8
  152. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +56 -24
  153. package/src/services/task/state-machine/guards.ts +64 -11
  154. package/src/services/task/state-machine/types.ts +16 -1
  155. package/src/services/task/state-machine/uiControlsComputer.ts +206 -32
  156. package/src/services/task/types.ts +114 -4
  157. package/src/services/task/voice/Voice.ts +462 -12
  158. package/src/services/task/voice/WebRTC.ts +5 -1
  159. package/src/services/task/voice/wxAppVoiceMethods.ts +307 -0
  160. package/src/services/wxAppTelephonyUtils.ts +14 -0
  161. package/src/types.ts +33 -9
  162. package/src/utils/AGENTS.md +21 -10
  163. package/src/utils/PageCache.ts +38 -5
  164. package/src/utils/ai-docs/utils-spec.md +21 -11
  165. package/test/unit/spec/cc.ts +1274 -0
  166. package/test/unit/spec/metrics/behavioral-events.ts +18 -0
  167. package/test/unit/spec/services/AddressBook.ts +37 -6
  168. package/test/unit/spec/services/AnswerCallOnWebexService.ts +223 -0
  169. package/test/unit/spec/services/EntryPoint.ts +87 -40
  170. package/test/unit/spec/services/Queue.ts +123 -12
  171. package/test/unit/spec/services/WebexCrossClientService.ts +261 -0
  172. package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +113 -0
  173. package/test/unit/spec/services/config/Util.ts +85 -0
  174. package/test/unit/spec/services/core/WebexRequest.ts +3 -1
  175. package/test/unit/spec/services/core/aqm-reqs.ts +113 -1
  176. package/test/unit/spec/services/task/Task.ts +200 -0
  177. package/test/unit/spec/services/task/TaskFactory.ts +39 -2
  178. package/test/unit/spec/services/task/TaskManager.ts +1294 -1
  179. package/test/unit/spec/services/task/WebexCallingUtils.ts +153 -0
  180. package/test/unit/spec/services/task/contact.ts +33 -0
  181. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +854 -72
  182. package/test/unit/spec/services/task/state-machine/guards.ts +210 -8
  183. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +445 -7
  184. package/test/unit/spec/services/task/voice/Voice.ts +874 -0
  185. package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +459 -0
  186. package/umd/contact-center.min.js +2 -2
  187. package/umd/contact-center.min.js.map +1 -1
@@ -52,12 +52,70 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
52
52
  * AgentContactHeld -> TaskEvent.HOLD_SUCCESS
53
53
  * AgentContactUnheld -> TaskEvent.UNHOLD_SUCCESS
54
54
  * AgentConsultEnded -> TaskEvent.CONSULT_END
55
+ * ParticipantLeftConference -> TaskEvent.PARTICIPANT_LEAVE
55
56
  * AgentContactEnded -> TaskEvent.CONTACT_ENDED
56
57
  * AgentWrapup -> TaskEvent.TASK_WRAPUP (wrapUpRequired)
57
58
  * AgentWrappedup -> TaskEvent.WRAPUP_COMPLETE
58
59
  *
59
60
  * (See TaskManager.mapEventToTaskStateMachineEvent for the full mapping table.)
60
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
+
61
119
  return {
62
120
  id: 'taskStateMachine',
63
121
  initial: TaskState.IDLE,
@@ -70,7 +128,7 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
70
128
  actions: ['updateTaskData', 'syncTaskDataFromEvent'],
71
129
  },
72
130
  [TaskEvent.CONTACT_OWNER_CHANGED]: {
73
- actions: ['updateTaskData', 'syncTaskDataFromEvent'],
131
+ actions: ['updateTaskData', 'syncTaskDataFromEvent', 'emitTaskHydrate'],
74
132
  },
75
133
  // HYDRATE: Update task data from AgentContact event
76
134
  // Note: State restoration with transitions is handled in IDLE state.
@@ -190,10 +248,17 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
190
248
  target: TaskState.TERMINATED,
191
249
  actions: ['updateTaskData', 'markEnded', 'emitTaskReject'],
192
250
  },
193
- [TaskEvent.TASK_WRAPUP]: {
194
- target: TaskState.TERMINATED,
195
- actions: ['updateTaskData', 'markEnded', 'emitTaskEnd'],
196
- },
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
+ ],
197
262
  [TaskEvent.CONTACT_ENDED]: {
198
263
  target: TaskState.TERMINATED,
199
264
  actions: ['updateTaskData', 'markEnded', 'emitTaskEnd'],
@@ -334,6 +399,7 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
334
399
  },
335
400
  // AgentConsultEnded from Stable Prod while on connected leg (external end consult).
336
401
  [TaskEvent.CONSULT_END]: [
402
+ ...currentAgentConsultEndTransitions(),
337
403
  {
338
404
  guard: ({context, event}) => {
339
405
  if (context.consultInitiator !== true) return false;
@@ -350,6 +416,14 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
350
416
  actions: ['updateTaskData', 'clearConsultState', 'emitTaskConsultEnd'],
351
417
  },
352
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
+ ],
353
427
  // AgentContactEnded Event
354
428
  [TaskEvent.CONTACT_ENDED]: [
355
429
  {
@@ -380,6 +454,17 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
380
454
  target: TaskState.WRAPPING_UP,
381
455
  actions: ['updateTaskData', 'markEnded', 'emitTaskWrapup'],
382
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
+ ],
383
468
  [TaskEvent.PAUSE_RECORDING]: {
384
469
  actions: ['updateTaskData', 'setRecordingState', 'emitTaskRecordingPaused'],
385
470
  },
@@ -401,6 +486,24 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
401
486
  target: TaskState.CONNECTED,
402
487
  actions: ['updateTaskData'],
403
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
+ ],
404
507
  },
405
508
  },
406
509
 
@@ -487,14 +590,30 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
487
590
  },
488
591
  ],
489
592
  // AgentConsultEnded from Stable Prod while main leg is held (external end consult).
490
- [TaskEvent.CONSULT_END]: {
491
- actions: ['updateTaskData', 'clearConsultState', 'emitTaskConsultEnd'],
492
- },
593
+ [TaskEvent.CONSULT_END]: [
594
+ ...currentAgentConsultEndTransitions(),
595
+ {actions: ['updateTaskData', 'clearConsultState', 'emitTaskConsultEnd']},
596
+ ],
597
+ [TaskEvent.PARTICIPANT_LEAVE]: [
598
+ ...currentAgentParticipantLeaveTransitions(),
599
+ {actions: ['updateTaskData', 'handleParticipantLeft', 'emitTaskParticipantLeft']},
600
+ ],
493
601
  // TODO: This may not be a valid transition, this needs to be checked as well
494
602
  [TaskEvent.TASK_WRAPUP]: {
495
603
  target: TaskState.WRAPPING_UP,
496
604
  actions: ['updateTaskData', 'markEnded', 'emitTaskWrapup'],
497
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
+ ],
498
617
  },
499
618
  },
500
619
 
@@ -507,6 +626,22 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
507
626
  [TaskEvent.UNHOLD_FAILED]: {
508
627
  target: TaskState.HELD,
509
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
+ ],
510
645
  },
511
646
  },
512
647
 
@@ -569,6 +704,7 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
569
704
  ],
570
705
  // AgentConsultEnded from Stable Prod during consult initiation (external end consult).
571
706
  [TaskEvent.CONSULT_END]: [
707
+ ...currentAgentConsultEndTransitions(),
572
708
  {
573
709
  guard: guards.isPrimaryMediaOnHold,
574
710
  target: TaskState.HELD,
@@ -579,6 +715,21 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
579
715
  actions: ['updateTaskData', 'clearConsultState', 'emitTaskConsultEnd'],
580
716
  },
581
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
+ ],
582
733
  },
583
734
  },
584
735
 
@@ -619,6 +770,7 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
619
770
 
620
771
  // AgentConsultEnded
621
772
  [TaskEvent.CONSULT_END]: [
773
+ ...currentAgentConsultEndTransitions(),
622
774
  {
623
775
  // Initiator returning to conference only while conference is still active.
624
776
  guard: ({context, event}) =>
@@ -686,7 +838,13 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
686
838
  {
687
839
  // Consulted agent → TERMINATED
688
840
  target: TaskState.TERMINATED,
689
- actions: ['updateTaskData'],
841
+ actions: [
842
+ 'updateTaskData',
843
+ 'markEnded',
844
+ 'clearConsultState',
845
+ 'emitTaskConsultEnd',
846
+ 'emitTaskEnd',
847
+ ],
690
848
  },
691
849
  ],
692
850
 
@@ -705,34 +863,10 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
705
863
  },
706
864
 
707
865
  [TaskEvent.PARTICIPANT_LEAVE]: [
866
+ ...currentAgentParticipantLeaveTransitions(),
708
867
  {
709
868
  guard: (params) =>
710
- guards.didCurrentAgentLeaveConference(params) && guards.shouldWrapUp(params),
711
- target: TaskState.WRAPPING_UP,
712
- actions: [
713
- 'updateTaskData',
714
- 'handleParticipantLeft',
715
- 'markEnded',
716
- 'clearConsultState',
717
- 'emitTaskParticipantLeft',
718
- 'emitTaskWrapup',
719
- ],
720
- },
721
- {
722
- guard: guards.didCurrentAgentLeaveConference,
723
- target: TaskState.TERMINATED,
724
- actions: [
725
- 'updateTaskData',
726
- 'handleParticipantLeft',
727
- 'markEnded',
728
- 'clearConsultState',
729
- 'emitTaskParticipantLeft',
730
- 'emitTaskEnd',
731
- ],
732
- },
733
- {
734
- guard: (params) =>
735
- !guards.didCurrentAgentLeaveConference(params) &&
869
+ !guards.didCurrentAgentLeaveMainInteraction(params) &&
736
870
  guards.shouldDowngradeConferenceToConnected(params),
737
871
  target: TaskState.CONNECTED,
738
872
  actions: [
@@ -825,6 +959,17 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
825
959
  target: TaskState.WRAPPING_UP,
826
960
  actions: ['updateTaskData', 'markEnded', 'clearConsultState', 'emitTaskWrapup'],
827
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
+ ],
828
973
  [TaskEvent.MERGE_TO_CONFERENCE]: {
829
974
  target: TaskState.CONF_INITIATING,
830
975
  },
@@ -860,6 +1005,7 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
860
1005
  },
861
1006
  // AgentConsultEnded while conference is initiating (end call before conference completes)
862
1007
  [TaskEvent.CONSULT_END]: [
1008
+ ...currentAgentConsultEndTransitions(),
863
1009
  {
864
1010
  guard: ({event}) => {
865
1011
  const taskData = getTaskDataFromEvent(event);
@@ -874,6 +1020,21 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
874
1020
  actions: ['updateTaskData', 'clearConsultState'],
875
1021
  },
876
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
+ ],
877
1038
  },
878
1039
  },
879
1040
 
@@ -901,6 +1062,7 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
901
1062
 
902
1063
  // Needed as all agents in conference get this event, hence we need to clear the consult state
903
1064
  [TaskEvent.CONSULT_END]: [
1065
+ ...currentAgentConsultEndTransitions(),
904
1066
  {
905
1067
  guard: ({context, event}) => {
906
1068
  const taskData = getTaskDataFromEvent(event);
@@ -975,37 +1137,11 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
975
1137
 
976
1138
  // Participant leaves - handle conference downgrade scenarios
977
1139
  [TaskEvent.PARTICIPANT_LEAVE]: [
978
- {
979
- // Only the leaving agent should wrap up → WRAPPING_UP
980
- guard: (params) =>
981
- guards.didCurrentAgentLeaveConference(params) && guards.shouldWrapUp(params),
982
- target: TaskState.WRAPPING_UP,
983
- actions: [
984
- 'updateTaskData',
985
- 'handleParticipantLeft',
986
- 'markEnded',
987
- 'clearConsultState',
988
- 'emitTaskParticipantLeft',
989
- 'emitTaskWrapup',
990
- ],
991
- },
992
- {
993
- // Only the leaving agent (no wrapup) → TERMINATED
994
- guard: guards.didCurrentAgentLeaveConference,
995
- target: TaskState.TERMINATED,
996
- actions: [
997
- 'updateTaskData',
998
- 'handleParticipantLeft',
999
- 'markEnded',
1000
- 'clearConsultState',
1001
- 'emitTaskParticipantLeft',
1002
- 'emitTaskEnd',
1003
- ],
1004
- },
1140
+ ...currentAgentParticipantLeaveTransitions(),
1005
1141
  {
1006
1142
  // Conference downgraded, customer present → CONNECTED
1007
1143
  guard: (params) =>
1008
- !guards.didCurrentAgentLeaveConference(params) &&
1144
+ !guards.didCurrentAgentLeaveMainInteraction(params) &&
1009
1145
  guards.shouldDowngradeConferenceToConnected(params),
1010
1146
  target: TaskState.CONNECTED,
1011
1147
  actions: [
@@ -1115,6 +1251,17 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
1115
1251
  target: TaskState.WRAPPING_UP,
1116
1252
  actions: ['updateTaskData', 'markEnded', 'clearConsultState', 'emitTaskWrapup'],
1117
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
+ ],
1118
1265
  },
1119
1266
  },
1120
1267
 
@@ -515,12 +515,15 @@ export const actions: TaskActionsMap = {
515
515
  }),
516
516
  handleConferenceStarted: assign({consultInitiator: false}),
517
517
 
518
- setConsultDestination: assign(({event}: TaskActionArgs) => {
518
+ setConsultDestination: assign(({context, event}: TaskActionArgs) => {
519
519
  if (!event || event.type !== TaskEvent.CONSULT) {
520
520
  return {};
521
521
  }
522
522
 
523
- const taskData = getTaskDataFromEvent(event);
523
+ // CONSULT is a user action and does not carry taskData. Preserve the current interaction
524
+ // snapshot while recording the destination; replacing it with only destAgentId would remove
525
+ // the main-leg membership needed to correlate an immediately following lifecycle event.
526
+ const taskData = context.taskData;
524
527
  const consultDestinationType =
525
528
  'destinationType' in event ? event.destinationType ?? null : null;
526
529
  const consultDestinationAgentId =
@@ -66,7 +66,7 @@ state-machine/
66
66
  - **State Graph and Transition Rules**: `TaskStateMachine.ts` defines all states, transition tables, and event handlers that drive the task lifecycle.
67
67
  - **Deterministic Context Updates**: `actions.ts` implements XState actions for task context mutation and provides emitter placeholders that `Task` overrides to surface SDK events.
68
68
  - **Transition Eligibility**: `guards.ts` contains pure predicates that gate transitions based on current context, task data, and backend state.
69
- - **UI Controls Computation**: `uiControlsComputer.ts` derives `TaskUIControls` from state and context for voice/digital channels, keeping UI enablement centralized.
69
+ - **UI Controls Computation**: `uiControlsComputer.ts` derives per-leg action controls and ordered Consult/Transfer destination categories from state, Task data, and injected profile policy for voice/digital channels. Consumers read `task.uiControls`; do not add a parallel policy method or UI-side decision tree.
70
70
  - **Typed Event Contracts**: `constants.ts` and `types.ts` define `TaskState`, `TaskEvent`, and the `TaskEventPayloadMap` so transitions and payloads stay type-safe.
71
71
  - **Public Exports**: `index.ts` exposes the state machine factory, event enums, and types for consumption by the task layer.
72
72
 
@@ -266,14 +266,18 @@ shouldWrapUpOrIsInitiator(context, event) {
266
266
  return Boolean(event.taskData?.wrapUpRequired || context.consultInitiator);
267
267
  }
268
268
 
269
- // Check whether the leaving participant is the current agent
270
- didCurrentAgentLeaveConference(context, event) {
269
+ // Check whether the current agent left the main interaction
270
+ didCurrentAgentLeaveMainInteraction(context, event) {
271
271
  const selfAgentId = getSelfAgentId(context, event.taskData);
272
272
  if (!selfAgentId) return false;
273
273
 
274
274
  const participantIdFromEvent = 'participantId' in event ? event.participantId : undefined;
275
275
  const participantId = participantIdFromEvent ?? event.taskData?.participantId;
276
- return Boolean(participantId) && participantId === selfAgentId;
276
+ if (Boolean(participantId) && participantId === selfAgentId) return true;
277
+ // Explicit hasLeft or removal of a previously active self from the participant map is terminal.
278
+ // PARTICIPANT_LEAVE naming another participant does not infer self departure from media.
279
+ // Only a from-conference CONSULT_END may compare mainCall membership, and only when self
280
+ // remains active in the participant map and on the consult leg. Partial ordinary calls are false.
277
281
  }
278
282
  ```
279
283
 
@@ -30,7 +30,7 @@ It is instantiated by `Task` and receives mapped backend/user events through `se
30
30
  2. `createActor(machine).start()`
31
31
  3. `TaskManager` and task APIs map external signals to `TaskEvent`
32
32
  4. Actor transitions update context and execute action overrides
33
- 5. `Task` recomputes UI controls and emits task-level events
33
+ 5. `Task` recomputes per-leg controls plus ordered Consult/Transfer destination availability and emits task-level events when either changes
34
34
 
35
35
  ---
36
36
 
@@ -464,10 +464,10 @@ It is instantiated by `Task` and receives mapped backend/user events through `se
464
464
  - Guard: none
465
465
  - Actions: `clearTransferConferenceRequested`
466
466
  - `PARTICIPANT_LEAVE` -> `WRAPPING_UP`
467
- - Guard: `guards.didCurrentAgentLeaveConference && guards.shouldWrapUp`
467
+ - Guard: `guards.didCurrentAgentLeaveMainInteraction && guards.shouldWrapUp`
468
468
  - Actions: `updateTaskData`, `handleParticipantLeft`, `markEnded`, `clearConsultState`, `emitTaskParticipantLeft`, `emitTaskWrapup`
469
469
  - `PARTICIPANT_LEAVE` -> `TERMINATED`
470
- - Guard: `guards.didCurrentAgentLeaveConference`
470
+ - Guard: `guards.didCurrentAgentLeaveMainInteraction`
471
471
  - Actions: `updateTaskData`, `handleParticipantLeft`, `markEnded`, `clearConsultState`, `emitTaskParticipantLeft`, `emitTaskEnd`
472
472
  - `PARTICIPANT_LEAVE` -> `CONNECTED`
473
473
  - Guard: `guards.shouldDowngradeConferenceToConnected`
@@ -475,6 +475,15 @@ It is instantiated by `Task` and receives mapped backend/user events through `se
475
475
  - `PARTICIPANT_LEAVE` -> stay `CONFERENCING` (default)
476
476
  - Guard: default
477
477
  - Actions: `updateTaskData`, `handleParticipantLeft`, `emitTaskParticipantLeft`
478
+
479
+ `didCurrentAgentLeaveMainInteraction` treats an explicit self participant ID,
480
+ self `hasLeft`, or removal from a previously active participant map as terminal.
481
+ A `PARTICIPANT_LEAVE` event naming another participant does not infer departure
482
+ from media membership. The previous-versus-updated `mainCall` comparison is
483
+ restricted to from-conference `CONSULT_END` nested-consult cleanup when self
484
+ remains active in the participant map and on the consult leg; partial ordinary CONNECTED/HELD
485
+ snapshots remain non-terminal.
486
+
478
487
  - `CONFERENCE_END` -> `WRAPPING_UP`
479
488
  - Guard: `guards.shouldWrapUp`
480
489
  - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `emitTaskWrapup`
@@ -809,7 +818,7 @@ Complete mapping from backend CC_EVENTS to internal TaskEvent types.
809
818
  | API `task.consultConference()` | `MERGE_TO_CONFERENCE` | CONSULTING → CONF_INITIATING | Starts merge flow |
810
819
  | `AgentConsultConferenced` | `CONFERENCE_START` | CONSULTING/CONF_INITIATING → CONFERENCING | `handleConferenceStarted` path |
811
820
  | `ParticipantJoinedConference` | `CONFERENCE_START` | CONFERENCING → CONFERENCING | Refresh + emit conference started |
812
- | `ParticipantLeftConference` | `PARTICIPANT_LEAVE` | CONFERENCING → WRAPPING_UP / TERMINATED / CONNECTED / stay | Uses `didCurrentAgentLeaveConference`, `shouldWrapUp`, `shouldDowngradeConferenceToConnected` |
821
+ | `ParticipantLeftConference` | `PARTICIPANT_LEAVE` | Any active call-control state → WRAPPING_UP / TERMINATED / CONNECTED / stay | Uses `didCurrentAgentLeaveMainInteraction`, `shouldWrapUp`, `shouldDowngradeConferenceToConnected` |
813
822
  | `AgentConsultConferenceEnded` | `CONFERENCE_END` | CONFERENCING → WRAPPING_UP / CONNECTED / TERMINATED | Guard-based branch |
814
823
  | `AgentConsultConferenceFailed` | `CONFERENCE_FAILED` | CONF_INITIATING → CONSULTING | Merge failed fallback |
815
824
  | `AgentConferenceTransferred` | `TRANSFER_CONFERENCE_SUCCESS` | CONSULTING/CONFERENCING branch logic | Initiator/receiver dependent |
@@ -921,8 +930,8 @@ stateDiagram-v2
921
930
 
922
931
  %% CONFERENCING
923
932
  CONFERENCING --> CONSULT_INITIATING: CONSULT (State Machine Event)
924
- CONFERENCING --> WRAPPING_UP: PARTICIPANT_LEAVE (CC Event) -> PARTICIPANT_LEAVE (State Machine Event) [didCurrentAgentLeaveConference && shouldWrapUp]
925
- CONFERENCING --> TERMINATED: PARTICIPANT_LEAVE (CC Event) -> PARTICIPANT_LEAVE (State Machine Event) [didCurrentAgentLeaveConference]
933
+ CONFERENCING --> WRAPPING_UP: PARTICIPANT_LEAVE (CC Event) -> PARTICIPANT_LEAVE (State Machine Event) [didCurrentAgentLeaveMainInteraction && shouldWrapUp]
934
+ CONFERENCING --> TERMINATED: PARTICIPANT_LEAVE (CC Event) -> PARTICIPANT_LEAVE (State Machine Event) [didCurrentAgentLeaveMainInteraction]
926
935
  CONFERENCING --> CONNECTED: PARTICIPANT_LEAVE (CC Event) -> PARTICIPANT_LEAVE (State Machine Event) [shouldDowngradeConferenceToConnected]
927
936
  CONFERENCING --> WRAPPING_UP: CONFERENCE_END (CC Event) -> CONFERENCE_END (State Machine Event) [shouldWrapUp]
928
937
  CONFERENCING --> CONNECTED: CONFERENCE_END (CC Event) -> CONFERENCE_END (State Machine Event) [customerInCall]
@@ -1038,8 +1047,8 @@ stateDiagram-v2
1038
1047
  CONFERENCING --> CONFERENCING: AGENT_CONFERENCE_TRANSFERRED -> TRANSFER_CONFERENCE_SUCCESS
1039
1048
  CONFERENCING --> CONFERENCING: AGENT_CONFERENCE_TRANSFER_FAILED-> TRANSFER_CONFERENCE_FAILED
1040
1049
 
1041
- CONFERENCING --> WRAPPING_UP: PARTICIPANT_LEFT_CONFERENCE -> PARTICIPANT_LEAVE [didCurrentAgentLeaveConference && shouldWrapUp]
1042
- CONFERENCING --> TERMINATED: PARTICIPANT_LEFT_CONFERENCE -> PARTICIPANT_LEAVE [didCurrentAgentLeaveConference]
1050
+ CONFERENCING --> WRAPPING_UP: PARTICIPANT_LEFT_CONFERENCE -> PARTICIPANT_LEAVE [didCurrentAgentLeaveMainInteraction && shouldWrapUp]
1051
+ CONFERENCING --> TERMINATED: PARTICIPANT_LEFT_CONFERENCE -> PARTICIPANT_LEAVE [didCurrentAgentLeaveMainInteraction]
1043
1052
  CONFERENCING --> CONNECTED: PARTICIPANT_LEFT_CONFERENCE -> PARTICIPANT_LEAVE [shouldDowngradeConferenceToConnected]
1044
1053
  CONFERENCING --> WRAPPING_UP: AGENT_CONSULT_CONFERENCE_ENDED -> CONFERENCE_END [shouldWrapUp]
1045
1054
  CONFERENCING --> CONNECTED: AGENT_CONSULT_CONFERENCE_ENDED -> CONFERENCE_END [customerInCall]