@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
|
@@ -12,11 +12,15 @@ var _constants2 = require("./constants");
|
|
|
12
12
|
var _types2 = require("../config/types");
|
|
13
13
|
var _types3 = require("../../types");
|
|
14
14
|
var _loggerProxy = _interopRequireDefault(require("../../logger-proxy"));
|
|
15
|
-
var _ = _interopRequireDefault(require("."));
|
|
16
|
-
var _MetricsManager = _interopRequireDefault(require("../../metrics/MetricsManager"));
|
|
17
|
-
var _constants3 = require("../../metrics/constants");
|
|
18
15
|
var _TaskUtils = require("./TaskUtils");
|
|
16
|
+
var _TaskFactory = _interopRequireDefault(require("./TaskFactory"));
|
|
17
|
+
var _WebRTC = _interopRequireDefault(require("./voice/WebRTC"));
|
|
18
|
+
var _stateMachine = require("./state-machine");
|
|
19
|
+
var _taskDataNormalizer = require("./taskDataNormalizer");
|
|
19
20
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
21
|
+
const CC_EVENT_SET = new Set(Object.values(_types2.CC_EVENTS));
|
|
22
|
+
const isCcEvent = value => CC_EVENT_SET.has(value);
|
|
23
|
+
|
|
20
24
|
/** @internal */
|
|
21
25
|
class TaskManager extends _events.default {
|
|
22
26
|
/**
|
|
@@ -25,46 +29,30 @@ class TaskManager extends _events.default {
|
|
|
25
29
|
* @private
|
|
26
30
|
*/
|
|
27
31
|
|
|
32
|
+
// eslint-disable-next-line no-use-before-define
|
|
33
|
+
|
|
28
34
|
/**
|
|
29
35
|
* @param contact - Routing Contact layer. Talks to AQMReq layer to convert events to promises
|
|
30
36
|
* @param webCallingService - Webrtc Service Layer
|
|
31
37
|
* @param webSocketManager - Websocket Manager to maintain websocket connection and keepalives
|
|
32
38
|
*/
|
|
33
|
-
constructor(apiAIAssistant, contact, webCallingService, webSocketManager) {
|
|
39
|
+
constructor(apiAIAssistant, contact, webCallingService, webSocketManager, rtdWebSocketManager) {
|
|
34
40
|
super();
|
|
35
41
|
this.apiAIAssistant = apiAIAssistant;
|
|
36
42
|
this.contact = contact;
|
|
37
|
-
this.taskCollection = {};
|
|
38
43
|
this.webCallingService = webCallingService;
|
|
39
44
|
this.webSocketManager = webSocketManager;
|
|
40
|
-
this.
|
|
45
|
+
this.rtdWebSocketManager = rtdWebSocketManager;
|
|
46
|
+
this.taskCollection = {};
|
|
47
|
+
this.webRtcEnabled = false;
|
|
41
48
|
this.registerTaskListeners();
|
|
42
49
|
this.registerIncomingCallEvent();
|
|
43
50
|
}
|
|
44
|
-
setWrapupData(wrapupData) {
|
|
45
|
-
this.wrapupData = wrapupData;
|
|
46
|
-
}
|
|
47
|
-
setAgentId(agentId) {
|
|
48
|
-
this.agentId = agentId;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Gets the current agent ID
|
|
53
|
-
* @returns {string} The agent ID set for this task manager instance
|
|
54
|
-
* @public
|
|
55
|
-
*/
|
|
56
|
-
getAgentId() {
|
|
57
|
-
return this.agentId;
|
|
58
|
-
}
|
|
59
|
-
setWebRtcEnabled(webRtcEnabled) {
|
|
60
|
-
this.webRtcEnabled = webRtcEnabled;
|
|
61
|
-
}
|
|
62
51
|
handleRealtimeWebsocketEvent(event) {
|
|
63
52
|
try {
|
|
64
53
|
const payload = JSON.parse(event);
|
|
65
|
-
const eventType = payload?.type || payload?.data?.notifType;
|
|
66
54
|
const interactionId = payload?.data?.data?.conversationId;
|
|
67
|
-
if (!
|
|
55
|
+
if (!interactionId) return;
|
|
68
56
|
const task = this.taskCollection[interactionId];
|
|
69
57
|
if (!task) {
|
|
70
58
|
_loggerProxy.default.info(`Realtime transcription task not found`, {
|
|
@@ -74,7 +62,7 @@ class TaskManager extends _events.default {
|
|
|
74
62
|
});
|
|
75
63
|
return;
|
|
76
64
|
}
|
|
77
|
-
task.emit(
|
|
65
|
+
task.emit(payload.type, payload.data);
|
|
78
66
|
} catch (error) {
|
|
79
67
|
_loggerProxy.default.error('Failed to parse RTD WebSocket message', {
|
|
80
68
|
module: _constants.TASK_MANAGER_FILE,
|
|
@@ -83,8 +71,41 @@ class TaskManager extends _events.default {
|
|
|
83
71
|
});
|
|
84
72
|
}
|
|
85
73
|
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Set config flags for task creation
|
|
77
|
+
*/
|
|
78
|
+
setConfigFlags(configFlags) {
|
|
79
|
+
this.configFlags = configFlags;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Set wrapup configuration data
|
|
84
|
+
*/
|
|
85
|
+
setWrapupData(wrapupData) {
|
|
86
|
+
this.wrapupData = wrapupData;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Set agent ID for task operations
|
|
91
|
+
*/
|
|
92
|
+
setAgentId(agentId) {
|
|
93
|
+
this.agentId = agentId;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Gets the current agent ID
|
|
98
|
+
* @returns {string} The agent ID set for this task manager instance
|
|
99
|
+
* @public
|
|
100
|
+
*/
|
|
101
|
+
getAgentId() {
|
|
102
|
+
return this.agentId;
|
|
103
|
+
}
|
|
104
|
+
setWebRtcEnabled(webRtcEnabled) {
|
|
105
|
+
this.webRtcEnabled = webRtcEnabled;
|
|
106
|
+
}
|
|
86
107
|
handleIncomingWebCall = call => {
|
|
87
|
-
const currentTask = Object.values(this.taskCollection).find(
|
|
108
|
+
const currentTask = Object.values(this.taskCollection).find(t => t.data.interaction.mediaType === _types.MEDIA_CHANNEL.TELEPHONY);
|
|
88
109
|
if (currentTask) {
|
|
89
110
|
this.webCallingService.mapCallToTask(call.getCallId(), currentTask.data.interactionId);
|
|
90
111
|
_loggerProxy.default.log(`Call mapped to task`, {
|
|
@@ -92,7 +113,12 @@ class TaskManager extends _events.default {
|
|
|
92
113
|
method: _constants2.METHODS.HANDLE_INCOMING_WEB_CALL,
|
|
93
114
|
interactionId: currentTask.data.interactionId
|
|
94
115
|
});
|
|
95
|
-
|
|
116
|
+
|
|
117
|
+
// Send TASK_INCOMING to state machine - it will emit on the task object
|
|
118
|
+
const eventPayload = TaskManager.mapEventToTaskStateMachineEvent(_types2.CC_EVENTS.AGENT_CONTACT_RESERVED, currentTask.data);
|
|
119
|
+
if (eventPayload && currentTask) {
|
|
120
|
+
currentTask.sendStateMachineEvent(eventPayload);
|
|
121
|
+
}
|
|
96
122
|
}
|
|
97
123
|
this.call = call;
|
|
98
124
|
};
|
|
@@ -102,468 +128,478 @@ class TaskManager extends _events.default {
|
|
|
102
128
|
unregisterIncomingCallEvent() {
|
|
103
129
|
this.webCallingService.off(_calling.LINE_EVENTS.INCOMING_CALL, this.handleIncomingWebCall);
|
|
104
130
|
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Map WebSocket CC_EVENTS to state machine TaskEvent
|
|
134
|
+
* @param ccEvent - The CC_EVENT type from WebSocket
|
|
135
|
+
* @param payload - The event payload
|
|
136
|
+
* @param agentId - Optional agent ID for state detection (needed for HYDRATE)
|
|
137
|
+
* @returns TaskEventPayload for state machine or null if no mapping
|
|
138
|
+
*/
|
|
139
|
+
static mapEventToTaskStateMachineEvent(ccEvent, payload, agentId) {
|
|
140
|
+
const mediaResourceId = payload.mediaResourceId || payload.interaction?.media?.[payload.interactionId]?.mediaResourceId;
|
|
141
|
+
switch (ccEvent) {
|
|
142
|
+
// CC -> TaskEvent mappings (see TaskStateMachine comment for quick reference)
|
|
143
|
+
case _types2.CC_EVENTS.AGENT_CONTACT_RESERVED:
|
|
144
|
+
// AgentContactReserved -> TASK_INCOMING
|
|
145
|
+
return {
|
|
146
|
+
type: _stateMachine.TaskEvent.TASK_INCOMING,
|
|
147
|
+
taskData: payload
|
|
148
|
+
};
|
|
149
|
+
case _types2.CC_EVENTS.AGENT_OFFER_CONTACT:
|
|
150
|
+
// AgentOfferContact -> TASK_OFFERED
|
|
151
|
+
return {
|
|
152
|
+
type: _stateMachine.TaskEvent.TASK_OFFERED,
|
|
153
|
+
taskData: payload
|
|
154
|
+
};
|
|
155
|
+
case _types2.CC_EVENTS.AGENT_CONTACT:
|
|
156
|
+
// AgentContact -> HYDRATE
|
|
157
|
+
// Include agentId for state detection (e.g., checking isWrapUp in participant data)
|
|
158
|
+
return {
|
|
159
|
+
type: _stateMachine.TaskEvent.HYDRATE,
|
|
160
|
+
taskData: payload,
|
|
161
|
+
agentId
|
|
162
|
+
};
|
|
163
|
+
case _types2.CC_EVENTS.CONTACT_UPDATED:
|
|
164
|
+
return {
|
|
165
|
+
type: _stateMachine.TaskEvent.CONTACT_UPDATED,
|
|
166
|
+
taskData: payload
|
|
167
|
+
};
|
|
168
|
+
case _types2.CC_EVENTS.CONTACT_OWNER_CHANGED:
|
|
169
|
+
return {
|
|
170
|
+
type: _stateMachine.TaskEvent.CONTACT_OWNER_CHANGED,
|
|
171
|
+
taskData: payload
|
|
172
|
+
};
|
|
173
|
+
case _types2.CC_EVENTS.AGENT_OFFER_CONSULT:
|
|
174
|
+
// AgentOfferConsult -> OFFER_CONSULT
|
|
175
|
+
return {
|
|
176
|
+
type: _stateMachine.TaskEvent.OFFER_CONSULT,
|
|
177
|
+
taskData: {
|
|
178
|
+
...payload,
|
|
179
|
+
isConsulted: true
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
case _types2.CC_EVENTS.AGENT_CONTACT_ASSIGNED:
|
|
183
|
+
// AgentContactAssigned -> ASSIGN
|
|
184
|
+
return {
|
|
185
|
+
type: _stateMachine.TaskEvent.ASSIGN,
|
|
186
|
+
taskData: payload
|
|
187
|
+
};
|
|
188
|
+
case _types2.CC_EVENTS.AGENT_CONTACT_HELD:
|
|
189
|
+
return {
|
|
190
|
+
type: _stateMachine.TaskEvent.HOLD_SUCCESS,
|
|
191
|
+
mediaResourceId: mediaResourceId || '',
|
|
192
|
+
taskData: payload
|
|
193
|
+
};
|
|
194
|
+
case _types2.CC_EVENTS.AGENT_CONTACT_UNHELD:
|
|
195
|
+
return {
|
|
196
|
+
type: _stateMachine.TaskEvent.UNHOLD_SUCCESS,
|
|
197
|
+
mediaResourceId: mediaResourceId || '',
|
|
198
|
+
taskData: payload
|
|
199
|
+
};
|
|
200
|
+
case _types2.CC_EVENTS.AGENT_CONSULT_CREATED:
|
|
201
|
+
return {
|
|
202
|
+
type: _stateMachine.TaskEvent.CONSULT_CREATED,
|
|
203
|
+
taskData: {
|
|
204
|
+
...payload,
|
|
205
|
+
isConsulted: false
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
case _types2.CC_EVENTS.AGENT_CONSULTING:
|
|
209
|
+
// AgentConsulting -> CONSULTING_ACTIVE
|
|
210
|
+
// use context to figure out if it's the initiator or receiver using consultInitiator from context
|
|
211
|
+
return {
|
|
212
|
+
type: _stateMachine.TaskEvent.CONSULTING_ACTIVE,
|
|
213
|
+
consultDestinationAgentJoined: true,
|
|
214
|
+
taskData: payload
|
|
215
|
+
};
|
|
216
|
+
case _types2.CC_EVENTS.AGENT_CONSULT_ENDED:
|
|
217
|
+
// AgentConsultEnded -> CONSULT_END
|
|
218
|
+
return {
|
|
219
|
+
type: _stateMachine.TaskEvent.CONSULT_END,
|
|
220
|
+
taskData: payload
|
|
221
|
+
};
|
|
222
|
+
case _types2.CC_EVENTS.AGENT_CONSULT_FAILED:
|
|
223
|
+
case _types2.CC_EVENTS.AGENT_CTQ_FAILED:
|
|
224
|
+
return {
|
|
225
|
+
type: _stateMachine.TaskEvent.CONSULT_FAILED,
|
|
226
|
+
reason: payload.reason,
|
|
227
|
+
taskData: payload
|
|
228
|
+
};
|
|
229
|
+
case _types2.CC_EVENTS.AGENT_CTQ_CANCELLED:
|
|
230
|
+
return {
|
|
231
|
+
type: _stateMachine.TaskEvent.CTQ_CANCEL,
|
|
232
|
+
taskData: payload
|
|
233
|
+
};
|
|
234
|
+
case _types2.CC_EVENTS.AGENT_CTQ_CANCEL_FAILED:
|
|
235
|
+
return {
|
|
236
|
+
type: _stateMachine.TaskEvent.CTQ_CANCEL_FAILED,
|
|
237
|
+
taskData: payload
|
|
238
|
+
};
|
|
239
|
+
case _types2.CC_EVENTS.AGENT_BLIND_TRANSFERRED: // AgentBlindTransferred -> TRANSFER_SUCCESS
|
|
240
|
+
case _types2.CC_EVENTS.AGENT_CONSULT_TRANSFERRED: // AgentConsultTransferred -> TRANSFER_SUCCESS
|
|
241
|
+
case _types2.CC_EVENTS.AGENT_VTEAM_TRANSFERRED:
|
|
242
|
+
// AgentVTeamTransferred -> TRANSFER_SUCCESS
|
|
243
|
+
return {
|
|
244
|
+
type: _stateMachine.TaskEvent.TRANSFER_SUCCESS,
|
|
245
|
+
taskData: payload
|
|
246
|
+
};
|
|
247
|
+
case _types2.CC_EVENTS.AGENT_WRAPUP:
|
|
248
|
+
return {
|
|
249
|
+
type: _stateMachine.TaskEvent.TASK_WRAPUP,
|
|
250
|
+
taskData: {
|
|
251
|
+
...payload,
|
|
252
|
+
wrapUpRequired: true
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
case _types2.CC_EVENTS.AGENT_CONTACT_UNASSIGNED:
|
|
256
|
+
return null;
|
|
257
|
+
// Add WRAPUP if needed
|
|
258
|
+
|
|
259
|
+
case _types2.CC_EVENTS.AGENT_BLIND_TRANSFER_FAILED:
|
|
260
|
+
case _types2.CC_EVENTS.AGENT_VTEAM_TRANSFER_FAILED:
|
|
261
|
+
case _types2.CC_EVENTS.AGENT_CONSULT_TRANSFER_FAILED:
|
|
262
|
+
case _types2.CC_EVENTS.AGENT_CONFERENCE_TRANSFER_FAILED:
|
|
263
|
+
return {
|
|
264
|
+
type: _stateMachine.TaskEvent.TRANSFER_FAILED,
|
|
265
|
+
taskData: payload
|
|
266
|
+
};
|
|
267
|
+
case _types2.CC_EVENTS.CONTACT_ENDED:
|
|
268
|
+
return {
|
|
269
|
+
type: _stateMachine.TaskEvent.CONTACT_ENDED,
|
|
270
|
+
taskData: {
|
|
271
|
+
...payload,
|
|
272
|
+
wrapUpRequired: payload.interaction?.state !== 'new'
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
case _types2.CC_EVENTS.AGENT_INVITE_FAILED:
|
|
276
|
+
return {
|
|
277
|
+
type: _stateMachine.TaskEvent.INVITE_FAILED,
|
|
278
|
+
reason: payload.reason
|
|
279
|
+
};
|
|
280
|
+
case _types2.CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED:
|
|
281
|
+
return {
|
|
282
|
+
type: _stateMachine.TaskEvent.ASSIGN_FAILED,
|
|
283
|
+
reason: payload.reason
|
|
284
|
+
};
|
|
285
|
+
case _types2.CC_EVENTS.AGENT_CONTACT_OFFER_RONA:
|
|
286
|
+
return {
|
|
287
|
+
type: _stateMachine.TaskEvent.RONA,
|
|
288
|
+
taskData: payload,
|
|
289
|
+
reason: payload.reason
|
|
290
|
+
};
|
|
291
|
+
case _types2.CC_EVENTS.AGENT_OUTBOUND_FAILED:
|
|
292
|
+
return {
|
|
293
|
+
type: _stateMachine.TaskEvent.OUTBOUND_FAILED,
|
|
294
|
+
reason: payload.reason
|
|
295
|
+
};
|
|
296
|
+
case _types2.CC_EVENTS.CONTACT_RECORDING_STARTED:
|
|
297
|
+
return {
|
|
298
|
+
type: _stateMachine.TaskEvent.RECORDING_STARTED,
|
|
299
|
+
taskData: payload
|
|
300
|
+
};
|
|
301
|
+
case _types2.CC_EVENTS.CONTACT_RECORDING_PAUSED:
|
|
302
|
+
return {
|
|
303
|
+
type: _stateMachine.TaskEvent.PAUSE_RECORDING,
|
|
304
|
+
taskData: payload
|
|
305
|
+
};
|
|
306
|
+
case _types2.CC_EVENTS.CONTACT_RECORDING_RESUMED:
|
|
307
|
+
return {
|
|
308
|
+
type: _stateMachine.TaskEvent.RESUME_RECORDING,
|
|
309
|
+
taskData: payload
|
|
310
|
+
};
|
|
311
|
+
case _types2.CC_EVENTS.AGENT_WRAPPEDUP:
|
|
312
|
+
return {
|
|
313
|
+
type: _stateMachine.TaskEvent.WRAPUP_COMPLETE,
|
|
314
|
+
taskData: payload
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// Conference events - these trigger state machine transition to CONFERENCING
|
|
318
|
+
case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCED:
|
|
319
|
+
case _types2.CC_EVENTS.PARTICIPANT_JOINED_CONFERENCE:
|
|
320
|
+
return {
|
|
321
|
+
type: _stateMachine.TaskEvent.CONFERENCE_START,
|
|
322
|
+
taskData: payload
|
|
323
|
+
};
|
|
324
|
+
case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCE_FAILED:
|
|
325
|
+
return {
|
|
326
|
+
type: _stateMachine.TaskEvent.CONFERENCE_FAILED,
|
|
327
|
+
reason: payload.reason,
|
|
328
|
+
taskData: payload
|
|
329
|
+
};
|
|
330
|
+
case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCE_ENDED:
|
|
331
|
+
return {
|
|
332
|
+
type: _stateMachine.TaskEvent.CONFERENCE_END,
|
|
333
|
+
taskData: payload
|
|
334
|
+
};
|
|
335
|
+
case _types2.CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE:
|
|
336
|
+
return {
|
|
337
|
+
type: _stateMachine.TaskEvent.PARTICIPANT_LEAVE,
|
|
338
|
+
taskData: payload,
|
|
339
|
+
participantId: payload?.participantId
|
|
340
|
+
};
|
|
341
|
+
case _types2.CC_EVENTS.AGENT_CONFERENCE_TRANSFERRED:
|
|
342
|
+
return {
|
|
343
|
+
type: _stateMachine.TaskEvent.TRANSFER_CONFERENCE_SUCCESS,
|
|
344
|
+
taskData: payload
|
|
345
|
+
};
|
|
346
|
+
default:
|
|
347
|
+
// Not all events need state machine mapping
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Register WebSocket message listeners for task events
|
|
354
|
+
*
|
|
355
|
+
* Main entry point that orchestrates event processing through a clear pipeline:
|
|
356
|
+
* 1. Parse and validate incoming WebSocket messages
|
|
357
|
+
* 2. Prepare event context with task and state machine mappings
|
|
358
|
+
* 3. Handle task lifecycle (creation, updates, collection management)
|
|
359
|
+
* 4. Send events to state machine (task-level transitions/emissions)
|
|
360
|
+
* 5. Cleanup is triggered via task events emitted by the state machine
|
|
361
|
+
*
|
|
362
|
+
* This architecture separates concerns:
|
|
363
|
+
* - TaskManager: Manages task collection lifecycle and operational concerns
|
|
364
|
+
* - State Machine: Manages individual task state and event emissions
|
|
365
|
+
*/
|
|
105
366
|
registerTaskListeners() {
|
|
106
367
|
this.webSocketManager.on('message', event => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
interactionId: payload.data.interactionId
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// Check if auto-answer should happen for this task
|
|
138
|
-
const shouldAutoAnswer = (0, _TaskUtils.shouldAutoAnswerTask)(payload.data, this.agentId, this.webCallingService.loginOption, this.webRtcEnabled);
|
|
139
|
-
task = new _.default(this.contact, this.webCallingService, {
|
|
140
|
-
...payload.data,
|
|
141
|
-
wrapUpRequired: payload.data.interaction?.participants?.[this.agentId]?.isWrapUp || false,
|
|
142
|
-
isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload.data),
|
|
143
|
-
isAutoAnswering: shouldAutoAnswer // Set flag before emitting
|
|
144
|
-
}, this.wrapupData, this.agentId);
|
|
145
|
-
this.taskCollection[payload.data.interactionId] = task;
|
|
146
|
-
// Condition 1: The state is=new i.e it is a incoming task
|
|
147
|
-
if (payload.data.interaction.state === 'new') {
|
|
148
|
-
_loggerProxy.default.log(`Got AGENT_CONTACT for a task with state=new, sending TASK_INCOMING event`, {
|
|
149
|
-
module: _constants.TASK_MANAGER_FILE,
|
|
150
|
-
method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
|
|
151
|
-
interactionId: payload.data.interactionId
|
|
152
|
-
});
|
|
153
|
-
this.emit(_types.TASK_EVENTS.TASK_INCOMING, task);
|
|
154
|
-
} else {
|
|
155
|
-
// Condition 2: The state is anything else i.e the task was connected
|
|
156
|
-
_loggerProxy.default.log(`Got AGENT_CONTACT for a task with state=${payload.data.interaction.state}, sending TASK_HYDRATE event`, {
|
|
157
|
-
module: _constants.TASK_MANAGER_FILE,
|
|
158
|
-
method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
|
|
159
|
-
interactionId: payload.data.interactionId
|
|
160
|
-
});
|
|
161
|
-
this.emit(_types.TASK_EVENTS.TASK_HYDRATE, task);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
break;
|
|
165
|
-
case _types2.CC_EVENTS.AGENT_CONTACT_RESERVED:
|
|
166
|
-
{
|
|
167
|
-
// Check if auto-answer should happen for this task
|
|
168
|
-
const shouldAutoAnswerReserved = (0, _TaskUtils.shouldAutoAnswerTask)(payload.data, this.agentId, this.webCallingService.loginOption, this.webRtcEnabled);
|
|
169
|
-
task = new _.default(this.contact, this.webCallingService, {
|
|
170
|
-
...payload.data,
|
|
171
|
-
isConsulted: false,
|
|
172
|
-
isAutoAnswering: shouldAutoAnswerReserved // Set flag before emitting
|
|
173
|
-
}, this.wrapupData, this.agentId);
|
|
174
|
-
this.taskCollection[payload.data.interactionId] = task;
|
|
175
|
-
if (this.webCallingService.loginOption !== _types3.LoginOption.BROWSER || task.data.interaction.mediaType !== _types.MEDIA_CHANNEL.TELEPHONY // for digital channels
|
|
176
|
-
) {
|
|
177
|
-
this.emit(_types.TASK_EVENTS.TASK_INCOMING, task);
|
|
178
|
-
} else if (this.call) {
|
|
179
|
-
this.emit(_types.TASK_EVENTS.TASK_INCOMING, task);
|
|
180
|
-
}
|
|
181
|
-
break;
|
|
182
|
-
}
|
|
183
|
-
case _types2.CC_EVENTS.AGENT_OFFER_CONTACT:
|
|
184
|
-
// We don't have to emit any event here since this will be result of promise.
|
|
185
|
-
task = this.updateTaskData(task, payload.data);
|
|
186
|
-
_loggerProxy.default.log(`Agent offer contact received for task`, {
|
|
187
|
-
module: _constants.TASK_MANAGER_FILE,
|
|
188
|
-
method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
|
|
189
|
-
interactionId: payload.data?.interactionId
|
|
190
|
-
});
|
|
191
|
-
this.emit(_types.TASK_EVENTS.TASK_OFFER_CONTACT, task);
|
|
192
|
-
|
|
193
|
-
// Handle auto-answer for offer contact
|
|
194
|
-
this.handleAutoAnswer(task);
|
|
195
|
-
break;
|
|
196
|
-
case _types2.CC_EVENTS.AGENT_OUTBOUND_FAILED:
|
|
197
|
-
if (task) {
|
|
198
|
-
task = this.updateTaskData(task, payload.data);
|
|
199
|
-
this.metricsManager.trackEvent(_constants3.METRIC_EVENT_NAMES.TASK_OUTDIAL_FAILED, {
|
|
200
|
-
..._MetricsManager.default.getCommonTrackingFieldForAQMResponse(payload.data),
|
|
201
|
-
taskId: payload.data.interactionId,
|
|
202
|
-
reason: payload.data.reasonCode || payload.data.reason
|
|
203
|
-
}, ['behavioral', 'operational']);
|
|
204
|
-
_loggerProxy.default.log(`Agent outbound failed for task`, {
|
|
205
|
-
module: _constants.TASK_MANAGER_FILE,
|
|
206
|
-
method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
|
|
207
|
-
interactionId: payload.data.interactionId
|
|
208
|
-
});
|
|
209
|
-
task.emit(_types.TASK_EVENTS.TASK_OUTDIAL_FAILED, payload.data.reason ?? 'UNKNOWN_REASON');
|
|
210
|
-
}
|
|
211
|
-
break;
|
|
212
|
-
case _types2.CC_EVENTS.AGENT_CONTACT_ASSIGNED:
|
|
213
|
-
// When a campaign preview contact is accepted, the assigned event may arrive
|
|
214
|
-
// with a new interactionId while the task is stored under the original
|
|
215
|
-
// reservationInteractionId. Fall back to that key so the task is found.
|
|
216
|
-
if (!task && payload.data.reservationInteractionId) {
|
|
217
|
-
task = this.taskCollection[payload.data.reservationInteractionId];
|
|
218
|
-
if (task) {
|
|
219
|
-
// Re-key the task under the new interaction ID and remove the old entry
|
|
220
|
-
delete this.taskCollection[payload.data.reservationInteractionId];
|
|
221
|
-
this.taskCollection[payload.data.interactionId] = task;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
if (task) {
|
|
225
|
-
task = this.updateTaskData(task, payload.data);
|
|
226
|
-
task.emit(_types.TASK_EVENTS.TASK_ASSIGNED, task);
|
|
227
|
-
}
|
|
228
|
-
break;
|
|
229
|
-
case _types2.CC_EVENTS.AGENT_CONTACT_UNASSIGNED:
|
|
230
|
-
task = this.updateTaskData(task, {
|
|
231
|
-
...payload.data,
|
|
232
|
-
wrapUpRequired: true
|
|
233
|
-
});
|
|
234
|
-
task.emit(_types.TASK_EVENTS.TASK_END, task);
|
|
235
|
-
break;
|
|
236
|
-
case _types2.CC_EVENTS.AGENT_CONTACT_OFFER_RONA:
|
|
237
|
-
case _types2.CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED:
|
|
238
|
-
case _types2.CC_EVENTS.AGENT_INVITE_FAILED:
|
|
239
|
-
{
|
|
240
|
-
_loggerProxy.default.warn(`[DEBUG-CAMPAIGN-CLEAR] Task removal triggered by ${payload.data.type}, interactionId=${payload.data.interactionId}, taskType=${task?.data?.type}`, {
|
|
241
|
-
module: _constants.TASK_MANAGER_FILE,
|
|
242
|
-
method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
|
|
243
|
-
interactionId: payload.data.interactionId
|
|
244
|
-
});
|
|
245
|
-
task = this.updateTaskData(task, payload.data);
|
|
246
|
-
const eventTypeToMetricMap = {
|
|
247
|
-
[_types2.CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED]: 'AGENT_CONTACT_ASSIGN_FAILED',
|
|
248
|
-
[_types2.CC_EVENTS.AGENT_INVITE_FAILED]: 'AGENT_INVITE_FAILED'
|
|
249
|
-
};
|
|
250
|
-
const metricEventName = eventTypeToMetricMap[payload.data.type] || 'AGENT_RONA';
|
|
251
|
-
this.metricsManager.trackEvent(_constants3.METRIC_EVENT_NAMES[metricEventName], {
|
|
252
|
-
..._MetricsManager.default.getCommonTrackingFieldForAQMResponse(payload.data),
|
|
253
|
-
taskId: payload.data.interactionId,
|
|
254
|
-
reason: payload.data.reason
|
|
255
|
-
}, ['behavioral', 'operational']);
|
|
256
|
-
this.handleTaskCleanup(task);
|
|
257
|
-
task.emit(_types.TASK_EVENTS.TASK_REJECT, payload.data.reason);
|
|
258
|
-
break;
|
|
259
|
-
}
|
|
260
|
-
case _types2.CC_EVENTS.CONTACT_ENDED:
|
|
261
|
-
// Update task data
|
|
262
|
-
if (task) {
|
|
263
|
-
_loggerProxy.default.warn(`[DEBUG-CAMPAIGN-CLEAR] CONTACT_ENDED, interactionId=${payload.data.interactionId}, taskType=${task?.data?.type}, state=${task?.data?.interaction?.state}`, {
|
|
264
|
-
module: _constants.TASK_MANAGER_FILE,
|
|
265
|
-
method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
|
|
266
|
-
interactionId: payload.data.interactionId
|
|
267
|
-
});
|
|
268
|
-
task = this.updateTaskData(task, {
|
|
269
|
-
...payload.data,
|
|
270
|
-
wrapUpRequired: payload.data.agentsPendingWrapUp?.includes(this.agentId) || false
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
// Handle cleanup based on whether task should be deleted
|
|
274
|
-
this.handleTaskCleanup(task);
|
|
275
|
-
task?.emit(_types.TASK_EVENTS.TASK_END, task);
|
|
276
|
-
}
|
|
277
|
-
break;
|
|
278
|
-
case _types2.CC_EVENTS.CAMPAIGN_CONTACT_UPDATED:
|
|
279
|
-
// CampaignContactUpdated is a non-terminal event (intermediate update during accept).
|
|
280
|
-
// Only update the task data — do NOT remove the task or emit TASK_END.
|
|
281
|
-
// Task cleanup is handled by CONTACT_ENDED or other terminal events.
|
|
282
|
-
if (task) {
|
|
283
|
-
task = this.updateTaskData(task, payload.data);
|
|
284
|
-
}
|
|
285
|
-
break;
|
|
286
|
-
case _types2.CC_EVENTS.CONTACT_MERGED:
|
|
287
|
-
task = this.handleContactMerged(task, payload.data);
|
|
288
|
-
break;
|
|
289
|
-
case _types2.CC_EVENTS.AGENT_CONTACT_HELD:
|
|
290
|
-
// As soon as the main interaction is held, we need to emit TASK_HOLD
|
|
291
|
-
task = this.updateTaskData(task, payload.data);
|
|
292
|
-
task.emit(_types.TASK_EVENTS.TASK_HOLD, task);
|
|
293
|
-
break;
|
|
294
|
-
case _types2.CC_EVENTS.AGENT_CONTACT_UNHELD:
|
|
295
|
-
// As soon as the main interaction is unheld, we need to emit TASK_RESUME
|
|
296
|
-
task = this.updateTaskData(task, payload.data);
|
|
297
|
-
task.emit(_types.TASK_EVENTS.TASK_RESUME, task);
|
|
298
|
-
break;
|
|
299
|
-
case _types2.CC_EVENTS.AGENT_VTEAM_TRANSFERRED:
|
|
300
|
-
task = this.updateTaskData(task, {
|
|
301
|
-
...payload.data,
|
|
302
|
-
wrapUpRequired: true
|
|
303
|
-
});
|
|
304
|
-
task.emit(_types.TASK_EVENTS.TASK_END, task);
|
|
305
|
-
break;
|
|
306
|
-
case _types2.CC_EVENTS.AGENT_CTQ_CANCEL_FAILED:
|
|
307
|
-
task = this.updateTaskData(task, payload.data);
|
|
308
|
-
task.emit(_types.TASK_EVENTS.TASK_CONSULT_QUEUE_FAILED, task);
|
|
309
|
-
break;
|
|
310
|
-
case _types2.CC_EVENTS.AGENT_CONSULT_CREATED:
|
|
311
|
-
// Received when self agent initiates a consult
|
|
312
|
-
task = this.updateTaskData(task, {
|
|
313
|
-
...payload.data,
|
|
314
|
-
isConsulted: false // This ensures that the task consult status is always reset
|
|
315
|
-
});
|
|
316
|
-
task.emit(_types.TASK_EVENTS.TASK_CONSULT_CREATED, task);
|
|
317
|
-
break;
|
|
318
|
-
case _types2.CC_EVENTS.AGENT_OFFER_CONSULT:
|
|
319
|
-
// Received when other agent sends us a consult offer
|
|
320
|
-
task = this.updateTaskData(task, {
|
|
321
|
-
...payload.data,
|
|
322
|
-
isConsulted: true // This ensures that the task is marked as us being requested for a consult
|
|
323
|
-
});
|
|
324
|
-
task.emit(_types.TASK_EVENTS.TASK_OFFER_CONSULT, task);
|
|
325
|
-
|
|
326
|
-
// Handle auto-answer for consult offer
|
|
327
|
-
this.handleAutoAnswer(task);
|
|
328
|
-
break;
|
|
329
|
-
case _types2.CC_EVENTS.AGENT_CONSULTING:
|
|
330
|
-
// Received when agent is in an active consult state
|
|
331
|
-
// TODO: Check if we can use backend consult state instead of isConsulted
|
|
332
|
-
task = this.updateTaskData(task, payload.data);
|
|
333
|
-
if (task.data.isConsulted) {
|
|
334
|
-
// Fire only if you are the agent who received the consult request
|
|
335
|
-
task.emit(_types.TASK_EVENTS.TASK_CONSULT_ACCEPTED, task);
|
|
336
|
-
} else {
|
|
337
|
-
// Fire only if you are the agent who initiated the consult
|
|
338
|
-
task.emit(_types.TASK_EVENTS.TASK_CONSULTING, task);
|
|
339
|
-
}
|
|
340
|
-
break;
|
|
341
|
-
case _types2.CC_EVENTS.AGENT_CONSULT_FAILED:
|
|
342
|
-
// This can only be received by the agent who initiated the consult.
|
|
343
|
-
// We need not emit any event here since this will be result of promise
|
|
344
|
-
task = this.updateTaskData(task, payload.data);
|
|
345
|
-
break;
|
|
346
|
-
case _types2.CC_EVENTS.AGENT_CONSULT_ENDED:
|
|
347
|
-
task = this.updateTaskData(task, payload.data);
|
|
348
|
-
if (task.data.isConsulted) {
|
|
349
|
-
// This will be the end state of the task as soon as we end the consult in case of
|
|
350
|
-
// us being offered a consult
|
|
351
|
-
this.removeTaskFromCollection(task);
|
|
352
|
-
}
|
|
353
|
-
task.emit(_types.TASK_EVENTS.TASK_CONSULT_END, task);
|
|
354
|
-
break;
|
|
355
|
-
case _types2.CC_EVENTS.AGENT_CTQ_CANCELLED:
|
|
356
|
-
// This event is received when the consult using queue is cancelled using API
|
|
357
|
-
task = this.updateTaskData(task, payload.data);
|
|
358
|
-
task.emit(_types.TASK_EVENTS.TASK_CONSULT_QUEUE_CANCELLED, task);
|
|
359
|
-
break;
|
|
360
|
-
case _types2.CC_EVENTS.AGENT_WRAPUP:
|
|
361
|
-
task = this.updateTaskData(task, {
|
|
362
|
-
...payload.data,
|
|
363
|
-
wrapUpRequired: true
|
|
364
|
-
});
|
|
365
|
-
task.emit(_types.TASK_EVENTS.TASK_END, task);
|
|
366
|
-
break;
|
|
367
|
-
case _types2.CC_EVENTS.AGENT_WRAPPEDUP:
|
|
368
|
-
task.cancelAutoWrapupTimer();
|
|
369
|
-
this.removeTaskFromCollection(task);
|
|
370
|
-
task.emit(_types.TASK_EVENTS.TASK_WRAPPEDUP, task);
|
|
371
|
-
break;
|
|
372
|
-
case _types2.CC_EVENTS.CONTACT_RECORDING_PAUSED:
|
|
373
|
-
task = this.updateTaskData(task, payload.data);
|
|
374
|
-
task.emit(_types.TASK_EVENTS.TASK_RECORDING_PAUSED, task);
|
|
375
|
-
break;
|
|
376
|
-
case _types2.CC_EVENTS.CONTACT_RECORDING_PAUSE_FAILED:
|
|
377
|
-
task = this.updateTaskData(task, payload.data);
|
|
378
|
-
task.emit(_types.TASK_EVENTS.TASK_RECORDING_PAUSE_FAILED, task);
|
|
379
|
-
break;
|
|
380
|
-
case _types2.CC_EVENTS.CONTACT_RECORDING_RESUMED:
|
|
381
|
-
task = this.updateTaskData(task, payload.data);
|
|
382
|
-
task.emit(_types.TASK_EVENTS.TASK_RECORDING_RESUMED, task);
|
|
383
|
-
break;
|
|
384
|
-
case _types2.CC_EVENTS.CONTACT_RECORDING_RESUME_FAILED:
|
|
385
|
-
task = this.updateTaskData(task, payload.data);
|
|
386
|
-
task.emit(_types.TASK_EVENTS.TASK_RECORDING_RESUME_FAILED, task);
|
|
387
|
-
break;
|
|
388
|
-
case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCING:
|
|
389
|
-
// Conference is being established - update task state and emit establishing event
|
|
390
|
-
task = this.updateTaskData(task, payload.data);
|
|
391
|
-
task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_ESTABLISHING, task);
|
|
392
|
-
break;
|
|
393
|
-
case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCED:
|
|
394
|
-
// Conference started successfully - update task state and emit event
|
|
395
|
-
task = this.updateTaskData(task, payload.data);
|
|
396
|
-
task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_STARTED, task);
|
|
397
|
-
break;
|
|
398
|
-
case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCE_FAILED:
|
|
399
|
-
// Conference failed - update task state and emit failure event
|
|
400
|
-
task = this.updateTaskData(task, payload.data);
|
|
401
|
-
task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_FAILED, task);
|
|
402
|
-
break;
|
|
403
|
-
case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCE_ENDED:
|
|
404
|
-
// Conference ended - update task state and emit event
|
|
405
|
-
task = this.updateTaskData(task, payload.data);
|
|
406
|
-
if (!task || (0, _TaskUtils.isPrimary)(task, this.agentId) || (0, _TaskUtils.isParticipantInMainInteraction)(task, this.agentId)) {
|
|
407
|
-
_loggerProxy.default.log('Primary or main interaction participant leaving conference');
|
|
408
|
-
} else {
|
|
409
|
-
this.removeTaskFromCollection(task);
|
|
410
|
-
}
|
|
411
|
-
task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_ENDED, task);
|
|
412
|
-
break;
|
|
413
|
-
case _types2.CC_EVENTS.PARTICIPANT_JOINED_CONFERENCE:
|
|
414
|
-
{
|
|
415
|
-
task = this.updateTaskData(task, {
|
|
416
|
-
...payload.data,
|
|
417
|
-
isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload.data)
|
|
418
|
-
});
|
|
419
|
-
task.emit(_types.TASK_EVENTS.TASK_PARTICIPANT_JOINED, task);
|
|
420
|
-
break;
|
|
421
|
-
}
|
|
422
|
-
case _types2.CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE:
|
|
423
|
-
{
|
|
424
|
-
// Conference ended - update task state and emit event
|
|
425
|
-
|
|
426
|
-
task = this.updateTaskData(task, {
|
|
427
|
-
...payload.data,
|
|
428
|
-
isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload.data)
|
|
429
|
-
});
|
|
430
|
-
if ((0, _TaskUtils.checkParticipantNotInInteraction)(task, this.agentId)) {
|
|
431
|
-
if ((0, _TaskUtils.isParticipantInMainInteraction)(task, this.agentId) || (0, _TaskUtils.isPrimary)(task, this.agentId)) {
|
|
432
|
-
_loggerProxy.default.log('Primary or main interaction participant leaving conference');
|
|
433
|
-
} else {
|
|
434
|
-
this.removeTaskFromCollection(task);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
task.emit(_types.TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
438
|
-
break;
|
|
439
|
-
}
|
|
440
|
-
case _types2.CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE_FAILED:
|
|
441
|
-
// Conference exit failed - update task state and emit failure event
|
|
442
|
-
task = this.updateTaskData(task, payload.data);
|
|
443
|
-
task.emit(_types.TASK_EVENTS.TASK_PARTICIPANT_LEFT_FAILED, task);
|
|
444
|
-
break;
|
|
445
|
-
case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCE_END_FAILED:
|
|
446
|
-
// Conference end failed - update task state with error details and emit failure event
|
|
447
|
-
task = this.updateTaskData(task, payload.data);
|
|
448
|
-
task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_END_FAILED, task);
|
|
449
|
-
break;
|
|
450
|
-
case _types2.CC_EVENTS.AGENT_CONFERENCE_TRANSFERRED:
|
|
451
|
-
// Conference was transferred - update task state and emit transfer success event
|
|
452
|
-
// Note: Backend should provide hasLeft and wrapUpRequired status
|
|
453
|
-
task = this.updateTaskData(task, payload.data);
|
|
454
|
-
task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_TRANSFERRED, task);
|
|
455
|
-
break;
|
|
456
|
-
case _types2.CC_EVENTS.AGENT_CONFERENCE_TRANSFER_FAILED:
|
|
457
|
-
// Conference transfer failed - update task state with error details and emit failure event
|
|
458
|
-
task = this.updateTaskData(task, payload.data);
|
|
459
|
-
task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_TRANSFER_FAILED, task);
|
|
460
|
-
break;
|
|
461
|
-
case _types2.CC_EVENTS.PARTICIPANT_POST_CALL_ACTIVITY:
|
|
462
|
-
// Post-call activity for participant - update task state with activity details
|
|
463
|
-
task = this.updateTaskData(task, payload.data);
|
|
464
|
-
task.emit(_types.TASK_EVENTS.TASK_POST_CALL_ACTIVITY, task);
|
|
465
|
-
break;
|
|
466
|
-
case _types2.CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION:
|
|
467
|
-
{
|
|
468
|
-
// Campaign preview contact offered to agent
|
|
469
|
-
// Create a task in the collection so subsequent events (e.g. AGENT_CONTACT_ASSIGNED
|
|
470
|
-
// after acceptPreviewContact) can find and update it.
|
|
471
|
-
// Emit TASK_CAMPAIGN_PREVIEW_RESERVATION instead of TASK_INCOMING so the call
|
|
472
|
-
// does not ring out to the customer before the agent explicitly accepts the preview contact.
|
|
473
|
-
_loggerProxy.default.log('Campaign preview reservation received', {
|
|
474
|
-
module: _constants.TASK_MANAGER_FILE,
|
|
475
|
-
method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
|
|
476
|
-
interactionId: payload.data.interactionId
|
|
477
|
-
});
|
|
478
|
-
if (!this.taskCollection[payload.data.interactionId]) {
|
|
479
|
-
task = new _.default(this.contact, this.webCallingService, {
|
|
480
|
-
...payload.data,
|
|
481
|
-
wrapUpRequired: false,
|
|
482
|
-
isConferenceInProgress: false,
|
|
483
|
-
isAutoAnswering: false
|
|
484
|
-
}, this.wrapupData, this.agentId);
|
|
485
|
-
this.taskCollection[payload.data.interactionId] = task;
|
|
486
|
-
} else {
|
|
487
|
-
task = this.updateTaskData(task, payload.data);
|
|
488
|
-
}
|
|
489
|
-
this.emit(_types.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, task);
|
|
490
|
-
break;
|
|
491
|
-
}
|
|
492
|
-
default:
|
|
493
|
-
break;
|
|
494
|
-
}
|
|
495
|
-
if (task) {
|
|
496
|
-
task.emit(payload.data.type, payload.data);
|
|
497
|
-
}
|
|
498
|
-
const transcriptInteractionId = payload.data?.interactionId || payload.data?.data?.conversationId || task?.data?.interactionId;
|
|
499
|
-
if (_constants2.TRANSCRIPT_EVENT_MAP[payload.data.type] && transcriptInteractionId) {
|
|
500
|
-
this.requestRealTimeTranscripts(payload.data.type, transcriptInteractionId);
|
|
501
|
-
}
|
|
368
|
+
// Step 1: Parse and validate the message
|
|
369
|
+
const message = TaskManager.parseWebSocketMessage(event);
|
|
370
|
+
if (!message) return;
|
|
371
|
+
|
|
372
|
+
// Step 2: Prepare event context
|
|
373
|
+
const eventContext = this.prepareEventContext(message);
|
|
374
|
+
if (!eventContext) return;
|
|
375
|
+
const actions = this.handleTaskLifecycleEvent(eventContext);
|
|
376
|
+
const {
|
|
377
|
+
task
|
|
378
|
+
} = actions;
|
|
379
|
+
if (!task) return;
|
|
380
|
+
const {
|
|
381
|
+
payload,
|
|
382
|
+
stateMachineEvent
|
|
383
|
+
} = eventContext;
|
|
384
|
+
|
|
385
|
+
// Always keep task.data updated (even for mapped events) so consumers relying
|
|
386
|
+
// on TaskManager-managed task instances see the latest payload.
|
|
387
|
+
if (payload) {
|
|
388
|
+
this.updateTaskData(task, payload);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Send event to state machine - this will trigger all TASK_EVENTS emissions
|
|
392
|
+
// including TASK_INCOMING which is now handled via the state machine callbacks
|
|
393
|
+
if (stateMachineEvent) {
|
|
394
|
+
task.sendStateMachineEvent(stateMachineEvent);
|
|
502
395
|
}
|
|
396
|
+
|
|
397
|
+
// Send transcript start/stop events for relevant CC events
|
|
398
|
+
this.requestRealTimeTranscripts(eventContext.eventType, payload.interactionId);
|
|
503
399
|
});
|
|
504
400
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
module: _constants.TASK_MANAGER_FILE,
|
|
512
|
-
method: _constants2.METHODS.UPDATE_TASK_DATA
|
|
513
|
-
});
|
|
514
|
-
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Parse and validate WebSocket message
|
|
404
|
+
* @returns Parsed message or null if invalid/keepalive
|
|
405
|
+
*/
|
|
406
|
+
static parseWebSocketMessage(event) {
|
|
515
407
|
try {
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
|
|
408
|
+
const payload = JSON.parse(event);
|
|
409
|
+
|
|
410
|
+
// Filter out keepalive messages
|
|
411
|
+
if (payload?.keepalive === 'true' || payload?.keepalive === true) {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Normalize task data if present
|
|
416
|
+
if (payload?.data?.interaction) {
|
|
417
|
+
payload.data = (0, _taskDataNormalizer.normalizeTaskData)(payload.data);
|
|
418
|
+
}
|
|
419
|
+
return payload;
|
|
519
420
|
} catch (error) {
|
|
520
|
-
_loggerProxy.default.error(
|
|
421
|
+
_loggerProxy.default.error('Failed to parse WebSocket message', {
|
|
521
422
|
module: _constants.TASK_MANAGER_FILE,
|
|
522
|
-
method:
|
|
523
|
-
|
|
423
|
+
method: 'parseWebSocketMessage',
|
|
424
|
+
error
|
|
524
425
|
});
|
|
525
|
-
return
|
|
426
|
+
return null;
|
|
526
427
|
}
|
|
527
428
|
}
|
|
528
429
|
|
|
529
430
|
/**
|
|
530
|
-
*
|
|
531
|
-
* @
|
|
532
|
-
* @param taskData - The task data from the event payload
|
|
533
|
-
* @returns Updated or newly created task
|
|
534
|
-
* @private
|
|
431
|
+
* Prepare context for event processing
|
|
432
|
+
* @returns Event context or null if event type is invalid
|
|
535
433
|
*/
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
434
|
+
prepareEventContext(message) {
|
|
435
|
+
const eventType = message.data?.type || message.type;
|
|
436
|
+
if (!eventType || !isCcEvent(eventType)) {
|
|
437
|
+
return null;
|
|
540
438
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
439
|
+
const interactionId = message.data.interactionId;
|
|
440
|
+
const task = this.taskCollection[interactionId];
|
|
441
|
+
const wasConsultedTask = Boolean(task?.data?.isConsulted);
|
|
442
|
+
const computeWrapUpRequired = () => {
|
|
443
|
+
if (message.data.wrapUpRequired !== undefined) {
|
|
444
|
+
return message.data.wrapUpRequired;
|
|
445
|
+
}
|
|
446
|
+
if (message.data.isConsulted !== undefined) {
|
|
447
|
+
return !message.data.isConsulted;
|
|
448
|
+
}
|
|
449
|
+
return !wasConsultedTask;
|
|
450
|
+
};
|
|
451
|
+
const adjustedPayload = eventType === _types2.CC_EVENTS.AGENT_CONSULT_TRANSFERRED || eventType === _types2.CC_EVENTS.AGENT_BLIND_TRANSFERRED || eventType === _types2.CC_EVENTS.AGENT_VTEAM_TRANSFERRED ? {
|
|
452
|
+
...message.data,
|
|
453
|
+
wrapUpRequired: computeWrapUpRequired()
|
|
454
|
+
} : message.data;
|
|
455
|
+
const stateMachineEvent = TaskManager.mapEventToTaskStateMachineEvent(eventType, adjustedPayload, this.agentId);
|
|
456
|
+
_loggerProxy.default.info(`Handling task event ${eventType}`, {
|
|
457
|
+
module: _constants.TASK_MANAGER_FILE,
|
|
458
|
+
method: 'prepareEventContext',
|
|
459
|
+
interactionId
|
|
460
|
+
});
|
|
461
|
+
return {
|
|
462
|
+
eventType,
|
|
463
|
+
payload: adjustedPayload,
|
|
464
|
+
task,
|
|
465
|
+
stateMachineEvent
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Handle task lifecycle events and determine required actions
|
|
471
|
+
*
|
|
472
|
+
* Delegates to specific event handlers based on event type. Each handler
|
|
473
|
+
* is responsible for TaskManager-level concerns:
|
|
474
|
+
* - Task creation and collection management
|
|
475
|
+
* - Metrics tracking
|
|
476
|
+
* - Resource cleanup decisions
|
|
477
|
+
*
|
|
478
|
+
* Note: Task-level state transitions and event emissions are handled by
|
|
479
|
+
* the task state machine via sendStateMachineEvent()
|
|
480
|
+
*/
|
|
481
|
+
handleTaskLifecycleEvent(context) {
|
|
482
|
+
const {
|
|
483
|
+
eventType
|
|
484
|
+
} = context;
|
|
485
|
+
switch (eventType) {
|
|
486
|
+
case _types2.CC_EVENTS.AGENT_CONTACT_RESERVED:
|
|
487
|
+
return this.handleContactReserved(context);
|
|
488
|
+
case _types2.CC_EVENTS.AGENT_CONTACT:
|
|
489
|
+
return this.handleAgentContact(context);
|
|
490
|
+
case _types2.CC_EVENTS.CONTACT_MERGED:
|
|
491
|
+
return this.handleContactMergedEvent(context);
|
|
492
|
+
default:
|
|
493
|
+
return {
|
|
494
|
+
task: context.task
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Handle AGENT_CONTACT_RESERVED event
|
|
501
|
+
* Creates a new task; state machine event is sent during processing
|
|
502
|
+
*/
|
|
503
|
+
handleContactReserved(context) {
|
|
504
|
+
const {
|
|
505
|
+
payload
|
|
506
|
+
} = context;
|
|
507
|
+
const isConsultedTask = payload.isConsulted === true || (0, _TaskUtils.isSecondaryEpDnAgent)(payload.interaction);
|
|
508
|
+
const shouldAutoAnswer = (0, _TaskUtils.shouldAutoAnswerTask)(payload, this.agentId, this.webCallingService.loginOption, this.webRtcEnabled);
|
|
509
|
+
const taskData = {
|
|
510
|
+
...payload,
|
|
511
|
+
isConsulted: isConsultedTask,
|
|
512
|
+
isAutoAnswering: shouldAutoAnswer
|
|
513
|
+
};
|
|
514
|
+
const task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId);
|
|
515
|
+
this.setupTaskListeners(task);
|
|
516
|
+
this.taskCollection[payload.interactionId] = task;
|
|
517
|
+
return {
|
|
518
|
+
task
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Handle AGENT_CONTACT event
|
|
524
|
+
* Re-creates task if missing (multi-session scenario)
|
|
525
|
+
*/
|
|
526
|
+
handleAgentContact(context) {
|
|
527
|
+
let {
|
|
528
|
+
task
|
|
529
|
+
} = context;
|
|
530
|
+
const {
|
|
531
|
+
payload
|
|
532
|
+
} = context;
|
|
533
|
+
if (!task) {
|
|
534
|
+
const isConsultedTask = payload.isConsulted === true || (0, _TaskUtils.isSecondaryEpDnAgent)(payload.interaction);
|
|
535
|
+
const shouldAutoAnswer = (0, _TaskUtils.shouldAutoAnswerTask)(payload, this.agentId, this.webCallingService.loginOption, this.webRtcEnabled);
|
|
536
|
+
const taskData = {
|
|
537
|
+
...payload,
|
|
538
|
+
isConsulted: isConsultedTask,
|
|
539
|
+
wrapUpRequired: payload.interaction?.participants?.[this.agentId]?.isWrapUp || false,
|
|
540
|
+
isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload),
|
|
541
|
+
isAutoAnswering: shouldAutoAnswer
|
|
542
|
+
};
|
|
543
|
+
task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId);
|
|
544
|
+
this.setupTaskListeners(task);
|
|
545
|
+
this.taskCollection[payload.interactionId] = task;
|
|
546
|
+
}
|
|
547
|
+
return {
|
|
548
|
+
task
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
updateTaskData(task, taskData) {
|
|
552
|
+
if (!task) {
|
|
553
|
+
throw new Error('Task not found for update');
|
|
554
|
+
}
|
|
555
|
+
const snapshot = task.stateMachineService?.getSnapshot?.();
|
|
556
|
+
const isConsultingFlow = snapshot?.value === 'CONSULTING' || taskData.interaction?.state === 'consulting';
|
|
557
|
+
const updateTaskData = isConsultingFlow ? {
|
|
558
|
+
...taskData,
|
|
559
|
+
destAgentId: taskData.destAgentId ?? snapshot?.context?.consultDestinationAgentId ?? null,
|
|
560
|
+
destinationType: taskData.destinationType ?? snapshot?.context?.consultDestinationType ?? null
|
|
561
|
+
} : taskData;
|
|
562
|
+
task.updateTaskData(updateTaskData);
|
|
563
|
+
this.taskCollection[taskData.interactionId] = task;
|
|
564
|
+
return task;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Setup listeners for task events that need to be bubbled up to TaskManager
|
|
569
|
+
* This replaces the previous callback injection pattern
|
|
570
|
+
*/
|
|
571
|
+
setupTaskListeners(task) {
|
|
572
|
+
// Listen for TASK_INCOMING and re-emit so webex.cc can notify consumers
|
|
573
|
+
task.on(_types.TASK_EVENTS.TASK_INCOMING, t => {
|
|
574
|
+
_loggerProxy.default.log(`Task incoming event received`, {
|
|
552
575
|
module: _constants.TASK_MANAGER_FILE,
|
|
553
576
|
method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
|
|
554
|
-
interactionId:
|
|
577
|
+
interactionId: t.data?.interactionId
|
|
555
578
|
});
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
579
|
+
this.emit(_types.TASK_EVENTS.TASK_INCOMING, t);
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
// Listen for TASK_HYDRATE on the task and re-emit on TaskManager
|
|
583
|
+
task.on(_types.TASK_EVENTS.TASK_HYDRATE, t => {
|
|
584
|
+
// Task data is already updated by the task itself before emitting
|
|
585
|
+
this.emit(_types.TASK_EVENTS.TASK_HYDRATE, t);
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
// Listen for internal cleanup signal emitted by the state machine
|
|
589
|
+
task.on(_types.TASK_EVENTS.TASK_CLEANUP, (t, options) => {
|
|
590
|
+
this.handleTaskCleanup(t);
|
|
591
|
+
if (options?.removeFromCollection) {
|
|
592
|
+
const interactionId = t?.data?.interactionId;
|
|
593
|
+
if (interactionId && this.taskCollection[interactionId]) {
|
|
594
|
+
this.removeTaskFromCollection(t);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
});
|
|
565
598
|
}
|
|
566
599
|
removeTaskFromCollection(task) {
|
|
600
|
+
if (typeof task.cancelAutoWrapupTimer === 'function') {
|
|
601
|
+
task.cancelAutoWrapupTimer();
|
|
602
|
+
}
|
|
567
603
|
if (task?.data?.interactionId) {
|
|
568
604
|
delete this.taskCollection[task.data.interactionId];
|
|
569
605
|
_loggerProxy.default.info(`Task removed from collection`, {
|
|
@@ -575,62 +611,63 @@ class TaskManager extends _events.default {
|
|
|
575
611
|
}
|
|
576
612
|
|
|
577
613
|
/**
|
|
578
|
-
* Handles
|
|
579
|
-
*
|
|
580
|
-
*
|
|
581
|
-
*
|
|
582
|
-
* 2. Agent-initiated WebRTC outdial calls
|
|
583
|
-
* 3. Agent-initiated digital outbound (Email/SMS) without previous transfers
|
|
584
|
-
*
|
|
585
|
-
* @param task - The task to auto-answer
|
|
614
|
+
* Handles CONTACT_MERGED event logic
|
|
615
|
+
* @param task - The task to process
|
|
616
|
+
* @param taskData - The task data from the event payload
|
|
617
|
+
* @returns Updated or newly created task
|
|
586
618
|
* @private
|
|
587
619
|
*/
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
620
|
+
handleContactMergedEvent(context) {
|
|
621
|
+
const {
|
|
622
|
+
payload
|
|
623
|
+
} = context;
|
|
624
|
+
let task = context.task;
|
|
625
|
+
if (payload.childInteractionId) {
|
|
626
|
+
// remove the child task from collection
|
|
627
|
+
this.removeTaskFromCollection(this.taskCollection[payload.childInteractionId]);
|
|
591
628
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
method: 'handleAutoAnswer',
|
|
595
|
-
interactionId: task.data.interactionId
|
|
596
|
-
});
|
|
597
|
-
try {
|
|
598
|
-
await task.accept();
|
|
599
|
-
_loggerProxy.default.info(`Task auto-answered successfully`, {
|
|
629
|
+
if (task) {
|
|
630
|
+
_loggerProxy.default.log(`Got CONTACT_MERGED: Task already exists in collection`, {
|
|
600
631
|
module: _constants.TASK_MANAGER_FILE,
|
|
601
|
-
method:
|
|
602
|
-
interactionId:
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
// Track successful auto-answer
|
|
606
|
-
this.metricsManager.trackEvent(_constants3.METRIC_EVENT_NAMES.TASK_AUTO_ANSWER_SUCCESS, {
|
|
607
|
-
taskId: task.data.interactionId,
|
|
608
|
-
mediaType: task.data.interaction.mediaType,
|
|
609
|
-
isAutoAnswered: true
|
|
610
|
-
}, ['behavioral', 'operational']);
|
|
611
|
-
// Emit task:autoAnswered event for widgets/UI to react
|
|
612
|
-
task.emit(_types.TASK_EVENTS.TASK_AUTO_ANSWERED, task);
|
|
613
|
-
} catch (error) {
|
|
614
|
-
// Reset isAutoAnswering flag on failure
|
|
615
|
-
task.updateTaskData({
|
|
616
|
-
...task.data,
|
|
617
|
-
isAutoAnswering: false
|
|
632
|
+
method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
|
|
633
|
+
interactionId: payload.interactionId
|
|
618
634
|
});
|
|
619
|
-
|
|
635
|
+
// update the task data
|
|
636
|
+
this.updateTaskData(task, payload);
|
|
637
|
+
} else {
|
|
638
|
+
// Case2 : Task is not present in taskCollection
|
|
639
|
+
_loggerProxy.default.log(`Got CONTACT_MERGED : Creating new task in taskManager`, {
|
|
620
640
|
module: _constants.TASK_MANAGER_FILE,
|
|
621
|
-
method:
|
|
622
|
-
interactionId:
|
|
623
|
-
error
|
|
641
|
+
method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
|
|
642
|
+
interactionId: payload.interactionId
|
|
624
643
|
});
|
|
644
|
+
const taskData = {
|
|
645
|
+
...payload,
|
|
646
|
+
wrapUpRequired: payload.interaction?.participants?.[this.agentId]?.isWrapUp || false,
|
|
647
|
+
isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload),
|
|
648
|
+
isConsulted: false
|
|
649
|
+
};
|
|
650
|
+
task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId);
|
|
651
|
+
this.taskCollection[payload.interactionId] = task;
|
|
625
652
|
|
|
626
|
-
//
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
653
|
+
// Transition the new task out of IDLE immediately so UI controls are
|
|
654
|
+
// computed before TASK_MERGED is emitted. This handles the race where
|
|
655
|
+
// AgentContactAssigned arrives before ContactMerged and gets dropped.
|
|
656
|
+
// Send HYDRATE before setupTaskListeners so the emitTaskHydrate action
|
|
657
|
+
// doesn't bubble up to the Widget (avoids duplicate listener registration).
|
|
658
|
+
task.sendStateMachineEvent({
|
|
659
|
+
type: _stateMachine.TaskEvent.HYDRATE,
|
|
660
|
+
taskData,
|
|
661
|
+
agentId: this.agentId
|
|
662
|
+
});
|
|
663
|
+
this.setupTaskListeners(task);
|
|
664
|
+
}
|
|
665
|
+
if (task) {
|
|
666
|
+
this.emit(_types.TASK_EVENTS.TASK_MERGED, task);
|
|
633
667
|
}
|
|
668
|
+
return {
|
|
669
|
+
task
|
|
670
|
+
};
|
|
634
671
|
}
|
|
635
672
|
|
|
636
673
|
/**
|
|
@@ -639,20 +676,19 @@ class TaskManager extends _events.default {
|
|
|
639
676
|
* @private
|
|
640
677
|
*/
|
|
641
678
|
handleTaskCleanup(task) {
|
|
642
|
-
|
|
643
|
-
if (this.webCallingService.loginOption === _types3.LoginOption.BROWSER && task.data.interaction.mediaType === 'telephony') {
|
|
679
|
+
if (this.webCallingService.loginOption === _types3.LoginOption.BROWSER && task.data.interaction.mediaType === _types.MEDIA_CHANNEL.TELEPHONY && task instanceof _WebRTC.default) {
|
|
644
680
|
task.unregisterWebCallListeners();
|
|
645
681
|
this.webCallingService.cleanUpCall();
|
|
646
682
|
}
|
|
647
683
|
const isOutdial = task.data.interaction.outboundType === 'OUTDIAL';
|
|
648
684
|
const isNew = task.data.interaction.state === 'new';
|
|
649
|
-
const needsWrapUp = task.data.agentsPendingWrapUp?.
|
|
685
|
+
const needsWrapUp = task.data.agentsPendingWrapUp?.length > 0;
|
|
650
686
|
|
|
651
687
|
// For OUTDIAL: only remove if NOT terminated (user-declined, no wrap-up follows)
|
|
688
|
+
// If terminated, keep task for wrap-up flow (CONTACT_ENDED → AGENT_WRAPUP)
|
|
652
689
|
// For non-OUTDIAL: remove if state is 'new'
|
|
653
690
|
// Always remove if secondary EpDn agent
|
|
654
|
-
if (isNew && !(isOutdial && needsWrapUp) || (0, _TaskUtils.isSecondaryEpDnAgent)(task.data.interaction)
|
|
655
|
-
) {
|
|
691
|
+
if (isNew && !(isOutdial && needsWrapUp) || (0, _TaskUtils.isSecondaryEpDnAgent)(task.data.interaction)) {
|
|
656
692
|
this.removeTaskFromCollection(task);
|
|
657
693
|
}
|
|
658
694
|
}
|
|
@@ -663,11 +699,12 @@ class TaskManager extends _events.default {
|
|
|
663
699
|
*/
|
|
664
700
|
requestRealTimeTranscripts(eventType, interactionId) {
|
|
665
701
|
const action = _constants2.TRANSCRIPT_EVENT_MAP[eventType];
|
|
666
|
-
if (!action || !this.apiAIAssistant
|
|
702
|
+
if (!action || !this.apiAIAssistant) return;
|
|
703
|
+
if (this.configFlags?.aiFeature?.realtimeTranscripts?.enable === false) return;
|
|
667
704
|
this.apiAIAssistant.sendEvent(this.agentId, interactionId, _types3.AIAssistantEventType.CUSTOM_EVENT, _types3.AIAssistantEventName.GET_TRANSCRIPTS, action).catch(error => {
|
|
668
705
|
_loggerProxy.default.error(`Failed to send transcript ${action} event`, {
|
|
669
706
|
module: _constants.TASK_MANAGER_FILE,
|
|
670
|
-
method:
|
|
707
|
+
method: _constants2.METHODS.REQUEST_REAL_TIME_TRANSCRIPTS,
|
|
671
708
|
interactionId,
|
|
672
709
|
error
|
|
673
710
|
});
|
|
@@ -676,18 +713,16 @@ class TaskManager extends _events.default {
|
|
|
676
713
|
getTask(taskId) {
|
|
677
714
|
return this.taskCollection[taskId];
|
|
678
715
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
};
|
|
686
|
-
static getTaskManager(apiAIAssistant, contact, webCallingService, webSocketManager) {
|
|
716
|
+
getAllTasks() {
|
|
717
|
+
return {
|
|
718
|
+
...this.taskCollection
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
static getTaskManager(apiAIAssistant, contact, webCallingService, webSocketManager, rtdWebSocketManager) {
|
|
687
722
|
if (!TaskManager.taskManager) {
|
|
688
|
-
TaskManager.taskManager = new TaskManager(apiAIAssistant, contact, webCallingService, webSocketManager);
|
|
723
|
+
TaskManager.taskManager = new TaskManager(apiAIAssistant, contact, webCallingService, webSocketManager, rtdWebSocketManager);
|
|
689
724
|
}
|
|
690
|
-
return
|
|
725
|
+
return TaskManager.taskManager;
|
|
691
726
|
}
|
|
692
727
|
}
|
|
693
728
|
exports.default = TaskManager;
|