@webex/contact-center 3.12.0-llmrefactor.2 → 3.12.0-llmrefactor.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.sdd/manifest.json +5 -4
- package/ai-docs/ARCHITECTURE.md +1 -1
- package/ai-docs/CONTRACTS.md +6 -2
- package/ai-docs/SECURITY.md +1 -1
- package/ai-docs/contact-center-spec.md +22 -4
- package/ai-docs/features/consult-transfer-list-policy/spec/feature-spec.md +362 -0
- package/dist/cc.js +440 -10
- package/dist/cc.js.map +1 -1
- package/dist/config.js +7 -0
- package/dist/config.js.map +1 -1
- package/dist/constants.js +10 -2
- package/dist/constants.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/metrics/behavioral-events.js +13 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +2 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/AddressBook.js +18 -15
- package/dist/services/AddressBook.js.map +1 -1
- package/dist/services/AnswerCallOnWebexService.js +174 -0
- package/dist/services/AnswerCallOnWebexService.js.map +1 -0
- package/dist/services/EntryPoint.js +46 -68
- package/dist/services/EntryPoint.js.map +1 -1
- package/dist/services/Queue.js +27 -22
- package/dist/services/Queue.js.map +1 -1
- package/dist/services/WebexCrossClientService.js +171 -0
- package/dist/services/WebexCrossClientService.js.map +1 -0
- package/dist/services/WxAppTelephonyMercurySync.js +93 -0
- package/dist/services/WxAppTelephonyMercurySync.js.map +1 -0
- package/dist/services/config/Util.js +3 -0
- package/dist/services/config/Util.js.map +1 -1
- package/dist/services/config/constants.js +10 -6
- package/dist/services/config/constants.js.map +1 -1
- package/dist/services/config/types.js +4 -0
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/core/Err.js.map +1 -1
- package/dist/services/core/WebexRequest.js +6 -2
- package/dist/services/core/WebexRequest.js.map +1 -1
- package/dist/services/core/aqm-reqs.js +28 -14
- package/dist/services/core/aqm-reqs.js.map +1 -1
- package/dist/services/core/types.js.map +1 -1
- package/dist/services/task/Task.js +73 -7
- package/dist/services/task/Task.js.map +1 -1
- package/dist/services/task/TaskFactory.js +8 -4
- package/dist/services/task/TaskFactory.js.map +1 -1
- package/dist/services/task/TaskManager.js +353 -21
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/WebexCallingUtils.js +70 -0
- package/dist/services/task/WebexCallingUtils.js.map +1 -0
- package/dist/services/task/constants.js +4 -1
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/contact.js +29 -0
- package/dist/services/task/contact.js.map +1 -1
- package/dist/services/task/digital/Digital.js +3 -2
- package/dist/services/task/digital/Digital.js.map +1 -1
- package/dist/services/task/state-machine/TaskStateMachine.js +135 -37
- package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -1
- package/dist/services/task/state-machine/actions.js +6 -1
- package/dist/services/task/state-machine/actions.js.map +1 -1
- package/dist/services/task/state-machine/guards.js +38 -11
- package/dist/services/task/state-machine/guards.js.map +1 -1
- package/dist/services/task/state-machine/types.js.map +1 -1
- package/dist/services/task/state-machine/uiControlsComputer.js +118 -18
- package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -1
- package/dist/services/task/types.js +16 -1
- package/dist/services/task/types.js.map +1 -1
- package/dist/services/task/voice/Voice.js +350 -14
- package/dist/services/task/voice/Voice.js.map +1 -1
- package/dist/services/task/voice/WebRTC.js +4 -1
- package/dist/services/task/voice/WebRTC.js.map +1 -1
- package/dist/services/task/voice/wxAppVoiceMethods.js +201 -0
- package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
- package/dist/services/wxAppTelephonyUtils.js +19 -0
- package/dist/services/wxAppTelephonyUtils.js.map +1 -0
- package/dist/types/cc.d.ts +77 -4
- package/dist/types/config.d.ts +7 -0
- package/dist/types/constants.d.ts +8 -0
- package/dist/types/index.d.ts +3 -1
- package/dist/types/metrics/constants.d.ts +2 -0
- package/dist/types/services/AddressBook.d.ts +2 -1
- package/dist/types/services/AnswerCallOnWebexService.d.ts +37 -0
- package/dist/types/services/EntryPoint.d.ts +7 -5
- package/dist/types/services/Queue.d.ts +5 -3
- package/dist/types/services/WebexCrossClientService.d.ts +28 -0
- package/dist/types/services/WxAppTelephonyMercurySync.d.ts +28 -0
- package/dist/types/services/config/constants.d.ts +8 -5
- package/dist/types/services/config/types.d.ts +17 -4
- package/dist/types/services/core/Err.d.ts +2 -0
- package/dist/types/services/core/WebexRequest.d.ts +1 -0
- package/dist/types/services/core/types.d.ts +2 -0
- package/dist/types/services/task/Task.d.ts +20 -2
- package/dist/types/services/task/TaskFactory.d.ts +2 -1
- package/dist/types/services/task/WebexCallingUtils.d.ts +11 -0
- package/dist/types/services/task/constants.d.ts +3 -0
- package/dist/types/services/task/contact.d.ts +4 -0
- package/dist/types/services/task/digital/Digital.d.ts +2 -2
- package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +260 -56
- package/dist/types/services/task/state-machine/guards.d.ts +6 -5
- package/dist/types/services/task/state-machine/types.d.ts +9 -1
- package/dist/types/services/task/types.d.ts +81 -4
- package/dist/types/services/task/voice/Voice.d.ts +48 -9
- package/dist/types/services/task/voice/WebRTC.d.ts +2 -2
- package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +52 -0
- package/dist/types/services/wxAppTelephonyUtils.d.ts +5 -0
- package/dist/types/types.d.ts +14 -8
- package/dist/types/utils/PageCache.d.ts +20 -3
- package/dist/types.js +7 -2
- package/dist/types.js.map +1 -1
- package/dist/utils/PageCache.js +19 -5
- package/dist/utils/PageCache.js.map +1 -1
- package/dist/webex.js +1 -1
- package/package.json +9 -9
- package/src/cc.ts +506 -9
- package/src/config.ts +7 -0
- package/src/constants.ts +8 -0
- package/src/index.ts +2 -0
- package/src/metrics/ai-docs/metrics-spec.md +9 -3
- package/src/metrics/behavioral-events.ts +14 -0
- package/src/metrics/constants.ts +2 -0
- package/src/services/AddressBook.ts +17 -6
- package/src/services/AnswerCallOnWebexService.ts +206 -0
- package/src/services/EntryPoint.ts +59 -60
- package/src/services/Queue.ts +29 -12
- package/src/services/WebexCrossClientService.ts +212 -0
- package/src/services/WxAppTelephonyMercurySync.ts +115 -0
- package/src/services/ai-docs/AGENTS.md +10 -10
- package/src/services/ai-docs/services-spec.md +6 -1
- package/src/services/config/Util.ts +3 -0
- package/src/services/config/ai-docs/AGENTS.md +1 -1
- package/src/services/config/ai-docs/ARCHITECTURE.md +2 -2
- package/src/services/config/ai-docs/config-spec.md +6 -0
- package/src/services/config/constants.ts +10 -6
- package/src/services/config/types.ts +16 -4
- package/src/services/core/Err.ts +1 -0
- package/src/services/core/WebexRequest.ts +3 -1
- package/src/services/core/ai-docs/core-spec.md +5 -1
- package/src/services/core/aqm-reqs.ts +30 -15
- package/src/services/core/types.ts +2 -0
- package/src/services/task/Task.ts +79 -8
- package/src/services/task/TaskFactory.ts +8 -3
- package/src/services/task/TaskManager.ts +522 -16
- package/src/services/task/WebexCallingUtils.ts +136 -0
- package/src/services/task/ai-docs/ARCHITECTURE.md +1 -0
- package/src/services/task/ai-docs/task-spec.md +152 -2
- package/src/services/task/constants.ts +3 -0
- package/src/services/task/contact.ts +30 -0
- package/src/services/task/digital/Digital.ts +4 -2
- package/src/services/task/state-machine/TaskStateMachine.ts +210 -63
- package/src/services/task/state-machine/actions.ts +5 -2
- package/src/services/task/state-machine/ai-docs/AGENTS.md +8 -4
- package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +17 -8
- package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +56 -24
- package/src/services/task/state-machine/guards.ts +64 -11
- package/src/services/task/state-machine/types.ts +16 -1
- package/src/services/task/state-machine/uiControlsComputer.ts +206 -32
- package/src/services/task/types.ts +114 -4
- package/src/services/task/voice/Voice.ts +462 -12
- package/src/services/task/voice/WebRTC.ts +5 -1
- package/src/services/task/voice/wxAppVoiceMethods.ts +307 -0
- package/src/services/wxAppTelephonyUtils.ts +14 -0
- package/src/types.ts +33 -9
- package/src/utils/AGENTS.md +21 -10
- package/src/utils/PageCache.ts +38 -5
- package/src/utils/ai-docs/utils-spec.md +21 -11
- package/test/unit/spec/cc.ts +1274 -0
- package/test/unit/spec/metrics/behavioral-events.ts +18 -0
- package/test/unit/spec/services/AddressBook.ts +37 -6
- package/test/unit/spec/services/AnswerCallOnWebexService.ts +223 -0
- package/test/unit/spec/services/EntryPoint.ts +87 -40
- package/test/unit/spec/services/Queue.ts +123 -12
- package/test/unit/spec/services/WebexCrossClientService.ts +261 -0
- package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +113 -0
- package/test/unit/spec/services/config/Util.ts +85 -0
- package/test/unit/spec/services/core/WebexRequest.ts +3 -1
- package/test/unit/spec/services/core/aqm-reqs.ts +113 -1
- package/test/unit/spec/services/task/Task.ts +200 -0
- package/test/unit/spec/services/task/TaskFactory.ts +39 -2
- package/test/unit/spec/services/task/TaskManager.ts +1294 -1
- package/test/unit/spec/services/task/WebexCallingUtils.ts +153 -0
- package/test/unit/spec/services/task/contact.ts +33 -0
- package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +854 -72
- package/test/unit/spec/services/task/state-machine/guards.ts +210 -8
- package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +445 -7
- package/test/unit/spec/services/task/voice/Voice.ts +874 -0
- package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +459 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -14,11 +14,14 @@ var _types3 = require("../../types");
|
|
|
14
14
|
var _loggerProxy = _interopRequireDefault(require("../../logger-proxy"));
|
|
15
15
|
var _TaskUtils = require("./TaskUtils");
|
|
16
16
|
var _TaskFactory = _interopRequireDefault(require("./TaskFactory"));
|
|
17
|
+
var _WebexCallingUtils = require("./WebexCallingUtils");
|
|
17
18
|
var _WebRTC = _interopRequireDefault(require("./voice/WebRTC"));
|
|
18
19
|
var _stateMachine = require("./state-machine");
|
|
20
|
+
var _constants3 = require("./state-machine/constants");
|
|
19
21
|
var _taskDataNormalizer = require("./taskDataNormalizer");
|
|
20
22
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
21
23
|
const CC_EVENT_SET = new Set(Object.values(_types2.CC_EVENTS));
|
|
24
|
+
const MAIN_INTERACTION_CORRELATED_EVENTS = new Set([_types2.CC_EVENTS.AGENT_CONSULT_ENDED, _types2.CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE]);
|
|
22
25
|
const isCcEvent = value => CC_EVENT_SET.has(value);
|
|
23
26
|
|
|
24
27
|
/** @internal */
|
|
@@ -109,6 +112,9 @@ class TaskManager extends _events.default {
|
|
|
109
112
|
setWebRtcEnabled(webRtcEnabled) {
|
|
110
113
|
this.webRtcEnabled = webRtcEnabled;
|
|
111
114
|
}
|
|
115
|
+
setAnswerCallOnWebexService(answerCallOnWebexService) {
|
|
116
|
+
this.answerCallOnWebexService = answerCallOnWebexService;
|
|
117
|
+
}
|
|
112
118
|
handleIncomingWebCall = call => {
|
|
113
119
|
const currentTask = Object.values(this.taskCollection).find(task => task.data.interaction.mediaType === 'telephony' && !(0, _TaskUtils.isCampaignPreviewReservation)(task));
|
|
114
120
|
if (currentTask) {
|
|
@@ -139,9 +145,10 @@ class TaskManager extends _events.default {
|
|
|
139
145
|
* @param ccEvent - The CC_EVENT type from WebSocket
|
|
140
146
|
* @param payload - The event payload
|
|
141
147
|
* @param agentId - Optional agent ID for state detection (needed for HYDRATE)
|
|
148
|
+
* @param task - Optional task instance for state-aware CONTACT_ENDED mapping
|
|
142
149
|
* @returns TaskEventPayload for state machine or null if no mapping
|
|
143
150
|
*/
|
|
144
|
-
static mapEventToTaskStateMachineEvent(ccEvent, payload, agentId) {
|
|
151
|
+
static mapEventToTaskStateMachineEvent(ccEvent, payload, agentId, task, enableWxBetterTogether) {
|
|
145
152
|
const mediaResourceId = payload.mediaResourceId || payload.interaction?.media?.[payload.interactionId]?.mediaResourceId;
|
|
146
153
|
switch (ccEvent) {
|
|
147
154
|
// CC -> TaskEvent mappings (see TaskStateMachine comment for quick reference)
|
|
@@ -180,6 +187,12 @@ class TaskManager extends _events.default {
|
|
|
180
187
|
agentId
|
|
181
188
|
};
|
|
182
189
|
case _types2.CC_EVENTS.CONTACT_UPDATED:
|
|
190
|
+
if (task && typeof payload.interaction?.owner === 'string' && payload.interaction.owner.trim().length > 0 && payload.interaction.owner !== task.data?.interaction?.owner) {
|
|
191
|
+
return {
|
|
192
|
+
type: _stateMachine.TaskEvent.CONTACT_OWNER_CHANGED,
|
|
193
|
+
taskData: payload
|
|
194
|
+
};
|
|
195
|
+
}
|
|
183
196
|
return {
|
|
184
197
|
type: _stateMachine.TaskEvent.CONTACT_UPDATED,
|
|
185
198
|
taskData: payload
|
|
@@ -284,13 +297,31 @@ class TaskManager extends _events.default {
|
|
|
284
297
|
taskData: payload
|
|
285
298
|
};
|
|
286
299
|
case _types2.CC_EVENTS.CONTACT_ENDED:
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
300
|
+
{
|
|
301
|
+
const wrapUpRequired = (0, _TaskUtils.isCampaignPreviewTask)(payload) ? false : payload.agentsPendingWrapUp?.includes(agentId || '') || false;
|
|
302
|
+
|
|
303
|
+
// Post-accept wxApp outdial agent-end delivers ContactEnded instead of
|
|
304
|
+
// AgentOutboundFailed. Normalize to OUTBOUND_FAILED so state machine
|
|
305
|
+
// enters WRAPPING_UP and emits TASK_OUTDIAL_FAILED (AGENT_ENDS).
|
|
306
|
+
// Pre-accept cancelTask decline stays CONTACT_ENDED → TERMINATED (no wrapup).
|
|
307
|
+
if (TaskManager.isAgentTerminatedOutdialWrapup(payload, wrapUpRequired, task, agentId, enableWxBetterTogether)) {
|
|
308
|
+
return {
|
|
309
|
+
type: _stateMachine.TaskEvent.OUTBOUND_FAILED,
|
|
310
|
+
reason: 'AGENT_ENDS',
|
|
311
|
+
taskData: {
|
|
312
|
+
...payload,
|
|
313
|
+
wrapUpRequired: true
|
|
314
|
+
}
|
|
315
|
+
};
|
|
292
316
|
}
|
|
293
|
-
|
|
317
|
+
return {
|
|
318
|
+
type: _stateMachine.TaskEvent.CONTACT_ENDED,
|
|
319
|
+
taskData: {
|
|
320
|
+
...payload,
|
|
321
|
+
wrapUpRequired
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
}
|
|
294
325
|
case _types2.CC_EVENTS.AGENT_INVITE_FAILED:
|
|
295
326
|
return {
|
|
296
327
|
type: _stateMachine.TaskEvent.INVITE_FAILED,
|
|
@@ -308,11 +339,18 @@ class TaskManager extends _events.default {
|
|
|
308
339
|
reason: payload.reason
|
|
309
340
|
};
|
|
310
341
|
case _types2.CC_EVENTS.AGENT_OUTBOUND_FAILED:
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
342
|
+
{
|
|
343
|
+
const isAgentTerminatedOutdial = payload.interaction?.outboundType === 'OUTDIAL' && (payload.terminatingParty === 'Agent' || payload.reason === 'AGENT_ENDS');
|
|
344
|
+
const suppressOutdialFailedPopup = isAgentTerminatedOutdial && !TaskManager.isWxAppManagedOutdialTask(agentId, payload, task, enableWxBetterTogether);
|
|
345
|
+
return {
|
|
346
|
+
type: _stateMachine.TaskEvent.OUTBOUND_FAILED,
|
|
347
|
+
taskData: suppressOutdialFailedPopup ? {
|
|
348
|
+
...payload,
|
|
349
|
+
suppressOutdialFailedPopup: true
|
|
350
|
+
} : payload,
|
|
351
|
+
reason: payload.reason
|
|
352
|
+
};
|
|
353
|
+
}
|
|
316
354
|
case _types2.CC_EVENTS.CAMPAIGN_PREVIEW_ACCEPT_FAILED:
|
|
317
355
|
return {
|
|
318
356
|
type: _stateMachine.TaskEvent.CAMPAIGN_PREVIEW_ACCEPT_FAILED,
|
|
@@ -385,6 +423,43 @@ class TaskManager extends _events.default {
|
|
|
385
423
|
}
|
|
386
424
|
}
|
|
387
425
|
|
|
426
|
+
/**
|
|
427
|
+
* True when the current agent's task is a wxApp-managed outdial (thick-client telephony).
|
|
428
|
+
*/
|
|
429
|
+
static isWxAppManagedOutdialTask(agentId, payload, task, enableWxBetterTogether) {
|
|
430
|
+
const wxAppEnabled = task?.uiControlConfig?.enableWxBetterTogether ?? enableWxBetterTogether ?? false;
|
|
431
|
+
if (!wxAppEnabled) {
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
const effectiveAgentId = agentId ?? task?.data?.agentId;
|
|
435
|
+
const participants = payload.interaction?.participants ?? task?.data?.interaction?.participants;
|
|
436
|
+
const details = (0, _WebexCallingUtils.getWebexCallingDeviceDetailsForAgent)(effectiveAgentId, participants);
|
|
437
|
+
return details?.deviceType === 'wxApp';
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* True when ContactEnded represents an agent-terminated wxApp outdial that should
|
|
442
|
+
* enter wrapup with an outdial-failed popup (legacy AgentOutboundFailed parity).
|
|
443
|
+
*/
|
|
444
|
+
static isAgentTerminatedOutdialWrapup(payload, wrapUpRequired, task, agentId, enableWxBetterTogether) {
|
|
445
|
+
const isOutdial = payload.interaction?.outboundType === 'OUTDIAL';
|
|
446
|
+
const agentTerminated = payload.terminatingParty === 'Agent';
|
|
447
|
+
if (!isOutdial || !agentTerminated) {
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
if (!TaskManager.isWxAppManagedOutdialTask(agentId, payload, task, enableWxBetterTogether)) {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
const wxAppAnswerPending = task?.uiControlConfig?.wxAppAnswerPending ?? false;
|
|
454
|
+
|
|
455
|
+
// Pre-accept decline: decline() clears wxAppAnswerPending; backend may still send state wrapUp.
|
|
456
|
+
if (!wxAppAnswerPending && !wrapUpRequired) {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
const interactionWrapUp = payload.interaction?.state === 'wrapUp';
|
|
460
|
+
return wrapUpRequired || interactionWrapUp || wxAppAnswerPending;
|
|
461
|
+
}
|
|
462
|
+
|
|
388
463
|
/**
|
|
389
464
|
* Register WebSocket message listeners for task events
|
|
390
465
|
*
|
|
@@ -493,6 +568,12 @@ class TaskManager extends _events.default {
|
|
|
493
568
|
this.taskCollection[interactionId] = task;
|
|
494
569
|
}
|
|
495
570
|
}
|
|
571
|
+
if (!task && eventType === _types2.CC_EVENTS.CONTACT_OWNER_CHANGED) {
|
|
572
|
+
task = this.findTaskForContactOwnerChange(message.data);
|
|
573
|
+
}
|
|
574
|
+
if (!task && MAIN_INTERACTION_CORRELATED_EVENTS.has(eventType)) {
|
|
575
|
+
task = this.findUniqueTaskByRelatedInteraction(message.data);
|
|
576
|
+
}
|
|
496
577
|
const wasConsultedTask = Boolean(task?.data?.isConsulted);
|
|
497
578
|
const computeWrapUpRequired = () => {
|
|
498
579
|
if (message.data.wrapUpRequired !== undefined) {
|
|
@@ -503,11 +584,17 @@ class TaskManager extends _events.default {
|
|
|
503
584
|
}
|
|
504
585
|
return !wasConsultedTask;
|
|
505
586
|
};
|
|
506
|
-
|
|
587
|
+
let adjustedPayload = eventType === _types2.CC_EVENTS.AGENT_CONSULT_TRANSFERRED || eventType === _types2.CC_EVENTS.AGENT_BLIND_TRANSFERRED || eventType === _types2.CC_EVENTS.AGENT_VTEAM_TRANSFERRED ? {
|
|
507
588
|
...message.data,
|
|
508
589
|
wrapUpRequired: computeWrapUpRequired()
|
|
509
590
|
} : message.data;
|
|
510
|
-
|
|
591
|
+
if (task && eventType === _types2.CC_EVENTS.CONTACT_OWNER_CHANGED) {
|
|
592
|
+
adjustedPayload = TaskManager.preserveMainTaskIdentity(task, adjustedPayload);
|
|
593
|
+
}
|
|
594
|
+
if (task && eventType === _types2.CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE) {
|
|
595
|
+
adjustedPayload = TaskManager.preserveConfirmedOwner(task, adjustedPayload);
|
|
596
|
+
}
|
|
597
|
+
const stateMachineEvent = TaskManager.mapEventToTaskStateMachineEvent(eventType, adjustedPayload, this.agentId, task, this.configFlags?.enableWxBetterTogether);
|
|
511
598
|
_loggerProxy.default.info(`Handling task event ${eventType}`, {
|
|
512
599
|
module: _constants.TASK_MANAGER_FILE,
|
|
513
600
|
method: 'prepareEventContext',
|
|
@@ -521,6 +608,134 @@ class TaskManager extends _events.default {
|
|
|
521
608
|
};
|
|
522
609
|
}
|
|
523
610
|
|
|
611
|
+
/**
|
|
612
|
+
* ContactOwnerChanged can identify the promoted agent's child interaction while
|
|
613
|
+
* the surviving task is stored under the main interaction. Accept one related
|
|
614
|
+
* task when its existing snapshot or the authoritative incoming promotion proves
|
|
615
|
+
* current-agent main-call membership, while excluding non-promoted and ambiguous matches.
|
|
616
|
+
*/
|
|
617
|
+
findTaskForContactOwnerChange(payload) {
|
|
618
|
+
const relatedCandidates = this.findRelatedTasks(payload, {
|
|
619
|
+
includeCollectionKeys: true,
|
|
620
|
+
includeMainCallMediaKeys: true
|
|
621
|
+
});
|
|
622
|
+
const incomingShowsCurrentAgentPromotion = this.isCurrentAgentPromotedOnIncomingMainCall(payload);
|
|
623
|
+
const eligibleCandidates = relatedCandidates.filter(candidate => this.isCurrentAgentActiveOnMainInteraction(candidate.data) || incomingShowsCurrentAgentPromotion);
|
|
624
|
+
if (eligibleCandidates.length === 1) {
|
|
625
|
+
return eligibleCandidates[0];
|
|
626
|
+
}
|
|
627
|
+
if (eligibleCandidates.length > 1) {
|
|
628
|
+
_loggerProxy.default.warn('Unable to correlate task event to a unique main interaction task', {
|
|
629
|
+
module: _constants.TASK_MANAGER_FILE,
|
|
630
|
+
method: 'findTaskForContactOwnerChange',
|
|
631
|
+
interactionId: payload.interaction?.mainInteractionId || payload.interaction?.parentInteractionId || payload.interaction?.callProcessingDetails?.parentInteractionId || payload.interactionId
|
|
632
|
+
});
|
|
633
|
+
return undefined;
|
|
634
|
+
}
|
|
635
|
+
return undefined;
|
|
636
|
+
}
|
|
637
|
+
isCurrentAgentActiveOnMainInteraction(taskData) {
|
|
638
|
+
const currentAgentId = this.agentId || taskData?.agentId;
|
|
639
|
+
if (!currentAgentId) return false;
|
|
640
|
+
return TaskManager.isParticipantActiveOnMainInteraction(taskData, currentAgentId);
|
|
641
|
+
}
|
|
642
|
+
static isParticipantActiveOnMainInteraction(taskData, participantId) {
|
|
643
|
+
const interaction = taskData?.interaction;
|
|
644
|
+
const participant = interaction?.participants?.[participantId];
|
|
645
|
+
if (!participant || participant.hasLeft === true) return false;
|
|
646
|
+
return Object.values(interaction?.media ?? {}).some(media => media?.mType === _constants3.MEDIA_TYPE_MAIN_CALL && media.participants?.includes(participantId));
|
|
647
|
+
}
|
|
648
|
+
isCurrentAgentPromotedOnIncomingMainCall(payload) {
|
|
649
|
+
return Boolean(this.agentId && payload.interaction?.owner === this.agentId && TaskManager.isParticipantActiveOnMainInteraction(payload, this.agentId));
|
|
650
|
+
}
|
|
651
|
+
static getMainCallMediaId(interaction) {
|
|
652
|
+
return Object.entries(interaction?.media ?? {}).find(([, media]) => media?.mType === _constants3.MEDIA_TYPE_MAIN_CALL)?.[0];
|
|
653
|
+
}
|
|
654
|
+
static getStableMainInteractionId(payload) {
|
|
655
|
+
return payload.interaction?.mainInteractionId || TaskManager.getMainCallMediaId(payload.interaction) || payload.interaction?.interactionId || payload.interactionId;
|
|
656
|
+
}
|
|
657
|
+
canRecoverTaskFromContactOwnerChanged(payload) {
|
|
658
|
+
const interaction = payload.interaction;
|
|
659
|
+
const stableInteractionId = TaskManager.getStableMainInteractionId(payload);
|
|
660
|
+
return Boolean(this.agentId && stableInteractionId && interaction?.mediaType === _types.MEDIA_CHANNEL.TELEPHONY && interaction.isTerminated !== true && interaction.owner === this.agentId && TaskManager.isParticipantActiveOnMainInteraction(payload, this.agentId));
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/** Keep a related owner-change event from re-keying the surviving main task to a child ID. */
|
|
664
|
+
static preserveMainTaskIdentity(task, payload) {
|
|
665
|
+
const currentMainInteractionId = task.data?.interaction?.mainInteractionId;
|
|
666
|
+
const payloadMainInteractionId = payload.interaction?.mainInteractionId;
|
|
667
|
+
const currentMainMediaId = TaskManager.getMainCallMediaId(task.data?.interaction);
|
|
668
|
+
const payloadMainMediaId = TaskManager.getMainCallMediaId(payload.interaction);
|
|
669
|
+
const stableInteractionId = currentMainInteractionId || payloadMainInteractionId || currentMainMediaId || payloadMainMediaId || payload.interaction?.interactionId || task.data?.interaction?.interactionId || task.data?.interactionId;
|
|
670
|
+
if (!stableInteractionId || stableInteractionId === payload.interactionId) {
|
|
671
|
+
return payload;
|
|
672
|
+
}
|
|
673
|
+
return {
|
|
674
|
+
...payload,
|
|
675
|
+
interactionId: stableInteractionId
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* ParticipantLeftConference may arrive after an owner update while still carrying
|
|
681
|
+
* the departed owner. Keep the confirmed owner only when the new roster proves that
|
|
682
|
+
* owner is active on mainCall and identifies the incoming owner as departed.
|
|
683
|
+
*/
|
|
684
|
+
static preserveConfirmedOwner(task, payload) {
|
|
685
|
+
const confirmedOwner = task.data?.interaction?.owner;
|
|
686
|
+
const incomingOwner = payload.interaction?.owner;
|
|
687
|
+
if (!confirmedOwner || !incomingOwner || confirmedOwner === incomingOwner) {
|
|
688
|
+
return payload;
|
|
689
|
+
}
|
|
690
|
+
const confirmedOwnerIsActive = TaskManager.isParticipantActiveOnMainInteraction(payload, confirmedOwner);
|
|
691
|
+
const incomingOwnerIsExplicitlyDeparted = payload.participantId === incomingOwner || payload.interaction?.participants?.[incomingOwner]?.hasLeft === true;
|
|
692
|
+
if (!confirmedOwnerIsActive || !incomingOwnerIsExplicitlyDeparted) {
|
|
693
|
+
return payload;
|
|
694
|
+
}
|
|
695
|
+
return {
|
|
696
|
+
...payload,
|
|
697
|
+
owner: confirmedOwner,
|
|
698
|
+
interaction: {
|
|
699
|
+
...payload.interaction,
|
|
700
|
+
owner: confirmedOwner
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Resolve lifecycle events that are emitted for the main interaction while the
|
|
707
|
+
* local task can still be indexed by a child consult interaction. Exact task
|
|
708
|
+
* keys are handled before this fallback. Multiple aliases of the same task are
|
|
709
|
+
* treated as one candidate; genuinely ambiguous matches are intentionally ignored.
|
|
710
|
+
*/
|
|
711
|
+
findUniqueTaskByRelatedInteraction(payload, candidateFilter = () => true) {
|
|
712
|
+
const candidates = this.findRelatedTasks(payload).filter(candidateFilter);
|
|
713
|
+
if (candidates.length === 1) {
|
|
714
|
+
return candidates[0];
|
|
715
|
+
}
|
|
716
|
+
if (candidates.length > 1) {
|
|
717
|
+
_loggerProxy.default.warn('Unable to correlate task event to a unique main interaction task', {
|
|
718
|
+
module: _constants.TASK_MANAGER_FILE,
|
|
719
|
+
method: 'findUniqueTaskByRelatedInteraction',
|
|
720
|
+
interactionId: payload.interaction?.mainInteractionId || payload.interaction?.parentInteractionId || payload.interaction?.callProcessingDetails?.parentInteractionId || payload.interactionId
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
return undefined;
|
|
724
|
+
}
|
|
725
|
+
findRelatedTasks(payload, options = {}) {
|
|
726
|
+
const {
|
|
727
|
+
includeCollectionKeys = false,
|
|
728
|
+
includeMainCallMediaKeys = false
|
|
729
|
+
} = options;
|
|
730
|
+
const payloadInteractionIds = new Set([payload.interactionId, payload.interaction?.interactionId, payload.interaction?.mainInteractionId, payload.interaction?.parentInteractionId, payload.interaction?.callProcessingDetails?.parentInteractionId, ...(includeMainCallMediaKeys ? [TaskManager.getMainCallMediaId(payload.interaction)] : [])].filter(interactionId => Boolean(interactionId)));
|
|
731
|
+
if (payloadInteractionIds.size === 0) return [];
|
|
732
|
+
return [...new Set(Object.entries(this.taskCollection).filter(([taskId, candidate]) => {
|
|
733
|
+
const taskInteraction = candidate?.data?.interaction;
|
|
734
|
+
const candidateInteractionIds = [...(includeCollectionKeys ? [taskId] : []), candidate?.data?.interactionId, taskInteraction?.interactionId, taskInteraction?.mainInteractionId, taskInteraction?.parentInteractionId, taskInteraction?.callProcessingDetails?.parentInteractionId, ...(includeMainCallMediaKeys ? [TaskManager.getMainCallMediaId(taskInteraction)] : [])];
|
|
735
|
+
return candidateInteractionIds.some(interactionId => Boolean(interactionId) && payloadInteractionIds.has(interactionId));
|
|
736
|
+
}).map(([, candidate]) => candidate))];
|
|
737
|
+
}
|
|
738
|
+
|
|
524
739
|
/**
|
|
525
740
|
* Handle task lifecycle events and determine required actions
|
|
526
741
|
*
|
|
@@ -544,6 +759,8 @@ class TaskManager extends _events.default {
|
|
|
544
759
|
return this.handleAgentContact(context);
|
|
545
760
|
case _types2.CC_EVENTS.CONTACT_MERGED:
|
|
546
761
|
return this.handleContactMergedEvent(context);
|
|
762
|
+
case _types2.CC_EVENTS.CONTACT_OWNER_CHANGED:
|
|
763
|
+
return this.handleContactOwnerChanged(context);
|
|
547
764
|
case _types2.CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION:
|
|
548
765
|
return this.handleCampaignPreviewReservation(context);
|
|
549
766
|
case _types2.CC_EVENTS.CAMPAIGN_CONTACT_UPDATED:
|
|
@@ -554,6 +771,63 @@ class TaskManager extends _events.default {
|
|
|
554
771
|
};
|
|
555
772
|
}
|
|
556
773
|
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Recover the promoted agent's active voice task when local task state was lost.
|
|
777
|
+
* Existing related tasks are never replaced: an unresolved or ambiguous relation
|
|
778
|
+
* is safer to ignore than to create a duplicate task.
|
|
779
|
+
*/
|
|
780
|
+
handleContactOwnerChanged(context) {
|
|
781
|
+
if (context.task) {
|
|
782
|
+
return {
|
|
783
|
+
task: context.task
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
const {
|
|
787
|
+
payload
|
|
788
|
+
} = context;
|
|
789
|
+
if (this.findRelatedTasks(payload, {
|
|
790
|
+
includeCollectionKeys: true,
|
|
791
|
+
includeMainCallMediaKeys: true
|
|
792
|
+
}).length > 0 || !this.canRecoverTaskFromContactOwnerChanged(payload)) {
|
|
793
|
+
return {};
|
|
794
|
+
}
|
|
795
|
+
const stableInteractionId = TaskManager.getStableMainInteractionId(payload);
|
|
796
|
+
if (!stableInteractionId || this.taskCollection[stableInteractionId]) {
|
|
797
|
+
return {};
|
|
798
|
+
}
|
|
799
|
+
const normalizedPayload = payload.interactionId === stableInteractionId ? payload : {
|
|
800
|
+
...payload,
|
|
801
|
+
interactionId: stableInteractionId
|
|
802
|
+
};
|
|
803
|
+
const taskData = {
|
|
804
|
+
...normalizedPayload,
|
|
805
|
+
owner: normalizedPayload.interaction.owner,
|
|
806
|
+
wrapUpRequired: normalizedPayload.interaction.participants?.[this.agentId]?.isWrapUp || false,
|
|
807
|
+
isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(normalizedPayload),
|
|
808
|
+
isConsulted: false,
|
|
809
|
+
isAutoAnswering: false
|
|
810
|
+
};
|
|
811
|
+
const task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId, this.answerCallOnWebexService);
|
|
812
|
+
this.taskCollection[stableInteractionId] = task;
|
|
813
|
+
|
|
814
|
+
// Restore the actor before installing listeners so the internal hydrate does
|
|
815
|
+
// not leak as a second public hydrate or as an incoming-task notification.
|
|
816
|
+
task.sendStateMachineEvent({
|
|
817
|
+
type: _stateMachine.TaskEvent.HYDRATE,
|
|
818
|
+
taskData,
|
|
819
|
+
agentId: this.agentId
|
|
820
|
+
});
|
|
821
|
+
this.setupTaskListeners(task);
|
|
822
|
+
context.payload = taskData;
|
|
823
|
+
context.stateMachineEvent = {
|
|
824
|
+
type: _stateMachine.TaskEvent.CONTACT_OWNER_CHANGED,
|
|
825
|
+
taskData
|
|
826
|
+
};
|
|
827
|
+
return {
|
|
828
|
+
task
|
|
829
|
+
};
|
|
830
|
+
}
|
|
557
831
|
handleCampaignContactUpdated(context) {
|
|
558
832
|
const {
|
|
559
833
|
payload
|
|
@@ -638,7 +912,7 @@ class TaskManager extends _events.default {
|
|
|
638
912
|
wrapUpRequired: false,
|
|
639
913
|
isConferenceInProgress: false,
|
|
640
914
|
isAutoAnswering: false
|
|
641
|
-
}, this.configFlags, this.wrapupData, this.agentId);
|
|
915
|
+
}, this.configFlags, this.wrapupData, this.agentId, this.answerCallOnWebexService);
|
|
642
916
|
this.setupTaskListeners(task);
|
|
643
917
|
this.taskCollection[payload.interactionId] = task;
|
|
644
918
|
} else {
|
|
@@ -664,7 +938,7 @@ class TaskManager extends _events.default {
|
|
|
664
938
|
isConsulted: isConsultedTask,
|
|
665
939
|
isAutoAnswering: shouldAutoAnswer
|
|
666
940
|
};
|
|
667
|
-
const task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId);
|
|
941
|
+
const task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId, this.answerCallOnWebexService);
|
|
668
942
|
this.setupTaskListeners(task);
|
|
669
943
|
this.taskCollection[payload.interactionId] = task;
|
|
670
944
|
return {
|
|
@@ -693,7 +967,7 @@ class TaskManager extends _events.default {
|
|
|
693
967
|
isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload),
|
|
694
968
|
isAutoAnswering: shouldAutoAnswer
|
|
695
969
|
};
|
|
696
|
-
task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId);
|
|
970
|
+
task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId, this.answerCallOnWebexService);
|
|
697
971
|
this.setupTaskListeners(task);
|
|
698
972
|
this.taskCollection[payload.interactionId] = task;
|
|
699
973
|
}
|
|
@@ -713,6 +987,14 @@ class TaskManager extends _events.default {
|
|
|
713
987
|
destinationType: taskData.destinationType ?? snapshot?.context?.consultDestinationType ?? null
|
|
714
988
|
} : taskData;
|
|
715
989
|
task.updateTaskData(updateTaskData);
|
|
990
|
+
|
|
991
|
+
// A main-interaction lifecycle event can re-key a child consult task. Remove
|
|
992
|
+
// any aliases first so consumers never observe duplicate task entries.
|
|
993
|
+
Object.entries(this.taskCollection).forEach(([taskId, candidate]) => {
|
|
994
|
+
if (candidate === task && taskId !== taskData.interactionId) {
|
|
995
|
+
delete this.taskCollection[taskId];
|
|
996
|
+
}
|
|
997
|
+
});
|
|
716
998
|
this.taskCollection[taskData.interactionId] = task;
|
|
717
999
|
return task;
|
|
718
1000
|
}
|
|
@@ -753,8 +1035,8 @@ class TaskManager extends _events.default {
|
|
|
753
1035
|
task.on(_types.TASK_EVENTS.TASK_CLEANUP, (t, options) => {
|
|
754
1036
|
this.handleTaskCleanup(t);
|
|
755
1037
|
if (options?.removeFromCollection) {
|
|
756
|
-
const
|
|
757
|
-
if (
|
|
1038
|
+
const taskIsStillCollected = Object.values(this.taskCollection).some(candidate => candidate === t);
|
|
1039
|
+
if (taskIsStillCollected) {
|
|
758
1040
|
this.removeTaskFromCollection(t);
|
|
759
1041
|
}
|
|
760
1042
|
}
|
|
@@ -765,7 +1047,11 @@ class TaskManager extends _events.default {
|
|
|
765
1047
|
task.cancelAutoWrapupTimer();
|
|
766
1048
|
}
|
|
767
1049
|
if (task?.data?.interactionId) {
|
|
768
|
-
|
|
1050
|
+
Object.entries(this.taskCollection).forEach(([taskId, candidate]) => {
|
|
1051
|
+
if (candidate === task) {
|
|
1052
|
+
delete this.taskCollection[taskId];
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
769
1055
|
_loggerProxy.default.info(`Task removed from collection`, {
|
|
770
1056
|
module: _constants.TASK_MANAGER_FILE,
|
|
771
1057
|
method: _constants2.METHODS.REMOVE_TASK_FROM_COLLECTION,
|
|
@@ -810,7 +1096,7 @@ class TaskManager extends _events.default {
|
|
|
810
1096
|
isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload),
|
|
811
1097
|
isConsulted: false
|
|
812
1098
|
};
|
|
813
|
-
task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId);
|
|
1099
|
+
task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId, this.answerCallOnWebexService);
|
|
814
1100
|
this.taskCollection[payload.interactionId] = task;
|
|
815
1101
|
|
|
816
1102
|
// Transition the new task out of IDLE immediately so UI controls are
|
|
@@ -884,6 +1170,52 @@ class TaskManager extends _events.default {
|
|
|
884
1170
|
...this.taskCollection
|
|
885
1171
|
};
|
|
886
1172
|
}
|
|
1173
|
+
|
|
1174
|
+
/**
|
|
1175
|
+
* Propagate runtime enableWxBetterTogether changes to config flags and active tasks.
|
|
1176
|
+
*/
|
|
1177
|
+
applyEnableWxBetterTogether(enabled) {
|
|
1178
|
+
if (this.configFlags) {
|
|
1179
|
+
this.configFlags = {
|
|
1180
|
+
...this.configFlags,
|
|
1181
|
+
enableWxBetterTogether: enabled
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
Object.values(this.taskCollection).forEach(task => {
|
|
1185
|
+
task.setEnableWxBetterTogether(enabled);
|
|
1186
|
+
if (enabled) {
|
|
1187
|
+
TaskManager.syncWxAppMuteFromCallDetailsForTask(task);
|
|
1188
|
+
}
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/**
|
|
1193
|
+
* Propagate wxApp mute state from Mercury sync to active voice tasks.
|
|
1194
|
+
*/
|
|
1195
|
+
applyWxAppMuteStateFromSync(callId, muted) {
|
|
1196
|
+
Object.values(this.taskCollection).forEach(task => {
|
|
1197
|
+
task.applyWxAppMuteStateFromSync(callId, muted);
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* Re-seeds wxApp mute state from GET telephony/calls/{callId} for one task.
|
|
1203
|
+
*/
|
|
1204
|
+
static syncWxAppMuteFromCallDetailsForTask(task) {
|
|
1205
|
+
const sync = task.syncWxAppMuteFromCallDetails?.bind(task);
|
|
1206
|
+
if (typeof sync === 'function') {
|
|
1207
|
+
sync().catch(() => undefined);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
/**
|
|
1212
|
+
* Re-seeds wxApp mute for all engaged voice tasks (reload / runtime enable backfill).
|
|
1213
|
+
*/
|
|
1214
|
+
syncWxAppMuteFromCallDetailsForAllTasks() {
|
|
1215
|
+
Object.values(this.taskCollection).forEach(task => {
|
|
1216
|
+
TaskManager.syncWxAppMuteFromCallDetailsForTask(task);
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
887
1219
|
static getTaskManager(apiAIAssistant, contact, webCallingService, webSocketManager, rtdWebSocketManager) {
|
|
888
1220
|
if (!TaskManager.taskManager) {
|
|
889
1221
|
TaskManager.taskManager = new TaskManager(apiAIAssistant, contact, webCallingService, webSocketManager, rtdWebSocketManager);
|