@webex/contact-center 3.12.0-next.9 → 3.12.0-task-refactor.2
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.
- package/AGENTS.md +438 -0
- package/ai-docs/README.md +131 -0
- package/ai-docs/RULES.md +455 -0
- package/ai-docs/patterns/event-driven-patterns.md +485 -0
- package/ai-docs/patterns/testing-patterns.md +480 -0
- package/ai-docs/patterns/typescript-patterns.md +365 -0
- package/ai-docs/templates/README.md +102 -0
- package/ai-docs/templates/documentation/create-agents-md.md +240 -0
- package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
- package/ai-docs/templates/existing-service/bug-fix.md +254 -0
- package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
- package/ai-docs/templates/new-method/00-master.md +80 -0
- package/ai-docs/templates/new-method/01-requirements.md +232 -0
- package/ai-docs/templates/new-method/02-implementation.md +295 -0
- package/ai-docs/templates/new-method/03-tests.md +201 -0
- package/ai-docs/templates/new-method/04-validation.md +141 -0
- package/ai-docs/templates/new-service/00-master.md +109 -0
- package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
- package/ai-docs/templates/new-service/02-code-generation.md +346 -0
- package/ai-docs/templates/new-service/03-integration.md +178 -0
- package/ai-docs/templates/new-service/04-test-generation.md +205 -0
- package/ai-docs/templates/new-service/05-validation.md +145 -0
- package/dist/cc.js +65 -123
- package/dist/cc.js.map +1 -1
- package/dist/constants.js +13 -2
- package/dist/constants.js.map +1 -1
- package/dist/index.js +13 -5
- package/dist/index.js.map +1 -1
- package/dist/metrics/behavioral-events.js +26 -13
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +7 -6
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/ApiAiAssistant.js +0 -3
- package/dist/services/ApiAiAssistant.js.map +1 -1
- package/dist/services/config/Util.js +2 -3
- package/dist/services/config/Util.js.map +1 -1
- package/dist/services/config/types.js +16 -14
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/constants.js +0 -1
- package/dist/services/constants.js.map +1 -1
- package/dist/services/core/Err.js.map +1 -1
- package/dist/services/core/Utils.js +79 -55
- package/dist/services/core/Utils.js.map +1 -1
- package/dist/services/core/aqm-reqs.js +17 -92
- package/dist/services/core/aqm-reqs.js.map +1 -1
- package/dist/services/core/websocket/WebSocketManager.js +5 -25
- package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
- package/dist/services/core/websocket/types.js.map +1 -1
- package/dist/services/index.js +1 -2
- package/dist/services/index.js.map +1 -1
- package/dist/services/task/Task.js +644 -0
- package/dist/services/task/Task.js.map +1 -0
- package/dist/services/task/TaskFactory.js +45 -0
- package/dist/services/task/TaskFactory.js.map +1 -0
- package/dist/services/task/TaskManager.js +570 -535
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +132 -28
- package/dist/services/task/TaskUtils.js.map +1 -1
- package/dist/services/task/constants.js +7 -6
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/dialer.js +0 -51
- package/dist/services/task/dialer.js.map +1 -1
- package/dist/services/task/digital/Digital.js +77 -0
- package/dist/services/task/digital/Digital.js.map +1 -0
- package/dist/services/task/state-machine/TaskStateMachine.js +634 -0
- package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
- package/dist/services/task/state-machine/actions.js +372 -0
- package/dist/services/task/state-machine/actions.js.map +1 -0
- package/dist/services/task/state-machine/constants.js +139 -0
- package/dist/services/task/state-machine/constants.js.map +1 -0
- package/dist/services/task/state-machine/guards.js +263 -0
- package/dist/services/task/state-machine/guards.js.map +1 -0
- package/dist/services/task/state-machine/index.js +53 -0
- package/dist/services/task/state-machine/index.js.map +1 -0
- package/dist/services/task/state-machine/types.js +54 -0
- package/dist/services/task/state-machine/types.js.map +1 -0
- package/dist/services/task/state-machine/uiControlsComputer.js +377 -0
- package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
- package/dist/services/task/taskDataNormalizer.js +99 -0
- package/dist/services/task/taskDataNormalizer.js.map +1 -0
- package/dist/services/task/types.js +157 -18
- package/dist/services/task/types.js.map +1 -1
- package/dist/services/task/voice/Voice.js +1031 -0
- package/dist/services/task/voice/Voice.js.map +1 -0
- package/dist/services/task/voice/WebRTC.js +149 -0
- package/dist/services/task/voice/WebRTC.js.map +1 -0
- package/dist/types/cc.d.ts +4 -33
- package/dist/types/constants.d.ts +13 -2
- package/dist/types/index.d.ts +11 -5
- package/dist/types/metrics/constants.d.ts +5 -3
- package/dist/types/services/ApiAiAssistant.d.ts +1 -1
- package/dist/types/services/config/types.d.ts +97 -25
- package/dist/types/services/core/Err.d.ts +0 -2
- package/dist/types/services/core/Utils.d.ts +25 -23
- package/dist/types/services/core/aqm-reqs.d.ts +0 -49
- package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -1
- package/dist/types/services/core/websocket/connection-service.d.ts +0 -1
- package/dist/types/services/core/websocket/types.d.ts +1 -1
- package/dist/types/services/index.d.ts +1 -1
- package/dist/types/services/task/Task.d.ts +146 -0
- package/dist/types/services/task/TaskFactory.d.ts +12 -0
- package/dist/types/services/task/TaskUtils.d.ts +39 -8
- package/dist/types/services/task/constants.d.ts +5 -4
- package/dist/types/services/task/dialer.d.ts +0 -15
- package/dist/types/services/task/digital/Digital.d.ts +22 -0
- package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +906 -0
- package/dist/types/services/task/state-machine/actions.d.ts +8 -0
- package/dist/types/services/task/state-machine/constants.d.ts +91 -0
- package/dist/types/services/task/state-machine/guards.d.ts +78 -0
- package/dist/types/services/task/state-machine/index.d.ts +13 -0
- package/dist/types/services/task/state-machine/types.d.ts +256 -0
- package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
- package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
- package/dist/types/services/task/types.d.ts +539 -88
- package/dist/types/services/task/voice/Voice.d.ts +183 -0
- package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
- package/dist/types/types.d.ts +68 -0
- package/dist/types/webex.d.ts +1 -0
- package/dist/types.js +70 -0
- package/dist/types.js.map +1 -1
- package/dist/webex.js +14 -2
- package/dist/webex.js.map +1 -1
- package/package.json +14 -11
- package/src/cc.ts +91 -177
- package/src/constants.ts +13 -2
- package/src/index.ts +14 -5
- package/src/metrics/ai-docs/AGENTS.md +348 -0
- package/src/metrics/ai-docs/ARCHITECTURE.md +336 -0
- package/src/metrics/behavioral-events.ts +28 -14
- package/src/metrics/constants.ts +7 -8
- package/src/services/ApiAiAssistant.ts +2 -4
- package/src/services/agent/ai-docs/AGENTS.md +238 -0
- package/src/services/agent/ai-docs/ARCHITECTURE.md +302 -0
- package/src/services/ai-docs/AGENTS.md +384 -0
- package/src/services/config/Util.ts +2 -3
- package/src/services/config/ai-docs/AGENTS.md +253 -0
- package/src/services/config/ai-docs/ARCHITECTURE.md +424 -0
- package/src/services/config/types.ts +108 -20
- package/src/services/constants.ts +0 -1
- package/src/services/core/Err.ts +0 -1
- package/src/services/core/Utils.ts +90 -67
- package/src/services/core/ai-docs/AGENTS.md +379 -0
- package/src/services/core/ai-docs/ARCHITECTURE.md +696 -0
- package/src/services/core/aqm-reqs.ts +22 -100
- package/src/services/core/websocket/WebSocketManager.ts +4 -23
- package/src/services/core/websocket/types.ts +1 -1
- package/src/services/index.ts +1 -2
- package/src/services/task/Task.ts +785 -0
- package/src/services/task/TaskFactory.ts +55 -0
- package/src/services/task/TaskManager.ts +579 -633
- package/src/services/task/TaskUtils.ts +175 -31
- package/src/services/task/ai-docs/AGENTS.md +448 -0
- package/src/services/task/ai-docs/ARCHITECTURE.md +573 -0
- package/src/services/task/constants.ts +5 -4
- package/src/services/task/dialer.ts +1 -56
- package/src/services/task/digital/Digital.ts +95 -0
- package/src/services/task/state-machine/TaskStateMachine.ts +793 -0
- package/src/services/task/state-machine/actions.ts +422 -0
- package/src/services/task/state-machine/ai-docs/AGENTS.md +495 -0
- package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1135 -0
- package/src/services/task/state-machine/constants.ts +150 -0
- package/src/services/task/state-machine/guards.ts +303 -0
- package/src/services/task/state-machine/index.ts +28 -0
- package/src/services/task/state-machine/types.ts +228 -0
- package/src/services/task/state-machine/uiControlsComputer.ts +542 -0
- package/src/services/task/taskDataNormalizer.ts +137 -0
- package/src/services/task/types.ts +641 -95
- package/src/services/task/voice/Voice.ts +1255 -0
- package/src/services/task/voice/WebRTC.ts +187 -0
- package/src/types.ts +88 -5
- package/src/utils/AGENTS.md +276 -0
- package/src/webex.js +2 -0
- package/test/unit/spec/cc.ts +59 -142
- package/test/unit/spec/logger-proxy.ts +70 -0
- package/test/unit/spec/services/ApiAiAssistant.ts +17 -0
- package/test/unit/spec/services/config/index.ts +26 -55
- package/test/unit/spec/services/core/Utils.ts +103 -52
- package/test/unit/spec/services/core/websocket/WebSocketManager.ts +48 -112
- package/test/unit/spec/services/core/websocket/connection-service.ts +5 -4
- package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
- package/test/unit/spec/services/task/Task.ts +416 -0
- package/test/unit/spec/services/task/TaskFactory.ts +62 -0
- package/test/unit/spec/services/task/TaskManager.ts +781 -1735
- package/test/unit/spec/services/task/TaskUtils.ts +125 -0
- package/test/unit/spec/services/task/dialer.ts +112 -198
- package/test/unit/spec/services/task/digital/Digital.ts +105 -0
- package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +473 -0
- package/test/unit/spec/services/task/state-machine/guards.ts +288 -0
- package/test/unit/spec/services/task/state-machine/types.ts +18 -0
- package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +147 -0
- package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
- package/test/unit/spec/services/task/voice/Voice.ts +587 -0
- package/test/unit/spec/services/task/voice/WebRTC.ts +242 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
- package/dist/services/task/index.js +0 -1525
- package/dist/services/task/index.js.map +0 -1
- package/dist/types/services/task/index.d.ts +0 -650
- package/src/services/task/index.ts +0 -1801
- package/test/unit/spec/services/task/index.ts +0 -2184
|
@@ -0,0 +1,793 @@
|
|
|
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} 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
|
+
* AgentContactEnded -> TaskEvent.CONTACT_ENDED
|
|
56
|
+
* AgentWrapup -> TaskEvent.TASK_WRAPUP (wrapUpRequired)
|
|
57
|
+
* AgentWrappedup -> TaskEvent.WRAPUP_COMPLETE
|
|
58
|
+
*
|
|
59
|
+
* (See TaskManager.mapEventToTaskStateMachineEvent for the full mapping table.)
|
|
60
|
+
*/
|
|
61
|
+
return {
|
|
62
|
+
id: 'taskStateMachine',
|
|
63
|
+
initial: TaskState.IDLE,
|
|
64
|
+
context: createInitialContext(uiControlConfig, TaskState.IDLE),
|
|
65
|
+
on: {
|
|
66
|
+
[TaskEvent.RECORDING_STARTED]: {
|
|
67
|
+
actions: ['updateTaskData', 'emitTaskRecordingStarted'],
|
|
68
|
+
},
|
|
69
|
+
[TaskEvent.CONTACT_UPDATED]: {
|
|
70
|
+
actions: ['updateTaskData', 'syncTaskDataFromEvent'],
|
|
71
|
+
},
|
|
72
|
+
[TaskEvent.CONTACT_OWNER_CHANGED]: {
|
|
73
|
+
actions: ['updateTaskData', 'syncTaskDataFromEvent'],
|
|
74
|
+
},
|
|
75
|
+
// HYDRATE: Update task data from AgentContact event
|
|
76
|
+
// Note: State restoration with transitions is handled in IDLE state.
|
|
77
|
+
// This root-level handler is for when task is already in another state (just updates data).
|
|
78
|
+
[TaskEvent.HYDRATE]: {
|
|
79
|
+
actions: ['updateTaskData', 'emitTaskHydrate'],
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
states: {
|
|
83
|
+
[TaskState.IDLE]: {
|
|
84
|
+
on: {
|
|
85
|
+
// HYDRATE: Restore state machine to correct state based on hydrated task data
|
|
86
|
+
// This handles page refresh/reconnection scenarios where task needs to be restored
|
|
87
|
+
// IMPORTANT: This MUST be in IDLE state (not root) because root-level events cannot
|
|
88
|
+
// transition to child states in XState
|
|
89
|
+
[TaskEvent.HYDRATE]: [
|
|
90
|
+
{
|
|
91
|
+
guard: guards.isInteractionTerminated,
|
|
92
|
+
target: TaskState.WRAPPING_UP,
|
|
93
|
+
actions: ['updateTaskData', 'markEnded', 'emitTaskHydrate'],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
guard: guards.isInteractionConsulting,
|
|
97
|
+
target: TaskState.CONSULTING,
|
|
98
|
+
actions: ['updateTaskData', 'emitTaskHydrate'],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
guard: guards.isInteractionHeld,
|
|
102
|
+
target: TaskState.HELD,
|
|
103
|
+
actions: ['updateTaskData', 'emitTaskHydrate'],
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
guard: guards.isInteractionConnected,
|
|
107
|
+
target: TaskState.CONNECTED,
|
|
108
|
+
actions: ['updateTaskData', 'emitTaskHydrate'],
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
guard: guards.isConferencingByParticipants,
|
|
112
|
+
target: TaskState.CONFERENCING,
|
|
113
|
+
actions: ['updateTaskData', 'emitTaskHydrate'],
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
// Default: just update data, stay in IDLE
|
|
117
|
+
actions: ['updateTaskData', 'emitTaskHydrate'],
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
// AgentContactReserved (applicable for direct incoming/consult/transfer/outdial)
|
|
121
|
+
[TaskEvent.TASK_INCOMING]: {
|
|
122
|
+
target: TaskState.OFFERED,
|
|
123
|
+
actions: ['initializeTask', 'emitTaskIncoming'],
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
// EP-DN split-leg ordering can deliver AgentConsulting before HYDRATE/TASK_INCOMING.
|
|
127
|
+
// Do not drop it in IDLE; bootstrap to CONSULTING using event taskData.
|
|
128
|
+
[TaskEvent.CONSULTING_ACTIVE]: {
|
|
129
|
+
target: TaskState.CONSULTING,
|
|
130
|
+
actions: [
|
|
131
|
+
'updateTaskData',
|
|
132
|
+
'setConsultInitiator',
|
|
133
|
+
'setConsultDestination',
|
|
134
|
+
'setConsultFromConference',
|
|
135
|
+
'setConsultAgentJoined',
|
|
136
|
+
'emitTaskConsultAccepted',
|
|
137
|
+
'emitTaskConsulting',
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
[TaskState.OFFERED]: {
|
|
144
|
+
on: {
|
|
145
|
+
// AgentContactOffer
|
|
146
|
+
[TaskEvent.TASK_OFFERED]: {
|
|
147
|
+
actions: ['updateTaskData', 'emitTaskOfferContact', 'requestAutoAnswer'],
|
|
148
|
+
},
|
|
149
|
+
// AgentContactAssigned
|
|
150
|
+
[TaskEvent.ASSIGN]: [
|
|
151
|
+
{
|
|
152
|
+
guard: guards.isConsultingAssignment,
|
|
153
|
+
target: TaskState.CONSULTING,
|
|
154
|
+
actions: ['updateTaskData', 'emitTaskConsultAccepted', 'emitTaskConsulting'],
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
target: TaskState.CONNECTED,
|
|
158
|
+
actions: ['updateTaskData', 'emitTaskAssigned'],
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
// AgentOfferContactRONA
|
|
162
|
+
[TaskEvent.RONA]: {
|
|
163
|
+
target: TaskState.TERMINATED,
|
|
164
|
+
actions: ['updateTaskData', 'markEnded', 'emitTaskReject'],
|
|
165
|
+
},
|
|
166
|
+
[TaskEvent.TASK_WRAPUP]: {
|
|
167
|
+
target: TaskState.TERMINATED,
|
|
168
|
+
actions: ['updateTaskData', 'markEnded', 'emitTaskEnd'],
|
|
169
|
+
},
|
|
170
|
+
[TaskEvent.CONTACT_ENDED]: {
|
|
171
|
+
target: TaskState.TERMINATED,
|
|
172
|
+
actions: ['updateTaskData', 'markEnded', 'emitTaskEnd'],
|
|
173
|
+
},
|
|
174
|
+
// This needs to be handled for all assign failed scenarios (contact, buddy)
|
|
175
|
+
// [AgentContactAssignFailed, AgentCtqFailed, AgentBlindTransferFailed,
|
|
176
|
+
// AgentVTeamTransferFailed, AgentConsultTransferFailed]
|
|
177
|
+
[TaskEvent.ASSIGN_FAILED]: {
|
|
178
|
+
target: TaskState.TERMINATED,
|
|
179
|
+
actions: ['updateTaskData', 'markEnded', 'emitTaskReject'],
|
|
180
|
+
},
|
|
181
|
+
[TaskEvent.INVITE_FAILED]: {
|
|
182
|
+
target: TaskState.TERMINATED,
|
|
183
|
+
actions: ['updateTaskData', 'markEnded', 'emitTaskReject'],
|
|
184
|
+
},
|
|
185
|
+
[TaskEvent.OUTBOUND_FAILED]: [
|
|
186
|
+
{
|
|
187
|
+
guard: guards.shouldWrapUp,
|
|
188
|
+
target: TaskState.WRAPPING_UP,
|
|
189
|
+
actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskWrapup'],
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
target: TaskState.TERMINATED,
|
|
193
|
+
actions: ['updateTaskData', 'markEnded', 'emitTaskOutdialFailed', 'emitTaskReject'],
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
// AgentConsulting comes for received after the initial consult is accepted
|
|
197
|
+
[TaskEvent.CONSULTING_ACTIVE]: [
|
|
198
|
+
{
|
|
199
|
+
target: TaskState.CONSULTING,
|
|
200
|
+
actions: [
|
|
201
|
+
'updateTaskData',
|
|
202
|
+
'setConsultAgentJoined',
|
|
203
|
+
'emitTaskConsultAccepted',
|
|
204
|
+
'emitTaskConsulting',
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
// agentOfferConsult happens only on the receiver side of consult
|
|
209
|
+
[TaskEvent.OFFER_CONSULT]: {
|
|
210
|
+
actions: ['updateTaskData', 'emitTaskOfferConsult', 'requestAutoAnswer'],
|
|
211
|
+
},
|
|
212
|
+
// AgentConsultFailed - when consulted agent (Agent 2) doesn't answer (RONA or decline)
|
|
213
|
+
// Clears the incoming consult notification by transitioning to TERMINATED
|
|
214
|
+
[TaskEvent.CONSULT_FAILED]: {
|
|
215
|
+
target: TaskState.TERMINATED,
|
|
216
|
+
actions: ['updateTaskData', 'clearConsultState', 'emitTaskReject'],
|
|
217
|
+
},
|
|
218
|
+
// AgentConsultEnded - when consult initiator (Agent 1) ends the consult before
|
|
219
|
+
// the consulted agent (Agent 2) accepts. Clears the incoming notification.
|
|
220
|
+
[TaskEvent.CONSULT_END]: {
|
|
221
|
+
target: TaskState.TERMINATED,
|
|
222
|
+
actions: ['updateTaskData', 'clearConsultState', 'emitTaskReject'],
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
[TaskState.CONNECTED]: {
|
|
228
|
+
on: {
|
|
229
|
+
// AgentConsulting may arrive while machine is CONNECTED (EP-DN/event ordering).
|
|
230
|
+
// Derive consultInitiator from payload so controls are set correctly.
|
|
231
|
+
[TaskEvent.CONSULTING_ACTIVE]: {
|
|
232
|
+
target: TaskState.CONSULTING,
|
|
233
|
+
actions: [
|
|
234
|
+
'updateTaskData',
|
|
235
|
+
'setConsultInitiator',
|
|
236
|
+
'setConsultDestination',
|
|
237
|
+
'setConsultAgentJoined',
|
|
238
|
+
'emitTaskConsultAccepted',
|
|
239
|
+
'emitTaskConsulting',
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
// AgentContactAssigned can be resent after consult transfers; keep context in sync
|
|
243
|
+
[TaskEvent.ASSIGN]: {
|
|
244
|
+
target: TaskState.CONNECTED,
|
|
245
|
+
actions: ['updateTaskData', 'emitTaskAssigned'],
|
|
246
|
+
},
|
|
247
|
+
// Click of hold button
|
|
248
|
+
[TaskEvent.HOLD_INITIATED]: {
|
|
249
|
+
target: TaskState.HOLD_INITIATING,
|
|
250
|
+
},
|
|
251
|
+
// Remote hold from another login session (multi-login)
|
|
252
|
+
[TaskEvent.HOLD_SUCCESS]: {
|
|
253
|
+
target: TaskState.HELD,
|
|
254
|
+
actions: ['updateTaskData', 'setHoldState', 'emitTaskHold'],
|
|
255
|
+
},
|
|
256
|
+
// Click of the consult button
|
|
257
|
+
[TaskEvent.CONSULT]: {
|
|
258
|
+
target: TaskState.CONSULT_INITIATING,
|
|
259
|
+
actions: ['setConsultInitiator', 'setConsultDestination'],
|
|
260
|
+
},
|
|
261
|
+
// AgentConsultTransferred / AgentVTeamTransferred / AgentBlindTransferred
|
|
262
|
+
[TaskEvent.TRANSFER_SUCCESS]: [
|
|
263
|
+
{
|
|
264
|
+
guard: guards.shouldWrapUpOrIsInitiator,
|
|
265
|
+
target: TaskState.WRAPPING_UP,
|
|
266
|
+
actions: ['updateTaskData', 'markEnded', 'clearConsultState', 'emitTaskWrapup'],
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
// Receiver goes to connected as he receives transferSuccess event
|
|
270
|
+
actions: ['updateTaskData', 'clearConsultState'],
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
[TaskEvent.TRANSFER_FAILED]: {
|
|
274
|
+
actions: ['updateTaskData'],
|
|
275
|
+
},
|
|
276
|
+
// AgentContactEnded Event
|
|
277
|
+
[TaskEvent.CONTACT_ENDED]: [
|
|
278
|
+
{
|
|
279
|
+
// Conference still active → CONFERENCING
|
|
280
|
+
guard: guards.conferenceInProgressFromEvent,
|
|
281
|
+
target: TaskState.CONFERENCING,
|
|
282
|
+
actions: ['updateTaskData', 'emitTaskConferenceStarted', 'requestCleanup'],
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
// Agent should wrap up → WRAPPING_UP
|
|
286
|
+
guard: guards.shouldWrapUp,
|
|
287
|
+
target: TaskState.WRAPPING_UP,
|
|
288
|
+
actions: ['updateTaskData', 'markEnded', 'emitTaskWrapup', 'requestCleanup'],
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
// Consulted agent → TERMINATED
|
|
292
|
+
target: TaskState.TERMINATED,
|
|
293
|
+
actions: ['updateTaskData', 'markEnded', 'emitTaskEnd'],
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
[TaskEvent.TASK_WRAPUP]: {
|
|
297
|
+
target: TaskState.WRAPPING_UP,
|
|
298
|
+
actions: ['updateTaskData', 'markEnded', 'emitTaskWrapup'],
|
|
299
|
+
},
|
|
300
|
+
[TaskEvent.PAUSE_RECORDING]: {
|
|
301
|
+
actions: ['updateTaskData', 'setRecordingState', 'emitTaskRecordingPaused'],
|
|
302
|
+
},
|
|
303
|
+
[TaskEvent.RESUME_RECORDING]: {
|
|
304
|
+
actions: ['updateTaskData', 'setRecordingState', 'emitTaskRecordingResumed'],
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
|
|
309
|
+
[TaskState.HOLD_INITIATING]: {
|
|
310
|
+
on: {
|
|
311
|
+
// AgentContactHeld Event
|
|
312
|
+
[TaskEvent.HOLD_SUCCESS]: {
|
|
313
|
+
target: TaskState.HELD,
|
|
314
|
+
actions: ['updateTaskData', 'setHoldState', 'emitTaskHold'],
|
|
315
|
+
},
|
|
316
|
+
// AgentContactHoldFailed Event
|
|
317
|
+
[TaskEvent.HOLD_FAILED]: {
|
|
318
|
+
target: TaskState.CONNECTED,
|
|
319
|
+
actions: ['updateTaskData'],
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
|
|
324
|
+
[TaskState.HELD]: {
|
|
325
|
+
on: {
|
|
326
|
+
// Click of the unhold button
|
|
327
|
+
[TaskEvent.UNHOLD_INITIATED]: {
|
|
328
|
+
target: TaskState.RESUME_INITIATING,
|
|
329
|
+
},
|
|
330
|
+
// Remote resume from another login session (multi-login)
|
|
331
|
+
[TaskEvent.UNHOLD_SUCCESS]: {
|
|
332
|
+
target: TaskState.CONNECTED,
|
|
333
|
+
actions: ['updateTaskData', 'setHoldState', 'emitTaskResume'],
|
|
334
|
+
},
|
|
335
|
+
// Click of the consult button
|
|
336
|
+
[TaskEvent.CONSULT]: {
|
|
337
|
+
target: TaskState.CONSULT_INITIATING,
|
|
338
|
+
actions: ['setConsultInitiator', 'setConsultDestination'],
|
|
339
|
+
},
|
|
340
|
+
// TODO: This may not be a valid transition, need to be removed
|
|
341
|
+
// AgentConsultTransferred / AgentVTeamTransferred / AgentBlindTransferred
|
|
342
|
+
[TaskEvent.TRANSFER_SUCCESS]: [
|
|
343
|
+
{
|
|
344
|
+
guard: guards.shouldWrapUpOrIsInitiator,
|
|
345
|
+
target: TaskState.WRAPPING_UP,
|
|
346
|
+
actions: ['updateTaskData', 'markEnded', 'emitTaskWrapup'],
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
target: TaskState.CONNECTED,
|
|
350
|
+
actions: ['updateTaskData', 'clearConsultState'],
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
[TaskEvent.TRANSFER_FAILED]: {
|
|
354
|
+
actions: ['updateTaskData'],
|
|
355
|
+
},
|
|
356
|
+
[TaskEvent.CONTACT_ENDED]: [
|
|
357
|
+
{
|
|
358
|
+
guard: guards.conferenceInProgressFromEvent,
|
|
359
|
+
target: TaskState.CONFERENCING,
|
|
360
|
+
actions: ['updateTaskData', 'emitTaskConferenceStarted', 'requestCleanup'],
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
guard: guards.shouldWrapUp,
|
|
364
|
+
target: TaskState.WRAPPING_UP,
|
|
365
|
+
actions: ['updateTaskData', 'markEnded', 'emitTaskWrapup', 'requestCleanup'],
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
target: TaskState.TERMINATED,
|
|
369
|
+
actions: ['updateTaskData', 'markEnded', 'emitTaskEnd'],
|
|
370
|
+
},
|
|
371
|
+
],
|
|
372
|
+
// TODO: This may not be a valid transition, this needs to be checked as well
|
|
373
|
+
[TaskEvent.TASK_WRAPUP]: {
|
|
374
|
+
target: TaskState.WRAPPING_UP,
|
|
375
|
+
actions: ['updateTaskData', 'markEnded', 'emitTaskWrapup'],
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
[TaskState.RESUME_INITIATING]: {
|
|
381
|
+
on: {
|
|
382
|
+
[TaskEvent.UNHOLD_SUCCESS]: {
|
|
383
|
+
target: TaskState.CONNECTED,
|
|
384
|
+
actions: ['updateTaskData', 'setHoldState', 'emitTaskResume'],
|
|
385
|
+
},
|
|
386
|
+
[TaskEvent.UNHOLD_FAILED]: {
|
|
387
|
+
target: TaskState.HELD,
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
|
|
392
|
+
[TaskState.CONSULT_INITIATING]: {
|
|
393
|
+
on: {
|
|
394
|
+
[TaskEvent.HOLD_SUCCESS]: {
|
|
395
|
+
actions: ['updateTaskData'],
|
|
396
|
+
},
|
|
397
|
+
[TaskEvent.HOLD_FAILED]: {
|
|
398
|
+
target: TaskState.CONNECTED,
|
|
399
|
+
actions: ['updateTaskData', 'handleConsultFailed'],
|
|
400
|
+
},
|
|
401
|
+
// AgentConsulting
|
|
402
|
+
// NOTE: Don't set consultDestinationAgentJoined here - wait for CONSULTING_ACTIVE
|
|
403
|
+
[TaskEvent.CONSULT_SUCCESS]: {
|
|
404
|
+
target: TaskState.CONSULTING,
|
|
405
|
+
actions: ['updateTaskData', 'setConsultInitiator'],
|
|
406
|
+
},
|
|
407
|
+
// AgentConsultFailed, API Failures, AgentCtqFailed
|
|
408
|
+
[TaskEvent.CONSULT_FAILED]: [
|
|
409
|
+
{
|
|
410
|
+
// Consult from conference → back to CONFERENCING
|
|
411
|
+
guard: ({context}) => context.consultFromConference === true,
|
|
412
|
+
target: TaskState.CONFERENCING,
|
|
413
|
+
actions: ['updateTaskData', 'handleConsultFailed'],
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
guard: guards.isPrimaryMediaOnHold,
|
|
417
|
+
target: TaskState.HELD,
|
|
418
|
+
actions: ['updateTaskData', 'handleConsultFailed'],
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
target: TaskState.CONNECTED,
|
|
422
|
+
actions: ['updateTaskData', 'handleConsultFailed'],
|
|
423
|
+
},
|
|
424
|
+
],
|
|
425
|
+
// AgentCtqCancelled Event
|
|
426
|
+
[TaskEvent.CTQ_CANCEL]: [
|
|
427
|
+
{
|
|
428
|
+
guard: guards.isPrimaryMediaOnHold,
|
|
429
|
+
target: TaskState.HELD,
|
|
430
|
+
actions: ['updateTaskData', 'clearConsultState'],
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
target: TaskState.CONNECTED,
|
|
434
|
+
actions: ['updateTaskData', 'clearConsultState'],
|
|
435
|
+
},
|
|
436
|
+
],
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
|
|
440
|
+
[TaskState.CONSULTING]: {
|
|
441
|
+
on: {
|
|
442
|
+
// AgentConsulting updates consulted agent arrival
|
|
443
|
+
[TaskEvent.CONSULTING_ACTIVE]: {
|
|
444
|
+
actions: [
|
|
445
|
+
'updateTaskData',
|
|
446
|
+
'setConsultAgentJoined',
|
|
447
|
+
'setConsultDestination',
|
|
448
|
+
'emitTaskConsulting',
|
|
449
|
+
],
|
|
450
|
+
},
|
|
451
|
+
|
|
452
|
+
// AgentConsultEnded
|
|
453
|
+
[TaskEvent.CONSULT_END]: [
|
|
454
|
+
{
|
|
455
|
+
// Initiator returning to conference (flag set OR backend still shows conference)
|
|
456
|
+
guard: ({context, event}) =>
|
|
457
|
+
context.consultInitiator === true &&
|
|
458
|
+
(context.consultFromConference === true ||
|
|
459
|
+
guards.conferenceInProgressFromEvent({context, event})),
|
|
460
|
+
target: TaskState.CONFERENCING,
|
|
461
|
+
actions: ['updateTaskData', 'clearConsultState', 'emitTaskConsultEnd'],
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
// Initiator already switched back to the main/customer leg
|
|
465
|
+
guard: ({context}) =>
|
|
466
|
+
context.consultInitiator === true && context.consultCallHeld === true,
|
|
467
|
+
target: TaskState.CONNECTED,
|
|
468
|
+
actions: ['updateTaskData', 'clearConsultState', 'emitTaskConsultEnd'],
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
// Initiator (no conference) → HELD
|
|
472
|
+
guard: ({context}) => context.consultInitiator === true,
|
|
473
|
+
target: TaskState.HELD,
|
|
474
|
+
actions: ['updateTaskData', 'clearConsultState', 'emitTaskConsultEnd'],
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
// Consulted agent → TERMINATED
|
|
478
|
+
target: TaskState.TERMINATED,
|
|
479
|
+
actions: ['updateTaskData'],
|
|
480
|
+
},
|
|
481
|
+
],
|
|
482
|
+
|
|
483
|
+
// Switch between consult and main call (UI-driven toggle)
|
|
484
|
+
[TaskEvent.SWITCH_TO_MAIN_CALL]: {
|
|
485
|
+
actions: ['handleSwitchToMainCall', 'emitTaskSwitchCall'],
|
|
486
|
+
},
|
|
487
|
+
[TaskEvent.SWITCH_TO_CONSULT]: {
|
|
488
|
+
actions: ['handleSwitchToConsult', 'emitTaskSwitchCall'],
|
|
489
|
+
},
|
|
490
|
+
[TaskEvent.HOLD_SUCCESS]: {
|
|
491
|
+
actions: ['updateTaskData', 'setHoldState', 'emitTaskHold'],
|
|
492
|
+
},
|
|
493
|
+
[TaskEvent.UNHOLD_SUCCESS]: {
|
|
494
|
+
actions: ['updateTaskData', 'setHoldState', 'emitTaskResume'],
|
|
495
|
+
},
|
|
496
|
+
|
|
497
|
+
[TaskEvent.TRANSFER_SUCCESS]: [
|
|
498
|
+
{
|
|
499
|
+
guard: guards.shouldWrapUpOrIsInitiator,
|
|
500
|
+
target: TaskState.WRAPPING_UP,
|
|
501
|
+
actions: ['updateTaskData', 'markEnded', 'clearConsultState', 'emitTaskWrapup'],
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
target: TaskState.CONNECTED,
|
|
505
|
+
actions: ['updateTaskData', 'clearConsultState'],
|
|
506
|
+
},
|
|
507
|
+
],
|
|
508
|
+
[TaskEvent.TRANSFER_FAILED]: {
|
|
509
|
+
actions: ['updateTaskData'],
|
|
510
|
+
},
|
|
511
|
+
[TaskEvent.TRANSFER_CONFERENCE]: {
|
|
512
|
+
// Track that this agent initiated the conference transfer so we can
|
|
513
|
+
// apply the correct lifecycle transition when success arrives.
|
|
514
|
+
actions: ['setTransferConferenceRequested', 'emitTaskTransferConference'],
|
|
515
|
+
},
|
|
516
|
+
[TaskEvent.TRANSFER_CONFERENCE_SUCCESS]: [
|
|
517
|
+
{
|
|
518
|
+
guard: ({context}) => context.transferConferenceRequested !== true,
|
|
519
|
+
actions: [
|
|
520
|
+
'updateTaskData',
|
|
521
|
+
'handleTransferConferenceSuccess',
|
|
522
|
+
'clearTransferConferenceRequested',
|
|
523
|
+
],
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
guard: guards.shouldWrapUp,
|
|
527
|
+
target: TaskState.WRAPPING_UP,
|
|
528
|
+
actions: [
|
|
529
|
+
'updateTaskData',
|
|
530
|
+
'markEnded',
|
|
531
|
+
'clearConsultState',
|
|
532
|
+
'handleTransferConferenceSuccess',
|
|
533
|
+
'clearTransferConferenceRequested',
|
|
534
|
+
'emitTaskWrapup',
|
|
535
|
+
],
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
// Non-initiator (consulted agent) stays in CONFERENCING
|
|
539
|
+
guard: ({context}) => !context.consultInitiator,
|
|
540
|
+
target: TaskState.CONFERENCING,
|
|
541
|
+
actions: [
|
|
542
|
+
'updateTaskData',
|
|
543
|
+
'clearConsultState',
|
|
544
|
+
'handleTransferConferenceSuccess',
|
|
545
|
+
'clearTransferConferenceRequested',
|
|
546
|
+
],
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
target: TaskState.TERMINATED,
|
|
550
|
+
actions: [
|
|
551
|
+
'updateTaskData',
|
|
552
|
+
'markEnded',
|
|
553
|
+
'clearConsultState',
|
|
554
|
+
'handleTransferConferenceSuccess',
|
|
555
|
+
'clearTransferConferenceRequested',
|
|
556
|
+
'emitTaskEnd',
|
|
557
|
+
],
|
|
558
|
+
},
|
|
559
|
+
],
|
|
560
|
+
[TaskEvent.TRANSFER_CONFERENCE_FAILED]: {
|
|
561
|
+
actions: ['clearTransferConferenceRequested', 'emitTaskTransferConferenceFailed'],
|
|
562
|
+
},
|
|
563
|
+
|
|
564
|
+
// AgentContactAssigned - receiver side becomes connected to customer
|
|
565
|
+
[TaskEvent.ASSIGN]: {
|
|
566
|
+
target: TaskState.CONNECTED,
|
|
567
|
+
actions: ['updateTaskData', 'emitTaskAssigned'],
|
|
568
|
+
},
|
|
569
|
+
// AgentContactEnded
|
|
570
|
+
[TaskEvent.CONTACT_ENDED]: {
|
|
571
|
+
target: TaskState.WRAPPING_UP,
|
|
572
|
+
actions: [
|
|
573
|
+
'updateTaskData',
|
|
574
|
+
'markEnded',
|
|
575
|
+
'clearConsultState',
|
|
576
|
+
'emitTaskWrapup',
|
|
577
|
+
'requestCleanup',
|
|
578
|
+
],
|
|
579
|
+
},
|
|
580
|
+
[TaskEvent.TASK_WRAPUP]: {
|
|
581
|
+
target: TaskState.WRAPPING_UP,
|
|
582
|
+
actions: ['updateTaskData', 'markEnded', 'clearConsultState', 'emitTaskWrapup'],
|
|
583
|
+
},
|
|
584
|
+
[TaskEvent.MERGE_TO_CONFERENCE]: {
|
|
585
|
+
target: TaskState.CONF_INITIATING,
|
|
586
|
+
},
|
|
587
|
+
// AgentConsultConferenced, ParticipantJoinedConference
|
|
588
|
+
[TaskEvent.CONFERENCE_START]: {
|
|
589
|
+
target: TaskState.CONFERENCING,
|
|
590
|
+
actions: ['handleConferenceStarted', 'clearConsultState'],
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
|
|
595
|
+
[TaskState.CONF_INITIATING]: {
|
|
596
|
+
on: {
|
|
597
|
+
// AgentConsultConferenced, ParticipantJoinedConference
|
|
598
|
+
[TaskEvent.CONFERENCE_START]: {
|
|
599
|
+
target: TaskState.CONFERENCING,
|
|
600
|
+
actions: ['handleConferenceStarted'],
|
|
601
|
+
},
|
|
602
|
+
// AgentConsultConferenceFailed
|
|
603
|
+
[TaskEvent.CONFERENCE_FAILED]: {
|
|
604
|
+
target: TaskState.CONSULTING,
|
|
605
|
+
actions: ['handleConferenceFailed', 'emitTaskConferenceFailed'],
|
|
606
|
+
},
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
|
|
610
|
+
[TaskState.CONFERENCING]: {
|
|
611
|
+
on: {
|
|
612
|
+
[TaskEvent.CONFERENCE_START]: {
|
|
613
|
+
actions: ['updateTaskData', 'clearConsultState', 'emitTaskConferenceStarted'],
|
|
614
|
+
},
|
|
615
|
+
[TaskEvent.EXIT_CONFERENCE_SUCCESS]: [
|
|
616
|
+
{
|
|
617
|
+
guard: guards.shouldWrapUp,
|
|
618
|
+
target: TaskState.WRAPPING_UP,
|
|
619
|
+
actions: ['updateTaskData', 'markEnded', 'clearConsultState', 'emitTaskWrapup'],
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
target: TaskState.TERMINATED,
|
|
623
|
+
actions: ['updateTaskData', 'markEnded', 'clearConsultState', 'emitTaskEnd'],
|
|
624
|
+
},
|
|
625
|
+
],
|
|
626
|
+
|
|
627
|
+
// Needed as all agents in conference get this event, hence we need to clear the consult state
|
|
628
|
+
[TaskEvent.CONSULT_END]: {
|
|
629
|
+
actions: ['updateTaskData', 'clearConsultState'],
|
|
630
|
+
},
|
|
631
|
+
|
|
632
|
+
[TaskEvent.HOLD_SUCCESS]: {
|
|
633
|
+
actions: ['updateTaskData', 'setHoldState', 'emitTaskHold'],
|
|
634
|
+
},
|
|
635
|
+
[TaskEvent.UNHOLD_SUCCESS]: {
|
|
636
|
+
actions: ['updateTaskData', 'setHoldState', 'emitTaskResume'],
|
|
637
|
+
},
|
|
638
|
+
|
|
639
|
+
// Start a new consult from within an active conference
|
|
640
|
+
[TaskEvent.CONSULT]: {
|
|
641
|
+
target: TaskState.CONSULT_INITIATING,
|
|
642
|
+
actions: ['setConsultInitiator', 'setConsultDestination', 'setConsultFromConference'],
|
|
643
|
+
},
|
|
644
|
+
|
|
645
|
+
// Participant leaves - handle conference downgrade scenarios
|
|
646
|
+
[TaskEvent.PARTICIPANT_LEAVE]: [
|
|
647
|
+
{
|
|
648
|
+
// Only the leaving agent should wrap up → WRAPPING_UP
|
|
649
|
+
guard: (params) =>
|
|
650
|
+
guards.didCurrentAgentLeaveConference(params) && guards.shouldWrapUp(params),
|
|
651
|
+
target: TaskState.WRAPPING_UP,
|
|
652
|
+
actions: [
|
|
653
|
+
'updateTaskData',
|
|
654
|
+
'handleParticipantLeft',
|
|
655
|
+
'markEnded',
|
|
656
|
+
'clearConsultState',
|
|
657
|
+
'emitTaskParticipantLeft',
|
|
658
|
+
'emitTaskWrapup',
|
|
659
|
+
],
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
// Only the leaving agent (no wrapup) → TERMINATED
|
|
663
|
+
guard: guards.didCurrentAgentLeaveConference,
|
|
664
|
+
target: TaskState.TERMINATED,
|
|
665
|
+
actions: [
|
|
666
|
+
'updateTaskData',
|
|
667
|
+
'handleParticipantLeft',
|
|
668
|
+
'markEnded',
|
|
669
|
+
'clearConsultState',
|
|
670
|
+
'emitTaskParticipantLeft',
|
|
671
|
+
'emitTaskEnd',
|
|
672
|
+
],
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
// Conference downgraded, customer present → CONNECTED
|
|
676
|
+
guard: (params) =>
|
|
677
|
+
!guards.didCurrentAgentLeaveConference(params) &&
|
|
678
|
+
guards.shouldDowngradeConferenceToConnected(params),
|
|
679
|
+
target: TaskState.CONNECTED,
|
|
680
|
+
actions: [
|
|
681
|
+
'updateTaskData',
|
|
682
|
+
'handleParticipantLeft',
|
|
683
|
+
'clearConsultState',
|
|
684
|
+
'emitTaskParticipantLeft',
|
|
685
|
+
'emitTaskConferenceEnded',
|
|
686
|
+
],
|
|
687
|
+
},
|
|
688
|
+
{actions: ['updateTaskData', 'handleParticipantLeft', 'emitTaskParticipantLeft']},
|
|
689
|
+
],
|
|
690
|
+
|
|
691
|
+
[TaskEvent.TRANSFER_CONFERENCE]: {
|
|
692
|
+
actions: ['setTransferConferenceRequested', 'emitTaskTransferConference'],
|
|
693
|
+
},
|
|
694
|
+
[TaskEvent.TRANSFER_CONFERENCE_SUCCESS]: [
|
|
695
|
+
{
|
|
696
|
+
// Not initiated by this agent → just refresh backend state.
|
|
697
|
+
guard: ({context}) => context.transferConferenceRequested !== true,
|
|
698
|
+
actions: [
|
|
699
|
+
'updateTaskData',
|
|
700
|
+
'handleTransferConferenceSuccess',
|
|
701
|
+
'clearTransferConferenceRequested',
|
|
702
|
+
],
|
|
703
|
+
},
|
|
704
|
+
],
|
|
705
|
+
[TaskEvent.TRANSFER_CONFERENCE_FAILED]: {
|
|
706
|
+
actions: ['clearTransferConferenceRequested', 'emitTaskTransferConferenceFailed'],
|
|
707
|
+
},
|
|
708
|
+
|
|
709
|
+
// Conference ends explicitly
|
|
710
|
+
[TaskEvent.CONFERENCE_END]: [
|
|
711
|
+
{
|
|
712
|
+
// Agent who should wrap up → WRAPPING_UP
|
|
713
|
+
guard: guards.shouldWrapUp,
|
|
714
|
+
target: TaskState.WRAPPING_UP,
|
|
715
|
+
actions: ['updateTaskData', 'markEnded', 'clearConsultState', 'emitTaskWrapup'],
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
// Customer still in call → CONNECTED
|
|
719
|
+
guard: ({context, event}) => {
|
|
720
|
+
if (context.exitingConference === true) return false;
|
|
721
|
+
const taskData = (event as any)?.taskData ?? context.taskData;
|
|
722
|
+
if (!taskData?.interaction) return false;
|
|
723
|
+
const mainCallId = taskData.interaction.mainInteractionId || taskData.interactionId;
|
|
724
|
+
if (!mainCallId) return false;
|
|
725
|
+
|
|
726
|
+
return getIsCustomerInCall(taskData.interaction, mainCallId);
|
|
727
|
+
},
|
|
728
|
+
target: TaskState.CONNECTED,
|
|
729
|
+
actions: ['updateTaskData', 'clearConsultState', 'emitTaskConferenceEnded'],
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
// Otherwise → TERMINATED
|
|
733
|
+
target: TaskState.TERMINATED,
|
|
734
|
+
actions: ['updateTaskData', 'markEnded', 'clearConsultState', 'emitTaskEnd'],
|
|
735
|
+
},
|
|
736
|
+
],
|
|
737
|
+
|
|
738
|
+
// CONTACT_ENDED in conference
|
|
739
|
+
[TaskEvent.CONTACT_ENDED]: [
|
|
740
|
+
{
|
|
741
|
+
// Conference still active → stay
|
|
742
|
+
actions: ['updateTaskData', 'requestCleanup'],
|
|
743
|
+
},
|
|
744
|
+
],
|
|
745
|
+
},
|
|
746
|
+
},
|
|
747
|
+
|
|
748
|
+
[TaskState.WRAPPING_UP]: {
|
|
749
|
+
// Only emit wrapup event on entry - task:end should only be emitted when COMPLETED
|
|
750
|
+
entry: ['emitTaskWrapup'],
|
|
751
|
+
on: {
|
|
752
|
+
// AgentWrappedup Event
|
|
753
|
+
[TaskEvent.WRAPUP_COMPLETE]: {
|
|
754
|
+
target: TaskState.COMPLETED,
|
|
755
|
+
actions: ['updateTaskData'],
|
|
756
|
+
},
|
|
757
|
+
},
|
|
758
|
+
},
|
|
759
|
+
|
|
760
|
+
[TaskState.COMPLETED]: {
|
|
761
|
+
type: 'final' as const,
|
|
762
|
+
entry: ['emitTaskWrappedup', 'cleanupResources'],
|
|
763
|
+
},
|
|
764
|
+
|
|
765
|
+
[TaskState.TERMINATED]: {
|
|
766
|
+
type: 'final' as const,
|
|
767
|
+
entry: ['cleanupResources'],
|
|
768
|
+
},
|
|
769
|
+
},
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Create a task state machine instance using only the built-in actions.
|
|
775
|
+
* The resulting machine is ready for most consumers that rely on the default
|
|
776
|
+
* context mutators declared in actions.ts.
|
|
777
|
+
*
|
|
778
|
+
* @param uiControlConfig - UI control configuration
|
|
779
|
+
* @returns StateMachine instance for task management
|
|
780
|
+
*/
|
|
781
|
+
export function createTaskStateMachine(
|
|
782
|
+
uiControlConfig: UIControlConfig,
|
|
783
|
+
options?: {actions?: Partial<TaskActionsMap>}
|
|
784
|
+
) {
|
|
785
|
+
return taskStateMachineSetup.createMachine(getTaskStateMachineConfig(uiControlConfig)).provide({
|
|
786
|
+
actions: {
|
|
787
|
+
...actions,
|
|
788
|
+
...(options?.actions ?? {}),
|
|
789
|
+
},
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
export type TaskStateMachine = ReturnType<typeof createTaskStateMachine>;
|