@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
|
@@ -27,13 +27,21 @@ import {
|
|
|
27
27
|
shouldAutoAnswerTask,
|
|
28
28
|
} from './TaskUtils';
|
|
29
29
|
import TaskFactory from './TaskFactory';
|
|
30
|
+
import AnswerCallOnWebexService from '../AnswerCallOnWebexService';
|
|
31
|
+
import {getWebexCallingDeviceDetailsForAgent} from './WebexCallingUtils';
|
|
30
32
|
import WebRTC from './voice/WebRTC';
|
|
31
33
|
import {TaskEvent, type TaskEventPayload} from './state-machine';
|
|
34
|
+
import {MEDIA_TYPE_MAIN_CALL} from './state-machine/constants';
|
|
32
35
|
import {normalizeTaskData} from './taskDataNormalizer';
|
|
33
36
|
import {ApiAIAssistant} from '../ApiAiAssistant';
|
|
34
37
|
|
|
35
38
|
const CC_EVENT_SET = new Set<CC_EVENTS>(Object.values(CC_EVENTS) as CC_EVENTS[]);
|
|
36
39
|
|
|
40
|
+
const MAIN_INTERACTION_CORRELATED_EVENTS = new Set<CC_EVENTS>([
|
|
41
|
+
CC_EVENTS.AGENT_CONSULT_ENDED,
|
|
42
|
+
CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE,
|
|
43
|
+
]);
|
|
44
|
+
|
|
37
45
|
const isCcEvent = (value: string): value is CC_EVENTS => CC_EVENT_SET.has(value as CC_EVENTS);
|
|
38
46
|
|
|
39
47
|
/** @internal */
|
|
@@ -55,6 +63,7 @@ export default class TaskManager extends EventEmitter {
|
|
|
55
63
|
private wrapupData: WrapupData;
|
|
56
64
|
private agentId: string;
|
|
57
65
|
private webRtcEnabled: boolean;
|
|
66
|
+
private answerCallOnWebexService?: AnswerCallOnWebexService;
|
|
58
67
|
private apiAIAssistant?: ApiAIAssistant;
|
|
59
68
|
/**
|
|
60
69
|
* @param contact - Routing Contact layer. Talks to AQMReq layer to convert events to promises
|
|
@@ -148,6 +157,10 @@ export default class TaskManager extends EventEmitter {
|
|
|
148
157
|
this.webRtcEnabled = webRtcEnabled;
|
|
149
158
|
}
|
|
150
159
|
|
|
160
|
+
public setAnswerCallOnWebexService(answerCallOnWebexService: AnswerCallOnWebexService) {
|
|
161
|
+
this.answerCallOnWebexService = answerCallOnWebexService;
|
|
162
|
+
}
|
|
163
|
+
|
|
151
164
|
private handleIncomingWebCall = (call: ICall) => {
|
|
152
165
|
const currentTask = Object.values(this.taskCollection).find(
|
|
153
166
|
(task) =>
|
|
@@ -187,12 +200,15 @@ export default class TaskManager extends EventEmitter {
|
|
|
187
200
|
* @param ccEvent - The CC_EVENT type from WebSocket
|
|
188
201
|
* @param payload - The event payload
|
|
189
202
|
* @param agentId - Optional agent ID for state detection (needed for HYDRATE)
|
|
203
|
+
* @param task - Optional task instance for state-aware CONTACT_ENDED mapping
|
|
190
204
|
* @returns TaskEventPayload for state machine or null if no mapping
|
|
191
205
|
*/
|
|
192
206
|
private static mapEventToTaskStateMachineEvent(
|
|
193
207
|
ccEvent: CC_EVENTS,
|
|
194
208
|
payload: WebSocketPayload,
|
|
195
|
-
agentId?: string
|
|
209
|
+
agentId?: string,
|
|
210
|
+
task?: ITask,
|
|
211
|
+
enableWxBetterTogether?: boolean
|
|
196
212
|
): TaskEventPayload | null {
|
|
197
213
|
const mediaResourceId =
|
|
198
214
|
payload.mediaResourceId ||
|
|
@@ -226,6 +242,15 @@ export default class TaskManager extends EventEmitter {
|
|
|
226
242
|
return {type: TaskEvent.HYDRATE, taskData: payload, agentId};
|
|
227
243
|
|
|
228
244
|
case CC_EVENTS.CONTACT_UPDATED:
|
|
245
|
+
if (
|
|
246
|
+
task &&
|
|
247
|
+
typeof payload.interaction?.owner === 'string' &&
|
|
248
|
+
payload.interaction.owner.trim().length > 0 &&
|
|
249
|
+
payload.interaction.owner !== task.data?.interaction?.owner
|
|
250
|
+
) {
|
|
251
|
+
return {type: TaskEvent.CONTACT_OWNER_CHANGED, taskData: payload};
|
|
252
|
+
}
|
|
253
|
+
|
|
229
254
|
return {type: TaskEvent.CONTACT_UPDATED, taskData: payload};
|
|
230
255
|
case CC_EVENTS.CONTACT_OWNER_CHANGED:
|
|
231
256
|
return {type: TaskEvent.CONTACT_OWNER_CHANGED, taskData: payload};
|
|
@@ -299,16 +324,39 @@ export default class TaskManager extends EventEmitter {
|
|
|
299
324
|
case CC_EVENTS.AGENT_CONFERENCE_TRANSFER_FAILED:
|
|
300
325
|
return {type: TaskEvent.TRANSFER_FAILED, taskData: payload};
|
|
301
326
|
|
|
302
|
-
case CC_EVENTS.CONTACT_ENDED:
|
|
327
|
+
case CC_EVENTS.CONTACT_ENDED: {
|
|
328
|
+
const wrapUpRequired = isCampaignPreviewTask(payload)
|
|
329
|
+
? false
|
|
330
|
+
: payload.agentsPendingWrapUp?.includes(agentId || '') || false;
|
|
331
|
+
|
|
332
|
+
// Post-accept wxApp outdial agent-end delivers ContactEnded instead of
|
|
333
|
+
// AgentOutboundFailed. Normalize to OUTBOUND_FAILED so state machine
|
|
334
|
+
// enters WRAPPING_UP and emits TASK_OUTDIAL_FAILED (AGENT_ENDS).
|
|
335
|
+
// Pre-accept cancelTask decline stays CONTACT_ENDED → TERMINATED (no wrapup).
|
|
336
|
+
if (
|
|
337
|
+
TaskManager.isAgentTerminatedOutdialWrapup(
|
|
338
|
+
payload,
|
|
339
|
+
wrapUpRequired,
|
|
340
|
+
task,
|
|
341
|
+
agentId,
|
|
342
|
+
enableWxBetterTogether
|
|
343
|
+
)
|
|
344
|
+
) {
|
|
345
|
+
return {
|
|
346
|
+
type: TaskEvent.OUTBOUND_FAILED,
|
|
347
|
+
reason: 'AGENT_ENDS',
|
|
348
|
+
taskData: {...payload, wrapUpRequired: true},
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
303
352
|
return {
|
|
304
353
|
type: TaskEvent.CONTACT_ENDED,
|
|
305
354
|
taskData: {
|
|
306
355
|
...payload,
|
|
307
|
-
wrapUpRequired
|
|
308
|
-
? false
|
|
309
|
-
: payload.agentsPendingWrapUp?.includes(agentId || '') || false,
|
|
356
|
+
wrapUpRequired,
|
|
310
357
|
},
|
|
311
358
|
};
|
|
359
|
+
}
|
|
312
360
|
|
|
313
361
|
case CC_EVENTS.AGENT_INVITE_FAILED:
|
|
314
362
|
return {type: TaskEvent.INVITE_FAILED, reason: payload.reason};
|
|
@@ -319,8 +367,23 @@ export default class TaskManager extends EventEmitter {
|
|
|
319
367
|
case CC_EVENTS.AGENT_CONTACT_OFFER_RONA:
|
|
320
368
|
return {type: TaskEvent.RONA, taskData: payload, reason: payload.reason};
|
|
321
369
|
|
|
322
|
-
case CC_EVENTS.AGENT_OUTBOUND_FAILED:
|
|
323
|
-
|
|
370
|
+
case CC_EVENTS.AGENT_OUTBOUND_FAILED: {
|
|
371
|
+
const isAgentTerminatedOutdial =
|
|
372
|
+
payload.interaction?.outboundType === 'OUTDIAL' &&
|
|
373
|
+
(payload.terminatingParty === 'Agent' || payload.reason === 'AGENT_ENDS');
|
|
374
|
+
|
|
375
|
+
const suppressOutdialFailedPopup =
|
|
376
|
+
isAgentTerminatedOutdial &&
|
|
377
|
+
!TaskManager.isWxAppManagedOutdialTask(agentId, payload, task, enableWxBetterTogether);
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
type: TaskEvent.OUTBOUND_FAILED,
|
|
381
|
+
taskData: suppressOutdialFailedPopup
|
|
382
|
+
? {...payload, suppressOutdialFailedPopup: true}
|
|
383
|
+
: payload,
|
|
384
|
+
reason: payload.reason,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
324
387
|
|
|
325
388
|
case CC_EVENTS.CAMPAIGN_PREVIEW_ACCEPT_FAILED:
|
|
326
389
|
return {type: TaskEvent.CAMPAIGN_PREVIEW_ACCEPT_FAILED, taskData: payload};
|
|
@@ -371,6 +434,68 @@ export default class TaskManager extends EventEmitter {
|
|
|
371
434
|
}
|
|
372
435
|
}
|
|
373
436
|
|
|
437
|
+
/**
|
|
438
|
+
* True when the current agent's task is a wxApp-managed outdial (thick-client telephony).
|
|
439
|
+
*/
|
|
440
|
+
private static isWxAppManagedOutdialTask(
|
|
441
|
+
agentId: string | undefined,
|
|
442
|
+
payload: WebSocketPayload,
|
|
443
|
+
task: ITask | undefined,
|
|
444
|
+
enableWxBetterTogether?: boolean
|
|
445
|
+
): boolean {
|
|
446
|
+
const wxAppEnabled =
|
|
447
|
+
(task as {uiControlConfig?: {enableWxBetterTogether?: boolean}})?.uiControlConfig
|
|
448
|
+
?.enableWxBetterTogether ??
|
|
449
|
+
enableWxBetterTogether ??
|
|
450
|
+
false;
|
|
451
|
+
|
|
452
|
+
if (!wxAppEnabled) {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const effectiveAgentId = agentId ?? task?.data?.agentId;
|
|
457
|
+
const participants = payload.interaction?.participants ?? task?.data?.interaction?.participants;
|
|
458
|
+
const details = getWebexCallingDeviceDetailsForAgent(effectiveAgentId, participants);
|
|
459
|
+
|
|
460
|
+
return details?.deviceType === 'wxApp';
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* True when ContactEnded represents an agent-terminated wxApp outdial that should
|
|
465
|
+
* enter wrapup with an outdial-failed popup (legacy AgentOutboundFailed parity).
|
|
466
|
+
*/
|
|
467
|
+
private static isAgentTerminatedOutdialWrapup(
|
|
468
|
+
payload: WebSocketPayload,
|
|
469
|
+
wrapUpRequired: boolean,
|
|
470
|
+
task?: ITask,
|
|
471
|
+
agentId?: string,
|
|
472
|
+
enableWxBetterTogether?: boolean
|
|
473
|
+
): boolean {
|
|
474
|
+
const isOutdial = payload.interaction?.outboundType === 'OUTDIAL';
|
|
475
|
+
const agentTerminated = payload.terminatingParty === 'Agent';
|
|
476
|
+
|
|
477
|
+
if (!isOutdial || !agentTerminated) {
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (!TaskManager.isWxAppManagedOutdialTask(agentId, payload, task, enableWxBetterTogether)) {
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const wxAppAnswerPending =
|
|
486
|
+
(task as {uiControlConfig?: {wxAppAnswerPending?: boolean}})?.uiControlConfig
|
|
487
|
+
?.wxAppAnswerPending ?? false;
|
|
488
|
+
|
|
489
|
+
// Pre-accept decline: decline() clears wxAppAnswerPending; backend may still send state wrapUp.
|
|
490
|
+
if (!wxAppAnswerPending && !wrapUpRequired) {
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const interactionWrapUp = payload.interaction?.state === 'wrapUp';
|
|
495
|
+
|
|
496
|
+
return wrapUpRequired || interactionWrapUp || wxAppAnswerPending;
|
|
497
|
+
}
|
|
498
|
+
|
|
374
499
|
/**
|
|
375
500
|
* Register WebSocket message listeners for task events
|
|
376
501
|
*
|
|
@@ -482,6 +607,14 @@ export default class TaskManager extends EventEmitter {
|
|
|
482
607
|
}
|
|
483
608
|
}
|
|
484
609
|
|
|
610
|
+
if (!task && eventType === CC_EVENTS.CONTACT_OWNER_CHANGED) {
|
|
611
|
+
task = this.findTaskForContactOwnerChange(message.data);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
if (!task && MAIN_INTERACTION_CORRELATED_EVENTS.has(eventType)) {
|
|
615
|
+
task = this.findUniqueTaskByRelatedInteraction(message.data);
|
|
616
|
+
}
|
|
617
|
+
|
|
485
618
|
const wasConsultedTask = Boolean(task?.data?.isConsulted);
|
|
486
619
|
const computeWrapUpRequired = () => {
|
|
487
620
|
if (message.data.wrapUpRequired !== undefined) {
|
|
@@ -494,7 +627,7 @@ export default class TaskManager extends EventEmitter {
|
|
|
494
627
|
return !wasConsultedTask;
|
|
495
628
|
};
|
|
496
629
|
|
|
497
|
-
|
|
630
|
+
let adjustedPayload: WebSocketPayload =
|
|
498
631
|
eventType === CC_EVENTS.AGENT_CONSULT_TRANSFERRED ||
|
|
499
632
|
eventType === CC_EVENTS.AGENT_BLIND_TRANSFERRED ||
|
|
500
633
|
eventType === CC_EVENTS.AGENT_VTEAM_TRANSFERRED
|
|
@@ -504,10 +637,20 @@ export default class TaskManager extends EventEmitter {
|
|
|
504
637
|
}
|
|
505
638
|
: message.data;
|
|
506
639
|
|
|
640
|
+
if (task && eventType === CC_EVENTS.CONTACT_OWNER_CHANGED) {
|
|
641
|
+
adjustedPayload = TaskManager.preserveMainTaskIdentity(task, adjustedPayload);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (task && eventType === CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE) {
|
|
645
|
+
adjustedPayload = TaskManager.preserveConfirmedOwner(task, adjustedPayload);
|
|
646
|
+
}
|
|
647
|
+
|
|
507
648
|
const stateMachineEvent = TaskManager.mapEventToTaskStateMachineEvent(
|
|
508
649
|
eventType,
|
|
509
650
|
adjustedPayload,
|
|
510
|
-
this.agentId
|
|
651
|
+
this.agentId,
|
|
652
|
+
task,
|
|
653
|
+
this.configFlags?.enableWxBetterTogether
|
|
511
654
|
);
|
|
512
655
|
|
|
513
656
|
LoggerProxy.info(`Handling task event ${eventType}`, {
|
|
@@ -524,6 +667,236 @@ export default class TaskManager extends EventEmitter {
|
|
|
524
667
|
};
|
|
525
668
|
}
|
|
526
669
|
|
|
670
|
+
/**
|
|
671
|
+
* ContactOwnerChanged can identify the promoted agent's child interaction while
|
|
672
|
+
* the surviving task is stored under the main interaction. Accept one related
|
|
673
|
+
* task when its existing snapshot or the authoritative incoming promotion proves
|
|
674
|
+
* current-agent main-call membership, while excluding non-promoted and ambiguous matches.
|
|
675
|
+
*/
|
|
676
|
+
private findTaskForContactOwnerChange(payload: WebSocketPayload): ITask | undefined {
|
|
677
|
+
const relatedCandidates = this.findRelatedTasks(payload, {
|
|
678
|
+
includeCollectionKeys: true,
|
|
679
|
+
includeMainCallMediaKeys: true,
|
|
680
|
+
});
|
|
681
|
+
const incomingShowsCurrentAgentPromotion =
|
|
682
|
+
this.isCurrentAgentPromotedOnIncomingMainCall(payload);
|
|
683
|
+
const eligibleCandidates = relatedCandidates.filter(
|
|
684
|
+
(candidate) =>
|
|
685
|
+
this.isCurrentAgentActiveOnMainInteraction(candidate.data) ||
|
|
686
|
+
incomingShowsCurrentAgentPromotion
|
|
687
|
+
);
|
|
688
|
+
|
|
689
|
+
if (eligibleCandidates.length === 1) {
|
|
690
|
+
return eligibleCandidates[0];
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (eligibleCandidates.length > 1) {
|
|
694
|
+
LoggerProxy.warn('Unable to correlate task event to a unique main interaction task', {
|
|
695
|
+
module: TASK_MANAGER_FILE,
|
|
696
|
+
method: 'findTaskForContactOwnerChange',
|
|
697
|
+
interactionId:
|
|
698
|
+
payload.interaction?.mainInteractionId ||
|
|
699
|
+
payload.interaction?.parentInteractionId ||
|
|
700
|
+
payload.interaction?.callProcessingDetails?.parentInteractionId ||
|
|
701
|
+
payload.interactionId,
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
return undefined;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
return undefined;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
private isCurrentAgentActiveOnMainInteraction(taskData: TaskData | undefined): boolean {
|
|
711
|
+
const currentAgentId = this.agentId || taskData?.agentId;
|
|
712
|
+
if (!currentAgentId) return false;
|
|
713
|
+
|
|
714
|
+
return TaskManager.isParticipantActiveOnMainInteraction(taskData, currentAgentId);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
private static isParticipantActiveOnMainInteraction(
|
|
718
|
+
taskData: TaskData | WebSocketPayload | undefined,
|
|
719
|
+
participantId: string
|
|
720
|
+
): boolean {
|
|
721
|
+
const interaction = taskData?.interaction;
|
|
722
|
+
const participant = interaction?.participants?.[participantId];
|
|
723
|
+
if (!participant || participant.hasLeft === true) return false;
|
|
724
|
+
|
|
725
|
+
return Object.values(interaction?.media ?? {}).some(
|
|
726
|
+
(media) =>
|
|
727
|
+
media?.mType === MEDIA_TYPE_MAIN_CALL && media.participants?.includes(participantId)
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
private isCurrentAgentPromotedOnIncomingMainCall(payload: WebSocketPayload): boolean {
|
|
732
|
+
return Boolean(
|
|
733
|
+
this.agentId &&
|
|
734
|
+
payload.interaction?.owner === this.agentId &&
|
|
735
|
+
TaskManager.isParticipantActiveOnMainInteraction(payload, this.agentId)
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
private static getMainCallMediaId(
|
|
740
|
+
interaction: TaskData['interaction'] | undefined
|
|
741
|
+
): string | undefined {
|
|
742
|
+
return Object.entries(interaction?.media ?? {}).find(
|
|
743
|
+
([, media]) => media?.mType === MEDIA_TYPE_MAIN_CALL
|
|
744
|
+
)?.[0];
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
private static getStableMainInteractionId(payload: WebSocketPayload): string | undefined {
|
|
748
|
+
return (
|
|
749
|
+
payload.interaction?.mainInteractionId ||
|
|
750
|
+
TaskManager.getMainCallMediaId(payload.interaction) ||
|
|
751
|
+
payload.interaction?.interactionId ||
|
|
752
|
+
payload.interactionId
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
private canRecoverTaskFromContactOwnerChanged(payload: WebSocketPayload): boolean {
|
|
757
|
+
const interaction = payload.interaction;
|
|
758
|
+
const stableInteractionId = TaskManager.getStableMainInteractionId(payload);
|
|
759
|
+
|
|
760
|
+
return Boolean(
|
|
761
|
+
this.agentId &&
|
|
762
|
+
stableInteractionId &&
|
|
763
|
+
interaction?.mediaType === MEDIA_CHANNEL.TELEPHONY &&
|
|
764
|
+
interaction.isTerminated !== true &&
|
|
765
|
+
interaction.owner === this.agentId &&
|
|
766
|
+
TaskManager.isParticipantActiveOnMainInteraction(payload, this.agentId)
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/** Keep a related owner-change event from re-keying the surviving main task to a child ID. */
|
|
771
|
+
private static preserveMainTaskIdentity(
|
|
772
|
+
task: ITask,
|
|
773
|
+
payload: WebSocketPayload
|
|
774
|
+
): WebSocketPayload {
|
|
775
|
+
const currentMainInteractionId = task.data?.interaction?.mainInteractionId;
|
|
776
|
+
const payloadMainInteractionId = payload.interaction?.mainInteractionId;
|
|
777
|
+
const currentMainMediaId = TaskManager.getMainCallMediaId(task.data?.interaction);
|
|
778
|
+
const payloadMainMediaId = TaskManager.getMainCallMediaId(payload.interaction);
|
|
779
|
+
const stableInteractionId =
|
|
780
|
+
currentMainInteractionId ||
|
|
781
|
+
payloadMainInteractionId ||
|
|
782
|
+
currentMainMediaId ||
|
|
783
|
+
payloadMainMediaId ||
|
|
784
|
+
payload.interaction?.interactionId ||
|
|
785
|
+
task.data?.interaction?.interactionId ||
|
|
786
|
+
task.data?.interactionId;
|
|
787
|
+
|
|
788
|
+
if (!stableInteractionId || stableInteractionId === payload.interactionId) {
|
|
789
|
+
return payload;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
return {...payload, interactionId: stableInteractionId};
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* ParticipantLeftConference may arrive after an owner update while still carrying
|
|
797
|
+
* the departed owner. Keep the confirmed owner only when the new roster proves that
|
|
798
|
+
* owner is active on mainCall and identifies the incoming owner as departed.
|
|
799
|
+
*/
|
|
800
|
+
private static preserveConfirmedOwner(task: ITask, payload: WebSocketPayload): WebSocketPayload {
|
|
801
|
+
const confirmedOwner = task.data?.interaction?.owner;
|
|
802
|
+
const incomingOwner = payload.interaction?.owner;
|
|
803
|
+
if (!confirmedOwner || !incomingOwner || confirmedOwner === incomingOwner) {
|
|
804
|
+
return payload;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const confirmedOwnerIsActive = TaskManager.isParticipantActiveOnMainInteraction(
|
|
808
|
+
payload,
|
|
809
|
+
confirmedOwner
|
|
810
|
+
);
|
|
811
|
+
const incomingOwnerIsExplicitlyDeparted =
|
|
812
|
+
payload.participantId === incomingOwner ||
|
|
813
|
+
payload.interaction?.participants?.[incomingOwner]?.hasLeft === true;
|
|
814
|
+
|
|
815
|
+
if (!confirmedOwnerIsActive || !incomingOwnerIsExplicitlyDeparted) {
|
|
816
|
+
return payload;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
return {
|
|
820
|
+
...payload,
|
|
821
|
+
owner: confirmedOwner,
|
|
822
|
+
interaction: {...payload.interaction, owner: confirmedOwner},
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Resolve lifecycle events that are emitted for the main interaction while the
|
|
828
|
+
* local task can still be indexed by a child consult interaction. Exact task
|
|
829
|
+
* keys are handled before this fallback. Multiple aliases of the same task are
|
|
830
|
+
* treated as one candidate; genuinely ambiguous matches are intentionally ignored.
|
|
831
|
+
*/
|
|
832
|
+
private findUniqueTaskByRelatedInteraction(
|
|
833
|
+
payload: WebSocketPayload,
|
|
834
|
+
candidateFilter: (task: ITask) => boolean = () => true
|
|
835
|
+
): ITask | undefined {
|
|
836
|
+
const candidates = this.findRelatedTasks(payload).filter(candidateFilter);
|
|
837
|
+
|
|
838
|
+
if (candidates.length === 1) {
|
|
839
|
+
return candidates[0];
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
if (candidates.length > 1) {
|
|
843
|
+
LoggerProxy.warn('Unable to correlate task event to a unique main interaction task', {
|
|
844
|
+
module: TASK_MANAGER_FILE,
|
|
845
|
+
method: 'findUniqueTaskByRelatedInteraction',
|
|
846
|
+
interactionId:
|
|
847
|
+
payload.interaction?.mainInteractionId ||
|
|
848
|
+
payload.interaction?.parentInteractionId ||
|
|
849
|
+
payload.interaction?.callProcessingDetails?.parentInteractionId ||
|
|
850
|
+
payload.interactionId,
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
return undefined;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
private findRelatedTasks(
|
|
858
|
+
payload: WebSocketPayload,
|
|
859
|
+
options: {includeCollectionKeys?: boolean; includeMainCallMediaKeys?: boolean} = {}
|
|
860
|
+
): ITask[] {
|
|
861
|
+
const {includeCollectionKeys = false, includeMainCallMediaKeys = false} = options;
|
|
862
|
+
const payloadInteractionIds = new Set(
|
|
863
|
+
[
|
|
864
|
+
payload.interactionId,
|
|
865
|
+
payload.interaction?.interactionId,
|
|
866
|
+
payload.interaction?.mainInteractionId,
|
|
867
|
+
payload.interaction?.parentInteractionId,
|
|
868
|
+
payload.interaction?.callProcessingDetails?.parentInteractionId,
|
|
869
|
+
...(includeMainCallMediaKeys ? [TaskManager.getMainCallMediaId(payload.interaction)] : []),
|
|
870
|
+
].filter((interactionId): interactionId is string => Boolean(interactionId))
|
|
871
|
+
);
|
|
872
|
+
if (payloadInteractionIds.size === 0) return [];
|
|
873
|
+
|
|
874
|
+
return [
|
|
875
|
+
...new Set(
|
|
876
|
+
Object.entries(this.taskCollection)
|
|
877
|
+
.filter(([taskId, candidate]) => {
|
|
878
|
+
const taskInteraction = candidate?.data?.interaction;
|
|
879
|
+
const candidateInteractionIds = [
|
|
880
|
+
...(includeCollectionKeys ? [taskId] : []),
|
|
881
|
+
candidate?.data?.interactionId,
|
|
882
|
+
taskInteraction?.interactionId,
|
|
883
|
+
taskInteraction?.mainInteractionId,
|
|
884
|
+
taskInteraction?.parentInteractionId,
|
|
885
|
+
taskInteraction?.callProcessingDetails?.parentInteractionId,
|
|
886
|
+
...(includeMainCallMediaKeys
|
|
887
|
+
? [TaskManager.getMainCallMediaId(taskInteraction)]
|
|
888
|
+
: []),
|
|
889
|
+
];
|
|
890
|
+
|
|
891
|
+
return candidateInteractionIds.some(
|
|
892
|
+
(interactionId) => Boolean(interactionId) && payloadInteractionIds.has(interactionId)
|
|
893
|
+
);
|
|
894
|
+
})
|
|
895
|
+
.map(([, candidate]) => candidate)
|
|
896
|
+
),
|
|
897
|
+
];
|
|
898
|
+
}
|
|
899
|
+
|
|
527
900
|
/**
|
|
528
901
|
* Handle task lifecycle events and determine required actions
|
|
529
902
|
*
|
|
@@ -549,6 +922,9 @@ export default class TaskManager extends EventEmitter {
|
|
|
549
922
|
case CC_EVENTS.CONTACT_MERGED:
|
|
550
923
|
return this.handleContactMergedEvent(context);
|
|
551
924
|
|
|
925
|
+
case CC_EVENTS.CONTACT_OWNER_CHANGED:
|
|
926
|
+
return this.handleContactOwnerChanged(context);
|
|
927
|
+
|
|
552
928
|
case CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION:
|
|
553
929
|
return this.handleCampaignPreviewReservation(context);
|
|
554
930
|
|
|
@@ -560,6 +936,74 @@ export default class TaskManager extends EventEmitter {
|
|
|
560
936
|
}
|
|
561
937
|
}
|
|
562
938
|
|
|
939
|
+
/**
|
|
940
|
+
* Recover the promoted agent's active voice task when local task state was lost.
|
|
941
|
+
* Existing related tasks are never replaced: an unresolved or ambiguous relation
|
|
942
|
+
* is safer to ignore than to create a duplicate task.
|
|
943
|
+
*/
|
|
944
|
+
private handleContactOwnerChanged(context: EventContext): TaskEventActions {
|
|
945
|
+
if (context.task) {
|
|
946
|
+
return {task: context.task};
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
const {payload} = context;
|
|
950
|
+
if (
|
|
951
|
+
this.findRelatedTasks(payload, {
|
|
952
|
+
includeCollectionKeys: true,
|
|
953
|
+
includeMainCallMediaKeys: true,
|
|
954
|
+
}).length > 0 ||
|
|
955
|
+
!this.canRecoverTaskFromContactOwnerChanged(payload)
|
|
956
|
+
) {
|
|
957
|
+
return {};
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
const stableInteractionId = TaskManager.getStableMainInteractionId(payload);
|
|
961
|
+
if (!stableInteractionId || this.taskCollection[stableInteractionId]) {
|
|
962
|
+
return {};
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
const normalizedPayload: WebSocketPayload =
|
|
966
|
+
payload.interactionId === stableInteractionId
|
|
967
|
+
? payload
|
|
968
|
+
: {...payload, interactionId: stableInteractionId};
|
|
969
|
+
const taskData: TaskData = {
|
|
970
|
+
...normalizedPayload,
|
|
971
|
+
owner: normalizedPayload.interaction.owner,
|
|
972
|
+
wrapUpRequired: normalizedPayload.interaction.participants?.[this.agentId]?.isWrapUp || false,
|
|
973
|
+
isConferenceInProgress: getIsConferenceInProgress(normalizedPayload),
|
|
974
|
+
isConsulted: false,
|
|
975
|
+
isAutoAnswering: false,
|
|
976
|
+
};
|
|
977
|
+
const task = TaskFactory.createTask(
|
|
978
|
+
this.contact,
|
|
979
|
+
this.webCallingService,
|
|
980
|
+
taskData,
|
|
981
|
+
this.configFlags,
|
|
982
|
+
this.wrapupData,
|
|
983
|
+
this.agentId,
|
|
984
|
+
this.answerCallOnWebexService
|
|
985
|
+
);
|
|
986
|
+
|
|
987
|
+
this.taskCollection[stableInteractionId] = task;
|
|
988
|
+
|
|
989
|
+
// Restore the actor before installing listeners so the internal hydrate does
|
|
990
|
+
// not leak as a second public hydrate or as an incoming-task notification.
|
|
991
|
+
task.sendStateMachineEvent({
|
|
992
|
+
type: TaskEvent.HYDRATE,
|
|
993
|
+
taskData,
|
|
994
|
+
agentId: this.agentId,
|
|
995
|
+
} as TaskEventPayload);
|
|
996
|
+
|
|
997
|
+
this.setupTaskListeners(task);
|
|
998
|
+
context.payload = taskData;
|
|
999
|
+
context.stateMachineEvent = {
|
|
1000
|
+
type: TaskEvent.CONTACT_OWNER_CHANGED,
|
|
1001
|
+
taskData,
|
|
1002
|
+
};
|
|
1003
|
+
|
|
1004
|
+
return {task};
|
|
1005
|
+
}
|
|
1006
|
+
|
|
563
1007
|
private handleCampaignContactUpdated(context: EventContext) {
|
|
564
1008
|
const {payload} = context;
|
|
565
1009
|
let {task} = context;
|
|
@@ -647,7 +1091,8 @@ export default class TaskManager extends EventEmitter {
|
|
|
647
1091
|
},
|
|
648
1092
|
this.configFlags,
|
|
649
1093
|
this.wrapupData,
|
|
650
|
-
this.agentId
|
|
1094
|
+
this.agentId,
|
|
1095
|
+
this.answerCallOnWebexService
|
|
651
1096
|
);
|
|
652
1097
|
this.setupTaskListeners(task);
|
|
653
1098
|
this.taskCollection[payload.interactionId] = task;
|
|
@@ -685,7 +1130,8 @@ export default class TaskManager extends EventEmitter {
|
|
|
685
1130
|
taskData,
|
|
686
1131
|
this.configFlags,
|
|
687
1132
|
this.wrapupData,
|
|
688
|
-
this.agentId
|
|
1133
|
+
this.agentId,
|
|
1134
|
+
this.answerCallOnWebexService
|
|
689
1135
|
);
|
|
690
1136
|
|
|
691
1137
|
this.setupTaskListeners(task);
|
|
@@ -725,7 +1171,8 @@ export default class TaskManager extends EventEmitter {
|
|
|
725
1171
|
taskData,
|
|
726
1172
|
this.configFlags,
|
|
727
1173
|
this.wrapupData,
|
|
728
|
-
this.agentId
|
|
1174
|
+
this.agentId,
|
|
1175
|
+
this.answerCallOnWebexService
|
|
729
1176
|
);
|
|
730
1177
|
this.setupTaskListeners(task);
|
|
731
1178
|
this.taskCollection[payload.interactionId] = task;
|
|
@@ -753,6 +1200,14 @@ export default class TaskManager extends EventEmitter {
|
|
|
753
1200
|
: taskData;
|
|
754
1201
|
|
|
755
1202
|
task.updateTaskData(updateTaskData);
|
|
1203
|
+
|
|
1204
|
+
// A main-interaction lifecycle event can re-key a child consult task. Remove
|
|
1205
|
+
// any aliases first so consumers never observe duplicate task entries.
|
|
1206
|
+
Object.entries(this.taskCollection).forEach(([taskId, candidate]) => {
|
|
1207
|
+
if (candidate === task && taskId !== taskData.interactionId) {
|
|
1208
|
+
delete this.taskCollection[taskId];
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
756
1211
|
this.taskCollection[taskData.interactionId] = task;
|
|
757
1212
|
|
|
758
1213
|
return task;
|
|
@@ -798,8 +1253,10 @@ export default class TaskManager extends EventEmitter {
|
|
|
798
1253
|
task.on(TASK_EVENTS.TASK_CLEANUP, (t: ITask, options?: {removeFromCollection?: boolean}) => {
|
|
799
1254
|
this.handleTaskCleanup(t);
|
|
800
1255
|
if (options?.removeFromCollection) {
|
|
801
|
-
const
|
|
802
|
-
|
|
1256
|
+
const taskIsStillCollected = Object.values(this.taskCollection).some(
|
|
1257
|
+
(candidate) => candidate === t
|
|
1258
|
+
);
|
|
1259
|
+
if (taskIsStillCollected) {
|
|
803
1260
|
this.removeTaskFromCollection(t);
|
|
804
1261
|
}
|
|
805
1262
|
}
|
|
@@ -811,7 +1268,11 @@ export default class TaskManager extends EventEmitter {
|
|
|
811
1268
|
task.cancelAutoWrapupTimer();
|
|
812
1269
|
}
|
|
813
1270
|
if (task?.data?.interactionId) {
|
|
814
|
-
|
|
1271
|
+
Object.entries(this.taskCollection).forEach(([taskId, candidate]) => {
|
|
1272
|
+
if (candidate === task) {
|
|
1273
|
+
delete this.taskCollection[taskId];
|
|
1274
|
+
}
|
|
1275
|
+
});
|
|
815
1276
|
LoggerProxy.info(`Task removed from collection`, {
|
|
816
1277
|
module: TASK_MANAGER_FILE,
|
|
817
1278
|
method: METHODS.REMOVE_TASK_FROM_COLLECTION,
|
|
@@ -864,7 +1325,8 @@ export default class TaskManager extends EventEmitter {
|
|
|
864
1325
|
taskData,
|
|
865
1326
|
this.configFlags,
|
|
866
1327
|
this.wrapupData,
|
|
867
|
-
this.agentId
|
|
1328
|
+
this.agentId,
|
|
1329
|
+
this.answerCallOnWebexService
|
|
868
1330
|
);
|
|
869
1331
|
this.taskCollection[payload.interactionId] = task;
|
|
870
1332
|
|
|
@@ -956,6 +1418,50 @@ export default class TaskManager extends EventEmitter {
|
|
|
956
1418
|
return {...this.taskCollection};
|
|
957
1419
|
}
|
|
958
1420
|
|
|
1421
|
+
/**
|
|
1422
|
+
* Propagate runtime enableWxBetterTogether changes to config flags and active tasks.
|
|
1423
|
+
*/
|
|
1424
|
+
public applyEnableWxBetterTogether(enabled: boolean): void {
|
|
1425
|
+
if (this.configFlags) {
|
|
1426
|
+
this.configFlags = {...this.configFlags, enableWxBetterTogether: enabled};
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
Object.values(this.taskCollection).forEach((task) => {
|
|
1430
|
+
task.setEnableWxBetterTogether(enabled);
|
|
1431
|
+
if (enabled) {
|
|
1432
|
+
TaskManager.syncWxAppMuteFromCallDetailsForTask(task);
|
|
1433
|
+
}
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
/**
|
|
1438
|
+
* Propagate wxApp mute state from Mercury sync to active voice tasks.
|
|
1439
|
+
*/
|
|
1440
|
+
public applyWxAppMuteStateFromSync(callId: string, muted: boolean): void {
|
|
1441
|
+
Object.values(this.taskCollection).forEach((task) => {
|
|
1442
|
+
task.applyWxAppMuteStateFromSync(callId, muted);
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
/**
|
|
1447
|
+
* Re-seeds wxApp mute state from GET telephony/calls/{callId} for one task.
|
|
1448
|
+
*/
|
|
1449
|
+
public static syncWxAppMuteFromCallDetailsForTask(task: ITask): void {
|
|
1450
|
+
const sync = task.syncWxAppMuteFromCallDetails?.bind(task);
|
|
1451
|
+
if (typeof sync === 'function') {
|
|
1452
|
+
sync().catch(() => undefined);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
/**
|
|
1457
|
+
* Re-seeds wxApp mute for all engaged voice tasks (reload / runtime enable backfill).
|
|
1458
|
+
*/
|
|
1459
|
+
public syncWxAppMuteFromCallDetailsForAllTasks(): void {
|
|
1460
|
+
Object.values(this.taskCollection).forEach((task) => {
|
|
1461
|
+
TaskManager.syncWxAppMuteFromCallDetailsForTask(task);
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
|
|
959
1465
|
public static getTaskManager(
|
|
960
1466
|
apiAIAssistant: ApiAIAssistant,
|
|
961
1467
|
contact: ReturnType<typeof routingContact>,
|